@softize/opus 23.0.0 → 24.0.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 (95) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/bin/cli.mjs +2 -0
  3. package/bin/lib/check.mjs +241 -42
  4. package/bin/lib/copy.mjs +16 -34
  5. package/docs/adr/0023-surfaces-share-one-anatomy-and-two-layouts.md +6 -0
  6. package/docs/adr/0039-filter-presentation-is-independent-from-filter-type.md +3 -3
  7. package/docs/adr/0040-tabs-use-a-single-navigation-presentation.md +5 -6
  8. package/docs/adr/0042-toggle-group-shares-the-grouped-control-grammar.md +79 -0
  9. package/docs/adr/0043-neutral-surfaces-and-interactions-use-opaque-role-tokens.md +73 -0
  10. package/docs/adr/0044-neutral-appearance-derives-from-bounded-axes-per-mode.md +133 -0
  11. package/docs/adr/0045-surface-replaces-content-and-card.md +230 -0
  12. package/docs/adr/0046-structural-body-owns-content-rhythm.md +103 -0
  13. package/docs/adr/0047-pane-is-only-a-split-layout-unit.md +71 -0
  14. package/docs/adr/0048-modal-intro-separates-content-from-chrome.md +34 -0
  15. package/package.json +5 -1
  16. package/registry/skills/build-opus-ui/SKILL.md +11 -11
  17. package/registry/skills/build-opus-ui/references/evaluations.md +10 -9
  18. package/registry/skills/build-opus-ui/references/ui-patterns.md +17 -16
  19. package/registry/templates/app/src/App.tsx +24 -12
  20. package/src/ui/appearance.ts +149 -0
  21. package/src/ui/components/patterns/action-form-card.tsx +46 -33
  22. package/src/ui/components/patterns/action-list-dialog.tsx +2 -4
  23. package/src/ui/components/patterns/confirm.tsx +3 -1
  24. package/src/ui/components/patterns/fill-region.ts +8 -7
  25. package/src/ui/components/patterns/form-dialog.tsx +1 -3
  26. package/src/ui/components/patterns/form.tsx +8 -7
  27. package/src/ui/components/patterns/list.tsx +15 -19
  28. package/src/ui/components/patterns/page.tsx +15 -11
  29. package/src/ui/components/patterns/pane.tsx +9 -240
  30. package/src/ui/components/patterns/presentation.tsx +14 -14
  31. package/src/ui/components/patterns/sidebar.tsx +7 -17
  32. package/src/ui/components/patterns/split.tsx +111 -59
  33. package/src/ui/components/patterns/{surface-header.tsx → structural-header.tsx} +50 -19
  34. package/src/ui/components/patterns/structural-media.tsx +26 -0
  35. package/src/ui/components/patterns/surface-anatomy.ts +71 -61
  36. package/src/ui/components/patterns/surface-assistant.tsx +2 -4
  37. package/src/ui/components/patterns/surface.tsx +620 -0
  38. package/src/ui/components/patterns/trigger.tsx +11 -9
  39. package/src/ui/components/primitives/ask.tsx +155 -83
  40. package/src/ui/components/primitives/button.tsx +11 -9
  41. package/src/ui/components/primitives/checkbox.tsx +8 -8
  42. package/src/ui/components/primitives/command.tsx +1 -1
  43. package/src/ui/components/primitives/composer.tsx +47 -45
  44. package/src/ui/components/primitives/detail.tsx +9 -2
  45. package/src/ui/components/primitives/dialog.tsx +60 -26
  46. package/src/ui/components/primitives/drawer.tsx +58 -12
  47. package/src/ui/components/primitives/icon-picker.tsx +90 -73
  48. package/src/ui/components/primitives/input-group.tsx +35 -28
  49. package/src/ui/components/primitives/input-otp.tsx +16 -16
  50. package/src/ui/components/primitives/input.tsx +20 -10
  51. package/src/ui/components/primitives/item.tsx +9 -12
  52. package/src/ui/components/primitives/menu.tsx +44 -65
  53. package/src/ui/components/primitives/metric-card.tsx +75 -59
  54. package/src/ui/components/primitives/radio-group.tsx +9 -9
  55. package/src/ui/components/primitives/select.tsx +344 -209
  56. package/src/ui/components/primitives/switch.tsx +10 -10
  57. package/src/ui/components/primitives/table.tsx +29 -24
  58. package/src/ui/components/primitives/tabs.tsx +4 -2
  59. package/src/ui/components/primitives/textarea.tsx +6 -6
  60. package/src/ui/components/primitives/toggle-group-context.ts +24 -0
  61. package/src/ui/components/primitives/toggle-group.tsx +116 -74
  62. package/src/ui/components/primitives/toggle.tsx +82 -16
  63. package/src/ui/docs/DocBrowser.tsx +77 -69
  64. package/src/ui/docs/content/action-form-card.md +9 -9
  65. package/src/ui/docs/content/action-list.md +76 -70
  66. package/src/ui/docs/content/action-view.md +33 -18
  67. package/src/ui/docs/content/button.md +1 -1
  68. package/src/ui/docs/content/communication.md +1 -1
  69. package/src/ui/docs/content/customization.md +64 -11
  70. package/src/ui/docs/content/detail.md +8 -6
  71. package/src/ui/docs/content/dialog.md +100 -58
  72. package/src/ui/docs/content/drawer.md +63 -19
  73. package/src/ui/docs/content/field.md +14 -15
  74. package/src/ui/docs/content/icon-tile.md +14 -13
  75. package/src/ui/docs/content/page.md +34 -26
  76. package/src/ui/docs/content/pane.md +41 -290
  77. package/src/ui/docs/content/presentation.md +3 -3
  78. package/src/ui/docs/content/separator.md +6 -6
  79. package/src/ui/docs/content/sidebar.md +68 -266
  80. package/src/ui/docs/content/skeleton.md +20 -18
  81. package/src/ui/docs/content/split.md +14 -14
  82. package/src/ui/docs/content/surface.md +513 -0
  83. package/src/ui/docs/content/tabs.md +20 -20
  84. package/src/ui/docs/content/toggle.md +117 -65
  85. package/src/ui/docs/content/tokens.md +124 -40
  86. package/src/ui/docs/registry.tsx +4 -6
  87. package/src/ui/index.ts +30 -27
  88. package/src/ui/meta.ts +15 -27
  89. package/src/ui/react.tsx +40 -43
  90. package/src/ui/theme.css +311 -64
  91. package/src/ui/components/patterns/content-header.tsx +0 -528
  92. package/src/ui/components/patterns/surface-media.tsx +0 -69
  93. package/src/ui/components/primitives/card.tsx +0 -191
  94. package/src/ui/docs/content/card.md +0 -292
  95. package/src/ui/docs/content/content.md +0 -275
