A motion language for the public landing — make the compounding learning-loop story kinetic while holding the brand bar (and a documented exception for the section-4 loop diagram)
0029-landing-motion-polish-pass
- Reversibility
- two-way door
DEC-0029 — Landing motion/polish pass (the compounding story, made kinetic)
Reversibility: two-way door — the motion is purely additive surface CSS + one observer script behind reveal classes; deleting the "MOTION & POLISH" section and the observer returns the landing to a fully-visible static page. The durable commitment is only that the landing has a calm, tokenized, reduced-motion-safe motion language that tells the compounding story.
Context
Recalibrate the Dossier brand identity — demote color, promote type + restraint + craft — then build the showcase landing recalibrated the brand and shipped a marketing/showcase landing at / (packages/site), including the abstract animated HeroLoop (its sub-decision #4). Ship the landing publicly behind a docs-gate flag, capture demand through two honest doors, into a list we own made that landing public and built the two-door capture funnel into it; Market to every organization; agencies are the highest-leverage channel, not a gate opened its message to every organization. The landing was structurally complete and verified, but static below the hero — the page told the compounding learning-loop story in words and a static diagram, but didn't move the way a premium "precise instrument" surface does.
This is a DEC-0018 follow-on: a principal-level motion/polish pass on that landing surface, shipped and Playwright-verified this session (2026-06-16) by the Principal Forward Deployed Engineer working with the Principal UX Engineer and Product Owner. It is implemented in a new "MOTION & POLISH" section of packages/site/src/styles/landing.css and in reveal classes + one IntersectionObserver script in packages/site/src/pages/index.astro. No @dossier/design token was added — the existing motion vocabulary (--ds-duration-*, --ds-ease-*, --ds-shadow-*, --ds-color-*) sufficed, so Establish the design system and the UX-engineering function's tokens-first SSOT is untouched.
The brand bar this motion had to hold
Warmth + intelligence come from type + editorial restraint + craft, NOT decoration or saturation (Recalibrate the Dossier brand identity — demote color, promote type + restraint + craft — then build the showcase landing). The motion brief made that concrete: the motion exists to tell the compounding learning-loop story and never to show off — "a precise instrument quietly running, not a SaaS spinner."
Options considered
1. Whether to add motion at all.
- (a) Keep the landing static below the hero. Rejected: a static page under-sells the compounding thesis the product is built on, and reads flat next to the polish bar Recalibrate the Dossier brand identity — demote color, promote type + restraint + craft — then build the showcase landing set.
- (b) Add a motion language (chosen). Calm, story-bearing motion: entrance choreography + a living loop diagram + restrained micro-interactions.
2. How much moves continuously — the brand-bar principle vs. a documented exception.
- (a) The product-owner's brief principle: "only the
HeroLoopmoves continuously; everything else reveals once and holds." The instinct is sound — continuous motion everywhere becomes a SaaS spinner, exactly the failure mode the brand bar names. - (b) The FDE's deliberate exception (chosen, and recorded as such): the section-4 "how it works" loop diagram also moves continuously, in addition to the
HeroLoop. Everything else still reveals once and holds, per the PO principle. This is the key revisitable judgment, captured in Decision + Rationale below so it can be revisited, not mistaken for an oversight.
3. Entrance technique — heavy library vs. vanilla observer.
- (a) A scroll/animation library (Motion, GSAP, astro-vtbot, etc.). Rejected: a new runtime dependency for what one observer does, against the VoidZero/Vite lean (Extraction runtime architecture — the moat / CLAUDE.md) and the dependency-free precedent
HeroLoopset (Recalibrate the Dossier brand identity — demote color, promote type + restraint + craft — then build the showcase landing #4). - (b) One vanilla
IntersectionObserver+ CSS (chosen). Zero deps; one-shot reveal then unobserve; rebinds onastro:page-loadso it survives the ClientRouter view transitions.
Decision
Ship a calm, tokenized, reduced-motion-safe motion language on the landing that makes the compounding learning-loop story kinetic — holding the PO's "reveal once and hold" principle everywhere except a documented exception for the section-4 loop diagram, which moves continuously because depicting compounding is its whole job.
What shipped
- Entrance choreography (the "reveal once and hold" half). Scroll-reveal: sections and their children rise (16px) + fade in once on entering the viewport, lightly staggered (
--reveal-iper child, ~70ms step); plus a hero on-load entrance (--hero-istagger; the artifact card gets a hair-heavierlanding-settlekeyframe). One vanillaIntersectionObserveradds.reveal--inthen unobserves non-loop reveals (one-shot), and rebinds onastro:page-loadwith a per-nodedata-reveal-boundguard so it survives ClientRouter. - The section-4 loop diagram comes alive (the documented exception). Three calm continuous motions reading as one compounding cycle: a travelling forward-flow dash (small
stroke-dashoffsettick sliding stage→stage, phased by--flow-i), a gently breathing OKF-graph node (the heart —4s,+2.5%scale viatransform-box: fill-box), and a continuously marching dashed return arc (3.2s, the feedback that makes the loop compound). Play/paused via adata-activeattribute the same observer toggles as the diagram enters/leaves view — so the compositor rests offscreen, mirroringHeroLoop's convention exactly. - Micro-interactions. Feature-card hover and keyboard-
focus-withinlift (translateY(-3px)+--ds-shadow-md+--ds-color-border-strong), primary-CTA hover/focus lift, and the external-link↗arrow nudgingtranslate(1px,-1px)on hover/focus.
The brand-bar principle and the FDE exception (record both)
- PO principle (recorded): only the
HeroLoopmoves continuously; everything else reveals once and holds. - FDE exception (recorded, deliberate): the section-4 loop diagram also moves continuously. Rationale in the next section. This is captured so a future reviewer revisits the call rather than treating it as accidental drift from the principle.
Rationale
Why the section-4 diagram is allowed to move continuously (the key judgment)
- They never compete for attention. The
HeroLoopis at the very top of the page and the section-4 diagram is far down — they are never on screen at the same time, so two continuously-moving elements never coexist in the viewport. The "no SaaS spinner" risk is about simultaneous, attention-grabbing motion; that risk doesn't materialize here. - Continuous motion is the truer depiction of compounding. The diagram's entire job is to show the loop compounding — ingest → extract → OKF graph → serve → curate, and a feedback return arc closing the loop. A one-shot reveal draws the loop once; continuous motion shows it running and accreting, which is the thesis (Dossier — Mission & North Star). Form follows the idea.
- The motion is calm by construction. 4s breathing at +2.5% scale, a slow (3.2s) marching return dash, and small travelling forward ticks — deliberately under the threshold of a spinner. It reads as a precise instrument quietly running, which is the brand bar, not a violation of it.
Why the rest of the discipline
- No new token, tokens-only. The existing
--ds-*motion vocabulary covered every duration, easing, shadow, and color; adding a token would have been unnecessary churn against Establish the design system and the UX-engineering function's SSOT. Grep-clean: zero hex in the motion section, no new palette. - Reduced-motion is a first-class state, not a frozen frame. Every animation sits inside
@media (prefers-reduced-motion: no-preference); underreducethe authored final state is fully visible (sections at opacity 1, the diagram's solid forward strokes + parked dashed return arc standing as the intended static design). Micro-interactions keep their shadow/border affordance underreduce; only the translate is suppressed. - No-JS / progressive enhancement. The
.revealresting state is the visible/final state. The hidden start (opacity:0; translateY(16px)) exists only under a.jsflag the page sets synchronously before first paint — and the same script that hides also guarantees a reveal — so content can never strand with JS off, JS broken, or before the observer fires. IfIntersectionObserveris absent, every.revealis shown immediately. - Compositor-friendly. Only
transform,opacity, andstroke-dashoffsetare animated;will-changeis hinted for the one-shot reveal then dropped once revealed; offscreen diagrams are paused viadata-active. verified, notasserted. The FDE reproduced the behavior with Playwright (see Consequences/Review) — this is mechanism-verified, not an assertion.
Consequences
- The landing now reads as a living instrument that tells the compounding story, while still honoring the brand bar — calm, tokenized, restrained.
- The PO's "reveal once and hold" principle now has one explicit, documented exception (the section-4 diagram). Any future motion work should treat continuous motion as the exception that must be argued for, not the default.
- Surface-only, additive, fully reversible. A new "MOTION & POLISH" section in
packages/site/src/styles/landing.css+ reveal classes and one observer inpackages/site/src/pages/index.astro. No@dossier/designtoken, no new dependency, no sovereignknowledge/file touched (Adopt OKF as Dossier's canonical knowledge format). Build-side; nothing added to the client-facing plugin subset (Plugin + marketplace packaging — distribution as the agency wedge, built from the canonical .claude/ primitives). HeroLoop's conventions are now the shared motion grammar of the landing — the section-4 diagram'sdata-activepause mirrors it, so the page has one coherent approach to offscreen compositor rest.- Accessibility is preserved end-to-end — keyboard parity (
focus-within/focus-visible), reduced-motion fully-visible, no-JS fully-visible.
FDE verification (Playwright, reproduced this session)
- All sections reveal to opacity 1 on scroll.
- The loop diagram's
data-activetoggles as it enters/leaves view, and its forward flow dash offset animates. - Feature hover lift confirmed.
- Reduced-motion fully-visible: at
scrollY 0under emulatedreduce, every section is opacity 1 with no orphaned hidden content. - The landing compiles — the production build under the Vercel adapter succeeded before an unrelated content error appeared (see the note below; that error is not part of this decision).
Review
Revisit the section-4 continuous-motion exception specifically if a future layout ever puts the hero and the loop diagram on screen together, or if user/analytics signal the page reads "busy" — the diagram can drop to a one-shot draw without touching the rest of the motion language (Ship the landing publicly behind a docs-gate flag, capture demand through two honest doors, into a list we own / Add Vercel Web Analytics to the public marketing landing (a cookieless demand signal — landing surface only, not the KB or product) provide the demand-signal lens). Otherwise revisit alongside any further Recalibrate the Dossier brand identity — demote color, promote type + restraint + craft — then build the showcase landing surface polish. The motion is a two-way door — delete the section to return to a fully-visible static landing.
Not part of this decision — flagged for routing (an integration-seam defect, NOT a motion call). During this work, the site dev/build began failing on an unrelated concurrent workstream: the Agentic "sprint board" architecture — a git-resident OKF task board worked by bounded, hook-governed Agent SDK loops board seeded
taskatoms underknowledge/tasks/whosestatusvalues (backlog,in_progress,done,review,blocked,claimed) are outside the site content schema'sstatusenum (draft|active|deprecated|supersededinpackages/site/src/content.config.ts). Astro syncs that collection on every dev/build, so it blocks the whole site. The FDE worked around it for QA only — pointed the dev server at a throwaway KB via.env.localDOSSIER_KB(gitignored) — and did not modify the board workstream's content or schema (that's the board owner's in-flight modeling call;task-design-task-concept-typeisin_progressandtask-board-site-surface's own acceptance criteria already call for extendingokfFields). This belongs to the board workstream / Astro Starlight Engineer, not here — see the log entry filed alongside this record.