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

decision read as Explain confidence verified status active 2026-06-16 owner ux-engineer
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.

2. How much moves continuously — the brand-bar principle vs. a documented exception.

  • (a) The product-owner's brief principle: "only the HeroLoop moves 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.

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

  1. 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-i per child, ~70ms step); plus a hero on-load entrance (--hero-i stagger; the artifact card gets a hair-heavier landing-settle keyframe). One vanilla IntersectionObserver adds .reveal--in then unobserves non-loop reveals (one-shot), and rebinds on astro:page-load with a per-node data-reveal-bound guard so it survives ClientRouter.
  2. 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-dashoffset tick sliding stage→stage, phased by --flow-i), a gently breathing OKF-graph node (the heart — 4s, +2.5% scale via transform-box: fill-box), and a continuously marching dashed return arc (3.2s, the feedback that makes the loop compound). Play/paused via a data-active attribute the same observer toggles as the diagram enters/leaves view — so the compositor rests offscreen, mirroring HeroLoop's convention exactly.
  3. Micro-interactions. Feature-card hover and keyboard-focus-within lift (translateY(-3px) + --ds-shadow-md + --ds-color-border-strong), primary-CTA hover/focus lift, and the external-link arrow nudging translate(1px,-1px) on hover/focus.

The brand-bar principle and the FDE exception (record both)

  • PO principle (recorded): only the HeroLoop moves 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)

  1. They never compete for attention. The HeroLoop is 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.
  2. 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.
  3. 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); under reduce the 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 under reduce; only the translate is suppressed.
  • No-JS / progressive enhancement. The .reveal resting state is the visible/final state. The hidden start (opacity:0; translateY(16px)) exists only under a .js flag 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. If IntersectionObserver is absent, every .reveal is shown immediately.
  • Compositor-friendly. Only transform, opacity, and stroke-dashoffset are animated; will-change is hinted for the one-shot reveal then dropped once revealed; offscreen diagrams are paused via data-active.
  • verified, not asserted. 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 in packages/site/src/pages/index.astro. No @dossier/design token, no new dependency, no sovereign knowledge/ 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's data-active pause 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-active toggles as it enters/leaves view, and its forward flow dash offset animates.
  • Feature hover lift confirmed.
  • Reduced-motion fully-visible: at scrollY 0 under emulated reduce, 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 task atoms under knowledge/tasks/ whose status values (backlog, in_progress, done, review, blocked, claimed) are outside the site content schema's status enum (draft|active|deprecated|superseded in packages/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.local DOSSIER_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-type is in_progress and task-board-site-surface's own acceptance criteria already call for extending okfFields). This belongs to the board workstream / Astro Starlight Engineer, not here — see the log entry filed alongside this record.