--- description: Skill Tree UI component - interactive progression tree for Explorer units globs: src/ui/components/SkillTreeUI.js, src/ui/components/SkillTreeUI.ts, src/types/SkillTreeUI.ts alwaysApply: false --- # **Skill Tree UI Rule** This rule defines the technical implementation for the SkillTreeUI component. This component renders the interactive progression tree for a specific Explorer. ## **1. Visual Architecture** **Style:** "Voxel-Web". We will use **CSS 3D Transforms** to render the nodes as rotating cubes, keeping the UI lightweight but consistent with the game's aesthetic. ### **A. The Tree Container (Scroll View)** - **Layout:** A vertical flex container - **Tiers:** Each "Rank" (Novice, Apprentice, etc.) is a horizontal row (Flexbox) - **Connections:** An `` overlay sits behind the nodes to draw connecting lines (cables) ### **B. The Node (CSS Voxel)** - **Structure:** A div with `preserve-3d` containing 6 faces - **Animation:** - _Locked:_ Static grey cube - _Available:_ Slowly bobbing, pulsing color - _Unlocked:_ Rotating slowly, emitting a glow (box-shadow) - **Content:** An icon (`` or FontAwesome) is mapped to the Front face ### **C. The Inspector (Footer)** - A slide-up panel showing details for the _selected_ node - Contains the "Unlock" button ## **2. TypeScript Interfaces (Data Model)** ```typescript // src/types/SkillTreeUI.ts export interface SkillTreeProps { /** The Unit object (source of state) */ unit: Explorer; /** The Tree Definition (source of layout) */ treeDef: SkillTreeDefinition; } export interface SkillNodeState { id: string; def: SkillNodeDefinition; status: 'LOCKED' | 'AVAILABLE' | 'UNLOCKED'; /** Calculated position for drawing lines */ domRect?: DOMRect; } export interface SkillTreeEvents { /** Dispatched when user attempts to spend SP */ 'unlock-request': { nodeId: string; cost: number; }; } ``` ## **3. Interaction Logic** ### **A. Node Status Calculation** The UI must determine the state of every node on render: 1. **UNLOCKED:** `unit.classMastery.unlockedNodes.includes(node.id)` 2. **AVAILABLE:** Not unlocked AND `parent` is Unlocked AND `unit.level >= node.req` 3. **LOCKED:** Everything else ### **B. Connection Drawing** Since nodes are DOM elements, we need a `ResizeObserver` to track their positions. - **Logic:** Calculate center `(x, y)` of Parent Node and Child Node relative to the Container - **Drawing:** Draw a `` or `` in the SVG layer with a "Circuit Board" style (90-degree bends) - **Styling:** - If Child is Unlocked: Line is **Bright Blue/Gold** (Neon) - If Child is Available: Line is **Dim** - If Child is Locked: Line is **Dark Grey** ## **4. Conditions of Acceptance (CoA)** **CoA 1: Dynamic Rendering** - The Tree must handle variable depths (Tier 1 to Tier 5) - Nodes must visibly update state immediately when `unit` prop changes (e.g., after unlocking) **CoA 2: Validation Feedback** - Clicking a "LOCKED" node should show the inspector but disable the button with a reason (e.g., "Requires: Shield Bash") - Clicking an "AVAILABLE" node with 0 SP should show "Insufficient Points" **CoA 3: Responsive Lines** - If the window resizes, the SVG connecting lines must redraw to connect the centers of the cubes accurately **CoA 4: Scroll Position** - On open, the view should automatically scroll to center on the _highest tier_ that has an "Available" node, so the player sees their next step ## **5. Implementation Requirements** Create `src/ui/components/SkillTreeUI.js` as a LitElement: 1. **CSS 3D:** Implement a `.voxel-node` class using `transform-style: preserve-3d` to create a cube. Use keyframes for rotation 2. **Layout:** Render the tree tiers using `flex-direction: column-reverse` (Tier 1 at bottom) 3. **SVG Lines:** Implement a `_updateConnections()` method that uses `getBoundingClientRect()` to draw lines between nodes in an absolute-positioned ``. Call this on resize and first render 4. **Interactivity:** Clicking a node selects it. Show details in a fixed footer 5. **Logic:** Calculate `LOCKED/AVAILABLE/UNLOCKED` state based on `this.unit.unlockedNodes`