@eduardoalvarez/arrecife 0.12.2 → 0.12.3

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.
package/dist/index.d.ts CHANGED
@@ -350,6 +350,11 @@ declare function PaginationItem(props: ComponentPropsWithoutRef<'li'>): react.JS
350
350
  type PaginationLinkProps = ComponentPropsWithoutRef<'a'> & {
351
351
  isActive?: boolean;
352
352
  };
353
+ /**
354
+ * Mono, bordered, and the current page in solid biolume, as the document draws
355
+ * it: «paginación mono, página actual en bioluz sólido». The page is where you
356
+ * are, so it is the one control that carries the accent fill.
357
+ */
353
358
  declare function PaginationLink({ className, isActive, ...props }: PaginationLinkProps): react.JSX.Element;
354
359
  declare function PaginationPrevious({ className, ...props }: PaginationLinkProps): react.JSX.Element;
355
360
  declare function PaginationNext({ className, ...props }: PaginationLinkProps): react.JSX.Element;
@@ -651,6 +656,13 @@ declare function Toaster({ duration, label }: ToasterProps): react.JSX.Element;
651
656
  declare const TooltipProvider: react.FC<TooltipPrimitive.TooltipProviderProps>;
652
657
  declare const Tooltip: react.FC<TooltipPrimitive.TooltipProps>;
653
658
  declare const TooltipTrigger: react.ForwardRefExoticComponent<TooltipPrimitive.TooltipTriggerProps & react.RefAttributes<HTMLButtonElement>>;
659
+ /**
660
+ * Inverted on purpose: the document draws it «espuma sobre oscuro, texto casco
661
+ * 13px/500 — se invierte a propósito para que destaque». `bg-text-primary` with
662
+ * `text-background` is that inversion in both modes: foam on the abyss page in
663
+ * dark, ink on paper in light. It is the one layer that does not sit on a
664
+ * surface token, which is exactly what makes it read as a layer.
665
+ */
654
666
  declare function TooltipContent({ className, sideOffset, ...props }: ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>): react.JSX.Element;
655
667
 
656
668
  /** Allowed tags. The list is short on purpose: this is not a styled `div`. */
@@ -812,15 +824,16 @@ type BlockquoteProps = Omit<ComponentPropsWithoutRef<'blockquote'>, 'cite'> & {
812
824
  source?: ReactNode;
813
825
  };
814
826
  /**
815
- * The side bar is `accent`, the interactive color, because a quotation is
816
- * somebody else's voice entering the text. It carries no decorative quote marks:
817
- * the system's glyphs are SVG, and an ornamental quote adds nothing the border
818
- * and the indent do not already say.
827
+ * The side bar is `warm`, 3px, and the text is italic, as the document draws it:
828
+ * «cita con borde arena 3px, cursiva, sin fondo». Sand is the human accent —
829
+ * quotes, categories, conversion — and biolume is the interactive one; a quote
830
+ * in biolume read as a link that could not be clicked. It carries no decorative
831
+ * quote marks: the border and the italic already say it is somebody's voice.
819
832
  */
820
833
  declare function Blockquote({ children, author, source, className, ...props }: BlockquoteProps): react.JSX.Element;
821
834
 
822
835
  /**
823
- * The path as a path: `~ / artículos / cómo-escalar-un-equipo`.
836
+ * The path as a path: `~ / artículos / el-camino-hacia-mi-primera-charla`.
824
837
  *
825
838
  * The `~` is neither decoration nor a house icon: it is the filesystem's home,
826
839
  * which is why the breadcrumb is mono and not sans. The separators use `border`
@@ -1375,6 +1388,11 @@ type NewsletterFormProps = Omit<ComponentPropsWithoutRef<'section'>, 'title' | '
1375
1388
  errorMessage?: ReactNode;
1376
1389
  /** The small print. It is the «sin spam», which is why it accepts a face. */
1377
1390
  disclaimer?: ReactNode;
1391
+ /**
1392
+ * The face beside the disclaimer. It is dropped when `aside` is set: the
1393
+ * manual allows one mascot per piece («Dos mascotas en la misma pieza.
1394
+ * Compiten entre sí y ninguna gana»), and the aside is the illustration.
1395
+ */
1378
1396
  expression?: Face | undefined;
1379
1397
  basePath?: string | undefined;
1380
1398
  submitLabel?: string;
@@ -1583,6 +1601,12 @@ type StatProps = Omit<ComponentPropsWithoutRef<'div'>, 'title'> & {
1583
1601
  tone?: 'neutral' | 'alert' | 'achievement';
1584
1602
  /** With `progress`, the metric reads as progress and adds the bar. */
1585
1603
  progress?: number | undefined;
1604
+ /**
1605
+ * The bar's accessible name. It defaults to `label` when `label` is a string;
1606
+ * a `label` that is markup has to spell it here, or the bar would announce
1607
+ * «[object Object]: 38%».
1608
+ */
1609
+ progressLabel?: string | undefined;
1586
1610
  /**
1587
1611
  * Glyph in a tinted circle, in the corner opposite the title. At 1em, and it
1588
1612
  * inherits `currentColor` from the badge, so it takes the tone without being
@@ -1618,7 +1642,7 @@ type StatDelta = {
1618
1642
  */
1619
1643
  direction: 'up' | 'down' | 'flat';
1620
1644
  };
