diff --git a/MASTER_PLAN.md b/MASTER_PLAN.md index 0a5ba89..d467305 100644 --- a/MASTER_PLAN.md +++ b/MASTER_PLAN.md @@ -152,6 +152,35 @@ Do not split them into unrelated cleanup/design buckets; execute them in order s the visual system, shell, navigation, terminal surface, map, command layer, run state, and visual QA converge on one product direction. +### ~~TC-066~~: ✅ Signature terminal-to-vessel startup animation + +**Priority:** P1 +**Status:** ✅ **DONE** (2026-07-29) + +The static first-paint lockup now performs one branded, truthful sequence before +settling. The colored icon square is gone: prompt and hull establish the vessel +together in open space, stern, stack, and terminal details resolve before the +wordmark takes focus, then all nine letters reveal in order through crisp masks +without changing font weight or fading through a blurry state. The completed +ship uses a restrained 1.25px harbor idle while restoration continues; the +animation never holds the ready handoff, status text remains reserved for +genuinely slow restoration, and `prefers-reduced-motion` renders the complete +lockup with no running animation. + +Fresh evidence: + +- `npx playwright test tests/app-shell.spec.ts tests/startup-splash.spec.ts` — + 4 passed, including the first-paint contract, hydration/terminal-spawn guard, + no-background-rectangle and fixed-weight nine-letter contracts, reduced-motion + contract, and controlled 160/600/840/1200ms narrative phases. +- `/tmp/termfleet-startup-letters-frames-refined.png` — eleven-frame contact + sheet from 0–1200ms, SHA-256 + `e2f9aef9be6a55e442f3aa10aa06a0cbd3e213f7bfaf11613b752ed23d3039dd`; + strict visual review passed the open-space vessel construction, early icon + completion, crisp letter sequence, stable spacing and weight, and centering. +- The identity study page exposes all eleven exact frames for direct review. +- `npm run build` passed. The loader intentionally never delays readiness. + ### ~~TC-065~~: ✅ Premium first-paint loading screen and ship-in-terminal identity **Priority:** P1 diff --git a/design/logo-concepts/index.html b/design/logo-concepts/index.html new file mode 100644 index 0000000..df3d21f --- /dev/null +++ b/design/logo-concepts/index.html @@ -0,0 +1,239 @@ + + + + + + TermFleet logo concepts + + + +
+
+

TermFleet identity study

+

All concept rounds in one place, newest first. Generated sheets are exploratory references, not production artwork.

+
+
+
+

Startup animation — frame-by-frame review

+

Eleven exact browser renders from 0–1200ms. Inspect the open-space vessel construction, fixed-weight letter reveal, alignment, and antialiasing in one complete grid.

+
+
+
TermFleet startup animation at 0 milliseconds
0ms
+
TermFleet startup animation at 120 milliseconds
120ms
+
TermFleet startup animation at 240 milliseconds
240ms
+
TermFleet startup animation at 360 milliseconds
360ms
+
TermFleet startup animation at 480 milliseconds
480ms
+
TermFleet startup animation at 600 milliseconds
600ms
+
TermFleet startup animation at 720 milliseconds
720ms
+
TermFleet startup animation at 840 milliseconds
840ms
+
TermFleet startup animation at 960 milliseconds
960ms
+
TermFleet startup animation at 1080 milliseconds
1080ms
+
TermFleet startup animation at 1200 milliseconds
1200ms
+
+
+
+
+

Review round — naval vessel vector proof

+

The selected vessel rebuilt as optically tuned SVG geometry for exact 16, 24, 32, 48, 64, and 96px sizes.

+
+ The selected naval vessel logo rebuilt as six optically tuned vector sizes +
+
+
+

Review round — crisp dock glyphs

+

Six deterministic SVG corrections shown enlarged and at their true 48px dock size. Nothing in this round is approved.

+
+ Six unapproved crisp TermFleet dock glyph variants shown large and at 48 pixels +
+
+
+

Review round — option 6 refinements

+

Eight unapproved refinements of option 6, varying only corner softness, module rise, spacing, and cadence.

+
+ Eight unapproved refinements of TermFleet logo option 6 +
+
+
+

Review round — proportion and corner radius

+

Twelve unapproved refinements: sharp across row one, subtly softened across row two, and visibly softer across row three.

+
+ Twelve unapproved command-fleet logo refinements exploring chevron proportions, module spacing, and corner radius +
+
+
+

Review round — fleet module spacing

+

Six unapproved refinements with smaller modules and distinct spacing rhythms. Numbered 1–3 across the top, then 4–6.

+
+ Six unapproved command-fleet logo refinements with smaller, differently spaced modules +
+
+
+

GPT Image — elegant command fleet

+

