@eduardoalvarez/arrecife 0.3.0 → 0.5.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 (41) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +121 -1
  3. package/dist/brand/index.cjs +29 -10
  4. package/dist/brand/index.d.cts +9 -1
  5. package/dist/brand/index.d.ts +9 -1
  6. package/dist/brand/index.js +4 -2
  7. package/dist/chart/index.cjs +287 -0
  8. package/dist/chart/index.d.cts +127 -0
  9. package/dist/chart/index.d.ts +127 -0
  10. package/dist/chart/index.js +103 -0
  11. package/dist/chunk-KPZNNMV5.js +83 -0
  12. package/dist/chunk-MLKGABMK.js +7 -0
  13. package/dist/chunk-NHS7ETKJ.js +27 -0
  14. package/dist/{chunk-I7GYZD2A.js → chunk-TSPJOM6K.js} +5 -7
  15. package/dist/chunk-UOWIDFCB.js +81 -0
  16. package/dist/{chunk-VSMW2CT4.js → chunk-VPT32GPG.js} +2 -1
  17. package/dist/{chunk-ND45OQOC.js → chunk-YZ2SDOVZ.js} +29 -35
  18. package/dist/chunk-ZEOQKRQ7.js +19 -0
  19. package/dist/form/index.cjs +301 -0
  20. package/dist/form/index.d.cts +86 -0
  21. package/dist/form/index.d.ts +86 -0
  22. package/dist/form/index.js +84 -0
  23. package/dist/index.cjs +1094 -293
  24. package/dist/index.d.cts +452 -37
  25. package/dist/index.d.ts +452 -37
  26. package/dist/index.js +963 -373
  27. package/dist/label-DuTvJGxD.d.cts +14 -0
  28. package/dist/label-DuTvJGxD.d.ts +14 -0
  29. package/dist/og/index.js +2 -1
  30. package/dist/shiki/index.js +2 -1
  31. package/dist/tema/index.cjs +94 -0
  32. package/dist/tema/index.d.cts +110 -0
  33. package/dist/tema/index.d.ts +110 -0
  34. package/dist/tema/index.js +2 -0
  35. package/dist/tokens/index.cjs +6 -0
  36. package/dist/tokens/index.d.cts +33 -1
  37. package/dist/tokens/index.d.ts +33 -1
  38. package/dist/tokens/index.js +3 -2
  39. package/dist/tokens/theme.css +43 -0
  40. package/llms.txt +351 -44
  41. package/package.json +31 -2
package/dist/index.d.ts CHANGED
@@ -1,14 +1,18 @@
1
- export { BrandToken, ColorMode, ColorToken, ControlToken, FontToken, GradientToken, RadiusToken, SintaxisToken, SizeToken, SpacingToken, Tokens, TypeScaleToken, brand, colors, control, dark, fonts, gradient, light, limits, motion, naming, radius, shadow, sintaxis, size, spacing, tagline, tokens, typeScale } from './tokens/index.js';
1
+ export { BrandToken, ColorMode, ColorToken, ControlToken, FontToken, GradientToken, RadiusToken, SeriesToken, SintaxisToken, SizeToken, SpacingToken, Tokens, TypeScaleToken, brand, colors, control, dark, fonts, gradient, light, limits, motion, naming, radius, series, shadow, sintaxis, size, spacing, tagline, tokens, typeScale } from './tokens/index.js';
2
+ import { Tema } from './tema/index.js';
3
+ export { TEMA_ATRIBUTO, TEMA_CLAVE, TEMA_EVENTO, alternarTema, aplicarTema, escucharTema, scriptTema, temaActual, temaGuardado, temaPreferido } from './tema/index.js';
2
4
  import * as react from 'react';
3
- import { ComponentPropsWithoutRef, ReactNode, ComponentProps, SVGProps } from 'react';
5
+ import { ComponentPropsWithoutRef, ReactNode, ComponentProps, RefObject, SVGProps } from 'react';
6
+ import * as AccordionPrimitive from '@radix-ui/react-accordion';
4
7
  import * as class_variance_authority_types from 'class-variance-authority/types';
5
8
  import { VariantProps } from 'class-variance-authority';
9
+ import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog';
6
10
  import * as AvatarPrimitive from '@radix-ui/react-avatar';
7
11
  import { DayPicker } from 'react-day-picker';
8
12
  import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
9
13
  import * as DialogPrimitive from '@radix-ui/react-dialog';
10
14
  import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
11
- import * as LabelPrimitive from '@radix-ui/react-label';
15
+ export { L as Label, a as LabelProps } from './label-DuTvJGxD.js';
12
16
  import * as PopoverPrimitive from '@radix-ui/react-popover';
13
17
  import * as ProgressPrimitive from '@radix-ui/react-progress';
14
18
  import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
@@ -22,6 +26,54 @@ import { C as Cara, P as Pose } from './catalogo-Du5ID-Hi.js';
22
26
  export { A as Aleta, F as Fondo, R as RUTA_ASSETS, a as aletas, c as caras, l as listaCaras, b as listaPoses, p as poses, u as usoDeCara } from './catalogo-Du5ID-Hi.js';
23
27
  export { CaraDeMascota, CaraDeMascotaProps, Isotipo, IsotipoProps, Logo, LogoProps, Mascota, MascotaProps } from './brand/index.js';
24
28
  import { ClassValue } from 'clsx';
29
+ import '@radix-ui/react-label';
30
+
31
+ /**
32
+ * El plegable. Lo pedían dos proyectos: el FAQ del portafolio y el temario de
33
+ * cursos, que es literalmente una lista de secciones que se abren.
34
+ *
35
+ * La altura SÍ se anima, y es la cuarta excepción declarada del sistema.
36
+ *
37
+ * Merece explicarse, porque la regla general es la contraria y este componente
38
+ * nació sin animar citándola. La diferencia es que aquí no APARECE nada: se
39
+ * abre un hueco, y todo lo que hay debajo del acordeón se desplaza. Sin
40
+ * transición ese desplazamiento es un salto, y quien acaba de pulsar pierde el
41
+ * sitio en la página — que es justo el daño que la regla «nada de movimiento»
42
+ * existe para evitar. Es la misma categoría que el panel lateral, la segunda
43
+ * excepción, y no la de una animación de entrada.
44
+ *
45
+ * Va detrás de `motion-safe`, dura `--duration-standard` y usa
46
+ * `--ease-standard`, así que no estrena un tiempo ni una curva. Quien pidió
47
+ * menos movimiento sigue viendo el panel aparecer donde va a quedarse.
48
+ *
49
+ * Ver `docs/decisiones.md` § 20.
50
+ *
51
+ * El galón, en cambio, gira sin transición: `transition-standard` solo cubre
52
+ * color y borde, así que `rotate` salta aunque la clase esté puesta. Es el mismo
53
+ * trato que recibe el ancho de `Progress`.
54
+ *
55
+ * La división entre items es `hairline`, no `border`: es una separación de
56
+ * lectura, no el borde de un control.
57
+ */
58
+ type AccordionProps = ComponentPropsWithoutRef<typeof AccordionPrimitive.Root>;
59
+ declare function Accordion({ className, ...props }: AccordionProps): react.JSX.Element;
60
+ declare function AccordionItem({ className, ...props }: ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>): react.JSX.Element;
61
+ /**
62
+ * El disparador es el encabezado, así que va DENTRO de un `<h3>`: Radix envuelve
63
+ * el botón en `AccordionPrimitive.Header`, que renderiza el elemento que se le
64
+ * pida. Sin eso, un lector de pantalla ve una lista de botones sueltos y pierde
65
+ * la estructura de la página, que es justo lo que un FAQ necesita conservar.
66
+ *
67
+ * `headingLevel` existe porque el nivel correcto depende de dónde se monte: en
68
+ * una página de FAQ el bloque cuelga de un `<h2>` de sección, y en un temario
69
+ * puede colgar de un `<h3>`. Fijarlo aquí sería adivinar.
70
+ */
71
+ type AccordionTriggerProps = ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger> & {
72
+ /** Nivel del encabezado que envuelve al disparador. */
73
+ headingLevel?: 2 | 3 | 4;
74
+ };
75
+ declare function AccordionTrigger({ className, children, headingLevel, ...props }: AccordionTriggerProps): react.JSX.Element;
76
+ declare function AccordionContent({ className, children, ...props }: ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>): react.JSX.Element;
25
77
 
