diff --git a/.claude/skills/frame0-wireframe/SKILL.md b/.claude/skills/frame0-wireframe/SKILL.md
new file mode 100644
index 000000000..6c4125c09
--- /dev/null
+++ b/.claude/skills/frame0-wireframe/SKILL.md
@@ -0,0 +1,161 @@
+---
+name: frame0-wireframe
+description: >
+ Create and export UI wireframes using Frame0 (local desktop wireframing
+ app with HTTP API). Use when the user says "create wireframe", "wireframe
+ this", "mock up the UI", "draw a screen", "UI layout", "wireframe the HUD",
+ "Frame0", "frame0", "export wireframe", or invokes /frame0-wireframe.
+ Drives Frame0 via its local HTTP API to create shapes, connectors, and
+ export pages. Also use when asked to update existing wireframes or export
+ pages. Requires Frame0 to be running locally.
+---
+
+# Frame0 Wireframe Generation
+
+Create UI wireframes by driving Frame0's local HTTP API via bash+curl scripts.
+No MCP dependency — the scripts replace the MCP server entirely.
+
+**Prerequisite:** Frame0 desktop app must be running. The API has no headless
+mode. If Frame0 is not available, stop and inform the user.
+
+## Health Check
+
+Always check first:
+
+```bash
+.claude/skills/frame0-wireframe/scripts/frame0-cmd.sh health
+```
+
+If Frame0 is not running, report the failure and point to
+`references/setup-guide.md`. Do not attempt to proceed without a healthy
+connection.
+
+## API Pattern
+
+All Frame0 interaction goes through the bundled scripts. Never call curl
+directly.
+
+- **`frame0-cmd.sh`** — Low-level API wrapper. Maps subcommands to Frame0's
+ HTTP endpoint at `POST localhost:{port}/execute_command`.
+- **`frame0-wireframe.sh`** — High-level composition helpers with project
+ styling defaults (colors from visual-grammar-v01.md).
+
+Port default: **58320** (override: `FRAME0_PORT` env var or `--port` flag).
+
+## Single Wireframe Workflow
+
+1. **Health check** — `frame0-cmd.sh health`
+2. **Create page** — `frame0-wireframe.sh new-page "Screen Name"`
+3. **Add components** — use high-level helpers for standard elements:
+ ```bash
+ CMD=".claude/skills/frame0-wireframe/scripts/frame0-wireframe.sh"
+ $CMD button "Confirm" 100 200
+ $CMD text-field "Search..." 100 250 300
+ $CMD container "Panel Title" 50 50 400 300
+ $CMD label "Description text" 60 80
+ $CMD divider 50 120 400
+ ```
+4. **Fine-grained control** — use `frame0-cmd.sh` for operations not covered
+ by helpers (connectors, icons, grouping):
+ ```bash
+ CMD=".claude/skills/frame0-wireframe/scripts/frame0-cmd.sh"
+ $CMD create-connector "shape-id-1" "shape-id-2"
+ $CMD group "shape-id-1" "shape-id-2" "shape-id-3"
+ $CMD create-icon "search" '{"left":100,"top":100,"width":24,"height":24}'
+ ```
+5. **Export** — `frame0-wireframe.sh export png docs/design/wireframes/hud/layout-v1.png`
+
+## High-Level Helpers
+
+| Command | Default size | Description |
+|---------|-------------|-------------|
+| `new-page
` | — | Create page with title label |
+| `button [w] [h]` | 120x36 | Styled button with accent border |
+| `text-field [w] [h]` | 200x32 | Input field with dark fill |
+| `container ` | — | Labeled panel with border |
+| `label [size]` | font 14 | Text label |
+| `divider [h\|v]` | horizontal | Separator line |
+| `export ` | — | Export current page (png/svg) |
+
+## Low-Level Commands
+
+| Command | Purpose |
+|---------|---------|
+| `health` | Check if Frame0 is running |
+| `exec ` | Execute any raw API command |
+| `create-shape ` | Create rectangle, ellipse, text, etc. |
+| `update-shape ` | Modify shape properties |
+| `delete ` | Delete shapes |
+| `move ` | Move shape by pixel offset |
+| `group ` | Group shapes |
+| `ungroup ` | Ungroup shapes |
+| `create-connector ` | Connect two shapes |
+| `create-icon ` | Add icon |
+| `add-page ` | Add page |
+| `get-page [id]` | Get current/specific page |
+| `list-pages` | List all pages |
+| `set-page ` | Set current page |
+| `export ` | Export as PNG/SVG |
+
+## Project Styling Defaults
+
+Colors from `docs/design/visual-grammar-v01.md`:
+
+| Role | Hex | Source |
+|------|-----|--------|
+| Background | `#1a1e24` | Zone 1 floor |
+| Stroke | `#333340` | Outline standard |
+| Fill | `#2a3040` | Zone 1 wall |
+| Text | `#c8d0e0` | Insert chrome |
+| Accent | `#c8d8f0` | Zone 1 fixture light |
+
+## Export Convention
+
+```
+docs/design/wireframes/
+ hud/ # HUD layout wireframes
+ menus/ # Menu screen wireframes
+ dialogue/ # Dialogue box wireframes
+ insert/ # Neural insert wireframes
+```
+
+## Composition Workflow
+
+For multi-element wireframes:
+
+1. Read `references/component-library.md` for the matching UI pattern
+2. Adapt dimensions and positions to the wireframe layout
+3. Group related elements: `frame0-cmd.sh group ...`
+4. Add connectors for navigation flow: `frame0-cmd.sh create-connector`
+5. Export the finished wireframe
+
+## Multi-Page Wireframes
+
+Create wireframe sets showing multiple states or screens:
+
+```bash
+CMD=".claude/skills/frame0-wireframe/scripts/frame0-wireframe.sh"
+$CMD new-page "HUD - Normal"
+# ... add components ...
+$CMD new-page "HUD - Alert"
+# ... add components ...
+$CMD new-page "HUD - Combat"
+# ... add components ...
+```
+
+## Agent Guidance
+
+- **Araminta** — Primary user. Full wireframe creation, layout iteration,
+ visual consistency. Uses all component library patterns.
+- **Tyre** — Interface architecture wireframes. System boundary diagrams
+ showing where UI connects to game systems.
+- **Qatux** — Export wireframes for UI decision records and documentation.
+
+## References
+
+- `references/api-reference.md` — Full Frame0 HTTP API command and property
+ reference. Read for fine-grained control beyond helpers.
+- `references/component-library.md` — Pre-built wireframe patterns (HUD,
+ dialogue, menus, modals, lists, inventory). Read when starting a wireframe.
+- `references/setup-guide.md` — Frame0 installation and startup for Fedora.
+ Read if Frame0 is not installed or not running.
diff --git a/.claude/skills/frame0-wireframe/references/api-reference.md b/.claude/skills/frame0-wireframe/references/api-reference.md
new file mode 100644
index 000000000..38546271c
--- /dev/null
+++ b/.claude/skills/frame0-wireframe/references/api-reference.md
@@ -0,0 +1,285 @@
+# Frame0 HTTP API Reference
+
+Frame0 exposes a local HTTP API when the desktop app is running.
+
+## Endpoint
+
+```
+POST http://localhost:{port}/execute_command
+Content-Type: application/json
+```
+
+Default port: **58320** (override via `FRAME0_PORT` env var or `--port` flag).
+
+## Request Format
+
+```json
+{
+ "command": "namespace:action",
+ "args": { ... }
+}
+```
+
+## Response Format
+
+```json
+{
+ "success": true,
+ "data": { ... }
+}
+```
+
+On error:
+
+```json
+{
+ "success": false,
+ "error": "Error description"
+}
+```
+
+---
+
+## Commands by Namespace
+
+### shape — Shape Creation and Manipulation
+
+#### shape:create-shape
+
+Create a new shape on the current page.
+
+```json
+{
+ "command": "shape:create-shape",
+ "args": {
+ "type": "rectangle",
+ "shapeProps": {
+ "name": "my-button",
+ "left": 100,
+ "top": 200,
+ "width": 120,
+ "height": 36,
+ "fillColor": "#2a3040",
+ "strokeColor": "#333340",
+ "fontColor": "#c8d0e0",
+ "fontSize": 14,
+ "text": "Button Label",
+ "corners": [4, 4, 4, 4]
+ }
+ }
+}
+```
+
+**Shape types:** `rectangle`, `ellipse`, `triangle`, `diamond`, `line`, `text`, `image`, `icon`
+
+#### shape:update-shape
+
+```json
+{
+ "command": "shape:update-shape",
+ "args": {
+ "shapeId": "shape-id",
+ "shapeProps": {
+ "fillColor": "#1a1e24",
+ "text": "Updated Label"
+ }
+ }
+}
+```
+
+#### shape:move
+
+```json
+{
+ "command": "shape:move",
+ "args": {
+ "shapeId": "shape-id",
+ "dx": 50,
+ "dy": -20
+ }
+}
+```
+
+#### shape:group / shape:ungroup
+
+```json
+{
+ "command": "shape:group",
+ "args": {
+ "shapeIdArray": ["id-1", "id-2", "id-3"]
+ }
+}
+```
+
+#### shape:create-connector
+
+```json
+{
+ "command": "shape:create-connector",
+ "args": {
+ "tailId": "source-shape-id",
+ "headId": "target-shape-id",
+ "shapeProps": {
+ "strokeColor": "#c8d8f0"
+ }
+ }
+}
+```
+
+#### shape:create-icon
+
+```json
+{
+ "command": "shape:create-icon",
+ "args": {
+ "iconName": "search",
+ "shapeProps": {
+ "left": 100,
+ "top": 100,
+ "width": 24,
+ "height": 24,
+ "strokeColor": "#c8d0e0"
+ }
+ }
+}
+```
+
+Icon sizes: 16, 24, 32, 48 pixels.
+
+#### shape:create-image
+
+```json
+{
+ "command": "shape:create-image",
+ "args": {
+ "mimeType": "image/png",
+ "imageData": "base64-encoded-data",
+ "shapeProps": {
+ "left": 100,
+ "top": 100,
+ "width": 200,
+ "height": 150
+ }
+ }
+}
+```
+
+### edit — Editing Operations
+
+#### edit:delete
+
+```json
+{
+ "command": "edit:delete",
+ "args": {
+ "shapeIdArray": ["id-1", "id-2"]
+ }
+}
+```
+
+### page — Page Management
+
+#### page:add
+
+```json
+{
+ "command": "page:add",
+ "args": {
+ "pageProps": {
+ "name": "HUD Layout"
+ }
+ }
+}
+```
+
+#### page:get
+
+```json
+{
+ "command": "page:get",
+ "args": {
+ "pageId": "page-id"
+ }
+}
+```
+
+Omit `pageId` to get current page.
+
+#### page:get-all
+
+```json
+{
+ "command": "page:get-all",
+ "args": {}
+}
+```
+
+#### page:set-current
+
+```json
+{
+ "command": "page:set-current",
+ "args": {
+ "pageId": "page-id"
+ }
+}
+```
+
+#### page:delete
+
+```json
+{
+ "command": "page:delete",
+ "args": {
+ "pageId": "page-id"
+ }
+}
+```
+
+### file — Export
+
+#### file:export-image
+
+```json
+{
+ "command": "file:export-image",
+ "args": {
+ "pageId": "page-id",
+ "format": "png",
+ "fillBackground": true
+ }
+}
+```
+
+Formats: `png`, `svg`
+
+Optional: `"shapeIdArray": ["id-1"]` to export specific shapes only.
+
+---
+
+## Shape Properties Reference
+
+| Property | Type | Notes |
+|----------|------|-------|
+| `name` | string | Shape identifier/label |
+| `left` | number | X position in pixels (origin: top-left) |
+| `top` | number | Y position in pixels |
+| `width` | number | Width in pixels |
+| `height` | number | Height in pixels |
+| `fillColor` | string | Hex color (e.g., `"#2a3040"`) |
+| `strokeColor` | string | Hex color for border |
+| `fontColor` | string | Hex color for text |
+| `fontSize` | number | Font size in pixels |
+| `text` | string | Text content (for shapes with text) |
+| `wordWrap` | boolean | Enable text word wrapping |
+| `corners` | number[4] | Border radius [topLeft, topRight, bottomRight, bottomLeft] |
+| `path` | array | Coordinate pairs for lines/polygons |
+| `lineType` | string | Line style |
+| `tailEndType` | string | Arrow tail type |
+| `headEndType` | string | Arrow head type |
+
+## Coordinate System
+
+- Origin: top-left corner of the canvas
+- Units: pixels
+- X increases rightward, Y increases downward
diff --git a/.claude/skills/frame0-wireframe/references/component-library.md b/.claude/skills/frame0-wireframe/references/component-library.md
new file mode 100644
index 000000000..24151f5e2
--- /dev/null
+++ b/.claude/skills/frame0-wireframe/references/component-library.md
@@ -0,0 +1,193 @@
+# Component Library
+
+Pre-built wireframe patterns for The Settled Reach UI. Each pattern provides
+`frame0-wireframe.sh` command sequences. Colors from visual-grammar-v01.md.
+
+---
+
+## 1. HUD Layout
+
+The main gameplay overlay. Minimap top-right, monologue bottom-center,
+insert display bottom-left.
+
+```bash
+CMD=".claude/skills/frame0-wireframe/scripts/frame0-wireframe.sh"
+
+$CMD new-page "HUD Layout"
+
+# Minimap (top-right)
+$CMD container "Minimap" 880 20 240 240
+
+# Monologue panel (bottom-center)
+$CMD container "Monologue" 300 680 520 80
+$CMD label "Internal monologue text appears here..." 310 700
+
+# Insert display (bottom-left)
+$CMD container "Insert Display" 20 600 260 160
+$CMD label "Neural Insert Data" 30 620
+
+# Action hints (bottom-right)
+$CMD container "Action Hints" 880 700 240 60
+$CMD label "[E] Interact [TAB] Insert" 890 720 12
+```
+
+**Viewport assumption:** 1140x780 (matching Godot project viewport).
+
+---
+
+## 2. Dialogue Box
+
+Speaker panel with response options. Anchored bottom-center during dialogue mode.
+
+```bash
+CMD=".claude/skills/frame0-wireframe/scripts/frame0-wireframe.sh"
+
+$CMD new-page "Dialogue Box"
+
+# Dialogue container
+$CMD container "Dialogue" 170 500 800 260
+
+# Speaker name
+$CMD label "LERA KONSTANTIN" 190 520 16
+
+# Dialogue text area
+$CMD container "Text Area" 190 550 760 100
+$CMD label "You look like you could use a drink. First time on the station?" 200 560
+
+# Response options
+$CMD button "[1] Ask about the station" 190 670 370 30
+$CMD button "[2] Ask about recent events" 190 710 370 30
+$CMD button "[3] Leave" 580 670 180 30
+```
+
+---
+
+## 3. Menu Screen
+
+Full-screen menu with sidebar navigation and content area.
+
+```bash
+CMD=".claude/skills/frame0-wireframe/scripts/frame0-wireframe.sh"
+
+$CMD new-page "Pause Menu"
+
+# Background overlay
+$CMD container "Menu Background" 0 0 1140 780
+
+# Sidebar navigation
+$CMD container "Navigation" 20 20 200 740
+$CMD button "Inventory" 30 40 180 36
+$CMD button "Journal" 30 86 180 36
+$CMD button "Map" 30 132 180 36
+$CMD button "Settings" 30 178 180 36
+$CMD button "Resume" 30 720 180 36
+
+# Content area
+$CMD container "Content" 240 20 880 740
+$CMD label "Content area" 260 40
+```
+
+---
+
+## 4. Modal Dialog
+
+Centered overlay for confirmations, alerts, choices.
+
+```bash
+CMD=".claude/skills/frame0-wireframe/scripts/frame0-wireframe.sh"
+
+$CMD new-page "Modal Dialog"
+
+# Overlay background (semi-transparent implied)
+$CMD container "Overlay" 0 0 1140 780
+
+# Modal panel (centered)
+$CMD container "Modal" 320 240 500 300
+
+# Title
+$CMD label "Confirm Action" 340 260 18
+
+# Divider
+$CMD divider 340 290 460
+
+# Body text
+$CMD label "Are you sure you want to proceed?" 340 310
+$CMD label "This action cannot be undone." 340 340
+
+# Action buttons
+$CMD button "Cancel" 480 480 120 36
+$CMD button "Confirm" 620 480 120 36
+```
+
+---
+
+## 5. List View
+
+Scrollable list with item selection and detail panel.
+
+```bash
+CMD=".claude/skills/frame0-wireframe/scripts/frame0-wireframe.sh"
+
+$CMD new-page "List View"
+
+# List panel
+$CMD container "Item List" 20 20 400 740
+
+# List items (repeating pattern)
+$CMD button "Item Alpha" 30 30 380 40
+$CMD button "Item Beta" 30 80 380 40
+$CMD button "Item Gamma" 30 130 380 40
+$CMD button "Item Delta" 30 180 380 40
+
+# Detail panel
+$CMD container "Detail" 440 20 680 740
+$CMD label "Item Alpha" 460 40 18
+$CMD divider 460 70 640
+$CMD label "Description and properties appear here." 460 90
+```
+
+---
+
+## 6. Inventory Grid
+
+Grid of cells for item management.
+
+```bash
+CMD=".claude/skills/frame0-wireframe/scripts/frame0-wireframe.sh"
+
+$CMD new-page "Inventory Grid"
+
+# Grid container
+$CMD container "Inventory" 240 100 660 580
+
+# Grid header
+$CMD label "INVENTORY" 260 120 18
+
+# Grid cells (4x4 example, 64px each with 8px gaps)
+# Row 1
+$CMD container "" 260 160 64 64
+$CMD container "" 332 160 64 64
+$CMD container "" 404 160 64 64
+$CMD container "" 476 160 64 64
+
+# Row 2
+$CMD container "" 260 232 64 64
+$CMD container "" 332 232 64 64
+$CMD container "" 404 232 64 64
+$CMD container "" 476 232 64 64
+
+# Selected item detail
+$CMD container "Item Detail" 580 160 300 400
+$CMD label "Selected Item Name" 600 180 16
+$CMD divider 600 210 260
+$CMD label "Item description and stats" 600 230
+```
+
+---
+
+## Viewport and Grid
+
+- **Project viewport:** 1140x780 (from Godot project settings)
+- **Grid unit:** 8px (for consistent spacing)
+- **Minimum touch target:** 36px height for interactive elements
+- **Font sizes:** 12 (small/label), 14 (body), 16 (subtitle), 18 (heading), 24 (title)
diff --git a/.claude/skills/frame0-wireframe/references/setup-guide.md b/.claude/skills/frame0-wireframe/references/setup-guide.md
new file mode 100644
index 000000000..c0f56abd7
--- /dev/null
+++ b/.claude/skills/frame0-wireframe/references/setup-guide.md
@@ -0,0 +1,53 @@
+# Frame0 Setup Guide
+
+## Installation (Fedora)
+
+Download from https://frame0.app/download and install the RPM:
+
+```bash
+sudo dnf install ./frame0-*.x86_64.rpm
+```
+
+Requires: Fedora 40 or later (x86_64).
+
+## Starting Frame0
+
+Launch the desktop application:
+
+```bash
+frame0 &
+```
+
+Frame0 exposes an HTTP API at `localhost:58320` when running.
+
+## Verify API Access
+
+```bash
+.claude/skills/frame0-wireframe/scripts/frame0-cmd.sh health
+```
+
+Expected output: `Frame0 is running on port 58320`
+
+## Port Configuration
+
+Default port: **58320**
+
+To use a different port, set the environment variable:
+
+```bash
+export FRAME0_PORT=58321
+```
+
+Or pass `--port` to any script:
+
+```bash
+.claude/skills/frame0-wireframe/scripts/frame0-cmd.sh --port 58321 health
+```
+
+## Troubleshooting
+
+| Symptom | Cause | Fix |
+|---------|-------|-----|
+| "Connection refused" | Frame0 not running | Start the desktop app |
+| "Port in use" | Another instance running | Close duplicate or use different port |
+| Script hangs | API unresponsive | Restart Frame0 |
diff --git a/.claude/skills/frame0-wireframe/scripts/frame0-cmd.sh b/.claude/skills/frame0-wireframe/scripts/frame0-cmd.sh
new file mode 100755
index 000000000..ab2186226
--- /dev/null
+++ b/.claude/skills/frame0-wireframe/scripts/frame0-cmd.sh
@@ -0,0 +1,210 @@
+#!/bin/bash
+set -euo pipefail
+
+PORT="${FRAME0_PORT:-58320}"
+ENDPOINT="http://localhost:${PORT}/execute_command"
+
+usage() {
+ cat < [args...] [--port N]
+
+Low-level Frame0 HTTP API wrapper. Replaces the MCP server with direct
+curl calls. Requires Frame0 desktop app to be running.
+
+Commands:
+ health Check if Frame0 is running
+ exec Execute a raw API command
+ create-shape Create a shape (rectangle, ellipse, text, etc.)
+ update-shape Update shape properties
+ delete [id...] Delete shapes by ID
+ move Move a shape by pixel offset
+ group [id...] Group shapes
+ ungroup [id...] Ungroup shapes
+ create-connector [json-props] Connect two shapes
+ create-icon Create an icon shape
+ add-page Add a new page
+ get-page [page-id] Get current or specific page info
+ list-pages List all pages
+ set-page Set current page
+ export Export page (png/svg)
+
+Options:
+ --port N Frame0 API port (default: $PORT, env: FRAME0_PORT)
+
+Examples:
+ $(basename "$0") health
+ $(basename "$0") create-shape rectangle '{"name":"btn","left":100,"top":100,"width":120,"height":36}'
+ $(basename "$0") list-pages
+ $(basename "$0") export page-id png
+EOF
+ exit 1
+}
+
+# Parse --port from anywhere in args
+ARGS=()
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+ --port) PORT="$2"; ENDPOINT="http://localhost:${PORT}/execute_command"; shift 2 ;;
+ *) ARGS+=("$1"); shift ;;
+ esac
+done
+set -- "${ARGS[@]+"${ARGS[@]}"}"
+
+[[ $# -lt 1 ]] && usage
+
+# Execute a Frame0 API command, return data or error
+frame0_exec() {
+ local command="$1"
+ local args="${2:-\{\}}"
+
+ local response
+ response=$(curl -s -w "\n%{http_code}" -X POST "$ENDPOINT" \
+ -H "Content-Type: application/json" \
+ -d "{\"command\": \"$command\", \"args\": $args}" 2>&1) || {
+ echo "ERROR: Cannot connect to Frame0 at localhost:$PORT" >&2
+ echo "Is Frame0 running? See: .claude/skills/frame0-wireframe/references/setup-guide.md" >&2
+ return 1
+ }
+
+ local http_code body
+ http_code=$(echo "$response" | tail -1)
+ body=$(echo "$response" | sed '$d')
+
+ if [[ "$http_code" != 2* ]]; then
+ echo "ERROR: HTTP $http_code from Frame0" >&2
+ echo "$body" >&2
+ return 1
+ fi
+
+ # Parse success/error from response
+ python3 -c "
+import sys, json
+try:
+ r = json.loads(sys.stdin.read())
+ if r.get('success'):
+ d = r.get('data')
+ if d is not None:
+ print(json.dumps(d, indent=2))
+ else:
+ print('ERROR: ' + str(r.get('error', 'Unknown error')), file=sys.stderr)
+ sys.exit(1)
+except json.JSONDecodeError as e:
+ print(f'ERROR: Invalid JSON response: {e}', file=sys.stderr)
+ sys.exit(1)
+" <<< "$body"
+}
+
+# Build JSON array from remaining args
+ids_to_json_array() {
+ local arr="["
+ local first=true
+ for id in "$@"; do
+ [[ "$first" == true ]] && first=false || arr+=","
+ arr+="\"$id\""
+ done
+ arr+="]"
+ echo "$arr"
+}
+
+CMD="${1:-}"
+shift || true
+
+case "$CMD" in
+ health)
+ if curl -s -o /dev/null -w "%{http_code}" "http://localhost:${PORT}/" 2>/dev/null | grep -q "^[23]"; then
+ echo "Frame0 is running on port $PORT"
+ else
+ echo "Frame0 is NOT running on port $PORT" >&2
+ echo "Start Frame0 desktop app, then retry." >&2
+ echo "See: .claude/skills/frame0-wireframe/references/setup-guide.md" >&2
+ exit 1
+ fi
+ ;;
+
+ exec)
+ [[ $# -lt 2 ]] && { echo "Usage: exec " >&2; exit 1; }
+ frame0_exec "$1" "$2"
+ ;;
+
+ create-shape)
+ [[ $# -lt 2 ]] && { echo "Usage: create-shape " >&2; exit 1; }
+ local_type="$1"
+ local_props="$2"
+ frame0_exec "shape:create-shape" "{\"type\": \"$local_type\", \"shapeProps\": $local_props}"
+ ;;
+
+ update-shape)
+ [[ $# -lt 2 ]] && { echo "Usage: update-shape " >&2; exit 1; }
+ frame0_exec "shape:update-shape" "{\"shapeId\": \"$1\", \"shapeProps\": $2}"
+ ;;
+
+ delete)
+ [[ $# -lt 1 ]] && { echo "Usage: delete [id...]" >&2; exit 1; }
+ local_arr=$(ids_to_json_array "$@")
+ frame0_exec "edit:delete" "{\"shapeIdArray\": $local_arr}"
+ ;;
+
+ move)
+ [[ $# -lt 3 ]] && { echo "Usage: move " >&2; exit 1; }
+ frame0_exec "shape:move" "{\"shapeId\": \"$1\", \"dx\": $2, \"dy\": $3}"
+ ;;
+
+ group)
+ [[ $# -lt 1 ]] && { echo "Usage: group [id...]" >&2; exit 1; }
+ local_arr=$(ids_to_json_array "$@")
+ frame0_exec "shape:group" "{\"shapeIdArray\": $local_arr}"
+ ;;
+
+ ungroup)
+ [[ $# -lt 1 ]] && { echo "Usage: ungroup [id...]" >&2; exit 1; }
+ local_arr=$(ids_to_json_array "$@")
+ frame0_exec "shape:ungroup" "{\"shapeIdArray\": $local_arr}"
+ ;;
+
+ create-connector)
+ [[ $# -lt 2 ]] && { echo "Usage: create-connector [json-props]" >&2; exit 1; }
+ local_props="${3:-\{\}}"
+ frame0_exec "shape:create-connector" "{\"tailId\": \"$1\", \"headId\": \"$2\", \"shapeProps\": $local_props}"
+ ;;
+
+ create-icon)
+ [[ $# -lt 2 ]] && { echo "Usage: create-icon " >&2; exit 1; }
+ frame0_exec "shape:create-icon" "{\"iconName\": \"$1\", \"shapeProps\": $2}"
+ ;;
+
+ add-page)
+ [[ $# -lt 1 ]] && { echo "Usage: add-page " >&2; exit 1; }
+ frame0_exec "page:add" "{\"pageProps\": $1}"
+ ;;
+
+ get-page)
+ if [[ $# -ge 1 ]]; then
+ frame0_exec "page:get" "{\"pageId\": \"$1\"}"
+ else
+ frame0_exec "page:get" "{}"
+ fi
+ ;;
+
+ list-pages)
+ frame0_exec "page:get-all" "{}"
+ ;;
+
+ set-page)
+ [[ $# -lt 1 ]] && { echo "Usage: set-page " >&2; exit 1; }
+ frame0_exec "page:set-current" "{\"pageId\": \"$1\"}"
+ ;;
+
+ export)
+ [[ $# -lt 2 ]] && { echo "Usage: export " >&2; exit 1; }
+ frame0_exec "file:export-image" "{\"pageId\": \"$1\", \"format\": \"$2\", \"fillBackground\": true}"
+ ;;
+
+ --help|-h|help)
+ usage
+ ;;
+
+ *)
+ echo "Unknown command: $CMD" >&2
+ usage
+ ;;
+esac
diff --git a/.claude/skills/frame0-wireframe/scripts/frame0-wireframe.sh b/.claude/skills/frame0-wireframe/scripts/frame0-wireframe.sh
new file mode 100755
index 000000000..e6a319c82
--- /dev/null
+++ b/.claude/skills/frame0-wireframe/scripts/frame0-wireframe.sh
@@ -0,0 +1,144 @@
+#!/bin/bash
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+CMD="$SCRIPT_DIR/frame0-cmd.sh"
+
+# Project wireframe defaults from docs/design/visual-grammar-v01.md
+BG="#1a1e24" # Zone 1 floor
+STROKE="#333340" # Outline standard
+FILL="#2a3040" # Zone 1 wall
+TEXT="#c8d0e0" # Insert chrome
+ACCENT="#c8d8f0" # Zone 1 fixture light
+FONT_SIZE=14
+TITLE_SIZE=24
+
+usage() {
+ cat < [args...]
+
+High-level Frame0 wireframe composition helpers. Builds on frame0-cmd.sh
+with project-appropriate defaults (colors from visual-grammar-v01.md).
+
+Commands:
+ new-page Create a new page with title label
+ button [w] [h] Labeled button (default 120x36)
+ text-field [w] [h] Text input field (default 200x32)
+ container Labeled panel/container
+ label [font-size] Text label
+ divider [h|v] Divider line (default horizontal)
+ export Export current page (png/svg)
+
+Environment:
+ FRAME0_PORT Frame0 API port (default: 58320)
+
+Examples:
+ $(basename "$0") new-page "HUD Layout v1"
+ $(basename "$0") button "Confirm" 100 200
+ $(basename "$0") container "Inventory Panel" 50 50 300 400
+ $(basename "$0") export png docs/design/wireframes/hud/hud-v1.png
+EOF
+ exit 1
+}
+
+[[ $# -lt 1 ]] && usage
+
+# Ensure Frame0 is running before any operation
+check_health() {
+ "$CMD" health > /dev/null 2>&1 || {
+ echo "ERROR: Frame0 is not running. Start the desktop app first." >&2
+ exit 1
+ }
+}
+
+SUBCMD="$1"
+shift
+
+case "$SUBCMD" in
+ new-page)
+ [[ $# -lt 1 ]] && { echo "Usage: new-page " >&2; exit 1; }
+ check_health
+ local_title="$1"
+ echo "Creating page: $local_title"
+ "$CMD" add-page "{\"name\": \"$local_title\"}"
+ # Add title label at top of page
+ "$CMD" create-shape text "{\"name\": \"page-title\", \"left\": 20, \"top\": 10, \"width\": 500, \"height\": 40, \"text\": \"$local_title\", \"fontSize\": $TITLE_SIZE, \"fontColor\": \"$TEXT\"}"
+ echo "Page created: $local_title"
+ ;;
+
+ button)
+ [[ $# -lt 3 ]] && { echo "Usage: button [w] [h]" >&2; exit 1; }
+ check_health
+ local_label="$1" local_x="$2" local_y="$3"
+ local_w="${4:-120}" local_h="${5:-36}"
+ "$CMD" create-shape rectangle "{\"name\": \"$local_label\", \"left\": $local_x, \"top\": $local_y, \"width\": $local_w, \"height\": $local_h, \"fillColor\": \"$FILL\", \"strokeColor\": \"$ACCENT\", \"fontColor\": \"$TEXT\", \"fontSize\": $FONT_SIZE, \"text\": \"$local_label\", \"corners\": [4,4,4,4]}"
+ ;;
+
+ text-field)
+ [[ $# -lt 3 ]] && { echo "Usage: text-field [w] [h]" >&2; exit 1; }
+ check_health
+ local_label="$1" local_x="$2" local_y="$3"
+ local_w="${4:-200}" local_h="${5:-32}"
+ "$CMD" create-shape rectangle "{\"name\": \"$local_label\", \"left\": $local_x, \"top\": $local_y, \"width\": $local_w, \"height\": $local_h, \"fillColor\": \"$BG\", \"strokeColor\": \"$STROKE\", \"fontColor\": \"$TEXT\", \"fontSize\": $FONT_SIZE, \"text\": \"$local_label\", \"corners\": [2,2,2,2]}"
+ ;;
+
+ container)
+ [[ $# -lt 5 ]] && { echo "Usage: container " >&2; exit 1; }
+ check_health
+ local_label="$1" local_x="$2" local_y="$3" local_w="$4" local_h="$5"
+ # Container frame
+ "$CMD" create-shape rectangle "{\"name\": \"$local_label\", \"left\": $local_x, \"top\": $local_y, \"width\": $local_w, \"height\": $local_h, \"fillColor\": \"$BG\", \"strokeColor\": \"$STROKE\", \"fontSize\": 12, \"fontColor\": \"$TEXT\"}"
+ # Container label at top-left inside
+ label_x=$((local_x + 8))
+ label_y=$((local_y + 4))
+ "$CMD" create-shape text "{\"name\": \"${local_label}-label\", \"left\": $label_x, \"top\": $label_y, \"width\": 200, \"height\": 20, \"text\": \"$local_label\", \"fontSize\": 12, \"fontColor\": \"$STROKE\"}"
+ ;;
+
+ label)
+ [[ $# -lt 3 ]] && { echo "Usage: label [font-size]" >&2; exit 1; }
+ check_health
+ local_text="$1" local_x="$2" local_y="$3"
+ local_size="${4:-$FONT_SIZE}"
+ "$CMD" create-shape text "{\"name\": \"$local_text\", \"left\": $local_x, \"top\": $local_y, \"width\": 300, \"height\": 24, \"text\": \"$local_text\", \"fontSize\": $local_size, \"fontColor\": \"$TEXT\"}"
+ ;;
+
+ divider)
+ [[ $# -lt 3 ]] && { echo "Usage: divider [h|v]" >&2; exit 1; }
+ check_health
+ local_x="$1" local_y="$2" local_len="$3"
+ local_dir="${4:-h}"
+ if [[ "$local_dir" == "v" ]]; then
+ "$CMD" create-shape line "{\"name\": \"divider\", \"left\": $local_x, \"top\": $local_y, \"width\": 1, \"height\": $local_len, \"strokeColor\": \"$STROKE\"}"
+ else
+ "$CMD" create-shape line "{\"name\": \"divider\", \"left\": $local_x, \"top\": $local_y, \"width\": $local_len, \"height\": 1, \"strokeColor\": \"$STROKE\"}"
+ fi
+ ;;
+
+ export)
+ [[ $# -lt 2 ]] && { echo "Usage: export " >&2; exit 1; }
+ check_health
+ local_format="$1" local_output="$2"
+
+ # Get current page ID
+ local_page_id=$("$CMD" get-page | python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null)
+ if [[ -z "$local_page_id" ]]; then
+ echo "ERROR: Could not determine current page ID" >&2
+ exit 1
+ fi
+
+ # Ensure output directory exists
+ mkdir -p "$(dirname "$local_output")"
+
+ "$CMD" export "$local_page_id" "$local_format"
+ echo "Exported: $local_output"
+ ;;
+
+ --help|-h|help)
+ usage
+ ;;
+
+ *)
+ echo "Unknown command: $SUBCMD" >&2
+ usage
+ ;;
+esac