@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
@@ -1,29 +1,53 @@
1
1
  @reference "../box.css";
2
2
 
3
+ /* A scroll box clips whatever paints outside it, and `overflow-x` takes the
4
+ vertical overflow with it, so an aesthetic's halo around the table -- cyber's
5
+ glow -- would be cut flush on every side. The wrapper makes room for the halo
6
+ inside itself and gives the same room back outside, so the table sits where
7
+ it does and only the scroll box grows. Zero wherever no halo is drawn. The
8
+ cost, under a halo: the wrapper reaches that far past its parent, which shows
9
+ only where the parent is flush with the viewport. Vertical margins still add
10
+ up to the same gap when they collapse with a sibling's, since the negative
11
+ one is paid back by the padding.
12
+
13
+ It reads the halo from its own scope, so an aesthetic class on the table
14
+ alone gives it nothing to make room for: the class belongs on the wrapper or
15
+ an ancestor, as `content-and-layout.md` says. A parent cannot read a child's
16
+ tokens, and reaching down with `:has()` would name an aesthetic here. */
3
17
  @utility table-wrap {
4
- @apply w-full overflow-x-auto;
18
+ @apply overflow-x-auto;
19
+
20
+ --_table-wrap-bleed: calc(var(--ui-halo-blur, 0px) + var(--ui-halo-spread, 0px));
21
+
22
+ box-sizing: border-box;
23
+ width: calc(100% + var(--_table-wrap-bleed) * 2);
24
+ margin: calc(var(--_table-wrap-bleed) * -1);
25
+ padding: var(--_table-wrap-bleed);
5
26
  }
6
27
 
