diff --git a/CHANGELOG.md b/CHANGELOG.md index 01da902e..3c86c39c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), ## Unreleased ### Added +- **components:** `.sf-btn` — first shipped button component (was staged/commented out). Colour families (`--primary`/`--neutral`/`--success`/`--warning`/`--info`/`--danger`), style treatments (`--secondary`/`--ghost`/`--outline`), a size scale (`--xs`/`--s`/`--l`/`--xl`), width (`--block`/`--block-cq`), and `:disabled`/`.is-loading` states. The new `--outline` modifier composes orthogonally with any colour family. Ships in the `*-components` and `full` bundles. (#493, #486) +- **components:** `.sf-card` — first shipped card component (was staged/commented out). `__header`/`__body`/`__footer`/`__media`/`__avatar`/`__title` subcomponents with concentric-radius math, plus `--bordered`/`--elevated`/`--interactive` modifiers. (#494) +- **tokens:** activated the button knobs `--sf-btn-gap`/`-font-size`/`-font-weight`/`-min-height`/`-border-width` and the full `--sf-card-*` token block (padding, gap, radius, radius-outer, bg, border, shadow, `--shadow--elevated`/`--shadow--hover`, media-ratio/-radius, heading-size, btn-font-size). +- **docs:** documented `.sf-btn`/`.sf-card` in `docs/components.md` with HTML examples, plus a copy-paste gradient-outline button recipe. - **macros:** `.sf-corner-scoop` — mask-based concave-corner cut, with `--top-left`/`--top-right`/`--bottom-left`/`--bottom-right` placement modifiers and `--sf-corner-scoop-size`/`--sf-corner-scoop-at` knobs. - **macros:** `.sf-overlap` / `.sf-overlap-host` — negative-margin overlap recipe (`--down`/`--start`/`--end` modifiers) driven by `--sf-overlap-pull`, with a host class that reserves matching space. diff --git a/badges/css-custom-data.json b/badges/css-custom-data.json index b72ff29a..6bbb7c2b 100644 --- a/badges/css-custom-data.json +++ b/badges/css-custom-data.json @@ -276,19 +276,114 @@ "description": "Width of full-bleed breakout elements inside a content grid.\n\nDefault: `var(--sf-container-wide)`", "syntax": "*" }, + { + "name": "--sf-btn-border-width", + "description": "Border thickness for .sf-btn (used by all fill, outline, and secondary treatments).\n\nDefault: `var(--sf-border-width-1)`", + "syntax": "*" + }, + { + "name": "--sf-btn-font-size", + "description": "Label font-size for .sf-btn. The size modifiers (--xs/--s/--l/--xl) override this per button.\n\nDefault: `var(--sf-text-m)`", + "syntax": "*" + }, + { + "name": "--sf-btn-font-weight", + "description": "Label font-weight for .sf-btn. Defaults to the interactive weight.\n\nDefault: `var(--sf-font-weight-interactive)`", + "syntax": "*" + }, + { + "name": "--sf-btn-gap", + "description": "Gap between an icon and the label inside .sf-btn.\n\nDefault: `var(--sf-space-2xs)`", + "syntax": "*" + }, + { + "name": "--sf-btn-min-height", + "description": "Minimum target height for .sf-btn. Defaults to --sf-touch-target so buttons meet the WCAG 2.2 target-size minimum.\n\nDefault: `var(--sf-touch-target)`", + "syntax": "*" + }, { "name": "--sf-btn-padding-block", - "description": "Override to give buttons a distinct visual language from form fields without touching global radius/spacing tokens.\n\nDefault: `var(--sf-space-xs)`", + "description": "Vertical (block) padding inside .sf-btn.\n\nDefault: `var(--sf-space-xs)`", "syntax": "*" }, { "name": "--sf-btn-padding-inline", - "description": "Override to give buttons a distinct visual language from form fields without touching global radius/spacing tokens.\n\nDefault: `var(--sf-space-m)`", + "description": "Horizontal (inline) padding inside .sf-btn.\n\nDefault: `var(--sf-space-m)`", "syntax": "*" }, { "name": "--sf-btn-radius", - "description": "Override to give buttons a distinct visual language from form fields without touching global radius/spacing tokens.\n\nDefault: `var(--sf-radius-m)`", + "description": "Corner radius for .sf-btn. Defaults to --sf-radius-m; gives buttons a distinct radius from form fields without touching global tokens.\n\nDefault: `var(--sf-radius-m)`", + "syntax": "*" + }, + { + "name": "--sf-card-bg", + "description": "Background surface for .sf-card. Defaults to --sf-color-surface.\n\nDefault: `var(--sf-color-surface)`", + "syntax": "*" + }, + { + "name": "--sf-card-border-color", + "description": "Border colour for .sf-card. Defaults to --sf-color-border.\n\nDefault: `var(--sf-color-border)`", + "syntax": "*" + }, + { + "name": "--sf-card-border-width", + "description": "Border thickness for .sf-card.\n\nDefault: `var(--sf-border-width-1)`", + "syntax": "*" + }, + { + "name": "--sf-card-btn-font-size", + "description": "Font-size a .sf-btn shrinks to when nested inside a .sf-card. Defaults to --sf-text-s.\n\nDefault: `var(--sf-text-s)`", + "syntax": "*" + }, + { + "name": "--sf-card-gap", + "description": "Divider spacing for .sf-card__header / __footer (padding + margin around the rule).\n\nDefault: `var(--sf-space-m)`", + "syntax": "*" + }, + { + "name": "--sf-card-heading-size", + "description": "Font-size for .sf-card__title. Defaults to --sf-text-xl.\n\nDefault: `var(--sf-text-xl)`", + "syntax": "*" + }, + { + "name": "--sf-card-media-radius", + "description": "Corner radius for .sf-card__media. Mirrors the card's inner radius by default.\n\nDefault: `var(--sf-card-radius, var(--sf-radius-m))`", + "syntax": "*" + }, + { + "name": "--sf-card-media-ratio", + "description": "Aspect ratio for .sf-card__media. Defaults to --sf-ratio-video (16/9).\n\nDefault: `var(--sf-ratio-video)`", + "syntax": "*" + }, + { + "name": "--sf-card-padding", + "description": "Inner padding for .sf-card. Also feeds the concentric-radius math (outer = inner radius + padding).\n\nDefault: `var(--sf-space-l)`", + "syntax": "*" + }, + { + "name": "--sf-card-radius", + "description": "Inner radius for .sf-card children such as __media. Combined with padding to derive the outer radius.\n\nDefault: `var(--sf-radius-m)`", + "syntax": "*" + }, + { + "name": "--sf-card-radius-outer", + "description": "Outer corner radius of .sf-card. Concentric with the inner radius: calc(--sf-card-radius + --sf-card-padding).\n\nDefault: `calc(var(--sf-card-radius) + var(--sf-card-padding))`", + "syntax": "*" + }, + { + "name": "--sf-card-shadow", + "description": "Base drop shadow for .sf-card. Defaults to --sf-shadow-s.\n\nDefault: `var(--sf-shadow-s)`", + "syntax": "*" + }, + { + "name": "--sf-card-shadow--elevated", + "description": "Drop shadow applied by the .sf-card--elevated modifier (border hidden, larger shadow).\n\nDefault: `var(--sf-shadow-l)`", + "syntax": "*" + }, + { + "name": "--sf-card-shadow--hover", + "description": "Drop shadow applied on hover/focus by the .sf-card--interactive modifier.\n\nDefault: `var(--sf-shadow-l)`", "syntax": "*" }, { diff --git a/configurator/package-lock.json b/configurator/package-lock.json index dd530f7f..3519fa28 100644 --- a/configurator/package-lock.json +++ b/configurator/package-lock.json @@ -1,12 +1,12 @@ { "name": "slashed-configurator", - "version": "0.6.35", + "version": "0.7.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "slashed-configurator", - "version": "0.6.35", + "version": "0.7.0", "dependencies": { "@lucide/svelte": "^1.23.0", "fflate": "^0.8.3" diff --git a/configurator/package.json b/configurator/package.json index da12e113..2b6f9149 100644 --- a/configurator/package.json +++ b/configurator/package.json @@ -1,7 +1,7 @@ { "name": "slashed-configurator", "private": true, - "version": "0.6.35", + "version": "0.7.0", "description": "Standalone Svelte 5 + Tailwind panel to configure every SLASHED token/knob and generate override CSS. Auto-synced from the framework's generated docs/api-index.json.", "type": "module", "scripts": { diff --git a/configurator/src/components/panels/BordersPanel.svelte b/configurator/src/components/panels/BordersPanel.svelte index f480d283..5f79e64d 100644 --- a/configurator/src/components/panels/BordersPanel.svelte +++ b/configurator/src/components/panels/BordersPanel.svelte @@ -27,6 +27,8 @@ { label: "Button radius", token: "--sf-btn-radius", unit: "rem", min: 0, max: 2, step: 0.05, default: 0.5, rawDefault: "var(--sf-radius-m)", help: "--sf-btn-radius" }, { label: "Button padding block", token: "--sf-btn-padding-block", unit: "rem", min: 0, max: 1, step: 0.025, default: 0.375, rawDefault: "var(--sf-space-xs)", help: "--sf-btn-padding-block" }, { label: "Button padding inline", token: "--sf-btn-padding-inline", unit: "rem", min: 0, max: 2, step: 0.025, default: 1, rawDefault: "var(--sf-space-m)", help: "--sf-btn-padding-inline" }, + { label: "Card radius (inner)", token: "--sf-card-radius", unit: "rem", min: 0, max: 2, step: 0.05, default: 0.5, rawDefault: "var(--sf-radius-m)", help: "--sf-card-radius" }, + { label: "Card padding", token: "--sf-card-padding", unit: "rem", min: 0, max: 3, step: 0.05, default: 1.5, rawDefault: "var(--sf-space-l)", help: "--sf-card-padding" }, { label: "Field radius", token: "--sf-field-radius", unit: "rem", min: 0, max: 2, step: 0.05, default: 0.5, rawDefault: "var(--sf-radius-m)", help: "--sf-field-radius" }, { label: "Field padding block", token: "--sf-field-padding-block", unit: "rem", min: 0, max: 1, step: 0.025, default: 0.375, rawDefault: "var(--sf-space-xs)", help: "--sf-field-padding-block" }, { label: "Field padding inline", token: "--sf-field-padding-inline", unit: "rem", min: 0, max: 2, step: 0.025, default: 0.75, rawDefault: "var(--sf-space-s)", help: "--sf-field-padding-inline" }, diff --git a/configurator/src/components/shell/PreviewPanel.svelte b/configurator/src/components/shell/PreviewPanel.svelte index fea3142a..ab24e3e5 100644 --- a/configurator/src/components/shell/PreviewPanel.svelte +++ b/configurator/src/components/shell/PreviewPanel.svelte @@ -114,9 +114,11 @@ .pv-emoji{font-size:2rem;line-height:1;} .pv-brand{font-weight:700;font-size:var(--sf-text-l);color:var(--sf-color-primary-600);} .pv-measure{max-inline-size:34rem;} - /* BEM components — .sf-btn / .sf-card / .sf-tag / .sf-field are staged off in - core until v0.8, so the preview ships its own token-driven equivalents. - These mirror the framework's reserved definitions one-to-one. */ + /* BEM components — .sf-btn / .sf-card shipped in v0.7.0; .sf-tag / .sf-field + are still staged. The preview keeps its own token-driven pv-* equivalents + so it stays isolated from whichever framework bundle is loaded (and can + show demo-only variants such as pv-card--soft). These track the + framework's reserved definitions. */ .pv-btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--sf-space-2xs);padding-block:var(--sf-space-xs);padding-inline:var(--sf-space-m);min-block-size:var(--sf-touch-target);font-family:inherit;font-size:var(--sf-text-m);font-weight:var(--sf-font-weight-interactive);line-height:var(--sf-leading-tight);white-space:nowrap;text-decoration:none;border:var(--sf-border-width-1) solid var(--sf-color-action);border-radius:var(--sf-radius-m);cursor:pointer;background:var(--sf-color-action);color:var(--sf-color-text--on-action);transition:var(--sf-transition-form-field);} .pv-btn--primary{background:var(--sf-color-primary);color:var(--sf-color-text--on-primary);border-color:var(--sf-color-primary);} .pv-btn--secondary{background:transparent;color:var(--sf-color-action);border-color:var(--sf-color-action);} diff --git a/configurator/src/data/api-index.generated.json b/configurator/src/data/api-index.generated.json index c9dda1c5..8a0e234c 100644 --- a/configurator/src/data/api-index.generated.json +++ b/configurator/src/data/api-index.generated.json @@ -2,7 +2,7 @@ "_sync": { "generatedBy": "configurator/scripts/sync-api.mjs", "source": "docs/api-index.json", - "tokensHash": "d49dd101dba4", + "tokensHash": "4e477120ce9c", "bundles": [ "full", "optimal", @@ -10,9 +10,9 @@ "optimal-utilities" ], "counts": { - "tokens": 687, + "tokens": 706, "byTier": { - "PUBLIC": 631, + "PUBLIC": 650, "PUBLIC-ADVANCED": 55, "INTERNAL": 1 }, @@ -20,7 +20,7 @@ "Layout tokens": 53, "Core tokens": 602, "Macro tokens": 26, - "Component tokens": 6 + "Component tokens": 25 } } }, @@ -1290,6 +1290,111 @@ "optimal-utilities" ] }, + { + "name": "--sf-btn-border-width", + "tier": "PUBLIC", + "role": "consumption", + "namespace": "btn", + "category": "Component tokens", + "group": "BUTTON TOKENS", + "description": "Padding and radius tokens that control button geometry. Override these to give buttons a distinct visual language from form fields.", + "note": "Border thickness for .sf-btn (used by all fill, outline, and secondary treatments).", + "value": "var(--sf-border-width-1)", + "aliasOf": "--sf-border-width-1", + "registered": false, + "syntax": null, + "fallbackOnly": false, + "optional": true, + "layer": "slashed.tokens", + "bundles": [ + "full", + "optimal-components" + ] + }, + { + "name": "--sf-btn-font-size", + "tier": "PUBLIC", + "role": "consumption", + "namespace": "btn", + "category": "Component tokens", + "group": "BUTTON TOKENS", + "description": "Padding and radius tokens that control button geometry. Override these to give buttons a distinct visual language from form fields.", + "note": "Label font-size for .sf-btn. The size modifiers (--xs/--s/--l/--xl) override this per button.", + "value": "var(--sf-text-m)", + "aliasOf": "--sf-text-m", + "registered": false, + "syntax": null, + "fallbackOnly": false, + "optional": true, + "layer": "slashed.tokens", + "bundles": [ + "full", + "optimal-components" + ] + }, + { + "name": "--sf-btn-font-weight", + "tier": "PUBLIC", + "role": "consumption", + "namespace": "btn", + "category": "Component tokens", + "group": "BUTTON TOKENS", + "description": "Padding and radius tokens that control button geometry. Override these to give buttons a distinct visual language from form fields.", + "note": "Label font-weight for .sf-btn. Defaults to the interactive weight.", + "value": "var(--sf-font-weight-interactive)", + "aliasOf": "--sf-font-weight-interactive", + "registered": false, + "syntax": null, + "fallbackOnly": false, + "optional": true, + "layer": "slashed.tokens", + "bundles": [ + "full", + "optimal-components" + ] + }, + { + "name": "--sf-btn-gap", + "tier": "PUBLIC", + "role": "consumption", + "namespace": "btn", + "category": "Component tokens", + "group": "BUTTON TOKENS", + "description": "Padding and radius tokens that control button geometry. Override these to give buttons a distinct visual language from form fields.", + "note": "Gap between an icon and the label inside .sf-btn.", + "value": "var(--sf-space-2xs)", + "aliasOf": "--sf-space-2xs", + "registered": false, + "syntax": null, + "fallbackOnly": false, + "optional": true, + "layer": "slashed.tokens", + "bundles": [ + "full", + "optimal-components" + ] + }, + { + "name": "--sf-btn-min-height", + "tier": "PUBLIC", + "role": "consumption", + "namespace": "btn", + "category": "Component tokens", + "group": "BUTTON TOKENS", + "description": "Padding and radius tokens that control button geometry. Override these to give buttons a distinct visual language from form fields.", + "note": "Minimum target height for .sf-btn. Defaults to --sf-touch-target so buttons meet the WCAG 2.2 target-size minimum.", + "value": "var(--sf-touch-target)", + "aliasOf": "--sf-touch-target", + "registered": false, + "syntax": null, + "fallbackOnly": false, + "optional": true, + "layer": "slashed.tokens", + "bundles": [ + "full", + "optimal-components" + ] + }, { "name": "--sf-btn-padding-block", "tier": "PUBLIC", @@ -1298,7 +1403,7 @@ "category": "Component tokens", "group": "BUTTON TOKENS", "description": "Padding and radius tokens that control button geometry. Override these to give buttons a distinct visual language from form fields.", - "note": "", + "note": "Vertical (block) padding inside .sf-btn.", "value": "var(--sf-space-xs)", "aliasOf": "--sf-space-xs", "registered": false, @@ -1319,7 +1424,7 @@ "category": "Component tokens", "group": "BUTTON TOKENS", "description": "Padding and radius tokens that control button geometry. Override these to give buttons a distinct visual language from form fields.", - "note": "", + "note": "Horizontal (inline) padding inside .sf-btn.", "value": "var(--sf-space-m)", "aliasOf": "--sf-space-m", "registered": false, @@ -1340,7 +1445,217 @@ "category": "Component tokens", "group": "BUTTON TOKENS", "description": "Padding and radius tokens that control button geometry. Override these to give buttons a distinct visual language from form fields.", - "note": "", + "note": "Corner radius for .sf-btn. Defaults to --sf-radius-m; gives buttons a distinct radius from form fields without touching global tokens.", + "value": "var(--sf-radius-m)", + "aliasOf": "--sf-radius-m", + "registered": false, + "syntax": null, + "fallbackOnly": false, + "optional": true, + "layer": "slashed.tokens", + "bundles": [ + "full", + "optimal-components" + ] + }, + { + "name": "--sf-card-bg", + "tier": "PUBLIC", + "role": "consumption", + "namespace": "card", + "category": "Component tokens", + "group": "CARD TOKENS", + "description": "Background surface for .sf-card. Defaults to --sf-color-surface.", + "note": "Background surface for .sf-card. Defaults to --sf-color-surface.", + "value": "var(--sf-color-surface)", + "aliasOf": "--sf-color-surface", + "registered": false, + "syntax": null, + "fallbackOnly": false, + "optional": true, + "layer": "slashed.tokens", + "bundles": [ + "full", + "optimal-components" + ] + }, + { + "name": "--sf-card-border-color", + "tier": "PUBLIC", + "role": "consumption", + "namespace": "card", + "category": "Component tokens", + "group": "CARD TOKENS", + "description": "Border colour for .sf-card. Defaults to --sf-color-border.", + "note": "Border colour for .sf-card. Defaults to --sf-color-border.", + "value": "var(--sf-color-border)", + "aliasOf": "--sf-color-border", + "registered": false, + "syntax": null, + "fallbackOnly": false, + "optional": true, + "layer": "slashed.tokens", + "bundles": [ + "full", + "optimal-components" + ] + }, + { + "name": "--sf-card-border-width", + "tier": "PUBLIC", + "role": "consumption", + "namespace": "card", + "category": "Component tokens", + "group": "CARD TOKENS", + "description": "Border thickness for .sf-card.", + "note": "Border thickness for .sf-card.", + "value": "var(--sf-border-width-1)", + "aliasOf": "--sf-border-width-1", + "registered": false, + "syntax": null, + "fallbackOnly": false, + "optional": true, + "layer": "slashed.tokens", + "bundles": [ + "full", + "optimal-components" + ] + }, + { + "name": "--sf-card-btn-font-size", + "tier": "PUBLIC", + "role": "consumption", + "namespace": "card", + "category": "Component tokens", + "group": "CARD TOKENS", + "description": "Font-size a .sf-btn shrinks to when nested inside a .sf-card. Defaults to --sf-text-s.", + "note": "Font-size a .sf-btn shrinks to when nested inside a .sf-card. Defaults to --sf-text-s.", + "value": "var(--sf-text-s)", + "aliasOf": "--sf-text-s", + "registered": false, + "syntax": null, + "fallbackOnly": false, + "optional": true, + "layer": "slashed.tokens", + "bundles": [ + "full", + "optimal-components" + ] + }, + { + "name": "--sf-card-gap", + "tier": "PUBLIC", + "role": "consumption", + "namespace": "card", + "category": "Component tokens", + "group": "CARD TOKENS", + "description": "Divider spacing for .sf-card__header / __footer (padding + margin around the rule).", + "note": "Divider spacing for .sf-card__header / __footer (padding + margin around the rule).", + "value": "var(--sf-space-m)", + "aliasOf": "--sf-space-m", + "registered": false, + "syntax": null, + "fallbackOnly": false, + "optional": true, + "layer": "slashed.tokens", + "bundles": [ + "full", + "optimal-components" + ] + }, + { + "name": "--sf-card-heading-size", + "tier": "PUBLIC", + "role": "consumption", + "namespace": "card", + "category": "Component tokens", + "group": "CARD TOKENS", + "description": "Font-size for .sf-card__title. Defaults to --sf-text-xl.", + "note": "Font-size for .sf-card__title. Defaults to --sf-text-xl.", + "value": "var(--sf-text-xl)", + "aliasOf": "--sf-text-xl", + "registered": false, + "syntax": null, + "fallbackOnly": false, + "optional": true, + "layer": "slashed.tokens", + "bundles": [ + "full", + "optimal-components" + ] + }, + { + "name": "--sf-card-media-radius", + "tier": "PUBLIC", + "role": "consumption", + "namespace": "card", + "category": "Component tokens", + "group": "CARD TOKENS", + "description": "Corner radius for .sf-card__media. Mirrors the card's inner radius by default.", + "note": "Corner radius for .sf-card__media. Mirrors the card's inner radius by default.", + "value": "var(--sf-card-radius, var(--sf-radius-m))", + "aliasOf": null, + "registered": false, + "syntax": null, + "fallbackOnly": false, + "optional": true, + "layer": "slashed.tokens", + "bundles": [ + "full", + "optimal-components" + ] + }, + { + "name": "--sf-card-media-ratio", + "tier": "PUBLIC", + "role": "consumption", + "namespace": "card", + "category": "Component tokens", + "group": "CARD TOKENS", + "description": "Aspect ratio for .sf-card__media. Defaults to --sf-ratio-video (16/9).", + "note": "Aspect ratio for .sf-card__media. Defaults to --sf-ratio-video (16/9).", + "value": "var(--sf-ratio-video)", + "aliasOf": "--sf-ratio-video", + "registered": false, + "syntax": null, + "fallbackOnly": false, + "optional": true, + "layer": "slashed.tokens", + "bundles": [ + "full", + "optimal-components" + ] + }, + { + "name": "--sf-card-padding", + "tier": "PUBLIC", + "role": "consumption", + "namespace": "card", + "category": "Component tokens", + "group": "CARD TOKENS", + "description": "Inner padding for .sf-card. Also feeds the concentric-radius math (outer = inner radius + padding).", + "note": "Inner padding for .sf-card. Also feeds the concentric-radius math (outer = inner radius + padding).", + "value": "var(--sf-space-l)", + "aliasOf": "--sf-space-l", + "registered": false, + "syntax": null, + "fallbackOnly": false, + "optional": true, + "layer": "slashed.tokens", + "bundles": [ + "full", + "optimal-components" + ] + }, + { + "name": "--sf-card-radius", + "tier": "PUBLIC", + "role": "consumption", + "namespace": "card", + "category": "Component tokens", + "group": "CARD TOKENS", + "description": "Inner radius for .sf-card children such as __media. Combined with padding to derive the outer radius.", + "note": "Inner radius for .sf-card children such as __media. Combined with padding to derive the outer radius.", "value": "var(--sf-radius-m)", "aliasOf": "--sf-radius-m", "registered": false, @@ -1353,6 +1668,90 @@ "optimal-components" ] }, + { + "name": "--sf-card-radius-outer", + "tier": "PUBLIC", + "role": "consumption", + "namespace": "card", + "category": "Component tokens", + "group": "CARD TOKENS", + "description": "Outer corner radius of .sf-card. Concentric with the inner radius: calc(--sf-card-radius + --sf-card-padding).", + "note": "Outer corner radius of .sf-card. Concentric with the inner radius: calc(--sf-card-radius + --sf-card-padding).", + "value": "calc(var(--sf-card-radius) + var(--sf-card-padding))", + "aliasOf": null, + "registered": false, + "syntax": null, + "fallbackOnly": false, + "optional": true, + "layer": "slashed.tokens", + "bundles": [ + "full", + "optimal-components" + ] + }, + { + "name": "--sf-card-shadow", + "tier": "PUBLIC", + "role": "consumption", + "namespace": "card", + "category": "Component tokens", + "group": "CARD TOKENS", + "description": "Base drop shadow for .sf-card. Defaults to --sf-shadow-s.", + "note": "Base drop shadow for .sf-card. Defaults to --sf-shadow-s.", + "value": "var(--sf-shadow-s)", + "aliasOf": "--sf-shadow-s", + "registered": false, + "syntax": null, + "fallbackOnly": false, + "optional": true, + "layer": "slashed.tokens", + "bundles": [ + "full", + "optimal-components" + ] + }, + { + "name": "--sf-card-shadow--elevated", + "tier": "PUBLIC", + "role": "consumption", + "namespace": "card", + "category": "Component tokens", + "group": "CARD TOKENS", + "description": "Drop shadow applied by the .sf-card--elevated modifier (border hidden, larger shadow).", + "note": "Drop shadow applied by the .sf-card--elevated modifier (border hidden, larger shadow).", + "value": "var(--sf-shadow-l)", + "aliasOf": "--sf-shadow-l", + "registered": false, + "syntax": null, + "fallbackOnly": false, + "optional": true, + "layer": "slashed.tokens", + "bundles": [ + "full", + "optimal-components" + ] + }, + { + "name": "--sf-card-shadow--hover", + "tier": "PUBLIC", + "role": "consumption", + "namespace": "card", + "category": "Component tokens", + "group": "CARD TOKENS", + "description": "Drop shadow applied on hover/focus by the .sf-card--interactive modifier.", + "note": "Drop shadow applied on hover/focus by the .sf-card--interactive modifier.", + "value": "var(--sf-shadow-l)", + "aliasOf": "--sf-shadow-l", + "registered": false, + "syntax": null, + "fallbackOnly": false, + "optional": true, + "layer": "slashed.tokens", + "bundles": [ + "full", + "optimal-components" + ] + }, { "name": "--sf-caret-color", "tier": "PUBLIC", diff --git a/configurator/src/data/classes.generated.json b/configurator/src/data/classes.generated.json index 3cc4a20b..04cddf37 100644 --- a/configurator/src/data/classes.generated.json +++ b/configurator/src/data/classes.generated.json @@ -604,6 +604,266 @@ "optional": false, "layer": "slashed.layout" }, + { + "name": "sf-btn", + "selector": ".sf-btn", + "kind": "component", + "category": "Components", + "group": ".sf-btn", + "description": "Interactive call-to-action button. Apply to + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +**Modifiers** + +| Group | Modifier | Effect | +|---|---|---| +| Colour | `--primary` `--neutral` `--success` `--warning` `--info` `--danger` | Swaps the colour family; base (no modifier) uses `--sf-color-action`. | +| Style | `--secondary` | Transparent fill, coloured text + border; subtle tint on hover. | +| Style | `--ghost` | Transparent fill **and** border; tint on hover. | +| Style | `--outline` | Coloured border/text, transparent fill; **fills** with the family colour on hover. Composes with any colour family. | +| Size | `--xs` `--s` `--l` `--xl` | Scales padding, font-size, and min-height (`m` = default). | +| Width | `--block` | Full width everywhere. | +| Width | `--block-cq` | Full width only inside a query container narrower than `20rem`. | +| State | `:disabled` / `.is-disabled` | Dimmed, `pointer-events: none`. | +| State | `.is-loading` | Hides content, shows a spinner (static under `prefers-reduced-motion: reduce`). | + +Every variant sets three rule-local custom properties — `--sf-btn-color`, +`--sf-btn-color--hover`, `--sf-btn-on-color` — which is what lets `--outline` +stay orthogonal to the colour families. + +**Tokens** (`optional/tokens.components.css`) + +| Token | Default | Controls | +|---|---|---| +| `--sf-btn-radius` | `var(--sf-radius-m)` | Corner radius | +| `--sf-btn-padding-block` | `var(--sf-space-xs)` | Vertical padding | +| `--sf-btn-padding-inline` | `var(--sf-space-m)` | Horizontal padding | +| `--sf-btn-gap` | `var(--sf-space-2xs)` | Gap between icon + label | +| `--sf-btn-font-size` | `var(--sf-text-m)` | Label size | +| `--sf-btn-font-weight` | `var(--sf-font-weight-interactive)` | Label weight | +| `--sf-btn-min-height` | `var(--sf-touch-target)` | Minimum target height (WCAG 2.2) | +| `--sf-btn-border-width` | `var(--sf-border-width-1)` | Border thickness | + +--- + +## `.sf-card` + +A padded content container with optional header/body/footer and media/avatar/title +subcomponents. Uses **concentric radius** math — `--sf-card-radius-outer` on the +card, `--sf-card-radius` on inner elements — so nested corners stay visually +proportional. + +```html +
+ +
+

