Add motion tiers (calm / standard / expressive) as a context-suggested stance - #49
Conversation
…d stance v0.3 shipped one motion setting for every prototype, but motion must be context-fit: cinematic hero-morphs in a dense dashboard are friction; a marketing landing with only a crossfade under-delivers. Gate motion intensity by a tier the skill decides from context and records in DESIGN.md — a stance, like signature-move / icon-stance / imagery-stance. Gate everything off html[data-motion="calm|standard|expressive"] (default standard): - calm — dense tools/dashboards: root transition is a fast crossfade only (no directional slide), no list->detail hero morph, .enter/.reveal render instantly, scroll-driven effects off. KEEP micro-interactions (:active press, hover, focus) — feedback, not friction. Loading/Speed is a separate control, untouched. - standard (default) — exactly today's v0.3 behavior; adds no rules. - expressive — marketing/landing/pitch: standard plus scroll-driven .reveal where supported, a gentle --spring easing (linear() with a cubic-bezier fallback, ~2-3% overshoot) on .enter, and slightly richer entrance distance/duration. Implementation: - index.html/404.html: inline anti-FOUC <head> script now also sets data-motion from localStorage before first paint (pagereveal fires pre-paint, so the tier must be on <html> before the transition CSS resolves); static html tag gets data-motion="standard" as the pre-JS default. - styles.css: --spring token (@supports linear() + cubic-bezier fallback); calm overrides ::view-transition-*(root) to a ~130ms fade and forces .enter/.reveal to their visible end-state; expressive applies --spring to .enter and upgrades .reveal to scroll-driven (animation-timeline: view(), reduced-motion-gated). The global prefers-reduced-motion block still wins over every tier. - vt.js: pageswap/pagereveal early-return the hero-morph naming under calm (root crossfade still runs via CSS). - app.js: reveal observer short-circuits under calm (show immediately). - new js/motion.js: mirrors theme.js — reads/writes localStorage['proto-motion'] (+ ?motion= param), applies data-motion, syncs aria-pressed, wires [data-motion-option] buttons. Loaded after theme.js in both html files. - control bar: compact Motion section (Calm / Std / Exp) — stays one row at 1440. - docs: discovery.md gains a "Motion tier" DESIGN.md decision; build.md documents the three tiers, the copy-list, file-tree, and script order. Zero-dep (CSS + vanilla JS + Tailwind CDN). Everything degrades cleanly — the tier only scales motion that already degrades on its own. SKILL.md untouched. Verified: node --check on vt/motion/theme/app; headless Chrome zero JS console errors under each tier; data-motion flips; calm reveals render instantly while press feedback stays; control bar one row at 1440; check-overflow.sh 390 PASS; prefers-reduced-motion neutralizes expressive (duration clamped, forced visible). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…g overshoot, editorial reconcile, calm entrance-only transition Fixes from adversarial review of PR #49 (motion tiers): - expressive .reveal no longer hardcodes opacity:0 — when the ViewTimeline is inactive (no scrollable overflow, or an overflow:hidden ancestor), content no longer gets stuck permanently invisible; the cascade falls back to the base .reveal/.reveal.visible pair with app.js's IntersectionObserver as a working safety net instead of a "harmless" no-op. - expressive .enter re-declares transition-delay after the transition shorthand, which was resetting it to 0 and collapsing the staggered entrance. - --spring's cubic-bezier fallback (used when linear() isn't supported) now actually overshoots (~2%, was ~0.02%) — cubic-bezier(.3, 1.25, .5, 1). - discovery.md: dropped "editorial" from the expressive tier's tone list so it no longer contradicts the pre-existing "corporate/editorial calm" pacing line. - build.md: reworded the expressive tier-table cell so it doesn't imply --spring applies to .reveal (it's .enter only; .reveal stays standard unless scroll-driven). - calm's .enter no longer uses a blanket transition:none (which also silenced later hover/press transitions on the same element) — now only transition-delay is zeroed and @starting-style is pinned to the end-state, so mount is still instant but the transition stays armed for whatever comes after. calm's .reveal keeps its transition:none as-is (load-bearing against a pre-JS fade). - anti-FOUC head script (index.html + 404.html) now validates the stored data-motion value against calm|standard|expressive before applying it. - SKILL.md: added "motion" to both control-bar enumerations, trimmed a redundant sentence elsewhere to stay within the 2600-token budget (2598). Verified in headless Chrome against the real scaffold: expressive .reveal on a non-scrolling page now resolves to opacity 1 (was stuck at 0); expressive .enter with --i:3 has transition-delay: 0.12s (was 0s); standard tier unchanged; calm .enter mounts instantly while keeping its transition armed for a later hover; the reduced-motion !important block is untouched and still neutralizes every tier. check-overflow.sh passes at 390px; control bar stays one row at 1440px. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Addressed the adversarial review findings in b11d4c3: 1 (MAJOR) — expressive 2 (MAJOR) — expressive 3 (MINOR) — 4 (MINOR) — editorial contradiction. Dropped "editorial" from the expressive tier's tone list in discovery.md (kept playful/bold → expressive), so it no longer conflicts with the "corporate/editorial calm" pacing line. 5 (MINOR) — SKILL.md missing "motion". Added "motion" to both control-bar enumerations (~line 83, ~110). Trimmed one redundant sentence in Step 0 to stay in budget. Final token count: 2598 (≤2600, per 6 (MINOR) — build.md over-claims. Reworded the expressive tier-table cell: 7 (POLISH) — calm's blanket 8 (NIT) — inline Verification (headless Chrome against the real scaffold, not just reasoning):
Not merging — leaving this for review. 🤖 Generated with Claude Code |
Why
v0.3 ships one motion setting for every prototype. But motion must be context-fit: a dense dashboard with cinematic hero-morphs is friction; a marketing landing with only a crossfade under-delivers. This gates motion intensity by a tier — a stance the skill decides from context and records in
DESIGN.md, exactly like the existing signature-move / icon-stance / imagery-stance decisions. standard is the default it dials up or down from.The three tiers —
html[data-motion="calm|standard|expressive"](defaultstandard).enter/.reveal:active/hover/focus)--spring+ richer distance/duration.revealupgraded to scroll-driven--springeasing (linear()with acubic-bezierfallback, ~2–3% overshoot) on.enter, slightly richer entrance distance/duration, and.revealupgraded to scroll-driven whereanimation-timeline: view()is supported.Changes
index.html/404.html— inline anti-FOUC<head>script now also setsdata-motionfromlocalStoragebefore first paint (pagerevealfires pre-paint, so the tier must be on<html>before the transition CSS resolves); static<html>tag getsdata-motion="standard". New compact Motion section (Calm / Std / Exp) in#proto-controls;motion.jsadded aftertheme.jsin the script order.css/styles.css—--springtoken (@supportslinear()+cubic-bezierfallback); calm overrides::view-transition-*(root)to a short fade and forces.enter/.revealto their visible end-state; expressive applies--springto.enterand adds a reduced-motion-gated scroll-driven.reveal. The globalprefers-reduced-motionblock still wins over every tier.js/vt.js—pageswap/pagerevealearly-return the hero-morph naming under calm (root crossfade still runs via CSS).js/app.js— reveal observer short-circuits under calm.js/motion.js(new) — mirrorstheme.js: reads/writeslocalStorage['proto-motion'](+?motion=param), appliesdata-motion, syncsaria-pressed, wires[data-motion-option]buttons.reference/discovery.md— new "Motion tier" DESIGN.md decision (picked from register + tone + screen types; a stance to decide and record, never a default to forget).reference/build.md— documents the three tiers, the copy-list, file-tree, and script order; cross-references "Page transitions" and.enter.Zero-dep (CSS + vanilla JS + Tailwind CDN). Everything degrades cleanly — the tier only scales motion that already degrades on its own.
SKILL.mduntouched (no headroom).Verification
node --checkonvt.js/motion.js/theme.js/app.js— all pass.html[data-motion]flips calm/standard/expressive via?motion=.opacity:1) while the global:activepress rule stays; standard unchanged (.revealopacity:0+translateY(12px)); expressive.entercomputes the springlinear()easing.scrollWidth == clientWidth).check-overflow.sh 390— PASS.prefers-reduced-motionneutralizes expressive:.enterduration clamped to0.01ms,.enter/.revealforced visible.Needs live tuning
The
--springlinear()curve and the expressive scroll-driven.revealanimation-rangeare set by reason, not by eye — worth a quick look on real content to confirm the overshoot reads as "gentle" and the reveal timing feels right, not laggy.🤖 Generated with Claude Code