@codenhub/styles 0.5.0 → 0.7.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 (119) hide show
  1. package/NOTICE +18 -16
  2. package/README.es.md +61 -0
  3. package/README.md +27 -22
  4. package/README.pt-BR.md +61 -0
  5. package/dist/aesthetics/chunky-tile.css +1 -1
  6. package/dist/aesthetics/cyber.css +1 -1
  7. package/dist/aesthetics/glass.css +1 -1
  8. package/dist/aesthetics/index.css +2 -2
  9. package/dist/aesthetics/neobrutalism.css +1 -1
  10. package/dist/aesthetics/pixel.css +1 -1
  11. package/dist/aesthetics/sketch.css +1 -1
  12. package/dist/components.css +2 -2
  13. package/dist/index.css +2 -2
  14. package/dist/native.css +2 -2
  15. package/dist/palette.css +1 -1
  16. package/dist/theme.css +2 -2
  17. package/dist/tw/aesthetics/chunky-tile.css +120 -62
  18. package/dist/tw/aesthetics/cyber.css +102 -40
  19. package/dist/tw/aesthetics/glass.css +371 -19
  20. package/dist/tw/aesthetics/index.css +2 -1
  21. package/dist/tw/aesthetics/neobrutalism.css +121 -33
  22. package/dist/tw/aesthetics/pixel.css +111 -16
  23. package/dist/tw/aesthetics/sketch.css +100 -28
  24. package/dist/tw/box.css +69 -28
  25. package/dist/tw/components/button.css +35 -14
  26. package/dist/tw/components/feedback.css +61 -58
  27. package/dist/tw/components/form.css +255 -142
  28. package/dist/tw/components/index.css +4 -9
  29. package/dist/tw/components/surface.css +80 -68
  30. package/dist/tw/components/tooltip.css +75 -41
  31. package/dist/tw/elevation.css +14 -22
  32. package/dist/tw/index.css +15 -19
  33. package/dist/tw/intent.css +35 -27
  34. package/dist/tw/native.css +22 -12
  35. package/dist/tw/palette.css +14 -11
  36. package/dist/tw/presentation.css +16 -30
  37. package/dist/tw/reset.css +36 -43
  38. package/dist/tw/surface.css +11 -12
  39. package/dist/tw/theme.css +87 -103
  40. package/dist/tw/utilities/content.css +311 -200
  41. package/dist/tw/utilities/layout.css +41 -37
  42. package/docs/accessibility.md +104 -58
  43. package/docs/changelog/0.1.1.md +1 -0
  44. package/docs/changelog/0.2.0.md +1 -0
  45. package/docs/changelog/0.3.0.md +1 -0
  46. package/docs/changelog/0.4.0.md +1 -0
  47. package/docs/changelog/0.5.0.md +8 -3
  48. package/docs/changelog/0.6.0.md +53 -0
  49. package/docs/changelog/0.7.0.md +50 -0
  50. package/docs/changelog/index.md +2 -0
  51. package/docs/concepts.md +188 -46
  52. package/docs/es/accessibility.md +150 -0
  53. package/docs/es/concepts.md +212 -0
  54. package/docs/es/index.md +104 -0
  55. package/docs/es/integrating/astro.md +35 -0
  56. package/docs/es/integrating/index.md +47 -0
  57. package/docs/es/integrating/nextjs.md +67 -0
  58. package/docs/es/integrating/svelte.md +45 -0
  59. package/docs/es/integrating/tailwind.md +78 -0
  60. package/docs/es/integrating/vue.md +31 -0
  61. package/docs/es/integrating/without-a-bundler.md +115 -0
  62. package/docs/es/setup.md +180 -0
  63. package/docs/es/usage/aesthetics.md +276 -0
  64. package/docs/es/usage/buttons.md +131 -0
  65. package/docs/es/usage/composing.md +164 -0
  66. package/docs/es/usage/content-and-layout.md +215 -0
  67. package/docs/es/usage/customizing.md +298 -0
  68. package/docs/es/usage/feedback.md +153 -0
  69. package/docs/es/usage/forms.md +231 -0
  70. package/docs/es/usage/index.md +29 -0
  71. package/docs/es/usage/solo.md +84 -0
  72. package/docs/es/usage/theming.md +296 -0
  73. package/docs/es/usage/tooltips.md +127 -0
  74. package/docs/index.md +89 -21
  75. package/docs/integrating/astro.md +3 -4
  76. package/docs/integrating/index.md +18 -13
  77. package/docs/integrating/nextjs.md +4 -5
  78. package/docs/integrating/svelte.md +5 -4
  79. package/docs/integrating/tailwind.md +29 -8
  80. package/docs/integrating/vue.md +3 -4
  81. package/docs/integrating/without-a-bundler.md +114 -0
  82. package/docs/pt-BR/accessibility.md +150 -0
  83. package/docs/pt-BR/concepts.md +212 -0
  84. package/docs/pt-BR/index.md +104 -0
  85. package/docs/pt-BR/integrating/astro.md +35 -0
  86. package/docs/pt-BR/integrating/index.md +47 -0
  87. package/docs/pt-BR/integrating/nextjs.md +67 -0
  88. package/docs/pt-BR/integrating/svelte.md +45 -0
  89. package/docs/pt-BR/integrating/tailwind.md +78 -0
  90. package/docs/pt-BR/integrating/vue.md +31 -0
  91. package/docs/pt-BR/integrating/without-a-bundler.md +115 -0
  92. package/docs/pt-BR/setup.md +180 -0
  93. package/docs/pt-BR/usage/aesthetics.md +276 -0
  94. package/docs/pt-BR/usage/buttons.md +131 -0
  95. package/docs/pt-BR/usage/composing.md +164 -0
  96. package/docs/pt-BR/usage/content-and-layout.md +215 -0
  97. package/docs/pt-BR/usage/customizing.md +298 -0
  98. package/docs/pt-BR/usage/feedback.md +153 -0
  99. package/docs/pt-BR/usage/forms.md +231 -0
  100. package/docs/pt-BR/usage/index.md +29 -0
  101. package/docs/pt-BR/usage/solo.md +84 -0
  102. package/docs/pt-BR/usage/theming.md +296 -0
  103. package/docs/pt-BR/usage/tooltips.md +127 -0
  104. package/docs/setup.md +134 -91
  105. package/docs/usage/aesthetics.md +102 -65
  106. package/docs/usage/buttons.md +108 -40
  107. package/docs/usage/composing.md +105 -92
  108. package/docs/usage/content-and-layout.md +159 -127
  109. package/docs/usage/customizing.md +112 -43
  110. package/docs/usage/feedback.md +135 -70
  111. package/docs/usage/forms.md +184 -99
  112. package/docs/usage/index.md +18 -14
  113. package/docs/usage/solo.md +83 -0
  114. package/docs/usage/theming.md +260 -119
  115. package/docs/usage/tooltips.md +103 -42
  116. package/llms-full.txt +2465 -1272
  117. package/llms.txt +25 -17
  118. package/package.json +28 -35
  119. package/dist/tw/components/loader.css +0 -117