Eight earlier refinements of the command-to-fleet idea. None are approved.

+
+ Eight elegant command-fleet logo variants +
+
+
+

Earlier vector interpretation — Command Fleet

+

An unapproved redraw of one command chevron directing four terminal blocks.

+
+
+
+
Earlier Command Fleet vector interpretation
+

Command Fleet

+

Sea-glass on blue-black, with no literal boat imagery.

+
+ + + +
+
+
+
+
+
+

GPT Image — color study

+

The four liked marks compared in amber, sea-glass, bone and naval ink, and signal coral.

+
+ Four TermFleet logo concepts shown in four color systems +
+
+
+

GPT Image — sea-glass family

+

Eight new terminal-maritime fusions derived from the four liked marks.

+
+ Eight sea-glass TermFleet logo variants +
+
+
+

GPT Image — focused vessel variants

+

Six refinements of the selected low command-vessel premise. Bottom-middle is candidate 5.

+
+ Six generated command-vessel logo variants +
+
+
+

GPT Image — broad exploration

+

Eight initial identity directions. The top-right mark became the focused premise above.

+
+ Eight generated TermFleet logo concepts +
+
+
+

Vector redraw — command vessel

+

A deterministic SVG redraw of the generated premise, shown at production sizes.

+
+
+
+
Command Vessel logo
+

Command Vessel

+

One connected hull with a terminal prompt cut into the silhouette.

+
+ + + +
+
+
+
+
+
+

Hand-built round two

+

Dockyard stencils designed at favicon scale before the GPT Image exploration.

+
+
+
+
Dock Stencil logo
+

01 — Dock Stencil

+

A heavy cursor plate with a bridge cut. Blunt and mechanical.

+
+ + + +
+
+
+
Harbor Block logo
+

02 — Harbor Block

+

The simplest legible tug silhouette, compressed into a cursor.

+
+ + + +
+
+
+
Command Keel logo
+

03 — Command Keel

+

Prompt and vessel fused into one continuous command mark.

