@codenhub/styles 0.7.0 → 0.8.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.
Files changed (121) hide show
  1. package/README.es.md +8 -7
  2. package/README.md +7 -6
  3. package/README.pt-BR.md +8 -7
  4. package/dist/aesthetics/chunky-tile.css +1 -1
  5. package/dist/aesthetics/cyber.css +1 -1
  6. package/dist/aesthetics/glass.css +1 -1
  7. package/dist/aesthetics/index.css +2 -2
  8. package/dist/aesthetics/neobrutalism.css +1 -1
  9. package/dist/aesthetics/sketch.css +1 -1
  10. package/dist/cli.js +189 -0
  11. package/dist/components.css +2 -2
  12. package/dist/index.css +2 -2
  13. package/dist/index.d.ts +1 -0
  14. package/dist/native.css +2 -2
  15. package/dist/palette.css +1 -1
  16. package/dist/theme.css +2 -2
  17. package/docs/accessibility.md +46 -27
  18. package/docs/changelog/0.8.0.md +70 -0
  19. package/docs/changelog/index.md +1 -0
  20. package/docs/es/accessibility.md +47 -28
  21. package/docs/es/index.md +3 -2
  22. package/docs/es/integrating/index.md +38 -10
  23. package/docs/es/integrating/tailwind.md +78 -8
  24. package/docs/es/integrating/without-a-bundler.md +128 -5
  25. package/docs/es/setup.md +42 -34
  26. package/docs/es/usage/aesthetics.md +19 -13
  27. package/docs/es/usage/buttons.md +6 -2
  28. package/docs/es/usage/composing.md +6 -2
  29. package/docs/es/usage/content-and-layout.md +8 -4
  30. package/docs/es/usage/customizing.md +19 -13
  31. package/docs/es/usage/feedback.md +5 -1
  32. package/docs/es/usage/forms.md +29 -4
  33. package/docs/es/usage/index.md +6 -2
  34. package/docs/es/usage/presets.md +92 -0
  35. package/docs/es/usage/solo.md +4 -2
  36. package/docs/es/usage/theming.md +28 -15
  37. package/docs/index.md +2 -1
  38. package/docs/integrating/index.md +38 -10
  39. package/docs/integrating/tailwind.md +77 -7
  40. package/docs/integrating/without-a-bundler.md +127 -4
  41. package/docs/pt-BR/accessibility.md +47 -28
  42. package/docs/pt-BR/index.md +3 -2
  43. package/docs/pt-BR/integrating/index.md +38 -10
  44. package/docs/pt-BR/integrating/tailwind.md +78 -8
  45. package/docs/pt-BR/integrating/without-a-bundler.md +128 -5
  46. package/docs/pt-BR/setup.md +42 -34
  47. package/docs/pt-BR/usage/aesthetics.md +19 -13
  48. package/docs/pt-BR/usage/buttons.md +6 -2
  49. package/docs/pt-BR/usage/composing.md +6 -2
  50. package/docs/pt-BR/usage/content-and-layout.md +8 -4
  51. package/docs/pt-BR/usage/customizing.md +19 -13
  52. package/docs/pt-BR/usage/feedback.md +5 -1
  53. package/docs/pt-BR/usage/forms.md +29 -4
  54. package/docs/pt-BR/usage/index.md +6 -2
  55. package/docs/pt-BR/usage/presets.md +92 -0
  56. package/docs/pt-BR/usage/solo.md +4 -2
  57. package/docs/pt-BR/usage/theming.md +21 -8
  58. package/docs/setup.md +30 -22
  59. package/docs/usage/aesthetics.md +18 -12
  60. package/docs/usage/buttons.md +5 -1
  61. package/docs/usage/composing.md +5 -1
  62. package/docs/usage/content-and-layout.md +7 -3
  63. package/docs/usage/customizing.md +18 -12
  64. package/docs/usage/feedback.md +4 -0
  65. package/docs/usage/forms.md +28 -3
  66. package/docs/usage/index.md +5 -1
  67. package/docs/usage/presets.md +91 -0
  68. package/docs/usage/solo.md +3 -1
  69. package/docs/usage/theming.md +20 -7
  70. package/llms-full.txt +592 -115
  71. package/package.json +113 -63
  72. package/{dist/tw → tw}/aesthetics/chunky-tile.css +39 -19
  73. package/{dist/tw → tw}/aesthetics/cyber.css +15 -24
  74. package/{dist/tw → tw}/aesthetics/glass.css +105 -36
  75. package/{dist/tw → tw}/aesthetics/neobrutalism.css +37 -20
  76. package/{dist/tw → tw}/aesthetics/sketch.css +152 -133
  77. package/{dist/tw → tw}/box.css +89 -12
  78. package/tw/components/button.css +11 -0
  79. package/tw/components/button.rules.css +187 -0
  80. package/tw/components/feedback.css +11 -0
  81. package/{dist/tw/components/feedback.css → tw/components/feedback.rules.css} +134 -30
  82. package/tw/components/form.css +11 -0
  83. package/{dist/tw/components/form.css → tw/components/form.rules.css} +524 -111
  84. package/tw/components/index.css +16 -0
  85. package/tw/components/rules.css +41 -0
  86. package/tw/components/surface.css +11 -0
  87. package/tw/components/surface.rules.css +222 -0
  88. package/tw/components/tooltip.css +11 -0
  89. package/{dist/tw/components/tooltip.css → tw/components/tooltip.rules.css} +43 -27
  90. package/{dist/tw → tw}/corner.css +18 -12
  91. package/{dist/tw → tw}/elevation.css +23 -17
  92. package/{dist/tw → tw}/index.css +11 -9
  93. package/tw/index.d.ts +1 -0
  94. package/tw/intent.css +312 -0
  95. package/tw/modifiers.css +127 -0
  96. package/tw/native.css +320 -0
  97. package/tw/presentation.css +139 -0
  98. package/tw/reset.css +10 -0
  99. package/{dist/tw/reset.css → tw/reset.rules.css} +7 -8
  100. package/tw/size.css +34 -0
  101. package/tw/surface.css +9 -0
  102. package/tw/surface.rules.css +212 -0
  103. package/{dist/tw → tw}/theme.css +92 -28
  104. package/tw/typography.css +8 -0
  105. package/{dist/tw/typography.css → tw/typography.rules.css} +0 -3
  106. package/{dist/tw → tw}/utilities/content.css +183 -63
  107. package/tw/utilities/index.css +11 -0
  108. package/tw/utilities/layout.css +208 -0
  109. package/tw/utilities/rules.css +8 -0
  110. package/dist/tw/components/button.css +0 -139
  111. package/dist/tw/components/index.css +0 -35
  112. package/dist/tw/components/surface.css +0 -184
  113. package/dist/tw/intent.css +0 -229
  114. package/dist/tw/native.css +0 -203
  115. package/dist/tw/palette.css +0 -460
  116. package/dist/tw/presentation.css +0 -68
  117. package/dist/tw/surface.css +0 -61
  118. package/dist/tw/utilities/index.css +0 -6
  119. package/dist/tw/utilities/layout.css +0 -144
  120. /package/{dist/tw → tw}/aesthetics/index.css +0 -0
  121. /package/{dist/tw → tw}/aesthetics/pixel.css +0 -0
