diff --git a/CHANGELOG.md b/CHANGELOG.md index 62b0b9e7..1dc31357 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), ## Unreleased +### Features +- **motion:** add `.sf-stagger` — put it on a parent and every direct child gets an incrementing `animation-delay`, so a group of time-based entrance animations (`.sf-fade-in` / `.sf-slide-in-*`) plays in sequence. Works on any number of children with no manual per-item index: where `sibling-index()` is supported the ramp is unbounded, otherwise it falls back to an 8-step `:nth-child` ramp (covering a 4-column grid's first two rows) that plateaus. One knob, `--sf-stagger-step` (default `75ms`), retunes the whole sequence; each child's delay is `index × --sf-stagger-step × --sf-motion-scale`. Choreography only — a child without its own animation just carries an inert delay, so animating only some children needs no opt-out on the rest. +- **tokens:** the `.sf-hover-*` transform utilities now read magnitude knobs instead of hard-coded values — `--sf-hover-grow-scale` (1.05), `--sf-hover-shrink-scale` (0.95), `--sf-hover-lift` (0.25em, float/sink), `--sf-hover-slide` (0.5em, slide-start/end) — so each effect's strength is one global override. + +### Breaking Changes +- **tokens:** removed the fixed `--sf-animation-delay-1…5` stagger tokens, superseded by the new `.sf-stagger` utility + `--sf-stagger-step` knob. Replace `style="animation-delay: var(--sf-animation-delay-N)"` on hand-indexed children with a single `.sf-stagger` class on their parent (see `docs/motion.md`); to keep a fixed manual delay, use a literal (e.g. `animation-delay: 150ms`). + ### Bug Fixes - **layout:** `.sf-bento` and `.sf-grid-cols-2/3/4/6` now actually respond to their `@container` breakpoints — both used to declare a `container` on themselves and then query that same container to resize themselves, which is a no-op per spec (a container can't be the subject of its own `@container` query). Fixed by relying on an ancestor container (`.sf-container` / `.sf-cq` / `.sf-fluid-cq`) instead, matching how `.sf-alternate` and `.sf-grid-cols-1-2` etc. already do it. `.sf-bento-wide` / `.sf-bento-featured` also no longer force a phantom 2nd column once the grid collapses to 1 column at the mobile breakpoint. diff --git a/badges/badge-optimal.json b/badges/badge-optimal.json index 46b854cc..31ef54a3 100644 --- a/badges/badge-optimal.json +++ b/badges/badge-optimal.json @@ -1,7 +1,7 @@ { "schemaVersion": 1, "label": "optimal", - "message": "19.5 kB gzip", + "message": "19.6 kB gzip", "color": "brightgreen", "namedLogo": "css3" } diff --git a/configurator/src/data/api-index.generated.json b/configurator/src/data/api-index.generated.json index ed70e0aa..d84edc17 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": "ea14629a05b1", + "tokensHash": "5624c486ab4d", "bundles": [ "full", "optimal" @@ -107,111 +107,6 @@ "optimal" ] }, - { - "name": "--sf-animation-delay-1", - "tier": "PUBLIC", - "role": "consumption", - "namespace": "animation", - "category": "Core tokens", - "group": "Animation presets", - "description": "Complete animation shorthand values — pair named @keyframes with duration and easing. Use directly as animation property values.", - "note": "Shortest stagger delay. Apply to the first element in a sequentially animated group.", - "value": "calc(75ms * var(--sf-motion-scale))", - "aliasOf": null, - "registered": false, - "syntax": null, - "fallbackOnly": false, - "optional": false, - "layer": "slashed.tokens", - "bundles": [ - "full", - "optimal" - ] - }, - { - "name": "--sf-animation-delay-2", - "tier": "PUBLIC", - "role": "consumption", - "namespace": "animation", - "category": "Core tokens", - "group": "Animation presets", - "description": "Complete animation shorthand values — pair named @keyframes with duration and easing. Use directly as animation property values.", - "note": "Second stagger delay step.", - "value": "calc(150ms * var(--sf-motion-scale))", - "aliasOf": null, - "registered": false, - "syntax": null, - "fallbackOnly": false, - "optional": false, - "layer": "slashed.tokens", - "bundles": [ - "full", - "optimal" - ] - }, - { - "name": "--sf-animation-delay-3", - "tier": "PUBLIC", - "role": "consumption", - "namespace": "animation", - "category": "Core tokens", - "group": "Animation presets", - "description": "Complete animation shorthand values — pair named @keyframes with duration and easing. Use directly as animation property values.", - "note": "Third stagger delay step.", - "value": "calc(225ms * var(--sf-motion-scale))", - "aliasOf": null, - "registered": false, - "syntax": null, - "fallbackOnly": false, - "optional": false, - "layer": "slashed.tokens", - "bundles": [ - "full", - "optimal" - ] - }, - { - "name": "--sf-animation-delay-4", - "tier": "PUBLIC", - "role": "consumption", - "namespace": "animation", - "category": "Core tokens", - "group": "Animation presets", - "description": "Complete animation shorthand values — pair named @keyframes with duration and easing. Use directly as animation property values.", - "note": "Fourth stagger delay step.", - "value": "calc(300ms * var(--sf-motion-scale))", - "aliasOf": null, - "registered": false, - "syntax": null, - "fallbackOnly": false, - "optional": false, - "layer": "slashed.tokens", - "bundles": [ - "full", - "optimal" - ] - }, - { - "name": "--sf-animation-delay-5", - "tier": "PUBLIC", - "role": "consumption", - "namespace": "animation", - "category": "Core tokens", - "group": "Animation presets", - "description": "Complete animation shorthand values — pair named @keyframes with duration and easing. Use directly as animation property values.", - "note": "Longest stagger delay. Apply to the last element in a sequentially animated group.", - "value": "calc(375ms * var(--sf-motion-scale))", - "aliasOf": null, - "registered": false, - "syntax": null, - "fallbackOnly": false, - "optional": false, - "layer": "slashed.tokens", - "bundles": [ - "full", - "optimal" - ] - }, { "name": "--sf-animation-fade-in", "tier": "PUBLIC", @@ -10519,6 +10414,90 @@ "optimal" ] }, + { + "name": "--sf-hover-grow-scale", + "tier": "PUBLIC", + "role": "knob", + "namespace": "hover", + "category": "Core tokens", + "group": "Animation presets", + "description": "Complete animation shorthand values — pair named @keyframes with duration and easing. Use directly as animation property values.", + "note": "Scale factor applied by .sf-hover-grow on hover (>1 grows).", + "value": "1.05", + "aliasOf": null, + "registered": false, + "syntax": null, + "fallbackOnly": false, + "optional": false, + "layer": "slashed.tokens", + "bundles": [ + "full", + "optimal" + ] + }, + { + "name": "--sf-hover-lift", + "tier": "PUBLIC", + "role": "knob", + "namespace": "hover", + "category": "Core tokens", + "group": "Animation presets", + "description": "Complete animation shorthand values — pair named @keyframes with duration and easing. Use directly as animation property values.", + "note": "Translate distance for .sf-hover-float (up) and .sf-hover-sink (down) on hover.", + "value": "0.25em", + "aliasOf": null, + "registered": false, + "syntax": null, + "fallbackOnly": false, + "optional": false, + "layer": "slashed.tokens", + "bundles": [ + "full", + "optimal" + ] + }, + { + "name": "--sf-hover-shrink-scale", + "tier": "PUBLIC", + "role": "knob", + "namespace": "hover", + "category": "Core tokens", + "group": "Animation presets", + "description": "Complete animation shorthand values — pair named @keyframes with duration and easing. Use directly as animation property values.", + "note": "Scale factor applied by .sf-hover-shrink on hover (<1 shrinks).", + "value": "0.95", + "aliasOf": null, + "registered": false, + "syntax": null, + "fallbackOnly": false, + "optional": false, + "layer": "slashed.tokens", + "bundles": [ + "full", + "optimal" + ] + }, + { + "name": "--sf-hover-slide", + "tier": "PUBLIC", + "role": "knob", + "namespace": "hover", + "category": "Core tokens", + "group": "Animation presets", + "description": "Complete animation shorthand values — pair named @keyframes with duration and easing. Use directly as animation property values.", + "note": "Translate distance for .sf-hover-slide-start / .sf-hover-slide-end on hover.", + "value": "0.5em", + "aliasOf": null, + "registered": false, + "syntax": null, + "fallbackOnly": false, + "optional": false, + "layer": "slashed.tokens", + "bundles": [ + "full", + "optimal" + ] + }, { "name": "--sf-icon-2xl", "tier": "PUBLIC", @@ -13648,6 +13627,27 @@ "optimal" ] }, + { + "name": "--sf-stagger-step", + "tier": "PUBLIC", + "role": "knob", + "namespace": "stagger", + "category": "Core tokens", + "group": "Animation presets", + "description": "Complete animation shorthand values — pair named @keyframes with duration and easing. Use directly as animation property values.", + "note": "Per-item delay increment for the .sf-stagger utility. One knob retunes the whole staggered sequence; the class multiplies it by each child's index and by --sf-motion-scale.", + "value": "75ms", + "aliasOf": null, + "registered": false, + "syntax": null, + "fallbackOnly": false, + "optional": false, + "layer": "slashed.tokens", + "bundles": [ + "full", + "optimal" + ] + }, { "name": "--sf-state-pending-opacity", "tier": "PUBLIC", diff --git a/configurator/src/data/classes.generated.json b/configurator/src/data/classes.generated.json index 3e24e7a2..88a7b6f6 100644 --- a/configurator/src/data/classes.generated.json +++ b/configurator/src/data/classes.generated.json @@ -2894,6 +2894,16 @@ "optional": false, "layer": "slashed.layout" }, + { + "name": "sf-stagger", + "selector": ".sf-stagger", + "kind": "motion", + "category": "Motion / animation", + "group": "", + "description": "Stagger — choreography, not the animation itself. Put .sf-stagger on a PARENT; every direct child gets an incrementing animation-delay so a time-based entrance (.sf-fade-in / .sf-slide-in-*) plays in sequence. Children WITHOUT an animation just carry an inert delay (no-op), so…", + "optional": false, + "layer": "slashed.motion" + }, { "name": "sf-sticky", "selector": ".sf-sticky", diff --git a/configurator/src/data/domain-patterns.json b/configurator/src/data/domain-patterns.json index 39c57052..190df640 100644 --- a/configurator/src/data/domain-patterns.json +++ b/configurator/src/data/domain-patterns.json @@ -13,7 +13,7 @@ "components": ["btn-", "card-"], "borders": ["radius", "border-width", "border-scale", "border-style", "divider"], "shadows": ["shadow"], - "motion": ["motion", "duration", "easing", "ease", "scroll-timeline"], + "motion": ["motion", "duration", "easing", "ease", "scroll-timeline", "stagger", "hover-"], "effects": ["filter", "blur", "backdrop", "opacity", "effect"], "misc": ["z-", "cursor", "ring", "focus", "outline", "selection", "icon", "sf-is-active", "sf-is-current", "sf-is-open", "sf-is-pressed", diff --git a/configurator/src/data/token-registry.generated.json b/configurator/src/data/token-registry.generated.json index d2f1fd0a..94704541 100644 --- a/configurator/src/data/token-registry.generated.json +++ b/configurator/src/data/token-registry.generated.json @@ -1,7 +1,7 @@ { "_meta": { "generatedBy": "scripts/gen-token-registry.js", - "nextId": 792 + "nextId": 797 }, "tokens": [ { @@ -22,23 +22,28 @@ }, { "id": 4, - "name": "--sf-animation-delay-1" + "name": "--sf-animation-delay-1", + "removed": true }, { "id": 5, - "name": "--sf-animation-delay-2" + "name": "--sf-animation-delay-2", + "removed": true }, { "id": 6, - "name": "--sf-animation-delay-3" + "name": "--sf-animation-delay-3", + "removed": true }, { "id": 7, - "name": "--sf-animation-delay-4" + "name": "--sf-animation-delay-4", + "removed": true }, { "id": 8, - "name": "--sf-animation-delay-5" + "name": "--sf-animation-delay-5", + "removed": true }, { "id": 9, @@ -3208,6 +3213,26 @@ { "id": 791, "name": "--sf-btn-xs-padding-inline" + }, + { + "id": 792, + "name": "--sf-hover-grow-scale" + }, + { + "id": 793, + "name": "--sf-hover-lift" + }, + { + "id": 794, + "name": "--sf-hover-shrink-scale" + }, + { + "id": 795, + "name": "--sf-hover-slide" + }, + { + "id": 796, + "name": "--sf-stagger-step" } ] } diff --git a/core/motion.css b/core/motion.css index 863b1599..4c5c6d2e 100644 --- a/core/motion.css +++ b/core/motion.css @@ -100,6 +100,45 @@ .sf-exit--scale-down { animation-name: sf-scale-down; } } + /* Stagger — choreography, not the animation itself. Put .sf-stagger on a + PARENT; every direct child gets an incrementing animation-delay so a + time-based entrance (.sf-fade-in / .sf-slide-in-*) plays in sequence. + Children WITHOUT an animation just carry an inert delay (no-op), so you + opt in per child with no need to exclude anything. Pairs with time-based + animations only — the .sf-entrance / .sf-exit families are scroll-driven + (their rhythm is animation-range, not animation-delay). + + One formula, one knob (--sf-stagger-step); the two paths below only + differ in how --sf-stagger-i (the child's index) is set. */ + .sf-stagger > * { + animation-delay: calc( + var(--sf-stagger-i, 0) * var(--sf-stagger-step, 75ms) * var(--sf-motion-scale, 1) + ); + } + + /* Modern path: real, unbounded index straight from the DOM position. */ + @supports (animation-delay: calc(sibling-index() * 1ms)) { + .sf-stagger > * { --sf-stagger-i: calc(sibling-index() - 1); } + } + + /* Fallback (no sibling-index()): a bounded :nth-child ramp reusing the same + formula. The default is the plateau value, so the 9th child onward shares + the last rung's delay instead of growing without limit — 8 rungs cover a + 4-column grid's first two visible rows. Split into @supports not(...) so + the modern override above wins cleanly without a specificity fight + (:nth-child would otherwise out-specify the sibling-index rule). */ + @supports not (animation-delay: calc(sibling-index() * 1ms)) { + .sf-stagger > * { --sf-stagger-i: 8; } + .sf-stagger > *:nth-child(1) { --sf-stagger-i: 0; } + .sf-stagger > *:nth-child(2) { --sf-stagger-i: 1; } + .sf-stagger > *:nth-child(3) { --sf-stagger-i: 2; } + .sf-stagger > *:nth-child(4) { --sf-stagger-i: 3; } + .sf-stagger > *:nth-child(5) { --sf-stagger-i: 4; } + .sf-stagger > *:nth-child(6) { --sf-stagger-i: 5; } + .sf-stagger > *:nth-child(7) { --sf-stagger-i: 6; } + .sf-stagger > *:nth-child(8) { --sf-stagger-i: 7; } + } + } @keyframes sf-fade-in { from { opacity: 0; } to { opacity: 1; } } diff --git a/core/tokens.css b/core/tokens.css index 3d0a332d..8f85e4fe 100644 --- a/core/tokens.css +++ b/core/tokens.css @@ -1364,14 +1364,21 @@ --sf-animation-spin: sf-spin var(--sf-duration-slower) linear infinite; --sf-animation-shimmer: sf-shimmer calc(1.5s * var(--sf-motion-scale)) var(--sf-ease-in-out) infinite; - /* Stagger delays — apply to successive items for entrance choreography - (e.g. style="animation-delay: var(--sf-animation-delay-2)"). Tokens - only; SLASHED ships no .sf-stagger utility class (by design — apply via token presets or your own BEM rules). */ - --sf-animation-delay-1: calc(75ms * var(--sf-motion-scale)); - --sf-animation-delay-2: calc(150ms * var(--sf-motion-scale)); - --sf-animation-delay-3: calc(225ms * var(--sf-motion-scale)); - --sf-animation-delay-4: calc(300ms * var(--sf-motion-scale)); - --sf-animation-delay-5: calc(375ms * var(--sf-motion-scale)); + /* Stagger step — the per-item delay increment consumed by the .sf-stagger + utility (core/motion.css). One knob retunes the whole choreography; the + class multiplies it by the child's index and by --sf-motion-scale, so the + rhythm follows the motion knob without baking the scale in here. */ + --sf-stagger-step: 75ms; + + /* Hover-transform magnitudes — the .sf-hover-* utilities (optional/ + utilities.css) read these so the effect strength is one global knob per + axis rather than hard-coded per class. --grow/--shrink are scale factors; + --lift is the float/sink translate distance; --slide is the slide + distance. */ + --sf-hover-grow-scale: 1.05; + --sf-hover-shrink-scale: 0.95; + --sf-hover-lift: 0.25em; + --sf-hover-slide: 0.5em; /* ---------------------------------------------------------- Scroll-driven animation range — consumer API. Attach these diff --git a/demos/full-api-demo-with-overrides.html b/demos/full-api-demo-with-overrides.html index b805c51c..a04dd405 100644 --- a/demos/full-api-demo-with-overrides.html +++ b/demos/full-api-demo-with-overrides.html @@ -122,7 +122,7 @@