package/dist/tw/box.css CHANGED
@@ -35,6 +35,21 @@
35
35
  own presentation instead of the container's reinterpretation of one. */
36
36
  --_fill-cap: 100%;
37
37
  --_capped: min(var(--ui-fill, var(--_d-fill)), var(--_fill-cap, 100%), var(--intent-fill-max, 100%));
38
+ /* Whether the intent's cap stopped the fill short, as 0% or 100%. A capped
39
+ fill is a plate the presentation asked to be full, so it rests on the page
40
+ background rather than on nothing: 20% of the page's ink over nothing
41
+ showed whatever sat behind a neutral `.solid` -- glass's backdrop, an
42
+ image, the colour of a filled card around it -- where on a plain page it
43
+ read as the light grey plate it is meant to be. The component's own cap
44
+ is measured out first, so a text control's 20% wash and a neutral
45
+ `.card.soft`'s 0% stay the tints they are, whatever the intent. Stepped
46
+ rather than scaled because the two percentages cannot be divided in every
47
+ engine yet. */
48
+ --_plate: clamp(
49
+ 0%,
50
+ calc((min(var(--ui-fill, var(--_d-fill)), var(--_fill-cap, 100%)) - var(--_capped)) * 1000),
51
+ 100%
52
+ );
38
53
  --_fill: calc(var(--_capped) * var(--ui-bg-alpha, 1));