package/src/ui/theme.css CHANGED
@@ -6,10 +6,11 @@
6
6
  *
7
7
  * ── DIVERGÊNCIAS DECLARADAS vs shadcn upstream (regra: o que não está aqui é DRIFT
8
8
  * e deve ser sincronizado; ver skill `build-opus-ui`) ─────────────────────────────
9
- * 1. Formato HSL nos tokens (upstream migrou pra oklch) — legibilidade/ferramentas
10
- * nossas; os VALORES acompanham o upstream, só o formato difere.
11
- * 2. Elevação dark: --card/--popover ACIMA de --background (10% vs 3.9%) — identidade
12
- * da casa (upstream v4 também elevou; calibramos pelo site deles em 2026-06).
9
+ * 1. Formato HSL nos tokens (upstream migrou pra oklch) — legibilidade/ferramentas nossas.
10
+ * 2. Hierarquia de superfície nos dois temas: canvas, superfície elevada, controle e estados
11
+ * neutros têm valores opacos por papel (ADR 0043), sem depender do fundo ancestral. A escala
12
+ * neutra é derivada de quatro eixos de aparência por modo (ADR 0044); os presets reproduzem
13
+ * os valores calibrados e o app personaliza por custom property.
13
14
  * 3. Radius mapeado (xs a 2xl em @theme inline) preservando o sentido pré-v4 dos
14
15
  * utilitários `rounded-*` — neutraliza o rename do v4 e mantém a escala monotônica.
15
16
  * 4. Escala shadow-* recomposta diretamente em duas camadas claras, difusas e com spread
@@ -18,10 +19,10 @@
18
19
  * canvas quase-branco; superfícies elevadas usam a mesma borda real dos demais papéis.
19
20
  * 6. Radius base em 0.75rem — identidade mais arredondada; os componentes continuam usando
20
21
  * diretamente a escala Tailwind, sem tokens por papel (ver docs/radius-scale.md).
21
- * Tudo o mais (escala neutra, semânticas) segue o upstream.
22
+ * Tudo o mais (semânticas e contratos das primitives) segue o upstream.
22
23
  */
23
- @import 'tailwindcss';
24
- @import 'tw-animate-css';
24
+ @import "tailwindcss";
25
+ @import "tw-animate-css";
25
26
  @plugin '@tailwindcss/typography';
26
27
 
27
28
  /* dark por classe (v4 é CSS-first) */
@@ -31,20 +32,112 @@
31
32
  /* Scrollbars e controles NATIVOS (date picker, autofill…) seguem o tema — sem
32
33
  isto o browser assume light e pinta scrollbar branca no dark. */
33
34
  color-scheme: light;
