Skip to content

Commit c637590

Browse files
committed
Hero h1 morphs into the top-left wordmark on scroll
Previously the hero just scaled-down-and-faded centered. Per the make-interfaces-feel-better skill, the panel should hand off visually to the sticky header's brand wordmark — a shared-element transition that explains where the title GOES, not just that it disappears. Four scroll-driven animations, all opacity/transform/filter only: hero-fade 0-280px .hero opacity 1→0, scale 1→0.92, translateY 0→-8px (panel dissolves) hero-h1-morph 0-240px h1 transform-origin top-left, scale 1→0.32, translate 0→(-32%,-50%) so it heads toward the top-left corner where the brand wordmark sits hero-p-fade 0-140px tagline fades early (the h1 carries the show on its own past 140px) brand-reveal 80-240px .brand opacity 0→1, scale 0.88→1, filter blur(4px)→0. Coming-into- focus reveal, staggered to start while the h1 is still en route. header-solidify 0-240px background 0.82→0.95 + shadow. Gives the header weight as it takes over from the hero panel. Two safety gates from impeccable + make-interfaces: @supports (animation-timeline: scroll()) — older browsers see the static layout, brand stays visible by default. @media (prefers-reduced-motion: no-preference) — reduced-motion users skip it. Brand hiding is scoped with body:has(.hero) so /examples/<slug> and /journeys/<slug> pages (no hero) keep their brand visible from the start — the morph only applies on home. Per the skill: animations only specify exact properties (opacity, transform, filter), not `transition: all`. No will-change because scroll-driven animations are already on the compositor on modern engines. 62 tests pass; lint clean.
1 parent 87a4dcf commit c637590

3 files changed

Lines changed: 36 additions & 12 deletions