Card title

+
+
+

Body content goes here.

+
+
+ +
+
+ + +
Flat, outlined (no shadow)
+
Floating (shadow, no border)
+
+ Whole card lifts on hover / keyboard focus + +``` + +A `.sf-btn` nested in a card automatically shrinks to `--sf-card-btn-font-size`. +Pair `--interactive` with `.sf-clickable-parent` (from `core/accessibility.css`) +when the entire card should be a single link. + +**Modifiers** + +| Modifier | Effect | +|---|---| +| `--bordered` | Keeps the border, drops the shadow (flat, outlined). | +| `--elevated` | Hides the border, uses a larger shadow (floating). | +| `--interactive` | Pointer cursor + shadow/translate lift on `:hover`/`:focus-within`; lift is suppressed under `prefers-reduced-motion: reduce`. | + +**Tokens** (`optional/tokens.components.css`) + +| Token | Default | Controls | +|---|---|---| +| `--sf-card-padding` | `var(--sf-space-l)` | Inner padding | +| `--sf-card-gap` | `var(--sf-space-m)` | Header/footer divider spacing | +| `--sf-card-radius` | `var(--sf-radius-m)` | Inner (media) radius | +| `--sf-card-radius-outer` | `calc(radius + padding)` | Outer card radius (concentric) | +| `--sf-card-bg` | `var(--sf-color-surface)` | Background | +| `--sf-card-border-width` | `var(--sf-border-width-1)` | Border thickness | +| `--sf-card-border-color` | `var(--sf-color-border)` | Border colour | +| `--sf-card-shadow` | `var(--sf-shadow-s)` | Base shadow | +| `--sf-card-shadow--elevated` | `var(--sf-shadow-l)` | `--elevated` shadow | +| `--sf-card-shadow--hover` | `var(--sf-shadow-l)` | `--interactive` hover shadow | +| `--sf-card-media-ratio` | `var(--sf-ratio-video)` | `__media` aspect ratio | +| `--sf-card-heading-size` | `var(--sf-text-xl)` | `__title` size | +| `--sf-card-btn-font-size` | `var(--sf-text-s)` | Nested `.sf-btn` size | + +**Composing card body layout** + +`.sf-card__body` is plain `display: block` — it doesn't have its own flex/grid +switch. For gap-controlled internal layout, compose it with the framework's +existing layout primitives instead of a card-specific modifier: + +```html + +
+

