stylelint-plugin-rhythmguard 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/README.md +103 -21
- package/assets/campaign-slides/scene-1.png +0 -0
- package/assets/campaign-slides/scene-2.png +0 -0
- package/assets/campaign-slides/scene-3.png +0 -0
- package/assets/campaign-slides/scene-4.png +0 -0
- package/assets/campaign-slides/scene-5.png +0 -0
- package/assets/campaign-slides/scene-6.png +0 -0
- package/assets/rhythmguard-campaign-60s-template.html +322 -0
- package/assets/rhythmguard-campaign-60s.gif +0 -0
- package/assets/rhythmguard-campaign-60s.webm +0 -0
- package/package.json +59 -14
- package/src/configs/expanded.js +27 -0
- package/src/configs/expanded.mjs +4 -0
- package/src/configs/logical.js +16 -0
- package/src/configs/logical.mjs +4 -0
- package/src/configs/migration.js +31 -0
- package/src/configs/migration.mjs +4 -0
- package/src/configs/recommended.mjs +4 -0
- package/src/configs/strict.mjs +4 -0
- package/src/configs/tailwind.mjs +4 -0
- package/src/eslint/index.js +16 -0
- package/src/eslint/index.mjs +8 -0
- package/src/eslint/rules/tailwind-class-use-scale.js +206 -0
- package/src/index.js +4 -0
- package/src/index.mjs +10 -0
- package/src/presets/index.mjs +12 -0
- package/src/rules/no-offscale-transform/index.js +68 -17
- package/src/rules/no-offscale-transform/index.mjs +7 -0
- package/src/rules/prefer-token/index.js +97 -22
- package/src/rules/prefer-token/index.mjs +7 -0
- package/src/rules/use-scale/index.js +67 -18
- package/src/rules/use-scale/index.mjs +7 -0
- package/src/utils/constants.js +81 -13
- package/src/utils/length.js +49 -11
- package/src/utils/options.js +335 -18
- package/src/utils/token-map.js +358 -0
- package/src/utils/value-utils.js +89 -10
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,36 @@ The format follows Keep a Changelog principles and semantic versioning.
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [1.4.0] - 2026-02-21
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- New shared configs:
|
|
14
|
+
- `stylelint-plugin-rhythmguard/configs/expanded`
|
|
15
|
+
- `stylelint-plugin-rhythmguard/configs/logical`
|
|
16
|
+
- `stylelint-plugin-rhythmguard/configs/migration`
|
|
17
|
+
- New ESLint companion export: `stylelint-plugin-rhythmguard/eslint` with rule:
|
|
18
|
+
- `rhythmguard-tailwind/tailwind-class-use-scale`
|
|
19
|
+
- Token migration source automation for `rhythmguard/prefer-token`:
|
|
20
|
+
- `tokenMapFromCssCustomProperties`
|
|
21
|
+
- `tokenMapFile`
|
|
22
|
+
- `tokenMapFromTailwindSpacing` + `tailwindConfigPath`
|
|
23
|
+
- ESM wrapper entry points for package root, configs, presets, rules, and ESLint companion.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- Broadened scale enforcement model with built-in property groups:
|
|
28
|
+
- `spacing`, `radius`, `typography`, `size`
|
|
29
|
+
- New per-property override options:
|
|
30
|
+
- `propertyGroups`
|
|
31
|
+
- `propertyScales`
|
|
32
|
+
- New math-function targeting options:
|
|
33
|
+
- `mathFunctionArguments`
|
|
34
|
+
- `ignoreMathFunctionArguments`
|
|
35
|
+
- New `unitStrategy` option (`convert` or `exact`) for non-convertible unit workflows.
|
|
36
|
+
- Compatibility updated to support Stylelint `^16 || ^17`.
|
|
37
|
+
- Tailwind token-map extraction now supports `.js`, `.cjs`, and `.mjs` config files and merges `theme.spacing` with `theme.extend.spacing`.
|
|
38
|
+
|
|
9
39
|
## [1.3.0] - 2026-02-17
|
|
10
40
|
|
|
11
41
|
### Added
|
package/README.md
CHANGED
|
@@ -12,23 +12,22 @@ High-precision spacing governance for CSS and design systems.
|
|
|
12
12
|
[](./LICENSE)
|
|
13
13
|
[](https://nodejs.org/)
|
|
14
14
|
|
|
15
|
-
`stylelint-plugin-rhythmguard` enforces
|
|
15
|
+
`stylelint-plugin-rhythmguard` enforces scale and token discipline across spacing, radius, typography, size, and translate motion offsets.
|
|
16
16
|
|
|
17
|
-
##
|
|
17
|
+
## Demo
|
|
18
18
|
|
|
19
19
|
<p align="center">
|
|
20
|
-
<a href="
|
|
21
|
-
<img src="
|
|
20
|
+
<a href="./assets/rhythmguard-campaign-60s.webm">
|
|
21
|
+
<img src="./assets/rhythmguard-campaign-60s.gif" width="100%" alt="Rhythmguard 60-second demo" />
|
|
22
22
|
</a>
|
|
23
23
|
</p>
|
|
24
24
|
|
|
25
|
-
- Full video (WebM): [rhythmguard-campaign-60s.webm](https://raw.githubusercontent.com/petrilahdelma/stylelint-plugin-rhythmguard/main/assets/rhythmguard-campaign-60s.webm)
|
|
26
|
-
|
|
27
25
|
I built Rhythmguard after 20 years of watching teams ignore spacing scales and ship arbitrary pixel values everywhere.
|
|
28
26
|
|
|
29
27
|
It is built for teams that want:
|
|
30
28
|
|
|
31
29
|
- zero random spacing values in production CSS
|
|
30
|
+
- consistent numeric scales for radius, typography, and sizing primitives
|
|
32
31
|
- token-first spacing workflows
|
|
33
32
|
- predictable autofix behavior for large migrations
|
|
34
33
|
- consistent layout rhythm across web surfaces
|
|
@@ -79,11 +78,44 @@ npm install --save-dev stylelint stylelint-plugin-rhythmguard
|
|
|
79
78
|
}
|
|
80
79
|
```
|
|
81
80
|
|
|
81
|
+
### Expanded config
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"extends": ["stylelint-plugin-rhythmguard/configs/expanded"]
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`expanded` enables scale enforcement for spacing + radius + typography + size property groups.
|
|
90
|
+
|
|
91
|
+
### Logical config
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"extends": ["stylelint-plugin-rhythmguard/configs/logical"]
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`logical` composes Rhythmguard strict mode with `stylelint-plugin-logical-css` recommended rules.
|
|
100
|
+
|
|
101
|
+
### Migration config
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"extends": ["stylelint-plugin-rhythmguard/configs/migration"]
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`migration` keeps on-scale numeric values temporarily while auto-building token mappings from CSS custom properties and optional Tailwind spacing config.
|
|
110
|
+
|
|
82
111
|
Stable shared config entry points:
|
|
83
112
|
|
|
84
113
|
- `stylelint-plugin-rhythmguard/configs/recommended`
|
|
85
114
|
- `stylelint-plugin-rhythmguard/configs/strict`
|
|
86
115
|
- `stylelint-plugin-rhythmguard/configs/tailwind`
|
|
116
|
+
- `stylelint-plugin-rhythmguard/configs/expanded`
|
|
117
|
+
- `stylelint-plugin-rhythmguard/configs/logical`
|
|
118
|
+
- `stylelint-plugin-rhythmguard/configs/migration`
|
|
87
119
|
|
|
88
120
|
### Full custom setup
|
|
89
121
|
|
|
@@ -95,13 +127,22 @@ Stable shared config entry points:
|
|
|
95
127
|
true,
|
|
96
128
|
{
|
|
97
129
|
"preset": "rhythmic-4",
|
|
130
|
+
"propertyGroups": ["spacing", "radius"],
|
|
131
|
+
"propertyScales": {
|
|
132
|
+
"font-size": [12, 14, 16, 20, 24]
|
|
133
|
+
},
|
|
98
134
|
"units": ["px", "rem", "em"],
|
|
135
|
+
"unitStrategy": "convert",
|
|
99
136
|
"baseFontSize": 16,
|
|
100
137
|
"tokenPattern": "^--space-",
|
|
101
138
|
"tokenFunctions": ["var", "theme", "token"],
|
|
102
139
|
"allowNegative": true,
|
|
103
140
|
"allowPercentages": true,
|
|
104
|
-
"fixToScale": true
|
|
141
|
+
"fixToScale": true,
|
|
142
|
+
"enforceInsideMathFunctions": true,
|
|
143
|
+
"mathFunctionArguments": {
|
|
144
|
+
"clamp": [1, 3]
|
|
145
|
+
}
|
|
105
146
|
}
|
|
106
147
|
],
|
|
107
148
|
"rhythmguard/prefer-token": [
|
|
@@ -109,6 +150,9 @@ Stable shared config entry points:
|
|
|
109
150
|
{
|
|
110
151
|
"tokenPattern": "^--space-",
|
|
111
152
|
"allowNumericScale": false,
|
|
153
|
+
"tokenMapFromCssCustomProperties": true,
|
|
154
|
+
"tokenMapFromTailwindSpacing": true,
|
|
155
|
+
"tailwindConfigPath": "./tailwind.config.mjs",
|
|
112
156
|
"tokenMap": {
|
|
113
157
|
"4px": "var(--space-1)",
|
|
114
158
|
"8px": "var(--space-2)",
|
|
@@ -162,7 +206,9 @@ Rhythmguard validates `secondaryOptions` for each rule before linting declaratio
|
|
|
162
206
|
|
|
163
207
|
- Unknown option names fail fast with Stylelint invalid option warnings.
|
|
164
208
|
- Invalid option shapes fail fast (for example string vs array mismatches).
|
|
165
|
-
- `properties` string entries are validated against supported CSS
|
|
209
|
+
- `properties` string entries are validated against supported scale-targetable CSS property names.
|
|
210
|
+
- `propertyGroups` values are validated against built-in groups: `spacing`, `radius`, `typography`, and `size`.
|
|
211
|
+
- Math function argument maps are validated per function (`calc`, `clamp`, `min`, `max`) and positive 1-based argument indexes.
|
|
166
212
|
|
|
167
213
|
Example typo that now fails immediately:
|
|
168
214
|
|
|
@@ -263,6 +309,10 @@ Checks:
|
|
|
263
309
|
- `inset*`, `scroll-margin*`, `scroll-padding*`
|
|
264
310
|
- `translate`, `translate-x`, `translate-y`, `translate-z`
|
|
265
311
|
- `transform` translation functions (`translate`, `translateX`, `translateY`, `translateZ`, `translate3d`)
|
|
312
|
+
- optional property groups:
|
|
313
|
+
- `radius` (`border-radius*`, corner radii, `outline-offset`)
|
|
314
|
+
- `typography` (`font-size`, `line-height`, `letter-spacing`, `word-spacing`)
|
|
315
|
+
- `size` (`width`, `height`, min/max size, logical `inline-size`/`block-size`)
|
|
266
316
|
|
|
267
317
|
Example:
|
|
268
318
|
|
|
@@ -288,6 +338,7 @@ Options:
|
|
|
288
338
|
| `customScale` | `Array<number|string>` | `undefined` | Highest-priority custom scale override |
|
|
289
339
|
| `scale` | `Array<number|string>` | `[0,4,8,12,16,24,32,40,48,64]` | Allowed spacing values |
|
|
290
340
|
| `units` | `string[]` | `['px','rem','em']` | Units considered for scale enforcement |
|
|
341
|
+
| `unitStrategy` | `'convert' \| 'exact'` | `'convert'` | `convert`: compare via px conversion (`px/rem/em`). `exact`: compare against same-unit scale values (for example `vw`, `cqi`) |
|
|
291
342
|
| `baseFontSize` | `number` | `16` | Used for `rem`/`em` conversion |
|
|
292
343
|
| `tokenPattern` | `string` | `^--space-` | Regex for accepted token variable names |
|
|
293
344
|
| `tokenFunctions` | `string[]` | `['var','theme','token']` | Functions treated as tokenized values |
|
|
@@ -295,7 +346,11 @@ Options:
|
|
|
295
346
|
| `allowPercentages` | `boolean` | `true` | Allows `%` values without scale checks |
|
|
296
347
|
| `fixToScale` | `boolean` | `true` | Enables nearest-value autofix |
|
|
297
348
|
| `enforceInsideMathFunctions` | `boolean` | `false` | Lints `calc()/clamp()/min()/max()` internals |
|
|
349
|
+
| `mathFunctionArguments` | `Record<mathFn, number[]>` | `{}` | Restricts linting to specific 1-based argument indexes per math function |
|
|
350
|
+
| `ignoreMathFunctionArguments` | `Record<mathFn, number[]>` | `{}` | Excludes specific 1-based argument indexes per math function |
|
|
351
|
+
| `propertyGroups` | `Array<'spacing' \| 'radius' \| 'typography' \| 'size'>` | `['spacing']` | Selects built-in property groups when `properties` is not provided |
|
|
298
352
|
| `properties` | `Array<string|RegExp>` | built-in spacing patterns | Override targeted property set; string values must be supported spacing property names |
|
|
353
|
+
| `propertyScales` | `Record<propertyOrRegex, scaleOrPreset>` | `{}` | Per-property scale overrides (supports exact names or `/regex/flags` keys) |
|
|
299
354
|
|
|
300
355
|
### `rhythmguard/prefer-token`
|
|
301
356
|
|
|
@@ -328,10 +383,20 @@ Options:
|
|
|
328
383
|
| `customScale` | `Array<number|string>` | `undefined` | Highest-priority custom scale override |
|
|
329
384
|
| `scale` | `Array<number|string>` | `[0,4,8,12,16,24,32,40,48,64]` | Used when `allowNumericScale` is enabled |
|
|
330
385
|
| `baseFontSize` | `number` | `16` | Used for scale checks with `rem`/`em` |
|
|
386
|
+
| `unitStrategy` | `'convert' \| 'exact'` | `'convert'` | Matching strategy when `allowNumericScale` is enabled |
|
|
387
|
+
| `units` | `string[]` | `['px','rem','em']` | Units considered for numeric scale checks |
|
|
331
388
|
| `enforceInsideMathFunctions` | `boolean` | `false` | Lints `calc()/clamp()/min()/max()` internals |
|
|
389
|
+
| `mathFunctionArguments` | `Record<mathFn, number[]>` | `{}` | Restricts linting to specific 1-based argument indexes per math function |
|
|
390
|
+
| `ignoreMathFunctionArguments` | `Record<mathFn, number[]>` | `{}` | Excludes specific 1-based argument indexes per math function |
|
|
332
391
|
| `tokenMap` | `Record<string,string>` | `{}` | Enables autofix from raw value to token |
|
|
392
|
+
| `tokenMapFile` | `string` | `null` | JSON file path to merge additional token mappings |
|
|
393
|
+
| `tokenMapFromCssCustomProperties` | `boolean` | `false` | Auto-builds mappings from matching custom property declarations in the same stylesheet |
|
|
394
|
+
| `tokenMapFromTailwindSpacing` | `boolean` | `false` | Auto-builds mappings from `theme.spacing` and `theme.extend.spacing` in Tailwind config |
|
|
395
|
+
| `tailwindConfigPath` | `string` | `null` | Path to Tailwind config used by `tokenMapFromTailwindSpacing` (`.js`, `.cjs`, `.mjs`) |
|
|
333
396
|
| `ignoreValues` | `string[]` | CSS global keywords + `auto` | Skips keyword literals |
|
|
397
|
+
| `propertyGroups` | `Array<'spacing' \| 'radius' \| 'typography' \| 'size'>` | `['spacing']` | Selects built-in property groups when `properties` is not provided |
|
|
334
398
|
| `properties` | `Array<string|RegExp>` | built-in spacing patterns | Override targeted property set; string values must be supported spacing property names |
|
|
399
|
+
| `propertyScales` | `Record<propertyOrRegex, scaleOrPreset>` | `{}` | Per-property scale overrides for numeric migration mode |
|
|
335
400
|
|
|
336
401
|
### `rhythmguard/no-offscale-transform`
|
|
337
402
|
|
|
@@ -353,7 +418,7 @@ Example:
|
|
|
353
418
|
|
|
354
419
|
Options:
|
|
355
420
|
|
|
356
|
-
`rhythmguard/no-offscale-transform` accepts the same scale options as `rhythmguard/use-scale`, but only for transform translation properties. Its secondary options are also validated for unknown keys and invalid value shapes.
|
|
421
|
+
`rhythmguard/no-offscale-transform` accepts the same scale options as `rhythmguard/use-scale` (including `unitStrategy`, math argument targeting, and deterministic autofix), but only for transform translation properties. Its secondary options are also validated for unknown keys and invalid value shapes.
|
|
357
422
|
|
|
358
423
|
## Tailwind CSS Integration
|
|
359
424
|
|
|
@@ -371,7 +436,29 @@ Rhythmguard works well in Tailwind projects, but it enforces what Stylelint can
|
|
|
371
436
|
- `class="p-4 gap-2"`
|
|
372
437
|
- `class="p-[13px] translate-y-[18px]"`
|
|
373
438
|
|
|
374
|
-
Those are not Stylelint declaration nodes, so they are outside
|
|
439
|
+
Those are not Stylelint declaration nodes, so they are outside Stylelint rule scope.
|
|
440
|
+
|
|
441
|
+
### Companion ESLint layer for class strings
|
|
442
|
+
|
|
443
|
+
Rhythmguard now ships an ESLint companion export for class-string governance:
|
|
444
|
+
|
|
445
|
+
```js
|
|
446
|
+
// eslint.config.js (flat config)
|
|
447
|
+
import rhythmguard from 'stylelint-plugin-rhythmguard/eslint';
|
|
448
|
+
|
|
449
|
+
export default [
|
|
450
|
+
{
|
|
451
|
+
plugins: {
|
|
452
|
+
'rhythmguard-tailwind': rhythmguard,
|
|
453
|
+
},
|
|
454
|
+
rules: {
|
|
455
|
+
'rhythmguard-tailwind/tailwind-class-use-scale': ['error', { scale: [0, 4, 8, 12, 16, 24, 32] }],
|
|
456
|
+
},
|
|
457
|
+
},
|
|
458
|
+
];
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
This rule targets arbitrary spacing utilities such as `p-[13px]`, `gap-[18px]`, `translate-x-[10px]`, and autofixes to the nearest configured scale value.
|
|
375
462
|
|
|
376
463
|
### Recommended stack for full Tailwind enforcement
|
|
377
464
|
|
|
@@ -390,7 +477,8 @@ Suggested setup:
|
|
|
390
477
|
|
|
391
478
|
Then pair with:
|
|
392
479
|
|
|
393
|
-
- `
|
|
480
|
+
- `stylelint-plugin-rhythmguard/eslint` for arbitrary spacing class-string scale enforcement.
|
|
481
|
+
- `eslint-plugin-tailwindcss` for broader class-string linting and conventions.
|
|
394
482
|
- `prettier-plugin-tailwindcss` for deterministic class ordering.
|
|
395
483
|
|
|
396
484
|
Detailed setup reference: [`docs/TAILWIND.md`](https://github.com/PetriLahdelma/stylelint-plugin-rhythmguard/blob/main/docs/TAILWIND.md).
|
|
@@ -399,14 +487,7 @@ Detailed setup reference: [`docs/TAILWIND.md`](https://github.com/PetriLahdelma/
|
|
|
399
487
|
|
|
400
488
|
By default, `tokenFunctions` includes `theme`, so values like `theme(spacing.4)` are treated as tokenized values.
|
|
401
489
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
We should extend Tailwind coverage thoroughly, but in the right architecture:
|
|
405
|
-
|
|
406
|
-
- keep `stylelint-plugin-rhythmguard` focused on CSS declaration enforcement
|
|
407
|
-
- add a complementary Tailwind class-string layer (ESLint/plugin side) for utility classes
|
|
408
|
-
|
|
409
|
-
This avoids brittle parsing hacks and gives full coverage without compromising rule quality.
|
|
490
|
+
This keeps CSS declaration enforcement and template class-string enforcement separated but coordinated.
|
|
410
491
|
|
|
411
492
|
## Programmatic Presets
|
|
412
493
|
|
|
@@ -417,6 +498,7 @@ console.log(rhythmguard.presets.listScalePresetNames());
|
|
|
417
498
|
console.log(rhythmguard.presets.listCommunityScalePresetNames());
|
|
418
499
|
console.log(rhythmguard.presets.getCommunityScaleMetadata('product-decimal-10'));
|
|
419
500
|
console.log(rhythmguard.presets.scales['rhythmic-4']);
|
|
501
|
+
console.log(Object.keys(rhythmguard.eslint.rules));
|
|
420
502
|
```
|
|
421
503
|
|
|
422
504
|
## Autofix Philosophy
|
|
@@ -430,9 +512,9 @@ It will not guess token mappings without your map.
|
|
|
430
512
|
|
|
431
513
|
## Compatibility
|
|
432
514
|
|
|
433
|
-
- Stylelint: `^16.0.0`
|
|
515
|
+
- Stylelint: `^16.0.0 || ^17.0.0`
|
|
434
516
|
- Node.js: `>=18.18.0`
|
|
435
|
-
- Module format: CommonJS
|
|
517
|
+
- Module format: dual `require` + `import` entry points (CommonJS + ESM wrappers)
|
|
436
518
|
- Note: Stylelint `16.0.0` has known autofix/API behavior differences; CI enforces floor compatibility and runs non-blocking full-suite observability on the floor version.
|
|
437
519
|
|
|
438
520
|
## Development
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Rhythmguard 60s Campaign Frame</title>
|
|
7
|
+
<style>
|
|
8
|
+
@font-face {
|
|
9
|
+
font-family: "GeistPixel";
|
|
10
|
+
src: url("../node_modules/geist/dist/fonts/geist-pixel/GeistPixel-Line.woff2") format("woff2");
|
|
11
|
+
font-weight: 400;
|
|
12
|
+
font-style: normal;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
:root {
|
|
16
|
+
--bg: #000;
|
|
17
|
+
--fg: #fff;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
* {
|
|
21
|
+
box-sizing: border-box;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
html,
|
|
25
|
+
body {
|
|
26
|
+
width: 1920px;
|
|
27
|
+
height: 1080px;
|
|
28
|
+
margin: 0;
|
|
29
|
+
padding: 0;
|
|
30
|
+
overflow: hidden;
|
|
31
|
+
background: var(--bg);
|
|
32
|
+
color: var(--fg);
|
|
33
|
+
font-family: "GeistPixel", monospace;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
body {
|
|
37
|
+
padding: 28px;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.frame {
|
|
41
|
+
width: 100%;
|
|
42
|
+
height: 100%;
|
|
43
|
+
border: 4px solid var(--fg);
|
|
44
|
+
display: grid;
|
|
45
|
+
grid-template-rows: 118px 1fr 110px;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.header {
|
|
49
|
+
border-bottom: 2px solid var(--fg);
|
|
50
|
+
display: flex;
|
|
51
|
+
align-items: center;
|
|
52
|
+
justify-content: space-between;
|
|
53
|
+
padding: 0 30px;
|
|
54
|
+
font-size: 34px;
|
|
55
|
+
letter-spacing: 0.5px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.main {
|
|
59
|
+
display: grid;
|
|
60
|
+
grid-template-columns: minmax(0, 2.1fr) minmax(0, 1fr);
|
|
61
|
+
gap: 28px;
|
|
62
|
+
padding: 28px 30px;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.hero {
|
|
66
|
+
border: 2px solid var(--fg);
|
|
67
|
+
padding: 26px;
|
|
68
|
+
display: grid;
|
|
69
|
+
grid-template-rows: auto auto auto 1fr;
|
|
70
|
+
gap: 18px;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.kicker {
|
|
74
|
+
font-size: 30px;
|
|
75
|
+
letter-spacing: 0.8px;
|
|
76
|
+
margin: 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.title {
|
|
80
|
+
font-size: 96px;
|
|
81
|
+
letter-spacing: 1px;
|
|
82
|
+
line-height: 1;
|
|
83
|
+
margin: 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.subtitle {
|
|
87
|
+
font-size: 42px;
|
|
88
|
+
letter-spacing: 0.8px;
|
|
89
|
+
margin: 0;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.terminal {
|
|
93
|
+
border: 2px solid var(--fg);
|
|
94
|
+
display: grid;
|
|
95
|
+
grid-template-rows: 50px 1fr;
|
|
96
|
+
min-height: 360px;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.terminal-bar {
|
|
100
|
+
border-bottom: 2px solid var(--fg);
|
|
101
|
+
display: flex;
|
|
102
|
+
align-items: center;
|
|
103
|
+
justify-content: space-between;
|
|
104
|
+
padding: 0 16px;
|
|
105
|
+
font-size: 24px;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.terminal-dots {
|
|
109
|
+
display: flex;
|
|
110
|
+
gap: 10px;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.terminal-dot {
|
|
114
|
+
width: 12px;
|
|
115
|
+
height: 12px;
|
|
116
|
+
border: 2px solid var(--fg);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
pre {
|
|
120
|
+
margin: 0;
|
|
121
|
+
padding: 16px;
|
|
122
|
+
font-family: "GeistPixel", monospace;
|
|
123
|
+
font-size: 30px;
|
|
124
|
+
line-height: 1.45;
|
|
125
|
+
white-space: pre-wrap;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.side {
|
|
129
|
+
display: grid;
|
|
130
|
+
grid-template-rows: 1fr 1fr;
|
|
131
|
+
gap: 18px;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.card {
|
|
135
|
+
border: 2px solid var(--fg);
|
|
136
|
+
padding: 18px;
|
|
137
|
+
display: grid;
|
|
138
|
+
grid-template-rows: auto 1fr;
|
|
139
|
+
gap: 12px;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.card h2 {
|
|
143
|
+
margin: 0;
|
|
144
|
+
font-size: 36px;
|
|
145
|
+
letter-spacing: 0.8px;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.rules {
|
|
149
|
+
margin: 0;
|
|
150
|
+
padding-left: 24px;
|
|
151
|
+
font-size: 31px;
|
|
152
|
+
line-height: 1.5;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.meta {
|
|
156
|
+
margin: 0;
|
|
157
|
+
font-size: 30px;
|
|
158
|
+
line-height: 1.5;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.footer {
|
|
162
|
+
border-top: 2px solid var(--fg);
|
|
163
|
+
display: flex;
|
|
164
|
+
align-items: center;
|
|
165
|
+
justify-content: space-between;
|
|
166
|
+
padding: 0 30px;
|
|
167
|
+
font-size: 30px;
|
|
168
|
+
}
|
|
169
|
+
</style>
|
|
170
|
+
</head>
|
|
171
|
+
<body>
|
|
172
|
+
<div class="frame">
|
|
173
|
+
<header class="header">
|
|
174
|
+
<div id="step">STEP 1 / 6</div>
|
|
175
|
+
<div>Rhythmguard</div>
|
|
176
|
+
</header>
|
|
177
|
+
<main class="main">
|
|
178
|
+
<section class="hero">
|
|
179
|
+
<p class="kicker" id="kicker">INSTALL + STRICT CONFIG</p>
|
|
180
|
+
<h1 class="title" id="title">NO RANDOM 13PX.</h1>
|
|
181
|
+
<p class="subtitle" id="subtitle">SCALE OR TOKEN. PERIOD.</p>
|
|
182
|
+
<div class="terminal">
|
|
183
|
+
<div class="terminal-bar">
|
|
184
|
+
<div class="terminal-dots">
|
|
185
|
+
<span class="terminal-dot"></span>
|
|
186
|
+
<span class="terminal-dot"></span>
|
|
187
|
+
<span class="terminal-dot"></span>
|
|
188
|
+
</div>
|
|
189
|
+
<span id="terminal-title">terminal // setup</span>
|
|
190
|
+
</div>
|
|
191
|
+
<pre id="code"></pre>
|
|
192
|
+
</div>
|
|
193
|
+
</section>
|
|
194
|
+
<aside class="side">
|
|
195
|
+
<section class="card">
|
|
196
|
+
<h2>RULES</h2>
|
|
197
|
+
<ul class="rules">
|
|
198
|
+
<li>use-scale</li>
|
|
199
|
+
<li>prefer-token</li>
|
|
200
|
+
<li>no-offscale-transform</li>
|
|
201
|
+
</ul>
|
|
202
|
+
</section>
|
|
203
|
+
<section class="card">
|
|
204
|
+
<h2>WHY</h2>
|
|
205
|
+
<p class="meta" id="why"></p>
|
|
206
|
+
</section>
|
|
207
|
+
</aside>
|
|
208
|
+
</main>
|
|
209
|
+
<footer class="footer">
|
|
210
|
+
<div>stylelint-plugin-rhythmguard</div>
|
|
211
|
+
</footer>
|
|
212
|
+
</div>
|
|
213
|
+
|
|
214
|
+
<script>
|
|
215
|
+
const scenes = [
|
|
216
|
+
{
|
|
217
|
+
kicker: "INSTALL + STRICT CONFIG",
|
|
218
|
+
title: "NO RANDOM 13PX.",
|
|
219
|
+
subtitle: "SCALE OR TOKEN. PERIOD.",
|
|
220
|
+
terminalTitle: "terminal // setup",
|
|
221
|
+
code: [
|
|
222
|
+
"$ npm i -D stylelint stylelint-plugin-rhythmguard",
|
|
223
|
+
"$ cat .stylelintrc.json",
|
|
224
|
+
"{",
|
|
225
|
+
' "extends": ["stylelint-plugin-rhythmguard/configs/strict"]',
|
|
226
|
+
"}",
|
|
227
|
+
'$ npx stylelint "src/**/*.css"',
|
|
228
|
+
],
|
|
229
|
+
why: "Shared config replaces ad-hoc spacing rules across files.",
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
kicker: "RULE 1 // USE-SCALE",
|
|
233
|
+
title: "LOCK TO SCALE.",
|
|
234
|
+
subtitle: "EVERY SPACING VALUE GETS CHECKED.",
|
|
235
|
+
terminalTitle: "report // use-scale",
|
|
236
|
+
code: [
|
|
237
|
+
"Button.css:12",
|
|
238
|
+
" Expected spacing from configured scale.",
|
|
239
|
+
" ✕ margin-top: 13px",
|
|
240
|
+
" ✓ margin-top: 12px",
|
|
241
|
+
"",
|
|
242
|
+
"Autofix maps to nearest safe scale step.",
|
|
243
|
+
],
|
|
244
|
+
why: "Scale discipline keeps layouts predictable in large systems.",
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
kicker: "RULE 2 // PREFER-TOKEN",
|
|
248
|
+
title: "TOKENS FIRST.",
|
|
249
|
+
subtitle: "RAW PIXELS BECOME DESIGN TOKENS.",
|
|
250
|
+
terminalTitle: "report // prefer-token",
|
|
251
|
+
code: [
|
|
252
|
+
"Card.css:8",
|
|
253
|
+
" Expected token, not raw literal.",
|
|
254
|
+
" ✕ padding: 16px",
|
|
255
|
+
" ✓ padding: var(--space-4)",
|
|
256
|
+
"",
|
|
257
|
+
"tokenMap applies safe automatic replacements.",
|
|
258
|
+
],
|
|
259
|
+
why: "Token usage makes theme changes and audits low-risk.",
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
kicker: "RULE 3 // TRANSFORM OFFSETS",
|
|
263
|
+
title: "MOTION IN RHYTHM.",
|
|
264
|
+
subtitle: "TRANSLATES STAY ON THE SAME SCALE.",
|
|
265
|
+
terminalTitle: "report // no-offscale-transform",
|
|
266
|
+
code: [
|
|
267
|
+
"Toast.css:22",
|
|
268
|
+
" Off-scale translate value detected.",
|
|
269
|
+
" ✕ transform: translateY(7px)",
|
|
270
|
+
" ✓ transform: translateY(8px)",
|
|
271
|
+
"",
|
|
272
|
+
"Motion offsets align with layout spacing values.",
|
|
273
|
+
],
|
|
274
|
+
why: "Spacing + motion consistency improves visual quality.",
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
kicker: "AUTOFIX WORKFLOW",
|
|
278
|
+
title: "FIX FAST, SAFELY.",
|
|
279
|
+
subtitle: "MIGRATE LEGACY CSS WITHOUT DRAMA.",
|
|
280
|
+
terminalTitle: "autofix // migration",
|
|
281
|
+
code: [
|
|
282
|
+
"$ npx stylelint \"src/**/*.css\" --fix",
|
|
283
|
+
"Applied fixes:",
|
|
284
|
+
" - use-scale: 124",
|
|
285
|
+
" - prefer-token: 98",
|
|
286
|
+
" - no-offscale-transform: 19",
|
|
287
|
+
"",
|
|
288
|
+
"Review diff and ship.",
|
|
289
|
+
],
|
|
290
|
+
why: "Autofix handles repetitive cleanup at production scale.",
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
kicker: "READY TO SHIP",
|
|
294
|
+
title: "RHYTHMGUARD",
|
|
295
|
+
subtitle: "SPACING GOVERNANCE FOR REAL-WORLD TEAMS.",
|
|
296
|
+
terminalTitle: "next // adopt",
|
|
297
|
+
code: [
|
|
298
|
+
"Extends available:",
|
|
299
|
+
" - configs/recommended",
|
|
300
|
+
" - configs/strict",
|
|
301
|
+
" - configs/tailwind",
|
|
302
|
+
"",
|
|
303
|
+
"No random spacing values in production CSS.",
|
|
304
|
+
],
|
|
305
|
+
why: "Consistent spacing rules reduce regressions release after release.",
|
|
306
|
+
},
|
|
307
|
+
];
|
|
308
|
+
|
|
309
|
+
const sceneQuery = Number(new URLSearchParams(window.location.search).get("scene") || "1");
|
|
310
|
+
const index = Math.min(Math.max(sceneQuery, 1), scenes.length) - 1;
|
|
311
|
+
const scene = scenes[index];
|
|
312
|
+
|
|
313
|
+
document.getElementById("step").textContent = `STEP ${index + 1} / ${scenes.length}`;
|
|
314
|
+
document.getElementById("kicker").textContent = scene.kicker;
|
|
315
|
+
document.getElementById("title").textContent = scene.title;
|
|
316
|
+
document.getElementById("subtitle").textContent = scene.subtitle;
|
|
317
|
+
document.getElementById("terminal-title").textContent = scene.terminalTitle;
|
|
318
|
+
document.getElementById("code").textContent = scene.code.join("\n");
|
|
319
|
+
document.getElementById("why").textContent = scene.why;
|
|
320
|
+
</script>
|
|
321
|
+
</body>
|
|
322
|
+
</html>
|
|
Binary file
|
|
Binary file
|