26
78
  /**
27
79
  * El aviso lleva el color en el fondo, no solo en el borde.
@@ -70,7 +122,7 @@ import { ClassValue } from 'clsx';
70
122
  * `docs/decisiones.md`.
71
123
  */
72
124
  declare const alert: (props?: ({
73
- variant?: "accent" | "success" | "warning" | "error" | null | undefined;
125
+ variant?: "error" | "accent" | "success" | "warning" | null | undefined;
74
126
  enfasis?: "sutil" | "fuerte" | null | undefined;
75
127
  } & class_variance_authority_types.ClassProp) | undefined) => string;
76
128
  type AlertProps = Omit<ComponentPropsWithoutRef<'div'>, 'title'> & VariantProps<typeof alert> & {
@@ -83,6 +135,50 @@ type AlertProps = Omit<ComponentPropsWithoutRef<'div'>, 'title'> & VariantProps<
83
135
  };
84
136
  declare function Alert({ className, variant, enfasis, title, icon, children, ...props }: AlertProps): react.JSX.Element;
85
137
 
138
+ /**
139
+ * La confirmación destructiva. NO es un `Dialog` con otro texto, y por eso está
140
+ * en su propio archivo y sobre su propia primitiva de Radix.
141
+ *
142
+ * Tres diferencias, y las tres importan en el momento en que alguien va a
143
+ * borrar un artículo:
144
+ *
145
+ * 1. El rol es `alertdialog`, no `dialog`. Un lector de pantalla lo anuncia
146
+ * con la descripción incluida, sin esperar a que se navegue hasta ella.
147
+ * 2. El foco inicial va al CANCELAR, no al primer elemento. Quien pulsa Enter
148
+ * por inercia no borra nada. Radix lo hace solo si el cancelar existe, y
149
+ * por eso `AlertDialogCancel` no es opcional en la práctica.
150
+ * 3. NO se cierra al pulsar fuera ni tiene aspa. Salir de una confirmación es
151
+ * una decisión, no un descuido: hay que decir que no.
152
+ *
153
+ * El botón de confirmar NO es rojo. El sistema no tiene variante de peligro
154
+ * —`Button` lo dice explícito— y el error vive en los avisos y en la validación
155
+ * de campo, no en un botón. Lo que comunica la gravedad es el texto: «Borrar el
156
+ * artículo», no «Aceptar».
157
+ */
158
+ declare const AlertDialog: react.FC<AlertDialogPrimitive.AlertDialogProps>;
159
+ declare const AlertDialogTrigger: react.ForwardRefExoticComponent<AlertDialogPrimitive.AlertDialogTriggerProps & react.RefAttributes<HTMLButtonElement>>;
160
+ declare function AlertDialogOverlay({ className, ...props }: ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>): react.JSX.Element;
161
+ /** Sin entrada animada, igual que `Dialog`: aparece donde va a quedarse. */
162
+ declare function AlertDialogContent({ className, children, ...props }: ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>): react.JSX.Element;
163
+ declare function AlertDialogHeader({ className, ...props }: ComponentPropsWithoutRef<'div'>): react.JSX.Element;
164
+ /**
165
+ * Cancelar a la IZQUIERDA de confirmar en escritorio y ABAJO en móvil, que es lo
166
+ * que da `flex-col-reverse`: el orden del DOM pone cancelar primero —es donde va
167
+ * el foco— y en columna el dedo lo encuentra donde toca sin cambiar la
168
+ * tabulación.
169
+ */
170
+ declare function AlertDialogFooter({ className, ...props }: ComponentPropsWithoutRef<'div'>): react.JSX.Element;
171
+ declare function AlertDialogTitle({ className, ...props }: ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>): react.JSX.Element;
172
+ /**
173
+ * Lo que se pierde, dicho entero. Es lo que el rol `alertdialog` hace que se
174
+ * anuncie de entrada, así que aquí no va «esta acción no se puede deshacer»
175
+ * suelto: va qué se borra y qué se lleva por delante.
176
+ */
177
+ declare function AlertDialogDescription({ className, ...props }: ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>): react.JSX.Element;
178
+ /** El que se lleva el foco al abrir. */
179
+ declare function AlertDialogCancel({ className, ...props }: ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>): react.JSX.Element;
180
+ declare function AlertDialogAction({ className, ...props }: ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>): react.JSX.Element;
181
+
86
182
  declare const avatar: (props?: ({
87
183
  size?: "sm" | "md" | "lg" | "xl" | null | undefined;
88
184
  } & class_variance_authority_types.ClassProp) | undefined) => string;
@@ -96,6 +192,40 @@ declare function Avatar({ className, size, ...props }: AvatarProps): react.JSX.E
96
192
  declare function AvatarImage({ className, ...props }: ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>): react.JSX.Element;
97
193
  /** Iniciales mientras la imagen carga, o cuando no hay imagen. */
98
194
  declare function AvatarFallback({ className, ...props }: ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>): react.JSX.Element;