34
- --background: hsl(0 0% 100%);
35
- --foreground: hsl(0 0% 3.9%);
36
- --card: hsl(0 0% 100%);
37
- --card-foreground: hsl(0 0% 3.9%);
38
- --popover: hsl(0 0% 100%);
39
- --popover-foreground: hsl(0 0% 3.9%);
35
+
36
+ /* ── Aparência (ADR 0044) ──────────────────────────────────────────────────────────
37
+ Quatro eixos inteiros de 0 a 100, declarados por modo. Estes são os presets: com eles a
38
+ escala neutra abaixo reproduz exatamente os valores calibrados do tema. O consumidor
39
+ sobrescreve `--appearance-light-*` e `--appearance-dark-*` (inline em <html>, por exemplo)
40
+ e remove a propriedade para voltar ao preset; `@softize/opus/ui/appearance` publica nomes e
41
+ presets. `--neutral-hue` é identidade do app — o matiz que a intensidade aplica aos
42
+ neutros — e não um eixo. Primary e contextos semânticos não participam desta derivação.
43
+
44
+ Os limites protegem o tema calibrado. Contraste e contraste entre camadas partem do preset
45
+ e só ampliam as separações. O brilho move as superfícies dentro de uma faixa em que o texto
46
+ secundário compensa a mudança. Nenhuma combinação de 0 a 100 fica abaixo do preset em
47
+ separação entre papéis nem em contraste do texto sobre as superfícies em repouso. */
48
+ --appearance-light-color-intensity: 0;
49
+ --appearance-light-contrast: 0;
50
+ --appearance-light-surface-brightness: 100;
51
+ --appearance-light-layer-contrast: 0;
52
+ --appearance-dark-color-intensity: 0;
53
+ --appearance-dark-contrast: 0;
54
+ --appearance-dark-surface-brightness: 50;
55
+ --appearance-dark-layer-contrast: 0;
56
+ --neutral-hue: 220;
57
+
58
+ /* Eixos ativos no modo claro. */
59
+ --appearance-color-intensity: var(--appearance-light-color-intensity);
60
+ --appearance-contrast: var(--appearance-light-contrast);
61
+ --appearance-surface-brightness: var(--appearance-light-surface-brightness);
62
+ --appearance-layer-contrast: var(--appearance-light-layer-contrast);
63
+
64
+ /* Escala neutra derivada (luminosidade em número, 0 a 100). No claro a superfície elevada
65
+ parte do branco; o canvas desce dela conforme o contraste entre camadas e o controle fica
66
+ entre os dois. Estados neutros, muted e bordas descem a partir do canvas — a superfície
67
+ mais escura — na direção do texto, em passos que o contraste amplia. O texto secundário
68
+ recebe só uma fração da saturação e escurece junto com as superfícies e com a intensidade,
69
+ para que nem o ajuste nem o matiz escolhido pelo app reduzam a leitura. */
70
+ --neutral-saturation: calc(var(--appearance-color-intensity) * 0.2%);
71
+ --neutral-text-saturation: calc(var(--appearance-color-intensity) * 0.08%);
72
+ --neutral-step: calc(1 + var(--appearance-contrast) * 0.01);
73
+ --neutral-layer-l: calc(95 + var(--appearance-surface-brightness) * 0.05);
74
+ --neutral-canvas-l: calc(
75
+ var(--neutral-layer-l) - 2.5 - var(--appearance-layer-contrast) * 0.025
76
+ );
77
+ --neutral-control-l: calc(
78
+ var(--neutral-canvas-l) + 1 + var(--appearance-layer-contrast) * 0.01
79
+ );
80
+ --neutral-muted-l: calc(var(--neutral-canvas-l) - 1.4 * var(--neutral-step));
81
+ --neutral-hover-l: calc(var(--neutral-canvas-l) - 2 * var(--neutral-step));
82
+ --neutral-selected-l: calc(var(--neutral-canvas-l) - 5 * var(--neutral-step));
83
+ --neutral-pressed-l: calc(
84
+ var(--neutral-canvas-l) - 6.5 * var(--neutral-step)
85
+ );
86
+ --neutral-line-l: calc(var(--neutral-canvas-l) - 5 * var(--neutral-step));
87
+ --neutral-ring-l: calc(var(--neutral-line-l) - 6.5 * var(--neutral-step));
88
+ --neutral-muted-foreground-l: calc(
89
+ 45.1 - (96.1 - var(--neutral-muted-l)) * 0.8 -
90
+ var(--appearance-color-intensity) * 0.04
91
+ );
92
+
93
+ --background: hsl(
94
+ var(--neutral-hue) var(--neutral-saturation)
95
+ calc(var(--neutral-canvas-l) * 1%)
96
+ );
97
+ --foreground: hsl(var(--neutral-hue) var(--neutral-saturation) 3.9%);
98
+ --card: hsl(
99
+ var(--neutral-hue) var(--neutral-saturation)
100
+ calc(var(--neutral-layer-l) * 1%)
101
+ );
102
+ --card-foreground: hsl(var(--neutral-hue) var(--neutral-saturation) 3.9%);
103
+ --control: hsl(
104
+ var(--neutral-hue) var(--neutral-saturation)
105
+ calc(var(--neutral-control-l) * 1%)
106
+ );
107
+ --interaction-hover: hsl(
108
+ var(--neutral-hue) var(--neutral-saturation)
109
+ calc(var(--neutral-hover-l) * 1%)
110
+ );
111
+ --interaction-selected: hsl(
112
+ var(--neutral-hue) var(--neutral-saturation)
113
+ calc(var(--neutral-selected-l) * 1%)
114
+ );
115
+ --interaction-pressed: hsl(
116
+ var(--neutral-hue) var(--neutral-saturation)
117
+ calc(var(--neutral-pressed-l) * 1%)
118
+ );
119
+ --popover: hsl(
120
+ var(--neutral-hue) var(--neutral-saturation)
121
+ calc(var(--neutral-layer-l) * 1%)
122
+ );
123
+ --popover-foreground: hsl(var(--neutral-hue) var(--neutral-saturation) 3.9%);
40
124
  --primary: hsl(0 0% 9%);