First paragraph.

+

Second paragraph, spaced via --sf-flow-space.

+
+ + +
+ New + Popular +
+``` + +--- + +## Recipe: gradient-outline button + +A gradient border isn't a shipped `.sf-btn` variant (solid outline covers the +common case), but it's a low-cost copy-paste recipe using the framework's gradient +tokens. It layers a gradient behind the button and masks out the centre so only the +border shows; `@property` makes the gradient animatable on hover. ```css -/* card */ ---sf-card-padding; --sf-card-gap; ---sf-card-radius; --sf-card-radius-outer; ---sf-card-bg; --sf-card-border-width; ---sf-card-border-color; --sf-card-shadow; - -/* button */ ---sf-button-pad-x; --sf-button-pad-y; ---sf-button-gap; --sf-button-radius; ---sf-button-font-weight; --sf-button-min-height; ---sf-button-border-width; +@property --sf-grad-a { syntax: ""; inherits: false; initial-value: transparent; } +@property --sf-grad-b { syntax: ""; inherits: false; initial-value: transparent; } + +.btn-gradient-outline { + position: relative; + background: transparent; + color: var(--sf-color-text); + border-color: transparent; +} +.btn-gradient-outline::before { + content: ""; + position: absolute; + inset: 0; + border-radius: inherit; + padding: var(--sf-border-width-1); + background: linear-gradient(120deg, var(--sf-grad-a), var(--sf-grad-b)); + /* Punch out the centre so only the padding (the "border") shows. */ + -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); + mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); + -webkit-mask-composite: xor; + mask-composite: exclude; + --sf-grad-a: var(--sf-color-primary); + --sf-grad-b: var(--sf-color-action); + transition: --sf-grad-a var(--sf-duration-normal), --sf-grad-b var(--sf-duration-normal); +} +.btn-gradient-outline:hover::before { + --sf-grad-a: var(--sf-color-action); + --sf-grad-b: var(--sf-color-primary); +} ``` -Don't use `--sf-button-*` token names for your own `.my-button` yet — they don't -resolve until the matching class ships. Use the global tokens (`--sf-space-*`, -`--sf-radius-*`, `--sf-color-*`) until then. +Apply it alongside `.sf-btn` (`class="sf-btn btn-gradient-outline"`) to inherit the +button's sizing, radius, and layout. The mask technique and animated `@property` +are within SLASHED's supported browser floor. ## Out of scope for 0.x @@ -63,7 +240,7 @@ tokens: ## Roadmap -Order not finalised; likely first batch is `.sf-card` + `.sf-button` (stable -tokens). Each minor that ships a component will uncomment its block in -`optional/components.css` + the matching token block, document it here with HTML -examples, add tests under `tests/`, and bump `MINOR` only (no breaking changes). +The first batch — `.sf-btn` + `.sf-card` — shipped in **v0.7.0**. Each subsequent +minor that ships a component uncomments its block in `optional/components.css` + +the matching token block, documents it here with HTML examples, adds tests under +`tests/`, and bumps `MINOR` only (no breaking changes). diff --git a/docs/llm-guide.md b/docs/llm-guide.md index 5da108e5..e8663a42 100644 --- a/docs/llm-guide.md +++ b/docs/llm-guide.md @@ -1043,11 +1043,39 @@ Component tokens from `optional/tokens.components.css`: --sf-field-padding-block: var(--sf-space-xs) --sf-field-padding-inline: var(--sf-space-s) -/* Buttons */ +/* Buttons — .sf-btn (live since 0.7.0) */ --sf-btn-radius: var(--sf-radius-m) --sf-btn-padding-block: var(--sf-space-xs) --sf-btn-padding-inline: var(--sf-space-m) -``` +--sf-btn-gap: var(--sf-space-2xs) +--sf-btn-font-size: var(--sf-text-m) +--sf-btn-font-weight: var(--sf-font-weight-interactive) +--sf-btn-min-height: var(--sf-touch-target) +--sf-btn-border-width: var(--sf-border-width-1) + +/* Cards — .sf-card (live since 0.7.0) */ +--sf-card-padding: var(--sf-space-l) +--sf-card-gap: var(--sf-space-m) +--sf-card-radius: var(--sf-radius-m) +--sf-card-radius-outer: calc(var(--sf-card-radius) + var(--sf-card-padding)) /* concentric */ +--sf-card-bg: var(--sf-color-surface) +--sf-card-border-width: var(--sf-border-width-1) +--sf-card-border-color: var(--sf-color-border) +--sf-card-shadow: var(--sf-shadow-s) +--sf-card-shadow--elevated: var(--sf-shadow-l) +--sf-card-shadow--hover: var(--sf-shadow-l) +--sf-card-media-ratio: var(--sf-ratio-video) +--sf-card-media-radius: var(--sf-card-radius, var(--sf-radius-m)) +--sf-card-heading-size: var(--sf-text-xl) +--sf-card-btn-font-size: var(--sf-text-s) +``` + +`.sf-btn` families: `--primary --neutral --success --warning --info --danger`; +treatments `--secondary --ghost --outline`; sizes `--xs --s --l --xl` (m default); +width `--block --block-cq`; states `:disabled`/`.is-disabled`/`.is-loading`. +`.sf-card` slots: `__header __body __footer __media __avatar __title`; modifiers +`--bordered --elevated --interactive`. Both ship in the `*-components` and `full` +bundles only. --- diff --git a/docs/registry.json b/docs/registry.json index 5a01ba6d..cac9f8d1 100644 --- a/docs/registry.json +++ b/docs/registry.json @@ -19,8 +19,8 @@ "optional/theme-example.css" ], "counts": { - "tokens": 687, - "sf_classes": 211, + "tokens": 706, + "sf_classes": 237, "is_classes": 40 } }, @@ -80,9 +80,28 @@ "--sf-box-border-width", "--sf-box-padding", "--sf-breakout-width", + "--sf-btn-border-width", + "--sf-btn-font-size", + "--sf-btn-font-weight", + "--sf-btn-gap", + "--sf-btn-min-height", "--sf-btn-padding-block", "--sf-btn-padding-inline", "--sf-btn-radius", + "--sf-card-bg", + "--sf-card-border-color", + "--sf-card-border-width", + "--sf-card-btn-font-size", + "--sf-card-gap", + "--sf-card-heading-size", + "--sf-card-media-radius", + "--sf-card-media-ratio", + "--sf-card-padding", + "--sf-card-radius", + "--sf-card-radius-outer", + "--sf-card-shadow", + "--sf-card-shadow--elevated", + "--sf-card-shadow--hover", "--sf-caret-color", "--sf-center-gutter", "--sf-center-max", @@ -729,6 +748,32 @@ "sf-bg", "sf-box", "sf-breakout", + "sf-btn", + "sf-btn--block", + "sf-btn--block-cq", + "sf-btn--danger", + "sf-btn--ghost", + "sf-btn--info", + "sf-btn--l", + "sf-btn--neutral", + "sf-btn--outline", + "sf-btn--primary", + "sf-btn--s", + "sf-btn--secondary", + "sf-btn--success", + "sf-btn--warning", + "sf-btn--xl", + "sf-btn--xs", + "sf-card", + "sf-card--bordered", + "sf-card--elevated", + "sf-card--interactive", + "sf-card__avatar", + "sf-card__body", + "sf-card__footer", + "sf-card__header", + "sf-card__media", + "sf-card__title", "sf-center", "sf-center--intrinsic", "sf-clickable-parent", diff --git a/docs/roadmap.md b/docs/roadmap.md index 38980c26..331f4159 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,6 +1,6 @@ # SLASHED — Roadmap -Current version: **0.6.35** +Current version: **0.7.0** ## Before v1.0 @@ -9,18 +9,17 @@ non-additive changes; the v0.6.x series focuses on the components layer.* ## Post-1.0 -- **Components layer** — the `slashed.components` layer and its stubs are already - declared; the eight `.sf-*` components land incrementally after v1.0, additive - only. Tracked in [#384](https://github.com/codeslash-dev/SLASHED/issues/384) - (consolidated tranche), with `.sf-btn` and `.sf-card` broken out individually - in [#493](https://github.com/codeslash-dev/SLASHED/issues/493) / - [#494](https://github.com/codeslash-dev/SLASHED/issues/494) — both fully - written but commented out in `optional/components.css`, staged until - activated. An outline `.sf-btn` variant is queued as a follow-on in - [#486](https://github.com/codeslash-dev/SLASHED/issues/486) once the base - variant set ships. **Note:** #384 and #493/#494 currently overlap in scope - (same button/card work tracked twice) — worth consolidating before - implementation starts. +- **Components layer** — the `slashed.components` layer is declared and its first + tranche is now live: **`.sf-btn` and `.sf-card` shipped in v0.7.0** + ([#493](https://github.com/codeslash-dev/SLASHED/issues/493) / + [#494](https://github.com/codeslash-dev/SLASHED/issues/494)), including the + outline `.sf-btn--outline` variant + ([#486](https://github.com/codeslash-dev/SLASHED/issues/486)), a button size + scale, and `.sf-card` modifiers. The remaining `.sf-*` components + (badge, tag, alert, avatar, modal, skeleton) stay staged in + `optional/components.css` and land incrementally in later minors. Tracked in + [#384](https://github.com/codeslash-dev/SLASHED/issues/384) (consolidated + tranche). - ~~**`@property` for spacing/shadow tokens**~~ ✓ **Done** — spacing (`--sf-radius-*`, `--sf-space-*`, semantic spacing) and shadow (`--sf-shadow-*`, `--sf-text-shadow-*`, `--sf-drop-shadow-*`) tokens are already registered via `@property` in @@ -54,13 +53,11 @@ non-additive changes; the v0.6.x series focuses on the components layer.* [#384](https://github.com/codeslash-dev/SLASHED/issues/384). - **Z-index utilities** — expose the named z-index scale as `.sf-z-*` classes (for example `.sf-z-modal` and `.sf-z-tooltip`) in `optional/utilities.css`. -- **First component tranche** — implement `.sf-btn`, `.sf-card`, `.sf-badge`, - and `.sf-tag` first, using `optional/tokens.components.css` and existing - `.is-*` states where appropriate. Tracked in - [#384](https://github.com/codeslash-dev/SLASHED/issues/384); `.sf-btn`/`.sf-card` - specifically in [#493](https://github.com/codeslash-dev/SLASHED/issues/493) / - [#494](https://github.com/codeslash-dev/SLASHED/issues/494) (see Post-1.0 note - on overlapping tracking above). +- **First component tranche** — `.sf-btn` and `.sf-card` shipped in v0.7.0 + (see Post-1.0 note); `.sf-badge` and `.sf-tag` are next, using + `optional/tokens.components.css` and existing `.is-*` states where + appropriate. Tracked in + [#384](https://github.com/codeslash-dev/SLASHED/issues/384). - **Standalone table pattern** — add `.sf-table`, `.sf-table-scroll`, and compact variants such as striped, bordered, and compact tables without changing global `table` styles or `.sf-prose table` behaviour. @@ -71,9 +68,11 @@ non-additive changes; the v0.6.x series focuses on the components layer.* `.sf-alert`, `.sf-avatar`, and `.sf-modal` with token-driven variants and native `` compatibility where applicable. Items 5–7 of [#384](https://github.com/codeslash-dev/SLASHED/issues/384). -- **Outline button variant** — a border-only `.sf-btn--outline` style, plus a - possible gradient-border follow-up, once the base `.sf-btn` variant set ships. - Tracked in [#486](https://github.com/codeslash-dev/SLASHED/issues/486). +- ~~**Outline button variant**~~ ✓ **Done (0.7.0)** — the border-only + `.sf-btn--outline` style shipped with the first component tranche; a + gradient-border version is documented as a copy-paste recipe in + `docs/components.md`. Tracked in + [#486](https://github.com/codeslash-dev/SLASHED/issues/486). - **Decorative animation utility classes** — the keyframes for `sf-spin`, `sf-shimmer`, `sf-ping`, `sf-blink`, and `sf-float` are already defined in `core/motion.css`; they need corresponding `.sf-spin`, `.sf-shimmer`, etc. diff --git a/docs/test-coverage-6-token-reference.html b/docs/test-coverage-6-token-reference.html index 54cb86e1..8a0e29d4 100644 --- a/docs/test-coverage-6-token-reference.html +++ b/docs/test-coverage-6-token-reference.html @@ -109,7 +109,7 @@

Full token reference — tokens

else apply(next); }); - var TOKENS = ["--sf-alternate-gap","--sf-alternate-inner-gap","--sf-animation-blink","--sf-animation-color-pulse","--sf-animation-delay-1","--sf-animation-delay-2","--sf-animation-delay-3","--sf-animation-delay-4","--sf-animation-delay-5","--sf-animation-fade-in","--sf-animation-fade-out","--sf-animation-float","--sf-animation-ping","--sf-animation-scale-down","--sf-animation-scale-up","--sf-animation-shimmer","--sf-animation-slide-in-down","--sf-animation-slide-in-left","--sf-animation-slide-in-right","--sf-animation-slide-in-up","--sf-animation-spin","--sf-aspect","--sf-bento-cols-default","--sf-bento-gap","--sf-bento-row-compact","--sf-bento-row-default","--sf-bento-row-tall","--sf-bg-fit","--sf-bg-inset","--sf-bg-position","--sf-bg-radius","--sf-bg-z","--sf-blur","--sf-body-color","--sf-body-em-style","--sf-body-font-family","--sf-body-font-size","--sf-body-font-weight","--sf-body-line-height","--sf-body-strong-weight","--sf-body-text-wrap","--sf-border","--sf-border-scale","--sf-border-strong","--sf-border-style","--sf-border-subtle","--sf-border-width-1","--sf-border-width-2","--sf-border-width-3","--sf-border-width-4","--sf-border-width-hairline","--sf-box-border-color","--sf-box-border-width","--sf-box-padding","--sf-breakout-width","--sf-btn-padding-block","--sf-btn-padding-inline","--sf-btn-radius","--sf-caret-color","--sf-center-gutter","--sf-center-max","--sf-cluster-align","--sf-cluster-gap","--sf-cluster-justify","--sf-code-font-size","--sf-color-action","--sf-color-action--active","--sf-color-action--hover","--sf-color-action-100","--sf-color-action-200","--sf-color-action-300","--sf-color-action-400","--sf-color-action-50","--sf-color-action-500","--sf-color-action-600","--sf-color-action-700","--sf-color-action-800","--sf-color-action-900","--sf-color-action-950","--sf-color-action-a10","--sf-color-action-a30","--sf-color-action-a5","--sf-color-action-a50","--sf-color-action-a80","--sf-color-action-darker","--sf-color-action-ghost","--sf-color-action-lighter","--sf-color-action-muted","--sf-color-action-source-dark","--sf-color-action-source-light","--sf-color-action-subtle","--sf-color-action-superdark","--sf-color-action-superlight","--sf-color-action-xdark","--sf-color-action-xlight","--sf-color-base","--sf-color-base--active","--sf-color-base--hover","--sf-color-base-100","--sf-color-base-200","--sf-color-base-300","--sf-color-base-400","--sf-color-base-50","--sf-color-base-500","--sf-color-base-600","--sf-color-base-700","--sf-color-base-800","--sf-color-base-900","--sf-color-base-950","--sf-color-base-a10","--sf-color-base-a30","--sf-color-base-a5","--sf-color-base-a50","--sf-color-base-a80","--sf-color-base-ghost","--sf-color-base-muted","--sf-color-base-source-dark","--sf-color-base-source-light","--sf-color-base-subtle","--sf-color-bg","--sf-color-bg--active","--sf-color-bg--disabled","--sf-color-bg--focus","--sf-color-bg--hover","--sf-color-bg--selected","--sf-color-black","--sf-color-border","--sf-color-border--disabled","--sf-color-border--focus","--sf-color-border--strong","--sf-color-border--subtle","--sf-color-border--translucent","--sf-color-code-bg","--sf-color-code-text","--sf-color-danger","--sf-color-danger-muted","--sf-color-danger-source-dark","--sf-color-danger-source-light","--sf-color-danger-strong","--sf-color-danger-subtle","--sf-color-dim","--sf-color-heading","--sf-color-info","--sf-color-info-muted","--sf-color-info-source-dark","--sf-color-info-source-light","--sf-color-info-strong","--sf-color-info-subtle","--sf-color-inset","--sf-color-inverse","--sf-color-link","--sf-color-link--active","--sf-color-link--disabled","--sf-color-link--hover","--sf-color-link--underline","--sf-color-link--visited","--sf-color-mark-bg","--sf-color-mark-text","--sf-color-neutral","--sf-color-neutral--active","--sf-color-neutral--hover","--sf-color-neutral-100","--sf-color-neutral-200","--sf-color-neutral-300","--sf-color-neutral-400","--sf-color-neutral-50","--sf-color-neutral-500","--sf-color-neutral-600","--sf-color-neutral-700","--sf-color-neutral-800","--sf-color-neutral-900","--sf-color-neutral-950","--sf-color-neutral-a10","--sf-color-neutral-a30","--sf-color-neutral-a5","--sf-color-neutral-a50","--sf-color-neutral-a80","--sf-color-neutral-darker","--sf-color-neutral-ghost","--sf-color-neutral-lighter","--sf-color-neutral-muted","--sf-color-neutral-source-dark","--sf-color-neutral-source-light","--sf-color-neutral-subtle","--sf-color-neutral-superdark","--sf-color-neutral-superlight","--sf-color-neutral-xdark","--sf-color-neutral-xlight","--sf-color-overlay","--sf-color-primary","--sf-color-primary--active","--sf-color-primary--hover","--sf-color-primary-100","--sf-color-primary-200","--sf-color-primary-300","--sf-color-primary-400","--sf-color-primary-50","--sf-color-primary-500","--sf-color-primary-600","--sf-color-primary-700","--sf-color-primary-800","--sf-color-primary-900","--sf-color-primary-950","--sf-color-primary-a10","--sf-color-primary-a30","--sf-color-primary-a5","--sf-color-primary-a50","--sf-color-primary-a80","--sf-color-primary-darker","--sf-color-primary-ghost","--sf-color-primary-lighter","--sf-color-primary-muted","--sf-color-primary-source-dark","--sf-color-primary-source-light","--sf-color-primary-subtle","--sf-color-primary-superdark","--sf-color-primary-superlight","--sf-color-primary-xdark","--sf-color-primary-xlight","--sf-color-raised","--sf-color-scheme","--sf-color-secondary","--sf-color-secondary--active","--sf-color-secondary--hover","--sf-color-secondary-100","--sf-color-secondary-200","--sf-color-secondary-300","--sf-color-secondary-400","--sf-color-secondary-50","--sf-color-secondary-500","--sf-color-secondary-600","--sf-color-secondary-700","--sf-color-secondary-800","--sf-color-secondary-900","--sf-color-secondary-950","--sf-color-secondary-a10","--sf-color-secondary-a30","--sf-color-secondary-a5","--sf-color-secondary-a50","--sf-color-secondary-a80","--sf-color-secondary-darker","--sf-color-secondary-ghost","--sf-color-secondary-lighter","--sf-color-secondary-muted","--sf-color-secondary-source-dark","--sf-color-secondary-source-light","--sf-color-secondary-subtle","--sf-color-secondary-superdark","--sf-color-secondary-superlight","--sf-color-secondary-xdark","--sf-color-secondary-xlight","--sf-color-selection-bg","--sf-color-selection-text","--sf-color-success","--sf-color-success-muted","--sf-color-success-source-dark","--sf-color-success-source-light","--sf-color-success-strong","--sf-color-success-subtle","--sf-color-surface","--sf-color-tertiary","--sf-color-tertiary--active","--sf-color-tertiary--hover","--sf-color-tertiary-100","--sf-color-tertiary-200","--sf-color-tertiary-300","--sf-color-tertiary-400","--sf-color-tertiary-50","--sf-color-tertiary-500","--sf-color-tertiary-600","--sf-color-tertiary-700","--sf-color-tertiary-800","--sf-color-tertiary-900","--sf-color-tertiary-950","--sf-color-tertiary-a10","--sf-color-tertiary-a30","--sf-color-tertiary-a5","--sf-color-tertiary-a50","--sf-color-tertiary-a80","--sf-color-tertiary-darker","--sf-color-tertiary-ghost","--sf-color-tertiary-lighter","--sf-color-tertiary-muted","--sf-color-tertiary-source-dark","--sf-color-tertiary-source-light","--sf-color-tertiary-subtle","--sf-color-tertiary-superdark","--sf-color-tertiary-superlight","--sf-color-tertiary-xdark","--sf-color-tertiary-xlight","--sf-color-text","--sf-color-text--disabled","--sf-color-text--inverse","--sf-color-text--muted","--sf-color-text--on-action","--sf-color-text--on-base","--sf-color-text--on-danger","--sf-color-text--on-info","--sf-color-text--on-inverse","--sf-color-text--on-neutral","--sf-color-text--on-primary","--sf-color-text--on-secondary","--sf-color-text--on-success","--sf-color-text--on-tertiary","--sf-color-text--on-warning","--sf-color-text--placeholder","--sf-color-text--secondary","--sf-color-warning","--sf-color-warning-muted","--sf-color-warning-source-dark","--sf-color-warning-source-light","--sf-color-warning-strong","--sf-color-warning-subtle","--sf-color-white","--sf-component-pad","--sf-container-default","--sf-container-full","--sf-container-narrow","--sf-container-prose","--sf-container-wide","--sf-content-gap","--sf-content-intrinsic-size","--sf-content-width","--sf-contrast-bias","--sf-contrast-threshold","--sf-corner-scoop-at","--sf-corner-scoop-size","--sf-cover-min-height","--sf-cover-padding","--sf-current-font-weight","--sf-display-l-line-height","--sf-display-m-line-height","--sf-display-s-line-height","--sf-divider-color","--sf-divider-gap","--sf-divider-style","--sf-divider-width","--sf-drop-shadow-l","--sf-drop-shadow-m","--sf-drop-shadow-s","--sf-duration-fast","--sf-duration-instant","--sf-duration-none","--sf-duration-normal","--sf-duration-slow","--sf-duration-slower","--sf-ease-bounce","--sf-ease-elastic","--sf-ease-in","--sf-ease-in-out","--sf-ease-linear","--sf-ease-out","--sf-ease-overshoot","--sf-ease-spring","--sf-equal-gap","--sf-equal-min-col","--sf-equal-rule-color","--sf-equal-rule-style","--sf-equal-rule-width","--sf-field-block","--sf-field-padding-block","--sf-field-padding-inline","--sf-field-radius","--sf-field-required-marker","--sf-flow-space","--sf-fluid-max-vw","--sf-fluid-min-vw","--sf-focus-ring-color","--sf-focus-ring-offset","--sf-focus-ring-shadow","--sf-focus-ring-style","--sf-focus-ring-width","--sf-font-body","--sf-font-display","--sf-font-features","--sf-font-geometric","--sf-font-heading","--sf-font-humanist","--sf-font-mono","--sf-font-numeric","--sf-font-slab","--sf-font-variation","--sf-font-weight-body","--sf-font-weight-bold","--sf-font-weight-display","--sf-font-weight-heading","--sf-font-weight-interactive","--sf-font-weight-light","--sf-font-weight-medium","--sf-font-weight-normal","--sf-font-weight-semibold","--sf-font-weight-strong","--sf-frame-ratio","--sf-gap","--sf-gradient-brand","--sf-gradient-fade--b","--sf-gradient-fade--l","--sf-gradient-fade--r","--sf-gradient-fade--t","--sf-gradient-primary","--sf-gradient-secondary","--sf-gradient-surface","--sf-gradient-tertiary","--sf-grid-gap","--sf-grid-min","--sf-grid-min-2xl","--sf-grid-min-l","--sf-grid-min-m","--sf-grid-min-s","--sf-grid-min-xl","--sf-grid-min-xs","--sf-gutter","--sf-h1-font-weight","--sf-h1-letter-spacing","--sf-h1-line-height","--sf-h1-max-width","--sf-h1-size","--sf-h2-font-weight","--sf-h2-letter-spacing","--sf-h2-line-height","--sf-h2-max-width","--sf-h2-size","--sf-h3-font-weight","--sf-h3-letter-spacing","--sf-h3-line-height","--sf-h3-max-width","--sf-h3-size","--sf-h4-font-weight","--sf-h4-letter-spacing","--sf-h4-line-height","--sf-h4-max-width","--sf-h4-size","--sf-h5-font-weight","--sf-h5-letter-spacing","--sf-h5-line-height","--sf-h5-max-width","--sf-h5-size","--sf-h6-font-weight","--sf-h6-letter-spacing","--sf-h6-line-height","--sf-h6-max-width","--sf-h6-size","--sf-header-height","--sf-header-height-desktop","--sf-header-height-mobile","--sf-heading-color","--sf-heading-font-family","--sf-heading-text-wrap","--sf-icon-2xl","--sf-icon-box-bg","--sf-icon-box-border","--sf-icon-box-pad","--sf-icon-box-radius","--sf-icon-l","--sf-icon-m","--sf-icon-s","--sf-icon-xl","--sf-icon-xs","--sf-imposter-margin","--sf-is-active","--sf-is-current","--sf-is-dark","--sf-is-open","--sf-is-pressed","--sf-leading-normal","--sf-leading-relaxed","--sf-leading-snug","--sf-leading-taper","--sf-leading-tight","--sf-line-clamp","--sf-link-external-marker","--sf-link-underline-offset","--sf-link-underline-thickness","--sf-lumlocker","--sf-mask-scrim-end","--sf-mask-scrim-start","--sf-motion-scale","--sf-object-fit","--sf-object-position","--sf-opacity-disabled","--sf-opacity-muted","--sf-optical-sizing","--sf-overlap-pull","--sf-palette-mix-100","--sf-palette-mix-200","--sf-palette-mix-300","--sf-palette-mix-400","--sf-palette-mix-50","--sf-palette-mix-600","--sf-palette-mix-700","--sf-palette-mix-800","--sf-palette-mix-900","--sf-palette-mix-950","--sf-print-base-size","--sf-print-page-margin","--sf-print-page-size","--sf-prose-block-margin","--sf-prose-blockquote-border","--sf-prose-blockquote-padding","--sf-prose-figcaption-size","--sf-prose-figure-margin","--sf-prose-heading-gap","--sf-prose-hr-margin","--sf-prose-list-gap","--sf-prose-marker-color","--sf-prose-media-margin","--sf-prose-media-radius","--sf-prose-nested-list-gap","--sf-prose-paragraph","--sf-prose-table-pad","--sf-radius-2xl","--sf-radius-2xs","--sf-radius-3xl","--sf-radius-4xl","--sf-radius-full","--sf-radius-l","--sf-radius-m","--sf-radius-none","--sf-radius-outer","--sf-radius-pill","--sf-radius-s","--sf-radius-scale","--sf-radius-xl","--sf-radius-xs","--sf-ratio-3-2","--sf-ratio-4-3","--sf-ratio-cinema","--sf-ratio-golden","--sf-ratio-portrait","--sf-ratio-square","--sf-ratio-video","--sf-reel-gap","--sf-reel-height","--sf-reel-item-width","--sf-safe-bottom","--sf-safe-left","--sf-safe-right","--sf-safe-top","--sf-scrim-color","--sf-scrim-direction","--sf-scrim-gradient","--sf-scrim-text-shadow","--sf-scroll-shadow-size","--sf-scroll-timeline-range-end","--sf-scroll-timeline-range-start","--sf-scrollbar-thumb","--sf-scrollbar-track","--sf-section-pad","--sf-section-pad--2xl","--sf-section-pad--l","--sf-section-pad--m","--sf-section-pad--s","--sf-section-pad--xl","--sf-section-pad--xs","--sf-section-scale","--sf-shadow-2xl","--sf-shadow-color","--sf-shadow-glow","--sf-shadow-glow-color","--sf-shadow-inner","--sf-shadow-l","--sf-shadow-lightness","--sf-shadow-m","--sf-shadow-none","--sf-shadow-s","--sf-shadow-strength","--sf-shadow-xl","--sf-shadow-xs","--sf-sidebar-gap","--sf-sidebar-min-width","--sf-sidebar-width","--sf-size-l","--sf-size-m","--sf-size-s","--sf-size-xl","--sf-size-xs","--sf-space-2xl","--sf-space-2xs","--sf-space-3xl","--sf-space-4xl","--sf-space-base-max","--sf-space-base-min","--sf-space-l","--sf-space-m","--sf-space-none","--sf-space-px","--sf-space-ratio-max","--sf-space-ratio-min","--sf-space-s","--sf-space-scale","--sf-space-xl","--sf-space-xs","--sf-stack-gap","--sf-state-pending-opacity","--sf-sticky-offset","--sf-sticky-offset-desktop","--sf-sticky-offset-mobile","--sf-surface-color","--sf-switcher-gap","--sf-switcher-threshold","--sf-text-2xl","--sf-text-2xl-font-weight","--sf-text-2xl-letter-spacing","--sf-text-2xl-line-height","--sf-text-2xl-max-width","--sf-text-2xs","--sf-text-2xs-font-weight","--sf-text-2xs-letter-spacing","--sf-text-2xs-line-height","--sf-text-2xs-max-width","--sf-text-3xl","--sf-text-3xl-font-weight","--sf-text-3xl-letter-spacing","--sf-text-3xl-line-height","--sf-text-3xl-max-width","--sf-text-4xl","--sf-text-4xl-font-weight","--sf-text-4xl-letter-spacing","--sf-text-4xl-line-height","--sf-text-4xl-max-width","--sf-text-base-max","--sf-text-base-min","--sf-text-display-base-max","--sf-text-display-base-min","--sf-text-display-l","--sf-text-display-m","--sf-text-display-s","--sf-text-display-scale","--sf-text-l","--sf-text-l-font-weight","--sf-text-l-letter-spacing","--sf-text-l-line-height","--sf-text-l-max-width","--sf-text-m","--sf-text-m-font-weight","--sf-text-m-letter-spacing","--sf-text-m-line-height","--sf-text-m-max-width","--sf-text-ratio-max","--sf-text-ratio-min","--sf-text-s","--sf-text-s-font-weight","--sf-text-s-letter-spacing","--sf-text-s-line-height","--sf-text-s-max-width","--sf-text-scale","--sf-text-shadow-l","--sf-text-shadow-m","--sf-text-shadow-none","--sf-text-shadow-s","--sf-text-xl","--sf-text-xl-font-weight","--sf-text-xl-letter-spacing","--sf-text-xl-line-height","--sf-text-xl-max-width","--sf-text-xs","--sf-text-xs-font-weight","--sf-text-xs-letter-spacing","--sf-text-xs-line-height","--sf-text-xs-max-width","--sf-theme-transition-duration","--sf-touch-target","--sf-tracking-normal","--sf-tracking-tight","--sf-tracking-wide","--sf-tracking-wider","--sf-tracking-widest","--sf-transition-colors","--sf-transition-enter","--sf-transition-exit","--sf-transition-fast","--sf-transition-form-field","--sf-transition-opacity","--sf-transition-overlay","--sf-transition-shadow","--sf-transition-slow","--sf-transition-transform","--sf-z-base","--sf-z-below","--sf-z-dropdown","--sf-z-fixed","--sf-z-modal","--sf-z-overlay","--sf-z-raised","--sf-z-sticky","--sf-z-toast","--sf-z-tooltip"]; + var TOKENS = ["--sf-alternate-gap","--sf-alternate-inner-gap","--sf-animation-blink","--sf-animation-color-pulse","--sf-animation-delay-1","--sf-animation-delay-2","--sf-animation-delay-3","--sf-animation-delay-4","--sf-animation-delay-5","--sf-animation-fade-in","--sf-animation-fade-out","--sf-animation-float","--sf-animation-ping","--sf-animation-scale-down","--sf-animation-scale-up","--sf-animation-shimmer","--sf-animation-slide-in-down","--sf-animation-slide-in-left","--sf-animation-slide-in-right","--sf-animation-slide-in-up","--sf-animation-spin","--sf-aspect","--sf-bento-cols-default","--sf-bento-gap","--sf-bento-row-compact","--sf-bento-row-default","--sf-bento-row-tall","--sf-bg-fit","--sf-bg-inset","--sf-bg-position","--sf-bg-radius","--sf-bg-z","--sf-blur","--sf-body-color","--sf-body-em-style","--sf-body-font-family","--sf-body-font-size","--sf-body-font-weight","--sf-body-line-height","--sf-body-strong-weight","--sf-body-text-wrap","--sf-border","--sf-border-scale","--sf-border-strong","--sf-border-style","--sf-border-subtle","--sf-border-width-1","--sf-border-width-2","--sf-border-width-3","--sf-border-width-4","--sf-border-width-hairline","--sf-box-border-color","--sf-box-border-width","--sf-box-padding","--sf-breakout-width","--sf-btn-border-width","--sf-btn-font-size","--sf-btn-font-weight","--sf-btn-gap","--sf-btn-min-height","--sf-btn-padding-block","--sf-btn-padding-inline","--sf-btn-radius","--sf-card-bg","--sf-card-border-color","--sf-card-border-width","--sf-card-btn-font-size","--sf-card-gap","--sf-card-heading-size","--sf-card-media-radius","--sf-card-media-ratio","--sf-card-padding","--sf-card-radius","--sf-card-radius-outer","--sf-card-shadow","--sf-card-shadow--elevated","--sf-card-shadow--hover","--sf-caret-color","--sf-center-gutter","--sf-center-max","--sf-cluster-align","--sf-cluster-gap","--sf-cluster-justify","--sf-code-font-size","--sf-color-action","--sf-color-action--active","--sf-color-action--hover","--sf-color-action-100","--sf-color-action-200","--sf-color-action-300","--sf-color-action-400","--sf-color-action-50","--sf-color-action-500","--sf-color-action-600","--sf-color-action-700","--sf-color-action-800","--sf-color-action-900","--sf-color-action-950","--sf-color-action-a10","--sf-color-action-a30","--sf-color-action-a5","--sf-color-action-a50","--sf-color-action-a80","--sf-color-action-darker","--sf-color-action-ghost","--sf-color-action-lighter","--sf-color-action-muted","--sf-color-action-source-dark","--sf-color-action-source-light","--sf-color-action-subtle","--sf-color-action-superdark","--sf-color-action-superlight","--sf-color-action-xdark","--sf-color-action-xlight","--sf-color-base","--sf-color-base--active","--sf-color-base--hover","--sf-color-base-100","--sf-color-base-200","--sf-color-base-300","--sf-color-base-400","--sf-color-base-50","--sf-color-base-500","--sf-color-base-600","--sf-color-base-700","--sf-color-base-800","--sf-color-base-900","--sf-color-base-950","--sf-color-base-a10","--sf-color-base-a30","--sf-color-base-a5","--sf-color-base-a50","--sf-color-base-a80","--sf-color-base-ghost","--sf-color-base-muted","--sf-color-base-source-dark","--sf-color-base-source-light","--sf-color-base-subtle","--sf-color-bg","--sf-color-bg--active","--sf-color-bg--disabled","--sf-color-bg--focus","--sf-color-bg--hover","--sf-color-bg--selected","--sf-color-black","--sf-color-border","--sf-color-border--disabled","--sf-color-border--focus","--sf-color-border--strong","--sf-color-border--subtle","--sf-color-border--translucent","--sf-color-code-bg","--sf-color-code-text","--sf-color-danger","--sf-color-danger-muted","--sf-color-danger-source-dark","--sf-color-danger-source-light","--sf-color-danger-strong","--sf-color-danger-subtle","--sf-color-dim","--sf-color-heading","--sf-color-info","--sf-color-info-muted","--sf-color-info-source-dark","--sf-color-info-source-light","--sf-color-info-strong","--sf-color-info-subtle","--sf-color-inset","--sf-color-inverse","--sf-color-link","--sf-color-link--active","--sf-color-link--disabled","--sf-color-link--hover","--sf-color-link--underline","--sf-color-link--visited","--sf-color-mark-bg","--sf-color-mark-text","--sf-color-neutral","--sf-color-neutral--active","--sf-color-neutral--hover","--sf-color-neutral-100","--sf-color-neutral-200","--sf-color-neutral-300","--sf-color-neutral-400","--sf-color-neutral-50","--sf-color-neutral-500","--sf-color-neutral-600","--sf-color-neutral-700","--sf-color-neutral-800","--sf-color-neutral-900","--sf-color-neutral-950","--sf-color-neutral-a10","--sf-color-neutral-a30","--sf-color-neutral-a5","--sf-color-neutral-a50","--sf-color-neutral-a80","--sf-color-neutral-darker","--sf-color-neutral-ghost","--sf-color-neutral-lighter","--sf-color-neutral-muted","--sf-color-neutral-source-dark","--sf-color-neutral-source-light","--sf-color-neutral-subtle","--sf-color-neutral-superdark","--sf-color-neutral-superlight","--sf-color-neutral-xdark","--sf-color-neutral-xlight","--sf-color-overlay","--sf-color-primary","--sf-color-primary--active","--sf-color-primary--hover","--sf-color-primary-100","--sf-color-primary-200","--sf-color-primary-300","--sf-color-primary-400","--sf-color-primary-50","--sf-color-primary-500","--sf-color-primary-600","--sf-color-primary-700","--sf-color-primary-800","--sf-color-primary-900","--sf-color-primary-950","--sf-color-primary-a10","--sf-color-primary-a30","--sf-color-primary-a5","--sf-color-primary-a50","--sf-color-primary-a80","--sf-color-primary-darker","--sf-color-primary-ghost","--sf-color-primary-lighter","--sf-color-primary-muted","--sf-color-primary-source-dark","--sf-color-primary-source-light","--sf-color-primary-subtle","--sf-color-primary-superdark","--sf-color-primary-superlight","--sf-color-primary-xdark","--sf-color-primary-xlight","--sf-color-raised","--sf-color-scheme","--sf-color-secondary","--sf-color-secondary--active","--sf-color-secondary--hover","--sf-color-secondary-100","--sf-color-secondary-200","--sf-color-secondary-300","--sf-color-secondary-400","--sf-color-secondary-50","--sf-color-secondary-500","--sf-color-secondary-600","--sf-color-secondary-700","--sf-color-secondary-800","--sf-color-secondary-900","--sf-color-secondary-950","--sf-color-secondary-a10","--sf-color-secondary-a30","--sf-color-secondary-a5","--sf-color-secondary-a50","--sf-color-secondary-a80","--sf-color-secondary-darker","--sf-color-secondary-ghost","--sf-color-secondary-lighter","--sf-color-secondary-muted","--sf-color-secondary-source-dark","--sf-color-secondary-source-light","--sf-color-secondary-subtle","--sf-color-secondary-superdark","--sf-color-secondary-superlight","--sf-color-secondary-xdark","--sf-color-secondary-xlight","--sf-color-selection-bg","--sf-color-selection-text","--sf-color-success","--sf-color-success-muted","--sf-color-success-source-dark","--sf-color-success-source-light","--sf-color-success-strong","--sf-color-success-subtle","--sf-color-surface","--sf-color-tertiary","--sf-color-tertiary--active","--sf-color-tertiary--hover","--sf-color-tertiary-100","--sf-color-tertiary-200","--sf-color-tertiary-300","--sf-color-tertiary-400","--sf-color-tertiary-50","--sf-color-tertiary-500","--sf-color-tertiary-600","--sf-color-tertiary-700","--sf-color-tertiary-800","--sf-color-tertiary-900","--sf-color-tertiary-950","--sf-color-tertiary-a10","--sf-color-tertiary-a30","--sf-color-tertiary-a5","--sf-color-tertiary-a50","--sf-color-tertiary-a80","--sf-color-tertiary-darker","--sf-color-tertiary-ghost","--sf-color-tertiary-lighter","--sf-color-tertiary-muted","--sf-color-tertiary-source-dark","--sf-color-tertiary-source-light","--sf-color-tertiary-subtle","--sf-color-tertiary-superdark","--sf-color-tertiary-superlight","--sf-color-tertiary-xdark","--sf-color-tertiary-xlight","--sf-color-text","--sf-color-text--disabled","--sf-color-text--inverse","--sf-color-text--muted","--sf-color-text--on-action","--sf-color-text--on-base","--sf-color-text--on-danger","--sf-color-text--on-info","--sf-color-text--on-inverse","--sf-color-text--on-neutral","--sf-color-text--on-primary","--sf-color-text--on-secondary","--sf-color-text--on-success","--sf-color-text--on-tertiary","--sf-color-text--on-warning","--sf-color-text--placeholder","--sf-color-text--secondary","--sf-color-warning","--sf-color-warning-muted","--sf-color-warning-source-dark","--sf-color-warning-source-light","--sf-color-warning-strong","--sf-color-warning-subtle","--sf-color-white","--sf-component-pad","--sf-container-default","--sf-container-full","--sf-container-narrow","--sf-container-prose","--sf-container-wide","--sf-content-gap","--sf-content-intrinsic-size","--sf-content-width","--sf-contrast-bias","--sf-contrast-threshold","--sf-corner-scoop-at","--sf-corner-scoop-size","--sf-cover-min-height","--sf-cover-padding","--sf-current-font-weight","--sf-display-l-line-height","--sf-display-m-line-height","--sf-display-s-line-height","--sf-divider-color","--sf-divider-gap","--sf-divider-style","--sf-divider-width","--sf-drop-shadow-l","--sf-drop-shadow-m","--sf-drop-shadow-s","--sf-duration-fast","--sf-duration-instant","--sf-duration-none","--sf-duration-normal","--sf-duration-slow","--sf-duration-slower","--sf-ease-bounce","--sf-ease-elastic","--sf-ease-in","--sf-ease-in-out","--sf-ease-linear","--sf-ease-out","--sf-ease-overshoot","--sf-ease-spring","--sf-equal-gap","--sf-equal-min-col","--sf-equal-rule-color","--sf-equal-rule-style","--sf-equal-rule-width","--sf-field-block","--sf-field-padding-block","--sf-field-padding-inline","--sf-field-radius","--sf-field-required-marker","--sf-flow-space","--sf-fluid-max-vw","--sf-fluid-min-vw","--sf-focus-ring-color","--sf-focus-ring-offset","--sf-focus-ring-shadow","--sf-focus-ring-style","--sf-focus-ring-width","--sf-font-body","--sf-font-display","--sf-font-features","--sf-font-geometric","--sf-font-heading","--sf-font-humanist","--sf-font-mono","--sf-font-numeric","--sf-font-slab","--sf-font-variation","--sf-font-weight-body","--sf-font-weight-bold","--sf-font-weight-display","--sf-font-weight-heading","--sf-font-weight-interactive","--sf-font-weight-light","--sf-font-weight-medium","--sf-font-weight-normal","--sf-font-weight-semibold","--sf-font-weight-strong","--sf-frame-ratio","--sf-gap","--sf-gradient-brand","--sf-gradient-fade--b","--sf-gradient-fade--l","--sf-gradient-fade--r","--sf-gradient-fade--t","--sf-gradient-primary","--sf-gradient-secondary","--sf-gradient-surface","--sf-gradient-tertiary","--sf-grid-gap","--sf-grid-min","--sf-grid-min-2xl","--sf-grid-min-l","--sf-grid-min-m","--sf-grid-min-s","--sf-grid-min-xl","--sf-grid-min-xs","--sf-gutter","--sf-h1-font-weight","--sf-h1-letter-spacing","--sf-h1-line-height","--sf-h1-max-width","--sf-h1-size","--sf-h2-font-weight","--sf-h2-letter-spacing","--sf-h2-line-height","--sf-h2-max-width","--sf-h2-size","--sf-h3-font-weight","--sf-h3-letter-spacing","--sf-h3-line-height","--sf-h3-max-width","--sf-h3-size","--sf-h4-font-weight","--sf-h4-letter-spacing","--sf-h4-line-height","--sf-h4-max-width","--sf-h4-size","--sf-h5-font-weight","--sf-h5-letter-spacing","--sf-h5-line-height","--sf-h5-max-width","--sf-h5-size","--sf-h6-font-weight","--sf-h6-letter-spacing","--sf-h6-line-height","--sf-h6-max-width","--sf-h6-size","--sf-header-height","--sf-header-height-desktop","--sf-header-height-mobile","--sf-heading-color","--sf-heading-font-family","--sf-heading-text-wrap","--sf-icon-2xl","--sf-icon-box-bg","--sf-icon-box-border","--sf-icon-box-pad","--sf-icon-box-radius","--sf-icon-l","--sf-icon-m","--sf-icon-s","--sf-icon-xl","--sf-icon-xs","--sf-imposter-margin","--sf-is-active","--sf-is-current","--sf-is-dark","--sf-is-open","--sf-is-pressed","--sf-leading-normal","--sf-leading-relaxed","--sf-leading-snug","--sf-leading-taper","--sf-leading-tight","--sf-line-clamp","--sf-link-external-marker","--sf-link-underline-offset","--sf-link-underline-thickness","--sf-lumlocker","--sf-mask-scrim-end","--sf-mask-scrim-start","--sf-motion-scale","--sf-object-fit","--sf-object-position","--sf-opacity-disabled","--sf-opacity-muted","--sf-optical-sizing","--sf-overlap-pull","--sf-palette-mix-100","--sf-palette-mix-200","--sf-palette-mix-300","--sf-palette-mix-400","--sf-palette-mix-50","--sf-palette-mix-600","--sf-palette-mix-700","--sf-palette-mix-800","--sf-palette-mix-900","--sf-palette-mix-950","--sf-print-base-size","--sf-print-page-margin","--sf-print-page-size","--sf-prose-block-margin","--sf-prose-blockquote-border","--sf-prose-blockquote-padding","--sf-prose-figcaption-size","--sf-prose-figure-margin","--sf-prose-heading-gap","--sf-prose-hr-margin","--sf-prose-list-gap","--sf-prose-marker-color","--sf-prose-media-margin","--sf-prose-media-radius","--sf-prose-nested-list-gap","--sf-prose-paragraph","--sf-prose-table-pad","--sf-radius-2xl","--sf-radius-2xs","--sf-radius-3xl","--sf-radius-4xl","--sf-radius-full","--sf-radius-l","--sf-radius-m","--sf-radius-none","--sf-radius-outer","--sf-radius-pill","--sf-radius-s","--sf-radius-scale","--sf-radius-xl","--sf-radius-xs","--sf-ratio-3-2","--sf-ratio-4-3","--sf-ratio-cinema","--sf-ratio-golden","--sf-ratio-portrait","--sf-ratio-square","--sf-ratio-video","--sf-reel-gap","--sf-reel-height","--sf-reel-item-width","--sf-safe-bottom","--sf-safe-left","--sf-safe-right","--sf-safe-top","--sf-scrim-color","--sf-scrim-direction","--sf-scrim-gradient","--sf-scrim-text-shadow","--sf-scroll-shadow-size","--sf-scroll-timeline-range-end","--sf-scroll-timeline-range-start","--sf-scrollbar-thumb","--sf-scrollbar-track","--sf-section-pad","--sf-section-pad--2xl","--sf-section-pad--l","--sf-section-pad--m","--sf-section-pad--s","--sf-section-pad--xl","--sf-section-pad--xs","--sf-section-scale","--sf-shadow-2xl","--sf-shadow-color","--sf-shadow-glow","--sf-shadow-glow-color","--sf-shadow-inner","--sf-shadow-l","--sf-shadow-lightness","--sf-shadow-m","--sf-shadow-none","--sf-shadow-s","--sf-shadow-strength","--sf-shadow-xl","--sf-shadow-xs","--sf-sidebar-gap","--sf-sidebar-min-width","--sf-sidebar-width","--sf-size-l","--sf-size-m","--sf-size-s","--sf-size-xl","--sf-size-xs","--sf-space-2xl","--sf-space-2xs","--sf-space-3xl","--sf-space-4xl","--sf-space-base-max","--sf-space-base-min","--sf-space-l","--sf-space-m","--sf-space-none","--sf-space-px","--sf-space-ratio-max","--sf-space-ratio-min","--sf-space-s","--sf-space-scale","--sf-space-xl","--sf-space-xs","--sf-stack-gap","--sf-state-pending-opacity","--sf-sticky-offset","--sf-sticky-offset-desktop","--sf-sticky-offset-mobile","--sf-surface-color","--sf-switcher-gap","--sf-switcher-threshold","--sf-text-2xl","--sf-text-2xl-font-weight","--sf-text-2xl-letter-spacing","--sf-text-2xl-line-height","--sf-text-2xl-max-width","--sf-text-2xs","--sf-text-2xs-font-weight","--sf-text-2xs-letter-spacing","--sf-text-2xs-line-height","--sf-text-2xs-max-width","--sf-text-3xl","--sf-text-3xl-font-weight","--sf-text-3xl-letter-spacing","--sf-text-3xl-line-height","--sf-text-3xl-max-width","--sf-text-4xl","--sf-text-4xl-font-weight","--sf-text-4xl-letter-spacing","--sf-text-4xl-line-height","--sf-text-4xl-max-width","--sf-text-base-max","--sf-text-base-min","--sf-text-display-base-max","--sf-text-display-base-min","--sf-text-display-l","--sf-text-display-m","--sf-text-display-s","--sf-text-display-scale","--sf-text-l","--sf-text-l-font-weight","--sf-text-l-letter-spacing","--sf-text-l-line-height","--sf-text-l-max-width","--sf-text-m","--sf-text-m-font-weight","--sf-text-m-letter-spacing","--sf-text-m-line-height","--sf-text-m-max-width","--sf-text-ratio-max","--sf-text-ratio-min","--sf-text-s","--sf-text-s-font-weight","--sf-text-s-letter-spacing","--sf-text-s-line-height","--sf-text-s-max-width","--sf-text-scale","--sf-text-shadow-l","--sf-text-shadow-m","--sf-text-shadow-none","--sf-text-shadow-s","--sf-text-xl","--sf-text-xl-font-weight","--sf-text-xl-letter-spacing","--sf-text-xl-line-height","--sf-text-xl-max-width","--sf-text-xs","--sf-text-xs-font-weight","--sf-text-xs-letter-spacing","--sf-text-xs-line-height","--sf-text-xs-max-width","--sf-theme-transition-duration","--sf-touch-target","--sf-tracking-normal","--sf-tracking-tight","--sf-tracking-wide","--sf-tracking-wider","--sf-tracking-widest","--sf-transition-colors","--sf-transition-enter","--sf-transition-exit","--sf-transition-fast","--sf-transition-form-field","--sf-transition-opacity","--sf-transition-overlay","--sf-transition-shadow","--sf-transition-slow","--sf-transition-transform","--sf-z-base","--sf-z-below","--sf-z-dropdown","--sf-z-fixed","--sf-z-modal","--sf-z-overlay","--sf-z-raised","--sf-z-sticky","--sf-z-toast","--sf-z-tooltip"]; document.getElementById('tok-count').textContent = TOKENS.length; var grid = document.getElementById('tok-grid'); var filter = document.getElementById('token-filter'); diff --git a/docs/token-annotations.json b/docs/token-annotations.json index daf49681..2740d9ca 100644 --- a/docs/token-annotations.json +++ b/docs/token-annotations.json @@ -723,6 +723,28 @@ "--sf-field-padding-block": "Vertical (block) inner padding for form field inputs.", "--sf-field-padding-inline": "Horizontal (inline) inner padding for form field inputs.", "--sf-field-radius": "Border radius for form field inputs. Defaults to --sf-radius-m; override to reshape all inputs at once.", + "--sf-btn-radius": "Corner radius for .sf-btn. Defaults to --sf-radius-m; gives buttons a distinct radius from form fields without touching global tokens.", + "--sf-btn-padding-block": "Vertical (block) padding inside .sf-btn.", + "--sf-btn-padding-inline": "Horizontal (inline) padding inside .sf-btn.", + "--sf-btn-gap": "Gap between an icon and the label inside .sf-btn.", + "--sf-btn-font-size": "Label font-size for .sf-btn. The size modifiers (--xs/--s/--l/--xl) override this per button.", + "--sf-btn-font-weight": "Label font-weight for .sf-btn. Defaults to the interactive weight.", + "--sf-btn-min-height": "Minimum target height for .sf-btn. Defaults to --sf-touch-target so buttons meet the WCAG 2.2 target-size minimum.", + "--sf-btn-border-width": "Border thickness for .sf-btn (used by all fill, outline, and secondary treatments).", + "--sf-card-padding": "Inner padding for .sf-card. Also feeds the concentric-radius math (outer = inner radius + padding).", + "--sf-card-gap": "Divider spacing for .sf-card__header / __footer (padding + margin around the rule).", + "--sf-card-radius": "Inner radius for .sf-card children such as __media. Combined with padding to derive the outer radius.", + "--sf-card-radius-outer": "Outer corner radius of .sf-card. Concentric with the inner radius: calc(--sf-card-radius + --sf-card-padding).", + "--sf-card-bg": "Background surface for .sf-card. Defaults to --sf-color-surface.", + "--sf-card-border-width": "Border thickness for .sf-card.", + "--sf-card-border-color": "Border colour for .sf-card. Defaults to --sf-color-border.", + "--sf-card-shadow": "Base drop shadow for .sf-card. Defaults to --sf-shadow-s.", + "--sf-card-shadow--elevated": "Drop shadow applied by the .sf-card--elevated modifier (border hidden, larger shadow).", + "--sf-card-shadow--hover": "Drop shadow applied on hover/focus by the .sf-card--interactive modifier.", + "--sf-card-media-ratio": "Aspect ratio for .sf-card__media. Defaults to --sf-ratio-video (16/9).", + "--sf-card-media-radius": "Corner radius for .sf-card__media. Mirrors the card's inner radius by default.", + "--sf-card-heading-size": "Font-size for .sf-card__title. Defaults to --sf-text-xl.", + "--sf-card-btn-font-size": "Font-size a .sf-btn shrinks to when nested inside a .sf-card. Defaults to --sf-text-s.", "--sf-h1-max-width": "Max-inline-size cap for

. Default none (unconstrained). Set to a ch value (e.g. 20ch) to prevent wide headings from spanning the full column on large screens.", "--sf-h2-max-width": "Max-inline-size cap for

. Default none.", "--sf-h3-max-width": "Max-inline-size cap for

. Default none.", @@ -800,6 +822,32 @@ "--sf-bg-z": "z-index of the background layer. Defaults to -2 so it sits behind sibling content but above the parent's own background." }, "classes": { + "sf-btn": "Interactive call-to-action button. Apply to `); + const cs = await computed(page, ['display', 'background-color', 'cursor']); + expect(cs.display).toBe('inline-flex'); + expect(TRANSPARENT.has(cs['background-color'])).toBe(false); + expect(cs.cursor).toBe('pointer'); + }); + + test('meets WCAG 2.2 minimum target height (>= 24px)', async ({ page }) => { + await mount(page, ``); + const h = await page.evaluate( + () => document.getElementById('t').getBoundingClientRect().height, + ); + expect(h).toBeGreaterThanOrEqual(24); + }); + + test('semantic colour families each produce a distinct fill', async ({ page }) => { + await mount( + page, + ` + + + `, + ); + const bgs = await page.evaluate(() => + ['base', 'danger', 'success', 'primary'].map( + (id) => getComputedStyle(document.getElementById(id)).backgroundColor, + ), + ); + // All four backgrounds are distinct → the family swap is working. + expect(new Set(bgs).size).toBe(4); + }); + + test('--secondary and --ghost are transparent-filled', async ({ page }) => { + await mount( + page, + ` + `, + ); + const [sec, ghost] = await page.evaluate(() => + ['sec', 'ghost'].map((id) => { + const cs = getComputedStyle(document.getElementById(id)); + return { bg: cs.backgroundColor, border: cs.borderTopColor }; + }), + ); + expect(['rgba(0, 0, 0, 0)', 'transparent']).toContain(sec.bg); + expect(['rgba(0, 0, 0, 0)', 'transparent']).toContain(ghost.bg); + // Ghost drops the border too; secondary keeps a visible one. + expect(['rgba(0, 0, 0, 0)', 'transparent']).toContain(ghost.border); + expect(['rgba(0, 0, 0, 0)', 'transparent']).not.toContain(sec.border); + }); + + test('--outline: transparent fill, border matches text colour', async ({ page }) => { + await mount(page, ``); + const cs = await page.evaluate(() => { + const c = getComputedStyle(document.getElementById('t')); + return { bg: c.backgroundColor, color: c.color, border: c.borderTopColor }; + }); + expect(TRANSPARENT.has(cs.bg)).toBe(true); + expect(cs.border).toBe(cs.color); + }); + + test('--outline fills with the family colour on hover', async ({ page }) => { + await mount(page, ``); + const before = await page.evaluate( + () => getComputedStyle(document.getElementById('t')).backgroundColor, + ); + await page.hover('#t'); + const after = await page.evaluate( + () => getComputedStyle(document.getElementById('t')).backgroundColor, + ); + expect(TRANSPARENT.has(before)).toBe(true); + expect(TRANSPARENT.has(after)).toBe(false); + }); + + test('--outline composes with a colour family (danger outline)', async ({ page }) => { + await mount( + page, + ` + `, + ); + const { fillBg, outColor } = await page.evaluate(() => ({ + fillBg: getComputedStyle(document.getElementById('fill')).backgroundColor, + outColor: getComputedStyle(document.getElementById('out')).color, + })); + // The outline's text colour equals the danger fill colour. + expect(outColor).toBe(fillBg); + }); + + test('size scale orders xs < m < l < xl by height', async ({ page }) => { + await mount( + page, + ` + + + `, + ); + const [xs, m, l, xl] = await page.evaluate(() => + ['xs', 'm', 'l', 'xl'].map((id) => document.getElementById(id).getBoundingClientRect().height), + ); + expect(xs).toBeLessThan(m); + expect(m).toBeLessThan(l); + expect(l).toBeLessThan(xl); + }); + + test('--block stretches to the container width', async ({ page }) => { + await mount( + page, + `
`, + ); + const w = await page.evaluate( + () => document.getElementById('t').getBoundingClientRect().width, + ); + expect(Math.round(w)).toBe(400); + }); + + test(':disabled removes pointer events and dims', async ({ page }) => { + await mount(page, ``); + const cs = await computed(page, ['pointer-events', 'opacity', 'cursor']); + expect(cs['pointer-events']).toBe('none'); + expect(parseFloat(cs.opacity)).toBeLessThan(1); + expect(cs.cursor).toBe('not-allowed'); + }); + + test('.is-loading hides content and sets a wait cursor', async ({ page }) => { + await mount(page, ``); + const cursor = await computed(page, ['cursor']); + const labelVis = await page.evaluate( + () => getComputedStyle(document.getElementById('lbl')).visibility, + ); + expect(cursor.cursor).toBe('wait'); + expect(labelVis).toBe('hidden'); + }); + + test('keyboard focus shows a visible outline ring', async ({ page }) => { + await mount(page, ``); + await page.keyboard.press('Tab'); + const outlineWidth = await page.evaluate(() => { + const el = document.getElementById('t'); + return el === document.activeElement + ? parseFloat(getComputedStyle(el).outlineWidth) + : -1; + }); + expect(outlineWidth).toBeGreaterThan(0); + }); + }); +} diff --git a/tests/card.spec.js b/tests/card.spec.js new file mode 100644 index 00000000..dc156397 --- /dev/null +++ b/tests/card.spec.js @@ -0,0 +1,113 @@ +// @ts-check +// Behavioural tests for the .sf-card component (shipped in 0.7.0). Loads the +// components bundle and asserts the base surface, concentric radius, header/ +// footer dividers, the --bordered/--elevated/--interactive modifiers, the +// nested-button font-size override, and the __media aspect ratio. Runs in both +// light and dark themes. +import { test, expect } from '@playwright/test'; +import path from 'node:path'; +import { renderWithBundle, NO_TRANSITIONS_STYLE } from './render-helpers.js'; + +const COMPONENTS_BUNDLE = path.join(process.cwd(), 'badges', 'slashed.optimal-components.css'); + +for (const theme of ['light', 'dark']) { + test.describe(`.sf-card — ${theme} theme`, () => { + async function mount(page, html) { + await renderWithBundle(page, html, { + bundle: COMPONENTS_BUNDLE, + extraStyle: NO_TRANSITIONS_STYLE, + }); + await page.evaluate((t) => document.documentElement.setAttribute('data-theme', t), theme); + } + + test('base card has padding, a border, a shadow, and a rounded outer radius', async ({ page }) => { + await mount(page, `
Body
`); + const cs = await page.evaluate(() => { + const c = getComputedStyle(document.getElementById('t')); + return { + pad: parseFloat(c.paddingTop), + border: parseFloat(c.borderTopWidth), + shadow: c.boxShadow, + radius: parseFloat(c.borderTopLeftRadius), + }; + }); + expect(cs.pad).toBeGreaterThan(0); + expect(cs.border).toBeGreaterThan(0); + expect(cs.shadow).not.toBe('none'); + // Concentric: outer radius = inner radius (~8px) + padding, so clearly > 8. + expect(cs.radius).toBeGreaterThan(12); + }); + + test('__header and __footer carry divider borders', async ({ page }) => { + await mount( + page, + `
+
H
+
B
+
F
+
`, + ); + const [hb, fb] = await page.evaluate(() => [ + parseFloat(getComputedStyle(document.getElementById('h')).borderBottomWidth), + parseFloat(getComputedStyle(document.getElementById('f')).borderTopWidth), + ]); + expect(hb).toBeGreaterThan(0); + expect(fb).toBeGreaterThan(0); + }); + + test('--bordered keeps the border but drops the shadow', async ({ page }) => { + await mount(page, `
Flat
`); + const cs = await page.evaluate(() => { + const c = getComputedStyle(document.getElementById('t')); + return { border: parseFloat(c.borderTopWidth), shadow: c.boxShadow }; + }); + expect(cs.border).toBeGreaterThan(0); + expect(cs.shadow).toBe('none'); + }); + + test('--elevated keeps a shadow but hides the border', async ({ page }) => { + await mount(page, `
Float
`); + const cs = await page.evaluate(() => { + const c = getComputedStyle(document.getElementById('t')); + return { borderColor: c.borderTopColor, shadow: c.boxShadow }; + }); + expect(cs.shadow).not.toBe('none'); + expect(['rgba(0, 0, 0, 0)', 'transparent']).toContain(cs.borderColor); + }); + + test('--interactive is a pointer affordance', async ({ page }) => { + await mount(page, `
Click
`); + const cursor = await page.evaluate( + () => getComputedStyle(document.getElementById('t')).cursor, + ); + expect(cursor).toBe('pointer'); + }); + + test('a nested .sf-btn shrinks relative to a standalone one', async ({ page }) => { + await mount( + page, + ` +
+ +
`, + ); + const [loose, nested] = await page.evaluate(() => + ['loose', 'nested'].map((id) => parseFloat(getComputedStyle(document.getElementById(id)).fontSize)), + ); + expect(nested).toBeLessThan(loose); + }); + + test('__media crops to a 16/9 aspect ratio', async ({ page }) => { + await mount( + page, + `
`, + ); + const ratio = await page.evaluate(() => { + const el = document.getElementById('t'); + const r = el.getBoundingClientRect(); + return r.width / r.height; + }); + expect(ratio).toBeCloseTo(16 / 9, 1); + }); + }); +} diff --git a/tests/token-api.snapshot.json b/tests/token-api.snapshot.json index 4ada5b63..da2b7478 100644 --- a/tests/token-api.snapshot.json +++ b/tests/token-api.snapshot.json @@ -54,9 +54,28 @@ "--sf-box-border-width", "--sf-box-padding", "--sf-breakout-width", + "--sf-btn-border-width", + "--sf-btn-font-size", + "--sf-btn-font-weight", + "--sf-btn-gap", + "--sf-btn-min-height", "--sf-btn-padding-block", "--sf-btn-padding-inline", "--sf-btn-radius", + "--sf-card-bg", + "--sf-card-border-color", + "--sf-card-border-width", + "--sf-card-btn-font-size", + "--sf-card-gap", + "--sf-card-heading-size", + "--sf-card-media-radius", + "--sf-card-media-ratio", + "--sf-card-padding", + "--sf-card-radius", + "--sf-card-radius-outer", + "--sf-card-shadow", + "--sf-card-shadow--elevated", + "--sf-card-shadow--hover", "--sf-caret-color", "--sf-center-gutter", "--sf-center-max", diff --git a/token-registry.json b/token-registry.json index b9393dfa..205013f1 100644 --- a/token-registry.json +++ b/token-registry.json @@ -1,7 +1,7 @@ { "_meta": { "generatedBy": "scripts/gen-token-registry.js", - "nextId": 717 + "nextId": 736 }, "tokens": [ { @@ -2901,6 +2901,82 @@ { "id": 716, "name": "--sf-overlap-pull" + }, + { + "id": 717, + "name": "--sf-btn-border-width" + }, + { + "id": 718, + "name": "--sf-btn-font-size" + }, + { + "id": 719, + "name": "--sf-btn-font-weight" + }, + { + "id": 720, + "name": "--sf-btn-gap" + }, + { + "id": 721, + "name": "--sf-btn-min-height" + }, + { + "id": 722, + "name": "--sf-card-bg" + }, + { + "id": 723, + "name": "--sf-card-border-color" + }, + { + "id": 724, + "name": "--sf-card-border-width" + }, + { + "id": 725, + "name": "--sf-card-btn-font-size" + }, + { + "id": 726, + "name": "--sf-card-gap" + }, + { + "id": 727, + "name": "--sf-card-heading-size" + }, + { + "id": 728, + "name": "--sf-card-media-radius" + }, + { + "id": 729, + "name": "--sf-card-media-ratio" + }, + { + "id": 730, + "name": "--sf-card-padding" + }, + { + "id": 731, + "name": "--sf-card-radius" + }, + { + "id": 732, + "name": "--sf-card-radius-outer" + }, + { + "id": 733, + "name": "--sf-card-shadow" + }, + { + "id": 734, + "name": "--sf-card-shadow--elevated" + }, + { + "id": 735, + "name": "--sf-card-shadow--hover" } ] }