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.1Hard 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.2
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. No exceptions.
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 surface in this system breathes on the same curve — CUBIC-BEZIER(0.16, 1, 0.3, 1) — so the whole document feels like a single instrument, played deliberately.
The whole system, two ways to drop it into your stack.
A skill bundle that installs into Cowork or any Anthropic runtime and enforces the rules at generation time, plus the raw spec for any model that takes a system prompt. As of v1.1.0 the skill is multi-target — one canonical token, three surfaces: HTML, React via Tailwind v4 @theme, and Figma Variables. Pick the level of enforcement you want — the underlying spec is identical.
Choose your fit
§ 03.1Two formats, two postures. Both ship the same DESIGN.md inside.
amaca-frontend.skill / .zip
A 6-step workflow that reads DESIGN.md, maps your intent against canonical tokens, generates var(--token) references only, and runs 13 verification checks before handing off. Multi-target as of v1.1.0: it reads HTML.md / REACT.md / FIGMA.md and emits to the surface you ask for. When you ask for something not in scale, the skill stops and proposes paths — workaround, planned release, or refactor. It never silently invents.
For Cowork, Claude Code, Cursor, VS Code (Copilot), Continue. Install once, invoke per task.
DESIGN.md
Tokens, components, principles, motion specs, accessibility rules — flat Markdown. The source of truth that the skill wraps. Paste into any system prompt. No enforcement workflow, full transparency.
For ChatGPT custom instructions, Gemini Gems knowledge base, any chat model with file upload.
How to install
§ 03.2Per-runtime, for code generation. Pick the row that matches your tool. Figma sync is separate — see row 02.
-
01 · COWORK / CLAUDE CODE
ANY ANTHROPIC RUNTIMEDrop the .skill bundle in.
Skills panel → Install from file → select
amaca-frontend.skill. Restart the session. Invoke withamaca-frontendor just describe an Amaca task — the skill auto-triggers on context. -
02 · FIGMA
— DESIGN TOOLSync to Figma — it writes, not just reads.
Not a skill drop. Connect the Figma connector (MCP): the skill pushes Amaca tokens as Figma Variables and generates component artboards directly into your file — bidirectional. Use a frontier-tier model for fidelity.
-
03 · CURSOR / VS CODE / WINDSURF · CONTINUE / AIDER
— EDITORS & ASSISTANTSDrop the spec at your tool's rules path.
These read a markdown rules file at the repo root — no zip upload. Place DESIGN.md (or the SKILL.md body) where your tool looks:
- Cursor
.cursor/rules/amaca.md - VS Code (Copilot)
.github/copilot-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.4.
- Cursor
-
04 · 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.4 capability tiers.
-
05 · 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.4 capability tiers.
-
06 · 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.4 capability tiers.
-
07 · 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.4 scan.
What's inside the spec
§ 03.3The DESIGN.md the skill bundles, and the raw download serves, contain:
| Block | Contents |
|---|---|
## Principles | The five rules. Verbatim from § 02. |
## Tokens · color | Every named hex, scale step, gradient, and the 85/10/5 proportion law. |
## Tokens · type | Display through caption — size, weight, leading, tracking. Family fallbacks. |
## Tokens · spacing & radius | The 4-px scale, radius steps, shadow specs. |
## Tokens · motion | Durations, easings, the single curve everything rides on. |
## Components | 14 components — buttons, forms, cards, badges, navigation, accordion, chat, loader, diagrams, checkbox — props, states, do/don't. |
## Voice | Editorial direction. Word lists. Phrasing examples. |
## Accessibility | Color pairs with ratios, focus rules, reduced-motion contracts. |
## Changelog | Every release, top to bottom. Same as § 23. |
Model capability tier
§ 03.4 · what to expectThe skill encodes a 6-step workflow and 13 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)), 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) uses obsidian-950 — 6.5:1. Light text fails WCAG AA (2.47:1). Applies to every magenta fill — primary button, CTAs, chips.
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.5Tailwind 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.6Two SemVer streams, one rule each.
Pinned to the system release. v2.6.0 here ships v2.6.0 of the spec. Strict SemVer from v1.2.6 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.0 ships with DESIGN.md v2.5.0 — multi-target across HTML, React, and Figma.
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.6| 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.1Black · 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.2Regular · 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.3Pairing rules
§ 05.4A 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.1Where to reach
§ 06.2| 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.1Elevation
§ 07.2Motion is feedback, not decoration.
Five durations, four easings. Standard for UI, Spring for playful reveals, Accel for exits, Decel for entrances.
Durations × easings
§ 08.1Click to re-trigger animations.
Duration tokens
§ 08.2| 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 |
Twelve columns, 24px gutters.
A standard 12-col grid with a 1180px max content width. Sidebar is fixed; content breathes.
The grid
§ 09.1Breakpoints
§ 09.2| 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.3Action, ranked.
One primary per screen. Everything else recedes. Five variants × three sizes × states.
Variants
§ 10.1Sizes
§ 10.2With icons
§ 10.3Forms 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.1Time 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.
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.1Arc · 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.2Click to re-trigger animations.
Small labels, loud signals.
Always Satoshi, always paired with a dot when live. Badges are feedback; they're never content.
Status variants
§ 13.1In context
§ 13.2Disclosure, 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.3| 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. Two-stage bubble entry: the container scales from 0.92 with an --ease-spring overshoot, then the inner content fades up 80ms behind on the standard ease. The wave on the typing dots loops on --d-slow. Under prefers-reduced-motion: reduce everything in this section degrades to instant.
| Element | Property | Duration · easing · delay |
|---|---|---|
.chat-bubble · container | transform (scale 0.92 → 1, spring overshoot) | --d-base · --ease-spring · 0ms |
.chat-bubble · container | opacity 0 → 1 | --d-base · --ease-standard · 0ms |
.chat-bubble-content · inner | opacity, translateY 3px → 0 | --d-quick · --ease-standard · 80ms delay |
.chat-msg.is-out · roll-off | opacity, translateY -8px, max-height 0 | --d-quick opacity/transform; --d-base collapse · --ease-accel |
.chat-dot · wave (loop) | translateY -4px, opacity 0.35 → 1 | --d-slow · --ease-standard · stagger 0 / 160 / 320ms |
.chat-composer-send · idle → ready | background, color | --d-quick · --ease-standard · 0ms |
| Reduced motion | All chat transitions disabled; .chat-dot animation stopped at rest; .chat-msg.is-out still hides (instant) so the surface never overcrowds. |
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. An IntersectionObserver fires once; 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.
| Stage | Property | Duration · easing · delay |
|---|---|---|
| Observer | threshold 0.15 | rootMargin 0px 0px -8% 0px · unobserve after fire |
| Nodes | opacity 0 → 1 | --d-base · --ease-decel · stagger ~70ms |
| Edges · clusters | opacity 0 → 1 | --d-base · --ease-decel · after nodes, +40ms each |
| Reduced motion | render to rest | no hidden state is ever set — 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.1Click to re-trigger animations.
Bars · comparison
§ 19.2Click to re-trigger animations.
Pie · composition
§ 19.3- Direct0%
- Organic0%
- Referral0%
- Social0%
- Color0%
- Spacing0%
- Typography0%
- Motion0%
Click to re-trigger animations.
Palette ramps
§ 19.4Click to re-trigger animations.
Timeline · gantt
§ 19.5Click to re-trigger animations.
Stepper
§ 19.6Click to re-trigger animations.
Progress bar
§ 19.7Click to re-trigger animation.
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.1Direct
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.2"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.3| 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. Nothing in the production palette falls below AA.
| 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-900 on magenta-500 | 6.3:1 | AA | Primary CTA (text on brand surface) |
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 |
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 |
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.
Version history, in public.
What changed, when, and why — read top to bottom. From v1.2.6 onward, the system follows strict SemVer: MAJOR for breaking changes, MINOR for additions, PATCH for fixes.
Releases
§ 23.1Latest first. Each entry documents what was added, refined, or fixed — and where unchanged, says so. Click a row to expand.
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.1 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.6 directly. Internal iterations between (drafts at v1.2.7 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 (v1.1.4 → v1.2.3) that were not shipped publicly. From v1.3.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.