39
54
  --_hover-fill: calc(min(100%, var(--_capped) + var(--hover-step)) * var(--ui-bg-alpha, 1));
40
55
  /* How much of the foreground is the contrast ink: what the presentation asks
@@ -57,40 +72,48 @@
57
72
  declaration beats the inherited one at any specificity; `surface`, `kbd`,
58
73
  `code`, `pre` and the tooltip each restate theirs after `@apply box`. */
59
74
  --_d-ground: transparent;
75
+ /* The ground, made the page background wherever `--_plate` says the cap
76
+ stopped the fill -- over a component's own ground too, so a capped
77
+ `.solid` surface under glass is opaque like every other `.solid`. */
78
+ --_ground: color-mix(in oklab, var(--color-background) var(--_plate), var(--_d-ground, transparent));
60
79
 
61
- --_bg: color-mix(in oklab, var(--intent-color) var(--_fill), var(--_d-ground, transparent));
80
+ --_bg: color-mix(in oklab, var(--intent-color) var(--_fill), var(--_ground));
62
81
  /* The two ends answer different questions: `--intent-contrast` prints *on* the
63
82
  fill, `--intent-strong` prints on the page (the slot for readable ink on a
64
83
  subtle fill). Reading `--intent-color` at the page end instead makes every
65
84
  colour do double duty and prints a soft amber component at 3.1:1.
66
85
  `--intent-contrast` keeps its own meaning -- `--_on-fill` reconciles it with
67
86
  a capped intent -- rather than being bent so neutral's contrast is the ink,
68
- which rendered a checked neutral checkbox black-on-black. */
87
+ which renders a checked neutral checkbox black-on-black. */
69
88
  --_fg: color-mix(in oklab, var(--intent-contrast) var(--_on-fill), var(--intent-strong));
70
89
  /* P3: the line fades out as the fill fills in, so a filled box has a seamless
71
- edge. It fades toward `transparent`, not toward the plate: the plate already
72
- runs under the border (nothing here clips the background to the padding
73
- box), so a line blended toward `--_bg` painted the plate a second time over
74
- itself. On an opaque plate that is invisible; on a translucent one -- a
75
- neutral fill, capped at 20%, or any fill `--ui-bg-alpha` thins -- it drew a
76
- ring around the box, 1.54:1 against its own plate in light. Faded toward
77
- nothing, the border shows the one coat beneath it.
90
+ edge. It fades toward `transparent`, not toward the plate: the plate
91
+ already runs under the border (nothing clips the background to the padding
92
+ box), so a line blended toward `--_bg` paints the plate a second time over
93
+ itself -- invisible on an opaque plate, a ring on a translucent one (a
94
+ neutral fill capped at 20%, or any fill `--ui-bg-alpha` thins). Faded
95
+ toward nothing, the border shows the one coat beneath it.
78
96
 
79
- Blend first, amount after (`--_edge-amount` below) -- the other order lands
80
- `.edgeless` on the fill colour, which the border box then paints as a second
81
- coat: a ring around an element that asked for no edge. See
97
+ Blend first, amount after (`--_edge-amount` below): the other order lands
98
+ `.edgeless` on the fill colour, which the border box paints as a second
99
+ coat -- a ring around an element that asked for no edge. See
82
100
  docs/internal/model.md#presentation. */
83
101
  --_line: color-mix(in oklab, transparent var(--ui-fill, var(--_d-fill)), var(--intent-border));