1621
- declare function Stat({ value, label, tone, progress, icon, description, delta, spark, className, ...props }: StatProps): react.JSX.Element;
1645
+ declare function Stat({ value, label, tone, progress, progressLabel, icon, description, delta, spark, className, ...props }: StatProps): react.JSX.Element;
1622
1646
 
1623
1647
  type TalkContent = {
1624
1648
  title: ReactNode;
package/dist/index.js CHANGED
@@ -1,22 +1,22 @@
1
1
  'use client';
2
- import { Label } from './chunk-W75O3Z77.js';
3
- export { Label } from './chunk-W75O3Z77.js';
4
- import { Text } from './chunk-BFYNBIVJ.js';
5
- export { Text } from './chunk-BFYNBIVJ.js';
6
- export { tokens } from './chunk-6QJQ6K7J.js';
2
+ import { Label } from './chunk-3SPOZC5F.js';
3
+ export { Label } from './chunk-3SPOZC5F.js';
4
+ import { Text } from './chunk-TJRXYXMG.js';
5
+ export { Text } from './chunk-TJRXYXMG.js';
6
+ export { tokens } from './chunk-GP4MRY2B.js';
7
7
  import { toggleTheme, currentTheme, watchTheme } from './chunk-GCRII2KQ.js';
8
8
  export { THEME_ATTRIBUTE, THEME_EVENT, THEME_KEY, applyTheme, currentTheme, preferredTheme, storedTheme, themeScript, toggleTheme, watchTheme } from './chunk-GCRII2KQ.js';
9
9
  import { alert, button, avatar, badge, category, metric, CARD_SURFACE, CARD } from './chunk-FF33ARRA.js';
10
10
  export { CARD, CARD_HOVER, CARD_SURFACE, alert as alertVariants, avatar as avatarVariants, badge as badgeVariants, button as buttonVariants, category as categoryBadgeVariants, metric as metricBadgeVariants } from './chunk-FF33ARRA.js';
11
11
  export { text as textVariants } from './chunk-ODBFN44D.js';
12
- import { MascotFace, Mascot } from './chunk-BRDTB44R.js';
13
- export { Isotype, Logo, Mascot, MascotFace } from './chunk-BRDTB44R.js';
14
- import { Icon } from './chunk-WU66TPJT.js';
15
- import { cn } from './chunk-TMUGT3Y3.js';
16
- export { cn } from './chunk-TMUGT3Y3.js';
12
+ import { MascotFace, Mascot } from './chunk-LS3PUZ6L.js';
13
+ export { Isotype, Logo, Mascot, MascotFace } from './chunk-LS3PUZ6L.js';
14
+ import { Icon } from './chunk-DKXTF6N7.js';
15
+ import { cn } from './chunk-RMA2KF5B.js';
16
+ export { cn } from './chunk-RMA2KF5B.js';
17
17
  export { ASSETS_PATH, faceList, faceUsage, faces, fins, poseList, poses } from './chunk-CKRSQPTX.js';
18
- import { naming } from './chunk-5YWGOFDX.js';
19
- export { brand, colors, control, dark, fonts, gradient, light, limits, motion, naming, radius, series, shadow, size, spacing, syntax, tagline, typeScale } from './chunk-5YWGOFDX.js';
18
+ import { naming } from './chunk-QQ5WRFHC.js';
19
+ export { brand, colors, control, dark, fonts, gradient, light, limits, motion, naming, radius, series, shadow, size, spacing, syntax, tagline, typeScale } from './chunk-QQ5WRFHC.js';
20
20
  import { CaretDown, XCircle, Warning, CheckCircle, Info, Camera, CircleNotch, CaretLeft, CaretRight, Minus, Check, X, DotsThree, CaretUp, Copy, ArrowUpRight, TrendDown, TrendUp, Sun, Moon, WarningCircle, Waveform, ArrowCounterClockwise, ArrowClockwise, SpeakerSlash, SpeakerHigh, ArrowsClockwise, Pause, Play, Heart } from '@phosphor-icons/react';
21
21
  import * as AccordionPrimitive from '@radix-ui/react-accordion';
22
22
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
@@ -532,7 +532,7 @@ function DateField({
532
532
  "hover:border-hairline-hover",
533
533
  "focus-visible:border-accent focus-ring",
534
534
  "disabled:pointer-events-none disabled:opacity-50",
535
- "data-[invalid]:border-error data-[invalid]:focus-visible:outline-error",
535
+ "aria-[invalid=true]:border-error aria-[invalid=true]:focus-visible:outline-error",
536
536
  // The native indicator inherits the active theme's scheme.
537
537
  "scheme-light dark:scheme-dark [&::-webkit-calendar-picker-indicator]:cursor-pointer",
538
538
  className
@@ -735,7 +735,7 @@ function Input({ className, invalid = false, ...props }) {
735
735
  "hover:border-hairline-hover",
736
736
  "focus-visible:border-accent focus-ring",
737
737
  "disabled:pointer-events-none disabled:opacity-50",
738
- "data-[invalid]:border-error data-[invalid]:focus-visible:outline-error",
738
+ "aria-[invalid=true]:border-error aria-[invalid=true]:focus-visible:outline-error",
739
739
  "file:mr-step-sm file:border-0 file:bg-transparent file:font-sans file:text-label file:text-text-secondary",
740
740
  className
741
741
  ),
@@ -772,12 +772,11 @@ function PaginationLink({ className, isActive = false, ...props }) {
772
772
  {
773
773
  "aria-current": isActive ? "page" : void 0,
774
774
  className: cn(
775
- "rounded-chip px-step-sm inline-flex h-9 min-w-9 cursor-pointer items-center justify-center",
776
- "font-sans text-ui text-text-secondary",
775
+ "rounded-chip px-step-sm inline-flex h-9 min-w-9 cursor-pointer items-center justify-center border",
776
+ "font-mono text-meta",
777
777
  "transition-standard",
778
- "hover:bg-surface hover:text-text-primary",
779
778
  "focus-ring",
780
- isActive && "bg-surface-raised text-text-primary",
779
+ isActive ? "border-accent bg-accent text-accent-on" : "border-border text-text-secondary hover:border-hairline-hover hover:text-text-primary",
781
780
  className
782
781
  ),
783
782
  ...props
@@ -889,6 +888,8 @@ function SelectTrigger({
889
888
  "transition-standard",
890
889
  "hover:border-hairline-hover",
891
890
  "focus-visible:border-accent focus-ring",
891
+ // `FormControl` marks an invalid field with `aria-invalid` only.
892
+ "aria-[invalid=true]:border-error aria-[invalid=true]:focus-visible:outline-error",
892
893
  "disabled:pointer-events-none disabled:opacity-50",
893
894
  "data-[placeholder]:text-text-muted",
894
895
  className
@@ -1272,7 +1273,7 @@ function Textarea({ className, invalid = false, ...props }) {
1272
1273
  "hover:border-hairline-hover",
1273
1274
  "focus-visible:border-accent focus-ring",
1274
1275
  "disabled:pointer-events-none disabled:opacity-50",
1275
- "data-[invalid]:border-error data-[invalid]:focus-visible:outline-error",
1276
+ "aria-[invalid=true]:border-error aria-[invalid=true]:focus-visible:outline-error",
1276
1277
  className
1277
1278
  ),
1278
1279
  ...props
@@ -1446,9 +1447,9 @@ function TooltipContent({
1446
1447
  {
1447
1448
  sideOffset,
1448
1449
  className: cn(
1449
- "z-50 px-step-sm py-1.5",
1450
- "rounded-chip border-border bg-surface-raised shadow-standard border",
1451
- "font-sans text-label text-text-primary",
1450
+ "z-50 px-step-sm py-step-xs",
1451
+ "rounded-chip bg-text-primary shadow-standard",
1452
+ "font-sans text-label text-background",
1452
1453
  className
1453
1454
  ),
1454
1455
  ...props
@@ -1552,7 +1553,9 @@ function ProgressBar({
1552
1553
  height,
1553
1554
  knob,
1554
1555
  progress,
1555
- redondeada = true,
1556
+ valueText,
1557
+ onKeyDown,
1558
+ rounded = true,
1556
1559
  tabIndex = 0,
1557
1560
  ...pointer
1558
1561
  }) {
@@ -1561,28 +1564,31 @@ function ProgressBar({
1561
1564
  {
1562
1565
  className: cn(
1563
1566
  "group bg-surface-raised relative cursor-pointer",
1567
+ "focus-ring",
1564
1568
  height,
1565
- redondeada && "rounded-pill"
1569
+ rounded && "rounded-pill"
1566
1570
  ),
1567
1571
  role: "slider",
1568
1572
  "aria-label": "Progreso del audio",
1569
1573
  "aria-valuemin": 0,
1570
1574
  "aria-valuemax": 100,
1571
1575
  "aria-valuenow": Math.round(progress),
1576
+ "aria-valuetext": valueText,
1572
1577
  tabIndex,
1578
+ onKeyDown,
1573
1579
  ...pointer,
1574
1580
  children: [
1575
1581
  /* @__PURE__ */ jsx(
1576
1582
  "div",
1577
1583
  {
1578
- className: cn("bg-accent absolute h-full", redondeada && "rounded-pill"),
1584
+ className: cn("bg-accent absolute h-full", rounded && "rounded-pill"),
1579
1585
  style: { width: `${progress}%` }
1580
1586
  }
1581
1587
  ),
1582
1588
  /* @__PURE__ */ jsx(
1583
1589
  "div",
1584
1590
  {
1585
- className: "rounded-pill bg-text-primary absolute top-1/2 -translate-y-1/2 opacity-0 transition-opacity group-hover:opacity-100",
1591
+ className: "rounded-pill bg-text-primary absolute top-1/2 -translate-y-1/2 opacity-0 group-hover:opacity-100 group-focus-within:opacity-100",
1586
1592
  style: {
1587
1593
  width: knob,
1588
1594
  height: knob,
@@ -1668,6 +1674,7 @@ function Volume({
1668
1674
  className: cn(
1669
1675
  "bg-surface-raised rounded-pill h-1 cursor-pointer appearance-none",
1670
1676
  "[accent-color:var(--color-accent)]",
1677
+ "focus-ring",
1671
1678
  width
1672
1679
  ),
1673
1680
  "aria-label": "Volumen"
@@ -1679,7 +1686,7 @@ function PlayIcon({ className, loading, error, playing }) {
1679
1686
  if (loading)
1680
1687
  return /* @__PURE__ */ jsx(Icon, { as: CircleNotch, className: cn("motion-safe:animate-spin", className) });
1681
1688
  if (error) return /* @__PURE__ */ jsx(Icon, { as: ArrowsClockwise, className });
1682
- if (playing) return /* @__PURE__ */ jsx(Icon, { as: Pause, className });
1689
+ if (playing) return /* @__PURE__ */ jsx(Icon, { as: Pause, tone: "current", className });
1683
1690
  return /* @__PURE__ */ jsx(Icon, { as: Play, className });
1684
1691
  }
1685
1692
  function PlayButton({
@@ -1698,7 +1705,8 @@ function PlayButton({
1698
1705
  className: cn(
1699
1706
  "text-accent-on transition-standard cursor-pointer",
1700
1707
  "focus-ring",
1701
- state.error ? "bg-error hover:bg-error/80" : "bg-accent hover:bg-accent-hover",
1708
+ // `danger` is the fill and `error` is text: the retry button is a fill.
1709
+ state.error ? "bg-danger text-danger-on hover:bg-danger-hover" : "bg-accent hover:bg-accent-hover",
1702
1710
  className
1703
1711
  ),
1704
1712
  "aria-label": label,
@@ -1830,6 +1838,29 @@ function AudioPlayer({ src, title, mode = "full", onFirstPlay }) {
1830
1838
  e.currentTarget.releasePointerCapture(e.pointerId);
1831
1839
  isDragging.current = false;
1832
1840
  }, []);
1841
+ const handleProgressKey = useCallback(
1842
+ (e) => {
1843
+ const audio = audioRef.current;
1844
+ if (!audio || !duration) return;
1845
+ const steps = {
1846
+ ArrowLeft: -5,
1847
+ ArrowDown: -5,
1848
+ ArrowRight: 5,
1849
+ ArrowUp: 5,
1850
+ PageDown: -15,
1851
+ PageUp: 15
1852
+ };
1853
+ let next;
1854
+ if (e.key === "Home") next = 0;
1855
+ else if (e.key === "End") next = duration;
1856
+ else if (e.key in steps) next = audio.currentTime + (steps[e.key] ?? 0);
1857
+ if (next === void 0) return;
1858
+ e.preventDefault();
1859
+ audio.currentTime = Math.max(0, Math.min(next, duration));
1860
+ setCurrentTime(audio.currentTime);
1861
+ },
1862
+ [duration]
1863
+ );
1833
1864
  const handleVolumeChange = useCallback((e) => {
1834
1865
  const audio = audioRef.current;
1835
1866
  if (!audio) return;
@@ -1870,7 +1901,9 @@ function AudioPlayer({ src, title, mode = "full", onFirstPlay }) {
1870
1901
  const pointer = {
1871
1902
  onPointerDown: handlePointerDown,
1872
1903
  onPointerMove: handlePointerMove,
1873
- onPointerUp: handlePointerUp
1904
+ onPointerUp: handlePointerUp,
1905
+ onKeyDown: handleProgressKey,
1906
+ valueText: `${formatTime(currentTime)} de ${formatTime(duration)}`
1874
1907
  };
1875
1908
  const floatingJsx = /* @__PURE__ */ jsxs(
1876
1909
  "div",
@@ -1888,7 +1921,7 @@ function AudioPlayer({ src, title, mode = "full", onFirstPlay }) {
1888
1921
  height: "h-1",
1889
1922
  knob: 10,
1890
1923
  progress,
1891
- redondeada: false,
1924
+ rounded: false,
1892
1925
  tabIndex: -1,
1893
1926
  ...pointer
1894
1927
  }
@@ -2074,10 +2107,10 @@ function Blockquote({ children, author, source, className, ...props }) {
2074
2107
  return /* @__PURE__ */ jsxs(
2075
2108
  "blockquote",
2076
2109
  {
2077
- className: cn("border-accent pl-step-md my-step-lg border-l-2", className),
2110
+ className: cn("border-warm pl-step-md my-step-lg border-l-3", className),
2078
2111
  ...props,
2079
2112
  children: [
2080
- /* @__PURE__ */ jsx(Text, { variant: "body", tone: "secondary", children }),
2113
+ /* @__PURE__ */ jsx(Text, { variant: "body", tone: "secondary", className: "italic", children }),
2081
2114
  author || source ? /* @__PURE__ */ jsx("footer", { className: "mt-step-sm", children: /* @__PURE__ */ jsxs(Text, { variant: "label", tone: "muted", as: "span", children: [
2082
2115
  author ? /* @__PURE__ */ jsx("cite", { className: "not-italic", children: author }) : null,
2083
2116
  author && source ? " \xB7 " : null,
@@ -2098,7 +2131,13 @@ function Breadcrumb({
2098
2131
  }) {
2099
2132
  const link = (href, content) => linkAsChild ? /* @__PURE__ */ jsx(Slot, { className: LINK, children: linkAsChild({ href, children: content }) }) : /* @__PURE__ */ jsx("a", { href, className: LINK, children: content });
2100
2133
  return /* @__PURE__ */ jsx("nav", { "aria-label": "Ruta", className: cn("font-mono text-meta text-text-muted", className), ...props, children: /* @__PURE__ */ jsxs("ol", { className: "gap-step-xs flex flex-wrap items-center", children: [
2101
- /* @__PURE__ */ jsx("li", { children: link(homeHref, /* @__PURE__ */ jsx("span", { "aria-label": homeLabel, children: "~" })) }),
2134
+ /* @__PURE__ */ jsx("li", { children: link(
2135
+ homeHref,
2136
+ /* @__PURE__ */ jsxs(Fragment, { children: [
2137
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "~" }),
2138
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: homeLabel })
2139
+ ] })
2140
+ ) }),
2102
2141
  items.map((item2, i) => {
2103
2142
  const last = i === items.length - 1;
2104
2143
  return /* @__PURE__ */ jsxs("li", { className: "gap-step-xs flex items-center", children: [
@@ -2388,7 +2427,7 @@ function EventCalendar({
2388
2427
  size: "sm",
2389
2428
  onClick: () => edit(event),
2390
2429
  "aria-label": `Editar \xAB${event.title}\xBB`,
2391
- children: "./edit"
2430
+ children: "./editar"
2392
2431
  }
2393
2432
  ) : null,
2394
2433
  onDeleteEvent ? /* @__PURE__ */ jsxs(AlertDialog, { children: [
@@ -2410,7 +2449,7 @@ function EventCalendar({
2410
2449
  ] }),
2411
2450
  /* @__PURE__ */ jsxs(AlertDialogFooter, { children: [
2412
2451
  /* @__PURE__ */ jsx(AlertDialogCancel, { children: "Mejor no" }),
2413
- /* @__PURE__ */ jsx(AlertDialogAction, { onClick: () => onDeleteEvent(event.id), children: "Borrar el event" })
2452
+ /* @__PURE__ */ jsx(AlertDialogAction, { onClick: () => onDeleteEvent(event.id), children: "Borrar el evento" })
2414
2453
  ] })
2415
2454
  ] })
2416
2455
  ] }) : null
@@ -2836,13 +2875,13 @@ function NewsletterForm({
2836
2875
  description,
2837
2876
  state = "idle",
2838
2877
  onSubmitEmail,
2839
- successMessage = "Ya est\xE1s dentro. Te llega un correo cada dos semanas, y nada m\xE1s.",
2878
+ successMessage = "Ya est\xE1s dentro. Te llega un correo al mes, y nada m\xE1s.",
2840
2879
  errorMessage = "No se pudo suscribir ese correo. Rev\xEDsalo y vuelve a intentar.",
2841
2880
  disclaimer,
2842
2881
  expression,
2843
2882
  basePath,
2844
2883
  submitLabel = "Suscribirme",
2845
- placeholder = "tu@email.dev",
2884
+ placeholder = "tu@correo.dev",
2846
2885
  fieldLabel = "Correo electr\xF3nico",
2847
2886
  nameField = false,
2848
2887
  nameLabel = "Nombre",
@@ -2877,7 +2916,7 @@ function NewsletterForm({
2877
2916
  type: "submit",
2878
2917
  variant: "conversion",
2879
2918
  loading: sending,
2880
- className: cn("disabled:opacity-60", nameField && "sm:self-start"),
2919
+ className: cn("w-full disabled:opacity-60 sm:w-auto", nameField && "sm:self-start"),
2881
2920
  children: submitLabel
2882
2921
  }
2883
2922
  );
@@ -2948,7 +2987,7 @@ function NewsletterForm({
2948
2987
  state === "success" ? /* @__PURE__ */ jsx(Alert, { id: `${id}-notice`, variant: "success", emphasis: "strong", children: successMessage }) : null,
2949
2988
  error ? /* @__PURE__ */ jsx(Alert, { id: `${id}-notice`, variant: "error", emphasis: "strong", children: errorMessage }) : null,
2950
2989
  disclaimer ? /* @__PURE__ */ jsxs("div", { className: "gap-step-sm flex items-center", children: [
2951
- expression ? /* @__PURE__ */ jsx(MascotFace, { expression, basePath, className: "w-10 max-w-none" }) : null,
2990
+ expression && !aside ? /* @__PURE__ */ jsx(MascotFace, { expression, basePath, className: "w-10 max-w-none" }) : null,
2952
2991
  /* @__PURE__ */ jsx(Text, { variant: "label", tone: "muted", as: "p", className: "font-normal", children: disclaimer })
2953
2992
  ] }) : null
2954
2993
  ] }),
@@ -3058,6 +3097,7 @@ function Stat({
3058
3097
  label,
3059
3098
  tone = "neutral",
3060
3099
  progress,
3100
+ progressLabel,
3061
3101
  icon,
3062
3102
  description,
3063
3103
  delta,
@@ -3065,6 +3105,7 @@ function Stat({
3065
3105
  className,
3066
3106
  ...props
3067
3107
  }) {
3108
+ const barName = progressLabel ?? (typeof label === "string" ? label : "Progreso");
3068
3109
  const DeltaGlyph = delta ? DELTA[delta.direction].glyph : null;
3069
3110
  return /* @__PURE__ */ jsxs("div", { className: cn(CARD_SURFACE, "p-step-lg gap-step-xs flex flex-col", className), ...props, children: [
3070
3111
  /* @__PURE__ */ jsxs("div", { className: "gap-step-sm flex items-start justify-between", children: [
@@ -3101,7 +3142,7 @@ function Stat({
3101
3142
  {
3102
3143
  value: progress,
3103
3144
  tone: tone === "neutral" ? "accent" : "warm",
3104
- label: `${label}: ${progress}%`,
3145
+ label: `${barName}: ${progress}%`,
3105
3146
  className: "mt-step-sm"
3106
3147
  }
3107
3148
  ) : null,
package/dist/og/index.cjs CHANGED
@@ -58,19 +58,20 @@ var typeScale = {
58
58
  h1: { tracking: "-0.03em" },
59
59
  eyebrow: { tracking: "0.12em"}
60
60
  };
61
- var deep = "#0D2129";
61
+ var ogDeep = "#0E2A30";
62
62
  var gradient = {
63
63
  dark: {
64
- /** The one used by the article OG template. 145°, from the document. */
65
- og: `linear-gradient(145deg, ${dark.background} 55%, ${deep} 100%)`
64
+ /** The article OG template. 145°, from the document. */
65
+ og: `linear-gradient(145deg, ${dark.background} 58%, ${ogDeep} 100%)`,
66
+ /** The talk OG template. 150°, from the document. */
67
+ ogTalk: `linear-gradient(150deg, ${dark.background} 55%, ${ogDeep} 100%)`
66
68
  },
67
69
  light: {
68
- og: `linear-gradient(145deg, ${light.background} 55%, ${light.surface} 100%)`
69
- }
70
+ og: `linear-gradient(145deg, ${light.background} 55%, ${light.surface} 100%)`}
70
71
  };
71
72
  var tagline = {
72
- /** Header, one line. */
73
- short: "Ayudo a equipos a escalar con criterio"};
73
+ /** Header, one line. Role and method, as in § 65. */
74
+ short: "Technical Lead \xB7 Spec-Driven Development"};
74
75
  var naming = {
75
76
  wordmark: "Eduardo \xC1lvarez",
76
77
  /** The domain, for the footer CLI signature: `$ cd ~/eduardoalvarez.dev/2026`. */
@@ -250,7 +251,7 @@ function talkTemplate(data) {
250
251
  const footer = [data.location, data.event ? void 0 : naming.domain].filter(Boolean).join(" \xB7 ");
251
252
  return card({
252
253
  mode: "dark",
253
- background: "none",
254
+ background: gradient.dark.ogTalk,
254
255
  eyebrow: { text: head, color: dark.accent },
255
256
  title: data.title,
256
257
  bajada: data.summary,
@@ -148,7 +148,7 @@ type TalkData = BaseData & {
148
148
  summary?: string | undefined;
149
149
  pose?: Pose | undefined;
150
150
  };
151
- /** Talk · eyebrow in biolume with the event and year, pose bleeding off the corner. */
151
+ /** Talk · 150° gradient, eyebrow in biolume with the event and year, pose bleeding off the corner. */
152
152
  declare function talkTemplate(data: TalkData): SatoriNode;
153
153
  type DefaultData = Omit<BaseData, 'title'> & {
154
154
  /** The name. The wordmark by default. */
@@ -148,7 +148,7 @@ type TalkData = BaseData & {
148
148
  summary?: string | undefined;
149
149
  pose?: Pose | undefined;
150
150
  };
151
- /** Talk · eyebrow in biolume with the event and year, pose bleeding off the corner. */
151
+ /** Talk · 150° gradient, eyebrow in biolume with the event and year, pose bleeding off the corner. */
152
152
  declare function talkTemplate(data: TalkData): SatoriNode;
153
153
  type DefaultData = Omit<BaseData, 'title'> & {
154
154
  /** The name. The wordmark by default. */
package/dist/og/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { ASSETS_PATH, faces, poses, fins } from '../chunk-CKRSQPTX.js';
2
- import { fonts, dark, light, naming, gradient, tagline, typeScale } from '../chunk-5YWGOFDX.js';
2
+ import { fonts, dark, light, naming, gradient, tagline, typeScale } from '../chunk-QQ5WRFHC.js';
3
3
 
4
4
  // src/og/templates.ts
5
5
  var OG = {
@@ -175,7 +175,7 @@ function talkTemplate(data) {
175
175
  const footer = [data.location, data.event ? void 0 : naming.domain].filter(Boolean).join(" \xB7 ");
176
176
  return card({
177
177
  mode: "dark",
178
- background: "none",
178
+ background: gradient.dark.ogTalk,
179
179
  eyebrow: { text: head, color: dark.accent },
180
180
  title: data.title,
181
181
  bajada: data.summary,
@@ -1,4 +1,4 @@
1
- import { syntax } from '../chunk-5YWGOFDX.js';
1
+ import { syntax } from '../chunk-QQ5WRFHC.js';
2
2
 
3
3
  // src/shiki/theme.ts
4
4
  var arrecife = {
@@ -263,17 +263,22 @@ var series = {
263
263
  light: [light.accent, light.warm, brand.body, light.textMuted]
264
264
  };
265
265
  var deep = "#0D2129";
266
+ var ogDeep = "#0E2A30";
266
267
  var gradient = {
267
268
  dark: {
268
269
  hero: `linear-gradient(160deg, ${dark.background} 60%, ${deep} 100%)`,
269
270
  section: `linear-gradient(150deg, ${dark.surface} 0%, ${deep} 100%)`,
270
- /** The one used by the article OG template. 145°, from the document. */
271
- og: `linear-gradient(145deg, ${dark.background} 55%, ${deep} 100%)`
271
+ /** The article OG template. 145°, from the document. */
272
+ og: `linear-gradient(145deg, ${dark.background} 58%, ${ogDeep} 100%)`,
273
+ /** The talk OG template. 150°, from the document. */
274
+ ogTalk: `linear-gradient(150deg, ${dark.background} 55%, ${ogDeep} 100%)`
272
275
  },
273
276
  light: {
274
277
  hero: `linear-gradient(160deg, ${light.background} 60%, ${light.surface} 100%)`,
275
278
  section: `linear-gradient(150deg, ${light.surface} 0%, ${light.background} 100%)`,
276
- og: `linear-gradient(145deg, ${light.background} 55%, ${light.surface} 100%)`
279
+ og: `linear-gradient(145deg, ${light.background} 55%, ${light.surface} 100%)`,
280
+ /** No light talk card exists; the key mirrors dark so both modes carry it. */
281
+ ogTalk: `linear-gradient(150deg, ${light.background} 55%, ${light.surface} 100%)`
277
282
  }
278
283
  };
279
284
  var shadow = {
@@ -285,12 +290,12 @@ var motion = {
285
290
  properties: "color, background-color, border-color, fill, stroke"
286
291
  };
287
292
  var tagline = {
288
- /** Hero. */
289
- long: "Ayudo a equipos de ingenier\xEDa a escalar con criterio",
290
- /** Header, one line. */
291
- short: "Ayudo a equipos a escalar con criterio",
292
- /** LinkedIn. */
293
- en: "Helping engineering teams scale with judgment"
293
+ /** Hero. The bajada approved in § 65. */
294
+ long: "Ense\xF1o a construir software con IA sin dejar de entender lo que hacemos.",
295
+ /** Header, one line. Role and method, as in § 65. */
296
+ short: "Technical Lead \xB7 Spec-Driven Development",
297
+ /** English surfaces. */
298
+ en: "I teach how to build software with AI without losing sight of what we build."
294
299
  };
295
300
  var naming = {
296
301
  wordmark: "Eduardo \xC1lvarez",
@@ -446,7 +446,7 @@ declare const size: {
446
446
  * invalid error 4.97:1
447
447
  *
448
448
  * `brand.body` (#3E7CB1) is NOT here: the system restricts it to fill, never
449
- * text, and it measures 4.2:1 in this context.
449
+ * text, and it measures 4.12:1 in this context.
450
450
  *
451
451
  * Numeric and boolean literals ride with strings in biolume. The document does
452
452
  * not assign them, and grouping them with strings — all three are literals — is
@@ -498,13 +498,17 @@ declare const gradient: {
498
498
  readonly dark: {
499
499
  readonly hero: "linear-gradient(160deg, #091319 60%, #0D2129 100%)";
500
500
  readonly section: "linear-gradient(150deg, #10202B 0%, #0D2129 100%)";
501
- /** The one used by the article OG template. 145°, from the document. */
502
- readonly og: "linear-gradient(145deg, #091319 55%, #0D2129 100%)";
501
+ /** The article OG template. 145°, from the document. */
502
+ readonly og: "linear-gradient(145deg, #091319 58%, #0E2A30 100%)";
503
+ /** The talk OG template. 150°, from the document. */
504
+ readonly ogTalk: "linear-gradient(150deg, #091319 55%, #0E2A30 100%)";
503
505
  };
504
506
  readonly light: {
505
507
  readonly hero: "linear-gradient(160deg, #F6F2EA 60%, #FFFFFF 100%)";
506
508
  readonly section: "linear-gradient(150deg, #FFFFFF 0%, #F6F2EA 100%)";
507
509
  readonly og: "linear-gradient(145deg, #F6F2EA 55%, #FFFFFF 100%)";
510
+ /** No light talk card exists; the key mirrors dark so both modes carry it. */
511
+ readonly ogTalk: "linear-gradient(150deg, #F6F2EA 55%, #FFFFFF 100%)";
508
512
  };
509
513
  };
510
514
  /** A single level. There is no elevation scale. */
@@ -522,12 +526,12 @@ declare const motion: {
522
526
  readonly properties: "color, background-color, border-color, fill, stroke";
523
527
  };
524
528
  declare const tagline: {
525
- /** Hero. */
526
- readonly long: "Ayudo a equipos de ingeniería a escalar con criterio";
527
- /** Header, one line. */
528
- readonly short: "Ayudo a equipos a escalar con criterio";
529
- /** LinkedIn. */
530
- readonly en: "Helping engineering teams scale with judgment";
529
+ /** Hero. The bajada approved in § 65. */
530
+ readonly long: "Enseño a construir software con IA sin dejar de entender lo que hacemos.";
531
+ /** Header, one line. Role and method, as in § 65. */
532
+ readonly short: "Technical Lead · Spec-Driven Development";
533
+ /** English surfaces. */
534
+ readonly en: "I teach how to build software with AI without losing sight of what we build.";
531
535
  };
532
536
  /**
533
537
  * The wordmark always reads «Eduardo Álvarez». The mascot is called Tiburoncín
@@ -758,12 +762,14 @@ declare const tokens: {
758
762
  readonly dark: {
759
763
  readonly hero: "linear-gradient(160deg, #091319 60%, #0D2129 100%)";
760
764
  readonly section: "linear-gradient(150deg, #10202B 0%, #0D2129 100%)";
761
- readonly og: "linear-gradient(145deg, #091319 55%, #0D2129 100%)";
765
+ readonly og: "linear-gradient(145deg, #091319 58%, #0E2A30 100%)";
766
+ readonly ogTalk: "linear-gradient(150deg, #091319 55%, #0E2A30 100%)";
762
767
  };
763
768
  readonly light: {
764
769
  readonly hero: "linear-gradient(160deg, #F6F2EA 60%, #FFFFFF 100%)";
765
770
  readonly section: "linear-gradient(150deg, #FFFFFF 0%, #F6F2EA 100%)";
766
771
  readonly og: "linear-gradient(145deg, #F6F2EA 55%, #FFFFFF 100%)";
772
+ readonly ogTalk: "linear-gradient(150deg, #F6F2EA 55%, #FFFFFF 100%)";
767
773
  };
768
774
  };
769
775
  readonly syntax: {
@@ -787,9 +793,9 @@ declare const tokens: {
787
793
  readonly properties: "color, background-color, border-color, fill, stroke";
788
794
  };
789
795
  readonly tagline: {
790
- readonly long: "Ayudo a equipos de ingeniería a escalar con criterio";
791
- readonly short: "Ayudo a equipos a escalar con criterio";
792
- readonly en: "Helping engineering teams scale with judgment";
796
+ readonly long: "Enseño a construir software con IA sin dejar de entender lo que hacemos.";
797
+ readonly short: "Technical Lead · Spec-Driven Development";
798
+ readonly en: "I teach how to build software with AI without losing sight of what we build.";
793
799
  };
794
800
  readonly naming: {
795
801
  readonly wordmark: "Eduardo Álvarez";