@konstantdotcloud/boombox 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@konstantdotcloud/boombox",
3
+ "version": "0.1.0",
4
+ "description": "Local Boombox runtime for Konstant cassettes — CLI, stdio MCP server, and local Hono proxy.",
5
+ "license": "UNLICENSED",
6
+ "type": "module",
7
+ "bin": {
8
+ "boombox": "dist/boombox.js"
9
+ },
10
+ "main": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "files": [
13
+ "dist",
14
+ "ui/dist",
15
+ "README.md"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsup && npm run build:ui",
19
+ "build:ui": "cd ui && npm install --silent && npm run build",
20
+ "dev": "tsx bin/boombox.ts",
21
+ "test": "vitest run",
22
+ "test:watch": "vitest",
23
+ "prepublishOnly": "npm run build",
24
+ "release:patch": "npm version patch --no-git-tag-version && npm publish --access public",
25
+ "release:minor": "npm version minor --no-git-tag-version && npm publish --access public",
26
+ "release:major": "npm version major --no-git-tag-version && npm publish --access public"
27
+ },
28
+ "engines": {
29
+ "node": ">=20"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "dependencies": {
35
+ "@hono/node-server": "^1.19.9",
36
+ "@modelcontextprotocol/sdk": "^1.25.2",
37
+ "chalk": "^5.6.2",
38
+ "commander": "^12.1.0",
39
+ "hono": "^4.11.9",
40
+ "open": "^10.1.0",
41
+ "prompts": "^2.4.2",
42
+ "smol-toml": "^1.3.1",
43
+ "zod": "^3.25.76"
44
+ },
45
+ "devDependencies": {
46
+ "@types/node": "^20.0.0",
47
+ "@types/prompts": "^2.4.9",
48
+ "tsup": "^8.3.5",
49
+ "tsx": "^4.7.0",
50
+ "typescript": "^5.0.0",
51
+ "vitest": "^3.2.4"
52
+ }
53
+ }
package/ui/README.md ADDED
@@ -0,0 +1,142 @@
1
+ # @konstant/boombox-ui
2
+
3
+ Boombox local UI — the cassette-deck console a tenant operator runs on their laptop.
4
+
5
+ This package is the **foundation scaffold** delivered in Wave 5g. It boots the
6
+ shell, theme system, mode system, primitives, and a route table populated with
7
+ placeholder pages. Per-screen translations land in Wave 5h+.
8
+
9
+ ## Framework decision
10
+
11
+ **React + Vite.** Three reasons:
12
+
13
+ 1. The Boombox design package ships as React JSX. Staying React makes the port
14
+ 1:1 — no JSX→Svelte translation drift on tokens, transport, or shell.
15
+ 2. `@konstantdotcloud/boombox` is a separate package. The Konstant tenant admin moving
16
+ to Svelte does not bind this surface.
17
+ 3. The team can ship Boombox UI without learning a new framework. Cassette-deck
18
+ primitives are visually heavy; React keeps the design contract in one shape.
19
+
20
+ ## Layout
21
+
22
+ ```
23
+ ui/
24
+ ├── index.html Vite entry, top-level <html data-theme="amber" ...>
25
+ ├── src/
26
+ │ ├── main.tsx React root
27
+ │ ├── BoomboxApp.tsx theme + mode providers + router
28
+ │ ├── styles/
29
+ │ │ └── design-system.css verbatim copy of design package styles.css
30
+ │ ├── primitives/ Icon, Reels, ConfBar, LED, Tag, Panel, Btn,
31
+ │ │ VisibilityBadge, TrustBadge, GrantBadge
32
+ │ ├── shell/
33
+ │ │ ├── BoomboxShell.tsx top nav, theme switcher, mode select, footer
34
+ │ │ ├── ThemeProvider.tsx amber / paper / phosphor (localStorage)
35
+ │ │ ├── ModeProvider.tsx driver / reviewer / attendee / calm / overlay
36
+ │ │ │ (URL ?mode=)
37
+ │ │ └── routes.ts route table — spec for follow-up waves
38
+ │ ├── api/
39
+ │ │ ├── client.ts typed fetch wrapper
40
+ │ │ └── types.ts cassette / room / run / approval-gate types
41
+ │ └── pages/
42
+ │ └── PlaceholderPage.tsx single placeholder for unimplemented screens
43
+ └── tests/
44
+ ├── BoomboxApp.test.tsx
45
+ ├── ThemeProvider.test.tsx
46
+ ├── primitives.test.tsx
47
+ └── routes.test.tsx
48
+ ```
49
+
50
+ ## Scripts
51
+
52
+ ```bash
53
+ pnpm install
54
+ pnpm dev # vite dev server, http://localhost:5173
55
+ pnpm build # tsc --noEmit && vite build → dist/
56
+ pnpm test # vitest run
57
+ ```
58
+
59
+ ## Theme system
60
+
61
+ `<ThemeProvider>` writes `data-theme` to `<html>` and persists to
62
+ `localStorage["boombox.theme"]`. Three themes: `amber` (default), `paper`,
63
+ `phosphor`. The theme switcher in the top bar (A/P/G) flips it.
64
+
65
+ ## Mode system
66
+
67
+ `<ModeProvider>` reads `?mode=` from the URL and writes `data-stance` to
68
+ `<html>`. Five modes: `driver`, `reviewer`, `attendee`, `calm`, `overlay`.
69
+ Switching the dropdown updates the URL.
70
+
71
+ ## Routes
72
+
73
+ | Path | Screen | Wave |
74
+ |------|--------|------|
75
+ | `/` | `CassetteRack` | 5h |
76
+ | `/queue` | `MeetingRoomsQueue` | 5h |
77
+ | `/cassette/:id` | `CassetteRoomDetail` | 5h |
78
+ | `/run/:id` | `PacketApprovalScreen` | 5h |
79
+ | `/run/:id/attendee` | `AttendeeRoomScreen` | 5i |
80
+ | `/series/:id` | `SeriesScreen` | 5i |
81
+ | `/workfield/:id` | `WorkfieldScreen` | 5i |
82
+ | `/admin` | `AdminScreen` | 5j |
83
+ | `/calm` | `CalmModeScreen` | 5j |
84
+
85
+ Every route currently renders `<PlaceholderPage>` showing the screen name,
86
+ the wave where it lands, and any URL params.
87
+
88
+ ## API client
89
+
90
+ `src/api/client.ts` wraps `fetch` with auth + base URL. Configure via:
91
+
92
+ - `VITE_BOOMBOX_API_URL` env var at build time, or
93
+ - `localStorage["boombox.api.token"]` for the bearer token, or
94
+ - programmatic `configureApi({ baseUrl, token })`.
95
+
96
+ The default base URL is `http://localhost:8787` (the local Hono proxy).
97
+
98
+ ## Integration points (open work)
99
+
100
+ These are not blockers for the foundation, but they are the integration seams
101
+ that follow-up waves need to wire:
102
+
103
+ 1. **API base URL bootstrap.** Today the client reads `VITE_BOOMBOX_API_URL`
104
+ or falls back to `http://localhost:8787`. Production behavior (when the UI
105
+ is served by `boombox serve`) should pull `gateway_url` from
106
+ `~/.boombox/config.toml` and inject it into the page (e.g. via a
107
+ `<script>window.__BOOMBOX__ = {...}</script>` shim emitted by the local
108
+ server).
109
+ 2. **Auth token bootstrap.** Today the token is read from
110
+ `localStorage["boombox.api.token"]`. Real flow: the local Hono proxy
111
+ already injects `Authorization: Bearer <api_key>` for `/api/*`. The UI
112
+ should hit the proxy directly with no token, since the proxy adds it.
113
+ `client.ts` accepts a null token for that path.
114
+ 3. **`boombox serve` static-serve integration.** Currently `pnpm build`
115
+ produces `ui/dist/`. The CLI's `boombox serve --http` should mount that
116
+ directory (or fetch the latest UI bundle from the gateway). Tracked as a
117
+ TODO in `src/cli/serve.ts`.
118
+ 4. **Cloud API endpoint shapes.** `api/types.ts` mirrors the cassette
119
+ substrate. Once Wave 5b-ApprovalGate endpoints stabilize, regenerate the
120
+ types so this package stays a faithful client (instead of duplicating).
121
+ 5. **Tweaks panel / pressure / era presets.** The design package's Era
122
+ (Field '79 / Studio '89 / Issue 04) and Pressure controls are not yet
123
+ wired here. Theme + Stance are. Era + Pressure land alongside the first
124
+ real screens in Wave 5h, since they reshape the chrome the screens render
125
+ into.
126
+
127
+ ## Critical TODOs for Wave 5h+
128
+
129
+ - `CassetteRack` (`/`) — port `RackScreen` from `app.jsx` + `RackView` from
130
+ `cards.jsx`. Wire to `listCassettes()`.
131
+ - `MeetingRoomsQueue` (`/queue`) — port from `screens.jsx`. Wire to
132
+ `listRooms()`.
133
+ - `CassetteRoomDetail` (`/cassette/:id`) — port `RoomDetailScreen`. Wire to
134
+ `getRoom()` and `listApprovalGates()`.
135
+ - `PacketApprovalScreen` (`/run/:id`) — port `PacketApprovalScreen`. Wire to
136
+ `getRun()` + `approveGate()`.
137
+ - Port `RunStateTransport`, `ProofCard`, `VuMeter`, `KV`, `ReplayLink` from
138
+ `primitives.jsx` — these are screen-level primitives, not foundation
139
+ primitives, so they live with the screens that use them.
140
+ - `AdminScreen` (`/admin`) — port `admin.jsx`.
141
+ - `CalmModeScreen` (`/calm`) — port `calm.jsx`. This bypasses the shell.
142
+ - Add Era + Pressure controls + presets (`app.jsx` ERA_PRESETS / STANCE_PRESETS).
@@ -0,0 +1 @@
1
+ @import"https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600;700&family=Geist:wght@300;400;500;600;700;800&display=swap";:root,[data-theme=amber]{--bg: #0a0a0a;--bg-1: #111110;--bg-2: #16161410;--panel: #131311;--panel-2: #1a1a17;--panel-3: #201f1b;--rule: #2a2823;--rule-strong: #3a372f;--rule-soft: #1f1d19;--ink: #f3ead4;--ink-2: #d8cca8;--ink-3: #a89b78;--ink-4: #6e6650;--ink-5: #46402f;--ink-mute: #2e2a1f;--amber: #d97706;--amber-hi: #f59e0b;--amber-glow: #fbbf2433;--amber-soft: #d9770622;--signal: var(--amber);--signal-hi: var(--amber-hi);--signal-glow: var(--amber-glow);--signal-soft: var(--amber-soft);--ok: #7fc97a;--ok-soft: #7fc97a18;--warn: #f5a524;--warn-soft: #f5a52420;--hold: #c9613a;--hold-soft: #c9613a22;--fail: #ef4444;--fail-soft: #ef444420;--info: #6aa6c9;--info-soft: #6aa6c920;--vis-private: #c9613a;--vis-org: #d97706;--vis-room: #6aa6c9;--vis-public: #7fc97a;--tape-1: #d97706;--tape-2: #c9613a;--tape-3: #6aa6c9;--tape-4: #7fc97a;--tape-5: #b48ead;--tape-6: #e8d4a8;--shadow: 0 1px 0 #00000080, 0 8px 24px #00000040;--inset: inset 0 1px 0 #ffffff08, inset 0 -1px 0 #00000060;--glow: 0 0 0 1px var(--signal), 0 0 24px var(--signal-glow)}[data-theme=paper]{--bg: #ece7dc;--bg-1: #e3ddcf;--bg-2: #d8d1c0;--panel: #f6f1e6;--panel-2: #ede7d8;--panel-3: #e1d9c5;--rule: #c8bfa6;--rule-strong: #a89e83;--rule-soft: #d9d1bb;--ink: #1a1814;--ink-2: #2e2a22;--ink-3: #5a513e;--ink-4: #847a62;--ink-5: #a89e83;--ink-mute: #c8bfa6;--signal: #ff5500;--signal-hi: #ff7a33;--signal-glow: #ff550020;--signal-soft: #ff550018;--ok: #2f7a3a;--ok-soft: #2f7a3a14;--warn: #b97300;--warn-soft: #b9730018;--hold: #a44318;--hold-soft: #a4431814;--fail: #b91c1c;--fail-soft: #b91c1c14;--info: #2c6e8c;--info-soft: #2c6e8c14;--vis-private: #a44318;--vis-org: #ff5500;--vis-room: #2c6e8c;--vis-public: #2f7a3a;--tape-1: #ff5500;--tape-2: #a44318;--tape-3: #2c6e8c;--tape-4: #2f7a3a;--tape-5: #6b3f7e;--tape-6: #d4a84a;--shadow: 0 1px 0 #ffffff80, 0 6px 16px #00000018;--inset: inset 0 1px 0 #ffffff80, inset 0 -1px 0 #00000010;--glow: 0 0 0 1px var(--signal), 0 0 0 4px var(--signal-soft)}[data-theme=phosphor]{--bg: #060a09;--bg-1: #09110e;--bg-2: #0c1714;--panel: #0d1614;--panel-2: #11201c;--panel-3: #142823;--rule: #1f3a32;--rule-strong: #2a5347;--rule-soft: #142822;--ink: #c0f5d8;--ink-2: #8de0b3;--ink-3: #5cb78a;--ink-4: #3a8862;--ink-5: #265a42;--ink-mute: #163524;--signal: #5cf2a8;--signal-hi: #8aff c2;--signal-hi: #88ffc2;--signal-glow: #5cf2a833;--signal-soft: #5cf2a818;--ok: #5cf2a8;--ok-soft: #5cf2a818;--warn: #f5d524;--warn-soft: #f5d52418;--hold: #ff8c5c;--hold-soft: #ff8c5c18;--fail: #ff5c7a;--fail-soft: #ff5c7a18;--info: #5cd5f2;--info-soft: #5cd5f218;--vis-private: #ff8c5c;--vis-org: #f5d524;--vis-room: #5cd5f2;--vis-public: #5cf2a8;--tape-1: #5cf2a8;--tape-2: #5cd5f2;--tape-3: #f5d524;--tape-4: #ff8c5c;--tape-5: #c08af2;--tape-6: #88ffc2;--shadow: 0 1px 0 #00000080, 0 8px 24px #00000060;--inset: inset 0 1px 0 #ffffff08, inset 0 -1px 0 #00000080;--glow: 0 0 0 1px var(--signal), 0 0 24px var(--signal-glow)}:root{--font-mono: "JetBrains Mono", ui-monospace, "Berkeley Mono", monospace;--font-sans: "Geist", system-ui, sans-serif;--t-xs: 10px;--t-sm: 11px;--t-md: 12px;--t-lg: 13px;--t-xl: 15px;--t-2xl: 18px;--t-3xl: 22px;--t-4xl: 32px;--t-5xl: 48px;--s-1: 4px;--s-2: 8px;--s-3: 12px;--s-4: 16px;--s-5: 20px;--s-6: 24px;--s-7: 32px;--s-8: 48px;--r-1: 2px;--r-2: 4px;--r-3: 6px;--r-4: 10px}*{box-sizing:border-box}html,body,#root{height:100%;margin:0;padding:0}body{background:var(--bg);color:var(--ink);font-family:var(--font-sans);font-size:var(--t-md);font-feature-settings:"ss01","cv11","tnum";-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}::-moz-selection{background:var(--signal);color:var(--bg)}::selection{background:var(--signal);color:var(--bg)}[data-theme=phosphor] body:after,[data-theme=amber][data-tactility=high] body:after{content:"";position:fixed;top:0;right:0;bottom:0;left:0;pointer-events:none;background:repeating-linear-gradient(0deg,transparent 0 2px,#00000018 2px 3px);z-index:1000;mix-blend-mode:multiply;opacity:.4}.mono{font-family:var(--font-mono);font-feature-settings:"tnum","zero","ss01"}.up{text-transform:uppercase;letter-spacing:.08em}.dim{color:var(--ink-3)}.dim2{color:var(--ink-4)}.hi{color:var(--ink)}.sig{color:var(--signal)}.kbd{font-family:var(--font-mono);font-size:10px;padding:1px 5px;border:1px solid var(--rule);border-radius:3px;color:var(--ink-3);background:var(--panel)}.rule{height:1px;background:var(--rule)}.tag{display:inline-flex;align-items:center;gap:6px;font-family:var(--font-mono);font-size:var(--t-xs);text-transform:uppercase;letter-spacing:.08em;padding:2px 6px;border:1px solid var(--rule);border-radius:var(--r-1);color:var(--ink-2);background:transparent;white-space:nowrap;height:18px}.tag.solid{background:var(--panel-2)}.tag.signal{color:var(--signal);border-color:var(--signal);background:var(--signal-soft)}.tag.ok{color:var(--ok);border-color:color-mix(in oklab,var(--ok) 50%,var(--rule));background:var(--ok-soft)}.tag.warn{color:var(--warn);border-color:color-mix(in oklab,var(--warn) 50%,var(--rule));background:var(--warn-soft)}.tag.hold{color:var(--hold);border-color:color-mix(in oklab,var(--hold) 50%,var(--rule));background:var(--hold-soft)}.tag.fail{color:var(--fail);border-color:color-mix(in oklab,var(--fail) 50%,var(--rule));background:var(--fail-soft)}.tag.info{color:var(--info);border-color:color-mix(in oklab,var(--info) 50%,var(--rule));background:var(--info-soft)}.tag.dot:before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor;box-shadow:0 0 6px currentColor}.tag.lg{height:22px;font-size:var(--t-sm);padding:3px 8px}.led{width:6px;height:6px;border-radius:50%;background:var(--ink-mute);display:inline-block}.led.on{background:var(--ok);box-shadow:0 0 6px var(--ok)}.led.warn{background:var(--warn);box-shadow:0 0 6px var(--warn)}.led.hold{background:var(--hold);box-shadow:0 0 6px var(--hold)}.led.fail{background:var(--fail);box-shadow:0 0 6px var(--fail)}.led.signal{background:var(--signal);box-shadow:0 0 8px var(--signal)}.led.pulse{animation:pulse var(--pulse-dur, 1.6s) infinite}@keyframes pulse{0%,to{opacity:1}50%{opacity:.35}}@keyframes spin{to{transform:rotate(360deg)}}@keyframes blink{50%{opacity:.25}}[data-pressure-band=low] .reel-spin{animation-duration:9s;opacity:.75}[data-pressure-band=low] .led.pulse{animation:none;opacity:.85}[data-pressure-band=high] .led.signal{box-shadow:0 0 calc(8px + 8px * var(--pressure, .5)) var(--signal)}[data-stance=attendee] .led.pulse,[data-stance=attendee] .reel-spin{animation:none}[data-stance=reviewer] .device{box-shadow:none}.btn{font-family:var(--font-mono);font-size:var(--t-sm);text-transform:uppercase;letter-spacing:.1em;padding:7px 12px;border:1px solid var(--rule-strong);background:var(--panel-2);color:var(--ink);border-radius:var(--r-2);cursor:pointer;display:inline-flex;align-items:center;gap:8px;height:30px;white-space:nowrap;transition:background 80ms,border-color 80ms,color 80ms}.btn:hover{border-color:var(--ink-3);background:var(--panel-3)}.btn:active{transform:translateY(1px)}.btn.ghost{background:transparent;border-color:var(--rule);color:var(--ink-2)}.btn.ghost:hover{background:var(--panel-2);color:var(--ink)}.btn.signal{background:var(--signal);color:var(--bg);border-color:var(--signal);font-weight:600}.btn.signal:hover{background:var(--signal-hi);border-color:var(--signal-hi)}.btn.danger{color:var(--fail);border-color:color-mix(in oklab,var(--fail) 40%,var(--rule))}.btn.danger:hover{background:var(--fail-soft)}.btn.sm{height:24px;padding:4px 8px;font-size:var(--t-xs)}.btn.icon{padding:0;width:30px;justify-content:center}.btn.icon.sm{width:24px}.btn[disabled]{opacity:.35;pointer-events:none}.panel{background:var(--panel);border:1px solid var(--rule);border-radius:var(--r-2)}.panel.flat{background:transparent}.panel.deep{background:var(--bg-1)}.panel-h{display:flex;align-items:center;gap:10px;padding:10px 12px;border-bottom:1px solid var(--rule);font-family:var(--font-mono);font-size:var(--t-xs);text-transform:uppercase;letter-spacing:.12em;color:var(--ink-3)}.panel-h .title{color:var(--ink);letter-spacing:.12em}.panel-h .spacer{flex:1}.panel-b{padding:12px}*::-webkit-scrollbar{width:8px;height:8px}*::-webkit-scrollbar-track{background:transparent}*::-webkit-scrollbar-thumb{background:var(--rule);border-radius:4px}*::-webkit-scrollbar-thumb:hover{background:var(--rule-strong)}.row{display:flex;gap:8px;align-items:center}.col{display:flex;flex-direction:column;gap:8px}.between{justify-content:space-between}.flex1{flex:1}.gap2{gap:8px}.gap3{gap:12px}.gap4{gap:16px}@keyframes reel-spin{to{transform:rotate(-360deg)}}.reel-spin{animation:reel-spin var(--reel-dur, 4s) linear infinite;transform-origin:center}.tape-track{height:4px;background:var(--ink-mute);border-radius:2px;position:relative;overflow:hidden}.tape-fill{position:absolute;top:0;right:0;bottom:0;left:0;background:linear-gradient(90deg,var(--signal),var(--signal-hi));width:var(--p, 0%);box-shadow:0 0 8px var(--signal-glow)}.device{background:radial-gradient(60% 100% at 50% 0%,color-mix(in oklab,var(--panel-2) 70%,transparent),transparent 60%),repeating-linear-gradient(0deg,transparent 0 3px,color-mix(in oklab,#ffffff 2%,transparent) 3px 4px),var(--panel);border:1px solid var(--rule);box-shadow:var(--shadow),var(--inset)}[data-theme=paper] .device{background:radial-gradient(60% 100% at 50% 0%,color-mix(in oklab,var(--panel-2) 70%,transparent),transparent 60%),repeating-linear-gradient(0deg,transparent 0 3px,color-mix(in oklab,#000000 2%,transparent) 3px 4px),var(--panel)}.frame{border:1px solid var(--rule);background:var(--panel)}.tnum{font-variant-numeric:tabular-nums}.trunc{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.sec-h{display:flex;align-items:baseline;gap:12px;padding:12px 0 8px;font-family:var(--font-mono);font-size:var(--t-xs);text-transform:uppercase;letter-spacing:.14em;color:var(--ink-3)}.sec-h .num{color:var(--signal)}.sec-h .line{flex:1;height:1px;background:var(--rule)}.calm-root{min-height:100vh;background:var(--bg);color:var(--ink);font-family:var(--font-sans, "Geist", system-ui, sans-serif);display:flex;flex-direction:column}.calm-topstrip{display:flex;align-items:center;justify-content:space-between;padding:14px 28px;border-bottom:1px solid var(--rule-soft);background:linear-gradient(180deg,var(--panel),transparent)}.calm-brand{display:flex;align-items:center;gap:10px}.calm-mark{display:inline-grid;place-items:center;width:22px;height:22px;border:1px solid var(--signal);color:var(--signal);background:var(--panel-3);border-radius:2px;font-size:11px;box-shadow:0 0 6px var(--signal-glow)}.calm-wordmark{font-family:var(--font-mono);font-size:11px;letter-spacing:.22em;color:var(--ink-2);font-weight:600}.calm-mode-tag{font-family:var(--font-mono);font-size:9.5px;letter-spacing:.2em;padding:2px 7px;border:1px solid var(--rule-strong);border-radius:2px;color:var(--ink-3);background:var(--panel-2)}.calm-page{max-width:720px;margin:56px auto 80px;padding:0 32px;width:100%;box-sizing:border-box}.calm-eyebrow{font-family:var(--font-mono);font-size:10.5px;letter-spacing:.18em;color:var(--ink-3);text-transform:uppercase;margin-bottom:18px}.calm-h1{font-size:44px;line-height:1.06;letter-spacing:-.02em;font-weight:600;margin:0 0 12px;color:var(--ink);text-wrap:pretty}.calm-sub{font-family:var(--font-mono);font-size:13px;color:var(--ink-3);margin-bottom:26px;letter-spacing:.02em}.calm-lede{font-size:18px;line-height:1.55;color:var(--ink-2);margin:0 0 28px;font-weight:400;text-wrap:pretty}.calm-lede strong{color:var(--ink);font-weight:600}.calm-rule{border:0;border-top:1px solid var(--rule-soft);margin:8px 0 28px}.calm-section{margin:0 0 32px}.calm-h2{font-size:14px;font-family:var(--font-mono);text-transform:uppercase;letter-spacing:.14em;color:var(--signal);font-weight:600;margin:0 0 12px}.calm-h3{font-size:13px;font-family:var(--font-mono);text-transform:uppercase;letter-spacing:.12em;color:var(--ink-2);font-weight:600;margin:0 0 10px}.calm-p{font-size:16.5px;line-height:1.6;color:var(--ink-2);margin:0 0 12px;text-wrap:pretty}.calm-p strong{color:var(--ink);font-weight:600}.calm-p em{color:var(--ink-2);font-style:italic}.calm-meta{font-family:var(--font-mono);font-size:11px;color:var(--ink-4);letter-spacing:.04em;margin:4px 0 0}.calm-actions{display:flex;gap:12px;margin:36px 0 28px;align-items:center;flex-wrap:wrap}.calm-btn-primary{background:var(--signal);color:var(--bg);border:1px solid var(--signal);padding:14px 22px;font-family:var(--font-sans, "Geist", system-ui);font-size:15px;font-weight:600;letter-spacing:.01em;border-radius:2px;cursor:pointer;box-shadow:0 0 12px var(--signal-glow);transition:filter .15s}.calm-btn-primary:hover{filter:brightness(1.1)}.calm-btn-primary:disabled{opacity:.4;cursor:not-allowed;box-shadow:none}.calm-btn-secondary{background:transparent;color:var(--ink-2);border:1px solid var(--rule-strong);padding:14px 22px;font-family:var(--font-sans, "Geist", system-ui);font-size:15px;font-weight:500;letter-spacing:.01em;border-radius:2px;cursor:pointer;transition:border-color .15s,color .15s}.calm-btn-secondary:hover{border-color:var(--ink-3);color:var(--ink)}.calm-btn-ghost{background:transparent;color:var(--ink-3);border:0;padding:8px 0;font-family:var(--font-mono);font-size:11px;letter-spacing:.12em;cursor:pointer;text-transform:uppercase;transition:color .15s}.calm-btn-ghost:hover{color:var(--ink)}.calm-btn-ghost--top{letter-spacing:.18em}.calm-sendback{margin:28px 0;padding:20px;border:1px solid var(--rule);border-radius:3px;background:var(--panel)}.calm-textarea{width:100%;background:var(--bg-1);border:1px solid var(--rule-strong);color:var(--ink);padding:12px 14px;font-family:var(--font-sans, "Geist", system-ui);font-size:14.5px;line-height:1.5;border-radius:2px;margin-bottom:14px;box-sizing:border-box;resize:vertical;outline:none}.calm-textarea:focus{border-color:var(--signal);box-shadow:0 0 0 1px var(--signal-soft)}.calm-footer{margin-top:56px;padding-top:22px;border-top:1px solid var(--rule-soft);display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:12px}.calm-foot-meta{font-family:var(--font-mono);font-size:10.5px;color:var(--ink-4);letter-spacing:.06em}.calm-done{text-align:left;padding:40px 0}[data-theme=paper] .calm-root{background:var(--bg)}[data-theme=paper] .calm-btn-primary{box-shadow:none}@media (max-width: 640px){.calm-page{margin:32px auto 60px;padding:0 20px}.calm-h1{font-size:32px}.calm-lede{font-size:16px}.calm-p{font-size:15.5px}.calm-actions{flex-direction:column;align-items:stretch}.calm-btn-primary,.calm-btn-secondary{width:100%;text-align:center}}