Skip to content

CSS: HSL + light-dark() + native nesting + :is() + @layer#3

Merged
Arty2 merged 2 commits intomasterfrom
claude/css-modernize
Apr 26, 2026
Merged

CSS: HSL + light-dark() + native nesting + :is() + @layer#3
Arty2 merged 2 commits intomasterfrom
claude/css-modernize

Conversation

@Arty2
Copy link
Copy Markdown
Owner

@Arty2 Arty2 commented Apr 26, 2026

Why

assets/styles/screen.css predates a generation of CSS features that have been baseline since 2022-23. This PR collapses the resulting boilerplate against four such features, plus a small Tier-3 polish pass. Net effect: −260 lines (-8%, 3265 → 3005); minified bundle 46 KB → 42.5 KB; cleaner cascade story.

Browser floor stays where it already was. The newest feature here (light-dark()) ships in Safari 17.5 (May 2024); everything else is 2022-23 or older. The file already uses :has(), aspect-ratio, and :is() in places, so this is incremental modernisation, not a floor change.

What

Two commits:

1. HSL color vars + light-dark() (27471b7)

  • All colour vars switch from RGB-comma triplets (--text-color: 34, 34, 50;) to HSL space-syntax (--text-color: 240 19% 16%;). Conversion is mechanical via Python's colorsys to avoid eyeballing rounding. HSL is more legible (hue / saturation / lightness vs. opaque RGB) and lets you derive variations by tweaking lightness alone.
  • All ~100 rgb() / rgba(var(--…), N) callsites become hsl(var(--…)) / hsl(var(--…) / N) (modern slash-alpha syntax).
  • The three duplicated @media (prefers-color-scheme: dark) blocks (default, peach, retro) collapse into single light-dark() declarations driven by :root's existing color-scheme: light dark. .scheme--light / .scheme--dark now just pin color-scheme.
  • Side-effect bug fix: the old .theme--peach had its dark block winning regardless of scheme (the bare .theme--peach selector appeared in both light and dark blocks; the later one won). With light-dark(), scheme actually drives the resolution.
  • essential.css gets the same treatment.

Breaking change for downstream user CSS that referenced the comma-syntax form (rgba(var(--text-color), .5)). Migration: hsl(var(--text-color) / .5). Pre-1.0 — no semver contract.

2. Native nesting + :is() + @layer + Tier-3 polish (ad61c07)

Tier 1.1 — Native CSS nesting. 11 prefix groups collapsed into nested forms: .gadgets, .video, .sharing, .simple-signup, .gallery, .meta, body > header, body > footer, .articles-list, #search__form / #search__results / #search__input, .taxon, .taxonomy-filter, plus the body > header .menu hover/focus tree. Hugo's minifier (tdewolff/minify) preserves & nesting through resources.Minify.

Tier 1.2 — :is(). ~15 collapse sites: the 12-selector main heading-anchor list (h1..h6[id], a[title], a[href*='.pdf'], ins/del[datetime], abbr/span[title]) and its :hover / :not(:hover) / :target variants; the 5-place .marginalia, .entry-content aside repetition; the image-link-disable list; the 1200px hover breakpoint group.

Tier 2.1 — @layer reset, structure, theming, typography, helpers, modules. The 6 named sections wrap in cascade layers in declaration order. User CSS outside any layer now wins over theme rules without !important — strictly improves the override story. Accessibility !importants (reduced-motion, scrollbar overrides, user-select) stay; they're meant to win against everything including unlayered user rules.

Tier 3.1 — inset shorthand. Three blocks collapsed:

  • figure:has(img.blend)::afterinset: 0
  • .iconinset-inline: 0
  • .gadgetsinset: 0 0 0 auto

Tier 3.2 — Logical properties. 31 margin-left/right and padding-left/right sites become margin-inline-* / padding-inline-* (also 1 in essential.css). Same line count, RTL-ready behind a single dir="rtl" flag.

Tier 3.3 — text-wrap. Headings get balance (no widow words); main p, main li, main dt, main dd get pretty (no orphan-line rivers).

