Skip to content

Delete the example's stylesheet - #23

Merged
JedimEmO merged 3 commits into
mainfrom
example-drop-raw-css
Jul 25, 2026
Merged

Delete the example's stylesheet#23
JedimEmO merged 3 commits into
mainfrom
example-drop-raw-css

Conversation

@JedimEmO

Copy link
Copy Markdown
Owner

Stacked on #22 (which is stacked on #21). Retarget as the stack lands.

examples/webpage/src/styles.rs is gone — all 297 lines of it. The site renders identically.

What replaced it

The 12 @keyframes move to keyframes.rs as one dwkeyframes! block. The ones used as bare classes get #[animation(...)]; the ones composed with a runtime delay are referenced through the handle, whose Display registers the rule:

.style("animation", &format!("{FADE_UP_KEYFRAMES} 600ms {}ms ease-out both", i * 60))

The dead dwind-glow-drift keyframe — declared, never used — is dropped.

The pointer spotlight is the interesting one. A ::before glow and a masked ::after ring, both parked at --sx/--sy, now expressed entirely as dwclass! variants. content: "" is supplied automatically; the four co-dependent mask declarations use the escape hatch:

"[&::after]:[-webkit-mask:linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0)] \
 [&::after]:[-webkit-mask-composite:xor] \
 [&::after]:[mask:linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0)] \
 [&::after]:[mask-composite:exclude]"

