From e3554ae97d3d9896eedbe073d435145d62a125a4 Mon Sep 17 00:00:00 2001 From: Samir Date: Mon, 14 Sep 2026 01:47:01 -0400 Subject: [PATCH] IV-159 Define the visual vocabulary contract User prompt: a lot better. Mm. That typeface thing is pretty... I feel like typeface, the number of sizes of fonts is a pretty prevalent problem in a lot of AI created websites. Like, humans want to see relative consistency in colors, font type faces, and then font sizes. And so, let's make sure we're applying that. Here, but then also at the construct level, um, and then with other apps. so that we don't have this kind of issue in the future. --- .agents/skills/structured-liquidity/SKILL.md | 13 +- artifacts/structured-liquidity/package.json | 2 + .../public/design-tokens.json | 86 +++++-- .../structured-liquidity/public/llms.txt | 1 + .../public/r/structured-liquidity-agent.json | 2 +- .../public/r/structured-liquidity.json | 11 +- .../public/structured-liquidity.css | 13 +- .../scripts/build-registry.mjs | 9 + .../scripts/check-visual-vocabulary.mjs | 171 ++++++++++++++ .../src/styles/10-tokens-base.css | 13 +- .../visual-vocabulary-baseline.json | 222 ++++++++++++++++++ .../visual-vocabulary.json | 41 ++++ docs/design-ethos-v2.md | 6 +- docs/visual-vocabulary.md | 36 +++ package.json | 3 +- 15 files changed, 597 insertions(+), 32 deletions(-) create mode 100644 artifacts/structured-liquidity/scripts/check-visual-vocabulary.mjs create mode 100644 artifacts/structured-liquidity/visual-vocabulary-baseline.json create mode 100644 artifacts/structured-liquidity/visual-vocabulary.json create mode 100644 docs/visual-vocabulary.md diff --git a/.agents/skills/structured-liquidity/SKILL.md b/.agents/skills/structured-liquidity/SKILL.md index b1b9e96..eb1eb8a 100644 --- a/.agents/skills/structured-liquidity/SKILL.md +++ b/.agents/skills/structured-liquidity/SKILL.md @@ -23,7 +23,7 @@ Follow these literally — they are what keep generated output on-brand: 4. **Exactly one accent.** `--accent` carries all emphasis. Never add a competing hue. 5. **Liquid is movement, not a gradient treatment.** Default information surfaces are flat. Express liquidity through moving selection markers, menu and popover settling, tab changes, scroll continuity, and transitions between areas. Use neutral translucency or `backdrop-filter` only at moving state boundaries such as navigation, menus, and overlays—not on every card. 6. **Destructive is gray, not red.** Destructive _containers_ use `--neg`. Error _text_ may warn in color. -7. **Fixed type roles.** display (`--display`, Inter) = headings/buttons/brand; body (`--body`, Inter) = reading copy; mono (`--mono`, Space Mono) = labels/data/captions/eyebrows. +7. **Fixed visual vocabulary.** Use no more than two actual typefaces across three roles: display (`--display`, Inter) for headings/buttons/brand; body (`--body`, Inter) for reading copy; mono (`--mono`, Space Mono) for labels/data/captions/eyebrows. Use the seven semantic size tiers and four weight tiers; do not add local font sizes or weights. Consume semantic color tokens instead of raw component colors. 8. **Leading icons.** Nav links and buttons take a leading Lucide icon, then the label. 9. **Motion proves mass.** On press/hover, nudge the element ~1px toward its shadow and grow the offset. 10. **Communicate with scientific modern clarity.** Use a rational grid, exact alignment, asymmetric balance, measured line lengths, purposeful whitespace, and compact mono captions for sources, state, and evidence. Swiss modernism and the International Typographic Style are the reference tradition. @@ -55,8 +55,17 @@ Apply these as CSS custom properties on `:root` (light-mode defaults): --display: "Inter", "Helvetica Neue", Arial, system-ui, sans-serif; --body: "Inter", "Helvetica Neue", Arial, system-ui, sans-serif; --mono: "Space Mono", "SFMono-Regular", ui-monospace, monospace; + --text-detail: 0.72rem; + --text-body: 0.95rem; + --text-lead: clamp(1rem, 1.3vw, 1.18rem); + --text-section: 1.25rem; + --text-title: 1.75rem; --text-display-xl: clamp(4.2rem, 10.5vw, 9.5rem); --text-display-lg: clamp(2.6rem, 5.5vw, 5.25rem); + --weight-regular: 500; + --weight-medium: 600; + --weight-bold: 700; + --weight-strong: 800; --measure-reading: 62ch; --measure-caption: 48ch; --grid-gutter: clamp(1rem, 2.5vw, 2.5rem); @@ -70,7 +79,7 @@ The canonical machine-readable set is `design-tokens.json` (W3C Design Tokens fo ## Type layout -Three roles — header / body / detail (display / body / mono). Inter carries both sans-serif roles; use scale, weight, measure, and alignment for hierarchy. Use Space Mono only for labels, data, code, captions, and eyebrows. Prefer one dominant statement, a 12-column or similarly disciplined grid, short reading measures, and figure captions that identify evidence and state. +Three roles — header / body / detail (display / body / mono) — resolve to at most two actual typefaces. Inter carries both sans-serif roles; use scale, weight, measure, and alignment for hierarchy. Use Space Mono only for labels, data, code, captions, and eyebrows. The seven allowed size tiers are detail, body, lead, section, title, display large, and display extra-large. Prefer one dominant statement, a 12-column or similarly disciplined grid, short reading measures, and figure captions that identify evidence and state. Existing raw declarations may be captured as migration debt, but a visual-vocabulary ratchet must prevent new raw type, size, weight, or color declarations. ## Motion contract diff --git a/artifacts/structured-liquidity/package.json b/artifacts/structured-liquidity/package.json index 64d3a2e..09a4214 100644 --- a/artifacts/structured-liquidity/package.json +++ b/artifacts/structured-liquidity/package.json @@ -10,6 +10,8 @@ "typecheck": "tsc -p tsconfig.json --noEmit", "registry": "node scripts/build-registry.mjs", "registry:check": "node scripts/check-registry.mjs", + "visual-vocabulary:check": "node scripts/check-visual-vocabulary.mjs", + "visual-vocabulary:baseline": "node scripts/check-visual-vocabulary.mjs --write-baseline", "adopters:check": "node scripts/check-adopters.mjs" }, "devDependencies": { diff --git a/artifacts/structured-liquidity/public/design-tokens.json b/artifacts/structured-liquidity/public/design-tokens.json index 8d90eea..8dbfceb 100644 --- a/artifacts/structured-liquidity/public/design-tokens.json +++ b/artifacts/structured-liquidity/public/design-tokens.json @@ -109,39 +109,52 @@ "fontFamily": { "$type": "fontFamily", "display": { - "$value": [ - "Inter", - "Helvetica Neue", - "Arial", - "system-ui", - "sans-serif" - ], + "$value": ["Inter", "Helvetica Neue", "Arial", "system-ui", "sans-serif"], "$description": "Headings, buttons, and brand. Use weight, scale, and alignment to create hierarchy.", "$extensions": { "cssVar": "--display" } }, "body": { - "$value": [ - "Inter", - "Helvetica Neue", - "Arial", - "system-ui", - "sans-serif" - ], + "$value": ["Inter", "Helvetica Neue", "Arial", "system-ui", "sans-serif"], "$description": "Paragraph and reading copy. Neutral and legible.", "$extensions": { "cssVar": "--body" } }, "mono": { - "$value": [ - "Space Mono", - "SFMono-Regular", - "ui-monospace", - "monospace" - ], + "$value": ["Space Mono", "SFMono-Regular", "ui-monospace", "monospace"], "$description": "Labels, captions, data, eyebrows. Never long paragraphs.", "$extensions": { "cssVar": "--mono" } } }, "scientificClarity": { + "detail": { + "$type": "dimension", + "$value": "0.72rem", + "$description": "Compact evidence labels, captions, eyebrows, and metadata.", + "$extensions": { "cssVar": "--text-detail" } + }, + "body": { + "$type": "dimension", + "$value": "0.95rem", + "$description": "Default interface and reading copy.", + "$extensions": { "cssVar": "--text-body" } + }, + "lead": { + "$type": "dimension", + "$value": "clamp(1rem, 1.3vw, 1.18rem)", + "$description": "Introductory copy that leads a section without becoming a heading.", + "$extensions": { "cssVar": "--text-lead" } + }, + "section": { + "$type": "dimension", + "$value": "1.25rem", + "$description": "Compact section headings inside applications and surfaces.", + "$extensions": { "cssVar": "--text-section" } + }, + "title": { + "$type": "dimension", + "$value": "1.75rem", + "$description": "Surface and panel titles below page-display scale.", + "$extensions": { "cssVar": "--text-title" } + }, "displayXL": { "$type": "dimension", "$value": "clamp(4.2rem, 10.5vw, 9.5rem)", @@ -179,6 +192,29 @@ "$extensions": { "cssVar": "--grid-gutter" } } }, + "fontWeight": { + "$type": "fontWeight", + "regular": { + "$value": 500, + "$description": "Default text weight.", + "$extensions": { "cssVar": "--weight-regular" } + }, + "medium": { + "$value": 600, + "$description": "Quiet emphasis and titles.", + "$extensions": { "cssVar": "--weight-medium" } + }, + "bold": { + "$value": 700, + "$description": "Labels and actionable emphasis.", + "$extensions": { "cssVar": "--weight-bold" } + }, + "strong": { + "$value": 800, + "$description": "Dominant headings and high-contrast hierarchy.", + "$extensions": { "cssVar": "--weight-strong" } + } + }, "motion": { "fast": { "$type": "duration", @@ -204,8 +240,14 @@ "$description": "Color-responsive dark-mode overrides applied when . The page field is derived from the active accent; there is no separate black mode.", "color": { "$type": "color", - "bg": { "$value": "color-mix(in srgb, var(--accent) 18%, #100b16)", "$extensions": { "cssVar": "--bg" } }, - "bg2": { "$value": "color-mix(in srgb, var(--accent) 27%, #15101c)", "$extensions": { "cssVar": "--bg-2" } }, + "bg": { + "$value": "color-mix(in srgb, var(--accent) 18%, #100b16)", + "$extensions": { "cssVar": "--bg" } + }, + "bg2": { + "$value": "color-mix(in srgb, var(--accent) 27%, #15101c)", + "$extensions": { "cssVar": "--bg-2" } + }, "ink": { "$value": "#f6f2f8", "$extensions": { "cssVar": "--ink" } }, "inkDim": { "$value": "#b9b0bf", diff --git a/artifacts/structured-liquidity/public/llms.txt b/artifacts/structured-liquidity/public/llms.txt index 700c5da..f22ee52 100644 --- a/artifacts/structured-liquidity/public/llms.txt +++ b/artifacts/structured-liquidity/public/llms.txt @@ -39,6 +39,7 @@ This installs the theme and `.agents/skills/structured-liquidity/SKILL.md`. Veri All visuals read from CSS custom properties on `:root`. The machine-readable token set (W3C Design Tokens Community Group format, with the CSS variable name in each `$extensions.cssVar`) is at: - [design-tokens.json](design-tokens.json): the full token set, covering color, glass, border, radius, flat shadow, font families, plus light-mode overrides. +- Typography is a controlled visual vocabulary: at most two actual typefaces across display/body/detail roles, seven semantic size tiers, and four semantic weights. New raw type or color declarations should fail a checked-in ratchet rather than silently expanding the system. Core defaults: diff --git a/artifacts/structured-liquidity/public/r/structured-liquidity-agent.json b/artifacts/structured-liquidity/public/r/structured-liquidity-agent.json index d67da84..cb13032 100644 --- a/artifacts/structured-liquidity/public/r/structured-liquidity-agent.json +++ b/artifacts/structured-liquidity/public/r/structured-liquidity-agent.json @@ -10,7 +10,7 @@ "files": [ { "path": "registry/rules/structured-liquidity/SKILL.md", - "content": "---\nname: structured-liquidity\ndescription: Apply the Structured Liquidity UI design language — Neobrutalist structure, Liquid Motion, and Scientific Modern Clarity. Use when building or restyling any UI that should follow Structured Liquidity, when the user references the language by name, or when adopting its tokens/components in a new project.\n---\n\n# Structured Liquidity\n\n**Neobrutalist structure. Liquid motion. Scientific modern clarity.**\n\n1. **Neobrutalist structure:** How the interface is physically framed—buttons, borders, navigation, footers, body containers, panels, square geometry, and selective flat shadows. It makes boundaries and actions tangible before effects are added.\n2. **Liquid Motion:** How the system behaves—continuity, layered neutral glass, and purposeful motion that make state and believable mass perceptible without becoming the only carrier of meaning. Every interaction has a complete reduced-motion state.\n3. **Scientific Modern Clarity:** How information is organized and meaning is communicated—rational grids, disciplined hierarchy, measured typography, captions, labeling, evidence, and purposeful whitespace. Swiss modernism and the International Typographic Style are the informing tradition: make information legible, ordered, and unmistakable. Every screen remains complete and useful without relying on color, glass, or motion.\n\nTogether these three tenets form the Structured Liquidity trifecta, represented by the three visible faces of the cube. Each tenet owns the clarity of its part of the interface. The living specimen, machine-readable tokens, and component registry are served by the `structured-liquidity` artifact.\n\n## The non-negotiable rules\n\nFollow these literally — they are what keep generated output on-brand:\n\n1. **Square corners.** `border-radius: var(--radius)` (default `0`). Only raise deliberately.\n2. **Flat offset shadows, used selectively.** `box-shadow: var(--hard-x) var(--hard-y) 0 0 var(--hard-shadow)`. Blur is always `0`. Reserve shadows for tactile affordances and deliberately layered cutout surfaces; flat information containers can rely on borders, rules, and alignment.\n3. **Load-bearing borders.** `var(--border-w) solid rgb(var(--edge))`. The edge is black in both light and dark mode.\n4. **Exactly one accent.** `--accent` carries all emphasis. Never add a competing hue.\n5. **Liquid is movement, not a gradient treatment.** Default information surfaces are flat. Express liquidity through moving selection markers, menu and popover settling, tab changes, scroll continuity, and transitions between areas. Use neutral translucency or `backdrop-filter` only at moving state boundaries such as navigation, menus, and overlays—not on every card.\n6. **Destructive is gray, not red.** Destructive _containers_ use `--neg`. Error _text_ may warn in color.\n7. **Fixed type roles.** display (`--display`, Inter) = headings/buttons/brand; body (`--body`, Inter) = reading copy; mono (`--mono`, Space Mono) = labels/data/captions/eyebrows.\n8. **Leading icons.** Nav links and buttons take a leading Lucide icon, then the label.\n9. **Motion proves mass.** On press/hover, nudge the element ~1px toward its shadow and grow the offset.\n10. **Communicate with scientific modern clarity.** Use a rational grid, exact alignment, asymmetric balance, measured line lengths, purposeful whitespace, and compact mono captions for sources, state, and evidence. Swiss modernism and the International Typographic Style are the reference tradition.\n11. **Monochrome carries information.** The accent identifies selection, progression, or one key relationship; it does not decorate the page.\n\n## Tokens\n\nApply these as CSS custom properties on `:root` (light-mode defaults):\n\n```css\n:root {\n --accent: #a388ee;\n --accent-ink: #000000;\n --bg: #f2f0ea;\n --bg-2: #ffffff;\n --ink: #111111;\n --ink-dim: #5d5d59;\n --edge: 0 0 0;\n --hard-shadow: #000000;\n --neg: #242424;\n --neg-ink: #ffffff;\n --glass-blur: 14px;\n --glass-tint: 255 255 255;\n --glass-alpha: 0.72;\n --border-w: 2px;\n --hard-x: 5px;\n --hard-y: 5px;\n --radius: 0px;\n --display: \"Inter\", \"Helvetica Neue\", Arial, system-ui, sans-serif;\n --body: \"Inter\", \"Helvetica Neue\", Arial, system-ui, sans-serif;\n --mono: \"Space Mono\", \"SFMono-Regular\", ui-monospace, monospace;\n --text-display-xl: clamp(4.2rem, 10.5vw, 9.5rem);\n --text-display-lg: clamp(2.6rem, 5.5vw, 5.25rem);\n --measure-reading: 62ch;\n --measure-caption: 48ch;\n --grid-gutter: clamp(1rem, 2.5vw, 2.5rem);\n --section-space: clamp(4rem, 9vw, 9rem);\n}\n```\n\nDark mode has one color-responsive atmospheric palette; there is no separate black mode. Set `data-mode=\"dark\"` on `` and derive `--bg:color-mix(in srgb,var(--accent) 8%,#0d0a11)` plus `--bg-2:color-mix(in srgb,var(--accent) 14%,#100d14)`, with `--ink:#f6f2f8; --ink-dim:#b9b0bf; --neg:#343434; --neg-ink:#fff; --glass-alpha:0.08`. Changing the single accent must retune the dark page field automatically: purple becomes deep aubergine, green becomes deep green, yellow becomes deep ochre. Tactile components may remain charcoal or near-black above that field.\n\nThe canonical machine-readable set is `design-tokens.json` (W3C Design Tokens format; each token's `$extensions.cssVar` gives the exact variable name).\n\n## Type layout\n\nThree roles — header / body / detail (display / body / mono). Inter carries both sans-serif roles; use scale, weight, measure, and alignment for hierarchy. Use Space Mono only for labels, data, code, captions, and eyebrows. Prefer one dominant statement, a 12-column or similarly disciplined grid, short reading measures, and figure captions that identify evidence and state.\n\n## Motion contract\n\nThe rigid frame never wobbles or morphs. Motion belongs inside it and must explain state, continuity, or spatial relationship. Prefer a shared marker that visibly travels between menu options, tabs, filters, navbar actions, and page areas over per-item gradient fills or decorative sheen. Use one liquid easing family; reserve ambient motion for a single focal specimen; provide a complete static state under `prefers-reduced-motion`. If the interface communicates the same thing without the animation, the motion is optional polish, not a dependency.\n\n## Components\n\nThe specimen ships a **shadcn-installable registry** served at `/r/*.json`. Two ways to adopt, depending on the target project:\n\n### Installation gate for agents\n\nBefore making the first UI edit in a target project, run:\n\n```bash\nnpx shadcn@latest add https://structured.glass/r/structured-liquidity-agent.json\n```\n\nVerify that the theme styles and `.agents/skills/structured-liquidity/SKILL.md` landed. Do not spend early iterations approximating the language by hand.\n\n**A. React + Tailwind project (preferred).** Use the shadcn CLI. Install the whole language (tokens + the three stylesheets + the kit script) first, then any component:\n\n```bash\nnpx shadcn@latest add https://structured.glass/r/structured-liquidity.json # base style — do this first\nnpx shadcn@latest add https://structured.glass/r/button.json # then any component\n```\n\nEach `registry:ui` item is a plain React component (no Radix) that emits the SL classes and declares the base style as a `registryDependency`, so the CSS comes along. The registry index lives at `/registry.json`.\n\n### Portfolio-grid default\n\nUse the installable `portfolio-card` component for company, investment, and venture grids. Its information hierarchy is persistent: logo or identity mark, company name, one concise description, state or category tags, and the primary action are visible without hover. Hover and press may reinforce tactility, but must never reveal information that is otherwise unavailable, especially on touch devices.\n\n```bash\nnpx shadcn@latest add https://structured.glass/r/portfolio-card.json\n```\n\nUse `PortfolioGrid` with `PortfolioCard` and its anatomy exports. Put `PortfolioCardTags` inside `PortfolioCardMedia` so state and category labels overlay the image plate instead of changing the body height. Keep card bodies aligned within the grid, use a consistent media plate for logo normalization, and use the action only when the destination is real. Unavailable or pre-launch companies render as non-link cards with their status exposed.\n\n**B. Any stack (plain HTML/CSS).** Include `structured-liquidity.css`, `structured-liquidity-components.css`, `structured-liquidity-kit.css`, and `structured-liquidity-kit.js`; load Inter and Space Mono; then use the SL classes directly. Core kit: `sl-btn`, `sl-badge`, `sl-toggle`/`sl-toggle-group`, `sl-switch`, `sl-check`, `sl-radio-item` (inside a `[data-radio-group]`), `sl-input`/`sl-label`, `sl-slider`, `sl-progress`, `sl-ava`, `sl-tabs` (`.tablist` buttons + `.panel` nodes), and overlays (`sl-overlay` + `sl-dialog`/`sl-sheet`). In plain HTML, interactive components are wired by `structured-liquidity-kit.js` via `data-toggle-aria`, `data-toggle-group`, `data-radio-group`, `data-open-overlay`, `data-close-overlay`.\n\n## Anatomy of a stateful glass surface inside a rigid frame\n\n```html\n
\n Title\n