Held back (intentionally)

  • Removing !important hacks that @layer makes redundant (taxon colour overrides, .icon outline). The cascade now favours user overrides regardless, so the !importants are inert rather than harmful — removing them is a tidy follow-up but risks specificity surprises if any internal rule was relying on them.
  • Tier 3.4 classless body > header menu rewrite. Worth ~10 lines and mechanical, but it makes assumptions about the rendered HTML structure I'd rather verify against the templates first.

Both are safe to revisit in a smaller PR.

Test plan

  • hugo --gc --minify clean on exampleSite/
  • html-validate clean (.github/html-validate.json)
  • wc -l screen.css: 3265 → 3005
  • Visual sanity: home, an article, an article with marginalia, gallery, filter demo, model-viewer demo, 404, search panel, scheme toggle, theme--peach + theme--retro. Compare against master.
  • Spot-check that a user override in unlayered CSS (e.g. body { color: red; }) wins without !important.

Generated by Claude Code

claude added 2 commits April 26, 2026 19:32
…ight-dark()

Switch the colour pipeline from RGB-comma-triplets fed to legacy rgb()/rgba()
to HSL space-syntax fed to modern hsl(): more legible values, easier
hue/saturation/lightness tweaks, and the modern slash-alpha syntax across the
board. All 100+ rgb()/rgba(var(--…)) callsites become hsl(var(--…)) /
hsl(var(--…) / N).

Collapse the three duplicated `@media (prefers-color-scheme: dark)` blocks
(default, peach, retro) into single light-dark() declarations driven by
:root's existing color-scheme: light dark. .scheme--light / .scheme--dark
now just pin color-scheme so light-dark() resolves predictably. Side-effect:
the original .theme--peach block had a bug where the dark variant always won
regardless of scheme; light-dark() restores the intended per-scheme behaviour.

Breaking change for downstream user CSS that referenced the comma-syntax form
(e.g. `rgba(var(--text-color), .5)`). Migration: switch to
`hsl(var(--text-color) / .5)`. Pre-1.0 — no semver contract broken.
Modernises the stylesheet against features that have been baseline since
2022-23. screen.css drops 260 lines (-8%, 3265 → 3005); minified output
46 KB → 42.5 KB.

* Tier 1.1 — Native CSS nesting. Collapsed 11 prefix groups into nested
  forms: .gadgets, .video, .sharing, .simple-signup, .gallery, .meta,
  body > header, body > footer, .articles-list, #search__form /
  #search__results / #search__input, .taxon, .taxonomy-filter, plus the
  body > header .menu hover/focus tree. Hugo's minifier (tdewolff)
  preserves & nesting through `resources.Minify`.

* Tier 1.2 — :is(). Collapsed the 12-selector main heading-anchor list
  (h1..h6[id], a[title], a[href*='.pdf'], ins/del[datetime],
  abbr/span[title]) and its :hover / :not(:hover) / :target variants
  into single :is() forms. Same for the 5-place .marginalia /
  .entry-content aside repetition, the image-link-disable list, and
  the 1200px hover breakpoint group. ~15 collapse sites total.

* Tier 2.1 — @layer reset, structure, theming, typography, helpers,
  modules. Wraps the 6 named sections in cascade layers. User CSS
  outside any layer now wins over theme rules without needing
  !important — strictly improves the override story. Accessibility
  !importants (reduced-motion, scrollbar overrides, user-select)
  stay; they're meant to win against everything including unlayered
  user rules.

* Tier 3.1 — `inset`. Three position blocks collapsed:
  figure:has(img.blend)::after  → inset: 0
  .icon                         → inset-inline: 0
  .gadgets                      → inset: 0 0 0 auto

* Tier 3.2 — Logical properties. 31 `margin-left/right` and
  `padding-left/right` sites become `margin-inline-*` /
  `padding-inline-*` (also 1 in essential.css). Same line count, RTL-
  ready.

* Tier 3.3 — `text-wrap`. Headings get `balance` (no widow words);
  `main p, main li, main dt, main dd` get `pretty` (no orphan-line
  rivers).
@Arty2 Arty2 merged commit c775f0c into master Apr 26, 2026
2 checks passed
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.

2 participants