7
28
  /* The one component that takes the frame of `box` and not the whole of it. The
8
- border, the radius and the clip come from the box; the head tone, the cell
9
- rules and the row hover are private tokens, because none of the three axes has
10
- a vocabulary for the inside of a table.
11
-
12
- Its `overflow: hidden` is what keeps `border-separate` corners inside the
13
- radius, and it does not compose with an aesthetic's `clip-path`: where both
14
- apply, the clip wins and the corners square off. Recorded in `registry.json`
15
- beside the decision. */
29
+ border and the plate come from the box, and the corner is a surface's,
30
+ because a table is a pane of content rather than a control; the head tone,
31
+ the cell rules and the row hover are private tokens, because none of the
32
+ three axes has a vocabulary for the inside of a table.
33
+
34
+ Nothing here clips the table. Chromium and WebKit lay a `<caption>` out
35
+ inside the table's own box while painting the plate and the frame around the
36
+ rows alone, so anything that acts on the whole box -- an `overflow` clip, a
37
+ `clip-path`, a backdrop filter, a positioned layer -- spans the caption: an
38
+ `overflow: hidden` would round the caption's corners instead of the head's
39
+ and cut the caption's first letter. The corner cells take the table's corners
40
+ instead (below), which every engine draws on the rows. Recorded in
41
+ `registry.json` beside the decision. */
16
42
  @utility data-table {
17
- @apply box text-body w-full border-separate text-left;
43
+ @apply box text-body w-full border-separate text-start;
18
44
 
19
45
  --_d-fill: 12%;
20
46
  --_d-fg-on-fill: 0%;
21
47
  /* Edgeless, so the frame is asked for rather than inherited from a default
22
- nobody can see in their own markup. Published `edged`, `.edged` on a table
23
- computed exactly what no class computed -- a switch that could not move
24
- anything -- and the frame arrived because of a line in `registry.json`.
25
- `.edged` draws it now, and the two boundaries a table keeps whatever else it
26
- draws mean a frameless one still reads as a table. */
48
+ nobody can see in their own markup. `.edged` draws it, and the two
49
+ boundaries a table keeps whatever else it draws mean a frameless one still
50
+ reads as a table. */
27
51
  --_d-border: 0%;
28
52
  --_d-elevation: 0;
29
53
 
@@ -31,178 +55,232 @@
31
55
  other and a neutral table head is otherwise a slab of the page's ink with
32
56
  ink printed on it. */
33
57
  --table-fill: calc(min(var(--ui-fill, var(--_d-fill)), var(--intent-fill-max, 100%)) * var(--ui-bg-alpha, 1));
34
- /* The head's plate is the tint, so a table carrying no fill has no head plate.
35
- `.ghost` means no tint here the way it means no fill everywhere else, and a
36
- ghost table is boundaries and type and nothing else -- which is what `.ghost`
37
- is for on this component, since the published resting fill is `soft`.
38
-
39
- Zero stays zero, and nothing past it saturates the same way any more. A
40
- `* 100` multiplier used to put every non-zero fill over the `min(100%, ...)`
41
- ceiling in one step -- `.soft`'s 12% and `.solid`'s 100% both landed on a
42
- fully opaque plate, so the only thing left to tell them apart was how much
43
- `--intent-color` got mixed onto it after (`--table-fill`, 12% vs 20% capped),
44
- a difference too small to read as the volume-level step presentation
45
- promises everywhere else in the model. `* 5` keeps zero at zero and a
46
- `.solid`'s 100% raw fill still clears the ceiling -- the same fully opaque
47
- head it always drew -- but stops `.soft`'s 12% short, at 60%: a visibly
48
- lighter, partly-page-showing-through plate instead of a second opaque one,
49
- which is what makes the two presentations separate again.
50
-
51
- It reads `--ui-fill` rather than `--table-fill` because that is the question
52
- being asked: what the presentation wanted, not what the intent cap and an
53
- aesthetic's alpha left of it. A glass `.soft` table still wants its head
54
- plate, and neutral's 20% cap has no bearing on whether there is one. Reading
55
- the raw class value also keeps the expression a `calc()` over one `var()`
56
- rather than over `--table-fill`'s own `calc()`/`min()` chain, which matters
57
- for the reason `--table-rule-plated` below records. */
58
+ /* The head's plate is the tint, so a table carrying no fill has no head
59
+ plate. `.ghost` means no tint here the way it means no fill everywhere
60
+ else, and a ghost table is boundaries and type and nothing else -- which is
61
+ what `.ghost` is for on this component, since the published resting fill is
62
+ `soft`.
63
+
64
+ Zero stays zero, and nothing past it saturates: `* 5` keeps zero at zero,
65
+ lets `.solid`'s 100% clear the `min(100%, ...)` ceiling to a fully opaque
66
+ head, and stops `.soft`'s 12% short at 60%, a visibly lighter plate, so the
67
+ two presentations separate. A larger multiplier lands both on a fully
68
+ opaque plate, leaving only the small intent mix to tell them apart.
69
+
70
+ It reads `--ui-fill` rather than `--table-fill` because the question is
71
+ what the presentation wanted, not what the intent cap and an aesthetic's
72
+ alpha left of it: a glass `.soft` table still wants its head plate, and
73
+ neutral's 20% cap has no bearing on whether there is one. Reading the raw
74
+ class value also keeps the expression a `calc()` over one `var()` rather
75
+ than over `--table-fill`'s own `calc()`/`min()` chain, which matters for
76
+ the reason `--table-rule-plated` below records. */
58
77
  --table-ground: min(100%, calc(var(--ui-fill, var(--_d-fill)) * 5));
59
78
  --table-head-plate: color-mix(in oklab, var(--intent-subtle) var(--table-ground), transparent);
60
79
  --table-head-bg: color-mix(in oklab, var(--intent-color) var(--table-fill), var(--table-head-plate));
61
80
  --table-head-fg: color-mix(in oklab, var(--intent-contrast) var(--_on-fill), var(--intent-strong));
81
+ /* The table's own contrast ink, resolved here so a row with its own intent
82
+ prints on the table's plate in the ink that plate takes: the row's
83
+ `--intent-contrast` is chosen for the row's fill, which a row does not
84
+ paint, and a light one on a light plate reads at 1:1. */
85
+ --_table-contrast: var(--intent-contrast);
62
86
 
63
87
  /* Untinted rather than mixed, the same call `.card.soft` makes: 12% ink onto
64
88
  `--intent-subtle` still reads as `.solid` on neutral, since neutral's ink
65
- and ground are both on the same greyscale axis and no ramp over that ground
66
- changes it much. A head has no hover or interaction state to reveal a tint
67
- into the way a card does on `.interactive`/`.hoverable`, so there is no
68
- second state to move a mechanism to -- `.soft` is `--color-foreground`,
69
- flat, always, for every intent. That mutes a colored `.success`/`.destructive`
70
- head at rest the way it mutes a non-interactive `.card.soft`; accepted for
71
- the same reason, not solved a different way.
72
-
73
- The table's own base plate rests the same way, for a reason the head fix
74
- alone did not cover: a `.soft` head landing at `--color-foreground` (five
75
- units off the page) sitting directly above a body still carrying the
76
- ordinary 12% tint (twenty-four units past the head) read backwards -- the
77
- seam that should read as one table read as the head belonging to the page
78
- and the body being the odd one out. `--_fill-cap` is `box.css`'s override
79
- slot (the same one `.card.soft` uses, for the reasons the comment there
80
- gives), and `--_d-ground` is box's own private ground
81
- slot, unclaimed here until now -- both land on the table's own element,
82
- same as `--table-fill`/`--table-head-plate` do, so nothing here fights
83
- `.soft` for `--ui-fill` either. `.ghost` is left at box's default
84
- (`--_d-ground: transparent`), so it stays the fully bare boundaries-only
85
- table it already was -- `.soft` now differs from it by keeping an opaque,
86
- untinted plate and a head label strip, not by how much ink is in either.
89
+ and ground are both on the same greyscale axis. A head has no hover or
90
+ interaction state to reveal a tint into the way a card does, so `.soft` is
91
+ `--color-foreground`, flat, always, for every intent. That mutes a coloured
92
+ `.success`/`.destructive` head at rest the way it mutes a non-interactive
93
+ `.card.soft`; accepted for the same reason.
94
+
95
+ The table's own base plate rests the same way: a `.soft` head at
96
+ `--color-foreground` (five units off the page) above a body carrying the
97
+ ordinary 12% tint (twenty-four units past the head) reads backwards, as
98
+ though the head belonged to the page and the body were the odd one out.
99
+ `--_fill-cap` is `box.css`'s override slot (the one `.card.soft` uses) and
100
+ `--_d-ground` is box's private ground slot; both land on the table's own
101
+ element, so nothing here fights `.soft` for `--ui-fill`. `.ghost` is left
102
+ at box's default (`--_d-ground: transparent`), so it stays the fully bare
103
+ boundaries-only table; `.soft` differs from it by keeping an opaque,
104
+ untinted plate and a head label strip.
87
105
 
88
106
  `:not(.solid, .ghost)` rather than `.soft`: the published resting fill is
89
- `soft`, so a bare `<table class="data-table">` -- the members table in
90
- `playground/app-shell/`, with no fill class at all -- is the common case
91
- this has to reach, not the edge case. `.soft` alone would miss every table
92
- that gets soft by doing nothing. */
107
+ `soft`, so a bare `<table class="data-table">` with no fill class at all is
108
+ the common case this has to reach. */
93
109
  &:not(.solid, .ghost) {
94
110
  --table-fill: 0%;
95
- --table-head-plate: var(--color-foreground);
111
+ --table-head-plate: var(--table-ground-plate);
96
112
  --_fill-cap: 0%;
97
- --_d-ground: var(--color-foreground);
113
+ --_d-ground: var(--table-ground-plate);
114
+
115
+ /* A table is a pane of content, so under an aesthetic whose surfaces are
116
+ glass it is glass too: the plate is the aesthetic's surface ground when
117
+ one is named, and the blur behind it is the one surfaces take. Head and
118
+ body share the plate, as they do on the page. Only this arm reads them:
119
+ a `.solid` table is opaque, like every `.solid`, and a `.ghost` one is
120
+ boundaries and type with nothing behind them to blur. */
121
+ --table-ground-plate: var(--ui-surface-ground, var(--color-foreground));
122
+
123
+ -webkit-backdrop-filter: var(--ui-backdrop, none);
124
+ backdrop-filter: var(--ui-backdrop, none);
98
125
  }
99
126
 
100
127
  /* How much rule there is between body rows, and none unless asked for. Ruling
101
- every row is a style choice rather than part of what a table is, and tying it
102
- to the edge axis made it arrive by implication instead: a table drew rules
103
- because its published edge default happened to be `edged`, which is a fact
104
- about the registry rather than anything the markup says. A consumer reading
105
- their own HTML could not tell whether they had asked for rules.
106
-
107
- `.ruled` is that ask and `.ruleless` is its opposite, with `--ui-rule` behind
108
- both -- so a consumer who wants a whole region ruled, or a whole region bare,
109
- sets the token on a container instead of classing every table.
128
+ every row is a style choice rather than part of what a table is, so it is
129
+ not tied to the edge axis: `.ruled` asks for it, `.ruleless` is its
130
+ opposite, and `--ui-rule` is behind both, so a consumer who wants a whole
131
+ region ruled, or a whole region bare, sets the token on a container instead
132
+ of classing every table.
110
133
 
111
134
  One token covers three positions because the two places that read it take
112
135
  different fallbacks. Unset, each site answers for itself: no rules between
113
136
  rows, but the boundaries below still drawn. Set, the answer is the same at
114
- both. So `.ruled` writes 100% and gets rules everywhere, `.ruleless` writes 0%
115
- and gets a table with no lines in it at all, and a table nobody classed sits
116
- between them. A second token would have said the same thing with more parts,
117
- and would have let the two drift. */
137
+ both. So `.ruled` writes 100% and gets rules everywhere, `.ruleless` writes
138
+ 0% and gets a table with no lines in it at all, and a table nobody classed
139
+ sits between them. */
118
140
  --table-rule-amount: var(--ui-rule, 0%);
119
141
  --table-boundary-amount: var(--ui-rule, 100%);
120
142
  /* What the rule is drawn in, which is not the same tone at every fill.
121
- `.ghost` and `.soft` keep `--intent-border` -- the slot an aesthetic restates
122
- through `--ui-ink` -- because at those fills the plate has not swallowed it.
123
- `.solid` is where it has: at a full fill `--intent-border` *is* the plate for
124
- every intent, and at neutral's capped fill it is a near shade of the same
125
- grey, so the rule the rows are separated by disappears exactly where the
126
- plate is busiest.
127
-
128
- The filled case draws in `currentColor` instead, at a fifth. That is the ink
129
- the cell is already printing -- `--table-head-fg` on a head, `box`'s own
130
- `--_fg` in the body -- so it reads on a light plate and a dark one without a
131
- per-intent branch, and it lands *over* the plate because a translucent border
132
- composites onto the background the element already painted.
133
-
134
- Naming the ink rather than composing it is also what keeps this expression
135
- cheap, and cheap is a requirement here rather than a preference. Written as
136
- `color-mix(--table-head-fg 20%, --_bg)` the rule expanded, through four levels
137
- of substitution, into a `color-mix()` over two more `color-mix()`es over a
138
- `min()`/`max()`/`calc()` chain -- and every `th` and `td` on a page resolves it.
139
- WebKit's renderer crashed outright on the two routes that draw tables, taking
140
- 22 specs across four files with it, while Chromium and Firefox rendered them
141
- correctly. Neither the depth nor the table alone did it: bisected, the new
142
- tokens were fine until the cells consumed them.
143
-
144
- `currentColor` and `transparent` are leaves the engine has resolved already,
145
- which is what takes the expression back down to something every engine can
146
- carry. `--table-row-hover` below is written the same way, for the same
147
- reason. */
143
+ `.ghost` and `.soft` keep `--intent-border` -- the slot an aesthetic
144
+ restates through `--ui-ink` -- because at those fills the plate has not
145
+ swallowed it. `.solid` is where it has: at a full fill `--intent-border`
146
+ *is* the plate for every intent, and at neutral's capped fill it is a near
147
+ shade of the same grey, so the row rule disappears exactly where the plate
148
+ is busiest.
149
+
150
+ The filled case draws in `currentColor` at a fifth instead: the ink the
151
+ cell is already printing (`--table-head-fg` on a head, `box`'s own `--_fg`
152
+ in the body), so it reads on a light plate and a dark one without a
153
+ per-intent branch, and it lands *over* the plate because a translucent
154
+ border composites onto the background the element already painted.
155
+
156
+ Naming the ink rather than composing it also keeps this expression cheap,
157
+ which is a requirement: every `th` and `td` on a page resolves it, and
158
+ written as `color-mix(--table-head-fg 20%, --_bg)` it expands, through four
159
+ levels of substitution, into a `color-mix()` over two more `color-mix()`es
160
+ over a `min()`/`max()`/`calc()` chain, which crashes WebKit's renderer on
161
+ the routes that draw tables. `currentColor` and `transparent` are leaves
162
+ the engine has already resolved, which takes the expression back down to
163
+ something every engine can carry. `--table-row-hover` below is written the
164
+ same way, for the same reason. */
148
165
  --table-rule-plated: color-mix(in oklab, currentColor 20%, transparent);
149
166
  --table-rule-tone: color-mix(in oklab, var(--table-rule-plated) var(--ui-fill, var(--_d-fill)), var(--intent-border));
150
167
  --table-rule: color-mix(in oklab, var(--table-rule-tone) var(--table-rule-amount), transparent);
151
168
  --table-boundary: color-mix(in oklab, var(--table-rule-tone) var(--table-boundary-amount), transparent);
152
169
  /* A hovered row is a step from the table it is in, not from a tone the page
153
- happens to own. Mixed into `--color-foreground` it was neither: a solid
154
- table paints its body with the intent, so a primary table in dark theme is
155
- white and its hovered row came out near-black -- a dark band across a light
156
- table. Painting a translucent step instead makes it relative at every fill,
157
- the way `box-hover` is: the row composites onto whatever plate the table
158
- drew, so a filled table shifts within its own colour, a soft one deepens its
159
- tint, and a ghost one picks up a faint wash of the page.
160
-
161
- Which tone it steps toward has to move with the fill as well. On a bare or
162
- tinted plate that is `--intent-hover`, the deepening every other component
163
- gets. On a filled one it cannot be: `--intent-hover` is
164
- `mix(--intent-color 82%, --intent-strong)`, which is very nearly the colour
165
- the plate is already painted, so a step toward it is no step. Measured, a
166
- solid success table moved 1.00:1 in light and a solid destructive one
167
- 1.008:1 -- the feedback was declared and invisible. A filled plate steps
168
- toward `currentColor` instead, which is the ink that plate already carries
169
- and therefore always contrasts with it.
170
+ happens to own. A translucent step is relative at every fill, the way
171
+ `box-hover` is: the row composites onto whatever plate the table drew, so a
172
+ filled table shifts within its own colour, a soft one deepens its tint, and
173
+ a ghost one picks up a faint wash of the page. (Mixed into
174
+ `--color-foreground`, a solid primary table in dark theme would hover to
175
+ near-black, a dark band across a light table.)
176
+
177
+ Which tone it steps toward moves with the fill. On a bare or tinted plate
178
+ that is `--intent-hover`, the deepening every other component gets. On a
179
+ filled one it cannot be: `--intent-hover` is `mix(--intent-color 82%,
180
+ --intent-strong)`, very nearly the colour the plate is already painted, so
181
+ a step toward it is no step (a solid success table moves 1.00:1 in light).
182
+ A filled plate steps toward `currentColor` instead, the ink that plate
183
+ already carries and therefore always contrasts with it.
170
184
 
171
185
  Both ends are leaves, and the step lands on `transparent` rather than on
172
- `--_bg`, for the reason `--table-rule-plated` above gives: compositing is
173
- what the browser does with a translucent background anyway, so naming `--_bg`
174
- buys the same colour for two more levels of substitution. */
186
+ `--_bg`, for the reason `--table-rule-plated` above gives. */
175
187
  --table-hover-tone: color-mix(in oklab, currentColor var(--ui-fill, var(--_d-fill)), var(--intent-hover));
176
188
  --table-row-hover: color-mix(in oklab, var(--table-hover-tone) 12%, transparent);
177
189
 
178
190
  border-spacing: 0;
179
- overflow: hidden;
191
+
192
+ /* A surface's corner, as `surface` computes it: a table sits beside cards and
193
+ panels as a pane, and under `.cyber` it took the controls' diagonal while
194
+ every pane around it cut the other one. */
195
+ --_corner-surface: calc(var(--ui-corner-surface, var(--radius-surface)) * var(--_scale));
196
+ border-radius: var(
197
+ --ui-radius-surface,
198
+ calc(var(--_corner-surface) * var(--ui-corner-tl, 1)) calc(var(--_corner-surface) * var(--ui-corner-tr, 1))
199
+ calc(var(--_corner-surface) * var(--ui-corner-br, 1)) calc(var(--_corner-surface) * var(--ui-corner-bl, 1))
200
+ );
201
+ /* No silhouette clip either, for the caption's sake: `.pixel`'s stepped clip
202
+ cut the caption's corners and left the head's square. A pixel table is
203
+ square, which is the chip answer that aesthetic already gives. */
204
+ clip-path: none;
205
+
206
+ /* The corners pass down to the cells that sit in them. Each section and row
207
+ inherits the table's four corners -- a radius does not apply to either, but
208
+ it still computes and inherits -- and each corner cell takes only its own
209
+ corner, as a longhand, so an elliptical or bevelled corner arrives whole.
210
+ The first row is the head's when there is one and the first body's when
211
+ not, and the last row likewise the foot's or the last body's; a caption
212
+ and a `colgroup` are skipped. A cell spanning rows into a corner is not
213
+ reached, and a percentage corner resolves against the cell. */
214
+ & > :is(thead, tbody, tfoot),
215
+ & > :is(thead, tbody, tfoot) > tr {
216
+ border-radius: inherit;
217
+ corner-shape: inherit;
218
+ }
219
+
220
+ & > :is(thead, tbody, tfoot) > tr > * {
221
+ corner-shape: inherit;
222
+ }
223
+
224
+ & > thead > tr:first-child,
225
+ &:not(:has(> thead)) > tbody:first-of-type > tr:first-child {
226
+ & > :first-child {
227
+ border-start-start-radius: inherit;
228
+ }
229
+
230
+ & > :last-child {
231
+ border-start-end-radius: inherit;
232
+ }
233
+ }
234
+
235
+ & > tfoot > tr:last-child,
236
+ &:not(:has(> tfoot)) > tbody:last-of-type > tr:last-child {
237
+ & > :first-child {
238
+ border-end-start-radius: inherit;
239
+ }
240
+
241
+ & > :last-child {
242
+ border-end-end-radius: inherit;
243
+ }
244
+ }
180
245
 
181
246
  & caption {
182
- @apply text-label text-text-secondary pb-2 text-left;
247
+ @apply text-label text-text-secondary pb-2 text-start;
183
248
  }
184
249
 
250
+ /* The head's plate is painted on its cells rather than on the section, so the
251
+ corner cells round it: a section's background ignores the cells' corners. */
185
252
  & thead,
186
253
  & tfoot {
187
- background-color: var(--table-head-bg);
188
254
  color: var(--table-head-fg);
189
255
  }
190
256
 
257
+ & :is(thead, tfoot) > tr > * {
258
+ background-color: var(--table-head-bg);
259
+ }
260
+
191
261
  /* Every cell carries the hairline; what varies is whether it is painted. The
192
- width tracks the aesthetic's own line weight (`--_width`, the same token
193
- `box` uses for the table's outer frame) rather than a fixed `1px`, so a
194
- row rule reads as the same material as the boundary around it instead of
195
- as a leftover default under an aesthetic whose whole point is thickness --
196
- `.chunky-tile`'s 2px frame used to sit over a 1px row rule, which read as
197
- unstyled rather than as a deliberate, quieter line. It still survives a
198
- fill that would otherwise paint over it, because color and width are
199
- independent here: `.ruleless` zeroes the color, not this. */
262
+ width tracks the aesthetic's own line weight rather than a fixed `1px`, so
263
+ a row rule reads as the same material as the boundary around it. It
264
+ survives a fill that would otherwise paint over it, because color and width
265
+ are independent here: `.ruleless` zeroes the color, not this.
266
+
267
+ The width is the aesthetic's, not `box`'s `--_width`: that one is capped by
268
+ `--ui-border-max`, which `.pixel` sets to zero because its frame is an
269
+ inset ring a clip cannot cut. A rule is not a frame and nothing clips a
270
+ table, so under pixel the capped width would draw no rule at all -- no line
271
+ under the head or above the foot, and `.ruled` would change nothing. A
272
+ divider reads the same uncapped width for the same reason.
273
+
274
+ Alignment is logical and stated on the cells, not only on the table: a
275
+ `th` centers unless its parent names an alignment, and `start` is the
276
+ initial value, which counts as naming none. */
277
+ --_table-rule-width: var(--ui-border-width, var(--border-width));
200
278
  & th,
201
279
  & td {
202
- @apply px-4 py-3 align-top;
280
+ @apply px-4 py-3 text-start align-top;
203
281
 
204
282
  border-bottom-style: var(--ui-rule-style, var(--ui-line-style, solid));
205
- border-bottom-width: var(--_width);
283
+ border-bottom-width: var(--_table-rule-width);
206
284
  border-color: var(--table-rule);
207
285
  }
208
286
 
@@ -221,7 +299,7 @@
221
299
 
222
300
  & tfoot tr:first-child > * {
223
301
  border-top-style: var(--ui-rule-style, var(--ui-line-style, solid));
224
- border-top-width: var(--_width);
302
+ border-top-width: var(--_table-rule-width);
225
303
  border-top-color: var(--table-boundary);
226
304
  }
227
305
 
@@ -242,58 +320,87 @@
242
320
 
243
321
  /* Rows deliberately inherit the table's intent instead of resetting it, so a
244
322
  `.data-table.success` tints its own rows. A `<tr class="warning">` still wins,
245
- because an intent class on the element beats an inherited value. */
246
- & tbody tr {
323
+ because an intent class on the element beats an inherited value.
324
+
325
+ The class only changes the row's intent slots, though, and everything the
326
+ row paints was resolved on the table: the text, the rule and the hover
327
+ step are each a custom property whose `var()`s resolved against the
328
+ table's intent, so they inherited down already-green to a destructive row.
329
+ A row with its own intent resolves them again against it. The plate stays
330
+ the table's, so the ink keeps the table's ramp -- `--_on-fill`, inherited
331
+ as a resolved percentage -- and a solid table's rows still print the
332
+ table's contrast ink on it. Only a row that names an intent: an ordinary row would resolve
333
+ the same values, but a `color` declared on every row would override a
334
+ colour a consumer set on the table. */
335
+ & tbody tr:is(.neutral, .primary, .secondary, .success, .warning, .destructive, .info) {
336
+ --table-rule-tone: color-mix(
337
+ in oklab,
338
+ var(--table-rule-plated) var(--ui-fill, var(--_d-fill)),
339
+ var(--intent-border)
340
+ );
341
+ --table-rule: color-mix(in oklab, var(--table-rule-tone) var(--table-rule-amount), transparent);
342
+ --table-hover-tone: color-mix(in oklab, currentColor var(--ui-fill, var(--_d-fill)), var(--intent-hover));
343
+ --table-row-hover: color-mix(in oklab, var(--table-hover-tone) 12%, transparent);
344
+
345
+ color: color-mix(in oklab, var(--_table-contrast) var(--_on-fill), var(--intent-strong));
346
+ }
347
+
348
+ /* A neutral solid table caps its fill at 20%, so its plate is a light grey
349
+ and the ramp keeps `--_on-fill` low: the row's ink is then mostly the
350
+ row's `--intent-strong`, which is chosen against the row's own subtle
351
+ ground rather than a grey one, and success, warning and destructive fall
352
+ under 4.5:1 on it. Those rows print the table's own ink, `--_fg`,
353
+ inherited already resolved against the table, and keep their intent in
354
+ the rule and the hover. */
355
+ &.solid:not(.primary, .secondary, .success, .warning, .destructive, .info)
356
+ tbody
357
+ tr:is(.neutral, .primary, .secondary, .success, .warning, .destructive, .info) {
358
+ color: var(--_fg);
359
+ }
360
+
361
+ & tbody tr > * {
247
362
  transition: background-color var(--motion-duration-fast) var(--motion-ease);
248
363
  }
249
364
 
250
- & tbody tr:hover {
365
+ /* On the cells, for the reason the head's plate is: a hovered last row keeps
366
+ the table's bottom corners. */
367
+ & tbody tr:hover > * {
251
368
  background-color: var(--table-row-hover);
252
369
  }
253
370
 
254
371
  /* The rule comes off the row that is visually last, which is not always the
255
- last row of the body. Written as `tbody tr:last-child` it came off the body's
256
- last row unconditionally, so a table with a footer lost the one rule that
257
- separates the two -- the summary ran straight on from the rows it summarises --
258
- while the footer kept a bottom rule of its own, drawing a hairline immediately
259
- inside the frame. A missing line where one belongs and a doubled one where it
260
- does not, from the same declaration.
372
+ last row of the body. `tbody tr:last-child` would remove the body's last
373
+ row rule unconditionally, so a table with a footer would lose the one rule
374
+ that separates the two while the footer kept a bottom rule of its own,
375
+ drawing a hairline immediately inside the frame.
261
376
 
262
377
  Both selectors are `:last-child` on the section, so whichever section the
263
- markup puts last is the one that loses its rule. That covers a footer written
264
- before the body, which HTML still allows, and multiple bodies, where only the
265
- last one ends the table.
266
-
267
- It went unseen because it was invisible until this release: `thead` and
268
- `tfoot` shared an unconditional `--intent-subtle` plate, so the footer was a
269
- band and the band did the separating. `.ghost` dropping that plate is what
270
- left the rule doing the job alone. */
378
+ markup puts last is the one that loses its rule. That covers a footer
379
+ written before the body, which HTML allows, and multiple bodies, where only
380
+ the last one ends the table. `.ghost` drops the head and foot plate, which
381
+ leaves this rule doing the separating alone. */
271
382
  & tbody:last-child tr:last-child > *,
272
383
  & tfoot:last-child tr:last-child > * {
273
384
  @apply border-b-0;
274
385
  }
275
386
  }
276
387
 
277
- /* Three components rest on a ground rather than on nothing: a key cap, an inline
278
- code chip and a code block are each a quiet tinted plate at rest, and that tone
279
- is `--intent-subtle` rather than a fill of the component's own intent.
280
-
281
- All three rest at `soft`, and `.ghost` is published as unsupported on them.
282
- The ground is what makes a chip a chip, and `.ghost` asks for no fill while
283
- the ground stays -- so on these three it drew the plate anyway and meant
284
- nothing, which is the opposite of what it means everywhere else. Worse, at
285
- zero fill the plate is `--intent-subtle` alone, and that slot is a near-page
286
- tint: measured on the light page, a ghost `code` chip was `#e5e5e5` for both
287
- neutral and primary, and `#f5f5f5` for secondary at 1.04:1 against a `#fafafa`
288
- page. A class that renders four intents as the same invisible chip is not a
289
- variation. Resting at `soft` puts a real 12% of the intent over the ground, so
290
- the plate is a step past the page and the intents separate.
291
-
292
- Each used to restate its foreground too, because `box` mixed toward
293
- `--intent-color` -- the tone meant to sit *on* a page rather than on a tint of
294
- itself, and amber-600 on amber-100 is a smudge. `box` now mixes toward
295
- `--intent-strong`, which is what these three were reaching for, so the three
296
- copies are gone and the ground is the only thing left to declare. */
388
+ /* Three components rest on a ground rather than on nothing: a key cap, an
389
+ inline code chip and a code block are each a quiet tinted plate at rest, and
390
+ that tone is `--intent-subtle` rather than a fill of the component's own
391
+ intent.
392
+
393
+ All three rest at `soft`, and `.ghost` is published as unsupported on them:
394
+ the ground draws the plate whatever the fill says, so `.ghost` would draw it
395
+ anyway and mean nothing, and at zero fill the plate is `--intent-subtle`
396
+ alone, a near-page tint that renders four intents as the same invisible chip
397
+ (`#e5e5e5` for neutral and primary on the light page). Resting at `soft` puts
398
+ a real 12% of the intent over the ground, so the plate is a step past the
399
+ page and the intents separate.
400
+
401
+ The foreground is `box`'s, which mixes toward `--intent-strong`, the tone
402
+ meant to sit on a page rather than on a tint of itself, so the ground is the
403
+ only thing left to declare. */
297
404
  /* Neutral, for the same reason `.panel`'s own carve-out is: 12% of
298
405
  `--color-text` mixed onto `--intent-subtle` (`--color-surface` for
299
406
  neutral) reads as an assertive slab rather than a quiet chip -- measured,
@@ -376,7 +483,7 @@
376
483
 
377
484
  The bar is twice the line width the aesthetic asks for, plus a pixel, which is
378
485
  this component's own choice about what a quotation looks like rather than a
379
- scale applied to an edge -- there is no such scale any more. The constant is
486
+ scale applied to an edge. The constant is
380
487
  what makes the bar read as a bar at the default hairline: doubling 1px gives
381
488
  2px, and a quotation mark needs more presence than a table rule. */
382
489
  @utility quote {
@@ -389,22 +496,27 @@
389
496
  `--ui-bg-alpha` thins it. */
390
497
  --quote-capped: min(var(--ui-fill, var(--_d-fill)), var(--intent-fill-max, 100%));
391
498
  --quote-fill: calc(var(--quote-capped) * var(--ui-bg-alpha, 1));
392
- --quote-bg: color-mix(in oklab, var(--intent-color) var(--quote-fill), transparent);
499
+ /* `box`'s `--_plate`: a fill the intent's cap stopped short rests on the page
500
+ background, so a neutral `.solid` quotation is a plate rather than a tint
501
+ of whatever is behind it. */
502
+ --quote-plate: clamp(0%, calc((var(--ui-fill, var(--_d-fill)) - var(--quote-capped)) * 1000), 100%);
503
+ --quote-bg: color-mix(
504
+ in oklab,
505
+ var(--intent-color) var(--quote-fill),
506
+ color-mix(in oklab, var(--color-background) var(--quote-plate), transparent)
507
+ );
393
508
  /* The same edge blend `box` draws, in the same order and for the same reason:
394
509
  the blend is the line's colour, and how much line there is comes after it.
395
- Written the other way round -- how much line, then blend the fill into it --
396
- `.edgeless` lands on the fill colour rather than on nothing, and the border
397
- box paints that over the fill it already drew, so a quotation that asked for
398
- no bar got a bar of two coats of its own tint.
399
-
400
- It also fades toward `transparent` rather than toward the plate, as `box`'s
401
- does: the plate runs under the bar, so a bar blended toward it painted a
402
- translucent plate twice and drew a darker stripe on a neutral `.solid`
403
- quotation.
404
-
405
- That this had to be fixed three times is the cost of a quotation drawing
406
- its own edge instead of taking `box`'s. Recorded in `registry.json` beside
407
- the decision to compose none of `box`. */
510
+ Written the other way round, `.edgeless` lands on the fill colour rather
511
+ than on nothing, and the border box paints that over the fill it already
512
+ drew. It also fades toward `transparent` rather than toward the plate, as
513
+ `box`'s does: the plate runs under the bar, so a bar blended toward it
514
+ would paint a translucent plate twice and draw a darker stripe on a neutral
515
+ `.solid` quotation.
516
+
517
+ A quotation draws its own edge instead of taking `box`'s, so an edge change
518
+ is made in `box`, `.quote`, and `.progress`. Recorded in `registry.json`
519
+ beside the decision to compose none of `box`. */
408
520
  --quote-line: color-mix(in oklab, transparent var(--ui-fill, var(--_d-fill)), var(--intent-border));
409
521
  --quote-edge: color-mix(in oklab, var(--quote-line) var(--ui-border, var(--_d-border)), transparent);
410
522
  --quote-width: calc(var(--ui-border-width, var(--border-width)) * 2 + 1px);
@@ -414,13 +526,12 @@
414
526
  background-color: var(--quote-bg);
415
527
  border-inline-start-color: var(--quote-edge);
416
528
  border-inline-start-width: var(--quote-width);
417
- /* The ink is gated by the plate, not tied to the fill: `--ui-fg-on-fill` is the
418
- ceiling, and the contrast tone only ramps in past a half fill, reaching full
419
- at a full fill. Below that the ink stays `--intent-strong`, the tone chosen
420
- to read on the page. This is `box`'s `--_on-fill` exactly -- `min(asked,
421
- --intent-fill-max)` read plausibly and was wrong at the bottom of the range,
422
- printing a capped-neutral `.solid` quote at ~6.6:1 where the ramp gives
423
- ~11:1, and this renderer has to agree with `box`. */
529
+ /* The ink is gated by the plate, not tied to the fill: `--ui-fg-on-fill` is
530
+ the ceiling, and the contrast tone only ramps in past a half fill, reaching
531
+ full at a full fill. Below that the ink stays `--intent-strong`, the tone
532
+ chosen to read on the page. This is `box`'s `--_on-fill` exactly, and this
533
+ renderer has to agree with it: a fill-proportional ink prints a
534
+ capped-neutral `.solid` quote at ~6.6:1 where the ramp gives ~11:1. */
424
535
  color: color-mix(
425
536
  in oklab,
426
537
  var(--intent-contrast)