@@ -0,0 +1,212 @@
1
+ /* What every surface shares. Two slots exist here and nowhere else, and that is
2
+ how an aesthetic reaches surfaces without naming one: only a surface resolves
3
+ `--ui-backdrop`, so glass blurs cards and panels while controls stay solid,
4
+ and only a surface resolves `--ui-surface-shadow`, so a two-layer drop shadow
5
+ does not land under every button on the page.
6
+
7
+ A primitive beside `box`, and like `box` it imports neither the theme nor
8
+ Tailwind: `feedback.css` and `tooltip.css` `@reference` it, and a referenced
9
+ file that reaches the theme strips its tokens from every entry that pulls it
10
+ in. See docs/internal/model.md (Which files import the theme). */
11
+ @utility surface {
12
+ @apply box block;
13
+
14
+ /* Bare and edged. `box` reads these three with no fallback, so `surface` --
15
+ typeable on its own -- owes all three; `card` restates its own. No shadow
16
+ geometry: depth is asked for (`.raised`/`.floating`, an aesthetic, the
17
+ registry elevation) rather than carried, so a plain card is flat. */
18
+ --_d-fill: 0%;
19
+ --_d-fg-on-fill: 0%;
20
+ --_d-border: 100%;
21
+
22
+ /* A surface rests on a ground of its own, so no ink from above passes
23
+ through it: what is inside sits on this plate. It hands its own down
24
+ only where its fill is a plate (`--_own-hold`, which `box` steps and
25
+ which does not inherit). Unfilled, it hands down none, so a neutral
26
+ button in a soft success alert keeps the page's ink, not the alert's. */
27
+ --_held-ink: var(--_fg-own);
28
+ --_held-on: var(--_own-hold);
29
+
30
+ /* The neutral reset, carried so `@apply surface` gets it: the reset in
31
+ `intent.css` names `.surface`, which a consumer's `.my-surface` is not, so
32
+ a surface inside a `.card.success` took the card's intent. Only for an
33
+ element that carries neither an intent class nor one of the package's own
34
+ surface classes; those keep the reset in `components`, where a consumer's
35
+ own intent class beats it, and `.tooltip-bubble` keeps its own no-intent
36
+ plate. Zero specificity, so every rule that writes an intent slot on the
37
+ element wins over it. The values track `.neutral`'s. */
38
+ /* In `families`, beside the intent classes: applied with an intent --
39
+ `@apply surface info` -- the reset and the intent land in one layer, where the
40
+ intent's 0-1-0 beats this. Left in the rule's own layer it would beat
41
+ every intent applied with it. */
42
+ @layer families {
43
+ :where(
44
+ &:not(
45
+ .surface,
46
+ .card,
47
+ .panel,
48
+ .alert,
49
+ .tooltip-bubble,
50
+ .primary,
51
+ .secondary,
52
+ .success,
53
+ .warning,
54
+ .destructive,
55
+ .info
56
+ )
57
+ ) {
58
+ --intent-color: var(--color-text);
59
+ --intent-contrast: var(--color-text-contrast);
60
+ --intent-hover: var(--color-text-hover);
61
+ --intent-strong: var(--color-text-strong);
62
+ --intent-subtle: var(--color-surface);
63
+ --intent-border: var(--ui-ink, var(--color-border));
64
+ --intent-fill-max: 20%;
65
+ }
66
+ }
67
+
68
+ /* The public token first, so an aesthetic (glass's translucent ground) or a
69
+ consumer names the ground outright; then the component's own quiet default,
70
+ which `.card.soft`, a neutral `.panel`, and a neutral `.alert` write as a
71
+ private rather than as the public token. Written as the public token, their
72
+ own declaration would beat glass's inherited one in a glass region, and
73
+ beat glass on the same element too (`.card.soft.glass` would go opaque).
74
+ Restated `initial` here so a quiet ground cannot inherit into a surface
75
+ nested inside one, the way `box` restates `--_d-ground`. See
76
+ docs/internal/model.md#cascade-layers. */
77
+ --_d-surface-ground: initial;
78
+ --_d-ground: var(--ui-surface-ground, var(--_d-surface-ground, var(--color-background)));
79
+
80
+ /* Whether this surface rests on a quiet neutral tint, as 0% or 100%: a
81
+ neutral intent (its fill cap is below 100%) and a resolved fill above
82
+ nothing and below that cap -- `.soft`, a panel's or an alert's default.
83
+ 12% of the page's near-black ink reads as a grey slab rather than a step,
84
+ so `.card`, `.panel`, and `.alert` read this to rest such a fill on the
85
+ quiet foreground instead. Stepped from the resolved fill rather than
86
+ keyed on a class on the element, because presentation cascades: a
87
+ container's `.soft` and the surface's own are one request and have to
88
+ render one plate. Stepped rather than divided, as `box`'s `--_plate` is,
89
+ because the percentages cannot be divided in every engine yet. Only the
90
+ components that reinterpret the tint apply it; `surface` alone keeps the
91
+ plain tint. */
92
+ --_quiet: min(
93
+ clamp(0%, calc((100% - var(--intent-fill-max, 100%)) * 1000), 100%),
94
+ clamp(0%, calc(var(--ui-fill, var(--_d-fill)) * 1000), 100%),
95
+ clamp(0%, calc((var(--intent-fill-max, 100%) - var(--ui-fill, var(--_d-fill))) * 1000), 100%)
96
+ );
97
+
98
+ /* The complete-shadow slot for a shadow that is not one layer (glass tucks two
99
+ under with negative spread). Only `surface` reads it -- through `--ui-shadow`
100
+ it would land an 18px drop under every button and chip -- like the backdrop. */
101
+ --_shadow: var(--ui-surface-shadow, var(--_shadow-parts)) var(--_halo-layer,);
102
+
103
+ /* The surface pair of `box`'s corner: the aesthetic's surface length, scaled
104
+ by the padding step and patterned by the same four corner switches. */
105
+ --_corner-surface: calc(var(--ui-corner-surface, var(--radius-surface)) * var(--_scale));
106
+ border-radius: var(
107
+ --ui-radius-surface,
108
+ calc(var(--_corner-surface) * var(--ui-corner-tl, 1)) calc(var(--_corner-surface) * var(--ui-corner-tr, 1))
109
+ calc(var(--_corner-surface) * var(--ui-corner-br, 1)) calc(var(--_corner-surface) * var(--ui-corner-bl, 1))
110
+ );
111
+ /* No silhouette clip, whatever the aesthetic's `--ui-clip`: a clip cuts
112
+ everything inside the element, and a surface is the box that holds other
113
+ things. Under `.pixel` a tooltip in a card computed visible and was never
114
+ painted, and a child's shadow and focus outline were cut with it. A
115
+ surface is square there, with the ring, as a table is. */
116
+ clip-path: none;
117
+ /* The painted layer: a pattern, a texture, or a gradient an aesthetic lays
118
+ over the plate. Surfaces only, like the backdrop, so a control's label and
119
+ `.select`'s chevron never sit on one. The aesthetic that sets it owns the
120
+ contrast of the text over it, and a consumer's own `background-image` still
121
+ beats this declaration. */
122
+ background-image: var(--ui-surface-image, none);
123
+ /* Prefixed property first, deliberately: written second the minifier folds the
124
+ pair into the prefixed one alone, and the standard property never reaches
125
+ `dist/` -- which drops the blur in Firefox (the one engine with
126
+ `backdrop-filter` and not the `-webkit-` alias). Measured on the built CSS.
127
+
128
+ Cleared under liquid glass, which draws the blur on a layer below instead:
129
+ a surface with a backdrop filter of its own is a backdrop root, and a layer
130
+ inside one sees only the surface. */
131
+ --_layers-clear: var(--_glass-liquid-on) none;
132
+ -webkit-backdrop-filter: var(--_layers-clear, var(--ui-backdrop, none));
133
+ backdrop-filter: var(--_layers-clear, var(--ui-backdrop, none));
134
+
135
+ /* Liquid glass's two layers, a blur under the content and a lens with its rim
136
+ and sheen above it. A pseudo-element is not a value a token reaches, so
137
+ they are drawn here, where `@apply surface` carries them, and glass hands
138
+ them its values: `--_glass-liquid-on` is the space-toggle flag the nearest
139
+ liquid region in a glass scope sets on and every other region sets off,
140
+ and the blur, lens, rim, and sheen are its privates. With no liquid region
141
+ in scope every declaration below is a no-op, so the surface names no
142
+ aesthetic's class and changes nothing without one.
143
+
144
+ Zero specificity, so anything that writes these on the element beats them:
145
+ the tooltip bubble's own `absolute`, a consumer's `relative` or `before:`
146
+ utility. Off, the anchor, the isolation, and the content revert to the
147
+ layer below, so a surface outside a liquid region keeps every positioning
148
+ and pseudo-element rule written for it in another layer; the layers' other
149
+ declarations compute to `unset`, which is what an undeclared one is. Both
150
+ slots are spent where the flag is on, so a host there carries no
151
+ `::before` or `::after` of its own. See
152
+ docs/internal/model.md#glass-liquid. */
153
+ :where(&) {
154
+ --_layers-anchor: var(--_glass-liquid-on) relative;
155
+ --_layers-isolation: var(--_glass-liquid-on) isolate;
156
+
157
+ position: var(--_layers-anchor, revert-layer);
158
+ isolation: var(--_layers-isolation, revert-layer);
159
+ }
160
+
161
+ :where(&)::before,
162
+ :where(&)::after {
163
+ --_layers-content: var(--_glass-liquid-on) "";
164
+
165
+ position: var(--_glass-liquid-on) absolute;
166
+ inset: var(--_glass-liquid-on) 0;
167
+ z-index: var(--_glass-liquid-on) -1;
168
+ border-radius: var(--_glass-liquid-on) inherit;
169
+ corner-shape: var(--_glass-liquid-on) inherit;
170
+ content: var(--_layers-content, revert-layer);
171
+ pointer-events: var(--_glass-liquid-on) none;
172
+ }
173
+
174
+ /* Prefixed first, for the reason above. */
175
+ :where(&)::before {
176
+ -webkit-backdrop-filter: var(--_glass-liquid-on) var(--_glass-liquid-blur);
177
+ backdrop-filter: var(--_glass-liquid-on) var(--_glass-liquid-blur);
178
+ }
179
+
180
+ :where(&)::after {
181
+ backdrop-filter: var(--_glass-liquid-on) var(--_glass-liquid-lens);
182
+ background-image: var(--_glass-liquid-on) var(--_glass-liquid-sheen);
183
+ box-shadow: var(--_glass-liquid-on) var(--_glass-liquid-rim);
184
+ }
185
+
186
+ /* A `<dialog>`: hidden while closed, centred once open. The user agent
187
+ promises both, at user-agent origin, which loses to any author rule at
188
+ all: `display: block` above beats the first, so a closed dialog rendered,
189
+ and a margin reset beats the second, so an open one pinned to the
190
+ top-left. Carried here so `@apply surface` keeps both; the package's own
191
+ surfaces also keep the unlayered hide in `components/surface.css`.
192
+
193
+ The hide outranks a display utility on specificity, 0-2-1 against 0-1-0,
194
+ so a closed `.my-sheet.flex` stays closed; written unlayered, the
195
+ consumer's own applied rule beats every layered one. The centring is a
196
+ default at zero specificity, so a consumer's margin -- a utility, or their
197
+ own CSS in the same layer -- still places the dialog. */
198
+ &:is(dialog):not([open]) {
199
+ display: none;
200
+ }
201
+
202
+ /* A closed popover, for the same reason: the user agent's
203
+ `[popover]:not(:popover-open)` hide loses to `display: block` above, so a
204
+ `.card.floating` menu rendered before it was ever shown. */
205
+ &[popover]:not(:popover-open) {
206
+ display: none;
207
+ }
208
+
209
+ :where(&:is(dialog)[open]) {
210
+ margin: auto;
211
+ }
212
+ }
@@ -2,23 +2,32 @@
2
2
  orders layers by the first time it meets each name, so a sheet that opened
