SAFFCA Design System
Applied reference. Where the design system's pieces — palette, grid, type, motion, components — live as roles, relationships, and refusals, not just values.
Orientation
What this is. A single navigable reference for SAFFCA's visual system as practised. It explains roles ("when do I use accent vs. muted?"), relationships ("how does this grid recompose?"), and refusals ("why no Bootstrap?"). The user-facing question this answers: what holds the look together, and when does each piece get used?
What this isn't. Not a token catalogue (the canonical values live in tokens.css — this doc explains what they're for). Not a content guide (see voice-guide.md). Not a theory paper (see design_philosophy.md). Not a snapshot of admin styling (Filament uses its own Amber theme — see §13).
Companion to design_rationale.md. Rationale = narrative + decisions. System = applied reference + governance + refusals. Read both. Where they disagree, tokens.css wins on values; this doc wins on roles.
Framing inheritance. This doc adopts the framing from the field essay How to Build a Design System (external, sibling private repo): a design system is a maintained dynamic agreement held by a value contract, seven faces (Structure / Seam / Signal / Graveyard / Ledger / Ritual / Residue), tokens-as-decisions, components-as-contracts, refusals, AI-assisted variation governance, and a Zone 5 for unmanaged territory.
§1 — Value contract
What this system protects:
- Dignity (AX-4) — no diminishment, no exoticisation, no condescension across personal / object / system scopes.
- Multi-partiality — artists, institutions, funders, visitors all find what they need; no single perspective dominates the surface.
- Graceful degradation (AX-6) — the visual logic holds without JavaScript; the tree of conventional navigation is never removed.
- URL-spine invariance (AX-3) — the URL is the contract; the topology around it is the designed transition.
- Relationships as content (AX-0) — visual hierarchy signals connection, not just categorisation.
- Accessibility floor — WCAG 2.2 + European Accessibility Act, structurally embedded (not retrofit).
- Continuity — the system must remain interpretable after staff turnover, locale, decay.
What this system refuses:
- Decoration without role.
- Polish at the cost of exit, traceability, accessibility, or maintainability.
- AI-generated variants without provenance.
- Fast-everywhere as panic with tooling.
The order of authority is: value → invariant → affordance → sensation → behaviour. The surface comes last; it carries values into use.
§2 — Seven faces of this system
The system honestly mapped, not aspirationally drawn.
Structure (the invariants)
tokens.css— the named decisions- ADR-002 — cascade-layer order:
reset → tokens → structure → typography → degraded → transitions → view-transitions → components - The URL spine and locale routing
- The WCAG 2.2 floor
LogsProvenancetrait +provenance_logstable (every entity carries history)- The voice guide (verbal invariants)
Seam (where change passes through)
- CSS custom properties in
tokens.css - Component CSS files in
resources/css/components/ - Inertia page components in
resources/js/Pages/ - The Filament boundary (
AdminPanelProvider.php) — admin/public token systems are isolated - spatie/laravel-translatable JSON columns (locale-aware content)
Signal (how failure reports itself)
saffca:check-errors— hourly scheduled error sweep- Playwright smoke tests
- Playwright visual regression —
tests/visual/with PNG baselines undertests/visual/screenshots.spec.js-snapshots/. Present, but baselines may need refresh after motion-drift fix and any FOLLOW-UP-A or FOLLOW-UP-C changes (see §14) - Gap: no token-usage analytics, no contrast-audit lint, no bundle-budget alarm
Graveyard (deprecation paths)
Currently absent. No graveyard.md exists. No deprecation contract, no migration windows, no "removal_window" metadata. Components deprecate by quiet edit. Listed as a queued follow-up — see §14.
Ledger (the reasoning trail)
- ADRs in
docs/decisions/— ADR-001 through ADR-008 MEMORY.md(auto-memory) and per-session history inmemory/session-history.mdprovenance_logs(polymorphic, append-only)source_tierandsource_refon entities (Gold / Silver / Bronze / Copper)
Ritual (cadenced practice)
- Pierre meetings shape direction (cadence currently undocumented — flag)
- Session protocol per CII Methodology §8: read MEMORY.md + dispatcher + tactical at session open; update at close
Residue (named, not hidden)
- Brand pivot 2026-04-28. Navy retired in favour of forest green; gold family shifted from olive Walnut + Rationale to yellow-warm Walnut (derived) + Pollen + Sage. New logo at
LOGO_20260428_R2_Pierre_r2.svg. Mood goal: "less blue-corporate, more Africa." Strict role enforcement codified in §4 — Walnut accent never on Forest ink (1.19:1 invisible); Pollen on Forest fails AA non-text (2.95:1) so the brightest yellow#e4b922from the SVG stays logo-internal until a gold-on-green ribbon is actually needed. - Motion drift fixed 2026-04-27. Rationale doc previously listed
150 / 300 / 500ms; live tokens are250 / 600 / 900ms. Reconciled. - Undefined-token references. Several components reference tokens that don't exist in
tokens.css:--color-text-muted,--color-ink-muted,--color-surface-muted,--color-danger(inartists.css,layout.css), plus literalcolor: #fffinauth.css:125. The raw-hex fallbacks render. Resolution = FOLLOW-UP-A in §14. (--color-ruleand--color-accent-darkwere both previously in this list; rule is now defined; accent-dark was renamed to--color-accent-hoverin auth.css 2026-04-28.) - Breakpoint inconsistency. 768 / 640 / 600 mixed across components. Canonicalise to 768 + 640 only via a future ADR — see §14.
- Filament/public token divergence. Admin uses
Color::Amber; public uses Forest + Walnut + Pollen + Sage. No bridge — see §13.
§3 — Tokens at a glance
Reference scan. Canonical values live in tokens.css; the link target on each row points to its line.
Brand mark — as rendered
The logo is where every token below is composed. Masthead and footer apply two different role-treatments of the same SVG:
The masthead variant carries the SVG's full palette (#166734 Forest ink, #cca42c Pollen rule, #e4b922 Bright Pollen stars, #57807f Sage figure). The footer variant flattens every fill to white via a CSS filter chain, then softens to 0.85 opacity so it sits with the rest of the footer text rhythm. Source: AppLayout.vue:45,97, layout.css:35–46,180–195.
Colour
Source rows: tokens.css:13–22.
Typography scale
Line heights --leading-tight/normal/relaxed = 1.2 / 1.5 / 1.75. Tracking --tracking-tight/normal/wide/wider = -0.02 / 0 / 0.05 / 0.1 em. See tokens.css:24–41.
Spacing
Adopted Option γ (minimum tweak) on 2026-04-27. xs and sm shifted up to 0.375 / 0.625rem to remove the two ×2 doublings that previously kicked the start of the curve. 6xl..11xl (linear +2rem) added to anchor logo widths on-scale: masthead = --space-11xl (20rem), footer = --space-10xl (18rem) — one step below.
Two-mode curve, deliberately. xs..5xl is perceptual — geometric ratios in the 1.33–1.5 band, where the eye reads steps as relative ("twice the gap" / "half the gap"). 5xl..8xl shifts to compositional — linear +2rem increments, where spacing serves layout anchors (logo widths, hero gutters, page rhythms) and clean integer distances matter more than perceptual ratios. The transition at ~8rem is the threshold where spacing stops being "feel" and starts being "fit." Ratios at the top narrow naturally (1.25 → 1.20 → 1.167) — that's the arithmetic of constant absolute steps over a growing baseline, not a flaw in the scale.
Source: tokens.css:44–58.
Layout
Page-rail tokens, rendered at one-third their real rem value so all three fit the doc page. Bars are proportional; numeric labels carry the actual value.
Source: tokens.css:55–57.
Motion
Three durations rendered as live-animating bars. Each bar fills over its duration and reverses, looping — the eye reads the difference between fast and slow at real timing.
Three easings rendered as travelling dots. Same distance, same duration (600ms); the curve is the variable. Watch how each dot decelerates differently:
Source: tokens.css:60–65.
Adjacency rings (spatial discovery)
Three concentric rings centred on the focal node. Real values are viewport-relative (vmin), proportional rendering shown here at the 35 / 55 / 75 ratios:
--adj-zoom-duration aliases --duration-slow (900ms — see Motion above). Source: tokens.css:68–71.
Z-index scale
Real layering: each card sits one z-level above the one below.
Source: tokens.css:74–77.
Radii
Three corner-radius tokens, rendered at real value:
Source: tokens.css:80–82.
§4 — Colour palette and relationships (focus area)
a) Palette anatomy
Brand pivot 2026-04-28 — colours canonical to LOGO_20260428_R2_Pierre_r2.svg. Three brand hues:
--color-ink#166734— Forest (logo wordmark / tagline / Africa outline). Replaces the prior navy. AA body 6.48:1 on cream.--color-paper#f8f7f3— warm cream--color-rule#cca42c— Pollen (logo wordmark-separator gold). Decorative on cream only (2.20:1 per WCAG SC 1.4.11).
Plus a derived text-role accent and an auxiliary:
--color-accent#7a6219— Walnut (text role; links / focus / hover on cream only; AA body 5.46:1). Same yellow-warm hue family as Pollen, darkened ~25% so it's readable as small text. Derived from the SVG; not in it.--color-sage#57807f— Sage (logo Egungun-figure teal). Auxiliary brand colour for chips, tags, category indicators.
Six utility colours that operationalise the brand:
--color-accent-hover#635014— accent under interaction--color-muted#6f6e62— secondary text (warm taupe; replaces the prior cool blue-grey to support the "less blue-corporate" direction)--color-border— quiet division--color-focus— focus ring (aliased to accent)--color-surface— elevation cue (white)--color-highlight— empty-state ground
Logo-internal (not tokenised):
#e4b922Bright Pollen — EU-stars fill in the SVG. AA non-text 3.72:1 on Forest. Tokenise as--color-rule-brightonly when a component actually needs gold-on-Forest ornament.
b) Application logic — the "when"
| Token | When to use | When NOT to use |
|---|---|---|
--color-ink |
Primary text. Header / footer ground. Structural authority. The default text colour everywhere. | Pure black anywhere — Forest replaces it. |
--color-paper |
Body background. The field everything sits on. Also: text colour on Forest-ink backgrounds (footer, ink ribbons). | As card surface (use --color-surface). |
--color-accent |
Interactive cues on cream: links, hover, focus, type badges. Text-role gold, cream backgrounds only. | Never on Forest ink (1.19:1 invisible). Never on large surfaces — that's decoration. |
--color-accent-hover |
The state continuation of accent under pointer / keyboard interaction. | As a default accent. |
--color-rule |
Decorative ornament on cream: hero rule, edges, the wordmark-separator inside the brand mark. | As a text colour anywhere. On Forest ink (2.95:1 — fails AA non-text). |
--color-sage |
Auxiliary — chips, tags, supporter / photographer / artist category indicators in admin and public. Quiet earth-tone register that complements Forest. | Body text. Primary surfaces. |
--color-muted |
Secondary text — metadata, dates, source provenance, captions. Warm-grey taupe. | Anything load-bearing. Body copy. |
--color-border |
Card edges, dividers, table rules. Quiet division. | To attract attention. As accent, rule, or sage. |
--color-focus |
Focus rings. Aliased to accent so accent's interaction-role is reinforced visually. | As a separate "branded accent." |
--color-surface |
Cards, modals — elevation cue against --color-paper. |
As body background (paper is the field). |
--color-highlight |
Placeholder cards, empty states, "no image yet" grounds. Absence-without-judgment. | As a hover state. As accent. |
The split between --color-accent and --color-rule is deliberate: text needs WCAG AA body 4.5:1; ornament only needs 3:1. By splitting, the system gets both — readable links and a properly bright gold for the hero rule, without compromising either.
c) Pairings and contrast
The intended pairings (computed against WCAG 2.1 AA):
| Foreground | Background | Use | Approx contrast |
|---|---|---|---|
--color-ink #166734 |
--color-paper #f8f7f3 |
Body text on page | 6.48 : 1 ✓ AA body (AAA-Large) |
--color-ink #166734 |
--color-surface #ffffff |
Body text on cards | 6.85 : 1 ✓ AA body |
--color-paper #f8f7f3 |
--color-ink #166734 |
Text on Forest-ink ground (footer, ink ribbons) | 6.48 : 1 ✓ AA body |
--color-muted #6f6e62 |
--color-paper #f8f7f3 |
Metadata on page | 4.80 : 1 ✓ AA body |
--color-accent #7a6219 |
--color-paper #f8f7f3 |
Links / hover / focus | 5.46 : 1 ✓ AA body |
--color-accent #7a6219 |
--color-surface #ffffff |
Links on cards | 5.77 : 1 ✓ AA body |
--color-accent #7a6219 |
--color-ink #166734 |
Walnut on Forest | 1.19 : 1 ✗ INVISIBLE — never use |
--color-paper #f8f7f3 |
--color-accent #7a6219 |
Inverted button label | 5.46 : 1 ✓ AA body |
--color-rule #cca42c |
--color-paper #f8f7f3 |
Hero rule, ornament edge | 2.20 : 1 — decorative-only per WCAG SC 1.4.11 |
--color-rule #cca42c |
--color-ink #166734 |
Pollen on Forest | 2.95 : 1 ✗ — fails AA non-text; do not use |
--color-sage #57807f |
--color-paper #f8f7f3 |
Auxiliary chips, tags | 4.08 : 1 ✓ AA non-text + AA-Large body |
Note on the rule on paper: at 2.1:1 it falls below AA-non-text 3:1. The rule is intended for graphical accent only, not user-interface lines. Where contrast matters (e.g. focus rings, links), use --color-accent. The hero rule is treated as a decorative atmospheric element — same category as a gold leaf in a fine-press masthead.
Anything pairing --color-muted with --color-highlight or --color-border falls below AA — don't.
d) Aspirations and current residue
The rules above are the target. Living deviations the system currently tolerates (logged in §2, queued in §14):
- Six tokens referenced in components don't exist in
tokens.css; the raw-hex fallbacks render. This means the colour palette in production is broader than what's declared here. - Literal
color: #fffappears inauth.css:125— should migrate to--color-paperor--color-surfacedepending on context.
The doc names these so they remediate, not so they hide.
e) Refusals (target-state)
- No raw hex in components. Tokens always.
- No new colour without a role.
- No dark mode currently — deferred decision (not a "no", but unresolved — see §14).
- Accent never used on large surfaces. It is a signal, not a wash.
f) Provenance
Derived from the SAFFCA logo. The system has now been through two pivots:
2026-04-27 — accent role split into two tokens against LOGO_20260427.svg: --color-accent → #7d6428 (Walnut), --color-rule → #caa84b (Rationale), --color-ink → #192744 (Navy). The home hero rule, previously rendered as var(--color-border) (a pale grey, never actually gold in the implementation), was rewired to var(--color-rule).
2026-04-28 — brand pivot to forest green. LOGO_20260428_R2_Pierre_r2.svg replaces the navy-era logo. User direction: "less blue-corporate vibe, more Africa." Tokens canonical to the new SVG where viable: --color-ink → #166734 (Forest), --color-rule → #cca42c (Pollen, the logo's wordmark-separator), --color-sage → #57807f (Sage, the Egungun-figure teal). The text-role accent --color-accent → #7a6219 is derived from the SVG's Pollen, darkened ~25% to keep AA-body 5.46:1 on cream — the SVG's own Pollen at 2.20:1 fails text contrast and is reserved for decorative-only use. Warm-grey --color-muted → #6f6e62 replaces the prior cool blue-grey, completing the move away from blue tones. The brightest yellow #e4b922 (EU stars) and the implementation of strict role enforcement (Walnut never on Forest ink at 1.19:1; Pollen never on Forest at 2.95:1) are documented in §4b's application-logic table.
The doc-as-spec ↔ logo-as-spec loop holds: where SVG values pass contrast, tokens take them canonical; where they don't, tokens are derived transparently from the same hue family.
Approved by Pierre 2026-04-28. The green regime is the settled brand.
§5 — Grid and spatial system (focus area)
The grid here is not a 12-col abstraction. It is a small set of named patterns that recompose responsively, plus a separate spatial-discovery layer (adjacency rings) that is conceptually distinct from the page grid.
a) Page-level layout
Three layout tokens carry the document container:
| Token | Value | Use |
|---|---|---|
--content-width |
72rem | Default max width for content rails |
--content-narrow |
48rem | Long-form text, narrative views (about, support) |
--sidebar-width |
16rem | Reserved (not used yet) |
structure.css is currently minimal — body is a flex column on --color-paper. The grid lives in components.
b) Component grid patterns
Codified from current emergent use. Each pattern is a contract: purpose, minimum cell, behaviour, source, and an anti-example.
| Pattern | Purpose | Min cell | Behaviour | Source | Anti-example |
|---|---|---|---|---|---|
| Card grid (standard) | Artists / team index | 260–300px | repeat(auto-fill, minmax(N, 1fr)) |
artists.css:5 (260px), team.css:20 (300px), events.css:5 (300px) |
Don't use as the sole hero |
| Card grid (compact) | Dense lists, supporters, mobile reflow | 140–160px | same | artists.css:10 (160px), pages.css:154 (supporters, 140px), artists.css:315 (140px ≤ 640px) |
Don't use for primary discovery |
| Mosaic | Events feature region | n/a | repeat(var(--mosaic-cols, 3), …) × rows |
events.css:116 |
Don't use without curation |
| Masonry (CSS columns) | Artwork browse, home hero artworks | 220–260px | columns: 4 N |
artworks.css:4 (250px), artworks.css:9 (220px browse), home.css:167 (260px) |
Reading order is column-down — flag in IA / a11y |
| Two-column profile | Artist profile | 300px 1fr |
Stacks ≤ 768px | artists.css:74, artists.css:303 |
Don't use for non-portrait subjects |
| Asymmetric hero | Home hero | 1.4fr 1fr |
Stacks ≤ 768px | home.css:7, home.css:90 |
Don't repeat off home |
Note: supporters live in pages.css (not a supporters.css — that file does not exist).
c) Breakpoints and recomposition
Three breakpoints in active use today:
| Width | Used by |
|---|---|
| 768px | Artist profile stack, home recompose, layout main, adjacency |
| 640px | Artist compact grid shrink, content-strip, team stack |
| 600px | Events grid stack |
The 600 / 640 mix is residue (§2). Canonicalise to 768 + 640 only (next-available ADR — see §14). Until then: 768 = "tablet recompose," 640 = "mobile pack," 600 = legacy events-only.
The principle from ux-vision.md is recompose, not reflow — at each breakpoint the layout is redrawn, not just narrowed.
d) Container queries
Not used today. Candidate where view-type recomposition would benefit (e.g. a card that must reflow based on its container width, not viewport width — adjacency edges, embedded rails). Listed in §14.
e) Adjacency rings (separate spatial concept)
Three rings at 35 / 55 / 75 vmin (--adj-ring1/2/3-radius) carry the spatial-discovery model documented in concept__permeable-spatial-navigation.md. This is not a page grid — it is a rendering geometry for computed adjacency. Page grid and adjacency rings co-exist; they don't reduce to each other.
f) Refusals
- No fixed-pixel page widths.
- No Bootstrap-style 12-column abstract grid (rejected per
CLAUDE.md). - No fixed-height content cards.
- No grids that deny mobile recomposition.
§6 — Type system
Family. Montserrat (Google Fonts), matching the WordPress theme. Weights loaded: 200, 300, 400, 500, 600, 700; italic 400 and 600. Mono fallback (--font-mono) reserved for code-like content.
Weight discipline.
- 200 — display (hero title only).
- 300 — section headings, view headlines.
- 400 — body copy.
- 500–600 — emphasis, navigation links, badges.
- 700 — reserved.
Tracking. --tracking-wide and --tracking-wider reserved for uppercase labels (nav, type badges, metadata caps). --tracking-tight reserved for display sizes. Body uses --tracking-normal.
Multilingual. hyphens: auto for FR and NL; hyphens: manual for EN. text-wrap: balance on headings. The system must hold across three languages without one becoming a second-class citizen.
Italic + 600 is the convention for quoted voice — artists describing their own practice (per Clean Language: use the artist's words, not a paraphrase), pulled passages in biography text, testimonials, and any content where the reader needs to feel the shift from institutional narration to first-person speech. Block quotes inherit this treatment; inline <em> should not, to preserve italic's legibility for body emphasis.
Implementation: typography.css. Tokens: tokens.css:24–41.
§7 — Motion
Three durations carry intent:
| Token | Value | Use |
|---|---|---|
--duration-fast |
250ms | Hover colour/opacity changes, focus rings |
--duration-normal |
600ms | Card transitions, layout shifts, route fades |
--duration-slow |
900ms | View-morphing transitions, adjacency zoom (--adj-zoom-duration aliases this) |
Three easings:
--easing-defaultcubic-bezier(0.4, 0, 0.2, 1) — standard.--easing-springcubic-bezier(0.34, 1.56, 0.64, 1) — playful (sparingly).--easing-depthcubic-bezier(0.42, 0, 0.58, 1) — depth/parallax suggestion.
Named view-transition groups. artwork-hero and artist-portrait are the two named morph anchors that survive route changes. See view-transitions.css.
Reduced motion is the floor, not a fallback. All timed work respects prefers-reduced-motion: reduce. Inertia transitions and named-group morphs disable themselves under that media query — see transitions.css and view-transitions.css.
Hover the buttons below to feel each duration:
§8 — Visual / aesthetic logic — applied (focus area)
The chain is value → invariant → affordance → sensation → behaviour. The system below is the chain made concrete.
Restraint as authority. Few colours, generous space, precise type. Institutional dignity (AX-4) does not arrive through ornament; it arrives through composure. The accent gets one role (interaction). The body gets one ground (paper). The hierarchy is carried by typography weight and spacing, not by colour proliferation.
Image primacy. Visual is content, not decoration. Chrome defers. Operationalised by image-led card grids (artwork hero takes the card; metadata is secondary) and by no-overlay artwork display — artworks are not draped with controls when shown in their dignity. See feedback_artwork-dignity-no-overlay.md in project memory.
Edge as continuity. Viewport edges hint at adjacency — content from neighbouring spaces leaks at the periphery. This is part of the system, not ornament. The adjacency rings (§3, §5e) are the geometry that makes edges meaningful: what is "near" is computed, not decorated.
Anti-decoration principle. Every colour, weight, motion, and grid choice serves content / hierarchy / relation (AX-0 — relationships are content). If a visual element does not serve one of these three, it does not belong. This is the cleanest available test: "What is this for?" If the answer is "atmosphere," the answer is no.
Parametric variation (P5). The system is meant to have range, not uniformity — see Munari's "a tree does not have identical leaves." The candidate axes (programme type, geographic context, temporal distance, artist medium) are not yet decided. Variation must emerge from meaningful parameters, not be imposed cosmetically (Eglash's African fractals, design_philosophy.md). Treat as Zone 5 until decided. The system permits variation; it does not yet specify which variation.
Graceful degradation as visual logic. The visual system holds without JavaScript. degraded.css is the floor. Image-led views must remain legible when images are missing — per-view-type fallback compositions are still TBD (rationale doc §"Missing Image Fallback") and tracked in §14.
§9 — Components as contracts (sketch)
Most components today live as ad-hoc CSS files plus Vue page components. They do not yet ship with the contract a real component should carry: anatomy, states, accessibility, content rules, refusals, deprecation path. This section sketches the contract format and shows one worked example. The rest is queued for iterative fill (§14).
Worked example: Artist card (standard)
| Field | Value |
|---|---|
| Purpose | Index-grid representation of an artist; entry point to artist profile |
| Where used | Artists index, home hero (artist row), event detail (participation list) |
| Anatomy | Portrait (3:4) → name → discipline / nationality (muted) |
| States | default · hover (border colour shifts to --color-accent) · focus-visible (focus ring on accent) · missing-image (text-led fallback — currently undefined) |
| Accessibility | Card is a single semantic link; portrait has descriptive alt; focus ring inherits --color-focus |
| Tokens | --color-surface ground · --color-ink name · --color-muted metadata · --color-accent interaction |
| Refusals | No overlay controls on the portrait. No hover-only information. No fixed pixel height |
| Source | artists.css:5–40 |
| Open | Missing-image fallback composition; parametric variation (e.g. artists with video) |
| Deprecation path | n/a (current canonical) |
Components inventory (contract-format pending)
- Artist card (worked above), artist profile (two-column), artist compact card
- Event card (16:9), event mosaic tile, event detail block
- Artwork tile (masonry, arbitrary aspect ratio), artwork hero (named-group morph anchor)
- Team card (3:4 portrait, click-to-expand bio — Pierre 2026-02-22)
- Supporter tag (compact card, link-aware)
- Page hero, section heading, gold rule
- Adjacency-ring view, content strip
- Auth card (note: references undefined tokens — §2 Residue)
Filling each row above is an iterative job. Don't block.
§10 — Refusals (consolidated)
Targets, with current residue acknowledged in §2. The system explicitly does not:
- Use Bootstrap or Tailwind on the public frontend (
CLAUDE.md, ADR-002). Filament retains its own Tailwind v3 inside the admin boundary — that is a separate stack. - Allow raw hex in components. Tokens always. Current residue: undefined-token fallbacks and one literal
#fff(§2). - Decorate without role. Every colour / weight / motion / grid choice answers a "what is this for?" check.
- Permit AI-generated variants without provenance. See §11.
- Trade exit, accessibility, traceability, or maintainability for polish. Polish is downstream of the value contract (§1), not in tension with it.
- Produce graceful degradation as a fallback only. It is the floor; the JS layer is enhancement.
§11 — AI-assisted variation governance
Claude (and other AI tools) participate in producing this codebase. The system's stance:
- Source classification applies. Per CII Methodology §5: Gold (
_assets/input/) > Silver (WP migration) > Bronze (correspondence) > Copper (web) > Rejected (fabricated). Token values are Gold by definition —tokens.cssis the single source of truth. - No fabrication. Don't invent values, don't synthesise component contracts that aren't earned by use, don't output a hex that isn't in
tokens.csswithout flagging it as new. - Provenance is explicit. When AI proposes a variant (component, token, layout pattern), it must declare what it derived from and what invariants from §1 it preserves.
- And-Yet on integrity drift. If a generated variant violates an invariant (accessibility, dignity, URL spine), the response is
And-Yet: log honestly what was attempted and what remains true. Seefeedback_pipeline-truth.mdfor the broader pattern. - Ledger lives in
MEMORY.md+ session-history + ADRs. A generated decision that survives is recorded; one that doesn't, isn't. - Verify before recommending. Before pointing a user at a token, file path, or component contract, check it exists now — see
feedback_verify-before-claiming-gaps.mdin project memory.
§12 — Public ↔ admin boundary
The admin panel (Filament v3.3) uses Color::Amber and Filament's own design system. Public frontend uses Forest / Walnut / Pollen / Cream (with Sage as auxiliary) and the layered CSS architecture per ADR-002. The two systems do not share tokens. This is a current state, not a settled answer:
- Bridging would require either porting Filament's theme to use SAFFCA tokens (heavy lift, fragile across Filament upgrades) or surfacing a small shared palette via CSS variables on the admin shell.
- Accepting divergence keeps Filament upgradable but means staff using both surfaces feel the seam.
Listed as an open question in §14. No decision in this doc.
§13 — Open design questions / Zone 5
Honest gaps. Surfaced, not closed.
- Spacing rhythm rules. Tokens exist (
--space-xs…--space-5xl); rules don't. Which--space-Nbetween which elements? Adjacent cards? Within cards? Above section headings? - Parametric variation axes (P5). Programme type? Geographic context? Temporal distance? Medium? Pick one, prototype, evaluate.
- Per-view-type image-less fallbacks. What does an artist card without a portrait look like? An event card without a hero image?
- Atmospheric treatment per node type (concept doc §2.5). What visual differences distinguish a residency from an event from an artwork at viewport edges?
- Container-query opportunities. Where would per-component recomposition beat per-viewport recomposition?
- Filament admin parity. Bridge or accept? See §12.
- Dark mode posture. Currently absent. Deferred decision, not a "no."
- Component contract rollout. Only one worked example today (§9). The format is set; the work is iterative.
- Visual regression baselines. Refresh after FOLLOW-UP-A and FOLLOW-UP-C land (§14).
- Graveyard process. No
graveyard.md, no deprecation contract. - Sustainability accounting. Asset weight, motion budget, third-party scripts, dark patterns audit. Not in any current pipeline.
- Resolve undefined-token references — see FOLLOW-UP-A in §14.
A design system with no wilderness becomes a small state.
§14 — Named follow-ups
These are not in this doc's scope. They are tracked here so they don't disappear.
FOLLOW-UP-A — Resolve undefined-token references (affects rendered UI today, not just docs). Components reference tokens that don't exist in tokens.css (--color-text-muted, --color-ink-muted, --color-surface-muted, --color-accent-dark, --color-danger) plus a literal #fff in auth.css:125. Browsers fall back to the inline hexes — production rendering is shaped by these unsanctioned values. Resolution: define the missing tokens in tokens.css (preferred) or migrate components to existing tokens and remove fallbacks. Track as a P-row. (--color-rule was previously in this list pre-2026-04-27 but is now defined.)
FOLLOW-UP-B — Refresh Playwright visual baselines under tests/visual/screenshots.spec.js-snapshots/ after any change that affects rendered output (FOLLOW-UP-A or FOLLOW-UP-C, not the doc-only motion fix in this batch).
FOLLOW-UP-C — Gold colour-quality pass. ✓ Done 2026-04-27, ✓ superseded by 2026-04-28 brand pivot. The Walnut + Rationale split was the right resolution against the navy logo; the forest-green pivot rotated the entire palette, so the gold values shifted to Pollen + derived Walnut. Roles preserved (text-AA accent + decorative rule); contrast figures recomputed in §4c. Visual baselines refresh queued (FOLLOW-UP-B) for the green-era render.
FOLLOW-UP-D — Render system.md in-browser. ✓ Done 2026-04-27. Live at /about/site/design-system (public, no env or auth gate; relocated 2026-05-16 from /about/design-system, which now 301-redirects, as part of the /about/site colophon). Backed by App\Http\Controllers\DesignSystemController, rendered via league/commonmark, wrapped in resources/views/design-system.blade.php with full design-token CSS so the inline-HTML demos render with real values.
Out of scope (queued, possibly via /schedule):
- Seed
docs/design/graveyard.mdwith deprecation contract template. - Draft breakpoint-canonicalisation ADR (next available number — ADR-008 is taken).
- Build out remaining component contracts iteratively (§9).
- Decide Filament admin token bridge vs accepted divergence (§12).