@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.
- package/CHANGELOG.md +55 -0
- package/LICENSE +21 -0
- package/README.md +520 -0
- package/assets/brand/face-annoyed.png +0 -0
- package/assets/brand/face-confused.png +0 -0
- package/assets/brand/face-hearts.png +0 -0
- package/assets/brand/face-laughing.png +0 -0
- package/assets/brand/face-shades.png +0 -0
- package/assets/brand/face-waiting.png +0 -0
- package/assets/brand/face-wink.png +0 -0
- package/assets/brand/fin-foam.png +0 -0
- package/assets/brand/fin.png +0 -0
- package/assets/brand/pose-desk.png +0 -0
- package/assets/brand/pose-laptop-coffee.png +0 -0
- package/assets/brand/pose-peek.png +0 -0
- package/assets/brand/pose-surf.png +0 -0
- package/dist/brand/index.cjs +228 -0
- package/dist/brand/index.d.cts +63 -0
- package/dist/brand/index.d.ts +63 -0
- package/dist/brand/index.js +3 -0
- package/dist/catalogo-Du5ID-Hi.d.cts +77 -0
- package/dist/catalogo-Du5ID-Hi.d.ts +77 -0
- package/dist/chunk-22OXRTAL.js +22 -0
- package/dist/chunk-DHF63QNM.js +231 -0
- package/dist/chunk-E3OMP2DL.js +36 -0
- package/dist/chunk-ZL5X6XO7.js +102 -0
- package/dist/index.cjs +3191 -0
- package/dist/index.d.cts +1024 -0
- package/dist/index.d.ts +1024 -0
- package/dist/index.js +2624 -0
- package/dist/og/index.cjs +322 -0
- package/dist/og/index.d.cts +164 -0
- package/dist/og/index.d.ts +164 -0
- package/dist/og/index.js +242 -0
- package/dist/shiki/index.cjs +127 -0
- package/dist/shiki/index.d.cts +16 -0
- package/dist/shiki/index.d.ts +16 -0
- package/dist/shiki/index.js +95 -0
- package/dist/tokens/index.cjs +263 -0
- package/dist/tokens/index.d.cts +623 -0
- package/dist/tokens/index.d.ts +623 -0
- package/dist/tokens/index.js +2 -0
- package/dist/tokens/theme.css +266 -0
- 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 };
|