@jjlmoya/utils-audiovisual 1.2.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/package.json +60 -0
- package/src/category/i18n/en.ts +198 -0
- package/src/category/i18n/es.ts +198 -0
- package/src/category/i18n/fr.ts +198 -0
- package/src/category/index.ts +17 -0
- package/src/category/seo.astro +15 -0
- package/src/components/PreviewNavSidebar.astro +116 -0
- package/src/components/PreviewToolbar.astro +143 -0
- package/src/data.ts +4 -0
- package/src/env.d.ts +5 -0
- package/src/index.ts +32 -0
- package/src/layouts/PreviewLayout.astro +117 -0
- package/src/pages/[locale]/[slug].astro +146 -0
- package/src/pages/[locale].astro +251 -0
- package/src/pages/index.astro +4 -0
- package/src/tests/faq_count.test.ts +19 -0
- package/src/tests/locale_completeness.test.ts +42 -0
- package/src/tests/mocks/astro_mock.js +2 -0
- package/src/tests/no_h1_in_components.test.ts +48 -0
- package/src/tests/seo_length.test.ts +22 -0
- package/src/tests/tool_validation.test.ts +17 -0
- package/src/tool/chromaticLens/bibliography.astro +17 -0
- package/src/tool/chromaticLens/component.astro +178 -0
- package/src/tool/chromaticLens/i18n/en.ts +246 -0
- package/src/tool/chromaticLens/i18n/es.ts +244 -0
- package/src/tool/chromaticLens/i18n/fr.ts +244 -0
- package/src/tool/chromaticLens/index.ts +43 -0
- package/src/tool/chromaticLens/logic.ts +87 -0
- package/src/tool/chromaticLens/seo.astro +15 -0
- package/src/tool/chromaticLens/style.css +308 -0
- package/src/tool/chromaticLens/ui.ts +109 -0
- package/src/tool/collageMaker/bibliography.astro +17 -0
- package/src/tool/collageMaker/component.astro +302 -0
- package/src/tool/collageMaker/i18n/en.ts +233 -0
- package/src/tool/collageMaker/i18n/es.ts +231 -0
- package/src/tool/collageMaker/i18n/fr.ts +231 -0
- package/src/tool/collageMaker/index.ts +51 -0
- package/src/tool/collageMaker/logic.ts +134 -0
- package/src/tool/collageMaker/seo.astro +15 -0
- package/src/tool/collageMaker/style.css +386 -0
- package/src/tool/exifCleaner/bibliography.astro +18 -0
- package/src/tool/exifCleaner/component.astro +162 -0
- package/src/tool/exifCleaner/i18n/en.ts +277 -0
- package/src/tool/exifCleaner/i18n/es.ts +277 -0
- package/src/tool/exifCleaner/i18n/fr.ts +277 -0
- package/src/tool/exifCleaner/index.ts +57 -0
- package/src/tool/exifCleaner/logic.ts +135 -0
- package/src/tool/exifCleaner/seo.astro +18 -0
- package/src/tool/exifCleaner/style.css +289 -0
- package/src/tool/exifCleaner/ui.ts +117 -0
- package/src/tool/imageCompressor/bibliography.astro +17 -0
- package/src/tool/imageCompressor/component.astro +262 -0
- package/src/tool/imageCompressor/i18n/en.ts +232 -0
- package/src/tool/imageCompressor/i18n/es.ts +230 -0
- package/src/tool/imageCompressor/i18n/fr.ts +230 -0
- package/src/tool/imageCompressor/index.ts +50 -0
- package/src/tool/imageCompressor/logic.ts +79 -0
- package/src/tool/imageCompressor/seo.astro +15 -0
- package/src/tool/imageCompressor/style.css +503 -0
- package/src/tool/printQualityCalculator/bibliography.astro +18 -0
- package/src/tool/printQualityCalculator/component.astro +318 -0
- package/src/tool/printQualityCalculator/i18n/en.ts +247 -0
- package/src/tool/printQualityCalculator/i18n/es.ts +245 -0
- package/src/tool/printQualityCalculator/i18n/fr.ts +245 -0
- package/src/tool/printQualityCalculator/index.ts +56 -0
- package/src/tool/printQualityCalculator/logic.ts +53 -0
- package/src/tool/printQualityCalculator/seo.astro +18 -0
- package/src/tool/printQualityCalculator/style.css +491 -0
- package/src/tool/printQualityCalculator/ui.ts +122 -0
- package/src/tool/privacyBlur/bibliography.astro +17 -0
- package/src/tool/privacyBlur/component.astro +230 -0
- package/src/tool/privacyBlur/i18n/en.ts +238 -0
- package/src/tool/privacyBlur/i18n/es.ts +236 -0
- package/src/tool/privacyBlur/i18n/fr.ts +236 -0
- package/src/tool/privacyBlur/index.ts +49 -0
- package/src/tool/privacyBlur/logic.ts +249 -0
- package/src/tool/privacyBlur/seo.astro +15 -0
- package/src/tool/privacyBlur/style.css +332 -0
- package/src/tool/privacyBlur/ui.ts +124 -0
- package/src/tool/subtitleSync/bibliography.astro +17 -0
- package/src/tool/subtitleSync/component.astro +187 -0
- package/src/tool/subtitleSync/i18n/en.ts +241 -0
- package/src/tool/subtitleSync/i18n/es.ts +241 -0
- package/src/tool/subtitleSync/i18n/fr.ts +241 -0
- package/src/tool/subtitleSync/index.ts +49 -0
- package/src/tool/subtitleSync/logic.ts +91 -0
- package/src/tool/subtitleSync/seo.astro +15 -0
- package/src/tool/subtitleSync/style.css +325 -0
- package/src/tool/subtitleSync/ui.ts +152 -0
- package/src/tool/timelapseCalculator/bibliography.astro +15 -0
- package/src/tool/timelapseCalculator/component.astro +148 -0
- package/src/tool/timelapseCalculator/i18n/en.ts +169 -0
- package/src/tool/timelapseCalculator/i18n/es.ts +169 -0
- package/src/tool/timelapseCalculator/i18n/fr.ts +169 -0
- package/src/tool/timelapseCalculator/index.ts +52 -0
- package/src/tool/timelapseCalculator/logic.ts +46 -0
- package/src/tool/timelapseCalculator/seo.astro +18 -0
- package/src/tool/timelapseCalculator/style.css +285 -0
- package/src/tool/tvDistance/bibliography.astro +17 -0
- package/src/tool/tvDistance/component.astro +178 -0
- package/src/tool/tvDistance/i18n/en.ts +223 -0
- package/src/tool/tvDistance/i18n/es.ts +223 -0
- package/src/tool/tvDistance/i18n/fr.ts +223 -0
- package/src/tool/tvDistance/index.ts +49 -0
- package/src/tool/tvDistance/logic.ts +47 -0
- package/src/tool/tvDistance/seo.astro +15 -0
- package/src/tool/tvDistance/style.css +435 -0
- package/src/tool/tvDistance/ui.ts +66 -0
- package/src/tool/videoFrameExtractor/bibliography.astro +17 -0
- package/src/tool/videoFrameExtractor/component.astro +285 -0
- package/src/tool/videoFrameExtractor/i18n/en.ts +235 -0
- package/src/tool/videoFrameExtractor/i18n/es.ts +235 -0
- package/src/tool/videoFrameExtractor/i18n/fr.ts +235 -0
- package/src/tool/videoFrameExtractor/index.ts +53 -0
- package/src/tool/videoFrameExtractor/logic.ts +49 -0
- package/src/tool/videoFrameExtractor/seo.astro +15 -0
- package/src/tool/videoFrameExtractor/style.css +426 -0
- package/src/tool/videoFrameExtractor/ui.ts +179 -0
- package/src/tools.ts +25 -0
- package/src/types.ts +72 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
|
+
import type { CollageMakerUI, CollageMakerLocaleContent } from '../index';
|
|
3
|
+
|
|
4
|
+
const slug = 'creador-collage-fotos';
|
|
5
|
+
const title = 'Creador de Collages Online - Diseña composiciones profesionales';
|
|
6
|
+
const description = 'Crea collages de fotos gratis en segundos. Elige entre múltiples diseños, ajusta bordes y descarga en alta calidad sin marcas de agua.';
|
|
7
|
+
|
|
8
|
+
const ui: CollageMakerUI = {
|
|
9
|
+
dropTitle: "Arrastra imágenes aquí",
|
|
10
|
+
dropSub: "o {link} · mín. 2, máx. 9",
|
|
11
|
+
dropLink: "selecciona archivos",
|
|
12
|
+
imgsLoaded: "Imágenes cargadas",
|
|
13
|
+
layoutLabel: "Diseño",
|
|
14
|
+
settingsLabel: "Ajustes",
|
|
15
|
+
borderLabel: "Borde",
|
|
16
|
+
borderColorLabel: "Color borde",
|
|
17
|
+
bgColorLabel: "Fondo",
|
|
18
|
+
downloadBtn: "Descargar",
|
|
19
|
+
previewTitle: "Vista previa",
|
|
20
|
+
needsImgs: "Necesitas {n} imágenes",
|
|
21
|
+
errorMin: "Necesitas al menos 2 imágenes",
|
|
22
|
+
errorMax: "Máximo 9 imágenes permitidas",
|
|
23
|
+
errorLoad: "Error al cargar las imágenes",
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const faq: CollageMakerLocaleContent['faq'] = [
|
|
27
|
+
{
|
|
28
|
+
question: "¿Cómo puedo cambiar el orden de las fotos?",
|
|
29
|
+
answer: "Las fotos se colocan en el collage siguiendo el orden en que aparecen en la lista de imágenes cargadas. Puedes eliminar una y volver a subirla para ajustar su posición.",
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
question: "¿Qué formato de salida tiene el collage?",
|
|
33
|
+
answer: "El collage se descarga en formato WebP de alta resolución, ideal para compartir en redes sociales sin perder nitidez.",
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const howTo: CollageMakerLocaleContent['howTo'] = [
|
|
38
|
+
{
|
|
39
|
+
name: "Sube tus fotos",
|
|
40
|
+
text: "Selecciona entre 2 y 9 imágenes desde tu explorador de archivos.",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: "Elige un diseño",
|
|
44
|
+
text: "Selecciona la cuadrícula que mejor se adapte al número de fotos que has subido.",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: "Personaliza el estilo",
|
|
48
|
+
text: "Ajusta el grosor y color del borde para darle un acabado profesional.",
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: "Descarga y comparte",
|
|
52
|
+
text: "Pulsa el botón de crear y descarga tu composición final al instante.",
|
|
53
|
+
},
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
const bibliography: CollageMakerLocaleContent['bibliography'] = [
|
|
57
|
+
{
|
|
58
|
+
name: "Composición Fotográfica: El Arte del Collage",
|
|
59
|
+
url: "https://es.wikipedia.org/wiki/Collage",
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
const seo: CollageMakerLocaleContent['seo'] = [
|
|
64
|
+
{
|
|
65
|
+
type: 'summary',
|
|
66
|
+
title: 'Creador de Collages Profesional Online',
|
|
67
|
+
items: [
|
|
68
|
+
'Múltiples diseños y cuadrículas predefinidas',
|
|
69
|
+
'Personalización de bordes y colores de fondo',
|
|
70
|
+
'Alta resolución 1200px lista para redes sociales',
|
|
71
|
+
'Procesamiento instantáneo sin límites de uso'
|
|
72
|
+
]
|
|
73
|
+
},
|
|
74
|
+
{ type: 'title', text: 'Diseño de Collages: Composiciones que Cuentan Historias', level: 2 },
|
|
75
|
+
{ type: 'paragraph', html: 'Un collage es más que una mezcla de fotos; es una narrativa visual que comunica emoción y contexto. Nuestra herramienta permite crear composiciones geométricas profesionales para Instagram, Facebook, Pinterest o proyectos personales sin necesidad de Photoshop o software costoso.' },
|
|
76
|
+
|
|
77
|
+
{ type: 'stats', items: [
|
|
78
|
+
{ value: '2-9', label: 'Imágenes por Collage', icon: 'mdi:image-multiple' },
|
|
79
|
+
{ value: '1200px', label: 'Resolución HD', icon: 'mdi:video-high-definition' },
|
|
80
|
+
{ value: 'Instant', label: 'Generación', icon: 'mdi:lightning-bolt' }
|
|
81
|
+
], columns: 3 },
|
|
82
|
+
|
|
83
|
+
{ type: 'title', text: 'Composición Visual: Principios de Diseño', level: 3 },
|
|
84
|
+
{ type: 'paragraph', html: 'La composición es el arte de organizar elementos visuales de forma que guíen la vista del espectador y comuniquen intención. Un buen collage equilibra:' },
|
|
85
|
+
{ type: 'list', items: [
|
|
86
|
+
'<strong>Balance:</strong> Distribución visual de peso (imágenes claras vs oscuras, grandes vs pequeñas).',
|
|
87
|
+
'<strong>Flujo Visual:</strong> El ojo debe recorrer la composición de forma natural, sin puntos muertos.',
|
|
88
|
+
'<strong>Contraste:</strong> Variaciones en color, tamaño y forma que atrapan la atención.',
|
|
89
|
+
'<strong>Unidad:</strong> Coherencia temática - las imágenes deben "hablar juntas" sobre la misma historia.'
|
|
90
|
+
], icon: 'mdi:check' },
|
|
91
|
+
|
|
92
|
+
{ type: 'card', title: 'Diseños Inteligentes y Adaptativos', html: 'Nuestro sistema calcula automáticamente la distribución óptima de espacio según el número de fotos. 2 imágenes = cuadrícula simétrica; 5 imágenes = distribución asimétrica equilibrada. Cada cuadrícula está diseñada para maximizar impacto visual.' },
|
|
93
|
+
|
|
94
|
+
{ type: 'comparative', items: [
|
|
95
|
+
{
|
|
96
|
+
title: 'Para Redes Sociales',
|
|
97
|
+
description: 'Instagram, TikTok, Facebook - formatos cuadrados',
|
|
98
|
+
icon: 'mdi:share-all',
|
|
99
|
+
points: [
|
|
100
|
+
'1200px es perfecto para feed de Instagram',
|
|
101
|
+
'Formato cuadrado evita recortes al publicar',
|
|
102
|
+
'Bordes personalizables para marca'
|
|
103
|
+
],
|
|
104
|
+
highlight: true
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
title: 'Para Portafolios',
|
|
108
|
+
description: 'Vitrinas visuales de fotógrafos y diseñadores',
|
|
109
|
+
icon: 'mdi:briefcase-outline',
|
|
110
|
+
points: [
|
|
111
|
+
'Muestra múltiples ángulos de un proyecto',
|
|
112
|
+
'Narrativa visual coherente',
|
|
113
|
+
'Impresión profesional en libro digital'
|
|
114
|
+
]
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
title: 'Para Regalos Personales',
|
|
118
|
+
description: 'Impresos, marcos y álbumes digitales',
|
|
119
|
+
icon: 'mdi:gift-outline',
|
|
120
|
+
points: [
|
|
121
|
+
'Recuerdos de eventos (bodas, viajes)',
|
|
122
|
+
'Alta resolución lista para imprimir',
|
|
123
|
+
'Diseño instantáneo sin esfuerzo manual'
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
], columns: 3 },
|
|
127
|
+
|
|
128
|
+
{ type: 'title', text: 'Personalización: Bordes y Colores', level: 3 },
|
|
129
|
+
{ type: 'table', headers: ['Elemento', 'Efecto Visual', 'Recomendación'], rows: [
|
|
130
|
+
['Borde Blanco', 'Limpio, minimalista, moderno', 'Redes sociales, portafolios digitales'],
|
|
131
|
+
['Borde Negro', 'Dramático, profesional, artístico', 'Fotografía de arte, moda, lujo'],
|
|
132
|
+
['Borde Neutro (gris)', 'Versátil, académico, corporate', 'Negocios, educación, neutralidad'],
|
|
133
|
+
['Sin Borde', 'Fusión, continuidad, inmersivo', 'Fondo uniforme, fotos que fluyen']
|
|
134
|
+
] },
|
|
135
|
+
|
|
136
|
+
{ type: 'proscons', items: [
|
|
137
|
+
{
|
|
138
|
+
pro: 'Diseños predefinidos profesionales - no necesitas saber de composición',
|
|
139
|
+
con: 'Opciones limitadas a cuadrículas preestablecidas'
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
pro: 'Resolución 1200px lista para redes sociales sin reescalado',
|
|
143
|
+
con: 'No es tan personalizable como Photoshop'
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
pro: 'Procesamiento 100% local - privacidad, velocidad, sin límites',
|
|
147
|
+
con: 'Requiere navegador moderno'
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
pro: 'Totalmente gratuito, sin marcas de agua, sin publicidad',
|
|
151
|
+
con: 'Sin opciones de edición individual (recortes, rotación)'
|
|
152
|
+
}
|
|
153
|
+
], proTitle: 'Ventajas', conTitle: 'Limitaciones' },
|
|
154
|
+
|
|
155
|
+
{ type: 'diagnostic', variant: 'success', title: 'Listo para Redes Sociales', icon: 'mdi:check-circle-outline', badge: 'Optimizado', html: '1200x1200px es la resolución ideal para Instagram. Soporta cualquier relación de aspecto, pero la salida cuadrada maximiza el impacto en feed, elimina recortes automáticos y se ve igual de bien en desktop, tablet y móvil.' },
|
|
156
|
+
|
|
157
|
+
{ type: 'glossary', items: [
|
|
158
|
+
{
|
|
159
|
+
term: 'Composición Visual',
|
|
160
|
+
definition: 'Arte de organizar elementos (color, forma, espacio) para guiar la vista del espectador y comunicar intención emocional.'
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
term: 'Regla de los Tercios',
|
|
164
|
+
definition: 'Principio de composición: divide la imagen en 9 áreas iguales (3x3). Posiciona sujetos en líneas de intersección para máximo impacto.'
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
term: 'Cuadrícula Simétrica',
|
|
168
|
+
definition: 'Divisiones iguales del espacio. Tranquilizante, ordenado. Ideal para pares de fotos o números pares.'
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
term: 'Cuadrícula Asimétrica',
|
|
172
|
+
definition: 'Divisiones desiguales. Dinámico, interesante, visual. Ideal para 5+ fotos con variedad de tamaños.'
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
term: 'Balance Visual',
|
|
176
|
+
definition: 'Equilibrio perceptual del peso visual. Imagen clara + oscura = balance; grande + pequeño = balance.'
|
|
177
|
+
}
|
|
178
|
+
] },
|
|
179
|
+
|
|
180
|
+
{ type: 'message', title: 'Narrativa Visual Instantánea', ariaLabel: 'Información sobre composición y collages', html: 'Cada collage cuenta una historia. Nuestro diseñador automático asegura que tus historias visuales sean balanceadas, profesionales y listas para el mundo. Sin esperar a que un diseñador gráfico haga el trabajo.' },
|
|
181
|
+
|
|
182
|
+
{ type: 'title', text: 'Crea, Comparte, Inspira', level: 3 },
|
|
183
|
+
{ type: 'paragraph', html: 'Un collage bien hecho es la diferencia entre un post olvidable y uno que tus seguidores recuerdan y comparten. Utiliza composición inteligente para que tus historias visuales impacten.' }
|
|
184
|
+
];
|
|
185
|
+
|
|
186
|
+
const faqSchema: WithContext<FAQPage> = {
|
|
187
|
+
'@context': 'https://schema.org',
|
|
188
|
+
'@type': 'FAQPage',
|
|
189
|
+
mainEntity: faq.map((item) => ({
|
|
190
|
+
'@type': 'Question',
|
|
191
|
+
name: item.question,
|
|
192
|
+
acceptedAnswer: { '@type': 'Answer', text: item.answer },
|
|
193
|
+
})),
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const howToSchema: WithContext<HowTo> = {
|
|
197
|
+
'@context': 'https://schema.org',
|
|
198
|
+
'@type': 'HowTo',
|
|
199
|
+
name: title,
|
|
200
|
+
description,
|
|
201
|
+
step: howTo.map((step) => ({
|
|
202
|
+
'@type': 'HowToStep',
|
|
203
|
+
name: step.name,
|
|
204
|
+
text: step.text,
|
|
205
|
+
})),
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const appSchema: WithContext<SoftwareApplication> = {
|
|
209
|
+
'@context': 'https://schema.org',
|
|
210
|
+
'@type': 'SoftwareApplication',
|
|
211
|
+
name: title,
|
|
212
|
+
description,
|
|
213
|
+
applicationCategory: 'UtilitiesApplication',
|
|
214
|
+
operatingSystem: 'Web',
|
|
215
|
+
offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
|
|
216
|
+
inLanguage: 'es',
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
export const content: CollageMakerLocaleContent = {
|
|
220
|
+
slug,
|
|
221
|
+
title,
|
|
222
|
+
description,
|
|
223
|
+
ui,
|
|
224
|
+
seo,
|
|
225
|
+
faq,
|
|
226
|
+
faqTitle: 'Preguntas frecuentes sobre creación de collages',
|
|
227
|
+
bibliography,
|
|
228
|
+
bibliographyTitle: 'Recursos técnicos sobre composición visual',
|
|
229
|
+
howTo,
|
|
230
|
+
schemas: [faqSchema as any, howToSchema as any, appSchema],
|
|
231
|
+
};
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
|
+
import type { CollageMakerUI, CollageMakerLocaleContent } from '../index';
|
|
3
|
+
|
|
4
|
+
const slug = 'createur-collage-photos-gratuit-compositions-professionnelles';
|
|
5
|
+
const title = 'Créateur de Collages en Ligne - Concevez des compositions professionnelles';
|
|
6
|
+
const description = 'Créez des collages de photos gratuitement en quelques secondes. Choisissez parmi plusieurs mises en page, ajustez les bordures et téléchargez en haute qualité sans filigrane.';
|
|
7
|
+
|
|
8
|
+
const ui: CollageMakerUI = {
|
|
9
|
+
dropTitle: "Faites glisser les images ici",
|
|
10
|
+
dropSub: "ou {link} - min. 2, max. 9",
|
|
11
|
+
dropLink: "sélectionnez des fichiers",
|
|
12
|
+
imgsLoaded: "Images chargées",
|
|
13
|
+
layoutLabel: "Mise en page",
|
|
14
|
+
settingsLabel: "Paramètres",
|
|
15
|
+
borderLabel: "Bordure",
|
|
16
|
+
borderColorLabel: "Couleur bordure",
|
|
17
|
+
bgColorLabel: "Fond",
|
|
18
|
+
downloadBtn: "Télécharger",
|
|
19
|
+
previewTitle: "Aperçu",
|
|
20
|
+
needsImgs: "Vous avez besoin de {n} images",
|
|
21
|
+
errorMin: "Vous avez besoin d'au moins 2 images",
|
|
22
|
+
errorMax: "Maximum 9 images autorisées",
|
|
23
|
+
errorLoad: "Erreur lors du chargement des images",
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const faq: CollageMakerLocaleContent['faq'] = [
|
|
27
|
+
{
|
|
28
|
+
question: "Comment puis-je changer l'ordre des photos ?",
|
|
29
|
+
answer: "Les photos sont placées dans le collage en suivant l'ordre dans lequel elles apparaissent dans la liste des images chargées. Vous pouvez en supprimer une et la télécharger à nouveau pour ajuster sa position.",
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
question: "Quel est le format de sortie du collage ?",
|
|
33
|
+
answer: "Le collage est téléchargé au format WebP haute résolution, idéal pour le partage sur les réseaux sociaux sans perte de netteté.",
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const howTo: CollageMakerLocaleContent['howTo'] = [
|
|
38
|
+
{
|
|
39
|
+
name: "Importez vos photos",
|
|
40
|
+
text: "Sélectionnez entre 2 et 9 images depuis votre explorateur de fichiers.",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: "Choisissez une mise en page",
|
|
44
|
+
text: "Sélectionnez la grille qui convient le mieux au nombre de photos que vous avez chargées.",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: "Personnalisez le style",
|
|
48
|
+
text: "Ajustez l'épaisseur et la couleur de la bordure pour donner une finition professionnelle.",
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: "Téléchargez et partagez",
|
|
52
|
+
text: "Appuyez sur le bouton de création et téléchargez instantanément votre composition finale.",
|
|
53
|
+
},
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
const bibliography: CollageMakerLocaleContent['bibliography'] = [
|
|
57
|
+
{
|
|
58
|
+
name: "Composition Photographique : L'Art du Collage",
|
|
59
|
+
url: "https://fr.wikipedia.org/wiki/Collage",
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
const seo: CollageMakerLocaleContent['seo'] = [
|
|
64
|
+
{
|
|
65
|
+
type: 'summary',
|
|
66
|
+
title: 'Créateur de Collages Professionnel en Ligne',
|
|
67
|
+
items: [
|
|
68
|
+
'Plusieurs mises en page et grilles prédéfinies',
|
|
69
|
+
'Personnalisation des bordures et des couleurs de fond',
|
|
70
|
+
'Haute résolution 1200px prête pour les réseaux sociaux',
|
|
71
|
+
'Traitement instantané sans limites d\'utilisation'
|
|
72
|
+
]
|
|
73
|
+
},
|
|
74
|
+
{ type: 'title', text: 'Conception de Collages : Des Compositions qui Racontent des Histoires', level: 2 },
|
|
75
|
+
{ type: 'paragraph', html: 'Un collage est plus qu\'un mélange de photos ; c\'est un récit visuel qui communique une émotion et un contexte. Notre outil permet de créer des compositions géométriques professionnelles pour Instagram, Facebook, Pinterest ou des projets personnels sans avoir besoin de Photoshop ou de logiciels coûteux.' },
|
|
76
|
+
|
|
77
|
+
{ type: 'stats', items: [
|
|
78
|
+
{ value: '2-9', label: 'Images par Collage', icon: 'mdi:image-multiple' },
|
|
79
|
+
{ value: '1200px', label: 'Résolution HD', icon: 'mdi:video-high-definition' },
|
|
80
|
+
{ value: 'Instantané', label: 'Génération', icon: 'mdi:lightning-bolt' }
|
|
81
|
+
], columns: 3 },
|
|
82
|
+
|
|
83
|
+
{ type: 'title', text: 'Composition Visuelle : Principes de Design', level: 3 },
|
|
84
|
+
{ type: 'paragraph', html: 'La composition est l\'art d\'organiser les éléments visuels de manière à guider l\'œil du spectateur et à communiquer une intention. Un bon collage équilibre :' },
|
|
85
|
+
{ type: 'list', items: [
|
|
86
|
+
'<strong>Équilibre :</strong> Distribution visuelle du poids (images claires vs sombres, grandes vs petites).',
|
|
87
|
+
'<strong>Flux Visuel :</strong> L\'œil doit parcourir la composition naturellement, sans points morts.',
|
|
88
|
+
'<strong>Contraste :</strong> Variations de couleur, de taille et de forme qui captent l\'attention.',
|
|
89
|
+
'<strong>Unité :</strong> Cohérence thématique - les images doivent "parler ensemble" de la même histoire.'
|
|
90
|
+
], icon: 'mdi:check' },
|
|
91
|
+
|
|
92
|
+
{ type: 'card', title: 'Mises en Page Intelligentes et Adaptatives', html: 'Notre système calcule automatiquement la distribution optimale de l\'espace en fonction du nombre de photos. 2 images = grille symétrique ; 5 images = distribution asymétrique équilibrée. Chaque grille est conçue pour maximiser l\'impact visuel.' },
|
|
93
|
+
|
|
94
|
+
{ type: 'comparative', items: [
|
|
95
|
+
{
|
|
96
|
+
title: 'Pour les Réseaux Sociaux',
|
|
97
|
+
description: 'Instagram, TikTok, Facebook - formats carrés',
|
|
98
|
+
icon: 'mdi:share-all',
|
|
99
|
+
points: [
|
|
100
|
+
'1200px est parfait pour le flux Instagram',
|
|
101
|
+
'Le format carré évite le recadrage lors de la publication',
|
|
102
|
+
'Bordures personnalisables pour le branding'
|
|
103
|
+
],
|
|
104
|
+
highlight: true
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
title: 'Pour les Portfolios',
|
|
108
|
+
description: 'Vitrines visuelles pour photographes et designers',
|
|
109
|
+
icon: 'mdi:briefcase-outline',
|
|
110
|
+
points: [
|
|
111
|
+
'Montrez plusieurs angles d\'un projet',
|
|
112
|
+
'Récit visuel cohérent',
|
|
113
|
+
'Impression professionnelle de livre numérique'
|
|
114
|
+
]
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
title: 'Pour les Cadeaux Personnels',
|
|
118
|
+
description: 'Impressions, cadres et albums numériques',
|
|
119
|
+
icon: 'mdi:gift-outline',
|
|
120
|
+
points: [
|
|
121
|
+
'Souvenirs d\'événements (mariages, voyages)',
|
|
122
|
+
'Haute résolution prête à imprimer',
|
|
123
|
+
'Conception instantanée sans effort manuel'
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
], columns: 3 },
|
|
127
|
+
|
|
128
|
+
{ type: 'title', text: 'Personnalisation : Bordures et Couleurs', level: 3 },
|
|
129
|
+
{ type: 'table', headers: ['Élément', 'Effet Visuel', 'Recommandation'], rows: [
|
|
130
|
+
['Bordure Blanche', 'Propre, minimaliste, moderne', 'Réseaux sociaux, portfolios numériques'],
|
|
131
|
+
['Bordure Noire', 'Dramatique, professionnel, artistique', 'Photographie d\'art, mode, luxe'],
|
|
132
|
+
['Bordure Neutre (gris)', 'Polyvalent, académique, professionnel', 'Affaires, éducation, neutralité'],
|
|
133
|
+
['Sans Bordure', 'Fusion, continuité, immersif', 'Fond uniforme, photos fluides']
|
|
134
|
+
] },
|
|
135
|
+
|
|
136
|
+
{ type: 'proscons', items: [
|
|
137
|
+
{
|
|
138
|
+
pro: 'Mises en page prédéfinies professionnelles - aucune connaissance en composition requise',
|
|
139
|
+
con: 'Options limitées aux grilles préétablies'
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
pro: 'Résolution 1200px prête pour les réseaux sociaux sans remise à l\'échelle',
|
|
143
|
+
con: 'Pas autant personnalisable que Photoshop'
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
pro: 'Traitement 100 % local - confidentialité, vitesse, sans limites',
|
|
147
|
+
con: 'Nécessite un navigateur moderne'
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
pro: 'Totalement gratuit, sans filigrane, sans publicité',
|
|
151
|
+
con: 'Pas d\'options d\'édition individuelles (recadrage, rotation)'
|
|
152
|
+
}
|
|
153
|
+
], proTitle: 'Avantages', conTitle: 'Limitations' },
|
|
154
|
+
|
|
155
|
+
{ type: 'diagnostic', variant: 'success', title: 'Prêt pour les Réseaux Sociaux', icon: 'mdi:check-circle-outline', badge: 'Optimisé', html: '1200x1200px est la résolution idéale pour Instagram. Il prend en charge n\'importe quel rapport d\'aspect, mais la sortie carrée maximise l\'impact du flux, élimine le recadrage automatique et se présente tout aussi bien sur ordinateur, tablette et mobile.' },
|
|
156
|
+
|
|
157
|
+
{ type: 'glossary', items: [
|
|
158
|
+
{
|
|
159
|
+
term: 'Composition Visuelle',
|
|
160
|
+
definition: 'Art d\'organiser des éléments (couleur, forme, espace) pour guider l\'œil du spectateur et communiquer une intention émotionnelle.'
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
term: 'Règle des Tiers',
|
|
164
|
+
definition: 'Principe de composition : divise l\'image en 9 zones égales (3x3). Positionnez les sujets sur les lignes d\'intersection pour un impact maximal.'
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
term: 'Grille Symétrique',
|
|
168
|
+
definition: 'Divisions égales de l\'espace. Rassurant, ordonné. Idéal pour les paires de photos.'
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
term: 'Grille Asymétrique',
|
|
172
|
+
definition: 'Divisions inégales. Dynamique, intéressant, visuel. Idéal pour plus de 5 photos.'
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
term: 'Équilibre Visuel',
|
|
176
|
+
definition: 'Équilibre perceptuel du poids visuel. Image claire + sombre = équilibre ; grande + petite = équilibre.'
|
|
177
|
+
}
|
|
178
|
+
] },
|
|
179
|
+
|
|
180
|
+
{ type: 'message', title: 'Récit Visuel Instantané', ariaLabel: 'Informations sur la composition et les collages', html: 'Chaque collage raconte une histoire. Notre concepteur automatique garantit que vos histoires visuelles sont équilibrées, professionnelles et prêtes pour le monde. Sans attendre qu\'un graphiste fasse le travail.' },
|
|
181
|
+
|
|
182
|
+
{ type: 'title', text: 'Créez, Partagez, Inspirez', level: 3 },
|
|
183
|
+
{ type: 'paragraph', html: 'Un collage bien fait est la différence entre une publication oubliable et une autre dont vos abonnés se souviennent et qu\'ils partagent. Utilisez la composition intelligente pour que vos histoires visuelles marquent les esprits.' }
|
|
184
|
+
];
|
|
185
|
+
|
|
186
|
+
const faqSchema: WithContext<FAQPage> = {
|
|
187
|
+
'@context': 'https://schema.org',
|
|
188
|
+
'@type': 'FAQPage',
|
|
189
|
+
mainEntity: faq.map((item) => ({
|
|
190
|
+
'@type': 'Question',
|
|
191
|
+
name: item.question,
|
|
192
|
+
acceptedAnswer: { '@type': 'Answer', text: item.answer },
|
|
193
|
+
})),
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const howToSchema: WithContext<HowTo> = {
|
|
197
|
+
'@context': 'https://schema.org',
|
|
198
|
+
'@type': 'HowTo',
|
|
199
|
+
name: title,
|
|
200
|
+
description,
|
|
201
|
+
step: howTo.map((step) => ({
|
|
202
|
+
'@type': 'HowToStep',
|
|
203
|
+
name: step.name,
|
|
204
|
+
text: step.text,
|
|
205
|
+
})),
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const appSchema: WithContext<SoftwareApplication> = {
|
|
209
|
+
'@context': 'https://schema.org',
|
|
210
|
+
'@type': 'SoftwareApplication',
|
|
211
|
+
name: title,
|
|
212
|
+
description,
|
|
213
|
+
applicationCategory: 'UtilitiesApplication',
|
|
214
|
+
operatingSystem: 'Web',
|
|
215
|
+
offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
|
|
216
|
+
inLanguage: 'fr',
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
export const content: CollageMakerLocaleContent = {
|
|
220
|
+
slug,
|
|
221
|
+
title,
|
|
222
|
+
description,
|
|
223
|
+
ui,
|
|
224
|
+
seo,
|
|
225
|
+
faqTitle: "Frequently Asked Questions",
|
|
226
|
+
faq,
|
|
227
|
+
bibliography,
|
|
228
|
+
bibliographyTitle: "References",
|
|
229
|
+
howTo,
|
|
230
|
+
schemas: [faqSchema as any, howToSchema as any, appSchema],
|
|
231
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { AudiovisualToolEntry, ToolLocaleContent, ToolDefinition } from '../../types';
|
|
2
|
+
import CollageMaker from './component.astro';
|
|
3
|
+
import CollageMakerSEO from './seo.astro';
|
|
4
|
+
import CollageMakerBibliography from './bibliography.astro';
|
|
5
|
+
|
|
6
|
+
export interface CollageMakerUI {
|
|
7
|
+
dropTitle: string;
|
|
8
|
+
dropSub: string;
|
|
9
|
+
dropLink: string;
|
|
10
|
+
imgsLoaded: string;
|
|
11
|
+
layoutLabel: string;
|
|
12
|
+
settingsLabel: string;
|
|
13
|
+
borderLabel: string;
|
|
14
|
+
borderColorLabel: string;
|
|
15
|
+
bgColorLabel: string;
|
|
16
|
+
downloadBtn: string;
|
|
17
|
+
previewTitle: string;
|
|
18
|
+
needsImgs: string;
|
|
19
|
+
errorMin: string;
|
|
20
|
+
errorMax: string;
|
|
21
|
+
errorLoad: string;
|
|
22
|
+
[key: string]: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type CollageMakerLocaleContent = ToolLocaleContent<CollageMakerUI>;
|
|
26
|
+
|
|
27
|
+
import { content as es } from './i18n/es';
|
|
28
|
+
import { content as en } from './i18n/en';
|
|
29
|
+
import { content as fr } from './i18n/fr';
|
|
30
|
+
|
|
31
|
+
export const collageMaker: AudiovisualToolEntry<CollageMakerUI> = {
|
|
32
|
+
id: 'creador-collage-fotos',
|
|
33
|
+
icons: {
|
|
34
|
+
bg: 'mdi:collage',
|
|
35
|
+
fg: 'mdi:palette-outline',
|
|
36
|
+
},
|
|
37
|
+
i18n: {
|
|
38
|
+
es: async () => es as unknown as CollageMakerLocaleContent,
|
|
39
|
+
en: async () => en as unknown as CollageMakerLocaleContent,
|
|
40
|
+
fr: async () => fr as unknown as CollageMakerLocaleContent,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export { CollageMaker, CollageMakerSEO, CollageMakerBibliography };
|
|
45
|
+
|
|
46
|
+
export const COLLAGE_MAKER_TOOL: ToolDefinition = {
|
|
47
|
+
entry: collageMaker as unknown as AudiovisualToolEntry,
|
|
48
|
+
Component: CollageMaker,
|
|
49
|
+
SEOComponent: CollageMakerSEO,
|
|
50
|
+
BibliographyComponent: CollageMakerBibliography,
|
|
51
|
+
};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
export interface CollageImageData {
|
|
2
|
+
src: string;
|
|
3
|
+
width: number;
|
|
4
|
+
height: number;
|
|
5
|
+
name: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface CollageLayout {
|
|
9
|
+
id: string;
|
|
10
|
+
imagesCount: number;
|
|
11
|
+
canvasWidth: number;
|
|
12
|
+
canvasHeight: number;
|
|
13
|
+
getPositions: (width: number, height: number, gap: number) => Array<[number, number, number, number]>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const COLLAGE_LAYOUTS: CollageLayout[] = [
|
|
17
|
+
{
|
|
18
|
+
id: "2x1",
|
|
19
|
+
imagesCount: 2,
|
|
20
|
+
canvasWidth: 800,
|
|
21
|
+
canvasHeight: 400,
|
|
22
|
+
getPositions: (w, h, g) => {
|
|
23
|
+
const cellW = (w - g * 3) / 2;
|
|
24
|
+
const cellH = h - g * 2;
|
|
25
|
+
return [
|
|
26
|
+
[g, g, cellW, cellH],
|
|
27
|
+
[g * 2 + cellW, g, cellW, cellH]
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: "1x2",
|
|
33
|
+
imagesCount: 2,
|
|
34
|
+
canvasWidth: 400,
|
|
35
|
+
canvasHeight: 800,
|
|
36
|
+
getPositions: (w, h, g) => {
|
|
37
|
+
const cellW = w - g * 2;
|
|
38
|
+
const cellH = (h - g * 3) / 2;
|
|
39
|
+
return [
|
|
40
|
+
[g, g, cellW, cellH],
|
|
41
|
+
[g, g * 2 + cellH, cellW, cellH]
|
|
42
|
+
];
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: "2x2",
|
|
47
|
+
imagesCount: 4,
|
|
48
|
+
canvasWidth: 800,
|
|
49
|
+
canvasHeight: 800,
|
|
50
|
+
getPositions: (w, _h, g) => {
|
|
51
|
+
const cellSize = (w - g * 3) / 2;
|
|
52
|
+
return [
|
|
53
|
+
[g, g, cellSize, cellSize],
|
|
54
|
+
[g * 2 + cellSize, g, cellSize, cellSize],
|
|
55
|
+
[g, g * 2 + cellSize, cellSize, cellSize],
|
|
56
|
+
[g * 2 + cellSize, g * 2 + cellSize, cellSize, cellSize]
|
|
57
|
+
];
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: "3x3",
|
|
62
|
+
imagesCount: 9,
|
|
63
|
+
canvasWidth: 900,
|
|
64
|
+
canvasHeight: 900,
|
|
65
|
+
getPositions: (w, _h, g) => {
|
|
66
|
+
const cellSize = (w - g * 4) / 3;
|
|
67
|
+
const pos: Array<[number, number, number, number]> = [];
|
|
68
|
+
for (let r = 0; r < 3; r++) {
|
|
69
|
+
for (let c = 0; c < 3; c++) {
|
|
70
|
+
pos.push([g * (c + 1) + cellSize * c, g * (r + 1) + cellSize * r, cellSize, cellSize]);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return pos;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
interface CollageOptions {
|
|
79
|
+
borderWidth: number;
|
|
80
|
+
borderColor: string;
|
|
81
|
+
bgColor: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function drawCollage(
|
|
85
|
+
canvas: HTMLCanvasElement,
|
|
86
|
+
images: CollageImageData[],
|
|
87
|
+
layoutId: string,
|
|
88
|
+
options: CollageOptions
|
|
89
|
+
): Promise<void> {
|
|
90
|
+
const { borderWidth, borderColor, bgColor } = options;
|
|
91
|
+
const layout = COLLAGE_LAYOUTS.find(l => l.id === layoutId);
|
|
92
|
+
if (!layout) throw new Error("Layout not found");
|
|
93
|
+
|
|
94
|
+
const ctx = canvas.getContext("2d");
|
|
95
|
+
if (!ctx) throw new Error("Canvas context not available");
|
|
96
|
+
|
|
97
|
+
canvas.width = layout.canvasWidth;
|
|
98
|
+
canvas.height = layout.canvasHeight;
|
|
99
|
+
ctx.fillStyle = bgColor;
|
|
100
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
101
|
+
|
|
102
|
+
const positions = layout.getPositions(canvas.width, canvas.height, borderWidth);
|
|
103
|
+
const imagesToUse = images.slice(0, positions.length);
|
|
104
|
+
|
|
105
|
+
for (let i = 0; i < imagesToUse.length; i++) {
|
|
106
|
+
const pos = positions[i];
|
|
107
|
+
if (!pos) continue;
|
|
108
|
+
const [x, y, w, h] = pos;
|
|
109
|
+
await drawImageOnCanvas(ctx, imagesToUse[i].src, { x, y, w, h });
|
|
110
|
+
|
|
111
|
+
if (borderWidth > 0) {
|
|
112
|
+
ctx.strokeStyle = borderColor;
|
|
113
|
+
ctx.lineWidth = borderWidth;
|
|
114
|
+
ctx.strokeRect(x - borderWidth / 2, y - borderWidth / 2, w + borderWidth, h + borderWidth);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function drawImageOnCanvas(
|
|
120
|
+
ctx: CanvasRenderingContext2D,
|
|
121
|
+
src: string,
|
|
122
|
+
rect: DrawRect
|
|
123
|
+
): Promise<void> {
|
|
124
|
+
const { x, y, w, h } = rect;
|
|
125
|
+
return new Promise((resolve, reject) => {
|
|
126
|
+
const img = new Image();
|
|
127
|
+
img.onload = () => {
|
|
128
|
+
ctx.drawImage(img, x, y, w, h);
|
|
129
|
+
resolve();
|
|
130
|
+
};
|
|
131
|
+
img.onerror = () => reject(new Error("Failed to draw image"));
|
|
132
|
+
img.src = src;
|
|
133
|
+
});
|
|
134
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { SEORenderer } from '@jjlmoya/utils-shared';
|
|
3
|
+
import { collageMaker } from './index';
|
|
4
|
+
import type { KnownLocale } from '../../types';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
locale?: KnownLocale;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { locale = 'es' } = Astro.props;
|
|
11
|
+
const content = await collageMaker.i18n[locale]?.();
|
|
12
|
+
if (!content) return null;
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
<SEORenderer content={{ locale, sections: content.seo || [] }} />
|