41
125
  --primary-foreground: hsl(0 0% 98%);
42
- --secondary: hsl(0 0% 96.1%);
43
- --secondary-foreground: hsl(0 0% 9%);
44
- --muted: hsl(0 0% 96.1%);
45
- --muted-foreground: hsl(0 0% 45.1%);
46
- --accent: hsl(0 0% 96.1%);
47
- --accent-foreground: hsl(0 0% 9%);
126
+ --secondary: hsl(
127
+ var(--neutral-hue) var(--neutral-saturation)
128
+ calc(var(--neutral-muted-l) * 1%)
129
+ );
130
+ --secondary-foreground: hsl(var(--neutral-hue) var(--neutral-saturation) 9%);
131
+ --muted: hsl(
132
+ var(--neutral-hue) var(--neutral-saturation)
133
+ calc(var(--neutral-muted-l) * 1%)
134
+ );
135
+ --muted-foreground: hsl(
136
+ var(--neutral-hue) var(--neutral-text-saturation)
137
+ calc(var(--neutral-muted-foreground-l) * 1%)
138
+ );
139
+ --accent: var(--interaction-hover);
140
+ --accent-foreground: hsl(var(--neutral-hue) var(--neutral-saturation) 9%);
48
141
  --destructive: hsl(0 84.2% 60.2%);
49
142
  --destructive-foreground: hsl(0 0% 98%);
50
143
  /* Contextos semânticos (ADR 0006): família independente do tratamento
@@ -58,7 +151,11 @@
58
151
  --context-primary-foreground: var(--primary-foreground);
59
152
  --context-primary-subtle: color-mix(in oklab, var(--primary) 8%, transparent);
60
153
  --context-primary-emphasis: var(--foreground);
61
- --context-primary-border: color-mix(in oklab, var(--primary) 25%, transparent);
154
+ --context-primary-border: color-mix(
155
+ in oklab,
156
+ var(--primary) 25%,
157
+ transparent
158
+ );
62
159
  /* info/success/warning: base, foreground, subtle e border (12 tokens) valem nos dois temas
63
160
  de propósito — são cores saturadas com alpha, que já se assentam sobre qualquer canvas;
64
161
  só `*-emphasis` (texto sobre o popover) precisa clarear no escuro, e é redefinido em .dark. */
@@ -79,35 +176,119 @@
79
176
  --context-warning-border: hsl(38 92% 50% / 0.35);
80
177
  --context-danger: var(--destructive);
81
178
  --context-danger-foreground: var(--destructive-foreground);
82
- --context-danger-subtle: color-mix(in oklab, var(--destructive) 10%, transparent);
179
+ --context-danger-subtle: color-mix(
180
+ in oklab,
181
+ var(--destructive) 10%,
182
+ transparent
183
+ );
83
184
  --context-danger-emphasis: hsl(0 72% 45%);
84
- --context-danger-border: color-mix(in oklab, var(--destructive) 35%, transparent);
85
- --border: hsl(0 0% 92.5%);
86
- --input: hsl(0 0% 92.5%);
87
- /* Ring de foco LEVE (pedido da casa): aro sutil, não um halo. Com ring-[0.1875rem] + /50,
88
- ~86% encosta na borda (92.5%) — um sussurro de foco. */
89
- --ring: hsl(0 0% 86%);
185
+ --context-danger-border: color-mix(
186
+ in oklab,
187
+ var(--destructive) 35%,
188
+ transparent
189
+ );
190
+ --border: hsl(
191
+ var(--neutral-hue) var(--neutral-saturation)
192
+ calc(var(--neutral-line-l) * 1%)
193
+ );
194
+ --input: hsl(
195
+ var(--neutral-hue) var(--neutral-saturation)
196
+ calc(var(--neutral-line-l) * 1%)
197
+ );
198
+ /* O aro acompanha a borda e o contraste escolhidos, preservando o degrau do preset. */
199
+ --ring: hsl(
200
+ var(--neutral-hue) var(--neutral-saturation)
201
+ calc(var(--neutral-ring-l) * 1%)
202
+ );
90
203
  --radius: 0.75rem;
