@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,187 @@
1
+ /* A filled, edgeless, raised box you can press. `box` paints it; everything
2
+ here is what makes it a button rather than some other box.
3
+
4
+ The resting values are the published default: a button with no presentation
5
+ class is solid and edgeless, and that is a decision written on three lines
6
+ rather than a `var()` fallback buried in an expression. */
7
+ @utility btn {
8
+ /* The neutral reset, carried so `@apply btn` gets it: the reset in
9
+ `intent.css` names `.btn`, which a consumer's `.my-btn` is not, so an
10
+ applied one inside a `.card.success` took the card's intent. In
11
+ `families`, beside the intent classes, so one applied with it wins; never
12
+ on `.btn` itself, which keeps the reset in `components`. See
13
+ docs/internal/model.md#what-apply-carries. */
14
+ @layer families {
15
+ :where(&:not(.btn, .primary, .secondary, .success, .warning, .destructive, .info)) {
16
+ --intent-color: var(--color-text);
17
+ --intent-contrast: var(--color-text-contrast);
18
+ --intent-hover: var(--color-text-hover);
19
+ --intent-strong: var(--color-text-strong);
20
+ --intent-subtle: var(--color-surface);
21
+ --intent-border: var(--ui-ink, var(--color-border));
22
+ --intent-fill-max: 20%;
23
+ }
24
+ }
25
+
26
+ /* The modifier flags, restated so a container's do not reach this button:
27
+ a `.card.p-xs` would otherwise shrink every button inside it. At zero
28
+ specificity in `families`, beside the flag utilities, so one on the
29
+ element wins, applied or as a class. See `modifiers.css`. */
30
+ @layer families {
31
+ :where(&:not(.icon)) {
32
+ --_m-icon: initial;
33
+ }
34
+
35
+ :where(&:not(.pill)) {
36
+ --_m-pill: initial;
37
+ }
38
+
39
+ :where(&:not(.p-xs)) {
40
+ --_m-p-xs: initial;
41
+ }
42
+
43
+ :where(&:not(.p-sm)) {
44
+ --_m-p-sm: initial;
45
+ }
46
+
47
+ :where(&:not(.p-lg)) {
48
+ --_m-p-lg: initial;
49
+ }
50
+ }
51
+
52
+ @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;
53
+
54
+ /* After `@apply box`: `box` states its own resting defaults, and the later
55
+ declaration wins. */
56
+ --_d-fill: 100%;
57
+ --_d-fg-on-fill: 100%;
58
+ --_d-border: 0%;
59
+ --_d-elevation: 1;
60
+ --_d-holds-ink: ;
61
+
62
+ /* With no fill, the neutral line is the region's unfilled ink when the
63
+ aesthetic names one -- glass does, for a `.ghost.edged` button. See `--_bare-ink` in
64
+ `box.css`. Unset, the reference is invalid and `box` keeps its line. */
65
+ --_bare-ink: var(--_unfilled-ink);
66
+
67
+ /* The default step's vertical padding gives back the line `box` draws, so a
68
+ default button is exactly `--control-height` tall like `.ipt` beside it,
69
+ under any aesthetic's line width; at the default 16px/1.5 text the label
70
+ and the padding fill it. The surrounding font, not the user agent's
71
+ smaller button one, on every entry: `/components` carries no reset to say
72
+ so, and a `.btn` link and a `.btn` button have to print the same label.
73
+ Both are the fallbacks of the size steps below. */
74
+ font-family: inherit;
75
+ /* The label is material, shared with `.badge`: weight, tracking, case, and a
76
+ text shadow, set by an aesthetic as tokens (`.chunky-tile`'s heavier,
77
+ tracked label), and a consumer's own `font-*`, `tracking-*`, `uppercase`, or
78
+ text-shadow utility still beats the aesthetic. Tracking, case, and shadow
79
+ fall back to `normal` and `none`, the label's own values, so none of them
80
+ inherits: body copy's wider tracking is set on `body`, and a button that
81
+ took it, or a container's, printed a tracked label nothing asked for. The
82
+ user agent already stops case and shadow inheriting into a button, and
83
+ `/native` maps `<button>` and the button inputs here. The shadow is written
84
+ without a colour, so it takes the label's own and cannot lower its
85
+ contrast. See docs/internal/model.md#cascade-layers. */
86
+ font-weight: var(--ui-label-weight, var(--font-weight-semibold));
87
+ letter-spacing: var(--ui-label-tracking, normal);
88
+ text-transform: var(--ui-label-case, none);
89
+ text-shadow: var(--ui-label-shadow, none);
90
+
91
+ /* The size steps. `sm` and `lg` raise a flag (`size.css`) and each line
92
+ below computes only while its flag is up, so the declarations that read
93
+ them fall back to the default step. Written in the component's own body,
94
+ not as `&.sm`, so `@apply btn sm` carries the step and a consumer's
95
+ `min-h-*` or `px-*` beats it as it beats the default. `--_scale-size`
96
+ is the step `box` scales the corner and the depth by. */
97
+ --_sm-h: var(--_size-sm) 2rem;
98
+ --_lg-h: var(--_size-lg) 3rem;
99
+ --_sm-px: var(--_size-sm) 0.75rem;
100
+ --_lg-px: var(--_size-lg) 1.25rem;
101
+ --_sm-py: var(--_size-sm) 0.25rem;
102
+ --_lg-py: var(--_size-lg) 0.625rem;
103
+ --_sm-text: var(--_size-sm) var(--text-sm);
104
+ --_lg-text: var(--_size-lg) 1.0625rem;
105
+ --_sm-leading: var(--_size-sm) var(--tw-leading, var(--text-sm--line-height));
106
+ --_sm-scale: var(--_size-sm) 0.75;
107
+ --_lg-scale: var(--_size-lg) 1.25;
108
+ --_scale-size: var(--_sm-scale, var(--_lg-scale));
109
+
110
+ /* The modifiers: `icon`, `pill`, and the padding steps. Each raises a flag
111
+ (`modifiers.css`) and the lines below compute only while theirs is up, as
112
+ the size steps above do, so `@apply btn icon` carries the shape. A line
113
+ that names two flags needs both.
114
+
115
+ Bare `sm`/`lg` are the size modifier's names, so the padding steps take
116
+ a `p-` prefix. `p-xs` is the tightest, below `p-sm`: a chip-sized
117
+ button in a toolbar or a table cell. */
118
+ --_icon-pad: var(--_m-icon) var(--_sm-icon-pad, 0.5rem);
119
+ --_sm-icon-pad: var(--_size-sm) calc(0.375rem - var(--_width));
120
+ --_xs-px: var(--_m-p-xs) --spacing(2);
121
+ --_xs-py: var(--_m-p-xs) --spacing(0.5);
122
+ --_psm-px: var(--_m-p-sm) --spacing(2.5);
123
+ --_psm-py: var(--_m-p-sm) --spacing(1);
124
+ --_plg-px: var(--_m-p-lg) --spacing(6);
125
+ --_plg-py: var(--_m-p-lg) --spacing(3);
126
+ /* An icon button's padding step is the same on every side. */
127
+ --_xs-icon-px: var(--_m-icon) var(--_xs-py);
128
+ --_psm-icon-px: var(--_m-icon) var(--_psm-py);
129
+ --_plg-icon-px: var(--_m-icon) var(--_plg-py);
130
+ --_xs-scale: var(--_m-p-xs) 0.5;
131
+ --_psm-scale: var(--_m-p-sm) 0.75;
132
+ --_plg-scale: var(--_m-p-lg) 1.25;
133
+ --_scale-pad: var(--_plg-scale, var(--_psm-scale, var(--_xs-scale)));
134
+
135
+ /* The size floor. `p-xs` is the one padding step that drops it for a
136
+ labeled button: at that tier the button is a chip in a toolbar or a table
137
+ cell, where `--control-height` is taller than the row it sits in; every
138
+ other size and padding class keeps the floor, so a plain button stays a
139
+ comfortable target.
140
+
141
+ An icon button drops both floors at every padding tier. It is already
142
+ square -- `aspect-ratio` ties its width to whatever height it lands on
143
+ -- so a surviving floor would keep the button bigger than its own glyph
144
+ however tight the padding asked to be. */
145
+ --_floor: var(--_sm-h, var(--_lg-h, var(--control-height)));
146
+ --_xs-floor: var(--_m-p-xs) 0px;
147
+ --_psm-icon-floor: var(--_m-icon) var(--_m-p-sm) 0px;
148
+ --_plg-icon-floor: var(--_m-icon) var(--_m-p-lg) 0px;
149
+ --_icon-floor: var(--_m-icon) var(--_xs-floor, var(--_psm-icon-floor, var(--_plg-icon-floor, var(--_floor))));
150
+
151
+ min-height: var(--_xs-floor, var(--_psm-icon-floor, var(--_plg-icon-floor, var(--_floor))));
152
+ /* Square at each size step. The width is named as well as the height,
153
+ because WebKit does not let `aspect-ratio` widen a box past its
154
+ `min-width`. */
155
+ min-width: var(--_icon-floor, auto);
156
+ --_icon-ratio: var(--_m-icon) 1 / 1;
157
+ aspect-ratio: var(--_icon-ratio, auto);
158
+ padding-inline: var(
159
+ --_plg-icon-px,
160
+ var(
161
+ --_psm-icon-px,
162
+ var(
163
+ --_xs-icon-px,
164
+ var(--_plg-px, var(--_psm-px, var(--_xs-px, var(--_icon-pad, var(--_sm-px, var(--_lg-px, 1rem))))))
165
+ )
166
+ )
167
+ );
168
+ padding-block: var(
169
+ --_plg-py,
170
+ var(--_psm-py, var(--_xs-py, var(--_icon-pad, var(--_sm-py, var(--_lg-py, calc(0.5rem - var(--_width)))))))
171
+ );
172
+ font-size: var(--_sm-text, var(--_lg-text, inherit));
173
+ line-height: var(--_sm-leading, inherit);
174
+
175
+ /* A pill takes the round material ahead of the box radius, which is
176
+ `box`'s own formula restated. Sketch gives the token the button's own box
177
+ outline; a bevel still cuts the full radius to points under cyber. */
178
+ --_pill-radius: var(--_m-pill) var(--ui-radius-pill, calc(infinity * 1px));
179
+ border-radius: var(
180
+ --_pill-radius,
181
+ var(
182
+ --ui-radius,
183
+ calc(var(--_corner) * var(--ui-corner-tl, 1)) calc(var(--_corner) * var(--ui-corner-tr, 1))
184
+ calc(var(--_corner) * var(--ui-corner-br, 1)) calc(var(--_corner) * var(--ui-corner-bl, 1))
185
+ )
186
+ );
187
+ }
@@ -0,0 +1,11 @@
1
+ @reference "tailwindcss";
2
+ @import "../theme.css";
3
+ @reference "../surface.css";
4
+
5
+ /* The focused entry: what the rules in `feedback.rules.css` need to compile on their
6
+ own, then the rules. An aggregate entry imports the rules file directly and
7
+ supplies those needs once -- Tailwind stacks a `@utility` each time a file
8
+ that defines it is imported or referenced, so a second route to `box.css`
9
+ doubles every `@apply box`. See docs/internal/model.md (One route to each
10
+ utility). */
11
+ @import "./feedback.rules.css";
@@ -1,8 +1,3 @@
1
- @reference "tailwindcss";
2
- @import "../theme.css";
3
- @reference "../box.css";
4
- @reference "../surface.css";
5
-
6
1
  @keyframes anim-skeleton {
7
2
  0% {
8
3
  background-position: 200% 0;
@@ -39,12 +34,11 @@
39
34
 
40
35
  /* Same carve-out as `.panel`'s, and for the same reason: a neutral alert
41
36
  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.
37
+ 250, 250`), a slab rather than a status signal. Only a quiet neutral tint
38
+ is untinted, through `surface`'s `--_quiet`, stepped from the resolved
39
+ fill so a container's `.ghost` or `.solid` renders the alert as the
40
+ alert's own `.ghost` or `.solid` would. A named intent keeps its ordinary wash, because untinting
41
+ `.alert.soft.destructive` would silence the status it exists to carry.
48
42
 
49
43
  Grounds on `--color-foreground` through `--_d-surface-ground`, exactly like
50
44
  `.panel` and a resting `.card.soft`, so glass's ground and a consumer's
@@ -52,10 +46,8 @@
52
46
  reveal a tint into either, so this is permanent. A neutral alert keeps its
53
47
  identity: `--_d-border: 100%` above holds regardless of fill, so it still
54
48
  reads as a framed box, just a quiet one instead of a grey slab. */
55
- &:not(.primary, .secondary, .success, .warning, .destructive, .info, .solid, .ghost) {
56
- --_fill-cap: 0%;
57
- --_d-surface-ground: var(--color-foreground);
58
- }
49
+ --_fill-cap: calc(100% - var(--_quiet));
50
+ --_d-surface-ground: color-mix(in oklab, var(--color-foreground) var(--_quiet), var(--color-background));
59
51
  }
60
52
 
61
53
  /* No icon is shipped. `.alert` is already `flex`, so an icon dropped in as a
@@ -82,17 +74,38 @@
82
74
  /* A soft, edgeless chip. Small enough that the structural silhouette would be
83
75
  most of its height, so it takes the tight one where an aesthetic ships a pair. */
84
76
  @utility badge {
77
+ /* The neutral reset, carried so `@apply badge` gets it, as `btn` carries its own. */
78
+ @layer families {
79
+ :where(&:not(.badge, .primary, .secondary, .success, .warning, .destructive, .info)) {
80
+ --intent-color: var(--color-text);
81
+ --intent-contrast: var(--color-text-contrast);
82
+ --intent-hover: var(--color-text-hover);
83
+ --intent-strong: var(--color-text-strong);
84
+ --intent-subtle: var(--color-surface);
85
+ --intent-border: var(--ui-ink, var(--color-border));
86
+ --intent-fill-max: 20%;
87
+ }
88
+ }
89
+
90
+ @apply box inline-flex min-h-6 w-fit items-center gap-1 px-2.5 text-xs;
91
+
92
+ /* After `@apply box`: `box` states its own resting defaults, and the later
93
+ declaration wins. */
85
94
  --_d-fill: 12%;
86
95
  --_d-fg-on-fill: 0%;
87
96
  --_d-border: 0%;
88
97
  --_d-elevation: 0;
89
98
 
90
- @apply box inline-flex min-h-6 w-fit items-center gap-1 px-2.5 text-xs;
99
+ /* With no fill, the neutral line is the region's unfilled ink when the
100
+ aesthetic names one -- glass does, for a `.ghost.edged` badge. See `--_bare-ink` in
101
+ `box.css`. Unset, the reference is invalid and `box` keeps its line. */
102
+ --_bare-ink: var(--_unfilled-ink);
91
103
 
92
- /* The label material `.btn` reads. Unset, tracking, case, and shadow inherit,
93
- as a badge's text always has. */
104
+ /* The label material `.btn` reads. Unset, tracking is `normal`, as `.btn`'s
105
+ is, so neither body copy's tracking nor a container's reaches the label;
106
+ case and shadow inherit, as a badge's text always has. */
94
107
  font-weight: var(--ui-label-weight, var(--font-weight-bold));
95
- letter-spacing: var(--ui-label-tracking);
108
+ letter-spacing: var(--ui-label-tracking, normal);
96
109
  text-transform: var(--ui-label-case);
97
110
  text-shadow: var(--ui-label-shadow);
98
111
 
@@ -108,6 +121,22 @@
108
121
  for content rather than a box with a look of its own, so there is nothing for
109
122
  a container's `.solid` or `.edgeless` to say about it. */
110
123
  @utility skeleton {
124
+ /* The indicator reset, carried so `@apply skeleton` gets it, as `btn` carries
125
+ its own: the neutral slots, with the two an indicator takes from the
126
+ colour it sits in. Without it an applied one read an undefined
127
+ `--intent-border`. */
128
+ @layer families {
129
+ :where(&:not(.skeleton, .primary, .secondary, .success, .warning, .destructive, .info)) {
130
+ --intent-color: currentColor;
131
+ --intent-contrast: var(--color-text-contrast);
132
+ --intent-hover: var(--color-text-hover);
133
+ --intent-strong: var(--color-text-strong);
134
+ --intent-subtle: var(--color-surface);
135
+ --intent-border: currentColor;
136
+ --intent-fill-max: 20%;
137
+ }
138
+ }
139
+
111
140
  --skeleton-base: color-mix(in oklab, var(--intent-color) 6%, var(--intent-subtle));
112
141
  --skeleton-sheen: color-mix(in oklab, var(--intent-color) 10%, var(--color-foreground));
113
142
 
@@ -131,6 +160,15 @@
131
160
  @media (prefers-reduced-motion: reduce) {
132
161
  animation: none;
133
162
  }
163
+
164
+ /* Forced colours remove background images, and the gradient is all a
165
+ skeleton is: it vanished. A system-colour outline keeps the placeholder's
166
+ shape on the page. A border, inside the box, so nothing moves. */
167
+ @media (forced-colors: active) {
168
+ box-sizing: border-box;
169
+ border: 1px solid CanvasText;
170
+ animation: none;
171
+ }
134
172
  }
135
173
 
136
174
  /* The track is a box, the fill is not: `box.css`'s formula composes the frame,
@@ -141,9 +179,52 @@
141
179
  docs/internal/model.md#components-that-do-not-take-the-whole-of-box for the
142
180
  decision and why the other two indicators do not get the same treatment. */
143
181
  @utility progress {
182
+ /* The indicator reset, carried so `@apply progress` gets it, as `btn` carries
183
+ its own: the neutral slots, with the two an indicator takes from the
184
+ colour it sits in. Without it an applied one read an undefined
185
+ `--intent-border`. */
186
+ @layer families {
187
+ :where(&:not(.progress, .primary, .secondary, .success, .warning, .destructive, .info)) {
188
+ --intent-color: currentColor;
189
+ --intent-contrast: var(--color-text-contrast);
190
+ --intent-hover: var(--color-text-hover);
191
+ --intent-strong: var(--color-text-strong);
192
+ --intent-subtle: var(--color-surface);
193
+ --intent-border: currentColor;
194
+ --intent-fill-max: 20%;
195
+ }
196
+ }
197
+
198
+ @layer families {
199
+ :where(&:not(.active)) {
200
+ --_m-active: initial;
201
+ }
202
+
203
+ :where(&:not(.indeterminate)) {
204
+ --_m-indeterminate: initial;
205
+ }
206
+ }
207
+
208
+ /* The two states. Each raises a flag (`modifiers.css`); the pseudo-elements
209
+ below read these lines, which inherit to them as the flags do. */
210
+ --_active-content: var(--_m-active) "";
211
+ --_active-shimmer: var(--_m-active) anim-skeleton calc(var(--motion-duration-slow) * 4) linear infinite;
212
+ --_indeterminate-width: var(--_m-indeterminate) 33%;
213
+ --_indeterminate-sweep: var(--_m-indeterminate) anim-progress-indeterminate 1.5s infinite ease-in-out;
214
+
215
+ /* Printed as drawn. A browser leaves backgrounds off paper by default, and
216
+ the bar is nothing but two backgrounds: the track and the value. */
217
+ print-color-adjust: exact;
218
+
144
219
  --_d-fill: 12%;
145
220
  --_d-fg-on-fill: 0%;
146
221
  --_d-border: 0%;
222
+ /* The track is a 12% tint, 1.2:1 against the page. With more contrast asked
223
+ for it is outlined, so where the bar ends is not left to the tint. An edge
224
+ class still decides: `--ui-border` is read before this. */
225
+ @media (prefers-contrast: more) {
226
+ --_d-border: 100%;
227
+ }
147
228
  --_d-elevation: 0;
148
229
  /* `--progress-color`/`--progress-surface`/`--progress-edge` are the Tier 1
149
230
  override path an aesthetic or consumer reaches `.progress` through (see
@@ -189,14 +270,19 @@
189
270
  border-radius: var(--ui-radius-pill, var(--ui-radius, var(--ui-corner, calc(infinity * 1px))));
190
271
  corner-shape: var(--ui-corner-shape, round);
191
272
  background-color: var(--progress-color, var(--intent-color));
192
- width: var(--progress-value, 0%);
273
+ width: var(--_indeterminate-width, var(--progress-value, 0%));
274
+ animation: var(--_indeterminate-sweep, none);
193
275
  z-index: 2;
194
276
  }
195
277
 
196
- &.active::before {
197
- content: "";
278
+ /* The shimmer, which generates no box unless `active`. Its animation is
279
+ gated with it: WebKit 26 lists an animation on a pseudo-element that
280
+ generates no box in `getAnimations()`, and an endless one there never
281
+ settles (`structure.spec.ts`, "reaches every line a component draws"). */
282
+ &::before {
283
+ content: var(--_active-content, none);
198
284
  @apply absolute inset-0 block h-full;
199
- animation: anim-skeleton calc(var(--motion-duration-slow) * 4) linear infinite;
285
+ animation: var(--_active-shimmer, none);
200
286
  background: linear-gradient(
201
287
  90deg,
202
288
  transparent,
@@ -207,11 +293,6 @@
207
293
  z-index: 1;
208
294
  }
209
295
 
210
- &.indeterminate::after {
211
- width: 33%;
212
- animation: anim-progress-indeterminate 1.5s infinite ease-in-out;
213
- }
214
-
215
296
  /* Reduced motion, for the entries without the reset; see `box.css`. The
216
297
  value moves without sliding, and the shimmer and the indeterminate sweep
217
298
  stop where the reset would stop them: the fill rests at the start. */
@@ -220,9 +301,32 @@
220
301
  transition-duration: 0.01ms;
221
302
  }
222
303
 
223
- &.active::before,
224
- &.indeterminate::after {
304
+ &::before,
305
+ &::after {
225
306
  animation: none;
226
307
  }
227
308
  }
228
309
  }
310
+
311
+ /* Forced colours repaint every background as Canvas, the value fill on a
312
+ Canvas track included, so a half-full bar read as an empty one. The bar opts
313
+ out, as the toggles do in `form.css`, and names system colours for every
314
+ part: the value in CanvasText, the track in Canvas, and a CanvasText
315
+ boundary at the track's own width, because a `.soft.edgeless` track has no
316
+ visible line and the extent of the bar is half of what it says. The shimmer
317
+ is decoration in the intent colour, so it goes. */
318
+ @media (forced-colors: active) {
319
+ .progress {
320
+ forced-color-adjust: none;
321
+ background-color: Canvas;
322
+ border-color: CanvasText;
323
+
324
+ &::after {
325
+ background-color: CanvasText;
326
+ }
327
+
328
+ &::before {
329
+ display: none;
330
+ }
331
+ }
332
+ }
@@ -0,0 +1,11 @@
1
+ @reference "tailwindcss";
2
+ @import "../theme.css";
3
+ @reference "../box.css";
4
+
5
+ /* The focused entry: what the rules in `form.rules.css` need to compile on their
6
+ own, then the rules. An aggregate entry imports the rules file directly and
7
+ supplies those needs once -- Tailwind stacks a `@utility` each time a file
8
+ that defines it is imported or referenced, so a second route to `box.css`
9
+ doubles every `@apply box`. See docs/internal/model.md (One route to each
10
+ utility). */
11
+ @import "./form.rules.css";