diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index df3d64d..e25a415 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -34,6 +34,9 @@ jobs: exit 1 fi + - name: Token contrast (WCAG AA on every style palette) + run: python3 scripts/check_contrast.py + - name: Context budget run: python3 scripts/validate.py --budget @@ -47,3 +50,30 @@ jobs: test -f "$tmp/.cursor/rules/agent-design-taste.mdc" test -f "$tmp/.windsurf/rules/agent-design-taste.md" test -f "$tmp/.github/copilot-instructions.md" + + render: + name: Rendered verification of the 15 example pages + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: "20" + + - name: Install Playwright Chromium + run: | + npm init -y >/dev/null 2>&1 + npm install --no-save playwright@1.56.1 + npx playwright install --with-deps chromium + + - name: Render every example at 1440 / 768 / 390 / 360 + run: node scripts/screenshot.mjs + + - name: Upload screenshots + if: always() + uses: actions/upload-artifact@v4 + with: + name: example-screenshots + path: screenshots/ + retention-days: 7 diff --git a/CHANGELOG.md b/CHANGELOG.md index fd1c4ea..be21d81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,52 @@ For a skill, "breaking" means: a renamed or moved file that installations reference, a changed workflow contract, or a removed rule an agent may be relying on. +## [2.1.0] — 2026-09-08 + +Enforcement release. 2.0.0 wrote the accessibility rules down; this one measures +them. Both new checks found real failures in this repository on their first run. + +### Added +- `scripts/check_contrast.py` — measures every style palette against WCAG AA: + text pairs at 4.5:1, focus indicators at 3:1, in both themes. It resolves dark + as light-then-dark (dark blocks are partial overrides, so the pairs most + likely to fail were the ones never being checked) and composites translucent + colors before measuring. Waivers require a written reason. +- CI now runs the contrast check, and **renders all 15 example pages** at + 1440/768/390/360 with `scripts/screenshot.mjs`, failing on mobile overflow and + uploading the screenshots as an artifact. The render check existed in 2.0.0 + but only ever ran by hand. + +### Fixed +- **Focus rings were invisible across 14 of 15 styles.** Every one used a + translucent color (alpha 0.30–0.45), compositing to 1.2–1.8:1 against its own + background — the exact 🔴 BLOCKER `ANTI-SLOP.md` defines. All are now solid. + Two styles were also contradicting their own §13: 05 documents a "thick black + outline" and shipped a pink ring at 1.98:1; 06 documents a "high-contrast + outline" and shipped neon yellow at 1.04:1. Both now use their text color. +- **14 of 15 example pages had no authored focus style at all** — no + `:focus-visible`, no `:focus`, no `outline`. Each now ships one, using a + palette color verified at 3:1 against that page's own background. +- **11 button/label pairs failed 4.5:1** — white-on-mid-tone labels at 1.86–4.48:1. + Fixed by adapting the label where the fill is a light tint, and by an + imperceptible darkening of the fill elsewhere; brand hues are preserved. +- **Glassmorphism's accent was `#8B5CF6`** — the exact hex `ANTI-SLOP.md` names + as the over-used default violet, and it failed contrast at 4.23:1. Now + `#7C3AED`: still unmistakably violet, off the flagged default, 5.70:1. +- `scripts/gen_tokens.py` treated **every** `:root` as the light palette, + including one nested inside `@media (prefers-reduced-motion: reduce)`. A token + misplaced there looked correct in the generated JSON while applying only to + reduced-motion users in a browser. At-rule blocks are now excluded. +- Example palettes re-synced to the corrected tokens, so no reference page + demonstrates a failure the tokens no longer have. + +### Changed +- `design-tokens/TOKENS-GUIDE.md`, `accessibility/ACCESSIBILITY.md`, + `CONTRIBUTING.md` and both READMEs document the enforced checks — including + why `--color-border` is deliberately *not* held to 3:1 (WCAG 1.4.11 exempts + boundaries not needed to identify a component; failing all 15 styles on + hairlines would get the check switched off). + ## [2.0.0] — 2026-09-04 The onboarding and routing release. The design knowledge was already here; this diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 378fea8..79ce837 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,11 +9,12 @@ measurably better, and bad if it only makes the documentation longer.** ## Before you open a PR ```bash -python3 scripts/validate.py # what CI runs +python3 scripts/validate.py # structure, links, generated files +python3 scripts/check_contrast.py # WCAG AA on every style palette node scripts/screenshot.mjs # render all 15 examples, catch overflow ``` -The first is what CI runs. It checks every style folder, every required file, every +All three run in CI. It checks every style folder, every required file, every internal link, every generated file's freshness, and the 24-section structure of the style DNAs. @@ -84,8 +85,10 @@ styles/16-your-style/ spacing, radius, shadows, borders, container, motion, motion-ease — including `--shadow-focus`. - [ ] Light theme in `:root`, dark theme in `[data-theme="dark"]`. -- [ ] Contrast verified: every text/background pair used together passes - 4.5:1 (body) or 3:1 (large text and UI boundaries). +- [ ] `python3 scripts/check_contrast.py` passes for the new style — every + text/background pair and the focus indicator, in both themes. Focus rings + must be **solid**: a translucent ring composites to roughly a third of the + contrast it looks like it has. - [ ] `example.html` opens in a browser with no build step and no network dependency beyond webfonts, and holds at **1440 / 768 / 390 / 360** — verify with `node scripts/screenshot.mjs `. diff --git a/README.md b/README.md index 6e20d41..3c5cac7 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ **Teach your coding agent *how to design* — not just how to code.** [![License: MIT](https://img.shields.io/badge/License-MIT-black.svg)](LICENSE) -[![Skill v2.0.0](https://img.shields.io/badge/skill-v2.0.0-black.svg)](SKILL.md) +[![Skill v2.0.0](https://img.shields.io/badge/skill-v2.1.0-black.svg)](SKILL.md) [![15 styles](https://img.shields.io/badge/styles-15-black.svg)](#the-15-styles) [![validate](https://github.com/aievolutionpl/agent-design-taste/actions/workflows/validate.yml/badge.svg)](https://github.com/aievolutionpl/agent-design-taste/actions/workflows/validate.yml) @@ -394,6 +394,7 @@ Fifteen is noise with a brief buried in it.** ```bash python3 scripts/validate.py # full structural check — what CI runs +python3 scripts/check_contrast.py # WCAG AA on every style palette python3 scripts/validate.py --budget # measured context cost per file python3 scripts/gen_tokens.py # regenerate tokens.json + tokens.tailwind.css python3 scripts/gen_manifest.py # regenerate styles/index.json + manifest @@ -403,7 +404,9 @@ node scripts/screenshot.mjs 10 # render a style at 1440/768/390/360 CI verifies that every style folder is complete, every required token category exists (including `--shadow-focus`), every internal link resolves, every `example.html` parses, all 15 DNAs share the 24-section architecture, and every -generated file is in sync with its source. +generated file is in sync with its source. It also **measures** every palette +against WCAG AA and **renders** all 15 example pages at 1440/768/390/360, +failing on horizontal overflow. The rules are enforced, not just written down. Adding style #16? [`CONTRIBUTING.md`](CONTRIBUTING.md) and [`docs/STYLE-TEMPLATE.md`](docs/STYLE-TEMPLATE.md) have the full checklist. diff --git a/README.pl.md b/README.pl.md index 253011b..2f2ba04 100644 --- a/README.pl.md +++ b/README.pl.md @@ -9,7 +9,7 @@ **Naucz swojego agenta *jak projektować* — nie tylko jak kodować.** [![Licencja: MIT](https://img.shields.io/badge/License-MIT-black.svg)](LICENSE) -[![Skill v2.0.0](https://img.shields.io/badge/skill-v2.0.0-black.svg)](SKILL.md) +[![Skill v2.0.0](https://img.shields.io/badge/skill-v2.1.0-black.svg)](SKILL.md) [![15 stylów](https://img.shields.io/badge/style-15-black.svg)](#15-stylów) [![validate](https://github.com/aievolutionpl/agent-design-taste/actions/workflows/validate.yml/badge.svg)](https://github.com/aievolutionpl/agent-design-taste/actions/workflows/validate.yml) @@ -408,6 +408,7 @@ szum z briefem gdzieś w środku.** ```bash python3 scripts/validate.py # pełna kontrola strukturalna — to samo co CI +python3 scripts/check_contrast.py # WCAG AA dla palety każdego stylu python3 scripts/validate.py --budget # zmierzony koszt kontekstu per plik python3 scripts/gen_tokens.py # regeneruje tokens.json + tokens.tailwind.css python3 scripts/gen_manifest.py # regeneruje styles/index.json + manifest @@ -417,7 +418,10 @@ node scripts/screenshot.mjs 10 # renderuje styl w 1440/768/390/360 CI sprawdza, czy każdy folder stylu jest kompletny, czy istnieją wszystkie wymagane kategorie tokenów (w tym `--shadow-focus`), czy każdy link wewnętrzny działa, czy każdy `example.html` się parsuje, czy wszystkie 15 DNA ma tę samą -24-sekcyjną strukturę i czy pliki generowane są zgodne ze źródłem. +24-sekcyjną strukturę i czy pliki generowane są zgodne ze źródłem. CI dodatkowo +**mierzy** każdą paletę względem WCAG AA i **renderuje** wszystkie 15 stron +przykładowych w 1440/768/390/360, kończąc błędem przy przewijaniu poziomym. +Reguły są egzekwowane, nie tylko zapisane. Dodajesz styl #16? [`CONTRIBUTING.md`](CONTRIBUTING.md) i [`docs/STYLE-TEMPLATE.md`](docs/STYLE-TEMPLATE.md) mają pełną listę kontrolną. diff --git a/SKILL.md b/SKILL.md index a34f543..d390fa5 100644 --- a/SKILL.md +++ b/SKILL.md @@ -2,7 +2,7 @@ name: agent-design-taste description: Use when generating, redesigning or reviewing any UI — landing page, dashboard, app screen, component, or design system. Teaches the agent to analyze product and audience first, choose one design style deliberately, build from real tokens, render it, and audit it — instead of producing generic "modern premium AI SaaS" output. license: MIT -version: 2.0.0 +version: 2.1.0 metadata: repository: https://github.com/aievolutionpl/agent-design-taste styles: 15 diff --git a/accessibility/ACCESSIBILITY.md b/accessibility/ACCESSIBILITY.md index 8118aff..a17df95 100644 --- a/accessibility/ACCESSIBILITY.md +++ b/accessibility/ACCESSIBILITY.md @@ -61,6 +61,14 @@ outlines without replacing them makes the interface unusable by keyboard — - The ring needs **3:1 against both** the component and the page behind it. A blue ring on a blue button is invisible. +- **Make the ring solid.** A ring at 30–40% opacity composites to roughly a + third of the contrast its hue suggests — `rgb(37 99 235 / 0.35)` on white + measures about 1.8:1, not the 5.2:1 the colour alone would give. Every style + in this repository shipped a translucent ring once; all of them failed. If + you want a soft glow, put it *outside* a solid ring, not instead of one. +- Verify it, don't eyeball it: `python3 scripts/check_contrast.py` resolves each + style's `--shadow-focus`, composites any alpha, and measures it against both + the page and the surface. - Style-specific traps: glass and neumorphism swallow thin rings — go thicker and solid; brutalism already *is* a thick outline, so keep it and add offset. - Never let a sticky header cover the focused element. `scroll-margin-top` on diff --git a/design-taste.manifest.json b/design-taste.manifest.json index e5df35f..0383fe8 100644 --- a/design-taste.manifest.json +++ b/design-taste.manifest.json @@ -3,8 +3,8 @@ "name": "agent-design-taste", "title": "Agent Design Taste", "tagline": "Design Intelligence for AI Agents", - "repositoryVersion": "2.0.0", - "skillVersion": "2.0.0", + "repositoryVersion": "2.1.0", + "skillVersion": "2.1.0", "license": "MIT", "repository": "https://github.com/aievolutionpl/agent-design-taste", "entrypoints": { @@ -126,6 +126,7 @@ "generateManifest": "scripts/gen_manifest.py", "validate": "scripts/validate.py", "screenshot": "scripts/screenshot.mjs", + "checkContrast": "scripts/check_contrast.py", "install": "scripts/install.sh" }, "styleCount": 15, diff --git a/design-tokens/TOKENS-GUIDE.md b/design-tokens/TOKENS-GUIDE.md index 6fa3769..481502e 100644 --- a/design-tokens/TOKENS-GUIDE.md +++ b/design-tokens/TOKENS-GUIDE.md @@ -50,9 +50,20 @@ Minimum color roles: `bg` · `surface` · `text` · `text-secondary` · `border` consuming project, and should be documented where it is added. 3. **No ad-hoc values.** If you type `13px` or `#7c3aed` in a component, either it should be a token or the tokens are wrong. Fix the tokens. -4. **Contrast is part of the token contract.** Every text/background pair used - together passes 4.5:1 (body) or 3:1 (large text and UI boundaries) — in both - themes. See `accessibility/ACCESSIBILITY.md`. +4. **Contrast is part of the token contract, and it is enforced.** + `python3 scripts/check_contrast.py` measures every text/background pair and + every focus indicator in both themes, and CI fails on a miss. It resolves the + dark palette as light-then-dark (dark blocks are partial overrides) and + composites translucent colors before measuring — a ring at 35% opacity is + measured as what it actually paints, not as the hue it was authored in. + A style that genuinely cannot pass a pair adds a waiver **with a reason** + rather than silencing the check. See `accessibility/ACCESSIBILITY.md`. + + Not checked at 3:1: `--color-border` against surfaces. WCAG 1.4.11 requires + that of boundaries *needed to identify a component*, not of every hairline — + dividers and subtle card edges are exempt, and failing all 15 styles on it + would get the check switched off. `--shadow-focus` carries that requirement + instead, and it is checked. 5. **Semantic over literal** where hierarchy matters: `--color-text-secondary`, not `--color-gray-500`. Literal scales are fine for spacing. diff --git a/scripts/check_contrast.py b/scripts/check_contrast.py new file mode 100755 index 0000000..ed9bc9d --- /dev/null +++ b/scripts/check_contrast.py @@ -0,0 +1,270 @@ +#!/usr/bin/env python3 +"""Verify every style's token palette against the contrast floor it promises. + +`design-tokens/TOKENS-GUIDE.md` rule 4 and `accessibility/ACCESSIBILITY.md` +both state that every text/background token pair used together must pass +WCAG AA. Nothing enforced it, so a style could ship a palette that fails its +own contract — which is how 13-y2k-retrofuturism's 1.1:1 hero headline +reached main. + + python3 scripts/check_contrast.py # all styles, both themes + python3 scripts/check_contrast.py 13 # one style + python3 scripts/check_contrast.py --verbose # show passing pairs too + +Exits non-zero on any unwaived failure. + +Dark themes are partial overrides: a token the dark block does not redefine +keeps its light value. The dark palette is therefore resolved as +light-then-dark, not read from the dark block alone — otherwise the pairs +most likely to fail are the ones never checked. +""" +from __future__ import annotations + +import glob +import json +import os +import re +import sys + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +os.chdir(ROOT) + +TEXT_AA = 4.5 # body text +LARGE_AA = 3.0 # large text, UI component boundaries, focus indicators + +# (foreground, background, threshold, label) +# +# Deliberately NOT here: --color-border against bg/surface. WCAG 1.4.11 requires +# 3:1 for boundaries *needed to identify a component*, not for every hairline — +# section dividers and subtle card edges are exempt, and almost every design +# system uses a ~1.2:1 rule for them. Failing all 15 styles on that would get +# this check switched off, which costs more than it buys. Hairlines are reported +# as advisory under --verbose instead. What genuinely must pass 3:1 is the +# focus indicator, and that is checked separately below from --shadow-focus. +PAIRS = [ + ("text", "bg", TEXT_AA, "body text on page"), + ("text", "surface", TEXT_AA, "body text on surface"), + ("text-secondary", "bg", TEXT_AA, "secondary text on page"), + ("text-secondary", "surface", TEXT_AA, "secondary text on surface"), + ("primary-contrast", "primary", TEXT_AA, "label on primary action"), + ("accent-contrast", "accent", TEXT_AA, "label on accent"), +] + +# Advisory only — never fails the run. +ADVISORY = [ + ("border", "bg", LARGE_AA, "hairline on page"), + ("border", "surface", LARGE_AA, "hairline on surface"), +] + +# A waiver needs a reason, and the reason has to name the trade-off. A style +# that cannot pass a pair says so out loud — it does not silence the check. +# key: (style_slug, theme, fg, bg) +WAIVERS: dict[tuple[str, str, str, str], str] = {} + + +VAR_RE = re.compile(r"var\(\s*--color-([\w-]+)\s*\)") +LITERAL_RE = re.compile(r"(#[0-9a-fA-F]{3,8}|rgba?\([^)]+\))") + + +def focus_color(shadows: dict, palette: dict) -> tuple[str, str] | None: + """Resolve the color a style's focus ring actually paints. + + Returns (raw_value, resolved_color_string) or None. --shadow-focus is where + the 3:1 non-text requirement really lands: it is the one boundary a keyboard + user must be able to see. + """ + raw = shadows.get("focus") + if not raw: + return None + m = VAR_RE.search(raw) + if m and m.group(1) in palette: + return (raw, palette[m.group(1)]) + m = LITERAL_RE.search(raw) + if m: + return (raw, m.group(1)) + return None + + +def parse_color(value: str) -> tuple[float, float, float, float] | None: + """Return (r, g, b, alpha) in 0-255 / 0-1, or None if not a solid color. + + Gradients, var() references and keywords are skipped rather than guessed at. + """ + v = value.strip().lower() + m = re.fullmatch(r"#([0-9a-f]{3,8})", v) + if m: + h = m.group(1) + if len(h) == 3: + h = "".join(c * 2 for c in h) + elif len(h) == 4: + h = "".join(c * 2 for c in h) + if len(h) == 6: + return (int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16), 1.0) + if len(h) == 8: + return (int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16), int(h[6:8], 16) / 255) + return None + m = re.fullmatch(r"rgba?\(([^)]+)\)", v) + if m: + parts = re.split(r"[,\s/]+", m.group(1).strip()) + parts = [p for p in parts if p] + if len(parts) >= 3: + try: + rgb = [float(p.rstrip("%")) * (2.55 if p.endswith("%") else 1) for p in parts[:3]] + except ValueError: + return None + a = 1.0 + if len(parts) >= 4: + try: + a = float(parts[3].rstrip("%")) / (100 if parts[3].endswith("%") else 1) + except ValueError: + a = 1.0 + return (rgb[0], rgb[1], rgb[2], a) + return None + + +def composite(fg: tuple[float, float, float, float], + bg: tuple[float, float, float, float]) -> tuple[float, float, float, float]: + """Flatten a translucent foreground onto its background before measuring — + a 60%-opacity label is not the color it was authored as.""" + a = fg[3] + if a >= 1.0: + return fg + return tuple(fg[i] * a + bg[i] * (1 - a) for i in range(3)) + (1.0,) + + +def luminance(c: tuple[float, float, float, float]) -> float: + def chan(x: float) -> float: + x /= 255 + return x / 12.92 if x <= 0.03928 else ((x + 0.055) / 1.055) ** 2.4 + r, g, b = chan(c[0]), chan(c[1]), chan(c[2]) + return 0.2126 * r + 0.7152 * g + 0.0722 * b + + +def ratio(fg: tuple, bg: tuple) -> float: + fg = composite(fg, bg) + l1, l2 = luminance(fg), luminance(bg) + hi, lo = max(l1, l2), min(l1, l2) + return (hi + 0.05) / (lo + 0.05) + + +def main() -> int: + args = [a for a in sys.argv[1:] if not a.startswith("-")] + verbose = "--verbose" in sys.argv or "-v" in sys.argv + + paths = sorted(glob.glob("styles/*/tokens.json")) + if args: + paths = [p for p in paths + if any(os.path.basename(os.path.dirname(p)).startswith(a) for a in args)] + if not paths: + print(f"no style matched {args}", file=sys.stderr) + return 2 + + failures: list[str] = [] + waived: list[str] = [] + skipped: list[str] = [] + checked = 0 + + for path in paths: + slug = os.path.basename(os.path.dirname(path)) + data = json.load(open(path, encoding="utf-8")) + light = data["light"].get("colors", {}) + # dark blocks are partial overrides — resolve against light + dark = {**light, **data["dark"].get("colors", {})} + + light_shadows = data["light"].get("shadows", {}) + dark_shadows = {**light_shadows, **data["dark"].get("shadows", {})} + + rows: list[str] = [] + for theme, palette, shadows in (("light", light, light_shadows), + ("dark", dark, dark_shadows)): + # --- focus indicator: the boundary that must be visible (3:1) --- + fc = focus_color(shadows, palette) + if fc: + raw, resolved = fc + fg = parse_color(resolved) + for bg_name in ("bg", "surface"): + if bg_name not in palette: + continue + bg = parse_color(palette[bg_name]) + if fg is None or bg is None: + skipped.append(f"{slug} {theme} focus/{bg_name}: unresolvable ({raw})") + continue + checked += 1 + r = ratio(fg, bg) + key = (slug, theme, "focus", bg_name) + if r >= LARGE_AA: + if verbose: + rows.append(f" {theme:<5} {'focus/' + bg_name:<22} {r:5.2f}:1") + elif key in WAIVERS: + waived.append(f"{slug} · {theme} · focus/{bg_name} {r:.2f}:1 " + f"(needs {LARGE_AA})") + rows.append(f" ~ {theme:<5} {'focus/' + bg_name:<22} " + f"{r:5.2f}:1 WAIVED") + else: + failures.append(f"{slug} · {theme} · focus ring on {bg_name} " + f"= {r:.2f}:1, needs {LARGE_AA}:1 — keyboard focus " + f"must be visible ({raw})") + rows.append(f" x {theme:<5} {'focus/' + bg_name:<22} " + f"{r:5.2f}:1 needs {LARGE_AA}") + + # --- advisory hairlines: reported, never failing --- + if verbose: + for fg_name, bg_name, floor, label in ADVISORY: + if fg_name in palette and bg_name in palette: + fg, bg = parse_color(palette[fg_name]), parse_color(palette[bg_name]) + if fg and bg: + rows.append(f" · {theme:<5} " + f"{fg_name + '/' + bg_name:<22} " + f"{ratio(fg, bg):5.2f}:1 advisory ({label})") + + for fg_name, bg_name, floor, label in PAIRS: + if fg_name not in palette or bg_name not in palette: + continue + fg = parse_color(palette[fg_name]) + bg = parse_color(palette[bg_name]) + if fg is None or bg is None: + skipped.append(f"{slug} {theme} {fg_name}/{bg_name}: not a solid color") + continue + checked += 1 + r = ratio(fg, bg) + ok = r >= floor + key = (slug, theme, fg_name, bg_name) + if not ok and key in WAIVERS: + waived.append(f"{slug} · {theme} · {fg_name}/{bg_name} " + f"{r:.2f}:1 (needs {floor}) — {label}") + rows.append(f" ~ {theme:<5} {fg_name}/{bg_name:<16} " + f"{r:5.2f}:1 WAIVED") + elif not ok: + failures.append(f"{slug} · {theme} · {fg_name} on {bg_name} " + f"= {r:.2f}:1, needs {floor}:1 — {label} " + f"({palette[fg_name]} on {palette[bg_name]})") + rows.append(f" x {theme:<5} {fg_name}/{bg_name:<16} " + f"{r:5.2f}:1 needs {floor}") + elif verbose: + rows.append(f" {theme:<5} {fg_name}/{bg_name:<16} {r:5.2f}:1") + if rows: + print(f"\n{slug}") + print("\n".join(rows)) + + print(f"\ncontrast check — {checked} token pairs across {len(paths)} style(s)") + if skipped and verbose: + for s in skipped: + print(f" SKIP {s}") + for w in waived: + print(f" WAIVED {w}") + for f in failures: + print(f" FAIL {f}") + + if failures: + print(f"\n{len(failures)} pair(s) below the floor.") + print("Fix the token, or — if the style genuinely cannot pass — add a WAIVER " + "in this script naming the trade-off, and say it in the style's §13.") + return 1 + print(f"\nAll pairs pass" + + (f" ({len(waived)} waived, each with a documented reason)" if waived else "") + + ".") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/gen_manifest.py b/scripts/gen_manifest.py index f6b98bb..19c68e3 100644 --- a/scripts/gen_manifest.py +++ b/scripts/gen_manifest.py @@ -13,8 +13,8 @@ import sys ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -SKILL_VERSION = "2.0.0" -REPO_VERSION = "2.0.0" +SKILL_VERSION = "2.1.0" +REPO_VERSION = "2.1.0" # slug: (name, identity, aliases, densities, recommendedFor, supporting, incompatible, vetoedBy, scoring) STYLES = { @@ -318,6 +318,7 @@ def build_manifest(index): "generateManifest": "scripts/gen_manifest.py", "validate": "scripts/validate.py", "screenshot": "scripts/screenshot.mjs", + "checkContrast": "scripts/check_contrast.py", "install": "scripts/install.sh", }, "styleCount": index["count"], diff --git a/scripts/gen_tokens.py b/scripts/gen_tokens.py index 8a067e0..1054faa 100644 --- a/scripts/gen_tokens.py +++ b/scripts/gen_tokens.py @@ -45,8 +45,35 @@ ] +def strip_at_rules(css: str) -> str: + """Drop @media/@supports blocks before parsing. + + A `:root` nested inside `@media (prefers-reduced-motion: reduce)` is not the + light palette — it applies only to that media condition. Merging it in made + a misplaced token look correct in the generated JSON while behaving + differently in a browser, so the palette is read from top-level rules only. + """ + out, i = [], 0 + pat = re.compile(r"@[a-zA-Z-]+[^{]*\{") + while True: + m = pat.search(css, i) + if not m: + out.append(css[i:]) + break + out.append(css[i:m.start()]) + depth, j = 1, m.end() + while j < len(css) and depth: + if css[j] == "{": + depth += 1 + elif css[j] == "}": + depth -= 1 + j += 1 + i = j + return "".join(out) + + def parse_tokens(css_path: str) -> dict[str, dict[str, str]]: - text = open(css_path, encoding="utf-8").read() + text = strip_at_rules(open(css_path, encoding="utf-8").read()) blocks = re.findall(r'(:root|\[data-theme="dark"\])\s*\{([^}]+)\}', text) out: dict[str, dict[str, str]] = {} for name, body in blocks: diff --git a/scripts/validate.py b/scripts/validate.py index 40bfa4e..722d771 100755 --- a/scripts/validate.py +++ b/scripts/validate.py @@ -83,6 +83,7 @@ def check(ok: bool, msg: str, warn: bool = False) -> bool: "adapters/windsurf/agent-design-taste.md", "adapters/copilot/copilot-instructions.md", "scripts/gen_tokens.py", "scripts/gen_manifest.py", "scripts/install.sh", + "scripts/check_contrast.py", "scripts/screenshot.mjs", ".github/workflows/validate.yml", ".gitignore", ] diff --git a/styles/01-minimalism/example.html b/styles/01-minimalism/example.html index a76d65a..851f8b2 100644 --- a/styles/01-minimalism/example.html +++ b/styles/01-minimalism/example.html @@ -48,6 +48,10 @@ nav ul{order:3;width:100%;gap:20px;flex-wrap:wrap} nav a{display:inline-flex;align-items:center;min-height:44px} } + +/* Authored focus indicator — ANTI-SLOP.md treats a missing or invisible + :focus-visible as a BLOCKER, so the reference page has to show one. */ +:focus-visible{outline:3px solid var(--accent);outline-offset:2px} diff --git a/styles/01-minimalism/tokens.css b/styles/01-minimalism/tokens.css index db637de..d2fe6b7 100644 --- a/styles/01-minimalism/tokens.css +++ b/styles/01-minimalism/tokens.css @@ -33,7 +33,7 @@ --radius-sm: 6px; --radius-md: 8px; --radius-lg: 12px; --shadow-none: none; - --shadow-focus: 0 0 0 3px rgb(37 99 235 / 0.35); + --shadow-focus: 0 0 0 3px var(--color-accent); --border-default: 1px solid var(--color-border); --border-strong: 1px solid var(--color-text); diff --git a/styles/01-minimalism/tokens.json b/styles/01-minimalism/tokens.json index 93159f4..391b0ce 100644 --- a/styles/01-minimalism/tokens.json +++ b/styles/01-minimalism/tokens.json @@ -50,7 +50,7 @@ }, "shadows": { "none": "none", - "focus": "0 0 0 3px rgb(37 99 235 / 0.35)" + "focus": "0 0 0 3px var(--color-accent)" }, "borders": { "default": "1px solid var(--color-border)", diff --git a/styles/01-minimalism/tokens.tailwind.css b/styles/01-minimalism/tokens.tailwind.css index ba1a2b8..2f08b98 100644 --- a/styles/01-minimalism/tokens.tailwind.css +++ b/styles/01-minimalism/tokens.tailwind.css @@ -45,7 +45,7 @@ --radius-lg: 12px; /* shadows */ --shadow-none: none; - --shadow-focus: 0 0 0 3px rgb(37 99 235 / 0.35); + --shadow-focus: 0 0 0 3px var(--color-accent); /* motionEase */ --ease-default: cubic-bezier(0.16, 1, 0.3, 1); /* container */ diff --git a/styles/02-glassmorphism/example.html b/styles/02-glassmorphism/example.html index 59ccb2d..cef3c83 100644 --- a/styles/02-glassmorphism/example.html +++ b/styles/02-glassmorphism/example.html @@ -7,7 +7,7 @@ diff --git a/styles/02-glassmorphism/tokens.css b/styles/02-glassmorphism/tokens.css index bc9c939..98959a3 100644 --- a/styles/02-glassmorphism/tokens.css +++ b/styles/02-glassmorphism/tokens.css @@ -7,7 +7,7 @@ --color-border: #E4E4EF; --color-primary: #111111; --color-primary-contrast: #FFFFFF; - --color-accent: #8B5CF6; + --color-accent: #7C3AED; --color-accent-contrast: #FFFFFF; --color-success: #16A34A; --color-warning: #D97706; @@ -47,7 +47,7 @@ --radius-sm: 8px; --radius-md: 12px; --radius-lg: 16px; --radius-xl: 24px; --shadow-card: 0 8px 32px rgb(0 0 0 / 0.12); - --shadow-focus: 0 0 0 3px rgb(139 92 246 / 0.40); + --shadow-focus: 0 0 0 3px var(--color-accent); --border-default: 1px solid var(--color-border); --border-glass: 1px solid var(--glass-border); @@ -63,7 +63,8 @@ /* Glass is fragile under motion preferences — freeze ambient drift */ @media (prefers-reduced-motion: reduce) { - :root { --motion-ambient: 0s; --motion-fast: 0ms; --motion-normal: 0ms; --motion-slow: 0ms; } + :root { --motion-ambient: 0s; --motion-fast: 0ms; --motion-normal: 0ms; --motion-slow: 0ms; +} } [data-theme="dark"] { --color-bg: #0F172A; @@ -75,6 +76,7 @@ --color-primary-contrast: #0F172A; --glass-bg: var(--glass-bg-dark); --color-accent: #A78BFA; + --color-accent-contrast: #111111; } /* Required usage guard */ diff --git a/styles/02-glassmorphism/tokens.json b/styles/02-glassmorphism/tokens.json index aa4ec34..a1f09ee 100644 --- a/styles/02-glassmorphism/tokens.json +++ b/styles/02-glassmorphism/tokens.json @@ -10,7 +10,7 @@ "border": "#E4E4EF", "primary": "#111111", "primary-contrast": "#FFFFFF", - "accent": "#8B5CF6", + "accent": "#7C3AED", "accent-contrast": "#FFFFFF", "success": "#16A34A", "warning": "#D97706", @@ -51,7 +51,7 @@ }, "shadows": { "card": "0 8px 32px rgb(0 0 0 / 0.12)", - "focus": "0 0 0 3px rgb(139 92 246 / 0.40)" + "focus": "0 0 0 3px var(--color-accent)" }, "borders": { "default": "1px solid var(--color-border)", @@ -61,10 +61,10 @@ "width": "1200px" }, "motion": { - "fast": "0ms", - "normal": "0ms", - "slow": "0ms", - "ambient": "0s" + "fast": "150ms", + "normal": "200ms", + "slow": "400ms", + "ambient": "60s" }, "motionEase": { "default": "cubic-bezier(0.16, 1, 0.3, 1)" @@ -79,7 +79,8 @@ "border": "rgb(255 255 255 / 0.10)", "primary": "#F8FAFC", "primary-contrast": "#0F172A", - "accent": "#A78BFA" + "accent": "#A78BFA", + "accent-contrast": "#111111" } } } diff --git a/styles/02-glassmorphism/tokens.tailwind.css b/styles/02-glassmorphism/tokens.tailwind.css index 846d71d..03b9ab2 100644 --- a/styles/02-glassmorphism/tokens.tailwind.css +++ b/styles/02-glassmorphism/tokens.tailwind.css @@ -10,7 +10,7 @@ --color-border: #E4E4EF; --color-primary: #111111; --color-primary-contrast: #FFFFFF; - --color-accent: #8B5CF6; + --color-accent: #7C3AED; --color-accent-contrast: #FFFFFF; --color-success: #16A34A; --color-warning: #D97706; @@ -46,7 +46,7 @@ --radius-xl: 24px; /* shadows */ --shadow-card: 0 8px 32px rgb(0 0 0 / 0.12); - --shadow-focus: 0 0 0 3px rgb(139 92 246 / 0.40); + --shadow-focus: 0 0 0 3px var(--color-accent); /* motionEase */ --ease-default: cubic-bezier(0.16, 1, 0.3, 1); /* container */ @@ -55,10 +55,10 @@ /* No Tailwind namespace — use via var() */ :root { - --motion-fast: 0ms; - --motion-normal: 0ms; - --motion-slow: 0ms; - --motion-ambient: 0s; + --motion-fast: 150ms; + --motion-normal: 200ms; + --motion-slow: 400ms; + --motion-ambient: 60s; --border-default: 1px solid var(--color-border); --border-glass: 1px solid var(--glass-border); } diff --git a/styles/03-liquid-glass/example.html b/styles/03-liquid-glass/example.html index ba6c88d..bf96953 100644 --- a/styles/03-liquid-glass/example.html +++ b/styles/03-liquid-glass/example.html @@ -7,7 +7,7 @@ diff --git a/styles/03-liquid-glass/tokens.css b/styles/03-liquid-glass/tokens.css index b2cca4b..c85633f 100644 --- a/styles/03-liquid-glass/tokens.css +++ b/styles/03-liquid-glass/tokens.css @@ -5,9 +5,9 @@ --color-text: #FFFFFF; --color-text-secondary: rgb(255 255 255 / 0.62); --color-border: rgb(255 255 255 / 0.12); - --color-primary: #0A84FF; + --color-primary: #0A74E0; --color-primary-contrast: #FFFFFF; - --color-accent: #0A84FF; + --color-accent: #0A74E0; --color-accent-contrast: #FFFFFF; --color-success: #30D158; --color-warning: #FFD60A; @@ -51,7 +51,7 @@ /* Continuous curves & capsules — the material's identity */ --radius-sm: 12px; --radius-md: 24px; --radius-lg: 32px; --radius-pill: 999px; --shadow-float: var(--lg-shadow); - --shadow-focus: 0 0 0 4px rgb(10 132 255 / 0.45); + --shadow-focus: 0 0 0 4px var(--color-accent); --border-default: 1px solid var(--color-border); @@ -89,6 +89,7 @@ .liquid { background: var(--lg-surface); } } @media (prefers-reduced-motion: reduce) { - :root { --motion-ambient: 0s; } + :root { --motion-ambient: 0s; +} * { transition-duration: 0ms !important; animation-duration: 0ms !important; } } \ No newline at end of file diff --git a/styles/03-liquid-glass/tokens.json b/styles/03-liquid-glass/tokens.json index bc4f930..d51ef3f 100644 --- a/styles/03-liquid-glass/tokens.json +++ b/styles/03-liquid-glass/tokens.json @@ -8,9 +8,9 @@ "text": "#FFFFFF", "text-secondary": "rgb(255 255 255 / 0.62)", "border": "rgb(255 255 255 / 0.12)", - "primary": "#0A84FF", + "primary": "#0A74E0", "primary-contrast": "#FFFFFF", - "accent": "#0A84FF", + "accent": "#0A74E0", "accent-contrast": "#FFFFFF", "success": "#30D158", "warning": "#FFD60A", @@ -51,7 +51,7 @@ }, "shadows": { "float": "var(--lg-shadow)", - "focus": "0 0 0 4px rgb(10 132 255 / 0.45)" + "focus": "0 0 0 4px var(--color-accent)" }, "borders": { "default": "1px solid var(--color-border)" @@ -63,7 +63,7 @@ "fast": "200ms", "normal": "350ms", "slow": "500ms", - "ambient": "0s" + "ambient": "90s" }, "motionEase": { "default": "cubic-bezier(0.32, 0.72, 0, 1)" diff --git a/styles/03-liquid-glass/tokens.tailwind.css b/styles/03-liquid-glass/tokens.tailwind.css index c0f4c6f..39293bf 100644 --- a/styles/03-liquid-glass/tokens.tailwind.css +++ b/styles/03-liquid-glass/tokens.tailwind.css @@ -8,9 +8,9 @@ --color-text: #FFFFFF; --color-text-secondary: rgb(255 255 255 / 0.62); --color-border: rgb(255 255 255 / 0.12); - --color-primary: #0A84FF; + --color-primary: #0A74E0; --color-primary-contrast: #FFFFFF; - --color-accent: #0A84FF; + --color-accent: #0A74E0; --color-accent-contrast: #FFFFFF; --color-success: #30D158; --color-warning: #FFD60A; @@ -46,7 +46,7 @@ --radius-pill: 999px; /* shadows */ --shadow-float: var(--lg-shadow); - --shadow-focus: 0 0 0 4px rgb(10 132 255 / 0.45); + --shadow-focus: 0 0 0 4px var(--color-accent); /* motionEase */ --ease-default: cubic-bezier(0.32, 0.72, 0, 1); /* container */ @@ -58,6 +58,6 @@ --motion-fast: 200ms; --motion-normal: 350ms; --motion-slow: 500ms; - --motion-ambient: 0s; + --motion-ambient: 90s; --border-default: 1px solid var(--color-border); } diff --git a/styles/04-bento-grid/example.html b/styles/04-bento-grid/example.html index f34f283..4f3e61e 100644 --- a/styles/04-bento-grid/example.html +++ b/styles/04-bento-grid/example.html @@ -7,7 +7,7 @@ @@ -113,7 +117,7 @@