Use this treatment when the surface communicates state or depth.

\n
\n```\n\n```css\n/* if you are not using the kit CSS, the stateful glass recipe is: */\n.stateful-glass {\n border: var(--border-w) solid rgb(var(--edge));\n border-radius: var(--radius);\n box-shadow: var(--hard-x) var(--hard-y) 0 0 var(--hard-shadow);\n background: rgba(var(--glass-tint), var(--glass-alpha));\n backdrop-filter: blur(var(--glass-blur)) saturate(150%);\n}\n```\n", + "content": "---\nname: structured-liquidity\ndescription: Apply the Structured Liquidity UI design language — Neobrutalist structure, Liquid Motion, and Scientific Modern Clarity. Use when building or restyling any UI that should follow Structured Liquidity, when the user references the language by name, or when adopting its tokens/components in a new project.\n---\n\n# Structured Liquidity\n\n**Neobrutalist structure. Liquid motion. Scientific modern clarity.**\n\n1. **Neobrutalist structure:** How the interface is physically framed—buttons, borders, navigation, footers, body containers, panels, square geometry, and selective flat shadows. It makes boundaries and actions tangible before effects are added.\n2. **Liquid Motion:** How the system behaves—continuity, layered neutral glass, and purposeful motion that make state and believable mass perceptible without becoming the only carrier of meaning. Every interaction has a complete reduced-motion state.\n3. **Scientific Modern Clarity:** How information is organized and meaning is communicated—rational grids, disciplined hierarchy, measured typography, captions, labeling, evidence, and purposeful whitespace. Swiss modernism and the International Typographic Style are the informing tradition: make information legible, ordered, and unmistakable. Every screen remains complete and useful without relying on color, glass, or motion.\n\nTogether these three tenets form the Structured Liquidity trifecta, represented by the three visible faces of the cube. Each tenet owns the clarity of its part of the interface. The living specimen, machine-readable tokens, and component registry are served by the `structured-liquidity` artifact.\n\n## The non-negotiable rules\n\nFollow these literally — they are what keep generated output on-brand:\n\n1. **Square corners.** `border-radius: var(--radius)` (default `0`). Only raise deliberately.\n2. **Flat offset shadows, used selectively.** `box-shadow: var(--hard-x) var(--hard-y) 0 0 var(--hard-shadow)`. Blur is always `0`. Reserve shadows for tactile affordances and deliberately layered cutout surfaces; flat information containers can rely on borders, rules, and alignment.\n3. **Load-bearing borders.** `var(--border-w) solid rgb(var(--edge))`. The edge is black in both light and dark mode.\n4. **Exactly one accent.** `--accent` carries all emphasis. Never add a competing hue.\n5. **Liquid is movement, not a gradient treatment.** Default information surfaces are flat. Express liquidity through moving selection markers, menu and popover settling, tab changes, scroll continuity, and transitions between areas. Use neutral translucency or `backdrop-filter` only at moving state boundaries such as navigation, menus, and overlays—not on every card.\n6. **Destructive is gray, not red.** Destructive _containers_ use `--neg`. Error _text_ may warn in color.\n7. **Fixed visual vocabulary.** Use no more than two actual typefaces across three roles: display (`--display`, Inter) for headings/buttons/brand; body (`--body`, Inter) for reading copy; mono (`--mono`, Space Mono) for labels/data/captions/eyebrows. Use the seven semantic size tiers and four weight tiers; do not add local font sizes or weights. Consume semantic color tokens instead of raw component colors.\n8. **Leading icons.** Nav links and buttons take a leading Lucide icon, then the label.\n9. **Motion proves mass.** On press/hover, nudge the element ~1px toward its shadow and grow the offset.\n10. **Communicate with scientific modern clarity.** Use a rational grid, exact alignment, asymmetric balance, measured line lengths, purposeful whitespace, and compact mono captions for sources, state, and evidence. Swiss modernism and the International Typographic Style are the reference tradition.\n11. **Monochrome carries information.** The accent identifies selection, progression, or one key relationship; it does not decorate the page.\n\n## Tokens\n\nApply these as CSS custom properties on `:root` (light-mode defaults):\n\n```css\n:root {\n --accent: #a388ee;\n --accent-ink: #000000;\n --bg: #f2f0ea;\n --bg-2: #ffffff;\n --ink: #111111;\n --ink-dim: #5d5d59;\n --edge: 0 0 0;\n --hard-shadow: #000000;\n --neg: #242424;\n --neg-ink: #ffffff;\n --glass-blur: 14px;\n --glass-tint: 255 255 255;\n --glass-alpha: 0.72;\n --border-w: 2px;\n --hard-x: 5px;\n --hard-y: 5px;\n --radius: 0px;\n --display: \"Inter\", \"Helvetica Neue\", Arial, system-ui, sans-serif;\n --body: \"Inter\", \"Helvetica Neue\", Arial, system-ui, sans-serif;\n --mono: \"Space Mono\", \"SFMono-Regular\", ui-monospace, monospace;\n --text-detail: 0.72rem;\n --text-body: 0.95rem;\n --text-lead: clamp(1rem, 1.3vw, 1.18rem);\n --text-section: 1.25rem;\n --text-title: 1.75rem;\n --text-display-xl: clamp(4.2rem, 10.5vw, 9.5rem);\n --text-display-lg: clamp(2.6rem, 5.5vw, 5.25rem);\n --weight-regular: 500;\n --weight-medium: 600;\n --weight-bold: 700;\n --weight-strong: 800;\n --measure-reading: 62ch;\n --measure-caption: 48ch;\n --grid-gutter: clamp(1rem, 2.5vw, 2.5rem);\n --section-space: clamp(4rem, 9vw, 9rem);\n}\n```\n\nDark mode has one color-responsive atmospheric palette; there is no separate black mode. Set `data-mode=\"dark\"` on `` and derive `--bg:color-mix(in srgb,var(--accent) 8%,#0d0a11)` plus `--bg-2:color-mix(in srgb,var(--accent) 14%,#100d14)`, with `--ink:#f6f2f8; --ink-dim:#b9b0bf; --neg:#343434; --neg-ink:#fff; --glass-alpha:0.08`. Changing the single accent must retune the dark page field automatically: purple becomes deep aubergine, green becomes deep green, yellow becomes deep ochre. Tactile components may remain charcoal or near-black above that field.\n\nThe canonical machine-readable set is `design-tokens.json` (W3C Design Tokens format; each token's `$extensions.cssVar` gives the exact variable name).\n\n## Type layout\n\nThree roles — header / body / detail (display / body / mono) — resolve to at most two actual typefaces. Inter carries both sans-serif roles; use scale, weight, measure, and alignment for hierarchy. Use Space Mono only for labels, data, code, captions, and eyebrows. The seven allowed size tiers are detail, body, lead, section, title, display large, and display extra-large. Prefer one dominant statement, a 12-column or similarly disciplined grid, short reading measures, and figure captions that identify evidence and state. Existing raw declarations may be captured as migration debt, but a visual-vocabulary ratchet must prevent new raw type, size, weight, or color declarations.\n\n## Motion contract\n\nThe rigid frame never wobbles or morphs. Motion belongs inside it and must explain state, continuity, or spatial relationship. Prefer a shared marker that visibly travels between menu options, tabs, filters, navbar actions, and page areas over per-item gradient fills or decorative sheen. Use one liquid easing family; reserve ambient motion for a single focal specimen; provide a complete static state under `prefers-reduced-motion`. If the interface communicates the same thing without the animation, the motion is optional polish, not a dependency.\n\n## Components\n\nThe specimen ships a **shadcn-installable registry** served at `/r/*.json`. Two ways to adopt, depending on the target project:\n\n### Installation gate for agents\n\nBefore making the first UI edit in a target project, run:\n\n```bash\nnpx shadcn@latest add https://structured.glass/r/structured-liquidity-agent.json\n```\n\nVerify that the theme styles and `.agents/skills/structured-liquidity/SKILL.md` landed. Do not spend early iterations approximating the language by hand.\n\n**A. React + Tailwind project (preferred).** Use the shadcn CLI. Install the whole language (tokens + the three stylesheets + the kit script) first, then any component:\n\n```bash\nnpx shadcn@latest add https://structured.glass/r/structured-liquidity.json # base style — do this first\nnpx shadcn@latest add https://structured.glass/r/button.json # then any component\n```\n\nEach `registry:ui` item is a plain React component (no Radix) that emits the SL classes and declares the base style as a `registryDependency`, so the CSS comes along. The registry index lives at `/registry.json`.\n\n### Portfolio-grid default\n\nUse the installable `portfolio-card` component for company, investment, and venture grids. Its information hierarchy is persistent: logo or identity mark, company name, one concise description, state or category tags, and the primary action are visible without hover. Hover and press may reinforce tactility, but must never reveal information that is otherwise unavailable, especially on touch devices.\n\n```bash\nnpx shadcn@latest add https://structured.glass/r/portfolio-card.json\n```\n\nUse `PortfolioGrid` with `PortfolioCard` and its anatomy exports. Put `PortfolioCardTags` inside `PortfolioCardMedia` so state and category labels overlay the image plate instead of changing the body height. Keep card bodies aligned within the grid, use a consistent media plate for logo normalization, and use the action only when the destination is real. Unavailable or pre-launch companies render as non-link cards with their status exposed.\n\n**B. Any stack (plain HTML/CSS).** Include `structured-liquidity.css`, `structured-liquidity-components.css`, `structured-liquidity-kit.css`, and `structured-liquidity-kit.js`; load Inter and Space Mono; then use the SL classes directly. Core kit: `sl-btn`, `sl-badge`, `sl-toggle`/`sl-toggle-group`, `sl-switch`, `sl-check`, `sl-radio-item` (inside a `[data-radio-group]`), `sl-input`/`sl-label`, `sl-slider`, `sl-progress`, `sl-ava`, `sl-tabs` (`.tablist` buttons + `.panel` nodes), and overlays (`sl-overlay` + `sl-dialog`/`sl-sheet`). In plain HTML, interactive components are wired by `structured-liquidity-kit.js` via `data-toggle-aria`, `data-toggle-group`, `data-radio-group`, `data-open-overlay`, `data-close-overlay`.\n\n## Anatomy of a stateful glass surface inside a rigid frame\n\n```html\n
\n Title\n

Use this treatment when the surface communicates state or depth.

\n
\n```\n\n```css\n/* if you are not using the kit CSS, the stateful glass recipe is: */\n.stateful-glass {\n border: var(--border-w) solid rgb(var(--edge));\n border-radius: var(--radius);\n box-shadow: var(--hard-x) var(--hard-y) 0 0 var(--hard-shadow);\n background: rgba(var(--glass-tint), var(--glass-alpha));\n backdrop-filter: blur(var(--glass-blur)) saturate(150%);\n}\n```\n", "type": "registry:file", "target": ".agents/skills/structured-liquidity/SKILL.md" } diff --git a/artifacts/structured-liquidity/public/r/structured-liquidity.json b/artifacts/structured-liquidity/public/r/structured-liquidity.json index 4c22cc5..e91eb76 100644 --- a/artifacts/structured-liquidity/public/r/structured-liquidity.json +++ b/artifacts/structured-liquidity/public/r/structured-liquidity.json @@ -35,8 +35,17 @@ "display": "\"Inter\",\"Helvetica Neue\",Arial,system-ui,sans-serif", "body": "\"Inter\",\"Helvetica Neue\",Arial,system-ui,sans-serif", "mono": "\"Space Mono\",\"SFMono-Regular\",ui-monospace,monospace", + "text-detail": "0.72rem", + "text-body": "0.95rem", + "text-lead": "clamp(1rem, 1.3vw, 1.18rem)", + "text-section": "1.25rem", + "text-title": "1.75rem", "text-display-xl": "clamp(4.2rem, 10.5vw, 9.5rem)", "text-display-lg": "clamp(2.6rem, 5.5vw, 5.25rem)", + "weight-regular": "500", + "weight-medium": "600", + "weight-bold": "700", + "weight-strong": "800", "measure-reading": "62ch", "measure-caption": "48ch", "grid-gutter": "clamp(1rem, 2.5vw, 2.5rem)", @@ -53,7 +62,7 @@ "files": [ { "path": "registry/styles/structured-liquidity.css", - "content": "/* ============================================================\n STRUCTURED LIQUIDITY — Neobrutalist structure. Liquid motion.\n Scientific modern clarity. This stylesheet IS\n the specimen.\n ============================================================ */\n\n:root {\n /* ---- tunable knobs (driven by Tweaks) ---- */\n --accent: #a388ee; /* neobrutalism.dev purple */\n --accent-ink: #000000; /* text that sits on accent */\n --glass-blur: 14px;\n --glass-tint: 255 255 255; /* rgb channels for glass fill */\n --glass-alpha: 0.72;\n --border-w: 2px;\n --hard-x: 5px;\n --hard-y: 5px;\n --radius: 0px;\n --shell-w: 1280px;\n --content-w: var(--shell-w);\n --viewport-gutter: 4vw;\n --body-surface: var(--bg);\n --floating-ui-clearance: 96px;\n\n /* ---- Scientific Modern Clarity, informed by Swiss modernism and the International Typographic Style ---- */\n --text-display-xl: clamp(4.2rem, 10.5vw, 9.5rem);\n --text-display-lg: clamp(2.6rem, 5.5vw, 5.25rem);\n --measure-reading: 62ch;\n --measure-caption: 48ch;\n --grid-gutter: clamp(1rem, 2.5vw, 2.5rem);\n --section-space: clamp(4rem, 9vw, 9rem);\n --clarity-gap: clamp(1.25rem, 3vw, 3rem);\n --rule-hairline: 1px;\n --diagram-line: color-mix(in srgb, var(--ink) 72%, transparent);\n --diagram-muted: color-mix(in srgb, var(--ink) 42%, transparent);\n\n /* ---- light-first paper palette ---- */\n --bg: #f2f0ea;\n --bg-2: #ffffff;\n --ink: #111111; /* foreground */\n --ink-dim: #5d5d59;\n --edge: 0 0 0; /* neobrutalism black border (rgb) */\n --hard-shadow: #000000;\n --neg: #242424; /* negative / destructive surface (gray, never red) */\n --neg-ink: #ffffff;\n\n --display: \"Inter\", \"Helvetica Neue\", Arial, system-ui, sans-serif;\n --body: \"Inter\", \"Helvetica Neue\", Arial, system-ui, sans-serif;\n --mono: \"Space Mono\", \"SFMono-Regular\", ui-monospace, monospace;\n}\n\n/* tighter rhythm — denser, less airy */\n\n[data-mode=\"light\"] {\n --bg: #f2f0ea;\n --bg-2: #ffffff;\n --ink: #111111;\n --ink-dim: #5d5d59;\n --edge: 0 0 0;\n --hard-shadow: #000000;\n --neg: #16171c; /* near-black negative surface in light mode */\n --neg-ink: #ffffff;\n --glass-tint: 255 255 255;\n --glass-alpha: 0.72;\n}\n\n[data-mode=\"dark\"] {\n /* Dark mode is color-responsive: the active accent tints the atmospheric\n field while tactile components remain charcoal. There is no black mode. */\n --bg: color-mix(in srgb, var(--accent) 18%, #100b16);\n --bg-2: color-mix(in srgb, var(--accent) 27%, #15101c);\n --ink: #f6f2f8;\n --ink-dim: #b9b0bf;\n --edge: 0 0 0;\n --hard-shadow: #000000;\n --neg: #343434;\n --neg-ink: #ffffff;\n --glass-tint: 255 255 255;\n --glass-alpha: 0.08;\n}\n\n* {\n box-sizing: border-box;\n}\n\nhtml {\n scroll-behavior: auto;\n background: var(--bg);\n}\n\nbody {\n margin: 0;\n position: relative;\n background: transparent;\n color: var(--ink);\n font-family: var(--display);\n font-weight: 440;\n line-height: 1.45;\n -webkit-font-smoothing: antialiased;\n overflow-x: hidden;\n}\n\n::selection {\n background: var(--accent);\n color: var(--accent-ink);\n}\n\n/* ============================================================\n BACKDROP — flat paper plus a measured technical grid. Liquid\n belongs to interaction and continuity, not decorative scenery.\n ============================================================ */\n\n.field {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n height: 100%;\n z-index: -2;\n overflow: hidden;\n pointer-events: none;\n background: var(--bg);\n}\n[data-mode=\"light\"] .field {\n opacity: 1;\n}\n[data-mode=\"dark\"] .field {\n opacity: 1;\n}\n\n.field .grid {\n position: absolute;\n inset: 0;\n background-image:\n linear-gradient(rgba(var(--edge), 0.045) 1px, transparent 1px),\n linear-gradient(90deg, rgba(var(--edge), 0.045) 1px, transparent 1px);\n background-size: 64px 64px;\n opacity: 0.7;\n}\n\n.grain {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n height: 100%;\n z-index: -1;\n pointer-events: none;\n display: none;\n}\n\n/* ============================================================\n STRUCTURAL SURFACE — flat by default. Add blur only to transient\n navigation, menus, overlays, and other moving state boundaries.\n ============================================================ */\n\n.glass {\n position: relative;\n background: var(--bg-2);\n border: var(--border-w) solid rgb(var(--edge));\n border-radius: var(--radius);\n box-shadow: none;\n}\n/* moving sheen + cursor specular */\n.glass::before,\n.glass::after {\n content: none;\n}\n\n.flat {\n /* brutalist flat (no glass) — for loud accent blocks */\n border: var(--border-w) solid rgb(var(--edge));\n border-radius: var(--radius);\n box-shadow: var(--hard-x) var(--hard-y) 0 0 var(--hard-shadow);\n}\n\n/* ============================================================\n TYPE\n ============================================================ */\n\n.eyebrow {\n font-family: var(--mono);\n font-size: 0.72rem;\n letter-spacing: 0.22em;\n text-transform: uppercase;\n color: var(--ink-dim);\n display: inline-flex;\n align-items: center;\n gap: 0.6em;\n}\n.eyebrow::before {\n content: \"\";\n width: 9px;\n height: 9px;\n background: var(--accent);\n display: inline-block;\n}\n\nh1,\nh2,\nh3 {\n font-weight: 800;\n line-height: 0.98;\n letter-spacing: -0.02em;\n margin: 0;\n text-wrap: balance;\n}\n\n.section-title {\n font-size: clamp(2rem, 4.2vw, 4.25rem);\n line-height: 0.93;\n letter-spacing: -0.045em;\n margin: 0.25em 0 0.3em;\n}\n.lead {\n font-size: clamp(1rem, 1.3vw, 1.18rem);\n color: var(--ink-dim);\n max-width: 60ch;\n text-wrap: pretty;\n}\n\n.accent {\n color: var(--accent);\n}\n.mono {\n font-family: var(--mono);\n}\n\n/* ============================================================\n LAYOUT\n ============================================================ */\n\n.wrap,\n.shell {\n width: min(var(--shell-w), calc(100vw - var(--viewport-gutter)));\n margin-inline: auto;\n}\n.sl-site-content,\n.sl-site-shell {\n width: min(100%, var(--shell-w));\n max-width: calc(100vw - var(--viewport-gutter));\n margin-inline: auto;\n}\n.sl-site-body {\n background: var(--body-surface);\n}\nsection {\n padding: clamp(3.5rem, 8vw, 7.5rem) 0;\n position: relative;\n border-top: 1px solid color-mix(in srgb, var(--ink) 22%, transparent);\n}\n.section-head {\n display: grid;\n grid-template-columns: minmax(0, 5fr) minmax(280px, 4fr);\n grid-template-rows: auto auto;\n column-gap: var(--grid-gutter);\n max-width: none;\n margin-bottom: clamp(2rem, 4vw, 4rem);\n}\n.section-head .eyebrow {\n grid-column: 1;\n grid-row: 1;\n align-self: start;\n margin-bottom: 0.8rem;\n}\n.section-head .section-title {\n grid-column: 1;\n grid-row: 2;\n}\n.section-head .lead {\n grid-column: 2;\n grid-row: 1 / span 2;\n align-self: end;\n margin: 0;\n}\n@media (max-width: 720px) {\n .section-head {\n grid-template-columns: 1fr;\n }\n .section-head .eyebrow,\n .section-head .section-title,\n .section-head .lead {\n grid-column: 1;\n grid-row: auto;\n }\n .section-head .lead { margin-top: 1rem; }\n}\n\n.reveal {\n opacity: 0;\n transform: translateY(26px);\n transition:\n opacity 0.7s cubic-bezier(0.2, 0.7, 0.2, 1),\n transform 0.7s cubic-bezier(0.2, 0.7, 0.2, 1);\n}\n.reveal.in {\n opacity: 1;\n transform: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .reveal {\n opacity: 1;\n transform: none;\n transition: none;\n }\n html {\n scroll-behavior: auto;\n }\n}\n\n@media print {\n .reveal {\n opacity: 1 !important;\n transform: none !important;\n }\n}\n", + "content": "/* ============================================================\n STRUCTURED LIQUIDITY — Neobrutalist structure. Liquid motion.\n Scientific modern clarity. This stylesheet IS\n the specimen.\n ============================================================ */\n\n:root {\n /* ---- tunable knobs (driven by Tweaks) ---- */\n --accent: #a388ee; /* neobrutalism.dev purple */\n --accent-ink: #000000; /* text that sits on accent */\n --glass-blur: 14px;\n --glass-tint: 255 255 255; /* rgb channels for glass fill */\n --glass-alpha: 0.72;\n --border-w: 2px;\n --hard-x: 5px;\n --hard-y: 5px;\n --radius: 0px;\n --shell-w: 1280px;\n --content-w: var(--shell-w);\n --viewport-gutter: 4vw;\n --body-surface: var(--bg);\n --floating-ui-clearance: 96px;\n\n /* ---- Scientific Modern Clarity, informed by Swiss modernism and the International Typographic Style ---- */\n --text-detail: 0.72rem;\n --text-body: 0.95rem;\n --text-lead: clamp(1rem, 1.3vw, 1.18rem);\n --text-section: 1.25rem;\n --text-title: 1.75rem;\n --text-display-xl: clamp(4.2rem, 10.5vw, 9.5rem);\n --text-display-lg: clamp(2.6rem, 5.5vw, 5.25rem);\n --weight-regular: 500;\n --weight-medium: 600;\n --weight-bold: 700;\n --weight-strong: 800;\n --measure-reading: 62ch;\n --measure-caption: 48ch;\n --grid-gutter: clamp(1rem, 2.5vw, 2.5rem);\n --section-space: clamp(4rem, 9vw, 9rem);\n --clarity-gap: clamp(1.25rem, 3vw, 3rem);\n --rule-hairline: 1px;\n --diagram-line: color-mix(in srgb, var(--ink) 72%, transparent);\n --diagram-muted: color-mix(in srgb, var(--ink) 42%, transparent);\n\n /* ---- light-first paper palette ---- */\n --bg: #f2f0ea;\n --bg-2: #ffffff;\n --ink: #111111; /* foreground */\n --ink-dim: #5d5d59;\n --edge: 0 0 0; /* neobrutalism black border (rgb) */\n --hard-shadow: #000000;\n --neg: #242424; /* negative / destructive surface (gray, never red) */\n --neg-ink: #ffffff;\n\n --display: \"Inter\", \"Helvetica Neue\", Arial, system-ui, sans-serif;\n --body: \"Inter\", \"Helvetica Neue\", Arial, system-ui, sans-serif;\n --mono: \"Space Mono\", \"SFMono-Regular\", ui-monospace, monospace;\n}\n\n/* tighter rhythm — denser, less airy */\n\n[data-mode=\"light\"] {\n --bg: #f2f0ea;\n --bg-2: #ffffff;\n --ink: #111111;\n --ink-dim: #5d5d59;\n --edge: 0 0 0;\n --hard-shadow: #000000;\n --neg: #16171c; /* near-black negative surface in light mode */\n --neg-ink: #ffffff;\n --glass-tint: 255 255 255;\n --glass-alpha: 0.72;\n}\n\n[data-mode=\"dark\"] {\n /* Dark mode is color-responsive: the active accent tints the atmospheric\n field while tactile components remain charcoal. There is no black mode. */\n --bg: color-mix(in srgb, var(--accent) 18%, #100b16);\n --bg-2: color-mix(in srgb, var(--accent) 27%, #15101c);\n --ink: #f6f2f8;\n --ink-dim: #b9b0bf;\n --edge: 0 0 0;\n --hard-shadow: #000000;\n --neg: #343434;\n --neg-ink: #ffffff;\n --glass-tint: 255 255 255;\n --glass-alpha: 0.08;\n}\n\n* {\n box-sizing: border-box;\n}\n\nhtml {\n scroll-behavior: auto;\n background: var(--bg);\n}\n\nbody {\n margin: 0;\n position: relative;\n background: transparent;\n color: var(--ink);\n font-family: var(--display);\n font-weight: 440;\n line-height: 1.45;\n -webkit-font-smoothing: antialiased;\n overflow-x: hidden;\n}\n\n::selection {\n background: var(--accent);\n color: var(--accent-ink);\n}\n\n/* ============================================================\n BACKDROP — flat paper plus a measured technical grid. Liquid\n belongs to interaction and continuity, not decorative scenery.\n ============================================================ */\n\n.field {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n height: 100%;\n z-index: -2;\n overflow: hidden;\n pointer-events: none;\n background: var(--bg);\n}\n[data-mode=\"light\"] .field {\n opacity: 1;\n}\n[data-mode=\"dark\"] .field {\n opacity: 1;\n}\n\n.field .grid {\n position: absolute;\n inset: 0;\n background-image:\n linear-gradient(rgba(var(--edge), 0.045) 1px, transparent 1px),\n linear-gradient(90deg, rgba(var(--edge), 0.045) 1px, transparent 1px);\n background-size: 64px 64px;\n opacity: 0.7;\n}\n\n.grain {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n height: 100%;\n z-index: -1;\n pointer-events: none;\n display: none;\n}\n\n/* ============================================================\n STRUCTURAL SURFACE — flat by default. Add blur only to transient\n navigation, menus, overlays, and other moving state boundaries.\n ============================================================ */\n\n.glass {\n position: relative;\n background: var(--bg-2);\n border: var(--border-w) solid rgb(var(--edge));\n border-radius: var(--radius);\n box-shadow: none;\n}\n/* moving sheen + cursor specular */\n.glass::before,\n.glass::after {\n content: none;\n}\n\n.flat {\n /* brutalist flat (no glass) — for loud accent blocks */\n border: var(--border-w) solid rgb(var(--edge));\n border-radius: var(--radius);\n box-shadow: var(--hard-x) var(--hard-y) 0 0 var(--hard-shadow);\n}\n\n/* ============================================================\n TYPE\n ============================================================ */\n\n.eyebrow {\n font-family: var(--mono);\n font-size: var(--text-detail);\n letter-spacing: 0.22em;\n text-transform: uppercase;\n color: var(--ink-dim);\n display: inline-flex;\n align-items: center;\n gap: 0.6em;\n}\n.eyebrow::before {\n content: \"\";\n width: 9px;\n height: 9px;\n background: var(--accent);\n display: inline-block;\n}\n\nh1,\nh2,\nh3 {\n font-weight: 800;\n line-height: 0.98;\n letter-spacing: -0.02em;\n margin: 0;\n text-wrap: balance;\n}\n\n.section-title {\n font-size: clamp(2rem, 4.2vw, 4.25rem);\n line-height: 0.93;\n letter-spacing: -0.045em;\n margin: 0.25em 0 0.3em;\n}\n.lead {\n font-size: var(--text-lead);\n color: var(--ink-dim);\n max-width: 60ch;\n text-wrap: pretty;\n}\n\n.accent {\n color: var(--accent);\n}\n.mono {\n font-family: var(--mono);\n}\n\n/* ============================================================\n LAYOUT\n ============================================================ */\n\n.wrap,\n.shell {\n width: min(var(--shell-w), calc(100vw - var(--viewport-gutter)));\n margin-inline: auto;\n}\n.sl-site-content,\n.sl-site-shell {\n width: min(100%, var(--shell-w));\n max-width: calc(100vw - var(--viewport-gutter));\n margin-inline: auto;\n}\n.sl-site-body {\n background: var(--body-surface);\n}\nsection {\n padding: clamp(3.5rem, 8vw, 7.5rem) 0;\n position: relative;\n border-top: 1px solid color-mix(in srgb, var(--ink) 22%, transparent);\n}\n.section-head {\n display: grid;\n grid-template-columns: minmax(0, 5fr) minmax(280px, 4fr);\n grid-template-rows: auto auto;\n column-gap: var(--grid-gutter);\n max-width: none;\n margin-bottom: clamp(2rem, 4vw, 4rem);\n}\n.section-head .eyebrow {\n grid-column: 1;\n grid-row: 1;\n align-self: start;\n margin-bottom: 0.8rem;\n}\n.section-head .section-title {\n grid-column: 1;\n grid-row: 2;\n}\n.section-head .lead {\n grid-column: 2;\n grid-row: 1 / span 2;\n align-self: end;\n margin: 0;\n}\n@media (max-width: 720px) {\n .section-head {\n grid-template-columns: 1fr;\n }\n .section-head .eyebrow,\n .section-head .section-title,\n .section-head .lead {\n grid-column: 1;\n grid-row: auto;\n }\n .section-head .lead { margin-top: 1rem; }\n}\n\n.reveal {\n opacity: 0;\n transform: translateY(26px);\n transition:\n opacity 0.7s cubic-bezier(0.2, 0.7, 0.2, 1),\n transform 0.7s cubic-bezier(0.2, 0.7, 0.2, 1);\n}\n.reveal.in {\n opacity: 1;\n transform: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .reveal {\n opacity: 1;\n transform: none;\n transition: none;\n }\n html {\n scroll-behavior: auto;\n }\n}\n\n@media print {\n .reveal {\n opacity: 1 !important;\n transform: none !important;\n }\n}\n", "type": "registry:file", "target": "styles/structured-liquidity.css" }, diff --git a/artifacts/structured-liquidity/public/structured-liquidity.css b/artifacts/structured-liquidity/public/structured-liquidity.css index 08bce7e..dbe745a 100644 --- a/artifacts/structured-liquidity/public/structured-liquidity.css +++ b/artifacts/structured-liquidity/public/structured-liquidity.css @@ -22,8 +22,17 @@ --floating-ui-clearance: 96px; /* ---- Scientific Modern Clarity, informed by Swiss modernism and the International Typographic Style ---- */ + --text-detail: 0.72rem; + --text-body: 0.95rem; + --text-lead: clamp(1rem, 1.3vw, 1.18rem); + --text-section: 1.25rem; + --text-title: 1.75rem; --text-display-xl: clamp(4.2rem, 10.5vw, 9.5rem); --text-display-lg: clamp(2.6rem, 5.5vw, 5.25rem); + --weight-regular: 500; + --weight-medium: 600; + --weight-bold: 700; + --weight-strong: 800; --measure-reading: 62ch; --measure-caption: 48ch; --grid-gutter: clamp(1rem, 2.5vw, 2.5rem); @@ -179,7 +188,7 @@ body { .eyebrow { font-family: var(--mono); - font-size: 0.72rem; + font-size: var(--text-detail); letter-spacing: 0.22em; text-transform: uppercase; color: var(--ink-dim); @@ -212,7 +221,7 @@ h3 { margin: 0.25em 0 0.3em; } .lead { - font-size: clamp(1rem, 1.3vw, 1.18rem); + font-size: var(--text-lead); color: var(--ink-dim); max-width: 60ch; text-wrap: pretty; diff --git a/artifacts/structured-liquidity/scripts/build-registry.mjs b/artifacts/structured-liquidity/scripts/build-registry.mjs index 17ab190..652b8f9 100644 --- a/artifacts/structured-liquidity/scripts/build-registry.mjs +++ b/artifacts/structured-liquidity/scripts/build-registry.mjs @@ -235,8 +235,17 @@ const cssVars = { display: '"Inter","Helvetica Neue",Arial,system-ui,sans-serif', body: '"Inter","Helvetica Neue",Arial,system-ui,sans-serif', mono: '"Space Mono","SFMono-Regular",ui-monospace,monospace', + "text-detail": "0.72rem", + "text-body": "0.95rem", + "text-lead": "clamp(1rem, 1.3vw, 1.18rem)", + "text-section": "1.25rem", + "text-title": "1.75rem", "text-display-xl": "clamp(4.2rem, 10.5vw, 9.5rem)", "text-display-lg": "clamp(2.6rem, 5.5vw, 5.25rem)", + "weight-regular": "500", + "weight-medium": "600", + "weight-bold": "700", + "weight-strong": "800", "measure-reading": "62ch", "measure-caption": "48ch", "grid-gutter": "clamp(1rem, 2.5vw, 2.5rem)", diff --git a/artifacts/structured-liquidity/scripts/check-visual-vocabulary.mjs b/artifacts/structured-liquidity/scripts/check-visual-vocabulary.mjs new file mode 100644 index 0000000..eb30981 --- /dev/null +++ b/artifacts/structured-liquidity/scripts/check-visual-vocabulary.mjs @@ -0,0 +1,171 @@ +import assert from "node:assert/strict"; +import { readFile, readdir, writeFile } from "node:fs/promises"; +import { extname, relative, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +const packageRoot = resolve(fileURLToPath(new URL("../", import.meta.url))); +const configPath = resolve(packageRoot, "visual-vocabulary.json"); +const config = JSON.parse(await readFile(configPath, "utf8")); +const baselinePath = resolve(packageRoot, config.baseline); + +const normalize = (value) => + value + .replace(/\s+/g, " ") + .trim() + .replace(/\s*!important$/, ""); +const escapePattern = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + +async function walk(path) { + const entries = await readdir(path, { withFileTypes: true }); + const files = []; + for (const entry of entries) { + const child = resolve(path, entry.name); + if (entry.isDirectory()) files.push(...(await walk(child))); + else if (config.extensions.includes(extname(entry.name))) files.push(child); + } + return files; +} + +const tokenSource = await readFile( + resolve(packageRoot, config.tokenSource), + "utf8", +); +for (const [token, value] of Object.entries({ + ...config.typeSizes, + ...config.fontWeights, +})) { + const declaration = new RegExp( + `${escapePattern(token)}\\s*:\\s*${escapePattern(value)}\\s*;`, + "i", + ); + assert.match( + tokenSource, + declaration, + `Missing visual-vocabulary token ${token}: ${value}`, + ); +} +for (const [token, family] of Object.entries(config.typefaceRoles)) { + const declaration = new RegExp( + `${escapePattern(token)}\\s*:\\s*([^;]+);`, + "i", + ).exec(tokenSource); + assert(declaration, `Missing visual-vocabulary typeface role ${token}`); + assert.match( + declaration[1], + new RegExp(escapePattern(family), "i"), + `${token} must resolve to ${family}`, + ); +} +for (const token of config.colorRoles) { + assert.match( + tokenSource, + new RegExp(`${escapePattern(token)}\\s*:`, "i"), + `Missing semantic color role ${token}`, + ); +} +assert( + new Set( + Object.values(config.typefaceRoles).map((family) => family.toLowerCase()), + ).size <= config.maxTypefaceFamilies, + `Visual vocabulary exceeds ${config.maxTypefaceFamilies} actual typefaces`, +); + +const declarations = + /(^|[;{])\s*(--[\w-]+|font|font-family|font-size|font-weight|color|background(?:-color)?|border(?:-(?:top|right|bottom|left))?-color|outline-color|fill|stroke)\s*:\s*([^;{}]+)/gim; +const literalColor = + /#[0-9a-f]{3,8}\b|(?:rgb|hsl)a?\([^)]*\)|(?:oklch|lab|lch)\([^)]*\)|\b(?:black|white|red|orange|yellow|green|blue|purple|pink|gray|grey)\b/gi; +const allowedFamilyVars = Object.keys(config.typefaceRoles) + .map((token) => `var(${token}`) + .concat(config.additionalTypefaceVariables ?? []); +const findings = {}; +const add = (file, kind, value) => { + const key = `${file}::${kind}::${normalize(value)}`; + findings[key] = (findings[key] ?? 0) + 1; +}; + +for (const root of config.scanRoots) { + for (const file of await walk(resolve(packageRoot, root))) { + const source = (await readFile(file, "utf8")).replace( + /\/\*[\s\S]*?\*\//g, + "", + ); + const name = relative(packageRoot, file); + for (const match of source.matchAll(declarations)) { + const property = match[2].toLowerCase(); + const value = normalize(match[3]); + if (property.startsWith("--text-") && !(property in config.typeSizes)) + add(name, "unapproved-size-token", value); + if (property.startsWith("--weight-") && !(property in config.fontWeights)) + add(name, "unapproved-weight-token", value); + if ( + property.startsWith("--") && + /(font|display|body|mono)/.test(property) && + !(property in config.typefaceRoles) + ) + add(name, "additional-typeface-token", value); + if ( + property === "font-family" && + !allowedFamilyVars.some((role) => value.includes(role)) && + !/^(inherit|unset)$/.test(value) + ) + add(name, "font-family", value); + if ( + property === "font-size" && + !/var\(--text-[\w-]+\)/.test(value) && + !/^(0|inherit|unset)$/.test(value) + ) + add(name, "font-size", value); + if ( + property === "font-weight" && + !/var\(--weight-[\w-]+\)/.test(value) && + !/^(inherit|unset)$/.test(value) + ) + add(name, "font-weight", value); + if ( + property === "font" && + (!/var\(--text-[\w-]+\)/.test(value) || + !allowedFamilyVars.some((role) => value.includes(role))) + ) + add(name, "font-shorthand", value); + for (const color of value.match(literalColor) ?? []) + add(name, "literal-color", color.toLowerCase()); + } + } +} + +const sorted = Object.fromEntries( + Object.entries(findings).sort(([a], [b]) => a.localeCompare(b)), +); +if (process.argv.includes("--write-baseline")) { + await writeFile( + baselinePath, + `${JSON.stringify({ version: 1, findings: sorted }, null, 2)}\n`, + ); + console.log( + `Visual-vocabulary baseline recorded with ${Object.values(sorted).reduce((sum, count) => sum + count, 0)} legacy declarations.`, + ); + process.exit(0); +} + +const baseline = JSON.parse(await readFile(baselinePath, "utf8")); +const allKeys = new Set([ + ...Object.keys(baseline.findings), + ...Object.keys(sorted), +]); +const changes = [...allKeys].filter( + (key) => (baseline.findings[key] ?? 0) !== (sorted[key] ?? 0), +); +if (changes.length) { + console.error( + "Visual-vocabulary baseline changed. New raw declarations are blocked; intentional cleanup must refresh the baseline.", + ); + for (const key of changes.slice(0, 30)) + console.error( + ` ${baseline.findings[key] ?? 0} -> ${sorted[key] ?? 0} ${key}`, + ); + process.exit(1); +} + +console.log( + `Visual vocabulary passed: ${config.maxTypefaceFamilies} base typefaces, ${Object.keys(config.typeSizes).length} size tiers, ${Object.keys(config.fontWeights).length} weights, ${config.colorRoles.length} color roles; legacy debt did not grow.`, +); diff --git a/artifacts/structured-liquidity/src/styles/10-tokens-base.css b/artifacts/structured-liquidity/src/styles/10-tokens-base.css index e55318a..95e4fc0 100644 --- a/artifacts/structured-liquidity/src/styles/10-tokens-base.css +++ b/artifacts/structured-liquidity/src/styles/10-tokens-base.css @@ -22,8 +22,17 @@ --floating-ui-clearance: 96px; /* ---- Scientific Modern Clarity, informed by Swiss modernism and the International Typographic Style ---- */ + --text-detail: 0.72rem; + --text-body: 0.95rem; + --text-lead: clamp(1rem, 1.3vw, 1.18rem); + --text-section: 1.25rem; + --text-title: 1.75rem; --text-display-xl: clamp(4.2rem, 10.5vw, 9.5rem); --text-display-lg: clamp(2.6rem, 5.5vw, 5.25rem); + --weight-regular: 500; + --weight-medium: 600; + --weight-bold: 700; + --weight-strong: 800; --measure-reading: 62ch; --measure-caption: 48ch; --grid-gutter: clamp(1rem, 2.5vw, 2.5rem); @@ -182,7 +191,7 @@ body { .eyebrow { font-family: var(--mono); - font-size: 0.72rem; + font-size: var(--text-detail); letter-spacing: 0.22em; text-transform: uppercase; color: var(--ink-dim); @@ -215,7 +224,7 @@ h3 { margin: 0.25em 0 0.3em; } .lead { - font-size: clamp(1rem, 1.3vw, 1.18rem); + font-size: var(--text-lead); color: var(--ink-dim); max-width: 60ch; text-wrap: pretty; diff --git a/artifacts/structured-liquidity/visual-vocabulary-baseline.json b/artifacts/structured-liquidity/visual-vocabulary-baseline.json new file mode 100644 index 0000000..d8583fb --- /dev/null +++ b/artifacts/structured-liquidity/visual-vocabulary-baseline.json @@ -0,0 +1,222 @@ +{ + "version": 1, + "findings": { + "src/components/site/Adopt.tsx::literal-color::#000000": 2, + "src/components/site/Adopt.tsx::literal-color::#111111": 1, + "src/components/site/Adopt.tsx::literal-color::#242424": 1, + "src/components/site/Adopt.tsx::literal-color::#5d5d59": 1, + "src/components/site/Adopt.tsx::literal-color::#a388ee": 1, + "src/components/site/Adopt.tsx::literal-color::#f2f0ea": 1, + "src/components/site/Adopt.tsx::literal-color::#ffffff": 2, + "src/components/site/kit/KitContentData.tsx::literal-color::rgba(var(--glass-tint)": 1, + "src/components/site/kit/KitDataDisplay.tsx::literal-color::#111111": 2, + "src/components/site/kit/KitDataDisplay.tsx::literal-color::#8a8a93": 1, + "src/components/site/kit/KitDataDisplay.tsx::literal-color::#ffffff": 3, + "src/components/site/kit/KitDataDisplay.tsx::literal-color::rgba(var(--glass-tint)": 1, + "src/index.css::additional-typeface-token::var(--body)": 1, + "src/index.css::additional-typeface-token::var(--display)": 1, + "src/index.css::additional-typeface-token::var(--mono)": 1, + "src/index.css::font-size::0.6875rem": 1, + "src/index.css::font-size::0.72rem": 1, + "src/index.css::font-size::0.7rem": 1, + "src/index.css::font-size::1rem": 1, + "src/index.css::font-size::clamp(3.1rem, 6vw, 6rem)": 1, + "src/index.css::font-size::clamp(3rem, 16vw, 4.6rem)": 1, + "src/index.css::font-weight::700": 4, + "src/index.css::font-weight::760": 2, + "src/index.css::literal-color::#5f3fc4": 1, + "src/index.css::literal-color::rgba(var(--edge)": 1, + "src/styles/10-tokens-base.css::additional-typeface-token::0.95rem": 1, + "src/styles/10-tokens-base.css::additional-typeface-token::clamp(2.6rem, 5.5vw, 5.25rem)": 1, + "src/styles/10-tokens-base.css::additional-typeface-token::clamp(4.2rem, 10.5vw, 9.5rem)": 1, + "src/styles/10-tokens-base.css::additional-typeface-token::var(--bg)": 1, + "src/styles/10-tokens-base.css::font-size::clamp(2rem, 4.2vw, 4.25rem)": 1, + "src/styles/10-tokens-base.css::font-weight::440": 1, + "src/styles/10-tokens-base.css::font-weight::800": 1, + "src/styles/10-tokens-base.css::literal-color::#000000": 4, + "src/styles/10-tokens-base.css::literal-color::#100b16": 1, + "src/styles/10-tokens-base.css::literal-color::#111111": 2, + "src/styles/10-tokens-base.css::literal-color::#15101c": 1, + "src/styles/10-tokens-base.css::literal-color::#16171c": 1, + "src/styles/10-tokens-base.css::literal-color::#242424": 1, + "src/styles/10-tokens-base.css::literal-color::#343434": 1, + "src/styles/10-tokens-base.css::literal-color::#5d5d59": 2, + "src/styles/10-tokens-base.css::literal-color::#a388ee": 1, + "src/styles/10-tokens-base.css::literal-color::#b9b0bf": 1, + "src/styles/10-tokens-base.css::literal-color::#f2f0ea": 2, + "src/styles/10-tokens-base.css::literal-color::#f6f2f8": 1, + "src/styles/10-tokens-base.css::literal-color::#ffffff": 5, + "src/styles/10-tokens-base.css::literal-color::rgba(17, 17, 17, 0.065)": 2, + "src/styles/10-tokens-base.css::literal-color::rgba(246, 242, 248, 0.07)": 1, + "src/styles/20-components.css::font-size::0.64rem": 1, + "src/styles/20-components.css::font-size::0.6rem": 1, + "src/styles/20-components.css::font-size::0.72rem": 2, + "src/styles/20-components.css::font-size::0.76rem": 3, + "src/styles/20-components.css::font-size::0.78rem": 3, + "src/styles/20-components.css::font-size::0.7rem": 3, + "src/styles/20-components.css::font-size::0.82rem": 1, + "src/styles/20-components.css::font-size::0.85rem": 1, + "src/styles/20-components.css::font-size::0.88rem": 1, + "src/styles/20-components.css::font-size::0.8rem": 1, + "src/styles/20-components.css::font-size::0.95rem": 1, + "src/styles/20-components.css::font-size::0.98rem": 1, + "src/styles/20-components.css::font-size::0.9rem": 1, + "src/styles/20-components.css::font-size::1.15rem": 1, + "src/styles/20-components.css::font-size::1.18rem": 1, + "src/styles/20-components.css::font-size::1.25rem": 1, + "src/styles/20-components.css::font-size::clamp(1.8rem, 3.5vw, 3rem)": 1, + "src/styles/20-components.css::font-weight::700": 2, + "src/styles/20-components.css::font-weight::800": 2, + "src/styles/20-components.css::font-weight::900": 1, + "src/styles/20-components.css::literal-color::rgb(var(--edge)": 5, + "src/styles/20-components.css::literal-color::rgba(var(--glass-tint)": 3, + "src/styles/20-components.css::literal-color::white": 5, + "src/styles/30-kit.css::font-shorthand::0.62rem/1.55 var(--mono)": 1, + "src/styles/30-kit.css::font-shorthand::0.78rem/1.3 var(--body)": 1, + "src/styles/30-kit.css::font-shorthand::0.84rem var(--body)": 1, + "src/styles/30-kit.css::font-shorthand::400 0.62rem/1.35 var(--mono)": 1, + "src/styles/30-kit.css::font-shorthand::400 0.64rem/1.2 var(--mono)": 1, + "src/styles/30-kit.css::font-shorthand::650 0.68rem/1 var(--body)": 1, + "src/styles/30-kit.css::font-shorthand::650 0.76rem/1 var(--body)": 1, + "src/styles/30-kit.css::font-shorthand::650 0.76rem/1.35 var(--body)": 1, + "src/styles/30-kit.css::font-shorthand::650 0.78rem/1 var(--body)": 1, + "src/styles/30-kit.css::font-shorthand::700 0.82rem/1.25 var(--display)": 1, + "src/styles/30-kit.css::font-shorthand::720 0.94rem/1.4 var(--body)": 1, + "src/styles/30-kit.css::font-shorthand::750 1rem/1 var(--display)": 1, + "src/styles/30-kit.css::font-shorthand::750 clamp(1.5rem, 4vw, 2.4rem)/1 var(--display)": 1, + "src/styles/30-kit.css::font-shorthand::800 1.05rem/1.15 var(--display)": 1, + "src/styles/30-kit.css::font-shorthand::800 clamp(1.8rem, 4vw, 2.55rem)/1 var(--display)": 1, + "src/styles/30-kit.css::font-size::0.56rem": 1, + "src/styles/30-kit.css::font-size::0.58rem": 5, + "src/styles/30-kit.css::font-size::0.62rem": 5, + "src/styles/30-kit.css::font-size::0.64rem": 1, + "src/styles/30-kit.css::font-size::0.66rem": 2, + "src/styles/30-kit.css::font-size::0.68rem": 6, + "src/styles/30-kit.css::font-size::0.6rem": 3, + "src/styles/30-kit.css::font-size::0.72rem": 5, + "src/styles/30-kit.css::font-size::0.74rem": 1, + "src/styles/30-kit.css::font-size::0.76rem": 4, + "src/styles/30-kit.css::font-size::0.78rem": 9, + "src/styles/30-kit.css::font-size::0.7rem": 3, + "src/styles/30-kit.css::font-size::0.82rem": 8, + "src/styles/30-kit.css::font-size::0.85rem": 4, + "src/styles/30-kit.css::font-size::0.86rem": 3, + "src/styles/30-kit.css::font-size::0.88rem": 1, + "src/styles/30-kit.css::font-size::0.8rem": 5, + "src/styles/30-kit.css::font-size::0.92rem": 1, + "src/styles/30-kit.css::font-size::0.95rem": 1, + "src/styles/30-kit.css::font-size::0.9rem": 4, + "src/styles/30-kit.css::font-size::1.1rem": 1, + "src/styles/30-kit.css::font-size::1.2rem": 1, + "src/styles/30-kit.css::font-size::1.3rem": 1, + "src/styles/30-kit.css::font-size::1rem": 4, + "src/styles/30-kit.css::font-size::clamp(1.35rem, 3vw, 2rem)": 1, + "src/styles/30-kit.css::font-weight::400": 1, + "src/styles/30-kit.css::font-weight::650": 1, + "src/styles/30-kit.css::font-weight::700": 12, + "src/styles/30-kit.css::font-weight::800": 4, + "src/styles/30-kit.css::font-weight::900": 1, + "src/styles/30-kit.css::literal-color::#000000": 1, + "src/styles/30-kit.css::literal-color::rgb(var(--edge)": 6, + "src/styles/30-kit.css::literal-color::rgb(var(--glass-tint)": 10, + "src/styles/30-kit.css::literal-color::rgba(0,0,0,0.55)": 1, + "src/styles/30-kit.css::literal-color::rgba(var(--edge)": 3, + "src/styles/30-kit.css::literal-color::rgba(var(--glass-tint)": 25, + "src/styles/40-inline.css::font-shorthand::12px/1.4 var(--mono)": 1, + "src/styles/40-inline.css::font-shorthand::700 0.74rem/1 var(--body)": 1, + "src/styles/40-inline.css::font-shorthand::700 0.82rem/1.2 var(--body)": 1, + "src/styles/40-inline.css::font-size::.56rem": 1, + "src/styles/40-inline.css::font-size::.62rem": 3, + "src/styles/40-inline.css::font-size::.65rem": 1, + "src/styles/40-inline.css::font-size::.68rem": 3, + "src/styles/40-inline.css::font-size::.72rem": 2, + "src/styles/40-inline.css::font-size::.95rem": 1, + "src/styles/40-inline.css::font-size::.9rem": 1, + "src/styles/40-inline.css::font-size::0.56rem": 1, + "src/styles/40-inline.css::font-size::0.58rem": 3, + "src/styles/40-inline.css::font-size::0.5rem": 1, + "src/styles/40-inline.css::font-size::0.62rem": 2, + "src/styles/40-inline.css::font-size::0.64rem": 2, + "src/styles/40-inline.css::font-size::0.66rem": 2, + "src/styles/40-inline.css::font-size::0.68rem": 4, + "src/styles/40-inline.css::font-size::0.6rem": 8, + "src/styles/40-inline.css::font-size::0.72rem": 8, + "src/styles/40-inline.css::font-size::0.74rem": 5, + "src/styles/40-inline.css::font-size::0.76rem": 2, + "src/styles/40-inline.css::font-size::0.78rem": 7, + "src/styles/40-inline.css::font-size::0.7rem": 3, + "src/styles/40-inline.css::font-size::0.82rem": 5, + "src/styles/40-inline.css::font-size::0.84rem": 1, + "src/styles/40-inline.css::font-size::0.85rem": 3, + "src/styles/40-inline.css::font-size::0.88rem": 2, + "src/styles/40-inline.css::font-size::0.8rem": 8, + "src/styles/40-inline.css::font-size::0.92rem": 4, + "src/styles/40-inline.css::font-size::0.94rem": 1, + "src/styles/40-inline.css::font-size::0.95rem": 2, + "src/styles/40-inline.css::font-size::0.9rem": 6, + "src/styles/40-inline.css::font-size::1.02rem": 2, + "src/styles/40-inline.css::font-size::1.05rem": 2, + "src/styles/40-inline.css::font-size::1.1rem": 1, + "src/styles/40-inline.css::font-size::1.25rem": 1, + "src/styles/40-inline.css::font-size::1.35rem": 2, + "src/styles/40-inline.css::font-size::1.3rem": 1, + "src/styles/40-inline.css::font-size::1.4rem": 2, + "src/styles/40-inline.css::font-size::1.55rem": 2, + "src/styles/40-inline.css::font-size::108%": 1, + "src/styles/40-inline.css::font-size::11px": 3, + "src/styles/40-inline.css::font-size::12px": 1, + "src/styles/40-inline.css::font-size::13px": 1, + "src/styles/40-inline.css::font-size::1rem": 4, + "src/styles/40-inline.css::font-size::5.2px": 1, + "src/styles/40-inline.css::font-size::5.5px": 1, + "src/styles/40-inline.css::font-size::9.5px": 1, + "src/styles/40-inline.css::font-size::clamp(.7rem,.72vw,.78rem)": 1, + "src/styles/40-inline.css::font-size::clamp(.7rem,.75vw,.82rem)": 1, + "src/styles/40-inline.css::font-size::clamp(1.05rem, 1.35vw, 1.28rem)": 1, + "src/styles/40-inline.css::font-size::clamp(1.05rem, 5cqi, 1.25rem)": 1, + "src/styles/40-inline.css::font-size::clamp(1.35rem,2vw,2rem)": 1, + "src/styles/40-inline.css::font-size::clamp(1.45rem, 9cqi, 2rem)": 1, + "src/styles/40-inline.css::font-size::clamp(1.65rem, 2.5vw, 2.55rem)": 1, + "src/styles/40-inline.css::font-size::clamp(1.8rem,4vw,3.2rem)": 1, + "src/styles/40-inline.css::font-size::clamp(1rem, 2vw, 1.72rem)": 1, + "src/styles/40-inline.css::font-size::clamp(2.6rem, 6vw, 6.2rem)": 1, + "src/styles/40-inline.css::font-size::clamp(2rem, 11.5vw, 3.1rem)": 1, + "src/styles/40-inline.css::font-size::clamp(3.4rem, 18vw, 6rem)": 1, + "src/styles/40-inline.css::font-weight::400": 1, + "src/styles/40-inline.css::font-weight::430": 1, + "src/styles/40-inline.css::font-weight::500": 1, + "src/styles/40-inline.css::font-weight::600": 1, + "src/styles/40-inline.css::font-weight::650": 2, + "src/styles/40-inline.css::font-weight::680": 1, + "src/styles/40-inline.css::font-weight::700": 15, + "src/styles/40-inline.css::font-weight::760": 1, + "src/styles/40-inline.css::font-weight::800": 13, + "src/styles/40-inline.css::literal-color::#000": 1, + "src/styles/40-inline.css::literal-color::#202020": 1, + "src/styles/40-inline.css::literal-color::#fff": 5, + "src/styles/40-inline.css::literal-color::#ffffff": 2, + "src/styles/40-inline.css::literal-color::rgb(var(--edge)": 9, + "src/styles/40-inline.css::literal-color::rgb(var(--glass-tint)": 27, + "src/styles/40-inline.css::literal-color::rgba(223,229,242,.86)": 1, + "src/styles/40-inline.css::literal-color::rgba(223,229,242,.92)": 1, + "src/styles/40-inline.css::literal-color::rgba(255,255,255,.35)": 1, + "src/styles/40-inline.css::literal-color::rgba(255,255,255,.5)": 1, + "src/styles/40-inline.css::literal-color::rgba(255,255,255,.55)": 1, + "src/styles/40-inline.css::literal-color::rgba(39,41,51,.82)": 1, + "src/styles/40-inline.css::literal-color::rgba(39,41,51,.9)": 1, + "src/styles/40-inline.css::literal-color::rgba(var(--edge)": 1, + "src/styles/40-inline.css::literal-color::rgba(var(--glass-tint)": 36, + "src/styles/40-inline.css::literal-color::white": 2, + "src/typeset.css::additional-typeface-token::inherit": 2, + "src/typeset.css::additional-typeface-token::ui-monospace, SFMono-Regular, Menlo, monospace": 1, + "src/typeset.css::font-family::var(--typeset-font-body)": 1, + "src/typeset.css::font-family::var(--typeset-font-heading)": 1, + "src/typeset.css::font-family::var(--typeset-font-mono)": 1, + "src/typeset.css::font-size::.875em": 1, + "src/typeset.css::font-size::1.3em": 1, + "src/typeset.css::font-size::1.65em": 1, + "src/typeset.css::font-size::2.25em": 1, + "src/typeset.css::font-size::var(--typeset-size)": 1, + "src/typeset.css::font-weight::650": 2 + } +} diff --git a/artifacts/structured-liquidity/visual-vocabulary.json b/artifacts/structured-liquidity/visual-vocabulary.json new file mode 100644 index 0000000..12dc00c --- /dev/null +++ b/artifacts/structured-liquidity/visual-vocabulary.json @@ -0,0 +1,41 @@ +{ + "version": 1, + "tokenSource": "src/styles/10-tokens-base.css", + "scanRoots": ["src"], + "extensions": [".css", ".tsx"], + "baseline": "visual-vocabulary-baseline.json", + "maxTypefaceFamilies": 2, + "typefaceRoles": { + "--display": "Inter", + "--body": "Inter", + "--mono": "Space Mono" + }, + "additionalTypefaceVariables": [], + "typeSizes": { + "--text-detail": "0.72rem", + "--text-body": "0.95rem", + "--text-lead": "clamp(1rem, 1.3vw, 1.18rem)", + "--text-section": "1.25rem", + "--text-title": "1.75rem", + "--text-display-lg": "clamp(2.6rem, 5.5vw, 5.25rem)", + "--text-display-xl": "clamp(4.2rem, 10.5vw, 9.5rem)" + }, + "fontWeights": { + "--weight-regular": "500", + "--weight-medium": "600", + "--weight-bold": "700", + "--weight-strong": "800" + }, + "colorRoles": [ + "--accent", + "--accent-ink", + "--bg", + "--bg-2", + "--ink", + "--ink-dim", + "--edge", + "--hard-shadow", + "--neg", + "--neg-ink" + ] +} diff --git a/docs/design-ethos-v2.md b/docs/design-ethos-v2.md index 5ab3d94..8e8cee6 100644 --- a/docs/design-ethos-v2.md +++ b/docs/design-ethos-v2.md @@ -1,5 +1,8 @@ # Structured Liquidity design ethos +The machine-enforced typography and color limits are documented in +[Visual vocabulary](visual-vocabulary.md). + ## The operating model **Neobrutalist structure. Liquid motion. Scientific modern clarity.** @@ -21,7 +24,8 @@ Structured Liquidity is a trifecta of three orthogonal design tenets, represente ## Expression rules - Use one accent for selection, progression, or a single key relationship. Monochrome does the explanatory work. -- Establish one dominant typographic statement per view, then use a restrained body measure, exact baselines, and mono evidence labels. +- Establish one dominant typographic statement per view, then use a restrained body measure, exact baselines, and mono evidence labels. Typography is a controlled vocabulary: use no more than two actual typefaces, map them to display/body/detail roles, and consume the seven semantic size tiers rather than inventing component-specific sizes. +- Treat color as the same kind of controlled vocabulary. Components consume semantic palette tokens instead of introducing raw values. - Treat diagrams as explanatory figures: label them, number them when useful, and provide a caption or source state. - Let the grid, alignment, and hierarchy convey order before adding borders or glass. - Reserve hard shadows for tactile affordances and deliberately layered cutout surfaces; not every container needs one. diff --git a/docs/visual-vocabulary.md b/docs/visual-vocabulary.md new file mode 100644 index 0000000..b8d47cb --- /dev/null +++ b/docs/visual-vocabulary.md @@ -0,0 +1,36 @@ +# Visual vocabulary + +Scientific modern clarity depends on repetition. A generated interface should not create a new +typeface, font size, weight, or color each time it needs emphasis. + +## Contract + +- Three semantic type roles resolve to no more than two actual typefaces: display, body, and detail. +- Seven size tiers cover product UI and public Pages: detail, body, lead, section, title, display + large, and display extra-large. +- Four weights provide hierarchy: regular, medium, bold, and strong. +- Color is expressed through semantic roles such as accent, paper, ink, edge, shadow, and negative + state rather than component-local literals. + +Tenant identity changes token values, not the number of decisions exposed inside a component. +Logos, illustrations, photography, and data visualizations may have their own color requirements; +their labels and surrounding interface still use the shared vocabulary. + +## Enforcement + +The visual-vocabulary checker validates the role tokens and a checked-in baseline of existing raw +declarations. The baseline is a migration ledger: it may shrink, but new or changed raw +font-family, font-size, font-weight, or color declarations fail until explicitly reviewed. This +lets established products improve incrementally while new components begin with the stricter rule. + +Run: + +```bash +pnpm --filter @workspace/structured-liquidity run visual-vocabulary:check +``` + +After an intentional cleanup, refresh the baseline in the same change: + +```bash +pnpm --filter @workspace/structured-liquidity run visual-vocabulary:baseline +``` diff --git a/package.json b/package.json index 56869f1..ce5b46e 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "license": "MIT", "scripts": { "preinstall": "sh -c 'rm -f package-lock.json yarn.lock; case \"$npm_config_user_agent\" in pnpm/*) ;; *) echo \"Use pnpm instead\" >&2; exit 1 ;; esac'", - "build": "pnpm run typecheck && pnpm -r --if-present run build", + "build": "pnpm run typecheck && pnpm run check:visual-vocabulary && pnpm -r --if-present run build", + "check:visual-vocabulary": "pnpm --filter @workspace/structured-liquidity run visual-vocabulary:check", "railway:build": "pnpm --filter @workspace/structured-liquidity run build && pnpm --filter @workspace/api-server run build", "railway:start": "pnpm --filter @workspace/api-server run start", "typecheck:libs": "tsc --build",