From 835c5c808463ee2fd489056696fdad567950026a Mon Sep 17 00:00:00 2001 From: jwinterm Date: Mon, 17 Aug 2026 14:19:56 -0400 Subject: [PATCH] feat(splash): a colour burst as the mark hands over to the page Subtle and half-transparent, per the brief. Four blobs, one per monogram cell, each in that letter's own colour, thrown outward from where the letter was: the mark comes apart into its own palette rather than flashing something unrelated. The peak opacity is 0.42 light / 0.5 dark and the gradients never reach full alpha, so it stays a wash rather than a curtain. Only transform and opacity animate. Both are compositor properties, so this does not repaint per frame. That is deliberate: animating a filter is what pegged a core on the matrix animation, because a filter change re-renders the whole filter graph every frame. Soft edges come from a static radial-gradient that is painted once and only ever transformed. The overlay could no longer fade as one piece. Ground and mark fade in 260ms while the burst runs 420ms plus a 25ms per-blob stagger, so fading the container would have taken the burst down with it halfway through. They fade independently now and the shell unmounts at 540ms instead of 300ms, which is the burst's real length rather than the cross-fade's. Hidden entirely under prefers-reduced-motion, though those visitors never reach the splash anyway. --- apps/web/src/app/globals.css | 37 ++++++++++ .../src/components/landing/hero-splash.tsx | 71 ++++++++++++++----- .../web/src/components/landing/home-shell.tsx | 3 +- 3 files changed, 92 insertions(+), 19 deletions(-) diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css index b71bc59..f4b32a1 100644 --- a/apps/web/src/app/globals.css +++ b/apps/web/src/app/globals.css @@ -327,3 +327,40 @@ html[data-splash="skip"] #splash-overlay { display: none !important; } + +/* Splash handover burst: the four pillar cells disperse as the mark hands over to the page. + The blobs sit where the monogram's four cells were (a 2x2 grid about the centre), so this + reads as the logo coming apart rather than as a generic flash. + + ONLY transform and opacity animate, both compositor properties. This deliberately avoids + animating filter/background: the matrix animation pegged a core doing exactly that, because + a filter change re-renders the whole filter graph every frame. Soft edges come from a static + radial-gradient that is painted once and then only transformed. */ +@keyframes splash-burst { + from { transform: translate(-50%, -50%) scale(0.35); opacity: var(--burst-peak); } + to { transform: translate(-50%, -50%) scale(3.1); opacity: 0; } +} +.splash-burst-blob { + position: absolute; + width: 34vmin; + height: 34vmin; + border-radius: 9999px; + opacity: 0; + will-change: transform, opacity; + animation: splash-burst 420ms cubic-bezier(0.16, 0.84, 0.44, 1) forwards; + /* Half-transparent by construction: the gradient's own alpha never reaches 1, and + --burst-peak holds the whole layer under half opacity. */ + --burst-peak: 0.42; +} +.dark .splash-burst-blob { --burst-peak: 0.5; } +.splash-burst-s { background: radial-gradient(circle, rgb(26 92 56 / 0.55), transparent 70%); } +.splash-burst-u { background: radial-gradient(circle, rgb(184 54 112 / 0.55), transparent 70%); } +.splash-burst-c { background: radial-gradient(circle, rgb(23 106 146 / 0.55), transparent 70%); } +.splash-burst-h { background: radial-gradient(circle, rgb(18 133 126 / 0.55), transparent 70%); } +.dark .splash-burst-s { background: radial-gradient(circle, rgb(53 146 92 / 0.55), transparent 70%); } +.dark .splash-burst-u { background: radial-gradient(circle, rgb(232 104 158 / 0.55), transparent 70%); } +.dark .splash-burst-c { background: radial-gradient(circle, rgb(74 173 224 / 0.55), transparent 70%); } +.dark .splash-burst-h { background: radial-gradient(circle, rgb(58 212 198 / 0.55), transparent 70%); } +@media (prefers-reduced-motion: reduce) { + .splash-burst-blob { display: none; } +} diff --git a/apps/web/src/components/landing/hero-splash.tsx b/apps/web/src/components/landing/hero-splash.tsx index 1bf68bf..afa99e7 100644 --- a/apps/web/src/components/landing/hero-splash.tsx +++ b/apps/web/src/components/landing/hero-splash.tsx @@ -12,6 +12,16 @@ const SPLASH_MS = 1337; // gate stacked on the first. const FADE_MS = 260; +// The four monogram cells, in the 2x2 grid the mark ends on: S and C in the left column, +// U and H in the right. The burst throws one blob out of each, in that letter's own colour, +// so the handover reads as the logo dispersing rather than as an unrelated flash. +const BURST_CELLS = [ + { key: "s", left: "44%", top: "44%" }, + { key: "u", left: "56%", top: "44%" }, + { key: "c", left: "44%", top: "56%" }, + { key: "h", left: "56%", top: "56%" }, +] as const; + /** * First-load splash: the pillar signature. * @@ -77,30 +87,55 @@ export function HeroSplash({ onEnter, leaving }: { onEnter: () => void; leaving? }; }, [leaving]); + const fade = { transitionDuration: `${FADE_MS}ms`, opacity: fadeOut ? 0 : 1 }; + return (