@eduardoalvarez/arrecife 0.12.2 → 0.12.4

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/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.12.4](https://github.com/Proskynete/arrecife/compare/v0.12.3...v0.12.4) (2026-09-24)
4
+
5
+
6
+ ### 🐛 Correcciones
7
+
8
+ * **tokens:** flat dark section, and the transcriptions re-synced from the 24 Sep canvases ([#86](https://github.com/Proskynete/arrecife/issues/86)) ([f5f5f5c](https://github.com/Proskynete/arrecife/commit/f5f5f5c6d45f65ae87ad3bf7dc7cad8e62be955a))
9
+
10
+ ## [0.12.3](https://github.com/Proskynete/arrecife/compare/v0.12.2...v0.12.3) (2026-09-24)
11
+
12
+
13
+ ### 🐛 Correcciones
14
+
15
+ * **components:** align ten components and the stories with the Design System canvas ([#85](https://github.com/Proskynete/arrecife/issues/85)) ([52f141d](https://github.com/Proskynete/arrecife/commit/52f141dcdc8739f6735f0b5082e7f2a19b54d383))
16
+ * **tokens:** the tagline follows the new positioning, so the header stops saying the old one ([#82](https://github.com/Proskynete/arrecife/issues/82)) ([01abbbf](https://github.com/Proskynete/arrecife/commit/01abbbf5f31fd0690137c5c169432bd8c34b7689))
17
+
18
+
19
+ ### 📚 Documentación
20
+
21
+ * re-sync the design system transcription and record where production diverged, §§ 69–75 ([#84](https://github.com/Proskynete/arrecife/issues/84)) ([64dddcf](https://github.com/Proskynete/arrecife/commit/64dddcf32490a65f0185cafffe3467d4fcf9f55d))
22
+
3
23
  ## [0.12.2](https://github.com/Proskynete/arrecife/compare/v0.12.1...v0.12.2) (2026-09-23)
4
24
 
5
25
 
package/README.md CHANGED
@@ -355,7 +355,7 @@ import { Text, Button, Badge, cn } from '@eduardoalvarez/arrecife';
355
355
  import type { TextProps } from '@eduardoalvarez/arrecife';
356
356
 
357
357
  <Text variant="eyebrow" tone="muted">charlas</Text>
358
- <Text as="h2" variant="h1">Escalar con criterio</Text>
358
+ <Text as="h2" variant="h1">Entender antes de construir</Text>
359
359
  <Text variant="body">Clamps itself to 68ch.</Text>
360
360
  <Text variant="ui" measure={false}>No clamp, for a narrow cell.</Text>
361
361
  ```
@@ -134,8 +134,8 @@ var spacing = {
134
134
  section: 96
135
135
  };
136
136
  var tagline = {
137
- /** Header, one line. */
138
- short: "Ayudo a equipos a escalar con criterio"};
137
+ /** Header, one line. Role and method, as in § 65. */
138
+ short: "Technical Lead \xB7 Spec-Driven Development"};
139
139
  var naming = {
140
140
  wordmark: "Eduardo \xC1lvarez"};
141
141
 
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- export { Isotype, Logo, Mascot, MascotFace } from '../chunk-BRDTB44R.js';
3
- import '../chunk-TMUGT3Y3.js';
2
+ export { Isotype, Logo, Mascot, MascotFace } from '../chunk-YZ4VVECJ.js';
3
+ import '../chunk-CHHN7NBY.js';
4
4
  export { ASSETS_PATH, faceList, faceUsage, faces, fins, poseList, poses } from '../chunk-CKRSQPTX.js';
5
- import '../chunk-5YWGOFDX.js';
5
+ import '../chunk-EYBN76H6.js';
@@ -1,8 +1,8 @@
1
1
  'use client';
2
- import { Text } from '../chunk-BFYNBIVJ.js';
2
+ import { Text } from '../chunk-IBX7VFDS.js';
3
3
  import '../chunk-ODBFN44D.js';
4
- import { cn } from '../chunk-TMUGT3Y3.js';
5
- import { radius } from '../chunk-5YWGOFDX.js';
4
+ import { cn } from '../chunk-CHHN7NBY.js';
5
+ import { radius } from '../chunk-EYBN76H6.js';
6
6
  import { useId } from 'react';
7
7
  import { ResponsiveContainer, Tooltip, Legend, AreaChart as AreaChart$1, CartesianGrid, XAxis, YAxis, Area, BarChart as BarChart$1, Bar, LineChart as LineChart$1, Line } from 'recharts';
8
8
  import { jsxs, jsx } from 'react/jsx-runtime';
@@ -1,4 +1,4 @@
1
- import { cn } from './chunk-TMUGT3Y3.js';
1
+ import { cn } from './chunk-CHHN7NBY.js';
2
2
  import { jsx } from 'react/jsx-runtime';
3
3
 
4
4
  var ICON_WEIGHT = "regular";
@@ -1,4 +1,4 @@
1
- import { spacing, control, radius, typeScale } from './chunk-5YWGOFDX.js';
1
+ import { spacing, control, radius, typeScale } from './chunk-EYBN76H6.js';
2
2
  import { clsx } from 'clsx';
3
3
  import { extendTailwindMerge } from 'tailwind-merge';
4
4
 
@@ -261,17 +261,28 @@ var series = {
261
261
  light: [light.accent, light.warm, brand.body, light.textMuted]
262
262
  };
263
263
  var deep = "#0D2129";
264
+ var ogDeep = "#0E2A30";
264
265
  var gradient = {
265
266
  dark: {
266
267
  hero: `linear-gradient(160deg, ${dark.background} 60%, ${deep} 100%)`,
267
- section: `linear-gradient(150deg, ${dark.surface} 0%, ${deep} 100%)`,
268
- /** The one used by the article OG template. 145°, from the document. */
269
- og: `linear-gradient(145deg, ${dark.background} 55%, ${deep} 100%)`
268
+ /**
269
+ * Flat, on purpose. The document drew it #10202B → #0D2129, a 0.11 L* sweep
270
+ * nobody can see; the author chose a flat fosa over inventing a second stop
271
+ * (§ 9). It stays a `linear-gradient` so `gradient-section`, which sets
272
+ * `background-image`, keeps working unchanged in every project.
273
+ */
274
+ section: `linear-gradient(${dark.surface}, ${dark.surface})`,
275
+ /** The article OG template. 145°, from the document. */
276
+ og: `linear-gradient(145deg, ${dark.background} 58%, ${ogDeep} 100%)`,
277
+ /** The talk OG template. 150°, from the document. */
278
+ ogTalk: `linear-gradient(150deg, ${dark.background} 55%, ${ogDeep} 100%)`
270
279
  },
271
280
  light: {
272
281
  hero: `linear-gradient(160deg, ${light.background} 60%, ${light.surface} 100%)`,
273
282
  section: `linear-gradient(150deg, ${light.surface} 0%, ${light.background} 100%)`,
274
- og: `linear-gradient(145deg, ${light.background} 55%, ${light.surface} 100%)`
283
+ og: `linear-gradient(145deg, ${light.background} 55%, ${light.surface} 100%)`,
284
+ /** No light talk card exists; the key mirrors dark so both modes carry it. */
285
+ ogTalk: `linear-gradient(150deg, ${light.background} 55%, ${light.surface} 100%)`
275
286
  }
276
287
  };
277
288
  var shadow = {
@@ -283,12 +294,12 @@ var motion = {
283
294
  properties: "color, background-color, border-color, fill, stroke"
284
295
  };
285
296
  var tagline = {
286
- /** Hero. */
287
- long: "Ayudo a equipos de ingenier\xEDa a escalar con criterio",
288
- /** Header, one line. */
289
- short: "Ayudo a equipos a escalar con criterio",
290
- /** LinkedIn. */
291
- en: "Helping engineering teams scale with judgment"
297
+ /** Hero. The bajada approved in § 65. */
298
+ long: "Ense\xF1o a construir software con IA sin dejar de entender lo que hacemos.",
299
+ /** Header, one line. Role and method, as in § 65. */
300
+ short: "Technical Lead \xB7 Spec-Driven Development",
301
+ /** English surfaces. */
302
+ en: "I teach how to build software with AI without losing sight of what we build."
292
303
  };
293
304
  var naming = {
294
305
  wordmark: "Eduardo \xC1lvarez",
@@ -1,5 +1,5 @@
1
1
  import { text } from './chunk-ODBFN44D.js';
2
- import { cn } from './chunk-TMUGT3Y3.js';
2
+ import { cn } from './chunk-CHHN7NBY.js';
3
3
  import { Slot } from '@radix-ui/react-slot';
4
4
  import 'class-variance-authority';
5
5
  import { jsx } from 'react/jsx-runtime';
@@ -1,4 +1,4 @@
1
- import { cn } from './chunk-TMUGT3Y3.js';
1
+ import { cn } from './chunk-CHHN7NBY.js';
2
2
  import * as LabelPrimitive from '@radix-ui/react-label';
3
3
  import { jsx } from 'react/jsx-runtime';
4
4
 
@@ -1,4 +1,4 @@
1
- import { naming, tagline, motion, shadow, series, syntax, gradient, size, spacing, control, radius, limits, typeScale, fonts, brand, colors } from './chunk-5YWGOFDX.js';
1
+ import { naming, tagline, motion, shadow, series, syntax, gradient, size, spacing, control, radius, limits, typeScale, fonts, brand, colors } from './chunk-EYBN76H6.js';
2
2
 
3
3
  // src/tokens/index.ts
4
4
  var tokens = {
@@ -1,6 +1,6 @@
1
- import { cn } from './chunk-TMUGT3Y3.js';
1
+ import { cn } from './chunk-CHHN7NBY.js';
2
2
  import { fins, ASSETS_PATH, poses, faces } from './chunk-CKRSQPTX.js';
3
- import { naming, tagline } from './chunk-5YWGOFDX.js';
3
+ import { naming, tagline } from './chunk-EYBN76H6.js';
4
4
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
5
5
 
6
6
  function Isotype({
@@ -1,9 +1,9 @@
1
1
  'use client';
2
- import { Label } from '../chunk-W75O3Z77.js';
3
- import { Text } from '../chunk-BFYNBIVJ.js';
2
+ import { Label } from '../chunk-NGVEMBLN.js';
3
+ import { Text } from '../chunk-IBX7VFDS.js';
4
4
  import '../chunk-ODBFN44D.js';
5
- import { cn } from '../chunk-TMUGT3Y3.js';
6
- import '../chunk-5YWGOFDX.js';
5
+ import { cn } from '../chunk-CHHN7NBY.js';
6
+ import '../chunk-EYBN76H6.js';
7
7
  import { createContext, useContext, useId } from 'react';
8
8
  import { FormProvider, Controller, useFormContext, useFormState } from 'react-hook-form';
9
9
  import { Slot } from '@radix-ui/react-slot';
@@ -1,3 +1,3 @@
1
- export { ICON_WEIGHT, Icon, TONE_WEIGHT } from '../chunk-WU66TPJT.js';
2
- import '../chunk-TMUGT3Y3.js';
3
- import '../chunk-5YWGOFDX.js';
1
+ export { ICON_WEIGHT, Icon, TONE_WEIGHT } from '../chunk-5V6ORCV5.js';
2
+ import '../chunk-CHHN7NBY.js';
3
+ import '../chunk-EYBN76H6.js';
package/dist/index.cjs CHANGED
@@ -326,17 +326,28 @@ var series = {
326
326
  light: [light.accent, light.warm, brand.body, light.textMuted]
327
327
  };
328
328
  var deep = "#0D2129";
329
+ var ogDeep = "#0E2A30";
329
330
  var gradient = {
330
331
  dark: {
331
332
  hero: `linear-gradient(160deg, ${dark.background} 60%, ${deep} 100%)`,
332
- section: `linear-gradient(150deg, ${dark.surface} 0%, ${deep} 100%)`,
333
- /** The one used by the article OG template. 145°, from the document. */
334
- og: `linear-gradient(145deg, ${dark.background} 55%, ${deep} 100%)`
333
+ /**
334
+ * Flat, on purpose. The document drew it #10202B → #0D2129, a 0.11 L* sweep
335
+ * nobody can see; the author chose a flat fosa over inventing a second stop
336
+ * (§ 9). It stays a `linear-gradient` so `gradient-section`, which sets
337
+ * `background-image`, keeps working unchanged in every project.
338
+ */
339
+ section: `linear-gradient(${dark.surface}, ${dark.surface})`,
340
+ /** The article OG template. 145°, from the document. */
341
+ og: `linear-gradient(145deg, ${dark.background} 58%, ${ogDeep} 100%)`,
342
+ /** The talk OG template. 150°, from the document. */
343
+ ogTalk: `linear-gradient(150deg, ${dark.background} 55%, ${ogDeep} 100%)`
335
344
  },
336
345
  light: {
337
346
  hero: `linear-gradient(160deg, ${light.background} 60%, ${light.surface} 100%)`,
338
347
  section: `linear-gradient(150deg, ${light.surface} 0%, ${light.background} 100%)`,
339
- og: `linear-gradient(145deg, ${light.background} 55%, ${light.surface} 100%)`
348
+ og: `linear-gradient(145deg, ${light.background} 55%, ${light.surface} 100%)`,
349
+ /** No light talk card exists; the key mirrors dark so both modes carry it. */
350
+ ogTalk: `linear-gradient(150deg, ${light.background} 55%, ${light.surface} 100%)`
340
351
  }
341
352
  };
342
353
  var shadow = {
@@ -348,12 +359,12 @@ var motion = {
348
359
  properties: "color, background-color, border-color, fill, stroke"
349
360
  };
350
361
  var tagline = {
351
- /** Hero. */
352
- long: "Ayudo a equipos de ingenier\xEDa a escalar con criterio",
353
- /** Header, one line. */
354
- short: "Ayudo a equipos a escalar con criterio",
355
- /** LinkedIn. */
356
- en: "Helping engineering teams scale with judgment"
362
+ /** Hero. The bajada approved in § 65. */
363
+ long: "Ense\xF1o a construir software con IA sin dejar de entender lo que hacemos.",
364
+ /** Header, one line. Role and method, as in § 65. */
365
+ short: "Technical Lead \xB7 Spec-Driven Development",
366
+ /** English surfaces. */
367
+ en: "I teach how to build software with AI without losing sight of what we build."
357
368
  };
358
369
  var naming = {
359
370
  wordmark: "Eduardo \xC1lvarez",
@@ -1225,7 +1236,7 @@ function DateField({
1225
1236
  "hover:border-hairline-hover",
1226
1237
  "focus-visible:border-accent focus-ring",
1227
1238
  "disabled:pointer-events-none disabled:opacity-50",
1228
- "data-[invalid]:border-error data-[invalid]:focus-visible:outline-error",
1239
+ "aria-[invalid=true]:border-error aria-[invalid=true]:focus-visible:outline-error",
1229
1240
  // The native indicator inherits the active theme's scheme.
1230
1241
  "scheme-light dark:scheme-dark [&::-webkit-calendar-picker-indicator]:cursor-pointer",
1231
1242
  className
@@ -1428,7 +1439,7 @@ function Input({ className, invalid = false, ...props }) {
1428
1439
  "hover:border-hairline-hover",
1429
1440
  "focus-visible:border-accent focus-ring",
1430
1441
  "disabled:pointer-events-none disabled:opacity-50",
1431
- "data-[invalid]:border-error data-[invalid]:focus-visible:outline-error",
1442
+ "aria-[invalid=true]:border-error aria-[invalid=true]:focus-visible:outline-error",
1432
1443
  "file:mr-step-sm file:border-0 file:bg-transparent file:font-sans file:text-label file:text-text-secondary",
1433
1444
  className
1434
1445
  ),
@@ -1478,12 +1489,11 @@ function PaginationLink({ className, isActive = false, ...props }) {
1478
1489
  {
1479
1490
  "aria-current": isActive ? "page" : void 0,
1480
1491
  className: cn(
1481
- "rounded-chip px-step-sm inline-flex h-9 min-w-9 cursor-pointer items-center justify-center",
1482
- "font-sans text-ui text-text-secondary",
1492
+ "rounded-chip px-step-sm inline-flex h-9 min-w-9 cursor-pointer items-center justify-center border",
1493
+ "font-mono text-meta",
1483
1494
  "transition-standard",
1484
- "hover:bg-surface hover:text-text-primary",
1485
1495
  "focus-ring",
1486
- isActive && "bg-surface-raised text-text-primary",
1496
+ isActive ? "border-accent bg-accent text-accent-on" : "border-border text-text-secondary hover:border-hairline-hover hover:text-text-primary",
1487
1497
  className
1488
1498
  ),
1489
1499
  ...props
@@ -1595,6 +1605,8 @@ function SelectTrigger({
1595
1605
  "transition-standard",
1596
1606
  "hover:border-hairline-hover",
1597
1607
  "focus-visible:border-accent focus-ring",
1608
+ // `FormControl` marks an invalid field with `aria-invalid` only.
1609
+ "aria-[invalid=true]:border-error aria-[invalid=true]:focus-visible:outline-error",
1598
1610
  "disabled:pointer-events-none disabled:opacity-50",
1599
1611
  "data-[placeholder]:text-text-muted",
1600
1612
  className
@@ -1978,7 +1990,7 @@ function Textarea({ className, invalid = false, ...props }) {
1978
1990
  "hover:border-hairline-hover",
1979
1991
  "focus-visible:border-accent focus-ring",
1980
1992
  "disabled:pointer-events-none disabled:opacity-50",
1981
- "data-[invalid]:border-error data-[invalid]:focus-visible:outline-error",
1993
+ "aria-[invalid=true]:border-error aria-[invalid=true]:focus-visible:outline-error",
1982
1994
  className
1983
1995
  ),
1984
1996
  ...props
@@ -2152,9 +2164,9 @@ function TooltipContent({
2152
2164
  {
2153
2165
  sideOffset,
2154
2166
  className: cn(
2155
- "z-50 px-step-sm py-1.5",
2156
- "rounded-chip border-border bg-surface-raised shadow-standard border",
2157
- "font-sans text-label text-text-primary",
2167
+ "z-50 px-step-sm py-step-xs",
2168
+ "rounded-chip bg-text-primary shadow-standard",
2169
+ "font-sans text-label text-background",
2158
2170
  className
2159
2171
  ),
2160
2172
  ...props
@@ -2258,7 +2270,9 @@ function ProgressBar({
2258
2270
  height,
2259
2271
  knob,
2260
2272
  progress,
2261
- redondeada = true,
2273
+ valueText,
2274
+ onKeyDown,
2275
+ rounded = true,
2262
2276
  tabIndex = 0,
2263
2277
  ...pointer
2264
2278
  }) {
@@ -2267,28 +2281,31 @@ function ProgressBar({
2267
2281
  {
2268
2282
  className: cn(
2269
2283
  "group bg-surface-raised relative cursor-pointer",
2284
+ "focus-ring",
2270
2285
  height,
2271
- redondeada && "rounded-pill"
2286
+ rounded && "rounded-pill"
2272
2287
  ),
2273
2288
  role: "slider",
2274
2289
  "aria-label": "Progreso del audio",
2275
2290
  "aria-valuemin": 0,
2276
2291
  "aria-valuemax": 100,
2277
2292
  "aria-valuenow": Math.round(progress),
2293
+ "aria-valuetext": valueText,
2278
2294
  tabIndex,
2295
+ onKeyDown,
2279
2296
  ...pointer,
2280
2297
  children: [
2281
2298
  /* @__PURE__ */ jsxRuntime.jsx(
2282
2299
  "div",
2283
2300
  {
2284
- className: cn("bg-accent absolute h-full", redondeada && "rounded-pill"),
2301
+ className: cn("bg-accent absolute h-full", rounded && "rounded-pill"),
2285
2302
  style: { width: `${progress}%` }
2286
2303
  }
2287
2304
  ),
2288
2305
  /* @__PURE__ */ jsxRuntime.jsx(
2289
2306
  "div",
2290
2307
  {
2291
- className: "rounded-pill bg-text-primary absolute top-1/2 -translate-y-1/2 opacity-0 transition-opacity group-hover:opacity-100",
2308
+ 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",
2292
2309
  style: {
2293
2310
  width: knob,
2294
2311
  height: knob,
@@ -2374,6 +2391,7 @@ function Volume({
2374
2391
  className: cn(
2375
2392
  "bg-surface-raised rounded-pill h-1 cursor-pointer appearance-none",
2376
2393
  "[accent-color:var(--color-accent)]",
2394
+ "focus-ring",
2377
2395
  width
2378
2396
  ),
2379
2397
  "aria-label": "Volumen"
@@ -2385,7 +2403,7 @@ function PlayIcon({ className, loading, error, playing }) {
2385
2403
  if (loading)
2386
2404
  return /* @__PURE__ */ jsxRuntime.jsx(Icon, { as: react.CircleNotch, className: cn("motion-safe:animate-spin", className) });
2387
2405
  if (error) return /* @__PURE__ */ jsxRuntime.jsx(Icon, { as: react.ArrowsClockwise, className });
2388
- if (playing) return /* @__PURE__ */ jsxRuntime.jsx(Icon, { as: react.Pause, className });
2406
+ if (playing) return /* @__PURE__ */ jsxRuntime.jsx(Icon, { as: react.Pause, tone: "current", className });
2389
2407
  return /* @__PURE__ */ jsxRuntime.jsx(Icon, { as: react.Play, className });
2390
2408
  }
2391
2409
  function PlayButton({
@@ -2404,7 +2422,8 @@ function PlayButton({
2404
2422
  className: cn(
2405
2423
  "text-accent-on transition-standard cursor-pointer",
2406
2424
  "focus-ring",
2407
- state.error ? "bg-error hover:bg-error/80" : "bg-accent hover:bg-accent-hover",
2425
+ // `danger` is the fill and `error` is text: the retry button is a fill.
2426
+ state.error ? "bg-danger text-danger-on hover:bg-danger-hover" : "bg-accent hover:bg-accent-hover",
2408
2427
  className
2409
2428
  ),
2410
2429
  "aria-label": label,
@@ -2536,6 +2555,29 @@ function AudioPlayer({ src, title, mode = "full", onFirstPlay }) {
2536
2555
  e.currentTarget.releasePointerCapture(e.pointerId);
2537
2556
  isDragging.current = false;
2538
2557
  }, []);
2558
+ const handleProgressKey = react$1.useCallback(
2559
+ (e) => {
2560
+ const audio = audioRef.current;
2561
+ if (!audio || !duration) return;
2562
+ const steps = {
2563
+ ArrowLeft: -5,
2564
+ ArrowDown: -5,
2565
+ ArrowRight: 5,
2566
+ ArrowUp: 5,
2567
+ PageDown: -15,
2568
+ PageUp: 15
2569
+ };
2570
+ let next;
2571
+ if (e.key === "Home") next = 0;
2572
+ else if (e.key === "End") next = duration;
2573
+ else if (e.key in steps) next = audio.currentTime + (steps[e.key] ?? 0);
2574
+ if (next === void 0) return;
2575
+ e.preventDefault();
2576
+ audio.currentTime = Math.max(0, Math.min(next, duration));
2577
+ setCurrentTime(audio.currentTime);
2578
+ },
2579
+ [duration]
2580
+ );
2539
2581
  const handleVolumeChange = react$1.useCallback((e) => {
2540
2582
  const audio = audioRef.current;
2541
2583
  if (!audio) return;
@@ -2576,7 +2618,9 @@ function AudioPlayer({ src, title, mode = "full", onFirstPlay }) {
2576
2618
  const pointer = {
2577
2619
  onPointerDown: handlePointerDown,
2578
2620
  onPointerMove: handlePointerMove,
2579
- onPointerUp: handlePointerUp
2621
+ onPointerUp: handlePointerUp,
2622
+ onKeyDown: handleProgressKey,
2623
+ valueText: `${formatTime(currentTime)} de ${formatTime(duration)}`
2580
2624
  };
2581
2625
  const floatingJsx = /* @__PURE__ */ jsxRuntime.jsxs(
2582
2626
  "div",
@@ -2594,7 +2638,7 @@ function AudioPlayer({ src, title, mode = "full", onFirstPlay }) {
2594
2638
  height: "h-1",
2595
2639
  knob: 10,
2596
2640
  progress,
2597
- redondeada: false,
2641
+ rounded: false,
2598
2642
  tabIndex: -1,
2599
2643
  ...pointer
2600
2644
  }
@@ -2780,10 +2824,10 @@ function Blockquote({ children, author, source, className, ...props }) {
2780
2824
  return /* @__PURE__ */ jsxRuntime.jsxs(
2781
2825
  "blockquote",
2782
2826
  {
2783
- className: cn("border-accent pl-step-md my-step-lg border-l-2", className),
2827
+ className: cn("border-warm pl-step-md my-step-lg border-l-3", className),
2784
2828
  ...props,
2785
2829
  children: [
2786
- /* @__PURE__ */ jsxRuntime.jsx(Text, { variant: "body", tone: "secondary", children }),
2830
+ /* @__PURE__ */ jsxRuntime.jsx(Text, { variant: "body", tone: "secondary", className: "italic", children }),
2787
2831
  author || source ? /* @__PURE__ */ jsxRuntime.jsx("footer", { className: "mt-step-sm", children: /* @__PURE__ */ jsxRuntime.jsxs(Text, { variant: "label", tone: "muted", as: "span", children: [
2788
2832
  author ? /* @__PURE__ */ jsxRuntime.jsx("cite", { className: "not-italic", children: author }) : null,
2789
2833
  author && source ? " \xB7 " : null,
@@ -2804,7 +2848,13 @@ function Breadcrumb({
2804
2848
  }) {
2805
2849
  const link = (href, content) => linkAsChild ? /* @__PURE__ */ jsxRuntime.jsx(reactSlot.Slot, { className: LINK, children: linkAsChild({ href, children: content }) }) : /* @__PURE__ */ jsxRuntime.jsx("a", { href, className: LINK, children: content });
2806
2850
  return /* @__PURE__ */ jsxRuntime.jsx("nav", { "aria-label": "Ruta", className: cn("font-mono text-meta text-text-muted", className), ...props, children: /* @__PURE__ */ jsxRuntime.jsxs("ol", { className: "gap-step-xs flex flex-wrap items-center", children: [
2807
- /* @__PURE__ */ jsxRuntime.jsx("li", { children: link(homeHref, /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-label": homeLabel, children: "~" })) }),
2851
+ /* @__PURE__ */ jsxRuntime.jsx("li", { children: link(
2852
+ homeHref,
2853
+ /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2854
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "~" }),
2855
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", children: homeLabel })
2856
+ ] })
2857
+ ) }),
2808
2858
  items.map((item2, i) => {
2809
2859
  const last = i === items.length - 1;
2810
2860
  return /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "gap-step-xs flex items-center", children: [
@@ -3163,7 +3213,7 @@ function EventCalendar({
3163
3213
  size: "sm",
3164
3214
  onClick: () => edit(event),
3165
3215
  "aria-label": `Editar \xAB${event.title}\xBB`,
3166
- children: "./edit"
3216
+ children: "./editar"
3167
3217
  }
3168
3218
  ) : null,
3169
3219
  onDeleteEvent ? /* @__PURE__ */ jsxRuntime.jsxs(AlertDialog, { children: [
@@ -3185,7 +3235,7 @@ function EventCalendar({
3185
3235
  ] }),
3186
3236
  /* @__PURE__ */ jsxRuntime.jsxs(AlertDialogFooter, { children: [
3187
3237
  /* @__PURE__ */ jsxRuntime.jsx(AlertDialogCancel, { children: "Mejor no" }),
3188
- /* @__PURE__ */ jsxRuntime.jsx(AlertDialogAction, { onClick: () => onDeleteEvent(event.id), children: "Borrar el event" })
3238
+ /* @__PURE__ */ jsxRuntime.jsx(AlertDialogAction, { onClick: () => onDeleteEvent(event.id), children: "Borrar el evento" })
3189
3239
  ] })
3190
3240
  ] })
3191
3241
  ] }) : null
@@ -3611,13 +3661,13 @@ function NewsletterForm({
3611
3661
  description,
3612
3662
  state = "idle",
3613
3663
  onSubmitEmail,
3614
- successMessage = "Ya est\xE1s dentro. Te llega un correo cada dos semanas, y nada m\xE1s.",
3664
+ successMessage = "Ya est\xE1s dentro. Te llega un correo al mes, y nada m\xE1s.",
3615
3665
  errorMessage = "No se pudo suscribir ese correo. Rev\xEDsalo y vuelve a intentar.",
3616
3666
  disclaimer,
3617
3667
  expression,
3618
3668
  basePath,
3619
3669
  submitLabel = "Suscribirme",
3620
- placeholder = "tu@email.dev",
3670
+ placeholder = "tu@correo.dev",
3621
3671
  fieldLabel = "Correo electr\xF3nico",
3622
3672
  nameField = false,
3623
3673
  nameLabel = "Nombre",
@@ -3652,7 +3702,7 @@ function NewsletterForm({
3652
3702
  type: "submit",
3653
3703
  variant: "conversion",
3654
3704
  loading: sending,
3655
- className: cn("disabled:opacity-60", nameField && "sm:self-start"),
3705
+ className: cn("w-full disabled:opacity-60 sm:w-auto", nameField && "sm:self-start"),
3656
3706
  children: submitLabel
3657
3707
  }
3658
3708
  );
@@ -3723,7 +3773,7 @@ function NewsletterForm({
3723
3773
  state === "success" ? /* @__PURE__ */ jsxRuntime.jsx(Alert, { id: `${id}-notice`, variant: "success", emphasis: "strong", children: successMessage }) : null,
3724
3774
  error ? /* @__PURE__ */ jsxRuntime.jsx(Alert, { id: `${id}-notice`, variant: "error", emphasis: "strong", children: errorMessage }) : null,
3725
3775
  disclaimer ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "gap-step-sm flex items-center", children: [
3726
- expression ? /* @__PURE__ */ jsxRuntime.jsx(MascotFace, { expression, basePath, className: "w-10 max-w-none" }) : null,
3776
+ expression && !aside ? /* @__PURE__ */ jsxRuntime.jsx(MascotFace, { expression, basePath, className: "w-10 max-w-none" }) : null,
3727
3777
  /* @__PURE__ */ jsxRuntime.jsx(Text, { variant: "label", tone: "muted", as: "p", className: "font-normal", children: disclaimer })
3728
3778
  ] }) : null
3729
3779
  ] }),
@@ -3833,6 +3883,7 @@ function Stat({
3833
3883
  label,
3834
3884
  tone = "neutral",
3835
3885
  progress,
3886
+ progressLabel,
3836
3887
  icon,
3837
3888
  description,
3838
3889
  delta,
@@ -3840,6 +3891,7 @@ function Stat({
3840
3891
  className,
3841
3892
  ...props
3842
3893
  }) {
3894
+ const barName = progressLabel ?? (typeof label === "string" ? label : "Progreso");
3843
3895
  const DeltaGlyph = delta ? DELTA[delta.direction].glyph : null;
3844
3896
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn(CARD_SURFACE, "p-step-lg gap-step-xs flex flex-col", className), ...props, children: [
3845
3897
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "gap-step-sm flex items-start justify-between", children: [
@@ -3876,7 +3928,7 @@ function Stat({
3876
3928
  {
3877
3929
  value: progress,
3878
3930
  tone: tone === "neutral" ? "accent" : "warm",
3879
- label: `${label}: ${progress}%`,
3931
+ label: `${barName}: ${progress}%`,
3880
3932
  className: "mt-step-sm"
3881
3933
  }
3882
3934
  ) : null,
package/dist/index.d.cts 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;