84
102
  /* How much line there is, as an input rather than a result. A component that
85
103
  must keep its line whatever the edge class says raises `--_edge-floor`
86
104
  instead of rewriting `--_edge`, and the composition above still runs.
87
105
 
88
- The distinction is not cosmetic. Rewriting the result drops every term the
89
- result was composed from, silently and with nothing to report: that is how
90
- `--ui-border` stopped reaching all six text controls, and how `--_d-border`
91
- sat declared and unread on each of them for a release. Bounding the input
92
- leaves the axis live, so `.edged` and `.edgeless` still mean what they say
93
- on a component that happens to floor itself. */
106
+ Rewriting the result drops every term it was composed from, silently and
107
+ with nothing to report. Bounding the input leaves the axis live, so
108
+ `.edged` and `.edgeless` still mean what they say on a component that
109
+ floors itself.
110
+
111
+ Restated at `0%` -- no floor -- for the reason `--_fill-cap` is restated
112
+ above: the floor inherits, so a field's or a tooltip bubble's `100%` reached
113
+ every box nested inside, and a `.btn.edgeless` clear button in an
114
+ `.input-group` drew a field's line it had asked not to have. Components
115
+ that floor themselves declare theirs after `@apply box`. */
116
+ --_edge-floor: 0%;
94
117
  --_edge-amount: max(var(--ui-border, var(--_d-border)), var(--_edge-floor, 0%));
95
118
  --_edge: color-mix(in oklab, var(--_line) var(--_edge-amount), transparent);
96
119
  --_width: min(var(--ui-border-width, var(--border-width)), var(--ui-border-max, 100px));
@@ -111,6 +134,10 @@
111
134
  public override, undeclared by the package. See docs/internal/model.md#material-tokens. */
112
135
  --_scale-size: initial;
113
136
  --_scale-pad: initial;
137
+ /* The padding the same modifiers set on a `.card` or `.panel`, restated for
138
+ the same reason: a `.card.p-xs` or `.flush` would otherwise pad every
139
+ surface nested inside it to its own value. */
140
+ --surface-padding: initial;
114
141
  --_scale: var(--ui-scale, min(var(--_scale-size, var(--_scale-pad, 1)), var(--_scale-pad, var(--_scale-size, 1))));
115
142
  /* Depth follows the step down and not up: a 4px slab under a 20px button
116
143
  swallows it, while a press that travels the aesthetic's whole depth has to
@@ -168,17 +195,22 @@
168
195
  on anything that set one. `--ui-surface-shadow` is that escape hatch narrowed
169
196
  to the single case that needs it. */
170
197
  --_shadow: var(--_shadow-parts) var(--_halo-layer,);
171
- --_focus-stack: var(--_shadow), inset 0 0 0 var(--ui-focus-inset) var(--focus-ring);
198
+ /* The inset focus layer an aesthetic that clips asks for, in two tones. The
199
+ ring is the primary colour, so on a `.solid.primary` box it was painted
200
+ over its own colour and a focused button was pixel for pixel a resting
201
+ one. A line of the page colour sits inside it: one of the two always
202
+ differs from the fill, whatever the fill is. Shadows paint first-on-top,
203
+ so the line is listed last and is the two pixels the ring leaves showing. */
204
+ --_focus-stack:
205
+ var(--_shadow), inset 0 0 0 var(--ui-focus-inset) var(--focus-ring),
206
+ inset 0 0 0 calc(var(--ui-focus-inset) + 2px) var(--color-background);
172
207
 
173
208
  /* Every box sizes itself as `min-height`/`min-width: var(--control-height)`
174
- plus its own padding and the border shorthand above, so its geometry is only
175
- right under `box-sizing: border-box`. `reset.css` sets that on `*`, but only
176
- the `.`, `/native` and `/tw/reset` entrypoints carry the reset -- and the
177
- first two pull Tailwind's Preflight, which sets it anyway. `/components` and
178
- the granular `/tw/*` component entrypoints ship neither, so a box there
179
- inherited the `content-box` default and every sized control came out
180
- inflated by its own padding and border. A component's own geometry is not an
181
- external reset's job; `box` owns it. */
209
+ plus its own padding and the border shorthand above, so its geometry is
210
+ only right under `border-box`. `reset.css` sets that on `*`, but only the
211
+ `.`, `/native` and `/tw/reset` entrypoints carry the reset; `/components`
212
+ and the granular `/tw/*` component entrypoints ship neither it nor
213
+ Preflight, so `box` owns it. */
182
214
  box-sizing: border-box;
