orbital-ds 0.7.0 → 0.9.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 (109) hide show
  1. package/AGENTS.md +145 -80
  2. package/README.md +87 -73
  3. package/components.manifest.json +1326 -1267
  4. package/contract.json +226 -168
  5. package/dist/hooks/use-ds-messages.d.ts +36 -0
  6. package/dist/hooks/use-ds-messages.js +12 -0
  7. package/dist/hooks/use-ds-motion.d.ts +17 -0
  8. package/dist/hooks/use-ds-motion.js +50 -0
  9. package/dist/lib/i18n.d.ts +46 -0
  10. package/dist/lib/i18n.js +109 -0
  11. package/dist/lib/motion.d.ts +74 -0
  12. package/dist/lib/motion.js +97 -0
  13. package/dist/styles.css +876 -528
  14. package/dist/themes/friendly.css +362 -318
  15. package/dist/themes/friendly.meta.json +75 -14
  16. package/dist/themes/friendly.scoped.css +362 -318
  17. package/dist/themes/qratia.meta.json +42 -12
  18. package/dist/themes/tactile.css +575 -429
  19. package/dist/themes/tactile.fonts.css +3 -2
  20. package/dist/themes/tactile.guide.md +82 -49
  21. package/dist/themes/tactile.meta.json +175 -16
  22. package/dist/themes/tactile.scoped.css +575 -429
  23. package/dist/themes/twitter.css +354 -298
  24. package/dist/themes/twitter.meta.json +83 -61
  25. package/dist/themes/twitter.scoped.css +354 -298
  26. package/dist/themes/zyro.css +443 -389
  27. package/dist/themes/zyro.meta.json +67 -14
  28. package/dist/themes/zyro.scoped.css +443 -389
  29. package/dist/ui/accordion.js +5 -5
  30. package/dist/ui/alert-dialog.js +3 -3
  31. package/dist/ui/alert.d.ts +1 -1
  32. package/dist/ui/alert.js +9 -3
  33. package/dist/ui/attachment.d.ts +1 -1
  34. package/dist/ui/attachment.js +4 -4
  35. package/dist/ui/avatar.js +6 -6
  36. package/dist/ui/ayuda.js +3 -2
  37. package/dist/ui/badge.d.ts +1 -1
  38. package/dist/ui/badge.js +9 -3
  39. package/dist/ui/banner.d.ts +14 -0
  40. package/dist/ui/banner.js +88 -0
  41. package/dist/ui/breadcrumb.js +7 -3
  42. package/dist/ui/bubble.js +6 -6
  43. package/dist/ui/button-group.js +4 -4
  44. package/dist/ui/button.d.ts +1 -1
  45. package/dist/ui/button.js +5 -5
  46. package/dist/ui/calendar.js +33 -6
  47. package/dist/ui/card.js +4 -4
  48. package/dist/ui/carousel.js +15 -8
  49. package/dist/ui/chart.js +7 -3
  50. package/dist/ui/checkbox.js +1 -1
  51. package/dist/ui/combobox.js +7 -7
  52. package/dist/ui/command.js +13 -11
  53. package/dist/ui/context-menu.js +8 -8
  54. package/dist/ui/dialog.js +10 -6
  55. package/dist/ui/drawer.js +9 -9
  56. package/dist/ui/dropdown-menu.js +8 -8
  57. package/dist/ui/empty.js +3 -3
  58. package/dist/ui/field.js +2 -2
  59. package/dist/ui/hover-card.js +1 -1
  60. package/dist/ui/i18n-provider.d.ts +11 -0
  61. package/dist/ui/i18n-provider.js +18 -0
  62. package/dist/ui/icon-tile.d.ts +1 -1
  63. package/dist/ui/input-group.d.ts +1 -1
  64. package/dist/ui/input-group.js +11 -9
  65. package/dist/ui/input-otp.js +3 -3
  66. package/dist/ui/input.js +1 -1
  67. package/dist/ui/item.d.ts +1 -1
  68. package/dist/ui/item.js +3 -3
  69. package/dist/ui/kbd.js +1 -1
  70. package/dist/ui/label.js +1 -1
  71. package/dist/ui/marker.d.ts +1 -1
  72. package/dist/ui/marker.js +2 -2
  73. package/dist/ui/menubar.js +9 -9
  74. package/dist/ui/message-scroller.js +5 -2
  75. package/dist/ui/message.js +2 -2
  76. package/dist/ui/motion-provider.d.ts +11 -0
  77. package/dist/ui/motion-provider.js +16 -0
  78. package/dist/ui/native-select.js +4 -4
  79. package/dist/ui/navigation-menu.js +7 -6
  80. package/dist/ui/pagination.js +19 -11
  81. package/dist/ui/popover.js +2 -2
  82. package/dist/ui/progress.js +2 -2
  83. package/dist/ui/questionnaire.js +20 -15
  84. package/dist/ui/radio-group.js +2 -2
  85. package/dist/ui/resizable.js +2 -2
  86. package/dist/ui/scroll-area.js +3 -3
  87. package/dist/ui/select.js +6 -6
  88. package/dist/ui/separator.js +1 -1
  89. package/dist/ui/sheet.js +6 -4
  90. package/dist/ui/sidebar.js +42 -30
  91. package/dist/ui/skeleton.js +1 -1
  92. package/dist/ui/slider.js +6 -6
  93. package/dist/ui/sonner.js +25 -8
  94. package/dist/ui/spinner.js +4 -1
  95. package/dist/ui/switch.js +2 -2
  96. package/dist/ui/table.js +3 -3
  97. package/dist/ui/tabs.js +5 -5
  98. package/dist/ui/textarea.js +1 -1
  99. package/dist/ui/toast.js +15 -12
  100. package/dist/ui/toggle-group.js +2 -2
  101. package/dist/ui/toggle.js +6 -4
  102. package/dist/ui/tooltip.js +2 -2
  103. package/docs/CONTRACT.md +183 -153
  104. package/package.json +160 -142
  105. package/scripts/brand-core.mjs +153 -0
  106. package/scripts/doctor.mjs +128 -7
  107. package/scripts/i18n.mjs +111 -0
  108. package/scripts/init.mjs +294 -57
  109. package/scripts/setup.mjs +99 -0
