A real report, written by 58 · Interaction States & Motion Audit, run against this repo — dogfood output, committed unedited. This is the artifact every brief ends in: findings that cite their evidence, ranked by severity, with a fix sketch each.
Read-only trace of every interactive species' state matrix and every animation's job, across template.html, build.py→SITE_CSS, studio.html, vitals.html. States are read from real selectors (a style that exists only in a mockup does not exist here).
Headline: motion is a strength — a tight duration set (.1 / .15 / .2 / .4s), one purposeful entrance keyframe, and prefers-reduced-motion honored in all four files. Feedback is good too (copy actions flash "Copied ✓"; the async GitHub loader shows "loading…" + disables). The problems are focus: the site's highest-traffic input — the catalog search — has its focus ring suppressed by outline:0, and the same bug hits the four context inputs and Studio's paste/repo inputs. Focus visibility outranks all polish, so those lead.
Backlog reconciliation (2026-07-09). Dispositions: S1–S3 (suppressed input focus rings) — FIXED:
:focus-visibleoverrides added for the search input, the four context inputs, and the Studio/Vitals paste + GitHub-repo inputs (keyboard ring restored, mouseoutline:0kept). S5 (no shared disabled style) & S6 (inconsistent press feedback) — FIXED: the redesign added sharedbutton:active/button:disabledto the detail pages, and this pass mirrored both into the landing and the Studio/Vitals tools. S7 (color-only inline links) — FIXED for the color-only footer links (hover underline); nav/crumb/footer links already had hovers. S4 (no input error state) — FIXED: the Studio GitHub-repo input shows a red.is-errorborder +aria-invalidon a failed load, cleared on edit. Motion-token naming (§4) — DEFERRED: values are already one consistent vocabulary; naming them is additive only. SeeFIXLOG.md.
✓ defined · ✗ missing · ⚠ broken/overridden · — n/a
| Species (selector) | Default | Hover | Focus-visible | Active/press | Disabled | Loading | Success/toggle |
|---|---|---|---|---|---|---|---|
.btn-primary/.btn-ghost | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✓ .done |
.copy (card) template:249 | ✓ | ✓ | ✓ | ✓ | — | — | ✓ .done |
.nav-cta template:60 | ✓ | ✓ | ✓ | ✗ | — | — | — |
.iconbtn (run/seq/link/paste) | ✓ | ✓ | ✓ | ✗ | ✗ | — | ✓ .on |
.fixbtn (Studio) studio:162 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ (JS) | ✓ .done |
.cc / .cp (copy) | ✓ | ✓ | ✓ | ✗ | — | — | ✓ .done |
.seqcopy template:276 | ✓ | ✓ | ✓ | ✓ | — | — | ✓ .done |
.view quick-view (<button>) | ✓ | ✓ | ✓ | ✗ | — | — | ✓ chev+aria |
Links a (nav/footer) | ✓ | ✓* | ✓ | — | — | — | — |
| Inline prose links (color-only) | ✓ | ✗ | ✓ | — | — | — | — |
.search input template:216 | ✓ | — | ⚠ suppressed | — | — | — | ✗ error |
.ctxbody input ×4 template:209 | ✓ | — | ⚠ suppressed | — | — | — | ✗ error |
.pastebox input/textarea studio:84 | ✓ | — | ⚠ overridden | — | — | — | ✗ error |
#ghref repo input | ✓ | — | ⚠ overridden | — | — | ✓** | ✗ (note only) |
Checkbox studio:118 | ✓ | ✗ | ✓ | — | — | — | ✓ :checked |
Toggle chips [aria-pressed] | ✓ | ✓ | ✓ | ✗ | — | — | ✓ pressed |
.drop zone studio:64 | ✓ | ✓ | ✓ | — (.over) | — | — | — |
Clickable cards (.pcard/.storecard/.seqcard/.lens) | ✓ | ✓ | ✓ | ✗ | — | — | — |
\* some a have hover, some don't. \** button shows "loading…", input itself unstyled.
The broken/empty cells that matter: the four ⚠ input rows (focus), the absent input error column (whole column empty), and the absent disabled style on .btn (only .fixbtn has one).
.search input{…border:0;outline:0} (template.html:216). The landing page's only focus rule is the global :focus-visible{outline:2px solid var(--amber)} (template.html:50), specificity (0,1,0). The input's own rule is (0,1,1) and sets outline:0, so it wins even while focused — the ring never renders. A keyboard user tabbing into the site's primary control (search across 75 briefs) sees nothing. There is no input:focus-visible override on the landing to rescue it (grep = none). Fix: add .search input:focus-visible{outline:2px solid var(--amber);outline-offset:2px} (or drop the outline:0).
.ctxbody input{…outline:0} (template.html:209) — #cstack, #ctype, #cstage, #cnotes. Specificity (0,1,1) > global (0,1,0) → focus ring suppressed. Same one-line fix per the input group.
Studio does declare input:focus-visible{outline:2px…} (studio.html:82), but the base .pastebox input,.pastebox textarea{…outline:0} (studio.html:84) has equal specificity (0,1,1) and comes later, so the cascade picks outline:0 in the focused state too. The intent is there; source order defeats it. Fix: move the :focus-visible rule after the base, or raise its specificity (.pastebox input:focus-visible). Same in vitals.html:69/71.
The GitHub-repo loader can fail (bad slug, private repo, network); today failure is signaled only by .note text (studio.html:226) while the offending input stays visually neutral — no red border, no aria-invalid. Add an .is-error input treatment tied to the same failure path that writes .note.
.btn has no disabled styleOnly .fixbtn:disabled (studio.html:169, opacity:.45;cursor:not-allowed) exists. If any .btn/.copy/.iconbtn is ever disabled it is indistinguishable from enabled — a latent trap. Define one shared :disabled/[aria-disabled] treatment.
:active{transform:scale(.96/.97)} is defined only on .btn, .copy, .fixbtn, .seqcopy. The many .iconbtn controls (mark-run, +seq, link, paste, clear), .nav-cta, and .view have no press state, so some buttons "give" under a click and others feel dead. Extend the :active scale to all button species (one rule on a shared class).
Anchors like <a style="color:var(--fc)">Report Studio</a> (build.py:784) and the Studio footer links carry color but no underline and no :hover, so link-ness rests on hue alone (ties to COLOR C8: color as sole signal). Add text-decoration or a hover underline to inline content links.
.done/flash swaps text to"Copied ✓" within one frame (DETAIL_JS, template copyText). No dead air.
ghload/demo set disabled=true +textContent="loading…" then "loaded ✓" (studio.html:611,623,630).
.fixbtn disables at zero selection (studio.html:490) with the counttext explaining why — good disabled clarity.
(no async), so no skeletons are needed.
| Animation | Job | Duration · Easing | Verdict |
|---|---|---|---|
.card rise entrance — fade + translateY(8px), staggered ≤420ms (template:240,242,848) | orient — rows entering the list | .4s · cubic-bezier(.2,.7,.3,1) | Keep, but consider skipping the stagger on filter re-render (it replays on every keystroke). |
Card/panel hover lift translateY(-1/-2/-3px), .seqcard translateX(3px) | confirm the hover target | .15s · ease | Keep |
:active press scale(.96/.97) | confirm the press | .1s · ease | Keep — extend to all buttons (S6) |
Chevron rotate(90deg) (.view, .full, .ctx) | connect open ↔ closed | .2s · ease | Keep |
Button filter:brightness() hover | confirm | .15s · ease | Keep |
.card.flash outline on hash-nav | orient — "here's the one you clicked" | — | Keep |
Text/.done swaps ("Copied ✓") | confirm the copy | JS, ~1.6s revert | Keep |
Durations in use: .1s ×6, .15s ×38 (the workhorse), .2s ×4, .4s ×1 — all inside the 100–300ms UI band (entrance at 400ms is fine). Easings: the implicit default ease on every transition + one cubic-bezier(.2,.7,.3,1) for the entrance. Essentially one motion vocabulary already — it just isn't named. Nothing here is decoration-without-meaning; nothing to cut.
Reduced motion (strength): honored in all four files — @media(prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}} (template:320, build:493) plus scroll-behavior:auto; Studio/Vitals kill transitions (studio:182, vitals:89). No autoplay, no parallax. Solid.
The values are already consistent; give them names so new components inherit them instead of re-typing .15s:
--dur-press: .1s; /* :active */
--dur-state: .15s; /* hover, color/border/filter */
--dur-move: .2s; /* chevrons, expand/rotate */
--dur-enter: .4s; /* list entrance */
--ease: ease; /* all state transitions */
--ease-enter: cubic-bezier(.2,.7,.3,1); /* entrance only */
Then the fixes above are cheap: one shared :focus-visible rule for inputs (S1–S3), one .is-error input state (S4), one :disabled treatment (S5), and one :active on a shared button class (S6).
Report only — states traced from real selectors; nothing edited. The must-fix is S1–S3 (input focus visibility) — it's an accessibility defect on the primary search control, not polish. Which fixes should I make?