Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .changeset/dev-initial-load-prescan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
'@fuzdev/fuz_css': patch
---

fix: complete utility CSS on the first dev page load

The Vite plugin now eagerly pre-scans project sources at dev-server startup
(new `prescan` option: `true` scans `src` under the Vite root, `false`
disables, or an array of directories) and resyncs clients whose HMR socket
connects after a missed CSS update. Previously the first cold-start page
load could render with incomplete utility classes until a manual refresh:
extraction state accumulated only from modules Vite had transformed so far,
and the corrective HMR update was dropped when the browser hadn't connected
yet. Bundled resources (base-CSS rule index, variable graph) also load
eagerly at dev-server startup now, overlapping their parse with the
pre-scan for faster cold starts.
26 changes: 26 additions & 0 deletions .changeset/micro-surface-hooks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
'@fuzdev/fuz_css': minor
---

feat: theme scrollbars, caret, dialog backdrop, and the OS contrast preference

New micro-surface defaults in `style.css`, each themable through a `var()`
fallback hook (registered in the knob catalog so `validate_theme` accepts
them):

- `scrollbar-color` on `:root` — thumb defaults to `var(--shade_40)` on a
transparent track, so scrollbars follow the theme's neutral; hooks
`--scrollbar_thumb_color` / `--scrollbar_track_color`
- `caret-color` on text inputs — defaults to `var(--accent_50)`, matching
selection and focus; hook `--caret_color`
- `dialog::backdrop` — defaults to `var(--darken_60)`, the same dim fuz_ui's
`Dialog` uses; hook `--backdrop_color`
- `@media (prefers-contrast: more)` maps the OS preference onto the curve
knobs, mirroring the `'high contrast'` theme's moves
(`--shade_lightness_00`, `--text_lightness_curve`); theme overrides beat
it from the `fuz.theme` layer

Also tunes the `'low contrast'` registry theme's shade compression
(`shade_lightness_00` 0.92 light / 0.245 dark) to the softest values that
pass every `check_theme` WCAG gate, so the whole registry passes its own
gates.
50 changes: 50 additions & 0 deletions .changeset/theme-knobs-and-checks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
'@fuzdev/fuz_css': minor
---

feat: add theme scale knobs, the knob catalog, and theme validation/gates/compile

New high-leverage theme knobs, each derived into existing token defaults so
one knob move reshapes a whole family while individual tokens stay pinnable:

- `--shadow_alpha_scale` — multiplies the `shadow_alpha_*` ramp (0 flattens
all shadows including button shadows, which reference the ramp)
- `--radius_scale` — multiplies the `border_radius_*` tiers (0 is sharp,
above 1 is rounder; per-element tiers survive)
- `--scale_factor` — multiplies the `space_*` scale (tight ↔ spacious)
- `--font_weight` — base body font weight (applied on `body`)
- `--heading_font_weight` — a hook with per-tier fallbacks (h1 300 … h5 900);
setting it flattens the heading weight ladder deliberately
- `--heading_font_family` — headings' font family (defaults to
`var(--font_family_serif)`)
- `--background_image` — decoration hook on `:root` for gradient skies,
vignettes, and textures (defaults to `none`)

New `knobs.ts` module: `theme_knobs`, a typed catalog of the theme-facing
knobs (`kind`, `axis`, `leverage`, `tier`, `bindable`, ranges, and the
`knob_axes` display order), which powers the inline theme editor on the
themes docs page. `variable_data.ts` gains `palette_glosses`, the letter →
color/default-intent-binding display data.

New `theme_check.ts` module with three functions that resolve a theme's
authored values back to numbers — numeric literals, `var(--hue_x)` binding
chains, and compiled-cap overrides — with knob defaults falling through to
the numeric twin in `ramps.ts`:

