From 0de213db0a644a11bf301d5dea7b2f7eed71584a Mon Sep 17 00:00:00 2001 From: Youssef Date: Fri, 31 Jul 2026 12:18:22 +0100 Subject: [PATCH] Scroll choreography: four earned patterns replace a blanket ban motion.md banned scroll-scrubbed animation 'unless there is a specific reason' while tier-c sanctioned animation-timeline outright - a live contradiction with no vocabulary between them. references/ scroll-choreography.md now IS the specific reason: four named patterns (pinned chapter, scrub-linked headline, progress spine, sticky diptych) with Baseline-2025 recipes, each inside @supports + reduced-motion + min-width guards so the base page is always the finished page. Two correctness rules are baked in: the animation shorthand resets animation-timeline (declare after), and pinned-pane children cannot drive their own view() (steps ride a named timeline on the tall track). Budget: one pattern per page (sloplint F8 will count), never on body text, linear easing for scrub, hero orchestration untouched, JS fallback is the IO reveal-once only. motion.md reroutes to the vocabulary, tier-c's decision row defers to it, macros 14/16 and f2 point at it, and SKILL.md loads it conditionally. --- skills/hallmark/SKILL.md | 2 +- .../components/f2-sticky-scroll-stack.md | 2 +- .../references/craft/tier-c-animation.md | 2 +- .../macrostructures/14-narrative-workflow.md | 2 +- .../macrostructures/16-feature-stack.md | 2 +- skills/hallmark/references/motion.md | 2 +- .../references/scroll-choreography.md | 111 ++++++++++++++++++ 7 files changed, 117 insertions(+), 6 deletions(-) create mode 100644 skills/hallmark/references/scroll-choreography.md diff --git a/skills/hallmark/SKILL.md b/skills/hallmark/SKILL.md index 92e1067..6a337cb 100644 --- a/skills/hallmark/SKILL.md +++ b/skills/hallmark/SKILL.md @@ -236,7 +236,7 @@ A custom system is complete (palette + pairing + axes), never a colour swap; its **Every build:** [`references/typography.md`](references/typography.md) · [`references/color.md`](references/color.md) · [`references/layout-and-space.md`](references/layout-and-space.md) · [`references/motion.md`](references/motion.md) · [`references/copy.md`](references/copy.md) · [`references/anti-patterns.md`](references/anti-patterns.md). -**Conditionally (be honest, no defensive pre-loads):** [`references/microinteractions.md`](references/microinteractions.md) when anything is interactive (most pages); [`references/interaction-and-states.md`](references/interaction-and-states.md) for stateful UI; [`references/responsive.md`](references/responsive.md) when mobile is in scope; [`references/structure.md`](references/structure.md) only when deviating from a named macrostructure; [`references/assets.md`](references/assets.md) only when an enrichment needs an external asset; [`references/texture.md`](references/texture.md) only when the picked theme earns texture (Riso, Carnival, Arcade, faint Newsprint) or a custom draw has print lineage; [`references/custom-theme.md`](references/custom-theme.md) only on the custom route; [`references/design-md.md`](references/design-md.md) only when the user asks to lock the system; [`references/preview-examples.md`](references/preview-examples.md) only if the Step 5 spec is not scaffolding enough. +**Conditionally (be honest, no defensive pre-loads):** [`references/microinteractions.md`](references/microinteractions.md) when anything is interactive (most pages); [`references/interaction-and-states.md`](references/interaction-and-states.md) for stateful UI; [`references/responsive.md`](references/responsive.md) when mobile is in scope; [`references/structure.md`](references/structure.md) only when deviating from a named macrostructure; [`references/assets.md`](references/assets.md) only when an enrichment needs an external asset; [`references/texture.md`](references/texture.md) only when the picked theme earns texture (Riso, Carnival, Arcade, faint Newsprint) or a custom draw has print lineage; [`references/scroll-choreography.md`](references/scroll-choreography.md) only when the brief asks for scroll story / cinematic pacing or the macro is Feature-stack / Narrative Workflow; [`references/custom-theme.md`](references/custom-theme.md) only on the custom route; [`references/design-md.md`](references/design-md.md) only when the user asks to lock the system; [`references/preview-examples.md`](references/preview-examples.md) only if the Step 5 spec is not scaffolding enough. **At the end only:** [`references/slop-test.md`](references/slop-test.md) strictly at Step 7 (pre-loading it costs thousands of tokens for nothing; `anti-patterns.md` is the pre-emit list); [`references/contract.md`](references/contract.md) at handoff; [`references/export-formats.md`](references/export-formats.md) only on `design.md` projects. diff --git a/skills/hallmark/references/components/f2-sticky-scroll-stack.md b/skills/hallmark/references/components/f2-sticky-scroll-stack.md index 05934c2..14f861a 100644 --- a/skills/hallmark/references/components/f2-sticky-scroll-stack.md +++ b/skills/hallmark/references/components/f2-sticky-scroll-stack.md @@ -1,7 +1,7 @@ ### F2 · Sticky-scroll stack Sticky left pane, scrolling right pane that cycles through related screenshots. *Use when:* feature has multiple sub-states worth showing in sequence. -*Don't confuse with:* F4 Step sequence (which is linearly numbered, not synced). +*Don't confuse with:* F4 Step sequence (which is linearly numbered, not synced). Scroll-scrub choreography on top of the pin lives in [`scroll-choreography.md`](../scroll-choreography.md) § Sticky diptych. ```html
diff --git a/skills/hallmark/references/craft/tier-c-animation.md b/skills/hallmark/references/craft/tier-c-animation.md index 841cac2..fcfb51c 100644 --- a/skills/hallmark/references/craft/tier-c-animation.md +++ b/skills/hallmark/references/craft/tier-c-animation.md @@ -79,7 +79,7 @@ The browser handles the cross-fade. No animation libraries needed for state chan ``` Single element, simple motion → CSS keyframes / @property Multiple elements, orchestrated entrance → Motion (React) or GSAP (vanilla / complex) -Scroll-progress-linked → animation-timeline (CSS) — or GSAP ScrollTrigger if complex +Scroll-progress-linked → ONE pattern from scroll-choreography.md (animation-timeline CSS); GSAP ScrollTrigger only when that pattern genuinely needs JS State change between two layouts → View Transitions API A list reflows in React → AutoAnimate A complex hero narrative with scrubbing → GSAP timeline + ScrollTrigger diff --git a/skills/hallmark/references/macrostructures/14-narrative-workflow.md b/skills/hallmark/references/macrostructures/14-narrative-workflow.md index cfe0a78..fe2d9d3 100644 --- a/skills/hallmark/references/macrostructures/14-narrative-workflow.md +++ b/skills/hallmark/references/macrostructures/14-narrative-workflow.md @@ -7,7 +7,7 @@ Numbered stages tell the story of how the user uses the product over time. Each - **Divider:** thick numbered rule between stages. - **Button:** stage-internal links; one global "Start at stage 1 →" at the foot. - **Image:** small product capture per stage, often annotated. -- **Reveal:** sweep horizontal as stages enter the viewport. +- **Reveal:** sweep horizontal as stages enter the viewport (a scrub-linked stage sequence may use [`scroll-choreography.md`](../scroll-choreography.md) § Pinned chapter instead - one pattern). Reach for it for products with explicit workflows — project management, design-to-dev pipelines, writing tools. diff --git a/skills/hallmark/references/macrostructures/16-feature-stack.md b/skills/hallmark/references/macrostructures/16-feature-stack.md index e693e6c..04db735 100644 --- a/skills/hallmark/references/macrostructures/16-feature-stack.md +++ b/skills/hallmark/references/macrostructures/16-feature-stack.md @@ -7,7 +7,7 @@ Sticky left pane (label / description) + scroll-synced right pane (screenshots c - **Divider:** section bands; the sticky pane re-anchors per section. - **Button:** in the sticky pane, set when the user reaches the section's detail count. - **Image:** the scrolling-right column is mostly imagery. -- **Reveal:** none of the spatial-fade kind; the sticky/scroll IS the motion. +- **Reveal:** none of the spatial-fade kind; the sticky/scroll IS the motion (choreograph it via [`scroll-choreography.md`](../scroll-choreography.md) § Sticky diptych or § Pinned chapter - one pattern). Reach for it for premium products, complex feature stories, anything where you want to control pacing as the user scrolls. diff --git a/skills/hallmark/references/motion.md b/skills/hallmark/references/motion.md index e3207a1..42d7011 100644 --- a/skills/hallmark/references/motion.md +++ b/skills/hallmark/references/motion.md @@ -70,7 +70,7 @@ Cap total stagger at ~500ms. Beyond that the page feels slow to settle. ## Scroll-linked motion - Use IntersectionObserver, **never** `scroll` event listeners. -- Use it only for *reveal once* effects. No parallax. No scroll-scrubbed animations unless there is a specific reason. +- Use it only for *reveal once* effects. No parallax. Scroll-scrubbed motion exists ONLY as the named patterns in [`scroll-choreography.md`](scroll-choreography.md), one per page; anything outside that vocabulary is banned. - Every scroll-triggered motion must have a reduced-motion fallback. ## State transitions diff --git a/skills/hallmark/references/scroll-choreography.md b/skills/hallmark/references/scroll-choreography.md new file mode 100644 index 0000000..e499a7a --- /dev/null +++ b/skills/hallmark/references/scroll-choreography.md @@ -0,0 +1,111 @@ +# Scroll choreography - the one earned pattern + +The default is still none: motion.md's IntersectionObserver reveal-once is the page norm. This file is the exception path - a scroll-linked pattern used ONCE, on structure, when the content genuinely has a sequence worth pinning or scrubbing. sloplint check F8 counts pattern families and flags a second one. + +## Budget (non-negotiable) + +- **One pattern per page.** Two scroll behaviours compete for the same attention. +- **Never on body text.** Targets are structural: a pinned section, ONE display element, a 2px spine, figures. Prose never moves under the reader. +- **Hero entrance rules unchanged.** The page-load orchestration in motion.md stays as is; scroll choreography never replaces it, and no element carries both. +- **Static end-state mandatory.** Base CSS shows the finished layout. The choreography lives entirely inside `@supports (animation-timeline: view())` + `@media (prefers-reduced-motion: no-preference) and (min-width: 40rem)` - reduced-motion, small screens, and non-supporting browsers get the complete page. +- **Order footgun:** the `animation` shorthand RESETS `animation-timeline` and `animation-range`. Declare them AFTER `animation`, always. +- **`linear` is correct here** - scrubbing maps scroll distance to progress 1:1; an easing curve would make the page feel detached from the thumb. +- A pattern counts as one of the page's three animation primitives (microinteractions.md). + +## 1 · Pinned chapter - steps reveal while the section is pinned + +The tall track owns a named view-timeline; the pinned pane's steps consume it in slices. (Elements inside a stuck pane cannot drive their own `view()` - they are not moving.) + +```html +
+
+

