diff --git a/AGENTS.md b/AGENTS.md index 43f9cd4..532c51a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -114,8 +114,12 @@ scoped-путям (`kit/**`, `preview/**`). `hct()` (эммит в sRGB-hex делает postcss-плагин `web/scripts/postcss-hct.mjs`) и только в preview.css. - Размеры: `--space-*`, `--text-*` (font-size), `--radius-*`, `--size-*`. -- Брейкпоинты: `--bp-mobile` (640px), `--bp-tablet` (800px), `--bp-desktop` - (1100px) — mobile-first. +- Брейкпоинты: `@custom-media --bp-mobile (max-width: 640px)` / + `--bp-tablet (800px)` / `--bp-desktop (1100px)` — объявляются в preview.css, + используются как `@media (--bp-*)`. CSS-переменные в `@media` не работают, + поэтому отдельных `--bp-*` токенов нет; раскрытие делает postcss-плагин + `postcss-custom-media` (конфиг `web/postcss.config.js`, определения + подтягиваются через `@csstools/postcss-global-data`). - z-index: `--z-*`; длительности/анимации: `--duration-*`, `--ease-*`. - Правило `custom-property-pattern` в stylelint тестирует паттерн **без** `--` (`--x` → `x`), а `declaration-property-value-disallowed-list` — целиком с `--`. diff --git a/web/eslint-plugins/design-tokens/no-hardcoded-in-svelte.js b/web/eslint-plugins/design-tokens/no-hardcoded-in-svelte.js index b284f19..9053fb7 100644 --- a/web/eslint-plugins/design-tokens/no-hardcoded-in-svelte.js +++ b/web/eslint-plugins/design-tokens/no-hardcoded-in-svelte.js @@ -36,7 +36,9 @@ export default { hardcodedZIndex: "Hardcoded z-index '{{value}}'. Use a var(--z-...) token.", hardcodedBreakpoint: - "Hardcoded breakpoint '{{value}}' in @media. Use a var(--bp-...) token.", + "Hardcoded breakpoint '{{value}}' in @media. Use the @custom-media name instead: declare `@custom-media --bp-* (...)` in preview.css and write `@media (--bp-*)`.", + varInMedia: + "var() inside @media '{{value}}'. Custom properties do not resolve in media queries — declare `@custom-media --bp-* (...)` in preview.css and use `@media (--bp-*)`.", colorMix: "color-mix() in component ({{value}}). Tokenize the result in the design CSS file.", }, @@ -69,6 +71,10 @@ export default { const BREAKPOINT_RE = /(?:(?:max|min)-width)\s*:\s*(?!var\()(\d+(?:\.\d+)?(?:px|rem|em))/gi; + // Custom properties do not resolve inside @media conditions, so any + // var(--bp-*) inside @media is a latent bug — the block never applies. + const VAR_IN_MEDIA_RE = /var\(\s*(--[a-zA-Z0-9-]+)/g; + // Sub-rule 6: color-mix() is banned anywhere in a component style. const COLOR_MIX_RE = /color-mix\s*\(/gi; @@ -80,6 +86,10 @@ export default { while ((m = BREAKPOINT_RE.exec(params)) !== null) { report(atRule, "hardcodedBreakpoint", { value: m[1] }); } + VAR_IN_MEDIA_RE.lastIndex = 0; + while ((m = VAR_IN_MEDIA_RE.exec(params)) !== null) { + report(atRule, "varInMedia", { value: m[1] }); + } }); // --- declarations --- // diff --git a/web/package.json b/web/package.json index 94a5ea7..64e4291 100644 --- a/web/package.json +++ b/web/package.json @@ -23,6 +23,7 @@ "refs-dom-audit": "node scripts/audit-dom.mjs" }, "devDependencies": { + "@csstools/postcss-global-data": "^4.0.0", "@eslint/js": "^10.0.1", "@panmdaa/colors": "^0.4.2", "@sveltejs/adapter-static": "^3.0.10", @@ -35,6 +36,7 @@ "globals": "^17.11.0", "playwright": "^1.62.1", "postcss": "^8.5.26", + "postcss-custom-media": "^12.0.1", "prettier": "^3.9.6", "prettier-plugin-svelte": "^4.1.1", "stylelint": "^17.14.1", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 826dc8b..43bd4c4 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -18,6 +18,9 @@ importers: specifier: ^1.33.0 version: 1.33.0(svelte@5.56.9(@typescript-eslint/types@8.67.0)) devDependencies: + '@csstools/postcss-global-data': + specifier: ^4.0.0 + version: 4.0.0(postcss@8.5.26) '@eslint/js': specifier: ^10.0.1 version: 10.0.1(eslint@10.9.0(supports-color@10.2.2)) @@ -54,6 +57,9 @@ importers: postcss: specifier: ^8.5.26 version: 8.5.26 + postcss-custom-media: + specifier: ^12.0.1 + version: 12.0.1(postcss@8.5.26) prettier: specifier: ^3.9.6 version: 3.9.6 @@ -104,6 +110,13 @@ packages: '@cacheable/utils@2.5.0': resolution: {integrity: sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==} + '@csstools/cascade-layer-name-parser@3.0.0': + resolution: {integrity: sha512-/3iksyevwRfSJx5yH0RkcrcYXwuhMQx3Juqf40t97PeEy2/Mz2TItZ/z/216qpe4GgOyFBP8MKIwVvytzHmfIQ==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + '@csstools/css-calc@3.3.0': resolution: {integrity: sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==} engines: {node: '>=20.19.0'} @@ -136,6 +149,12 @@ packages: '@csstools/css-parser-algorithms': ^4.0.0 '@csstools/css-tokenizer': ^4.0.0 + '@csstools/postcss-global-data@4.0.0': + resolution: {integrity: sha512-mPKrL3Tzt8k1V+hTkU8XTH6JKRekB97oKHv/MekC9nfmRa+gMf1swlHRt8YK722sYN4xOipl17FZst99jsScLA==} + engines: {node: '>=20.19.0'} + peerDependencies: + postcss: ^8.4 + '@csstools/selector-resolve-nested@4.0.1': resolution: {integrity: sha512-j3vdQu0XwLME5qOTWxm8cnmvsf423R2YL6DbKklCHZwkDm7UdKNu6RPlw4REIJhSlKBICY3B70/7QZdicLqZgg==} engines: {node: '>=20.19.0'} @@ -1176,6 +1195,12 @@ packages: engines: {node: '>=20'} hasBin: true + postcss-custom-media@12.0.1: + resolution: {integrity: sha512-66syE14+VeqkUf0rRX0bvbTCbNRJF132jD+ceo8th1dap2YJEAqpdh5uG98CE3IbgHT7m9XM0GIlOazNWqQdeA==} + engines: {node: '>=20.19.0'} + peerDependencies: + postcss: ^8.4 + postcss-load-config@3.1.4: resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} engines: {node: '>= 10'} @@ -1582,6 +1607,11 @@ snapshots: hashery: 1.5.1 keyv: 5.6.0 + '@csstools/cascade-layer-name-parser@3.0.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + '@csstools/css-calc@3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': dependencies: '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) @@ -1602,6 +1632,10 @@ snapshots: '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 + '@csstools/postcss-global-data@4.0.0(postcss@8.5.26)': + dependencies: + postcss: 8.5.26 + '@csstools/selector-resolve-nested@4.0.1(postcss-selector-parser@7.1.5)': dependencies: postcss-selector-parser: 7.1.5 @@ -2520,6 +2554,14 @@ snapshots: optionalDependencies: fsevents: 2.3.2 + postcss-custom-media@12.0.1(postcss@8.5.26): + dependencies: + '@csstools/cascade-layer-name-parser': 3.0.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + '@csstools/media-query-list-parser': 5.0.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + postcss: 8.5.26 + postcss-load-config@3.1.4(postcss@8.5.26): dependencies: lilconfig: 2.1.0 diff --git a/web/postcss.config.js b/web/postcss.config.js index b5e58e5..7595293 100644 --- a/web/postcss.config.js +++ b/web/postcss.config.js @@ -1,5 +1,17 @@ import postcssHct from "./scripts/postcss-hct.mjs"; +import postcssGlobalData from "@csstools/postcss-global-data"; +import postcssCustomMedia from "postcss-custom-media"; export default { - plugins: [postcssHct], + plugins: [ + // Breakpoints live only in preview.css (next to the --bp-* tokens); + // global-data injects them so @media (--bp-*) expands in every file. + postcssGlobalData({ + files: ["src/preview.css"], + }), + postcssCustomMedia({ + preserve: false, + }), + postcssHct, + ], }; diff --git a/web/src/lib/components/kit/FileChip.svelte b/web/src/lib/components/kit/FileChip.svelte deleted file mode 100644 index de2a9ac..0000000 --- a/web/src/lib/components/kit/FileChip.svelte +++ /dev/null @@ -1,37 +0,0 @@ - - -
- - {name} - {#if size}{size}{/if} -
- - diff --git a/web/src/lib/components/kit/PipelineFooter.svelte b/web/src/lib/components/kit/PipelineFooter.svelte deleted file mode 100644 index ffd9ef2..0000000 --- a/web/src/lib/components/kit/PipelineFooter.svelte +++ /dev/null @@ -1,44 +0,0 @@ - - - - - diff --git a/web/src/lib/components/kit/PreviewPanel.svelte b/web/src/lib/components/kit/PreviewPanel.svelte deleted file mode 100644 index 8175a5f..0000000 --- a/web/src/lib/components/kit/PreviewPanel.svelte +++ /dev/null @@ -1,107 +0,0 @@ - - - - {#if resultUrl} - - {/if} - -
- {#if chainError} - - {/if} - {#if needsSource && !sourceImg} - - {:else if needsSource && sourceImg} -
-
- - diff --git a/web/src/lib/components/kit/PreviewStack.svelte b/web/src/lib/components/kit/PreviewStack.svelte deleted file mode 100644 index c113287..0000000 --- a/web/src/lib/components/kit/PreviewStack.svelte +++ /dev/null @@ -1,29 +0,0 @@ - - -
- {@render children()} -
- - diff --git a/web/src/lib/components/kit/PreviewTile.svelte b/web/src/lib/components/kit/PreviewTile.svelte deleted file mode 100644 index fb85a42..0000000 --- a/web/src/lib/components/kit/PreviewTile.svelte +++ /dev/null @@ -1,74 +0,0 @@ - - -
-
{@render children()}
-
- {label} - {caption} -
-
- - diff --git a/web/src/lib/components/kit/SectionLabel.svelte b/web/src/lib/components/kit/SectionLabel.svelte deleted file mode 100644 index 02d59aa..0000000 --- a/web/src/lib/components/kit/SectionLabel.svelte +++ /dev/null @@ -1,60 +0,0 @@ - - -
-
- {label} - {title}{#if meta}{meta}{/if} -
- {#if actions} - {#if unwrapActions} - {@render actions()} - {:else} -
- {@render actions()} -
- {/if} - {/if} -
- - diff --git a/web/src/lib/components/kit/WorkspaceHeader.svelte b/web/src/lib/components/kit/WorkspaceHeader.svelte deleted file mode 100644 index adfdc1d..0000000 --- a/web/src/lib/components/kit/WorkspaceHeader.svelte +++ /dev/null @@ -1,73 +0,0 @@ - - -
- {eyebrowA} / - {eyebrowB} -
-
-
-

{title}

- {#if lede}

{lede}

{/if} -
- {#if file} - - {/if} -
- - diff --git a/web/src/lib/components/kit/fields/FieldGrid.svelte b/web/src/lib/components/kit/fields/FieldGrid.svelte deleted file mode 100644 index ca4c4b2..0000000 --- a/web/src/lib/components/kit/fields/FieldGrid.svelte +++ /dev/null @@ -1,35 +0,0 @@ - - -
- {@render children()} -
- - diff --git a/web/src/lib/components/kit/fields/NumberField.svelte b/web/src/lib/components/kit/fields/NumberField.svelte deleted file mode 100644 index 424e47d..0000000 --- a/web/src/lib/components/kit/fields/NumberField.svelte +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - diff --git a/web/src/lib/components/kit/fields/ToggleRow.svelte b/web/src/lib/components/kit/fields/ToggleRow.svelte deleted file mode 100644 index 088cd0d..0000000 --- a/web/src/lib/components/kit/fields/ToggleRow.svelte +++ /dev/null @@ -1,23 +0,0 @@ - - -
- {label} - -
- - diff --git a/web/src/lib/components/kit/layout/PageGrid.svelte b/web/src/lib/components/kit/layout/PageGrid.svelte deleted file mode 100644 index a258e30..0000000 --- a/web/src/lib/components/kit/layout/PageGrid.svelte +++ /dev/null @@ -1,38 +0,0 @@ - - -
-
{@render settings()}
- {@render preview()} -
- - diff --git a/web/src/lib/components/kit/ui/TopNav.svelte b/web/src/lib/components/kit/ui/TopNav.svelte index fc6902c..86255ac 100644 --- a/web/src/lib/components/kit/ui/TopNav.svelte +++ b/web/src/lib/components/kit/ui/TopNav.svelte @@ -3,7 +3,6 @@