+
+ + + +
+
+
+
+
+ + diff --git a/design/logo-concepts/termfleet-approved-option-3-app-icon.png b/design/logo-concepts/termfleet-approved-option-3-app-icon.png new file mode 100644 index 0000000..4f72a23 Binary files /dev/null and b/design/logo-concepts/termfleet-approved-option-3-app-icon.png differ diff --git a/design/logo-concepts/termfleet-approved-option-3-crop.png b/design/logo-concepts/termfleet-approved-option-3-crop.png new file mode 100644 index 0000000..93fe46d Binary files /dev/null and b/design/logo-concepts/termfleet-approved-option-3-crop.png differ diff --git a/design/logo-concepts/termfleet-command-fleet.svg b/design/logo-concepts/termfleet-command-fleet.svg new file mode 100644 index 0000000..dfb525b --- /dev/null +++ b/design/logo-concepts/termfleet-command-fleet.svg @@ -0,0 +1,14 @@ + + TermFleet Command Fleet + A sea-glass command chevron directing four terminal blocks. + + + + + + + + + + + diff --git a/design/logo-concepts/termfleet-command-keel.svg b/design/logo-concepts/termfleet-command-keel.svg new file mode 100644 index 0000000..c587dae --- /dev/null +++ b/design/logo-concepts/termfleet-command-keel.svg @@ -0,0 +1,7 @@ + + TermFleet Command Keel + A single dockyard stencil combining a command chevron with a forward cursor keel. + + + + diff --git a/design/logo-concepts/termfleet-command-vessel.svg b/design/logo-concepts/termfleet-command-vessel.svg new file mode 100644 index 0000000..940371b --- /dev/null +++ b/design/logo-concepts/termfleet-command-vessel.svg @@ -0,0 +1,7 @@ + + TermFleet Command Vessel + A low industrial command vessel with a terminal prompt cut into its cursor-like hull. + + + diff --git a/design/logo-concepts/termfleet-dock-glyph-study.png b/design/logo-concepts/termfleet-dock-glyph-study.png new file mode 100644 index 0000000..68241ca Binary files /dev/null and b/design/logo-concepts/termfleet-dock-glyph-study.png differ diff --git a/design/logo-concepts/termfleet-dock-glyph-study.svg b/design/logo-concepts/termfleet-dock-glyph-study.svg new file mode 100644 index 0000000..a3a358e --- /dev/null +++ b/design/logo-concepts/termfleet-dock-glyph-study.svg @@ -0,0 +1,95 @@ + + TermFleet dock glyph study + Six crisp dock-scale variations of the approved terminal prompt and fleet module mark. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Dock glyph study + Large view plus true 48px preview · review only + + + + 1 · calm pair + + + + + + 2 · rising pair + + + + + + 3 · clear ascent + + + + + + 4 · shallow cadence + + + + + + 5 · weighted pair + + + + + + 6 · terminal line + + + + diff --git a/design/logo-concepts/termfleet-dock-stencil.svg b/design/logo-concepts/termfleet-dock-stencil.svg new file mode 100644 index 0000000..1cec00f --- /dev/null +++ b/design/logo-concepts/termfleet-dock-stencil.svg @@ -0,0 +1,8 @@ + + TermFleet Dock Stencil + A heavy terminal prompt and low dockyard cursor with a workboat bridge cut. + + + diff --git a/design/logo-concepts/termfleet-drydock.svg b/design/logo-concepts/termfleet-drydock.svg new file mode 100644 index 0000000..98c2e9f --- /dev/null +++ b/design/logo-concepts/termfleet-drydock.svg @@ -0,0 +1,8 @@ + + TermFleet Drydock logo + A sturdy terminal prompt and geometric boat built from matching modular blocks. + + + + + diff --git a/design/logo-concepts/termfleet-gpt-color-study.png b/design/logo-concepts/termfleet-gpt-color-study.png new file mode 100644 index 0000000..47365df Binary files /dev/null and b/design/logo-concepts/termfleet-gpt-color-study.png differ diff --git a/design/logo-concepts/termfleet-gpt-command-vessel-variants.png b/design/logo-concepts/termfleet-gpt-command-vessel-variants.png new file mode 100644 index 0000000..7d1f14b Binary files /dev/null and b/design/logo-concepts/termfleet-gpt-command-vessel-variants.png differ diff --git a/design/logo-concepts/termfleet-gpt-elegant-variants.png b/design/logo-concepts/termfleet-gpt-elegant-variants.png new file mode 100644 index 0000000..bc03edc Binary files /dev/null and b/design/logo-concepts/termfleet-gpt-elegant-variants.png differ diff --git a/design/logo-concepts/termfleet-gpt-image-concepts.png b/design/logo-concepts/termfleet-gpt-image-concepts.png new file mode 100644 index 0000000..cbc5a69 Binary files /dev/null and b/design/logo-concepts/termfleet-gpt-image-concepts.png differ diff --git a/design/logo-concepts/termfleet-gpt-module-spacing-variants.png b/design/logo-concepts/termfleet-gpt-module-spacing-variants.png new file mode 100644 index 0000000..da07177 Binary files /dev/null and b/design/logo-concepts/termfleet-gpt-module-spacing-variants.png differ diff --git a/design/logo-concepts/termfleet-gpt-option-6-refinements.png b/design/logo-concepts/termfleet-gpt-option-6-refinements.png new file mode 100644 index 0000000..f496f84 Binary files /dev/null and b/design/logo-concepts/termfleet-gpt-option-6-refinements.png differ diff --git a/design/logo-concepts/termfleet-gpt-radius-proportion-variants.png b/design/logo-concepts/termfleet-gpt-radius-proportion-variants.png new file mode 100644 index 0000000..1c0ee3f Binary files /dev/null and b/design/logo-concepts/termfleet-gpt-radius-proportion-variants.png differ diff --git a/design/logo-concepts/termfleet-gpt-seaglass-variants.png b/design/logo-concepts/termfleet-gpt-seaglass-variants.png new file mode 100644 index 0000000..bdbf9bf Binary files /dev/null and b/design/logo-concepts/termfleet-gpt-seaglass-variants.png differ diff --git a/design/logo-concepts/termfleet-harbor-block.svg b/design/logo-concepts/termfleet-harbor-block.svg new file mode 100644 index 0000000..a817719 --- /dev/null +++ b/design/logo-concepts/termfleet-harbor-block.svg @@ -0,0 +1,8 @@ + + TermFleet Harbor Block + A terminal prompt and cursor block shaped like a compact harbor tug. + + + + + diff --git a/design/logo-concepts/termfleet-harbor-line.svg b/design/logo-concepts/termfleet-harbor-line.svg new file mode 100644 index 0000000..e83d647 --- /dev/null +++ b/design/logo-concepts/termfleet-harbor-line.svg @@ -0,0 +1,9 @@ + + TermFleet Harbor Line logo + A reduced terminal prompt and boat cursor cut from one continuous industrial line. + + + + + diff --git a/design/logo-concepts/termfleet-signal-cursor.svg b/design/logo-concepts/termfleet-signal-cursor.svg new file mode 100644 index 0000000..5d5cdab --- /dev/null +++ b/design/logo-concepts/termfleet-signal-cursor.svg @@ -0,0 +1,12 @@ + + TermFleet Signal Cursor logo + A terminal prompt followed by a monoline workboat cursor inside a rounded terminal frame. + + + + + + + + + diff --git a/design/logo-concepts/termfleet-startup-animation-frames.png b/design/logo-concepts/termfleet-startup-animation-frames.png new file mode 100644 index 0000000..ecc5dab Binary files /dev/null and b/design/logo-concepts/termfleet-startup-animation-frames.png differ diff --git a/design/logo-concepts/termfleet-startup-frame-0000.png b/design/logo-concepts/termfleet-startup-frame-0000.png new file mode 100644 index 0000000..84ae15d Binary files /dev/null and b/design/logo-concepts/termfleet-startup-frame-0000.png differ diff --git a/design/logo-concepts/termfleet-startup-frame-0120.png b/design/logo-concepts/termfleet-startup-frame-0120.png new file mode 100644 index 0000000..7aea5e0 Binary files /dev/null and b/design/logo-concepts/termfleet-startup-frame-0120.png differ diff --git a/design/logo-concepts/termfleet-startup-frame-0240.png b/design/logo-concepts/termfleet-startup-frame-0240.png new file mode 100644 index 0000000..b1cf0d8 Binary files /dev/null and b/design/logo-concepts/termfleet-startup-frame-0240.png differ diff --git a/design/logo-concepts/termfleet-startup-frame-0360.png b/design/logo-concepts/termfleet-startup-frame-0360.png new file mode 100644 index 0000000..c91756e Binary files /dev/null and b/design/logo-concepts/termfleet-startup-frame-0360.png differ diff --git a/design/logo-concepts/termfleet-startup-frame-0480.png b/design/logo-concepts/termfleet-startup-frame-0480.png new file mode 100644 index 0000000..b4c3959 Binary files /dev/null and b/design/logo-concepts/termfleet-startup-frame-0480.png differ diff --git a/design/logo-concepts/termfleet-startup-frame-0600.png b/design/logo-concepts/termfleet-startup-frame-0600.png new file mode 100644 index 0000000..adbb48d Binary files /dev/null and b/design/logo-concepts/termfleet-startup-frame-0600.png differ diff --git a/design/logo-concepts/termfleet-startup-frame-0720.png b/design/logo-concepts/termfleet-startup-frame-0720.png new file mode 100644 index 0000000..1b287ec Binary files /dev/null and b/design/logo-concepts/termfleet-startup-frame-0720.png differ diff --git a/design/logo-concepts/termfleet-startup-frame-0840.png b/design/logo-concepts/termfleet-startup-frame-0840.png new file mode 100644 index 0000000..d84dec0 Binary files /dev/null and b/design/logo-concepts/termfleet-startup-frame-0840.png differ diff --git a/design/logo-concepts/termfleet-startup-frame-0960.png b/design/logo-concepts/termfleet-startup-frame-0960.png new file mode 100644 index 0000000..bac073e Binary files /dev/null and b/design/logo-concepts/termfleet-startup-frame-0960.png differ diff --git a/design/logo-concepts/termfleet-startup-frame-1080.png b/design/logo-concepts/termfleet-startup-frame-1080.png new file mode 100644 index 0000000..e20aaf1 Binary files /dev/null and b/design/logo-concepts/termfleet-startup-frame-1080.png differ diff --git a/design/logo-concepts/termfleet-startup-frame-1200.png b/design/logo-concepts/termfleet-startup-frame-1200.png new file mode 100644 index 0000000..280b6f5 Binary files /dev/null and b/design/logo-concepts/termfleet-startup-frame-1200.png differ diff --git a/design/logo-concepts/termfleet-vessel-navy-bone-crop.png b/design/logo-concepts/termfleet-vessel-navy-bone-crop.png new file mode 100644 index 0000000..9c8521c Binary files /dev/null and b/design/logo-concepts/termfleet-vessel-navy-bone-crop.png differ diff --git a/design/logo-concepts/termfleet-vessel-navy-bone-square.png b/design/logo-concepts/termfleet-vessel-navy-bone-square.png new file mode 100644 index 0000000..e9187ff Binary files /dev/null and b/design/logo-concepts/termfleet-vessel-navy-bone-square.png differ diff --git a/design/logo-concepts/termfleet-vessel-size-study.html b/design/logo-concepts/termfleet-vessel-size-study.html new file mode 100644 index 0000000..5c29635 --- /dev/null +++ b/design/logo-concepts/termfleet-vessel-size-study.html @@ -0,0 +1,59 @@ + + + + + + TermFleet vessel size study + + + +
+