3
3
  `components` first would rank it below `theme` and `base`; stating Tailwind's
4
4
  own order here, and at the top of every entrypoint, makes every load order
5
- rank the same. The four class families -- intent, presentation, elevation,
6
- and every aesthetic -- sit in `components`, below every utility, so a
7
- consumer's own utility or unlayered CSS beats them. Components are
8
- `@utility` rules in `utilities`. See docs/internal/model.md#cascade-layers. */
5
+ rank the same. Intent, presentation, elevation, corner, and size are
6
+ `@utility` rules whose bodies sit in `utilities.families`, a sublayer that
7
+ loses to every rule written straight into `utilities` -- a component's, or
8
+ a consumer's own utility -- so `@apply` and the variants reach them and an
9
+ override still beats them. The intent floor and resets, and every
10
+ aesthetic's token class, stay in `components`. Components are `@utility`
11
+ rules in `utilities`. See docs/internal/model.md#cascade-layers. */
9
12
  @layer theme, base, components, utilities;
10
13
 
11
14
  @import "tailwindcss/theme.css" layer(theme);
12
- @import "./intent.css" layer(components);
13
- @import "./presentation.css" layer(components);
14
- @import "./elevation.css" layer(components);
15
- @import "./corner.css" layer(components);
15
+ /* Unlayered imports: a `@utility` has to be top-level, and each of these files
16
+ places its own rules -- the class families in `utilities.families`, the
17
+ intent floor and resets in `components`. */
18
+ @import "./intent.css";
19
+ @import "./presentation.css";
20
+ @import "./elevation.css";
21
+ @import "./corner.css";
22
+ @import "./size.css";
23
+ @import "./modifiers.css";
16
24
  /* Everything that is not `src/` -- see the note in `index.css`. Keeps a
17
25
  `--color-*` reference in a docs example from shipping as a real theme entry. */