package/dist/ui/toggle.js CHANGED
@@ -3,7 +3,7 @@ import { Toggle as TogglePrimitive } from "@base-ui/react/toggle";
3
3
  import { cva } from "class-variance-authority";
4
4
  import { cn } from "../lib/utils";
5
5
  const toggleVariants = cva(
6
- "group/toggle inline-flex items-center justify-center gap-1 rounded-control text-sm font-medium whitespace-nowrap transition-colors outline-none hover:bg-muted hover:text-foreground focus-visible:border-ring focus-visible:ring-(length:--ds-ring-width) focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 aria-pressed:bg-muted dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
6
+ "group/toggle inline-flex items-center justify-center gap-1 rounded-control text-sm font-medium whitespace-nowrap transition-colors duration-(--ds-motion-snap) outline-none hover:bg-muted hover:text-foreground focus-visible:border-ring focus-visible:ring-(length:--ds-ring-width) focus-visible:ring-(--ds-ring-color) disabled:pointer-events-none disabled:opacity-(--ds-disabled-opacity) aria-invalid:border-(--ds-invalid-border) aria-invalid:ring-(--ds-invalid-ring) aria-pressed:bg-muted [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
7
7
  {
8
8
  variants: {
9
9
  variant: {
@@ -11,9 +11,9 @@ const toggleVariants = cva(
11
11
  outline: "border border-input bg-transparent hover:bg-muted"
12
12
  },
13
13
  size: {
14
- default: "h-9 min-w-9 px-3 has-data-[icon=inline-end]:pr-2.5 has-data-[icon=inline-start]:pl-2.5",
15
- sm: "h-8 min-w-8 px-3 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
16
- lg: "h-10 min-w-10 px-4 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3"
14
+ default: "h-(--ds-control-h-md) min-w-(--ds-control-h-md) px-3 has-data-[icon=inline-end]:pr-2.5 has-data-[icon=inline-start]:pl-2.5",
15
+ sm: "h-(--ds-control-h-sm) min-w-(--ds-control-h-sm) px-3 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
16
+ lg: "h-(--ds-control-h-lg) min-w-(--ds-control-h-lg) px-4 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3"
17
17
  }
18
18
  },
19
19
  defaultVariants: {
@@ -32,6 +32,8 @@ function Toggle({
32
32
  TogglePrimitive,
33
33
  {
34
34
  "data-slot": "toggle",
35
+ "data-variant": variant,
36
+ "data-size": size,
35
37
  className: cn(toggleVariants({ variant, size, className })),
36
38
  ...props
37
39
  }
@@ -42,13 +42,13 @@ function TooltipContent({
42
42
  {
43
43
  "data-slot": "tooltip-content",
44
44
  className: cn(
45
- "z-(--ds-z-overlay) inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-2xl bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-(--ds-z-overlay) **:data-[slot=kbd]:rounded-control data-starting-style:animate-in data-starting-style:fade-in-0 data-starting-style:zoom-in-95 data-ending-style:animate-out data-ending-style:fade-out-0 data-ending-style:zoom-out-95",
45
+ "z-(--ds-z-overlay) inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-tooltip bg-(--ds-tooltip-bg) px-3 py-1.5 text-xs text-(--ds-tooltip-fg) has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-(--ds-z-overlay) **:data-[slot=kbd]:rounded-control data-starting-style:animate-in data-starting-style:fade-in-0 data-starting-style:zoom-in-95 data-ending-style:animate-out data-ending-style:fade-out-0 data-ending-style:zoom-out-95",
46
46
  className
47
47
  ),
48
48
  ...props,
49
49
  children: [
50
50
  children,
51
- /* @__PURE__ */ jsx(TooltipPrimitive.Arrow, { "data-slot": "tooltip-arrow", className: "z-(--ds-z-overlay) size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground data-[side=left]:translate-x-[-1.5px] data-[side=right]:translate-x-[1.5px]" })
51
+ /* @__PURE__ */ jsx(TooltipPrimitive.Arrow, { "data-slot": "tooltip-arrow", className: "z-(--ds-z-overlay) size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-xs bg-(--ds-tooltip-bg) fill-(--ds-tooltip-bg) data-[side=left]:translate-x-[-1.5px] data-[side=right]:translate-x-[1.5px]" })
52
52
  ]
53
53
  }
54
54
  )
package/docs/CONTRACT.md CHANGED
@@ -1,153 +1,183 @@
1
- # Contrato de tema de orbital-ds
2
-
3
- Un **tema** es un fichero CSS (`themes/<nombre>.css`) que da valores a un conjunto
4
- cerrado de variables. Los componentes **solo** leen esas variables: ninguno lleva
5
- colores, medidas, sombras, capas ni duraciones escritos a mano. Por eso cambiar de
6
- tema cambia todo el aspecto sin tocar un componente.
7
-
8
- El contrato vive en [`contract.json`](../contract.json) y lo comprueba
9
- `pnpm check:ds` (que forma parte de `pnpm verify`): si a un tema le falta un token
10
- obligatorio, el build falla.
11
-
12
- ## Cómo se usa
13
-
14
- ```css
15
- @import 'orbital-ds/themes/qratia.fonts.css'; /* 0. las FUENTES del tema: lo PRIMERO del fichero */
16
- @import 'tailwindcss';
17
- @plugin '@tailwindcss/typography';
18
- @import 'tw-animate-css';
19
-
20
- @import 'orbital-ds/styles.css'; /* 1. la ESTRUCTURA común */
21
- @import 'orbital-ds/themes/qratia.css'; /* 2. un TEMA (después, para poder sobrescribir) */
22
-
23
- @source '../node_modules/orbital-ds/dist'; /* que Tailwind vea las clases del paquete */
24
- ```
25
-
26
- El tema se importa **después** de la estructura: así puede sobrescribir también la
27
- escala de radios y las sombras, que viven en `@theme`.
28
-
29
- Las **fuentes** van en un fichero aparte por tema (`themes/<tema>.fonts.css`, solo un
30
- `@import url(...)` de Google Fonts) porque un `@import` remoto solo vale al principio
31
- de la hoja. Importado el primero, el empaquetador lo sube al inicio y se cargan. Dentro
32
- del propio `<tema>.css` no se pueden poner.
33
-
34
- Los títulos usan `--font-display` (por defecto, la misma fuente que el cuerpo; un tema
35
- con fuente de titulares, como zyro, la sobrescribe).
36
-
37
- Para ajustar un valor sin crear un tema, se sobrescribe la variable al final:
38
-
39
- ```css
40
- :root { --primary: oklch(0.55 0.2 260); }
41
- ```
42
-
43
- ## Tokens obligatorios (todo tema, en `:root`)
44
-
45
- | Grupo | Tokens |
46
- |---|---|
47
- | Forma | `--radius` |
48
- | Superficies y texto | `--background`, `--foreground`, `--card`, `--card-foreground`, `--popover`, `--popover-foreground` |
49
- | Acción | `--primary`, `--primary-foreground`, `--secondary`, `--secondary-foreground`, `--accent`, `--accent-foreground` |
50
- | Atenuado | `--muted`, `--muted-foreground` |
51
- | Destructivo | `--destructive`, `--destructive-foreground` |
52
- | Líneas y foco | `--border`, `--input`, `--ring` |
53
- | Gráficos | `--chart-1` … `--chart-5` |
54
- | Sidebar | `--sidebar`, `--sidebar-foreground`, `--sidebar-primary`, `--sidebar-primary-foreground`, `--sidebar-accent`, `--sidebar-accent-foreground`, `--sidebar-border`, `--sidebar-ring` |
55
- | Estados | `--danger`, `--warning`, `--success`, `--info`, `--neutral` y cada uno con su `-soft` |
56
- | Texto acentuado | `--link`, `--link-hover` |
57
- | Superficie hundida | `--inset`, `--inset-foreground` |
58
- | Marca | `--brand`, `--brand-bright` |
59
- | Tipografía | `--font-sans` (en `:root` o en `@theme`); opcional `--font-display` para titulares |
60
-
61
- En `.dark` se redefine lo que cambie; `check:ds` avisa (no falla) de los tokens
62
- principales que se hereden de la piel clara.
63
-
64
- `--destructive` es el rojo de **acciones** (botón de borrar); `--danger` es el rojo
65
- de **estado** (etiqueta de nivel crítico). Pueden coincidir, pero son huecos distintos.
66
-
67
- ## Tokens con valor por defecto (el tema los puede cambiar)
68
-
69
- Los define la estructura con un valor neutro; un tema los sobrescribe si quiere.
70
-
71
- | Hueco | Variables |
72
- |---|---|
73
- | Radios semánticos | `--radius-control`, `--radius-overlay` (y la rampa `--radius-sm … 2xl`, derivada de `--radius`) |
74
- | Sombras | `--shadow-card`, `--shadow-card-hover`, `--shadow-control`, `--shadow-raised`, `--shadow-overlay`, `--shadow-popover` |
75
- | Altura de controles | `--ds-control-h-sm`, `-md`, `-lg` |
76
- | Espaciado interno | `--ds-card-spacing`, `--ds-card-spacing-sm`, `--ds-field-px`, `--ds-cell-p`, `--ds-overlay-p`, `--ds-overlay-gap`, `--ds-popover-p`, `--ds-menu-p`, `--ds-menu-item-px`, `--ds-menu-item-py`, `--ds-button-px-sm/md/lg`, `--ds-badge-px` |
77
- | Capas | `--ds-z-raised`, `--ds-z-sticky`, `--ds-z-sidebar` (sobre la cabecera sticky), `--ds-z-overlay` |
78
- | Movimiento | `--ds-motion-fast`, `--ds-motion-base`, `--ds-motion-slow`, `--ds-motion-blink` |
79
- | Foco | `--ds-ring-width` |
80
- | Lateral | `--ds-sidebar-icon`, `--ds-sidebar-icon-stroke` (iconos), `--ds-sidebar-rail-w` (ancho contraído), `--ds-sidebar-rail-item` (tecla del raíl), `--ds-sidebar-logo` (logo y avatar), `--ds-sidebar-sep-inset` (separadores; negativo = de borde a borde). **Todo tema los define** (contrato) |
81
- | Velo | `--color-scrim` |
82
- | **Materiales** | `--ds-card-backdrop`, `--ds-overlay-backdrop`, `--ds-field-backdrop`, `--ds-sidebar-backdrop` (un `backdrop-filter` completo; por defecto `none`) y `--ds-page-backdrop` (un `background-image` para el fondo de la página; por defecto `none`) |
83
-
84
- Los **materiales** son lo que hace posible un tema de cristal como `zyro`: con valores
85
- neutros no hay efecto ni coste; con `blur(28px) saturate(180%)` las superficies se
86
- vuelven translúcidas.
87
-
88
- ## Capa de marca (`brand.css`): lo que cambia una app, y nunca se pisa
89
-
90
- Tres capas, de menos a más personal:
91
-
92
- 1. **Estructura y componentes** (paquete): se actualizan.
93
- 2. **Tema y personalidad** (paquete: `qratia`, `zyro`…): formas, sombras, materiales,
94
- anatomía del sidebar. Se actualizan con versión y changelog.
95
- 3. **Marca** (`src/brand.css`, **en la app**): el grupo estable de variables que una marca
96
- suele cambiar (`brand.tokens` en `contract.json`: color principal, foco, enlace,
97
- acento, radio, fuentes). Se importa **después** del tema, así gana, y **ninguna
98
- actualización lo toca**. Sin fichero, o vacío, se aplica el estándar del tema.
99
-
100
- ```css
101
- @import 'orbital-ds/themes/qratia.css';
102
- @import './brand.css'; /* tuyo: gana al tema */
103
- ```
104
-
105
- `pnpm exec orbital-ds-init` lo crea la primera vez (con las variables comentadas) y **nunca
106
- sobrescribe** uno existente.
107
-
108
- **Por qué una actualización no lo rompe:** los nombres de ese grupo son estables; quitar o
109
- renombrar uno es versión **mayor**, con aviso antes. Además,
110
- `orbital-ds-doctor --css src/brand.css` falla si el fichero usa una `--ds-*` que el paquete
111
- ya no define, para que una renombrada no se ignore en silencio.
112
-
113
- **Cambiar el color principal:** mueve juntas `primary`, `ring`, `link`, `brand`,
114
- `sidebar-primary` y `chart-1`, y comprueba el contraste del texto sobre ellas. Nunca edites
115
- el tema dentro de `node_modules` para personalizar una app.
116
-
117
- ## Reglas de nombres
118
-
119
- - Todo en **inglés**: tokens, props y valores de variantes.
120
- - Los nombres que Tailwind convierte en utilidades (`--color-*`, `--radius-*`,
121
- `--shadow-*`) siguen su convención y **no llevan prefijo**. El resto de variables
122
- propias llevan el prefijo **`--ds-`** para no chocar con las de una app.
123
- - Un componente que necesite un hueco nuevo **añade el token** con un valor que deje
124
- el aspecto como estaba; nunca escribe el valor en el componente.
125
-
126
- ## Qué comprueba `pnpm check:ds`
127
-
128
- 1. **Contrato**: cada tema define todos los tokens obligatorios.
129
- 2. **Repetidos**: ninguna variable se define dos veces en el mismo bloque.
130
- 3. **Valores fijos** en `src/ui/*.tsx`: colores literales (hex, `rgb()`, `oklch()`
131
- salvo derivados de un token), `z-<n>`, `duration-<n>`, `bg-black`/`bg-white`,
132
- sombras de la escala de Tailwind, `text-[<n>px]`, radios fuera de la rampa y
133
- colores de la paleta de Tailwind.
134
-
135
- También se puede pasar el CSS de una app: `orbital-ds-doctor --css src/styles.css`
136
- (detecta variables repetidas).
137
-
138
- ## Crear un tema nuevo
139
-
140
- 1. Copia `themes/qratia.css` (o `zyro.css`) a `themes/<nombre>.css`.
141
- 2. Cambia los valores; no quites ninguno.
142
- 3. `pnpm check:ds`: te dice qué falta.
143
- 4. Opcional: usa los materiales para dar cristal, relieve o un fondo propio.
144
-
145
- Se puede partir de un tema de shadcn (por ejemplo exportado desde tweakcn): usa los
146
- mismos nombres estándar (`--background`, `--primary`…). Habrá que añadir los tokens
147
- propios (`danger`, `warning`, `success`, `info`, `neutral`, `link`, `inset`, `brand`).
148
-
149
- ## Cosas que un tema NO puede cambiar
150
-
151
- La **anatomía** de un componente (otro diseño de sidebar o de tarjeta) no es un valor,
152
- es estructura: se resuelve con variantes del componente. Un fondo animado (como la
153
- malla WebGL de zyro) es código, no una variable: se ofrece como componente opcional.
1
+ # Contrato de tema de orbital-ds
2
+
3
+ Un **tema** es un fichero CSS (`themes/<nombre>.css`) que da valores a un conjunto
4
+ cerrado de variables. Los componentes **solo** leen esas variables: ninguno lleva
5
+ colores, medidas, sombras, capas ni duraciones escritos a mano. Por eso cambiar de
6
+ tema cambia todo el aspecto sin tocar un componente.
7
+
8
+ El contrato vive en [`contract.json`](../contract.json) y lo comprueba
9
+ `pnpm check:ds` (que forma parte de `pnpm verify`): si a un tema le falta un token
10
+ obligatorio, el build falla.
11
+
12
+ ## Cómo se usa
13
+
14
+ ```css
15
+ @import 'orbital-ds/themes/qratia.fonts.css'; /* 0. las FUENTES del tema: lo PRIMERO del fichero */
16
+ @import 'tailwindcss';
17
+ @plugin '@tailwindcss/typography';
18
+ @import 'tw-animate-css';
19
+
20
+ @import 'orbital-ds/styles.css'; /* 1. la ESTRUCTURA común */
21
+ @import 'orbital-ds/themes/qratia.css'; /* 2. un TEMA (después, para poder sobrescribir) */
22
+
23
+ @source '../node_modules/orbital-ds/dist'; /* que Tailwind vea las clases del paquete */
24
+ ```
25
+
26
+ El tema se importa **después** de la estructura: así puede sobrescribir también la
27
+ escala de radios y las sombras, que viven en `@theme`.
28
+
29
+ Las **fuentes** van en un fichero aparte por tema (`themes/<tema>.fonts.css`, solo un
30
+ `@import url(...)` de Google Fonts) porque un `@import` remoto solo vale al principio
31
+ de la hoja. Importado el primero, el empaquetador lo sube al inicio y se cargan. Dentro
32
+ del propio `<tema>.css` no se pueden poner.
33
+
34
+ Los títulos usan `--font-display` (por defecto, la misma fuente que el cuerpo; un tema
35
+ con fuente de titulares, como zyro o tactile, la sobrescribe). Vale para h1–h4 y para
36
+ **todos** los títulos de componente (card, dialog, sheet, alert, alert-dialog, drawer,
37
+ empty, popover), con los ejes de `--ds-display-variation`.
38
+
39
+ Para ajustar un valor sin crear un tema, se sobrescribe la variable al final:
40
+
41
+ ```css
42
+ :root { --primary: oklch(0.55 0.2 260); }
43
+ ```
44
+
45
+ ## Tokens obligatorios (todo tema, en `:root`)
46
+
47
+ | Grupo | Tokens |
48
+ |---|---|
49
+ | Forma | `--radius` |
50
+ | Superficies y texto | `--background`, `--foreground`, `--card`, `--card-foreground`, `--popover`, `--popover-foreground` |
51
+ | Acción | `--primary`, `--primary-foreground`, `--secondary`, `--secondary-foreground`, `--accent`, `--accent-foreground` |
52
+ | Atenuado | `--muted`, `--muted-foreground` |
53
+ | Destructivo | `--destructive`, `--destructive-foreground` |
54
+ | Líneas y foco | `--border`, `--input`, `--ring` |
55
+ | Gráficos | `--chart-1` … `--chart-5` |
56
+ | Sidebar | `--sidebar`, `--sidebar-foreground`, `--sidebar-primary`, `--sidebar-primary-foreground`, `--sidebar-accent`, `--sidebar-accent-foreground`, `--sidebar-border`, `--sidebar-ring` |
57
+ | Estados | `--danger`, `--warning`, `--success`, `--info`, `--neutral` y cada uno con su `-soft` |
58
+ | Texto acentuado | `--link`, `--link-hover` |
59
+ | Superficie hundida | `--inset`, `--inset-foreground` |
60
+ | Marca | `--brand`, `--brand-bright` |
61
+ | Tipografía | `--font-sans` (en `:root` o en `@theme`); opcional `--font-display` para titulares |
62
+
63
+ En `.dark` se redefine lo que cambie; `check:ds` avisa (no falla) de los tokens
64
+ principales que se hereden de la piel clara.
65
+
66
+ `--destructive` es el rojo de **acciones** (botón de borrar); `--danger` es el rojo
67
+ de **estado** (etiqueta de nivel crítico). Pueden coincidir, pero son huecos distintos.
68
+
69
+ ## Tokens con valor por defecto (el tema los puede cambiar)
70
+
71
+ Los define la estructura con un valor neutro; un tema los sobrescribe si quiere.
72
+
73
+ | Hueco | Variables |
74
+ |---|---|
75
+ | Radios semánticos | `--radius-control`, `--radius-overlay` (y la rampa `--radius-sm … 2xl`, derivada de `--radius`) |
76
+ | Sombras | `--shadow-card`, `--shadow-card-hover`, `--shadow-control`, `--shadow-raised`, `--shadow-overlay`, `--shadow-popover` |
77
+ | Altura de controles | `--ds-control-h-sm`, `-md`, `-lg` |
78
+ | Espaciado interno | `--ds-card-spacing`, `--ds-card-spacing-sm`, `--ds-field-px`, `--ds-cell-p`, `--ds-overlay-p`, `--ds-overlay-gap`, `--ds-popover-p`, `--ds-menu-p`, `--ds-menu-item-px`, `--ds-menu-item-py`, `--ds-button-px-sm/md/lg`, `--ds-badge-px` |
79
+ | Capas | `--ds-z-raised`, `--ds-z-sticky`, `--ds-z-sidebar` (sobre la cabecera sticky), `--ds-z-overlay` |
80
+ | Movimiento | `--ds-motion-fast`, `--ds-motion-base`, `--ds-motion-slow`, `--ds-motion-blink` |
81
+ | Foco | `--ds-ring-width` |
82
+ | Lateral | `--ds-sidebar-icon`, `--ds-sidebar-icon-stroke` (iconos), `--ds-sidebar-rail-w` (ancho contraído), `--ds-sidebar-rail-item` (tecla del raíl), `--ds-sidebar-logo` (logo y avatar), `--ds-sidebar-sep-inset` (separadores; negativo = de borde a borde). **Todo tema los define** (contrato) |
83
+ | **Gesto de tecla** | `--ds-key-outline`, `--ds-key-edge` (colores), `--ds-key-border` (contorno), `--ds-key-edge-w` / `-sm` / `-pressed` (arista en reposo, en controles pequeños y al pulsar), `--ds-key-press` / `-sm` (cuánto se hunde), `--ds-key-radius` / `-sm`, `--ds-key-weight`, `--ds-key-disabled-opacity`, `--ds-rule-w` (filete de superficies), `--ds-overlay-edge-w` (arista de diálogos). Valores neutros en la estructura; los usa la personalidad de un tema como `tactile` |
84
+ | **Rotulador** | `--ds-marker` (color; `transparent` lo apaga), `--ds-marker-thickness` (grosor de la franja), `--ds-marker-offset` (cuánto sube); gesto **común**: lo dibuja la estructura en los títulos de cualquier tema |
85
+ | **Títulos y etiquetas** | `--ds-display-variation` (ejes de la fuente de titulares, p. ej. `"SOFT" 50, "WONK" 1`), `--ds-heading-weight` (h1–h3), `--ds-title-weight`, `--ds-title-leading` (títulos de componente), `--ds-heading-tracking`, `--ds-label-tracking` (versalitas), `--ds-strong-weight`, `--ds-nav-weight`, `--ds-tabs-gap` |
86
+ | **Personalidad común** | `--ds-radius-pill`, `--ds-button-case`, `--ds-button-tracking`, `--ds-button-outline-w`, `--ds-shadow-button` / `-hover`, `--ds-hover-lift`, `--ds-hover-opacity`, `--ds-tab-weight` / `-active-weight`, `--ds-tab-indicator-w`, `--ds-label-weight`, `--ds-nav-active-weight`. Huecos que **cualquier** tema usa en su personalidad (píldoras, botones en mayúsculas, brillo, subrayado de pestañas…) |
87
+ | Velo | `--color-scrim` |
88
+ | **Materiales** | `--ds-card-backdrop`, `--ds-overlay-backdrop`, `--ds-field-backdrop`, `--ds-sidebar-backdrop` (un `backdrop-filter` completo; por defecto `none`) y `--ds-page-backdrop` (un `background-image` para el fondo de la página; por defecto `none`) |
89
+
90
+ Los **materiales** son lo que hace posible un tema de cristal como `zyro`: con valores
91
+ neutros no hay efecto ni coste; con `blur(28px) saturate(180%)` las superficies se
92
+ vuelven translúcidas.
93
+
94
+ ## Capa de marca (`brand.css`): lo que cambia una app, y nunca se pisa
95
+
96
+ Tres capas, de menos a más personal:
97
+
98
+ 1. **Estructura y componentes** (paquete): se actualizan.
99
+ 2. **Tema y personalidad** (paquete: `qratia`, `zyro`…): formas, sombras, materiales,
100
+ anatomía del sidebar. Se actualizan con versión y changelog.
101
+ 3. **Marca** (`src/brand.css`, **en la app**): el grupo estable de variables que una marca
102
+ suele cambiar (`brand.tokens` en `contract.json`: color principal, foco, enlace,
103
+ acento, radio, fuentes). Se importa **después** del tema, así gana, y **ninguna
104
+ actualización lo toca**. Sin fichero, o vacío, se aplica el estándar del tema.
105
+
106
+ ```css
107
+ @import 'orbital-ds/themes/qratia.css';
108
+ @import './brand.css'; /* tuyo: gana al tema */
109
+ ```
110
+
111
+ `pnpm exec orbital-ds-init` lo crea la primera vez (con las variables comentadas) y **nunca
112
+ sobrescribe** uno existente.
113
+
114
+ **Por qué una actualización no lo rompe:** los nombres de ese grupo son estables; quitar o
115
+ renombrar uno es versión **mayor**, con aviso antes. Además,
116
+ `orbital-ds-doctor --css src/brand.css` falla si el fichero usa una `--ds-*` que el paquete
117
+ ya no define, para que una renombrada no se ignore en silencio.
118
+
119
+ **Cambiar el color principal:** mueve juntas `primary`, `ring`, `link`, `brand`,
120
+ `sidebar-primary` y `chart-1`, y comprueba el contraste del texto sobre ellas. Nunca edites
121
+ el tema dentro de `node_modules` para personalizar una app.
122
+
123
+ ## Reglas de nombres
124
+
125
+ - Todo en **inglés**: tokens, props y valores de variantes.
126
+ - Los nombres que Tailwind convierte en utilidades (`--color-*`, `--radius-*`,
127
+ `--shadow-*`) siguen su convención y **no llevan prefijo**. El resto de variables
128
+ propias llevan el prefijo **`--ds-`** para no chocar con las de una app.
129
+ - Un componente que necesite un hueco nuevo **añade el token** con un valor que deje
130
+ el aspecto como estaba; nunca escribe el valor en el componente.
131
+
132
+ ## Qué comprueba `pnpm check:ds`
133
+
134
+ 1. **Contrato**: cada tema define todos los tokens obligatorios.
135
+ 2. **Repetidos**: ninguna variable se define dos veces en el mismo bloque.
136
+ 3. **Valores fijos** en `src/ui/*.tsx`: colores literales (hex, `rgb()`, `oklch()`
137
+ salvo derivados de un token), `z-<n>`, `duration-<n>`, `bg-black`/`bg-white`,
138
+ sombras de la escala de Tailwind, `text-[<n>px]`, radios fuera de la rampa y
139
+ colores de la paleta de Tailwind.
140
+
141
+ 4. **Personalidad solo con variables**: una regla de personalidad (cualquier propiedad
142
+ que no sea `--x`) no escribe medidas, colores, pesos, opacidades, interlineados ni
143
+ duraciones; pide el hueco a una variable.
144
+ 5. **Familias completas** (`slotFamilies` en `contract.json`): si la personalidad
145
+ estiliza una pieza (`checkbox`), estiliza sus hermanas (`radio-group-item`). Familias:
146
+ choice, field, modal, floating, title, toggle, track.
147
+ 6. **Contraste AA** de los pares de texto principales y de los textos de estado; resuelve
148
+ `var(--x)`, así que un color derivado (`--sidebar-primary: var(--primary)`) también se mide.
149
+
150
+ Las reglas 4 y 5 son **error** en un tema con `"conformance": "strict"` en su ficha
151
+ (`<tema>.meta.json`) y **aviso** en los demás. **Todos los temas del paquete son estrictos**,
152
+ y un tema nuevo debe nacer así.
153
+
154
+ ### Un solo sitio para cada cosa
155
+
156
+ | Quién | Dónde cambia el aspecto | Qué toca |
157
+ |---|---|---|
158
+ | Autor de un tema | bloque «VARIABLES» del tema (`:root` y `.dark`) | valores; las reglas de PERSONALIDAD solo los leen |
159
+ | App (o su agente) | `src/brand.css` | cualquier variable pública: color, forma, fuentes, gesto (`--ds-key-*`, `--ds-marker`, `--ds-radius-pill`…) |
160
+
161
+ Ninguna regla escribe un valor, así que cambiar una variable cambia **todas** las piezas que
162
+ la usan, en cualquier tema. Si una variable que lee la personalidad deriva de otra que cambia
163
+ de piel (p. ej. una sombra con el color de marca), el tema la repite en `.dark`.
164
+
165
+ También se puede pasar el CSS de una app: `orbital-ds-doctor --css src/styles.css`
166
+ (detecta variables repetidas).
167
+
168
+ ## Crear un tema nuevo
169
+
170
+ 1. Copia `themes/qratia.css` (o `zyro.css`) a `themes/<nombre>.css`.
171
+ 2. Cambia los valores; no quites ninguno.
172
+ 3. `pnpm check:ds`: te dice qué falta.
173
+ 4. Opcional: usa los materiales para dar cristal, relieve o un fondo propio.
174
+
175
+ Se puede partir de un tema de shadcn (por ejemplo exportado desde tweakcn): usa los
176
+ mismos nombres estándar (`--background`, `--primary`…). Habrá que añadir los tokens
177
+ propios (`danger`, `warning`, `success`, `info`, `neutral`, `link`, `inset`, `brand`).
178
+
179
+ ## Cosas que un tema NO puede cambiar
180
+
181
+ La **anatomía** de un componente (otro diseño de sidebar o de tarjeta) no es un valor,
182
+ es estructura: se resuelve con variantes del componente. Un fondo animado (como la
183
+ malla WebGL de zyro) es código, no una variable: se ofrece como componente opcional.