diff --git a/.changeset/empty-knives-walk.md b/.changeset/empty-knives-walk.md
new file mode 100644
index 000000000..dbfa077b1
--- /dev/null
+++ b/.changeset/empty-knives-walk.md
@@ -0,0 +1,5 @@
+---
+'@fuzdev/fuz_css': minor
+---
+
+implement CSS literal classes
diff --git a/.gitignore b/.gitignore
index bdef460f4..5117c8d01 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,14 +1,15 @@
# Deps
-node_modules
+node_modules/
# Output
-/.svelte-kit
-/build
-/dist
-/dist_*
-/target
-/.gro
-/.zzz
+.svelte-kit/
+build/
+dist/
+dist_*/
+target/
+.gro/
+.fuz/
+.zzz/
# Env
.env*
diff --git a/CLAUDE.md b/CLAUDE.md
index 2086a1452..7f1d6be49 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -1,9 +1,22 @@
-# Fuz CSS framework and design system
+# fuz_css framework and design system
-CSS framework and design system built around **semantic styles** and **style variables** (design tokens as CSS custom properties). Early alpha with breaking changes ahead.
+CSS framework and design system for semantic HTML.
+It styles HTML elements by default and integrates
+custom properties, themes, and utility classes into a complete system.
+Early alpha with breaking changes ahead.
For code style, see the `fuz-stack` skill. For UI components (themes, color scheme controls), see `@fuzdev/fuz_ui`.
+## Gro commands
+
+```bash
+gro check # typecheck, test, lint, format check (run before committing)
+gro typecheck # typecheck only (faster iteration)
+gro test # run tests (SKIP_EXAMPLE_TESTS=1 to skip slow integration tests)
+gro gen # regenerate theme.css and other .gen files
+gro build # build the package for production
+```
+
## Design decisions
### Two core concepts
@@ -26,7 +39,56 @@ For code style, see the `fuz-stack` skill. For UI components (themes, color sche
### Smart utility class generation
-[gen_fuz_css.ts](src/lib/gen_fuz_css.ts) scans source files with regex extractors, collects class names, and outputs only CSS for classes actually used. Dynamic [interpreters](src/lib/css_class_interpreters.ts) handle pattern-based classes like `opacity_50`, `font_weight_700`, `z_index_100`.
+Two generators available, both using AST-based extraction ([css_class_extractor.ts](src/lib/css_class_extractor.ts)) and per-file caching with content hash validation:
+
+1. **Gro generator** - [gen_fuz_css.ts](src/lib/gen_fuz_css.ts) for SvelteKit projects using Gro
+2. **Vite plugin** - [vite_plugin_fuz_css.ts](src/lib/vite_plugin_fuz_css.ts) for Svelte/React/Preact/Solid via `virtual:fuz.css`
+
+Both output only CSS for classes actually used. Supports Svelte 5.16+ class syntax, JSX `className`, clsx/cn calls, and `// @fuz-classes` comment hints.
+
+**Shared options (both generators):**
+
+- `filter_file` - Which files to extract from (default: `.svelte`, `.html`, `.ts`, `.js`, `.tsx`, `.jsx`, excluding tests/gen files)
+- `class_definitions` - Additional definitions to merge with builtins (user takes precedence)
+- `class_interpreters` - Custom interpreters (replaces builtins if provided)
+- `include_classes` - Classes to always include (for dynamic class names)
+- `exclude_classes` - Classes to exclude (filter false positives)
+- `acorn_plugins` - Additional acorn plugins (use `acorn-jsx` for React/Preact/Solid)
+- `on_error` - `'log'` (default) or `'throw'` for CSS-literal errors
+- `cache_dir` - Cache directory (default: `.fuz/cache/css`)
+
+**Gro-only options:**
+
+- `include_stats` - Include file statistics in output
+- `project_root` - Project root directory (default: `process.cwd()`)
+- `concurrency` - Max concurrent file processing (default: 8)
+- `cache_io_concurrency` - Max concurrent cache I/O (default: 50)
+
+**Key implementation differences:**
+
+- **HMR**: Vite plugin has HMR with 10ms debouncing; Gro regenerates on file change
+- **Cache writes**: Vite uses fire-and-forget; Gro awaits with concurrency control
+- **External files**: Both use hashed paths in `_external/` subdirectory for files outside project root (e.g., symlinked deps with pnpm)
+- **Error types**: Both throw `CssGenerationError` with `diagnostics` property for programmatic error access
+- **CI behavior**: Both skip cache writes on CI
+
+### Three class types
+
+- **Token classes** - Map to style variables: `p_md`, `color_a_5`, `gap_lg`
+- **Composite classes** - Multi-property shortcuts: `box`, `row`, `ellipsis`
+- **Literal classes** - CSS `property:value` syntax: `display:flex`, `opacity:50%`
+
+All class types support modifiers: responsive (`md:`), state (`hover:`), color-scheme (`dark:`), pseudo-element (`before:`).
+
+### CSS-literal syntax
+
+Literal classes use `property:value` syntax that maps 1:1 to CSS:
+
+- `display:flex` â `display: flex;`
+- `hover:opacity:80%` â `:hover { opacity: 80%; }`
+- `md:dark:hover:opacity:80%` â nested media/ancestor/state wrappers
+
+Space encoding uses `~` for multi-value properties (`margin:0~auto`). Arbitrary breakpoints via `min-width(800px):` and `max-width(600px):`.
## Variable naming
@@ -44,11 +106,30 @@ See [variables.ts](src/lib/variables.ts) for definitions, [variable_data.ts](src
## Usage
-Import [style.css](src/lib/style.css) + [theme.css](src/lib/theme.css) for base styles. Optionally generate project-specific `fuz.css` using [gen_fuz_css()](src/lib/gen_fuz_css.ts) in a Gro generator.
+Import [style.css](src/lib/style.css) + [theme.css](src/lib/theme.css) for base styles. Generate utility classes via:
+
+**SvelteKit (Gro):** Use [gen_fuz_css()](src/lib/gen_fuz_css.ts) in a `.gen.css.ts` file.
+
+**Vite (Svelte/React/Preact/Solid):**
+
+```ts
+// vite.config.ts
+import {vite_plugin_fuz_css} from '@fuzdev/fuz_css/vite_plugin_fuz_css.js';
+import jsx from 'acorn-jsx'; // only needed for JSX frameworks
+
+export default defineConfig({
+ plugins: [vite_plugin_fuz_css({acorn_plugins: [jsx()]})],
+});
+
+// main.ts
+import '@fuzdev/fuz_css/style.css';
+import '@fuzdev/fuz_css/theme.css'; // or bring your own
+import 'virtual:fuz.css';
+```
## Docs
-[src/routes/docs/](src/routes/docs/) has pages for: colors, themes, variables, classes, typography, buttons, forms, elements, layout, borders, shadows, shading. See [tomes.ts](src/routes/docs/tomes.ts) for structure.
+[src/routes/docs/](src/routes/docs/) has pages for: introduction, api, examples, semantic, themes, variables, classes, colors, buttons, elements, forms, typography, borders, shading, shadows, layout. See [tomes.ts](src/routes/docs/tomes.ts) for structure.
## File organization
@@ -58,18 +139,35 @@ Import [style.css](src/lib/style.css) + [theme.css](src/lib/theme.css) for base
- [variables.ts](src/lib/variables.ts) - All style variable definitions (~250+)
- [variable.ts](src/lib/variable.ts) - `StyleVariable` type and validation
-- [variable_data.ts](src/lib/variable_data.ts) - Size variants, color intensities, CSS data values
+- [variable_data.ts](src/lib/variable_data.ts) - Size, color, and border variant definitions
- [theme.ts](src/lib/theme.ts) - Theme rendering, `ColorScheme` type, `render_theme_style()`
- [themes.ts](src/lib/themes.ts) - Theme definitions (base, low/high contrast)
+**CSS extraction:**
+
+- [css_class_extractor.ts](src/lib/css_class_extractor.ts) - AST-based class extraction from Svelte/TS/JSX files
+- [file_filter.ts](src/lib/file_filter.ts) - `FileFilter` type and `filter_file_default` for filtering extractable files
+- [diagnostics.ts](src/lib/diagnostics.ts) - `SourceLocation`, `ExtractionDiagnostic`, `GenerationDiagnostic` types
+
**CSS generation:**
-- [gen_fuz_css.ts](src/lib/gen_fuz_css.ts) - Main generator API for Gro
-- [css_classes.ts](src/lib/css_classes.ts) - All static class definitions (~1000+)
-- [css_class_generators.ts](src/lib/css_class_generators.ts) - Class template generation functions
-- [css_class_composites.ts](src/lib/css_class_composites.ts) - Composite classes (`.box`, `.row`, `.column`, `.ellipsis`)
-- [css_class_interpreters.ts](src/lib/css_class_interpreters.ts) - Dynamic interpreters for opacity, font-weight, z-index, border-radius
-- [css_class_helpers.ts](src/lib/css_class_helpers.ts) - CSS class extraction, `CssClasses` collection, `generate_classes_css()`
+- [gen_fuz_css.ts](src/lib/gen_fuz_css.ts) - Gro generator API with per-file caching
+- [vite_plugin_fuz_css.ts](src/lib/vite_plugin_fuz_css.ts) - Vite plugin for Svelte/React/Preact/Solid, `virtual:fuz.css` virtual module with HMR
+- [css_cache.ts](src/lib/css_cache.ts) - Cache infrastructure (`.fuz/cache/css/`)
+- [css_classes.ts](src/lib/css_classes.ts) - `CssClasses` collection for tracking classes per-file
+- [css_class_generation.ts](src/lib/css_class_generation.ts) - `CssClassDefinition` types, `generate_classes_css()`
+- [css_class_definitions.ts](src/lib/css_class_definitions.ts) - Combined token + composite class registry
+- [css_class_generators.ts](src/lib/css_class_generators.ts) - Token class template generators
+- [css_class_composites.ts](src/lib/css_class_composites.ts) - Composite classes (`.box`, `.row`, `.column`, `.ellipsis`, `.pane`, `.panel`)
+- [css_class_resolution.ts](src/lib/css_class_resolution.ts) - `resolve_composes()` for composing definitions
+- [css_class_interpreters.ts](src/lib/css_class_interpreters.ts) - `modified_class_interpreter` and `css_literal_interpreter`
+- [css_literal.ts](src/lib/css_literal.ts) - CSS-literal parser and validator
+- [css_ruleset_parser.ts](src/lib/css_ruleset_parser.ts) - CSS ruleset parsing, selector modification
+- [modifiers.ts](src/lib/modifiers.ts) - Modifier definitions (breakpoints, states, pseudo-elements)
+
+**Example utilities:**
+
+- [example_class_utilities.ts](src/lib/example_class_utilities.ts) - Demo exports for testing node_modules extraction
**Stylesheets:**
@@ -83,7 +181,31 @@ Import [style.css](src/lib/style.css) + [theme.css](src/lib/theme.css) for base
- [fuz.css](src/routes/fuz.css) - Generated optimized utility classes for this site
- [fuz.gen.css.ts](src/routes/fuz.gen.css.ts) - Generator using `gen_fuz_css()`
+### Examples - [examples/](examples/)
+
+Vite plugin examples:
+
+- [vite-svelte/](examples/vite-svelte/) - Svelte 5 example
+- [vite-react/](examples/vite-react/) - React 19 example
+- [vite-preact/](examples/vite-preact/) - Preact example
+- [vite-solid/](examples/vite-solid/) - Solid example
+
+Each demonstrates token, composite, and literal classes with responsive/hover/dark modifiers. Uses classes from [example_class_utilities.ts](src/lib/example_class_utilities.ts) to verify node_modules extraction.
+
+**Important:** All 4 example App files must be kept in sync. When updating one, update all others with equivalent changes (accounting for framework differences like React's `className` vs others' `class`). The [vite_plugin_examples.test.ts](src/test/vite_plugin_examples.test.ts) verifies all examples produce the same CSS classes.
+
### Tests - [src/test/](src/test/)
+- [variable.test.ts](src/test/variable.test.ts) - StyleVariable type validation
- [variables.test.ts](src/test/variables.test.ts) - Variable consistency (no duplicates, valid names)
-- [css_class_helpers.test.ts](src/test/css_class_helpers.test.ts) - CSS extraction from Svelte/JS patterns
+- [styles.test.ts](src/test/styles.test.ts) - Style output tests
+- [css_classes.test.ts](src/test/css_classes.test.ts) - CssClasses collection tests
+- [css_cache.test.ts](src/test/css_cache.test.ts) - Cache save/load, version invalidation, atomic writes
+- [css_class_generation.test.ts](src/test/css_class_generation.test.ts) - CSS escaping, generation, interpreters
+- [css_class_resolution.test.ts](src/test/css_class_resolution.test.ts) - Class resolution, cycle detection
+- [css_class_extractor.test.ts](src/test/css_class_extractor.test.ts) - AST extraction, location tracking
+- [css_class_extractor.jsx.test.ts](src/test/css_class_extractor.jsx.test.ts) - JSX-specific extraction
+- [css_literal.test.ts](src/test/css_literal.test.ts) - CSS-literal parsing, validation, modifiers
+- [css_ruleset_parser.test.ts](src/test/css_ruleset_parser.test.ts) - Ruleset parsing, selector modification
+- [diagnostics.test.ts](src/test/diagnostics.test.ts) - Diagnostic formatting tests
+- [vite_plugin_examples.test.ts](src/test/vite_plugin_examples.test.ts) - Integration tests building examples (skip with `SKIP_EXAMPLE_TESTS=1`)
diff --git a/README.md b/README.md
index 743335684..1835d4be9 100644
--- a/README.md
+++ b/README.md
@@ -1,16 +1,20 @@
-# Fuz CSS
+# fuz_css
[
](https://css.fuz.dev/)
-> CSS framework and design system ðŋ magical organic stylesheets
+> CSS with more utility ðŠī
+
+fuz_css is a CSS framework and design system for semantic HTML.
+It styles elements by default and
+integrates custom properties, themes, and utility classes into a complete system.
+It's Svelte-first but works with plain HTML/JS/TS, React, Preact, Solid, and other JSX frameworks.
-Fuz CSS is a CSS framework and design system built around semantic styles and style variables.
It's in early alpha with breaking changes ahead.
+Feedback and design input in
+the [discussions](https://github.com/fuzdev/fuz_css/discussions).
-- view the [docs](https://css.fuz.dev/docs) at [css.fuz.dev](https://css.fuz.dev/)
-- help with feedback and design in the [issues](https://github.com/fuzdev/fuz_css/issues)
- and [discussions](https://github.com/fuzdev/fuz_css/discussions)
-- more about the stack at [fuz.dev](https://www.fuz.dev/)
+View the [docs](https://css.fuz.dev/docs) at [css.fuz.dev](https://css.fuz.dev/).
+More about the stack at [fuz.dev](https://www.fuz.dev/)
## License [ðĶ](https://wikipedia.org/wiki/Free_and_open-source_software)
diff --git a/eslint.config.js b/eslint.config.js
index 75527ca8a..c59655eb8 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -2,4 +2,5 @@ import {configs, ts_config} from '@ryanatkn/eslint-config';
ts_config.rules['no-console'] = 1;
-export default configs;
+// Ignore examples directory - each example has its own tsconfig
+export default [{ignores: ['examples/**']}, ...configs];
diff --git a/examples/vite-preact/index.html b/examples/vite-preact/index.html
new file mode 100644
index 000000000..75bf0acd5
--- /dev/null
+++ b/examples/vite-preact/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ fuz_css + Preact
+
+
+
+
+
+
diff --git a/examples/vite-preact/package-lock.json b/examples/vite-preact/package-lock.json
new file mode 100644
index 000000000..f0bd659c1
--- /dev/null
+++ b/examples/vite-preact/package-lock.json
@@ -0,0 +1,2158 @@
+{
+ "name": "vite-preact-fuz-css-example",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "vite-preact-fuz-css-example",
+ "dependencies": {
+ "preact": "^10"
+ },
+ "devDependencies": {
+ "@fuzdev/fuz_css": "file:../..",
+ "@preact/preset-vite": "^2",
+ "acorn-jsx": "^5",
+ "typescript": "^5",
+ "vite": "^7"
+ }
+ },
+ "../..": {
+ "name": "@fuzdev/fuz_css",
+ "version": "0.43.0",
+ "dev": true,
+ "license": "MIT",
+ "devDependencies": {
+ "@changesets/changelog-git": "^0.2.1",
+ "@fuzdev/fuz_code": "^0.39.0",
+ "@fuzdev/fuz_ui": "^0.177.1",
+ "@fuzdev/fuz_util": "^0.45.3",
+ "@ryanatkn/eslint-config": "^0.9.0",
+ "@ryanatkn/gro": "^0.185.0",
+ "@sveltejs/acorn-typescript": "^1.0.8",
+ "@sveltejs/adapter-static": "^3.0.10",
+ "@sveltejs/kit": "^2.49.1",
+ "@sveltejs/package": "^2.5.7",
+ "@sveltejs/vite-plugin-svelte": "^6.2.1",
+ "@types/node": "^24.10.1",
+ "@webref/css": "^8.1.3",
+ "acorn-jsx": "^5.3.2",
+ "eslint": "^9.39.1",
+ "eslint-plugin-svelte": "^3.13.1",
+ "prettier": "^3.7.4",
+ "prettier-plugin-svelte": "^3.4.1",
+ "svelte": "^5.45.6",
+ "svelte-check": "^4.3.4",
+ "tslib": "^2.8.1",
+ "typescript": "^5.9.3",
+ "typescript-eslint": "^8.48.1",
+ "vitest": "^4.0.15",
+ "zimmerframe": "^1.1.4",
+ "zod": "^4.1.13"
+ },
+ "engines": {
+ "node": ">=22.15"
+ },
+ "funding": {
+ "url": "https://www.ryanatkn.com/funding"
+ },
+ "peerDependencies": {
+ "@fuzdev/fuz_util": ">=0.42.0",
+ "@sveltejs/acorn-typescript": "^1",
+ "@webref/css": "^8",
+ "acorn-jsx": "^5",
+ "zimmerframe": "^1"
+ },
+ "peerDependenciesMeta": {
+ "@fuzdev/fuz_util": {
+ "optional": true
+ },
+ "@sveltejs/acorn-typescript": {
+ "optional": true
+ },
+ "@webref/css": {
+ "optional": true
+ },
+ "acorn-jsx": {
+ "optional": true
+ },
+ "zimmerframe": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz",
+ "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz",
+ "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz",
+ "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/generator": "^7.28.6",
+ "@babel/helper-compilation-targets": "^7.28.6",
+ "@babel/helper-module-transforms": "^7.28.6",
+ "@babel/helpers": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/template": "^7.28.6",
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz",
+ "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-annotate-as-pure": {
+ "version": "7.27.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz",
+ "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.27.3"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
+ "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.28.6",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
+ "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
+ "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.28.6",
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "@babel/traverse": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
+ "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz",
+ "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz",
+ "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.6"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-jsx": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz",
+ "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.28.6.tgz",
+ "integrity": "sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.27.3",
+ "@babel/helper-module-imports": "^7.28.6",
+ "@babel/helper-plugin-utils": "^7.28.6",
+ "@babel/plugin-syntax-jsx": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-development": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz",
+ "integrity": "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/plugin-transform-react-jsx": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
+ "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz",
+ "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/generator": "^7.28.6",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.28.6",
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.28.6",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz",
+ "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz",
+ "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz",
+ "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz",
+ "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz",
+ "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz",
+ "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz",
+ "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz",
+ "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz",
+ "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz",
+ "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz",
+ "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz",
+ "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz",
+ "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz",
+ "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz",
+ "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz",
+ "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz",
+ "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz",
+ "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz",
+ "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz",
+ "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz",
+ "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz",
+ "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz",
+ "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz",
+ "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz",
+ "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz",
+ "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz",
+ "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@fuzdev/fuz_css": {
+ "resolved": "../..",
+ "link": true
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@preact/preset-vite": {
+ "version": "2.10.2",
+ "resolved": "https://registry.npmjs.org/@preact/preset-vite/-/preset-vite-2.10.2.tgz",
+ "integrity": "sha512-K9wHlJOtkE+cGqlyQ5v9kL3Ge0Ql4LlIZjkUTL+1zf3nNdF88F9UZN6VTV8jdzBX9Fl7WSzeNMSDG7qECPmSmg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/plugin-transform-react-jsx": "^7.22.15",
+ "@babel/plugin-transform-react-jsx-development": "^7.22.5",
+ "@prefresh/vite": "^2.4.1",
+ "@rollup/pluginutils": "^4.1.1",
+ "babel-plugin-transform-hook-names": "^1.0.2",
+ "debug": "^4.3.4",
+ "picocolors": "^1.1.1",
+ "vite-prerender-plugin": "^0.5.3"
+ },
+ "peerDependencies": {
+ "@babel/core": "7.x",
+ "vite": "2.x || 3.x || 4.x || 5.x || 6.x || 7.x"
+ }
+ },
+ "node_modules/@prefresh/babel-plugin": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/@prefresh/babel-plugin/-/babel-plugin-0.5.2.tgz",
+ "integrity": "sha512-AOl4HG6dAxWkJ5ndPHBgBa49oo/9bOiJuRDKHLSTyH+Fd9x00shTXpdiTj1W41l6oQIwUOAgJeHMn4QwIDpHkA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@prefresh/core": {
+ "version": "1.5.9",
+ "resolved": "https://registry.npmjs.org/@prefresh/core/-/core-1.5.9.tgz",
+ "integrity": "sha512-IKBKCPaz34OFVC+adiQ2qaTF5qdztO2/4ZPf4KsRTgjKosWqxVXmEbxCiUydYZRY8GVie+DQlKzQr9gt6HQ+EQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "preact": "^10.0.0 || ^11.0.0-0"
+ }
+ },
+ "node_modules/@prefresh/utils": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@prefresh/utils/-/utils-1.2.1.tgz",
+ "integrity": "sha512-vq/sIuN5nYfYzvyayXI4C2QkprfNaHUQ9ZX+3xLD8nL3rWyzpxOm1+K7RtMbhd+66QcaISViK7amjnheQ/4WZw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@prefresh/vite": {
+ "version": "2.4.11",
+ "resolved": "https://registry.npmjs.org/@prefresh/vite/-/vite-2.4.11.tgz",
+ "integrity": "sha512-/XjURQqdRiCG3NpMmWqE9kJwrg9IchIOWHzulCfqg2sRe/8oQ1g5De7xrk9lbqPIQLn7ntBkKdqWXIj4E9YXyg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.22.1",
+ "@prefresh/babel-plugin": "0.5.2",
+ "@prefresh/core": "^1.5.0",
+ "@prefresh/utils": "^1.2.0",
+ "@rollup/pluginutils": "^4.2.1"
+ },
+ "peerDependencies": {
+ "preact": "^10.4.0 || ^11.0.0-0",
+ "vite": ">=2.0.0"
+ }
+ },
+ "node_modules/@rollup/pluginutils": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz",
+ "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "estree-walker": "^2.0.1",
+ "picomatch": "^2.2.2"
+ },
+ "engines": {
+ "node": ">= 8.0.0"
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz",
+ "integrity": "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.1.tgz",
+ "integrity": "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz",
+ "integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.1.tgz",
+ "integrity": "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz",
+ "integrity": "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.1.tgz",
+ "integrity": "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.1.tgz",
+ "integrity": "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.1.tgz",
+ "integrity": "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz",
+ "integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.1.tgz",
+ "integrity": "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.1.tgz",
+ "integrity": "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.1.tgz",
+ "integrity": "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.1.tgz",
+ "integrity": "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.1.tgz",
+ "integrity": "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.1.tgz",
+ "integrity": "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.1.tgz",
+ "integrity": "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.1.tgz",
+ "integrity": "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.1.tgz",
+ "integrity": "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.1.tgz",
+ "integrity": "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.1.tgz",
+ "integrity": "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz",
+ "integrity": "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.1.tgz",
+ "integrity": "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.1.tgz",
+ "integrity": "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.1.tgz",
+ "integrity": "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.1.tgz",
+ "integrity": "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/acorn": {
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/babel-plugin-transform-hook-names": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-hook-names/-/babel-plugin-transform-hook-names-1.0.2.tgz",
+ "integrity": "sha512-5gafyjyyBTTdX/tQQ0hRgu4AhNHG/hqWi0ZZmg2xvs2FgRkJXzDNKBZCyoYqgFkovfDrgM8OoKg8karoUvWeCw==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@babel/core": "^7.12.10"
+ }
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.9.14",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.14.tgz",
+ "integrity": "sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.js"
+ }
+ },
+ "node_modules/boolbase": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
+ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
+ "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.9.0",
+ "caniuse-lite": "^1.0.30001759",
+ "electron-to-chromium": "^1.5.263",
+ "node-releases": "^2.0.27",
+ "update-browserslist-db": "^1.2.0"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001764",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001764.tgz",
+ "integrity": "sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/css-select": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
+ "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "boolbase": "^1.0.0",
+ "css-what": "^6.1.0",
+ "domhandler": "^5.0.2",
+ "domutils": "^3.0.1",
+ "nth-check": "^2.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/fb55"
+ }
+ },
+ "node_modules/css-what": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz",
+ "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">= 6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/fb55"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/dom-serializer": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
+ "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.2",
+ "entities": "^4.2.0"
+ },
+ "funding": {
+ "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
+ }
+ },
+ "node_modules/domelementtype": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
+ "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fb55"
+ }
+ ],
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/domhandler": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
+ "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "domelementtype": "^2.3.0"
+ },
+ "engines": {
+ "node": ">= 4"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/domhandler?sponsor=1"
+ }
+ },
+ "node_modules/domutils": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
+ "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "dom-serializer": "^2.0.0",
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/domutils?sponsor=1"
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.267",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz",
+ "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz",
+ "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.27.2",
+ "@esbuild/android-arm": "0.27.2",
+ "@esbuild/android-arm64": "0.27.2",
+ "@esbuild/android-x64": "0.27.2",
+ "@esbuild/darwin-arm64": "0.27.2",
+ "@esbuild/darwin-x64": "0.27.2",
+ "@esbuild/freebsd-arm64": "0.27.2",
+ "@esbuild/freebsd-x64": "0.27.2",
+ "@esbuild/linux-arm": "0.27.2",
+ "@esbuild/linux-arm64": "0.27.2",
+ "@esbuild/linux-ia32": "0.27.2",
+ "@esbuild/linux-loong64": "0.27.2",
+ "@esbuild/linux-mips64el": "0.27.2",
+ "@esbuild/linux-ppc64": "0.27.2",
+ "@esbuild/linux-riscv64": "0.27.2",
+ "@esbuild/linux-s390x": "0.27.2",
+ "@esbuild/linux-x64": "0.27.2",
+ "@esbuild/netbsd-arm64": "0.27.2",
+ "@esbuild/netbsd-x64": "0.27.2",
+ "@esbuild/openbsd-arm64": "0.27.2",
+ "@esbuild/openbsd-x64": "0.27.2",
+ "@esbuild/openharmony-arm64": "0.27.2",
+ "@esbuild/sunos-x64": "0.27.2",
+ "@esbuild/win32-arm64": "0.27.2",
+ "@esbuild/win32-ia32": "0.27.2",
+ "@esbuild/win32-x64": "0.27.2"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/he": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "he": "bin/he"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/kolorist": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz",
+ "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/node-html-parser": {
+ "version": "6.1.13",
+ "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.13.tgz",
+ "integrity": "sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "css-select": "^5.1.0",
+ "he": "1.2.0"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.27",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
+ "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nth-check": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
+ "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "boolbase": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/nth-check?sponsor=1"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/preact": {
+ "version": "10.28.2",
+ "resolved": "https://registry.npmjs.org/preact/-/preact-10.28.2.tgz",
+ "integrity": "sha512-lbteaWGzGHdlIuiJ0l2Jq454m6kcpI1zNje6d8MlGAFlYvP2GO4ibnat7P74Esfz4sPTdM6UxtTwh/d3pwM9JA==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/preact"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz",
+ "integrity": "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.55.1",
+ "@rollup/rollup-android-arm64": "4.55.1",
+ "@rollup/rollup-darwin-arm64": "4.55.1",
+ "@rollup/rollup-darwin-x64": "4.55.1",
+ "@rollup/rollup-freebsd-arm64": "4.55.1",
+ "@rollup/rollup-freebsd-x64": "4.55.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.55.1",
+ "@rollup/rollup-linux-arm-musleabihf": "4.55.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.55.1",
+ "@rollup/rollup-linux-arm64-musl": "4.55.1",
+ "@rollup/rollup-linux-loong64-gnu": "4.55.1",
+ "@rollup/rollup-linux-loong64-musl": "4.55.1",
+ "@rollup/rollup-linux-ppc64-gnu": "4.55.1",
+ "@rollup/rollup-linux-ppc64-musl": "4.55.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.55.1",
+ "@rollup/rollup-linux-riscv64-musl": "4.55.1",
+ "@rollup/rollup-linux-s390x-gnu": "4.55.1",
+ "@rollup/rollup-linux-x64-gnu": "4.55.1",
+ "@rollup/rollup-linux-x64-musl": "4.55.1",
+ "@rollup/rollup-openbsd-x64": "4.55.1",
+ "@rollup/rollup-openharmony-arm64": "4.55.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.55.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.55.1",
+ "@rollup/rollup-win32-x64-gnu": "4.55.1",
+ "@rollup/rollup-win32-x64-msvc": "4.55.1",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/simple-code-frame": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/simple-code-frame/-/simple-code-frame-1.3.0.tgz",
+ "integrity": "sha512-MB4pQmETUBlNs62BBeRjIFGeuy/x6gGKh7+eRUemn1rCFhqo7K+4slPqsyizCbcbYLnaYqaoZ2FWsZ/jN06D8w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "kolorist": "^1.6.0"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.7.6",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz",
+ "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/stack-trace": {
+ "version": "1.0.0-pre2",
+ "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-1.0.0-pre2.tgz",
+ "integrity": "sha512-2ztBJRek8IVofG9DBJqdy2N5kulaacX30Nz7xmkYF6ale9WBVmIy6mFBchvGX7Vx/MyjBhx+Rcxqrj+dbOnQ6A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tinyglobby/node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tinyglobby/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "7.3.1",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
+ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.27.0",
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3",
+ "postcss": "^8.5.6",
+ "rollup": "^4.43.0",
+ "tinyglobby": "^0.2.15"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "lightningcss": "^1.21.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite-prerender-plugin": {
+ "version": "0.5.12",
+ "resolved": "https://registry.npmjs.org/vite-prerender-plugin/-/vite-prerender-plugin-0.5.12.tgz",
+ "integrity": "sha512-EiwhbMn+flg14EysbLTmZSzq8NGTxhytgK3bf4aGRF1evWLGwZiHiUJ1KZDvbxgKbMf2pG6fJWGEa3UZXOnR1g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "kolorist": "^1.8.0",
+ "magic-string": "0.x >= 0.26.0",
+ "node-html-parser": "^6.1.12",
+ "simple-code-frame": "^1.3.0",
+ "source-map": "^0.7.4",
+ "stack-trace": "^1.0.0-pre2"
+ },
+ "peerDependencies": {
+ "vite": "5.x || 6.x || 7.x"
+ }
+ },
+ "node_modules/vite/node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ }
+ }
+}
diff --git a/examples/vite-preact/package.json b/examples/vite-preact/package.json
new file mode 100644
index 000000000..a386926a6
--- /dev/null
+++ b/examples/vite-preact/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "vite-preact-fuz-css-example",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "preact": "^10"
+ },
+ "devDependencies": {
+ "@fuzdev/fuz_css": "file:../..",
+ "@preact/preset-vite": "^2",
+ "acorn-jsx": "^5",
+ "typescript": "^5",
+ "vite": "^7"
+ }
+}
diff --git a/examples/vite-preact/src/App.tsx b/examples/vite-preact/src/App.tsx
new file mode 100644
index 000000000..945b434e9
--- /dev/null
+++ b/examples/vite-preact/src/App.tsx
@@ -0,0 +1,152 @@
+import {useState} from 'preact/hooks';
+
+// Import from node_modules to verify extraction works for dependencies
+import {
+ // Naming patterns (all CLASS_NAME_PATTERN suffix variants)
+ demoClass,
+ demo_class,
+ demoClasses,
+ demo_classes,
+ demoClassName,
+ demo_class_name,
+ demoClassNames,
+ demo_class_names,
+ demoClassList,
+ demo_class_list,
+ demoClassLists,
+ demo_class_lists,
+ DEMO_CLASS,
+ // Expression patterns
+ ternaryClass,
+ logicalClass,
+ arrayClasses,
+ objectClasses,
+ // Comment hint examples
+ fromComment,
+} from '@fuzdev/fuz_css/example_class_utilities.js';
+
+export const App = () => {
+ const [count, setCount] = useState(0);
+
+ return (
+
+
+
+ fuz_css + Preact
+ Utility classes generated on-demand via Vite plugin (docs, source)
+
+
+ {/* Class types */}
+
+ Class types
+
+
+
Token classes
+
.p_md .bg_d_2
+
.pl_xl5 .font_size_lg
+
.shadow_sm
+
+
+
+
Composite classes
+
.box
+
+ .ellipsis -- this text truncates with ellipsis when it overflows
+
+
+
+
+
Literal classes
+
.opacity:60%
+
.color:var(--color_j_5)
+
.box-shadow:0~4px~8px~rgb(0,0,0,0.2) (~ encodes spaces)
+
+
+
+ {/* Modifiers */}
+
+ Modifiers
+
+
+
Responsive
+
+
+
.column .gap_md on mobile, .md:flex-direction:row .md:gap_lg on medium+ screens
+
+
+
Resize the window to see the layout switch from column to row
+
+
+
.min-width(543px):font_size_lg -- arbitrary breakpoint
+
+
+
+
Interactive
+
+
+ .hover:border_color_b .hover:outline_color_b .active:border_color_d .active:outline_color_d
+
+
+
+ .hover:border_color_g .hover:outline_color_g .active:border_color_h .active:outline_color_h
+
+
+
+
+ {/* Extraction */}
+
+ Extraction
+ Classes detected via naming conventions, expressions, and comments (examples imported from node_modules to verify dependency scanning)
+
+
+
Naming patterns
+
demoClass: .{demoClass}
+
demo_class: .{demo_class}
+
DEMO_CLASS: .{DEMO_CLASS}
+
demoClasses: .mb_xs2 .ml_xs
+
demo_classes: .mb_xs .ml_sm
+
demoClassName: .{demoClassName}
+
demo_class_name: .{demo_class_name}
+
demoClassNames: .mb_lg .ml_md
+
demo_class_names: .mb_xl .ml_lg
+
demoClassList: .{demoClassList}
+
demo_class_list: .{demo_class_list}
+
demoClassLists: .mb_xl4 .ml_xl
+
demo_class_lists: .mb_xl5 .ml_xl2
+
+
+
+
Expression patterns
+
+ {`true ? 'mt_xs' : 'mt_sm'`} â .{ternaryClass} (both branches extracted)
+
+
+ {`true && 'mt_md'`} â .{logicalClass}
+
+
+ {`['mt_lg', 'mt_xl']`} â .{arrayClasses.join(', .')}
+
+
+ {`{ mt_xl2: 'mt_xl2', mt_xl3: 'mt_xl3' }`} â keys extracted from object
+
+
+
+
+
Comment hints
+
+
+
+
+
+
+
+ );
+};
diff --git a/examples/vite-preact/src/main.tsx b/examples/vite-preact/src/main.tsx
new file mode 100644
index 000000000..7e2989347
--- /dev/null
+++ b/examples/vite-preact/src/main.tsx
@@ -0,0 +1,8 @@
+import '@fuzdev/fuz_css/style.css';
+import '@fuzdev/fuz_css/theme.css';
+import 'virtual:fuz.css';
+
+import {render} from 'preact';
+import {App} from './App.tsx';
+
+render(, document.getElementById('root')!);
diff --git a/examples/vite-preact/src/vite-env.d.ts b/examples/vite-preact/src/vite-env.d.ts
new file mode 100644
index 000000000..241d205b0
--- /dev/null
+++ b/examples/vite-preact/src/vite-env.d.ts
@@ -0,0 +1,6 @@
+///
+
+declare module 'virtual:fuz.css' {
+ const css: string;
+ export default css;
+}
diff --git a/examples/vite-preact/tsconfig.json b/examples/vite-preact/tsconfig.json
new file mode 100644
index 000000000..79c4a1d61
--- /dev/null
+++ b/examples/vite-preact/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "isolatedModules": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "jsx": "react-jsx",
+ "jsxImportSource": "preact",
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["src"]
+}
diff --git a/examples/vite-preact/vite.config.ts b/examples/vite-preact/vite.config.ts
new file mode 100644
index 000000000..88f82c483
--- /dev/null
+++ b/examples/vite-preact/vite.config.ts
@@ -0,0 +1,14 @@
+import {defineConfig} from 'vite';
+import preact from '@preact/preset-vite';
+import jsx from 'acorn-jsx';
+import {vite_plugin_fuz_css} from '@fuzdev/fuz_css/vite_plugin_fuz_css.js';
+
+export default defineConfig({
+ plugins: [
+ // Plugin order doesn't matter for preact (fuz_css uses enforce: 'pre')
+ preact(),
+ vite_plugin_fuz_css({
+ acorn_plugins: [jsx()],
+ }),
+ ],
+});
diff --git a/examples/vite-react/index.html b/examples/vite-react/index.html
new file mode 100644
index 000000000..7ede35528
--- /dev/null
+++ b/examples/vite-react/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ fuz_css + React
+
+
+
+
+
+
diff --git a/examples/vite-react/package-lock.json b/examples/vite-react/package-lock.json
new file mode 100644
index 000000000..6ad9b4ae6
--- /dev/null
+++ b/examples/vite-react/package-lock.json
@@ -0,0 +1,1909 @@
+{
+ "name": "vite-react-fuz-css-example",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "vite-react-fuz-css-example",
+ "dependencies": {
+ "react": "^19",
+ "react-dom": "^19"
+ },
+ "devDependencies": {
+ "@fuzdev/fuz_css": "file:../..",
+ "@types/react": "^19",
+ "@types/react-dom": "^19",
+ "@vitejs/plugin-react": "^5",
+ "acorn-jsx": "^5",
+ "typescript": "^5",
+ "vite": "^7"
+ }
+ },
+ "../..": {
+ "name": "@fuzdev/fuz_css",
+ "version": "0.43.0",
+ "dev": true,
+ "license": "MIT",
+ "devDependencies": {
+ "@changesets/changelog-git": "^0.2.1",
+ "@fuzdev/fuz_code": "^0.39.0",
+ "@fuzdev/fuz_ui": "^0.177.1",
+ "@fuzdev/fuz_util": "^0.45.3",
+ "@ryanatkn/eslint-config": "^0.9.0",
+ "@ryanatkn/gro": "^0.185.0",
+ "@sveltejs/acorn-typescript": "^1.0.8",
+ "@sveltejs/adapter-static": "^3.0.10",
+ "@sveltejs/kit": "^2.49.1",
+ "@sveltejs/package": "^2.5.7",
+ "@sveltejs/vite-plugin-svelte": "^6.2.1",
+ "@types/node": "^24.10.1",
+ "@webref/css": "^8.1.3",
+ "acorn-jsx": "^5.3.2",
+ "eslint": "^9.39.1",
+ "eslint-plugin-svelte": "^3.13.1",
+ "prettier": "^3.7.4",
+ "prettier-plugin-svelte": "^3.4.1",
+ "svelte": "^5.45.6",
+ "svelte-check": "^4.3.4",
+ "tslib": "^2.8.1",
+ "typescript": "^5.9.3",
+ "typescript-eslint": "^8.48.1",
+ "vitest": "^4.0.15",
+ "zimmerframe": "^1.1.4",
+ "zod": "^4.1.13"
+ },
+ "engines": {
+ "node": ">=22.15"
+ },
+ "funding": {
+ "url": "https://www.ryanatkn.com/funding"
+ },
+ "peerDependencies": {
+ "@fuzdev/fuz_util": ">=0.42.0",
+ "@sveltejs/acorn-typescript": "^1",
+ "@webref/css": "^8",
+ "acorn-jsx": "^5",
+ "zimmerframe": "^1"
+ },
+ "peerDependenciesMeta": {
+ "@fuzdev/fuz_util": {
+ "optional": true
+ },
+ "@sveltejs/acorn-typescript": {
+ "optional": true
+ },
+ "@webref/css": {
+ "optional": true
+ },
+ "acorn-jsx": {
+ "optional": true
+ },
+ "zimmerframe": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz",
+ "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz",
+ "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz",
+ "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/generator": "^7.28.6",
+ "@babel/helper-compilation-targets": "^7.28.6",
+ "@babel/helper-module-transforms": "^7.28.6",
+ "@babel/helpers": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/template": "^7.28.6",
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz",
+ "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
+ "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.28.6",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
+ "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
+ "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.28.6",
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "@babel/traverse": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
+ "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz",
+ "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz",
+ "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.6"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
+ "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
+ "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
+ "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz",
+ "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/generator": "^7.28.6",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.28.6",
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.28.6",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz",
+ "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz",
+ "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz",
+ "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz",
+ "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz",
+ "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz",
+ "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz",
+ "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz",
+ "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz",
+ "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz",
+ "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz",
+ "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz",
+ "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz",
+ "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz",
+ "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz",
+ "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz",
+ "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz",
+ "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz",
+ "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz",
+ "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz",
+ "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz",
+ "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz",
+ "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz",
+ "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz",
+ "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz",
+ "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz",
+ "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz",
+ "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@fuzdev/fuz_css": {
+ "resolved": "../..",
+ "link": true
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-beta.53",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz",
+ "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz",
+ "integrity": "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.1.tgz",
+ "integrity": "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz",
+ "integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.1.tgz",
+ "integrity": "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz",
+ "integrity": "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.1.tgz",
+ "integrity": "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.1.tgz",
+ "integrity": "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.1.tgz",
+ "integrity": "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz",
+ "integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.1.tgz",
+ "integrity": "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.1.tgz",
+ "integrity": "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.1.tgz",
+ "integrity": "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.1.tgz",
+ "integrity": "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.1.tgz",
+ "integrity": "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.1.tgz",
+ "integrity": "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.1.tgz",
+ "integrity": "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.1.tgz",
+ "integrity": "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.1.tgz",
+ "integrity": "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.1.tgz",
+ "integrity": "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.1.tgz",
+ "integrity": "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz",
+ "integrity": "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.1.tgz",
+ "integrity": "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.1.tgz",
+ "integrity": "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.1.tgz",
+ "integrity": "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.1.tgz",
+ "integrity": "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "19.2.8",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.8.tgz",
+ "integrity": "sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "19.2.3",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
+ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^19.2.0"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.2.tgz",
+ "integrity": "sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.28.5",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-beta.53",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.18.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.9.14",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.14.tgz",
+ "integrity": "sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.js"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
+ "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.9.0",
+ "caniuse-lite": "^1.0.30001759",
+ "electron-to-chromium": "^1.5.263",
+ "node-releases": "^2.0.27",
+ "update-browserslist-db": "^1.2.0"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001764",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001764.tgz",
+ "integrity": "sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.267",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz",
+ "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/esbuild": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz",
+ "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.27.2",
+ "@esbuild/android-arm": "0.27.2",
+ "@esbuild/android-arm64": "0.27.2",
+ "@esbuild/android-x64": "0.27.2",
+ "@esbuild/darwin-arm64": "0.27.2",
+ "@esbuild/darwin-x64": "0.27.2",
+ "@esbuild/freebsd-arm64": "0.27.2",
+ "@esbuild/freebsd-x64": "0.27.2",
+ "@esbuild/linux-arm": "0.27.2",
+ "@esbuild/linux-arm64": "0.27.2",
+ "@esbuild/linux-ia32": "0.27.2",
+ "@esbuild/linux-loong64": "0.27.2",
+ "@esbuild/linux-mips64el": "0.27.2",
+ "@esbuild/linux-ppc64": "0.27.2",
+ "@esbuild/linux-riscv64": "0.27.2",
+ "@esbuild/linux-s390x": "0.27.2",
+ "@esbuild/linux-x64": "0.27.2",
+ "@esbuild/netbsd-arm64": "0.27.2",
+ "@esbuild/netbsd-x64": "0.27.2",
+ "@esbuild/openbsd-arm64": "0.27.2",
+ "@esbuild/openbsd-x64": "0.27.2",
+ "@esbuild/openharmony-arm64": "0.27.2",
+ "@esbuild/sunos-x64": "0.27.2",
+ "@esbuild/win32-arm64": "0.27.2",
+ "@esbuild/win32-ia32": "0.27.2",
+ "@esbuild/win32-x64": "0.27.2"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.27",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
+ "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.2.3",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
+ "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.2.3",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
+ "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.27.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.3"
+ }
+ },
+ "node_modules/react-refresh": {
+ "version": "0.18.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz",
+ "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz",
+ "integrity": "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.55.1",
+ "@rollup/rollup-android-arm64": "4.55.1",
+ "@rollup/rollup-darwin-arm64": "4.55.1",
+ "@rollup/rollup-darwin-x64": "4.55.1",
+ "@rollup/rollup-freebsd-arm64": "4.55.1",
+ "@rollup/rollup-freebsd-x64": "4.55.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.55.1",
+ "@rollup/rollup-linux-arm-musleabihf": "4.55.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.55.1",
+ "@rollup/rollup-linux-arm64-musl": "4.55.1",
+ "@rollup/rollup-linux-loong64-gnu": "4.55.1",
+ "@rollup/rollup-linux-loong64-musl": "4.55.1",
+ "@rollup/rollup-linux-ppc64-gnu": "4.55.1",
+ "@rollup/rollup-linux-ppc64-musl": "4.55.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.55.1",
+ "@rollup/rollup-linux-riscv64-musl": "4.55.1",
+ "@rollup/rollup-linux-s390x-gnu": "4.55.1",
+ "@rollup/rollup-linux-x64-gnu": "4.55.1",
+ "@rollup/rollup-linux-x64-musl": "4.55.1",
+ "@rollup/rollup-openbsd-x64": "4.55.1",
+ "@rollup/rollup-openharmony-arm64": "4.55.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.55.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.55.1",
+ "@rollup/rollup-win32-x64-gnu": "4.55.1",
+ "@rollup/rollup-win32-x64-msvc": "4.55.1",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+ "license": "MIT"
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "7.3.1",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
+ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.27.0",
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3",
+ "postcss": "^8.5.6",
+ "rollup": "^4.43.0",
+ "tinyglobby": "^0.2.15"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "lightningcss": "^1.21.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ }
+ }
+}
diff --git a/examples/vite-react/package.json b/examples/vite-react/package.json
new file mode 100644
index 000000000..30f1fb351
--- /dev/null
+++ b/examples/vite-react/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "vite-react-fuz-css-example",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "react": "^19",
+ "react-dom": "^19"
+ },
+ "devDependencies": {
+ "@fuzdev/fuz_css": "file:../..",
+ "@types/react": "^19",
+ "@types/react-dom": "^19",
+ "@vitejs/plugin-react": "^5",
+ "acorn-jsx": "^5",
+ "typescript": "^5",
+ "vite": "^7"
+ }
+}
diff --git a/examples/vite-react/src/App.tsx b/examples/vite-react/src/App.tsx
new file mode 100644
index 000000000..c586a2ae5
--- /dev/null
+++ b/examples/vite-react/src/App.tsx
@@ -0,0 +1,152 @@
+import {useState} from 'react';
+
+// Import from node_modules to verify extraction works for dependencies
+import {
+ // Naming patterns (all CLASS_NAME_PATTERN suffix variants)
+ demoClass,
+ demo_class,
+ demoClasses,
+ demo_classes,
+ demoClassName,
+ demo_class_name,
+ demoClassNames,
+ demo_class_names,
+ demoClassList,
+ demo_class_list,
+ demoClassLists,
+ demo_class_lists,
+ DEMO_CLASS,
+ // Expression patterns
+ ternaryClass,
+ logicalClass,
+ arrayClasses,
+ objectClasses,
+ // Comment hint examples
+ fromComment,
+} from '@fuzdev/fuz_css/example_class_utilities.js';
+
+export const App = () => {
+ const [count, setCount] = useState(0);
+
+ return (
+
+
+
+
+ {/* Class types */}
+
+ Class types
+
+
+
Token classes
+
.p_md .bg_d_2
+
.pl_xl5 .font_size_lg
+
.shadow_sm
+
+
+
+
Composite classes
+
.box
+
+ .ellipsis -- this text truncates with ellipsis when it overflows
+
+
+
+
+
Literal classes
+
.opacity:60%
+
.color:var(--color_j_5)
+
.box-shadow:0~4px~8px~rgb(0,0,0,0.2) (~ encodes spaces)
+
+
+
+ {/* Modifiers */}
+
+ Modifiers
+
+
+
Responsive
+
+
+
.column .gap_md on mobile, .md:flex-direction:row .md:gap_lg on medium+ screens
+
+
+
Resize the window to see the layout switch from column to row
+
+
+
.min-width(543px):font_size_lg -- arbitrary breakpoint
+
+
+
+
Interactive
+
+
+ .hover:border_color_b .hover:outline_color_b .active:border_color_d .active:outline_color_d
+
+
+
+ .hover:border_color_g .hover:outline_color_g .active:border_color_h .active:outline_color_h
+
+
+
+
+ {/* Extraction */}
+
+ Extraction
+ Classes detected via naming conventions, expressions, and comments (examples imported from node_modules to verify dependency scanning)
+
+
+
Naming patterns
+
demoClass: .{demoClass}
+
demo_class: .{demo_class}
+
DEMO_CLASS: .{DEMO_CLASS}
+
demoClasses: .mb_xs2 .ml_xs
+
demo_classes: .mb_xs .ml_sm
+
demoClassName: .{demoClassName}
+
demo_class_name: .{demo_class_name}
+
demoClassNames: .mb_lg .ml_md
+
demo_class_names: .mb_xl .ml_lg
+
demoClassList: .{demoClassList}
+
demo_class_list: .{demo_class_list}
+
demoClassLists: .mb_xl4 .ml_xl
+
demo_class_lists: .mb_xl5 .ml_xl2
+
+
+
+
Expression patterns
+
+ {`true ? 'mt_xs' : 'mt_sm'`} â .{ternaryClass} (both branches extracted)
+
+
+ {`true && 'mt_md'`} â .{logicalClass}
+
+
+ {`['mt_lg', 'mt_xl']`} â .{arrayClasses.join(', .')}
+
+
+ {`{ mt_xl2: 'mt_xl2', mt_xl3: 'mt_xl3' }`} â keys extracted from object
+
+
+
+
+
Comment hints
+
+ // @fuz-classes {fromComment} â .{fromComment}
+
+
+
+
+
+
+
+ );
+};
diff --git a/examples/vite-react/src/main.tsx b/examples/vite-react/src/main.tsx
new file mode 100644
index 000000000..4f3067a01
--- /dev/null
+++ b/examples/vite-react/src/main.tsx
@@ -0,0 +1,8 @@
+import '@fuzdev/fuz_css/style.css';
+import '@fuzdev/fuz_css/theme.css';
+import 'virtual:fuz.css';
+
+import {createRoot} from 'react-dom/client';
+import {App} from './App.tsx';
+
+createRoot(document.getElementById('root')!).render();
diff --git a/examples/vite-react/src/vite-env.d.ts b/examples/vite-react/src/vite-env.d.ts
new file mode 100644
index 000000000..241d205b0
--- /dev/null
+++ b/examples/vite-react/src/vite-env.d.ts
@@ -0,0 +1,6 @@
+///
+
+declare module 'virtual:fuz.css' {
+ const css: string;
+ export default css;
+}
diff --git a/examples/vite-react/tsconfig.json b/examples/vite-react/tsconfig.json
new file mode 100644
index 000000000..a31bbb0e8
--- /dev/null
+++ b/examples/vite-react/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "isolatedModules": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "jsx": "react-jsx",
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["src"]
+}
diff --git a/examples/vite-react/vite.config.ts b/examples/vite-react/vite.config.ts
new file mode 100644
index 000000000..fa39ab941
--- /dev/null
+++ b/examples/vite-react/vite.config.ts
@@ -0,0 +1,14 @@
+import {defineConfig} from 'vite';
+import react from '@vitejs/plugin-react';
+import jsx from 'acorn-jsx';
+import {vite_plugin_fuz_css} from '@fuzdev/fuz_css/vite_plugin_fuz_css.js';
+
+export default defineConfig({
+ plugins: [
+ // Plugin order doesn't matter for react (fuz_css uses enforce: 'pre')
+ react(),
+ vite_plugin_fuz_css({
+ acorn_plugins: [jsx()],
+ }),
+ ],
+});
diff --git a/examples/vite-solid/index.html b/examples/vite-solid/index.html
new file mode 100644
index 000000000..7e289f88e
--- /dev/null
+++ b/examples/vite-solid/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ fuz_css + Solid
+
+
+
+
+
+
diff --git a/examples/vite-solid/package-lock.json b/examples/vite-solid/package-lock.json
new file mode 100644
index 000000000..02beccdcb
--- /dev/null
+++ b/examples/vite-solid/package-lock.json
@@ -0,0 +1,2007 @@
+{
+ "name": "vite-solid-fuz-css-example",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "vite-solid-fuz-css-example",
+ "dependencies": {
+ "solid-js": "^1"
+ },
+ "devDependencies": {
+ "@fuzdev/fuz_css": "file:../..",
+ "acorn-jsx": "^5",
+ "typescript": "^5",
+ "vite": "^7",
+ "vite-plugin-solid": "^2"
+ }
+ },
+ "../..": {
+ "name": "@fuzdev/fuz_css",
+ "version": "0.43.0",
+ "dev": true,
+ "license": "MIT",
+ "devDependencies": {
+ "@changesets/changelog-git": "^0.2.1",
+ "@fuzdev/fuz_code": "^0.39.0",
+ "@fuzdev/fuz_ui": "^0.177.1",
+ "@fuzdev/fuz_util": "^0.45.3",
+ "@ryanatkn/eslint-config": "^0.9.0",
+ "@ryanatkn/gro": "^0.185.0",
+ "@sveltejs/acorn-typescript": "^1.0.8",
+ "@sveltejs/adapter-static": "^3.0.10",
+ "@sveltejs/kit": "^2.49.1",
+ "@sveltejs/package": "^2.5.7",
+ "@sveltejs/vite-plugin-svelte": "^6.2.1",
+ "@types/node": "^24.10.1",
+ "@webref/css": "^8.1.3",
+ "acorn-jsx": "^5.3.2",
+ "eslint": "^9.39.1",
+ "eslint-plugin-svelte": "^3.13.1",
+ "prettier": "^3.7.4",
+ "prettier-plugin-svelte": "^3.4.1",
+ "svelte": "^5.45.6",
+ "svelte-check": "^4.3.4",
+ "tslib": "^2.8.1",
+ "typescript": "^5.9.3",
+ "typescript-eslint": "^8.48.1",
+ "vitest": "^4.0.15",
+ "zimmerframe": "^1.1.4",
+ "zod": "^4.1.13"
+ },
+ "engines": {
+ "node": ">=22.15"
+ },
+ "funding": {
+ "url": "https://www.ryanatkn.com/funding"
+ },
+ "peerDependencies": {
+ "@fuzdev/fuz_util": ">=0.42.0",
+ "@sveltejs/acorn-typescript": "^1",
+ "@webref/css": "^8",
+ "acorn-jsx": "^5",
+ "zimmerframe": "^1"
+ },
+ "peerDependenciesMeta": {
+ "@fuzdev/fuz_util": {
+ "optional": true
+ },
+ "@sveltejs/acorn-typescript": {
+ "optional": true
+ },
+ "@webref/css": {
+ "optional": true
+ },
+ "acorn-jsx": {
+ "optional": true
+ },
+ "zimmerframe": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz",
+ "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz",
+ "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz",
+ "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/generator": "^7.28.6",
+ "@babel/helper-compilation-targets": "^7.28.6",
+ "@babel/helper-module-transforms": "^7.28.6",
+ "@babel/helpers": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/template": "^7.28.6",
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz",
+ "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
+ "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.28.6",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
+ "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
+ "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.28.6",
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "@babel/traverse": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
+ "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz",
+ "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz",
+ "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.6"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-jsx": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz",
+ "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
+ "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz",
+ "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/generator": "^7.28.6",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.28.6",
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.28.6",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz",
+ "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz",
+ "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz",
+ "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz",
+ "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz",
+ "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz",
+ "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz",
+ "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz",
+ "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz",
+ "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz",
+ "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz",
+ "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz",
+ "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz",
+ "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz",
+ "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz",
+ "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz",
+ "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz",
+ "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz",
+ "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz",
+ "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz",
+ "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz",
+ "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz",
+ "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz",
+ "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz",
+ "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz",
+ "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz",
+ "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz",
+ "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@fuzdev/fuz_css": {
+ "resolved": "../..",
+ "link": true
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz",
+ "integrity": "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.1.tgz",
+ "integrity": "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz",
+ "integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.1.tgz",
+ "integrity": "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz",
+ "integrity": "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.1.tgz",
+ "integrity": "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.1.tgz",
+ "integrity": "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.1.tgz",
+ "integrity": "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz",
+ "integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.1.tgz",
+ "integrity": "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.1.tgz",
+ "integrity": "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.1.tgz",
+ "integrity": "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.1.tgz",
+ "integrity": "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.1.tgz",
+ "integrity": "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.1.tgz",
+ "integrity": "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.1.tgz",
+ "integrity": "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.1.tgz",
+ "integrity": "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.1.tgz",
+ "integrity": "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.1.tgz",
+ "integrity": "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.1.tgz",
+ "integrity": "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz",
+ "integrity": "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.1.tgz",
+ "integrity": "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.1.tgz",
+ "integrity": "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.1.tgz",
+ "integrity": "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.1.tgz",
+ "integrity": "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/acorn": {
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/babel-plugin-jsx-dom-expressions": {
+ "version": "0.40.3",
+ "resolved": "https://registry.npmjs.org/babel-plugin-jsx-dom-expressions/-/babel-plugin-jsx-dom-expressions-0.40.3.tgz",
+ "integrity": "sha512-5HOwwt0BYiv/zxl7j8Pf2bGL6rDXfV6nUhLs8ygBX+EFJXzBPHM/euj9j/6deMZ6wa52Wb2PBaAV5U/jKwIY1w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "7.18.6",
+ "@babel/plugin-syntax-jsx": "^7.18.6",
+ "@babel/types": "^7.20.7",
+ "html-entities": "2.3.3",
+ "parse5": "^7.1.2"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.20.12"
+ }
+ },
+ "node_modules/babel-plugin-jsx-dom-expressions/node_modules/@babel/helper-module-imports": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz",
+ "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.18.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/babel-preset-solid": {
+ "version": "1.9.10",
+ "resolved": "https://registry.npmjs.org/babel-preset-solid/-/babel-preset-solid-1.9.10.tgz",
+ "integrity": "sha512-HCelrgua/Y+kqO8RyL04JBWS/cVdrtUv/h45GntgQY+cJl4eBcKkCDV3TdMjtKx1nXwRaR9QXslM/Npm1dxdZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "babel-plugin-jsx-dom-expressions": "^0.40.3"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0",
+ "solid-js": "^1.9.10"
+ },
+ "peerDependenciesMeta": {
+ "solid-js": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.9.14",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.14.tgz",
+ "integrity": "sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.js"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
+ "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.9.0",
+ "caniuse-lite": "^1.0.30001759",
+ "electron-to-chromium": "^1.5.263",
+ "node-releases": "^2.0.27",
+ "update-browserslist-db": "^1.2.0"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001764",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001764.tgz",
+ "integrity": "sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.267",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz",
+ "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/entities": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
+ "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz",
+ "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.27.2",
+ "@esbuild/android-arm": "0.27.2",
+ "@esbuild/android-arm64": "0.27.2",
+ "@esbuild/android-x64": "0.27.2",
+ "@esbuild/darwin-arm64": "0.27.2",
+ "@esbuild/darwin-x64": "0.27.2",
+ "@esbuild/freebsd-arm64": "0.27.2",
+ "@esbuild/freebsd-x64": "0.27.2",
+ "@esbuild/linux-arm": "0.27.2",
+ "@esbuild/linux-arm64": "0.27.2",
+ "@esbuild/linux-ia32": "0.27.2",
+ "@esbuild/linux-loong64": "0.27.2",
+ "@esbuild/linux-mips64el": "0.27.2",
+ "@esbuild/linux-ppc64": "0.27.2",
+ "@esbuild/linux-riscv64": "0.27.2",
+ "@esbuild/linux-s390x": "0.27.2",
+ "@esbuild/linux-x64": "0.27.2",
+ "@esbuild/netbsd-arm64": "0.27.2",
+ "@esbuild/netbsd-x64": "0.27.2",
+ "@esbuild/openbsd-arm64": "0.27.2",
+ "@esbuild/openbsd-x64": "0.27.2",
+ "@esbuild/openharmony-arm64": "0.27.2",
+ "@esbuild/sunos-x64": "0.27.2",
+ "@esbuild/win32-arm64": "0.27.2",
+ "@esbuild/win32-ia32": "0.27.2",
+ "@esbuild/win32-x64": "0.27.2"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/html-entities": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz",
+ "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/is-what": {
+ "version": "4.1.16",
+ "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz",
+ "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.13"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mesqueeb"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/merge-anything": {
+ "version": "5.1.7",
+ "resolved": "https://registry.npmjs.org/merge-anything/-/merge-anything-5.1.7.tgz",
+ "integrity": "sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-what": "^4.1.8"
+ },
+ "engines": {
+ "node": ">=12.13"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mesqueeb"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.27",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
+ "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/parse5": {
+ "version": "7.3.0",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
+ "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "entities": "^6.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz",
+ "integrity": "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.55.1",
+ "@rollup/rollup-android-arm64": "4.55.1",
+ "@rollup/rollup-darwin-arm64": "4.55.1",
+ "@rollup/rollup-darwin-x64": "4.55.1",
+ "@rollup/rollup-freebsd-arm64": "4.55.1",
+ "@rollup/rollup-freebsd-x64": "4.55.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.55.1",
+ "@rollup/rollup-linux-arm-musleabihf": "4.55.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.55.1",
+ "@rollup/rollup-linux-arm64-musl": "4.55.1",
+ "@rollup/rollup-linux-loong64-gnu": "4.55.1",
+ "@rollup/rollup-linux-loong64-musl": "4.55.1",
+ "@rollup/rollup-linux-ppc64-gnu": "4.55.1",
+ "@rollup/rollup-linux-ppc64-musl": "4.55.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.55.1",
+ "@rollup/rollup-linux-riscv64-musl": "4.55.1",
+ "@rollup/rollup-linux-s390x-gnu": "4.55.1",
+ "@rollup/rollup-linux-x64-gnu": "4.55.1",
+ "@rollup/rollup-linux-x64-musl": "4.55.1",
+ "@rollup/rollup-openbsd-x64": "4.55.1",
+ "@rollup/rollup-openharmony-arm64": "4.55.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.55.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.55.1",
+ "@rollup/rollup-win32-x64-gnu": "4.55.1",
+ "@rollup/rollup-win32-x64-msvc": "4.55.1",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/seroval": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.3.2.tgz",
+ "integrity": "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/seroval-plugins": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.3.3.tgz",
+ "integrity": "sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "seroval": "^1.0"
+ }
+ },
+ "node_modules/solid-js": {
+ "version": "1.9.10",
+ "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.10.tgz",
+ "integrity": "sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew==",
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "^3.1.0",
+ "seroval": "~1.3.0",
+ "seroval-plugins": "~1.3.0"
+ }
+ },
+ "node_modules/solid-refresh": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/solid-refresh/-/solid-refresh-0.6.3.tgz",
+ "integrity": "sha512-F3aPsX6hVw9ttm5LYlth8Q15x6MlI/J3Dn+o3EQyRTtTxidepSTwAYdozt01/YA+7ObcciagGEyXIopGZzQtbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/generator": "^7.23.6",
+ "@babel/helper-module-imports": "^7.22.15",
+ "@babel/types": "^7.23.6"
+ },
+ "peerDependencies": {
+ "solid-js": "^1.3"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "7.3.1",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
+ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.27.0",
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3",
+ "postcss": "^8.5.6",
+ "rollup": "^4.43.0",
+ "tinyglobby": "^0.2.15"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "lightningcss": "^1.21.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite-plugin-solid": {
+ "version": "2.11.10",
+ "resolved": "https://registry.npmjs.org/vite-plugin-solid/-/vite-plugin-solid-2.11.10.tgz",
+ "integrity": "sha512-Yr1dQybmtDtDAHkii6hXuc1oVH9CPcS/Zb2jN/P36qqcrkNnVPsMTzQ06jyzFPFjj3U1IYKMVt/9ZqcwGCEbjw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.23.3",
+ "@types/babel__core": "^7.20.4",
+ "babel-preset-solid": "^1.8.4",
+ "merge-anything": "^5.1.7",
+ "solid-refresh": "^0.6.3",
+ "vitefu": "^1.0.4"
+ },
+ "peerDependencies": {
+ "@testing-library/jest-dom": "^5.16.6 || ^5.17.0 || ^6.*",
+ "solid-js": "^1.7.2",
+ "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@testing-library/jest-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vitefu": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz",
+ "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==",
+ "dev": true,
+ "license": "MIT",
+ "workspaces": [
+ "tests/deps/*",
+ "tests/projects/*",
+ "tests/projects/workspace/packages/*"
+ ],
+ "peerDependencies": {
+ "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0"
+ },
+ "peerDependenciesMeta": {
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ }
+ }
+}
diff --git a/examples/vite-solid/package.json b/examples/vite-solid/package.json
new file mode 100644
index 000000000..3c521ae07
--- /dev/null
+++ b/examples/vite-solid/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "vite-solid-fuz-css-example",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "solid-js": "^1"
+ },
+ "devDependencies": {
+ "@fuzdev/fuz_css": "file:../..",
+ "acorn-jsx": "^5",
+ "typescript": "^5",
+ "vite": "^7",
+ "vite-plugin-solid": "^2"
+ }
+}
diff --git a/examples/vite-solid/src/App.tsx b/examples/vite-solid/src/App.tsx
new file mode 100644
index 000000000..dc4582a5e
--- /dev/null
+++ b/examples/vite-solid/src/App.tsx
@@ -0,0 +1,152 @@
+import {createSignal, For} from 'solid-js';
+
+// Import from node_modules to verify extraction works for dependencies
+import {
+ // Naming patterns (all CLASS_NAME_PATTERN suffix variants)
+ demoClass,
+ demo_class,
+ demoClasses,
+ demo_classes,
+ demoClassName,
+ demo_class_name,
+ demoClassNames,
+ demo_class_names,
+ demoClassList,
+ demo_class_list,
+ demoClassLists,
+ demo_class_lists,
+ DEMO_CLASS,
+ // Expression patterns
+ ternaryClass,
+ logicalClass,
+ arrayClasses,
+ objectClasses,
+ // Comment hint examples
+ fromComment,
+} from '@fuzdev/fuz_css/example_class_utilities.js';
+
+export const App = () => {
+ const [count, setCount] = createSignal(0);
+
+ return (
+
+
+
+ fuz_css + Solid
+ Utility classes generated on-demand via Vite plugin (docs, source)
+
+
+ {/* Class types */}
+
+ Class types
+
+
+
Token classes
+
.p_md .bg_d_2
+
.pl_xl5 .font_size_lg
+
.shadow_sm
+
+
+
+
Composite classes
+
.box
+
+ .ellipsis -- this text truncates with ellipsis when it overflows
+
+
+
+
+
Literal classes
+
.opacity:60%
+
.color:var(--color_j_5)
+
.box-shadow:0~4px~8px~rgb(0,0,0,0.2) (~ encodes spaces)
+
+
+
+ {/* Modifiers */}
+
+ Modifiers
+
+
+
Responsive
+
+
+
.column .gap_md on mobile, .md:flex-direction:row .md:gap_lg on medium+ screens
+
+
+
Resize the window to see the layout switch from column to row
+
+
+
.min-width(543px):font_size_lg -- arbitrary breakpoint
+
+
+
+
Interactive
+
+
+ .hover:border_color_b .hover:outline_color_b .active:border_color_d .active:outline_color_d
+
+
+
+ .hover:border_color_g .hover:outline_color_g .active:border_color_h .active:outline_color_h
+
+
+
+
+ {/* Extraction */}
+
+ Extraction
+ Classes detected via naming conventions, expressions, and comments (examples imported from node_modules to verify dependency scanning)
+
+
+
Naming patterns
+
demoClass: .{demoClass}
+
demo_class: .{demo_class}
+
DEMO_CLASS: .{DEMO_CLASS}
+
demoClasses: .mb_xs2 .ml_xs
+
demo_classes: .mb_xs .ml_sm
+
demoClassName: .{demoClassName}
+
demo_class_name: .{demo_class_name}
+
demoClassNames: .mb_lg .ml_md
+
demo_class_names: .mb_xl .ml_lg
+
demoClassList: .{demoClassList}
+
demo_class_list: .{demo_class_list}
+
demoClassLists: .mb_xl4 .ml_xl
+
demo_class_lists: .mb_xl5 .ml_xl2
+
+
+
+
Expression patterns
+
+ {`true ? 'mt_xs' : 'mt_sm'`} â .{ternaryClass} (both branches extracted)
+
+
+ {`true && 'mt_md'`} â .{logicalClass}
+
+
+ {`['mt_lg', 'mt_xl']`} â .{arrayClasses.join(', .')}
+
+
+ {`{ mt_xl2: 'mt_xl2', mt_xl3: 'mt_xl3' }`} â keys extracted from object
+
+
+
+
+
Comment hints
+
+
+
+
+
+
+
+ );
+};
diff --git a/examples/vite-solid/src/main.tsx b/examples/vite-solid/src/main.tsx
new file mode 100644
index 000000000..d4ca9082f
--- /dev/null
+++ b/examples/vite-solid/src/main.tsx
@@ -0,0 +1,8 @@
+import '@fuzdev/fuz_css/style.css';
+import '@fuzdev/fuz_css/theme.css';
+import 'virtual:fuz.css';
+
+import {render} from 'solid-js/web';
+import {App} from './App.tsx';
+
+render(() => , document.getElementById('root')!);
diff --git a/examples/vite-solid/src/vite-env.d.ts b/examples/vite-solid/src/vite-env.d.ts
new file mode 100644
index 000000000..241d205b0
--- /dev/null
+++ b/examples/vite-solid/src/vite-env.d.ts
@@ -0,0 +1,6 @@
+///
+
+declare module 'virtual:fuz.css' {
+ const css: string;
+ export default css;
+}
diff --git a/examples/vite-solid/tsconfig.json b/examples/vite-solid/tsconfig.json
new file mode 100644
index 000000000..afbd76a06
--- /dev/null
+++ b/examples/vite-solid/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "isolatedModules": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "jsx": "preserve",
+ "jsxImportSource": "solid-js",
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["src"]
+}
diff --git a/examples/vite-solid/vite.config.ts b/examples/vite-solid/vite.config.ts
new file mode 100644
index 000000000..2b31a7fad
--- /dev/null
+++ b/examples/vite-solid/vite.config.ts
@@ -0,0 +1,14 @@
+import {defineConfig} from 'vite';
+import solid from 'vite-plugin-solid';
+import jsx from 'acorn-jsx';
+import {vite_plugin_fuz_css} from '@fuzdev/fuz_css/vite_plugin_fuz_css.js';
+
+export default defineConfig({
+ plugins: [
+ // fuz_css must be listed first for solid (enforce: 'pre' alone isn't sufficient)
+ vite_plugin_fuz_css({
+ acorn_plugins: [jsx()],
+ }),
+ solid(),
+ ],
+});
diff --git a/examples/vite-svelte/index.html b/examples/vite-svelte/index.html
new file mode 100644
index 000000000..d3652ec98
--- /dev/null
+++ b/examples/vite-svelte/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ fuz_css + Svelte
+
+
+
+
+
+
diff --git a/examples/vite-svelte/package-lock.json b/examples/vite-svelte/package-lock.json
new file mode 100644
index 000000000..bc9525ad8
--- /dev/null
+++ b/examples/vite-svelte/package-lock.json
@@ -0,0 +1,1439 @@
+{
+ "name": "vite-svelte-fuz-css-example",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "vite-svelte-fuz-css-example",
+ "dependencies": {
+ "svelte": "^5"
+ },
+ "devDependencies": {
+ "@fuzdev/fuz_css": "file:../..",
+ "@sveltejs/vite-plugin-svelte": "^6",
+ "typescript": "^5",
+ "vite": "^7"
+ }
+ },
+ "../..": {
+ "name": "@fuzdev/fuz_css",
+ "version": "0.43.0",
+ "dev": true,
+ "license": "MIT",
+ "devDependencies": {
+ "@changesets/changelog-git": "^0.2.1",
+ "@fuzdev/fuz_code": "^0.39.0",
+ "@fuzdev/fuz_ui": "^0.177.1",
+ "@fuzdev/fuz_util": "^0.45.3",
+ "@ryanatkn/eslint-config": "^0.9.0",
+ "@ryanatkn/gro": "^0.185.0",
+ "@sveltejs/acorn-typescript": "^1.0.8",
+ "@sveltejs/adapter-static": "^3.0.10",
+ "@sveltejs/kit": "^2.49.1",
+ "@sveltejs/package": "^2.5.7",
+ "@sveltejs/vite-plugin-svelte": "^6.2.1",
+ "@types/node": "^24.10.1",
+ "@webref/css": "^8.1.3",
+ "acorn-jsx": "^5.3.2",
+ "eslint": "^9.39.1",
+ "eslint-plugin-svelte": "^3.13.1",
+ "prettier": "^3.7.4",
+ "prettier-plugin-svelte": "^3.4.1",
+ "svelte": "^5.45.6",
+ "svelte-check": "^4.3.4",
+ "tslib": "^2.8.1",
+ "typescript": "^5.9.3",
+ "typescript-eslint": "^8.48.1",
+ "vitest": "^4.0.15",
+ "zimmerframe": "^1.1.4",
+ "zod": "^4.1.13"
+ },
+ "engines": {
+ "node": ">=22.15"
+ },
+ "funding": {
+ "url": "https://www.ryanatkn.com/funding"
+ },
+ "peerDependencies": {
+ "@fuzdev/fuz_util": ">=0.42.0",
+ "@sveltejs/acorn-typescript": "^1",
+ "@webref/css": "^8",
+ "acorn-jsx": "^5",
+ "zimmerframe": "^1"
+ },
+ "peerDependenciesMeta": {
+ "@fuzdev/fuz_util": {
+ "optional": true
+ },
+ "@sveltejs/acorn-typescript": {
+ "optional": true
+ },
+ "@webref/css": {
+ "optional": true
+ },
+ "acorn-jsx": {
+ "optional": true
+ },
+ "zimmerframe": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz",
+ "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz",
+ "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz",
+ "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz",
+ "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz",
+ "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz",
+ "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz",
+ "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz",
+ "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz",
+ "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz",
+ "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz",
+ "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz",
+ "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz",
+ "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz",
+ "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz",
+ "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz",
+ "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz",
+ "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz",
+ "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz",
+ "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz",
+ "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz",
+ "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz",
+ "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz",
+ "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz",
+ "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz",
+ "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz",
+ "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@fuzdev/fuz_css": {
+ "resolved": "../..",
+ "link": true
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz",
+ "integrity": "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.1.tgz",
+ "integrity": "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz",
+ "integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.1.tgz",
+ "integrity": "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz",
+ "integrity": "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.1.tgz",
+ "integrity": "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.1.tgz",
+ "integrity": "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.1.tgz",
+ "integrity": "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz",
+ "integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.1.tgz",
+ "integrity": "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.1.tgz",
+ "integrity": "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.1.tgz",
+ "integrity": "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.1.tgz",
+ "integrity": "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.1.tgz",
+ "integrity": "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.1.tgz",
+ "integrity": "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.1.tgz",
+ "integrity": "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.1.tgz",
+ "integrity": "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.1.tgz",
+ "integrity": "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.1.tgz",
+ "integrity": "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.1.tgz",
+ "integrity": "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz",
+ "integrity": "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.1.tgz",
+ "integrity": "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.1.tgz",
+ "integrity": "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.1.tgz",
+ "integrity": "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.1.tgz",
+ "integrity": "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@sveltejs/acorn-typescript": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.8.tgz",
+ "integrity": "sha512-esgN+54+q0NjB0Y/4BomT9samII7jGwNy/2a3wNZbT2A2RpmXsXwUt24LvLhx6jUq2gVk4cWEvcRO6MFQbOfNA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^8.9.0"
+ }
+ },
+ "node_modules/@sveltejs/vite-plugin-svelte": {
+ "version": "6.2.4",
+ "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.2.4.tgz",
+ "integrity": "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0",
+ "deepmerge": "^4.3.1",
+ "magic-string": "^0.30.21",
+ "obug": "^2.1.0",
+ "vitefu": "^1.1.1"
+ },
+ "engines": {
+ "node": "^20.19 || ^22.12 || >=24"
+ },
+ "peerDependencies": {
+ "svelte": "^5.0.0",
+ "vite": "^6.3.0 || ^7.0.0"
+ }
+ },
+ "node_modules/@sveltejs/vite-plugin-svelte-inspector": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-5.0.2.tgz",
+ "integrity": "sha512-TZzRTcEtZffICSAoZGkPSl6Etsj2torOVrx6Uw0KpXxrec9Gg6jFWQ60Q3+LmNGfZSxHRCZL7vXVZIWmuV50Ig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "obug": "^2.1.0"
+ },
+ "engines": {
+ "node": "^20.19 || ^22.12 || >=24"
+ },
+ "peerDependencies": {
+ "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0",
+ "svelte": "^5.0.0",
+ "vite": "^6.3.0 || ^7.0.0"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "license": "MIT"
+ },
+ "node_modules/acorn": {
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/aria-query": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
+ "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/axobject-query": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
+ "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/deepmerge": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/devalue": {
+ "version": "5.6.1",
+ "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.1.tgz",
+ "integrity": "sha512-jDwizj+IlEZBunHcOuuFVBnIMPAEHvTsJj0BcIp94xYguLRVBcXO853px/MyIJvbVzWdsGvrRweIUWJw8hBP7A==",
+ "license": "MIT"
+ },
+ "node_modules/esbuild": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz",
+ "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.27.2",
+ "@esbuild/android-arm": "0.27.2",
+ "@esbuild/android-arm64": "0.27.2",
+ "@esbuild/android-x64": "0.27.2",
+ "@esbuild/darwin-arm64": "0.27.2",
+ "@esbuild/darwin-x64": "0.27.2",
+ "@esbuild/freebsd-arm64": "0.27.2",
+ "@esbuild/freebsd-x64": "0.27.2",
+ "@esbuild/linux-arm": "0.27.2",
+ "@esbuild/linux-arm64": "0.27.2",
+ "@esbuild/linux-ia32": "0.27.2",
+ "@esbuild/linux-loong64": "0.27.2",
+ "@esbuild/linux-mips64el": "0.27.2",
+ "@esbuild/linux-ppc64": "0.27.2",
+ "@esbuild/linux-riscv64": "0.27.2",
+ "@esbuild/linux-s390x": "0.27.2",
+ "@esbuild/linux-x64": "0.27.2",
+ "@esbuild/netbsd-arm64": "0.27.2",
+ "@esbuild/netbsd-x64": "0.27.2",
+ "@esbuild/openbsd-arm64": "0.27.2",
+ "@esbuild/openbsd-x64": "0.27.2",
+ "@esbuild/openharmony-arm64": "0.27.2",
+ "@esbuild/sunos-x64": "0.27.2",
+ "@esbuild/win32-arm64": "0.27.2",
+ "@esbuild/win32-ia32": "0.27.2",
+ "@esbuild/win32-x64": "0.27.2"
+ }
+ },
+ "node_modules/esm-env": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz",
+ "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==",
+ "license": "MIT"
+ },
+ "node_modules/esrap": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.1.tgz",
+ "integrity": "sha512-GiYWG34AN/4CUyaWAgunGt0Rxvr1PTMlGC0vvEov/uOQYWne2bpN03Um+k8jT+q3op33mKouP2zeJ6OlM+qeUg==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.4.15"
+ }
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/is-reference": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
+ "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.6"
+ }
+ },
+ "node_modules/locate-character": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
+ "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
+ "license": "MIT"
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/obug": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz",
+ "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==",
+ "dev": true,
+ "funding": [
+ "https://github.com/sponsors/sxzz",
+ "https://opencollective.com/debug"
+ ],
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz",
+ "integrity": "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.55.1",
+ "@rollup/rollup-android-arm64": "4.55.1",
+ "@rollup/rollup-darwin-arm64": "4.55.1",
+ "@rollup/rollup-darwin-x64": "4.55.1",
+ "@rollup/rollup-freebsd-arm64": "4.55.1",
+ "@rollup/rollup-freebsd-x64": "4.55.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.55.1",
+ "@rollup/rollup-linux-arm-musleabihf": "4.55.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.55.1",
+ "@rollup/rollup-linux-arm64-musl": "4.55.1",
+ "@rollup/rollup-linux-loong64-gnu": "4.55.1",
+ "@rollup/rollup-linux-loong64-musl": "4.55.1",
+ "@rollup/rollup-linux-ppc64-gnu": "4.55.1",
+ "@rollup/rollup-linux-ppc64-musl": "4.55.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.55.1",
+ "@rollup/rollup-linux-riscv64-musl": "4.55.1",
+ "@rollup/rollup-linux-s390x-gnu": "4.55.1",
+ "@rollup/rollup-linux-x64-gnu": "4.55.1",
+ "@rollup/rollup-linux-x64-musl": "4.55.1",
+ "@rollup/rollup-openbsd-x64": "4.55.1",
+ "@rollup/rollup-openharmony-arm64": "4.55.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.55.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.55.1",
+ "@rollup/rollup-win32-x64-gnu": "4.55.1",
+ "@rollup/rollup-win32-x64-msvc": "4.55.1",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/svelte": {
+ "version": "5.46.3",
+ "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.46.3.tgz",
+ "integrity": "sha512-Y5juST3x+/ySty5tYJCVWa6Corkxpt25bUZQHqOceg9xfMUtDsFx6rCsG6cYf1cA6vzDi66HIvaki0byZZX95A==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/remapping": "^2.3.4",
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@sveltejs/acorn-typescript": "^1.0.5",
+ "@types/estree": "^1.0.5",
+ "acorn": "^8.12.1",
+ "aria-query": "^5.3.1",
+ "axobject-query": "^4.1.0",
+ "clsx": "^2.1.1",
+ "devalue": "^5.5.0",
+ "esm-env": "^1.2.1",
+ "esrap": "^2.2.1",
+ "is-reference": "^3.0.3",
+ "locate-character": "^3.0.0",
+ "magic-string": "^0.30.11",
+ "zimmerframe": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/vite": {
+ "version": "7.3.1",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
+ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.27.0",
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3",
+ "postcss": "^8.5.6",
+ "rollup": "^4.43.0",
+ "tinyglobby": "^0.2.15"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "lightningcss": "^1.21.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vitefu": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz",
+ "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==",
+ "dev": true,
+ "license": "MIT",
+ "workspaces": [
+ "tests/deps/*",
+ "tests/projects/*",
+ "tests/projects/workspace/packages/*"
+ ],
+ "peerDependencies": {
+ "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0"
+ },
+ "peerDependenciesMeta": {
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/zimmerframe": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz",
+ "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==",
+ "license": "MIT"
+ }
+ }
+}
diff --git a/examples/vite-svelte/package.json b/examples/vite-svelte/package.json
new file mode 100644
index 000000000..6595c48c4
--- /dev/null
+++ b/examples/vite-svelte/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "vite-svelte-fuz-css-example",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "svelte": "^5"
+ },
+ "devDependencies": {
+ "@fuzdev/fuz_css": "file:../..",
+ "@sveltejs/vite-plugin-svelte": "^6",
+ "typescript": "^5",
+ "vite": "^7"
+ }
+}
diff --git a/examples/vite-svelte/src/App.svelte b/examples/vite-svelte/src/App.svelte
new file mode 100644
index 000000000..1d7fb611c
--- /dev/null
+++ b/examples/vite-svelte/src/App.svelte
@@ -0,0 +1,178 @@
+
+
+
+
+
+ fuz_css + Svelte
+
+ Utility classes generated on-demand via Vite plugin (docs, source)
+
+
+
+
+
+ Class types
+
+
+
Token classes
+
.p_md .bg_d_2
+
.pl_xl5 .font_size_lg
+
.shadow_sm
+
+
+
+
Composite classes
+
.box
+
+ .ellipsis -- this text truncates with ellipsis when it overflows
+
+
+
+
+
Literal classes
+
.opacity:60%
+
.color:var(--color_j_5)
+
+ .box-shadow:0~4px~8px~rgb(0,0,0,0.2) (~ encodes spaces)
+
+
+
+
+
+
+ Modifiers
+
+
+
Responsive
+
+
+
.column .gap_md on mobile, .md:flex-direction:row .md:gap_lg on medium+ screens
+
+
+
Resize the window to see the layout switch from column to row
+
+
+
+ .min-width(543px):font_size_lg -- arbitrary breakpoint
+
+
+
+
+
Interactive
+
+
+ .hover:border_color_b .hover:outline_color_b .active:border_color_d
+ .active:outline_color_d
+
+
+
+ .hover:border_color_g .hover:outline_color_g .active:border_color_h
+ .active:outline_color_h
+
+
+
+
+
+
+ Extraction
+
+ Classes detected via naming conventions, expressions, and comments (examples imported from node_modules to verify dependency scanning)
+
+
+
+
Naming patterns
+
demoClass: .{demoClass}
+
demo_class: .{demo_class}
+
DEMO_CLASS: .{DEMO_CLASS}
+
demoClasses: .mb_xs2 .ml_xs
+
demo_classes: .mb_xs .ml_sm
+
demoClassName: .{demoClassName}
+
demo_class_name: .{demo_class_name}
+
demoClassNames: .mb_lg .ml_md
+
demo_class_names: .mb_xl .ml_lg
+
demoClassList: .{demoClassList}
+
demo_class_list: .{demo_class_list}
+
demoClassLists: .mb_xl4 .ml_xl
+
demo_class_lists: .mb_xl5 .ml_xl2
+
+
+
+
Expression patterns
+
+ {"true ? 'mt_xs' : 'mt_sm'"} â .{ternaryClass} (both branches extracted)
+
+
+ {"true && 'mt_md'"} â .{logicalClass}
+
+
+ {"['mt_lg', 'mt_xl']"} â .{arrayClasses.join(', .')}
+
+
+ {`{ mt_xl2: 'mt_xl2', mt_xl3: 'mt_xl3' }`} â keys extracted from object
+
+
+
+
+
Comment hints
+
+
+
+
+
+
+
diff --git a/examples/vite-svelte/src/main.ts b/examples/vite-svelte/src/main.ts
new file mode 100644
index 000000000..f50b05321
--- /dev/null
+++ b/examples/vite-svelte/src/main.ts
@@ -0,0 +1,8 @@
+import '@fuzdev/fuz_css/style.css';
+import '@fuzdev/fuz_css/theme.css';
+import 'virtual:fuz.css';
+
+import {mount} from 'svelte';
+import App from './App.svelte';
+
+mount(App, {target: document.getElementById('root')!});
diff --git a/examples/vite-svelte/src/vite-env.d.ts b/examples/vite-svelte/src/vite-env.d.ts
new file mode 100644
index 000000000..241d205b0
--- /dev/null
+++ b/examples/vite-svelte/src/vite-env.d.ts
@@ -0,0 +1,6 @@
+///
+
+declare module 'virtual:fuz.css' {
+ const css: string;
+ export default css;
+}
diff --git a/examples/vite-svelte/tsconfig.json b/examples/vite-svelte/tsconfig.json
new file mode 100644
index 000000000..394f77eb2
--- /dev/null
+++ b/examples/vite-svelte/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "isolatedModules": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["src"]
+}
diff --git a/examples/vite-svelte/vite.config.ts b/examples/vite-svelte/vite.config.ts
new file mode 100644
index 000000000..d94ba0216
--- /dev/null
+++ b/examples/vite-svelte/vite.config.ts
@@ -0,0 +1,11 @@
+import {defineConfig} from 'vite';
+import {svelte} from '@sveltejs/vite-plugin-svelte';
+import {vite_plugin_fuz_css} from '@fuzdev/fuz_css/vite_plugin_fuz_css.js';
+
+export default defineConfig({
+ plugins: [
+ // fuz_css must be listed first for svelte (enforce: 'pre' alone isn't sufficient)
+ vite_plugin_fuz_css(),
+ svelte(),
+ ],
+});
diff --git a/package-lock.json b/package-lock.json
index 4d9e22e8c..e5f1ffea6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,16 +10,19 @@
"license": "MIT",
"devDependencies": {
"@changesets/changelog-git": "^0.2.1",
- "@fuzdev/fuz_code": "^0.38.0",
- "@fuzdev/fuz_ui": "^0.177.0",
- "@fuzdev/fuz_util": "^0.45.1",
+ "@fuzdev/fuz_code": "^0.39.0",
+ "@fuzdev/fuz_ui": "^0.177.1",
+ "@fuzdev/fuz_util": "^0.45.3",
"@ryanatkn/eslint-config": "^0.9.0",
- "@ryanatkn/gro": "^0.184.0",
+ "@ryanatkn/gro": "^0.185.0",
+ "@sveltejs/acorn-typescript": "^1.0.8",
"@sveltejs/adapter-static": "^3.0.10",
"@sveltejs/kit": "^2.49.1",
"@sveltejs/package": "^2.5.7",
"@sveltejs/vite-plugin-svelte": "^6.2.1",
"@types/node": "^24.10.1",
+ "@webref/css": "^8.1.3",
+ "acorn-jsx": "^5.3.2",
"eslint": "^9.39.1",
"eslint-plugin-svelte": "^3.13.1",
"prettier": "^3.7.4",
@@ -30,6 +33,7 @@
"typescript": "^5.9.3",
"typescript-eslint": "^8.48.1",
"vitest": "^4.0.15",
+ "zimmerframe": "^1.1.4",
"zod": "^4.1.13"
},
"engines": {
@@ -39,11 +43,27 @@
"url": "https://www.ryanatkn.com/funding"
},
"peerDependencies": {
- "@fuzdev/fuz_util": ">=0.42.0"
+ "@fuzdev/fuz_util": ">=0.42.0",
+ "@sveltejs/acorn-typescript": "^1",
+ "@webref/css": "^8",
+ "acorn-jsx": "^5",
+ "zimmerframe": "^1"
},
"peerDependenciesMeta": {
"@fuzdev/fuz_util": {
"optional": true
+ },
+ "@sveltejs/acorn-typescript": {
+ "optional": true
+ },
+ "@webref/css": {
+ "optional": true
+ },
+ "acorn-jsx": {
+ "optional": true
+ },
+ "zimmerframe": {
+ "optional": true
}
}
},
@@ -724,9 +744,9 @@
}
},
"node_modules/@fuzdev/fuz_code": {
- "version": "0.38.0",
- "resolved": "https://registry.npmjs.org/@fuzdev/fuz_code/-/fuz_code-0.38.0.tgz",
- "integrity": "sha512-11ow5NZbgdDnvNH46LDYaVdWjjo1yQSJRpkYr2OhZGcxc2FiZ/eU16T0xT6OhSJg+pRM9SRUPyNLI2k3jiW7Ig==",
+ "version": "0.39.0",
+ "resolved": "https://registry.npmjs.org/@fuzdev/fuz_code/-/fuz_code-0.39.0.tgz",
+ "integrity": "sha512-0YlInOGBNlnCAp3TKKv6gSjoyco3JAI9e/epplmnQMnAAHRTHvblJDWv8ePiFUJBQJJqGsJ2NldxswK9Vp7LXA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -771,9 +791,9 @@
}
},
"node_modules/@fuzdev/fuz_ui": {
- "version": "0.177.0",
- "resolved": "https://registry.npmjs.org/@fuzdev/fuz_ui/-/fuz_ui-0.177.0.tgz",
- "integrity": "sha512-b9LEzjL78NHtEI0yDOCTz/XyUjm/sdrCDSQ4jNHOtpANOF8cRijemldXWcfs9UEoexnIK8wDg8WV4OnpD5Hvqw==",
+ "version": "0.177.1",
+ "resolved": "https://registry.npmjs.org/@fuzdev/fuz_ui/-/fuz_ui-0.177.1.tgz",
+ "integrity": "sha512-pdrqh3K4/9gTO1tz5EOv28GN0f8wCAd1ufZFd5ckMi82TMrS7bM/rJZOktzFomjV0WuyVlqsLw9VsSROiF9cBw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -810,9 +830,9 @@
}
},
"node_modules/@fuzdev/fuz_util": {
- "version": "0.45.1",
- "resolved": "https://registry.npmjs.org/@fuzdev/fuz_util/-/fuz_util-0.45.1.tgz",
- "integrity": "sha512-szJ6FPXkeuNzoqxXwiC1q9xMiWZM37wiCyGdFLVVBq8FxwIpG1MAup/ZFyQC22QLg8sdybCbPadWiaYYUhhtUA==",
+ "version": "0.45.3",
+ "resolved": "https://registry.npmjs.org/@fuzdev/fuz_util/-/fuz_util-0.45.3.tgz",
+ "integrity": "sha512-N0xaUwFxGG1FuEkcVqB4t8Gqs2ReCVmmQf1kI7gErGuyKV9mRycHSFWFyT7/hQ9K4/0epsZj4cejj5fYIjkG/Q==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1541,9 +1561,9 @@
}
},
"node_modules/@ryanatkn/gro": {
- "version": "0.184.0",
- "resolved": "https://registry.npmjs.org/@ryanatkn/gro/-/gro-0.184.0.tgz",
- "integrity": "sha512-J8Us3xspyjVBCuOG36+4X9qZiH5ydrjRRSN9Pfwk+xNgJsH5v11GXTl9PLkKvHJVHJ8qb4KFX/XA1k8bAmIM5g==",
+ "version": "0.185.0",
+ "resolved": "https://registry.npmjs.org/@ryanatkn/gro/-/gro-0.185.0.tgz",
+ "integrity": "sha512-Sr2Jrkz/RcyPP8N4BXzyRQhABgGAgsMJ0OpMpNVfN4WozKDbrN6YUZsHgZpF/byT5YSUz+6Hlhsx+4xrTMObKg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1552,8 +1572,8 @@
"esm-env": "^1.2.2",
"mri": "^1.2.0",
"oxc-parser": "^0.99.0",
- "prettier": "^3.6.2",
- "prettier-plugin-svelte": "^3.4.0",
+ "prettier": "^3.7.4",
+ "prettier-plugin-svelte": "^3.4.1",
"ts-blank-space": "^0.6.2",
"tslib": "^2.8.1",
"zod": "^4.1.13"
@@ -1625,9 +1645,9 @@
"license": "MIT"
},
"node_modules/@sveltejs/acorn-typescript": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.5.tgz",
- "integrity": "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==",
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.8.tgz",
+ "integrity": "sha512-esgN+54+q0NjB0Y/4BomT9samII7jGwNy/2a3wNZbT2A2RpmXsXwUt24LvLhx6jUq2gVk4cWEvcRO6MFQbOfNA==",
"dev": true,
"license": "MIT",
"peerDependencies": {
@@ -2203,6 +2223,16 @@
"url": "https://opencollective.com/vitest"
}
},
+ "node_modules/@webref/css": {
+ "version": "8.1.3",
+ "resolved": "https://registry.npmjs.org/@webref/css/-/css-8.1.3.tgz",
+ "integrity": "sha512-lSniLpIAm3G5o0NJxUr1Ci0mXCxIwQ31cyvdU5L0jnWXb41I6Sg0KWqxZXRVh6Jgf03egoTCaqhAn4S3VRzCZQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "css-tree": "^3.1.0"
+ }
+ },
"node_modules/acorn": {
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
@@ -2429,6 +2459,21 @@
"node": ">= 8"
}
},
+ "node_modules/css-tree": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz",
+ "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "mdn-data": "2.12.2",
+ "source-map-js": "^1.0.1"
+ },
+ "engines": {
+ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
+ }
+ },
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@@ -3142,6 +3187,14 @@
"@jridgewell/sourcemap-codec": "^1.5.5"
}
},
+ "node_modules/mdn-data": {
+ "version": "2.12.2",
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz",
+ "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==",
+ "dev": true,
+ "license": "CC0-1.0",
+ "peer": true
+ },
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -4718,9 +4771,9 @@
}
},
"node_modules/zimmerframe": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz",
- "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==",
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz",
+ "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==",
"dev": true,
"license": "MIT"
},
diff --git a/package.json b/package.json
index f98ae64da..cd7d7a3ba 100644
--- a/package.json
+++ b/package.json
@@ -1,9 +1,9 @@
{
"name": "@fuzdev/fuz_css",
"version": "0.43.0",
- "description": "CSS framework and design system",
- "motto": "magical organic stylesheets",
- "glyph": "ðŋ",
+ "description": "CSS framework and design system for semantic HTML",
+ "motto": "CSS with more utility",
+ "glyph": "ðŠī",
"logo": "logo.svg",
"logo_alt": "a fuzzy tuft of green moss",
"license": "MIT",
@@ -30,25 +30,44 @@
"node": ">=22.15"
},
"peerDependencies": {
- "@fuzdev/fuz_util": ">=0.42.0"
+ "@fuzdev/fuz_util": ">=0.42.0",
+ "@sveltejs/acorn-typescript": "^1",
+ "@webref/css": "^8",
+ "acorn-jsx": "^5",
+ "zimmerframe": "^1"
},
"peerDependenciesMeta": {
"@fuzdev/fuz_util": {
"optional": true
+ },
+ "@sveltejs/acorn-typescript": {
+ "optional": true
+ },
+ "@webref/css": {
+ "optional": true
+ },
+ "acorn-jsx": {
+ "optional": true
+ },
+ "zimmerframe": {
+ "optional": true
}
},
"devDependencies": {
"@changesets/changelog-git": "^0.2.1",
- "@fuzdev/fuz_code": "^0.38.0",
- "@fuzdev/fuz_ui": "^0.177.0",
- "@fuzdev/fuz_util": "^0.45.1",
+ "@fuzdev/fuz_code": "^0.39.0",
+ "@fuzdev/fuz_ui": "^0.177.1",
+ "@fuzdev/fuz_util": "^0.45.3",
"@ryanatkn/eslint-config": "^0.9.0",
- "@ryanatkn/gro": "^0.184.0",
+ "@ryanatkn/gro": "^0.185.0",
+ "@sveltejs/acorn-typescript": "^1.0.8",
"@sveltejs/adapter-static": "^3.0.10",
"@sveltejs/kit": "^2.49.1",
"@sveltejs/package": "^2.5.7",
"@sveltejs/vite-plugin-svelte": "^6.2.1",
"@types/node": "^24.10.1",
+ "@webref/css": "^8.1.3",
+ "acorn-jsx": "^5.3.2",
"eslint": "^9.39.1",
"eslint-plugin-svelte": "^3.13.1",
"prettier": "^3.7.4",
@@ -59,6 +78,7 @@
"typescript": "^5.9.3",
"typescript-eslint": "^8.48.1",
"vitest": "^4.0.15",
+ "zimmerframe": "^1.1.4",
"zod": "^4.1.13"
},
"prettier": {
diff --git a/src/lib/css_cache.ts b/src/lib/css_cache.ts
new file mode 100644
index 000000000..410bdbfd1
--- /dev/null
+++ b/src/lib/css_cache.ts
@@ -0,0 +1,178 @@
+/**
+ * Cache infrastructure for incremental CSS class extraction.
+ *
+ * Provides per-file caching with content hash validation to avoid
+ * re-extracting classes from unchanged files.
+ *
+ * @module
+ */
+
+import {mkdir, readFile, writeFile, unlink, rename} from 'node:fs/promises';
+import {dirname, join} from 'node:path';
+
+import type {SourceLocation, ExtractionDiagnostic} from './diagnostics.js';
+
+/**
+ * Computes SHA-256 hash of content using Web Crypto API.
+ */
+export const compute_hash = async (content: string): Promise => {
+ const encoder = new TextEncoder();
+ const buffer = encoder.encode(content);
+ const digested = await crypto.subtle.digest('SHA-256', buffer);
+ const bytes = Array.from(new Uint8Array(digested));
+ let hex = '';
+ for (const h of bytes) {
+ hex += h.toString(16).padStart(2, '0');
+ }
+ return hex;
+};
+
+/**
+ * Default cache directory relative to project root.
+ */
+export const DEFAULT_CACHE_DIR = '.fuz/cache/css';
+
+/**
+ * Cache version. Bump when any of these change:
+ * - `CachedExtraction` schema
+ * - `extract_css_classes_with_locations()` logic or output
+ * - `ExtractionDiagnostic` or `SourceLocation` structure
+ *
+ * v2: Use null instead of empty arrays for classes/diagnostics
+ * v3: Add explicit_classes for @fuz-classes warnings
+ */
+const CACHE_VERSION = 1;
+
+/**
+ * Cached extraction result for a single file.
+ * Uses `null` instead of empty arrays to avoid allocation overhead.
+ */
+export interface CachedExtraction {
+ /** Cache version - invalidates cache when bumped */
+ v: number;
+ /** SHA-256 hash of the source file contents */
+ content_hash: string;
+ /** Classes as [name, locations] tuples, or null if none */
+ classes: Array<[string, Array]> | null;
+ /** Classes from @fuz-classes comments, or null if none */
+ explicit_classes: Array | null;
+ /** Extraction diagnostics, or null if none */
+ diagnostics: Array | null;
+}
+
+/**
+ * Computes the cache file path for a source file.
+ * Cache structure mirrors source tree: `src/lib/Foo.svelte` â `.fuz/cache/css/src/lib/Foo.svelte.json`
+ *
+ * @param source_path - Absolute path to the source file
+ * @param cache_dir - Absolute path to the cache directory
+ * @param project_root - Normalized project root (must end with `/`)
+ */
+export const get_cache_path = (
+ source_path: string,
+ cache_dir: string,
+ project_root: string,
+): string => {
+ if (!source_path.startsWith(project_root)) {
+ throw new Error(`Source path "${source_path}" is not under project root "${project_root}"`);
+ }
+ const relative = source_path.slice(project_root.length);
+ return join(cache_dir, relative + '.json');
+};
+
+/**
+ * Loads a cached extraction result from disk.
+ * Returns `null` if the cache is missing, corrupted, or has a version mismatch.
+ * This makes the cache self-healing: any error triggers re-extraction.
+ *
+ * @param cache_path - Absolute path to the cache file
+ */
+export const load_cached_extraction = async (
+ cache_path: string,
+): Promise => {
+ try {
+ const content = await readFile(cache_path, 'utf8');
+ const cached = JSON.parse(content) as CachedExtraction;
+
+ // Invalidate if version mismatch
+ if (cached.v !== CACHE_VERSION) {
+ return null;
+ }
+
+ return cached;
+ } catch {
+ // Handles: file not found, invalid JSON, truncated file, permission errors
+ // All cases: return null to trigger re-extraction (self-healing)
+ return null;
+ }
+};
+
+/**
+ * Saves an extraction result to the cache.
+ * Uses atomic write (temp file + rename) for crash safety.
+ * Converts empty arrays to null to avoid allocation overhead on load.
+ *
+ * @param cache_path - Absolute path to the cache file
+ * @param content_hash - SHA-256 hash of the source file contents
+ * @param classes - Extracted classes with their locations, or null if none
+ * @param explicit_classes - Classes from @fuz-classes comments, or null if none
+ * @param diagnostics - Extraction diagnostics, or null if none
+ */
+export const save_cached_extraction = async (
+ cache_path: string,
+ content_hash: string,
+ classes: Map> | null,
+ explicit_classes: Set | null,
+ diagnostics: Array | null,
+): Promise => {
+ // Convert to null if empty to save allocation on load
+ const classes_array = classes && classes.size > 0 ? Array.from(classes.entries()) : null;
+ const explicit_array =
+ explicit_classes && explicit_classes.size > 0 ? Array.from(explicit_classes) : null;
+ const diagnostics_array = diagnostics && diagnostics.length > 0 ? diagnostics : null;
+
+ const data: CachedExtraction = {
+ v: CACHE_VERSION,
+ content_hash,
+ classes: classes_array,
+ explicit_classes: explicit_array,
+ diagnostics: diagnostics_array,
+ };
+
+ // Atomic write: temp file + rename
+ // Include pid + timestamp to avoid conflicts with concurrent writes
+ await mkdir(dirname(cache_path), {recursive: true});
+ const temp_path = cache_path + '.tmp.' + process.pid + '.' + Date.now();
+ await writeFile(temp_path, JSON.stringify(data));
+ await rename(temp_path, cache_path);
+};
+
+/**
+ * Deletes a cached extraction file.
+ * Silently succeeds if the file doesn't exist.
+ *
+ * @param cache_path - Absolute path to the cache file
+ */
+export const delete_cached_extraction = async (cache_path: string): Promise => {
+ await unlink(cache_path).catch(() => {
+ // Ignore if already gone
+ });
+};
+
+/**
+ * Converts a cached extraction back to the runtime format.
+ * Preserves null semantics (null = empty).
+ *
+ * @param cached - Cached extraction data
+ */
+export const from_cached_extraction = (
+ cached: CachedExtraction,
+): {
+ classes: Map> | null;
+ explicit_classes: Set | null;
+ diagnostics: Array | null;
+} => ({
+ classes: cached.classes ? new Map(cached.classes) : null,
+ explicit_classes: cached.explicit_classes ? new Set(cached.explicit_classes) : null,
+ diagnostics: cached.diagnostics,
+});
diff --git a/src/lib/css_class_composites.ts b/src/lib/css_class_composites.ts
index e583b096a..afece8f26 100644
--- a/src/lib/css_class_composites.ts
+++ b/src/lib/css_class_composites.ts
@@ -1,6 +1,6 @@
-import type {CssClassDeclaration} from './css_class_helpers.js';
+import type {CssClassDefinition} from './css_class_generation.js';
-export const css_class_composites: Record = {
+export const css_class_composites: Record = {
pixelated: {
declaration: `
image-rendering: -webkit-optimize-contrast; /* Safari */
@@ -9,206 +9,38 @@ export const css_class_composites: Record = {
+ // Composite classes go first, so they can be overridden by the more specific classes.
+ ...css_class_composites,
+
+ /*
+
+ typography
+
+ */
+ font_family_sans: {declaration: 'font-family: var(--font_family_sans);'},
+ font_family_serif: {declaration: 'font-family: var(--font_family_serif);'},
+ font_family_mono: {declaration: 'font-family: var(--font_family_mono);'},
+
+ ...generate_property_classes(
+ 'line-height',
+ line_height_variants,
+ (v) => `var(--line_height_${v})`,
+ ),
+ ...generate_property_classes(
+ 'font-size',
+ font_size_variants,
+ (v) => `var(--font_size_${v}); --font_size: var(--font_size_${v})`,
+ ),
+ ...generate_property_classes(
+ 'font-size',
+ icon_size_variants,
+ (v) => `var(--icon_size_${v}); --font_size: var(--icon_size_${v})`,
+ 'icon_size',
+ ),
+
+ /*
+
+ colors
+
+ */
+ ...generate_property_classes(
+ 'color',
+ text_color_variants.map(String),
+ (v) => `var(--text_color_${v})`,
+ 'text_color',
+ ),
+ ...generate_property_classes(
+ 'background-color',
+ intensity_variants.map(String),
+ (v) => `var(--darken_${v})`,
+ 'darken',
+ ),
+ ...generate_property_classes(
+ 'background-color',
+ intensity_variants.map(String),
+ (v) => `var(--lighten_${v})`,
+ 'lighten',
+ ),
+ bg: {declaration: 'background-color: var(--bg);'},
+ fg: {declaration: 'background-color: var(--fg);'},
+ ...generate_property_classes(
+ 'background-color',
+ intensity_variants.map(String),
+ (v) => `var(--bg_${v})`,
+ 'bg',
+ ),
+ ...generate_property_classes(
+ 'background-color',
+ intensity_variants.map(String),
+ (v) => `var(--fg_${v})`,
+ 'fg',
+ ),
+ ...generate_property_classes(
+ 'color',
+ intensity_variants.map(String),
+ (v) => `var(--darken_${v})`,
+ 'color_darken',
+ ),
+ ...generate_property_classes(
+ 'color',
+ intensity_variants.map(String),
+ (v) => `var(--lighten_${v})`,
+ 'color_lighten',
+ ),
+ color_bg: {declaration: 'color: var(--bg);'},
+ color_fg: {declaration: 'color: var(--fg);'},
+ ...generate_property_classes(
+ 'color',
+ intensity_variants.map(String),
+ (v) => `var(--bg_${v})`,
+ 'color_bg',
+ ),
+ ...generate_property_classes(
+ 'color',
+ intensity_variants.map(String),
+ (v) => `var(--fg_${v})`,
+ 'color_fg',
+ ),
+ ...generate_classes(
+ (hue: string) => ({
+ name: `hue_${hue}`,
+ css: `--hue: var(--hue_${hue});`,
+ }),
+ color_variants,
+ ),
+ ...generate_classes(
+ (hue: string, intensity: number) => ({
+ name: `color_${hue}_${intensity}`,
+ css: `color: var(--color_${hue}_${intensity});`,
+ }),
+ color_variants,
+ intensity_variants,
+ ),
+ ...generate_classes(
+ (hue: string, intensity: number) => ({
+ name: `bg_${hue}_${intensity}`,
+ css: `background-color: var(--color_${hue}_${intensity});`,
+ }),
+ color_variants,
+ intensity_variants,
+ ),
+
+ /*
+
+ borders
+
+ */
+ ...generate_property_classes(
+ 'border-color',
+ border_color_intensity_variants.map(String),
+ (v) => `var(--border_color_${v})`,
+ ),
+ ...generate_property_classes('border-color', color_variants, (v) => `var(--border_color_${v})`),
+ ...generate_property_classes(
+ 'outline-color',
+ border_color_intensity_variants.map(String),
+ (v) => `var(--border_color_${v})`,
+ ),
+ ...generate_property_classes('outline-color', color_variants, (v) => `var(--border_color_${v})`),
+
+ ...generate_property_classes(
+ 'border-width',
+ border_width_variants.map(String),
+ (v) => `var(--border_width_${v})`,
+ ),
+ ...generate_property_classes(
+ 'outline-width',
+ border_width_variants.map(String),
+ (v) => `var(--border_width_${v})`,
+ ),
+ outline_width_focus: {declaration: 'outline-width: var(--outline_width_focus);'},
+ outline_width_active: {declaration: 'outline-width: var(--outline_width_active);'},
+
+ ...generate_property_classes(
+ 'border-radius',
+ border_radius_variants,
+ (v) => `var(--border_radius_${v})`,
+ ),
+ ...generate_border_radius_corners(border_radius_variants, (v) => `var(--border_radius_${v})`),
+
+ /*
+
+ shadows
+
+ */
+ ...generate_shadow_classes(shadow_size_variants, {
+ xs: '1',
+ sm: '2',
+ md: '3',
+ lg: '4',
+ xl: '5',
+ }),
+ ...generate_classes(
+ (value: string) => ({
+ name: `shadow_color_${value}`,
+ css: `--shadow_color: var(--shadow_color_${value});`,
+ }),
+ shadow_semantic_values,
+ ),
+ ...generate_classes(
+ (hue: string) => ({
+ name: `shadow_color_${hue}`,
+ css: `--shadow_color: var(--shadow_color_${hue});`,
+ }),
+ color_variants,
+ ),
+ ...generate_classes(
+ (alpha: number) => ({
+ name: `shadow_alpha_${alpha}`,
+ css: `--shadow_alpha: var(--shadow_alpha_${alpha});`,
+ }),
+ shadow_alpha_variants,
+ ),
+
+ /*
+
+ layout
+
+ */
+ ...generate_property_classes('width', space_variants, (v) => `var(--space_${v})`),
+ ...generate_property_classes('height', space_variants, (v) => `var(--space_${v})`),
+
+ ...generate_classes(
+ (v: string) => ({
+ name: `width_atmost_${v}`,
+ css: `width: 100%; max-width: var(--distance_${v});`,
+ }),
+ distance_variants,
+ ),
+ ...generate_classes(
+ (v: string) => ({
+ name: `width_atleast_${v}`,
+ css: `width: 100%; min-width: var(--distance_${v});`,
+ }),
+ distance_variants,
+ ),
+ ...generate_classes(
+ (v: string) => ({
+ name: `height_atmost_${v}`,
+ css: `height: 100%; max-height: var(--distance_${v});`,
+ }),
+ distance_variants,
+ ),
+ ...generate_classes(
+ (v: string) => ({
+ name: `height_atleast_${v}`,
+ css: `height: 100%; min-height: var(--distance_${v});`,
+ }),
+ distance_variants,
+ ),
+
+ ...generate_property_classes('top', space_variants, format_spacing_value),
+ ...generate_property_classes('right', space_variants, format_spacing_value),
+ ...generate_property_classes('bottom', space_variants, format_spacing_value),
+ ...generate_property_classes('left', space_variants, format_spacing_value),
+ ...generate_property_classes('inset', space_variants, format_spacing_value),
+
+ ...generate_directional_classes('padding', ['0', ...space_variants], format_spacing_value),
+ ...generate_directional_classes('margin', ['0', 'auto', ...space_variants], format_spacing_value),
+ ...generate_property_classes('gap', space_variants, format_spacing_value),
+ ...generate_property_classes('column-gap', space_variants, format_spacing_value),
+ ...generate_property_classes('row-gap', space_variants, format_spacing_value),
+};
diff --git a/src/lib/css_class_extractor.ts b/src/lib/css_class_extractor.ts
new file mode 100644
index 000000000..43fe6b9d6
--- /dev/null
+++ b/src/lib/css_class_extractor.ts
@@ -0,0 +1,1035 @@
+/**
+ * AST-based CSS class extraction for Svelte and TypeScript files.
+ *
+ * Replaces regex-based extraction with proper parsing to handle:
+ * - `class="display:flex"` - string attributes
+ * - `class={{ active, disabled: !enabled }}` - object attributes (Svelte 5.16+)
+ * - `class={[cond && 'box', 'display:flex']}` - array attributes (Svelte 5.16+)
+ * - `class:active={cond}` - class directives
+ * - `clsx('foo', { bar: true })` - class utility function calls
+ * - Variables with class-related names
+ * - `// @fuz-classes class1 class2` - comment hints for dynamic classes
+ *
+ * @module
+ */
+
+import {parse as parse_svelte, type AST} from 'svelte/compiler';
+import {walk, type Visitors} from 'zimmerframe';
+import {Parser, type Node} from 'acorn';
+import {tsPlugin} from '@sveltejs/acorn-typescript';
+
+import {type SourceLocation, type ExtractionDiagnostic} from './diagnostics.js';
+
+//
+// Types
+//
+
+/**
+ * Extraction result with classes mapped to their source locations.
+ * Uses `null` instead of empty collections to avoid allocation overhead.
+ *
+ * Uses embedded diagnostics (rather than a Result type) because file extraction
+ * can partially succeed: some classes may be extracted while others produce errors.
+ * This differs from {@link CssLiteralParseResult} which uses a discriminated union
+ * because single-class parsing is binary success/failure.
+ */
+export interface ExtractionResult {
+ /**
+ * Map from class name to locations where it was used, or null if none.
+ * Keys = unique classes, values = locations for diagnostics/IDE integration.
+ */
+ classes: Map> | null;
+ /**
+ * Classes explicitly annotated via `@fuz-classes` comments, or null if none.
+ * These should produce warnings if they can't be resolved during generation.
+ */
+ explicit_classes: Set | null;
+ /** Variables that were used in class contexts, or null if none */
+ tracked_vars: Set | null;
+ /** Diagnostics from the extraction phase, or null if none */
+ diagnostics: Array | null;
+}
+
+/**
+ * Acorn plugin type - a function that extends the Parser class.
+ */
+export type AcornPlugin = (BaseParser: typeof Parser) => typeof Parser;
+
+/**
+ * Options for CSS class extraction.
+ */
+export interface ExtractCssClassesOptions {
+ /**
+ * File path used to determine extraction method (Svelte vs TS)
+ * and for location tracking in diagnostics.
+ */
+ filename?: string;
+ /**
+ * Additional acorn plugins to use when parsing TS/JS files.
+ * Useful for adding JSX support via `acorn-jsx` for React projects.
+ *
+ * @example
+ * ```ts
+ * import jsx from 'acorn-jsx';
+ * extract_css_classes(source, { acorn_plugins: [jsx()] });
+ * ```
+ */
+ acorn_plugins?: Array;
+}
+
+//
+// Utilities
+//
+
+/**
+ * Helper class for converting character offsets to line/column positions.
+ * Svelte template nodes (Comment, Text, ExpressionTag) only have char offsets,
+ * so this class enables efficient conversion.
+ *
+ * Build: O(n) where n = source length
+ * Lookup: O(log m) where m = number of lines (binary search)
+ */
+export class SourceIndex {
+ private line_starts: Array;
+
+ constructor(source: string) {
+ this.line_starts = [0];
+ for (let i = 0; i < source.length; i++) {
+ if (source[i] === '\n') this.line_starts.push(i + 1);
+ }
+ }
+
+ /**
+ * Converts a character offset to a source location.
+ *
+ * @param offset - 0-based character offset in the source
+ * @param file - File path for the location
+ * @returns SourceLocation with 1-based line and column
+ */
+ get_location(offset: number, file: string): SourceLocation {
+ // Binary search for line
+ let low = 0;
+ let high = this.line_starts.length - 1;
+ while (low < high) {
+ const mid = Math.ceil((low + high) / 2);
+ if (this.line_starts[mid]! <= offset) low = mid;
+ else high = mid - 1;
+ }
+ return {file, line: low + 1, column: offset - this.line_starts[low]! + 1};
+ }
+}
+
+/**
+ * Adds a class with its location to the extraction result.
+ *
+ * @param classes - Map of classes to locations
+ * @param class_name - Class name to add
+ * @param location - Source location where the class was found
+ * @mutates classes - Adds or appends to the map entry
+ */
+const add_class_with_location = (
+ classes: Map>,
+ class_name: string,
+ location: SourceLocation,
+): void => {
+ const existing = classes.get(class_name);
+ if (existing) {
+ existing.push(location);
+ } else {
+ classes.set(class_name, [location]);
+ }
+};
+
+// Known class utility function names
+const CLASS_UTILITY_FUNCTIONS = new Set([
+ 'clsx', // clsx package
+ 'cn', // common alias (shadcn/ui convention)
+ 'classNames', // classnames package
+ 'classnames', // lowercase variant
+ 'cx', // emotion and other libs
+]);
+
+// Svelte 5 runes that wrap expressions we should extract from
+const SVELTE_RUNES = new Set(['$derived', '$state']);
+
+// Pattern for variables with class-related names
+const CLASS_NAME_PATTERN = /(class|classes|class_?names?|class_?lists?)$/i;
+
+/**
+ * State maintained during AST walking.
+ */
+interface WalkState {
+ /** Map from class name to locations */
+ classes: Map>;
+ /** Classes explicitly annotated via @fuz-classes comments */
+ explicit_classes: Set;
+ /** Variables used in class contexts */
+ tracked_vars: Set;
+ /** Variables with class-like names, mapped to their initializers */
+ class_name_vars: Map;
+ /** Whether we're in a class context (for tracking variable usage) */
+ in_class_context: boolean;
+ /** File path for creating locations */
+ file: string;
+ /** Source index for char offset â line/column conversion (template only) */
+ source_index: SourceIndex | null;
+ /** Diagnostics collected during extraction */
+ diagnostics: Array;
+}
+
+/**
+ * Adds a class to the state with a location.
+ */
+const add_class = (state: WalkState, class_name: string, location: SourceLocation): void => {
+ add_class_with_location(state.classes, class_name, location);
+};
+
+/**
+ * Creates a location from a character offset using the source index.
+ */
+const location_from_offset = (state: WalkState, offset: number): SourceLocation => {
+ if (state.source_index) {
+ return state.source_index.get_location(offset, state.file);
+ }
+ // Fallback for script context (should have line/column from AST)
+ return {file: state.file, line: 1, column: 1};
+};
+
+/**
+ * Extracts CSS classes from a Svelte file using AST parsing.
+ *
+ * @param source - The Svelte file source code
+ * @param file - File path for location tracking
+ * @returns Extraction result with classes, tracked variables, and diagnostics
+ */
+export const extract_from_svelte = (source: string, file = ''): ExtractionResult => {
+ const classes: Map> = new Map();
+ const explicit_classes: Set = new Set();
+ const tracked_vars: Set = new Set();
+ const class_name_vars: Map = new Map();
+ const diagnostics: Array = [];
+ const source_index = new SourceIndex(source);
+
+ let ast: AST.Root;
+ try {
+ ast = parse_svelte(source, {modern: true});
+ } catch (err) {
+ // Emit diagnostic about parse failure
+ diagnostics.push({
+ phase: 'extraction',
+ level: 'warning',
+ message: `Failed to parse Svelte file: ${err instanceof Error ? err.message : 'unknown error'}`,
+ suggestion: 'Check for syntax errors in the file',
+ location: {file, line: 1, column: 1},
+ });
+ // Return with diagnostics (classes/tracked_vars empty, so null)
+ return {classes: null, explicit_classes: null, tracked_vars: null, diagnostics};
+ }
+
+ const state: WalkState = {
+ classes,
+ explicit_classes,
+ tracked_vars,
+ class_name_vars,
+ in_class_context: false,
+ file,
+ source_index,
+ diagnostics,
+ };
+
+ // Extract from @fuz-classes comments via AST (Svelte Comment nodes)
+ extract_fuz_classes_from_svelte_comments(ast, state);
+
+ // Walk the template AST
+ walk_template(ast.fragment, state);
+
+ // Walk the script AST (module and instance) including @fuz-classes comments
+ if (ast.instance) {
+ extract_fuz_classes_from_script(ast.instance, source, state);
+ walk_script(ast.instance.content, state);
+ }
+ if (ast.module) {
+ extract_fuz_classes_from_script(ast.module, source, state);
+ walk_script(ast.module.content, state);
+ }
+
+ // Second pass: extract from tracked variables that weren't already processed
+ if (tracked_vars.size > 0 && (ast.instance || ast.module)) {
+ extract_from_tracked_vars(ast, state);
+ }
+
+ // Convert empty to null
+ return {
+ classes: classes.size > 0 ? classes : null,
+ explicit_classes: explicit_classes.size > 0 ? explicit_classes : null,
+ tracked_vars: tracked_vars.size > 0 ? tracked_vars : null,
+ diagnostics: diagnostics.length > 0 ? diagnostics : null,
+ };
+};
+
+/**
+ * Parses @fuz-classes content from a comment, handling the colon variant.
+ * Returns the list of class names or null if not a @fuz-classes comment.
+ */
+const FUZ_CLASSES_PATTERN = /^\s*@fuz-classes(:?)\s+(.+?)\s*$/;
+
+const parse_fuz_classes_comment = (
+ content: string,
+ location: SourceLocation,
+ diagnostics: Array,
+): Array | null => {
+ // Match @fuz-classes with optional colon
+ const match = FUZ_CLASSES_PATTERN.exec(content);
+ if (!match) return null;
+
+ const has_colon = match[1] === ':';
+ const class_list = match[2]!;
+
+ // Emit warning if colon was used (unnecessary but supported)
+ if (has_colon) {
+ diagnostics.push({
+ phase: 'extraction',
+ level: 'warning',
+ message: '@fuz-classes: colon is unnecessary',
+ suggestion: 'Use @fuz-classes without the colon',
+ location,
+ });
+ }
+
+ return class_list.split(/\s+/).filter(Boolean);
+};
+
+/**
+ * Extracts @fuz-classes from Svelte HTML Comment nodes.
+ */
+const extract_fuz_classes_from_svelte_comments = (ast: AST.Root, state: WalkState): void => {
+ // Walk the fragment looking for Comment nodes
+ const visitors: Visitors = {
+ Comment(node, {state}) {
+ const location = location_from_offset(state, node.start);
+ const classes = parse_fuz_classes_comment(node.data, location, state.diagnostics);
+ if (classes) {
+ for (const cls of classes) {
+ add_class(state, cls, location);
+ state.explicit_classes.add(cls);
+ }
+ }
+ },
+ };
+
+ walk(ast.fragment as AST.SvelteNode, state, visitors);
+};
+
+/**
+ * Extracts @fuz-classes from script blocks by re-parsing with acorn.
+ * Svelte's parser doesn't expose JS comments, so we parse the
+ * script source separately to get comments via acorn's onComment callback.
+ */
+const extract_fuz_classes_from_script = (
+ script: AST.Script,
+ source: string,
+ state: WalkState,
+): void => {
+ // Extract the script source using start/end positions
+ // Svelte AST nodes have start/end but the TypeScript types don't include them
+ const content = script.content as unknown as {start: number; end: number};
+ const script_source = source.slice(content.start, content.end);
+
+ // Calculate the line offset for proper location reporting
+ // Count newlines before the script content starts
+ let line_offset = 0;
+ for (let i = 0; i < content.start; i++) {
+ if (source[i] === '\n') line_offset++;
+ }
+
+ // Collect comments via acorn's onComment callback
+ const comments: Array<{value: string; loc: {start: {line: number; column: number}}}> = [];
+
+ try {
+ const parser = Parser.extend(tsPlugin());
+ parser.parse(script_source, {
+ ecmaVersion: 'latest',
+ sourceType: 'module',
+ locations: true,
+ onComment: (
+ _block: boolean,
+ text: string,
+ _start: number,
+ _end: number,
+ startLoc?: {line: number; column: number},
+ ) => {
+ if (startLoc) {
+ comments.push({value: text, loc: {start: startLoc}});
+ }
+ },
+ });
+ } catch {
+ // If parsing fails, we can't extract comments
+ return;
+ }
+
+ // Process @fuz-classes comments
+ for (const comment of comments) {
+ const location: SourceLocation = {
+ file: state.file,
+ line: comment.loc.start.line + line_offset,
+ column: comment.loc.start.column + 1,
+ };
+ const class_list = parse_fuz_classes_comment(comment.value, location, state.diagnostics);
+ if (class_list) {
+ for (const cls of class_list) {
+ add_class(state, cls, location);
+ state.explicit_classes.add(cls);
+ }
+ }
+ }
+};
+
+/**
+ * Extracts CSS classes from a TypeScript/JS file using AST parsing.
+ *
+ * @param source - The TS/JS file source code
+ * @param file - File path for location tracking
+ * @param acorn_plugins - Additional acorn plugins (e.g., acorn-jsx for React)
+ * @returns Extraction result with classes, tracked variables, and diagnostics
+ */
+export const extract_from_ts = (
+ source: string,
+ file = '',
+ acorn_plugins?: Array,
+): ExtractionResult => {
+ const classes: Map> = new Map();
+ const explicit_classes: Set = new Set();
+ const tracked_vars: Set = new Set();
+ const class_name_vars: Map = new Map();
+ const diagnostics: Array = [];
+
+ // Collect comments via acorn's onComment callback
+ const comments: Array<{value: string; loc: {start: {line: number; column: number}}}> = [];
+
+ let ast: Node;
+ try {
+ // Use acorn with TypeScript plugin, plus any additional plugins (e.g., jsx)
+ const plugins: Array = [tsPlugin(), ...(acorn_plugins ?? [])];
+ const parser = plugins.reduce((p, plugin) => plugin(p), Parser);
+ ast = parser.parse(source, {
+ ecmaVersion: 'latest',
+ sourceType: 'module',
+ locations: true,
+ onComment: (
+ _block: boolean,
+ text: string,
+ _start: number,
+ _end: number,
+ startLoc?: {line: number; column: number},
+ ) => {
+ if (startLoc) {
+ comments.push({value: text, loc: {start: startLoc}});
+ }
+ },
+ });
+ } catch (err) {
+ // Emit diagnostic about parse failure
+ diagnostics.push({
+ phase: 'extraction',
+ level: 'warning',
+ message: `Failed to parse TypeScript/JS file: ${err instanceof Error ? err.message : 'unknown error'}`,
+ suggestion: 'Check for syntax errors in the file',
+ location: {file, line: 1, column: 1},
+ });
+ // Return with diagnostics (classes/tracked_vars empty, so null)
+ return {classes: null, explicit_classes: null, tracked_vars: null, diagnostics};
+ }
+
+ // Process @fuz-classes comments
+ for (const comment of comments) {
+ const location: SourceLocation = {
+ file,
+ line: comment.loc.start.line,
+ column: comment.loc.start.column + 1,
+ };
+ const class_list = parse_fuz_classes_comment(comment.value, location, diagnostics);
+ if (class_list) {
+ for (const cls of class_list) {
+ add_class_with_location(classes, cls, location);
+ explicit_classes.add(cls);
+ }
+ }
+ }
+
+ const state: WalkState = {
+ classes,
+ explicit_classes,
+ tracked_vars,
+ class_name_vars,
+ in_class_context: false,
+ file,
+ source_index: null, // Not needed for TS files - acorn provides locations
+ diagnostics,
+ };
+
+ walk_script(ast, state);
+
+ // Second pass: extract from tracked variables that weren't already processed
+ // This handles JSX patterns where className={foo} is encountered after const foo = '...'
+ if (tracked_vars.size > 0) {
+ extract_from_tracked_vars_in_script(ast, state);
+ }
+
+ // Convert empty to null
+ return {
+ classes: classes.size > 0 ? classes : null,
+ explicit_classes: explicit_classes.size > 0 ? explicit_classes : null,
+ tracked_vars: tracked_vars.size > 0 ? tracked_vars : null,
+ diagnostics: diagnostics.length > 0 ? diagnostics : null,
+ };
+};
+
+/**
+ * Unified extraction function that auto-detects file type.
+ * Returns just the class names as a Set.
+ *
+ * @param source - The file source code
+ * @param options - Extraction options
+ * @returns Set of class names
+ */
+export const extract_css_classes = (
+ source: string,
+ options: ExtractCssClassesOptions = {},
+): Set => {
+ const result = extract_css_classes_with_locations(source, options);
+ return result.classes ? new Set(result.classes.keys()) : new Set();
+};
+
+/**
+ * Unified extraction function that auto-detects file type.
+ * Returns full extraction result with locations and diagnostics.
+ *
+ * @param source - The file source code
+ * @param options - Extraction options
+ * @returns Full extraction result with classes, tracked variables, and diagnostics
+ */
+export const extract_css_classes_with_locations = (
+ source: string,
+ options: ExtractCssClassesOptions = {},
+): ExtractionResult => {
+ const {filename, acorn_plugins} = options;
+ const ext = filename ? filename.slice(filename.lastIndexOf('.')) : '';
+ const file = filename ?? '';
+
+ if (ext === '.svelte' || ext === '.html') {
+ return extract_from_svelte(source, file);
+ } else if (ext === '.ts' || ext === '.js' || ext === '.tsx' || ext === '.jsx') {
+ return extract_from_ts(source, file, acorn_plugins);
+ }
+
+ // Default to Svelte-style extraction (handles both)
+ const svelte_result = extract_from_svelte(source, file);
+ if (svelte_result.classes) {
+ return svelte_result;
+ }
+ return extract_from_ts(source, file, acorn_plugins);
+};
+
+// Template AST walking
+
+/**
+ * Walks the Svelte template AST to extract class names.
+ */
+const walk_template = (fragment: AST.Fragment, state: WalkState): void => {
+ const visitors: Visitors = {
+ // Handle regular elements and components
+ RegularElement(node, {state, next}) {
+ process_element_attributes(node.attributes, state);
+ next();
+ },
+ SvelteElement(node, {state, next}) {
+ process_element_attributes(node.attributes, state);
+ next();
+ },
+ SvelteComponent(node, {state, next}) {
+ process_element_attributes(node.attributes, state);
+ next();
+ },
+ Component(node, {state, next}) {
+ process_element_attributes(node.attributes, state);
+ next();
+ },
+ SvelteFragment(node, {state, next}) {
+ process_element_attributes(node.attributes, state);
+ next();
+ },
+ };
+
+ walk(fragment as AST.SvelteNode, state, visitors);
+};
+
+/**
+ * Processes attributes on an element to extract class names.
+ */
+const process_element_attributes = (
+ attributes: Array,
+ state: WalkState,
+): void => {
+ for (const attr of attributes) {
+ // Handle class attribute
+ if (attr.type === 'Attribute' && attr.name === 'class') {
+ extract_from_attribute_value(attr.value, state);
+ }
+
+ // Handle class: directive
+ if (attr.type === 'ClassDirective') {
+ add_class(state, attr.name, location_from_offset(state, attr.start));
+ }
+ }
+};
+
+/**
+ * Extracts classes from an attribute value.
+ * Handles string literals, expressions, objects, and arrays.
+ */
+const extract_from_attribute_value = (value: AST.Attribute['value'], state: WalkState): void => {
+ if (value === true) {
+ // Boolean attribute, no classes
+ return;
+ }
+
+ // Handle array of Text and ExpressionTag (e.g., class="foo {expr} bar")
+ if (Array.isArray(value)) {
+ for (const part of value) {
+ if (part.type === 'Text') {
+ // Static text: split on whitespace
+ const location = location_from_offset(state, part.start);
+ for (const cls of part.data.split(/\s+/).filter(Boolean)) {
+ add_class(state, cls, location);
+ }
+ } else {
+ // ExpressionTag: extract from the expression
+ state.in_class_context = true;
+ extract_from_expression(part.expression, state);
+ state.in_class_context = false;
+ }
+ }
+ return;
+ }
+
+ // Handle single ExpressionTag (e.g., class={expression})
+ // At this point, value is an ExpressionTag
+ state.in_class_context = true;
+ extract_from_expression(value.expression, state);
+ state.in_class_context = false;
+};
+
+/**
+ * Extracts classes from a TS expression.
+ * Handles strings, arrays, objects, conditionals, and function calls.
+ */
+const extract_from_expression = (expr: AST.SvelteNode, state: WalkState): void => {
+ // Get location from the expression's start offset
+ const get_location = (): SourceLocation => {
+ const node = expr as unknown as {start?: number; loc?: {start: {line: number; column: number}}};
+ if (node.loc) {
+ return {file: state.file, line: node.loc.start.line, column: node.loc.start.column + 1};
+ }
+ if (node.start !== undefined) {
+ return location_from_offset(state, node.start);
+ }
+ return {file: state.file, line: 1, column: 1};
+ };
+
+ switch (expr.type) {
+ case 'Literal': {
+ // String literal
+ const node = expr as unknown as {value: unknown};
+ if (typeof node.value === 'string') {
+ const location = get_location();
+ for (const cls of node.value.split(/\s+/).filter(Boolean)) {
+ add_class(state, cls, location);
+ }
+ }
+ break;
+ }
+
+ case 'TemplateLiteral': {
+ // Template literal - extract static parts that are complete tokens
+ // Only extract tokens that are whitespace-bounded (not fragments like `icon-` from `icon-${size}`)
+ const node = expr as unknown as {
+ quasis: Array<{
+ value: {raw: string};
+ start?: number;
+ loc?: {start: {line: number; column: number}};
+ }>;
+ expressions: Array;
+ };
+
+ const has_expressions = node.expressions.length > 0;
+
+ for (let i = 0; i < node.quasis.length; i++) {
+ const quasi = node.quasis[i]!;
+ if (!quasi.value.raw) continue;
+
+ const location = quasi.loc
+ ? {file: state.file, line: quasi.loc.start.line, column: quasi.loc.start.column + 1}
+ : quasi.start !== undefined
+ ? location_from_offset(state, quasi.start)
+ : get_location();
+
+ const raw = quasi.value.raw;
+
+ if (!has_expressions) {
+ // No expressions - extract all tokens (pure static template literal)
+ for (const cls of raw.split(/\s+/).filter(Boolean)) {
+ add_class(state, cls, location);
+ }
+ } else {
+ // Has expressions - only extract complete tokens
+ // A token is complete if bounded by whitespace/string-boundary on both sides
+ const is_first = i === 0;
+ const is_last = i === node.quasis.length - 1;
+ const has_expr_before = !is_first;
+ const has_expr_after = !is_last;
+
+ // Split preserving info about boundaries
+ const tokens = raw.split(/\s+/);
+ const starts_with_ws = /^\s/.test(raw);
+ const ends_with_ws = /\s$/.test(raw);
+
+ for (let j = 0; j < tokens.length; j++) {
+ const token = tokens[j];
+ if (!token) continue;
+
+ const is_first_token = j === 0;
+ const is_last_token = j === tokens.length - 1;
+
+ // Token is bounded on the left if:
+ // - It's the first token AND (is_first quasi OR starts_with_ws)
+ // - OR it's not the first token (preceded by whitespace from split)
+ const bounded_left = !is_first_token || is_first || (has_expr_before && starts_with_ws);
+
+ // Token is bounded on the right if:
+ // - It's the last token AND (is_last quasi OR ends_with_ws)
+ // - OR it's not the last token (followed by whitespace from split)
+ const bounded_right = !is_last_token || is_last || (has_expr_after && ends_with_ws);
+
+ if (bounded_left && bounded_right) {
+ add_class(state, token, location);
+ }
+ }
+ }
+ }
+
+ // Also extract from expressions (e.g., ternaries inside the template)
+ for (const subexpr of node.expressions) {
+ extract_from_expression(subexpr as AST.SvelteNode, state);
+ }
+ break;
+ }
+
+ case 'ArrayExpression': {
+ // Array: extract from each element
+ const node = expr as unknown as {elements: Array};
+ for (const element of node.elements) {
+ if (element) {
+ extract_from_expression(element as AST.SvelteNode, state);
+ }
+ }
+ break;
+ }
+
+ case 'ObjectExpression': {
+ // Object: extract keys (values are conditions)
+ const node = expr as unknown as {
+ properties: Array<{
+ type: string;
+ key: unknown;
+ computed: boolean;
+ start?: number;
+ loc?: {start: {line: number; column: number}};
+ }>;
+ };
+ for (const prop of node.properties) {
+ if (prop.type === 'Property' && !prop.computed) {
+ // Non-computed key - extract the key as a class name
+ const key = prop.key as {type: string; name?: string; value?: string};
+ const location = prop.loc
+ ? {file: state.file, line: prop.loc.start.line, column: prop.loc.start.column + 1}
+ : prop.start !== undefined
+ ? location_from_offset(state, prop.start)
+ : get_location();
+ if (key.type === 'Identifier') {
+ add_class(state, key.name!, location);
+ } else if (key.type === 'Literal' && typeof key.value === 'string') {
+ // Handle string keys like { 'display:flex': condition }
+ for (const cls of key.value.split(/\s+/).filter(Boolean)) {
+ add_class(state, cls, location);
+ }
+ }
+ }
+ }
+ break;
+ }
+
+ case 'ConditionalExpression': {
+ // Ternary: extract from both branches
+ const node = expr as unknown as {consequent: unknown; alternate: unknown};
+ extract_from_expression(node.consequent as AST.SvelteNode, state);
+ extract_from_expression(node.alternate as AST.SvelteNode, state);
+ break;
+ }
+
+ case 'LogicalExpression': {
+ // && or ||: extract from both sides
+ const node = expr as unknown as {left: unknown; right: unknown};
+ extract_from_expression(node.left as AST.SvelteNode, state);
+ extract_from_expression(node.right as AST.SvelteNode, state);
+ break;
+ }
+
+ case 'CallExpression': {
+ // Function call: check if it's a class utility function or Svelte rune
+ const node = expr as unknown as {
+ callee: {type: string; name?: string; object?: {name?: string}; property?: {name?: string}};
+ arguments: Array;
+ };
+
+ let should_extract = false;
+
+ if (node.callee.type === 'Identifier') {
+ // Direct call: clsx(), cn(), $derived(), etc.
+ should_extract =
+ CLASS_UTILITY_FUNCTIONS.has(node.callee.name!) || SVELTE_RUNES.has(node.callee.name!);
+ } else if (node.callee.type === 'MemberExpression') {
+ // Member call: $derived.by(), etc.
+ const obj = node.callee.object;
+ if (obj?.name && SVELTE_RUNES.has(obj.name)) {
+ should_extract = true;
+ }
+ }
+
+ if (should_extract) {
+ for (const arg of node.arguments) {
+ extract_from_expression(arg as AST.SvelteNode, state);
+ }
+ }
+ break;
+ }
+
+ case 'Identifier': {
+ // Variable reference: track it for later extraction
+ const node = expr as unknown as {name: string};
+ if (state.in_class_context) {
+ state.tracked_vars.add(node.name);
+ }
+ break;
+ }
+
+ case 'MemberExpression': {
+ // Member access like obj.prop - we can't statically extract
+ // but track the root identifier
+ break;
+ }
+
+ case 'TaggedTemplateExpression': {
+ // Tagged template literal like css`class-name` - extract from the template
+ const node = expr as unknown as {quasi: unknown};
+ if (node.quasi) {
+ extract_from_expression(node.quasi as AST.SvelteNode, state);
+ }
+ break;
+ }
+
+ case 'ArrowFunctionExpression':
+ case 'FunctionExpression': {
+ // Function expression: extract from the body
+ // Handles $derived.by(() => cond ? 'a' : 'b')
+ const node = expr as unknown as {body: unknown};
+ if (node.body) {
+ extract_from_expression(node.body as AST.SvelteNode, state);
+ }
+ break;
+ }
+
+ case 'BlockStatement': {
+ // Block statement: walk all statements looking for return statements
+ const node = expr as unknown as {body: Array};
+ for (const stmt of node.body) {
+ extract_from_expression(stmt as AST.SvelteNode, state);
+ }
+ break;
+ }
+
+ case 'ReturnStatement': {
+ // Return statement: extract from the argument
+ const node = expr as unknown as {argument: unknown};
+ if (node.argument) {
+ extract_from_expression(node.argument as AST.SvelteNode, state);
+ }
+ break;
+ }
+
+ default:
+ // Other expression types we don't handle
+ break;
+ }
+};
+
+// Script AST walking
+
+/**
+ * Extracts classes from a JSX attribute value (React className, Preact/Solid class, Solid classList).
+ * Handles string literals and expression containers.
+ * Sets in_class_context to enable variable tracking.
+ */
+const extract_from_jsx_attribute_value = (value: unknown, state: WalkState): void => {
+ const node = value as {
+ type: string;
+ value?: string;
+ expression?: unknown;
+ loc?: {start: {line: number; column: number}};
+ };
+
+ if (node.type === 'Literal' && typeof node.value === 'string') {
+ // Static className="foo bar"
+ const location: SourceLocation = node.loc
+ ? {file: state.file, line: node.loc.start.line, column: node.loc.start.column + 1}
+ : {file: state.file, line: 1, column: 1};
+ for (const cls of node.value.split(/\s+/).filter(Boolean)) {
+ add_class(state, cls, location);
+ }
+ } else if (node.type === 'JSXExpressionContainer' && node.expression) {
+ // Dynamic className={expr} - enable variable tracking
+ const prev_context = state.in_class_context;
+ state.in_class_context = true;
+ extract_from_expression(node.expression as AST.SvelteNode, state);
+ state.in_class_context = prev_context;
+ }
+};
+
+/**
+ * Walks a TypeScript/JS AST to extract class names.
+ */
+const walk_script = (ast: unknown, state: WalkState): void => {
+ const visitors: Visitors = {
+ // Variable declarations
+ VariableDeclarator(node, {state, next}) {
+ const declarator = node as unknown as {id: {type: string; name: string}; init: unknown};
+ if (declarator.id.type === 'Identifier') {
+ const name = declarator.id.name;
+ // Check if variable name matches class pattern
+ if (CLASS_NAME_PATTERN.test(name)) {
+ state.class_name_vars.set(name, declarator.init);
+ if (declarator.init) {
+ extract_from_expression(declarator.init as AST.SvelteNode, state);
+ }
+ }
+ // Also check if this variable was tracked from usage
+ if (state.tracked_vars.has(name) && declarator.init) {
+ extract_from_expression(declarator.init as AST.SvelteNode, state);
+ }
+ }
+ next();
+ },
+
+ // Call expressions (for clsx/cn calls outside of class attributes)
+ CallExpression(node, {state, next}) {
+ const call = node as unknown as {
+ callee: {type: string; name?: string};
+ arguments: Array;
+ };
+ if (call.callee.type === 'Identifier' && CLASS_UTILITY_FUNCTIONS.has(call.callee.name!)) {
+ for (const arg of call.arguments) {
+ extract_from_expression(arg as AST.SvelteNode, state);
+ }
+ }
+ next();
+ },
+
+ // Object properties with class-related keys
+ Property(node, {state, next}) {
+ const prop = node as unknown as {
+ key: {type: string; name?: string; value?: string};
+ value: unknown;
+ computed: boolean;
+ };
+ if (!prop.computed) {
+ // Extract key name from identifier or string literal
+ let key_name: string | undefined;
+ if (prop.key.type === 'Identifier') {
+ key_name = prop.key.name;
+ } else if (prop.key.type === 'Literal' && typeof prop.key.value === 'string') {
+ key_name = prop.key.value;
+ }
+ if (
+ key_name &&
+ (key_name === 'class' || key_name === 'className' || CLASS_NAME_PATTERN.test(key_name))
+ ) {
+ extract_from_expression(prop.value as AST.SvelteNode, state);
+ }
+ }
+ next();
+ },
+
+ // JSX elements (React/Preact/Solid) - extract class-related attributes
+ // These are only present when acorn-jsx plugin is used
+ JSXElement(node, {state, next}) {
+ const element = node as unknown as {
+ openingElement: {
+ attributes: Array<{
+ type: string;
+ name?: {type: string; name?: string};
+ value?: unknown;
+ }>;
+ };
+ };
+ for (const attr of element.openingElement.attributes) {
+ if (attr.type === 'JSXAttribute' && attr.value) {
+ const attr_name = attr.name?.name;
+ // className (React), class (Preact/Solid)
+ if (attr_name === 'className' || attr_name === 'class') {
+ extract_from_jsx_attribute_value(attr.value, state);
+ }
+ // classList (Solid) - object syntax like classList={{ active: isActive }}
+ else if (attr_name === 'classList') {
+ extract_from_jsx_attribute_value(attr.value, state);
+ }
+ }
+ }
+ next();
+ },
+ };
+
+ walk(ast as Node, state, visitors);
+};
+
+/**
+ * Second pass to extract from tracked variables in Svelte scripts.
+ */
+const extract_from_tracked_vars = (ast: AST.Root, state: WalkState): void => {
+ const scripts = [ast.instance?.content, ast.module?.content].filter(Boolean);
+
+ for (const script of scripts) {
+ extract_from_tracked_vars_in_script(script as unknown as Node, state);
+ }
+};
+
+/**
+ * Second pass to extract from tracked variables in a standalone script AST.
+ * Used for both Svelte scripts and standalone TS/JS files (including JSX).
+ */
+const extract_from_tracked_vars_in_script = (ast: Node, state: WalkState): void => {
+ const find_visitors: Visitors = {
+ VariableDeclarator(node, {state}) {
+ const declarator = node as unknown as {id: {type: string; name: string}; init: unknown};
+ if (
+ declarator.id.type === 'Identifier' &&
+ state.tracked_vars.has(declarator.id.name) &&
+ !state.class_name_vars.has(declarator.id.name) &&
+ declarator.init
+ ) {
+ extract_from_expression(declarator.init as AST.SvelteNode, state);
+ }
+ },
+ };
+
+ walk(ast, state, find_visitors);
+};
diff --git a/src/lib/css_class_generation.ts b/src/lib/css_class_generation.ts
new file mode 100644
index 000000000..ce6310188
--- /dev/null
+++ b/src/lib/css_class_generation.ts
@@ -0,0 +1,361 @@
+/**
+ * CSS class generation utilities.
+ *
+ * Produces CSS output from class definitions, handles interpretation of
+ * dynamic classes, and provides collection management for extracted classes.
+ *
+ * @module
+ */
+
+import type {Logger} from '@fuzdev/fuz_util/log.js';
+
+import {
+ type SourceLocation,
+ type InterpreterDiagnostic,
+ type GenerationDiagnostic,
+ create_generation_diagnostic,
+} from './diagnostics.js';
+import {
+ parse_ruleset,
+ is_single_selector_ruleset,
+ ruleset_contains_class,
+} from './css_ruleset_parser.js';
+import {resolve_class_definition} from './css_class_resolution.js';
+import {get_modifier} from './modifiers.js';
+
+//
+// CSS Utilities
+//
+
+/**
+ * Escapes special characters in a CSS class name for use in a selector.
+ * CSS selectors require escaping of characters like `:`, `%`, `(`, `)`, etc.
+ *
+ * @example
+ * escape_css_selector('display:flex') // 'display\\:flex'
+ * escape_css_selector('opacity:80%') // 'opacity\\:80\\%'
+ * escape_css_selector('nth-child(2n)') // 'nth-child\\(2n\\)'
+ */
+export const escape_css_selector = (name: string): string => {
+ return name.replace(/[!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g, '\\$&');
+};
+
+//
+// Class Definitions
+//
+
+export interface CssClassDefinitionBase {
+ comment?: string;
+}
+
+/** Pure utility composition (composes only). */
+export interface CssClassDefinitionComposition extends CssClassDefinitionBase {
+ composes: Array;
+ declaration?: never;
+ ruleset?: never;
+}
+
+/** Custom CSS declaration (optionally seeded with composes). */
+export interface CssClassDefinitionDeclaration extends CssClassDefinitionBase {
+ declaration: string;
+ composes?: Array;
+ ruleset?: never;
+}
+
+/** Full ruleset with selectors. */
+export interface CssClassDefinitionRuleset extends CssClassDefinitionBase {
+ ruleset: string;
+ classes?: never;
+ declaration?: never;
+}
+
+/** Static definitions (not interpreter-based). */
+export type CssClassDefinitionStatic =
+ | CssClassDefinitionComposition
+ | CssClassDefinitionDeclaration
+ | CssClassDefinitionRuleset;
+
+/** Full union including interpreters. */
+export type CssClassDefinition = CssClassDefinitionStatic | CssClassDefinitionInterpreter;
+
+/**
+ * Context passed to CSS class interpreters.
+ * Provides access to logging, diagnostics collection, and the class registry.
+ */
+export interface CssClassInterpreterContext {
+ /** Optional logger for warnings/errors */
+ log?: Logger;
+ /** Diagnostics array to collect warnings and errors */
+ diagnostics: Array;
+ /** All known CSS class definitions (token + composite classes) */
+ class_definitions: Record;
+ /** Valid CSS properties for literal validation, or null to skip validation */
+ css_properties: Set | null;
+}
+
+/** Interpreter for dynamic CSS class generation based on pattern matching. */
+export interface CssClassDefinitionInterpreter extends CssClassDefinitionBase {
+ pattern: RegExp;
+ /**
+ * @mutates ctx.diagnostics - Implementations push errors/warnings to the diagnostics array
+ */
+ interpret: (matched: RegExpMatchArray, ctx: CssClassInterpreterContext) => string | null;
+}
+
+//
+// CSS Generation
+//
+
+/**
+ * Result from CSS class generation.
+ */
+export interface GenerateClassesCssResult {
+ css: string;
+ diagnostics: Array;
+}
+
+export interface GenerateClassesCssOptions {
+ class_names: Iterable;
+ class_definitions: Record;
+ interpreters: Array;
+ /** Valid CSS properties for literal validation, or null to skip validation */
+ css_properties: Set | null;
+ log?: Logger;
+ class_locations?: Map | null>;
+ /**
+ * Classes that were explicitly annotated (via @fuz-classes or include_classes).
+ * Unresolved explicit classes produce warnings.
+ */
+ explicit_classes?: Set | null;
+}
+
+export const generate_classes_css = (
+ options: GenerateClassesCssOptions,
+): GenerateClassesCssResult => {
+ const {
+ class_names,
+ class_definitions,
+ interpreters,
+ css_properties,
+ log,
+ class_locations,
+ explicit_classes,
+ } = options;
+ const interpreter_diagnostics: Array = [];
+ const diagnostics: Array = [];
+
+ // Create interpreter context with access to all class definitions
+ const ctx: CssClassInterpreterContext = {
+ log,
+ diagnostics: interpreter_diagnostics,
+ class_definitions,
+ css_properties,
+ };
+
+ // Create a map that has the index of each class name as the key
+ const indexes: Map = new Map();
+ const keys = Object.keys(class_definitions);
+ for (let i = 0; i < keys.length; i++) {
+ indexes.set(keys[i]!, i);
+ }
+
+ // Helper to get the maximum state modifier order from a class name
+ // This ensures proper cascade: hover (5) comes before active (6)
+ const get_state_modifier_order = (class_name: string): number => {
+ const parts = class_name.split(':');
+ let max_order = 0;
+ for (const part of parts.slice(0, -1)) {
+ // Check each potential modifier prefix
+ const modifier = get_modifier(part);
+ if (modifier?.type === 'state' && modifier.order !== undefined) {
+ max_order = Math.max(max_order, modifier.order);
+ }
+ }
+ return max_order;
+ };
+
+ // Sort classes: first by definition index, then by state modifier order, then alphabetically
+ const sorted_classes = (Array.isArray(class_names) ? class_names : Array.from(class_names)).sort(
+ (a, b) => {
+ const index_a = indexes.get(a) ?? Number.MAX_VALUE;
+ const index_b = indexes.get(b) ?? Number.MAX_VALUE;
+ if (index_a !== index_b) return index_a - index_b;
+ // For classes with modifiers, sort by state modifier order (for proper cascade)
+ const order_a = get_state_modifier_order(a);
+ const order_b = get_state_modifier_order(b);
+ if (order_a !== order_b) return order_a - order_b;
+ return a.localeCompare(b); // alphabetic tiebreaker for stable sort
+ },
+ );
+
+ let css = '';
+ for (const c of sorted_classes) {
+ let v = class_definitions[c];
+
+ // Track diagnostics count before this class
+ const diag_count_before = interpreter_diagnostics.length;
+
+ // If not found statically, try interpreters
+ let interpreter_matched = false;
+ if (!v) {
+ for (const interpreter of interpreters) {
+ const matched = c.match(interpreter.pattern);
+ if (matched) {
+ interpreter_matched = true;
+ const result = interpreter.interpret(matched, ctx);
+ if (result) {
+ // Check if the result is a full ruleset (contains braces)
+ // or just a declaration
+ if (result.includes('{')) {
+ // Full ruleset - use as-is
+ v = {ruleset: result, comment: interpreter.comment};
+ } else {
+ // Simple declaration
+ v = {declaration: result, comment: interpreter.comment};
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ // Convert any new interpreter diagnostics to GenerationDiagnostic with locations
+ // For non-explicit classes, downgrade CSS property errors to warnings (may be from other CSS systems)
+ // Structural errors (circular refs, unknown composes) remain errors regardless
+ for (let i = diag_count_before; i < interpreter_diagnostics.length; i++) {
+ const diag = interpreter_diagnostics[i]!;
+ const locations = class_locations?.get(diag.class_name) ?? null;
+ const is_explicit = explicit_classes?.has(diag.class_name) ?? false;
+ const is_css_property_error =
+ diag.level === 'error' && diag.message.startsWith('Unknown CSS property');
+ const level = is_css_property_error && !is_explicit ? 'warning' : diag.level;
+ diagnostics.push(create_generation_diagnostic({...diag, level}, locations));
+ }
+
+ if (!v) {
+ // Error if this was an explicitly requested class (via @fuz-classes or include_classes)
+ // but only if no interpreter pattern matched (if one matched but failed, error already reported)
+ if (explicit_classes?.has(c) && !interpreter_matched) {
+ const locations = class_locations?.get(c) ?? null;
+ diagnostics.push({
+ phase: 'generation',
+ level: 'error',
+ message: 'No matching class definition found',
+ class_name: c,
+ suggestion: 'Check spelling or add a custom class definition',
+ locations,
+ });
+ }
+ continue;
+ }
+
+ const {comment} = v;
+
+ if (comment) {
+ const trimmed = comment.trim();
+ if (trimmed.includes('\n')) {
+ // Multi-line CSS comment
+ const lines = trimmed.split('\n').map((line) => line.trim());
+ css += `/*\n${lines.join('\n')}\n*/\n`;
+ } else {
+ css += `/* ${trimmed} */\n`;
+ }
+ }
+
+ // Handle composes-based or declaration-based definitions
+ if ('composes' in v || 'declaration' in v) {
+ const resolution_result = resolve_class_definition(v, c, class_definitions, css_properties);
+ if (!resolution_result.ok) {
+ // Add error diagnostic and skip this class
+ diagnostics.push({
+ phase: 'generation',
+ level: 'error',
+ message: resolution_result.error.message,
+ class_name: c,
+ suggestion: resolution_result.error.suggestion,
+ locations: class_locations?.get(c) ?? null,
+ });
+ continue;
+ }
+ // Add warnings if any
+ if (resolution_result.warnings) {
+ for (const warning of resolution_result.warnings) {
+ diagnostics.push(create_generation_diagnostic(warning, class_locations?.get(c) ?? null));
+ }
+ }
+ if (resolution_result.declaration) {
+ css += `.${escape_css_selector(c)} { ${resolution_result.declaration} }\n`;
+ }
+ } else if ('ruleset' in v) {
+ // Check for empty ruleset
+ // eslint-disable-next-line @typescript-eslint/prefer-optional-chain
+ if (!v.ruleset || !v.ruleset.trim()) {
+ diagnostics.push({
+ phase: 'generation',
+ level: 'warning',
+ message: `Ruleset "${c}" is empty`,
+ class_name: c,
+ suggestion: `Add CSS rules or remove the empty ruleset definition`,
+ locations: class_locations?.get(c) ?? null,
+ });
+ continue;
+ }
+
+ css += v.ruleset.trim() + '\n';
+
+ // Validate ruleset and emit warnings
+ try {
+ const parsed = parse_ruleset(v.ruleset);
+ // Use CSS-escaped class name for matching (handles special chars like colons)
+ const escaped_class = escape_css_selector(c);
+
+ // Warn if no selector contains the expected class name
+ if (!ruleset_contains_class(parsed.rules, escaped_class)) {
+ diagnostics.push({
+ phase: 'generation',
+ level: 'warning',
+ message: `Ruleset "${c}" has no selectors containing ".${c}"`,
+ class_name: c,
+ suggestion: `Ensure at least one selector uses ".${c}" so the class works when applied`,
+ locations: class_locations?.get(c) ?? null,
+ });
+ }
+
+ // Warn if this ruleset could be converted to declaration format
+ // Skip for interpreter-generated rulesets (e.g., CSS literals) - they intentionally use rulesets
+ // Skip if ruleset has at-rules (e.g., @media) - these need the wrapper
+ // Strip comments before checking (/* ... */ can precede @media)
+ const ruleset_without_comments = v.ruleset.replace(/\/\*[\s\S]*?\*\//g, '').trim();
+ const has_at_rules = ruleset_without_comments.startsWith('@');
+ if (
+ !interpreter_matched &&
+ !has_at_rules &&
+ is_single_selector_ruleset(parsed.rules, escaped_class)
+ ) {
+ diagnostics.push({
+ phase: 'generation',
+ level: 'warning',
+ message: `Ruleset "${c}" has a single selector and could be converted to declaration format for modifier support`,
+ class_name: c,
+ suggestion: `Convert to: { declaration: '${parsed.rules[0]?.declarations.replace(/\s+/g, ' ').trim()}' }`,
+ locations: class_locations?.get(c) ?? null,
+ });
+ }
+ } catch (e) {
+ // Warn about parse errors so users can investigate
+ const error_message = e instanceof Error ? e.message : String(e);
+ diagnostics.push({
+ phase: 'generation',
+ level: 'warning',
+ message: `Failed to parse ruleset for "${c}": ${error_message}`,
+ class_name: c,
+ suggestion: 'Check for CSS syntax errors in the ruleset',
+ locations: class_locations?.get(c) ?? null,
+ });
+ }
+ }
+ // Note: Interpreted types are converted to declaration above, so no else clause needed
+ }
+
+ return {css, diagnostics};
+};
diff --git a/src/lib/css_class_generators.ts b/src/lib/css_class_generators.ts
index 7e0f4f862..7af06de25 100644
--- a/src/lib/css_class_generators.ts
+++ b/src/lib/css_class_generators.ts
@@ -1,14 +1,14 @@
-import type {CssClassDeclaration} from './css_class_helpers.js';
+import type {CssClassDefinition} from './css_class_generation.js';
-export type ClassTemplateResult = {
+export type GeneratedClassResult = {
name: string;
css: string;
} | null;
export type ClassTemplateFn =
- | ((v1: T1) => ClassTemplateResult)
- | ((v1: T1, v2: T2) => ClassTemplateResult)
- | ((v1: T1, v2: T2, v3: T3) => ClassTemplateResult);
+ | ((v1: T1) => GeneratedClassResult)
+ | ((v1: T1, v2: T2) => GeneratedClassResult)
+ | ((v1: T1, v2: T2, v3: T3) => GeneratedClassResult);
/**
* Generates CSS class declarations from templates.
@@ -39,8 +39,8 @@ export const generate_classes = (
values: Iterable,
secondary?: Iterable,
tertiary?: Iterable,
-): Record => {
- const result: Record = {};
+): Record => {
+ const result: Record = {};
for (const v1 of values) {
if (secondary) {
@@ -70,33 +70,11 @@ export const generate_classes = (
return result;
};
-// TODO refactor with `src/lib/variable_data.ts`, we may want `css_data.ts` or something
-export const CSS_GLOBALS = ['inherit', 'initial', 'revert', 'revert_layer', 'unset'] as const;
-export type CssGlobal = (typeof CSS_GLOBALS)[number];
-
export const CSS_DIRECTIONS = ['top', 'right', 'bottom', 'left'] as const;
export type CssDirection = (typeof CSS_DIRECTIONS)[number];
-// TODO add '0' and '10' ?
-export const COLOR_INTENSITIES = ['1', '2', '3', '4', '5', '6', '7', '8', '9'] as const;
-export type ColorIntensity = (typeof COLOR_INTENSITIES)[number];
-
-// Helper to convert snake_case to kebab-case for CSS property values
-export const to_kebab = (str: string): string => str.replace(/_/g, '-');
-
// Helper to convert any string to a valid CSS variable name (snake_case)
-export const to_variable_name = (str: string): string => str.replace(/[-\s]+/g, '_');
-
-// Helper to generate global value classes for any CSS property
-export const generate_global_classes = (property: string): Record => {
- return generate_classes(
- (global: (typeof CSS_GLOBALS)[number]) => ({
- name: `${to_variable_name(property)}_${global}`,
- css: `${property}: ${to_kebab(global)};`,
- }),
- CSS_GLOBALS,
- );
-};
+export const format_variable_name = (str: string): string => str.replace(/[-\s]+/g, '_');
/**
* Format spacing values for CSS (handles 0, auto, percentages, pixels, and CSS variables).
@@ -132,9 +110,8 @@ export const format_dimension_value = (value: string): string => {
/**
* Generate classes for a single CSS property with various values.
- * This is the most common pattern, used by display, visibility, float, etc.
*
- * @param property - The CSS property name (e.g. 'display', 'gap')
+ * @param property - The CSS property name (e.g. 'font-size', 'gap')
* @param values - The values to generate classes for
* @param formatter - Optional function to format values (e.g. v => `var(--space_${v})`)
* @param prefix - Optional class name prefix (defaults to property with dashes replaced by underscores)
@@ -143,11 +120,11 @@ export const generate_property_classes = (
property: string,
values: Iterable,
formatter?: (value: string) => string,
- prefix: string = to_variable_name(property),
-): Record => {
+ prefix: string = format_variable_name(property),
+): Record => {
return generate_classes(
(value: string) => ({
- name: `${prefix}_${to_variable_name(value)}`,
+ name: `${prefix}_${format_variable_name(value)}`,
css: `${property}: ${formatter?.(value) ?? value};`,
}),
values,
@@ -166,7 +143,7 @@ export const generate_directional_classes = (
property: string,
values: Iterable,
formatter?: (v: string) => string,
-): Record => {
+): Record => {
const prefix = property[0]; // 'm' for margin, 'p' for padding
return generate_classes(
@@ -175,20 +152,29 @@ export const generate_directional_classes = (
// Map variants to their configurations
const configs: Record = {
- '': {name: `${prefix}_${to_variable_name(value)}`, css: `${property}: ${formatted};`},
- t: {name: `${prefix}t_${to_variable_name(value)}`, css: `${property}-top: ${formatted};`},
- r: {name: `${prefix}r_${to_variable_name(value)}`, css: `${property}-right: ${formatted};`},
+ '': {name: `${prefix}_${format_variable_name(value)}`, css: `${property}: ${formatted};`},
+ t: {
+ name: `${prefix}t_${format_variable_name(value)}`,
+ css: `${property}-top: ${formatted};`,
+ },
+ r: {
+ name: `${prefix}r_${format_variable_name(value)}`,
+ css: `${property}-right: ${formatted};`,
+ },
b: {
- name: `${prefix}b_${to_variable_name(value)}`,
+ name: `${prefix}b_${format_variable_name(value)}`,
css: `${property}-bottom: ${formatted};`,
},
- l: {name: `${prefix}l_${to_variable_name(value)}`, css: `${property}-left: ${formatted};`},
+ l: {
+ name: `${prefix}l_${format_variable_name(value)}`,
+ css: `${property}-left: ${formatted};`,
+ },
x: {
- name: `${prefix}x_${to_variable_name(value)}`,
+ name: `${prefix}x_${format_variable_name(value)}`,
css: `${property}-left: ${formatted};\t${property}-right: ${formatted};`,
},
y: {
- name: `${prefix}y_${to_variable_name(value)}`,
+ name: `${prefix}y_${format_variable_name(value)}`,
css: `${property}-top: ${formatted};\t${property}-bottom: ${formatted};`,
},
};
@@ -200,30 +186,6 @@ export const generate_directional_classes = (
);
};
-/**
- * Generate classes for properties with axis variants (e.g. overflow, overflow-x, overflow-y).
- *
- * @param property - The base CSS property name (e.g. 'overflow')
- * @param values - The values to generate classes for
- */
-export const generate_property_with_axes = (
- property: string,
- values: Iterable,
-): Record => {
- return generate_classes(
- (axis: string, value: string) => {
- const prop = axis === '' ? property : `${property}-${axis}`;
- const prefix = axis === '' ? property : `${property}_${axis}`;
- return {
- name: `${to_variable_name(prefix)}_${to_variable_name(value)}`,
- css: `${prop}: ${value};`,
- };
- },
- ['', 'x', 'y'],
- values,
- );
-};
-
/**
* Generate border radius corner classes for all four corners.
* Creates classes for top-left, top-right, bottom-left, bottom-right corners.
@@ -234,7 +196,7 @@ export const generate_property_with_axes = (
export const generate_border_radius_corners = (
values: Iterable,
formatter?: (value: string) => string,
-): Record => {
+): Record => {
const corners = [
{prop: 'border-top-left-radius', name: 'border_top_left_radius'},
{prop: 'border-top-right-radius', name: 'border_top_right_radius'},
@@ -244,7 +206,7 @@ export const generate_border_radius_corners = (
return generate_classes(
(corner: (typeof corners)[0], value: string) => ({
- name: `${corner.name}_${to_variable_name(value)}`,
+ name: `${corner.name}_${format_variable_name(value)}`,
css: `${corner.prop}: ${formatter?.(value) ?? value};`,
}),
corners,
@@ -263,7 +225,7 @@ export const generate_border_radius_corners = (
export const generate_shadow_classes = (
sizes: Iterable,
alpha_mapping: Record,
-): Record => {
+): Record => {
const shadow_types = [
{prefix: 'shadow', var_prefix: 'shadow'},
{prefix: 'shadow_top', var_prefix: 'shadow_top'},
diff --git a/src/lib/css_class_helpers.ts b/src/lib/css_class_helpers.ts
deleted file mode 100644
index 3051d076a..000000000
--- a/src/lib/css_class_helpers.ts
+++ /dev/null
@@ -1,225 +0,0 @@
-import type {Logger} from '@fuzdev/fuz_util/log.js';
-
-// TODO maybe just use the Svelte (and Oxc?) parser instead of this regexp madness?
-
-export interface CssExtractor {
- matcher: RegExp;
- mapper: (matched: RegExpExecArray) => Array;
-}
-
-const CSS_CLASS_EXTRACTORS: Array = [
- // `class:a`
- {
- matcher: /(?]+)/gi,
- mapper: (matched) => [matched[1]!],
- }, // initial capture group is fake just because the second regexp uses a capture group for its back reference
-
- // `class="a"`, `classes="a"`, `classes = 'a b'`, `classes: 'a b'` with any whitespace around the `=`/`:`
- {
- matcher: /(?
- matched[2]!
- .replace(
- // omit all expressions with best-effort - it's not perfect especially
- // around double quote strings in JS in Svelte expressions, but using single quotes is better imo
- /\S*{(?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})*}\S*/g,
- // same failures:
- // /\S*{(?:[^{}]*|'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.|\$\{(?:[^{}]*|{[^{}]*})*\})*`|{(?:[^{}]*|'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.|\$\{(?:[^{}]*|{[^{}]*})*\})*`)*})*}\S*/g,
- // 3 failures:
- // /\S*{(?:[^{}`'"]*|[`'"]((?:[^\\`'"]|\\.|\$\{[^}]*\})*)[`'"]|{[^{}]*})*}\S*/g,
- '',
- )
- .split(/\s+/)
- .filter(Boolean),
- },
- // arrays like `class: ['a', 'b']`, `classes = ['a', 'b']`, `classes={['a', 'b']`
- {
- matcher: /(? => {
- const content = matched[1]!;
- if (content.includes('[')) return []; // TODO @many ideally fix instead of bailing, but maybe we need a real JS parser?
- const items = content.split(',').map((item) => item.trim());
-
- return items
- .reduce((classes: Array, item: string) => {
- // Match string literals within each item
- const string_literals = item.match(/(['"`])((?:(?!\1)[^\\]|\\.)*?)\1/g);
- if (!string_literals) return classes;
-
- // Check if the item contains concatenation
- const has_concatenation = /\s*\+\s*/.test(item);
-
- if (!has_concatenation && string_literals.length === 1) {
- const content = string_literals[0].slice(1, -1); // remove quotes
- if (!content.includes('${')) {
- classes.push(content.replace(/\\(['"`])/g, '$1').trim());
- }
- }
-
- return classes;
- }, [])
- .filter(Boolean); // Filter out empty strings
- },
- },
-];
-
-/**
- * Returns a Set of CSS classes from a string of HTML/Svelte/JS/TS content.
- * Handles class attributes, directives, and various forms of CSS class declarations.
- */
-export const collect_css_classes = (
- contents: string,
- extractors: Array = CSS_CLASS_EXTRACTORS,
-): Set => {
- const all_classes: Set = new Set();
-
- for (const extractor of extractors) {
- for (const c of extract_classes(contents, extractor)) {
- all_classes.add(c);
- }
- }
-
- return all_classes;
-};
-
-const extract_classes = (contents: string, {matcher, mapper}: CssExtractor): Set => {
- const classes: Set = new Set();
- let matched: RegExpExecArray | null;
- while ((matched = matcher.exec(contents)) !== null) {
- for (const c of mapper(matched)) {
- classes.add(c);
- }
- }
- return classes;
-};
-
-export class CssClasses {
- include_classes: Set | null;
-
- #all: Set = new Set();
-
- #by_id: Map> = new Map();
-
- #dirty = true;
-
- constructor(include_classes: Set | null = null) {
- this.include_classes = include_classes;
- }
-
- add(id: string, classes: Set): void {
- this.#dirty = true;
- this.#by_id.set(id, classes);
- }
-
- delete(id: string): void {
- this.#dirty = true;
- this.#by_id.delete(id);
- }
-
- get(): Set {
- if (this.#dirty) {
- this.#dirty = false;
- this.#recalculate();
- }
- return this.#all;
- }
-
- #recalculate(): void {
- this.#all.clear();
- if (this.include_classes) {
- for (const c of this.include_classes) {
- this.#all.add(c);
- }
- }
- for (const classes of this.#by_id.values()) {
- for (const c of classes) {
- this.#all.add(c);
- }
- }
- }
-}
-
-export type CssClassDeclaration =
- | CssClassDeclarationItem
- | CssClassDeclarationGroup
- | CssClassDeclarationInterpreter;
-
-export interface CssClassDeclarationBase {
- comment?: string;
-}
-
-export interface CssClassDeclarationItem extends CssClassDeclarationBase {
- declaration: string;
-}
-export interface CssClassDeclarationGroup extends CssClassDeclarationBase {
- ruleset: string;
-}
-export interface CssClassDeclarationInterpreter extends CssClassDeclarationBase {
- pattern: RegExp;
- interpret: (matched: RegExpMatchArray, log?: Logger) => string | null;
-}
-
-export const generate_classes_css = (
- classes: Iterable,
- classes_by_name: Record,
- interpreters: Array,
- log?: Logger,
-): string => {
- // TODO when the API is redesigned this kind of thing should be cached
- // Create a map that has the index of each class name as the key
- const indexes: Map = new Map();
- const keys = Object.keys(classes_by_name);
- for (let i = 0; i < keys.length; i++) {
- indexes.set(keys[i]!, i);
- }
-
- // If any classes are unknown, just put them at the end
- const sorted_classes = (Array.isArray(classes) ? classes : Array.from(classes)).sort((a, b) => {
- const index_a = indexes.get(a) ?? Number.MAX_VALUE;
- const index_b = indexes.get(b) ?? Number.MAX_VALUE;
- if (index_a !== index_b) return index_a - index_b;
- return a.localeCompare(b); // alphabetic tiebreaker for stable sort
- });
-
- let css = '';
- for (const c of sorted_classes) {
- let v = classes_by_name[c];
-
- // If not found statically, try interpreters
- if (!v) {
- for (const interpreter of interpreters) {
- const matched = c.match(interpreter.pattern);
- if (matched) {
- const declaration = interpreter.interpret(matched, log);
- if (declaration) {
- v = {declaration, comment: interpreter.comment};
- break;
- }
- }
- }
- }
-
- if (!v) {
- // diagnostic
- // if (!/^[a-z_0-9]+$/.test(c)) {
- // console.error('invalid class detected, fix the regexps', c);
- // }
- continue;
- }
-
- const {comment} = v;
-
- if (comment) {
- css += comment.includes('\n') ? `/*\n${comment}\n*/\n` : `/** ${comment} */\n`;
- }
-
- if ('declaration' in v) {
- css += `.${c} { ${v.declaration} }\n`;
- } else if ('ruleset' in v) {
- css += v.ruleset + '\n';
- }
- // Note: Interpreted types are converted to declaration above, so no else clause needed
- }
-
- return css;
-};
diff --git a/src/lib/css_class_interpreters.ts b/src/lib/css_class_interpreters.ts
index 9139bb22f..3cd5b09a3 100644
--- a/src/lib/css_class_interpreters.ts
+++ b/src/lib/css_class_interpreters.ts
@@ -1,99 +1,191 @@
-import type {CssClassDeclarationInterpreter} from './css_class_helpers.js';
-import {Z_INDEX_MAX} from './variable_data.js';
+import {escape_css_selector, type CssClassDefinitionInterpreter} from './css_class_generation.js';
+import {
+ is_possible_css_literal,
+ interpret_css_literal,
+ generate_css_literal_simple,
+ extract_segments,
+ extract_and_validate_modifiers,
+ type CssLiteralOutput,
+} from './css_literal.js';
+import {generate_modified_ruleset} from './css_ruleset_parser.js';
+import {resolve_class_definition} from './css_class_resolution.js';
/**
- * Interpreter for opacity classes (opacity_0 through opacity_100).
+ * Interpreter for modified token/composite classes (e.g., `hover:p_md`, `md:box`, `dark:hover:panel`).
+ * Applies modifiers to existing declaration-based or ruleset-based classes.
+ *
+ * This interpreter must run BEFORE css_literal_interpreter to handle cases like `hover:box`
+ * where `box` is a known class (not a CSS property).
*/
-export const opacity_interpreter: CssClassDeclarationInterpreter = {
- pattern: /^opacity_(\d+)$/,
- interpret: (matched, log) => {
- const value = parseInt(matched[1]!, 10);
- if (value < 0 || value > 100) {
- log?.warn(`Invalid opacity value: ${value}. Must be between 0 and 100.`);
+export const modified_class_interpreter: CssClassDefinitionInterpreter = {
+ pattern: /^.+:.+$/,
+ interpret: (matched, ctx) => {
+ const class_name = matched[0];
+ const segments = extract_segments(class_name);
+
+ // Extract modifiers from the front
+ const result = extract_and_validate_modifiers(segments, class_name);
+
+ if (!result.ok) {
+ // Modifier validation error - let css_literal_interpreter try
return null;
}
- return `opacity: ${value === 0 ? '0' : value === 100 ? '1' : `${value}%`};`;
- },
- // comment: 'Interpreted opacity value',
-};
-/**
- * Interpreter for font-weight classes,
- * `font_weight_1` through `font_weight_1000` following the CSS spec.
- */
-export const font_weight_interpreter: CssClassDeclarationInterpreter = {
- pattern: /^font_weight_(\d+)$/,
- interpret: (matched, log) => {
- const value = parseInt(matched[1]!, 10);
- if (value < 1 || value > 1000) {
- log?.warn(`Invalid font-weight value: ${value}. Must be between 1 and 1000.`);
+ const {modifiers, remaining} = result;
+
+ // Must have exactly one remaining segment (the base class name)
+ if (remaining.length !== 1) {
return null;
}
- return `font-weight: ${value}; --font_weight: ${value};`;
- },
-};
-/**
- * Interpreter for border-radius percentage classes,
- * `border_radius_0` through `border_radius_100`.
- */
-export const border_radius_interpreter: CssClassDeclarationInterpreter = {
- pattern: /^border_radius_(\d+)$/,
- interpret: (matched, log) => {
- const value = parseInt(matched[1]!, 10);
- if (value < 0 || value > 100) {
- log?.warn(`Invalid border-radius percentage: ${value}. Must be between 0 and 100.`);
+ const base_class_name = remaining[0]!;
+
+ // Check if the base class is known
+ const base_class = ctx.class_definitions[base_class_name];
+ if (!base_class) {
return null;
}
- return `border-radius: ${value === 0 ? '0' : `${value}%`};`;
- },
-};
-/**
- * Interpreter for border radius corner percentage classes,
- * handles all four corners: top-left, top-right, bottom-left, bottom-right.
- * Examples: `border_top_left_radius_50`, `border_bottom_right_radius_100`.
- */
-export const border_radius_corners_interpreter: CssClassDeclarationInterpreter = {
- pattern: /^border_(top|bottom)_(left|right)_radius_(\d+)$/,
- interpret: (matched, log) => {
- const vertical = matched[1]!;
- const horizontal = matched[2]!;
- const value = parseInt(matched[3]!, 10);
- if (value < 0 || value > 100) {
- log?.warn(
- `Invalid border-${vertical}-${horizontal}-radius percentage: ${value}. Must be between 0 and 100.`,
- );
+ // Must have at least one modifier (otherwise it's just the base class)
+ const has_modifiers =
+ modifiers.media ||
+ modifiers.ancestor ||
+ modifiers.states.length > 0 ||
+ modifiers.pseudo_element;
+ if (!has_modifiers) {
return null;
}
- return `border-${vertical}-${horizontal}-radius: ${value === 0 ? '0' : `${value}%`};`;
+
+ const escaped_class_name = escape_css_selector(class_name);
+
+ // Build state and pseudo-element CSS suffixes
+ let state_css = '';
+ for (const state of modifiers.states) {
+ state_css += state.css;
+ }
+ const pseudo_element_css = modifiers.pseudo_element?.css ?? '';
+
+ // Handle composes-based or declaration-based definitions
+ if ('composes' in base_class || 'declaration' in base_class) {
+ const resolution_result = resolve_class_definition(
+ base_class,
+ base_class_name,
+ ctx.class_definitions,
+ ctx.css_properties,
+ );
+ if (!resolution_result.ok) {
+ ctx.diagnostics.push(resolution_result.error);
+ return null;
+ }
+ // Add warnings if any
+ if (resolution_result.warnings) {
+ for (const warning of resolution_result.warnings) {
+ ctx.diagnostics.push(warning);
+ }
+ }
+ if (!resolution_result.declaration) {
+ return null;
+ }
+
+ // Build the selector
+ let selector = `.${escaped_class_name}`;
+ selector += state_css;
+ selector += pseudo_element_css;
+
+ // Create output compatible with generate_css_literal_simple
+ const output: CssLiteralOutput = {
+ declaration: resolution_result.declaration,
+ selector,
+ media_wrapper: modifiers.media?.css ?? null,
+ ancestor_wrapper: modifiers.ancestor?.css ?? null,
+ };
+
+ const css = generate_css_literal_simple(output);
+ return css.trimEnd();
+ }
+
+ // Handle ruleset-based classes
+ if ('ruleset' in base_class && base_class.ruleset) {
+ const result = generate_modified_ruleset(
+ base_class.ruleset,
+ base_class_name,
+ escaped_class_name,
+ state_css,
+ pseudo_element_css,
+ modifiers.media?.css ?? null,
+ modifiers.ancestor?.css ?? null,
+ );
+
+ // Emit warnings for skipped modifiers
+ if (result.skipped_modifiers) {
+ for (const skipped of result.skipped_modifiers) {
+ if (skipped.reason === 'pseudo_element_conflict') {
+ ctx.diagnostics.push({
+ level: 'warning',
+ class_name,
+ message: `Rule "${skipped.selector}" already contains a pseudo-element; "${skipped.conflicting_modifier}" modifier was not applied`,
+ suggestion: `The rule is included with just the class renamed`,
+ });
+ } else {
+ ctx.diagnostics.push({
+ level: 'warning',
+ class_name,
+ message: `Rule "${skipped.selector}" already contains "${skipped.conflicting_modifier}"; modifier was not applied to avoid redundancy`,
+ suggestion: `The rule is included with just the class renamed`,
+ });
+ }
+ }
+ }
+
+ return result.css.trimEnd();
+ }
+
+ return null;
},
};
/**
- * Interpreter for z-index classes,
- * `z_index_0` through `z_index_${Z_INDEX_MAX}` (max CSS z-index).
+ * Interpreter for CSS-literal classes (e.g., `display:flex`, `hover:opacity:80%`).
+ * Generates full CSS rulesets including any modifier wrappers.
*/
-export const z_index_interpreter: CssClassDeclarationInterpreter = {
- pattern: /^z_index_(\d+)$/,
- interpret: (matched, log) => {
- const value = parseInt(matched[1]!, 10);
- if (value < 0 || value > Z_INDEX_MAX) {
- log?.warn(`Invalid z-index value: ${value}. Must be between 0 and ${Z_INDEX_MAX}.`);
+export const css_literal_interpreter: CssClassDefinitionInterpreter = {
+ pattern: /^.+:.+$/,
+ interpret: (matched, ctx) => {
+ const class_name = matched[0];
+
+ if (!is_possible_css_literal(class_name)) {
+ return null;
+ }
+
+ const escaped_class_name = escape_css_selector(class_name);
+ const result = interpret_css_literal(class_name, escaped_class_name, ctx.css_properties);
+
+ if (!result.ok) {
+ ctx.diagnostics.push(result.error);
return null;
}
- return `z-index: ${value};`;
+
+ // Collect warnings for upstream handling
+ if (result.warnings) {
+ for (const warning of result.warnings) {
+ ctx.diagnostics.push(warning);
+ }
+ }
+
+ // Generate the full CSS including any wrappers
+ const css = generate_css_literal_simple(result.output);
+
+ // Return the CSS but strip trailing newline for consistency
+ return css.trimEnd();
},
};
/**
* Collection of all builtin interpreters for dynamic CSS class generation.
+ * Order matters: modified_class_interpreter runs first to handle `hover:box` before
+ * css_literal_interpreter tries to interpret it as `hover:box` (property:value).
*/
-export const css_class_interpreters: Array = [
- opacity_interpreter,
- font_weight_interpreter,
- border_radius_interpreter,
- border_radius_corners_interpreter,
- z_index_interpreter,
- // add new default interpreters here
+export const css_class_interpreters: Array = [
+ modified_class_interpreter,
+ css_literal_interpreter,
];
diff --git a/src/lib/css_class_resolution.ts b/src/lib/css_class_resolution.ts
new file mode 100644
index 000000000..d5036601a
--- /dev/null
+++ b/src/lib/css_class_resolution.ts
@@ -0,0 +1,333 @@
+/**
+ * CSS class resolution utilities for composing class definitions.
+ *
+ * Provides the `resolve_composes` helper to recursively resolve class names
+ * to their combined CSS declarations.
+ *
+ * @module
+ */
+
+import {levenshtein_distance} from '@fuzdev/fuz_util/string.js';
+
+import type {InterpreterDiagnostic} from './diagnostics.js';
+import type {CssClassDefinition, CssClassDefinitionStatic} from './css_class_generation.js';
+import {
+ try_resolve_literal,
+ extract_segments,
+ extract_and_validate_modifiers,
+ has_extracted_modifiers,
+ suggest_modifier,
+} from './css_literal.js';
+
+/**
+ * Result from resolving a `composes` array to combined declarations.
+ */
+export type ResolveComposesResult =
+ | {ok: true; declaration: string; warnings: Array | null}
+ | {ok: false; error: InterpreterDiagnostic};
+
+/**
+ * Resolves a class definition's declaration, handling `composes` composition.
+ *
+ * If the definition has a `composes` property, resolves those classes recursively
+ * and combines with any explicit `declaration`. If no `composes`, returns the
+ * explicit `declaration` directly.
+ *
+ * @param def - The class definition to resolve
+ * @param class_name - The name of the class being resolved (for error messages)
+ * @param definitions - Record of all known class definitions
+ * @param css_properties - Set of valid CSS properties for literal validation, or null to skip
+ * @returns Combined declaration or an error
+ */
+export const resolve_class_definition = (
+ def: CssClassDefinitionStatic,
+ class_name: string,
+ definitions: Record,
+ css_properties: Set | null = null,
+): ResolveComposesResult => {
+ let warnings: Array | null = null;
+
+ // Handle composes-based definitions
+ if ('composes' in def && def.composes) {
+ const result = resolve_composes(
+ def.composes,
+ definitions,
+ new Set([class_name]),
+ new Set(),
+ class_name,
+ css_properties,
+ );
+ if (!result.ok) return result;
+
+ // Combine resolved declarations with explicit declaration (if present)
+ let combined = result.declaration;
+ if ('declaration' in def) {
+ const trimmed = def.declaration?.trim();
+ if (trimmed) {
+ combined = combined ? `${combined} ${trimmed}` : trimmed;
+ } else if (def.declaration !== undefined) {
+ // Warn about empty declaration
+ warnings = result.warnings ? [...result.warnings] : [];
+ warnings.push({
+ level: 'warning',
+ class_name,
+ message: `Class "${class_name}" has an empty declaration`,
+ suggestion: 'Remove the empty declaration property or add CSS',
+ });
+ }
+ }
+ if (!warnings && result.warnings) {
+ warnings = result.warnings;
+ }
+ return {ok: true, declaration: combined, warnings};
+ }
+
+ // Handle declaration-only definitions
+ if ('declaration' in def) {
+ const trimmed = def.declaration?.trim();
+ if (!trimmed && def.declaration !== undefined) {
+ warnings = [
+ {
+ level: 'warning',
+ class_name,
+ message: `Class "${class_name}" has an empty declaration`,
+ suggestion: 'Remove the empty declaration property or add CSS',
+ },
+ ];
+ }
+ return {ok: true, declaration: trimmed ?? '', warnings};
+ }
+
+ // Ruleset definitions don't have a single declaration
+ return {ok: true, declaration: '', warnings: null};
+};
+
+/**
+ * Resolves an array of class names to their combined CSS declarations.
+ *
+ * Recursively resolves nested `composes` arrays and combines all declarations.
+ * Validates that referenced classes exist and are resolvable (not rulesets or interpreters).
+ * Supports unmodified CSS literals (e.g., `text-align:center`) in the composes array.
+ *
+ * Deduplication behavior:
+ * - Diamond dependencies (class reached via different composition branches) are silently skipped
+ * - Redundant listings (class already included by an earlier sibling in this array) emit a warning
+ *
+ * @param class_names - Array of class names to resolve
+ * @param definitions - Record of all known class definitions
+ * @param resolution_stack - Set of class names currently being resolved (for cycle detection)
+ * @param visited - Set of all class names already resolved (for deduplication)
+ * @param original_class_name - The class name being defined (for error messages)
+ * @param css_properties - Set of valid CSS properties for literal validation, or null to skip
+ * @returns Combined declarations or an error
+ * @mutates resolution_stack - Temporarily adds/removes names during recursion
+ * @mutates visited - Adds resolved class names for deduplication
+ */
+export const resolve_composes = (
+ class_names: Array,
+ definitions: Record,
+ resolution_stack: Set,
+ visited: Set,
+ original_class_name: string,
+ css_properties: Set | null = null,
+): ResolveComposesResult => {
+ const declarations: Array = [];
+ let warnings: Array | null = null;
+
+ // Snapshot of visited at start - classes added during this array's processing are redundant
+ const visited_at_start: ReadonlySet = new Set(visited);
+
+ for (const name of class_names) {
+ // Cycle detection (current path)
+ if (resolution_stack.has(name)) {
+ const cycle_path = [...resolution_stack, name].join(' â ');
+ return {
+ ok: false,
+ error: {
+ level: 'error',
+ class_name: original_class_name,
+ message: `Circular reference detected: ${cycle_path}`,
+ suggestion: 'Remove the circular dependency from composes arrays',
+ },
+ };
+ }
+
+ // Deduplication - skip if already resolved
+ if (visited.has(name)) {
+ // Warn if redundant (added during this array's processing), not diamond (was already there)
+ if (!visited_at_start.has(name)) {
+ warnings ??= [];
+ warnings.push({
+ level: 'warning',
+ class_name: original_class_name,
+ message: `Class "${name}" is redundant`,
+ suggestion: 'Already included by another class in this definition',
+ });
+ }
+ continue;
+ }
+
+ const def = definitions[name];
+ if (!def) {
+ // Check if this looks like a modified class (hover:box, md:p_lg)
+ const segments = extract_segments(name);
+ if (segments.length >= 2) {
+ const mod_result = extract_and_validate_modifiers(segments, name);
+ if (
+ mod_result.ok &&
+ mod_result.remaining.length === 1 &&
+ has_extracted_modifiers(mod_result.modifiers)
+ ) {
+ const base_name = mod_result.remaining[0]!;
+ const base_def = definitions[base_name];
+ if (base_def) {
+ // Modified existing class - can't compose
+ return {
+ ok: false,
+ error: {
+ level: 'error',
+ class_name: original_class_name,
+ message: `Modified class "${name}" cannot be used in composes array`,
+ suggestion: 'Apply modified classes directly in markup, not in composes arrays',
+ },
+ };
+ } else {
+ // Looks like modifier:unknown - report the unknown base
+ return {
+ ok: false,
+ error: {
+ level: 'error',
+ class_name: original_class_name,
+ message: `Unknown class "${base_name}" in composes array`,
+ suggestion: `Check that "${base_name}" is defined in class_definitions`,
+ },
+ };
+ }
+ }
+ }
+
+ // Try parsing as CSS literal
+ const literal_result = try_resolve_literal(name, css_properties, original_class_name);
+ if (literal_result.ok) {
+ visited.add(name);
+ declarations.push(literal_result.declaration);
+ if (literal_result.warnings) {
+ warnings ??= [];
+ warnings.push(...literal_result.warnings);
+ }
+ continue;
+ }
+ // If literal parsing returned an error, check for modifier typo pattern
+ if (literal_result.error) {
+ // Check for typo'd modifier where last segment is a known class
+ // Handles: hovr:box, md:hovr:box, etc.
+ const prop_match = /Unknown CSS property "([^"]+)"/.exec(literal_result.error.message);
+ if (prop_match) {
+ const failed_prop = prop_match[1]!;
+ const suggested = suggest_modifier(failed_prop);
+ // Only suggest if it's a close typo (edit distance 1)
+ if (suggested && levenshtein_distance(failed_prop, suggested) === 1) {
+ // Check if the last segment is a known class
+ const last_colon = name.lastIndexOf(':');
+ const potential_class = name.slice(last_colon + 1);
+ if (definitions[potential_class]) {
+ // Reconstruct the corrected class name
+ const corrected =
+ name.slice(0, name.lastIndexOf(failed_prop)) + suggested + ':' + potential_class;
+ return {
+ ok: false,
+ error: {
+ level: 'error',
+ class_name: original_class_name,
+ message: `Unknown modifier "${failed_prop}" with class "${potential_class}"`,
+ suggestion: `Did you mean "${corrected}"? Note: modified classes cannot be used in composes`,
+ },
+ };
+ }
+ }
+ }
+ return {ok: false, error: literal_result.error};
+ }
+ // Not a literal - fall through to original "unknown class" error
+ return {
+ ok: false,
+ error: {
+ level: 'error',
+ class_name: original_class_name,
+ message: `Unknown class "${name}" in composes array`,
+ suggestion: `Check that "${name}" is defined in class_definitions`,
+ },
+ };
+ }
+
+ // Only static definitions allowed (not interpreters)
+ if ('pattern' in def) {
+ return {
+ ok: false,
+ error: {
+ level: 'error',
+ class_name: original_class_name,
+ message: `Cannot reference interpreter pattern "${name}" in composes array`,
+ suggestion: 'Only static class definitions can be referenced',
+ },
+ };
+ }
+
+ // Ruleset not allowed in composes
+ if ('ruleset' in def && def.ruleset) {
+ return {
+ ok: false,
+ error: {
+ level: 'error',
+ class_name: original_class_name,
+ message: `Cannot reference ruleset class "${name}" in composes array`,
+ suggestion: 'Ruleset classes have multiple selectors and cannot be inlined',
+ },
+ };
+ }
+
+ // Mark as visited before processing (for deduplication)
+ visited.add(name);
+
+ // Recursive resolution for nested composes
+ if ('composes' in def && def.composes) {
+ resolution_stack.add(name);
+ const nested = resolve_composes(
+ def.composes,
+ definitions,
+ resolution_stack,
+ visited,
+ original_class_name,
+ css_properties,
+ );
+ resolution_stack.delete(name);
+ if (!nested.ok) return nested; // Propagate error
+ if (nested.declaration) {
+ declarations.push(nested.declaration);
+ }
+ if (nested.warnings) {
+ warnings ??= [];
+ warnings.push(...nested.warnings);
+ }
+ }
+
+ // Add the declaration if present (trimmed)
+ if ('declaration' in def) {
+ const trimmed = def.declaration?.trim();
+ if (trimmed) {
+ declarations.push(trimmed);
+ } else if (def.declaration !== undefined) {
+ // Warn about empty declaration
+ warnings ??= [];
+ warnings.push({
+ level: 'warning',
+ class_name: name,
+ message: `Class "${name}" has an empty declaration`,
+ suggestion: 'Remove the empty declaration property or add CSS',
+ });
+ }
+ }
+ }
+
+ return {ok: true, declaration: declarations.join(' '), warnings};
+};
diff --git a/src/lib/css_classes.ts b/src/lib/css_classes.ts
index c9c7cae27..5afc78abc 100644
--- a/src/lib/css_classes.ts
+++ b/src/lib/css_classes.ts
@@ -1,407 +1,200 @@
-import type {CssClassDeclaration} from './css_class_helpers.js';
-import {
- generate_classes,
- to_kebab,
- CSS_GLOBALS,
- COLOR_INTENSITIES,
- generate_property_classes,
- generate_directional_classes,
- generate_property_with_axes,
- generate_border_radius_corners,
- generate_shadow_classes,
- format_spacing_value,
- format_dimension_value,
-} from './css_class_generators.js';
-import {
- space_variants,
- color_variants,
- text_color_variants,
- font_size_variants,
- icon_size_variants,
- line_height_variants,
- border_radius_variants,
- border_width_variants,
- alignment_values,
- justify_values,
- overflow_values,
- border_style_values,
- display_values,
- text_align_values,
- vertical_align_values,
- word_break_values,
- position_values,
- visibility_values,
- float_values,
- flex_wrap_values,
- flex_direction_values,
- overflow_wrap_values,
- scrollbar_width_values,
- scrollbar_gutter_values,
- shadow_semantic_values,
- shadow_alpha_variants,
-} from './variable_data.js';
-import {css_class_composites} from './css_class_composites.js';
-
-// TODO add animation support, either as a separate thing or rename `css_classes_by_name` to be more generic, like `css_by_name` - need to collect `animation: foo ...` names like we do classes
-
-// TODO think about variable support (much harder problem, need dependency graph)
-
-// TODO modifiers for :hover/:active/:focus (how? do we need to give up the compat with JS identifier names?)
-
/**
- * @see `generate_classes_css`
+ * Collection management for extracted CSS classes.
+ *
+ * Tracks classes per-file for efficient incremental updates during watch mode.
+ * Uses `null` instead of empty collections to avoid allocation overhead.
+ *
+ * @module
*/
-export const css_classes_by_name: Record = {
- // Composite classes go first, so they can be overridden by the more specific classes.
- ...css_class_composites,
-
- ...generate_property_classes('position', position_values),
- ...generate_property_classes('position', CSS_GLOBALS, to_kebab),
-
- ...generate_property_classes('display', display_values),
- ...generate_property_classes('display', CSS_GLOBALS, to_kebab),
-
- ...generate_property_classes('visibility', visibility_values),
- ...generate_property_classes('visibility', CSS_GLOBALS, to_kebab),
-
- ...generate_property_classes('float', float_values),
- ...generate_property_classes('float', CSS_GLOBALS, to_kebab),
-
- ...generate_property_with_axes('overflow', overflow_values),
-
- ...generate_property_classes('overflow-wrap', overflow_wrap_values),
- ...generate_property_classes('overflow-wrap', CSS_GLOBALS, to_kebab, 'overflow_wrap'),
-
- ...generate_property_classes('scrollbar-width', scrollbar_width_values),
- ...generate_property_classes('scrollbar-width', CSS_GLOBALS, to_kebab, 'scrollbar_width'),
-
- ...generate_property_classes('scrollbar-gutter', scrollbar_gutter_values),
- ...generate_property_classes('scrollbar-gutter', CSS_GLOBALS, to_kebab, 'scrollbar_gutter'),
-
- flex_1: {declaration: 'flex: 1;'},
- ...generate_property_classes('flex-wrap', flex_wrap_values),
- ...generate_property_classes('flex-wrap', CSS_GLOBALS, to_kebab),
- ...generate_property_classes('flex-direction', flex_direction_values),
- ...generate_property_classes('flex-direction', CSS_GLOBALS, to_kebab),
- flex_grow_1: {declaration: 'flex-grow: 1;'},
- flex_grow_0: {declaration: 'flex-grow: 0;'},
- flex_shrink_1: {declaration: 'flex-shrink: 1;'},
- flex_shrink_0: {declaration: 'flex-shrink: 0;'},
-
- ...generate_property_classes('align-items', alignment_values),
- ...generate_property_classes('align-content', [
- ...alignment_values,
- 'space-between',
- 'space-around',
- 'space-evenly',
- ]),
- ...generate_property_classes('align-self', alignment_values),
- ...generate_property_classes('justify-content', justify_values),
- ...generate_property_classes('justify-items', [
- 'center',
- 'start',
- 'end',
- 'left',
- 'right',
- 'baseline',
- 'stretch',
- ]),
- ...generate_property_classes('justify-self', [
- 'center',
- 'start',
- 'end',
- 'left',
- 'right',
- 'baseline',
- 'stretch',
- ]),
- flip_x: {declaration: 'transform: scaleX(-1);'},
- flip_y: {declaration: 'transform: scaleY(-1);'},
- flip_xy: {declaration: 'transform: scaleX(-1) scaleY(-1);'},
-
- font_family_sans: {declaration: 'font-family: var(--font_family_sans);'},
- font_family_serif: {declaration: 'font-family: var(--font_family_serif);'},
- font_family_mono: {declaration: 'font-family: var(--font_family_mono);'},
-
- ...generate_property_classes('line-height', ['0', '1', ...line_height_variants], (v) =>
- v === '0' || v === '1' ? v : `var(--line_height_${v})`,
- ),
- ...generate_property_classes(
- 'font-size',
- font_size_variants,
- (v) => `var(--font_size_${v}); --font_size: var(--font_size_${v})`,
- ),
- ...generate_property_classes(
- 'font-size',
- icon_size_variants,
- (v) => `var(--icon_size_${v}); --font_size: var(--icon_size_${v})`,
- 'icon_size',
- ),
- // TODO some of these need to be filled out and include CSS_GLOBALS (but maybe the API should be opt-out?)
- ...generate_property_classes('text-align', text_align_values),
- ...generate_property_classes('vertical-align', vertical_align_values),
- ...generate_property_classes('word-break', word_break_values),
- ...generate_property_classes('word-break', CSS_GLOBALS, to_kebab),
- ...generate_property_classes('white-space', [
- 'normal',
- 'nowrap',
- 'pre',
- 'pre-wrap',
- 'pre-line',
- 'break-spaces',
- ]),
- ...generate_property_classes('white-space-collapse', [
- 'collapse',
- 'preserve',
- 'preserve-breaks',
- 'preserve-spaces',
- 'break-spaces',
- ]),
- ...generate_property_classes('white-space-collapse', CSS_GLOBALS, to_kebab),
- ...generate_property_classes('text-wrap', ['wrap', 'nowrap', 'balance', 'pretty', 'stable']),
- ...generate_property_classes('user-select', ['none', 'auto', 'text', 'all']),
- ...generate_property_classes('user-select', CSS_GLOBALS, to_kebab),
-
- /*
- colors
+import type {SourceLocation, ExtractionDiagnostic} from './diagnostics.js';
- */
- ...generate_property_classes(
- 'color',
- text_color_variants.map(String),
- (v) => `var(--text_color_${v})`,
- 'text_color',
- ),
- ...generate_property_classes(
- 'background-color',
- [1, 2, 3, 4, 5, 6, 7, 8, 9].map(String),
- (v) => `var(--darken_${v})`,
- 'darken',
- ),
- ...generate_property_classes(
- 'background-color',
- [1, 2, 3, 4, 5, 6, 7, 8, 9].map(String),
- (v) => `var(--lighten_${v})`,
- 'lighten',
- ),
- bg: {declaration: 'background-color: var(--bg);'},
- fg: {declaration: 'background-color: var(--fg);'},
- ...generate_property_classes(
- 'background-color',
- [1, 2, 3, 4, 5, 6, 7, 8, 9].map(String),
- (v) => `var(--bg_${v})`,
- 'bg',
- ),
- ...generate_property_classes(
- 'background-color',
- [1, 2, 3, 4, 5, 6, 7, 8, 9].map(String),
- (v) => `var(--fg_${v})`,
- 'fg',
- ),
- ...generate_property_classes(
- 'color',
- [1, 2, 3, 4, 5, 6, 7, 8, 9].map(String),
- (v) => `var(--darken_${v})`,
- 'color_darken',
- ),
- ...generate_property_classes(
- 'color',
- [1, 2, 3, 4, 5, 6, 7, 8, 9].map(String),
- (v) => `var(--lighten_${v})`,
- 'color_lighten',
- ),
- color_bg: {declaration: 'color: var(--bg);'},
- color_fg: {declaration: 'color: var(--fg);'},
- ...generate_property_classes(
- 'color',
- [1, 2, 3, 4, 5, 6, 7, 8, 9].map(String),
- (v) => `var(--bg_${v})`,
- 'color_bg',
- ),
- ...generate_property_classes(
- 'color',
- [1, 2, 3, 4, 5, 6, 7, 8, 9].map(String),
- (v) => `var(--fg_${v})`,
- 'color_fg',
- ),
- ...generate_classes(
- (hue: string) => ({
- name: `hue_${hue}`,
- css: `--hue: var(--hue_${hue});`,
- }),
- color_variants,
- ),
- ...generate_classes(
- (hue: string, intensity: string) => ({
- name: `color_${hue}_${intensity}`,
- css: `color: var(--color_${hue}_${intensity});`,
- }),
- color_variants,
- COLOR_INTENSITIES,
- ),
- ...generate_classes(
- (hue: string, intensity: string) => ({
- name: `bg_${hue}_${intensity}`,
- css: `background-color: var(--color_${hue}_${intensity});`,
- }),
- color_variants,
- COLOR_INTENSITIES,
- ),
- ...generate_property_classes(
- 'border-color',
- [1, 2, 3, 4, 5].map(String),
- (v) => `var(--border_color_${v})`,
- ),
- ...generate_property_classes('border-color', color_variants, (v) => `var(--border_color_${v})`),
- border_color_transparent: {declaration: 'border-color: transparent;'},
- ...generate_property_classes(
- 'outline-color',
- [1, 2, 3, 4, 5].map(String),
- (v) => `var(--border_color_${v})`,
- ),
- ...generate_property_classes('outline-color', color_variants, (v) => `var(--border_color_${v})`),
- outline_color_transparent: {declaration: 'outline-color: transparent;'},
-
- ...generate_property_classes('border-width', ['0', ...border_width_variants.map(String)], (v) =>
- v === '0' ? '0' : `var(--border_width_${v})`,
- ),
- ...generate_property_classes('outline-width', ['0', ...border_width_variants.map(String)], (v) =>
- v === '0' ? '0' : `var(--border_width_${v})`,
- ),
- outline_width_focus: {declaration: 'outline-width: var(--outline_width_focus);'},
- outline_width_active: {declaration: 'outline-width: var(--outline_width_active);'},
-
- ...generate_property_classes('border-style', border_style_values),
- ...generate_property_classes('border-style', CSS_GLOBALS, to_kebab),
-
- ...generate_property_classes('outline-style', border_style_values),
- ...generate_property_classes('outline-style', CSS_GLOBALS, to_kebab),
-
- ...generate_property_classes(
- 'border-radius',
- border_radius_variants,
- (v) => `var(--border_radius_${v})`,
- ),
- ...generate_border_radius_corners(border_radius_variants, (v) => `var(--border_radius_${v})`),
-
- /*
-
- shadows
-
- */
- ...generate_shadow_classes(['xs', 'sm', 'md', 'lg', 'xl'], {
- xs: '1',
- sm: '2',
- md: '3',
- lg: '4',
- xl: '5',
- }),
- ...generate_classes(
- (value: string) => ({
- name: `shadow_color_${value}`,
- css: `--shadow_color: var(--shadow_color_${value});`,
- }),
- shadow_semantic_values,
- ),
- ...generate_classes(
- (hue: string) => ({
- name: `shadow_color_${hue}`,
- css: `--shadow_color: var(--shadow_color_${hue});`,
- }),
- color_variants,
- ),
- ...generate_classes(
- (alpha: number) => ({
- name: `shadow_alpha_${alpha}`,
- css: `--shadow_alpha: var(--shadow_alpha_${alpha});`,
- }),
- shadow_alpha_variants,
- ),
-
- /* higher specificity */
- /* TODO which order should these be in? */
- shadow_inherit: {declaration: 'box-shadow: inherit;'},
- shadow_none: {declaration: 'box-shadow: none;'},
-
- /*
-
- layout
-
- */
- ...generate_property_classes(
- 'width',
- [
- '0',
- '100',
- '1px',
- '2px',
- '3px',
- 'auto',
- 'max-content',
- 'min-content',
- 'fit-content',
- 'stretch',
- ...space_variants,
- ],
- format_dimension_value,
- ),
- ...generate_property_classes(
- 'height',
- [
- '0',
- '100',
- '1px',
- '2px',
- '3px',
- 'auto',
- 'max-content',
- 'min-content',
- 'fit-content',
- 'stretch',
- ...space_variants,
- ],
- format_dimension_value,
- ),
-
- ...generate_property_classes(
- 'top',
- ['0', '100', '1px', '2px', '3px', 'auto', ...space_variants],
- format_spacing_value,
- ),
- ...generate_property_classes(
- 'right',
- ['0', '100', '1px', '2px', '3px', 'auto', ...space_variants],
- format_spacing_value,
- ),
- ...generate_property_classes(
- 'bottom',
- ['0', '100', '1px', '2px', '3px', 'auto', ...space_variants],
- format_spacing_value,
- ),
- ...generate_property_classes(
- 'left',
- ['0', '100', '1px', '2px', '3px', 'auto', ...space_variants],
- format_spacing_value,
- ),
-
- ...generate_property_classes(
- 'inset',
- ['0', '100', '1px', '2px', '3px', 'auto', ...space_variants],
- format_spacing_value,
- ),
-
- ...generate_directional_classes(
- 'padding',
- ['0', '100', '1px', '2px', '3px', ...space_variants],
- format_spacing_value,
- ),
- ...generate_directional_classes(
- 'margin',
- ['0', '100', '1px', '2px', '3px', 'auto', ...space_variants],
- format_spacing_value,
- ),
- ...generate_property_classes('gap', space_variants, format_spacing_value),
- ...generate_property_classes('column-gap', space_variants, format_spacing_value),
- ...generate_property_classes('row-gap', space_variants, format_spacing_value),
-};
+/**
+ * Collection of CSS classes extracted from source files.
+ * Tracks classes per-file for efficient incremental updates.
+ * Handles include/exclude filtering and explicit class tracking.
+ * Uses `null` instead of empty collections to avoid allocation overhead.
+ */
+export class CssClasses {
+ #include_classes: Set | null;
+ #exclude_classes: Set | null;
+
+ #all: Set = new Set();
+
+ #all_with_locations: Map> = new Map();
+
+ /** Combined map with include_classes (null locations) and extracted classes (actual locations) */
+ #all_with_locations_including_includes: Map | null> = new Map();
+
+ /** Classes by file id (files with no classes are not stored) */
+ #by_id: Map>> = new Map();
+
+ /** Explicit classes (from @fuz-classes) by file id */
+ #explicit_by_id: Map> = new Map();
+
+ /** Aggregated explicit classes (from extraction + include_classes, minus exclude_classes) */
+ #explicit: Set | null = null;
+
+ /** Diagnostics stored per-file so they're replaced when a file is updated */
+ #diagnostics_by_id: Map> = new Map();
+
+ #dirty = true;
+
+ /**
+ * Creates a new CssClasses collection.
+ *
+ * @param include_classes - Classes to always include (also treated as explicit for warnings)
+ * @param exclude_classes - Classes to exclude from output (also suppresses warnings)
+ */
+ constructor(
+ include_classes: Set | null = null,
+ exclude_classes: Set | null = null,
+ ) {
+ this.#include_classes = include_classes;
+ this.#exclude_classes = exclude_classes;
+ }
+
+ /**
+ * Adds extraction results for a file.
+ * Replaces any previous classes and diagnostics for this file.
+ *
+ * @param id - File identifier
+ * @param classes - Map of class names to their source locations, or null if none
+ * @param explicit_classes - Classes from @fuz-classes comments, or null if none
+ * @param diagnostics - Extraction diagnostics from this file, or null if none
+ */
+ add(
+ id: string,
+ classes: Map> | null,
+ explicit_classes?: Set | null,
+ diagnostics?: Array | null,
+ ): void {
+ this.#dirty = true;
+ if (classes) {
+ this.#by_id.set(id, classes);
+ } else {
+ this.#by_id.delete(id);
+ }
+ if (explicit_classes) {
+ this.#explicit_by_id.set(id, explicit_classes);
+ } else {
+ this.#explicit_by_id.delete(id);
+ }
+ if (diagnostics) {
+ this.#diagnostics_by_id.set(id, diagnostics);
+ } else {
+ // Clear any old diagnostics for this file
+ this.#diagnostics_by_id.delete(id);
+ }
+ }
+
+ delete(id: string): void {
+ this.#dirty = true;
+ this.#by_id.delete(id);
+ this.#explicit_by_id.delete(id);
+ this.#diagnostics_by_id.delete(id);
+ }
+
+ /**
+ * Gets all unique class names as a Set (with exclude filter applied).
+ */
+ get(): Set {
+ if (this.#dirty) {
+ this.#dirty = false;
+ this.#recalculate();
+ }
+ return this.#all;
+ }
+
+ /**
+ * Gets all classes with their source locations (with exclude filter applied).
+ * Locations from include_classes are null.
+ */
+ get_with_locations(): Map | null> {
+ if (this.#dirty) {
+ this.#dirty = false;
+ this.#recalculate();
+ }
+ return this.#all_with_locations_including_includes;
+ }
+
+ /**
+ * Gets all classes and their locations in a single call.
+ * More efficient than calling `get()` and `get_with_locations()` separately
+ * when both are needed (avoids potential double recalculation).
+ *
+ * Results have exclude filter applied and explicit_classes includes include_classes.
+ */
+ get_all(): {
+ all_classes: Set;
+ all_classes_with_locations: Map | null>;
+ explicit_classes: Set | null;
+ } {
+ if (this.#dirty) {
+ this.#dirty = false;
+ this.#recalculate();
+ }
+ return {
+ all_classes: this.#all,
+ all_classes_with_locations: this.#all_with_locations_including_includes,
+ explicit_classes: this.#explicit,
+ };
+ }
+
+ /**
+ * Gets all extraction diagnostics from all files.
+ */
+ get_diagnostics(): Array {
+ const result: Array = [];
+ for (const diagnostics of this.#diagnostics_by_id.values()) {
+ result.push(...diagnostics);
+ }
+ return result;
+ }
+
+ #recalculate(): void {
+ this.#all.clear();
+ this.#all_with_locations.clear();
+ this.#all_with_locations_including_includes.clear();
+ this.#explicit = null;
+
+ const exclude = this.#exclude_classes;
+
+ // Add include_classes first (with null locations - no source)
+ if (this.#include_classes) {
+ for (const c of this.#include_classes) {
+ if (exclude?.has(c)) continue;
+ this.#all.add(c);
+ this.#all_with_locations_including_includes.set(c, null);
+ // include_classes are also explicit (user explicitly wants them)
+ (this.#explicit ??= new Set()).add(c);
+ }
+ }
+
+ // Aggregate from all files
+ for (const classes of this.#by_id.values()) {
+ for (const [cls, locations] of classes) {
+ if (exclude?.has(cls)) continue;
+ this.#all.add(cls);
+ const existing = this.#all_with_locations.get(cls);
+ if (existing) {
+ existing.push(...locations);
+ } else {
+ this.#all_with_locations.set(cls, [...locations]);
+ }
+ // Add to combined map only if not already from include_classes
+ if (!this.#all_with_locations_including_includes.has(cls)) {
+ this.#all_with_locations_including_includes.set(cls, this.#all_with_locations.get(cls)!);
+ }
+ }
+ }
+
+ // Aggregate explicit classes from all files (minus excludes)
+ for (const explicit of this.#explicit_by_id.values()) {
+ for (const cls of explicit) {
+ if (exclude?.has(cls)) continue;
+ (this.#explicit ??= new Set()).add(cls);
+ }
+ }
+ }
+}
diff --git a/src/lib/css_literal.ts b/src/lib/css_literal.ts
new file mode 100644
index 000000000..4615c1812
--- /dev/null
+++ b/src/lib/css_literal.ts
@@ -0,0 +1,771 @@
+/**
+ * CSS-literal syntax parser, validator, and interpreter.
+ *
+ * Enables writing utility classes using actual CSS syntax:
+ *
+ * - `display:flex` â `.display\:flex { display: flex; }`
+ * - `hover:opacity:80%` â `.hover\:opacity\:80\%:hover { opacity: 80%; }`
+ * - `md:dark:hover:before:opacity:80%` â nested CSS with media query, ancestor, state, pseudo-element
+ *
+ * @see {@link https://github.com/fuzdev/fuz_css} for documentation
+ * @module
+ */
+
+import {levenshtein_distance} from '@fuzdev/fuz_util/string.js';
+
+import {type InterpreterDiagnostic} from './diagnostics.js';
+import {get_modifier, get_all_modifier_names, type ModifierDefinition} from './modifiers.js';
+
+//
+// Types
+//
+
+/**
+ * Parsed CSS-literal class with all components extracted.
+ */
+export interface ParsedCssLiteral {
+ /** Original class name */
+ class_name: string;
+ /** Media modifier (breakpoint or feature query) */
+ media: ModifierDefinition | null;
+ /** Ancestor modifier (dark/light) */
+ ancestor: ModifierDefinition | null;
+ /** State modifiers in alphabetical order (can have multiple) */
+ states: Array;
+ /** Pseudo-element modifier (before, after, etc.) */
+ pseudo_element: ModifierDefinition | null;
+ /** CSS property name */
+ property: string;
+ /** CSS value (with ~ replaced by spaces) */
+ value: string;
+}
+
+/**
+ * Result of parsing a CSS-literal class name.
+ *
+ * Uses a discriminated union (Result type) because parsing a single class
+ * is binary: it either succeeds or fails entirely. This differs from
+ * {@link ExtractionResult} which uses embedded diagnostics because file
+ * extraction can partially succeed (some classes extracted, others have errors).
+ *
+ * Uses `| null` for diagnostics to avoid allocating empty arrays.
+ * Callers should use a guard pattern: `if (result.diagnostics) { ... }`
+ */
+export type CssLiteralParseResult =
+ | {ok: true; parsed: ParsedCssLiteral; diagnostics: Array | null}
+ | {ok: false; error: InterpreterDiagnostic};
+
+/**
+ * Extracted modifiers from a class name.
+ * Used by both CSS-literal parsing and modified class interpretation.
+ */
+export interface ExtractedModifiers {
+ /** Media modifier (breakpoint or feature query) */
+ media: ModifierDefinition | null;
+ /** Ancestor modifier (dark/light) */
+ ancestor: ModifierDefinition | null;
+ /** State modifiers in alphabetical order (can have multiple) */
+ states: Array;
+ /** Pseudo-element modifier (before, after, etc.) */
+ pseudo_element: ModifierDefinition | null;
+}
+
+/**
+ * Result of extracting modifiers from segments.
+ */
+export type ModifierExtractionResult =
+ | {ok: true; modifiers: ExtractedModifiers; remaining: Array}
+ | {ok: false; error: InterpreterDiagnostic};
+
+/**
+ * Result of interpreting a CSS-literal class.
+ *
+ * Uses `| null` for warnings to avoid allocating empty arrays.
+ * Callers should use a guard pattern: `if (result.warnings) { ... }`
+ */
+export type InterpretCssLiteralResult =
+ | {ok: true; output: CssLiteralOutput; warnings: Array | null}
+ | {ok: false; error: InterpreterDiagnostic};
+
+//
+// CSS Property Validation
+//
+
+/**
+ * Loads CSS properties from @webref/css.
+ * Returns a fresh Set each time - callers should cache the result if needed.
+ */
+export const load_css_properties = async (): Promise> => {
+ const webref = await import('@webref/css');
+ const indexed = await webref.default.index();
+ return new Set(Object.keys(indexed.properties));
+};
+
+/**
+ * Checks if a property name is a valid CSS property.
+ * Custom properties (--*) always return true.
+ *
+ * @param property - The CSS property name to validate
+ * @param properties - Set of valid CSS properties from `load_css_properties()`.
+ * Pass `null` to skip validation.
+ * @returns True if valid CSS property or custom property
+ */
+export const is_valid_css_property = (
+ property: string,
+ properties: Set | null,
+): boolean => {
+ // Custom properties are always valid
+ if (property.startsWith('--')) return true;
+
+ // If no properties provided, skip validation
+ if (!properties) return true;
+
+ return properties.has(property);
+};
+
+/**
+ * Suggests a correct property name for a typo using Levenshtein distance.
+ *
+ * @param typo - The mistyped property name
+ * @param properties - Set of valid CSS properties from `load_css_properties()`.
+ * Pass `null` to skip suggestions.
+ * @returns The suggested property or null if no close match (Levenshtein distance > 2)
+ */
+export const suggest_css_property = (
+ typo: string,
+ properties: Set | null,
+): string | null => {
+ if (!properties) return null;
+
+ let best_match: string | null = null;
+ let best_distance = 3; // Only suggest if distance <= 2
+
+ for (const prop of properties) {
+ const distance = levenshtein_distance(typo, prop);
+ if (distance < best_distance) {
+ best_distance = distance;
+ best_match = prop;
+ }
+ }
+
+ return best_match;
+};
+
+/**
+ * Suggests a correct modifier name for a typo using Levenshtein distance.
+ *
+ * @param typo - The mistyped modifier name
+ * @returns The suggested modifier or null if no close match (Levenshtein distance > 2)
+ */
+export const suggest_modifier = (typo: string): string | null => {
+ const all_names = get_all_modifier_names();
+ let best_match: string | null = null;
+ let best_distance = 3;
+
+ for (const name of all_names) {
+ const distance = levenshtein_distance(typo, name);
+ if (distance < best_distance) {
+ best_distance = distance;
+ best_match = name;
+ }
+ }
+
+ return best_match;
+};
+
+//
+// Value Formatting
+//
+
+/**
+ * Formats a CSS-literal value for CSS output.
+ * - Replaces `~` with space
+ * - Ensures space before `!important`
+ *
+ * @param value - Raw value from class name
+ * @returns Formatted CSS value
+ */
+export const format_css_value = (value: string): string => {
+ let result = value.replace(/~/g, ' ');
+ result = result.replace(/!important$/, ' !important');
+ return result;
+};
+
+/**
+ * Pattern to detect calc expressions possibly missing spaces around + or -.
+ * CSS requires spaces around + and - in calc().
+ */
+const CALC_MISSING_SPACE_PATTERN = /calc\([^)]*\d+[%a-z]*[+-]\d/i;
+
+/**
+ * Checks if a value contains a possibly invalid calc expression.
+ *
+ * @param value - The formatted CSS value
+ * @returns Warning message if suspicious, null otherwise
+ */
+export const check_calc_expression = (value: string): string | null => {
+ if (CALC_MISSING_SPACE_PATTERN.test(value)) {
+ return `Possible invalid calc expression. CSS requires spaces around + and - in calc(). Use ~ for spaces: "calc(100%~-~20px)"`;
+ }
+ return null;
+};
+
+//
+// Parsing
+//
+
+/**
+ * Checks if a class name could be a CSS-literal class.
+ * Quick check before attempting full parse.
+ *
+ * @param class_name - The class name to check
+ * @returns True if it could be CSS-literal syntax
+ */
+export const is_possible_css_literal = (class_name: string): boolean => {
+ // Must contain at least one colon
+ if (!class_name.includes(':')) return false;
+
+ // Should not match existing class patterns (underscore-based)
+ if (/^[a-z_]+_[a-z0-9_]+$/.test(class_name)) return false;
+
+ // Basic structure check: at minimum "property:value"
+ const parts = class_name.split(':');
+ if (parts.length < 2) return false;
+
+ // First part should be non-empty (property or first modifier)
+ if (parts[0] === '') return false;
+
+ // Last part should be the value, shouldn't be empty
+ if (parts[parts.length - 1] === '') return false;
+
+ return true;
+};
+
+/**
+ * Extracts colon-separated segments from a class name, handling parentheses.
+ * Parenthesized content (like function arguments) is kept intact.
+ *
+ * @example
+ * extract_segments('md:hover:display:flex') â ['md', 'hover', 'display', 'flex']
+ * extract_segments('nth-child(2n+1):color:red') â ['nth-child(2n+1)', 'color', 'red']
+ * extract_segments('width:calc(100%-20px)') â ['width', 'calc(100%-20px)']
+ */
+export const extract_segments = (class_name: string): Array => {
+ const segments: Array = [];
+ let current = '';
+ let paren_depth = 0;
+
+ for (const char of class_name) {
+ if (char === '(') {
+ paren_depth++;
+ current += char;
+ } else if (char === ')') {
+ paren_depth--;
+ current += char;
+ } else if (char === ':' && paren_depth === 0) {
+ if (current) {
+ segments.push(current);
+ current = '';
+ }
+ } else {
+ current += char;
+ }
+ }
+
+ if (current) {
+ segments.push(current);
+ }
+
+ return segments;
+};
+
+/**
+ * Extracts and validates modifiers from the beginning of a segments array.
+ * Modifiers are consumed from the front until a non-modifier segment is found.
+ *
+ * Used by both CSS-literal parsing and modified class interpretation.
+ *
+ * @param segments - Array of colon-separated segments
+ * @param class_name - Original class name for error messages
+ * @returns ModifierExtractionResult with modifiers and remaining segments, or error
+ */
+export const extract_and_validate_modifiers = (
+ segments: Array,
+ class_name: string,
+): ModifierExtractionResult => {
+ let media: ModifierDefinition | null = null;
+ let ancestor: ModifierDefinition | null = null;
+ const states: Array = [];
+ let pseudo_element: ModifierDefinition | null = null;
+
+ let i = 0;
+ for (; i < segments.length; i++) {
+ const segment = segments[i]!;
+ const modifier = get_modifier(segment);
+
+ // If not a modifier, stop - remaining segments are the base class/property:value
+ if (!modifier) {
+ break;
+ }
+
+ // Validate order based on modifier type
+ switch (modifier.type) {
+ case 'media': {
+ if (media) {
+ return {
+ ok: false,
+ error: {
+ level: 'error',
+ message: `Multiple media modifiers not allowed`,
+ class_name,
+ suggestion: null,
+ },
+ };
+ }
+ if (ancestor) {
+ return {
+ ok: false,
+ error: {
+ level: 'error',
+ message: `Media modifier must come before ancestor modifier`,
+ class_name,
+ suggestion: `Move "${segment}" before "${ancestor.name}"`,
+ },
+ };
+ }
+ if (states.length > 0) {
+ return {
+ ok: false,
+ error: {
+ level: 'error',
+ message: `Media modifier must come before state modifiers`,
+ class_name,
+ suggestion: `Move "${segment}" before "${states[0]!.name}"`,
+ },
+ };
+ }
+ if (pseudo_element) {
+ return {
+ ok: false,
+ error: {
+ level: 'error',
+ message: `Media modifier must come before pseudo-element`,
+ class_name,
+ suggestion: `Move "${segment}" before "${pseudo_element.name}"`,
+ },
+ };
+ }
+ media = modifier;
+ break;
+ }
+
+ case 'ancestor': {
+ if (ancestor) {
+ return {
+ ok: false,
+ error: {
+ level: 'error',
+ message: `Modifiers "${ancestor.name}" and "${segment}" are mutually exclusive`,
+ class_name,
+ suggestion: null,
+ },
+ };
+ }
+ if (states.length > 0) {
+ return {
+ ok: false,
+ error: {
+ level: 'error',
+ message: `Ancestor modifier must come before state modifiers`,
+ class_name,
+ suggestion: `Move "${segment}" before "${states[0]!.name}"`,
+ },
+ };
+ }
+ if (pseudo_element) {
+ return {
+ ok: false,
+ error: {
+ level: 'error',
+ message: `Ancestor modifier must come before pseudo-element`,
+ class_name,
+ suggestion: `Move "${segment}" before "${pseudo_element.name}"`,
+ },
+ };
+ }
+ ancestor = modifier;
+ break;
+ }
+
+ case 'state': {
+ if (pseudo_element) {
+ return {
+ ok: false,
+ error: {
+ level: 'error',
+ message: `State modifiers must come before pseudo-element`,
+ class_name,
+ suggestion: `Move "${segment}" before "${pseudo_element.name}"`,
+ },
+ };
+ }
+ // Check alphabetical order (full string comparison)
+ if (states.length > 0) {
+ const prev = states[states.length - 1]!;
+ if (segment < prev.name) {
+ return {
+ ok: false,
+ error: {
+ level: 'error',
+ message: `State modifiers must be in alphabetical order: "${prev.name}:${segment}" should be "${segment}:${prev.name}"`,
+ class_name,
+ suggestion: `Reorder to: ...${segment}:${prev.name}:...`,
+ },
+ };
+ }
+ }
+ states.push(modifier);
+ break;
+ }
+
+ case 'pseudo-element': {
+ if (pseudo_element) {
+ return {
+ ok: false,
+ error: {
+ level: 'error',
+ message: `Multiple pseudo-element modifiers not allowed`,
+ class_name,
+ suggestion: null,
+ },
+ };
+ }
+ pseudo_element = modifier;
+ break;
+ }
+ }
+ }
+
+ return {
+ ok: true,
+ modifiers: {media, ancestor, states, pseudo_element},
+ remaining: segments.slice(i),
+ };
+};
+
+/**
+ * Parses a CSS-literal class name into its components.
+ *
+ * @param class_name - The class name to parse
+ * @param css_properties - Set of valid CSS properties from `load_css_properties()`.
+ * Pass `null` to skip property validation.
+ * @returns CssLiteralParseResult with parsed data or error
+ */
+export const parse_css_literal = (
+ class_name: string,
+ css_properties: Set | null,
+): CssLiteralParseResult => {
+ const segments = extract_segments(class_name);
+
+ if (segments.length < 2) {
+ return {
+ ok: false,
+ error: {
+ level: 'error',
+ message: `Invalid CSS-literal syntax: expected "property:value" format`,
+ class_name,
+ suggestion: null,
+ },
+ };
+ }
+
+ // Work backwards from end to find property:value
+ // Everything before that is modifiers
+ const value = segments[segments.length - 1]!;
+ const property = segments[segments.length - 2]!;
+ const modifier_segments = segments.slice(0, -2);
+
+ let diagnostics: Array | null = null;
+
+ // Validate modifiers using shared validation logic
+ const modifier_result = extract_and_validate_modifiers(modifier_segments, class_name);
+
+ if (!modifier_result.ok) {
+ return {ok: false, error: modifier_result.error};
+ }
+
+ // All segments should have been consumed as modifiers
+ // If any remain, they're unknown modifiers (since we already separated property:value)
+ if (modifier_result.remaining.length > 0) {
+ const unknown = modifier_result.remaining[0]!;
+ const suggestion = suggest_modifier(unknown);
+ return {
+ ok: false,
+ error: {
+ level: 'error',
+ message: `Unknown modifier "${unknown}"`,
+ class_name,
+ suggestion: suggestion ? `Did you mean "${suggestion}"?` : null,
+ },
+ };
+ }
+
+ const {media, ancestor, states, pseudo_element} = modifier_result.modifiers;
+
+ // Validate property
+ if (!is_valid_css_property(property, css_properties)) {
+ const suggestion = suggest_css_property(property, css_properties);
+ return {
+ ok: false,
+ error: {
+ level: 'error',
+ message: `Unknown CSS property "${property}"`,
+ class_name,
+ suggestion: suggestion ? `Did you mean "${suggestion}"?` : null,
+ },
+ };
+ }
+
+ // Format value
+ const formatted_value = format_css_value(value);
+
+ // Check for calc warnings
+ const calc_warning = check_calc_expression(formatted_value);
+ if (calc_warning) {
+ (diagnostics ??= []).push({
+ level: 'warning',
+ message: calc_warning,
+ class_name,
+ suggestion: `Use ~ for spaces in calc expressions`,
+ });
+ }
+
+ return {
+ ok: true,
+ parsed: {
+ class_name,
+ media,
+ ancestor,
+ states,
+ pseudo_element,
+ property,
+ value: formatted_value,
+ },
+ diagnostics,
+ };
+};
+
+//
+// CSS Generation
+//
+
+/**
+ * Generates the CSS selector for a parsed CSS-literal class.
+ * Includes state pseudo-classes and pseudo-element in the selector.
+ */
+export const generate_selector = (escaped_class_name: string, parsed: ParsedCssLiteral): string => {
+ let selector = `.${escaped_class_name}`;
+
+ // Add state pseudo-classes
+ for (const state of parsed.states) {
+ selector += state.css;
+ }
+
+ // Add pseudo-element (must come last)
+ if (parsed.pseudo_element) {
+ selector += parsed.pseudo_element.css;
+ }
+
+ return selector;
+};
+
+/**
+ * Generates the CSS declaration for a parsed CSS-literal class.
+ */
+export const generate_declaration = (parsed: ParsedCssLiteral): string => {
+ return `${parsed.property}: ${parsed.value};`;
+};
+
+/**
+ * Information needed to generate CSS output for a CSS-literal class.
+ */
+export interface CssLiteralOutput {
+ /** CSS declaration (property: value;) */
+ declaration: string;
+ /** Full CSS selector including pseudo-classes/elements */
+ selector: string;
+ /** Media query wrapper if any */
+ media_wrapper: string | null;
+ /** Ancestor wrapper if any */
+ ancestor_wrapper: string | null;
+}
+
+/**
+ * Interprets a CSS-literal class and returns CSS generation info.
+ *
+ * Callers should first check `is_possible_css_literal()` to filter non-CSS-literal classes.
+ *
+ * @param class_name - The class name to interpret
+ * @param escaped_class_name - The CSS-escaped version of the class name
+ * @param css_properties - Set of valid CSS properties from `load_css_properties()`.
+ * Pass `null` to skip property validation.
+ * @returns Result with output and warnings on success, or error on failure
+ */
+export const interpret_css_literal = (
+ class_name: string,
+ escaped_class_name: string,
+ css_properties: Set | null,
+): InterpretCssLiteralResult => {
+ const result = parse_css_literal(class_name, css_properties);
+
+ if (!result.ok) {
+ return {ok: false, error: result.error};
+ }
+
+ const {parsed, diagnostics} = result;
+
+ return {
+ ok: true,
+ output: {
+ declaration: generate_declaration(parsed),
+ selector: generate_selector(escaped_class_name, parsed),
+ media_wrapper: parsed.media?.css ?? null,
+ ancestor_wrapper: parsed.ancestor?.css ?? null,
+ },
+ warnings: diagnostics,
+ };
+};
+
+//
+// Composition Support
+//
+
+/**
+ * Result of attempting to resolve a CSS literal for composition.
+ */
+export type LiteralResolutionResult =
+ | {ok: true; declaration: string; warnings: Array | null}
+ | {ok: false; error: InterpreterDiagnostic | null};
+
+/**
+ * Checks if a parsed CSS literal has any modifiers.
+ */
+export const has_modifiers = (parsed: ParsedCssLiteral): boolean => {
+ return !!(parsed.media || parsed.ancestor || parsed.states.length > 0 || parsed.pseudo_element);
+};
+
+/**
+ * Checks if extracted modifiers contain any modifier.
+ * Useful for tooling that needs to detect modified classes.
+ */
+export const has_extracted_modifiers = (modifiers: ExtractedModifiers): boolean => {
+ return !!(
+ modifiers.media ||
+ modifiers.ancestor ||
+ modifiers.states.length > 0 ||
+ modifiers.pseudo_element
+ );
+};
+
+/**
+ * Attempts to resolve a class name as an unmodified CSS literal for composition.
+ *
+ * Used by `resolve_composes` to support literals in `composes` arrays.
+ * Returns the declaration if successful, null if not a literal, or an error
+ * if it's a literal with issues (modifiers, invalid property, etc.).
+ *
+ * @param class_name - The class name to try resolving
+ * @param css_properties - Set of valid CSS properties, or null to skip validation
+ * @param context_class_name - The class being defined (for error messages)
+ * @returns Resolution result with declaration, or error, or null if not a literal
+ */
+export const try_resolve_literal = (
+ class_name: string,
+ css_properties: Set | null,
+ context_class_name: string,
+): LiteralResolutionResult => {
+ // Quick check - must look like a CSS literal
+ if (!is_possible_css_literal(class_name)) {
+ return {ok: false, error: null};
+ }
+
+ // Parse the literal
+ const result = parse_css_literal(class_name, css_properties);
+
+ if (!result.ok) {
+ return {ok: false, error: contextualize_error(result.error, context_class_name)};
+ }
+
+ const {parsed, diagnostics} = result;
+
+ // Check for modifiers - modified literals cannot be composed
+ if (has_modifiers(parsed)) {
+ return {
+ ok: false,
+ error: {
+ level: 'error',
+ class_name: context_class_name,
+ message: `Modified class "${class_name}" cannot be used in composes array`,
+ suggestion: 'Apply modified classes directly in markup, not in composes arrays',
+ },
+ };
+ }
+
+ // Success - return the declaration
+ return {
+ ok: true,
+ declaration: generate_declaration(parsed),
+ warnings: diagnostics,
+ };
+};
+
+/**
+ * Updates error to use the context class name (the class being defined).
+ */
+const contextualize_error = (
+ error: InterpreterDiagnostic,
+ context_class_name: string,
+): InterpreterDiagnostic => ({
+ ...error,
+ class_name: context_class_name,
+});
+
+/**
+ * Generates simple CSS for a CSS-literal class (without grouping).
+ * Used by the interpreter for basic output.
+ *
+ * @param output - The CSS-literal output info
+ * @returns CSS string for this class
+ */
+export const generate_css_literal_simple = (output: CssLiteralOutput): string => {
+ let css = '';
+ let indent = '';
+
+ // Open media wrapper if present
+ if (output.media_wrapper) {
+ css += `${output.media_wrapper} {\n`;
+ indent = '\t';
+ }
+
+ // Open ancestor wrapper if present
+ if (output.ancestor_wrapper) {
+ css += `${indent}${output.ancestor_wrapper} {\n`;
+ indent += '\t';
+ }
+
+ // Write the rule
+ css += `${indent}${output.selector} { ${output.declaration} }\n`;
+
+ // Close ancestor wrapper
+ if (output.ancestor_wrapper) {
+ indent = indent.slice(0, -1);
+ css += `${indent}}\n`;
+ }
+
+ // Close media wrapper
+ if (output.media_wrapper) {
+ css += '}\n';
+ }
+
+ return css;
+};
diff --git a/src/lib/css_ruleset_parser.ts b/src/lib/css_ruleset_parser.ts
new file mode 100644
index 000000000..a620176e2
--- /dev/null
+++ b/src/lib/css_ruleset_parser.ts
@@ -0,0 +1,638 @@
+/**
+ * CSS ruleset parser using Svelte's CSS parser.
+ *
+ * Parses CSS rulesets to extract selectors and declarations with position information.
+ * Used for:
+ * - Phase 0a: Detecting single-selector rulesets that could be converted to declaration format
+ * - Phase 2: Modifying selectors for modifier support on composite classes
+ *
+ * NOTE: We wrap CSS in a `';
+
+/**
+ * Parses a CSS ruleset string using Svelte's CSS parser.
+ *
+ * @param css - Raw CSS string (e.g., ".box { display: flex; }")
+ * @returns ParsedRuleset with structured rule data and positions
+ */
+export const parse_ruleset = (css: string): ParsedRuleset => {
+ const wrapper_offset = STYLE_WRAPPER_PREFIX.length;
+ const wrapped = `${STYLE_WRAPPER_PREFIX}${css}${STYLE_WRAPPER_SUFFIX}`;
+
+ const ast = parse(wrapped, {modern: true});
+ const rules: Array = [];
+
+ if (!ast.css) {
+ return {rules, wrapper_offset};
+ }
+
+ // Walk the CSS AST to find rules
+ walk_css_children(ast.css, css, wrapper_offset, rules);
+
+ return {rules, wrapper_offset};
+};
+
+/**
+ * Walks CSS AST children to extract rules.
+ * @mutates rules - Pushes extracted rules to the array
+ */
+const walk_css_children = (
+ node: AST.CSS.StyleSheet | AST.CSS.Atrule,
+ original_css: string,
+ wrapper_offset: number,
+ rules: Array,
+): void => {
+ const children = 'children' in node ? node.children : [];
+
+ for (const child of children) {
+ if (child.type === 'Rule') {
+ extract_rule(child, original_css, wrapper_offset, rules);
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+ } else if (child.type === 'Atrule' && child.block) {
+ // Recurse into at-rules (like @media) - rules are in block.children
+ walk_css_block(child.block, original_css, wrapper_offset, rules);
+ }
+ }
+};
+
+/**
+ * Walks a CSS block (from an at-rule) to extract rules.
+ * @mutates rules - Pushes extracted rules to the array
+ */
+const walk_css_block = (
+ block: AST.CSS.Block,
+ original_css: string,
+ wrapper_offset: number,
+ rules: Array,
+): void => {
+ for (const child of block.children) {
+ if (child.type === 'Rule') {
+ extract_rule(child, original_css, wrapper_offset, rules);
+ } else if (child.type === 'Atrule' && child.block) {
+ // Handle nested at-rules
+ walk_css_block(child.block, original_css, wrapper_offset, rules);
+ }
+ }
+};
+
+/**
+ * Extracts a single rule from the AST.
+ * @mutates rules - Pushes the extracted rule to the array
+ */
+const extract_rule = (
+ rule: AST.CSS.Rule,
+ original_css: string,
+ wrapper_offset: number,
+ rules: Array,
+): void => {
+ const prelude = rule.prelude;
+
+ // Get the full selector text from the original CSS
+ const selector_start = prelude.start - wrapper_offset;
+ const selector_end = prelude.end - wrapper_offset;
+ const selector = original_css.slice(selector_start, selector_end);
+
+ // Get declarations from the block
+ const block = rule.block;
+ const block_start = block.start - wrapper_offset;
+ const block_end = block.end - wrapper_offset;
+
+ // Extract just the declarations (content between braces)
+ const block_content = original_css.slice(block_start, block_end);
+ const declarations = block_content.slice(1, -1).trim(); // Remove { } and trim
+
+ rules.push({
+ selector,
+ selector_start,
+ selector_end,
+ declarations,
+ rule_start: rule.start - wrapper_offset,
+ rule_end: rule.end - wrapper_offset,
+ });
+};
+
+/**
+ * Checks if a ruleset has only a single simple selector (just the class name).
+ * Used to detect rulesets that could be converted to declaration format.
+ *
+ * @param rules - Parsed rules from the ruleset
+ * @param escaped_class_name - The CSS-escaped class name (e.g., "box" or "hover\\:card")
+ * @returns True if there's exactly one rule with selector ".class_name"
+ */
+export const is_single_selector_ruleset = (
+ rules: Array,
+ escaped_class_name: string,
+): boolean => {
+ if (rules.length !== 1) return false;
+
+ const rule = rules[0]!;
+ const expected_selector = `.${escaped_class_name}`;
+
+ // Normalize whitespace in selector for comparison
+ const normalized_selector = rule.selector.trim();
+
+ return normalized_selector === expected_selector;
+};
+
+/**
+ * Checks if any selector in the ruleset contains the expected class name.
+ * Used to validate that ruleset definitions match their key.
+ *
+ * @param rules - Parsed rules from the ruleset
+ * @param escaped_class_name - The CSS-escaped class name (e.g., "clickable" or "hover\\:card")
+ * @returns True if at least one selector contains ".class_name"
+ */
+export const ruleset_contains_class = (
+ rules: Array,
+ escaped_class_name: string,
+): boolean => {
+ // Match .class_name but not as part of another class (e.g., .class_name_foo)
+ const pattern = new RegExp(`\\.${escape_regexp(escaped_class_name)}(?![\\w-])`);
+ return rules.some((rule) => pattern.test(rule.selector));
+};
+
+/**
+ * Extracts the CSS comment from a ruleset (if any).
+ * Looks for comments before the first rule.
+ *
+ * @param css - Raw CSS string
+ * @param rules - Parsed rules
+ * @returns Comment text without delimiters, or null if no comment
+ */
+export const extract_css_comment = (css: string, rules: Array): string | null => {
+ if (rules.length === 0) return null;
+
+ const first_rule_start = rules[0]!.rule_start;
+ const before_rule = css.slice(0, first_rule_start).trim();
+
+ // Check for /* */ comment
+ const comment_pattern = /\/\*\s*([\s\S]*?)\s*\*\//;
+ const comment_match = comment_pattern.exec(before_rule);
+ if (comment_match) {
+ return comment_match[1]!.trim();
+ }
+
+ return null;
+};
+
+//
+// Selector Modification
+//
+
+/**
+ * Information about a modifier that was skipped for a selector during ruleset modification.
+ * The selector is still included in output, just without the conflicting modifier applied.
+ */
+export interface SkippedModifierInfo {
+ /** The specific selector where the modifier was skipped (not the full selector list) */
+ selector: string;
+ /** Reason the modifier was skipped */
+ reason: 'pseudo_element_conflict' | 'state_conflict';
+ /** The conflicting modifier that was not applied (e.g., "::before" or ":hover") */
+ conflicting_modifier: string;
+}
+
+/**
+ * Result from modifying a selector group with conflict detection.
+ *
+ * Uses `| null` for skipped_modifiers to avoid allocating empty arrays.
+ * Callers should use a guard pattern: `if (result.skipped_modifiers) { ... }`
+ */
+export interface ModifiedSelectorGroupResult {
+ /** The modified selector list as a string */
+ selector: string;
+ /** Information about modifiers skipped for specific selectors, or null if none */
+ skipped_modifiers: Array | null;
+}
+
+/**
+ * Result from generating a modified ruleset.
+ *
+ * Uses `| null` for skipped_modifiers to avoid allocating empty arrays.
+ * Callers should use a guard pattern: `if (result.skipped_modifiers) { ... }`
+ */
+export interface ModifiedRulesetResult {
+ /** The generated CSS */
+ css: string;
+ /** Information about modifiers that were skipped for certain rules, or null if none */
+ skipped_modifiers: Array | null;
+}
+
+/**
+ * Skips an identifier (class name, pseudo-class name, etc.) starting at `start`.
+ *
+ * @returns New position after the identifier
+ */
+const skip_identifier = (selector: string, start: number): number => {
+ let pos = start;
+ while (pos < selector.length && /[\w-]/.test(selector[pos]!)) {
+ pos++;
+ }
+ return pos;
+};
+
+/**
+ * CSS2 pseudo-elements that use single-colon syntax.
+ * CSS3 uses double-colon (::before) but CSS2 syntax (:before) is still valid.
+ */
+const CSS2_PSEUDO_ELEMENTS = /:(before|after|first-letter|first-line)(?![\w-])/;
+
+/**
+ * Checks if a selector contains a pseudo-element (::before, ::after, etc.).
+ * Also detects CSS2 single-colon syntax (:before, :after, :first-letter, :first-line).
+ */
+const selector_has_pseudo_element = (selector: string): boolean => {
+ return selector.includes('::') || CSS2_PSEUDO_ELEMENTS.test(selector);
+};
+
+/**
+ * Checks if a selector already contains a specific state pseudo-class.
+ * Handles functional pseudo-classes like :not(:hover) by checking for the state
+ * both as a direct pseudo-class and within functional pseudo-classes.
+ *
+ * @param selector - The CSS selector to check
+ * @param state - The state to look for (e.g., ":hover", ":focus")
+ * @returns True if the selector already contains this state
+ */
+const selector_has_state = (selector: string, state: string): boolean => {
+ // Simple check: does the selector contain the state string?
+ // This catches both direct usage (.foo:hover) and within functional pseudo-classes (.foo:not(:hover))
+ return selector.includes(state);
+};
+
+/**
+ * Splits a selector list by commas, respecting parentheses, brackets, and quoted strings.
+ *
+ * @example
+ * split_selector_list('.a, .b') â ['.a', '.b']
+ * split_selector_list('.a:not(.b), .c') â ['.a:not(.b)', '.c']
+ * split_selector_list('.a[data-x="a,b"], .c') â ['.a[data-x="a,b"]', '.c']
+ */
+export const split_selector_list = (selector_group: string): Array => {
+ const selectors: Array = [];
+ let current = '';
+ let paren_depth = 0;
+ let bracket_depth = 0;
+ let in_string: '"' | "'" | null = null;
+
+ for (let i = 0; i < selector_group.length; i++) {
+ const char = selector_group[i]!;
+ const prev_char = i > 0 ? selector_group[i - 1] : '';
+
+ // Handle string boundaries (but not escaped quotes)
+ if ((char === '"' || char === "'") && prev_char !== '\\') {
+ if (in_string === null) {
+ in_string = char;
+ } else if (in_string === char) {
+ in_string = null;
+ }
+ current += char;
+ } else if (in_string !== null) {
+ // Inside a string - just accumulate
+ current += char;
+ } else if (char === '(') {
+ paren_depth++;
+ current += char;
+ } else if (char === ')') {
+ paren_depth--;
+ current += char;
+ } else if (char === '[') {
+ bracket_depth++;
+ current += char;
+ } else if (char === ']') {
+ bracket_depth--;
+ current += char;
+ } else if (char === ',' && paren_depth === 0 && bracket_depth === 0) {
+ selectors.push(current.trim());
+ current = '';
+ } else {
+ current += char;
+ }
+ }
+
+ if (current.trim()) {
+ selectors.push(current.trim());
+ }
+
+ return selectors;
+};
+
+/**
+ * Finds the end position of the compound selector containing the class at class_pos.
+ * A compound selector is a sequence of simple selectors without combinators.
+ *
+ * @param selector - The CSS selector string
+ * @param class_pos - Position of the `.` in `.class_name`
+ * @returns Position where state modifiers should be inserted (before any pseudo-element)
+ */
+export const find_compound_end = (selector: string, class_pos: number): number => {
+ let pos = class_pos + 1; // Skip the dot
+ pos = skip_identifier(selector, pos); // Skip the class name
+
+ // Continue scanning while we see parts of the same compound selector
+ while (pos < selector.length) {
+ const char = selector[pos]!;
+
+ if (char === '.') {
+ // Another class - skip it
+ pos++;
+ pos = skip_identifier(selector, pos);
+ } else if (char === '#') {
+ // ID selector - skip it
+ pos++;
+ pos = skip_identifier(selector, pos);
+ } else if (char === '[') {
+ // Attribute selector - find matching ], respecting quoted strings
+ pos++; // Skip [
+ let in_string: '"' | "'" | null = null;
+ while (pos < selector.length) {
+ const c = selector[pos]!;
+ const prev = pos > 0 ? selector[pos - 1] : '';
+ if ((c === '"' || c === "'") && prev !== '\\') {
+ if (in_string === null) {
+ in_string = c;
+ } else if (in_string === c) {
+ in_string = null;
+ }
+ } else if (c === ']' && in_string === null) {
+ pos++; // Skip ]
+ break;
+ }
+ pos++;
+ }
+ } else if (char === ':') {
+ // Pseudo-class or pseudo-element
+ if (selector[pos + 1] === ':') {
+ // CSS3 pseudo-element (::before) - stop here (state comes before pseudo-element)
+ break;
+ }
+ // Check for CSS2 pseudo-elements (:before, :after, :first-letter, :first-line)
+ const rest = selector.slice(pos);
+ if (CSS2_PSEUDO_ELEMENTS.test(rest)) {
+ // CSS2 pseudo-element - stop here
+ break;
+ }
+ // Pseudo-class - skip it (including functional ones like :not())
+ pos++; // Skip :
+ pos = skip_identifier(selector, pos);
+ if (pos < selector.length && selector[pos] === '(') {
+ // Functional pseudo-class - find matching )
+ let paren_depth = 1;
+ pos++;
+ while (pos < selector.length && paren_depth > 0) {
+ if (selector[pos] === '(') paren_depth++;
+ else if (selector[pos] === ')') paren_depth--;
+ pos++;
+ }
+ }
+ } else {
+ // Hit whitespace, combinator, or something else - compound ends here
+ break;
+ }
+ }
+
+ return pos;
+};
+
+/**
+ * Modifies a single CSS selector to add modifiers.
+ *
+ * @param selector - A single CSS selector (not a selector list)
+ * @param original_class - The base class name (e.g., "menu_item")
+ * @param new_class_escaped - The escaped new class name (e.g., "hover\\:menu_item")
+ * @param state_css - State modifier CSS to insert (e.g., ":hover")
+ * @param pseudo_element_css - Pseudo-element modifier CSS to insert (e.g., "::before")
+ * @returns Modified selector
+ *
+ * @example
+ * modify_single_selector('.menu_item', 'menu_item', 'hover\\:menu_item', ':hover', '')
+ * // â '.hover\\:menu_item:hover'
+ *
+ * modify_single_selector('.menu_item .icon', 'menu_item', 'hover\\:menu_item', ':hover', '')
+ * // â '.hover\\:menu_item:hover .icon'
+ *
+ * modify_single_selector('.menu_item.selected', 'menu_item', 'hover\\:menu_item', ':hover', '')
+ * // â '.hover\\:menu_item.selected:hover'
+ */
+export const modify_single_selector = (
+ selector: string,
+ original_class: string,
+ new_class_escaped: string,
+ state_css: string,
+ pseudo_element_css: string,
+): string => {
+ // Find the target class (must match the class name exactly, not as part of another class)
+ const class_pattern = new RegExp(`\\.${escape_regexp(original_class)}(?![\\w-])`);
+ const match = class_pattern.exec(selector);
+ if (!match) return selector; // Class not in this selector
+
+ const class_pos = match.index;
+
+ // Find where the compound selector ends (where to insert state)
+ const compound_end = find_compound_end(selector, class_pos);
+
+ // Build the modified selector
+ // Insert state and pseudo-element at compound_end (before any existing pseudo-element)
+ const suffix = state_css + pseudo_element_css;
+ let result = selector.slice(0, compound_end) + suffix + selector.slice(compound_end);
+
+ // Replace the class name with the new escaped name
+ result = result.replace(class_pattern, `.${new_class_escaped}`);
+
+ return result;
+};
+
+/**
+ * Modifies a selector list (comma-separated selectors) to add modifiers.
+ * Handles conflicts per-selector: if one selector in a list has a conflict,
+ * only that selector skips the modifier; other selectors still get it.
+ *
+ * @param selector_group - CSS selector list (may contain commas)
+ * @param original_class - The base class name
+ * @param new_class_escaped - The escaped new class name
+ * @param states_to_add - Individual state modifiers (e.g., [":hover", ":focus"])
+ * @param pseudo_element_css - Pseudo-element modifier CSS to insert (e.g., "::before")
+ * @returns Result with modified selector list and information about skipped modifiers
+ */
+export const modify_selector_group = (
+ selector_group: string,
+ original_class: string,
+ new_class_escaped: string,
+ states_to_add: Array,
+ pseudo_element_css: string,
+): ModifiedSelectorGroupResult => {
+ const selectors = split_selector_list(selector_group);
+ let skipped_modifiers: Array | null = null;
+ const adding_pseudo_element = pseudo_element_css !== '';
+
+ const modified_selectors = selectors.map((selector) => {
+ const trimmed = selector.trim();
+
+ // Check pseudo-element conflict for this specific selector
+ const has_pseudo_element_conflict =
+ adding_pseudo_element && selector_has_pseudo_element(trimmed);
+
+ // Check state conflicts for this specific selector - filter to non-conflicting states
+ const non_conflicting_states: Array = [];
+ for (const state of states_to_add) {
+ if (selector_has_state(trimmed, state)) {
+ // Record the skip for this specific selector
+ (skipped_modifiers ??= []).push({
+ selector: trimmed,
+ reason: 'state_conflict',
+ conflicting_modifier: state,
+ });
+ } else {
+ non_conflicting_states.push(state);
+ }
+ }
+
+ // Record pseudo-element skip for this specific selector
+ if (has_pseudo_element_conflict) {
+ (skipped_modifiers ??= []).push({
+ selector: trimmed,
+ reason: 'pseudo_element_conflict',
+ conflicting_modifier: pseudo_element_css,
+ });
+ }
+
+ // Build effective modifiers for this selector
+ const effective_state_css = non_conflicting_states.join('');
+ const effective_pseudo_element_css = has_pseudo_element_conflict ? '' : pseudo_element_css;
+
+ return modify_single_selector(
+ trimmed,
+ original_class,
+ new_class_escaped,
+ effective_state_css,
+ effective_pseudo_element_css,
+ );
+ });
+
+ return {
+ selector: modified_selectors.join(',\n'),
+ skipped_modifiers,
+ };
+};
+
+/**
+ * Generates CSS for a modified ruleset with applied modifiers.
+ *
+ * Conflict handling is per-selector within selector lists:
+ * - For `.plain:hover, .plain:active` with `hover:` modifier, only `.plain:hover` skips
+ * the `:hover` addition; `.plain:active` still gets `:hover` appended.
+ * - For multiple states like `:hover:focus`, each is checked individually; conflicting
+ * states are skipped while non-conflicting ones are still applied.
+ *
+ * @param original_ruleset - The original CSS ruleset string
+ * @param original_class - The base class name
+ * @param new_class_escaped - The escaped new class name with modifiers
+ * @param state_css - State modifier CSS (e.g., ":hover" or ":hover:focus")
+ * @param pseudo_element_css - Pseudo-element modifier CSS (e.g., "::before")
+ * @param media_wrapper - Media query wrapper (e.g., "@media (width >= 48rem)")
+ * @param ancestor_wrapper - Ancestor wrapper (e.g., ":root.dark")
+ * @returns Result with generated CSS and information about skipped modifiers
+ */
+export const generate_modified_ruleset = (
+ original_ruleset: string,
+ original_class: string,
+ new_class_escaped: string,
+ state_css: string,
+ pseudo_element_css: string,
+ media_wrapper: string | null,
+ ancestor_wrapper: string | null,
+): ModifiedRulesetResult => {
+ const parsed = parse_ruleset(original_ruleset);
+ let skipped_modifiers: Array | null = null;
+
+ // Extract individual states for per-selector conflict detection (e.g., ":hover:focus" â [":hover", ":focus"])
+ const states_to_add = state_css.match(/:[a-z-]+/g) ?? [];
+
+ let css = '';
+ let indent = '';
+
+ // Open media wrapper if present
+ if (media_wrapper) {
+ css += `${media_wrapper} {\n`;
+ indent = '\t';
+ }
+
+ // Open ancestor wrapper if present
+ if (ancestor_wrapper) {
+ css += `${indent}${ancestor_wrapper} {\n`;
+ indent += '\t';
+ }
+
+ // Generate each rule with modified selector (conflict detection happens per-selector in modify_selector_group)
+ for (const rule of parsed.rules) {
+ const result = modify_selector_group(
+ rule.selector,
+ original_class,
+ new_class_escaped,
+ states_to_add,
+ pseudo_element_css,
+ );
+
+ // Collect skip info from per-selector conflict detection
+ if (result.skipped_modifiers) {
+ (skipped_modifiers ??= []).push(...result.skipped_modifiers);
+ }
+
+ css += `${indent}${result.selector} { ${rule.declarations} }\n`;
+ }
+
+ // Close ancestor wrapper
+ if (ancestor_wrapper) {
+ indent = indent.slice(0, -1);
+ css += `${indent}}\n`;
+ }
+
+ // Close media wrapper
+ if (media_wrapper) {
+ css += '}\n';
+ }
+
+ return {css, skipped_modifiers};
+};
diff --git a/src/lib/diagnostics.ts b/src/lib/diagnostics.ts
new file mode 100644
index 000000000..37ca9a780
--- /dev/null
+++ b/src/lib/diagnostics.ts
@@ -0,0 +1,139 @@
+/**
+ * Diagnostic types for CSS class extraction and generation.
+ *
+ * Provides a unified diagnostic system across all phases:
+ * - Extraction: Parsing source files to find class names
+ * - Generation: Producing CSS output from class definitions
+ *
+ * @module
+ */
+
+//
+// Source Location
+//
+
+/**
+ * Source location for IDE/LSP integration.
+ */
+export interface SourceLocation {
+ file: string;
+ /** 1-based line number */
+ line: number;
+ /** 1-based column number */
+ column: number;
+}
+
+//
+// Diagnostic Types
+//
+
+/**
+ * Base diagnostic with common fields.
+ */
+export interface BaseDiagnostic {
+ level: 'error' | 'warning';
+ message: string;
+ suggestion: string | null;
+}
+
+/**
+ * Diagnostic from the extraction phase.
+ */
+export interface ExtractionDiagnostic extends BaseDiagnostic {
+ phase: 'extraction';
+ location: SourceLocation;
+}
+
+/**
+ * Diagnostic from the generation phase.
+ */
+export interface GenerationDiagnostic {
+ phase: 'generation';
+ level: 'error' | 'warning';
+ message: string;
+ suggestion: string | null;
+ class_name: string;
+ /** Source locations where this class was used, or null if from include_classes */
+ locations: Array | null;
+}
+
+/**
+ * Union of all diagnostic types.
+ */
+export type Diagnostic = ExtractionDiagnostic | GenerationDiagnostic;
+
+/**
+ * Diagnostic from CSS class interpretation.
+ * Used internally by interpreters; converted to GenerationDiagnostic with locations.
+ */
+export interface InterpreterDiagnostic {
+ level: 'error' | 'warning';
+ message: string;
+ class_name: string;
+ suggestion: string | null;
+}
+
+//
+// Diagnostic Utilities
+//
+
+/**
+ * Converts a InterpreterDiagnostic to a GenerationDiagnostic with locations.
+ *
+ * @param diagnostic - Interpreter diagnostic to convert
+ * @param locations - Source locations where the class was used
+ */
+export const create_generation_diagnostic = (
+ diagnostic: InterpreterDiagnostic,
+ locations: Array | null,
+): GenerationDiagnostic => ({
+ phase: 'generation',
+ level: diagnostic.level,
+ message: diagnostic.message,
+ class_name: diagnostic.class_name,
+ suggestion: diagnostic.suggestion ?? null,
+ locations,
+});
+
+/**
+ * Formats a diagnostic for display.
+ */
+export const format_diagnostic = (d: Diagnostic): string => {
+ const suggestion = d.suggestion ? ` (${d.suggestion})` : '';
+ if (d.phase === 'extraction') {
+ return ` - ${d.location.file}:${d.location.line}:${d.location.column}: ${d.message}${suggestion}`;
+ }
+ const loc = d.locations?.[0];
+ const location_str = loc ? `${loc.file}:${loc.line}:${loc.column}: ` : '';
+ return ` - ${location_str}${d.class_name}: ${d.message}${suggestion}`;
+};
+
+/**
+ * Error thrown when CSS generation encounters errors or warnings
+ * (depending on `on_error` and `on_warning` settings).
+ * Contains the full diagnostics array for programmatic access.
+ */
+export class CssGenerationError extends Error {
+ diagnostics: Array;
+
+ constructor(diagnostics: Array) {
+ const errors = diagnostics.filter((d) => d.level === 'error');
+ const warnings = diagnostics.filter((d) => d.level === 'warning');
+
+ const parts: Array = [];
+ if (errors.length > 0) {
+ parts.push(`${errors.length} error${errors.length === 1 ? '' : 's'}`);
+ }
+ if (warnings.length > 0) {
+ parts.push(`${warnings.length} warning${warnings.length === 1 ? '' : 's'}`);
+ }
+
+ const summary = parts.length > 0 ? parts.join(' and ') : '0 issues';
+ const formatted = diagnostics.map(format_diagnostic).join('\n');
+ const message = `CSS generation failed with ${summary}:\n${formatted}`;
+
+ super(message);
+ this.name = 'CssGenerationError';
+ this.diagnostics = diagnostics;
+ }
+}
diff --git a/src/lib/example_class_utilities.ts b/src/lib/example_class_utilities.ts
new file mode 100644
index 000000000..e73dc258b
--- /dev/null
+++ b/src/lib/example_class_utilities.ts
@@ -0,0 +1,93 @@
+/**
+ * Example CSS class exports demonstrating node_modules extraction.
+ *
+ * This module exists to verify that the Vite plugin extracts classes from
+ * dependencies in node_modules. The exports test specific extraction patterns
+ * that require being in a separate module.
+ *
+ * **Important:** Variable names must match `CLASS_NAME_PATTERN` in css_class_extractor.ts.
+ * Supported suffixes: `class`, `classes`, `className`, `classNames`, `classList`, `classLists`
+ * (also snake_case variants like `class_name`, `class_names`, `class_list`, `class_lists`).
+ *
+ * **Patterns demonstrated:**
+ * - Naming patterns: All CLASS_NAME_PATTERN suffix variants
+ * - Expression patterns: Ternary, logical AND, arrays
+ * - Comment hints: `@fuz-classes` directive
+ *
+ * Token, composite, and literal classes are demonstrated inline in the examples
+ * since they don't require special extraction testing.
+ *
+ * @module
+ */
+
+/* eslint-disable no-constant-condition, @typescript-eslint/no-unnecessary-condition, no-constant-binary-expression */
+
+//
+// Naming patterns - all CLASS_NAME_PATTERN suffix variants (mb_* + ml_* for plurals)
+//
+
+/** `*Class` suffix (camelCase) */
+export const demoClass = 'mb_xs5';
+
+/** `*_class` suffix (snake_case) */
+export const demo_class = 'mb_xs4';
+
+/** `SCREAMING_SNAKE_CASE` naming */
+export const DEMO_CLASS = 'mb_xs3';
+
+/** `*Classes` suffix (camelCase) - plural, multiple classes */
+export const demoClasses = 'mb_xs2 ml_xs';
+
+/** `*_classes` suffix (snake_case) - plural, multiple classes */
+export const demo_classes = 'mb_xs ml_sm';
+
+/** `*ClassName` suffix (camelCase) */
+export const demoClassName = 'mb_sm';
+
+/** `*class_name` suffix (snake_case) */
+export const demo_class_name = 'mb_md';
+
+/** `*ClassNames` suffix (camelCase) - plural, multiple classes */
+export const demoClassNames = 'mb_lg ml_md';
+
+/** `*class_names` suffix (snake_case) - plural, multiple classes */
+export const demo_class_names = 'mb_xl ml_lg';
+
+/** `*ClassList` suffix (camelCase) */
+export const demoClassList = 'mb_xl2';
+
+/** `*class_list` suffix (snake_case) */
+export const demo_class_list = 'mb_xl3';
+
+/** `*ClassLists` suffix (camelCase) - plural, multiple classes */
+export const demoClassLists = 'mb_xl4 ml_xl';
+
+/** `*class_lists` suffix (snake_case) - plural, multiple classes */
+export const demo_class_lists = 'mb_xl5 ml_xl2';
+
+//
+// Expression patterns - ternary, logical AND, array (mt_* incrementing)
+//
+
+/** Ternary expression - both branches extracted */
+export const ternaryClass = true ? 'mt_xs' : 'mt_sm';
+
+/** Logical AND - truthy value extracted */
+export const logicalClass = true && 'mt_md';
+
+/** Array - all elements extracted */
+export const arrayClasses = ['mt_lg', 'mt_xl'];
+
+/** Object - keys extracted as class names */
+export const objectClasses = {mt_xl2: 'mt_xl2', mt_xl3: 'mt_xl3'};
+
+//
+// Comment hint examples - @fuz-classes directive
+//
+
+// @fuz-classes shadow_lg
+/**
+ * Comment hint - extracted via @fuz-classes, NOT via variable name pattern.
+ * Variable name `fromComment` doesn't end in class/classes/className.
+ */
+export const fromComment = 'shadow_lg';
diff --git a/src/lib/file_filter.ts b/src/lib/file_filter.ts
new file mode 100644
index 000000000..1d6bfe07a
--- /dev/null
+++ b/src/lib/file_filter.ts
@@ -0,0 +1,35 @@
+/**
+ * File filtering utilities for CSS class extraction.
+ *
+ * @module
+ */
+
+/**
+ * Filter function to determine which files to process for CSS class extraction.
+ */
+export type FileFilter = (path: string) => boolean;
+
+/**
+ * Default file filter for CSS class extraction.
+ * Includes .svelte, .html, .ts, .js, .tsx, .jsx files.
+ * Excludes test files and generated files.
+ */
+export const filter_file_default: FileFilter = (path) => {
+ if (
+ path.includes('.test.') ||
+ path.includes('/test/') ||
+ path.includes('/tests/') ||
+ path.includes('.gen.')
+ ) {
+ return false;
+ }
+ const ext = path.slice(path.lastIndexOf('.'));
+ return (
+ ext === '.svelte' ||
+ ext === '.html' ||
+ ext === '.ts' ||
+ ext === '.js' ||
+ ext === '.tsx' ||
+ ext === '.jsx'
+ );
+};
diff --git a/src/lib/gen_fuz_css.ts b/src/lib/gen_fuz_css.ts
index 5033e2dcb..189b13a32 100644
--- a/src/lib/gen_fuz_css.ts
+++ b/src/lib/gen_fuz_css.ts
@@ -1,57 +1,233 @@
+/**
+ * Gro generator for creating optimized utility CSS from extracted class names.
+ * Scans source files, extracts CSS classes with AST-based parsing, and generates
+ * only the CSS for classes actually used. Includes per-file caching with content
+ * hash validation for fast incremental rebuilds.
+ *
+ * @module
+ */
+
+import {join} from 'node:path';
import type {Gen} from '@ryanatkn/gro/gen.js';
-import type {FileFilter} from '@fuzdev/fuz_util/path.js';
+import {map_concurrent, each_concurrent} from '@fuzdev/fuz_util/async.js';
+import {type FileFilter, filter_file_default} from './file_filter.js';
+import {extract_css_classes_with_locations, type AcornPlugin} from './css_class_extractor.js';
+import {
+ type SourceLocation,
+ type ExtractionDiagnostic,
+ type Diagnostic,
+ format_diagnostic,
+ CssGenerationError,
+} from './diagnostics.js';
+import {CssClasses} from './css_classes.js';
import {
- collect_css_classes,
- CssClasses,
generate_classes_css,
- type CssClassDeclaration,
- type CssClassDeclarationInterpreter,
-} from './css_class_helpers.js';
-import {css_classes_by_name} from './css_classes.js';
+ type CssClassDefinition,
+ type CssClassDefinitionInterpreter,
+} from './css_class_generation.js';
+import {css_class_definitions} from './css_class_definitions.js';
import {css_class_interpreters} from './css_class_interpreters.js';
+import {load_css_properties} from './css_literal.js';
+import {
+ DEFAULT_CACHE_DIR,
+ get_cache_path,
+ load_cached_extraction,
+ save_cached_extraction,
+ delete_cached_extraction,
+ from_cached_extraction,
+ compute_hash,
+} from './css_cache.js';
+
+/**
+ * Skip cache on CI (no point writing cache that won't be reused).
+ * Handles CI=1, CI=true, and other truthy values.
+ */
+const is_ci = !!process.env.CI;
+
+/**
+ * Default concurrency for main loop: cache read + extract.
+ * This is NOT true CPU parallelism - Node.js JS is single-threaded.
+ * The value controls I/O interleaving (overlapping cache reads with parsing)
+ * and memory budget for in-flight operations. Higher values offer diminishing
+ * returns since AST parsing is synchronous on the main thread.
+ */
+const DEFAULT_CONCURRENCY = 8;
+
+/**
+ * Default concurrency for cache writes/deletes (I/O-bound).
+ * Safe to set high since Node's libuv thread pool (default 4 threads)
+ * limits actual parallel I/O operations. Memory pressure from buffered
+ * writes is the main constraint, but cache entries are small JSON files.
+ */
+const DEFAULT_CACHE_IO_CONCURRENCY = 50;
+
+/**
+ * Computes cache path for a file.
+ * Internal files use relative paths mirroring source tree.
+ * External files (outside project root) use hashed absolute paths in `_external/`.
+ */
+const get_file_cache_path = async (
+ file_id: string,
+ cache_dir: string,
+ project_root: string,
+): Promise => {
+ const is_internal = file_id.startsWith(project_root);
+ return is_internal
+ ? get_cache_path(file_id, cache_dir, project_root)
+ : join(cache_dir, '_external', (await compute_hash(file_id)).slice(0, 16) + '.json');
+};
+
+/**
+ * Result from extracting CSS classes from a single file.
+ * Used internally during parallel extraction with caching.
+ * Uses `null` instead of empty collections to avoid allocation overhead.
+ */
+interface FileExtraction {
+ id: string;
+ /** Extracted classes, or null if none */
+ classes: Map> | null;
+ /** Classes from @fuz-classes comments, or null if none */
+ explicit_classes: Set | null;
+ /** Extraction diagnostics, or null if none */
+ diagnostics: Array | null;
+ /** Cache path to write to, or null if no write needed (cache hit or CI) */
+ cache_path: string | null;
+ content_hash: string;
+}
export interface GenFuzCssOptions {
- filter_file?: FileFilter | null;
+ filter_file?: FileFilter;
include_stats?: boolean;
- classes_by_name?: Record;
- class_interpreters?: Array;
+ /**
+ * Additional class definitions to merge with defaults.
+ * User definitions take precedence over defaults with the same name.
+ * Required when `include_default_definitions` is `false`.
+ */
+ class_definitions?: Record;
+ /**
+ * Whether to include default class definitions (token and composite classes).
+ * When `false`, `class_definitions` is required.
+ * @default true
+ */
+ include_default_definitions?: boolean;
+ /**
+ * Custom interpreters for dynamic class generation.
+ * Replaces the builtin interpreters entirely if provided.
+ */
+ class_interpreters?: Array;
+ /**
+ * How to handle CSS-literal errors during generation.
+ * - 'log': Log errors, skip invalid classes, continue
+ * - 'throw': Throw on first error, fail the build
+ * @default 'throw' in CI, 'log' otherwise
+ */
+ on_error?: 'log' | 'throw';
+ /**
+ * How to handle warnings during generation.
+ * - 'log': Log warnings, continue
+ * - 'throw': Throw on first warning, fail the build
+ * - 'ignore': Suppress warnings entirely
+ * @default 'log'
+ */
+ on_warning?: 'log' | 'throw' | 'ignore';
+ /**
+ * Classes to always include in the output, regardless of whether they're detected in source files.
+ * Useful for dynamically generated class names that can't be statically extracted.
+ */
+ include_classes?: Iterable;
+ /**
+ * Classes to exclude from the output, even if they're detected in source files.
+ * Useful for filtering out false positives from extraction.
+ */
+ exclude_classes?: Iterable;
+ /**
+ * Cache directory relative to project_root.
+ * @default DEFAULT_CACHE_DIR
+ */
+ cache_dir?: string;
+ /**
+ * Project root directory. Source paths must be under this directory.
+ * @default process.cwd()
+ */
+ project_root?: string;
+ /**
+ * Max concurrent file processing (cache read + extract).
+ * Bottlenecked by CPU-bound AST parsing.
+ * @default 8
+ */
+ concurrency?: number;
+ /**
+ * Max concurrent cache writes and deletes (I/O-bound).
+ * @default 50
+ */
+ cache_io_concurrency?: number;
+ /**
+ * Additional acorn plugins to use when parsing TS/JS files.
+ * Useful for adding JSX support via `acorn-jsx` for React projects.
+ *
+ * @example
+ * ```ts
+ * import jsx from 'acorn-jsx';
+ * export const gen = gen_fuz_css({
+ * acorn_plugins: [jsx()],
+ * });
+ * ```
+ */
+ acorn_plugins?: Array;
}
-const filter_file_default: FileFilter = (path) => {
- if (path.includes('.test.') || path.includes('/test/') || path.includes('.gen.')) {
- return false;
- }
- const ext = path.slice(path.lastIndexOf('.'));
- return ext === '.svelte' || ext === '.ts' || ext === '.js';
-};
-
export const gen_fuz_css = (options: GenFuzCssOptions = {}): Gen => {
const {
filter_file = filter_file_default,
include_stats = false,
- classes_by_name = css_classes_by_name,
+ class_definitions: user_class_definitions,
+ include_default_definitions = true,
class_interpreters = css_class_interpreters,
+ on_error = is_ci ? 'throw' : 'log',
+ on_warning = 'log',
+ include_classes,
+ exclude_classes,
+ cache_dir = DEFAULT_CACHE_DIR,
+ project_root: project_root_option,
+ concurrency = DEFAULT_CONCURRENCY,
+ cache_io_concurrency = DEFAULT_CACHE_IO_CONCURRENCY,
+ acorn_plugins,
} = options;
+ // Convert to Sets for efficient lookup
+ const include_set = include_classes ? new Set(include_classes) : null;
+ const exclude_set = exclude_classes ? new Set(exclude_classes) : null;
+
+ // Instance-level state for watch mode cleanup
+ let previous_paths: Set | null = null;
+
return {
- dependencies: 'all',
- // TODO optimize, do we need to handle deleted files or removed classes though?
- // This isn't as much a problem in watch mode but isn't clean.
- // dependencies: ({changed_file_id, filer}) => {
- // if (!changed_file_id) return 'all';
- // const disknode = filer.get_by_id(changed_file_id);
- // if (disknode?.contents && collect_css_classes(disknode.contents).size) {
- // return 'all';
- // }
- // return null;
- // },
+ // Filter dependencies to skip non-extractable files.
+ // Returns 'all' when an extractable file changes, null otherwise.
+ dependencies: ({changed_file_id}) => {
+ if (!changed_file_id) return 'all';
+ if (filter_file(changed_file_id)) return 'all';
+ return null; // Ignore .json, .md, etc.
+ },
+
generate: async ({filer, log, origin_path}) => {
- log.info('generating Fuz CSS classes...');
+ log.info('generating fuz_css classes...');
+
+ // Load CSS properties for validation before generation
+ const css_properties = await load_css_properties();
await filer.init();
- const css_classes = new CssClasses();
+ // Normalize project root - ensure it ends with /
+ const raw_project_root = project_root_option ?? process.cwd();
+ const project_root = raw_project_root.endsWith('/')
+ ? raw_project_root
+ : raw_project_root + '/';
+ const resolved_cache_dir = join(project_root, cache_dir);
+
+ const css_classes = new CssClasses(include_set, exclude_set);
+ const current_paths: Set = new Set();
const stats = {
total_files: filer.files.size,
@@ -60,8 +236,17 @@ export const gen_fuz_css = (options: GenFuzCssOptions = {}): Gen => {
processed_files: 0,
files_with_content: 0,
files_with_classes: 0,
+ cache_hits: 0,
+ cache_misses: 0,
};
+ // Collect nodes to process
+ const nodes: Array<{
+ id: string;
+ contents: string;
+ content_hash: string;
+ }> = [];
+
for (const disknode of filer.files.values()) {
if (disknode.external) {
stats.external_files++;
@@ -69,28 +254,116 @@ export const gen_fuz_css = (options: GenFuzCssOptions = {}): Gen => {
stats.internal_files++;
}
- if (filter_file && !filter_file(disknode.id)) {
+ if (!filter_file(disknode.id)) {
continue;
}
stats.processed_files++;
- if (disknode.contents !== null) {
+ if (disknode.contents !== null && disknode.content_hash !== null) {
stats.files_with_content++;
- const classes = collect_css_classes(disknode.contents);
- if (classes.size > 0) {
- css_classes.add(disknode.id, classes);
+ nodes.push({
+ id: disknode.id,
+ contents: disknode.contents,
+ content_hash: disknode.content_hash,
+ });
+ }
+ }
+
+ // Parallel extraction with cache check
+ const extractions: Array = await map_concurrent(
+ nodes,
+ async (node): Promise => {
+ current_paths.add(node.id);
+ const cache_path = await get_file_cache_path(node.id, resolved_cache_dir, project_root);
+
+ // Try cache (skip on CI)
+ if (!is_ci) {
+ const cached = await load_cached_extraction(cache_path);
+ if (cached && cached.content_hash === node.content_hash) {
+ // Cache hit
+ stats.cache_hits++;
+ return {
+ id: node.id,
+ ...from_cached_extraction(cached),
+ cache_path: null,
+ content_hash: node.content_hash,
+ };
+ }
+ }
+
+ // Cache miss - extract
+ stats.cache_misses++;
+ const result = extract_css_classes_with_locations(node.contents, {
+ filename: node.id,
+ acorn_plugins,
+ });
+
+ return {
+ id: node.id,
+ classes: result.classes,
+ explicit_classes: result.explicit_classes,
+ diagnostics: result.diagnostics,
+ cache_path: is_ci ? null : cache_path,
+ content_hash: node.content_hash,
+ };
+ },
+ concurrency,
+ );
+
+ // Add to CssClasses (null = empty, so use truthiness check)
+ for (const {id, classes, explicit_classes, diagnostics} of extractions) {
+ if (classes || explicit_classes || diagnostics) {
+ css_classes.add(id, classes, explicit_classes, diagnostics);
+ if (classes) {
stats.files_with_classes++;
}
}
}
- const unique_classes = new Set();
- for (const file_classes of css_classes.get().values()) {
- for (const class_name of file_classes) {
- unique_classes.add(class_name);
+ // Collect cache writes (entries that need writing)
+ const cache_writes = extractions.filter(
+ (e): e is FileExtraction & {cache_path: string} => e.cache_path !== null,
+ );
+
+ // Parallel cache writes (await completion)
+ if (cache_writes.length > 0) {
+ await each_concurrent(
+ cache_writes,
+ async ({cache_path, content_hash, classes, explicit_classes, diagnostics}) => {
+ await save_cached_extraction(
+ cache_path,
+ content_hash,
+ classes,
+ explicit_classes,
+ diagnostics,
+ );
+ },
+ cache_io_concurrency,
+ ).catch((err) => log.warn('Cache write error:', err));
+ }
+
+ // Watch mode cleanup: delete cache files for removed source files
+ // Note: Empty directories are intentionally left behind (rare case, not worth the cost)
+ if (!is_ci && previous_paths) {
+ const paths_to_delete = [...previous_paths].filter((p) => !current_paths.has(p));
+ if (paths_to_delete.length > 0) {
+ await each_concurrent(
+ paths_to_delete,
+ async (path) => {
+ const cache_path = await get_file_cache_path(path, resolved_cache_dir, project_root);
+ await delete_cached_extraction(cache_path);
+ },
+ cache_io_concurrency,
+ ).catch(() => {
+ // Ignore deletion errors
+ });
}
}
+ previous_paths = current_paths;
+
+ // Get all classes with locations (already filtered by exclude_classes)
+ const {all_classes, all_classes_with_locations, explicit_classes} = css_classes.get_all();
if (include_stats) {
log.info('File statistics:');
@@ -100,29 +373,82 @@ export const gen_fuz_css = (options: GenFuzCssOptions = {}): Gen => {
log.info(` Files processed (passed filter): ${stats.processed_files}`);
log.info(` With content: ${stats.files_with_content}`);
log.info(` With CSS classes: ${stats.files_with_classes}`);
- log.info(` Unique CSS classes found: ${unique_classes.size}`);
+ log.info(` Cache: ${stats.cache_hits} hits, ${stats.cache_misses} misses`);
+ log.info(` Unique CSS classes found: ${all_classes.size}`);
+ }
+
+ // Merge class definitions (user definitions take precedence)
+ if (!include_default_definitions && !user_class_definitions) {
+ throw new Error('class_definitions is required when include_default_definitions is false');
}
+ const all_class_definitions = include_default_definitions
+ ? user_class_definitions
+ ? {...css_class_definitions, ...user_class_definitions}
+ : css_class_definitions
+ : user_class_definitions!;
- const css = generate_classes_css(css_classes.get(), classes_by_name, class_interpreters, log);
+ const result = generate_classes_css({
+ class_names: all_classes,
+ class_definitions: all_class_definitions,
+ interpreters: class_interpreters,
+ css_properties,
+ log,
+ class_locations: all_classes_with_locations,
+ explicit_classes,
+ });
+
+ // Collect all diagnostics: extraction + generation
+ const all_diagnostics: Array = [
+ ...css_classes.get_diagnostics(),
+ ...result.diagnostics,
+ ];
+
+ // Separate errors and warnings
+ const errors = all_diagnostics.filter((d) => d.level === 'error');
+ const warnings = all_diagnostics.filter((d) => d.level === 'warning');
+
+ // Handle warnings based on on_warning setting
+ if (warnings.length > 0) {
+ if (on_warning === 'throw') {
+ throw new CssGenerationError(warnings);
+ } else if (on_warning === 'log') {
+ for (const warning of warnings) {
+ log.warn(format_diagnostic(warning));
+ }
+ }
+ // 'ignore' - do nothing
+ }
+
+ // Handle errors based on on_error setting
+ if (errors.length > 0) {
+ if (on_error === 'throw') {
+ throw new CssGenerationError(errors);
+ }
+ // 'log' mode - log each error with details
+ for (const error of errors) {
+ log.error(format_diagnostic(error));
+ }
+ }
const banner = `generated by ${origin_path}`;
const content_parts = [`/* ${banner} */`];
if (include_stats) {
- const performance_note = `/* *
+ const performance_note = `/* *
* File statistics:
* - Total files in filer: ${stats.total_files}
* - External dependencies: ${stats.external_files}
* - Internal project files: ${stats.internal_files}
* - Files processed (passed filter): ${stats.processed_files}
* - Files with CSS classes: ${stats.files_with_classes}
- * - Unique classes found: ${unique_classes.size}
+ * - Cache: ${stats.cache_hits} hits, ${stats.cache_misses} misses
+ * - Unique classes found: ${all_classes.size}
*/`;
content_parts.push(performance_note);
}
- content_parts.push(css);
+ content_parts.push(result.css);
content_parts.push(`/* ${banner} */`);
return content_parts.join('\n\n');
diff --git a/src/lib/modifiers.ts b/src/lib/modifiers.ts
new file mode 100644
index 000000000..9a95fce6a
--- /dev/null
+++ b/src/lib/modifiers.ts
@@ -0,0 +1,305 @@
+/**
+ * Declarative modifier definitions for CSS-literal syntax.
+ *
+ * Modifiers enable responsive, state-based, and contextual styling:
+ * - Media modifiers: `md:`, `print:`, `motion-safe:`
+ * - Ancestor modifiers: `dark:`, `light:`
+ * - State modifiers: `hover:`, `focus:`, `disabled:`
+ * - Pseudo-element modifiers: `before:`, `after:`
+ *
+ * @see {@link https://github.com/fuzdev/fuz_css} for documentation
+ * @module
+ */
+
+/**
+ * Type of modifier determining its position in the class name and CSS output.
+ *
+ * Order in class names: `[media:][ancestor:][state...:][pseudo-element:]property:value`
+ */
+export type ModifierType = 'media' | 'ancestor' | 'state' | 'pseudo-element';
+
+/**
+ * Definition for a single modifier.
+ */
+export interface ModifierDefinition {
+ /** The prefix used in class names (e.g., 'hover', 'md', 'dark') */
+ name: string;
+ /** Type determines position in modifier order and CSS output behavior */
+ type: ModifierType;
+ /** The CSS output - wrapper for media/ancestor, suffix for state/pseudo-element */
+ css: string;
+ /** Optional ordering within type (for breakpoints, sorted by this value) */
+ order?: number;
+}
+
+/**
+ * All modifier definitions in a single declarative structure.
+ * Adding a new modifier requires only adding to this array.
+ */
+export const MODIFIERS: Array = [
+ // Media modifiers - viewport breakpoints (mobile-first)
+ {name: 'sm', type: 'media', css: '@media (width >= 40rem)', order: 1},
+ {name: 'md', type: 'media', css: '@media (width >= 48rem)', order: 2},
+ {name: 'lg', type: 'media', css: '@media (width >= 64rem)', order: 3},
+ {name: 'xl', type: 'media', css: '@media (width >= 80rem)', order: 4},
+ {name: '2xl', type: 'media', css: '@media (width >= 96rem)', order: 5},
+
+ // Max-width variants (for targeting below a breakpoint)
+ {name: 'max-sm', type: 'media', css: '@media (width < 40rem)', order: 11},
+ {name: 'max-md', type: 'media', css: '@media (width < 48rem)', order: 12},
+ {name: 'max-lg', type: 'media', css: '@media (width < 64rem)', order: 13},
+ {name: 'max-xl', type: 'media', css: '@media (width < 80rem)', order: 14},
+ {name: 'max-2xl', type: 'media', css: '@media (width < 96rem)', order: 15},
+
+ // Media modifiers - feature queries
+ {name: 'print', type: 'media', css: '@media print'},
+ {name: 'motion-safe', type: 'media', css: '@media (prefers-reduced-motion: no-preference)'},
+ {name: 'motion-reduce', type: 'media', css: '@media (prefers-reduced-motion: reduce)'},
+ {name: 'contrast-more', type: 'media', css: '@media (prefers-contrast: more)'},
+ {name: 'contrast-less', type: 'media', css: '@media (prefers-contrast: less)'},
+ {name: 'portrait', type: 'media', css: '@media (orientation: portrait)'},
+ {name: 'landscape', type: 'media', css: '@media (orientation: landscape)'},
+ {name: 'forced-colors', type: 'media', css: '@media (forced-colors: active)'},
+
+ // Ancestor modifiers - color scheme
+ {name: 'dark', type: 'ancestor', css: ':root.dark'},
+ {name: 'light', type: 'ancestor', css: ':root.light'},
+
+ // State modifiers - interaction (ordered for proper cascade: LVFHA)
+ {name: 'any-link', type: 'state', css: ':any-link'},
+ {name: 'link', type: 'state', css: ':link'},
+ {name: 'visited', type: 'state', css: ':visited', order: 1},
+ {name: 'focus-within', type: 'state', css: ':focus-within', order: 2},
+ {name: 'focus', type: 'state', css: ':focus', order: 3},
+ {name: 'focus-visible', type: 'state', css: ':focus-visible', order: 4},
+ {name: 'hover', type: 'state', css: ':hover', order: 5},
+ {name: 'active', type: 'state', css: ':active', order: 6},
+ {name: 'target', type: 'state', css: ':target', order: 7},
+
+ // State modifiers - form states
+ {name: 'autofill', type: 'state', css: ':autofill'},
+ {name: 'blank', type: 'state', css: ':blank'},
+ {name: 'disabled', type: 'state', css: ':disabled'},
+ {name: 'enabled', type: 'state', css: ':enabled'},
+ {name: 'checked', type: 'state', css: ':checked'},
+ {name: 'indeterminate', type: 'state', css: ':indeterminate'},
+ {name: 'default', type: 'state', css: ':default'},
+ {name: 'required', type: 'state', css: ':required'},
+ {name: 'optional', type: 'state', css: ':optional'},
+ {name: 'valid', type: 'state', css: ':valid'},
+ {name: 'invalid', type: 'state', css: ':invalid'},
+ {name: 'user-valid', type: 'state', css: ':user-valid'},
+ {name: 'user-invalid', type: 'state', css: ':user-invalid'},
+ {name: 'in-range', type: 'state', css: ':in-range'},
+ {name: 'out-of-range', type: 'state', css: ':out-of-range'},
+ {name: 'placeholder-shown', type: 'state', css: ':placeholder-shown'},
+ {name: 'read-only', type: 'state', css: ':read-only'},
+ {name: 'read-write', type: 'state', css: ':read-write'},
+
+ // State modifiers - structural
+ {name: 'first', type: 'state', css: ':first-child'},
+ {name: 'last', type: 'state', css: ':last-child'},
+ {name: 'only', type: 'state', css: ':only-child'},
+ {name: 'first-of-type', type: 'state', css: ':first-of-type'},
+ {name: 'last-of-type', type: 'state', css: ':last-of-type'},
+ {name: 'only-of-type', type: 'state', css: ':only-of-type'},
+ {name: 'odd', type: 'state', css: ':nth-child(odd)'},
+ {name: 'even', type: 'state', css: ':nth-child(even)'},
+ {name: 'empty', type: 'state', css: ':empty'},
+ // Note: nth-child(N), nth-last-child(N), nth-of-type(N), nth-last-of-type(N) are handled dynamically
+
+ // State modifiers - UI states
+ {name: 'fullscreen', type: 'state', css: ':fullscreen'},
+ {name: 'modal', type: 'state', css: ':modal'},
+ {name: 'open', type: 'state', css: ':open'},
+ {name: 'popover-open', type: 'state', css: ':popover-open'},
+
+ // State modifiers - media states
+ {name: 'paused', type: 'state', css: ':paused'},
+ {name: 'playing', type: 'state', css: ':playing'},
+
+ // Pseudo-element modifiers
+ {name: 'before', type: 'pseudo-element', css: '::before'},
+ {name: 'after', type: 'pseudo-element', css: '::after'},
+ {name: 'cue', type: 'pseudo-element', css: '::cue'},
+ {name: 'first-letter', type: 'pseudo-element', css: '::first-letter'},
+ {name: 'first-line', type: 'pseudo-element', css: '::first-line'},
+ {name: 'placeholder', type: 'pseudo-element', css: '::placeholder'},
+ {name: 'selection', type: 'pseudo-element', css: '::selection'},
+ {name: 'marker', type: 'pseudo-element', css: '::marker'},
+ {name: 'file', type: 'pseudo-element', css: '::file-selector-button'},
+ {name: 'backdrop', type: 'pseudo-element', css: '::backdrop'},
+];
+
+// Generated lookup maps for efficient access
+
+/** Map of media modifier names to their CSS output */
+export const MEDIA_MODIFIERS: Map = new Map(
+ MODIFIERS.filter((m) => m.type === 'media').map((m) => [m.name, m]),
+);
+
+/** Map of ancestor modifier names to their CSS output */
+export const ANCESTOR_MODIFIERS: Map = new Map(
+ MODIFIERS.filter((m) => m.type === 'ancestor').map((m) => [m.name, m]),
+);
+
+/** Map of state modifier names to their CSS output */
+export const STATE_MODIFIERS: Map = new Map(
+ MODIFIERS.filter((m) => m.type === 'state').map((m) => [m.name, m]),
+);
+
+/** Map of pseudo-element modifier names to their CSS output */
+export const PSEUDO_ELEMENT_MODIFIERS: Map = new Map(
+ MODIFIERS.filter((m) => m.type === 'pseudo-element').map((m) => [m.name, m]),
+);
+
+/** All modifier names for quick lookup */
+export const ALL_MODIFIER_NAMES: Set = new Set(MODIFIERS.map((m) => m.name));
+
+/**
+ * Pattern for arbitrary min-width breakpoints: `min-width(800px):`
+ */
+export const ARBITRARY_MIN_WIDTH_PATTERN = /^min-width\(([^)]+)\)$/;
+
+/**
+ * Pattern for arbitrary max-width breakpoints: `max-width(600px):`
+ */
+export const ARBITRARY_MAX_WIDTH_PATTERN = /^max-width\(([^)]+)\)$/;
+
+/**
+ * Pattern for parameterized nth-child: `nth-child(2n+1):`
+ */
+export const NTH_CHILD_PATTERN = /^nth-child\(([^)]+)\)$/;
+
+/**
+ * Pattern for parameterized nth-last-child: `nth-last-child(2n+1):`
+ */
+export const NTH_LAST_CHILD_PATTERN = /^nth-last-child\(([^)]+)\)$/;
+
+/**
+ * Pattern for parameterized nth-of-type: `nth-of-type(2n):`
+ */
+export const NTH_OF_TYPE_PATTERN = /^nth-of-type\(([^)]+)\)$/;
+
+/**
+ * Pattern for parameterized nth-last-of-type: `nth-last-of-type(2n):`
+ */
+export const NTH_LAST_OF_TYPE_PATTERN = /^nth-last-of-type\(([^)]+)\)$/;
+
+/**
+ * Parses an arbitrary breakpoint modifier.
+ *
+ * @returns The CSS media query or null if not an arbitrary breakpoint
+ */
+export const parse_arbitrary_breakpoint = (segment: string): string | null => {
+ const min_match = ARBITRARY_MIN_WIDTH_PATTERN.exec(segment);
+ if (min_match) {
+ return `@media (width >= ${min_match[1]})`;
+ }
+
+ const max_match = ARBITRARY_MAX_WIDTH_PATTERN.exec(segment);
+ if (max_match) {
+ return `@media (width < ${max_match[1]})`;
+ }
+
+ return null;
+};
+
+/**
+ * Parses a parameterized state modifier (nth-child, nth-last-child, nth-of-type, nth-last-of-type).
+ *
+ * @returns Object with name (including parameter) and CSS, or null if not parameterized
+ */
+export const parse_parameterized_state = (
+ segment: string,
+): {name: string; css: string; type: 'state'} | null => {
+ const nth_child_match = NTH_CHILD_PATTERN.exec(segment);
+ if (nth_child_match) {
+ return {
+ name: segment,
+ css: `:nth-child(${nth_child_match[1]})`,
+ type: 'state',
+ };
+ }
+
+ const nth_last_child_match = NTH_LAST_CHILD_PATTERN.exec(segment);
+ if (nth_last_child_match) {
+ return {
+ name: segment,
+ css: `:nth-last-child(${nth_last_child_match[1]})`,
+ type: 'state',
+ };
+ }
+
+ const nth_of_type_match = NTH_OF_TYPE_PATTERN.exec(segment);
+ if (nth_of_type_match) {
+ return {
+ name: segment,
+ css: `:nth-of-type(${nth_of_type_match[1]})`,
+ type: 'state',
+ };
+ }
+
+ const nth_last_of_type_match = NTH_LAST_OF_TYPE_PATTERN.exec(segment);
+ if (nth_last_of_type_match) {
+ return {
+ name: segment,
+ css: `:nth-last-of-type(${nth_last_of_type_match[1]})`,
+ type: 'state',
+ };
+ }
+
+ return null;
+};
+
+/**
+ * Gets the modifier definition for a segment.
+ * Handles both static modifiers and dynamic patterns (arbitrary breakpoints, parameterized states).
+ *
+ * @returns The modifier definition or null if not a known modifier
+ */
+export const get_modifier = (
+ segment: string,
+): (ModifierDefinition & {is_arbitrary?: boolean}) | null => {
+ // Check static modifiers first
+ const media = MEDIA_MODIFIERS.get(segment);
+ if (media) return media;
+
+ const ancestor = ANCESTOR_MODIFIERS.get(segment);
+ if (ancestor) return ancestor;
+
+ const state = STATE_MODIFIERS.get(segment);
+ if (state) return state;
+
+ const pseudo = PSEUDO_ELEMENT_MODIFIERS.get(segment);
+ if (pseudo) return pseudo;
+
+ // Check arbitrary breakpoints
+ const arbitrary_css = parse_arbitrary_breakpoint(segment);
+ if (arbitrary_css) {
+ return {
+ name: segment,
+ type: 'media',
+ css: arbitrary_css,
+ is_arbitrary: true,
+ };
+ }
+
+ // Check parameterized state modifiers
+ const parameterized = parse_parameterized_state(segment);
+ if (parameterized) {
+ return {
+ ...parameterized,
+ is_arbitrary: true,
+ };
+ }
+
+ return null;
+};
+
+/**
+ * Gets all modifier names for error message suggestions.
+ */
+export const get_all_modifier_names = (): Array => {
+ return Array.from(ALL_MODIFIER_NAMES).sort();
+};
diff --git a/src/lib/style.css b/src/lib/style.css
index 343237556..aa990eb8e 100644
--- a/src/lib/style.css
+++ b/src/lib/style.css
@@ -1,6 +1,6 @@
/*
-A CSS Fuz CSS theme file like `@fuzdev/fuz_css/theme.css`
+A CSS fuz_css theme file like `@fuzdev/fuz_css/theme.css`
is expected to be imported alongside this one
containing the base variable declarations.
By default that's the `'base'` theme's variables,
@@ -11,7 +11,7 @@ Some notes:
- acts as a CSS reset and also sets minimal defaults for elements with themable variables
- uses `:where` on all selectors with more specificity than a single tag/class
to lower specificity so utility classes override them -
- this also means the Fuz CSS stylesheet is is less likely to
+ this also means the fuz_css stylesheet is is less likely to
interfere with the page's styles regardless of where it's imported
- `.unstyled` class opts out of opinionated styling (colors, borders, decorative properties)
while keeping normalizations (font inheritance, border-collapse) -
@@ -136,8 +136,7 @@ Respects `hidden="until-found"` for find-in-page support.
:where(:is(h1, h2, h3, h4, h5, h6, .heading):not(.unstyled)) {
font-family: var(--font_family_serif);
- font-size: var(--font_size, var(--font_size_md));
- font-weight: var(--font_weight);
+ font-size: var(--font_size, inherit);
line-height: var(--line_height_sm);
text-wrap: balance; /* @see https://developer.mozilla.org/en-US/docs/Web/CSS/text-wrap#balance */
/* TODO use this pattern elsewhere? provides API to components like `MdnLogo` */
@@ -146,38 +145,38 @@ Respects `hidden="until-found"` for find-in-page support.
:where(h1:not(.unstyled)) {
--font_size: var(--font_size_xl3);
- --font_weight: 300;
+ font-weight: 300;
margin-bottom: var(--space_xl5);
/* TODO strange to omit only this one, but seems to be generally my desired behavior */
/* margin-top: var(--space_xl7); */
}
:where(h2:not(.unstyled)) {
--font_size: var(--font_size_xl2);
- --font_weight: 400;
+ font-weight: 400;
margin-bottom: var(--space_xl4);
margin-top: var(--space_xl6);
}
:where(h3:not(.unstyled)) {
--font_size: var(--font_size_xl);
- --font_weight: 500;
+ font-weight: 500;
margin-bottom: var(--space_xl3);
margin-top: var(--space_xl5);
}
:where(h4:not(.unstyled)) {
--font_size: var(--font_size_lg);
- --font_weight: 700;
+ font-weight: 700;
margin-bottom: var(--space_xl2);
margin-top: var(--space_xl4);
}
:where(h5:not(.unstyled)) {
--font_size: var(--font_size_md);
- --font_weight: 900;
+ font-weight: 900;
margin-bottom: var(--space_xl);
margin-top: var(--space_xl3);
}
:where(h6:not(.unstyled)) {
--font_size: var(--font_size_sm);
- --font_weight: 600;
+ font-weight: 600;
margin-bottom: var(--space_lg);
margin-top: var(--space_xl2);
text-transform: uppercase;
@@ -490,7 +489,6 @@ for disabled colors without needing a wrapper .disabled class */
}
:where(label.row:not(.unstyled)) {
justify-content: flex-start;
- align-items: center;
}
:where(label.row:not(.unstyled) :is(input[type='checkbox'], input[type='radio']):not(.unstyled)) {
margin-right: var(--space_md);
diff --git a/src/lib/variable_data.ts b/src/lib/variable_data.ts
index bf3886445..a498d2ff4 100644
--- a/src/lib/variable_data.ts
+++ b/src/lib/variable_data.ts
@@ -76,8 +76,8 @@ export const shadow_variant_prefixes = [
'shadow_inset_bottom_',
] as const;
-export type ShadowSizeVariant = ArrayElement;
-export const shadow_font_size_variants = ['xs', 'sm', 'md', 'lg', 'xl'] as const;
+export type ShadowSizeVariant = ArrayElement;
+export const shadow_size_variants = ['xs', 'sm', 'md', 'lg', 'xl'] as const;
export type ShadowAlphaVariant = ArrayElement;
export const shadow_alpha_variants = [1, 2, 3, 4, 5] as const;
@@ -102,117 +102,19 @@ export const icon_sizes = {
export type ColorVariant = ArrayElement;
export const color_variants = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'] as const;
+// TODO maybe add 0 and 10?
+export type IntensityVariant = ArrayElement;
+export const intensity_variants = [1, 2, 3, 4, 5, 6, 7, 8, 9] as const;
+
+export type BorderColorIntensityVariant = ArrayElement;
+export const border_color_intensity_variants = [1, 2, 3, 4, 5] as const;
+
export type BorderWidthVariant = ArrayElement;
export const border_width_variants = [1, 2, 3, 4, 5, 6, 7, 8, 9] as const;
export type OutlineWidthVariant = ArrayElement;
export const outline_width_variants = ['focus', 'active'] as const;
-export type AlignmentValue = ArrayElement;
-export const alignment_values = ['center', 'start', 'end', 'baseline', 'stretch'] as const;
-
-export type JustifyValue = ArrayElement;
-export const justify_values = [
- 'center',
- 'start',
- 'end',
- 'left',
- 'right',
- 'space-between',
- 'space-around',
- 'space-evenly',
- 'stretch',
-] as const;
-
-export type OverflowValue = ArrayElement;
-export const overflow_values = ['auto', 'hidden', 'scroll', 'clip', 'visible'] as const;
-
-export type BorderStyleValue = ArrayElement;
-export const border_style_values = [
- 'none',
- 'hidden',
- 'dotted',
- 'dashed',
- 'solid',
- 'double',
- 'groove',
- 'ridge',
- 'inset',
- 'outset',
-] as const;
-
-export type DisplayValue = ArrayElement;
-export const display_values = [
- 'none',
- 'contents',
- 'block',
- 'flow-root',
- 'inline',
- 'inline-block',
- 'run-in',
- 'list-item',
- 'inline list-item',
- 'flex',
- 'inline-flex',
- 'grid',
- 'inline-grid',
- 'ruby',
- 'block ruby',
- 'table',
- 'inline-table',
-] as const;
-
-export type TextAlignValue = ArrayElement;
-export const text_align_values = [
- 'start',
- 'end',
- 'left',
- 'right',
- 'center',
- 'justify',
- 'justify-all',
- 'match-parent',
-] as const;
-
-export type VerticalAlignValue = ArrayElement;
-export const vertical_align_values = [
- 'baseline',
- 'sub',
- 'super',
- 'text-top',
- 'text-bottom',
- 'middle',
- 'top',
- 'bottom',
-] as const;
-
-export type WordBreakValue = ArrayElement;
-export const word_break_values = ['normal', 'break-all', 'keep-all'] as const;
-
-export type PositionValue = ArrayElement;
-export const position_values = ['static', 'relative', 'absolute', 'fixed', 'sticky'] as const;
-
-export type VisibilityValue = ArrayElement;
-export const visibility_values = ['visible', 'hidden', 'collapse'] as const;
-
-export type FloatValue = ArrayElement;
-export const float_values = ['none', 'left', 'right', 'inline-start'] as const;
-
-export type FlexWrapValue = ArrayElement;
-export const flex_wrap_values = ['nowrap', 'wrap', 'wrap-reverse'] as const;
-
-export type FlexDirectionValue = ArrayElement;
-export const flex_direction_values = ['row', 'row-reverse', 'column', 'column-reverse'] as const;
-
-export type OverflowWrapValue = ArrayElement;
-export const overflow_wrap_values = ['normal', 'anywhere', 'break-word'] as const;
-
-export type ScrollbarWidthValue = ArrayElement;
-export const scrollbar_width_values = ['auto', 'thin', 'none'] as const;
-
-export type ScrollbarGutterValue = ArrayElement;
-export const scrollbar_gutter_values = ['auto', 'stable', 'stable both-edges'] as const;
-
/**
* Maximum value for CSS z-index property (32-bit signed integer max).
*/
diff --git a/src/lib/vite_plugin_fuz_css.ts b/src/lib/vite_plugin_fuz_css.ts
new file mode 100644
index 000000000..306f5e7c5
--- /dev/null
+++ b/src/lib/vite_plugin_fuz_css.ts
@@ -0,0 +1,508 @@
+/**
+ * Vite plugin for fuz_css utility class generation.
+ *
+ * Uses Vite's transform hook to extract CSS classes from source files
+ * as they're processed, including node_modules dependencies.
+ * Generates CSS on-demand via virtual module with HMR support.
+ *
+ * @example
+ * ```ts
+ * // vite.config.ts
+ * import {defineConfig} from 'vite';
+ * import jsx from 'acorn-jsx';
+ * import {vite_plugin_fuz_css} from '@fuzdev/fuz_css/vite_plugin_fuz_css.js';
+ *
+ * export default defineConfig({
+ * plugins: [
+ * vite_plugin_fuz_css({
+ * acorn_plugins: [jsx()],
+ * }),
+ * ],
+ * });
+ * ```
+ *
+ * @module
+ */
+
+import type {Plugin, ViteDevServer} from 'vite';
+import {join} from 'node:path';
+
+import {extract_css_classes_with_locations, type AcornPlugin} from './css_class_extractor.js';
+import {type Diagnostic, CssGenerationError} from './diagnostics.js';
+import {
+ generate_classes_css,
+ type CssClassDefinition,
+ type CssClassDefinitionInterpreter,
+} from './css_class_generation.js';
+import {css_class_definitions} from './css_class_definitions.js';
+import {css_class_interpreters} from './css_class_interpreters.js';
+import {load_css_properties} from './css_literal.js';
+import {
+ DEFAULT_CACHE_DIR,
+ get_cache_path,
+ load_cached_extraction,
+ save_cached_extraction,
+ delete_cached_extraction,
+ from_cached_extraction,
+ compute_hash,
+} from './css_cache.js';
+import {type FileFilter, filter_file_default} from './file_filter.js';
+import {CssClasses} from './css_classes.js';
+
+/* eslint-disable no-console */
+
+/** Marker comment used to delimit generated CSS in the output. */
+export const FUZ_CSS_MARKER = '/* generated by vite_plugin_fuz_css */';
+
+const VIRTUAL_ID = 'virtual:fuz.css';
+// In dev mode, resolve to .js so Vite treats it as JS (for HMR handling)
+// In build mode, resolve to .css for proper CSS bundling
+const RESOLVED_VIRTUAL_ID_JS = '\0virtual:fuz.css.js';
+const RESOLVED_VIRTUAL_ID_CSS = '\0virtual:fuz.css';
+
+// TODO investigate: Dev mode uses a JS wrapper that injects CSS and self-accepts HMR.
+// We couldn't get plain CSS with `css-update` to work for virtual modules - there's a
+// mismatch between `data-vite-dev-id` (set to `\0virtual:fuz.css`) and the importable
+// URL (`/@id/__x00__virtual:fuz.css`). UnoCSS uses `js-update` with `mod.url` for plain
+// CSS but that broke all HMR when we tried it. Areas to investigate:
+// - How does Vite's CSS HMR actually resolve virtual module URLs?
+// - Why does UnoCSS's approach work for them but not here?
+// - Is there a way to control what `data-vite-dev-id` gets set to?
+// The current JS wrapper approach works reliably, but plain CSS would be cleaner.
+
+/**
+ * Skip cache on CI (no point writing cache that won't be reused).
+ */
+const is_ci = !!process.env.CI;
+
+/**
+ * Options for the fuz_css Vite plugin.
+ */
+export interface VitePluginFuzCssOptions {
+ /**
+ * Filter function to determine which files to extract classes from.
+ * By default, extracts from .svelte, .html, .ts, .js, .tsx, .jsx files,
+ * excluding test files and .gen files.
+ */
+ filter_file?: FileFilter;
+ /**
+ * Additional class definitions to merge with defaults.
+ * User definitions take precedence over defaults with the same name.
+ * Required when `include_default_definitions` is `false`.
+ */
+ class_definitions?: Record;
+ /**
+ * Whether to include default class definitions (token and composite classes).
+ * When `false`, `class_definitions` is required.
+ * @default true
+ */
+ include_default_definitions?: boolean;
+ /**
+ * Custom interpreters for dynamic class generation.
+ * Replaces the builtin interpreters entirely if provided.
+ */
+ class_interpreters?: Array;
+ /**
+ * Classes to always include in the output, regardless of detection.
+ */
+ include_classes?: Iterable;
+ /**
+ * Classes to exclude from the output, even if detected.
+ */
+ exclude_classes?: Iterable;
+ /**
+ * Additional acorn plugins for parsing.
+ * Use `acorn-jsx` for React/Preact/Solid projects.
+ */
+ acorn_plugins?: Array;
+ /**
+ * How to handle CSS-literal errors during generation.
+ * - 'log': Log errors, skip invalid classes, continue
+ * - 'throw': Throw on first error, fail the build
+ * @default 'throw' in CI, 'log' otherwise
+ */
+ on_error?: 'log' | 'throw';
+ /**
+ * How to handle warnings during generation.
+ * - 'log': Log warnings, continue
+ * - 'throw': Throw on first warning, fail the build
+ * - 'ignore': Suppress warnings entirely
+ * @default 'log'
+ */
+ on_warning?: 'log' | 'throw' | 'ignore';
+ /**
+ * Cache directory relative to project root.
+ * @default DEFAULT_CACHE_DIR
+ */
+ cache_dir?: string;
+}
+
+/**
+ * Creates the fuz_css Vite plugin.
+ *
+ * Extracts CSS classes from source files during Vite's transform phase
+ * and generates optimized CSS via the `virtual:fuz.css` virtual module.
+ */
+export const vite_plugin_fuz_css = (options: VitePluginFuzCssOptions = {}): Plugin => {
+ const {
+ filter_file = filter_file_default,
+ class_definitions: user_class_definitions,
+ include_default_definitions = true,
+ class_interpreters = css_class_interpreters,
+ include_classes,
+ exclude_classes,
+ acorn_plugins,
+ on_error = is_ci ? 'throw' : 'log',
+ on_warning = 'log',
+ cache_dir = DEFAULT_CACHE_DIR,
+ } = options;
+
+ // Merge class definitions (user definitions take precedence)
+ if (!include_default_definitions && !user_class_definitions) {
+ throw new Error('class_definitions is required when include_default_definitions is false');
+ }
+ const all_class_definitions = include_default_definitions
+ ? user_class_definitions
+ ? {...css_class_definitions, ...user_class_definitions}
+ : css_class_definitions
+ : user_class_definitions!;
+
+ // Convert to Sets for efficient lookup
+ const include_set = include_classes ? new Set(include_classes) : null;
+ const exclude_set = exclude_classes ? new Set(exclude_classes) : null;
+
+ // Plugin state
+ const css_classes = new CssClasses(include_set, exclude_set);
+ const hashes: Map = new Map();
+ let virtual_module_loaded = false;
+ let server: ViteDevServer | null = null;
+ let css_properties: Set | null = null;
+ let resolved_cache_dir: string | null = null;
+ let project_root: string | null = null;
+ let hmr_timeout: ReturnType | null = null;
+ let last_generated_css: string | null = null;
+
+ /** Logs a warning message (works in both dev and build) */
+ const log_warn = (msg: string): void => {
+ if (server) {
+ server.config.logger.warn(msg);
+ } else {
+ console.warn(msg);
+ }
+ };
+
+ /** Logs an error message (works in both dev and build) */
+ const log_error = (msg: string): void => {
+ if (server) {
+ server.config.logger.error(msg);
+ } else {
+ console.error(msg);
+ }
+ };
+
+ /**
+ * Computes cache path for a file.
+ * Internal files use relative paths, external files use hashed absolute paths.
+ */
+ const get_file_cache_path = async (file_id: string): Promise => {
+ const is_internal = file_id.startsWith(project_root!);
+ return is_internal
+ ? get_cache_path(file_id, resolved_cache_dir!, project_root!)
+ : join(
+ resolved_cache_dir!,
+ '_external',
+ (await compute_hash(file_id)).slice(0, 16) + '.json',
+ );
+ };
+
+ /**
+ * Generates CSS from the current classes.
+ */
+ const generate_css = (): string => {
+ const {
+ all_classes: classes,
+ all_classes_with_locations: locations,
+ explicit_classes,
+ } = css_classes.get_all();
+
+ const result = generate_classes_css({
+ class_names: classes,
+ class_definitions: all_class_definitions,
+ interpreters: class_interpreters,
+ css_properties,
+ class_locations: locations,
+ explicit_classes,
+ });
+
+ // Collect all diagnostics: extraction + generation
+ const all_diagnostics: Array = [
+ ...css_classes.get_diagnostics(),
+ ...result.diagnostics,
+ ];
+
+ // Separate errors and warnings
+ const errors = all_diagnostics.filter((d) => d.level === 'error');
+ const warnings = all_diagnostics.filter((d) => d.level === 'warning');
+
+ // Handle warnings based on on_warning setting
+ if (warnings.length > 0) {
+ if (on_warning === 'throw') {
+ throw new CssGenerationError(warnings);
+ } else if (on_warning === 'log') {
+ for (const d of result.diagnostics.filter((d) => d.level === 'warning')) {
+ log_warn(`[fuz_css] ${d.class_name}: ${d.message}`);
+ }
+ }
+ // 'ignore' - do nothing
+ }
+
+ // Handle errors based on on_error setting
+ if (errors.length > 0) {
+ if (on_error === 'throw') {
+ throw new CssGenerationError(errors);
+ }
+ // Log errors (extraction diagnostics already logged in transform)
+ for (const d of result.diagnostics.filter((d) => d.level === 'error')) {
+ log_error(`[fuz_css] ${d.class_name}: ${d.message}`);
+ }
+ }
+
+ return `${FUZ_CSS_MARKER}\n\n${result.css}\n\n${FUZ_CSS_MARKER}`;
+ };
+
+ /**
+ * Invalidates the virtual module and triggers HMR.
+ * Debounced to avoid spamming updates when multiple files change rapidly.
+ * Only triggers if the generated CSS actually changed.
+ */
+ const invalidate_virtual_module = (): void => {
+ if (!server) return;
+
+ // Debounce: wait 10ms for more changes before triggering HMR
+ if (hmr_timeout) {
+ clearTimeout(hmr_timeout);
+ }
+ hmr_timeout = setTimeout(() => {
+ hmr_timeout = null;
+
+ // Check if CSS actually changed
+ const new_css = generate_css();
+ if (new_css === last_generated_css) {
+ return; // No change, skip HMR
+ }
+ last_generated_css = new_css;
+
+ const mod = server!.moduleGraph.getModuleById(RESOLVED_VIRTUAL_ID_JS);
+ if (mod) {
+ server!.moduleGraph.invalidateModule(mod);
+ // TODO investigate: This hardcoded path matches Vite's URL encoding for virtual
+ // modules. Using `mod.url` doesn't work (it's `\0virtual:fuz.css.js`). Could break
+ // if Vite changes their encoding scheme. Is there a proper API for this?
+ const hmr_path = '/@id/__x00__virtual:fuz.css.js';
+ const hot = server?.hot ?? server!.ws;
+ hot.send({
+ type: 'update',
+ updates: [
+ {
+ type: 'js-update',
+ path: hmr_path,
+ acceptedPath: hmr_path,
+ timestamp: Date.now(),
+ },
+ ],
+ });
+ }
+ }, 10);
+ };
+
+ return {
+ name: 'vite-plugin-fuz-css',
+ // Run before other plugins (like Svelte) to see original source files
+ enforce: 'pre',
+
+ configResolved(resolved_config) {
+ const root = resolved_config.root;
+ project_root = root.endsWith('/') ? root : root + '/';
+ resolved_cache_dir = join(root, cache_dir);
+ },
+
+ configureServer(dev_server) {
+ server = dev_server;
+
+ // Handle file deletion - watcher 'unlink' event
+ dev_server.watcher.on('unlink', (file) => {
+ if (hashes.has(file)) {
+ css_classes.delete(file);
+ hashes.delete(file);
+
+ // Delete cache file (fire and forget)
+ if (!is_ci && resolved_cache_dir && project_root) {
+ get_file_cache_path(file)
+ .then((cache_path) => delete_cached_extraction(cache_path))
+ .catch(() => {
+ // Ignore cache deletion errors
+ });
+ }
+
+ if (virtual_module_loaded) {
+ invalidate_virtual_module();
+ }
+ }
+ });
+ },
+
+ async buildStart() {
+ // Load CSS properties for validation
+ css_properties = await load_css_properties();
+ },
+
+ resolveId(id) {
+ if (id === VIRTUAL_ID) {
+ // In dev mode, resolve to .js for HMR support
+ // In build mode, resolve to .css for proper bundling
+ return server ? RESOLVED_VIRTUAL_ID_JS : RESOLVED_VIRTUAL_ID_CSS;
+ }
+ return undefined;
+ },
+
+ load(id) {
+ // Dev mode: JS module that injects CSS and handles HMR
+ if (id === RESOLVED_VIRTUAL_ID_JS) {
+ virtual_module_loaded = true;
+ const css = generate_css();
+ last_generated_css = css; // Track for HMR diffing
+ const escaped_css = JSON.stringify(css);
+ return `
+const css = ${escaped_css};
+
+// Find existing style tag or create new one
+let style = document.querySelector('style[data-fuz-css]');
+if (!style) {
+ style = document.createElement('style');
+ style.setAttribute('data-fuz-css', '');
+ document.head.appendChild(style);
+}
+style.textContent = css;
+
+if (import.meta.hot) {
+ import.meta.hot.accept();
+}
+
+export {};
+`;
+ }
+ // Build mode: plain CSS
+ if (id === RESOLVED_VIRTUAL_ID_CSS) {
+ virtual_module_loaded = true;
+ // Return empty CSS for build - generateBundle will append the complete CSS
+ return '/* fuz_css placeholder */';
+ }
+ return undefined;
+ },
+
+ generateBundle(_options, bundle) {
+ // Regenerate CSS with all extracted classes and append to the CSS asset
+ // This runs after all transforms are complete, so all classes are available
+ const generated_css = generate_css();
+
+ // Find the main CSS asset and append our generated CSS
+ // Vite combines all CSS into one or more asset files
+ for (const chunk of Object.values(bundle)) {
+ if (
+ chunk.type === 'asset' &&
+ typeof chunk.source === 'string' &&
+ chunk.fileName.endsWith('.css')
+ ) {
+ // Append the complete generated CSS to the end
+ chunk.source = chunk.source + '\n' + generated_css;
+ break; // Only append to first CSS asset
+ }
+ }
+ },
+
+ async transform(code, id) {
+ // Skip non-matching files
+ if (!filter_file(id)) {
+ return null;
+ }
+
+ // Compute content hash
+ const hash = await compute_hash(code);
+ const existing_hash = hashes.get(id);
+
+ // Check if unchanged
+ if (existing_hash === hash) {
+ return null;
+ }
+
+ // Try cache (if not CI and we have cache dir)
+ if (!is_ci && resolved_cache_dir && project_root) {
+ const cache_path = await get_file_cache_path(id);
+ const cached = await load_cached_extraction(cache_path);
+
+ if (cached?.content_hash === hash) {
+ // Cache hit
+ const {classes, explicit_classes, diagnostics} = from_cached_extraction(cached);
+ css_classes.add(id, classes, explicit_classes, diagnostics);
+ hashes.set(id, hash);
+
+ if (virtual_module_loaded) {
+ invalidate_virtual_module();
+ }
+ return null;
+ }
+ }
+
+ // Extract classes
+ const result = extract_css_classes_with_locations(code, {
+ filename: id,
+ acorn_plugins,
+ });
+
+ // Log extraction diagnostics
+ if (result.diagnostics) {
+ for (const d of result.diagnostics) {
+ const loc = `${d.location.file}:${d.location.line}:${d.location.column}`;
+ const msg = `[fuz_css] ${loc}: ${d.message}`;
+ if (d.level === 'error') {
+ log_error(msg);
+ } else if (on_warning === 'log') {
+ log_warn(msg);
+ }
+ // 'ignore' and 'throw' (handled at generation time) - don't log here
+ }
+ }
+
+ // Update CssClasses
+ css_classes.add(id, result.classes, result.explicit_classes, result.diagnostics);
+ hashes.set(id, hash);
+
+ // Save to cache (fire and forget - don't block transform)
+ if (!is_ci && resolved_cache_dir && project_root) {
+ get_file_cache_path(id)
+ .then((cache_path) =>
+ save_cached_extraction(
+ cache_path,
+ hash,
+ result.classes,
+ result.explicit_classes,
+ result.diagnostics,
+ ),
+ )
+ .catch(() => {
+ // Ignore cache errors
+ });
+ }
+
+ // Trigger HMR if virtual module already loaded
+ if (virtual_module_loaded) {
+ invalidate_virtual_module();
+ }
+
+ return null;
+ },
+
+ // Note: handleHotUpdate not needed - transform hook handles file changes,
+ // and configureServer's watcher.on('unlink') handles file deletion
+ };
+};
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
index b3d649d45..ce3a35856 100644
--- a/src/routes/+layout.svelte
+++ b/src/routes/+layout.svelte
@@ -28,7 +28,7 @@
- Fuz CSS - magical organic stylesheets
+ fuz_css - CSS with more utility
@@ -40,7 +40,7 @@
-{#if typeof icon === 'string'}{icon}{:else}{@render icon()}{/if}
{#if children}{@render children()}{:else}{final_path}{/if}
diff --git a/src/routes/FontSizeControl.svelte b/src/routes/FontSizeControl.svelte
index 4cf86f737..a525c9092 100644
--- a/src/routes/FontSizeControl.svelte
+++ b/src/routes/FontSizeControl.svelte
@@ -23,14 +23,14 @@