From 583498b18d74708f6a7a0495f963b389c31af21c Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 8 Jul 2026 11:15:13 +0000 Subject: [PATCH 1/3] fix(site): repair hero acronym unfold, compact header, retire demo.html links MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The homepage hero, header, and demo links all needed work. Hero acronym re-trigger: the SLASHED letter-row animated each word's real width on hover and tried to keep the hovered letter under the cursor by counter-translating the centered row by a hardcoded per-word amount. Those amounts couldn't match the real glyph widths, so siblings leapt (measured 283px) and slid under the cursor, re-firing the hover on a neighbour. Rework the reveal to be out of flow: the unfolded text is 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 (verified 0px shift), so re-triggering is structurally impossible. Floor the hover-mode font size so the longest word (Deterministic) never clips on narrow hover-capable widths. Header padding: the header used .sf-section--xs, but that still resolves to the section vertical-rhythm scale (--sf-space-xl, ~52px a side), stacking a ~150px band around a 49px nav. Pin the header to a compact bar (94px). demo.html retirement (site links only): point every user-facing link — the homepage nav, hero CTA, docs list, and the shared _layouts nav — to the test-coverage suite / Full API Demo instead. The doc-prose references in layout/states/motion now point to the matching coverage pages. demo.html stays on disk as the internal test fixture the Playwright specs and the class-coverage gate depend on. Copy verified against source: 729 tokens, 15 layers, 6-token rebrand, exactly two components, and the Chrome 125+/Safari 18+/Firefox 129+ floor all check out and are unchanged. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_017iAYFfHmHUnbDTgA7LHBmx --- _layouts/default.html | 2 +- docs/layout.md | 2 +- docs/motion.md | 2 +- docs/states.md | 2 +- index.html | 78 +++++++++++++++++++++---------------------- 5 files changed, 43 insertions(+), 43 deletions(-) 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/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..b76488c6 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; @@ -131,48 +136,44 @@ 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; } + /* 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; } @@ -393,7 +394,7 @@ From aabfb5b95cbf3a196f04e846637127448b1036f1 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 8 Jul 2026 11:25:49 +0000 Subject: [PATCH 2/3] chore(demos): rebuild full-api demos for v0.7.5 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The generated full-api demos were still stamped v0.7.3 — the v0.7.5 version bump synced the version artifacts but didn't rebuild demos/, so the artifact drift gate (check-artifacts.js) failed. Regenerating via demos/generate.mjs updates only the version stamp in each file (no content change), which is what the gate prescribes. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_017iAYFfHmHUnbDTgA7LHBmx --- demos/full-api-demo-with-overrides.html | 2 +- demos/full-api-demo.html | 2 +- demos/ultimate-override.css | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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. From 08b6c07ce9483d560ae87a4915c1dbcf75deefce Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 8 Jul 2026 11:28:58 +0000 Subject: [PATCH 3/3] fix(site): gate hero unfold on a fine hovering pointer, not any-hover MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The acronym-hero collapse/unfold interaction (and the "hover the name" hint) were gated on @media (any-hover: hover), which is true on hybrid devices — a touchscreen laptop, or a phone with a stylus/mouse — even while the user is touching. That stranded the acronym in its collapsed, clipped state with no touch way to reveal the words, contradicting the page's own stated intent that touch shows the full words. Gate both the interaction and the hint on the PRIMARY input actually being a fine pointer that can hover: @media (hover: hover) and (pointer: fine). Touch / coarse-pointer devices now reliably fall through to the default in-flow state where every word is already shown. Verified: desktop mouse gets the interactive collapse + hint; a coarse-pointer phone gets full words, no hint. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_017iAYFfHmHUnbDTgA7LHBmx --- index.html | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/index.html b/index.html index b76488c6..abd934a1 100644 --- a/index.html +++ b/index.html @@ -135,7 +135,14 @@ font-weight: var(--sf-font-weight-light); margin-inline: 0.1em; } - @media (any-hover: hover) { + /* 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 @@ -366,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);