@eduardoalvarez/arrecife 0.1.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 (44) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/LICENSE +21 -0
  3. package/README.md +520 -0
  4. package/assets/brand/face-annoyed.png +0 -0
  5. package/assets/brand/face-confused.png +0 -0
  6. package/assets/brand/face-hearts.png +0 -0
  7. package/assets/brand/face-laughing.png +0 -0
  8. package/assets/brand/face-shades.png +0 -0
  9. package/assets/brand/face-waiting.png +0 -0
  10. package/assets/brand/face-wink.png +0 -0
  11. package/assets/brand/fin-foam.png +0 -0
  12. package/assets/brand/fin.png +0 -0
  13. package/assets/brand/pose-desk.png +0 -0
  14. package/assets/brand/pose-laptop-coffee.png +0 -0
  15. package/assets/brand/pose-peek.png +0 -0
  16. package/assets/brand/pose-surf.png +0 -0
  17. package/dist/brand/index.cjs +228 -0
  18. package/dist/brand/index.d.cts +63 -0
  19. package/dist/brand/index.d.ts +63 -0
  20. package/dist/brand/index.js +3 -0
  21. package/dist/catalogo-Du5ID-Hi.d.cts +77 -0
  22. package/dist/catalogo-Du5ID-Hi.d.ts +77 -0
  23. package/dist/chunk-22OXRTAL.js +22 -0
  24. package/dist/chunk-DHF63QNM.js +231 -0
  25. package/dist/chunk-E3OMP2DL.js +36 -0
  26. package/dist/chunk-ZL5X6XO7.js +102 -0
  27. package/dist/index.cjs +3191 -0
  28. package/dist/index.d.cts +1024 -0
  29. package/dist/index.d.ts +1024 -0
  30. package/dist/index.js +2624 -0
  31. package/dist/og/index.cjs +322 -0
  32. package/dist/og/index.d.cts +164 -0
  33. package/dist/og/index.d.ts +164 -0
  34. package/dist/og/index.js +242 -0
  35. package/dist/shiki/index.cjs +127 -0
  36. package/dist/shiki/index.d.cts +16 -0
  37. package/dist/shiki/index.d.ts +16 -0
  38. package/dist/shiki/index.js +95 -0
  39. package/dist/tokens/index.cjs +263 -0
  40. package/dist/tokens/index.d.cts +623 -0
  41. package/dist/tokens/index.d.ts +623 -0
  42. package/dist/tokens/index.js +2 -0
  43. package/dist/tokens/theme.css +266 -0
  44. package/package.json +128 -0