- `validate_theme` — the structural lint: non-empty name, `StyleVariable`
shape, and known variable names as errors, plus advisory type/range
warnings for the knob-tier variables.
- `check_theme` — evaluates the gamut, ramp-monotonicity, and contrast gates
(the same thresholds the repo's tests assert for the defaults) against an
arbitrary theme. Report-only, never throws.
- `compile_theme` — recomputes each theme's per-stop worst-hue chroma caps
from its own hues, lightness ramp, and hue shift, then emits
`palette_chroma_NN` overrides where the baked caps no longer fit (a
rotated, monochrome, or dark-only theme), and re-checks the result.

The gate thresholds are exported constants (`GATE_BODY_TEXT`,
`GATE_SUBTLE_TEXT`, `GATE_LINK`, `GATE_UI`, `GATE_FILL_TEXT`). `ramps.ts`
gains `ramp_hue_shift_offset` (the numeric twin of the hue-shift CSS
emitter) and `compute_palette_chroma_caps` (the generalized worst-hue cap
search behind the baked table and the compile step); `ramp_chroma` and
`render_chroma_stop_css` take optional knob/cap parameters.
19 changes: 19 additions & 0 deletions .changeset/theme-scheme-stance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
'@fuzdev/fuz_css': minor
---

feat: add the theme scheme stance

`Theme` gains `scheme?: 'dual' | 'light' | 'dark'` (default `'dual'`). A
single-scheme theme renders that appearance in both color schemes:
`render_theme_style` mirrors every scheme-adaptive default the theme doesn't
override (including the `palette_chroma_NN` gamut-cap stops, so the mirrored
scheme gets correct caps) and pins `color-scheme` on the scope so form
controls and native scrollbars agree. The mirror is exported as
`scheme_stance_variables`. A stanced theme's own variables are best authored
single-slot in the light/base position.

`validate_theme` checks the field, and `check_theme`/`compile_theme` resolve
through the same mirror so the gates and cap recomputation evaluate the
stanced reality in both schemes. The necromancer and terminal exemplars use
the stance instead of hand-mirrored ramp knobs.
72 changes: 72 additions & 0 deletions .changeset/twelve-shrimps-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
---
'@fuzdev/fuz_css': minor
---

feat: rework the color system to derived OKLCH, add semantic intents, cascade layers, and themes

The color system is now derived: curve knobs → ramp stops → color stops →
utility classes, computed in pure CSS (`calc()`/`pow()`/`oklch()`), fitted to
minimize the perceptual delta from the old HSL palette. Breaking changes:

- **`color_` family renamed to `palette_`**: variables `--color_a_50` →
`--palette_a_50`, token classes `.color_a_50` → `.palette_a_50`, and semantic
component classes `.color_a`–`.color_j` → `.palette_a`–`.palette_j`. In
compound class families the letter alone implies the palette:
`border_color_X_NN` → `border_X_NN`, `outline_color_X_NN` → `outline_X_NN`,
`shadow_color_X_NN` → `shadow_X_NN`, and `bg_X_NN` keeps its name. The
letterless families are unchanged (`border_color_NN` alpha ramp,
`outline_color_NN` shade outlines, `shadow_color_umbra` semantic colors).
At the TS level, the letter list in `variable_data.ts` renames with the
family: `ColorVariant`/`color_variants` → `PaletteVariant`/`palette_variants`.
- **`--hue_a`…`--hue_j` values reinterpreted** as OKLCH hue angles (blue is
now `250`, not HSL `210`). Any consumer CSS doing `hsl(var(--hue_x) …)`
breaks — use `oklch(<l> <c> var(--hue_x))` or the palette/intent stops.
- **`--tint_hue`/`--tint_saturation` removed** → `--hue_neutral` (defaults to
`var(--hue_f)`) + `--neutral_chroma` (peak chroma of the neutral scales).
- **Absolute `_light`/`_dark` variants removed**: the ~286 generated
variables (`--color_a_50_light`-style, including `--shade_XX_light/dark`)
and all their classes — `color_X_NN_light/dark`, `bg_X_NN_light/dark`, and
`shade_NN_light/dark`. Write the literal color or define one custom
property instead.
- **New curve knobs** (the promoted theme API): `--chroma_scale`,
`--hue_shift`, `--palette_lightness_00/_100/_curve` (same trio for
`shade_`/`text_`), `--palette_chroma_min/_max/_curve` (clamped per stop by
baked worst-hue sRGB gamut caps), plus derived per-stop variables
(`--palette_lightness_NN`, `--palette_chroma_NN`, `--chroma_shape_NN`,
`--hue_shift_NN`) that themes can pin individually.
- **New semantic intent knobs**: `--hue_accent`, `--hue_positive`,
`--hue_negative`, `--hue_caution`, `--hue_info`, each deriving a full
13-stop scale through the shared ramps (`--accent_00`…`--accent_100`, same
for positive/negative/caution/info) with matching lazily-generated text and
background token classes (`.positive_50`, `.bg_caution_10`), plus the
`--selection_color` site variable and the `intent_variants`/`IntentVariant`
list in `variable_data.ts`. Links, focus, selection, selected states,
`accent-color`, and disabled-active feedback all route through them; focus
now follows the element color (via `--outline_color`) with the accent as
fallback.
- **Cascade layers**: all shipped CSS is layered `fuz.base` (defaults) <
`fuz.theme` (theme overrides) < `fuz.utilities` (generated classes);
consumers' unlayered styles beat everything. `render_theme_style` lost its
`specificity` option (the `:root:root` hack) and gained
`layer?: string | null` (default `'fuz.theme'`); `generate_theme_css` lost
its specificity parameter; the `theme_specificity` generator option is
removed. `render_theme_style`'s default-theme special case now keys on
empty `variables` rather than the `'base'` name — a theme that carries
variables always renders them regardless of its name; an empty theme still
renders nothing by default and the full `default_variables` set under
`empty_default_theme: false`. Custom `base_css` input is re-layered into
`fuz.base` in bundled output — its own `@layer` identities aren't
preserved.
- **`color-mix()` interpolation moved from `in hsl` to `in oklab`** in button
fills/borders, composites, and shadow classes.
- **Themes**: one module per theme under `themes/`
(`@fuzdev/fuz_css/themes/necromancer.ts` etc.); `themes.ts` exports the
curated `default_themes` registry (base, low contrast, high contrast)
with unregistered expressive exemplars (necromancer, sunset ember,
brutalish, terminal green + the `create_terminal_theme(hue)` factory).
The contrast themes are rewritten as curve-knob overrides.
- **New design-time modules**: `ramps.ts` (fitted knob constants, numeric
evaluators, CSS emitters), `oklch.ts` (OKLCH↔sRGB + gamut math), `wcag.ts`
(luminance/contrast), with tests gating every default stop for gamut,
monotonicity, and contrast (AA/AAA thresholds the old palette partly
failed).
Loading