How it works

+
+
+
+``` +```css +.chapter { min-height: 280vh; } /* track length = pin duration */ +.chapter__pin { position: sticky; top: calc(var(--banner-height, 0px) + var(--space-xl)); } +@supports (animation-timeline: view()) { + @media (prefers-reduced-motion: no-preference) and (min-width: 40rem) { + .chapter { view-timeline: --chapter block; } + .chapter__steps li { animation: chapter-step linear both; animation-timeline: --chapter; } + .chapter__steps li:nth-child(1) { animation-range: contain 5% contain 30%; } + .chapter__steps li:nth-child(2) { animation-range: contain 30% contain 55%; } + .chapter__steps li:nth-child(3) { animation-range: contain 55% contain 80%; } + @keyframes chapter-step { from { opacity: 0.2; transform: translateY(8px); } to { opacity: 1; transform: none; } } + } +} +``` + +## 2 · Scrub-linked headline - ONE display element, clip wipe + +For a mid-page statement head (Ft5 / T3 register), never the hero h1 (that is load-orchestration territory). One element per page, ever. + +```css +.statement__head { /* base: fully drawn */ } +@supports (animation-timeline: view()) { + @media (prefers-reduced-motion: no-preference) and (min-width: 40rem) { + .statement__head { + animation: head-wipe linear both; + animation-timeline: view(); + animation-range: entry 20% cover 45%; + } + @keyframes head-wipe { + from { clip-path: inset(0 100% 0 0); transform: translateX(-0.25em); } + to { clip-path: inset(0 0 0 0); transform: none; } + } + } +} +``` + +## 3 · Progress spine - scroll(root) scaling a 2px rule + +Reading progress as a hairline, not a chrome bar. A functional indicator, so `linear` is canon. Hidden below 40rem (it eats the mobile gutter). + +```css +.spine { position: fixed; inset-block: 0; left: max(1rem, 2vw); width: 2px; background: var(--color-rule); } +.spine::after { content: ""; position: absolute; inset: 0; background: var(--color-accent); + transform-origin: top; transform: scaleY(0); } +@media (max-width: 40rem) { .spine { display: none; } } +@supports (animation-timeline: scroll()) { + @media (prefers-reduced-motion: no-preference) and (min-width: 40rem) { + .spine::after { animation: spine-grow linear both; animation-timeline: scroll(root); } + @keyframes spine-grow { to { transform: scaleY(1); } } + } +} +``` + +## 4 · Sticky diptych - alternating two-pane pin + +Structure and pin mechanics come from [`components/f2-sticky-scroll-stack.md`](components/f2-sticky-scroll-stack.md) - markup, `--banner-height` offset, and mobile collapse are defined there; do not restate them. This recipe adds only the choreography: the scrolling figures fade in on their own `view()` (they move, so self-timelines work), and consecutive diptychs alternate the pinned side. + +```css +/* f2 base: .sticky-stack grid + .pane-sticky pin, per the component file */ +.sticky-stack:nth-of-type(even) .pane-sticky { order: 2; } /* alternate the pin side */ +@supports (animation-timeline: view()) { + @media (prefers-reduced-motion: no-preference) and (min-width: 40rem) { + .pane-scroll figure { animation: fig-in linear both; animation-timeline: view(); + animation-range: entry 0% entry 60%; } + @keyframes fig-in { from { opacity: 0.3; transform: translateY(12px); } to { opacity: 1; transform: none; } } + } +} +``` + +## JS fallback (optional, shared) + +When JS is present and `animation-timeline` is not, the one sanctioned fallback is motion.md's reveal-once - never a scroll listener, never a scrub polyfill: + +```html + +``` + +Pair with a 420ms `--ease-out` transition to the end state. Without JS and without support, the base static layout is already the finished page - that is the contract.