195
+ type AvatarUploadProps = Omit<ComponentPropsWithoutRef<'div'>, 'onSelect' | 'children'> & VariantProps<typeof avatar> & {
196
+ /** La imagen actual, ya subida. La previsualización local la gana mientras dure. */
197
+ src?: string | undefined;
198
+ /** Iniciales mientras no hay imagen. */
199
+ fallback?: ReactNode;
200
+ /** Se dispara con el archivo elegido. La subida la hace el proyecto. */
201
+ onSelectFile?: ((archivo: File) => void) | undefined;
202
+ /** Qué acepta el diálogo del sistema. */
203
+ accept?: string;
204
+ /** Nombre accesible del control. Es lo único que lo nombra: no hay texto visible. */
205
+ label?: string;
206
+ disabled?: boolean | undefined;
207
+ };
208
+ /**
209
+ * El avatar que se puede cambiar. `Avatar` muestra; este además deja elegir.
210
+ *
211
+ * Es presentacional, como `NewsletterForm`: emite `onSelectFile` con el `File` y
212
+ * ahí se acaba su trabajo. La subida no entra —cada proyecto tiene su
213
+ * almacenamiento y su endpoint— y un componente que hiciera el `POST` sería
214
+ * infraestructura, que es el tercer criterio de entrada y el que más se salta.
215
+ *
216
+ * La previsualización es LOCAL y no espera a que la subida termine. Es la
217
+ * diferencia entre un control que responde y uno que parece roto: entre elegir
218
+ * el archivo y que el servidor devuelva la URL pueden pasar segundos, y sin
219
+ * previa el avatar se queda con la foto vieja como si no hubiera pasado nada.
220
+ * El `objectURL` se revoca al cambiar y al desmontar; no revocarlo es una fuga
221
+ * de memoria que no da la cara hasta la décima foto.
222
+ *
223
+ * El control es un `<label>` con un `<input type="file">` oculto dentro, no un
224
+ * `<button>` que dispara un click sintético. El input real trae el diálogo del
225
+ * sistema, el arrastrar-y-soltar del navegador y el foco por teclado; el botón
226
+ * falso hay que reconstruirlo entero y siempre falta algo.
227
+ */
228
+ declare function AvatarUpload({ src, fallback, onSelectFile, accept, label, size, disabled, className, ...props }: AvatarUploadProps): react.JSX.Element;
99
229
 
100
230
  /**
101
231
  * TRES familias de etiqueta, no una.
@@ -130,7 +260,7 @@ declare function AvatarFallback({ className, ...props }: ComponentPropsWithoutRe
130
260
  * «Publicado» o «Borrador» con todas sus letras.
131
261
  */
132
262
  declare const badge: (props?: ({
133
- variant?: "accent" | "success" | "warning" | "error" | "neutral" | "warm" | null | undefined;
263
+ variant?: "error" | "accent" | "success" | "warning" | "neutral" | "warm" | null | undefined;
134
264
  } & class_variance_authority_types.ClassProp) | undefined) => string;
135
265
  type BadgeProps = ComponentPropsWithoutRef<'span'> & VariantProps<typeof badge>;
136
266
  declare function Badge({ className, variant, ...props }: BadgeProps): react.JSX.Element;
@@ -329,15 +459,6 @@ type InputProps = ComponentPropsWithoutRef<'input'> & {
329
459
  };
330
460
  declare function Input({ className, invalid, ...props }: InputProps): react.JSX.Element;
331
461
 
332
- type LabelProps = ComponentPropsWithoutRef<typeof LabelPrimitive.Root>;
333
- /**
334
- * La escala `label`: 13px, que es el mínimo absoluto en pantalla del sistema.
335
- *
336
- * Se atenúa junto al control cuando este está deshabilitado, para que la
337
- * pareja etiqueta-control se lea siempre como una sola unidad.
338
- */
339
- declare function Label({ className, ...props }: LabelProps): react.JSX.Element;
340
-
341
462
  declare function Pagination({ className, ...props }: ComponentPropsWithoutRef<'nav'>): react.JSX.Element;
342
463
  declare function PaginationContent({ className, ...props }: ComponentPropsWithoutRef<'ul'>): react.JSX.Element;
343
464
  declare function PaginationItem(props: ComponentPropsWithoutRef<'li'>): react.JSX.Element;
@@ -482,17 +603,73 @@ type TextareaProps = ComponentPropsWithoutRef<'textarea'> & {
482
603
  };
483
604
  declare function Textarea({ className, invalid, ...props }: TextareaProps): react.JSX.Element;
484
605
 
485
- declare const ToastProvider: react.FC<ToastPrimitive.ToastProviderProps>;
486
606
  declare const ToastAction: react.ForwardRefExoticComponent<ToastPrimitive.ToastActionProps & react.RefAttributes<HTMLButtonElement>>;