18
26
 
19
27
  /* Color tokens are declared once with `light-dark()`, which resolves at the
20
28
  point of use against the consuming element's computed `color-scheme`. Theme
21
- selectors below therefore set `color-scheme` and nothing else. Verified
29
+ selectors below therefore set `color-scheme`, and the region's own `color`
30
+ so its text re-resolves too. Verified
22
31
  across Chromium, Firefox, and WebKit, including resolution through
23
32
  `color-mix()` and through Tailwind's `@theme`, which emits theme values as
24
33
  unregistered custom properties rather than `@property` registrations.
@@ -45,8 +54,12 @@
45
54
  /* Neutral-400 in light: the heaviest grey that still leaves a field legible
46
55
  (2.48:1 against the page). A lighter line lands within a few RGB units of a
47
56
  `.card.soft.edged`'s own fill and reads as no boundary. Dark's neutral-700
48
- already separates (27 units). */
49
- --color-border: light-dark(var(--color-neutral-400), var(--color-neutral-700));
57
+ already separates (27 units).
58
+
59
+ `--_contrast-border` is set only under `prefers-contrast: more`, below.
60
+ Read here rather than restating the token there, so a consumer who sets
61
+ `--color-border` replaces both and keeps their colour. */
62
+ --color-border: var(--_contrast-border, light-dark(var(--color-neutral-400), var(--color-neutral-700)));
50
63
  /* Nothing in the package draws a border that darkens on hover; it is a
51
64
  public override point for a consumer's own. */
