From 7335196fada0d238ea27201717fe062693e29c6c Mon Sep 17 00:00:00 2001 From: Nayan Date: Sat, 8 Aug 2026 15:24:00 +0530 Subject: [PATCH] feat(web): the hero stage and its airship mock The hero is a mock of the editor rather than a screenshot: the same inspector sections, dock, composer and picker chrome, rebuilt in React so it scales, scrubs with scroll and stays legible at any width. A screenshot would have been less code and would have been wrong within a week. --- .../hero/airship-mock/agent-timeline.tsx | 33 + .../hero/airship-mock/bottom-bar.tsx | 70 + .../hero/airship-mock/composer-field.tsx | 40 + .../hero/airship-mock/design-dock.tsx | 214 ++ .../hero/airship-mock/diff-card.tsx | 29 + .../hero/airship-mock/editor-glyph.tsx | 40 + .../hero/airship-mock/inline-overlay.tsx | 36 + .../hero/airship-mock/inspector-controls.tsx | 161 ++ .../hero/airship-mock/inspector-section.tsx | 38 + .../hero/airship-mock/mock-page.tsx | 56 + .../hero/airship-mock/picker-overlay.tsx | 41 + .../src/components/hero/browser-window.tsx | 113 + .../src/components/hero/desktop-backdrop.tsx | 20 + apps/web/src/components/hero/hero-stage.tsx | 131 ++ apps/web/src/components/hero/mock-cursor.tsx | 29 + apps/web/src/lib/hero-measure.ts | 46 + apps/web/src/lib/hero-scale.ts | 61 + apps/web/src/lib/measure-hero.ts | 146 ++ apps/web/src/lib/use-frame-scrub.ts | 69 + .../src/lib/use-isomorphic-layout-effect.ts | 12 + apps/web/src/styles/hero-desktop.css | 316 +++ apps/web/src/styles/hero-overlay.css | 1147 +++++++++++ apps/web/src/styles/hero-timeline.css | 1813 +++++++++++++++++ 23 files changed, 4661 insertions(+) create mode 100644 apps/web/src/components/hero/airship-mock/agent-timeline.tsx create mode 100644 apps/web/src/components/hero/airship-mock/bottom-bar.tsx create mode 100644 apps/web/src/components/hero/airship-mock/composer-field.tsx create mode 100644 apps/web/src/components/hero/airship-mock/design-dock.tsx create mode 100644 apps/web/src/components/hero/airship-mock/diff-card.tsx create mode 100644 apps/web/src/components/hero/airship-mock/editor-glyph.tsx create mode 100644 apps/web/src/components/hero/airship-mock/inline-overlay.tsx create mode 100644 apps/web/src/components/hero/airship-mock/inspector-controls.tsx create mode 100644 apps/web/src/components/hero/airship-mock/inspector-section.tsx create mode 100644 apps/web/src/components/hero/airship-mock/mock-page.tsx create mode 100644 apps/web/src/components/hero/airship-mock/picker-overlay.tsx create mode 100644 apps/web/src/components/hero/browser-window.tsx create mode 100644 apps/web/src/components/hero/desktop-backdrop.tsx create mode 100644 apps/web/src/components/hero/hero-stage.tsx create mode 100644 apps/web/src/components/hero/mock-cursor.tsx create mode 100644 apps/web/src/lib/hero-measure.ts create mode 100644 apps/web/src/lib/hero-scale.ts create mode 100644 apps/web/src/lib/measure-hero.ts create mode 100644 apps/web/src/lib/use-frame-scrub.ts create mode 100644 apps/web/src/lib/use-isomorphic-layout-effect.ts create mode 100644 apps/web/src/styles/hero-desktop.css create mode 100644 apps/web/src/styles/hero-overlay.css create mode 100644 apps/web/src/styles/hero-timeline.css diff --git a/apps/web/src/components/hero/airship-mock/agent-timeline.tsx b/apps/web/src/components/hero/airship-mock/agent-timeline.tsx new file mode 100644 index 0000000..637fe82 --- /dev/null +++ b/apps/web/src/components/hero/airship-mock/agent-timeline.tsx @@ -0,0 +1,33 @@ +import { MOCK_TOOL_CALLS } from "#/content/mock-page"; + +/** + * The agent's tool calls, streaming. + * + * Rendered in Claude Code's own transcript grammar — a filled marker and a bold + * tool name for the call, an indented `⎿` elbow for its result — because that is + * exactly what airship's real overlay renders. Someone who has used the tool + * should recognise this before they read a word of it. + * + * Every row is present in the DOM from the start and revealed by the timeline; + * nothing is mounted or unmounted, so the transcript's height never jumps + * mid-stream. + */ +export function AgentTimeline() { + return ( +
+ {MOCK_TOOL_CALLS.map((call, index) => ( +
+
+ ● + {call.tool} + {call.args} +
+
+ ⌋ + {call.result} +
+
+ ))} +
+ ); +} diff --git a/apps/web/src/components/hero/airship-mock/bottom-bar.tsx b/apps/web/src/components/hero/airship-mock/bottom-bar.tsx new file mode 100644 index 0000000..3c2ac98 --- /dev/null +++ b/apps/web/src/components/hero/airship-mock/bottom-bar.tsx @@ -0,0 +1,70 @@ +import { EditorGlyph } from "#/components/hero/airship-mock/editor-glyph"; + +/** + * The floating bottom bar. + * + * Compact and understated on purpose: a small radius, a hairline border and a + * ring rather than a pill with a drop shadow. It is the one piece of chrome that + * is always on screen, and the version of it that announces itself gets tiring + * within a minute. + * + * Left to right: undo/redo, the tool group, inspect, the Edit|View toggle, and + * the pending-tweak counter — which is the only thing on the bar that moves. + */ +export function BottomBar() { + return ( +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Edit + + + + View + + + + {/* + Collapsed to zero width until the first tweak lands, so the bar has no + empty slot waiting to be filled — it simply grows. + */} + + + 1 + 2 + + +
+ ); +} diff --git a/apps/web/src/components/hero/airship-mock/composer-field.tsx b/apps/web/src/components/hero/airship-mock/composer-field.tsx new file mode 100644 index 0000000..4c92542 --- /dev/null +++ b/apps/web/src/components/hero/airship-mock/composer-field.tsx @@ -0,0 +1,40 @@ +import { EditorGlyph } from "#/components/hero/airship-mock/editor-glyph"; +import { MOCK_PROMPT, MOCK_SELECTION } from "#/content/mock-page"; + +/** + * Where you say what you want. + * + * One bordered box holding the selection chip, the prompt and the send button — + * which is the shape of airship's real composer, and the shape of the claim: the + * chip says *what*, the sentence says *what about it*, and that is the entire + * interface. + * + * The prompt is real text clipped from the right by the timeline rather than a + * string assembled by a timer. That keeps the typing honest under the pause + * button and under `prefers-reduced-motion`, and means it costs nothing when the + * hero is scrolled out of view. + */ +export function ComposerField() { + return ( +
+
+
+ + {MOCK_SELECTION.tag} + × + +
+ +
+ Describe the change… + {MOCK_PROMPT} + +
+ + + + +
+
+ ); +} diff --git a/apps/web/src/components/hero/airship-mock/design-dock.tsx b/apps/web/src/components/hero/airship-mock/design-dock.tsx new file mode 100644 index 0000000..62ff265 --- /dev/null +++ b/apps/web/src/components/hero/airship-mock/design-dock.tsx @@ -0,0 +1,214 @@ +import { AgentTimeline } from "#/components/hero/airship-mock/agent-timeline"; +import { ComposerField } from "#/components/hero/airship-mock/composer-field"; +import { DiffCard } from "#/components/hero/airship-mock/diff-card"; +import { EditorGlyph } from "#/components/hero/airship-mock/editor-glyph"; +import { + NumField, + PaintRow, + Segmented, + Select, + SwapValue, +} from "#/components/hero/airship-mock/inspector-controls"; +import { InspectorSection } from "#/components/hero/airship-mock/inspector-section"; +import { MOCK_PROMPT, MOCK_SELECTION, MOCK_STATUS } from "#/content/mock-page"; +import { TWEAKS } from "#/content/timeline"; + +/* + * The nine align cells, in the real panel's order: horizontal, vertical, then + * distribute-and-tidy. + */ +const ALIGN_GROUPS = [ + ["alignLeft", "alignCenterH", "alignRight"], + ["alignTop", "alignCenterV", "alignBottom"], + ["distributeH", "distributeV", "tidy"], +] as const; + +const TABS = [ + { glyph: "logo", label: "Agent" }, + { glyph: "design", label: "Edit" }, + { glyph: "code", label: "CSS" }, + { glyph: "layers", label: "DOM" }, +] as const; + +/** + * One dock, four tabs. + * + * ── A deliberate departure from the real editor ────────────────────────────── + * airship actually runs TWO docks: chat on the left at 340px, inspector on the + * right at 360px. This mock folds the chat in as an *Agent* tab instead. + * + * That is a simplification for the hero specifically, and it is worth being + * honest about. Two 200px panels flanking a 285px strip of page reads as clutter + * at this render scale, and it splits attention exactly where the page is trying + * to make one point. One panel with the agent in front says the same thing more + * quietly — and the Edit / CSS / DOM tabs beside it still show that a full + * inspector is part of the product. + * + * Everything INSIDE the tabs is transcribed 1:1 as usual. It is the arrangement + * that is editorial, not the components. + */ +export function DesignDock() { + return ( +
+
+ + + Airship + + + + + + + + + +
+ + {/* + Empty and filled states stack in one relative box and cross-fade, so + neither ever reflows the dock. + */} +
+
+ + Ask airship to change anything + + Pick an element first to scope it. + +
+ +
+
+ {TABS.map((tab) => ( + + + {tab.label} + + ))} +
+ + {/* Both tab bodies stacked; the timeline decides which is in front. */} +
+
+
+
{MOCK_PROMPT}
+ +
+ + +
+ + {/* Both labels in one grid cell, so the row does not + re-wrap when "Applying…" becomes "Done". */} + + + {MOCK_STATUS.working} + + {MOCK_STATUS.done} + +
+ + +
+
+ + +
+ +
+
+ + + {MOCK_SELECTION.sourceFile}:{MOCK_SELECTION.sourceLine} + + + +
+ {ALIGN_GROUPS.map((group) => ( + + {group.map((glyph) => ( + + + + ))} + + ))} +
+ + +
+ + +
+
+ + + + + + +
+ + + } + /> +
+
+ Blend +