From 5a81b787c9df00f9a455e14765695d9754c2f6ef Mon Sep 17 00:00:00 2001 From: ChristopherPHolder Date: Sat, 31 Oct 2026 11:54:05 +0100 Subject: [PATCH 1/3] docs: make the landing page logo crisp and theme-aware The 170px PNG mark blurred at hero size and on high-density screens, its black outline vanished in dark mode, and on mobile the hero repeated the logo right under the header. - Rebuild the mark as vector art from the PNG: the outline is the line work's centre line refitted as even-width strokes, the portholes and antenna tip are true rings, and the dome fills are smoothed palette regions. 26 KB, 11 KB gzipped. - Draw it with so the outline (stroke) and body (color) follow new --logo-ink / --logo-paper tokens, light and dark. - Show the hero mark only on wide screens, aligned with the top of the hero copy. --- .../docs/public/images/user-flow-mark.png | Bin 15126 -> 0 bytes .../docs/public/images/user-flow-mark.svg | 41 ++++++++++++++++++ packages/docs/src/app/app.html | 14 +++--- packages/docs/src/app/home/home.html | 10 ++--- packages/docs/src/app/home/home.scss | 27 +++++------- packages/docs/src/styles.scss | 11 +++++ 6 files changed, 72 insertions(+), 31 deletions(-) delete mode 100644 packages/docs/public/images/user-flow-mark.png create mode 100644 packages/docs/public/images/user-flow-mark.svg diff --git a/packages/docs/public/images/user-flow-mark.png b/packages/docs/public/images/user-flow-mark.png deleted file mode 100644 index d540814566dd83d04d67bed0e2d54fa9ab5421df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15126 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 PUB3Y&6(wrKjDr3bVcq-= diff --git a/packages/docs/public/images/user-flow-mark.svg b/packages/docs/public/images/user-flow-mark.svg new file mode 100644 index 00000000..024fd5db --- /dev/null +++ b/packages/docs/public/images/user-flow-mark.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/docs/src/app/app.html b/packages/docs/src/app/app.html index b2b31c46..e0a20171 100644 --- a/packages/docs/src/app/app.html +++ b/packages/docs/src/app/app.html @@ -2,13 +2,13 @@
- + user-flow
diff --git a/packages/docs/src/app/home/home.scss b/packages/docs/src/app/home/home.scss index a3b1a06f..dbeba40e 100644 --- a/packages/docs/src/app/home/home.scss +++ b/packages/docs/src/app/home/home.scss @@ -18,37 +18,30 @@ } } +// The header already shows the mark, so the hero only repeats it where +// there is a second column to fill. .hero-visual { - display: flex; - justify-content: flex-start; - order: -1; + display: none; } @media (min-width: 900px) { .hero-visual { + display: flex; justify-content: center; - order: 0; + align-self: start; } } .hero-mark { - width: 96px; - height: 96px; - padding: var(--space-4); - border-radius: var(--radius-2xl); + box-sizing: content-box; + width: 170px; + height: 170px; + padding: var(--space-8); + border-radius: var(--radius-3xl); background: var(--surface-subtle); border: 1px solid var(--border); } -@media (min-width: 900px) { - .hero-mark { - width: 240px; - height: 240px; - padding: var(--space-8); - border-radius: var(--radius-3xl); - } -} - .hero-eyebrow { margin: 0 0 var(--space-4); color: var(--link); diff --git a/packages/docs/src/styles.scss b/packages/docs/src/styles.scss index 89925645..95f0d361 100644 --- a/packages/docs/src/styles.scss +++ b/packages/docs/src/styles.scss @@ -46,6 +46,8 @@ --callout-warning-bg: var(--color-warning-50); --callout-warning-border: var(--color-warning-300); --callout-warning-title: var(--color-warning-800); + --logo-ink: var(--color-neutral-950); + --logo-paper: var(--color-surface); } @mixin dark-theme { @@ -90,6 +92,8 @@ --callout-warning-bg: var(--color-warning-950); --callout-warning-border: var(--color-warning-800); --callout-warning-title: var(--color-warning-300); + --logo-ink: var(--color-neutral-50); + --logo-paper: var(--color-neutral-800); } @media (prefers-color-scheme: dark) { @@ -571,6 +575,13 @@ pre { } /* Theme toggle: the icon is picked by CSS so server and client markup match */ +/* The user-flow mark, drawn with so its outline (stroke) and body (color) follow the theme. */ +.uf-mark { + display: block; + stroke: var(--logo-ink); + color: var(--logo-paper); +} + .theme-toggle { display: inline-flex; align-items: center; From bc77471239f3cc0e9a4dbacb1369b4b2a894a96a Mon Sep 17 00:00:00 2001 From: ChristopherPHolder Date: Sat, 31 Oct 2026 11:54:06 +0100 Subject: [PATCH 2/3] chore: add .nvmrc for Node 22 The docs build needs Node >=22.19 (see engines). On 22.18 Angular's route extraction crashes with "Cannot read properties of undefined (reading 'exports')", so let nvm pick up a current Node 22. --- .nvmrc | 1 + 1 file changed, 1 insertion(+) create mode 100644 .nvmrc diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..2bd5a0a9 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +22 From 8de7a604893cb4e20799d631bd56ce41c9a2213e Mon Sep 17 00:00:00 2001 From: ChristopherPHolder Date: Tue, 3 Nov 2026 22:58:43 +0100 Subject: [PATCH 3/3] docs: animate the landing page hero The hero repeated the static logo in a card. It now shows the saucer flying over the three kinds of step a flow is made of: navigation, timespan and snapshot. It pauses above each one while a beam lights the card and its ring fills, then swoops back to start again. - New HeroScene component, pure CSS keyframes so server and client markup match and the home route still prerenders. Every timeline shares one duration; each target is shifted with a negative delay so it lights exactly as the saucer arrives. - Lighthouse's step icons (ring, clock, aperture) redrawn as strokes, with a one-word note under each step: page load, interaction, page state. - Colours, spacing, radii and shadows come from the design tokens, dark mode follows the theme variables, and under prefers-reduced-motion the saucer parks above the row with every step lit. - The hero's second column widens to 360px, 420px above 1100px. It stays hidden below 900px, where the header logo is enough. --- packages/docs/src/app/home/hero-scene.html | 50 +++ packages/docs/src/app/home/hero-scene.scss | 397 +++++++++++++++++++++ packages/docs/src/app/home/hero-scene.ts | 27 ++ packages/docs/src/app/home/home.html | 6 +- packages/docs/src/app/home/home.scss | 23 +- packages/docs/src/app/home/home.ts | 3 +- 6 files changed, 487 insertions(+), 19 deletions(-) create mode 100644 packages/docs/src/app/home/hero-scene.html create mode 100644 packages/docs/src/app/home/hero-scene.scss create mode 100644 packages/docs/src/app/home/hero-scene.ts diff --git a/packages/docs/src/app/home/hero-scene.html b/packages/docs/src/app/home/hero-scene.html new file mode 100644 index 00000000..3870303b --- /dev/null +++ b/packages/docs/src/app/home/hero-scene.html @@ -0,0 +1,50 @@ + diff --git a/packages/docs/src/app/home/hero-scene.scss b/packages/docs/src/app/home/hero-scene.scss new file mode 100644 index 00000000..87052bc0 --- /dev/null +++ b/packages/docs/src/app/home/hero-scene.scss @@ -0,0 +1,397 @@ +// One pass of the saucer across the three steps. Every keyframe set below +// shares this duration, so the beam, the tilt and the target highlights stay +// in step with the flight path. +$cycle: 14s; +// Each target lags the first by one hover-and-travel segment (30% of $cycle). +// Target highlights are keyed for the first column and shifted with a +// negative delay of (cycle - i * segment), which starts them i segments late. +$segment: $cycle * 0.3; +// Circumference of the gauge ring (r = 17). +$ring: 106.8; + +:host { + display: block; + width: 100%; +} + +.scene { + --scene-height: 300px; + --mark-size: 108px; + --target-gap: var(--space-2); + + position: relative; + height: var(--scene-height); + padding: var(--space-5); + border-radius: var(--radius-3xl); + border: 1px solid var(--border); + background: var(--surface-subtle); + overflow: hidden; + isolation: isolate; + + // A faint dot grid behind everything, fading towards the edges. + &::before { + content: ''; + position: absolute; + inset: 0; + z-index: -1; + background-image: radial-gradient( + var(--border-strong) 1px, + transparent 1.5px + ); + background-size: 20px 20px; + background-position: 10px 10px; + mask-image: radial-gradient( + ellipse 70% 65% at 50% 42%, + #000 30%, + transparent 100% + ); + opacity: 0.8; + } +} + +/* Flight path: the saucer slides between the four columns and swoops back. */ +.flight { + position: absolute; + top: var(--space-4); + left: var(--space-5); + right: var(--space-5); + height: var(--mark-size); + animation: flight $cycle ease-in-out infinite; +} + +.ufo { + position: relative; + width: calc(100% / 3); + height: 100%; + display: flex; + justify-content: center; +} + +.ufo-tilt { + position: relative; + z-index: 1; + animation: tilt $cycle ease-in-out infinite; +} + +.ufo-bob { + animation: bob 3.2s ease-in-out infinite alternate; +} + +.ufo-mark { + width: var(--mark-size); + height: var(--mark-size); + filter: drop-shadow( + 0 10px 14px color-mix(in srgb, var(--color-neutral-950) 18%, transparent) + ); +} + +/* The beam hangs from the saucer and only shows while it hovers a target. */ +.beam { + position: absolute; + top: calc(var(--mark-size) * 0.64); + left: 50%; + width: 164px; + height: 124px; + translate: -50% 0; + clip-path: polygon(41% 0, 59% 0, 100% 100%, 0 100%); + background: linear-gradient( + to bottom, + color-mix(in srgb, var(--accent) 42%, transparent), + color-mix(in srgb, var(--accent) 10%, transparent) 70%, + transparent + ); + opacity: 0; + animation: beam $cycle ease-in-out infinite; + + &::after { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient( + to bottom, + color-mix(in srgb, var(--accent) 30%, transparent), + transparent 60% + ); + animation: beam-pulse 1.6s ease-in-out infinite alternate; + } +} + +/* The four targets, one per column, lit in turn. */ +.targets { + position: absolute; + left: var(--space-5); + right: var(--space-5); + bottom: var(--space-4); + margin: 0; + padding: 0; + list-style: none; + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: var(--target-gap); +} + +.target { + display: flex; + flex-direction: column; + align-items: center; + gap: var(--space-2); + padding: var(--space-3) var(--space-2) var(--space-2-5); + border-radius: var(--radius-xl); + border: 1px solid var(--border); + background: var(--surface-raised); + text-align: center; + animation: target-lift $cycle ease-in-out infinite; + animation-delay: calc(var(--i) * #{$segment} - #{$cycle}); +} + +.gauge { + position: relative; + width: 44px; + height: 44px; + display: grid; + place-items: center; +} + +.gauge-ring { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + rotate: -90deg; + fill: none; + stroke-width: 3; + stroke-linecap: round; +} + +.gauge-track { + stroke: var(--border-strong); +} + +.gauge-fill { + stroke: var(--accent); + stroke-dasharray: $ring; + stroke-dashoffset: $ring; + animation: + gauge-fill $cycle ease-in-out infinite, + gauge-visibility $cycle linear infinite; + animation-delay: calc(var(--i) * #{$segment} - #{$cycle}); +} + +.gauge-icon { + position: relative; + width: 20px; + height: 20px; + color: var(--text-subtle); + animation: icon-colour $cycle ease-in-out infinite; + animation-delay: calc(var(--i) * #{$segment} - #{$cycle}); +} + +.target-label { + font-size: 0.8125rem; + line-height: 1.125rem; + font-weight: 500; + color: var(--text-muted); + animation: label-colour $cycle ease-in-out infinite; + animation-delay: calc(var(--i) * #{$segment} - #{$cycle}); +} + +.target-note { + margin-top: calc(-1 * var(--space-1)); + font-size: 0.6875rem; + line-height: 1rem; + color: var(--text-subtle); +} + +/* Timeline, in percent of $cycle: + hover 0–24, travel 24–30, hover 30–54, travel 54–60, hover 60–84, + swoop back 84–100. */ +@keyframes flight { + 0%, + 24% { + transform: translate(0, 0); + } + 30%, + 54% { + transform: translate(33.333%, 0); + } + 60%, + 84% { + transform: translate(66.667%, 0); + } + 92% { + transform: translate(33.333%, -34px); + } + 100% { + transform: translate(0, 0); + } +} + +@keyframes tilt { + 0%, + 24%, + 30%, + 54%, + 60%, + 84%, + 100% { + transform: rotate(0deg); + } + 27%, + 57% { + transform: rotate(-6deg); + } + 90% { + transform: rotate(9deg); + } +} + +@keyframes bob { + from { + transform: translateY(-5px); + } + to { + transform: translateY(5px); + } +} + +@keyframes beam { + 0%, + 25%, + 30%, + 55%, + 60%, + 85%, + 100% { + opacity: 0; + } + 3%, + 23%, + 33%, + 53%, + 63%, + 83% { + opacity: 1; + } +} + +@keyframes beam-pulse { + from { + opacity: 0.35; + } + to { + opacity: 1; + } +} + +/* Target highlights are written for the first column; the others shift by + their animation-delay. */ +@keyframes target-lift { + 0%, + 24%, + 100% { + transform: translateY(-4px); + border-color: var(--accent-outline); + background: var(--accent-soft); + box-shadow: var(--shadow-md); + } + 28%, + 96% { + transform: translateY(0); + border-color: var(--border); + background: var(--surface-raised); + box-shadow: none; + } +} + +@keyframes gauge-fill { + 0%, + 2% { + stroke-dashoffset: $ring; + } + 19%, + 27% { + stroke-dashoffset: 0; + } + 28%, + 100% { + stroke-dashoffset: $ring; + } +} + +@keyframes gauge-visibility { + 0%, + 24%, + 100% { + opacity: 1; + } + 27%, + 99% { + opacity: 0; + } +} + +@keyframes icon-colour { + 0%, + 24%, + 100% { + color: var(--accent); + } + 28%, + 96% { + color: var(--text-subtle); + } +} + +@keyframes label-colour { + 0%, + 24%, + 100% { + color: var(--text-heading); + } + 28%, + 96% { + color: var(--text-muted); + } +} + +/* Reduced motion: park the saucer above the row, light everything. */ +@media (prefers-reduced-motion: reduce) { + .flight, + .ufo-tilt, + .ufo-bob, + .beam, + .beam::after, + .target, + .gauge-fill, + .gauge-icon, + .target-label { + animation: none; + } + + .flight { + transform: translate(33.333%, 0); + } + + .beam { + opacity: 0.7; + width: 100%; + clip-path: polygon(44% 0, 56% 0, 100% 100%, 0 100%); + } + + .target { + border-color: var(--accent-outline); + background: var(--accent-soft); + } + + .gauge-fill { + stroke-dashoffset: 0; + } + + .gauge-icon { + color: var(--accent); + } + + .target-label { + color: var(--text-heading); + } +} diff --git a/packages/docs/src/app/home/hero-scene.ts b/packages/docs/src/app/home/hero-scene.ts new file mode 100644 index 00000000..13c95421 --- /dev/null +++ b/packages/docs/src/app/home/hero-scene.ts @@ -0,0 +1,27 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; + +/** + * The hero illustration: the user-flow saucer hovers over the three kinds of + * step a flow is made of, pausing above each one while its ring fills. + * + * The animation is pure CSS so the server and the client render the same + * markup, and it stops under `prefers-reduced-motion`. + */ +@Component({ + selector: 'docs-hero-scene', + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: './hero-scene.html', + styleUrl: './hero-scene.scss', + host: { + role: 'img', + 'aria-label': + 'The user-flow saucer hovering over a navigation, a timespan and a snapshot step, measuring each in turn.', + }, +}) +export class HeroScene { + readonly targets = [ + { label: 'Navigation', note: 'Page load', icon: 'navigation' }, + { label: 'Timespan', note: 'Interaction', icon: 'timespan' }, + { label: 'Snapshot', note: 'Page state', icon: 'snapshot' }, + ] as const; +} diff --git a/packages/docs/src/app/home/home.html b/packages/docs/src/app/home/home.html index ce95dcb9..c26df290 100644 --- a/packages/docs/src/app/home/home.html +++ b/packages/docs/src/app/home/home.html @@ -31,10 +31,8 @@

npx user-flow collect -