Know your numbers
before you guess them.

Growth is compounding, not spiking.

- +
diff --git a/styles/04-bento-grid/tokens.css b/styles/04-bento-grid/tokens.css index 283fe93..72aa87e 100644 --- a/styles/04-bento-grid/tokens.css +++ b/styles/04-bento-grid/tokens.css @@ -8,7 +8,7 @@ --color-border: #E2E2DE; --color-primary: #111111; --color-primary-contrast: #FFFFFF; - --color-accent: #0D9488; + --color-accent: #0C857A; --color-accent-contrast: #FFFFFF; --color-success: #16A34A; --color-warning: #D97706; @@ -36,7 +36,7 @@ --radius-sm: 8px; --radius-md: 12px; --radius-lg: 16px; --shadow-tile-hover: 0 2px 0 0 var(--color-border); - --shadow-focus: 0 0 0 3px rgb(13 148 136 / 0.35); + --shadow-focus: 0 0 0 3px var(--color-accent); --border-default: 1px solid var(--color-border); --border-strong: 2px solid var(--color-text); @@ -59,4 +59,5 @@ --color-primary: #F4F4F2; --color-primary-contrast: #0C0C0B; --color-accent: #2DD4BF; + --color-accent-contrast: #111111; } \ No newline at end of file diff --git a/styles/04-bento-grid/tokens.json b/styles/04-bento-grid/tokens.json index 1be81cf..dd70fe2 100644 --- a/styles/04-bento-grid/tokens.json +++ b/styles/04-bento-grid/tokens.json @@ -11,7 +11,7 @@ "border": "#E2E2DE", "primary": "#111111", "primary-contrast": "#FFFFFF", - "accent": "#0D9488", + "accent": "#0C857A", "accent-contrast": "#FFFFFF", "success": "#16A34A", "warning": "#D97706", @@ -51,7 +51,7 @@ }, "shadows": { "tile-hover": "0 2px 0 0 var(--color-border)", - "focus": "0 0 0 3px rgb(13 148 136 / 0.35)" + "focus": "0 0 0 3px var(--color-accent)" }, "borders": { "default": "1px solid var(--color-border)", @@ -80,7 +80,8 @@ "border": "#262624", "primary": "#F4F4F2", "primary-contrast": "#0C0C0B", - "accent": "#2DD4BF" + "accent": "#2DD4BF", + "accent-contrast": "#111111" } } } diff --git a/styles/04-bento-grid/tokens.tailwind.css b/styles/04-bento-grid/tokens.tailwind.css index 34b8ffe..4a212f3 100644 --- a/styles/04-bento-grid/tokens.tailwind.css +++ b/styles/04-bento-grid/tokens.tailwind.css @@ -11,7 +11,7 @@ --color-border: #E2E2DE; --color-primary: #111111; --color-primary-contrast: #FFFFFF; - --color-accent: #0D9488; + --color-accent: #0C857A; --color-accent-contrast: #FFFFFF; --color-success: #16A34A; --color-warning: #D97706; @@ -46,7 +46,7 @@ --radius-lg: 16px; /* shadows */ --shadow-tile-hover: 0 2px 0 0 var(--color-border); - --shadow-focus: 0 0 0 3px rgb(13 148 136 / 0.35); + --shadow-focus: 0 0 0 3px var(--color-accent); /* motionEase */ --ease-default: cubic-bezier(0.16, 1, 0.3, 1); /* container */ diff --git a/styles/05-neo-brutalism/example.html b/styles/05-neo-brutalism/example.html index eade065..b6ebb09 100644 --- a/styles/05-neo-brutalism/example.html +++ b/styles/05-neo-brutalism/example.html @@ -88,6 +88,10 @@ nav ul{order:3;width:100%;gap:20px;flex-wrap:wrap} nav a{display:inline-flex;align-items:center;min-height:44px} } + +/* Authored focus indicator — ANTI-SLOP.md treats a missing or invisible + :focus-visible as a BLOCKER, so the reference page has to show one. */ +:focus-visible{outline:3px solid var(--ink);outline-offset:2px} diff --git a/styles/05-neo-brutalism/tokens.css b/styles/05-neo-brutalism/tokens.css index 8172723..7376bdb 100644 --- a/styles/05-neo-brutalism/tokens.css +++ b/styles/05-neo-brutalism/tokens.css @@ -38,7 +38,7 @@ --shadow-offset-sm: 3px 3px 0 var(--color-border); --shadow-offset-md: 6px 6px 0 var(--color-border); --shadow-offset-lg: 10px 10px 0 var(--color-border); - --shadow-focus: 4px 4px 0 var(--color-accent); + --shadow-focus: 4px 4px 0 var(--color-text); --border-default: 2px solid var(--color-border); --border-thick: 3px solid var(--color-border); diff --git a/styles/05-neo-brutalism/tokens.json b/styles/05-neo-brutalism/tokens.json index 648f49d..a8a8eb2 100644 --- a/styles/05-neo-brutalism/tokens.json +++ b/styles/05-neo-brutalism/tokens.json @@ -55,7 +55,7 @@ "offset-sm": "3px 3px 0 var(--color-border)", "offset-md": "6px 6px 0 var(--color-border)", "offset-lg": "10px 10px 0 var(--color-border)", - "focus": "4px 4px 0 var(--color-accent)" + "focus": "4px 4px 0 var(--color-text)" }, "borders": { "default": "2px solid var(--color-border)", diff --git a/styles/05-neo-brutalism/tokens.tailwind.css b/styles/05-neo-brutalism/tokens.tailwind.css index 352427b..b31b0dc 100644 --- a/styles/05-neo-brutalism/tokens.tailwind.css +++ b/styles/05-neo-brutalism/tokens.tailwind.css @@ -50,7 +50,7 @@ --shadow-offset-sm: 3px 3px 0 var(--color-border); --shadow-offset-md: 6px 6px 0 var(--color-border); --shadow-offset-lg: 10px 10px 0 var(--color-border); - --shadow-focus: 4px 4px 0 var(--color-accent); + --shadow-focus: 4px 4px 0 var(--color-text); /* motionEase */ --ease-default: steps(4, end); /* container */ diff --git a/styles/06-brutalist-anti-grid/example.html b/styles/06-brutalist-anti-grid/example.html index 07610bd..079a6e7 100644 --- a/styles/06-brutalist-anti-grid/example.html +++ b/styles/06-brutalist-anti-grid/example.html @@ -66,6 +66,10 @@ nav a{display:inline-flex;align-items:center;min-height:44px} } .hero{overflow-x:clip} + +/* Authored focus indicator — ANTI-SLOP.md treats a missing or invisible + :focus-visible as a BLOCKER, so the reference page has to show one. */ +:focus-visible{outline:3px solid var(--text);outline-offset:2px} diff --git a/styles/06-brutalist-anti-grid/tokens.css b/styles/06-brutalist-anti-grid/tokens.css index acc75f9..5fcf597 100644 --- a/styles/06-brutalist-anti-grid/tokens.css +++ b/styles/06-brutalist-anti-grid/tokens.css @@ -39,7 +39,7 @@ --shadow-hard: 8px 8px 0 var(--color-text); --shadow-hard-sm: 4px 4px 0 var(--color-text); --shadow-hard-accent: 8px 8px 0 var(--color-accent); - --shadow-focus: 0 0 0 3px var(--color-accent); + --shadow-focus: 0 0 0 3px var(--color-text); --tilt-min: -2.5deg; --tilt-max: 2.5deg; diff --git a/styles/06-brutalist-anti-grid/tokens.json b/styles/06-brutalist-anti-grid/tokens.json index 2052d32..bb0aac9 100644 --- a/styles/06-brutalist-anti-grid/tokens.json +++ b/styles/06-brutalist-anti-grid/tokens.json @@ -56,7 +56,7 @@ "hard": "8px 8px 0 var(--color-text)", "hard-sm": "4px 4px 0 var(--color-text)", "hard-accent": "8px 8px 0 var(--color-accent)", - "focus": "0 0 0 3px var(--color-accent)" + "focus": "0 0 0 3px var(--color-text)" }, "container": { "width": "1200px" diff --git a/styles/06-brutalist-anti-grid/tokens.tailwind.css b/styles/06-brutalist-anti-grid/tokens.tailwind.css index 4382167..0b2babb 100644 --- a/styles/06-brutalist-anti-grid/tokens.tailwind.css +++ b/styles/06-brutalist-anti-grid/tokens.tailwind.css @@ -47,7 +47,7 @@ --shadow-hard: 8px 8px 0 var(--color-text); --shadow-hard-sm: 4px 4px 0 var(--color-text); --shadow-hard-accent: 8px 8px 0 var(--color-accent); - --shadow-focus: 0 0 0 3px var(--color-accent); + --shadow-focus: 0 0 0 3px var(--color-text); /* motionEase */ --ease-default: linear; /* container */ diff --git a/styles/08-claymorphism/example.html b/styles/08-claymorphism/example.html index 4576430..497968e 100644 --- a/styles/08-claymorphism/example.html +++ b/styles/08-claymorphism/example.html @@ -8,7 +8,7 @@ diff --git a/styles/08-claymorphism/tokens.css b/styles/08-claymorphism/tokens.css index 48970b9..bdcb039 100644 --- a/styles/08-claymorphism/tokens.css +++ b/styles/08-claymorphism/tokens.css @@ -6,7 +6,7 @@ --color-text: #2D2438; --color-text-secondary: #6B5E7F; --color-border: rgba(45, 36, 56, 0.08); - --color-primary: #7C5CFC; + --color-primary: #7052E8; --color-primary-soft: #E6DEFF; --color-primary-contrast: #FFFFFF; --color-accent: #FF8FA3; @@ -39,7 +39,7 @@ --shadow-btn: inset 0 -4px 0 rgba(0, 0, 0, 0.18), 0 12px 24px rgba(124, 92, 252, 0.30); --shadow-btn-press: inset 0 -1px 0 rgba(0, 0, 0, 0.18), 0 4px 8px rgba(124, 92, 252, 0.25); --shadow-inset: inset 0 2px 6px rgba(45, 36, 56, 0.12); - --shadow-focus: 0 0 0 4px rgba(124, 92, 252, 0.35); + --shadow-focus: 0 0 0 4px var(--color-primary); --container-width: 1140px; @@ -69,4 +69,5 @@ --shadow-btn: inset 0 -4px 0 rgba(0, 0, 0, 0.35), 0 12px 24px rgba(0, 0, 0, 0.45); --shadow-btn-press: inset 0 -1px 0 rgba(0, 0, 0, 0.35), 0 4px 8px rgba(0, 0, 0, 0.40); --shadow-inset: inset 0 2px 6px rgba(0, 0, 0, 0.35); + --color-primary-contrast: #111111; } \ No newline at end of file diff --git a/styles/08-claymorphism/tokens.json b/styles/08-claymorphism/tokens.json index 50a275c..717076a 100644 --- a/styles/08-claymorphism/tokens.json +++ b/styles/08-claymorphism/tokens.json @@ -9,7 +9,7 @@ "text": "#2D2438", "text-secondary": "#6B5E7F", "border": "rgba(45, 36, 56, 0.08)", - "primary": "#7C5CFC", + "primary": "#7052E8", "primary-soft": "#E6DEFF", "primary-contrast": "#FFFFFF", "accent": "#FF8FA3", @@ -55,7 +55,7 @@ "btn": "inset 0 -4px 0 rgba(0, 0, 0, 0.18), 0 12px 24px rgba(124, 92, 252, 0.30)", "btn-press": "inset 0 -1px 0 rgba(0, 0, 0, 0.18), 0 4px 8px rgba(124, 92, 252, 0.25)", "inset": "inset 0 2px 6px rgba(45, 36, 56, 0.12)", - "focus": "0 0 0 4px rgba(124, 92, 252, 0.35)" + "focus": "0 0 0 4px var(--color-primary)" }, "container": { "width": "1140px" @@ -84,7 +84,8 @@ "primary": "#9B7DFF", "primary-soft": "#3D2E68", "accent": "#FFA6B5", - "accent-soft": "#4A2E3C" + "accent-soft": "#4A2E3C", + "primary-contrast": "#111111" }, "shadows": { "clay": "8px 8px 24px rgba(0, 0, 0, 0.45), -8px -8px 24px rgba(155, 125, 255, 0.07),\n inset 0 2px 0 rgba(255, 255, 255, 0.06)", diff --git a/styles/08-claymorphism/tokens.tailwind.css b/styles/08-claymorphism/tokens.tailwind.css index e6a27b6..c93ed28 100644 --- a/styles/08-claymorphism/tokens.tailwind.css +++ b/styles/08-claymorphism/tokens.tailwind.css @@ -9,7 +9,7 @@ --color-text: #2D2438; --color-text-secondary: #6B5E7F; --color-border: rgba(45, 36, 56, 0.08); - --color-primary: #7C5CFC; + --color-primary: #7052E8; --color-primary-soft: #E6DEFF; --color-primary-contrast: #FFFFFF; --color-accent: #FF8FA3; @@ -52,7 +52,7 @@ --shadow-btn: inset 0 -4px 0 rgba(0, 0, 0, 0.18), 0 12px 24px rgba(124, 92, 252, 0.30); --shadow-btn-press: inset 0 -1px 0 rgba(0, 0, 0, 0.18), 0 4px 8px rgba(124, 92, 252, 0.25); --shadow-inset: inset 0 2px 6px rgba(45, 36, 56, 0.12); - --shadow-focus: 0 0 0 4px rgba(124, 92, 252, 0.35); + --shadow-focus: 0 0 0 4px var(--color-primary); /* motionEase */ --ease-default: cubic-bezier(0.34, 1.56, 0.64, 1); /* container */ diff --git a/styles/09-skeuomorphism-tactile/example.html b/styles/09-skeuomorphism-tactile/example.html index acb41dd..976614a 100644 --- a/styles/09-skeuomorphism-tactile/example.html +++ b/styles/09-skeuomorphism-tactile/example.html @@ -103,6 +103,10 @@ nav ul{order:3;width:100%;gap:20px;flex-wrap:wrap} nav a{display:inline-flex;align-items:center;min-height:44px} } + +/* Authored focus indicator — ANTI-SLOP.md treats a missing or invisible + :focus-visible as a BLOCKER, so the reference page has to show one. */ +:focus-visible{outline:3px solid var(--text);outline-offset:2px} diff --git a/styles/09-skeuomorphism-tactile/tokens.css b/styles/09-skeuomorphism-tactile/tokens.css index 2830cd9..d174cf2 100644 --- a/styles/09-skeuomorphism-tactile/tokens.css +++ b/styles/09-skeuomorphism-tactile/tokens.css @@ -45,7 +45,7 @@ inset 0 1px 0 rgba(255, 255, 255, 0.35); --shadow-knob: 0 3px 8px rgba(0, 0, 0, 0.30), inset 0 1px 0 rgba(255, 255, 255, 0.55), inset 0 -2px 4px rgba(0, 0, 0, 0.25); - --shadow-focus: 0 0 0 3px rgba(47, 111, 237, 0.40); + --shadow-focus: 0 0 0 3px var(--color-primary); --texture-brushed: repeating-linear-gradient(90deg, rgba(255,255,255,.04) 0 1px, transparent 1px 3px); --container-width: 1160px; @@ -77,4 +77,5 @@ inset 0 1px 0 rgba(255, 255, 255, 0.18); --shadow-knob: 0 3px 8px rgba(0, 0, 0, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.16), inset 0 -2px 4px rgba(0, 0, 0, 0.45); + --color-primary-contrast: #111111; } \ No newline at end of file diff --git a/styles/09-skeuomorphism-tactile/tokens.json b/styles/09-skeuomorphism-tactile/tokens.json index 7e650b9..814eda8 100644 --- a/styles/09-skeuomorphism-tactile/tokens.json +++ b/styles/09-skeuomorphism-tactile/tokens.json @@ -56,7 +56,7 @@ "well": "inset 0 2px 4px rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(255, 255, 255, 0.5)", "btn": "0 2px 0 rgba(0, 0, 0, 0.35), 0 4px 10px rgba(0, 0, 0, 0.15),\n inset 0 1px 0 rgba(255, 255, 255, 0.35)", "knob": "0 3px 8px rgba(0, 0, 0, 0.30), inset 0 1px 0 rgba(255, 255, 255, 0.55),\n inset 0 -2px 4px rgba(0, 0, 0, 0.25)", - "focus": "0 0 0 3px rgba(47, 111, 237, 0.40)" + "focus": "0 0 0 3px var(--color-primary)" }, "container": { "width": "1160px" @@ -83,7 +83,8 @@ "text-secondary": "#9AA0A6", "primary": "#4F87FF", "action-hw": "#FF6B14", - "readout": "#FFC133" + "readout": "#FFC133", + "primary-contrast": "#111111" }, "shadows": { "raised": "0 2px 2px rgba(0, 0, 0, 0.55), 0 4px 10px rgba(0, 0, 0, 0.35),\n inset 0 1px 0 rgba(255, 255, 255, 0.12)", diff --git a/styles/09-skeuomorphism-tactile/tokens.tailwind.css b/styles/09-skeuomorphism-tactile/tokens.tailwind.css index 3637ac0..05db9f1 100644 --- a/styles/09-skeuomorphism-tactile/tokens.tailwind.css +++ b/styles/09-skeuomorphism-tactile/tokens.tailwind.css @@ -54,7 +54,7 @@ inset 0 1px 0 rgba(255, 255, 255, 0.35); --shadow-knob: 0 3px 8px rgba(0, 0, 0, 0.30), inset 0 1px 0 rgba(255, 255, 255, 0.55), inset 0 -2px 4px rgba(0, 0, 0, 0.25); - --shadow-focus: 0 0 0 3px rgba(47, 111, 237, 0.40); + --shadow-focus: 0 0 0 3px var(--color-primary); /* motionEase */ --ease-default: cubic-bezier(0.16, 1, 0.3, 1); /* container */ diff --git a/styles/10-swiss-international/example.html b/styles/10-swiss-international/example.html index 4b676e7..4bc4740 100644 --- a/styles/10-swiss-international/example.html +++ b/styles/10-swiss-international/example.html @@ -56,6 +56,10 @@ nav ul{order:3;width:100%;gap:20px;flex-wrap:wrap} nav a{display:inline-flex;align-items:center;min-height:44px} } + +/* Authored focus indicator — ANTI-SLOP.md treats a missing or invisible + :focus-visible as a BLOCKER, so the reference page has to show one. */ +:focus-visible{outline:3px solid var(--accent);outline-offset:2px} diff --git a/styles/10-swiss-international/tokens.css b/styles/10-swiss-international/tokens.css index bbdf582..9bdeb96 100644 --- a/styles/10-swiss-international/tokens.css +++ b/styles/10-swiss-international/tokens.css @@ -33,7 +33,7 @@ --radius-sm: 0px; --radius-md: 2px; --radius-lg: 2px; --shadow-none: none; - --shadow-focus: 0 0 0 3px rgb(226 35 26 / 0.35); + --shadow-focus: 0 0 0 3px var(--color-accent); --border-default: 1px solid var(--color-border); --border-strong: 1px solid var(--color-text); diff --git a/styles/10-swiss-international/tokens.json b/styles/10-swiss-international/tokens.json index af51030..e4240c4 100644 --- a/styles/10-swiss-international/tokens.json +++ b/styles/10-swiss-international/tokens.json @@ -50,7 +50,7 @@ }, "shadows": { "none": "none", - "focus": "0 0 0 3px rgb(226 35 26 / 0.35)" + "focus": "0 0 0 3px var(--color-accent)" }, "borders": { "default": "1px solid var(--color-border)", diff --git a/styles/10-swiss-international/tokens.tailwind.css b/styles/10-swiss-international/tokens.tailwind.css index f9fcb5a..6414a4f 100644 --- a/styles/10-swiss-international/tokens.tailwind.css +++ b/styles/10-swiss-international/tokens.tailwind.css @@ -45,7 +45,7 @@ --radius-lg: 2px; /* shadows */ --shadow-none: none; - --shadow-focus: 0 0 0 3px rgb(226 35 26 / 0.35); + --shadow-focus: 0 0 0 3px var(--color-accent); /* motionEase */ --ease-default: cubic-bezier(0.2, 0, 0, 1); /* container */ diff --git a/styles/11-editorial-magazine/example.html b/styles/11-editorial-magazine/example.html index 39e6b2c..5061d25 100644 --- a/styles/11-editorial-magazine/example.html +++ b/styles/11-editorial-magazine/example.html @@ -52,6 +52,10 @@ nav ul{order:3;width:100%;gap:20px;flex-wrap:wrap} nav a{display:inline-flex;align-items:center;min-height:44px} } + +/* Authored focus indicator — ANTI-SLOP.md treats a missing or invisible + :focus-visible as a BLOCKER, so the reference page has to show one. */ +:focus-visible{outline:3px solid var(--accent);outline-offset:2px} diff --git a/styles/11-editorial-magazine/tokens.css b/styles/11-editorial-magazine/tokens.css index 989905e..51d2725 100644 --- a/styles/11-editorial-magazine/tokens.css +++ b/styles/11-editorial-magazine/tokens.css @@ -36,7 +36,7 @@ --radius-sm: 2px; --radius-md: 2px; --radius-lg: 4px; --shadow-none: none; --shadow-paper: 0 8px 24px rgb(26 24 21 / 0.08); - --shadow-focus: 0 0 0 3px rgb(140 47 27 / 0.3); + --shadow-focus: 0 0 0 3px var(--color-accent); --border-default: 1px solid var(--color-border); --border-strong: 1px solid var(--color-text); @@ -56,4 +56,5 @@ --color-border: #35302A; --color-primary: #F2ECE2; --color-primary-contrast: #14110E; + --shadow-focus: 0 0 0 3px var(--color-text); } \ No newline at end of file diff --git a/styles/11-editorial-magazine/tokens.json b/styles/11-editorial-magazine/tokens.json index 44ccfae..b97eed3 100644 --- a/styles/11-editorial-magazine/tokens.json +++ b/styles/11-editorial-magazine/tokens.json @@ -53,7 +53,7 @@ "shadows": { "none": "none", "paper": "0 8px 24px rgb(26 24 21 / 0.08)", - "focus": "0 0 0 3px rgb(140 47 27 / 0.3)" + "focus": "0 0 0 3px var(--color-accent)" }, "borders": { "default": "1px solid var(--color-border)", @@ -80,6 +80,9 @@ "border": "#35302A", "primary": "#F2ECE2", "primary-contrast": "#14110E" + }, + "shadows": { + "focus": "0 0 0 3px var(--color-text)" } } } diff --git a/styles/11-editorial-magazine/tokens.tailwind.css b/styles/11-editorial-magazine/tokens.tailwind.css index 013e37a..5093f4a 100644 --- a/styles/11-editorial-magazine/tokens.tailwind.css +++ b/styles/11-editorial-magazine/tokens.tailwind.css @@ -48,7 +48,7 @@ /* shadows */ --shadow-none: none; --shadow-paper: 0 8px 24px rgb(26 24 21 / 0.08); - --shadow-focus: 0 0 0 3px rgb(140 47 27 / 0.3); + --shadow-focus: 0 0 0 3px var(--color-accent); /* motionEase */ --ease-default: cubic-bezier(0.22, 1, 0.36, 1); /* container */ diff --git a/styles/12-maximalism/example.html b/styles/12-maximalism/example.html index 73b3192..593fe66 100644 --- a/styles/12-maximalism/example.html +++ b/styles/12-maximalism/example.html @@ -7,7 +7,7 @@ diff --git a/styles/12-maximalism/tokens.css b/styles/12-maximalism/tokens.css index 44cfc22..ec67ca1 100644 --- a/styles/12-maximalism/tokens.css +++ b/styles/12-maximalism/tokens.css @@ -6,7 +6,7 @@ --color-text: #1A0B2E; --color-text-secondary: #5C4A72; --color-border: #1A0B2E; - --color-primary: #E8442E; + --color-primary: #CF3A26; --color-primary-contrast: #FDF6EC; --color-accent-1: #FFB000; /* marigold */ --color-accent-2: #7B2FA0; /* violet */ @@ -40,7 +40,7 @@ --border-thick: 2px solid var(--color-border); --shadow-hard: 4px 4px 0 var(--color-border); --shadow-hard-lg: 8px 8px 0 var(--color-border); - --shadow-focus: 0 0 0 3px var(--color-accent-1); + --shadow-focus: 0 0 0 3px var(--color-primary); --pattern-dots: radial-gradient(var(--color-border) 1.5px, transparent 1.5px); --pattern-dots-size: 14px 14px; @@ -69,5 +69,6 @@ --color-primary-contrast: #1A0B2E; } @media (prefers-reduced-motion: reduce) { - :root { --marquee-speed: 0px; --motion-ease: linear; } + :root { --marquee-speed: 0px; --motion-ease: linear; +} } \ No newline at end of file diff --git a/styles/12-maximalism/tokens.json b/styles/12-maximalism/tokens.json index 7f6e1f9..f4b9ec0 100644 --- a/styles/12-maximalism/tokens.json +++ b/styles/12-maximalism/tokens.json @@ -9,7 +9,7 @@ "text": "#1A0B2E", "text-secondary": "#5C4A72", "border": "#1A0B2E", - "primary": "#E8442E", + "primary": "#CF3A26", "primary-contrast": "#FDF6EC", "accent-1": "#FFB000", "accent-2": "#7B2FA0", @@ -56,7 +56,7 @@ "shadows": { "hard": "4px 4px 0 var(--color-border)", "hard-lg": "8px 8px 0 var(--color-border)", - "focus": "0 0 0 3px var(--color-accent-1)" + "focus": "0 0 0 3px var(--color-primary)" }, "container": { "width": "1200px" @@ -67,7 +67,7 @@ "slow": "450ms" }, "motionEase": { - "default": "linear" + "default": "cubic-bezier(0.34, 1.56, 0.64, 1)" } }, "dark": { diff --git a/styles/12-maximalism/tokens.tailwind.css b/styles/12-maximalism/tokens.tailwind.css index da921fb..bedd384 100644 --- a/styles/12-maximalism/tokens.tailwind.css +++ b/styles/12-maximalism/tokens.tailwind.css @@ -9,7 +9,7 @@ --color-text: #1A0B2E; --color-text-secondary: #5C4A72; --color-border: #1A0B2E; - --color-primary: #E8442E; + --color-primary: #CF3A26; --color-primary-contrast: #FDF6EC; --color-accent-1: #FFB000; --color-accent-2: #7B2FA0; @@ -48,9 +48,9 @@ /* shadows */ --shadow-hard: 4px 4px 0 var(--color-border); --shadow-hard-lg: 8px 8px 0 var(--color-border); - --shadow-focus: 0 0 0 3px var(--color-accent-1); + --shadow-focus: 0 0 0 3px var(--color-primary); /* motionEase */ - --ease-default: linear; + --ease-default: cubic-bezier(0.34, 1.56, 0.64, 1); /* container */ --container-width: 1200px; } diff --git a/styles/13-y2k-retrofuturism/example.html b/styles/13-y2k-retrofuturism/example.html index 9314d34..569e0bd 100644 --- a/styles/13-y2k-retrofuturism/example.html +++ b/styles/13-y2k-retrofuturism/example.html @@ -73,6 +73,10 @@ nav ul{order:3;width:100%;gap:20px;flex-wrap:wrap} nav a{display:inline-flex;align-items:center;min-height:44px} } + +/* Authored focus indicator — ANTI-SLOP.md treats a missing or invisible + :focus-visible as a BLOCKER, so the reference page has to show one. */ +:focus-visible{outline:3px solid var(--primary);outline-offset:2px} diff --git a/styles/13-y2k-retrofuturism/tokens.css b/styles/13-y2k-retrofuturism/tokens.css index f7e62fb..2b83025 100644 --- a/styles/13-y2k-retrofuturism/tokens.css +++ b/styles/13-y2k-retrofuturism/tokens.css @@ -3,7 +3,7 @@ --color-bg: #E8F1F8; --color-surface: #FFFFFF; --color-text: #1B2A3A; - --color-text-secondary: #5A7086; + --color-text-secondary: #596F85; --color-border: #B9CCDD; --color-primary: #3B5BDB; --color-primary-contrast: #FFFFFF; @@ -48,7 +48,7 @@ --bezel-shadow: inset 0 -3px 4px rgb(0 0 0 / 0.12); --bezel: var(--bezel-highlight), var(--bezel-shadow); --shadow-panel: 0 8px 24px rgb(27 42 58 / 0.10); - --shadow-focus: 0 0 0 3px rgb(59 91 219 / 0.35); + --shadow-focus: 0 0 0 3px var(--color-primary); --container-width: 1200px; diff --git a/styles/13-y2k-retrofuturism/tokens.json b/styles/13-y2k-retrofuturism/tokens.json index a2c6837..90f2184 100644 --- a/styles/13-y2k-retrofuturism/tokens.json +++ b/styles/13-y2k-retrofuturism/tokens.json @@ -6,7 +6,7 @@ "bg": "#E8F1F8", "surface": "#FFFFFF", "text": "#1B2A3A", - "text-secondary": "#5A7086", + "text-secondary": "#596F85", "border": "#B9CCDD", "primary": "#3B5BDB", "primary-contrast": "#FFFFFF", @@ -52,7 +52,7 @@ }, "shadows": { "panel": "0 8px 24px rgb(27 42 58 / 0.10)", - "focus": "0 0 0 3px rgb(59 91 219 / 0.35)" + "focus": "0 0 0 3px var(--color-primary)" }, "container": { "width": "1200px" @@ -67,7 +67,7 @@ "slow": "400ms" }, "motionEase": { - "default": "linear" + "default": "cubic-bezier(0.22, 1, 0.36, 1)" } }, "dark": { diff --git a/styles/13-y2k-retrofuturism/tokens.tailwind.css b/styles/13-y2k-retrofuturism/tokens.tailwind.css index d2f8d1a..c7b98d1 100644 --- a/styles/13-y2k-retrofuturism/tokens.tailwind.css +++ b/styles/13-y2k-retrofuturism/tokens.tailwind.css @@ -6,7 +6,7 @@ --color-bg: #E8F1F8; --color-surface: #FFFFFF; --color-text: #1B2A3A; - --color-text-secondary: #5A7086; + --color-text-secondary: #596F85; --color-border: #B9CCDD; --color-primary: #3B5BDB; --color-primary-contrast: #FFFFFF; @@ -47,9 +47,9 @@ --radius-pill: 999px; /* shadows */ --shadow-panel: 0 8px 24px rgb(27 42 58 / 0.10); - --shadow-focus: 0 0 0 3px rgb(59 91 219 / 0.35); + --shadow-focus: 0 0 0 3px var(--color-primary); /* motionEase */ - --ease-default: linear; + --ease-default: cubic-bezier(0.22, 1, 0.36, 1); /* container */ --container-width: 1200px; } diff --git a/styles/14-3d-spatial-ui/example.html b/styles/14-3d-spatial-ui/example.html index 11722c6..a5c6093 100644 --- a/styles/14-3d-spatial-ui/example.html +++ b/styles/14-3d-spatial-ui/example.html @@ -117,6 +117,10 @@ nav ul{order:3;width:100%;gap:20px;flex-wrap:wrap} nav a{display:inline-flex;align-items:center;min-height:44px} } + +/* Authored focus indicator — ANTI-SLOP.md treats a missing or invisible + :focus-visible as a BLOCKER, so the reference page has to show one. */ +:focus-visible{outline:3px solid var(--accent);outline-offset:2px} diff --git a/styles/14-3d-spatial-ui/tokens.css b/styles/14-3d-spatial-ui/tokens.css index 79ac320..2faf0f3 100644 --- a/styles/14-3d-spatial-ui/tokens.css +++ b/styles/14-3d-spatial-ui/tokens.css @@ -44,7 +44,7 @@ --shadow-plane: 0 24px 60px -24px rgb(16 16 20 / 0.25); --shadow-lift: 0 32px 72px -28px rgb(16 16 20 / 0.35); - --shadow-focus: 0 0 0 3px rgb(79 70 229 / 0.35); + --shadow-focus: 0 0 0 3px var(--color-accent); --border-default: 1px solid var(--color-border); --border-strong: 1px solid var(--color-text); diff --git a/styles/14-3d-spatial-ui/tokens.json b/styles/14-3d-spatial-ui/tokens.json index bb7e907..de9f828 100644 --- a/styles/14-3d-spatial-ui/tokens.json +++ b/styles/14-3d-spatial-ui/tokens.json @@ -52,7 +52,7 @@ "shadows": { "plane": "0 24px 60px -24px rgb(16 16 20 / 0.25)", "lift": "0 32px 72px -28px rgb(16 16 20 / 0.35)", - "focus": "0 0 0 3px rgb(79 70 229 / 0.35)" + "focus": "0 0 0 3px var(--color-accent)" }, "borders": { "default": "1px solid var(--color-border)", diff --git a/styles/14-3d-spatial-ui/tokens.tailwind.css b/styles/14-3d-spatial-ui/tokens.tailwind.css index 4c8f1cb..44ac28c 100644 --- a/styles/14-3d-spatial-ui/tokens.tailwind.css +++ b/styles/14-3d-spatial-ui/tokens.tailwind.css @@ -47,7 +47,7 @@ /* shadows */ --shadow-plane: 0 24px 60px -24px rgb(16 16 20 / 0.25); --shadow-lift: 0 32px 72px -28px rgb(16 16 20 / 0.35); - --shadow-focus: 0 0 0 3px rgb(79 70 229 / 0.35); + --shadow-focus: 0 0 0 3px var(--color-accent); /* motionEase */ --ease-default: cubic-bezier(0.16, 1, 0.3, 1); /* container */ diff --git a/styles/15-expressive-kinetic-typography/example.html b/styles/15-expressive-kinetic-typography/example.html index 6ef073a..73ca5d8 100644 --- a/styles/15-expressive-kinetic-typography/example.html +++ b/styles/15-expressive-kinetic-typography/example.html @@ -9,7 +9,7 @@ diff --git a/styles/15-expressive-kinetic-typography/tokens.css b/styles/15-expressive-kinetic-typography/tokens.css index 345ed8d..37c2fdf 100644 --- a/styles/15-expressive-kinetic-typography/tokens.css +++ b/styles/15-expressive-kinetic-typography/tokens.css @@ -7,7 +7,7 @@ --color-border: #E8E3DA; --color-primary: #14120E; --color-primary-contrast: #FAF7F2; - --color-accent: #E4572E; + --color-accent: #C24A27; --color-accent-contrast: #FAF7F2; --color-success: #16A34A; --color-warning: #D97706; @@ -74,5 +74,5 @@ --motion-reveal: 0ms; --motion-marquee: 999999s; /* effectively paused at first frame */ --vf-rest: 'wght' 700, 'SOFT' 0, 'WONK' 0; - } +} } \ No newline at end of file diff --git a/styles/15-expressive-kinetic-typography/tokens.json b/styles/15-expressive-kinetic-typography/tokens.json index fe1908a..b1ef759 100644 --- a/styles/15-expressive-kinetic-typography/tokens.json +++ b/styles/15-expressive-kinetic-typography/tokens.json @@ -10,7 +10,7 @@ "border": "#E8E3DA", "primary": "#14120E", "primary-contrast": "#FAF7F2", - "accent": "#E4572E", + "accent": "#C24A27", "accent-contrast": "#FAF7F2", "success": "#16A34A", "warning": "#D97706", @@ -62,8 +62,8 @@ }, "motion": { "hover": "250ms", - "reveal": "0ms", - "marquee": "999999s", + "reveal": "700ms", + "marquee": "42s", "stagger": "60ms", "board": "steps(8, end)" }, diff --git a/styles/15-expressive-kinetic-typography/tokens.tailwind.css b/styles/15-expressive-kinetic-typography/tokens.tailwind.css index a688863..1faf245 100644 --- a/styles/15-expressive-kinetic-typography/tokens.tailwind.css +++ b/styles/15-expressive-kinetic-typography/tokens.tailwind.css @@ -10,7 +10,7 @@ --color-border: #E8E3DA; --color-primary: #14120E; --color-primary-contrast: #FAF7F2; - --color-accent: #E4572E; + --color-accent: #C24A27; --color-accent-contrast: #FAF7F2; --color-success: #16A34A; --color-warning: #D97706; @@ -56,8 +56,8 @@ /* No Tailwind namespace — use via var() */ :root { --motion-hover: 250ms; - --motion-reveal: 0ms; - --motion-marquee: 999999s; + --motion-reveal: 700ms; + --motion-marquee: 42s; --motion-stagger: 60ms; --motion-board: steps(8, end); --border-default: 1px solid var(--color-border); diff --git a/styles/index.json b/styles/index.json index 624b400..bd8c552 100644 --- a/styles/index.json +++ b/styles/index.json @@ -1,6 +1,6 @@ { "$comment": "Machine-readable style routing index. Generated by scripts/gen_manifest.py — do not edit by hand.", - "version": "2.0.0", + "version": "2.1.0", "count": 15, "signals": { "density": [