diff --git a/_layouts/default.html b/_layouts/default.html index 98cb1f0c..11729cb8 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -28,7 +28,7 @@ SLASHED Docs Tokens - Demo + Coverage GitHub Configurator → diff --git a/demos/full-api-demo-with-overrides.html b/demos/full-api-demo-with-overrides.html index ac4ccb47..7192ebe1 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.3 · optimal-components bundle from jsDelivr CDN · 324 classes · 729 tokens (238 configurable)

+

v0.7.5 · optimal-components bundle from jsDelivr CDN · 324 classes · 729 tokens (238 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.
diff --git a/demos/full-api-demo.html b/demos/full-api-demo.html index 9c35ce5c..d30e99fe 100644 --- a/demos/full-api-demo.html +++ b/demos/full-api-demo.html @@ -121,7 +121,7 @@

SLASHED Full API Demo

-

v0.7.3 · optimal-components bundle from jsDelivr CDN · 324 classes · 729 tokens (238 configurable)

+

v0.7.5 · optimal-components bundle from jsDelivr CDN · 324 classes · 729 tokens (238 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.
diff --git a/demos/ultimate-override.css b/demos/ultimate-override.css index fabf3279..bdc26606 100644 --- a/demos/ultimate-override.css +++ b/demos/ultimate-override.css @@ -1,6 +1,6 @@ /* ============================================================================ ULTIMATE OVERRIDE — generated by demos/generate.mjs (do not edit by hand) - SLASHED v0.7.3 + SLASHED v0.7.5 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. diff --git a/docs/layout.md b/docs/layout.md index bc581104..fcfa3a77 100644 --- a/docs/layout.md +++ b/docs/layout.md @@ -5,7 +5,7 @@ SLASHED ships breakpoint-free, container-query-driven layout primitives in the per-instance tokens you can override inline (`style="--sf-stack-gap: …"`). Tokens are declared in `core/tokens.layout.css`. -All primitives are demoed in [`demo.html`](demo.html). +All primitives are demoed in the [layout coverage page](test-coverage-3-layout.html). ## The primitives diff --git a/docs/motion.md b/docs/motion.md index 4141c509..b90c3421 100644 --- a/docs/motion.md +++ b/docs/motion.md @@ -5,7 +5,7 @@ SLASHED ships animation primitives in the `slashed.motion` layer `@media (prefers-reduced-motion: no-preference)` -- users who prefer reduced motion see no animation from the framework. -All animations are demoed in [`demo.html`](demo.html). +All animations are demoed in the [macros & states coverage page](test-coverage-4-macros-states.html). ## Design principles diff --git a/docs/states.md b/docs/states.md index 4b676169..f23e2cbf 100644 --- a/docs/states.md +++ b/docs/states.md @@ -6,7 +6,7 @@ They live in the `slashed.states` layer, are single-class / low-specificity, and set the **minimum** needed to communicate the state; components layer visuals on top. -All states are exercised in [`demo.html`](demo.html). +All states are exercised in the [macros & states coverage page](test-coverage-4-macros-states.html). ## Reference diff --git a/index.html b/index.html index e7d66b41..abd934a1 100644 --- a/index.html +++ b/index.html @@ -65,6 +65,11 @@ letter-spacing: var(--sf-tracking-tight); } .site-nav .brand b { color: var(--sf-color-primary); } + /* The header is a nav bar, not a content section: .sf-section--xs still + resolves to the section rhythm scale (--sf-space-xl ≈ 3.25rem a side), + which stacks a ~150px-tall band around a 49px nav. Pin it to a compact, + symmetric bar instead — the gutter (inline padding) stays. */ + header:has(.site-nav) { padding-block: var(--sf-space-s); } /* The link cluster is the part that has room to give: on narrow viewports it's dropped in favor of the primary CTA + theme toggle, instead of wrapping into three cramped rows. */ @@ -116,7 +121,7 @@ color: var(--sf-color-heading); margin: 0; } - .al { display: inline-flex; align-items: baseline; cursor: default; } + .al { position: relative; display: inline-flex; align-items: baseline; cursor: default; } .al b { background-image: var(--sf-gradient-brand); background-clip: text; @@ -130,49 +135,52 @@ font-weight: var(--sf-font-weight-light); margin-inline: 0.1em; } - @media (any-hover: hover) { - /* Per-word widths (ch). Each word expands to exactly --w, and the - centered row is counter-shifted by --w/2, so the hovered letter - stays put under the cursor instead of sliding onto a neighbour - and re-firing the hover. */ - .al:nth-of-type(1) { --w: 8.2ch; } - .al:nth-of-type(2) { --w: 3.1ch; } - .al:nth-of-type(3) { --w: 6ch; } - .al:nth-of-type(4) { --w: 7.8ch; } - .al:nth-of-type(5) { --w: 4.3ch; } - .al:nth-of-type(6) { --w: 5ch; } - .al:nth-of-type(7) { --w: 9.8ch; } + /* Gate the collapsed/unfold interaction on a PRIMARY input that can + actually hover (a fine pointer), not `any-hover` — which is true on + hybrid devices (touchscreen laptops, a phone with a stylus/mouse) + even while the user is touching, which would strand the acronym in + its collapsed state with no touch way to reveal the words. Touch / + coarse-pointer devices keep the default in-flow state above, where + every word is already shown. */ + @media (hover: hover) and (pointer: fine) { + /* Collapsed to the tight letter-row; each word unfolds on hover. + The unfolded text is taken OUT OF FLOW — absolutely positioned and + revealed with clip-path — so unfolding a word changes the size and + position of nothing else. No letter ever moves under the cursor, so + a hovered word can't slide onto a neighbour and re-fire the hover. + (The old approach animated real width and tried to counter-shift the + centered row by a hardcoded per-word amount; the amounts couldn't + match the real glyph widths, so siblings leapt and re-triggered.) */ .acronym-hero { - /* Never wrap mid-interaction, and cap the size — relative to the - hero's own container, with a floor so a hover-capable device - at a narrow width (a resized browser, a laptop touchscreen) - never shrinks below a readable size — so the widest unfolded - word still fits the row on one line. */ - flex-wrap: nowrap; - row-gap: 0; - font-size: max(2.75rem, min(var(--sf-text-4xl), 4.4cqi)); + /* Never wrap mid-interaction; cap the size relative to the hero's + own container, with a readable floor for narrow hover-capable + viewports (resized browser, laptop touchscreen). */ + flex-wrap: nowrap; + row-gap: 0; + /* Floor sized so the longest unfolded word (Deterministic) still + fits the hero without clipping on narrow hover-capable widths. */ + font-size: max(2.4rem, min(var(--sf-text-4xl), 4.4cqi)); line-height: var(--sf-display-l-line-height); - transition: translate var(--sf-duration-slow) var(--sf-ease-out); } .al { white-space: nowrap; } .al .rest { - display: inline-block; - width: 0; - opacity: 0; - overflow: hidden; - transition: width var(--sf-duration-slow) var(--sf-ease-out), - opacity var(--sf-duration-normal) var(--sf-ease-out); + position: absolute; + inset-block-start: 0; + inset-inline-start: 100%; /* immediately after the bold letter */ + width: max-content; + opacity: 0; + clip-path: inset(0 100% 0 0); /* hidden by clip, so it occupies no layout space */ + transition: clip-path var(--sf-duration-slow) var(--sf-ease-out), + opacity var(--sf-duration-normal) var(--sf-ease-out); + } + /* The unfolded word overlays its dimmed neighbours cleanly. */ + .al:hover { z-index: 1; } + .al:hover .rest { + opacity: 1; + clip-path: inset(0 0 0 0); + transition-delay: 60ms; } - .al:hover .rest { width: var(--w); opacity: 1; transition-delay: 120ms; } .acronym-hero:hover .al:not(:hover) b { opacity: 0.35; } - .acronym-hero:has(.al:hover) { transition-delay: 120ms; } - .acronym-hero:has(.al:nth-of-type(1):hover) { translate: calc(8.2ch / 2) 0; } - .acronym-hero:has(.al:nth-of-type(2):hover) { translate: calc(3.1ch / 2) 0; } - .acronym-hero:has(.al:nth-of-type(3):hover) { translate: calc(6ch / 2) 0; } - .acronym-hero:has(.al:nth-of-type(4):hover) { translate: calc(7.8ch / 2) 0; } - .acronym-hero:has(.al:nth-of-type(5):hover) { translate: calc(4.3ch / 2) 0; } - .acronym-hero:has(.al:nth-of-type(6):hover) { translate: calc(5ch / 2) 0; } - .acronym-hero:has(.al:nth-of-type(7):hover) { translate: calc(9.8ch / 2) 0; } } @media (prefers-reduced-motion: reduce) { .acronym-hero, .al b, .al .rest { transition: none; } @@ -365,9 +373,11 @@ .doc-list a small { color: var(--sf-color-text--muted); margin-inline-start: var(--sf-space-2xs); } .footer-meta, .footer-meta a { color: var(--sf-color-text--muted); font-size: var(--sf-text-xs); } - /* The hover hint only makes sense where hovering is a thing. */ + /* The hover hint only makes sense where hovering is a thing — and it + must match the interaction gate above, or a hybrid touch device + would see "hover the name" over an already-unfolded acronym. */ .hover-hint { display: none; } - @media (any-hover: hover) { .hover-hint { display: inline; } } + @media (hover: hover) and (pointer: fine) { .hover-hint { display: inline; } } .eyebrow { font-size: var(--sf-text-xs); @@ -393,7 +403,7 @@