SLASHED Full API Demo

-

v0.7.16 · full bundle from jsDelivr CDN · 329 classes · 755 tokens (265 configurable)

+

v0.7.16 · full bundle from jsDelivr CDN · 330 classes · 755 tokens (270 configurable)

ultimate-override.css is ACTIVE. Every value below is recomputed from perturbed knob tokens — toggle it off in the toolbar, or compare against the un-overridden page.
@@ -1108,7 +1108,7 @@

State classes 28

-

Motion / animation 21

+

Motion / animation 22

.sf-color-pulse
@@ -1194,6 +1194,10 @@

Motion / animation 21

.sf-slide-in-up
slide-in-up
+
+
.sf-stagger
+
1
2
3
+
@@ -1303,8 +1307,8 @@

ease 8

--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

transition 10

--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
-

animation 23

-
--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-slide-out-down
--sf-animation-slide-out-left
--sf-animation-slide-out-right
--sf-animation-slide-out-up
--sf-animation-spin
+

animation 18

+
--sf-animation-blink
--sf-animation-color-pulse
--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-slide-out-down
--sf-animation-slide-out-left
--sf-animation-slide-out-right
--sf-animation-slide-out-up
--sf-animation-spin

border 10

--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

focus 5

@@ -1391,6 +1395,8 @@

header 3

--sf-header-height
--sf-header-height-desktop
--sf-header-height-mobile

heading 3

--sf-heading-color
--sf-heading-font-family
--sf-heading-text-wrap
+

hover 4

+
--sf-hover-grow-scale
--sf-hover-lift
--sf-hover-shrink-scale
--sf-hover-slide

icon 10

--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

imposter 1

@@ -1439,6 +1445,8 @@

sidebar 3

--sf-sidebar-gap
--sf-sidebar-min-width
--sf-sidebar-width

stack 1

--sf-stack-gap
+

stagger 1

+
--sf-stagger-step

state 1

--sf-state-pending-opacity

sticky 3

diff --git a/demos/full-api-demo.html b/demos/full-api-demo.html index ff55ae8d..7bb9616c 100644 --- a/demos/full-api-demo.html +++ b/demos/full-api-demo.html @@ -121,7 +121,7 @@

SLASHED Full API Demo

-

v0.7.16 · full bundle from jsDelivr CDN · 329 classes · 755 tokens (265 configurable)

+

v0.7.16 · full bundle from jsDelivr CDN · 330 classes · 755 tokens (270 configurable)

Baseline render with default tokens. Use the toolbar to switch theme, toggle the ultimate override live, or replay motion. The always-on override variant is full-api-demo-with-overrides.html.
@@ -1107,7 +1107,7 @@

State classes 28

-

Motion / animation 21

+

Motion / animation 22

.sf-color-pulse
@@ -1193,6 +1193,10 @@

Motion / animation 21

.sf-slide-in-up
slide-in-up
+
+
.sf-stagger
+
1
2
3
+
@@ -1302,8 +1306,8 @@

ease 8

--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

transition 10

--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
-

animation 23

-
--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-slide-out-down
--sf-animation-slide-out-left
--sf-animation-slide-out-right
--sf-animation-slide-out-up
--sf-animation-spin
+

animation 18

+
--sf-animation-blink
--sf-animation-color-pulse
--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-slide-out-down
--sf-animation-slide-out-left
--sf-animation-slide-out-right
--sf-animation-slide-out-up
--sf-animation-spin

border 10

--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

focus 5

@@ -1390,6 +1394,8 @@

header 3

--sf-header-height
--sf-header-height-desktop
--sf-header-height-mobile