487
- declare function ToastViewport({ className, ...props }: ComponentPropsWithoutRef<typeof ToastPrimitive.Viewport>): react.JSX.Element;
488
- declare const toast: (props?: ({
489
- variant?: "success" | "error" | "neutral" | null | undefined;
607
+ declare const toast$1: (props?: ({
608
+ variant?: "error" | "success" | "neutral" | null | undefined;
490
609
  } & class_variance_authority_types.ClassProp) | undefined) => string;
491
- type ToastProps = ComponentPropsWithoutRef<typeof ToastPrimitive.Root> & VariantProps<typeof toast>;
492
- /** Sin deslizamiento de entrada: el aviso aparece donde va a quedarse. */
493
- declare function Toast({ className, variant, children, ...props }: ToastProps): react.JSX.Element;
494
- declare function ToastTitle({ className, ...props }: ComponentPropsWithoutRef<typeof ToastPrimitive.Title>): react.JSX.Element;
495
- declare function ToastDescription({ className, ...props }: ComponentPropsWithoutRef<typeof ToastPrimitive.Description>): react.JSX.Element;
610
+ type ToastProps = ComponentPropsWithoutRef<typeof ToastPrimitive.Root> & VariantProps<typeof toast$1>;
611
+
612
+ /**
613
+ * La cara imperativa del `Toast` de Radix: `toast('Guardado')` desde cualquier
614
+ * sitio, sin pasar el aviso por props hasta el componente que lo dispara.
615
+ *
616
+ * Existe porque dos proyectos traían `sonner` para esto. `Toast` cubre el mismo
617
+ * rol y tiene otra forma: Radix es declarativo con proveedor, y para mostrar un
618
+ * aviso desde el `catch` de un `fetch` hay que subir estado hasta donde vive el
619
+ * proveedor. Eso es exactamente lo que `sonner` evita, y es una necesidad real,
620
+ * no una preferencia de API.
621
+ *
622
+ * La alternativa era que los dos proyectos se adaptaran. Se descartó: el aviso
623
+ * lo dispara la capa de datos, que no tiene —ni debería tener— un componente
624
+ * cerca al que subirle un `useState`.
625
+ *
626
+ * Lo que NO se copió de `sonner` es el catálogo entero. No hay `toast.promise`,
627
+ * ni `toast.custom`, ni posiciones configurables, ni apilado con perspectiva:
628
+ * son cuatro variantes de lo mismo y cada una es superficie pública que hay que
629
+ * mantener. Están las tres formas que los proyectos usan de verdad —neutral,
630
+ * éxito, error—, `dismiss` y nada más.
631
+ *
632
+ * El estado vive en un módulo, no en un contexto, porque el punto es que se
633
+ * pueda llamar desde fuera del árbol. `Toaster` se suscribe con
634
+ * `useSyncExternalStore`, que es la forma que React 19 tiene de leer un estado
635
+ * externo sin efectos ni renders en cascada.
636
+ */
637
+ type ToastVariant = NonNullable<ToastProps['variant']>;
638
+ type ToastOptions = {
639
+ /** La primera línea, en negrita. Sin ella el aviso es una sola frase. */
640
+ title?: ReactNode;
641
+ description?: ReactNode;
642
+ variant?: ToastVariant;
643
+ /** Milisegundos en pantalla. `Infinity` lo deja hasta que se cierre a mano. */
644
+ duration?: number;
645
+ /** Un `ToastAction`, si el aviso ofrece deshacer. */
646
+ action?: ReactNode;
647
+ };
648
+ type Lanzador = {
649
+ (mensaje: ReactNode, opciones?: ToastOptions): string;
650
+ success: (mensaje: ReactNode, opciones?: ToastOptions) => string;
651
+ error: (mensaje: ReactNode, opciones?: ToastOptions) => string;
652
+ dismiss: (id?: string) => void;
653
+ };
654
+ /**
655
+ * Lanza un aviso. Devuelve su id, que es lo que hay que guardar para cerrarlo a
656
+ * mano —el caso de «guardando…» que se reemplaza cuando termina la petición.
657
+ */
658
+ declare const toast: Lanzador;
659
+ type ToasterProps = {
660
+ /** Cuánto dura un aviso que no dice lo contrario. */
661
+ duration?: number;
662
+ /**
663
+ * Nombre del landmark que Radix crea para la región de avisos. Se traduce
664
+ * porque lo lee un lector de pantalla, y el default de Radix está en inglés.
665
+ */
666
+ label?: string;
667
+ };
668
+ /**
669
+ * Va UNA vez, lo más arriba posible del árbol. Dos `Toaster` montados pintan
670
+ * cada aviso dos veces: la lista es del módulo, no de la instancia.
671
+ */
672
+ declare function Toaster({ duration, label }: ToasterProps): react.JSX.Element;
496
673
 
497
674
  declare const TooltipProvider: react.FC<TooltipPrimitive.TooltipProviderProps>;
498
675
  declare const Tooltip: react.FC<TooltipPrimitive.TooltipProps>;
@@ -500,8 +677,8 @@ declare const TooltipTrigger: react.ForwardRefExoticComponent<TooltipPrimitive.T
500
677
  declare function TooltipContent({ className, sideOffset, ...props }: ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>): react.JSX.Element;
501
678
 
502
679
  declare const texto: (props?: ({
503
- variant?: "display" | "body" | "h1" | "h2" | "h3" | "label" | "meta" | "stat" | "lead" | "ui" | "tag" | "chip" | "eyebrow" | null | undefined;
504
- tone?: "accent" | "success" | "warning" | "error" | "warm" | "primary" | "secondary" | "muted" | null | undefined;
680
+ variant?: "display" | "h2" | "h3" | "label" | "body" | "h1" | "meta" | "stat" | "lead" | "ui" | "tag" | "chip" | "eyebrow" | null | undefined;
681
+ tone?: "error" | "accent" | "success" | "warning" | "primary" | "secondary" | "warm" | "muted" | null | undefined;
505
682
  } & class_variance_authority_types.ClassProp) | undefined) => string;
506
683
  /** Etiquetas admitidas. La lista es corta a propósito: no es un `div` con estilo. */
507
684
  type Etiqueta = 'h1' | 'h2' | 'h3' | 'h4' | 'p' | 'span' | 'strong' | 'em' | 'figcaption' | 'caption' | 'legend' | 'dt' | 'dd' | 'li';
@@ -537,6 +714,16 @@ type ArticleCardProps = Omit<TarjetaProps, 'children' | 'title'> & {
537
714
  dateTime?: string | undefined;
538
715
  readingMinutes?: number | undefined;
539
716
  tags?: readonly string[] | undefined;
717
+ /**
718
+ * Nivel del titular. `h3` por defecto: una tarjeta suelta en una rejilla no
719
+ * gana el nivel que su posición no le da.
720
+ *
721
+ * Acotado a dos valores a propósito. La página de listado —donde las tarjetas
722
+ * SÍ son el encabezado principal de la sección— necesita `h2`, y esa
723
+ * intención se perdía con la constante; abrirlo hasta `h4` o `h5`, en cambio,
724
+ * es invitar a saltarse niveles, que es el fallo que la constante evitaba.
725
+ */
726
+ headingLevel?: 2 | 3;
540
727
  };
541
728
  /**
542
729
  * La línea de metadatos va en `meta` y no en `eyebrow`: `18 ago 2026 · 8 min de
@@ -546,7 +733,7 @@ type ArticleCardProps = Omit<TarjetaProps, 'children' | 'title'> & {
546
733
  * Los tags son la familia CATEGORÍA — píldora de arena en minúscula —, no la de
547
734
  * estado. Un slug es lo que se lee `engineering-culture`.
548
735
  */
549
- declare function ArticleCard({ title, excerpt, date, dateTime, readingMinutes, tags, className, ...props }: ArticleCardProps): react.JSX.Element;
736
+ declare function ArticleCard({ title, excerpt, date, dateTime, readingMinutes, tags, headingLevel, className, ...props }: ArticleCardProps): react.JSX.Element;
550
737
 
551
738
  /**
552
739
  * La firma al pie del artículo: avatar 52px, nombre 15/500 y el rol en mono
@@ -720,6 +907,53 @@ type EmptyStateProps = Omit<ComponentPropsWithoutRef<'div'>, 'title'> & {
720
907
  };
721
908
  declare function EmptyState({ expresion, title, description, action, basePath, className, ...props }: EmptyStateProps): react.JSX.Element;
722
909
 
910
+ /**
911
+ * El calendario con eventos: verlos, crearlos, editarlos y borrarlos.
912
+ *
913
+ * `Calendar` es la primitiva y sigue siendo una rejilla de fechas: sirve para
914
+ * ELEGIR un día y no sabe nada de contenido. Esto es la agenda, y por eso vive
915
+ * en `components/` y no al lado de aquella — tiene estado, tiene formulario y
916
+ * codifica cómo se ve un día con cosas dentro.
917
+ *
918
+ * Es presentacional, como el resto de la librería: recibe `events` y emite
919
+ * `onCreateEvent`, `onUpdateEvent` y `onDeleteEvent`. No guarda nada, no llama a
920
+ * ninguna API y no genera ids — el id lo pone quien persiste, porque es quien
921
+ * sabe si viene de una base de datos o de un fichero.
922
+ *
923
+ * DOS COLUMNAS y no un popover sobre el día. El popover es lo que hace todo el
924
+ * mundo y esconde el contenido detrás de un clic: con la lista al lado, un mes
925
+ * con quince eventos se lee de un vistazo y el día seleccionado no tapa la
926
+ * rejilla. En pantalla estrecha se apilan.
927
+ *
928
+ * El borrado pasa por `AlertDialog` y no por un botón directo. Es exactamente el
929
+ * caso para el que existe: una acción destructiva sin deshacer.
930
+ */
931
+ type EventoCalendario = {
932
+ /** Lo pone el proyecto. La librería nunca lo inventa. */
933
+ id: string;
934
+ /** Cuándo empieza, con hora. */
935
+ start: Date;
936
+ title: string;
937
+ /** `warm` cuando el evento es el problema, igual que en `Stat`. */
938
+ tone?: 'accent' | 'warm' | undefined;
939
+ };
940
+ type EventCalendarProps = Omit<ComponentPropsWithoutRef<'div'>, 'onSelect' | 'children'> & {
941
+ events: readonly EventoCalendario[];
942
+ /** Sin él, la agenda es de solo lectura y no se pinta el formulario. */
943
+ onCreateEvent?: ((evento: Omit<EventoCalendario, 'id'>) => void) | undefined;
944
+ onUpdateEvent?: ((evento: EventoCalendario) => void) | undefined;
945
+ onDeleteEvent?: ((id: string) => void) | undefined;
946
+ /** Día seleccionado, si el proyecto lo controla. Sin él, empieza en hoy. */
947
+ selected?: Date | undefined;
948
+ onSelectDay?: ((dia: Date) => void) | undefined;
949
+ /** Encabezado del panel. Por defecto, `es` de date-fns, como `Calendar`. */
950
+ formatDay?: ((dia: Date) => string) | undefined;
951
+ formatTime?: ((fecha: Date) => string) | undefined;
952
+ /** Texto del panel cuando el día elegido no tiene nada. */
953
+ emptyMessage?: ReactNode;
954
+ };
955
+ declare function EventCalendar({ events, onCreateEvent, onUpdateEvent, onDeleteEvent, selected, onSelectDay, formatDay, formatTime, emptyMessage, className, ...props }: EventCalendarProps): react.JSX.Element;
956
+
723
957
  /**
724
958
  * El pie, y la firma CLI del sitio: `$ cd ~/eduardoalvarez.dev/2026`.
725
959
  *
@@ -747,8 +981,16 @@ type FooterProps = ComponentPropsWithoutRef<'footer'> & {
747
981
  year?: number;
748
982
  /** Enlaces de texto: aviso legal, RSS, mapa del sitio. */
749
983
  children?: ReactNode;
984
+ /**
985
+ * La fila de marca: la aleta y el wordmark, arriba del todo.
986
+ *
987
+ * Existe porque sin ella acababa metida en `children` con un `w-full` para
988
+ * que se llevara su propia línea. Funcionaba y era un apaño: la marca no es
989
+ * un enlace de texto más, y una ranura propia lo dice en el tipo.
990
+ */
991
+ brand?: ReactNode;
750
992
  };
751
- declare function Footer({ social, year, children, className, ...props }: FooterProps): react.JSX.Element;
993
+ declare function Footer({ social, year, children, brand, className, ...props }: FooterProps): react.JSX.Element;
752
994
  type FooterLinkProps = ComponentPropsWithoutRef<'a'> & {
753
995
  asChild?: boolean | undefined;
754
996
  };
@@ -759,13 +1001,22 @@ declare function FooterLink({ asChild, className, ...props }: FooterLinkProps):
759
1001
  * conversión, y por la misma razón: si hay dos, no hay ninguno.
760
1002
  *
761
1003
  * Degradado, radio de panel, texto al 62 % del ancho y la pose sangrando por el
762
- * borde inferior derecho. NUNCA centrada: una mascota centrada bajo un titular
763
- * es una ilustración de portada, y esto es una cabecera.
1004
+ * borde inferior derecho. Eso es la variante `cabecera`, que es el defecto.
764
1005
  *
765
1006
  * En móvil no hay borde por el que sangrar, así que la pose baja al flujo, bajo
766
1007
  * los botones. No es un `hidden` en pantalla pequeña: la pose es el 40 % de la
767
1008
  * personalidad del hero.
768
1009
  *
1010
+ * La otra variante es `centrado`, y existe porque la regla de arriba tiene un
1011
+ * caso donde no aplica. «Nunca centrada» se escribió contra el hero de una
1012
+ * página con más contenido debajo: ahí una mascota centrada bajo el titular es
1013
+ * una ilustración de portada, no una cabecera. Pero una página de enlaces es
1014
+ * centrada de extremo a extremo y la mascota es el protagonista, no el remate.
1015
+ * Ese proyecto se saltaba `Hero` entero por esto, que es peor: una regla
1016
+ * declarada y con nombre se discute; una copia del degradado en otro repo se
1017
+ * desincroniza. La pose va ARRIBA del titular, no debajo, para que siga sin
1018
+ * leerse como la ilustración que cierra un bloque de texto.
1019
+ *
769
1020
  * El degradado viene de `--gradient-hero`, así que sigue el modo. No hay ángulo
770
1021
  * escrito a mano en ningún proyecto.
771
1022
  */
@@ -779,8 +1030,13 @@ type HeroProps = Omit<ComponentPropsWithoutRef<'section'>, 'title'> & {
779
1030
  /** La pose de Tiburoncín. Sin ella el hero es un panel con texto. */
780
1031
  pose?: Pose | undefined;
781
1032
  basePath?: string | undefined;
1033
+ /**
1034
+ * `cabecera` sangra la pose por la esquina; `centrado` la pone arriba y
1035
+ * centra el texto, para una página que es solo esto.
1036
+ */
1037
+ variant?: 'cabecera' | 'centrado';
782
1038
  };
783
- declare function Hero({ title, eyebrow, description, action, pose, basePath, className, ...props }: HeroProps): react.JSX.Element;
1039
+ declare function Hero({ title, eyebrow, description, action, pose, basePath, variant, className, ...props }: HeroProps): react.JSX.Element;
784
1040
 
785
1041
  type LinkRowProps = Omit<TarjetaProps, 'children'> & {
786
1042
  name: ReactNode;
@@ -809,11 +1065,17 @@ declare function LinkRow({ name, description, icon, external, className, ...prop
809
1065
  * Renderiza `<header>` colgando directamente del `body`, así que ES el landmark
810
1066
  * `banner` del sitio. Por eso `PageHeader` va dentro de `<main>` y no es
811
1067
  * landmark: dos banners en una página son un fallo de accesibilidad.
1068
+ *
1069
+ * Los items van a la DERECHA, pegados a las acciones, no a continuación de la
1070
+ * marca. Con la marca a la izquierda y los items justo detrás, el bloque de
1071
+ * navegación queda flotando en medio de la barra y el ojo tiene que cruzar el
1072
+ * hueco dos veces: una para leer la marca y otra para volver a buscar la
1073
+ * sección. Agrupados a la derecha, marca y navegación son dos anclas y no tres.
812
1074
  */
813
1075
  type NavProps = ComponentPropsWithoutRef<'header'> & {
814
1076
  /** El logo, a la izquierda. */
815
1077
  brand?: ReactNode;
816
- /** Los items de sección, en el centro. */
1078
+ /** Los items de sección. Van a la derecha, pegados a `actions`. */
817
1079
  children?: ReactNode;
818
1080
  /** Acciones a la derecha: conversión, cambio de tema, buscar. */
819
1081
  actions?: ReactNode;
@@ -832,6 +1094,13 @@ type NavItemProps = ComponentPropsWithoutRef<'a'> & {
832
1094
  * no una convención que haya que recordar en cinco proyectos. Va `aria-hidden`,
833
1095
  * así que un lector de pantalla anuncia «artículos» y no «punto barra
834
1096
  * artículos».
1097
+ *
1098
+ * La sección actual va entre CORCHETES además de en bioluz y subrayada. No es
1099
+ * decoración: el subrayado y el color son la misma señal —«esto destaca»— y en
1100
+ * una barra de seis items en mono, a 13px, esa señal se lee peor de lo que
1101
+ * parece en una maqueta. Los corchetes son la forma en que una terminal marca la
1102
+ * ruta activa, así que dicen «estás aquí» sin depender de que se distinga el
1103
+ * color. Van `aria-hidden`, porque quien escucha ya tiene `aria-current`.
835
1104
  */
836
1105
  declare function NavItem({ active, asChild, className, children, ...props }: NavItemProps): react.JSX.Element;
837
1106
 
@@ -851,14 +1120,30 @@ declare function NavItem({ active, asChild, className, children, ...props }: Nav
851
1120
  * receta sutil, las dos líneas se leen como una sola caja.
852
1121
  *
853
1122
  * Es uno de los sitios donde la mascota puede aparecer: el «sin spam».
1123
+ *
1124
+ * El campo de nombre es OPCIONAL y está apagado por defecto. No es una prop de
1125
+ * estilo: el endpoint de uno de los proyectos valida nombre y correo y responde
1126
+ * 400 si falta el primero, así que un formulario de un solo campo ahí no es un
1127
+ * formulario más pobre — es uno que envía algo que el servidor rechaza. El
1128
+ * nombre viaja como SEGUNDO argumento de `onSubmitEmail`, para que las llamadas
1129
+ * que ya existen —las que solo declaran `(email)`— sigan compilando.
1130
+ *
1131
+ * Lo que la librería NO hace es validar el nombre. El proyecto que lo pide lo
1132
+ * acota entre 2 y 50 caracteres y solo letras y acentos; esa regla es suya y del
1133
+ * servidor que la comprueba de verdad, y copiarla aquí sería tener dos fuentes
1134
+ * que se desincronizan en silencio. `nameInputProps` está para que el proyecto
1135
+ * ponga la suya.
854
1136
  */
855
1137
  type NewsletterState = 'reposo' | 'enviando' | 'exito' | 'error';
856
1138
  type NewsletterFormProps = Omit<ComponentPropsWithoutRef<'section'>, 'title' | 'onSubmit'> & {
857
1139
  title: ReactNode;
858
1140
  description?: ReactNode;
859
1141
  state?: NewsletterState;
860
- /** Se dispara con el correo ya leído del campo. */
861
- onSubmitEmail?: ((email: string) => void) | undefined;
1142
+ /**
1143
+ * Se dispara con el correo ya leído del campo, y con el nombre si el campo
1144
+ * está puesto.
1145
+ */
1146
+ onSubmitEmail?: ((email: string, name?: string) => void) | undefined;
862
1147
  successMessage?: ReactNode;
863
1148
  errorMessage?: ReactNode;
864
1149
  /** La letra pequeña. Es el «sin spam», y por eso admite cara. */
@@ -868,8 +1153,17 @@ type NewsletterFormProps = Omit<ComponentPropsWithoutRef<'section'>, 'title' | '
868
1153
  submitLabel?: string;
869
1154
  placeholder?: string;
870
1155
  fieldLabel?: string;
1156
+ /** Añade el campo de nombre delante del correo. */
1157
+ nameField?: boolean;
1158
+ nameLabel?: string;
1159
+ namePlaceholder?: string;
1160
+ /**
1161
+ * Lo que el proyecto necesite colgar del campo de nombre: `minLength`,
1162
+ * `maxLength`, `pattern`. La librería no impone ninguna de las tres.
1163
+ */
1164
+ nameInputProps?: Omit<InputProps, 'id' | 'name' | 'disabled'> | undefined;
871
1165
  };
872
- declare function NewsletterForm({ title, description, state, onSubmitEmail, successMessage, errorMessage, disclaimer, expresion, basePath, submitLabel, placeholder, fieldLabel, className, ...props }: NewsletterFormProps): react.JSX.Element;
1166
+ declare function NewsletterForm({ title, description, state, onSubmitEmail, successMessage, errorMessage, disclaimer, expresion, basePath, submitLabel, placeholder, fieldLabel, nameField, nameLabel, namePlaceholder, nameInputProps, className, ...props }: NewsletterFormProps): react.JSX.Element;
873
1167
 
874
1168
  /**
875
1169
  * Una sola cabecera en dos escalas, no dos componentes.
@@ -908,6 +1202,43 @@ type PageHeaderProps = Omit<ComponentPropsWithoutRef<'header'>, 'title'> & Varia
908
1202
  };
909
1203
  declare function PageHeader({ title, eyebrow, description, action, size, as, className, ...props }: PageHeaderProps): react.JSX.Element;
910
1204
 
1205
+ /**
1206
+ * Cuánto llevas leído. NO es `Progress` con otro nombre.
1207
+ *
1208
+ * `Progress` mide una tarea: hay un total conocido, alguien la empezó y va a
1209
+ * terminar. Esto mide una POSICIÓN en un documento, que se puede recorrer en
1210
+ * los dos sentidos y de la que no hay nada que completar. Por eso no lleva
1211
+ * `role="progressbar"` ni valor accesible: va `aria-hidden`.
1212
+ *
1213
+ * Eso último es deliberado y es la decisión que hay que defender. Un lector de
1214
+ * pantalla ya sabe dónde está en el documento y anunciarle «37 %» cada vez que
1215
+ * se mueve es ruido, no información. La barra es orientación visual, y lo que
1216
+ * es solo visual se declara como tal.
1217
+ *
1218
+ * El ancho se escribe directamente, sin transición: `transition-standard` solo
1219
+ * cubre color y borde, así que la barra sigue al scroll en vez de perseguirlo.
1220
+ *
1221
+ * La medición va dentro de `requestAnimationFrame`. Leer `scrollTop` en el
1222
+ * manejador de scroll fuerza un reflujo síncrono en cada evento, y en un
1223
+ * artículo largo eso se nota en el propio scroll — el efecto contrario al que
1224
+ * busca la pieza.
1225
+ */
1226
+ type ScrollingProgressBarProps = Omit<ComponentPropsWithoutRef<'div'>, 'children'> & {
1227
+ /**
1228
+ * El elemento que se mide. Sin él, el documento entero.
1229
+ *
1230
+ * Se pasa cuando la barra debe seguir SOLO al artículo: si la página tiene
1231
+ * una cabecera alta y un pie con enlaces, medir el documento marca el 100 %
1232
+ * cuando todavía quedan dos párrafos.
1233
+ */
1234
+ target?: RefObject<HTMLElement | null> | undefined;
1235
+ /** Arena en vez de bioluz, para igualar el progreso de curso. */
1236
+ tone?: 'accent' | 'warm';
1237
+ /** Pega la barra al borde superior de la ventana. */
1238
+ sticky?: boolean;
1239
+ };
1240
+ declare function ScrollingProgressBar({ target, tone, sticky, className, ...props }: ScrollingProgressBarProps): react.JSX.Element;
1241
+
911
1242
  /**
912
1243
  * La barra lateral del admin del blog.
913
1244
  *
@@ -942,6 +1273,11 @@ declare function SidebarNav({ title, version, branch, children, className, ...pr
942
1273
  * es un dato; un 0 de design systems es el problema del que trata la charla.
943
1274
  * Por eso `tone` no es una paleta abierta — son dos valores y significan cosas
944
1275
  * distintas.
1276
+ *
1277
+ * El orden de lectura es icono + título, el número grande, y la bajada debajo.
1278
+ * El número va en MEDIO y no al final a propósito: es lo que se viene a leer, y
1279
+ * una bajada de dos líneas entre el título y la cifra la entierra. Arriba queda
1280
+ * de qué va, en medio cuánto, y abajo el matiz que solo lee quien se para.
945
1281
  */
946
1282
  type StatProps = Omit<ComponentPropsWithoutRef<'div'>, 'title'> & {
947
1283
  /** El número, ya formateado. La librería no impone locale. */
@@ -952,8 +1288,18 @@ type StatProps = Omit<ComponentPropsWithoutRef<'div'>, 'title'> & {
952
1288
  tone?: 'neutral' | 'alerta';
953
1289
  /** Con `progress`, la métrica se lee como avance y añade la barra. */
954
1290
  progress?: number | undefined;
1291
+ /**
1292
+ * Glifo al lado del título, a 1em. Hereda `currentColor`, así que sigue al
1293
+ * tono del título y no hay que teñirlo aparte.
1294
+ */
1295
+ icon?: ReactNode;
1296
+ /**
1297
+ * La bajada: el matiz que el número solo no da. «12 aplicaciones» no dice si
1298
+ * son muchas, y aquí es donde se dice.
1299
+ */
1300
+ description?: ReactNode;
955
1301
  };
956
- declare function Stat({ value, label, tone, progress, className, ...props }: StatProps): react.JSX.Element;
1302
+ declare function Stat({ value, label, tone, progress, icon, description, className, ...props }: StatProps): react.JSX.Element;
957
1303
 
958
1304
  type TalkCardProps = Omit<TarjetaProps, 'children' | 'title'> & {
959
1305
  title: ReactNode;
@@ -964,8 +1310,63 @@ type TalkCardProps = Omit<TarjetaProps, 'children' | 'title'> & {
964
1310
  location?: ReactNode;
965
1311
  /** Etiqueta corta de estado: «con vídeo», «próxima», «solo audio». */
966
1312
  status?: ReactNode;
1313
+ /**
1314
+ * De qué iba la charla. Se corta a dos líneas, igual que el `excerpt` de
1315
+ * `ArticleCard`, para que la rejilla no se desalinee.
1316
+ */
1317
+ description?: ReactNode;
1318
+ };
1319
+ declare function TalkCard({ title, event, date, dateTime, location, status, description, className, ...props }: TalkCardProps): react.JSX.Element;
1320
+
1321
+ /**
1322
+ * El control que faltaba. La librería definía todo el sistema de temas y no
1323
+ * exponía lo que lo cambia, así que dos proyectos lo reimplementaban.
1324
+ *
1325
+ * Lo difícil no es el botón: es que la primera pintura no parpadee y que la
1326
+ * elección sobreviva a la navegación. Eso vive en `@eduardoalvarez/arrecife/tema`,
1327
+ * que no importa React —lo consume un Astro que no monta ninguno— y de ahí sale
1328
+ * `scriptTema`, que va inline en el `<head>`. Sin ese script, este botón
1329
+ * funciona y aun así se ve el fogonazo en cada carga.
1330
+ *
1331
+ * Los DOS iconos se renderizan siempre y el que sobra lo esconde el CSS con la
1332
+ * variante `light:`. No es una optimización: es lo que evita que el servidor y
1333
+ * el cliente discrepen. El servidor no sabe qué tema eligió quien va a leer, así
1334
+ * que cualquier icono que elija en el HTML tiene la mitad de probabilidades de
1335
+ * ser el equivocado, y corregirlo al hidratar es el parpadeo otra vez.
1336
+ *
1337
+ * El nombre accesible NO dice a qué modo se va. Sería más informativo y sería
1338
+ * una mentira la mitad del tiempo por lo mismo de arriba: el HTML del servidor
1339
+ * lo fija antes de saber el tema. «Cambiar de tema» es cierto siempre.
1340
+ */
1341
+ type ThemeToggleProps = Omit<ComponentPropsWithoutRef<'button'>, 'onClick'> & {
1342
+ /** Nombre accesible. El botón no tiene texto visible, así que es lo único que lo nombra. */
1343
+ label?: string;
1344
+ /** Se dispara con el tema que quedó puesto, por si el proyecto quiere anotarlo. */
1345
+ onThemeChange?: ((tema: Tema) => void) | undefined;
1346
+ variant?: ButtonProps['variant'];
1347
+ size?: ButtonProps['size'];
967
1348
  };
968
- declare function TalkCard({ title, event, date, dateTime, location, status, className, ...props }: TalkCardProps): react.JSX.Element;
1349
+ declare function ThemeToggle({ label, onThemeChange, variant, size, className, ...props }: ThemeToggleProps): react.JSX.Element;
1350
+ /**
1351
+ * El tema puesto ahora mismo, para un proyecto que necesite ramificar en React
1352
+ * —un logo distinto por modo, una imagen que no tiene versión clara—.
1353
+ *
1354
+ * Es `useSyncExternalStore` y no un `useState` con un efecto detrás porque el
1355
+ * tema es exactamente eso: un estado que vive fuera de React, en un atributo del
1356
+ * `<html>` que puede cambiar sin que React se entere. Escribirlo con un efecto
1357
+ * que llama a `setState` en el montaje es el patrón que dispara un render en
1358
+ * cascada y que la regla `set-state-in-effect` señala con razón.
1359
+ *
1360
+ * `getServerSnapshot` devuelve `'dark'` porque en el servidor no hay `document`.
1361
+ * El primer render del cliente coincide con el del servidor y el valor real
1362
+ * entra después, que es la misma discrepancia de hidratación que `ThemeToggle`
1363
+ * evita renderizando los dos iconos.
1364
+ *
1365
+ * De ahí la regla de uso: si lo que ramifica es SOLO estilo, esto no hace falta
1366
+ * y la variante `light:` es mejor — no re-renderiza nada. Esto es para cuando
1367
+ * cambia el contenido.
1368
+ */
1369
+ declare function useTema(): Tema;
969
1370
 
970
1371
  /**
971
1372
  * «En esta página». El índice del artículo largo.
@@ -976,6 +1377,20 @@ declare function TalkCard({ title, event, date, dateTime, location, status, clas
976
1377
  *
977
1378
  * El activo se marca con `aria-current`, no solo con color — la sección en la
978
1379
  * que estás no puede comunicarse únicamente con bioluz.
1380
+ *
1381
+ * Y ese atributo es además EL GANCHO: las clases del activo se aplican con la
1382
+ * variante `aria-[current]:`, no con un ternario en el render. La diferencia es
1383
+ * la que hay entre servir solo controlado y servir también sin controlar.
1384
+ *
1385
+ * Un sitio Astro resuelve el scroll-spy con quince líneas de script que ponen
1386
+ * `aria-current` en el enlace visible y quitan el del anterior. Con el estado
1387
+ * calculado en el render, ese script no podía hacer nada: había que hidratar el
1388
+ * índice como isla de React en cada artículo para algo que cuesta cero
1389
+ * JavaScript de framework. Ahora el CSS reacciona al atributo y las dos formas
1390
+ * de usarlo dan el mismo resultado.
1391
+ *
1392
+ * El gancho es la PRESENCIA del atributo, así que se quita para desmarcar; no
1393
+ * se pone `aria-current="false"`.
979
1394
  */
980
1395
  type Entrada = {
981
1396
  /** El ancla, con `#`. */
@@ -1021,4 +1436,4 @@ declare namespace social {
1021
1436
  export { social_Correo as Correo, social_Discord as Discord, social_GitHub as GitHub, social_Instagram as Instagram, social_LinkedIn as LinkedIn, social_Rss as Rss, social_X as X, social_YouTube as YouTube };
1022
1437
  }
1023
1438
 
1024
- export { Alert, type AlertProps, ArticleCard, type ArticleCardProps, AudioPlayer, type AudioPlayerMode, type AudioPlayerProps, AuthorCard, type AuthorCardProps, Avatar, AvatarFallback, AvatarImage, type AvatarProps, Badge, type BadgeProps, Blockquote, type BlockquoteProps, Breadcrumb, type BreadcrumbProps, Button, type ButtonProps, Calendar, type CalendarProps, Cara, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, CategoryBadge, type CategoryBadgeProps, Checkbox, type CheckboxProps, Code, CodeBlock, type CodeBlockProps, type CodeProps, CourseCard, type CourseCardProps, DateField, type DateFieldProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogTitle, DialogTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, type EmptyStateProps, type Entrada, Footer, FooterLink, type FooterLinkProps, type FooterProps, HOVER_TARJETA, Hero, type HeroProps, Input, type InputProps, Label, type LabelProps, LinkRow, type LinkRowProps, MetricBadge, type MetricBadgeProps, type Migaja, Nav, NavItem, type NavItemProps, type NavProps, NewsletterForm, type NewsletterFormProps, type NewsletterState, PageHeader, type PageHeaderProps, Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, type PaginationLinkProps, PaginationNext, PaginationPrevious, Popover, PopoverAnchor, PopoverContent, type PopoverContentProps, PopoverTrigger, Pose, Progress, type ProgressProps, RadioGroup, RadioGroupItem, type RadioGroupItemProps, type RadioGroupProps, type Red, SUPERFICIE_TARJETA, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue, Separator, type SeparatorProps, Sheet, SheetBody, SheetClose, SheetContent, type SheetContentProps, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, SidebarItem, type SidebarItemProps, SidebarNav, type SidebarNavProps, Skeleton, type SkeletonProps, Stat, type StatProps, Switch, type SwitchProps, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableOfContents, type TableOfContentsProps, TableRow, Tabs, TabsContent, TabsList, type TabsProps, TabsTrigger, TalkCard, type TalkCardProps, Text, type TextProps, Textarea, type TextareaProps, Toast, ToastAction, ToastDescription, type ToastProps, ToastProvider, ToastTitle, ToastViewport, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, alert as alertVariants, avatar as avatarVariants, badge as badgeVariants, button as buttonVariants, categoria as categoryBadgeVariants, cn, social, texto as textVariants };
1439
+ export { Accordion, AccordionContent, AccordionItem, type AccordionProps, AccordionTrigger, type AccordionTriggerProps, Alert, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogTitle, AlertDialogTrigger, type AlertProps, ArticleCard, type ArticleCardProps, AudioPlayer, type AudioPlayerMode, type AudioPlayerProps, AuthorCard, type AuthorCardProps, Avatar, AvatarFallback, AvatarImage, type AvatarProps, AvatarUpload, type AvatarUploadProps, Badge, type BadgeProps, Blockquote, type BlockquoteProps, Breadcrumb, type BreadcrumbProps, Button, type ButtonProps, Calendar, type CalendarProps, Cara, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, CategoryBadge, type CategoryBadgeProps, Checkbox, type CheckboxProps, Code, CodeBlock, type CodeBlockProps, type CodeProps, CourseCard, type CourseCardProps, DateField, type DateFieldProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogTitle, DialogTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, type EmptyStateProps, type Entrada, EventCalendar, type EventCalendarProps, type EventoCalendario, Footer, FooterLink, type FooterLinkProps, type FooterProps, HOVER_TARJETA, Hero, type HeroProps, Input, type InputProps, LinkRow, type LinkRowProps, MetricBadge, type MetricBadgeProps, type Migaja, Nav, NavItem, type NavItemProps, type NavProps, NewsletterForm, type NewsletterFormProps, type NewsletterState, PageHeader, type PageHeaderProps, Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, type PaginationLinkProps, PaginationNext, PaginationPrevious, Popover, PopoverAnchor, PopoverContent, type PopoverContentProps, PopoverTrigger, Pose, Progress, type ProgressProps, RadioGroup, RadioGroupItem, type RadioGroupItemProps, type RadioGroupProps, type Red, SUPERFICIE_TARJETA, ScrollingProgressBar, type ScrollingProgressBarProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue, Separator, type SeparatorProps, Sheet, SheetBody, SheetClose, SheetContent, type SheetContentProps, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, SidebarItem, type SidebarItemProps, SidebarNav, type SidebarNavProps, Skeleton, type SkeletonProps, Stat, type StatProps, Switch, type SwitchProps, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableOfContents, type TableOfContentsProps, TableRow, Tabs, TabsContent, TabsList, type TabsProps, TabsTrigger, TalkCard, type TalkCardProps, Tema, Text, type TextProps, Textarea, type TextareaProps, ThemeToggle, type ThemeToggleProps, ToastAction, type ToastOptions, type ToastVariant, Toaster, type ToasterProps, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, alert as alertVariants, avatar as avatarVariants, badge as badgeVariants, button as buttonVariants, categoria as categoryBadgeVariants, cn, social, texto as textVariants, toast, useTema };