Design products that reshape the future.
Motion-first. AI-readable. Built in the open.
Amaca is my personal design system. It started as a side project — a place to practice the kind of system I'd been wanting to build for years (full story here). It's grown into something more specific: a design system where motion is a foundation, not a finish, and where every rule is written so an AI agent can read and apply it as cleanly as a human can.
What's inside
§ 01.1The system in three layers — hard tokens at the base, components built strictly from them, and the applied patterns that compose both. Each layer cites the one below it, never the reverse.
Hard tokens
The tokens everything else is built from. Color, type, spacing, motion, and the rules that hold them together.
Build blocks
The primitives I reach for when designing a product surface. Buttons, forms, cards, and the patterns that emerge from them.
In practice
How the system performs in practice. Data viz, voice, accessibility, and a worked case study template using every primitive in the kit.
The mark
§ 01.2The symbol: three isometric cubes in motion forming a triangle, one cube per product. It ships as a fixed asset, and the shell renders it inside the brand tile.
Three isometric cubes forming a triangle, in motion.
Each cube is a product. The triangle is a system — components snapping together, breaking apart, reassembling. The motion is intrinsic, not applied: the mark is a Lottie, a JSON file describing every rotation and every transition. The logo follows the same logic as the system itself — written, not decorated.
Five rules. Exceptions earn their keep.
These are the principles I keep returning to. They're not absolute — design systems live in tension between rules and exceptions — but they're the questions I ask first when something feels off.
The obvious answer, on purpose.
If a user has to decode an icon or guess at a label, we failed. Metaphors earn their keep or get cut.
Every decision shows its work.
Case studies document the path, not just the destination. Sketches, data, dead ends — the version that didn't ship is part of the file too.
Rigor the eye picks up before the mind does.
Spacing, timing, typographic rhythm — when they're locked, the work reads as considered before a single word is parsed. The rules are in Foundations.
Restraint is what makes accents land.
Most of the surface stays neutral. Color, motion, weight — they only show up where the work needs them, never as decoration.
Interfaces are not static. Neither is this document.
The way something arrives, settles, responds carries meaning. Motion in this system is consistent and intentional — every transition follows the same rhythm, so the whole document reads as one instrument.
Every entrance in this system breathes on the same curve — CUBIC-BEZIER(0.16, 1, 0.3, 1), the signature ease, living at --ease-decel since v2.0.0 — so the whole document feels like a single instrument, played deliberately.
Your design system, compiled for every tool.
Pick your tool, grab the file — or the turnkey .zip — and drop it in. Every download is token-only: it makes any AI reference your tokens by name instead of inventing values. Same source on every surface — the spec is DESIGN.md, the tokens are tokens.css / tokens.dtcg.json. MIT.
Pick your tool
§ 03.0Start with the three on top, or jump to your stack. One download per tool — a small bundle with the file, the tokens/spec it needs, and a usage README inside.
Claude — skill + CLAUDE.md
The rules as a Claude skill (tokens bundled) + a CLAUDE.md for Claude Code.
amaca-frontend.skillFigma — /amaca-figma skill
In-canvas custom skill: builds with Amaca Variables, audits what landed on canvas, repairs in a verify loop. Fetch-first — it never invents values.
amaca-figma.mdGoogle design.md
The spec in the Google Labs design.md standard — paste into Stitch's project box.
DESIGN.mdInstall it, don't unzip it
One package, two manifests, one skill — Agent Plugins 1.0 for Cursor, Codex, Copilot, VS Code and Kiro, plus the Claude Code manifest. /plugin marketplace add angelomacaione/amaca-design
AGENTS.md
One repo-root file read natively by 28+ agents (Codex, Cursor, Copilot, Windsurf, Aider…).
ChatGPT · Gemini
Self-contained paste-in with the token values inlined — no companion files.
Cursor · Copilot
Scoped rules — amaca-core.mdc always on, amaca-html.mdc and amaca-react.mdc on the files they govern — plus .github/copilot-instructions.md and the agent skill Cursor reads from .agents/skills/.
v0 · Lovable · Base44
Same self-contained paste-in — these builders have no filesystem.
Tailwind v4 @theme
Every token as a @theme block — generates bg-magenta-500, p-4, rounded-lg.
Raw CSS variables
tokens.css (vars + reset) + components.css — var(--token) everywhere, no build step.
DTCG pipeline
Tokens as W3C DTCG JSON — stable 2025.10 format — for Style Dictionary, Tokens Studio, any DTCG-aware build.
How to install
§ 03.1Per-runtime. Pick the row that matches your tool — the first three mirror the hero cards above.
-
01 · COWORK / CLAUDE CODE
ANY ANTHROPIC RUNTIMEDrop the .skill bundle in.
Settings → Capabilities → Skills → upload
amaca-frontend.skill. Restart the session. Invoke withamaca-frontendor just describe an Amaca task — the skill auto-triggers on context. -
02 · FIGMA
— AGENT SKILLInstall it like the Claude skill, invoke /amaca-figma.
In any Figma file: prompt box → attachment icon → Skills → Add skill → upload
amaca-figma.md. Then/amaca-figma: it fetches the live spec first, generates with Amaca Variables, and self-audits in a verify loop — or audits a selection on canvas evidence. Code-side sync (tokens pushed as Variables over the Figma MCP) stays available through theamaca-frontendskill. -
03 · GOOGLE STITCH
— DESIGN.MDPaste the spec into the project box.
Download
DESIGN.md— the spec ships in the Google Labsdesign.mdstandard. On Stitch's Start with your project screen, paste it into the existing DESIGN.md box (or upload the file). Pick the most capable model for brand fidelity.Frontier-tier model required. On lighter models the spec is read as passive context, not instructions — see § 03.3 capability tiers.
-
04 · CURSOR / VS CODE / WINDSURF · CONTINUE / AIDER
— EDITORS & ASSISTANTSDrop the spec at your tool's rules path.
These read markdown rules from your repo. Unzip
amaca-ide.zipat the repo root — since v3.1.0 it ships core + per-target rules, attached only to the files they govern:- Cursor
.cursor/rules/amaca-core.mdc · amaca-html.mdc · amaca-react.mdc - Cursor · Codex (skill)
.agents/skills/amaca-frontend/ - VS Code (Copilot)
.github/copilot-instructions.md + instructions/amaca-*.instructions.md - Windsurf
.windsurf/rules/amaca.md - Aider
CONVENTIONS.md - Continuerules in config
The runtime reads it as instructions and grounds on the spec.
Model-gated — fidelity tracks the model behind the editor. See § 03.3.
- Cursor
-
05 · GEMINI ADVANCED
— GEMSSpec as knowledge, instructions as system prompt.
Create a new Gem. Paste the SKILL.md body (without YAML frontmatter) into Instructions. Upload DESIGN.md as the Gem's knowledge file. Save. That Gem now speaks Amaca for any frontend task.
Frontier-tier model required. On lighter models the spec is read as passive context, not instructions — see § 03.3 capability tiers.
-
06 · CHATGPT
— CUSTOM GPTSame pattern as Gems.
Create a Custom GPT. SKILL.md body as instructions. DESIGN.md as knowledge file. Same result.
Frontier-tier model required. On lighter models the spec is read as passive context, not instructions — see § 03.3 capability tiers.
-
07 · ONE-OFF
ANY LLM CHATAttach and prefix.
Drop DESIGN.md inline in the chat. Prefix your prompt with "Use this as the canonical reference for tokens, components, and voice — generate strictly compliant frontend code." Lighter than the skill but works when nothing else does.
Frontier-tier model required. On lighter models the spec is read as passive context, not instructions — see § 03.3 capability tiers.
-
08 · REPLIT / LOVABLE / V0 / BOLT / BASE44
— APP BUILDERSFeed the tokens, not the skill.
Prompt-to-app builders impose their own stack (usually React + Tailwind + shadcn) and treat a pasted spec as soft context. Give them the Tailwind
@themetokens as the project foundation — those travel; the enforcement workflow does not. Replit is closest (its Agent reads project files — drop a rules file). Base44 is the tightest (locked stack). Compliance is model- and platform-gated: a frontier model + tokens-as-foundation gets most of the way — verify with the § 03.3 scan.
What's inside the spec
§ 03.2The DESIGN.md the skill bundles, and the raw download serves, is organized in the Google design.md grammar:
| Block | Contents |
|---|---|
## Overview | How to use the file, quick reference, token roles in the YAML front-matter. |
## Colors | Every named hex, scale step, gradient, and the 85/10/5 proportion law. |
## Typography | Display through caption — size, weight, leading, tracking. Family fallbacks. |
## Layout · Shapes · Elevation | The 4-px scale, radius steps, shadow specs. |
## Motion | Durations, easings, the single curve everything rides on. |
## Components | Buttons, forms, cards, badges, navigation, accordion, chat, loader, diagrams, checkbox, select — props, states, do/don't. |
## Do's and Don'ts | The five principles, the anti-patterns, and the rules for AI agents. Voice included. |
## Accessibility | Color pairs with ratios, focus rules, reduced-motion contracts. |
## Changelog | Every release, top to bottom. Same as § 24.1. |
Model capability tier
§ 03.3 · what to expectThe skill encodes a 7-step workflow and 14 verification checks. Whether the runtime model executes them rigorously depends on its reasoning capacity, instruction-following fidelity, and how it grounds on uploaded files.
Production-grade enforcement.
Claude Opus 4.x, Claude Sonnet 4.x, GPT-5, Gemini 3 Pro. Explicit tool calls to read DESIGN.md, syntactic token registry checks, gap-surfacing protocol invoked correctly, voice register maintained.
Validated empirically: Gemini 3.1 Pro held strict Amaca compliance on a design-tool runtime.
Usable output, partial compliance.
Claude Haiku, GPT-5 mini, Gemini 3 Flash. Token references are mostly canonical, principles are cited, structure is sound. Expect two weaknesses:
- 01Class name proliferation. New classes where canonical components would compose.
- 02Micro-inconsistencies. Radius drift, occasional minor token substitution, edge cases on multi-component composition.
#XXXXXX instead of var(--color)), raw px values (16px instead of var(--s-4)), raw duration literals (600ms instead of var(--d-*) — ratified continuous loops are the only exception), --ease-spring on effect properties (color, background, opacity, shadow — § 08.3 RIGID), new .classname introductions outside the canonical § 3 registry, and missing @media (prefers-reduced-motion: reduce) overrides. Catches most drift regardless of model tier.
magenta-500 (#F051D5) defaults to obsidian-950 — 6.5:1, AA. One ratified exception (v2.6.0, § 6.3): .btn-primary carries obsidian-050 at ≈2.8:1 — kept on perceptual grounds, scoped to that selector only. Every other magenta fill — chips, badges, non-button CTAs — stays dark-text.
Beyond model size, the runtime's file-grounding architecture matters. Anthropic-stack tools (Claude Code, Cowork, Cursor with Claude, Continue with Claude) read uploaded files via explicit tool calls — what you upload is what gets read. Consumer chat tools that abstract file uploads as context embeddings may produce performative compliance: the model announces reading DESIGN.md but the actual grounding is shallow.
For non-Anthropic runtimes the most reliable deployment is the platform's custom-assistant wrapper — Gemini Gems, ChatGPT Custom GPT — where instructions and knowledge files load at session level, not per-message context.
React — native
§ 03.4Tailwind v4 @theme is native: tokens become first-class utilities (bg-magenta-500, rounded-md) straight from tokens.css — no Google spec, no build hack. React is first-class in amaca-frontend v1.1.
bg-magenta-500 · text-obsidian-950 · rounded-md
Every token resolves to a utility class against the same var() chain the CSS surface uses. No CSS modules, no translation layer.
@import "amaca-design/styles/theme.css";
One line wires the full token scale into a Tailwind v4 project. theme.css is a @theme projection of tokens.css — var() aliases, no duplicated literals.
design.md / W3C DTCG
tokens.json
The Google design.md / W3C DTCG export stays available as an optional, generated projection for lint, diff, and cross-tool interop — not the thing that unlocks React. Same one-way model: derived from tokens.css, not authored separately.
Versioning & license
§ 03.5Two SemVer streams, one rule each.
Pinned to the system release — the version in the site header is the version of the spec this page ships. Strict SemVer from v1.2.0 onward — minor bumps for additions, major for breaking renames.
Decoupled from spec version. The skill's own SemVer covers workflow changes — new posture rules, verification checks, deploy targets. v1.1.9 re-bundles on the current spec — multi-target across HTML, React, and Figma, and teaches the stat count-up motion rule. The in-canvas Figma skill (amaca-figma) versions separately.
Use it in client work, internal tools, your own systems. Attribution appreciated, not required.
Magenta on Obsidian, one accent, twelve neutrals.
The palette is built around a single primary accent — magenta — set against a calibrated obsidian neutral scale. Two supporting colors (cyan and petrol) carry secondary emphasis when needed, and four semantic colors are reserved exclusively for feedback. The system is dark-first because most of my product work lives in dark interfaces; the obsidian scale is engineered to carry typographic weight without strain.
Brand · signal
§ 04.1The magenta range. Use for primary actions, links, data emphasis, and gradients. Never for body text.
Obsidian · neutrals
§ 04.2The working surface. Twelve-step scale from page bedrock to bone. Body text lives at 100; metadata at 300; edges at 700.
Secondary · electric cyan
§ 04.3High-voltage cyan. Use for focus accents, highlighted states, and secondary emphasis — never as a second primary.
Tertiary · petrol
§ 04.4Deep petrol teal. Used for default badges, quiet tags, and neutral chips that shouldn’t read as brand but still need color.
Semantic · signals
§ 04.5Reserved for feedback only — success, warning, danger, info. Never use as decoration.
Usage matrix
§ 04.6One row per color token: value, sanctioned use, and the misuse to refuse. The 85/10/5 law decides the proportions; this matrix decides the placements.
| Token | Value | Use | Don't |
|---|---|---|---|
--magenta-500 | #F051D5 | Primary CTA, focus ring, emphasis | Body text, large backgrounds |
--grad-signal | magenta 500→300 | Headlines, accents, data highlights | More than one per screen |
--obsidian-950 | #07090B | Page background only | Card surfaces |
--obsidian-100 | #E3E7EC | Body text on dark | Pure white — never use #FFF |
--danger | #FF5B5B | Destructive actions, form errors | Warnings or neutral states |
The 85/10/5 law
§ 04.7The proportion rule for every screen, slide, and case study. Obsidian is the ground; brand and signal are accents.
RULE≥ 85% Obsidian, ≤ 10% Magenta + Cyan combined, ≤ 5% semantic. If a layout violates this rule, it's decoration, not hierarchy.
Satoshi, at every scale.
One typeface carries the whole voice. Light through Black, with a monospace companion for metadata and engineering context. Tight letter-spacing at display sizes, loose leading at body.
Display scale
§ 05.1The display end of the scale — hero and headline steps with size, leading, tracking, and weight declared per row. Negative tracking is part of the voice at this size, not a tweak.
Black · 900
Reshape.
Bold · 700
Design that compounds.
Bold · 700
A case for quiet precision.
Bold · 700
Systems outlive the work
Bold · 700
Evidence over opinion
Semibold · 600
The obvious answer, made inevitable
Text scale
§ 05.2The text scale, lede to micro: the steps running copy actually uses. Each row declares its size and leading — reference the token, never the number.
Regular · 400
Lede paragraph. Sits under page titles. Used once per page to set the register before body text takes over.
Regular · 400
Body copy. Reads comfortably at 62–72 characters per line. Default for paragraphs, long-form notes, and case studies. Leading stays loose to respect the dark background — tight type on dark surfaces strains eyes.
Regular · 400
Small text. Captions, table rows, secondary UI copy.
Regular · 400
Caption · image credit, footnote, timestamp.
Weights available
§ 05.3The Satoshi weights the system ships. One typeface carries every register — the mono voice is Satoshi too, tracked and upper-cased, never a second family.
Pairing rules
§ 05.4Numbered rules for combining the scale: tracking bounds, weight pairings, the register mixes that hold. Pair inside them.
A 4-pixel grid, held religiously.
Every margin, padding, and gap snaps to a multiple of 4. Thirteen steps carry the whole system, from hairline to hero.
Scale
§ 06.1The spacing scale: a 4px grid from --s-0 to --s-32. Every gap, inset, and rhythm in the system is one of these steps.
Where to reach
§ 06.2Where to reach on the scale for the common cases — icon gaps, button padding, card rhythm, section breath. The fastest route from a layout decision to its token.
| Context | Token | Notes |
|---|---|---|
| Inline icon–label gap | --s-2 | 8px. Always. |
| Button padding (vertical) | --s-12 | 12px standard, --s-8 mini. --s-16 large. |
| Card interior padding | --s-6 | 24px standard. --s-8 on hero cards. |
| Subsection vertical rhythm | --s-20 | 80px between subsections. |
| Page top padding | --s-12 | 48px below top bar. |
| Section → section | --s-24 | 96px breathing room on long pages. |
Soft corners, hard shadows.
Radius stays small — 8 to 12 px is the working range. Shadows are low-key, inset-first, reserved for depth hierarchy not decoration.
Radius scale
§ 07.1The radius scale, --r-none to --r-full. Sub-4px hairlines are a ratified closed list (§ Shapes in the contract), not scale members.
Elevation
§ 07.2Four shadow levels and the two brand glows. Elevation on this ground is tonal — an inset highlight over a soft drop — never a hard outline.
Motion is feedback, not decoration.
Six durations, four easings. Standard for UI, Spring for playful reveals, Accel for exits, Decel for entrances, Draw for data reveals.
Durations × easings
§ 08.1The motion vocabulary at a glance: four easings and where each belongs, with the durations they usually ride. The pairing rules are in § 08.3.
Click to re-trigger animations.
Duration tokens
§ 08.2Six duration tokens, each named for a job. Stat count-up is a RIGID rule: numeric stats and KPIs compose from zero to their target riding --d-draw · --ease-decel and land exactly on the final value; under reduced motion they jump straight to it.
| Token | ms | Use |
|---|---|---|
--d-instant | 100 | Hover state, tap feedback |
--d-quick | 200 | Button states, focus rings |
--d-base | 350 | Default — most UI transitions |
--d-slow | 600 | Page enters, panel slides |
--d-scene | 900 | Hero reveals, orchestrated sequences |
--d-draw | 1200 | Chart draw-on, count-up, progressive data reveals |
Easing tokens & pairing
§ 08.3The trigger decides the pairing: user-triggered states ride --d-quick · --ease-standard; system-triggered transitions — content entering, leaving, or moving without a direct pointer cause — ride --d-base · --ease-decel. Spatial vs Effects is a RIGID rule: every animated property has a type, and the type gates the easing. Spatial properties — transform, position, size, shape — may use any easing, including --ease-spring. Effect properties — color, background, opacity, shadow — use no-overshoot curves only; --ease-spring on an effect property is off-system, and greppable per line.
| Token | Curve | Use |
|---|---|---|
--ease-standard | cubic-bezier(0.22, 1, 0.36, 1) | Default for user-triggered states — hover, press, focus |
--ease-accel | cubic-bezier(0.7, 0, 0.84, 0) | Exits, dismissals |
--ease-decel | cubic-bezier(0.16, 1, 0.3, 1) | Default for system-triggered transitions — entrances, reveals. The system signature |
--ease-spring | cubic-bezier(0.34, 1.56, 0.64, 1) | Pop-in, delight, overshoot — spatial properties only |
Motion per component
§ 08.4Every component that moves documents its motion in one fixed schema — same four columns everywhere, no prose specs. The Motion column accepts token pairs only; the Property column declares the type (spatial / effect) so the rule above is checkable on the row itself; the Reduced motion column uses a closed vocabulary — instant · fade-only · none — recording only how the component lands when the global kill-switch (§ 21.3) fires. Seeded with the site-canonical patterns; every new component ships its rows here — and in DESIGN.md § Motion — in the same release. Continuous loops are a ratified class: they run while the component is mounted, on per-component literal clocks — the --d-* scale describes perceived transitions, not idle rhythm. Mechanical loops ride linear (or steps), never an easing curve; every loop is ratified by its own loop row above, and a loop literal without a row is drift — the raw-duration grep exception covers exactly this list, nothing else.
| Component | Trigger | Property (type) | Motion | Reduced motion |
|---|---|---|---|---|
Entrance reveal [data-fade] | enter viewport | opacity (effect) · translateY 12px→0 (spatial) | --d-slow · --ease-decel | instant |
| Accordion | open / close | panel grid-rows (spatial) · chevron rotate 90° (spatial) | --d-base · --ease-decel | instant |
| Accordion | open | panel body opacity (effect) | --d-quick · --ease-decel · delay 80ms | instant |
| Accordion | hover / open state | trigger + chevron color (effect) | --d-quick · --ease-decel | instant |
| Tabs | select | indicator transform + width (spatial) | --d-base · --ease-decel | instant |
| Tabs | panel switch | panel opacity (effect) · translateY 6px→0 (spatial) | --d-quick · --ease-standard | instant |
| Card | hover | border-color · box-shadow (effect) | --d-quick · --ease-standard | instant |
| Lightbox | open / close | backdrop + content opacity (effect) — no scale-in | --d-quick · --ease-standard | instant |
| Chat | message enter · container | scale 0.92→1 (spatial) | --d-base · --ease-spring | instant |
| Chat | message enter · container | opacity (effect) | --d-base · --ease-standard | instant |
| Chat | message enter · content | opacity (effect) · translateY 3px→0 (spatial) | --d-quick · --ease-standard · delay 80ms | instant |
| Chat | roll-off .is-out | opacity (effect) · translateY −8px (spatial) | --d-quick · --ease-accel | instant — still hides |
| Chat | roll-off .is-out | max-height + margin collapse (spatial) | --d-base · --ease-accel | instant — still hides |
| Chat | typing dots (loop) | translateY 0→−4px→0 (spatial) · opacity (effect) | --d-slow · --ease-standard · stagger 0/160/320ms | instant — rest state |
| Chat | send idle → ready | background + color (effect) | --d-quick · --ease-standard | instant |
| Loader | success | check scale-in (spatial) · anim fade + scale 0.7 (spatial/effect) | --d-base · --ease-spring | instant |
| Loader | skeleton handoff | loader → skeleton swap | after a --d-scene wait | instant swap, shimmer disabled |
| Diagrams | enter viewport · nodes | opacity (effect) | --d-base · --ease-decel · stagger ~70ms | instant — straight to resolved |
| Diagrams | enter viewport · edges + clusters | opacity (effect) | --d-base · --ease-decel · after nodes, +40ms each | instant — straight to resolved |
| Stat / KPI | enter viewport | text content count-up 0→target (effect) | --d-draw · --ease-decel | instant — jump to final |
| Loader | wait (loop) | brand mark GIF (spatial) | media clock (GIF/Lottie) · infinite | hidden — fallback ring shown |
| Loader | wait (loop) | fallback ring rotate (spatial) | 1400ms · linear · infinite | ratified exception — keeps spinning |
| Loader | label cycle (loop) | label opacity crossfade (effect) | 2400ms cycle · 200ms crossfade | instant swap |
| Skeleton | wait (loop) | shimmer sweep (effect) | 1600ms · linear · infinite | disabled |
| A11y caret (demo) | idle (loop) | caret opacity blink (effect) | 1s · steps(1) · infinite | stopped by the kill-switch |
Twelve columns, 24px gutters.
A standard 12-col grid with a 1180px max content width. Sidebar is fixed; content breathes.
The grid
§ 09.1The page grid — twelve columns compressing to eight, then four. Columns are page anatomy; the component grid presets in § 09.3 handle everything inside a section.
Breakpoints
§ 09.2The four breakpoints with their columns and gutters. Layout changes at these widths and nowhere else.
| Name | Min width | Columns | Gutter |
|---|---|---|---|
| Mobile | 0 | 4 | 16px |
| Tablet | 720 | 8 | 20px |
| Desktop | 1024 | 12 | 24px |
| Wide | 1440 | 12 | 24px, capped at 1180 content |
Common layouts
§ 09.3The compositions the system ships — sidebar and content, hero three-up, editorial split — and how each reflows down the breakpoints.
Action, ranked.
One primary per screen. Everything else recedes. Five variants × three sizes × states.
Variants
§ 10.1The button variants, one job each. One primary per screen; everything else supports it.
Sizes
§ 10.2Three sizes — 32, 40, 48 — and the default is the middle one. Size signals density, not importance: the variant does that.
With icons
§ 10.3Icon-and-label compositions, leading and trailing. The gap is --s-2, always; the label stays.
Forms that stay out of the way.
Low-contrast edges. Focus glow carries the attention load. Labels in mono to keep them distinct from content.
Fields
§ 11.1Every field in one place — text, search, select, textarea — labels persistent, helper text below. States live in the contract's state matrix; error is aria-invalid, never color alone.
Time input
§ 11.2HH:MM, 24h. Auto-formats as you type — the colon is inserted after the second digit, and each position is bounded so an invalid time is impossible to enter. No type="time": the native picker doesn't respect the system.
2
H2max 3 if H1 = 2, else 0–9
M1max 5
M20–9
maxlength="5"+inputmode="numeric"are required.- The
:is inserted automatically — users only type digits. - Optional by default; can be marked required per context.
- Focus & error states inherit from § 11.1.
Date input
§ 11.3DD/MM/YYYY, single date. Typed input is digit-by-digit masked (slashes auto-insert) and the trailing icon opens a calendar popover that stays in sync. No native type="date" — the browser picker doesn't respect the system. Optional min/max attributes constrain both the typed value and the picker.
2026-05-01 → 2026-12-31.
3
D2max 1 if D1 = 3; if D1 = 0, must be 1–9; else 0–9
M1max 1
M2max 2 if M1 = 1; if M1 = 0, must be 1–9; else 0–9
Y1–40–9
maxlength="10"+inputmode="numeric"required.- Slashes are inserted automatically — users only type digits.
- Real-date check (e.g. 31/02 rejected) runs on blur, not while typing.
data-min&data-maxin ISOYYYY-MM-DD; both optional.- Trailing icon & ↓ on the input both open the picker.
- Locale: English month/day labels, week starts Monday.
- Single date only. For range selection, see § 11.4.
Date range
§ 11.4Two coupled inputs sharing one popover. Two months side-by-side on desktop, stacked on mobile. Click 1 sets the start, click 2 sets the end — clicking before start swaps. Footer presets cover the analytics defaults; the helper line auto-counts inclusive days.
- Click 1 → start. Hover preview shows tentative range.
- Click 2 ≥ start → commits end.
- Click 2 < start → swaps (becomes new start).
- Same-day click twice → single-day range.
- Esc → cancel pending selection & close.
- Two inputs:
data-range-start&data-range-endinside adata-range-groupwrapper. - Same digit-by-digit mask as § 11.3.
- Popover anchors to the start input. 580px wide, collapses to 280px stacked under 600px viewport.
- Helper auto-renders
N days(inclusive count) on commit. data-min&data-maxon either input bound the whole range.
Choice controls
§ 11.5Three controls, one family. Shape carries the meaning before colour does: square with a tick, circle with a dot, track with a knob. What separates them is not how they look but when they commit — the switch applies immediately, the checkbox and the radio apply on submit.
Containers with metadata bones.
Every card carries a micro-header with context — project code, date, index. The case-file influence sits here, quietly.
Project card
§ 12.1The project card: micro-header, media, title, body, and at most one action. Surfaces ride the card ramp; the media scales on hover, the card itself never lifts.
Arc · Automotive HMI
Rethinking the center stack for a next-gen EV. 18 months, 4 platforms.
Project Orion — IoT platform
Pending state card. This payload is merely illustrative.
Field · observability app
On-call dashboards for a SaaS infra company.
Click to re-trigger animations.
Stat card
§ 12.2The stat card: a quiet data surface on the same ramp as the project card. One number, one accent at most, and the delta keeps its sign — color never carries direction alone.
Click to re-trigger animations.
Small labels, loud signals.
Always Satoshi, always paired with a dot when live. Badges are feedback; they're never content. Alerts, toasts and tooltips are the rest of the feedback family — the same discipline at three different distances from the reader.
Status variants
§ 13.1The badge variants: semantic color plus the dot. Shape and label carry the state; the tint reinforces it.
In context
§ 13.2Badges in running context — rows, cards, headers — where they annotate a noun rather than announce a state.
Alert
§ 13.3Inline, in flow, addressed to the whole surface. The fill never changes — semantics ride the 2px left rule and the icon, so the accent budget stays spent on actions, not on furniture. An alert is never dismissible — not on a timer, and not by the reader either.
tokens.css line 47 — unterminated custom property.Click to re-trigger animations.
Toast
§ 13.4What an alert becomes when the message is about an action that just happened somewhere else. Transient, stacked bottom-right, three visible at most. Auto-dismiss is opt-in and bounded: only for a toast with no action that is not an error, and hover or focus pauses the countdown.
Click to re-trigger animations.
Tooltip
§ 13.5A label for something already visible. Never a container for content, never the sole carrier of meaning. Opens on hover and on focus — a tooltip reachable only by pointer fails the accessibility floor.
Do / Don't
§ 13.6The three feedback components differ by how long the message is true, and that is what decides which one you reach for. An alert states a condition; a toast reports an event; a tooltip labels a thing already on screen.
Let an alert clear when the condition clears. If the reader has to be able to make the message go away, it was never an alert — it was a toast.
Give an alert a close button. It lets someone hide a state that is still true, and the next reader inherits a screen that lies.
Carry the semantics on the left rule and the icon. The surface stays --obsidian-850 in every variant, so the accent budget stays spent on actions.
Tint the fill red or green to say "error" or "success". That spends the 10% band on furniture, and colour alone never carries meaning.
Mirror every toast somewhere durable. A toast is a notification of a result, never the record of it.
Auto-hide a toast that carries an Undo or an error. The countdown races the reader; hover and focus must pause it.
Keep one tooltip open at a time, opened on hover and on focus. Two bubbles on screen overlap, and an overlapped label cannot be read.
Put a link, a button, or anything required inside a tooltip. There is no accessible path to reach it, and it disappears the moment the pointer leaves.
Disclosure, on demand.
For dense reference content where users scan headers first and read selectively. Single-open by default; chevron rotates 90° on expand. Keyboard-operable, prefers-reduced-motion safe.
Default · single-open
§ 15.1Used inside case-study briefs and project requirements lists. Opening one panel closes the others — keeps the eye anchored. First item is open on mount so the surface never reads as inert.
Clean informative text that is displayed only when someone wants to deep dive more. Crucial to cluster topics under the same group.
Ship the platform as a living surface, not a release. Feature-flag everything; let production teams roll out capabilities to fleets in cohorts without redeploying the console.
Workstations run 10–14h shifts under variable lighting. Type stays at 16px minimum; primary actions clear AAA contrast on every surface; error states never rely on hue alone.
Every OTA action — operator, target VIN range, package hash, timestamp — is logged and exportable. The interface surfaces this trail in-context, not in a separate compliance tool.
Multi-open · FAQ
§ 15.2When users compare answers side-by-side. Add data-multi on the wrapper — items toggle independently. Use sparingly; default to single-open.
Yes — typically as a 12–16 week engagement with embedded engineering pairing. I don't ship throwaway redesigns; the system has to outlive my involvement.
Two weeks for a discovery sprint — research, audit, a short written direction. Anything shorter doesn't leave room to disagree with the brief, which is usually where the work is.
Remote-first, with on-site weeks for kickoff and major milestones. I'm based in Milan and travel comfortably across EU.
Anatomy & rules
§ 15.3Each part of the accordion with the rule that governs it — hit target, single control, chevron direction, duration by measured height.
| Part | Rule |
|---|---|
.acc-trigger | Full-row hit target — minimum 56px tall. Entire row is a single button; never put a separate toggle inside. |
.acc-chevron | Rotates from 0° (closed) to 90° (open) over 320ms on the standard ease. Color shifts to magenta-400 on open. No swap-icon. |
.acc-num | Optional. Mono index in the form NN / NN. Use when the set has a fixed, ordered count; omit on FAQ-style lists. |
.acc-panel | Animates via grid-template-rows 0fr → 1fr — no JS height measurement. Body fades in 80ms after the row starts opening. |
aria-expanded | Required on every trigger. Toggled in JS in lockstep with .is-open. The panel carries role="region" and an id referenced by aria-controls. |
| Reduced motion | Under prefers-reduced-motion: reduce, the row swaps instantly — no rotation, no fade, no grid transition. |
Conversations that earn their bubbles.
A chatbot interface lives or dies on cadence. Bubbles land with a small spring overshoot, typing indicators promise a reply, and the conversation never overcrowds the surface — older messages roll off after three exchanges. Own bubbles use --magenta-700 (deep brand), so --magenta-500 stays reserved for CTAs and focus.
Anatomy
§ 16.1Uniform --r-lg (12px) on all four corners — no tail. Avatar appears only on the first bubble of a bot streak; subsequent bot bubbles get a 32px invisible spacer so the column stays aligned. Own bubbles fill with --magenta-700, the deep brand accent — the brighter --magenta-500 stays reserved for primary actions elsewhere in the system.
Live demo
§ 16.2Three scripted exchanges with a chatbot. Each turn: typing indicator first, then the bubble lands. After the third exchange completes, older messages roll off the top so the surface never overcrowds. Hit replay to restart.
Composer
§ 16.3Textarea expands from 36px to 132px before scrolling. Send is dim while empty; lights up magenta on first keystroke. Attach is ghost-styled, hover only. Enter sends; ⇧ Enter inserts a newline.
Motion spec
§ 16.4Every transition resolves to a token, in the canonical § 08.4 grammar. Two-stage bubble entry: the container scales from 0.92 with an --ease-spring overshoot (spatial only — the opacity rides --ease-standard), then the inner content fades up 80ms behind.
| Trigger | Property (type) | Motion | Reduced motion |
|---|---|---|---|
message enter · container .chat-bubble | scale 0.92→1 (spatial) | --d-base · --ease-spring | instant |
| message enter · container | opacity (effect) | --d-base · --ease-standard | instant |
message enter · content .chat-bubble-content | opacity (effect) · translateY 3px→0 (spatial) | --d-quick · --ease-standard · delay 80ms | instant |
roll-off .chat-msg.is-out | opacity (effect) · translateY −8px (spatial) | --d-quick · --ease-accel | instant — still hides |
roll-off .chat-msg.is-out | max-height + margin collapse (spatial) | --d-base · --ease-accel | instant — still hides |
typing dots .chat-dot (loop) | translateY 0→−4px→0 (spatial) · opacity (effect) | --d-slow · --ease-standard · stagger 0/160/320ms | instant — rest state |
send idle → ready .chat-composer-send | background + color (effect) | --d-quick · --ease-standard | instant |
Do / Don't
§ 16.5Anti-patterns surfaced from early chatbot builds. Most are 85/10/5 violations dressed up as enthusiasm.
Own bubbles fill with --magenta-700 — the deep brand accent. Bright --magenta-500 stays reserved for CTAs and focus rings.
Paint own bubbles with --magenta-500. The bubble starts reading as a CTA and the actual primary action on the page loses its weight.
Anchor the typing indicator inside a bot bubble — same shape, same surface, same column. The reader's eye stays where the next message will land.
Float "Amaca is typing…" as a gutter caption below the conversation. For a single-bot UI it's noise; the avatar already says who's typing.
Show the avatar only on the first bot bubble of a streak. The rest of the streak gets a 32px invisible spacer so the column stays aligned.
Repeat the avatar on every bot bubble. It hijacks the eye and steals the budget from content.
Roll older messages off the top after the conversation passes ~3 exchanges. Fade up + collapse in --d-quick on --ease-accel.
Let the demo grow unbounded. A demo that fills the viewport buries the motion you're trying to document.
The logo, in motion as a promise.
A loader is a contract: something is happening, don't leave. The Amaca logo carries that contract at four scales — inside a button, beside an input, over a card, across the viewport. Under prefers-reduced-motion the Lottie steps aside and a single magenta ring spins in its place.
Scale
§ 17.1Four sizes, four tokens. The Lottie scales linearly — the file is vector. Hit-area follows the box exactly; no extra padding.
Variants
§ 17.2Five shapes for five waits. The standalone is the floor; everything else composes from it. Success swaps the Lottie for a check on --ease-spring when the wait resolves green.
Compositions
§ 17.3Four real-world placements. Click any Trigger to start the wait, watch the loader hold the surface, then resolve.
A submit handler is in flight. The button stays the same size; the label crossfades to the loader so layout never shifts.
Anatomy
§ 17.4Tokenized spacing between logo and label, padding on overlay variants, alpha on the backdrop. Each measurement maps to a system token — no raw px in component code.
Do / Don't
§ 17.5A loader has a job. If it isn't communicating a real wait, it's decoration — and decoration on a brand mark dilutes the mark.
Show the loader within 100ms of the action that triggered the wait. Late loaders read as a glitch, not feedback.
Pop the loader after the response already returned. A 200ms wait deserves no loader at all — show the result.
One full-page loader per route at a time. After 800ms hand off to skeleton frames so the page reads as structurally present.
Stack a section loader inside a card that already sits inside a full-page loader. The user can't tell which wait is which.
Pair every loader with an accessible name — aria-label on the loader root or a visible label. Screen readers announce role=status politely.
Use the loader as a decorative animation in the corner of a hero. The mark is in motion because something is loading — never because the page is.
Resolve to the success state when a wait completes green — the checkmark crossfades in over the logo on --ease-spring and the live region announces.
Let the loader keep spinning after the wait is over. A loader that won't stop is a bug surface, not a state.
Structure, drawn on the same grid.
Flowcharts and system maps from a textual source — auto-laid-out, then themed entirely from tokens. The library only ever sees values read off :root at runtime, so there is no hardcoded hex anywhere: the single source of truth stays tokens.css. Everything is neutral obsidian; exactly one magenta node marks the narrative entry. On scroll, nodes cascade in, then the edges that connect them.
Flowchart · node-edge
§ 18.1Decisions and paths. Rectangles are steps, diamonds are gates, dashed edges are loops. One magenta node — the entry — anchors where to start reading. Everything else recedes to obsidian.
System architecture · grouped
§ 18.2Box-and-line with grouping. Subgraphs become clusters — --obsidian-850 fill, --obsidian-700 border — so related boxes read as one zone. The one magenta node is the source of truth every surface derives from.
Semantic states · legend
§ 18.3The one case for color beyond magenta. When a node encodes a real state, it carries the semantic hue — but never alone: every state node also changes shape and spells the state in its label (§ 6 floor #1). A legend maps each pairing.
Anatomy
§ 18.4A <figure class="diagram"> wraps a .diagram-canvas[role="img"] (holding the rendered SVG) and a .diagram-caption. Surface, border, and radius are coherent with .card. Semantic-state diagrams add a .diagram-legend.
| Class | Role |
|---|---|
.diagram | Figure shell. --obsidian-900 surface, 1px --obsidian-700 border, --r-lg, --s-6 padding — the .card frame. |
.diagram-canvas | Render target. Carries role="img" + a descriptive aria-label; the library injects the SVG here. A textual source sits in a <template class="diagram-src"> until render. |
.diagram-caption | Mono micro, --obsidian-400, FIG-NN · title — echoes .card-meta. The index tints --magenta-400. |
.diagram-legend | Optional. Only on semantic-state diagrams. Each row pairs a shape swatch + a written label — color never carries meaning alone. |
Token map — applied through themeVariables populated from getComputedStyle(:root), plus classDef strings built from the same tokens at runtime. The theming config holds zero hex literals.
| Element | Token |
|---|---|
| Canvas / background | --obsidian-900 |
| Node fill | --obsidian-800 |
| Node border · text | --obsidian-600 · --obsidian-100 |
| Edge line + arrowhead | --obsidian-500 |
| Edge label | --obsidian-300 on --obsidian-900 |
| Cluster fill · border | --obsidian-850 · --obsidian-700 |
| Node radius · font | --r-md · Satoshi (--font-sans) |
| Focus node border | --magenta-500 · one per diagram |
| State nodes | --success · --warning · --danger |
Motion spec
§ 18.5Entrance reuses the system's established reveal patterns — nothing new, in the canonical § 08.4 grammar. An IntersectionObserver fires once (threshold 0.15, rootMargin 0px 0px -8% 0px, unobserve after fire); nodes cascade in, then edges and clusters. Opacity only: transforms on the generated SVG <g> groups compose with the cascade and break the auto-layout positioning. The hook runs after the async render resolves, not on first paint.
| Trigger | Property (type) | Motion | Reduced motion |
|---|---|---|---|
| enter viewport · nodes | opacity (effect) | --d-base · --ease-decel · stagger ~70ms | instant — straight to resolved |
| enter viewport · edges + clusters | opacity (effect) | --d-base · --ease-decel · after nodes, +40ms each | instant — straight to resolved |
Do / Don't
§ 18.6A diagram is structure, not decoration. Most of these are 85/10/5 violations or auto-layout breakers.
Keep every node neutral obsidian and spend the magenta once — on the entry or the narrative focus. One accent per diagram is the budget.
Color nodes by category with a per-type rainbow palette. The hues stop meaning anything and the surface goes loud.
Read tokens from :root at runtime and feed them to themeVariables and classDef. The source of truth stays tokens.css.
Paste hex literals into themeVariables. A copied #0B0E12 drifts the moment a token moves — it's a regression on day one.
Animate the entrance with opacity only, gated on an IntersectionObserver that fires once after render resolves.
Apply CSS transform to the SVG <g> groups. It composes with the auto-layout transforms and the diagram falls apart — set position via the SVG attribute, animate opacity only.
When a node encodes a real state, pair the semantic color with a distinct shape and a written label, and add a legend.
Fill nodes with gradients or bleed a diffuse magenta glow down the edges. Depth comes from token borders, never from decoration.
Charts that respect the surface.
Dark canvas. One accent hue per chart. Grid lines at 8% opacity. Numbers in Satoshi, axis labels in mono.
Line · area
§ 19.1Line and area: one series lit, the rest recede. The draw-on traces once on reveal — data motion is information, not decoration.
Click to re-trigger animations.
Bars · comparison
§ 19.2Bars for comparison, labeled at the value. One accent series against neutrals — 85/10/5 applies to data too.
Click to re-trigger animations.
Pie · composition
§ 19.3Composition as a donut: slices from the magenta ramp, the legend as the labelling surface, percentages counting in lockstep with the slices. Component spec: § 3.26 in the contract.
- Direct0%
- Organic0%
- Referral0%
- Social0%
- Color0%
- Spacing0%
- Typography0%
- Motion0%
Click to re-trigger animations.
Palette ramps
§ 19.4The two data palettes: a sequential magenta ramp for density, a closed categorical set for series — and nothing outside them.
Timeline · gantt
§ 19.5The gantt: workstreams across weeks, diamonds for milestones. Time reads left to right on the neutral grid; state reads from the fill.
Click to re-trigger animations.
Stepper
§ 19.6The stepper: completed segments fill, the active step breathes, upcoming stay quiet. Progress is shape and fill first, color second.
Click to re-trigger animations.
Progress bar
§ 19.7The linear meter: animates from zero to its target on view and lands exactly — the stat count-up contract, horizontal. The percentage is stated in text beside the bar.
Click to re-trigger animations.
Geographic · hex map
§ 19.8A dot-density world drawn as a honeycomb. The landmass isn't hand-drawn: it's the public-domain Natural Earth 1:110m dataset (loaded as TopoJSON via world-atlas), rasterised into the hex grid by canvas sampling — the canonical d3 dotted-map pipeline — and every node is placed at its true longitude / latitude. The land is a single neutral obsidian fill — quiet, the 85% — so the status hexes are the only thing that reads as data. Every fill is a token, no hardcoded hex; color is always paired with a label in the legend (§ 6 #1). One accent set, paired with labels — the landmass never competes. Hover lights the grid cell-by-cell; otherwise static — no auto-advance, no decorative loop (§ 6 #6).
Table
§ 19.9The plainest data surface in the system, and the one this documentation leans on most. No zebra striping — the row rule carries the scan, and a second device would double the noise. The header is a label, not a heading: mono-uppercase, the same register as a field label.
| Part | Treatment | Note |
|---|---|---|
| Frame | --obsidian-800 border, --r-md | Overflow hidden, so the radius clips the first and last rows |
| Header | Mono 10px on --obsidian-850 | Uppercase, --obsidian-400, weight 500 |
| Cell | 13px, padding --s-3 --s-4 | Left-aligned, including numbers |
| Row rule | 1px solid --obsidian-800 | The last row drops it |
| Inline code | .table code | Mono 12px, --magenta-400 |
| Secondary cell | .muted | This column, throughout |
Pure quantities are the one exception to left alignment: align them right, set tabular-nums.
How this system writes.
Amaca speaks in two registers. The live site is editorial, written for a designer reading on a Tuesday afternoon — paragraphed, contextual, first-person. The DESIGN.md on GitHub is the same system expressed differently — declarative, machine-readable, one rule per line. Both serve the design. Neither imitates the other. This section sets the rules for the editorial register. The DESIGN.md handles its own.
Voice attributes
§ 20.1The voice attributes, each with the grammar that enforces it — active voice, subject before verb, claims tied to evidence.
Direct
Active voice. Subject before verb. Say what happened, not what was happening.
Specific
Numbers when you have them, scope when you don't.
Honest
Name the tradeoffs. Name the dead ends. No mythologizing.
Considered
No filler, no flourish. Every sentence earns its place.
Do / Don't
§ 20.2Voice by example: the same message said in-system and off-system, side by side.
"This pattern didn't work in early testing. Users read 'send' as destructive, so we changed the label to 'queue for review."
"Through iterative exploration and rigorous testing, we arrived at an optimal labeling solution that better aligned with user mental models."
"Three weeks of work, two releases, one major refactor. The token system is cleaner; the components are not."
"Through a holistic, iterative process leveraging cross-functional collaboration, we achieved a more streamlined and impactful system."
"This is v0.1. The Don'ts list is short because it grows with use."
"Excited to share v0.1 of our latest design system initiative — stay tuned for what's next!"
Vocabulary
§ 20.3The lexicon: the word to use, the words it replaces, and why. Concrete beats inflated everywhere copy ships.
| Use | Instead of | Because |
|---|---|---|
ship | "roll out", "launch" | Concrete, not inflated |
cut | "reduce", "optimize" | Action, not abstraction |
wrong | "non-optimal" | Be willing to be wrong out loud |
we learned | "key insights surfaced" | Who, not what |
the version that didn't ship | "alternative explorations" | Honest about process |
it didn't work | "results were mixed" | The reader can tell either way |
For AI agents
§ 20.4If you're a language model generating copy for surfaces that use this system, four hard constraints:
When in doubt, prefer the shorter sentence and the lower register. Restraint is the system's voice. Quiet, then loud.
Write nominal sentence fragments as headlines or pay-offs. Use complete sentences.
Use always, never, or no exceptions as rhetorical emphasis. Use them only when literally true.
Use the words delight, world-class, journey, unlock, supercharge, holistic, synergy, leverage (as verb), or seamless. None of them carry meaning.
Contrast by intent, not accident.
Every text/surface pair in the system is verified against WCAG 2.2. The system is dark-first, not dark-only — every token has a light-mode equivalent resolved at build time. Motion, focus, and touch targets follow the same rule: specified, not assumed.
Contrast · pairs
§ 21.1Ratios measured against the actual text/surface pair each token forms in production. Body text clears AAA; brand and signal colors clear AA normal or better. One pair sits below AA by ratified exception — the primary button label, documented below and in the v2.6.0 release.
| Pair | Ratio | Level | Use |
|---|---|---|---|
obsidian-100 on obsidian-950 | 16.1:1 | AAA | Body text, headings |
obsidian-200 on obsidian-900 | 10.5:1 | AAA | Lede, supporting copy |
obsidian-300 on obsidian-900 | 6.3:1 | AA | Metadata, caption, subsection desc |
obsidian-050 on magenta-500 | ≈2.8:1 | EXC* | Primary button label (ratified v2.6.0) |
obsidian-950 on magenta-500 | 6.5:1 | AA | Default for every other magenta fill |
magenta-400 on obsidian-900 | 7.3:1 | AAA | Link rest, eyebrow, accent text |
magenta-300 on obsidian-900 | 8.9:1 | AAA | Link hover |
secondary-500 on obsidian-950 | 15.8:1 | AAA | Focus ring, secondary signal |
success on obsidian-900 | 15.0:1 | AAA | Positive badge, delta up, "do" label |
warning on obsidian-900 | 12.1:1 | AAA | Caution badge, draft state |
danger on obsidian-900 | 6.3:1 | AA | Error text, destructive label, "don't" |
info on obsidian-900 | 10.3:1 | AAA | Info badge, neutral signal |
* Ratified exception, § 6.3: below the 4.5:1 AA bar, kept on perceptual grounds — on high-chroma magenta, near-white separates more cleanly than the higher-contrast dark label. Scoped to .btn-primary only. Under APCA (the WCAG 3 candidate method) the ranking inverts: the light label scores Lc 55.8 vs 47.4 for the dark one — the perceptual model agrees.
Focus · keyboard
§ 21.2Every interactive element has a visible focus state. The system ships two patterns: a dual-ring for pressable elements (outline + brand halo), and an inline glow for text inputs. Verified against components.css.
| Selector | Treatment | Contrast on obsidian-950 |
|---|---|---|
.btn, .nav-item, | outline: 2px solid obsidian-100; offset: 3px + 0 0 0 4px rgba(magenta,.35) halo | 16.1:1 |
.input, .textarea, | Border shifts to magenta-500 + 0 0 0 3px rgba(magenta,.15) glow | 6.5:1 |
.cs-img | Border magenta-500 + wide 48px glow (case-study imagery) | 6.5:1 |
.skip-link | Off-screen at top: -100px; jumps to top: 12px on focus | 6.5:1 |
Motion · safety
§ 21.3Under prefers-reduced-motion: reduce, the system ships a global kill-switch plus per-component overrides for hover-transforms that would otherwise feel jumpy. No parallax, no auto-advance, anywhere in the system.
| Rule | Scope | Effect |
|---|---|---|
*, *::before, *::after | Global (all elements) | animation-duration + transition-duration → 0.01ms !important |
[data-fade] | Section reveals | Skip fade-in — final state painted immediately |
.card-media | Media tiles on hover | Strip transform: scale — no zoom |
.cs-img | Case-study imagery | Strip caption transform: scale on hover/focus |
.tab-indicator, | Tab switcher | No indicator slide, no panel translate — instant swap |
.lightbox | Image overlay | No transition on open/close |
window.__motion gate | Motion One — cards, data viz, counters | JS animations jump to final state; counters land on target |
The floor
§ 21.4Non-negotiable rules. Any component that can't meet all seven doesn't ship.
Color never carries meaning alone. Every status uses color + shape + label.
No text below 12px. No body text below 14px. Line height never under 1.4 for running text.
Every form field has a persistent label. Placeholder is not a label.
Every image has alt. Decorative images carry alt="" explicitly.
Focus order follows reading order. tabindex is used for fixes, never for flow.
Auto-advancing content is forbidden. Users drive timing — no carousels, no timed dismissals.
Touch hit area targets 44×44 on mobile, 32×32 minimum on dense desktop. Extend beyond the visual when needed.
The whole system, in practice.
Every primitive — type, color, spacing, component — applied to one narrative. Use this as the starting frame for every case study.
Rebuilding Arc's in-car interface, one glance at a time.
A next-generation automotive HMI for a connected EV — reshaping how drivers interact with their vehicle at 70mph and at a standstill.
The legacy stack made drivers work.
Arc's previous HMI borrowed patterns from mobile — deep hierarchies, tap-heavy flows — and dropped them into a 70mph context. Users were spending 38 seconds on average to complete the top-three priority tasks. That number needed to move.
We started with a constraint: every primary task completes in under 3 interactions, under 10 seconds, eyes-on-road. Everything else followed.
Measure twice, cut once.
Three months of in-vehicle observation before a single screen was drawn. We instrumented the legacy HMI and logged every interaction over a 6-week period, then correlated with eye-tracking data from our research partners.
The map that came back was unambiguous: 73% of distracted-driving events clustered around three flows. Those three became our design targets.
Measured, shipped, maintained.
Shipped to 2 vehicle platforms in Q4 2024. The token system that emerged from this project became the foundation for Arc's entire product design language — and for this design system.
What I'd do differently.
We underinvested in the companion mobile app for the first 6 months, treating it as a delivery vehicle for the in-car experience rather than its own surface. By the time we corrected course, three flows had to be rebuilt. The lesson — surfaces are equal citizens from day one — is now baked into how I scope every multi-platform project.
Slides that hold the system.
Eight canonical layouts on a fixed 1920×1080 canvas. Same tokens, same 85/10/5 restraint as every other surface — the deck just happens to be 16:9. One magenta moment per slide; everything else stays obsidian.
The deck
§ 23.1A worked 8-slide deck — the Arc HMI case study, told on-system. Navigate with the controls, the dots, or the ← / → arrow keys. No auto-advance (a11y floor #6); position is remembered across reloads.
Layouts
§ 23.2The eight templates, authored at full 1920×1080 and scaled to fit. Each is a single .slide with a layout modifier; the chrome (brand, footer, page number) is shared across all of them.
Anatomy
§ 23.3Every slide is authored in a 1920×1080 coordinate space, then scaled to whatever container holds it. The chrome and safe margins are constant so a deck reads as one object.
| Part | Spec |
|---|---|
.slide canvas | 1920×1080, 16:9, surface --obsidian-950. Scaled via transform: scale(), never reflowed. |
| Safe margin | 120px left/right, 88px top/bottom (--pad-x / --pad-y). Content lives inside. |
.slide-brand | Top-left lockup — logo mark + “Amaca” wordmark. Present on every slide. |
.slide-foot | Footer rail — project · date (left), page NN / total (right). Mono, --obsidian-400; the live page index tints --magenta-400. |
| Type | Its own optical scale — the UI scale (§ 05) is tuned for an 1180px column, not a 1920px canvas. Titles 64–150px, body 30px, mono chrome 20–24px. |
| Accent | ≤5% magenta — one keyword, one numeral, or the eyebrow rule. The rest is obsidian (§ 08). |
Layout modifiers — each is a single class on .slide:
| Class | Use |
|---|---|
.slide--cover | Title / opening |
.slide--divider | Section divider with ghosted numeral |
.slide--content | Title + body copy |
.slide--split | Two-column — heading + metadata list |
.slide--stat | One oversized metric |
.slide--data | Chart slide (Data Viz vocabulary) |
.slide--image | Full-bleed media + scrim + caption |
.slide--closing | Sign-off / contact |
Rules
§ 23.4The presentation rules: one magenta moment per slide, neutrals for everything else, evidence over adjectives.
Spend one magenta moment per slide — a keyword, a numeral, or the eyebrow rule. Everything else stays obsidian (85/10/5).
Flood a slide background with a magenta gradient or tint more than one element. That reads as decoration, not signal.
Keep the 1920×1080 canvas fixed and scale it. Nav controls live outside the scaled frame so they stay usable at any size.
Auto-advance the deck or loop decorative motion. Navigation is manual — arrow keys, controls, or dots (a11y floor #6).
Version history, in public.
What changed, when, and why — read top to bottom. From v1.2.0 onward, the system follows strict SemVer: MAJOR for breaking changes, MINOR for additions, PATCH for fixes.
Releases
§ 24.1Latest first. Each entry documents what was added, refined, or fixed — and where unchanged, says so. Click a row to expand.
MAJOR. The first real run of the installed plugin put the contract in front of a generator: it declared two divergences between the Card spec and the CSS, and a human eye found the third — an anatomy the spec did not have, so the generator invented spacing. A DTCG audit the day before had found seven tokens missing from the projection. Same lacuna from three angles in two days: the inventories were guarded, the projections and declarations were not.
Breaking
- The card renders the surfaces the spec always declared — background
--obsidian-800, border--obsidian-700, hover--obsidian-600with--sh-2. The token table already said--obsidian-800→ Card; the CSS had shipped one ramp step darker, shadowless, since the section was written. Decided on a measured render, not on token names. .card-headerbecomes.card-meta, no alias — the spec taught a class the CSS did not know, so a generator following the contract emitted an unstyled div. The spec name wins, cleanly.- The mono-label register collapses to
--t-caption— a de facto 11px register lived off-scale in 21 CSS rules and 46 site spots. Chart-head titles and the brand name (14px) join--t-body; the chart card adopts the card ramp; skip-link and pie-legend geometry collapse to scale steps. .info-stripis removed — the v3.4.0 promise, removal scheduled for v4.0.0, is kept.
Added
- Seven promotions — Page shell, Grid system, Breadcrumb, Skip link, Chart card, Pie, Brand mark: every
css-onlyrow cleared at its milestone, each promotion an audit, and each audit found something — a sticky topbar on a raw z-index, a brand glow in a color no token declares, a skip jump that did not clear the topbar it exists to skip, a section entrance running under reduced motion. Fixed with the promotion that found them. - The debt came due, and cleared — 126 exact-parity px literals now read their token; six rules left their demo captions and became framing prose, verbatim; 41 subsections gained the prose that says what the thing is for. Zero findings in declared debt, for the first time since the mechanism exists.
- The DTCG projection is contractualized — the seven
z-*tokens entertokens.dtcg.json, the file stamps its own release,dtcgjoinsdeploy_targets, and the spec declares the direction: a generated projection oftokens.css, never edited by hand. - Checks 25–28 — the DTCG round-trip; every class the spec emits exists in the CSS and declared surfaces match it; every id unique; every rgba speaks a token’s color. Each born from the drift it now prevents; all four mutation-tested before first use.
- The skill verifies its own output —
amaca-frontendv1.2.0 shipsscripts/verify-output.py: deterministic, stdlib-only, reading the closed lists from the bundle’s own spec copies. Output is deliverable only on exit 0. A control described in prose is not a control; now it is executable.
Fixed
- Dead names the spec taught —
.badge-live/.badge-draftand.field-label, examples and live site spans rendering unstyled, one of them styled inllms-full.txtby a rule the CSS never had. All aligned to the shipped names; check 26 makes the class unrepeatable. - The stat count-up rode a curve the rule does not name — hardcoded 1200ms and easeOutCubic against a RIGID rule declaring
--d-draw·--ease-decel. It now reads the duration off:rootat run time and solves the declared bezier exactly. - The pie demos pasted hex — 24 attribute literals, all token values written by hand, now
var()styles withrole="img"on both charts. id="main-content"was duplicated — the skip target was ambiguous, and the jump did not clear the sticky topbar. One id, on the content wrapper; check 27 guards uniqueness.
MINOR. No token changed. Cursor started reading agent skills in January and the IDE download never caught up, so it shipped the constraints without the procedure. Fixing that led to three pieces of drift in this repo that every gate reported green: the version line the spec calls canonical, a download button named after a file deleted three releases ago, and a debt mechanism whose deadlines never arrived.
Added
- The agent skill reaches the IDE — since Cursor 2.4 the Agent Skills standard is a first-class input, read from
.cursor/skills/,.agents/skills/and, for compatibility,.claude/skills/and.codex/skills/.amaca-ide.zipnow carriesamaca-frontendunpacked under.agents/skills/amaca-frontend/— the vendor-neutral path, so one folder serves Cursor and Codex alike. The spec is not duplicated inside it: the bundle already shipsDESIGN.mdat root, and a second copy is a second thing that can drift. The three wirings compose rather than compete — the paste-in, the scoped.mdclayer, and the skill, which carries the multi-target workflow a rules file structurally cannot. - Check 21 — the document's own version line — check 14 is called one version, five places. The spec calls a sixth place the source of truth, and no check covered it. Now one does, comparing the version and its date against the frontmatter.
- The skill becomes installable —
amaca-plugin.zip— Agent Plugins 1.0 was published on 6 August by Amazon, Cursor, Microsoft, OpenAI, Vercel and Google: one vendor-neutral way to package agent skills and MCP servers, read by Cursor, OpenAI Codex, GitHub Copilot, VS Code and Kiro. Anthropic is not at that table and Claude Code keeps its own format — but the two manifests live at different paths and never collide, so this is one package with two manifests and a single skill inside, not two packages. Verified, not assumed:claude plugin validate --strictpasses clean on the emitted artifact, and it does not see the rootplugin.json. The spec ships inside the skill here, in one copy — unlikeamaca-ide.zip, whose root is your repo; a plugin root is an install directory nobody browses.CLAUDE.mdis deliberately absent: Claude Code fails--stricton it, and says why — context belongs in a skill. - The repo is now a plugin marketplace —
/plugin marketplace add angelomacaione/amaca-design, then/plugin install amaca-design@amaca. The entry installs from the published zip over HTTPS with a SHA-256 pin — no npm, no clone. The pin is not decoration: Agent Plugins defines a package format and leaves distribution, provenance and trust explicitly outside it, so verifiable integrity is the publisher's job. Check 22 recomputes the digest against the bytes on disk, because a hash that nobody re-checks is a hash that goes stale.
Fixed
- The contract declared itself two minors behind, for two releases — the version line read 3.2.0 while the frontmatter and the whole site read 3.4.0. Every stamp the harness could see was correct; the only one it could not see was the one the spec calls canonical.
- Declared debt never came due — the gate suppressed a check whenever a debt was declared, and never compared the release it named to the current version. A debt dated v3.5.0 would have stayed silent at v9.0.0. The mechanism described as declared and dated was only declared — the date was decorative, and 71 open findings would never have surfaced on their own. Deadlines now expire: at or past the release it names, a debt blocks like anything else. Deferring stays legitimate; it has to be done on purpose, by moving the date.
- The release checklist stated its own number of checks, and the number was wrong — step 0 read nineteen against a harness of twenty-one, and this release added three more, so any figure written there would have rotted again within the same version. Step 0 no longer states a count: the harness prints it.
Changed
- The IDE download card and its button — the card read
.cursor/rules/amaca.mdcand the button was labelledamaca.mdc, a file replaced in v3.1.0 by the core / html / react split. The site had been offering a download named after a file that had not existed for three releases. llms.txtindexed three AI-tool entry points out of seven —CLAUDE.md, the paste-in, theamaca-frontendskill and the IDE bundle were all missing. An index that omits four of the surfaces it exists to advertise teaches a retrieving agent that they do not exist.- Debt re-dated to v3.6.0 — the seven css-only registry rows, the teaching-grammar note, and checks 03, 17 and 18. Deliberate deferral, not drift: with the expiry fixed, the new date is a real deadline.
Trigger
A competitive watch found that Cursor had changed its primary input shape in January and nobody noticed, because every run re-read the page about rules — which by construction cannot mention a format arriving beside it. Checking what we actually emit against what Cursor actually reads deflated the alarm and left one real gap. Pointing the same reflex at this repo — verify the artefact, not the description of it — found the other three. All of them had the shape this system knows best: no symptom, and a gate reporting green.
MINOR. The token rule works because the token list is closed. Components had no equivalent list, so a model couldn't tell "not covered" from "not yet written" — it invented. This release closes the component inventory, gives states the same fixed grammar motion got in v3.3.0, and documents seven components that were either shipped-but-invisible or missing entirely.
Added
- Component registry (§ 3.0 of the spec) — the closed inventory of components, with a four-state vocabulary: canonical (spec written) · css-only (shipped in CSS, spec owed, milestone named) · off-system (stop and ask) · site-only (documentation chrome, not the contract). A component not in the table is off-system. This is what makes the token rule executable one level up — a generator can now detect non-coverage instead of inventing.
- State grammar — one fixed six-column schema for every interactive component (state · background · border · foreground · ring · other) with a closed state vocabulary and two RIGID rows: focus-visible is mandatory for anything focusable, error is mandatory for every form control. The state index aggregates every ratified row.
- Stacking scale — seven named layers, from
--z-stickyto--z-max. The values name the system as built — nothing was renumbered. A raw z-index of 10 or more is now greppable as a violation. - Duration by distance (RIGID) — perceived velocity is distance ÷ duration, so a component whose content varies by an order of magnitude cannot have one honest duration. Three buckets, three existing tokens, no computed values: under 120px rides
--d-base, 120–600px--d-slow, over 600px--d-scene. - Teaching grammar — the closed list of the four surfaces a component is documented on: framing prose, Do / Don't, demo caption, contract card. Written after an audit found five competing grammars and no rule saying which to use — 41 of 90 subsections with no framing prose, Do / Don't in 6 sections of 24, and the demo caption doing two incompatible jobs. RIGID: a rule never lives in a demo caption, and a demo with no choreography gets no replay button.
- Browser chrome — scrollbar,
::selectionandcaret-colorpromoted from the CSS into the contract, with a closure clause and one ratified exception. The scrollbar had been specified since v1 and had zero occurrences in the spec. - Date picker — the most complex component in the system, shipped since v2 and invisible in the contract until now — full anatomy, the closed day-flag set, range with two months, presets, year jump.
- Table, Checkbox, Switch — shipped in CSS, never documented. Now canonical, with state matrices.
- Radio, Alert, Toast, Tooltip — four components that existed nowhere. Radio completes the choice-control family (square with a tick, circle with a dot — shape carries meaning before colour). Alert never tints its fill, and is not dismissible at all — not on a timer and not by the reader: a close button would let someone hide a state that is still true. If the reader must be able to make it go away, it is a toast. That is the line between the two components. The icon labels the title, so a titled alert drops its body to a second row aligned with the icon rather than hanging indented off a short label. Toast auto-dismisses only when it carries no action and no error, and enters and leaves on the side it lives on — 24px from the right, the exit retracing the entry on the same axis, differing in speed and not in path. Tooltip opens on hover and on focus, holds no interactive content, and is never the sole carrier of meaning — and at most one is open at a time: opening one closes every other, since two overlapping bubbles make both unreadable.
.btn-outline— the fifth button variant, shipped in v2 and never listed in the variant table.
Changed
- Input states — three prose bullets replaced by a state matrix. Error is marked with
aria-invalid="true"and the border follows from it. - Focus rings are now named — pressable halo and field glow, so the state grammar cites them instead of re-typing the rgba literal on every row. The choice controls join the pressable list.
.info-strip→.alert— the un-specced ancestor becomes a deprecated alias. Still works; removal scheduled for v4.0.0.
Fixed
--font-monowas never a monospaced stack —tokens.csscarried a copy of--font-sanswhile the spec and the DTCG file both declared a real mono stack, a correction applied to the export in v3.1.0 and never propagated to the CSS. The system ran on two typographic identities: 134 token references rendering Satoshi, 38 hardcoded stacks rendering real mono. The CSS was right — Amaca is a single-typeface system and the mono register is Satoshi plus letter-spacing, uppercase and tabular figures. Spec, DTCG and the 38 stacks all realigned; a real monospaced stack is now off-system.- One duration across a 34× range of distances — the § 15 accordion panel is 60px, the § 24.1 changelog panel is 2037px, and both rode 600ms from the same rule: 100 px/s against 3395 px/s. They read as different components because they were moving at different speeds. Accordion panels now pick their duration token by measured height (§ 08.3) — 206 px/s and 1299 px/s, from the same closed scale. Only the choice is computed; the value is always a token.
- The loader inside
.btn-primarywas dark while the label was light — the dark silhouette was ratified when the primary label was still dark; § 6.3 flipped the label to near-white two releases later and the loader was never revisited, so the two halves of the same button carried opposite treatments. The rule behind it is now written: anything composed inside.btn-primarywears the label's colour. - 704
<code>elements ran on the browser's monospaced default — only two were ever styled, so every other inline fragment fell back to a real monospaced typeface. The § 11.2 rules card showed both, a line apart.code, kbd, samp, prenow ride the mono register in the reset. - The composer's staged state was two rows tall for one line of text —
rows="2"left the text at the top and pushed attach and send to the bottom, so the two side-by-side demos didn't match. Shipped that way since the composer landed. var(--brand)does not exist — 13 uses — the digit labels in the time and date input rule cards referenced an undeclared token. The fallback was a plausible grey, so it never read as broken.- 153 raw px in inline styles — swapped to tokens at exact parity. 115 off-scale literals remain, scheduled with the teaching-grammar debt.
- The input error state had no CSS — the spec has promised a danger border since v2.0.0; only the helper text was ever implemented.
- Checkbox and switch had no focus ring — both shipped without
:focus-visible— an accessibility floor violation in the two most-used form controls after the text input. - Raw hex, eight survivors — including two
#fffthat contradicted the no-pure-white rule fixed for::selectionin v3.3.0.components.cssnow contains zero raw hex. - Motion token pairs, seven survivors — transitions carrying a duration with no easing half. The two
transition: allon the choice controls are now explicit property lists, so the Spatial-vs-Effects rule is checkable per line.
Trigger
An audit against v3.3.0 found three classes of failure with one root cause. Spec that existed but was invisible in the contract (the scrollbar, specified in CSS, zero mentions in the spec). Components shipped in CSS and never documented (Date picker, Table, Checkbox, Switch) — the model reinvented them though a canon already existed. Components that existed nowhere, so the model invented from scratch. Writing the registry surfaced the second finding: the audit that closes the inventory is also the audit that finds the drift — eight raw hex, seven half-written token pairs, two missing focus rings, and an error state documented for four versions but never implemented.
MINOR. The motion documentation becomes homogeneous: one fixed grammar for every component that moves, two new RIGID rules, and the anti-drift net extended to catch what it couldn't see. Shaped by a comparative research pass across M3 Expressive, Figma, Carbon, Atlassian, Fluent 2, Polaris and the DTCG standard. No token value changes.
New · Motion grammar (§ 08.3–08.4)
- Spatial vs Effects (RIGID) — every animated property has a type, and the type gates the easing: spatial (transform, position, size, shape) may ride any curve including
--ease-spring; effect (color, background, opacity, shadow) never overshoots. Mechanically greppable, per line. - Trigger-based pairing — user-triggered states ride
--d-quick·--ease-standard; system-triggered transitions ride--d-base·--ease-decel. The trigger decides, not taste. - Per-component motion schema — one fixed four-column table (trigger · property+type · token pair · reduced-motion, closed vocabulary
instant/fade-only/none) replaces prose specs. Card, Accordion, Tabs, Lightbox, Chat, Loader and Diagrams migrated; § 16.4 and § 18.5 converted to the canonical columns. - Motion index — the aggregate of every ratified per-component spec, in § 08.4 and DESIGN.md § Motion: the system's whole choreography in one table.
- Continuous loops (ratified class) — loops run while mounted, on per-component literal clocks,
linear/stepsonly, each ratified by its own index row (brand mark, fallback ring, label cycle, shimmer, caret). The raw-duration grep exception is now a closed list. - § 08.3 Easing tokens — the four curves get their own table on the site (they lived only in DESIGN.md and the demo), each with intent annotation on the row.
Changed · Enforcement
The grep audit, the anti-patterns table and AI-agent rule #6 now catch raw ms durations (ratified loops excepted) and --ease-spring on effect properties. Propagated to every download surface: AI-INSTRUCTIONS, CLAUDE.md, AGENTS.md, Copilot/Cursor rules, llms-full.txt; amaca-figma reaches v1.4 (check 8 gains the Spatial/Effects RIGID); the amaca-frontend bundle re-bakes with the pairing section and new checks (H8, R10, F9–F10), and FIGMA.md v1.1 maps the motion scale to Figma's native Timing and Easing variable types.
Fixed
- Vercel Web Analytics — the tracking snippet (PR #9, May 12) was lost in a later
index.htmlrewrite; the site shipped without it and the dashboard read zero. Re-added before</body>. - Token drift, three survivors —
[data-fade]rode a raw600ms→--d-slow; the law-card bar rode a raw bezier →--ease-standardand a1100msclip-path →--d-draw, closing the last off-scale draw-on literal the v2.7.0 audit had missed. - Changelog renumbered to strict SemVer — v2.4.1 → v2.4.0, v1.2.6 → v1.2.0, v1.1.4 → v1.1.0: the first release of each line is now X.Y.0, consolidation notes and COMPARE links updated. The public sequence has no skipped numbers.
- § Overview metadata stamp — read Version 3.1.0 · Jul 6 while the system shipped 3.2.0: realigned, and the release checklist in DESIGN.md § Versioning now names it explicitly so it can't drift again.
Trigger
The foundations lived in § 08 but per-component motion rules were scattered across component sections with no uniform grammar — the exact pathology a machine-readable system can't afford. A verified research pass over how the leading systems structure motion (spring schemes, composite tokens, intent annotations, kill-switch contracts) shaped the schema; the strongest patterns — Atlassian's intent bundles, M3's spatial/effects taxonomy, Polaris's per-token guidance — landed as Amaca rules.
MINOR. The system reaches the Figma canvas: an in-canvas custom skill for the Figma agent, on the Agent Skills single-file standard. No token, value, or component contract changed.
New · Figma agent skill — /amaca-figma
- Two entry points — generate builds on canvas binding Amaca-named Variables; audit lints a selection on canvas evidence: bindings, token existence, the 85/10/5 budget, contrast pairs, the component registry, type and spacing scale.
- Verify loop — audit runs after every generate; repairs target only the flagged nodes, capped at two passes. A finding that survives a repair is treated as a system gap and routed to the gap protocol, not another loop.
- Fetch-first, fail-stop — the skill reads
/llms-full.txtbefore any action and refuses to generate from memory. It never invents a hex, px, or easing. - Install — in a Figma file: prompt box → Skills → Add skill → upload
amaca-figma.md→ invoke/amaca-figma. Ships asdownloads/amaca-figma.md+zips/amaca-figma.zip.
New · Motion rule — stat count-up (RIGID)
Numeric stats and KPI values count up from zero to the target on entrance (--d-draw · --ease-decel) and land exactly on the final value; under reduced motion they jump straight to it. Already the site's counter behavior — now normative in § 08 for every deploy target, taught by the amaca-frontend targets (v1.1.9) and audited by /amaca-figma (check 10).
Changed · downloads grid (§ 03.0)
- Hero swap — the Figma card takes the second hero slot;
AGENTS.mdmoves to the top of the AI integrations column. Its bundle is unchanged. - Builders card — Figma Make hands off to the dedicated Figma card; the row now reads v0 · Lovable · Base44. Same paste-in bundle (
amaca-appbuilder.zip), contents unchanged. - § 03 instructions reconciled — the install rows now mirror the hero cards: the Figma row teaches the in-canvas skill (install like Claude, invoke
/amaca-figma) and a Google Stitch row is added; the IDE row reflects the v3.1.0 core + per-target layout; the spec-contents table speaks the design.md grammar; versioning cards refreshed (CURRENT v1.1.9); subsection numbering closed (03.1–03.5, was 03.2–03.6). tokens.dtcg.json→ DTCG stable 2025.10 — colors as colorSpace/components objects (hex kept as fallback), dimensions and durations as value+unit objects, easings as cubicBezier arrays, shadows structured, font stacks as arrays. Repairs in passing: two gradient tokens re-typed from cubicBezier to other,font-monocorrected to the ui-monospace stack, em letter-spacing kept as strings (dimension is px|rem only). The draft-format file remains in git history.llms-full.txtversion stamp — the self-contained context file now declares the spec version and release date it was baked from, so fetch-first consumers (this skill included) can echo what they loaded.
Trigger
Post-Config 2026 the Figma agent accepts custom skills on the same Agent Skills standard this repo already targets — the deliverable is loadable as-is. Occupying the in-canvas surface extends multi-deploy to where designers work. The skill is advisory by nature (the agent is non-deterministic); the deterministic gate remains the amaca-frontend bundle.
MINOR. Two amaca.design-side parity moves — no token, value, or component contract changed. The flagship now ships what the amaca.ai compiler emits.
New · IDE download — core + per-target rules
- Cursor split — the single
.cursor/rules/amaca.mdcis replaced byamaca-core.mdc(alwaysApply: true— token discipline, the laws, the a11y floor) plus glob-scopedamaca-html.mdc(CSS/HTML) andamaca-react.mdc(JSX/TSX, Tailwind v4@theme). The core stays loaded everywhere; the heavy target rules attach only to the files they govern. - Copilot per-target —
.github/copilot-instructions.md(repo-wide) plus.github/instructions/amaca-{html,react}.instructions.mdwithapplyToglobs. - Canonical targets —
html · react · figma. Figma is a Variables deploy, not a glob target, so it carries no.mdc.
New · site /llms.txt
- Curated AI index —
/llms.txtpoints agents at the docs worth reading (DESIGN.md, tokens.css, theme.css, tokens.dtcg.json, AGENTS.md) by absolute URL, plus a self-contained/llms-full.txtwith the rules and full token table inlined. Read by Cursor, Copilot, and Claude/Perplexity in retrieval. robots.txt+sitemap.xml— permissive crawl policy that names the AI/LLM crawlers (GPTBot, OAI-SearchBot, ChatGPT-User, ClaudeBot, Claude-Web, PerplexityBot, Google-Extended) as allowed so/llms.txtis never moot, plus a sitemap listing/and/DESIGN.md. Independent layers fromllms.txt(access + search vs inference-time index).
Changed · downloads
- App-builder bundle split out — the App-builder card (Figma Make · v0 · Lovable · Base44 · Bolt) now downloads its own
amaca-appbuilder.zipinstead of sharingamaca-chat.zip, so the two no longer collide as one filename. Both ship the same self-containedAI-INSTRUCTIONS.mdpaste-in, baked from one source viabuild-paste-in-bundles.sh— edit once, both stay in sync.
Trigger
Reconciling the hand-authored gold against the compiler's current output surfaced two gaps: the IDE download was a single rule file where the product emits core + per-target, and the site had no hosted llms.txt. Closing those pulled in the adjacent AI-surface hygiene (robots/sitemap) and a download-naming dedupe (App-builder). All close to parity — the flagship eats its own output.
MAJOR. A document-grammar break, not a token change. DESIGN.md is rewritten to the Google Labs design.md open standard — the eight canonical sections in order plus a structured front-matter token block — so any conforming consumer (Stitch, an agent, the amaca-compiler conformance lint) reads it as a valid design.md by construction, not by omission. No token, value, or component contract changed; every screen renders identically to v2.8.0.
Changed
- Eight canonical sections — Overview · Colors · Typography · Layout · Elevation & Depth · Shapes · Components · Do's and Don'ts, in that order. The old numbered sections fold in: Principles / Voice / 85·10·5 / Anti-patterns → Do's and Don'ts; the token sub-sections → Colors / Typography / Layout / Elevation & Depth / Shapes; component specs → Components.
- Structured front-matter — Machine-readable
colors:(with an explicitprimary: #F051D5role + the full obsidian/magenta scales),typography:,spacing:,rounded:. Theprimaryrole is the load-bearing bit: without it a consumer can regenerate the palette and ignore the brand. - Extension sections, preserved — Motion, Iconography, Accessibility, Code conventions, Multi-deploy and IDE integration are kept verbatim as marked extension sections after the canonical eight — the richness the standard has no slot for, lint-safe.
Migration
Section numbers changed. In-prose cross-references written as "§ N.M" now point to the same content under its new home (e.g. former § 3.10 Dropdown → Components → Dropdown / Select; § 6.3 → Accessibility → Ratified exceptions; § 2.8 → Motion). A migration note in the spec changelog carries the mapping.
Trigger
amaca.design is the dogfood for amaca-compiler: what the site ships should equal what the compiler emits when it ingests this system. The compiler's output already conforms to the Google design.md grammar — so the hand-authored spec was restructured to the same standard, written at its best. The site is now the maximal-compliant instance, not a bespoke doc the lint can't read.
Also shipped in 3.0.0
- New component · Presentations — slide-deck templates (a fixed 1920×1080 canvas scaled to 16:9, eight layouts), with a live demo deck + layout gallery in Applied. Slide pieces enter on the system's staggered-reveal motion: numeric values count up, listed rows choreograph one at a time, data bars wipe in.
- Downloads · "Pick your tool" — your design system compiled for every tool: a Claude skill +
CLAUDE.md, the universalAGENTS.md, Googledesign.md, IDE rules (Cursor.mdc/ Copilot), a self-contained paste-in for ChatGPT/Gemini/builders, and the token formats (Tailwind@theme, raw CSS, DTCG). One turnkey bundle per tool, each with a usage README inside. - Data Viz · interactive hex world map (§ 19.8) — the public-domain Natural Earth 1:110m land rasterised into a token-styled honeycomb (d3-geo + world-atlas), status nodes placed at true longitude/latitude, click-to-inspect node card, labeled legend.
MINOR. One component refinement: the custom listbox now places itself so it never covers the content it sits above.
Changed · Dropdown / Select
- Viewport-aware placement (variant B) — The custom listbox opens below the trigger and flips above when there isn't room, clamps its
max-heightto the available space (internal scroll) and clamps horizontally — so the menu never covers adjacent content (e.g. a chat composer the control sits above).position:fixedmeasured from the trigger's rect when near a viewport edge; the oldposition:absolute; top:calc(100% + 4px)stays valid only when there's always room below. - Native
<select>(variant A) discouraged directly above content — The browser/OS renders the option popup over the control and can cover what's beneath it; near a viewport edge or above must-see content, reach for variant B. - Dismissal extended — A fixed-position menu also closes on
scrollandresize(its anchored coordinates would otherwise drift from the trigger).
Trigger
A native <select> sitting just above a chat composer covered the input with its option popup. The fix is a placement contract, ratified into § Components → Dropdown / Select, that any control near a viewport edge or above must-see content follows.
PATCH. A ratified no-change: the primary button label was evaluated at three weights and stays Medium 500 — documented so the decision has a date and a rationale, not just a habit.
Ratified
- § 3.1 · Primary label weight — Compared Regular 400, Medium 500 and Bold 700 in context (15px and 13px scales). Under APCA stroke weight is a contrast input and 700 scores strongest — but the bold label shifts the button's voice, and the legibility budget is already carried by the § 6.3 pairing. Medium 500 holds: don't bold the primary label for emphasis, don't drop below 500. Noted in the § 6.3 APCA evidence so the question doesn't reopen.
- System constraint, recorded — Satoshi ships no SemiBold cut:
font-weight: 600silently resolves to Bold 700. The real choice on this system is 500 vs 700.
Skill
Bundle re-baked as v1.1.5 on this spec; HTML.md's a11y note carries the weight rule.
Trigger
Follow-up to the v2.7.1 APCA work: would a semibold label improve accessibility? WCAG 2 is weight-blind, APCA rewards weight, the system has no 600 cut — a side-by-side evaluation closed the question in Medium's favor.
PATCH. Documentation and one token-value refinement. The floor's only ratified exception (§ 6.3, primary button label) gets quantified with the WCAG 3 candidate method — and the perceptual model agrees with the choice.
Fixed
- § 6.3 · APCA evidence — Under APCA the ranking inverts:
--obsidian-050on--magenta-500scores Lc 55.8 vs 47.4 for the dark label. WCAG 2.x under-estimates light text on saturated mid-tone fills (the "orange button" failure mode). Added with it: the ceiling note (4.5:1 is unreachable on #F051D5 — pure white tops at 3.07:1) and a compliance mode for AA-contractual contexts (dark-label variant, on request, never default). - Ratio honesty — The declared ≈3.0:1 was the measured 2.83. Corrected to ≈2.8:1 in every living section (§ 3.1, § 6.1, § 6.3 spec · § 03.4, § 21.1 site). Historical changelog untouched.
- § 21.1 · EXC badge — Dropped the warning tint: the asterisk already marks the exception, and semantic colors are feedback-only (§ 04.5) — a documentation badge reading as an alarm was off-doctrine.
- ::selection literal —
tokens.csscarriedcolor:#fff, contradicting the no-#FFF rule and § 6.3's "near-white" description of the precedent. Nowvar(--obsidian-050). - § 7.3 · Runtime-token anti-drift, codified — Live in two domains (Mermaid theming § 18, the
window.__motionhelper) but missing as a general rule. Now canonical in code conventions: tokens read off:rootat every JS boundary; a hand-copied value is a regression.
Skill
Bundle re-baked as v1.1.4 on this spec; HTML.md's a11y note carries the APCA evidence and the measured ratio. Release rule, now standing: every relevant DS change ships into the skill in the same release.
Trigger
An accessibility question — is there an accessible non-pure white on magenta-500? — led to measuring the exception with both models. No light color can pass WCAG 2 on this fill; APCA inverts the ranking in the light label's favor. The floor's only exception is now its most evidence-based section.
MINOR. A motion audit (2026-06-11) found the system's animations running on six engines with two structural leaks: Motion One blocks duplicated easing values as literals and escaped the reduced-motion kill-switch, and chart draw-on choreography lived off-scale. This release closes both — one new token, zero new engines.
Added · tokens
- --d-draw · 1200ms — Sixth duration. Data draw-on: chart tracing, count-up, progressive data reveals. The scale topped out at
--d-scene(900ms, hero reveals), but a line tracing itself or a number counting up is a different semantic category — data revealing, not UI entering — and lives naturally at 1.2s. Projected to Tailwind asduration-drawviatheme.css.
Fixed · motion integrity
- Motion tokens, one source — The Motion One module now reads
--d-*and--ease-*off:rootat runtime and hands them to consumers (same pattern § 18 uses for color). The hand-copied easing arrays in cards (§ 12) and data viz (§ 19) are gone — if a token moves, the JS follows. The v2.0.0 lesson, finally encoded. - Reduced-motion gate for Motion One — The § 21.3 CSS kill-switch can't reach WAAPI/JS animation. Every
animate()call is now gated: underprefers-reduced-motion: reduceit jumps straight to the final state and counters land on their target. Cards and data viz — the most animated surfaces in the system — were the only blocks without a JS check. - Durations snapped to scale — Data viz literals (0.5 / 0.7 / 0.75 / 0.9 / 1.2 / 1.4s) now resolve to tokens: draw-on and count-up ride
--d-draw, baseline sweeps ride--d-scene, bar and ramp reveals ride--d-slow. Choreography delays and staggers stay literal by design — they are composition, not vocabulary. - § 08 demo fallback —
playMotionfell back tocubic-bezier(0.2, 0.8, 0.2, 1)— a curve that exists nowhere in the system — and 240ms, the pre-v2.0.0 base. Now reads--ease-standardand--d-quickoff:root.
Trigger
An external motion audit read every animation path in the codebase and found the virtuous pattern — tokens read off :root at runtime — already canonized in § 18, while the Motion One blocks next to it still carried hand-copied values. Extending the system's own best pattern to its most animated surfaces is the whole release.
PATCH. Documentation-only. The accessibility exception ratified in v2.6.0 (§ 3.1 primary button label) is now reflected everywhere the old rule survived. No token changes, no component API breaks — every screen renders identically to v2.6.0.
Fixed
- § 03.4 · A11Y floor — Rewritten. The old note required
obsidian-950on every magenta fill and called light text a failure; it now states the dark-text default and documents the single ratified exception, scoped to.btn-primary. - § 21.1 · Contrast pairs — The
obsidian-900onmagenta-500row described the pre-v2.6.0 button. Replaced with two rows:obsidian-050onmagenta-500(≈3.0:1, ratified exception, primary button label) andobsidian-950onmagenta-500(6.5:1, AA, default for every other magenta fill). Section lede updated — "nothing falls below AA" is no longer literally true, so it no longer says it. - § 02 · Principles heading — "Five rules. No exceptions." violated § 20.4 (no exceptions as rhetorical emphasis) and, after v2.6.0, was literally false. Retitled: "Five rules. Exceptions earn their keep."
- § 02 · Signature curve copy — Still claimed every surface rides
cubic-bezier(0.16, 1, 0.3, 1); since v2.0.0 that curve lives at--ease-deceland--ease-standardis the Framer ease. The copy now says what the tokens say. - § 03 · Skill references — The docs described the skill as v1.1.0, 6 steps, 13 checks; the shipped bundle is two releases ahead — 7 steps and 14 checks since v1.1. All § 03 references reconciled, and the bundle re-baked as v1.1.2 on the v2.6.1 spec so the download matches the page that describes it.
- styles/theme.css — v2.5.0 declared it shipped; it never reached the repo. It now exists: a Tailwind v4
@themeprojection oftokens.css—var()aliases, zero duplicated literals, same-name tokens (--font-*,--ease-*) declared@theme inlineto avoid circular aliases. Verified against a real Tailwind v4 build: 21/21 utilities generate. One spec correction landed with it: the duration namespace in § 13.1 is--transition-duration-*, not--duration-*— the generated utility (duration-quick) is unchanged.
Trigger
An external audit (2026-06-11) read the live document end to end and found three sections still describing the system as it was before v2.0.0 and v2.6.0. A spec that sells enforcement can't disagree with itself — reconciling the copy to the ratified decisions is the whole release.
MINOR. A new component: auto-layout flowcharts and system-architecture diagrams, rendered from a textual source and themed entirely from tokens. Additive — tokens.css is untouched and no component API breaks.
Added · components
- § 18 · Diagrams — Flowcharts / node-edge graphs and system architecture (box-and-line with grouping), via an auto-layout library (Mermaid, theme
base) laid out with the ELK engine for orthogonal, FigJam-style right-angle edges (dagre fallback). The theming config holds zero hardcoded hex: tokens are read off:rootwithgetComputedStyleat runtime and passed intothemeVariables, and the focus / stateclassDefstrings are built from the same tokens at render time — the source of truth staystokens.css. 85/10/5 holds: everything neutral obsidian, exactly one--magenta-500focus node per diagram; semantic states (--success/--warning/--danger) only when a node encodes a real state, always with a distinct shape + label + a.diagram-legend— never color alone. Anatomy:<figure class="diagram">→.diagram-canvas[role="img"]holding the SVG →.diagram-caption(FIG-NN · title), coherent with.card.
Motion
- On-scroll entrance — Reuses the established reveal patterns. An
IntersectionObserver(threshold 0.15,rootMargin '0px 0px -8% 0px', unobserve after fire) cascades nodes in (~70ms stagger), then edges and clusters. Opacity only — no CSStransformon the generated SVG<g>groups; they compose with the cascade and break the auto-layout positioning. Hooked after the async render resolves.prefers-reduced-motion: reducerenders straight to rest.
Refined
- § 3.1 · Button (primary) — Primary label moves dark → near-white:
--obsidian-050on--magenta-500. A ratified accessibility exception (§ 6.3): the pair is ≈ 3.0 : 1, below the 4.5 : 1 AA bar, kept on perceptual grounds — on the high-chroma magenta, near-white separates more cleanly than the higher-contrast dark label. Scoped to.btn-primary; rest and hover are bounded to--magenta-500(no lighten), fill and dual-ring focus unchanged. The CTA stays--magenta-500, so the magenta doctrine and chat reservation are untouched. - Section numbering — Diagrams lands in the Components group as § 18. The Applied and Meta groups shift by one — Data Viz 18→19, Voice 19→20, Accessibility 20→21, Case Study 21→22, Changelog 22→23. Sidebar nav, in-page anchors, and cross-references all updated; no external links break.
Trigger
The multi-deploy docs (v2.5.0) needed a system map and there was no on-system way to draw one. The open question — how to theme an auto-layout library without pasting hex into its config — is closed by reading tokens off :root at runtime. That is the rationale for § 18.
MINOR. React and Figma move from roadmap to shipped. One canonical token now projects to three surfaces — CSS, Tailwind v4, Figma Variables — defined in a new § 13 and emitted by styles/theme.css. Additive: tokens.css and every component spec are untouched; existing HTML output is byte-for-byte identical.
Added
- § 13 · Multi-deploy compatibility — One token, three surfaces.
var(--magenta-500)↔bg-magenta-500(React / Tailwind v4) ↔ the Figma Variablecolor/magenta/500, all resolving to #F051D5. The direction is one-way: tokens are the source, every target derives. styles/theme.css— A Tailwind v4@themeprojection oftokens.css.var()aliases, no duplicated literals. Wire it with@import "amaca-design/styles/theme.css"and the full token scale becomes utilities.- DESIGN.md YAML frontmatter — Machine-parseable header:
version,updated,last_synced,deploy_targets. The markdown body is unchanged below it. - amaca-frontend skill v1.1.0 — Multi-target. Reads HTML.md / REACT.md / FIGMA.md for the surface you request and ingests
theme.cssfor React output. HTML, React via Tailwind v4@theme, and Figma Variables all ship.
Refined
- § 03.5 · Multi-deploy export — Reframed from roadmap to shipped. The section now documents the three-surface model and the
theme.cssimport. The Googledesign.mdopen-spec dependency is dropped — the React/Tailwind path is Tailwind v4@themenative, not generated by an external tool. The DTCG export is restated as Amaca's own roadmap, fully decoupled. - Version reconciliation — Spec, skill, header badge, and overview meta all reconciled to v2.5.0 / skill v1.1.0. § 03.1 and § 03.6 reflect the multi-target skill and the spec ↔ skill version split.
Trigger
The amaca-frontend skill reached v1.1 and its reference docs cited a § 13 and a Tailwind v4 @theme projection that did not yet exist — the skill shipped ahead of its contract. § 13 and theme.css close that gap: the contract the skill already references is now real and canonical.
PATCH on top of a documentation refresh. The § 03 Meta surface is rewritten end-to-end: two delivery formats (skill bundle + raw spec), per-runtime install guide, model capability tier, and the Tailwind / DTCG export roadmap. No token changes, no component API breaks — every screen in the system renders identically to v2.3.0.
Added · documentation
- § 03.1 · Choose your fit — Two formats, two postures. The
amaca-frontend.skillbundle (and universal.zipmirror) for Cowork and Anthropic-stack runtimes — a 6-step workflow, 13 verification checks, gap-surfacing protocol. The rawDESIGN.mdfor any chat model that takes a system prompt. Both wrap the same spec; the skill enforces it at generation time. - § 03.2 · How to install — Per-runtime instructions for Cowork / Claude Code, Cursor / Continue / Windsurf / Aider, Gemini Gems, ChatGPT Custom GPT, and one-off chat attachment. The five rows replace the previous three generic workflows.
- § 03.4 · Model capability tier — New section. Frontier vs lightweight expectations, with measured scores (8.5/10 overall, 9.5/10 on gap handling across N=4 scenarios on the Anthropic stack), the 30-second post-generation scan checklist, and the runtime architecture caveat for consumer chat tools that abstract file grounding.
- § 03.5 · Tailwind export — Roadmap. When YAML frontmatter lands in DESIGN.md v2.5.0+, three tooling integrations unlock via the Google
design.mdopen spec: lint (token references, WCAG AA contrast, orphans, section order), diff (token-level regression detection), and CSS export to Tailwind v4@themeor W3C DTCGtokens.json. v1.1 of the skill adds React-via-Tailwind generation on top. - § 03.6 · Versioning & license — Two SemVer streams, documented. The spec follows the system release; the skill versions independently and ships with whichever DESIGN.md is current. License stays MIT.
Refined
- Page-level framing — § 03 retitled from "packaged for machines" to "two ways to drop it into your stack." The lede now names the spec/skill split up front instead of pitching a single Markdown file.
- § 03.3 · Components row — The "what's inside" table now states 13 components explicitly (buttons, forms, cards, badges, navigation, accordion, chat, loader, checkbox, …) rather than the earlier abbreviated list. Cross-reference to Changelog bumped to § 23 to match current numbering.
Fixed
- Stray markup in versioning copy — The old § 03.4 versioning card had a duplicated
</p>closing tag mid-sentence (a paste artifact from an earlier draft). Replaced by the rewritten § 03.6 block. - Spacing token in § 03.1 picker cards — Initial pass used
var(--s-7), which is not part of the 4-px scale (the system skips 7) and resolved to0px. Cards now usevar(--s-8)for full padding.
Trigger
Operating the system across multiple AI runtimes — Cowork, Cursor, ChatGPT, Gemini — surfaced a single point of friction: the docs assumed one delivery mode (paste the spec) when in practice two exist, with very different enforcement guarantees. v2.4.0 closes that gap and pre-loads the v2.5.0+ Tailwind roadmap so the next minor release lands against a documented surface, not a blank one.
MINOR release. Two new components ship — a chat & messaging set built for chatbot interfaces, and a brand-logo loader at four scales with five composition variants. The 85/10/5 law card gets an entrance choreography. The Applied and Meta groups renumber by one to keep Components contiguous (16 Chat, 17 Loader); no token, no API breaks.
Added · components
- § 16 · Chat & Messaging — Bot and own bubbles at uniform
--r-lg(no tail), avatar on the first bubble of a bot streak, two-stage entry choreography (containerscale(0.92→1)on--ease-springat--d-base, inner content fades up 80ms behind on--ease-standard), typing indicator with a 3-dot wave loop on--d-slow, scripted live demo with replay, full composer specimen (textarea + send + attach). Own bubbles fill with--magenta-700so--magenta-500stays reserved for CTAs and focus. - § 17 · Loader — Brand mark (Lottie/GIF) at four scales —
--loader-xs16 /--loader-sm24 /--loader-md48 /--loader-lg96. Five variants: standalone, with-label (cycling phrases on a 2.4s interval), inside-button (replaces label when.is-loading), skeleton handoff, and success state with a check that scales in on--ease-spring. Four real-world compositions: form submit, async input validation, full-page boot in a device frame, and a cross-component placement inside the chat composer. Underprefers-reduced-motion: reducethe brand mark steps aside and a single magenta CSS ring spinner takes its place.
Added · tokens
--loader-xs / sm / md / lg— Component-specific size scale at16 / 24 / 48 / 96px. Lives intokens.cssalongside layout tokens.
Refined
- § 04.7 · 85/10/5 law card — Entrance choreography added. On scroll into view, the proportion bar wipes left-to-right via
clip-path: inset(0 100% 0 0) → inset(0 0 0 0)on--ease-decel, then the three legend rows and the rule block fade-and-rise in sequence (stagger 180ms).IntersectionObserverfires once at threshold 0.25; reduced-motion users see the resolved state instantly. - Chat own-bubble color — Discarded the "hot last" rule (latest own message tinted bright magenta). Unified all own bubbles on
--magenta-700(deep brand) with--obsidian-050text — 6.26:1 contrast, near-AAA at body size. The brighter--magenta-500stays reserved for CTAs and focus rings, so the 85/10/5 budget holds in long conversations. - Loader inside
.btn-primary— Cyan-on-magenta is off-system. Resolved viafilter: brightness(0)on.btn-primary .loader-anim, collapsing the cubes to a near-black silhouette without introducing a second source file. Applies at rest and while loading. - Section numbering — Applied (Data Viz, Voice, Accessibility, Case Study) and Meta (Changelog) groups shift by one —
16→18 / 17→19 / 18→20 / 19→21 / 20→22— to make room for the two new Components entries. Sidebar nav, in-page anchors, and cross-references all updated; no external links break.
Accessibility
- Chat conversation — The scroll region carries
role="log" aria-live="polite" aria-relevant="additions"; each bubble includes a visually-hidden sender prefix ("Amaca said:" / "You said:") so the read order survives when the avatar is suppressed on streak follow-ups; the typing bubble carriesaria-label="Amaca is typing"; the composer textareas get a persistentaria-label. - Focus visibility — Dual-ring focus state (
--obsidian-100outline + magenta halo) added to.chat-composer-attach,.chat-composer-send, and.chat-demo-replay. Touch hit areas on composer buttons bump from 36 to 44px below 720px wide. - Loader semantics — Every loader carries
role="status"with a contextualaria-label. The cycling label variant updates the host'saria-labelin lockstep with the visible phrase, so screen readers hear the real wait rather than a generic "Loading".
MINOR release. Three new components ship: a calendar-popover date input, a two-month date-range picker with analytics presets, and an iOS-style scroll-wheel time picker. The release also closes two spec gaps surfaced by a real implementation (pipeline-dashboard, 2026-05-12): Tabs were under-specified and the dropdown / select pattern was missing entirely.
Added · components
- § 11.2 Time picker — iOS-style scroll wheel. 24h, configurable minute step (default 5), 7 visible items, infinite loop via triple-buffer + auto-rewind, native
scroll-snap-type: y mandatory. Magenta band overlay + top/bottom fade gradient. Click on item snaps with smooth scroll; ↑/↓ on a focused wheel step ±one. ↓ on the input opens the popover. - § 11.3 Date input — DD/MM/YYYY single date. Digit-by-digit masked (D1≤3, D2 depends on D1, M1≤1, M2 depends on M1, slashes auto-insert). Trailing calendar icon opens a popover with year-jump view (decade grid 4×3), Today shortcut, optional
data-min/data-maxbounds. Real-date validation (31/02 rejected) fires on blur. - § 11.4 Date range — Two coupled inputs (
data-range-start/data-range-end) sharing one popover anchored to start. Two months side-by-side; collapses to stacked vertical under 600px. Click 1 sets start, hover preview, click 2 commits end (swaps if before start). Four analytics presets in footer: Today, Last 7 days, Last 30 days, Last month. Helper auto-rendersN days(inclusive count).
Refined · spec gaps closed
- § 3.7 Tabs — Expanded from a single line to a full spec: canonical HTML, required classes, the indicator-positioning JS pattern (
getBoundingClientRecton button + parent,transform: translateX+ width interpolation, resize listener, doublerequestAnimationFramefor initial position),prefers-reduced-motionhandling, panel fade-in with opacity + 6px translateY, and the "never crossfade, never slide both" rule. - § 3.10 Dropdown / Select — New section. Two variants: native enhanced (
<select class="select">with appearance:none + inline SVG chevron, canonical stroke%238A94A3, 36px right-pad, hover/focus borders) and custom listbox (.select-wrap > .select-trigger + .select-menu > .select-option) with full keyboard nav, focus-trap, click-outside dismissal, and guidance on when to prefer each.
Fixed
- Date picker hover flicker — The
:hoverrule on.dp-daynow excludes every range state (is-in-range,is-range-preview,is-range-start,is-range-end); previously obsidian-800 was overpainting the magenta range fills frame-by-frame. - Range second-click reliability — The hover preview no longer re-renders DOM (was destroying the cell under the cursor before
clickcould register). Cells are cached ongrid._cellsand only their state classes get repainted viapaintRange(). Commit moved fromclicktomousedownwithpreventDefault. - Stepper label drift on replay — The reset used a flag shared with dots (
_baseT, never set on labels) and re-parsedcyfrom the already-offset attribute, accumulating +10px per replay. Replaced with a dedicated_labelInitflag and a cleantranslate(cx, cy)reset. - Replay button capture phase — Listener moved to capture phase so the chart reset survives any downstream
stopPropagation.
Compatibility
| Area | Status |
|---|---|
| All tokens (color, type, spacing, radius, shadow, motion) | Unchanged |
| Existing component APIs | Unchanged |
| New components: Time picker, Date input, Date range | Added |
| § 3.7 Tabs, § 3.10 Dropdown | Spec expanded — no markup changes required |
Trigger
A real implementation — pipeline-dashboard, 2026-05-12 — surfaced two under-specified areas. Closing those gaps is the rationale for the spec-side updates in § 3.7 and § 3.10.
MINOR release. Adds the first new component since v2.0.0 — a fully-masked time input — alongside deterministic landing-page routing and a stepper transform refactor that fixes a long-standing replay bug. design.md bumped to v2.1.0; drop the new spec into your LLM rules.
Added
- § 11.2 · Time input — HH:MM, 24h. Digit-by-digit masked: H1 max 2, H2 max 3 if H1=2 else 0–9, M1 max 5, M2 0–9. The colon inserts automatically after the second digit. No native
type="time"— the browser picker doesn't respect the system.maxlength="5"+inputmode="numeric"are required. Optional by default; can be marked required per context. Focus & error states inherit from § 11.1.
Refined
- Anchor-link routing — landing on
amaca.design(no hash) now always resolves to#overview. The previous localStorage fallback that remembered the last-visited section across visits has been removed — fresh visits are deterministic. URL is rewritten viahistory.replaceStateto avoid extra history entries. - Stepper SVG transforms — refactored to use the SVG
transformattribute as the single source of truth (instead of CSStransform-box: fill-box, which v2.0.1 had documented). A generic[data-fade]{transform: translateY(12px)}rule was silently composing with the group transforms via cascade and breaking dot positioning. Replay now drives the SVG attribute directly. .law-rulemobile padding — uniform 20px padding on small viewports with a 24px left-pad to compensate for the magenta border. Internal gap reduced from 32px to 12px for compact stacking.
Documented
- § 3.9 — Time input full spec (rules, contract, reference implementation).
- § 7.3 — SVG group transforms guidance rewritten: prefer SVG attribute over CSS. Stepper choreography updated. Anchor-link routing pattern added.
Compatibility
| Area | Status |
|---|---|
| All tokens (color, type, spacing, radius, shadow, motion) | Unchanged |
| Existing component APIs | Unchanged |
| New component: Time input | Added |
Note on history
v2.1.0 follows v2.0.0 directly. The internal v2.0.1 patch (stepper hardening, outcome card stat refactor, staggered text reveals) was never publicly tagged — its contents are folded into this release alongside the new additions. From v2.1.0 onward the system continues to follow strict SemVer.
First major release. Motion vocabulary recalibrated end to end — easing curves redistributed semantically, durations restretched across the full scale. Everything else stays. One reason to bump the major: one set of changes worth knowing about.
Breaking changes
This release shifts the values of the motion system. Token names are unchanged. If you reference Amaca tokens in another project, audit motion before upgrading.
Easing values reassigned
The four easing tokens kept their names. The curves under them moved to better match what the names mean.
| Token | Before | After |
|---|---|---|
--ease-standard | cubic-bezier(0.16, 1, 0.3, 1) | cubic-bezier(0.22, 1, 0.36, 1) |
--ease-decel | [previous curve] | cubic-bezier(0.16, 1, 0.3, 1) |
--ease-accel | cubic-bezier(0.7, 0, 0.84, 0) | unchanged |
--ease-spring | cubic-bezier(0.34, 1.56, 0.64, 1) | unchanged |
The curve cubic-bezier(0.16, 1, 0.3, 1) — the system signature — now lives where it semantically belongs: --ease-decel. Use it for entrances and reveals, where the slow tail in the curve has always been doing decel work. --ease-standard is now the Framer ease, sized for everyday UI state changes (hover, focus, button press).
Migration: if a component was relying on --ease-standard to behave like the old signature curve, switch it to --ease-decel. If it was using --ease-standard for generic UI feedback, leave it — the new curve does the job.
Duration values rebalanced
The five duration tokens have been restretched. Names unchanged — only values shift. The new scale stretches the slower end (more breathing room on entrances and orchestrated reveals) and grounds the faster end on round numbers.
| Token | Before | After |
|---|---|---|
--d-instant | 80ms | 100ms |
--d-quick | 160ms | 200ms |
--d-base | 240ms | 350ms |
--d-slow | 400ms | 600ms |
--d-scene | 640ms | 900ms |
Migration: no code changes required if you reference durations via var(--d-*). If any duration is hardcoded in milliseconds, search for the old values and confirm intent.
Refined
- Motion — Additional animations integrated across components. The new easing/duration pairings applied throughout.
- § 7 · Grid & Layout — Grid system extended to tablet and mobile breakpoints. Column counts, gutters, and content widths now documented for every viewport.
- § 14 · Voice & Tone — Section rewritten for sharper register. Do/Don't examples expanded. Vocabulary table tightened.
design.md— Spec file regenerated for v2.0.0. Drop the new version into LLM system prompts to keep AI-generated work in sync with the live system.
Fixed
- Mobile responsiveness — Multiple layout fixes across breakpoints. Long titles, dense tables, and stat cards now behave correctly on narrow viewports.
Compatibility
| Area | Status |
|---|---|
| Color tokens | Unchanged |
| Typography tokens | Unchanged |
| Spacing scale | Unchanged |
| Radius & shadow | Unchanged |
| Component APIs | Unchanged |
| Motion easing names | Unchanged |
| Motion easing values | Breaking — curves redistributed |
| Motion duration names | Unchanged |
| Motion duration values | Breaking — five values rebalanced |
Note on history
v2.0.0 follows v1.2.0 directly. Internal iterations between (drafts at v1.2.1 and beyond) have been consolidated into this release. From v2.0.0, the system follows strict SemVer with no skipped numbers in public releases.
First substantive expansion since v1.0. The system gains three new components, a full accessibility posture, a public changelog, deep-linkable sections, and a downloadable spec for AI-assisted design.
Added · new components
- § 15 · Accordion — Collapsible content blocks for FAQs, dense documentation, and progressive disclosure. Single-open and multi-open variants. Keyboard navigation built in.
- § 19.5 · Timeline (Gantt) — Horizontal time-based visualization for project phases, roadmaps, and case study chronologies. Date-aware, with milestone markers.
- § 19.6 · Stepper — Linear progress indicator for multi-step flows (forms, onboarding, case study walkthroughs). Horizontal and vertical variants.
Added · new sections
- § 21 · Accessibility — Color pairs with documented contrast ratios,
prefers-reduced-motionhandling, focus & keyboard order, and hard rules that gate every component decision. - § 23 · Changelog — Version history, in public. What changed, when, and why.
- § 03 · Documentation — Single Markdown file (
design.md) containing every token, component spec, principle, and rule. Built to feed into LLMs as context for AI-assisted design work. Updated alongside the system — every release ships a fresh spec.
Added · navigation
- Anchor links — Every section header now carries a hash. Deep-link any paragraph:
amaca.design/#13-accordion,amaca.design/#16-accessibility. The system became citable.
Refined
- § 04 · Colors — Visual presentation refined. Token names, hex values, and intended use unchanged — no breaking changes to consumers.
- § 10 · Buttons — Vertical padding tuned for better optical balance across sizes.
- Copy — Several section intros and microcopy tightened for rhythm.
Added · meta
- Favicon — Amaca mark serves across browser tabs and bookmarks.
- Open Graph image — Link previews on social and chat render the system's identity instead of a generic screenshot.
Unchanged
No token renames. No component API shifts. No breaking changes.
Note on history
This release consolidates internal iterations after v1.1.0 that were not shipped publicly. From v1.2.0 onward, the system follows strict SemVer.
Maintenance release. Small fixes, one meaningful polish pass on donut charts.
Fixed
- § 04 · Colors — Primary token
#F051D5was mislabeled "Cube Cyan" in the palette reference. Corrected to "Magenta" — its actual value for the last nine months. - § Overview — Page metadata stamp updated (version + date drift).
- Responsiveness — Layout fixes across mobile breakpoints.
- Interactions — Added states for tabs, images, and thumbs.
Refined · § 19.3 Donut charts
Slice geometry rebuilt from stroked arcs to true ring-segment paths.
- All four corners of each slice now render with a real
2pxradius (matchesrx=2on bars in § 19.2). - Inter-slice gap halved from ~6px to ~3px of circumference for tighter visual rhythm.
- Draw-on animation preserved via angular sweep.
- Before — half-circle round caps (13px radius — too soft) or butt caps (sharp — inconsistent with bars).
- After — 2px on all 4 edges. Matches the rest of the data viz vocabulary.
Unchanged
Everything else. No breaking changes, no token renames, no component API shifts.
Infrastructure
- Repository — Cleanup: removed duplicate nested folders and orphaned font/logo files from root.
- Documentation — Added
READMEandLICENSEto public repo. - Branch protection — Ruleset activated on
main(PR-only merges).
Initial public release. Prima pubblicazione pubblica del design system Amaca, live su amaca.design.
Added · Foundations
- Color — Magenta brand range, obsidian neutrals, electric cyan secondary, petrol tertiary, semantic signals.
- Typography — Satoshi as primary typeface across all weights (Light to Black).
- Spacing, radius & shadow — Core layout tokens.
- Motion — 5 durations × 4 easings, all synchronized on
cubic-bezier(0.16, 1, 0.3, 1). - Grid & layout — Foundational structure across breakpoints.
Added · Components
- Buttons — Primary set with full state coverage.
- Inputs & forms — Field, label, and validation patterns.
- Cards — Container variants for content surfaces.
- Badges — Compact status and category markers.
- Navigation — Top-level wayfinding patterns.
Added · Applied
- Data visualization — Charting vocabulary aligned to brand tokens.
- Voice & tone — Editorial direction across surfaces.
- Case study template — Reusable narrative structure for project work.
Principles
Five principles — clarity before cleverness · evidence over opinion · precision is a feeling · quiet then loud · motion is a material.
Infrastructure
- Hosting — Static site on Vercel.
- DNS — Cloudflare.
- Repository — Public on GitHub, MIT License.
- Versioning — SemVer adopted.