heading 3

--sf-heading-color
--sf-heading-font-family
--sf-heading-text-wrap
+

hover 4

+
--sf-hover-grow-scale
--sf-hover-lift
--sf-hover-shrink-scale
--sf-hover-slide

icon 10

--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

imposter 1

@@ -1438,6 +1444,8 @@

sidebar 3

--sf-sidebar-gap
--sf-sidebar-min-width
--sf-sidebar-width

stack 1

--sf-stack-gap
+

stagger 1

+
--sf-stagger-step

state 1

--sf-state-pending-opacity

sticky 3

diff --git a/demos/generate.mjs b/demos/generate.mjs index 4b3ad94d..5b167267 100644 --- a/demos/generate.mjs +++ b/demos/generate.mjs @@ -192,6 +192,10 @@ const CURATED = { '--sf-lumlocker': '0.5', '--sf-shadow-lightness': '0.4', '--sf-state-pending-opacity': '0.4', + // hover-transform magnitudes (unitless scale factors) + stagger step (time) + '--sf-hover-grow-scale': '1.4', + '--sf-hover-shrink-scale': '0.6', + '--sf-stagger-step': '200ms', // scale knobs → amplify everything downstream '--sf-border-scale': '1.6', '--sf-radius-scale': '1.6', @@ -355,7 +359,16 @@ function example(c) { case 'layout': return layoutExample(n, applied); case 'macro': return macroExample(n, applied); case 'state': return stateExample(n); - case 'motion': return `
${n.replace('sf-', '')}
`; + case 'motion': + // .sf-stagger is a parent-level choreography helper — it needs multiple + // animated children to show anything, unlike the single-element motion + // classes handled by the generic tile below. + if (n === 'sf-stagger') { + return `
${ + [1, 2, 3].map((i) => `
${i}
`).join('') + }
`; + } + return `
${n.replace('sf-', '')}
`; case 'accessibility': return a11yExample(n, applied); case 'print': return printExample(n); case 'theme': return `
${n}
`; diff --git a/demos/ultimate-override.css b/demos/ultimate-override.css index cd41ea64..935c049c 100644 --- a/demos/ultimate-override.css +++ b/demos/ultimate-override.css @@ -5,7 +5,7 @@ Perturbs every CONFIGURABLE (role: "knob") token to a valid, visibly-distinct value so full-api-demo.html can prove each one is wired end to end. - Coverage: 237 of 265 knob tokens overridden. + Coverage: 242 of 270 knob tokens overridden. 28 knobs are intentionally NOT perturbed (structural / meta / sentinel) — listed with reasons at the bottom of this file. ============================================================================ */ @@ -213,6 +213,12 @@ /* heading */ --sf-heading-text-wrap: pretty; /* was: balance */ + /* hover */ + --sf-hover-grow-scale: 1.4; /* was: 1.05 */ + --sf-hover-lift: 0.4em; /* was: 0.25em */ + --sf-hover-shrink-scale: 0.6; /* was: 0.95 */ + --sf-hover-slide: 0.8em; /* was: 0.5em */ + /* icon */ --sf-icon-2xl: 6.4em; /* was: 4em */ --sf-icon-box-pad: 0.8em; /* was: 0.5em */ @@ -335,6 +341,9 @@ --sf-space-ratio-min: 1.4; /* was: 1.25 */ --sf-space-scale: 1.6; /* was: 1 */ + /* stagger */ + --sf-stagger-step: 200ms; /* was: 75ms */ + /* state */ --sf-state-pending-opacity: 0.4; /* was: 0.7 */ diff --git a/dist/css-custom-data.json b/dist/css-custom-data.json index 52028b27..a8ea6069 100644 --- a/dist/css-custom-data.json +++ b/dist/css-custom-data.json @@ -21,31 +21,6 @@ "description": "Pulsing color loop. Use for live-status badges (recording, live). Respect prefers-reduced-motion.\n\nDefault: `sf-color-pulse var(--sf-duration-slow) var(--sf-ease-in-out) infinite`", "syntax": "*" }, - { - "name": "--sf-animation-delay-1", - "description": "Shortest stagger delay. Apply to the first element in a sequentially animated group.\n\nDefault: `calc(75ms * var(--sf-motion-scale))`", - "syntax": "*" - }, - { - "name": "--sf-animation-delay-2", - "description": "Second stagger delay step.\n\nDefault: `calc(150ms * var(--sf-motion-scale))`", - "syntax": "*" - }, - { - "name": "--sf-animation-delay-3", - "description": "Third stagger delay step.\n\nDefault: `calc(225ms * var(--sf-motion-scale))`", - "syntax": "*" - }, - { - "name": "--sf-animation-delay-4", - "description": "Fourth stagger delay step.\n\nDefault: `calc(300ms * var(--sf-motion-scale))`", - "syntax": "*" - }, - { - "name": "--sf-animation-delay-5", - "description": "Longest stagger delay. Apply to the last element in a sequentially animated group.\n\nDefault: `calc(375ms * var(--sf-motion-scale))`", - "syntax": "*" - }, { "name": "--sf-animation-fade-in", "description": "Fade-in entrance animation shorthand. Use directly as animation property value.\n\nDefault: `sf-fade-in var(--sf-duration-normal) var(--sf-ease-out) both`", @@ -2511,6 +2486,26 @@ "description": "text-wrap for headings. 'balance' prevents orphaned short last lines in multi-line headings.\n\nDefault: `balance`", "syntax": "*" }, + { + "name": "--sf-hover-grow-scale", + "description": "Scale factor applied by .sf-hover-grow on hover (>1 grows).\n\nDefault: `1.05`", + "syntax": "*" + }, + { + "name": "--sf-hover-lift", + "description": "Translate distance for .sf-hover-float (up) and .sf-hover-sink (down) on hover.\n\nDefault: `0.25em`", + "syntax": "*" + }, + { + "name": "--sf-hover-shrink-scale", + "description": "Scale factor applied by .sf-hover-shrink on hover (<1 shrinks).\n\nDefault: `0.95`", + "syntax": "*" + }, + { + "name": "--sf-hover-slide", + "description": "Translate distance for .sf-hover-slide-start / .sf-hover-slide-end on hover.\n\nDefault: `0.5em`", + "syntax": "*" + }, { "name": "--sf-icon-2xl", "description": "2× large icon size (~40px+). Hero or onboarding icons.\n\nDefault: `4em`", @@ -3251,6 +3246,11 @@ "description": "Vertical gap between stacked block-level elements in the stack layout.\n\nDefault: `var(--sf-content-gap)`", "syntax": "*" }, + { + "name": "--sf-stagger-step", + "description": "Per-item delay increment for the .sf-stagger utility. One knob retunes the whole staggered sequence; the class multiplies it by each child's index and by --sf-motion-scale.\n\nDefault: `75ms`", + "syntax": "*" + }, { "name": "--sf-state-pending-opacity", "description": "Opacity for pending / loading state elements.\n\nDefault: `0.7`", diff --git a/docs/api-index.json b/docs/api-index.json index 889a8443..b49c965e 100644 --- a/docs/api-index.json +++ b/docs/api-index.json @@ -26,23 +26,23 @@ "optimal" ], "counts": { - "total": 1085, + "total": 1086, "by_type": { - "class": 330, + "class": 331, "token": 755 }, "by_tier": { "INTERNAL": 1, - "PUBLIC": 1025, + "PUBLIC": 1026, "PUBLIC-ADVANCED": 59 }, "by_role": { - "consumption": 490, - "knob": 265 + "consumption": 485, + "knob": 270 }, "tokens": 755, - "classes": 330, - "sf_classes": 293, + "classes": 331, + "sf_classes": 294, "is_classes": 28, "unprefixed_classes": 9, "by_category": { @@ -55,7 +55,7 @@ "Layout tokens": 53, "Macro classes": 58, "Macro tokens": 34, - "Motion / animation": 21, + "Motion / animation": 22, "Print": 4, "State classes": 28, "Theme example": 1, @@ -195,136 +195,6 @@ "optimal" ] }, - { - "name": "--sf-animation-delay-1", - "type": "token", - "tier": "PUBLIC", - "role": "consumption", - "namespace": "animation", - "category": "Core tokens", - "area": "core", - "group": "Animation presets", - "description": "Shortest stagger delay. Apply to the first element in a sequentially animated group.", - "value": "calc(75ms * var(--sf-motion-scale))", - "aliasOf": null, - "registered": false, - "animatable": false, - "syntax": null, - "inherits": null, - "optional": false, - "layer": "slashed.tokens", - "sourceFiles": [ - "core/tokens.css" - ], - "bundles": [ - "full", - "optimal" - ] - }, - { - "name": "--sf-animation-delay-2", - "type": "token", - "tier": "PUBLIC", - "role": "consumption", - "namespace": "animation", - "category": "Core tokens", - "area": "core", - "group": "Animation presets", - "description": "Second stagger delay step.", - "value": "calc(150ms * var(--sf-motion-scale))", - "aliasOf": null, - "registered": false, - "animatable": false, - "syntax": null, - "inherits": null, - "optional": false, - "layer": "slashed.tokens", - "sourceFiles": [ - "core/tokens.css" - ], - "bundles": [ - "full", - "optimal" - ] - }, - { - "name": "--sf-animation-delay-3", - "type": "token", - "tier": "PUBLIC", - "role": "consumption", - "namespace": "animation", - "category": "Core tokens", - "area": "core", - "group": "Animation presets", - "description": "Third stagger delay step.", - "value": "calc(225ms * var(--sf-motion-scale))", - "aliasOf": null, - "registered": false, - "animatable": false, - "syntax": null, - "inherits": null, - "optional": false, - "layer": "slashed.tokens", - "sourceFiles": [ - "core/tokens.css" - ], - "bundles": [ - "full", - "optimal" - ] - }, - { - "name": "--sf-animation-delay-4", - "type": "token", - "tier": "PUBLIC", - "role": "consumption", - "namespace": "animation", - "category": "Core tokens", - "area": "core", - "group": "Animation presets", - "description": "Fourth stagger delay step.", - "value": "calc(300ms * var(--sf-motion-scale))", - "aliasOf": null, - "registered": false, - "animatable": false, - "syntax": null, - "inherits": null, - "optional": false, - "layer": "slashed.tokens", - "sourceFiles": [ - "core/tokens.css" - ], - "bundles": [ - "full", - "optimal" - ] - }, - { - "name": "--sf-animation-delay-5", - "type": "token", - "tier": "PUBLIC", - "role": "consumption", - "namespace": "animation", - "category": "Core tokens", - "area": "core", - "group": "Animation presets", - "description": "Longest stagger delay. Apply to the last element in a sequentially animated group.", - "value": "calc(375ms * var(--sf-motion-scale))", - "aliasOf": null, - "registered": false, - "animatable": false, - "syntax": null, - "inherits": null, - "optional": false, - "layer": "slashed.tokens", - "sourceFiles": [ - "core/tokens.css" - ], - "bundles": [ - "full", - "optimal" - ] - }, { "name": "--sf-animation-fade-in", "type": "token", @@ -13097,6 +12967,110 @@ "optimal" ] }, + { + "name": "--sf-hover-grow-scale", + "type": "token", + "tier": "PUBLIC", + "role": "knob", + "namespace": "hover", + "category": "Core tokens", + "area": "core", + "group": "Animation presets", + "description": "Scale factor applied by .sf-hover-grow on hover (>1 grows).", + "value": "1.05", + "aliasOf": null, + "registered": false, + "animatable": false, + "syntax": null, + "inherits": null, + "optional": false, + "layer": "slashed.tokens", + "sourceFiles": [ + "core/tokens.css" + ], + "bundles": [ + "full", + "optimal" + ] + }, + { + "name": "--sf-hover-lift", + "type": "token", + "tier": "PUBLIC", + "role": "knob", + "namespace": "hover", + "category": "Core tokens", + "area": "core", + "group": "Animation presets", + "description": "Translate distance for .sf-hover-float (up) and .sf-hover-sink (down) on hover.", + "value": "0.25em", + "aliasOf": null, + "registered": false, + "animatable": false, + "syntax": null, + "inherits": null, + "optional": false, + "layer": "slashed.tokens", + "sourceFiles": [ + "core/tokens.css" + ], + "bundles": [ + "full", + "optimal" + ] + }, + { + "name": "--sf-hover-shrink-scale", + "type": "token", + "tier": "PUBLIC", + "role": "knob", + "namespace": "hover", + "category": "Core tokens", + "area": "core", + "group": "Animation presets", + "description": "Scale factor applied by .sf-hover-shrink on hover (<1 shrinks).", + "value": "0.95", + "aliasOf": null, + "registered": false, + "animatable": false, + "syntax": null, + "inherits": null, + "optional": false, + "layer": "slashed.tokens", + "sourceFiles": [ + "core/tokens.css" + ], + "bundles": [ + "full", + "optimal" + ] + }, + { + "name": "--sf-hover-slide", + "type": "token", + "tier": "PUBLIC", + "role": "knob", + "namespace": "hover", + "category": "Core tokens", + "area": "core", + "group": "Animation presets", + "description": "Translate distance for .sf-hover-slide-start / .sf-hover-slide-end on hover.", + "value": "0.5em", + "aliasOf": null, + "registered": false, + "animatable": false, + "syntax": null, + "inherits": null, + "optional": false, + "layer": "slashed.tokens", + "sourceFiles": [ + "core/tokens.css" + ], + "bundles": [ + "full", + "optimal" + ] + }, { "name": "--sf-icon-2xl", "type": "token", @@ -16971,6 +16945,32 @@ "optimal" ] }, + { + "name": "--sf-stagger-step", + "type": "token", + "tier": "PUBLIC", + "role": "knob", + "namespace": "stagger", + "category": "Core tokens", + "area": "core", + "group": "Animation presets", + "description": "Per-item delay increment for the .sf-stagger utility. One knob retunes the whole staggered sequence; the class multiplies it by each child's index and by --sf-motion-scale.", + "value": "75ms", + "aliasOf": null, + "registered": false, + "animatable": false, + "syntax": null, + "inherits": null, + "optional": false, + "layer": "slashed.tokens", + "sourceFiles": [ + "core/tokens.css" + ], + "bundles": [ + "full", + "optimal" + ] + }, { "name": "--sf-state-pending-opacity", "type": "token", @@ -26279,6 +26279,29 @@ "optimal" ] }, + { + "name": "sf-stagger", + "type": "class", + "tier": "PUBLIC", + "selector": ".sf-stagger", + "prefix": "sf", + "kind": "motion", + "category": "Motion / animation", + "area": "motion", + "group": "", + "description": "Stagger — choreography, not the animation itself. Put .sf-stagger on a PARENT; every direct child gets an incrementing animation-delay so a time-based entrance (.sf-fade-in / .sf-slide-in-*) plays in sequence. Children WITHOUT an animation just carry an inert delay (no-op), so…", + "isVariant": false, + "baseClass": null, + "optional": false, + "layer": "slashed.motion", + "sourceFiles": [ + "core/motion.css" + ], + "bundles": [ + "full", + "optimal" + ] + }, { "name": "sf-sticky", "type": "class", diff --git a/docs/api-index.md b/docs/api-index.md index 1a1fde20..2313c27b 100644 --- a/docs/api-index.md +++ b/docs/api-index.md @@ -10,11 +10,11 @@ and a short description. The machine-readable companion (with all columns) is [registry.json](registry.json); for the tier contract see [architecture.md](architecture.md). -**1085 elements** — 755 tokens, 330 classes. +**1086 elements** — 755 tokens, 331 classes. | Tier | Count | Meaning | |---|---|---| -| PUBLIC | 1025 | Everyday surface. SemVer-stable. | +| PUBLIC | 1026 | Everyday surface. SemVer-stable. | | PUBLIC-ADVANCED | 59 | Same SemVer guarantee; niche/powerful. | | INTERNAL | 1 | Implementation detail; may change without a major bump. | @@ -77,11 +77,6 @@ and a short description. The machine-readable companion (with all columns) is |---|---|---|---|---|---| | `--sf-animation-blink` | PUBLIC | consumption | animation | `sf-blink calc(1s * var(--sf-motion-scale)) steps(1, end) infinite` | Blinking visibility loop. Use for cursor indicators or critical attention cues. Respect prefers-reduced-motion. | | `--sf-animation-color-pulse` | PUBLIC | consumption | animation | `sf-color-pulse var(--sf-duration-slow) var(--sf-ease-in-out) infinite` | Pulsing color loop. Use for live-status badges (recording, live). Respect prefers-reduced-motion. | -| `--sf-animation-delay-1` | PUBLIC | consumption | animation | `calc(75ms * var(--sf-motion-scale))` | Shortest stagger delay. Apply to the first element in a sequentially animated group. | -| `--sf-animation-delay-2` | PUBLIC | consumption | animation | `calc(150ms * var(--sf-motion-scale))` | Second stagger delay step. | -| `--sf-animation-delay-3` | PUBLIC | consumption | animation | `calc(225ms * var(--sf-motion-scale))` | Third stagger delay step. | -| `--sf-animation-delay-4` | PUBLIC | consumption | animation | `calc(300ms * var(--sf-motion-scale))` | Fourth stagger delay step. | -| `--sf-animation-delay-5` | PUBLIC | consumption | animation | `calc(375ms * var(--sf-motion-scale))` | Longest stagger delay. Apply to the last element in a sequentially animated group. | | `--sf-animation-fade-in` | PUBLIC | consumption | animation | `sf-fade-in var(--sf-duration-normal) var(--sf-ease-out) both` | Fade-in entrance animation shorthand. Use directly as animation property value. | | `--sf-animation-fade-out` | PUBLIC | consumption | animation | `sf-fade-out var(--sf-duration-normal) var(--sf-ease-in) both` | Fade-out exit animation shorthand. | | `--sf-animation-float` | PUBLIC | consumption | animation | `sf-float calc(3s * var(--sf-motion-scale)) var(--sf-ease-in-out) infinite` | Gentle floating / levitating loop. Use for hero illustrations or floating UI elements. Decorative — disable for prefers-reduced-motion. | @@ -488,6 +483,10 @@ and a short description. The machine-readable companion (with all columns) is | `--sf-heading-color` | PUBLIC | consumption | heading | `var(--sf-color-heading)` | Text color for all headings (h1–h6). Alias of --sf-color-heading. | | `--sf-heading-font-family` | PUBLIC | consumption | heading | `var(--sf-font-heading)` | Font family for all headings. Alias of --sf-font-heading. | | `--sf-heading-text-wrap` | PUBLIC | knob | heading | `balance` | text-wrap for headings. 'balance' prevents orphaned short last lines in multi-line headings. | +| `--sf-hover-grow-scale` | PUBLIC | knob | hover | `1.05` | Scale factor applied by .sf-hover-grow on hover (>1 grows). | +| `--sf-hover-lift` | PUBLIC | knob | hover | `0.25em` | Translate distance for .sf-hover-float (up) and .sf-hover-sink (down) on hover. | +| `--sf-hover-shrink-scale` | PUBLIC | knob | hover | `0.95` | Scale factor applied by .sf-hover-shrink on hover (<1 shrinks). | +| `--sf-hover-slide` | PUBLIC | knob | hover | `0.5em` | Translate distance for .sf-hover-slide-start / .sf-hover-slide-end on hover. | | `--sf-icon-2xl` | PUBLIC | knob | icon | `4em` | 2× large icon size (~40px+). Hero or onboarding icons. | | `--sf-icon-l` | PUBLIC | knob | icon | `2em` | Large icon size (~24px). Standalone icon buttons. | | `--sf-icon-m` | PUBLIC | knob | icon | `1.5em` | Medium icon size (~20px). Default for inline and button icons. | @@ -604,6 +603,7 @@ and a short description. The machine-readable companion (with all columns) is | `--sf-space-scale` | PUBLIC-ADVANCED | knob | space | `1` | Global spacing multiplier. Scales the fluid spacing ramp. Increase above 1 for airier layouts; reduce below 1 for compact UIs. | | `--sf-space-xl` | PUBLIC | consumption | space | `calc(clamp(calc(var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), 2) * 1rem), calc((var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), 2) - var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), 2)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), 2) * 1rem), calc(var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), 2) * 1rem)) * var(--sf-space-scale))` | 32px-equivalent spacing. Feature row gaps, generous section padding. | | `--sf-space-xs` | PUBLIC | consumption | space | `calc(clamp(calc(var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), -2) * 1rem), calc((var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), -2) - var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), -2)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), -2) * 1rem), calc(var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), -2) * 1rem)) * var(--sf-space-scale))` | 8px-equivalent spacing. Compact list rows, tab padding. | +| `--sf-stagger-step` | PUBLIC | knob | stagger | `75ms` | Per-item delay increment for the .sf-stagger utility. One knob retunes the whole staggered sequence; the class multiplies it by each child's index and by --sf-motion-scale. | | `--sf-state-pending-opacity` | PUBLIC | knob | state | `0.7` | Opacity for pending / loading state elements. | | `--sf-sticky-offset` | PUBLIC | consumption | sticky | `clamp( var(--sf-sticky-offset-mobile), calc((var(--sf-sticky-offset-desktop) - var(--sf-sticky-offset-mobile)) / ((var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * 1rem) * (100vw - var(--sf-fluid-min-vw) * 1rem) + var(--sf-sticky-offset-mobile)), var(--sf-sticky-offset-desktop))` | top offset for position: sticky elements, accounting for the header height. | | `--sf-sticky-offset-desktop` | PUBLIC | consumption | sticky | `var(--sf-header-height-desktop)` | Sticky offset at desktop breakpoints. | @@ -795,7 +795,7 @@ and a short description. The machine-readable companion (with all columns) is | `--sf-surface-bg-size` | PUBLIC | knob | surface | `cover` | background-size for the .sf-surface-bg image. Default: cover. | | `--sf-surface-color` | PUBLIC | consumption | surface | `var(--sf-color-base)` | Input for the generic .sf-surface macro. Set any color (including palette shades); the macro derives background, auto-contrast foreground, and the contextual token set from it. | -## Classes (330) +## Classes (331) ### Accessibility (8) @@ -1064,7 +1064,7 @@ and a short description. The machine-readable companion (with all columns) is | `.sf-text-protect` | PUBLIC | macro | Text protect | Wraps text in a translucent dark gradient overlay to ensure legibility when placed over a light or variable background image. | | `.sf-truncate` | PUBLIC | macro | Truncate / line-clamp | Single-line text overflow with ellipsis. Equivalent to the classic text-overflow: ellipsis trio (white-space nowrap, overflow hidden, text-overflow ellipsis). | -### Motion / animation (21) +### Motion / animation (22) | Class | Tier | Kind | Group | Description | |---|---|---|---|---| @@ -1089,6 +1089,7 @@ and a short description. The machine-readable companion (with all columns) is | `.sf-slide-in-left` | PUBLIC | motion | Animation presets | One-shot slide-in-from-right animation (slides to the left). Scoped to no-preference. | | `.sf-slide-in-right` | PUBLIC | motion | Animation presets | One-shot slide-in-from-left animation (slides to the right). Scoped to no-preference. | | `.sf-slide-in-up` | PUBLIC | motion | Animation presets | One-shot slide-in-from-below animation. Scoped to no-preference. | +| `.sf-stagger` | PUBLIC | motion | — | Stagger — choreography, not the animation itself. Put .sf-stagger on a PARENT; every direct child gets an incrementing animation-delay so a time-based entrance (.sf-fade-in / .sf-slide-in-*) plays in sequence. Children WITHOUT an animation just carry an inert delay (no-op), so… | ### Print (4) diff --git a/docs/classes.md b/docs/classes.md index a39eb213..6f893ab6 100644 --- a/docs/classes.md +++ b/docs/classes.md @@ -3,7 +3,7 @@ > **Generated** from source by `scripts/gen-class-reference.js` — > run `npm run docs:classes` to refresh. Do not edit by hand. -**293 .sf-classes, 28 .sf-is-classes.** Every `.sf-*` layout/macro class and `.sf-is-*` state class. +**294 .sf-classes, 28 .sf-is-classes.** Every `.sf-*` layout/macro class and `.sf-is-*` state class. See [architecture.md](architecture.md) for layer order and naming conventions. ## Layout primitives (`core/layout.css`) @@ -273,7 +273,7 @@ See [architecture.md](architecture.md) for layer order and naming conventions. ## Motion / entrances (`core/motion.css`) -21 classes. +22 classes. | Class | |---| @@ -298,6 +298,7 @@ See [architecture.md](architecture.md) for layer order and naming conventions. | `.sf-slide-in-left` | | `.sf-slide-in-right` | | `.sf-slide-in-up` | +| `.sf-stagger` | ## Print utilities (`core/print.css`) diff --git a/docs/demo.html b/docs/demo.html index 5a55268d..b96bec22 100644 --- a/docs/demo.html +++ b/docs/demo.html @@ -1299,6 +1299,14 @@

Animation Presets scale-up +

.sf-stagger (motion.css — parent choreographs children's animation-delay; click to replay)

+
    +
  • 1
  • +
  • 2
  • +
  • 3
  • +
  • 4
  • +
+

@property Color Interpolation (motion.css — sf-color-pulse)

Demonstrates smooth oklch color animation via @property-registered tokens. @@ -3160,6 +3168,19 @@

.sf-exit--* — s }); }); + // Replay the stagger demo on click — reset every child so the staggered + // entrance plays again (the delays only fire once on load otherwise). + const staggerDemo = document.getElementById('demo-stagger'); + if (staggerDemo) { + staggerDemo.addEventListener('click', () => { + staggerDemo.querySelectorAll('*').forEach(child => { + child.style.animation = 'none'; + void child.offsetWidth; // force reflow + child.style.animation = ''; + }); + }); + } + // Fade-out demo: separate trigger button + target, so the trigger stays // visible while the target plays the animation (which ends at opacity: 0). document.querySelectorAll('[data-fade-out-trigger]').forEach(btn => { diff --git a/docs/llm-guide.md b/docs/llm-guide.md index 4759c759..b8814ad7 100644 --- a/docs/llm-guide.md +++ b/docs/llm-guide.md @@ -1004,6 +1004,15 @@ Ready-made hover transforms: `.sf-hover-grow` / `-shrink` / `-float` / `-sink` / ``` +The magnitude of each effect is one global knob (override on `:root` or any scope): + +```css +--sf-hover-grow-scale /* .sf-hover-grow scale factor, default 1.05 */ +--sf-hover-shrink-scale /* .sf-hover-shrink scale factor, default 0.95 */ +--sf-hover-lift /* .sf-hover-float / -sink distance, default 0.25em */ +--sf-hover-slide /* .sf-hover-slide-start / -end distance, default 0.5em */ +``` + Small opt-in helpers (all `optional/utilities.css`): - `.sf-list-none` — drop list marker + inline-start padding in one class. @@ -1027,8 +1036,14 @@ Ready-made `animation` values — keyframe + duration + easing + fill-mode. --sf-animation-shimmer /* Skeleton loader */ --sf-animation-color-pulse /* Live status badge */ -/* Stagger delays — scaled by motion-scale */ ---sf-animation-delay-1 / -2 / -3 / -4 / -5 /* 75ms … 375ms */ +/* Stagger — put .sf-stagger on a parent; each direct child gets an + incrementing animation-delay = index × --sf-stagger-step × --sf-motion-scale. + Index is unbounded where sibling-index() is supported, else an 8-step + :nth-child fallback that plateaus (children 9+ share the last delay). + Gated by prefers-reduced-motion: no-preference. Pair with the time-based + looping classes (.sf-fade-in / .sf-slide-in-*); the scroll-driven + .sf-entrance--*/.sf-exit--* path is not staggered (it uses animation-range). */ +--sf-stagger-step /* per-item delay increment, default 75ms */ ``` ### 9.5 Scroll-driven animations @@ -1427,9 +1442,15 @@ a:visited { color: var(--sf-color-link--visited); } animation: var(--sf-animation-slide-in-up); } -/* Staggered list */ -.list-item:nth-child(1) { animation-delay: var(--sf-animation-delay-1); } -.list-item:nth-child(2) { animation-delay: var(--sf-animation-delay-2); } +/* Staggered list — .sf-stagger on the parent choreographs every child; each + item still needs its own entrance animation (here .sf-fade-in). */ +``` +```html +
    +
  • First
  • +
  • Second
  • + +
``` ### Section-level dark mode diff --git a/docs/macros.md b/docs/macros.md index 9ee517bf..de99c0a5 100644 --- a/docs/macros.md +++ b/docs/macros.md @@ -690,6 +690,49 @@ Lives in `core/motion.css`, layer `slashed.motion`. --- +## `.sf-stagger` + +Choreography helper: put it on a **parent** and every direct child receives +an incrementing `animation-delay`, so a group of time-based entrance +animations plays in sequence. + +```html +
    +
  • First
  • +
  • Second
  • +
  • Third
  • +
+``` + +`.sf-stagger` sets **only** the delay — each child still needs its own +time-based entrance animation (the fade / slide-in looping classes in +[motion.md](./motion.md)). A child without one carries an inert delay (a +no-op), so animating only some children needs no opt-out on the rest. + +Tokens: + +| Token | Default | What it controls | +|---|---|---| +| `--sf-stagger-step` | `75ms` | per-item delay increment | + +Each child's delay is `index × --sf-stagger-step × --sf-motion-scale`. Where +`sibling-index()` is supported the index is unbounded; otherwise an 8-step +`:nth-child` ramp (covering a 4-column grid's first two rows) plateaus so +arbitrarily long lists still animate. + +**Best paired with the time-based fade / slide-in looping classes** (see +[motion.md](./motion.md)), which stagger consistently everywhere. On the +scroll-driven path (`.sf-entrance--*`/`.sf-exit--*` under +`animation-timeline: view()`) the rhythm is `animation-range`, not +`animation-delay`, so stagger has no effect there — though `.sf-entrance--*` +does stagger in its time-based fallback on engines without `view()`, while +`.sf-exit--*` has no such fallback. Gated by +`prefers-reduced-motion: no-preference`. + +Lives in `core/motion.css`, layer `slashed.motion`. + +--- + ## `.sf-corner-scoop` A corner that curves **away** from the box (a concave "notch"), instead diff --git a/docs/motion.md b/docs/motion.md index 32311370..f3c8f645 100644 --- a/docs/motion.md +++ b/docs/motion.md @@ -70,7 +70,9 @@ your own components: ### Entrance animations Apply one of these classes to trigger a one-shot entrance animation. -Combine with a `--sf-animation-delay-*` token for staggered sequences. +These are scroll-driven (their timing is `animation-range`, not +`animation-delay`); for staggering a group of **time-based** entrances +(`.sf-fade-in` / `.sf-slide-in-*`), see [Stagger](#stagger) below. | Class | Effect | |---|---| @@ -110,25 +112,42 @@ All keyframe names use the `sf-` prefix: - `sf-float`, `sf-ping`, `sf-spin`, `sf-shimmer`, `sf-blink` - `sf-color-pulse` -## Stagger delays +## Stagger -Use the delay tokens to stagger children: +Put `.sf-stagger` on a **parent**; every direct child gets an incrementing +`animation-delay`, so a time-based entrance plays in sequence. `.sf-stagger` +is choreography only — each child still needs its own animation +(`.sf-fade-in`, `.sf-slide-in-*`, …). Children without one just carry an +inert delay, so you opt in per child with nothing to exclude. ```html -
First
-
Second
-
Third
+
    +
  • First
  • +
  • Second
  • +
  • Third
  • + +
``` +The per-item increment is one knob: + | Token | Value | |---|---| -| `--sf-animation-delay-1` | `75ms` | -| `--sf-animation-delay-2` | `150ms` | -| `--sf-animation-delay-3` | `225ms` | -| `--sf-animation-delay-4` | `300ms` | -| `--sf-animation-delay-5` | `375ms` | +| `--sf-stagger-step` | `75ms` | + +Each child's delay is `index × --sf-stagger-step × --sf-motion-scale`. + +Where the browser supports `sibling-index()` the ramp is unbounded; older +engines fall back to an 8-step `:nth-child` ramp (covering a 4-column grid's +first two rows) that then plateaus, so arbitrarily long lists still animate. -> Delay values are also scaled by `--sf-motion-scale`. +> Best paired with the time-based looping classes (`.sf-fade-in` / +> `.sf-slide-in-*`), which stagger consistently everywhere. On the +> scroll-driven path — `.sf-entrance--*`/`.sf-exit--*` under +> `animation-timeline: view()` — the rhythm is `animation-range`, not +> `animation-delay`, so stagger has no effect there. (`.sf-entrance--*` +> does fall back to a time-based one-shot on engines without `view()`, +> where the delay *does* apply; `.sf-exit--*` has no such fallback.) ## Motion scale diff --git a/docs/registry.json b/docs/registry.json index 0e65ba51..bfafa997 100644 --- a/docs/registry.json +++ b/docs/registry.json @@ -21,7 +21,7 @@ ], "counts": { "tokens": 755, - "sf_classes": 293, + "sf_classes": 294, "is_classes": 28 } }, @@ -30,11 +30,6 @@ "--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", @@ -528,6 +523,10 @@ "--sf-heading-color", "--sf-heading-font-family", "--sf-heading-text-wrap", + "--sf-hover-grow-scale", + "--sf-hover-lift", + "--sf-hover-shrink-scale", + "--sf-hover-slide", "--sf-icon-2xl", "--sf-icon-box-bg", "--sf-icon-box-border", @@ -677,6 +676,7 @@ "--sf-space-xl", "--sf-space-xs", "--sf-stack-gap", + "--sf-stagger-step", "--sf-state-pending-opacity", "--sf-sticky-offset", "--sf-sticky-offset-desktop", @@ -1039,6 +1039,7 @@ "sf-stack--stretch", "sf-stack--xl", "sf-stack--xs", + "sf-stagger", "sf-sticky", "sf-sticky--l", "sf-sticky--m", diff --git a/docs/test-coverage-6-token-reference.html b/docs/test-coverage-6-token-reference.html index f289e227..d8b1d7a8 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-slide-out-down","--sf-animation-slide-out-left","--sf-animation-slide-out-right","--sf-animation-slide-out-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-layer-fit","--sf-bg-layer-inset","--sf-bg-layer-position","--sf-bg-layer-radius","--sf-bg-layer-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-scale","--sf-btn-font-size","--sf-btn-font-weight","--sf-btn-gap","--sf-btn-l-font-size","--sf-btn-l-min-height","--sf-btn-l-padding-block","--sf-btn-l-padding-inline","--sf-btn-m-font-size","--sf-btn-m-min-height","--sf-btn-m-padding-block","--sf-btn-m-padding-inline","--sf-btn-min-height","--sf-btn-padding-block","--sf-btn-padding-inline","--sf-btn-radius","--sf-btn-s-font-size","--sf-btn-s-min-height","--sf-btn-s-padding-block","--sf-btn-s-padding-inline","--sf-btn-xl-font-size","--sf-btn-xl-min-height","--sf-btn-xl-padding-block","--sf-btn-xl-padding-inline","--sf-btn-xs-font-size","--sf-btn-xs-min-height","--sf-btn-xs-padding-block","--sf-btn-xs-padding-inline","--sf-card-avatar-size","--sf-card-bg","--sf-card-border-color","--sf-card-border-width","--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-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-tint","--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-muted","--sf-color-base-source-dark","--sf-color-base-source-light","--sf-color-base-subtle","--sf-color-base-tint","--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-danger-tint","--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-info-tint","--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-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-tint","--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-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-tint","--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-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-tint","--sf-color-secondary-xdark","--sf-color-secondary-xlight","--sf-color-selection-bg","--sf-color-selection-bg--alt","--sf-color-selection-text","--sf-color-selection-text--alt","--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-success-tint","--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-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-tint","--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-warning-tint","--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-drop-shadow-xl","--sf-drop-shadow-xs","--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-fluid-width","--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-action","--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-label","--sf-link-external-marker","--sf-link-underline-offset","--sf-link-underline-thickness","--sf-lumlocker","--sf-mask-scrim-end","--sf-mask-scrim-start","--sf-media-radius","--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-exit-end","--sf-scroll-timeline-range-exit-start","--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-bg-animation","--sf-surface-bg-attachment","--sf-surface-bg-color","--sf-surface-bg-image","--sf-surface-bg-overlay","--sf-surface-bg-position","--sf-surface-bg-repeat","--sf-surface-bg-size","--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-shadow-xl","--sf-text-shadow-xs","--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-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-slide-out-down","--sf-animation-slide-out-left","--sf-animation-slide-out-right","--sf-animation-slide-out-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-layer-fit","--sf-bg-layer-inset","--sf-bg-layer-position","--sf-bg-layer-radius","--sf-bg-layer-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-scale","--sf-btn-font-size","--sf-btn-font-weight","--sf-btn-gap","--sf-btn-l-font-size","--sf-btn-l-min-height","--sf-btn-l-padding-block","--sf-btn-l-padding-inline","--sf-btn-m-font-size","--sf-btn-m-min-height","--sf-btn-m-padding-block","--sf-btn-m-padding-inline","--sf-btn-min-height","--sf-btn-padding-block","--sf-btn-padding-inline","--sf-btn-radius","--sf-btn-s-font-size","--sf-btn-s-min-height","--sf-btn-s-padding-block","--sf-btn-s-padding-inline","--sf-btn-xl-font-size","--sf-btn-xl-min-height","--sf-btn-xl-padding-block","--sf-btn-xl-padding-inline","--sf-btn-xs-font-size","--sf-btn-xs-min-height","--sf-btn-xs-padding-block","--sf-btn-xs-padding-inline","--sf-card-avatar-size","--sf-card-bg","--sf-card-border-color","--sf-card-border-width","--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-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-tint","--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-muted","--sf-color-base-source-dark","--sf-color-base-source-light","--sf-color-base-subtle","--sf-color-base-tint","--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-danger-tint","--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-info-tint","--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-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-tint","--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-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-tint","--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-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-tint","--sf-color-secondary-xdark","--sf-color-secondary-xlight","--sf-color-selection-bg","--sf-color-selection-bg--alt","--sf-color-selection-text","--sf-color-selection-text--alt","--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-success-tint","--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-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-tint","--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-warning-tint","--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-drop-shadow-xl","--sf-drop-shadow-xs","--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-fluid-width","--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-action","--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-hover-grow-scale","--sf-hover-lift","--sf-hover-shrink-scale","--sf-hover-slide","--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-label","--sf-link-external-marker","--sf-link-underline-offset","--sf-link-underline-thickness","--sf-lumlocker","--sf-mask-scrim-end","--sf-mask-scrim-start","--sf-media-radius","--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-exit-end","--sf-scroll-timeline-range-exit-start","--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-stagger-step","--sf-state-pending-opacity","--sf-sticky-offset","--sf-sticky-offset-desktop","--sf-sticky-offset-mobile","--sf-surface-bg-animation","--sf-surface-bg-attachment","--sf-surface-bg-color","--sf-surface-bg-image","--sf-surface-bg-overlay","--sf-surface-bg-position","--sf-surface-bg-repeat","--sf-surface-bg-size","--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-shadow-xl","--sf-text-shadow-xs","--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 63b3a2be..65114ab1 100644 --- a/docs/token-annotations.json +++ b/docs/token-annotations.json @@ -407,11 +407,11 @@ "--sf-color-dim": "Backdrop overlay color for modals, drawers, and lightboxes.", "--sf-animation-blink": "Blinking visibility loop. Use for cursor indicators or critical attention cues. Respect prefers-reduced-motion.", "--sf-animation-color-pulse": "Pulsing color loop. Use for live-status badges (recording, live). Respect prefers-reduced-motion.", - "--sf-animation-delay-1": "Shortest stagger delay. Apply to the first element in a sequentially animated group.", - "--sf-animation-delay-2": "Second stagger delay step.", - "--sf-animation-delay-3": "Third stagger delay step.", - "--sf-animation-delay-4": "Fourth stagger delay step.", - "--sf-animation-delay-5": "Longest stagger delay. Apply to the last element in a sequentially animated group.", + "--sf-stagger-step": "Per-item delay increment for the .sf-stagger utility. One knob retunes the whole staggered sequence; the class multiplies it by each child's index and by --sf-motion-scale.", + "--sf-hover-grow-scale": "Scale factor applied by .sf-hover-grow on hover (>1 grows).", + "--sf-hover-shrink-scale": "Scale factor applied by .sf-hover-shrink on hover (<1 shrinks).", + "--sf-hover-lift": "Translate distance for .sf-hover-float (up) and .sf-hover-sink (down) on hover.", + "--sf-hover-slide": "Translate distance for .sf-hover-slide-start / .sf-hover-slide-end on hover.", "--sf-animation-fade-in": "Fade-in entrance animation shorthand. Use directly as animation property value.", "--sf-animation-fade-out": "Fade-out exit animation shorthand.", "--sf-animation-float": "Gentle floating / levitating loop. Use for hero illustrations or floating UI elements. Decorative — disable for prefers-reduced-motion.", diff --git a/docs/token-index.json b/docs/token-index.json index 468b3a97..6a7b0e06 100644 --- a/docs/token-index.json +++ b/docs/token-index.json @@ -15,8 +15,8 @@ "INTERNAL": 1 }, "by_role": { - "knob": 265, - "consumption": 490 + "knob": 270, + "consumption": 485 } } }, @@ -53,46 +53,6 @@ ], "value": "sf-color-pulse var(--sf-duration-slow) var(--sf-ease-in-out) infinite" }, - "--sf-animation-delay-1": { - "tier": "PUBLIC", - "role": "consumption", - "files": [ - "core/tokens.css" - ], - "value": "calc(75ms * var(--sf-motion-scale))" - }, - "--sf-animation-delay-2": { - "tier": "PUBLIC", - "role": "consumption", - "files": [ - "core/tokens.css" - ], - "value": "calc(150ms * var(--sf-motion-scale))" - }, - "--sf-animation-delay-3": { - "tier": "PUBLIC", - "role": "consumption", - "files": [ - "core/tokens.css" - ], - "value": "calc(225ms * var(--sf-motion-scale))" - }, - "--sf-animation-delay-4": { - "tier": "PUBLIC", - "role": "consumption", - "files": [ - "core/tokens.css" - ], - "value": "calc(300ms * var(--sf-motion-scale))" - }, - "--sf-animation-delay-5": { - "tier": "PUBLIC", - "role": "consumption", - "files": [ - "core/tokens.css" - ], - "value": "calc(375ms * var(--sf-motion-scale))" - }, "--sf-animation-fade-in": { "tier": "PUBLIC", "role": "consumption", @@ -4037,6 +3997,38 @@ ], "value": "balance" }, + "--sf-hover-grow-scale": { + "tier": "PUBLIC", + "role": "knob", + "files": [ + "core/tokens.css" + ], + "value": "1.05" + }, + "--sf-hover-lift": { + "tier": "PUBLIC", + "role": "knob", + "files": [ + "core/tokens.css" + ], + "value": "0.25em" + }, + "--sf-hover-shrink-scale": { + "tier": "PUBLIC", + "role": "knob", + "files": [ + "core/tokens.css" + ], + "value": "0.95" + }, + "--sf-hover-slide": { + "tier": "PUBLIC", + "role": "knob", + "files": [ + "core/tokens.css" + ], + "value": "0.5em" + }, "--sf-icon-2xl": { "tier": "PUBLIC", "role": "knob", @@ -5229,6 +5221,14 @@ ], "value": "var(--sf-content-gap)" }, + "--sf-stagger-step": { + "tier": "PUBLIC", + "role": "knob", + "files": [ + "core/tokens.css" + ], + "value": "75ms" + }, "--sf-state-pending-opacity": { "tier": "PUBLIC", "role": "knob", diff --git a/docs/token-index.md b/docs/token-index.md index 70cf76cf..7725350a 100644 --- a/docs/token-index.md +++ b/docs/token-index.md @@ -22,8 +22,8 @@ declared value (a value that references `var(--sf-…)` is a derived output): | Role | Count | Meaning | |---|---|---| -| knob | 265 | Input you **set** to configure the system (a literal primitive: length, number, colour literal, keyword, font stack, easing curve …). | -| consumption | 490 | Ready-to-use output you **read**; derived from other tokens via `var(--sf-…)` (incl. `light-dark()`/`oklch(from …)`/`color-mix()`). | +| knob | 270 | Input you **set** to configure the system (a literal primitive: length, number, colour literal, keyword, font stack, easing curve …). | +| consumption | 485 | Ready-to-use output you **read**; derived from other tokens via `var(--sf-…)` (incl. `light-dark()`/`oklch(from …)`/`color-mix()`). | ## INTERNAL tokens @@ -99,11 +99,6 @@ declared value (a value that references `var(--sf-…)` is a derived output): | `--sf-alternate-inner-gap` | PUBLIC | consumption | Layout | `var(--sf-gap)` | | `--sf-animation-blink` | PUBLIC | consumption | Core | `sf-blink calc(1s * var(--sf-motion-scale)) steps(1, end) infinite` | | `--sf-animation-color-pulse` | PUBLIC | consumption | Core | `sf-color-pulse var(--sf-duration-slow) var(--sf-ease-in-out) infinite` | -| `--sf-animation-delay-1` | PUBLIC | consumption | Core | `calc(75ms * var(--sf-motion-scale))` | -| `--sf-animation-delay-2` | PUBLIC | consumption | Core | `calc(150ms * var(--sf-motion-scale))` | -| `--sf-animation-delay-3` | PUBLIC | consumption | Core | `calc(225ms * var(--sf-motion-scale))` | -| `--sf-animation-delay-4` | PUBLIC | consumption | Core | `calc(300ms * var(--sf-motion-scale))` | -| `--sf-animation-delay-5` | PUBLIC | consumption | Core | `calc(375ms * var(--sf-motion-scale))` | | `--sf-animation-fade-in` | PUBLIC | consumption | Core | `sf-fade-in var(--sf-duration-normal) var(--sf-ease-out) both` | | `--sf-animation-fade-out` | PUBLIC | consumption | Core | `sf-fade-out var(--sf-duration-normal) var(--sf-ease-in) both` | | `--sf-animation-float` | PUBLIC | consumption | Core | `sf-float calc(3s * var(--sf-motion-scale)) var(--sf-ease-in-out) infinite` | @@ -597,6 +592,10 @@ declared value (a value that references `var(--sf-…)` is a derived output): | `--sf-heading-color` | PUBLIC | consumption | Core | `var(--sf-color-heading)` | | `--sf-heading-font-family` | PUBLIC | consumption | Core | `var(--sf-font-heading)` | | `--sf-heading-text-wrap` | PUBLIC | knob | Core | `balance` | +| `--sf-hover-grow-scale` | PUBLIC | knob | Core | `1.05` | +| `--sf-hover-lift` | PUBLIC | knob | Core | `0.25em` | +| `--sf-hover-shrink-scale` | PUBLIC | knob | Core | `0.95` | +| `--sf-hover-slide` | PUBLIC | knob | Core | `0.5em` | | `--sf-icon-2xl` | PUBLIC | knob | Core | `4em` | | `--sf-icon-box-bg` | PUBLIC | consumption | Layout | `var(--sf-color-inset)` | | `--sf-icon-box-border` | PUBLIC | consumption | Layout | `var(--sf-border-width-1) solid var(--sf-color-border)` | @@ -746,6 +745,7 @@ declared value (a value that references `var(--sf-…)` is a derived output): | `--sf-space-xl` | PUBLIC | consumption | Core | `calc(clamp(calc(var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), 2) * 1rem), calc((var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), 2) - var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), 2)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), 2) * 1rem), calc(var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), 2) * 1rem)) * var(--sf-space-scale))` | | `--sf-space-xs` | PUBLIC | consumption | Core | `calc(clamp(calc(var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), -2) * 1rem), calc((var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), -2) - var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), -2)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), -2) * 1rem), calc(var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), -2) * 1rem)) * var(--sf-space-scale))` | | `--sf-stack-gap` | PUBLIC | consumption | Layout | `var(--sf-content-gap)` | +| `--sf-stagger-step` | PUBLIC | knob | Core | `75ms` | | `--sf-state-pending-opacity` | PUBLIC | knob | Core | `0.7` | | `--sf-sticky-offset` | PUBLIC | consumption | Core | `clamp( var(--sf-sticky-offset-mobile), calc((var(--sf-sticky-offset-desktop) - var(--sf-sticky-offset-mobile)) / ((var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * 1rem) * (100vw - var(--sf-fluid-min-vw) * 1rem) + var(--sf-sticky-offset-mobile)), var(--sf-sticky-offset-desktop))` | | `--sf-sticky-offset-desktop` | PUBLIC | consumption | Core | `var(--sf-header-height-desktop)` | diff --git a/docs/tokens.md b/docs/tokens.md index 990b71d0..58f4b351 100644 --- a/docs/tokens.md +++ b/docs/tokens.md @@ -19,11 +19,6 @@ rebrand workflow. |---|---| | `--sf-animation-blink` | `sf-blink calc(1s * var(--sf-motion-scale)) steps(1, end) infinite` | | `--sf-animation-color-pulse` | `sf-color-pulse var(--sf-duration-slow) var(--sf-ease-in-out) infinite` | -| `--sf-animation-delay-1` | `calc(75ms * var(--sf-motion-scale))` | -| `--sf-animation-delay-2` | `calc(150ms * var(--sf-motion-scale))` | -| `--sf-animation-delay-3` | `calc(225ms * var(--sf-motion-scale))` | -| `--sf-animation-delay-4` | `calc(300ms * var(--sf-motion-scale))` | -| `--sf-animation-delay-5` | `calc(375ms * var(--sf-motion-scale))` | | `--sf-animation-fade-in` | `sf-fade-in var(--sf-duration-normal) var(--sf-ease-out) both` | | `--sf-animation-fade-out` | `sf-fade-out var(--sf-duration-normal) var(--sf-ease-in) both` | | `--sf-animation-float` | `sf-float calc(3s * var(--sf-motion-scale)) var(--sf-ease-in-out) infinite` | @@ -430,6 +425,10 @@ rebrand workflow. | `--sf-heading-color` | `var(--sf-color-heading)` | | `--sf-heading-font-family` | `var(--sf-font-heading)` | | `--sf-heading-text-wrap` | `balance` | +| `--sf-hover-grow-scale` | `1.05` | +| `--sf-hover-lift` | `0.25em` | +| `--sf-hover-shrink-scale` | `0.95` | +| `--sf-hover-slide` | `0.5em` | | `--sf-icon-2xl` | `4em` | | `--sf-icon-l` | `2em` | | `--sf-icon-m` | `1.5em` | @@ -546,6 +545,7 @@ rebrand workflow. | `--sf-space-scale` | `1` | | `--sf-space-xl` | `calc(clamp(calc(var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), 2) * 1rem), calc((var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), 2) - var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), 2)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), 2) * 1rem), calc(var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), 2) * 1rem)) * var(--sf-space-scale))` | | `--sf-space-xs` | `calc(clamp(calc(var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), -2) * 1rem), calc((var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), -2) - var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), -2)) / (var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * (var(--sf-fluid-width) - var(--sf-fluid-min-vw) * 1rem) + var(--sf-space-base-min) * pow(var(--sf-space-ratio-min), -2) * 1rem), calc(var(--sf-space-base-max) * pow(var(--sf-space-ratio-max), -2) * 1rem)) * var(--sf-space-scale))` | +| `--sf-stagger-step` | `75ms` | | `--sf-state-pending-opacity` | `0.7` | | `--sf-sticky-offset` | `clamp( var(--sf-sticky-offset-mobile), calc((var(--sf-sticky-offset-desktop) - var(--sf-sticky-offset-mobile)) / ((var(--sf-fluid-max-vw) - var(--sf-fluid-min-vw)) * 1rem) * (100vw - var(--sf-fluid-min-vw) * 1rem) + var(--sf-sticky-offset-mobile)), var(--sf-sticky-offset-desktop))` | | `--sf-sticky-offset-desktop` | `var(--sf-header-height-desktop)` | diff --git a/optional/utilities.css b/optional/utilities.css index 8f4ef8ec..e80830f0 100644 --- a/optional/utilities.css +++ b/optional/utilities.css @@ -139,12 +139,16 @@ transition-duration: var(--sf-duration-normal); transition-timing-function: var(--sf-ease-out); } - .sf-hover-grow:hover { scale: 1.05; } - .sf-hover-shrink:hover { scale: 0.95; } - .sf-hover-float:hover { translate: 0 -0.25em; } - .sf-hover-sink:hover { translate: 0 0.25em; } - .sf-hover-slide-start:hover { translate: -0.5em 0; } - .sf-hover-slide-end:hover { translate: 0.5em 0; } + .sf-hover-grow:hover { scale: var(--sf-hover-grow-scale, 1.05); } + .sf-hover-shrink:hover { scale: var(--sf-hover-shrink-scale, 0.95); } + .sf-hover-float:hover { translate: 0 calc(-1 * var(--sf-hover-lift, 0.25em)); } + .sf-hover-sink:hover { translate: 0 var(--sf-hover-lift, 0.25em); } + .sf-hover-slide-start:hover { translate: calc(-1 * var(--sf-hover-slide, 0.5em)) 0; } + .sf-hover-slide-end:hover { translate: var(--sf-hover-slide, 0.5em) 0; } + /* translate is physical (X axis), but the -start/-end names are logical — + flip the horizontal direction under RTL so "start"/"end" stay correct. */ + :where([dir="rtl"]) .sf-hover-slide-start:hover { translate: var(--sf-hover-slide, 0.5em) 0; } + :where([dir="rtl"]) .sf-hover-slide-end:hover { translate: calc(-1 * var(--sf-hover-slide, 0.5em)) 0; } /* ============================================================ * LIST RESET (.sf-list-none) diff --git a/reports/full-api-audit/results/tokens-report.json b/reports/full-api-audit/results/tokens-report.json index de4edd2e..ce419042 100644 --- a/reports/full-api-audit/results/tokens-report.json +++ b/reports/full-api-audit/results/tokens-report.json @@ -66,71 +66,6 @@ "darkComputed": "sf-color-pulse calc(400ms * 1) cubic-bezier(0.4, 0, 0.2, 1) infinite", "changedInDark": false }, - { - "name": "--sf-animation-delay-1", - "tier": "PUBLIC", - "role": "consumption", - "category": "Core tokens", - "declared": "calc(75ms * var(--sf-motion-scale))", - "aliasOf": null, - "issues": [], - "computed": "calc(75ms * 1)", - "shown": "calc(75ms * 1)", - "darkComputed": "calc(75ms * 1)", - "changedInDark": false - }, - { - "name": "--sf-animation-delay-2", - "tier": "PUBLIC", - "role": "consumption", - "category": "Core tokens", - "declared": "calc(150ms * var(--sf-motion-scale))", - "aliasOf": null, - "issues": [], - "computed": "calc(150ms * 1)", - "shown": "calc(150ms * 1)", - "darkComputed": "calc(150ms * 1)", - "changedInDark": false - }, - { - "name": "--sf-animation-delay-3", - "tier": "PUBLIC", - "role": "consumption", - "category": "Core tokens", - "declared": "calc(225ms * var(--sf-motion-scale))", - "aliasOf": null, - "issues": [], - "computed": "calc(225ms * 1)", - "shown": "calc(225ms * 1)", - "darkComputed": "calc(225ms * 1)", - "changedInDark": false - }, - { - "name": "--sf-animation-delay-4", - "tier": "PUBLIC", - "role": "consumption", - "category": "Core tokens", - "declared": "calc(300ms * var(--sf-motion-scale))", - "aliasOf": null, - "issues": [], - "computed": "calc(300ms * 1)", - "shown": "calc(300ms * 1)", - "darkComputed": "calc(300ms * 1)", - "changedInDark": false - }, - { - "name": "--sf-animation-delay-5", - "tier": "PUBLIC", - "role": "consumption", - "category": "Core tokens", - "declared": "calc(375ms * var(--sf-motion-scale))", - "aliasOf": null, - "issues": [], - "computed": "calc(375ms * 1)", - "shown": "calc(375ms * 1)", - "darkComputed": "calc(375ms * 1)", - "changedInDark": false - }, { "name": "--sf-animation-fade-in", "tier": "PUBLIC", @@ -9928,6 +9863,71 @@ "shown": "1060", "darkComputed": "1060", "changedInDark": false + }, + { + "name": "--sf-stagger-step", + "tier": "PUBLIC", + "role": "knob", + "category": "Core tokens", + "declared": "75ms", + "aliasOf": null, + "issues": [], + "computed": "75ms", + "shown": "75ms", + "darkComputed": "75ms", + "changedInDark": false + }, + { + "name": "--sf-hover-grow-scale", + "tier": "PUBLIC", + "role": "knob", + "category": "Core tokens", + "declared": "1.05", + "aliasOf": null, + "issues": [], + "computed": "1.05", + "shown": "1.05", + "darkComputed": "1.05", + "changedInDark": false + }, + { + "name": "--sf-hover-shrink-scale", + "tier": "PUBLIC", + "role": "knob", + "category": "Core tokens", + "declared": "0.95", + "aliasOf": null, + "issues": [], + "computed": "0.95", + "shown": "0.95", + "darkComputed": "0.95", + "changedInDark": false + }, + { + "name": "--sf-hover-lift", + "tier": "PUBLIC", + "role": "knob", + "category": "Core tokens", + "declared": "0.25em", + "aliasOf": null, + "issues": [], + "computed": "4px", + "shown": "4px", + "darkComputed": "4px", + "changedInDark": false + }, + { + "name": "--sf-hover-slide", + "tier": "PUBLIC", + "role": "knob", + "category": "Core tokens", + "declared": "0.5em", + "aliasOf": null, + "issues": [], + "computed": "8px", + "shown": "8px", + "darkComputed": "8px", + "changedInDark": false } ] } diff --git a/tests/motion.spec.js b/tests/motion.spec.js index 0c034fce..b896b3c6 100644 --- a/tests/motion.spec.js +++ b/tests/motion.spec.js @@ -52,6 +52,39 @@ test.describe('motion — prefers-reduced-motion: no-preference', () => { await renderWithBundle(page, `
x
`); expect(await prop(page, 'animation-name')).toBe('sf-fade-in'); }); + + test('.sf-stagger gives successive children an incrementing animation-delay', async ({ page }) => { + await renderWithBundle( + page, + `
    +
  • 1
  • +
  • 2
  • +
  • 3
  • +