91
204
  }
92
205
 
93
206
  .dark {
94
207
  color-scheme: dark;
95
- --background: hsl(0 0% 3.9%);
96
- --foreground: hsl(0 0% 98%);
97
- /* Elevação com contraste de verdade (dialog/card "sobem" da página, como no site
98
- do shadcn): superfície ~10% sobre fundo 3.9%, borda um ponto acima pra definir. */
99
- --card: hsl(0 0% 10%);
100
- --card-foreground: hsl(0 0% 98%);
101
- --popover: hsl(0 0% 10%);
102
- --popover-foreground: hsl(0 0% 98%);
208
+
209
+ /* Eixos ativos no modo escuro. */
210
+ --appearance-color-intensity: var(--appearance-dark-color-intensity);
211
+ --appearance-contrast: var(--appearance-dark-contrast);
212
+ --appearance-surface-brightness: var(--appearance-dark-surface-brightness);
213
+ --appearance-layer-contrast: var(--appearance-dark-layer-contrast);
214
+
215
+ /* No escuro o brilho leva o canvas do preto, em 0, até um escuro suave, em 100. Elevação com
216
+ contraste de verdade (dialog e card "sobem" da página): a camada sobe do canvas conforme o
217
+ contraste entre camadas e o controle fica entre os dois. Estados neutros, muted e bordas
218
+ sobem a partir da camada — a superfície mais clara — na direção do texto. Mesmos papéis do
219
+ claro, direção invertida. O texto secundário clareia quando as superfícies sobem acima do
220
+ preset e nunca escurece abaixo dele. */
221
+ --neutral-saturation: calc(var(--appearance-color-intensity) * 0.2%);
222
+ --neutral-text-saturation: calc(var(--appearance-color-intensity) * 0.08%);
223
+ --neutral-step: calc(1 + var(--appearance-contrast) * 0.006);
224
+ --neutral-canvas-l: calc(var(--appearance-surface-brightness) * 0.078);
225
+ --neutral-layer-l: calc(
226
+ var(--neutral-canvas-l) + 6.1 + var(--appearance-layer-contrast) * 0.03
227
+ );
228
+ --neutral-control-l: calc(
229
+ var(--neutral-canvas-l) + 3.8 + var(--appearance-layer-contrast) * 0.018
230
+ );
231
+ --neutral-muted-l: calc(var(--neutral-layer-l) + 4.9 * var(--neutral-step));
232
+ --neutral-hover-l: calc(var(--neutral-layer-l) + 4.9 * var(--neutral-step));
233
+ --neutral-selected-l: calc(
234
+ var(--neutral-layer-l) + 8.5 * var(--neutral-step)
235
+ );
236
+ --neutral-pressed-l: calc(var(--neutral-layer-l) + 11 * var(--neutral-step));
237
+ --neutral-line-l: calc(var(--neutral-layer-l) + 6.5 * var(--neutral-step));
238
+ --neutral-ring-l: calc(var(--neutral-line-l) + 19.5 * var(--neutral-step));
239
+ --neutral-muted-foreground-l: calc(
240
+ 63.9 + max(0, var(--neutral-muted-l) - 14.9) * 1.6 +
241
+ var(--appearance-color-intensity) * 0.04
242
+ );
243
+
244
+ --background: hsl(
245
+ var(--neutral-hue) var(--neutral-saturation)
246
+ calc(var(--neutral-canvas-l) * 1%)
247
+ );
248
+ --foreground: hsl(var(--neutral-hue) var(--neutral-saturation) 98%);
249
+ --card: hsl(
250
+ var(--neutral-hue) var(--neutral-saturation)
251
+ calc(var(--neutral-layer-l) * 1%)
252
+ );
253
+ --card-foreground: hsl(var(--neutral-hue) var(--neutral-saturation) 98%);
254
+ --control: hsl(
255
+ var(--neutral-hue) var(--neutral-saturation)
256
+ calc(var(--neutral-control-l) * 1%)
257
+ );
258
+ --interaction-hover: hsl(
259
+ var(--neutral-hue) var(--neutral-saturation)
260
+ calc(var(--neutral-hover-l) * 1%)
261
+ );
262
+ --interaction-selected: hsl(
263
+ var(--neutral-hue) var(--neutral-saturation)
264
+ calc(var(--neutral-selected-l) * 1%)
265
+ );
266
+ --interaction-pressed: hsl(
267
+ var(--neutral-hue) var(--neutral-saturation)
268
+ calc(var(--neutral-pressed-l) * 1%)
269
+ );
270
+ --popover: hsl(
271
+ var(--neutral-hue) var(--neutral-saturation)
272
+ calc(var(--neutral-layer-l) * 1%)
273
+ );
274
+ --popover-foreground: hsl(var(--neutral-hue) var(--neutral-saturation) 98%);
103
275
  --primary: hsl(0 0% 98%);
104
276
  --primary-foreground: hsl(0 0% 9%);
105
- --secondary: hsl(0 0% 14.9%);
106
- --secondary-foreground: hsl(0 0% 98%);
107
- --muted: hsl(0 0% 14.9%);
108
- --muted-foreground: hsl(0 0% 63.9%);
109
- --accent: hsl(0 0% 14.9%);
110
- --accent-foreground: hsl(0 0% 98%);
277
+ --secondary: hsl(
278
+ var(--neutral-hue) var(--neutral-saturation)
279
+ calc(var(--neutral-muted-l) * 1%)
280
+ );
281
+ --secondary-foreground: hsl(var(--neutral-hue) var(--neutral-saturation) 98%);
282
+ --muted: hsl(
283
+ var(--neutral-hue) var(--neutral-saturation)
284
+ calc(var(--neutral-muted-l) * 1%)
285
+ );
286
+ --muted-foreground: hsl(
287
+ var(--neutral-hue) var(--neutral-text-saturation)
288
+ calc(var(--neutral-muted-foreground-l) * 1%)
289
+ );
290
+ --accent: var(--interaction-hover);
291
+ --accent-foreground: hsl(var(--neutral-hue) var(--neutral-saturation) 98%);
111
292
  /* Vermelho CLARO no escuro (era 30.6% — calibrado só pra FUNDO, com texto branco).
112
293
  Como o menu/alert/field/erros usam `text-destructive` como FOREGROUND, o valor
113
294
  escuro dava ~1,7:1 sobre o popover — ilegível. ~58% passa o AA como texto, o mesmo
@@ -119,10 +300,18 @@
119
300
  --context-success-emphasis: hsl(142 69% 58%);
120
301
  --context-warning-emphasis: hsl(43 96% 56%);
121
302
  --context-danger-emphasis: hsl(0 91% 71%);
122
- --border: hsl(0 0% 16.5%);
123
- --input: hsl(0 0% 16.5%);
124
- /* No escuro o 83.1% virava um halo quase branco; ~36% marca o foco sem brilhar. */
125
- --ring: hsl(0 0% 36%);
303
+ --border: hsl(
304
+ var(--neutral-hue) var(--neutral-saturation)
305
+ calc(var(--neutral-line-l) * 1%)
306
+ );
307
+ --input: hsl(
308
+ var(--neutral-hue) var(--neutral-saturation)
309
+ calc(var(--neutral-line-l) * 1%)
310
+ );
311
+ --ring: hsl(
312
+ var(--neutral-hue) var(--neutral-saturation)
313
+ calc(var(--neutral-ring-l) * 1%)
314
+ );
126
315
  }