@@ -0,0 +1,322 @@
1
+ 'use strict';
2
+
3
+ // src/brand/catalogo.ts
4
+ var caras = {
5
+ annoyed: "face-annoyed.png",
6
+ confused: "face-confused.png",
7
+ hearts: "face-hearts.png",
8
+ laughing: "face-laughing.png",
9
+ shades: "face-shades.png",
10
+ waiting: "face-waiting.png",
11
+ wink: "face-wink.png"
12
+ };
13
+ var poses = {
14
+ desk: "pose-desk.png",
15
+ "laptop-coffee": "pose-laptop-coffee.png",
16
+ peek: "pose-peek.png",
17
+ surf: "pose-surf.png"
18
+ };
19
+ var aletas = {
20
+ /** Dos azules. Para fondos claros. */
21
+ color: "fin.png",
22
+ /** Silueta a una tinta. Para fondos oscuros. */
23
+ espuma: "fin-foam.png"
24
+ };
25
+ var RUTA_ASSETS = "/brand";
26
+
27
+ // src/tokens/tokens.ts
28
+ var dark = {
29
+ background: "#091319",
30
+ // corriente · menús, tabs activos
31
+ border: "#22414F",
32
+ // hairline en hover de tarjeta
33
+ textPrimary: "#EDF4F3",
34
+ // espuma 16.84:1
35
+ textSecondary: "#A7BCC4",
36
+ // plancton 5.57:1 nunca bajo 13px
37
+ accent: "#35D6C0",
38
+ // tinta sobre bioluz
39
+ warm: "#F2A65A"};
40
+ var light = {
41
+ background: "#F6F2EA",
42
+ surfaceRaised: "#EFE9DE",
43
+ // hairline en hover de tarjeta
44
+ textPrimary: "#0B1524",
45
+ // 16.40:1
46
+ textSecondary: "#3D4B58",
47
+ warm: "#A65B27"};
48
+ var fonts = {
49
+ /** SOLO titulares y números grandes. Nunca cuerpo. */
50
+ display: '"Bricolage Grotesque", ui-sans-serif, system-ui, sans-serif',
51
+ /** Cuerpo e interfaz. */
52
+ sans: '"Geist", ui-sans-serif, system-ui, sans-serif',
53
+ /** Código, rutas, etiquetas, metadatos, firma CLI. */
54
+ mono: '"JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace'
55
+ };
56
+ var typeScale = {
57
+ h1: { tracking: "-0.03em" },
58
+ eyebrow: { tracking: "0.12em"}
59
+ };
60
+ var profundo = "#0D2129";
61
+ var gradient = {
62
+ dark: {
63
+ /** El de la plantilla OG de artículo. 145°, del documento. */
64
+ og: `linear-gradient(145deg, ${dark.background} 55%, ${profundo} 100%)`
65
+ },
66
+ light: {
67
+ og: `linear-gradient(145deg, ${light.background} 55%, ${light.surfaceRaised} 100%)`
68
+ }
69
+ };
70
+ var tagline = {
71
+ /** Header, una línea. */
72
+ corto: "Ayudo a equipos a escalar con criterio"};
73
+ var naming = {
74
+ wordmark: "Eduardo \xC1lvarez",
75
+ /** El dominio, para la firma CLI del footer: `$ cd ~/eduardoalvarez.dev/2026`. */
76
+ domain: "eduardoalvarez.dev"
77
+ };
78
+
79
+ // src/og/plantillas.ts
80
+ var OG = {
81
+ width: 1200,
82
+ height: 630,
83
+ margen: 64,
84
+ /** Alto máximo de la mascota. */
85
+ mascota: 430,
86
+ /** La aleta de la firma. */
87
+ aletaFirma: 34,
88
+ /**
89
+ * Hueco que deja la columna cuando la pose va a la izquierda (curso).
90
+ *
91
+ * Son 560 y no 420 porque una pose completa a 430 de alto mide ~534 de ancho:
92
+ * con el hueco justo, el tiburón se comía el titular. La reserva se mide por
93
+ * el ancho real de la pose, no por el margen.
94
+ */
95
+ reservaMascota: 560
96
+ };
97
+ function el(type, props, children) {
98
+ return { type, props: children === void 0 ? props : { ...props, children } };
99
+ }
100
+ function caja(style, children) {
101
+ return el("div", { style: { display: "flex", ...style } }, children);
102
+ }
103
+ function texto(style, contenido) {
104
+ return el("div", { style: { display: "flex", ...style } }, contenido);
105
+ }
106
+ function eyebrow(contenido, color) {
107
+ return texto(
108
+ {
109
+ fontFamily: fonts.mono,
110
+ fontSize: 20,
111
+ letterSpacing: typeScale.eyebrow.tracking,
112
+ textTransform: "uppercase",
113
+ color
114
+ },
115
+ contenido
116
+ );
117
+ }
118
+ function titular(contenido, color) {
119
+ return texto(
120
+ {
121
+ fontFamily: fonts.display,
122
+ fontSize: 58,
123
+ fontWeight: 800,
124
+ lineHeight: 1.06,
125
+ letterSpacing: typeScale.h1.tracking,
126
+ color,
127
+ // 70 % del LIENZO, no de la columna. Con la mascota en su propia columna
128
+ // el 70 % relativo daba ~400px y el titular se truncaba a media palabra.
129
+ maxWidth: OG.width * 0.7,
130
+ display: "-webkit-box",
131
+ WebkitBoxOrient: "vertical",
132
+ WebkitLineClamp: 3,
133
+ overflow: "hidden",
134
+ textOverflow: "ellipsis"
135
+ },
136
+ contenido
137
+ );
138
+ }
139
+ function firma(contenido, color, modo, base) {
140
+ return caja({ alignItems: "center", gap: 14 }, [
141
+ el("img", {
142
+ alt: "",
143
+ src: `${base}/${modo === "oscuro" ? aletas.espuma : aletas.color}`,
144
+ height: OG.aletaFirma,
145
+ style: { height: OG.aletaFirma }
146
+ }),
147
+ texto({ fontFamily: fonts.mono, fontSize: 21, color }, contenido)
148
+ ]);
149
+ }
150
+ function tarjeta(opciones) {
151
+ const columna = caja(
152
+ {
153
+ position: "relative",
154
+ flexDirection: "column",
155
+ justifyContent: "space-between",
156
+ width: "100%",
157
+ height: "100%",
158
+ padding: OG.margen,
159
+ // Cuando la pose va a la izquierda hay que dejarle el hueco: es lo único
160
+ // que cambia de la retícula, y solo lo hace la plantilla de curso.
161
+ paddingLeft: opciones.mascotaIzquierda ? OG.reservaMascota : OG.margen
162
+ },
163
+ [
164
+ eyebrow(opciones.eyebrow.texto, opciones.eyebrow.color),
165
+ caja({ flexDirection: "column", gap: 18 }, [
166
+ titular(opciones.title, opciones.tinta),
167
+ opciones.bajada ? texto(
168
+ {
169
+ fontFamily: fonts.sans,
170
+ fontSize: 26,
171
+ color: opciones.tintaSecundaria,
172
+ maxWidth: OG.width * 0.7
173
+ },
174
+ opciones.bajada
175
+ ) : null
176
+ ]),
177
+ firma(opciones.firma, opciones.firmaColor, opciones.modo, opciones.base)
178
+ ]
179
+ );
180
+ return caja(
181
+ {
182
+ position: "relative",
183
+ width: OG.width,
184
+ height: OG.height,
185
+ backgroundImage: opciones.fondo,
186
+ backgroundColor: opciones.modo === "oscuro" ? dark.background : light.background,
187
+ fontFamily: fonts.sans,
188
+ overflow: "hidden"
189
+ },
190
+ [
191
+ opciones.mascota ? caja(
192
+ {
193
+ position: "absolute",
194
+ bottom: 0,
195
+ ...opciones.mascotaIzquierda ? { left: 0 } : { right: 0 },
196
+ alignItems: "flex-end"
197
+ },
198
+ opciones.mascota
199
+ ) : null,
200
+ columna
201
+ ]
202
+ );
203
+ }
204
+ function mascotaPose(pose, base, alto = OG.mascota) {
205
+ return el("img", { alt: "", src: `${base}/${poses[pose]}`, height: alto, style: { height: alto } });
206
+ }
207
+ function plantillaArticulo(datos) {
208
+ const base = datos.basePath ?? RUTA_ASSETS;
209
+ const cara = datos.expresion ?? "hearts";
210
+ const meta = [datos.category, datos.readingMinutes ? `${datos.readingMinutes} min` : void 0].filter(Boolean).join(" \xB7 ");
211
+ return tarjeta({
212
+ modo: "oscuro",
213
+ fondo: gradient.dark.og,
214
+ eyebrow: { texto: meta || "art\xEDculo", color: dark.warm },
215
+ title: datos.title,
216
+ firma: naming.domain,
217
+ firmaColor: dark.textSecondary,
218
+ base,
219
+ tinta: dark.textPrimary,
220
+ tintaSecundaria: dark.textSecondary,
221
+ mascota: el("img", {
222
+ alt: "",
223
+ src: `${base}/${caras[cara]}`,
224
+ height: 300,
225
+ style: { height: 300, marginBottom: OG.margen, marginRight: OG.margen }
226
+ })
227
+ });
228
+ }
229
+ function plantillaCurso(datos) {
230
+ const base = datos.basePath ?? RUTA_ASSETS;
231
+ const meta = [datos.modules ? `${datos.modules} m\xF3dulos` : void 0, datos.duration].filter(Boolean).join(" \xB7 ");
232
+ return tarjeta({
233
+ modo: "claro",
234
+ fondo: gradient.light.og,
235
+ eyebrow: { texto: "nuevo curso", color: light.warm },
236
+ title: datos.title,
237
+ bajada: meta || void 0,
238
+ firma: datos.url ?? `cursos.${naming.domain}`,
239
+ firmaColor: light.warm,
240
+ base,
241
+ tinta: light.textPrimary,
242
+ tintaSecundaria: light.textSecondary,
243
+ mascotaIzquierda: true,
244
+ mascota: mascotaPose(datos.pose ?? "desk", base)
245
+ });
246
+ }
247
+ function plantillaCharla(datos) {
248
+ const base = datos.basePath ?? RUTA_ASSETS;
249
+ const cabeza = ["charla", datos.event, datos.year].filter(Boolean).join(" \xB7 ");
250
+ const pie = [datos.location, datos.event ? void 0 : naming.domain].filter(Boolean).join(" \xB7 ");
251
+ return tarjeta({
252
+ modo: "oscuro",
253
+ fondo: "none",
254
+ eyebrow: { texto: cabeza, color: dark.accent },
255
+ title: datos.title,
256
+ bajada: datos.summary,
257
+ firma: pie || naming.domain,
258
+ firmaColor: dark.textSecondary,
259
+ base,
260
+ tinta: dark.textPrimary,
261
+ tintaSecundaria: dark.textSecondary,
262
+ // Sangra: se sale por abajo y por la derecha, como la portada de slide.
263
+ mascota: el("img", {
264
+ alt: "",
265
+ src: `${base}/${poses[datos.pose ?? "surf"]}`,
266
+ height: 470,
267
+ style: { height: 470, marginBottom: -46, marginRight: -36 }
268
+ })
269
+ });
270
+ }
271
+ function plantillaDefecto(datos = {}) {
272
+ const base = datos.basePath ?? RUTA_ASSETS;
273
+ return caja(
274
+ {
275
+ width: OG.width,
276
+ height: OG.height,
277
+ alignItems: "center",
278
+ backgroundColor: dark.background,
279
+ fontFamily: fonts.sans
280
+ },
281
+ [
282
+ // Columna de la aleta: 290 de ancho, que es donde cae el divisor.
283
+ caja(
284
+ { width: 290, height: "100%", alignItems: "center", justifyContent: "center" },
285
+ // El halo. Un círculo de bioluz al 12 % detrás de la aleta.
286
+ caja(
287
+ {
288
+ width: 260,
289
+ height: 260,
290
+ borderRadius: 999,
291
+ backgroundColor: `${dark.accent}1F`,
292
+ alignItems: "center",
293
+ justifyContent: "center"
294
+ },
295
+ el("img", { alt: "", src: `${base}/${aletas.espuma}`, height: 200, style: { height: 200 } })
296
+ )
297
+ ),
298
+ // El divisor, exactamente en x=290.
299
+ caja({ width: 1, height: 340, backgroundColor: dark.border }),
300
+ caja(
301
+ { flexDirection: "column", gap: 20, paddingLeft: OG.margen, paddingRight: OG.margen, flex: 1 },
302
+ [
303
+ texto(
304
+ { fontFamily: fonts.sans, fontSize: 72, fontWeight: 700, color: dark.textPrimary, lineHeight: 1.05 },
305
+ datos.title ?? naming.wordmark
306
+ ),
307
+ texto(
308
+ { fontFamily: fonts.sans, fontSize: 28, fontWeight: 400, color: dark.textSecondary, lineHeight: 1.4 },
309
+ datos.description ?? tagline.corto
310
+ )
311
+ ]
312
+ )
313
+ ]
314
+ );
315
+ }
316
+
317
+ exports.OG = OG;
318
+ exports.plantillaArticulo = plantillaArticulo;
319
+ exports.plantillaBase = tarjeta;
320
+ exports.plantillaCharla = plantillaCharla;
321
+ exports.plantillaCurso = plantillaCurso;
322
+ exports.plantillaDefecto = plantillaDefecto;
@@ -0,0 +1,164 @@
1
+ import { C as Cara, P as Pose } from '../catalogo-Du5ID-Hi.cjs';
2
+
3
+ /**
4
+ * Las cuatro plantillas de Open Graph, 1200×630.
5
+ *
6
+ * NO son componentes de React. Devuelven el árbol de elementos que Satori
7
+ * espera —`{ type, props }`— construido solo con tokens y con el catálogo de la
8
+ * marca, que es dato puro. Este módulo se puede importar desde un worker, un
9
+ * script de build o una función de borde sin montar nada.
10
+ *
11
+ * Es el caso que justifica la restricción de `src/tokens/`: si un token hubiera
12
+ * terminado dependiendo de un componente, este archivo no existiría.
13
+ *
14
+ * LA RETÍCULA ES UNA, del documento: eyebrow arriba, titular a la izquierda,
15
+ * firma abajo y la mascota anclada a la derecha. Lo único que cambia entre
16
+ * plantillas es el fondo y qué pose entra. Por eso no hay cuatro maquetaciones
17
+ * aquí: hay una, y tres parámetros.
18
+ *
19
+ * lienzo 1200×630 · margen 64
20
+ * eyebrow mono 20 · tracking 0.12em
21
+ * titular display 800 · 58 · máx 3 líneas al 70 % del ancho
22
+ * firma mono 21 · con la aleta a 34
23
+ * mascota anclada a la derecha · alto máx 430
24
+ *
25
+ * La de por defecto es la excepción declarada: retícula propia de dos columnas
26
+ * con la aleta a 200 y divisor en x=290, y Geist 72/700 y 28/400 en vez de la
27
+ * display. Está en el documento como excepción, así que está aquí como función
28
+ * aparte y no como un `if` dentro de la común.
29
+ *
30
+ * LA ALETA NO ES UN PARÁMETRO. El documento avisa de que «es el error más fácil
31
+ * de cometer en un generador, porque el fondo es un parámetro»: espuma en las
32
+ * tres plantillas oscuras, dos azules en la de curso. Aquí la elige el modo de
33
+ * la plantilla, igual que `Isotipo` la elige con `sobre`. No hay forma de pedir
34
+ * la combinación mala.
35
+ *
36
+ * El titular se trunca a 3 líneas con elipsis y NUNCA baja de tamaño: un titular
37
+ * a 44 y otro a 58 en el mismo feed se ven como dos marcas distintas.
38
+ *
39
+ * Las imágenes llevan `alt=''`. Satori no lo necesita —pinta a SVG—, pero la
40
+ * mascota y la aleta SON decorativas aquí: lo que la tarjeta dice es el titular,
41
+ * y el `alt` de una OG lo pone el `<meta property="og:image:alt">` de la página.
42
+ * Además, la story las monta como `<img>` de verdad y sin `alt` no pasan axe.
43
+ *
44
+ * Uso:
45
+ *
46
+ * import satori from 'satori';
47
+ * import { plantillaArticulo, OG } from '@eduardoalvarez/arrecife/og';
48
+ *
49
+ * const svg = await satori(plantillaArticulo({ title, category, readingMinutes }), {
50
+ * width: OG.width,
51
+ * height: OG.height,
52
+ * fonts: [...],
53
+ * });
54
+ */
55
+
56
+ type NodoSatori = {
57
+ type: string;
58
+ props: Record<string, unknown> & {
59
+ style?: Record<string, string | number>;
60
+ children?: Hijo | readonly Hijo[];
61
+ };
62
+ };
63
+ type Hijo = NodoSatori | string | null | undefined | false;
64
+ /** El lienzo y la retícula. Son las medidas de producción. */
65
+ declare const OG: {
66
+ readonly width: 1200;
67
+ readonly height: 630;
68
+ readonly margen: 64;
69
+ /** Alto máximo de la mascota. */
70
+ readonly mascota: 430;
71
+ /** La aleta de la firma. */
72
+ readonly aletaFirma: 34;
73
+ /**
74
+ * Hueco que deja la columna cuando la pose va a la izquierda (curso).
75
+ *
76
+ * Son 560 y no 420 porque una pose completa a 430 de alto mide ~534 de ancho:
77
+ * con el hueco justo, el tiburón se comía el titular. La reserva se mide por
78
+ * el ancho real de la pose, no por el margen.
79
+ */
80
+ readonly reservaMascota: 560;
81
+ };
82
+ type DatosBase = {
83
+ title: string;
84
+ /**
85
+ * Dónde se sirven los PNG. Satori no lee del disco: en un worker esto tiene
86
+ * que ser una URL absoluta o un `data:` URI.
87
+ */
88
+ basePath?: string | undefined;
89
+ };
90
+ /**
91
+ * La retícula compartida. Tres de las cuatro plantillas son esto con otro fondo
92
+ * y otra pieza de mascota.
93
+ */
94
+ declare function tarjeta(opciones: {
95
+ modo: 'oscuro' | 'claro';
96
+ fondo: string;
97
+ eyebrow: {
98
+ texto: string;
99
+ color: string;
100
+ };
101
+ title: string;
102
+ /** Línea bajo el titular, opcional. */
103
+ bajada?: string | undefined;
104
+ firma: string;
105
+ firmaColor: string;
106
+ base: string;
107
+ /** La pieza de la derecha, ya montada. */
108
+ mascota?: NodoSatori | null | undefined;
109
+ /** `curso` la pone a la izquierda: es la única que invierte la retícula. */
110
+ mascotaIzquierda?: boolean | undefined;
111
+ tinta: string;
112
+ tintaSecundaria: string;
113
+ }): NodoSatori;
114
+ type DatosArticulo = DatosBase & {
115
+ /** El slug de la categoría. Va en arena, en versalitas. */
116
+ category?: string | undefined;
117
+ readingMinutes?: number | undefined;
118
+ /**
119
+ * La cara, «según el tono del texto». No hay defecto pensado como neutro:
120
+ * `hearts` es la que el manual asigna al OG de artículo.
121
+ */
122
+ expresion?: Cara | undefined;
123
+ };
124
+ /** Artículo · degradado 145° sobre abismo, categoría y lectura en arena. */
125
+ declare function plantillaArticulo(datos: DatosArticulo): NodoSatori;
126
+ type DatosCurso = DatosBase & {
127
+ modules?: number | undefined;
128
+ duration?: string | undefined;
129
+ /** El dominio del pie. Por defecto, el subdominio de cursos. */
130
+ url?: string | undefined;
131
+ pose?: Pose | undefined;
132
+ };
133
+ /**
134
+ * Curso · LA ÚNICA PLANTILLA EN CLARO.
135
+ *
136
+ * Pose completa a la izquierda —invierte la retícula—, eyebrow y URL en arena
137
+ * oscura, y por tanto la aleta a dos azules. Esa última parte no se decide aquí:
138
+ * sale de que el modo es claro.
139
+ */
140
+ declare function plantillaCurso(datos: DatosCurso): NodoSatori;
141
+ type DatosCharla = DatosBase & {
142
+ event?: string | undefined;
143
+ year?: string | number | undefined;
144
+ location?: string | undefined;
145
+ summary?: string | undefined;
146
+ pose?: Pose | undefined;
147
+ };
148
+ /** Charla · eyebrow en bioluz con evento y año, pose sangrando por la esquina. */
149
+ declare function plantillaCharla(datos: DatosCharla): NodoSatori;
150
+ type DatosDefecto = Omit<DatosBase, 'title'> & {
151
+ /** El nombre. Por defecto, el wordmark. */
152
+ title?: string | undefined;
153
+ description?: string | undefined;
154
+ };
155
+ /**
156
+ * Por defecto · la excepción declarada del documento.
157
+ *
158
+ * Retícula propia de dos columnas: la aleta en bioluz a 200 con halo, divisor en
159
+ * x=290 y Geist 72/700 y 28/400 — NO la display. No comparte casco con las otras
160
+ * tres porque el documento dice que la define el generador, no la tabla.
161
+ */
162
+ declare function plantillaDefecto(datos?: DatosDefecto): NodoSatori;
163
+
164
+ export { type DatosArticulo, type DatosBase, type DatosCharla, type DatosCurso, type DatosDefecto, type NodoSatori, OG, plantillaArticulo, tarjeta as plantillaBase, plantillaCharla, plantillaCurso, plantillaDefecto };
@@ -0,0 +1,164 @@
1
+ import { C as Cara, P as Pose } from '../catalogo-Du5ID-Hi.js';
2
+
3
+ /**
4
+ * Las cuatro plantillas de Open Graph, 1200×630.
5
+ *
6
+ * NO son componentes de React. Devuelven el árbol de elementos que Satori
7
+ * espera —`{ type, props }`— construido solo con tokens y con el catálogo de la
8
+ * marca, que es dato puro. Este módulo se puede importar desde un worker, un
9
+ * script de build o una función de borde sin montar nada.
10
+ *
11
+ * Es el caso que justifica la restricción de `src/tokens/`: si un token hubiera
12
+ * terminado dependiendo de un componente, este archivo no existiría.
13
+ *
14
+ * LA RETÍCULA ES UNA, del documento: eyebrow arriba, titular a la izquierda,
15
+ * firma abajo y la mascota anclada a la derecha. Lo único que cambia entre
16
+ * plantillas es el fondo y qué pose entra. Por eso no hay cuatro maquetaciones
17
+ * aquí: hay una, y tres parámetros.
18
+ *
19
+ * lienzo 1200×630 · margen 64
20
+ * eyebrow mono 20 · tracking 0.12em
21
+ * titular display 800 · 58 · máx 3 líneas al 70 % del ancho
22
+ * firma mono 21 · con la aleta a 34
23
+ * mascota anclada a la derecha · alto máx 430
24
+ *
25
+ * La de por defecto es la excepción declarada: retícula propia de dos columnas
26
+ * con la aleta a 200 y divisor en x=290, y Geist 72/700 y 28/400 en vez de la
27
+ * display. Está en el documento como excepción, así que está aquí como función
28
+ * aparte y no como un `if` dentro de la común.
29
+ *
30
+ * LA ALETA NO ES UN PARÁMETRO. El documento avisa de que «es el error más fácil
31
+ * de cometer en un generador, porque el fondo es un parámetro»: espuma en las
32
+ * tres plantillas oscuras, dos azules en la de curso. Aquí la elige el modo de
33
+ * la plantilla, igual que `Isotipo` la elige con `sobre`. No hay forma de pedir
34
+ * la combinación mala.
35
+ *
36
+ * El titular se trunca a 3 líneas con elipsis y NUNCA baja de tamaño: un titular
37
+ * a 44 y otro a 58 en el mismo feed se ven como dos marcas distintas.
38
+ *
39
+ * Las imágenes llevan `alt=''`. Satori no lo necesita —pinta a SVG—, pero la
40
+ * mascota y la aleta SON decorativas aquí: lo que la tarjeta dice es el titular,
41
+ * y el `alt` de una OG lo pone el `<meta property="og:image:alt">` de la página.
42
+ * Además, la story las monta como `<img>` de verdad y sin `alt` no pasan axe.
43
+ *
44
+ * Uso:
45
+ *
46
+ * import satori from 'satori';
47
+ * import { plantillaArticulo, OG } from '@eduardoalvarez/arrecife/og';
48
+ *
49
+ * const svg = await satori(plantillaArticulo({ title, category, readingMinutes }), {
50
+ * width: OG.width,
51
+ * height: OG.height,
52
+ * fonts: [...],
53
+ * });
54
+ */
55
+
56
+ type NodoSatori = {
57
+ type: string;
58
+ props: Record<string, unknown> & {
59
+ style?: Record<string, string | number>;
60
+ children?: Hijo | readonly Hijo[];
61
+ };
62
+ };
63
+ type Hijo = NodoSatori | string | null | undefined | false;
64
+ /** El lienzo y la retícula. Son las medidas de producción. */
65
+ declare const OG: {
66
+ readonly width: 1200;
67
+ readonly height: 630;
68
+ readonly margen: 64;
69
+ /** Alto máximo de la mascota. */
70
+ readonly mascota: 430;
71
+ /** La aleta de la firma. */
72
+ readonly aletaFirma: 34;
73
+ /**
74
+ * Hueco que deja la columna cuando la pose va a la izquierda (curso).
75
+ *
76
+ * Son 560 y no 420 porque una pose completa a 430 de alto mide ~534 de ancho:
77
+ * con el hueco justo, el tiburón se comía el titular. La reserva se mide por
78
+ * el ancho real de la pose, no por el margen.
79
+ */
80
+ readonly reservaMascota: 560;
81
+ };
82
+ type DatosBase = {
83
+ title: string;
84
+ /**
85
+ * Dónde se sirven los PNG. Satori no lee del disco: en un worker esto tiene
86
+ * que ser una URL absoluta o un `data:` URI.
87
+ */
88
+ basePath?: string | undefined;
89
+ };
90
+ /**
91
+ * La retícula compartida. Tres de las cuatro plantillas son esto con otro fondo
92
+ * y otra pieza de mascota.
93
+ */
94
+ declare function tarjeta(opciones: {
95
+ modo: 'oscuro' | 'claro';
96
+ fondo: string;
97
+ eyebrow: {
98
+ texto: string;
99
+ color: string;
100
+ };
101
+ title: string;
102
+ /** Línea bajo el titular, opcional. */
103
+ bajada?: string | undefined;
104
+ firma: string;
105
+ firmaColor: string;
106
+ base: string;
107
+ /** La pieza de la derecha, ya montada. */
108
+ mascota?: NodoSatori | null | undefined;
109
+ /** `curso` la pone a la izquierda: es la única que invierte la retícula. */
110
+ mascotaIzquierda?: boolean | undefined;
111
+ tinta: string;
112
+ tintaSecundaria: string;
113
+ }): NodoSatori;
114
+ type DatosArticulo = DatosBase & {
115
+ /** El slug de la categoría. Va en arena, en versalitas. */
116
+ category?: string | undefined;
117
+ readingMinutes?: number | undefined;
118
+ /**
119
+ * La cara, «según el tono del texto». No hay defecto pensado como neutro:
120
+ * `hearts` es la que el manual asigna al OG de artículo.
121
+ */
122
+ expresion?: Cara | undefined;
123
+ };
124
+ /** Artículo · degradado 145° sobre abismo, categoría y lectura en arena. */
125
+ declare function plantillaArticulo(datos: DatosArticulo): NodoSatori;
126
+ type DatosCurso = DatosBase & {
127
+ modules?: number | undefined;
128
+ duration?: string | undefined;
129
+ /** El dominio del pie. Por defecto, el subdominio de cursos. */
130
+ url?: string | undefined;
131
+ pose?: Pose | undefined;
132
+ };
133
+ /**
134
+ * Curso · LA ÚNICA PLANTILLA EN CLARO.
135
+ *
136
+ * Pose completa a la izquierda —invierte la retícula—, eyebrow y URL en arena
137
+ * oscura, y por tanto la aleta a dos azules. Esa última parte no se decide aquí:
138
+ * sale de que el modo es claro.
139
+ */
140
+ declare function plantillaCurso(datos: DatosCurso): NodoSatori;
141
+ type DatosCharla = DatosBase & {
142
+ event?: string | undefined;
143
+ year?: string | number | undefined;
144
+ location?: string | undefined;
145
+ summary?: string | undefined;
146
+ pose?: Pose | undefined;
147
+ };
148
+ /** Charla · eyebrow en bioluz con evento y año, pose sangrando por la esquina. */
149
+ declare function plantillaCharla(datos: DatosCharla): NodoSatori;
150
+ type DatosDefecto = Omit<DatosBase, 'title'> & {
151
+ /** El nombre. Por defecto, el wordmark. */
152
+ title?: string | undefined;
153
+ description?: string | undefined;
154
+ };
155
+ /**
156
+ * Por defecto · la excepción declarada del documento.
157
+ *
158
+ * Retícula propia de dos columnas: la aleta en bioluz a 200 con halo, divisor en
159
+ * x=290 y Geist 72/700 y 28/400 — NO la display. No comparte casco con las otras
160
+ * tres porque el documento dice que la define el generador, no la tabla.
161
+ */
162
+ declare function plantillaDefecto(datos?: DatosDefecto): NodoSatori;
163
+
164
+ export { type DatosArticulo, type DatosBase, type DatosCharla, type DatosCurso, type DatosDefecto, type NodoSatori, OG, plantillaArticulo, tarjeta as plantillaBase, plantillaCharla, plantillaCurso, plantillaDefecto };