# Design Document: 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