File tree

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,29 @@
2828
.brand { font-weight: 800; }
2929
.nav-links { display: flex; gap: .35rem; }
3030
.nav-links a { padding: 0 .9rem; color: var(--muted); }
31-
.hero { overflow: hidden; border: 1px solid var(--hairline); border-radius: 1rem; padding: clamp(1.25rem, 3.5vw, 2.5rem); margin-bottom: 1.25rem; background: linear-gradient(135deg, var(--surface), var(--surface-3)); box-shadow: 0 1px 3px rgba(82, 16, 0, 0.04), 0 4px 12px rgba(82, 16, 0, 0.02); transform-origin: top center; }
32-
.hero h1 { font-size: clamp(2rem, 4vw, 3rem); margin-bottom: var(--space-3); }
31+
.hero { overflow: hidden; border: 1px solid var(--hairline); border-radius: 1rem; padding: clamp(1.25rem, 3.5vw, 2.5rem); margin-bottom: 1.25rem; background: linear-gradient(135deg, var(--surface), var(--surface-3)); box-shadow: 0 1px 3px rgba(82, 16, 0, 0.04), 0 4px 12px rgba(82, 16, 0, 0.02); transform-origin: top left; }
32+
.hero h1 { font-size: clamp(2rem, 4vw, 3rem); margin-bottom: var(--space-3); transform-origin: top left; }
3333
.hero p { max-width: 60ch; color: var(--muted); font-size: 1rem; }
3434
@supports (animation-timeline: scroll()) {
3535
@media (prefers-reduced-motion: no-preference) {
36-
.hero { animation: hero-collapse linear forwards; animation-timeline: scroll(root); animation-range: 0 320px; }
36+
.hero { animation: hero-fade linear forwards; animation-timeline: scroll(root); animation-range: 0 280px; }
37+
.hero h1 { animation: hero-h1-morph linear forwards; animation-timeline: scroll(root); animation-range: 0 240px; }
38+
.hero p { animation: hero-p-fade linear forwards; animation-timeline: scroll(root); animation-range: 0 140px; }
39+
body:has(.hero) header .brand { opacity: 0; filter: blur(4px); transform: scale(0.88); animation: brand-reveal linear forwards; animation-timeline: scroll(root); animation-range: 80px 240px; }
3740
header { animation: header-solidify linear forwards; animation-timeline: scroll(root); animation-range: 0 240px; }
3841
}
3942
}
40-
@keyframes hero-collapse {
41-
to { transform: scale(0.55) translateY(-32px); opacity: 0; }
43+
@keyframes hero-fade {
44+
to { opacity: 0; transform: scale(0.92) translateY(-8px); }
45+
}
46+
@keyframes hero-h1-morph {
47+
to { transform: scale(0.32) translate(-32%, -50%); opacity: 0; }
48+
}
49+
@keyframes hero-p-fade {
50+
to { opacity: 0; transform: translateY(-4px); }
51+
}
52+
@keyframes brand-reveal {
53+
to { opacity: 1; filter: blur(0); transform: scale(1); }
4254
}
4355
@keyframes header-solidify {
4456
to { background: rgba(245, 241, 235, 0.95); box-shadow: 0 1px 8px rgba(82, 16, 0, 0.06); }

public/site.css

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,29 @@
2828
.brand { font-weight: 800; }
2929
.nav-links { display: flex; gap: .35rem; }
3030
.nav-links a { padding: 0 .9rem; color: var(--muted); }
31-
.hero { overflow: hidden; border: 1px solid var(--hairline); border-radius: 1rem; padding: clamp(1.25rem, 3.5vw, 2.5rem); margin-bottom: 1.25rem; background: linear-gradient(135deg, var(--surface), var(--surface-3)); box-shadow: 0 1px 3px rgba(82, 16, 0, 0.04), 0 4px 12px rgba(82, 16, 0, 0.02); transform-origin: top center; }
32-
.hero h1 { font-size: clamp(2rem, 4vw, 3rem); margin-bottom: var(--space-3); }
31+
.hero { overflow: hidden; border: 1px solid var(--hairline); border-radius: 1rem; padding: clamp(1.25rem, 3.5vw, 2.5rem); margin-bottom: 1.25rem; background: linear-gradient(135deg, var(--surface), var(--surface-3)); box-shadow: 0 1px 3px rgba(82, 16, 0, 0.04), 0 4px 12px rgba(82, 16, 0, 0.02); transform-origin: top left; }
32+
.hero h1 { font-size: clamp(2rem, 4vw, 3rem); margin-bottom: var(--space-3); transform-origin: top left; }
3333
.hero p { max-width: 60ch; color: var(--muted); font-size: 1rem; }
3434
@supports (animation-timeline: scroll()) {
3535
@media (prefers-reduced-motion: no-preference) {
36-
.hero { animation: hero-collapse linear forwards; animation-timeline: scroll(root); animation-range: 0 320px; }
36+
.hero { animation: hero-fade linear forwards; animation-timeline: scroll(root); animation-range: 0 280px; }
37+
.hero h1 { animation: hero-h1-morph linear forwards; animation-timeline: scroll(root); animation-range: 0 240px; }
38+
.hero p { animation: hero-p-fade linear forwards; animation-timeline: scroll(root); animation-range: 0 140px; }
39+
body:has(.hero) header .brand { opacity: 0; filter: blur(4px); transform: scale(0.88); animation: brand-reveal linear forwards; animation-timeline: scroll(root); animation-range: 80px 240px; }
3740
header { animation: header-solidify linear forwards; animation-timeline: scroll(root); animation-range: 0 240px; }
3841
}
3942
}
40-
@keyframes hero-collapse {
41-
to { transform: scale(0.55) translateY(-32px); opacity: 0; }
43+
@keyframes hero-fade {
44+
to { opacity: 0; transform: scale(0.92) translateY(-8px); }
45+
}
46+
@keyframes hero-h1-morph {
47+
to { transform: scale(0.32) translate(-32%, -50%); opacity: 0; }
48+
}
49+
@keyframes hero-p-fade {
50+
to { opacity: 0; transform: translateY(-4px); }
51+
}
52+
@keyframes brand-reveal {
53+
to { opacity: 1; filter: blur(0); transform: scale(1); }
4254
}
4355
@keyframes header-solidify {
4456
to { background: rgba(245, 241, 235, 0.95); box-shadow: 0 1px 8px rgba(82, 16, 0, 0.06); }

src/asset_manifest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
# Generated by scripts/fingerprint_assets.py. Do not edit by hand.
2-
ASSET_PATHS = {'SITE_CSS': '/site.2bb247856b55.css', 'SYNTAX_JS': '/syntax-highlight.3b6c7f730d46.js', 'EDITOR_JS': '/editor.dd81f5171b14.js'}
3-
HTML_CACHE_VERSION = '2bb58e966c67'
2+
ASSET_PATHS = {'SITE_CSS': '/site.b7913985255d.css', 'SYNTAX_JS': '/syntax-highlight.3b6c7f730d46.js', 'EDITOR_JS': '/editor.dd81f5171b14.js'}
3+
HTML_CACHE_VERSION = '801d4d11e527'

0 commit comments

Comments
 (0)