52
65
  --color-border-hover: light-dark(var(--color-neutral-600), var(--color-neutral-500));
@@ -172,13 +185,25 @@
172
185
  instead of inferring it by omitting a shadow declaration. These are raw
173
186
  shadows for elements a consumer styles; the elevation modifier classes
174
187
  (`.flat`/`.raised`/`.floating` in elevation.css) multiply an aesthetic's
175
- shadow parts instead, and are not read from here. */
188
+ shadow parts instead, and are not read from here.
189
+
190
+ They resolve `--elevation-color` here, once, so a container's own
191
+ `--elevation-color` does not reach them. Declaring them on every element
192
+ instead would re-resolve it, and would also redeclare them under a
193
+ container that sets its own `--elevation-sm`, which no descendant would
194
+ then see. A consumer's override wins over a colour that follows the
195
+ container; docs/usage/theming.md#foundation-tokens says so. */
176
196
  --elevation-none: none;
177
197
  --elevation-sm: 0 1px 2px var(--elevation-color), 0 1px 3px var(--elevation-color);
178
198
  --elevation-md: 0 4px 6px -1px var(--elevation-color), 0 2px 4px -2px var(--elevation-color);
179
199
  --elevation-lg: 0 20px 25px -5px var(--elevation-color), 0 8px 10px -6px var(--elevation-color);
