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.
+
+
+
0ms
+
120ms
+
240ms
+
360ms
+
480ms
+
600ms
+
720ms
+
840ms
+
960ms
+
1080ms
+
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.
+
+
+
+
+
+
Review round — crisp dock glyphs
+
Six deterministic SVG corrections shown enlarged and at their true 48px dock size. Nothing in this round is approved.
+
+
+
+
+
+
Review round — option 6 refinements
+
Eight unapproved refinements of option 6, varying only corner softness, module rise, spacing, and cadence.
+
+
+
+
+
+
Review round — proportion and corner radius
+
Twelve unapproved refinements: sharp across row one, subtly softened across row two, and visibly softer across row three.
+
+
+
+
+
+
Review round — fleet module spacing
+
Six unapproved refinements with smaller modules and distinct spacing rhythms. Numbered 1–3 across the top, then 4–6.
+
+
+
+
+
+
GPT Image — elegant command fleet
+
Eight earlier refinements of the command-to-fleet idea. None are approved.
+
+
+
+
+
+
Earlier vector interpretation — Command Fleet
+
An unapproved redraw of one command chevron directing four terminal blocks.
+
+
+
+
+ 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.
+
+
+
+
+
+
GPT Image — sea-glass family
+
Eight new terminal-maritime fusions derived from the four liked marks.
+
+
+
+
+
+
GPT Image — focused vessel variants
+
Six refinements of the selected low command-vessel premise. Bottom-middle is candidate 5.
+
+
+
+
+
+
GPT Image — broad exploration
+
Eight initial identity directions. The top-right mark became the focused premise above.
+
+
+
+
+
+
Vector redraw — command vessel
+
A deterministic SVG redraw of the generated premise, shown at production sizes.
+
+
+
+
+ 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.
+
+
+
+
+ 01 — Dock Stencil
+ A heavy cursor plate with a bridge cut. Blunt and mechanical.
+
+
+
+
+ 02 — Harbor Block
+ The simplest legible tug silhouette, compressed into a cursor.
+
+
+
+
+ 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
+
+ T
+ e
+ r
+ m
+ F
+ l
+ e
+ e
+ t
+
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`,
+ });
+ }
+});