127
316
 
128
317
  /* Syntax highlighting (CodeBlock via prism-react-renderer): paleta calma mapeada por CSS
@@ -156,6 +345,10 @@
156
345
  --color-foreground: var(--foreground);
157
346
  --color-card: var(--card);
158
347
  --color-card-foreground: var(--card-foreground);
348
+ --color-control: var(--control);
349
+ --color-interaction-hover: var(--interaction-hover);
350
+ --color-interaction-selected: var(--interaction-selected);
351
+ --color-interaction-pressed: var(--interaction-pressed);
159
352
  --color-popover: var(--popover);
160
353
  --color-popover-foreground: var(--popover-foreground);
161
354
  --color-primary: var(--primary);
@@ -211,14 +404,38 @@
211
404
 
212
405
  /* Escala Tailwind, com receita direta para o compilador preservar modifiers `/opacity`.
213
406
  `light-dark()` mantém o peso adequado em cada tema sem uma variável intermediária. */
214
- --shadow-2xs: 0 0.0625rem 0.125rem -0.0625rem light-dark(rgb(15 15 15 / 0.03), rgb(0 0 0 / 0.12));
215
- --shadow-xs: 0 0.0625rem 0.125rem -0.0625rem light-dark(rgb(15 15 15 / 0.025), rgb(0 0 0 / 0.12)), 0 0.125rem 0.25rem -0.125rem light-dark(rgb(15 15 15 / 0.035), rgb(0 0 0 / 0.18));
216
- --shadow-sm: 0 0.125rem 0.25rem -0.125rem light-dark(rgb(15 15 15 / 0.025), rgb(0 0 0 / 0.16)), 0 0.25rem 0.5rem -0.25rem light-dark(rgb(15 15 15 / 0.04), rgb(0 0 0 / 0.28));
217
- --shadow: 0 0.125rem 0.375rem -0.125rem light-dark(rgb(15 15 15 / 0.03), rgb(0 0 0 / 0.16)), 0 0.375rem 0.875rem -0.375rem light-dark(rgb(15 15 15 / 0.05), rgb(0 0 0 / 0.29));
218
- --shadow-md: 0 0.25rem 0.5rem -0.25rem light-dark(rgb(15 15 15 / 0.03), rgb(0 0 0 / 0.16)), 0 0.625rem 1.75rem -0.5rem light-dark(rgb(15 15 15 / 0.065), rgb(0 0 0 / 0.3));
219
- --shadow-lg: 0 0.375rem 1rem -0.375rem light-dark(rgb(15 15 15 / 0.03), rgb(0 0 0 / 0.18)), 0 1.125rem 3rem -0.75rem light-dark(rgb(15 15 15 / 0.07), rgb(0 0 0 / 0.3));
220
- --shadow-xl: 0 0.5rem 1.5rem -0.625rem light-dark(rgb(15 15 15 / 0.04), rgb(0 0 0 / 0.2)), 0 1.5rem 4rem -1.125rem light-dark(rgb(15 15 15 / 0.08), rgb(0 0 0 / 0.34));
221
- --shadow-2xl: 0 0.75rem 2rem -0.875rem light-dark(rgb(15 15 15 / 0.05), rgb(0 0 0 / 0.22)), 0 2rem 5.5rem -1.5rem light-dark(rgb(15 15 15 / 0.1), rgb(0 0 0 / 0.38));
407
+ --shadow-2xs: 0 0.0625rem 0.125rem -0.0625rem
408
+ light-dark(rgb(15 15 15 / 0.03), rgb(0 0 0 / 0.12));
409
+ --shadow-xs:
410
+ 0 0.0625rem 0.125rem -0.0625rem
411
+ light-dark(rgb(15 15 15 / 0.025), rgb(0 0 0 / 0.12)),
412
+ 0 0.125rem 0.25rem -0.125rem
413
+ light-dark(rgb(15 15 15 / 0.035), rgb(0 0 0 / 0.18));
414
+ --shadow-sm:
415
+ 0 0.125rem 0.25rem -0.125rem
416
+ light-dark(rgb(15 15 15 / 0.025), rgb(0 0 0 / 0.16)),
417
+ 0 0.25rem 0.5rem -0.25rem
418
+ light-dark(rgb(15 15 15 / 0.04), rgb(0 0 0 / 0.28));
419
+ --shadow:
420
+ 0 0.125rem 0.375rem -0.125rem
421
+ light-dark(rgb(15 15 15 / 0.03), rgb(0 0 0 / 0.16)),
422
+ 0 0.375rem 0.875rem -0.375rem
423
+ light-dark(rgb(15 15 15 / 0.05), rgb(0 0 0 / 0.29));
424
+ --shadow-md:
425
+ 0 0.25rem 0.5rem -0.25rem
426
+ light-dark(rgb(15 15 15 / 0.03), rgb(0 0 0 / 0.16)),
427
+ 0 0.625rem 1.75rem -0.5rem
428
+ light-dark(rgb(15 15 15 / 0.065), rgb(0 0 0 / 0.3));
429
+ --shadow-lg:
430
+ 0 0.375rem 1rem -0.375rem
431
+ light-dark(rgb(15 15 15 / 0.03), rgb(0 0 0 / 0.18)),
432
+ 0 1.125rem 3rem -0.75rem light-dark(rgb(15 15 15 / 0.07), rgb(0 0 0 / 0.3));
433
+ --shadow-xl:
434
+ 0 0.5rem 1.5rem -0.625rem light-dark(rgb(15 15 15 / 0.04), rgb(0 0 0 / 0.2)),
435
+ 0 1.5rem 4rem -1.125rem light-dark(rgb(15 15 15 / 0.08), rgb(0 0 0 / 0.34));
436
+ --shadow-2xl:
437
+ 0 0.75rem 2rem -0.875rem light-dark(rgb(15 15 15 / 0.05), rgb(0 0 0 / 0.22)),
438
+ 0 2rem 5.5rem -1.5rem light-dark(rgb(15 15 15 / 0.1), rgb(0 0 0 / 0.38));
222
439
  }