180
200
 
181
- --focus-ring: color-mix(in srgb, var(--color-primary) 64%, transparent);
201
+ /* `--focus-ring` is not declared: its default is the brand colour
202
+ at 64%, and declared here it resolved against `:root`'s
203
+ `--color-primary` and inherited, so a region that set its own brand
204
+ colour filled a primary button in it and ringed it in the page's. Every
205
+ reader falls back to the mix on the focused element instead, and a
206
+ consumer's `--focus-ring` on any ancestor still inherits. */
182
207
  /* `--focus-ring-offset` is not declared: its default is the negative of the
183
208
  line width of whatever is focused, which only the focused element knows,
184
209
  so the outline computes it there -- see `reset.css`. Declared here it
@@ -201,28 +226,29 @@
201
226
  /* The base look answers a press with a small scale-in. `box-active` applies it on
202
227
  `.btn` and `.card.interactive`; every aesthetic sets its own
203
228
  `--ui-active-transform` on its class, in `components`, so those win over
204
- this whichever sheet loads first. Reduced motion opts out. Kept outside `@theme` because a media query cannot go
205
- inside it. */
229
+ this whichever sheet loads first. Reduced motion keeps it and drops only
230
+ its motion, in `box`. */
206
231
  :root {
207
232
  --ui-active-transform: scale(0.97);
208
233
  }
209
234
 
210
- @media (prefers-reduced-motion: reduce) {
211
- :root {
212
- --ui-active-transform: none;
213
- }
214
- }
215
-
235
+ /* A forced theme re-resolves the region's own text as well as its scheme.
236
+ `color` inherits as a computed colour, already resolved against the scheme
237
+ above, so a `<p>` in an `<aside class="dark">` on a light page kept the
238
+ light theme's near-black. No background: a region that wants the theme's
239
+ ground says so. In `theme`, so every utility and component still wins. */
216
240
  .light,
217
241
  .theme-light,
218
242
  [data-theme="light"] {
219
243
  color-scheme: light;
244
+ color: var(--color-text);
220
245
  }
221
246
 
222
247
  .dark,
223
248
  .theme-dark,