Verified in a real browser — the computed ::before background tracks the cursor: radial-gradient(352px at 24.83….

The scroll-reveal cascade — parent-state .reveal-in > * rules plus :nth-child stagger — is child-selector variants on the container. The container no longer needs a marker class at all.

Scrollbars become [&::-webkit-scrollbar-thumb]: variants. The marquee's hover-pause becomes [&:hover > *]:[animation-play-state:paused] — the classic "hover the parent, animate the child" case that no element-level class can express. Glass panels and the gradient-text sheen become named mixins.

40 inline .style() calls that duplicated utilities dwind already had (position 9×, text-decoration: none 6×, font: inherit 4×, z-index, …) are now dwclass!. That part was never a library gap — the example just wasn't written carefully.

What honestly stays raw

The <style> block in index.html, holding only document-level things: font families, :root colour-scheme, ::selection, a site-wide :focus-visible ring, and the blanket prefers-reduced-motion clamp. The .reveal-section override there is deleted along with the class.

The film-grain feTurbulence data URI stays an inline .style() — it's a one-off asset, not a reusable value.

Docs

The two capabilities that already worked but were undiscoverable are now taught, with live examples:

  • Pseudoclasses page: ::before/::after variants and arbitrary declarations (conic gradient, writing-mode, a --accent custom property read back through var()).
  • Animation page: dwkeyframes!, the handle-Display pattern for runtime delays, and @((prefers-reduced-motion: reduce)).

Verification

19 routes clean with no console errors; no horizontal overflow at 1440px or 390px; reduced motion honoured; ⌘K palette and the signal lab unchanged (10 → 75 property writes on a slider drag). Screenshots are pixel-identical to the pre-migration build.

🤖 Generated with Claude Code

JedimEmO added a commit that referenced this pull request Jul 25, 2026
From review of #22. All three were real; two of them undercut the exact
guarantee the feature exists to provide.

Animation variants did not register their keyframes
---------------------------------------------------
dwkeyframes! called ensure() from the generated class, but a modified form
— hover:animate-x, [&::before]:animate-x — compiles the declaration *text*
into a fresh class and never touches that class. Those animations
referenced a @Keyframes rule that was never injected.

Registration now hangs off the declaration text: the emitted *_RAW is an
AnimationDecl whose Deref registers, rather than a plain &str. Codegen is
unchanged — `.raw(&*IDENT)` derefs either one to `&str` — so every path
dwclass! can take is covered. I had documented this as a known hole; it
was too central to leave.

dwclass! silently discarded what it could not parse
--------------------------------------------------
Two bugs compounded here.

`many0` stops at the first unparseable class and reports success with the
remainder untouched, and parse_class_string ignored that remainder. One
malformed class therefore deleted itself *and every class after it*, with
no diagnostic — the precise failure mode dwclass! exists to prevent. It
now refuses, naming the offending text.

And the declaration-body parser used a character allow-list built on
nom's `is_alphanumeric`, which takes a u8 — so `c as u8` truncated every
multi-byte character and `[content:'→']` could not parse. Combined with
the above, PR #23's own docs example silently compiled to zero classes.
The body is now a deny-list of the four bracket delimiters, since a CSS
value can hold any character.

Also dropped the `_`-means-space rewrite. Spaces already work inside the
brackets, so it bought nothing and corrupted `var(--brand_color)`.

Composed pseudo-element utilities clobbered explicit content
------------------------------------------------------------
Every before:/after: utility has to emit a content, or nothing renders.
But each utility is its own class with its own rule, so the literal
`content: ""` from `before:absolute` won by source order over the
`content: 'x'` from `before:[content:'x']`.

I claimed raw()'s append semantics made Tailwind's --tw-content
indirection unnecessary. That was wrong: appending only orders
declarations *within* one class, not across composed utilities. Content
now goes through --dw-content, so the content declaration is identical in
every class and only the value varies. content-empty / content-none set
the property for the same reason.

Tests
-----
42 native (up from 29), including the dwkeyframes! parser and codegen,
which previously had none. Browser suite is 12 (up from 9); the three new
cases are the exact probes from the review, each verified failing before
the fix.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
JedimEmO added a commit that referenced this pull request Jul 25, 2026
From review of #23. Deleting styles.rs removed a
`transform: none !important` rule that suppressed the spotlight tilt under
prefers-reduced-motion, and nothing replaced it — the blanket clamp in
index.html only shortens transitions and animations, so pointer movement
still tilted cards, just instantly. The README claimed otherwise.

A @media block cannot help here: the tilt and the magnetic drift are
style_signal writes, so the preference has to be read in Rust. Both now
take it as a signal — which also means a preference changed while the page
is open takes effect immediately, where the old !important rule needed a
reload to matter for anything JS-driven.

Verified in Chrome by hovering a card under both settings:
  no-preference  rotateX(2.03deg) rotateY(-2.41deg)
  reduce         rotateX(0deg)    rotateY(0deg)

Also corrects two doc claims that the #22 fixes invalidated: values are no
longer underscore-rewritten, and pseudo-element content composes through a
custom property rather than by source order.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@JedimEmO
JedimEmO force-pushed the example-drop-raw-css branch from d5f75c5 to 6267c91 Compare July 25, 2026 13:42
JedimEmO added a commit that referenced this pull request Jul 25, 2026
From review of #22. All three were real; two of them undercut the exact
guarantee the feature exists to provide.

Animation variants did not register their keyframes
---------------------------------------------------
dwkeyframes! called ensure() from the generated class, but a modified form
— hover:animate-x, [&::before]:animate-x — compiles the declaration *text*
into a fresh class and never touches that class. Those animations
referenced a @Keyframes rule that was never injected.

Registration now hangs off the declaration text: the emitted *_RAW is an
AnimationDecl whose Deref registers, rather than a plain &str. Codegen is
unchanged — `.raw(&*IDENT)` derefs either one to `&str` — so every path
dwclass! can take is covered. I had documented this as a known hole; it
was too central to leave.

dwclass! silently discarded what it could not parse
--------------------------------------------------
Two bugs compounded here.

`many0` stops at the first unparseable class and reports success with the
remainder untouched, and parse_class_string ignored that remainder. One
malformed class therefore deleted itself *and every class after it*, with
no diagnostic — the precise failure mode dwclass! exists to prevent. It
now refuses, naming the offending text.

And the declaration-body parser used a character allow-list built on
nom's `is_alphanumeric`, which takes a u8 — so `c as u8` truncated every
multi-byte character and `[content:'→']` could not parse. Combined with
the above, PR #23's own docs example silently compiled to zero classes.
The body is now a deny-list of the four bracket delimiters, since a CSS
value can hold any character.

Also dropped the `_`-means-space rewrite. Spaces already work inside the
brackets, so it bought nothing and corrupted `var(--brand_color)`.

Composed pseudo-element utilities clobbered explicit content
------------------------------------------------------------
Every before:/after: utility has to emit a content, or nothing renders.
But each utility is its own class with its own rule, so the literal
`content: ""` from `before:absolute` won by source order over the
`content: 'x'` from `before:[content:'x']`.

I claimed raw()'s append semantics made Tailwind's --tw-content
indirection unnecessary. That was wrong: appending only orders
declarations *within* one class, not across composed utilities. Content
now goes through --dw-content, so the content declaration is identical in
every class and only the value varies. content-empty / content-none set
the property for the same reason.

Tests
-----
42 native (up from 29), including the dwkeyframes! parser and codegen,
which previously had none. Browser suite is 12 (up from 9); the three new
cases are the exact probes from the review, each verified failing before
the fix.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@JedimEmO
JedimEmO force-pushed the dwind-keyframes-and-arbitrary-values branch from 5fc0334 to 064b5f9 Compare July 25, 2026 13:44
JedimEmO added a commit that referenced this pull request Jul 25, 2026
From review of #23. Deleting styles.rs removed a
`transform: none !important` rule that suppressed the spotlight tilt under
prefers-reduced-motion, and nothing replaced it — the blanket clamp in
index.html only shortens transitions and animations, so pointer movement
still tilted cards, just instantly. The README claimed otherwise.

A @media block cannot help here: the tilt and the magnetic drift are
style_signal writes, so the preference has to be read in Rust. Both now
take it as a signal — which also means a preference changed while the page
is open takes effect immediately, where the old !important rule needed a
reload to matter for anything JS-driven.

Verified in Chrome by hovering a card under both settings:
  no-preference  rotateX(2.03deg) rotateY(-2.41deg)
  reduce         rotateX(0deg)    rotateY(0deg)

Also corrects two doc claims that the #22 fixes invalidated: values are no
longer underscore-rewritten, and pseudo-element content composes through a
custom property rather than by source order.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@JedimEmO
JedimEmO force-pushed the example-drop-raw-css branch from 6267c91 to f8dfceb Compare July 25, 2026 13:44
JedimEmO added a commit that referenced this pull request Jul 25, 2026
From review of #22. All three were real; two of them undercut the exact
guarantee the feature exists to provide.

Animation variants did not register their keyframes
---------------------------------------------------
dwkeyframes! called ensure() from the generated class, but a modified form
— hover:animate-x, [&::before]:animate-x — compiles the declaration *text*
into a fresh class and never touches that class. Those animations
referenced a @Keyframes rule that was never injected.

Registration now hangs off the declaration text: the emitted *_RAW is an
AnimationDecl whose Deref registers, rather than a plain &str. Codegen is
unchanged — `.raw(&*IDENT)` derefs either one to `&str` — so every path
dwclass! can take is covered. I had documented this as a known hole; it
was too central to leave.

dwclass! silently discarded what it could not parse
--------------------------------------------------
Two bugs compounded here.

`many0` stops at the first unparseable class and reports success with the
remainder untouched, and parse_class_string ignored that remainder. One
malformed class therefore deleted itself *and every class after it*, with
no diagnostic — the precise failure mode dwclass! exists to prevent. It
now refuses, naming the offending text.

And the declaration-body parser used a character allow-list built on
nom's `is_alphanumeric`, which takes a u8 — so `c as u8` truncated every
multi-byte character and `[content:'→']` could not parse. Combined with
the above, PR #23's own docs example silently compiled to zero classes.
The body is now a deny-list of the four bracket delimiters, since a CSS
value can hold any character.

Also dropped the `_`-means-space rewrite. Spaces already work inside the
brackets, so it bought nothing and corrupted `var(--brand_color)`.

Composed pseudo-element utilities clobbered explicit content
------------------------------------------------------------
Every before:/after: utility has to emit a content, or nothing renders.
But each utility is its own class with its own rule, so the literal
`content: ""` from `before:absolute` won by source order over the
`content: 'x'` from `before:[content:'x']`.

I claimed raw()'s append semantics made Tailwind's --tw-content
indirection unnecessary. That was wrong: appending only orders
declarations *within* one class, not across composed utilities. Content
now goes through --dw-content, so the content declaration is identical in
every class and only the value varies. content-empty / content-none set
the property for the same reason.

Tests
-----
42 native (up from 29), including the dwkeyframes! parser and codegen,
which previously had none. Browser suite is 12 (up from 9); the three new
cases are the exact probes from the review, each verified failing before
the fix.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@JedimEmO
JedimEmO force-pushed the dwind-keyframes-and-arbitrary-values branch from 064b5f9 to 2ba788b Compare July 25, 2026 13:44
JedimEmO added a commit that referenced this pull request Jul 25, 2026
From review of #23. Deleting styles.rs removed a
`transform: none !important` rule that suppressed the spotlight tilt under
prefers-reduced-motion, and nothing replaced it — the blanket clamp in
index.html only shortens transitions and animations, so pointer movement
still tilted cards, just instantly. The README claimed otherwise.

A @media block cannot help here: the tilt and the magnetic drift are
style_signal writes, so the preference has to be read in Rust. Both now
take it as a signal — which also means a preference changed while the page
is open takes effect immediately, where the old !important rule needed a
reload to matter for anything JS-driven.

Verified in Chrome by hovering a card under both settings:
  no-preference  rotateX(2.03deg) rotateY(-2.41deg)
  reduce         rotateX(0deg)    rotateY(0deg)

Also corrects two doc claims that the #22 fixes invalidated: values are no
longer underscore-rewritten, and pseudo-element content composes through a
custom property rather than by source order.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@JedimEmO
JedimEmO force-pushed the example-drop-raw-css branch 2 times, most recently from 42e7ed6 to 08f8276 Compare July 25, 2026 13:52
JedimEmO added a commit that referenced this pull request Jul 25, 2026
From review of #23. Deleting styles.rs removed a
`transform: none !important` rule that suppressed the spotlight tilt under
prefers-reduced-motion, and nothing replaced it — the blanket clamp in
index.html only shortens transitions and animations, so pointer movement
still tilted cards, just instantly. The README claimed otherwise.

A @media block cannot help here: the tilt and the magnetic drift are
style_signal writes, so the preference has to be read in Rust. Both now
take it as a signal — which also means a preference changed while the page
is open takes effect immediately, where the old !important rule needed a
reload to matter for anything JS-driven.

Verified in Chrome by hovering a card under both settings:
  no-preference  rotateX(2.03deg) rotateY(-2.41deg)
  reduce         rotateX(0deg)    rotateY(0deg)

Also corrects two doc claims that the #22 fixes invalidated: values are no
longer underscore-rewritten, and pseudo-element content composes through a
custom property rather than by source order.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@JedimEmO
JedimEmO force-pushed the example-drop-raw-css branch from 08f8276 to 9d82855 Compare July 25, 2026 13:52
JedimEmO added a commit that referenced this pull request Jul 25, 2026
From re-review of #23. The previous commit gated spotlight_tilt but not
magnetic — I ran a scripted patch that reported success without checking
it had matched, and my browser probe only hovered a spotlight card, so
neither caught it. magnetic is used on every CTA on the site.

Both decisions are now pure functions with unit tests, so the rule is a
plain assertion rather than something only a hovering browser can check.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
JedimEmO and others added 3 commits July 25, 2026 16:10
styles.rs is gone — all 297 lines of it. The site renders identically.

What replaced it, using the capabilities added in the previous commit:

- The 12 @Keyframes move to keyframes.rs as a single dwkeyframes! block.
  Ones used as bare classes get #[animation(...)]; the ones composed with a
  runtime delay are referenced through the handle, whose Display registers
  the rule. The dead dwind-glow-drift keyframe is dropped.

- The pointer spotlight — a ::before glow and a masked ::after ring, both
  parked at --sx/--sy — is now dwclass! variants. content: "" is supplied
  automatically, and the four co-dependent mask declarations use the
  [property:value] escape hatch. Verified in a browser: the computed
  ::before background tracks the cursor.

- The scroll-reveal cascade, including the parent-state .reveal-in > *
  rules and the :nth-child stagger, is child-selector variants on the
  container. The container no longer needs a marker class at all.

- Scrollbars become [&::-webkit-scrollbar-thumb]: variants, the marquee's
  hover-pause becomes [&:hover > *]: — the classic "hover the parent,
  animate the child" case that no element-level class can express — and
  the glass panel and gradient-text sheen become named mixins.

40 inline .style() calls that duplicated utilities dwind already had
(position, z-index, text-decoration, font: inherit, ...) are now dwclass!.

The only CSS left is the <style> block in index.html, and it holds only
document-level things: font families, :root colour-scheme, ::selection, a
site-wide :focus-visible ring, and the blanket prefers-reduced-motion
clamp. The .reveal-section override there is deleted along with the class.

Docs gain the two capabilities that were previously undiscoverable: the
Pseudoclasses page now covers ::before/::after variants and arbitrary
declarations with live examples, and the Animation page covers
dwkeyframes! and the @((prefers-reduced-motion: reduce)) conditional.

Verified: 19 routes clean with no console errors, no horizontal overflow
at 1440px or 390px, reduced motion honoured, ⌘K palette and the signal
lab unchanged (10 -> 75 property writes on a slider drag).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
From review of #23. Deleting styles.rs removed a
`transform: none !important` rule that suppressed the spotlight tilt under
prefers-reduced-motion, and nothing replaced it — the blanket clamp in
index.html only shortens transitions and animations, so pointer movement
still tilted cards, just instantly. The README claimed otherwise.

A @media block cannot help here: the tilt and the magnetic drift are
style_signal writes, so the preference has to be read in Rust. Both now
take it as a signal — which also means a preference changed while the page
is open takes effect immediately, where the old !important rule needed a
reload to matter for anything JS-driven.

Verified in Chrome by hovering a card under both settings:
  no-preference  rotateX(2.03deg) rotateY(-2.41deg)
  reduce         rotateX(0deg)    rotateY(0deg)

Also corrects two doc claims that the #22 fixes invalidated: values are no
longer underscore-rewritten, and pseudo-element content composes through a
custom property rather than by source order.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
From re-review of #23. The previous commit gated spotlight_tilt but not
magnetic — I ran a scripted patch that reported success without checking
it had matched, and my browser probe only hovered a spotlight card, so
neither caught it. magnetic is used on every CTA on the site.

Both decisions are now pure functions with unit tests, so the rule is a
plain assertion rather than something only a hovering browser can check.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@JedimEmO
JedimEmO force-pushed the example-drop-raw-css branch from fb663ea to 787972f Compare July 25, 2026 14:12
@JedimEmO
JedimEmO changed the base branch from dwind-keyframes-and-arbitrary-values to main July 25, 2026 14:29
@JedimEmO
JedimEmO merged commit df3f9bf into main Jul 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant