# Canvas Feature Specification ## Overview The canvas is the infinite zoomable/pannable workspace where krates live. It serves as the primary interaction surface for exploring Kubernetes clusters. ## Core Responsibilities - Render an infinite 2D workspace (12000×8000px world) - Handle camera navigation (pan/zoom) - Manage Level of Detail (LOD) based on zoom level - Display a CAD grid overlay - Coordinate global interactions (space panning, spotlight trigger) ## Technical Requirements ### Viewport & World Layer - Full viewport (`position: fixed; inset: 0`) - Background color: `#0b0e13` - World layer: 12000×8000px div transformed via `translate(camX, camY) scale(zoom)` - All krates/windows are absolutely positioned children ### Grid Overlay (CSS background-image) - Fine grid: 34px spacing, rgba(125,145,175,.04) - Coarse grid: 170px spacing, rgba(125,145,175,.075) - Both X and Y repeating linear gradients ### Camera State ```typescript interface CanvasState { camX: number; // world-to-screen X offset camY: number; // world-to-screen Y offset zoom: number; // 0.16 – 2.2; default 0.92 flying: boolean; // true during animated camera transitions dragging: boolean; // true during pan drag collapsed: boolean; // LOD: true when zoom < 0.4 spacePanning: boolean; // true while space is held } ``` ## Navigation ### Scroll Wheel - ** Default **: Pan canvas - ** Ctrl/⌘ + scroll **: Zoom canvas (intercepted via wheel event with `e.ctrlKey || e.metaKey`) ### Space + Drag Panning - Critical: must work even when shell window has keyboard focus - Implementation: - `document.addEventListener('keydown', handler, { capture: true })` on space - Set "panning" flag, prevent default on body - Forward `mousemove` to canvas pan handler - Release on `keyup` for space - Do NOT use `e.stopPropagation()` - allow event propagation ### Click/Typed Key Triggers - ** Click on empty canvas **: Open spotlight - ** Type any key **: Open spotlight with that character pre-seeded ### Zoom Levels / LOD - ** zoom > 0.5 **: Normal view — krates expanded, all windows visible, draggable - ** zoom < 0.4 **: Collapsed view — krates become 230px wide overview cards, no windows rendered - ** Hysteresis**: Collapse at 0.4, re-expand at 0.5 (prevents flickering) ### Animations - CSS `transform` with `transition` only for programmatic "fly" animations: `.52s cubic-bezier(.22,.8,.28,1)` - During user scroll/drag: `transition: none` for immediacy ## UI Components ### Top Bar - Position: `absolute; top: 0; left: 0; right: 0; z-index: 10; height ~56px` - Layout: `display: flex; align-items: center; padding: 13px 18px` - Background: none (transparent) - Left side: Logo pill, cluster pill, krate count pill - Right side: Synced pill, admin button, roster avatars ### Bottom Hint Bar - Position: `absolute; right: 18px; bottom: 18px` - Shows keyboard hints (status bar) ### Zoom Pill - Position: `absolute; left: 18px; bottom: 18px` - Displays current zoom percentage ### Minimap - Position: `absolute; right: 18px; bottom: 64px` - Size: 180×120px - Shows all krates as small rectangles - Viewport indicator rectangle showing current view frustum - Click anywhere to fly camera to that world position ## Interaction Summary | Interaction | Behavior | |---|---| | Scroll wheel | Pan canvas | | Ctrl/⌘ + scroll | Zoom canvas | | Space + drag | Pan canvas (even over windows/shells) | | Click empty canvas | Open spotlight | | Type any key | Open spotlight pre-seeded | | Minimap click | Fly camera to position | ## Gotchas 1. **Space pan over shells**: Must intercept with `{ capture: true }`, prevent default, but don't stop propagation 2. **Ctrl/⌘+scroll**: Use `{ capture: true }` on canvas wheel handler to intercept before window scroll containers 3. **Camera transitions**: Only animate during programmatic fly, not user gestures 4. **LOD state**: Use stable boolean `collapsed` in state, not derived from `zoom` on every render