223
440
 
224
441
  /* Base canônica do consumidor — vem DAQUI, não do entry de cada app. Tailwind v4
@@ -261,7 +478,12 @@
261
478
  }
262
479
  :root:is(:hover, :focus, :focus-within),
263
480
  :root *:is(:hover, :focus, :focus-within) {
264
- scrollbar-color: color-mix(in srgb, var(--muted-foreground) 35%, transparent) transparent;
481
+ scrollbar-color: color-mix(
482
+ in srgb,
483
+ var(--muted-foreground) 35%,
484
+ transparent
485
+ )
486
+ transparent;
265
487
  }
266
488
  :root::-webkit-scrollbar,
267
489
  :root *::-webkit-scrollbar {
@@ -281,7 +503,11 @@
281
503
  }
282
504
  :root:is(:hover, :focus, :focus-within)::-webkit-scrollbar-thumb,
283
505
  :root *:is(:hover, :focus, :focus-within)::-webkit-scrollbar-thumb {
284
- background-color: color-mix(in srgb, var(--muted-foreground) 35%, transparent);
506
+ background-color: color-mix(
507
+ in srgb,
508
+ var(--muted-foreground) 35%,
509
+ transparent
510
+ );
285
511
  }
286
512
  }
287
513
 
@@ -311,19 +537,28 @@
311
537
  * O guard [class~='not-prose'] espelha o escopo do plugin typography: sem ele a
312
538
  * regra custom vaza pra dentro do not-prose (o CodeBlock ganhava uma SEGUNDA
313
539
  * borda no <pre>, colada na do contêiner — a "borda duplicada"). */