183
215
  background-color: var(--_bg);
184
216
  color: var(--_fg);
@@ -221,6 +253,15 @@
221
253
  transform var(--motion-duration-normal) var(--motion-ease),
222
254
  translate var(--motion-duration-normal) var(--motion-ease);
223
255
 
256
+ /* Reduced motion stops here as well as in `reset.css`: `/components` and the
257
+ focused `/tw/*` entries carry no reset, so each component that moves says
258
+ so itself -- this for everything `box` paints, and the components with a
259
+ transition or animation of their own beside it. The same near-zero
260
+ duration the reset uses, so a `transitionend` still fires. */
261
+ @media (prefers-reduced-motion: reduce) {
262
+ transition-duration: 0.01ms;
263
+ }
264
+
224
265
  /* An aesthetic that clips cannot show the global focus outline, so it declares
225
266
  `--ui-focus-inset` and gets an inset layer instead. With no such aesthetic
226
267
  the token is undefined, which makes the whole stack invalid at computed-value
@@ -254,7 +295,7 @@
254
295
  and wrong for something you type into, so a component opts in. */
255
296
  @utility box-hover {
256
297
  &:not(:disabled, .disabled, [aria-disabled="true"], [data-disabled]):hover {
257
- --_bg: color-mix(in oklab, var(--intent-hover) var(--_hover-fill), var(--_d-ground, transparent));
298
+ --_bg: color-mix(in oklab, var(--intent-hover) var(--_hover-fill), var(--_ground));
258
299
  /* P3 again, against the hover tone: the line still fades as the fill fills
259
300
  in, but toward the hover ink. Without this the edge keeps the resting ink
260
301
  -- a ring of the colour the box just left. */
@@ -17,6 +17,18 @@
17
17
  @apply box box-hover box-active relative inline-flex w-fit cursor-pointer items-center justify-center gap-2 px-4 py-2 whitespace-nowrap no-underline select-none;
18
18
 
19
19
  min-height: var(--control-height);
20
+ /* The vertical padding gives back the line `box` draws, so a default button
21
+ is exactly `--control-height` tall like `.ipt` beside it, under any
22
+ aesthetic's line width. At the default 16px/1.5 text the label and the
23
+ padding fill it; the size and padding modifiers set their own. */
24
+ padding-block: calc(0.5rem - var(--_width));
25
+ /* The surrounding font, not the user agent's smaller button one, on every
26
+ entry: `/components` carries no reset to say so, and a `.btn` link and a
27
+ `.btn` button have to print the same label. `.sm` and `.lg` set their own
28
+ size over this. */
29
+ font-family: inherit;
30
+ font-size: inherit;
31
+ line-height: inherit;
20
32
  /* The label is material, shared with `.badge`: weight, tracking, case, and a
21
33
  text shadow, set by an aesthetic as tokens (`.chunky-tile`'s heavier,
22
34
  tracked label), and a consumer's own `font-*`, `tracking-*`, `uppercase`, or
@@ -50,20 +62,29 @@
50
62
  min-width: var(--control-height);
51
63
  }
52
64
 
53
- /* Bare `sm`/`lg` are already the size modifier's class names, so `p-`
54
- prefixes here to avoid colliding with a button's own size. `.p-xs` is the
55
- tightest step, below `.p-sm`: a chip-sized button in a toolbar or a table
56
- cell.
57
-
58
- `.icon` drops both floors -- `min-width` from its own base rule below and
59
- `min-height` from `--control-height` -- at every padding tier, labeled
60
- buttons keep the height floor everywhere but `.p-xs`. `.icon`
61
- already asks to be square: `aspect-square` ties its width to whatever
62
- height it lands on, so a height floor that survives keeps the button
63
- bigger than its own glyph regardless of how tight the padding asked to be
64
- -- the one combination `.p-xs` used to get right and the other two tiers
65
- did not, leaving a `.icon.p-sm` floating in a box far bigger than it
66
- needed and a `.icon.p-lg` doing the same one step further out. */
65
+ /* The size steps for an icon button, square at each: `.sm`'s 2rem, with the
66
+ padding giving back the line width the way the default button's does, and
67
+ `.lg`'s 3rem. Each names its width as well as its height, because WebKit
68
+ does not let `aspect-ratio` widen a box past its `min-width`. */
69
+ &.icon.sm {
70
+ min-width: 2rem;
71
+ padding: calc(0.375rem - var(--_width));
72
+ }
73
+
74
+ &.icon.lg {
75
+ min-width: 3rem;
76
+ }
77
+
78
+ /* Bare `sm`/`lg` are the size modifier's class names, so `p-` prefixes here.
79
+ `.p-xs` is the tightest step, below `.p-sm`: a chip-sized button in a
80
+ toolbar or a table cell.
81
+
82
+ `.icon` drops both floors -- `min-width` from its own base rule above and
83
+ `min-height` from `--control-height` -- at every padding tier; labeled
84
+ buttons keep the height floor everywhere but `.p-xs`. `.icon` is already
85
+ square: `aspect-square` ties its width to whatever height it lands on, so a
86
+ surviving height floor would keep the button bigger than its own glyph
87
+ however tight the padding asked to be. */
67
88
  &.p-xs {
68
89
  @apply px-2 py-0.5;
69
90
 
@@ -38,50 +38,45 @@
38
38
  --_d-elevation: 0;
39
39
 
40
40
  /* Same carve-out as `.panel`'s, and for the same reason: a neutral alert
41
- mixes 12% of near-black ink onto the page, measured `217, 217, 217`
42
- against `250, 250, 250` -- the identical slab `.panel` read before its
43
- own fix, not a status-signaling problem, since this is scoped to no
44
- named intent. A named intent keeps its ordinary wash; untinting
45
- `.alert.soft.destructive` too would silence the status it exists to
46
- carry rather than quiet it, which is why an earlier, unconditional
47
- version of this fix was rejected for `.alert` specifically. This
48
- version never reaches that case: `:not(...)` names every intent class,
49
- plus `.solid`/`.ghost` so an explicit presentation on the element is
50
- never clamped to a cap it did not ask for.
51
-
52
- Grounds on `--color-foreground` through `--_d-surface-ground`, exactly
53
- like `.panel` and a resting `.card.soft`, so glass's ground and a
54
- consumer's `--ui-surface-ground` still win over it -- an
55
- alert has no hover state to reveal a tint into either, so this is
56
- permanent rather than revealed. Unlike a neutral panel or badge, an
57
- alert cannot lose its visual identity from this: `--_d-border: 100%`
58
- above holds regardless of fill, so a neutral alert still reads as a
59
- framed box, just a quiet one instead of a grey slab. */
41
+ mixes 12% of near-black ink onto the page (`217, 217, 217` against `250,
42
+ 250, 250`), a slab rather than a status signal. Scoped to no named intent:
43
+ a named intent keeps its ordinary wash, because untinting
44
+ `.alert.soft.destructive` would silence the status it exists to carry
45
+ rather than quiet it. `:not(...)` names every intent class, plus
46
+ `.solid`/`.ghost` so an explicit presentation on the element is never
47
+ clamped to a cap it did not ask for.
48
+
49
+ Grounds on `--color-foreground` through `--_d-surface-ground`, exactly like
50
+ `.panel` and a resting `.card.soft`, so glass's ground and a consumer's
51
+ `--ui-surface-ground` still win over it. An alert has no hover state to
52
+ reveal a tint into either, so this is permanent. A neutral alert keeps its
53
+ identity: `--_d-border: 100%` above holds regardless of fill, so it still
54
+ reads as a framed box, just a quiet one instead of a grey slab. */
60
55
  &:not(.primary, .secondary, .success, .warning, .destructive, .info, .solid, .ghost) {
61
56
  --_fill-cap: 0%;
62
57
  --_d-surface-ground: var(--color-foreground);
63
58
  }
64
59
  }
65
60
 
66
- /* No icon is shipped: the five hardcoded per-intent masks this used to paint on
67
- a `::before` are gone, the same way the per-input-type icon was removed from
68
- `.input-group` -- see `docs/internal/model.md#input-group-owns-the-box-the-control-inside-gives-it-up`.
69
- `.alert` is
70
- already `flex`, so an icon dropped in as a real child is spaced by `gap-3`
71
- with nothing to trigger: `.alert-icon` only fixes its size and alignment, not
72
- whether one is present. Any icon element works, the same contract
73
- `.input-group` documents -- an inline `<svg>`, an `<img>`, or a class from an
74
- icon set such as `@codenhub/icons`. */
61
+ /* No icon is shipped. `.alert` is already `flex`, so an icon dropped in as a
62
+ real child is spaced by `gap-3`: `.alert-icon` only fixes its size and
63
+ alignment, not whether one is present. Any icon element works, the same
64
+ contract `.input-group` documents -- an inline `<svg>`, an `<img>`, or a
65
+ class from an icon set such as `@codenhub/icons`. See
66
+ `docs/internal/model.md#input-group-owns-the-box-the-control-inside-gives-it-up`. */
75
67
  @utility alert-icon {
76
68
  @apply inline-flex flex-none items-center justify-center;
77
69
 
78
- /* `!important`: an icon-set glyph (`@codenhub/icons` among them) carries its
79
- own `width`/`height` on a selector this utility cannot outrank -- an
80
- attribute selector reads more specific than one class regardless of
81
- stylesheet order. Sizing what you drop in is this utility's one job, so it
82
- has to win over the glyph's own opinion. */
83
- width: 1.25rem !important;
84
- height: 1.25rem !important;
70
+ /* A `@codenhub/icons` glyph sizes itself in an unlayered rule, which no
71
+ layered rule outranks whatever its specificity, but it reads its size from
72
+ `--ic-size`, so the size goes there as well as on the box for any other
73
+ icon. No `!important`: that would shut out the consumer's own resize too.
74
+ An icon set with another prefix, or one that sizes unlayered with no knob,
75
+ keeps its own size. */
76
+ --ic-size: 1.25rem;
77
+
78
+ width: 1.25rem;
79
+ height: 1.25rem;
85
80
  }
86
81
 
87
82
  /* A soft, edgeless chip. Small enough that the structural silhouette would be
@@ -111,10 +106,7 @@
111
106
 
112
107
  /* An indicator reads intent and no presentation token at all. It is a stand-in
113
108
  for content rather than a box with a look of its own, so there is nothing for
114
- a container's `.solid` or `.edgeless` to say about it -- which is also what
115
- removes the visibility floor that used to fight `--ui-fill` for control of the
116
- base tone, and the transparent border that only ever appeared if something up
117
- the tree asked for an edge. */
109
+ a container's `.solid` or `.edgeless` to say about it. */
118
110
  @utility skeleton {
119
111
  --skeleton-base: color-mix(in oklab, var(--intent-color) 6%, var(--intent-subtle));
120
112
  --skeleton-sheen: color-mix(in oklab, var(--intent-color) 10%, var(--color-foreground));
@@ -133,15 +125,21 @@
133
125
  calc(var(--_corner) * var(--ui-corner-br, 1)) calc(var(--_corner) * var(--ui-corner-bl, 1))
134
126
  );
135
127
  corner-shape: var(--ui-corner-shape, round);
128
+
129
+ /* Reduced motion stops the shimmer here, for the entries without the reset;
130
+ see `box.css`. The gradient stays, so the placeholder still reads. */
131
+ @media (prefers-reduced-motion: reduce) {
132
+ animation: none;
133
+ }
136
134
  }
137
135
 
138
136
  /* The track is a box, the fill is not: `box.css`'s formula composes the frame,
139
137
  the value fill stays `--intent-color` at full strength with no presentation,
140
- same contract as `.loader`/`.skeleton`. `.progress` cannot `@apply box`
138
+ same contract as `.skeleton`. `.progress` cannot `@apply box`
141
139
  (the fill is a `::after`, not the element's own background), so it hand-
142
140
  reimplements the formula the way `.quote` does -- see
143
141
  docs/internal/model.md#components-that-do-not-take-the-whole-of-box for the
144
- decision and why the other three indicators do not get the same treatment. */
142
+ decision and why the other two indicators do not get the same treatment. */
145
143
  @utility progress {
146
144
  --_d-fill: 12%;
147
145
  --_d-fg-on-fill: 0%;
@@ -161,17 +159,16 @@
161
159
  not `.pre`/`.code`/`.kbd`'s opaque `--intent-subtle` ground. */
162
160
  --_progress-surface: color-mix(in oklab, var(--intent-color) var(--_progress-fill), transparent);
163
161
  /* One `color-mix()`, not `box`/`.quote`'s two chained through a `--_line`
164
- step: a WebKit build measurably destabilizes composing dozens of tracks
165
- each carrying a three-level color-mix chain into the same page -- fine
166
- at ten, a reproducible ~30-minute CI hang at forty, isolated by removing
167
- one variable at a time (element count, then border at all, then chain
168
- depth) until only chain depth reproduced it. `box`/`.quote`'s extra step
169
- blends the line toward the fill first so a heavily filled box does not
170
- draw a ring of a different colour around its own plate -- a real risk up
171
- to `.solid`'s 100% fill. `.progress` never reaches that: `.solid` is
172
- unsupported here (`registry.json`), so the fill this reads never exceeds
173
- `.soft`'s 12%, and mixing straight from `--intent-border` at that fill
174
- is indistinguishable from blending through the surface first. */
162
+ step: a WebKit build destabilizes when composing dozens of tracks each
163
+ carrying a three-level color-mix chain into one page (fine at ten, a
164
+ ~30-minute CI hang at forty; only chain depth reproduces it).
165
+ `box`/`.quote`'s extra step blends the line toward the fill first so a
166
+ heavily filled box does not draw a ring of a different colour around its
167
+ own plate -- a real risk up to `.solid`'s 100% fill. `.progress` never
168
+ reaches that: `.solid` is unsupported here (`registry.json`), so the fill
169
+ this reads never exceeds `.soft`'s 12%, and mixing straight from
170
+ `--intent-border` at that fill is indistinguishable from blending through
171
+ the surface first. */
175
172
  --_progress-edge: color-mix(in oklab, var(--intent-border) var(--ui-border, var(--_d-border)), transparent);
176
173
 
177
174
  @apply relative h-2.5 w-full overflow-hidden;
@@ -213,13 +210,19 @@
213
210
  &.indeterminate::after {
214
211
  width: 33%;
215
212
  animation: anim-progress-indeterminate 1.5s infinite ease-in-out;
216
- /* No logical form exists for transform-origin, so the inline-start edge
217
- is named directly and mirrored under :dir(rtl) to match the
218
- inset-inline-start positioning above. */
219
- transform-origin: left;
213
+ }
214
+
215
+ /* Reduced motion, for the entries without the reset; see `box.css`. The
216
+ value moves without sliding, and the shimmer and the indeterminate sweep
217
+ stop where the reset would stop them: the fill rests at the start. */
218
+ @media (prefers-reduced-motion: reduce) {
219
+ &::after {
220
+ transition-duration: 0.01ms;
221
+ }
220
222
 
221
- :dir(rtl) & {
222
- transform-origin: right;
223
+ &.active::before,
224
+ &.indeterminate::after {
225
+ animation: none;
223
226
  }
224
227
  }
225
228
  }