Naval vessel · optically tuned vectors

+

Review only · dedicated geometry for every target size · judge the smallest previews first

+
+
16 px
+
24 px
+
32 px
+
48 px
+
64 px
+
96 px
+
+
+ + diff --git a/design/logo-concepts/termfleet-vessel-size-study.png b/design/logo-concepts/termfleet-vessel-size-study.png new file mode 100644 index 0000000..c37e1eb Binary files /dev/null and b/design/logo-concepts/termfleet-vessel-size-study.png differ diff --git a/design/logo-concepts/termfleet-vessel-vector-size-study.png b/design/logo-concepts/termfleet-vessel-vector-size-study.png new file mode 100644 index 0000000..e034adb Binary files /dev/null and b/design/logo-concepts/termfleet-vessel-vector-size-study.png differ diff --git a/design/logo-concepts/vessel-vector/termfleet-vessel-16.svg b/design/logo-concepts/vessel-vector/termfleet-vessel-16.svg new file mode 100644 index 0000000..f960784 --- /dev/null +++ b/design/logo-concepts/vessel-vector/termfleet-vessel-16.svg @@ -0,0 +1,10 @@ + + TermFleet vessel icon at 16 pixels + A simplified naval vessel containing a terminal chevron. + + + + + + + diff --git a/design/logo-concepts/vessel-vector/termfleet-vessel-24.svg b/design/logo-concepts/vessel-vector/termfleet-vessel-24.svg new file mode 100644 index 0000000..f659f2c --- /dev/null +++ b/design/logo-concepts/vessel-vector/termfleet-vessel-24.svg @@ -0,0 +1,13 @@ + + TermFleet vessel icon at 24 pixels + A compact naval vessel containing a terminal chevron. + + + + + + + + + + diff --git a/design/logo-concepts/vessel-vector/termfleet-vessel-32.svg b/design/logo-concepts/vessel-vector/termfleet-vessel-32.svg new file mode 100644 index 0000000..bc2e574 --- /dev/null +++ b/design/logo-concepts/vessel-vector/termfleet-vessel-32.svg @@ -0,0 +1,14 @@ + + TermFleet vessel icon at 32 pixels + A naval vessel containing a terminal chevron. + + + + + + + + + + + diff --git a/design/logo-concepts/vessel-vector/termfleet-vessel-48.svg b/design/logo-concepts/vessel-vector/termfleet-vessel-48.svg new file mode 100644 index 0000000..a4caf0d --- /dev/null +++ b/design/logo-concepts/vessel-vector/termfleet-vessel-48.svg @@ -0,0 +1,14 @@ + + TermFleet vessel icon at 48 pixels + A side-profile naval vessel containing a terminal chevron. + + + + + + + + + + + diff --git a/design/logo-concepts/vessel-vector/termfleet-vessel-64.svg b/design/logo-concepts/vessel-vector/termfleet-vessel-64.svg new file mode 100644 index 0000000..b6f57fa --- /dev/null +++ b/design/logo-concepts/vessel-vector/termfleet-vessel-64.svg @@ -0,0 +1,14 @@ + + TermFleet vessel icon at 64 pixels + A side-profile naval vessel containing a terminal chevron. + + + + + + + + + + + diff --git a/design/logo-concepts/vessel-vector/termfleet-vessel-96.svg b/design/logo-concepts/vessel-vector/termfleet-vessel-96.svg new file mode 100644 index 0000000..a4b5eeb --- /dev/null +++ b/design/logo-concepts/vessel-vector/termfleet-vessel-96.svg @@ -0,0 +1,14 @@ + + TermFleet vessel icon at 96 pixels + A side-profile naval vessel containing a terminal chevron. + + + + + + + + + + + diff --git a/design/logo-concepts/vessel-vector/termfleet-vessel-master.svg b/design/logo-concepts/vessel-vector/termfleet-vessel-master.svg new file mode 100644 index 0000000..a00f10d --- /dev/null +++ b/design/logo-concepts/vessel-vector/termfleet-vessel-master.svg @@ -0,0 +1,14 @@ + + TermFleet naval vessel master logo + A navy side-profile vessel with stern steps, a two-part stack, and a bone terminal chevron. + + + + + + + + + + + diff --git a/docs/regression-matrix.md b/docs/regression-matrix.md index 5ff6bf0..2426aa1 100644 --- a/docs/regression-matrix.md +++ b/docs/regression-matrix.md @@ -90,7 +90,7 @@ Playwright suite; the per-row specs are the precise guards. | 6.3 | Duplicate project groups on folder re-open | Canonical group per normalized root (TC-034). | `tests/project-reconciliation.spec.ts` | ✅ | | 6.4 | Dev window shows stale code | WebKitGTK disk cache served stale JS (now disabled in launchers). | _launcher-level; no automated guard_ | ❌ | | 6.5 | App-shell smoke (boot without crash) | App mounts. | `tests/app-shell.spec.ts` | ✅ | -| 6.6 | Startup opens on a blank or distracting partial workspace, or terminals mount before the restored layout is ready | The webview had no first-paint shell, while the existing hydration fallback covered only the workspace surface. A static branded gate now owns first paint, keeps the app inert, and exits only after hydration plus a painted frame. | `tests/startup-splash.spec.ts`, `tests/app-shell.spec.ts` | ⚠️ Browser runtime + source contract; packaged WebKit first paint still needs live release proof | +| 6.6 | Startup opens on a blank or distracting partial workspace, terminals mount before restoration, the vessel sits inside an unwanted colored square, or the wordmark changes weight/clarity while appearing | The webview had no first-paint shell, while the existing hydration fallback covered only the workspace surface. The inline gate now owns first paint and tells one restrained terminal-to-vessel story in open space: prompt and hull establish the vessel together, all ship geometry resolves before the wordmark takes focus, and nine fixed-weight letters reveal through crisp masks before the quiet vessel idle. It never delays readiness and becomes fully static for reduced motion. | `tests/startup-splash.spec.ts` (hydration, terminal-spawn guard, no-background-rectangle contract, nine-letter stagger and fixed-weight contract, reduced motion, controlled 160/600/840/1200ms phases), `tests/app-shell.spec.ts` | ✅ browser runtime + source contract + dense rendered-frame review | ## 7. Release / packaging / OSS diff --git a/index.html b/index.html index e0b48b4..84c6c00 100644 --- a/index.html +++ b/index.html @@ -36,24 +36,73 @@ align-items: center; gap: 27px; width: min(430px, calc(100vw - 48px)); - opacity: 0; - transform: translateY(calc(-3vh + 3px)); - animation: termfleet-lockup-arrive 360ms cubic-bezier(0.22, 1, 0.36, 1) 40ms both; + opacity: 1; + transform: translateY(-3vh); } .termfleet-startup__mark { + display: block; width: 92px; height: 92px; overflow: visible; } + .termfleet-loader__prompt, + .termfleet-loader__cursor, + .termfleet-loader__hull, + .termfleet-loader__stern, + .termfleet-loader__stack, + .termfleet-loader__terminal { + transform-box: fill-box; + transform-origin: center; + } + + .termfleet-loader__prompt { + stroke-dasharray: 34; + stroke-dashoffset: 34; + opacity: 0; + animation: termfleet-prompt-draw 520ms cubic-bezier(0.22, 1, 0.36, 1) 40ms both; + } + + .termfleet-loader__cursor { + opacity: 0; + animation: termfleet-cursor-depart 460ms cubic-bezier(0.22, 1, 0.36, 1) 80ms both; + } + + .termfleet-loader__vessel { + animation: termfleet-vessel-idle 2400ms cubic-bezier(0.37, 0, 0.63, 1) 760ms infinite alternate; + } + + .termfleet-loader__hull { + opacity: 0; + animation: termfleet-vessel-assemble 560ms cubic-bezier(0.16, 1, 0.3, 1) 20ms both; + } + + .termfleet-loader__stern { + opacity: 0; + animation: termfleet-stern-assemble 300ms cubic-bezier(0.16, 1, 0.3, 1) 220ms both; + } + + .termfleet-loader__stack { + opacity: 0; + animation: termfleet-stack-assemble 260ms cubic-bezier(0.16, 1, 0.3, 1) 280ms both; + } + + .termfleet-loader__terminal { + opacity: 0; + animation: termfleet-terminal-arrive 220ms cubic-bezier(0.22, 1, 0.36, 1) 380ms both; + } + .termfleet-startup__copy { display: grid; gap: 9px; min-width: 0; + opacity: 1; + transform: none; } .termfleet-startup__name { + display: flex; margin: 0; color: #e6e9ec; font-size: 1.875rem; @@ -62,6 +111,14 @@ line-height: 1; } + .termfleet-startup__letter { + display: inline-block; + clip-path: inset(0 100% 0 0); + transform: translateY(5px); + animation: termfleet-letter-arrive 300ms cubic-bezier(0.16, 1, 0.3, 1) + calc(600ms + (var(--letter-index) * 40ms)) both; + } + .termfleet-startup__status { position: absolute; top: calc(100% + 13px); @@ -79,14 +136,113 @@ opacity: 1; } - @keyframes termfleet-lockup-arrive { + @keyframes termfleet-prompt-draw { + 0% { + stroke-dashoffset: 34; + opacity: 0; + } + 18% { + opacity: 1; + } + 58% { + stroke-dashoffset: 0; + opacity: 1; + } + 78% { + stroke-dashoffset: 0; + opacity: 1; + } + 100% { + stroke-dashoffset: 0; + opacity: 0; + } + } + + @keyframes termfleet-cursor-depart { + 0%, + 24% { + opacity: 0; + transform: translateX(-4px); + } + 42%, + 68% { + opacity: 0.72; + transform: translateX(0); + } + 100% { + opacity: 0; + transform: translateX(7px); + } + } + + @keyframes termfleet-vessel-assemble { + 0% { + opacity: 0; + transform: translateX(-6px) scaleX(0.9); + } + 18% { + opacity: 0.45; + } + 76%, + 100% { + opacity: 1; + transform: translateX(0) scaleX(1); + } + } + + @keyframes termfleet-stern-assemble { + 0%, + 28% { + opacity: 0; + transform: translateX(7px); + } + 100% { + opacity: 1; + transform: translateX(0); + } + } + + @keyframes termfleet-stack-assemble { + 0%, + 24% { + opacity: 0; + transform: translateY(7px); + } + 100% { + opacity: 1; + transform: translateY(0); + } + } + + @keyframes termfleet-terminal-arrive { from { opacity: 0; - transform: translateY(calc(-3vh + 3px)); + transform: translateX(-5px); } to { opacity: 1; - transform: translateY(-3vh); + transform: translateX(0); + } + } + + @keyframes termfleet-vessel-idle { + from { + transform: translateY(0); + } + to { + transform: translateY(-1.25px); + } + } + + @keyframes termfleet-letter-arrive { + 0% { + clip-path: inset(0 100% 0 0); + transform: translateY(5px); + } + 68%, + 100% { + clip-path: inset(0 0 0 0); + transform: translateY(0); } } @@ -113,10 +269,27 @@ } .termfleet-startup__lockup { - animation: none; opacity: 1; transform: translateY(-3vh); } + + .termfleet-loader__prompt, + .termfleet-loader__cursor { + display: none; + } + + .termfleet-loader__vessel, + .termfleet-loader__hull, + .termfleet-loader__stern, + .termfleet-loader__stack, + .termfleet-loader__terminal, + .termfleet-startup__copy, + .termfleet-startup__letter { + animation: none; + opacity: 1; + transform: none; + clip-path: none; + } } @@ -129,14 +302,67 @@ aria-label="Starting TermFleet" >
- + + + + + + + + + + + + + + + +
-