224
249
  [data-theme="dark"] {
225
250
  color-scheme: dark;
251
+ color: var(--color-text);
226
252
  }
227
253
  }
228
254
 
@@ -232,13 +258,14 @@
232
258
  The second arm follows the OS preference. `--color-*` tokens resolve through
233
259
  `light-dark()` against the computed `color-scheme`, so with no explicit
234
260
  selector `:root`'s `color-scheme: light dark` already follows it -- see
235
- `concepts.md#theme-selection`. A selector list cannot query computed style,
236
- but it can match "OS prefers dark, and not under a forced-light subtree" with
237
- a plain descendant exclusion: `:not(:where(.light, .light *, ...))` matches
238
- only when no ancestor (or the element itself) carries one of the three light
239
- spellings. The exclusion cannot see a subtree forced light by other means,
240
- where `light-dark()` would still honor it; that is out of contract, since
241
- those three spellings are the only supported way to force a theme.
261
+ `docs/usage/theming.md#light-and-dark`. A selector list cannot query
262
+ computed style, but it can match "OS prefers dark, and not under a
263
+ forced-light subtree" with a plain descendant exclusion:
264
+ `:not(:where(.light, .light *, ...))` matches only when no ancestor (or the
265
+ element itself) carries one of the three light spellings. The exclusion
266
+ cannot see a subtree forced light by other means, where `light-dark()` would
267
+ still honor it; that is out of contract, since those three spellings are the
268
+ only supported way to force a theme.
242
269
 
243
270
  Each arm is independent, so an explicit selector wins over the OS preference
244
271
  at any depth. It does not win over an explicit selector of the opposite
@@ -257,6 +284,43 @@
257
284
  }
258
285
  }
259
286
 
287
+ /* Paper is the light ground. A browser leaves backgrounds off a printed page
288
+ unless told otherwise, so a dark page printed its near-white text on no
289
+ ground at all. Every token resolves through `light-dark()`, so printing in
290
+ the light scheme re-resolves all of them, a forced-dark region's included.
291
+ In `theme`, after the selectors above and at their specificity, so it beats
292
+ them and a consumer's utility still beats it. It does not reach a `dark:`
293
+ utility, which keys on the class and not on the scheme. */
294
+ @layer theme {
295
+ @media print {
296
+ :root,
297
+ .dark,
298
+ .theme-dark,
299
+ [data-theme="dark"] {
300
+ color-scheme: light;
301
+ }
302
+ }
303
+ }
304
+
305
+ /* More contrast, asked for by the reader. The quiet surface line is 2.48:1
306
+ against the light page and 1.91:1 against the dark one by choice; here it
307
+ takes a grey two steps heavier, neutral-600 in light and neutral-400 in
308
+ dark. The control grey between them is not enough: `box` fades a line by the
309
+ fill under it, and on a neutral `.badge.soft.edged` neutral-500 lands at
310
+ 2.9:1 against its own tint. One token, so every neutral line that reads it
311
+ follows: surfaces, dividers, table rules, a neutral button's edge.
312
+ `--color-border` reads this slot first, so a consumer's own value for the
313
+ token replaces both and still wins.
314
+ See docs/accessibility.md. */
315
+ @layer theme {
316
+ @media (prefers-contrast: more) {
317
+ :root,
318
+ :host {
319
+ --_contrast-border: light-dark(var(--color-neutral-600), var(--color-neutral-400));
320
+ }
321
+ }
322
+ }
323
+
260
324
  /* Forced colours draw the plain version of every structural part: a solid line
261
325
  and nothing an aesthetic layers on top. Unlayered, so it beats every layered
262
326
  declaration of these tokens -- an aesthetic's and a consumer's utility alike --
@@ -0,0 +1,8 @@
1
+ @reference "tailwindcss";
2
+ @import "./theme.css";
3
+
4
+ /* The focused entry: Tailwind and the theme, which the rules need to compile
5
+ on their own, then the rules. An aggregate entry imports the rules file and
6
+ brings the theme itself, once. See docs/internal/model.md (One route to each
7
+ utility). */
8
+ @import "./typography.rules.css";
@@ -1,6 +1,3 @@
1
- @reference "tailwindcss";
2
- @import "./theme.css";
3
-
4
1
  /* `text-6xl` and up bundle `line-height: 1`, which sets a heading's line box to
5
2
  the font size exactly: descenders reach the box edge and a wrapped heading's
6
3
  lines collide. The two largest steps set an explicit leading that still