`, + ); + // 1st child → index 0 → 0s; 2nd child → index 1 → 1 × 75ms. + const second = seconds(await prop(page, 'animation-delay')); + expect(second).toBeCloseTo(0.075, 3); + const third = await page.evaluate( + () => parseFloat(getComputedStyle(document.getElementById('t3')).animationDelay), + ); + expect(third).toBeCloseTo(0.15, 3); // index 2 → 2 × 75ms + }); + + test('.sf-stagger delay follows --sf-stagger-step and --sf-motion-scale', async ({ page }) => { + await renderWithBundle( + page, + `
    +
  • 1
  • 2
  • +
`, + ); + await page.evaluate(() => { + document.documentElement.style.setProperty('--sf-stagger-step', '100ms'); + document.documentElement.style.setProperty('--sf-motion-scale', '2'); + }); + // 2nd child index 1 → 1 × 100ms × 2 = 200ms. + expect(seconds(await prop(page, 'animation-delay'))).toBeCloseTo(0.2, 3); + }); }); test.describe('motion — prefers-reduced-motion: reduce', () => { diff --git a/tests/token-api.snapshot.json b/tests/token-api.snapshot.json index 2c0aea41..08485708 100644 --- a/tests/token-api.snapshot.json +++ b/tests/token-api.snapshot.json @@ -3,11 +3,6 @@ "--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", @@ -501,6 +496,10 @@ "--sf-heading-color", "--sf-heading-font-family", "--sf-heading-text-wrap", + "--sf-hover-grow-scale", + "--sf-hover-lift", + "--sf-hover-shrink-scale", + "--sf-hover-slide", "--sf-icon-2xl", "--sf-icon-box-bg", "--sf-icon-box-border", @@ -650,6 +649,7 @@ "--sf-space-xl", "--sf-space-xs", "--sf-stack-gap", + "--sf-stagger-step", "--sf-state-pending-opacity", "--sf-sticky-offset", "--sf-sticky-offset-desktop", diff --git a/token-registry.json b/token-registry.json index d2f1fd0a..94704541 100644 --- a/token-registry.json +++ b/token-registry.json @@ -1,7 +1,7 @@ { "_meta": { "generatedBy": "scripts/gen-token-registry.js", - "nextId": 792 + "nextId": 797 }, "tokens": [ { @@ -22,23 +22,28 @@ }, { "id": 4, - "name": "--sf-animation-delay-1" + "name": "--sf-animation-delay-1", + "removed": true }, { "id": 5, - "name": "--sf-animation-delay-2" + "name": "--sf-animation-delay-2", + "removed": true }, { "id": 6, - "name": "--sf-animation-delay-3" + "name": "--sf-animation-delay-3", + "removed": true }, { "id": 7, - "name": "--sf-animation-delay-4" + "name": "--sf-animation-delay-4", + "removed": true }, { "id": 8, - "name": "--sf-animation-delay-5" + "name": "--sf-animation-delay-5", + "removed": true }, { "id": 9, @@ -3208,6 +3213,26 @@ { "id": 791, "name": "--sf-btn-xs-padding-inline" + }, + { + "id": 792, + "name": "--sf-hover-grow-scale" + }, + { + "id": 793, + "name": "--sf-hover-lift" + }, + { + "id": 794, + "name": "--sf-hover-shrink-scale" + }, + { + "id": 795, + "name": "--sf-hover-slide" + }, + { + "id": 796, + "name": "--sf-stagger-step" } ] }