TermFleet

+

+ + + + + + + + + +

Starting TermFleet

diff --git a/tests/app-shell.spec.ts b/tests/app-shell.spec.ts index 6a4e78b..8dd8eae 100644 --- a/tests/app-shell.spec.ts +++ b/tests/app-shell.spec.ts @@ -27,7 +27,13 @@ test("TermFleet app shell owns first paint and package metadata", async () => { expect(indexHtml).toContain("Starting TermFleet"); expect(indexHtml).toContain(''); expect(indexHtml).toContain('src="/src/main.tsx"'); - expect(indexHtml.match(/termfleet-vessel-master\.svg/g)).toHaveLength(3); + expect(indexHtml.match(/termfleet-vessel-master\.svg/g)).toHaveLength(2); + expect(indexHtml).toContain('class="termfleet-startup__mark"'); + expect(indexHtml).toContain('class="termfleet-loader__prompt"'); + expect(indexHtml).toContain('class="termfleet-loader__vessel"'); + expect(indexHtml).toContain("termfleet-prompt-draw"); + expect(indexHtml).toContain("termfleet-vessel-assemble"); + expect(indexHtml).toContain("termfleet-vessel-idle"); expect(indexHtml).not.toContain("srcset="); expect(indexHtml).not.toContain("FlowState"); expect(indexHtml).not.toContain("fs-loader"); diff --git a/tests/startup-splash.spec.ts b/tests/startup-splash.spec.ts index 77794c9..d26b75e 100644 --- a/tests/startup-splash.spec.ts +++ b/tests/startup-splash.spec.ts @@ -58,16 +58,31 @@ test("startup screen holds the app until workspace restoration is painted", asyn const motionContract = await startup.evaluate((element) => { const lockup = element.querySelector(".termfleet-startup__lockup"); - const mark = element.querySelector(".termfleet-startup__mark"); + const prompt = element.querySelector(".termfleet-loader__prompt"); + const vessel = element.querySelector(".termfleet-loader__vessel"); + const hull = element.querySelector(".termfleet-loader__hull"); return { lockupIterations: lockup ? getComputedStyle(lockup).animationIterationCount : "", - markAnimation: mark ? getComputedStyle(mark).animationName : "", + lockupAnimation: lockup ? getComputedStyle(lockup).animationName : "", + promptIterations: prompt ? getComputedStyle(prompt).animationIterationCount : "", + promptAnimation: prompt ? getComputedStyle(prompt).animationName : "", + vesselIterations: vessel ? getComputedStyle(vessel).animationIterationCount : "", + vesselAnimation: vessel ? getComputedStyle(vessel).animationName : "", + hullIterations: hull ? getComputedStyle(hull).animationIterationCount : "", + hullAnimation: hull ? getComputedStyle(hull).animationName : "", }; }); expect(motionContract.lockupIterations).toBe("1"); - expect(motionContract.markAnimation).toBe("none"); + expect(motionContract.lockupAnimation).toBe("none"); + expect(motionContract.promptIterations).toBe("1"); + expect(motionContract.promptAnimation).toContain("termfleet-prompt-draw"); + expect(motionContract.vesselIterations).toBe("infinite"); + expect(motionContract.vesselAnimation).toContain("termfleet-vessel-idle"); + expect(motionContract.hullIterations).toBe("1"); + expect(motionContract.hullAnimation).toContain("termfleet-vessel-assemble"); const startupScreenshot = "/tmp/termfleet-startup-final.png"; + await page.waitForTimeout(900); await page.screenshot({ path: startupScreenshot }); await testInfo.attach("startup screen", { path: startupScreenshot, @@ -103,3 +118,81 @@ test("reduced motion keeps the startup mark still", async ({ page }) => { ); expect(animations).toEqual([]); }); + +test("startup animation tells a terminal-to-vessel story", async ({ page }, testInfo) => { + await page.goto("http://127.0.0.1:5177/", { waitUntil: "domcontentloaded" }); + + const startup = page.locator("#termfleet-startup"); + await expect(startup).toHaveAttribute("data-startup-state", "restoring"); + await expect(startup.locator(".termfleet-startup__mark > rect")).toHaveCount(0); + + const letters = startup.locator(".termfleet-startup__letter"); + await expect(letters).toHaveCount(9); + await expect(startup.locator(".termfleet-startup__name")).toHaveAttribute("aria-label", "TermFleet"); + + const letterMotion = await letters.evaluateAll((elements) => + elements.map((element) => { + const style = getComputedStyle(element); + return { + animationName: style.animationName, + delay: style.animationDelay, + weight: style.fontWeight, + }; + }), + ); + expect(letterMotion.every(({ animationName }) => animationName === "termfleet-letter-arrive")).toBe(true); + expect(new Set(letterMotion.map(({ delay }) => delay)).size).toBe(9); + expect(new Set(letterMotion.map(({ weight }) => weight)).size).toBe(1); + + const capturePhase = async (name: string, time: number) => { + await startup.evaluate((element, currentTime) => { + element.getAnimations({ subtree: true }).forEach((animation) => { + animation.pause(); + animation.currentTime = currentTime; + }); + }, time); + const path = `/tmp/termfleet-startup-${name}.png`; + await page.screenshot({ path }); + await testInfo.attach(`startup ${name}`, { path, contentType: "image/png" }); + }; + + await capturePhase("command", 160); + await expect(startup.locator(".termfleet-loader__prompt")).not.toHaveCSS("opacity", "0"); + await expect(startup.locator(".termfleet-loader__hull")).not.toHaveCSS("opacity", "0"); + + await capturePhase("vessel", 600); + await expect(startup.locator(".termfleet-loader__hull")).toHaveCSS("opacity", "1"); + await expect(startup.locator(".termfleet-loader__stern")).toHaveCSS("opacity", "1"); + await expect(startup.locator(".termfleet-loader__stack")).toHaveCSS("opacity", "1"); + await expect(startup.locator(".termfleet-loader__terminal")).toHaveCSS("opacity", "1"); + + await capturePhase("wordmark", 840); + await expect(startup.locator(".termfleet-loader__prompt")).toHaveCSS("opacity", "0"); + expect( + await letters.evaluateAll((elements) => + elements.filter( + (element) => getComputedStyle(element).clipPath !== "inset(0px 100% 0px 0px)", + ).length, + ), + ).toBeGreaterThan(4); + + await capturePhase("complete", 1200); + await expect(startup.locator(".termfleet-loader__terminal")).toHaveCSS("opacity", "1"); + expect( + await letters.evaluateAll((elements) => + elements.every((element) => getComputedStyle(element).clipPath === "inset(0px)"), + ), + ).toBe(true); + + for (const time of [0, 120, 240, 360, 480, 600, 720, 840, 960, 1080, 1200]) { + await startup.evaluate((element, currentTime) => { + element.getAnimations({ subtree: true }).forEach((animation) => { + animation.pause(); + animation.currentTime = currentTime; + }); + }, time); + await startup.locator(".termfleet-startup__lockup").screenshot({ + path: `/tmp/termfleet-startup-frame-${String(time).padStart(4, "0")}.png`, + }); + } +});