314
- .prose :where(code):not(:where(pre code, [class~='not-prose'], [class~='not-prose'] *)) {
540
+ .prose
541
+ :where(code):not(
542
+ :where(pre code, [class~="not-prose"], [class~="not-prose"] *)
543
+ ) {
315
544
  border-radius: calc(var(--radius) - 0.25rem);
316
545
  background: var(--muted);
317
546
  padding: 0.125rem 0.375rem;
318
547
  font-weight: 400;
319
548
  }
320
- .prose :where(code):not(:where(pre code, [class~='not-prose'], [class~='not-prose'] *))::before,
321
- .prose :where(code):not(:where(pre code, [class~='not-prose'], [class~='not-prose'] *))::after {
549
+ .prose
550
+ :where(code):not(
551
+ :where(pre code, [class~="not-prose"], [class~="not-prose"] *)
552
+ )::before,
553
+ .prose
554
+ :where(code):not(
555
+ :where(pre code, [class~="not-prose"], [class~="not-prose"] *)
556
+ )::after {
322
557
  content: none;
323
558
  }
324
559
 
325
560
  /* Code block = superfície da casa (borda + bg muted) — só o <pre> CRU do prose. */
326
- .prose :where(pre):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
561
+ .prose :where(pre):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
327
562
  border: 1px solid var(--border); /* px: hairline do bloco de código — 1 pixel físico, não escala. */
328
563
  }
329
564
 
@@ -347,11 +582,15 @@
347
582
  justify-content: center;
348
583
  }
349
584
 
350
- .toaster .opus-toast[data-sonner-toast] .opus-toast-close[data-close-button]:focus-visible {
585
+ .toaster
586
+ .opus-toast[data-sonner-toast]
587
+ .opus-toast-close[data-close-button]:focus-visible {
351
588
  box-shadow: 0 0 0 0.125rem color-mix(in srgb, var(--ring) 50%, transparent);
352
589
  }
353
590
 
354
- .toaster .opus-toast[data-sonner-toast] .opus-toast-close[data-close-button][data-disabled='true'] {
591
+ .toaster
592
+ .opus-toast[data-sonner-toast]
593
+ .opus-toast-close[data-close-button][data-disabled="true"] {
355
594
  pointer-events: none;
356
595
  opacity: 0.5;
357
596
  }
@@ -367,15 +606,23 @@
367
606
  background: var(--popover);
368
607
  color: var(--muted-foreground);
369
608
  outline: none;
370
- transition: color 150ms, background-color 150ms, box-shadow 150ms;
609
+ transition:
610
+ color 150ms,
611
+ background-color 150ms,
612
+ box-shadow 150ms;
371
613
  }
372
614
 
373
- .toaster .opus-toast[data-sonner-toast] .opus-toast-close[data-close-button]:hover {
615
+ .toaster
616
+ .opus-toast[data-sonner-toast]
617
+ .opus-toast-close[data-close-button]:hover {
374
618
  background: var(--accent);
375
619
  color: var(--accent-foreground);
376
620
  }
377
621
 
378
- .toaster .opus-toast[data-sonner-toast] .opus-toast-close[data-close-button] svg {
622
+ .toaster
623
+ .opus-toast[data-sonner-toast]
624
+ .opus-toast-close[data-close-button]
625
+ svg {
379
626
  width: 1rem;
380
627
  height: 1rem;
381
628
  }