@jjlmoya/utils-creative 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.
Files changed (71) hide show
  1. package/package.json +64 -0
  2. package/src/category/i18n/en.ts +9 -0
  3. package/src/category/i18n/es.ts +9 -0
  4. package/src/category/i18n/fr.ts +9 -0
  5. package/src/category/index.ts +34 -0
  6. package/src/category/seo.astro +15 -0
  7. package/src/components/PreviewNavSidebar.astro +116 -0
  8. package/src/components/PreviewToolbar.astro +143 -0
  9. package/src/data.ts +6 -0
  10. package/src/env.d.ts +5 -0
  11. package/src/index.ts +27 -0
  12. package/src/layouts/PreviewLayout.astro +117 -0
  13. package/src/pages/[locale]/[slug].astro +146 -0
  14. package/src/pages/[locale].astro +251 -0
  15. package/src/pages/index.astro +4 -0
  16. package/src/tests/faq_count.test.ts +19 -0
  17. package/src/tests/locale_completeness.test.ts +42 -0
  18. package/src/tests/mocks/astro_mock.js +2 -0
  19. package/src/tests/no_h1_in_components.test.ts +48 -0
  20. package/src/tests/seo_length.test.ts +22 -0
  21. package/src/tests/tool_validation.test.ts +17 -0
  22. package/src/tool/bead-pattern-generator/bibliography.astro +18 -0
  23. package/src/tool/bead-pattern-generator/component.astro +372 -0
  24. package/src/tool/bead-pattern-generator/i18n/en.ts +61 -0
  25. package/src/tool/bead-pattern-generator/i18n/es.ts +68 -0
  26. package/src/tool/bead-pattern-generator/i18n/fr.ts +61 -0
  27. package/src/tool/bead-pattern-generator/index.ts +37 -0
  28. package/src/tool/bead-pattern-generator/seo.astro +14 -0
  29. package/src/tool/bead-pattern-generator/style.css +511 -0
  30. package/src/tool/dice-roller/bibliography.astro +17 -0
  31. package/src/tool/dice-roller/component.astro +230 -0
  32. package/src/tool/dice-roller/i18n/en.ts +87 -0
  33. package/src/tool/dice-roller/i18n/es.ts +89 -0
  34. package/src/tool/dice-roller/i18n/fr.ts +87 -0
  35. package/src/tool/dice-roller/index.ts +37 -0
  36. package/src/tool/dice-roller/seo.astro +14 -0
  37. package/src/tool/dice-roller/style.css +482 -0
  38. package/src/tool/excuse-generator/bibliography.astro +18 -0
  39. package/src/tool/excuse-generator/component.astro +140 -0
  40. package/src/tool/excuse-generator/i18n/en.ts +80 -0
  41. package/src/tool/excuse-generator/i18n/es.ts +84 -0
  42. package/src/tool/excuse-generator/i18n/fr.ts +80 -0
  43. package/src/tool/excuse-generator/index.ts +42 -0
  44. package/src/tool/excuse-generator/seo.astro +14 -0
  45. package/src/tool/excuse-generator/style.css +316 -0
  46. package/src/tool/fortune-cookie/bibliography.astro +18 -0
  47. package/src/tool/fortune-cookie/component.astro +299 -0
  48. package/src/tool/fortune-cookie/i18n/en.ts +85 -0
  49. package/src/tool/fortune-cookie/i18n/es.ts +90 -0
  50. package/src/tool/fortune-cookie/i18n/fr.ts +85 -0
  51. package/src/tool/fortune-cookie/index.ts +40 -0
  52. package/src/tool/fortune-cookie/seo.astro +14 -0
  53. package/src/tool/fortune-cookie/style.css +332 -0
  54. package/src/tool/synesthesia-painter/bibliography.astro +17 -0
  55. package/src/tool/synesthesia-painter/component.astro +110 -0
  56. package/src/tool/synesthesia-painter/i18n/en.ts +80 -0
  57. package/src/tool/synesthesia-painter/i18n/es.ts +82 -0
  58. package/src/tool/synesthesia-painter/i18n/fr.ts +80 -0
  59. package/src/tool/synesthesia-painter/index.ts +39 -0
  60. package/src/tool/synesthesia-painter/seo.astro +14 -0
  61. package/src/tool/synesthesia-painter/style.css +234 -0
  62. package/src/tool/zalgo-generator/bibliography.astro +18 -0
  63. package/src/tool/zalgo-generator/component.astro +195 -0
  64. package/src/tool/zalgo-generator/i18n/en.ts +60 -0
  65. package/src/tool/zalgo-generator/i18n/es.ts +67 -0
  66. package/src/tool/zalgo-generator/i18n/fr.ts +60 -0
  67. package/src/tool/zalgo-generator/index.ts +38 -0
  68. package/src/tool/zalgo-generator/seo.astro +14 -0
  69. package/src/tool/zalgo-generator/style.css +558 -0
  70. package/src/tools.ts +4 -0
  71. package/src/types.ts +72 -0
@@ -0,0 +1,372 @@
1
+ ---
2
+ import './style.css';
3
+ ---
4
+
5
+ <div class="bead-wrapper">
6
+ <div class="bead-card" id="main-container">
7
+ <div class="bead-top-bar"></div>
8
+
9
+ <div class="bead-inner">
10
+ <div class="bead-header">
11
+ <div class="bead-header-title">
12
+ <h2>Laboratorio de Patrones</h2>
13
+ <p>Ingeniería de color para tus manos</p>
14
+ </div>
15
+
16
+ <div class="bead-controls">
17
+ <div class="bead-control-group">
18
+ <label class="bead-control-label">Tamaño (Ancho)</label>
19
+ <div class="bead-slider-row">
20
+ <input type="range" id="grid-width" min="10" max="100" value="40" class="bead-slider bead-slider-pink" />
21
+ <span id="grid-width-value" class="bead-slider-val bead-val-pink">40</span>
22
+ </div>
23
+ </div>
24
+
25
+ <div class="bead-control-group">
26
+ <label class="bead-control-label">Colores</label>
27
+ <div class="bead-slider-row">
28
+ <input type="range" id="color-count" min="2" max="32" value="12" class="bead-slider bead-slider-purple" />
29
+ <span id="color-count-value" class="bead-slider-val bead-val-purple">12</span>
30
+ </div>
31
+ </div>
32
+
33
+ <div class="bead-control-group">
34
+ <label class="bead-control-label">Opciones</label>
35
+ <div class="bead-options-row">
36
+ <label class="bead-checkbox-label">
37
+ <input type="checkbox" id="show-rulers" class="bead-checkbox" />
38
+ <span>Reglas</span>
39
+ </label>
40
+ <div class="bead-divider"></div>
41
+ <label class="bead-checkbox-label" title="Patrón Sorpresa">
42
+ <input type="checkbox" id="show-symbols" class="bead-checkbox" />
43
+ <span>Símbolos</span>
44
+ </label>
45
+ </div>
46
+ </div>
47
+
48
+ <button id="download-btn" disabled class="bead-download-btn">
49
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="currentColor">
50
+ <path fill-rule="evenodd" d="M3 17a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm3.293-7.707a1 1 0 011.414 0L9 10.586V3a1 1 0 112 0v7.586l1.293-1.293a1 1 0 111.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z" clip-rule="evenodd"></path>
51
+ </svg>
52
+ <span>Descargar</span>
53
+ </button>
54
+ </div>
55
+ </div>
56
+
57
+ <div class="bead-canvas-area">
58
+ <div class="bead-drop-zone" id="drop-zone">
59
+ <input type="file" id="image-upload" accept="image/*" class="bead-file-input" />
60
+ <div class="bead-drop-content">
61
+ <div class="bead-upload-icon">
62
+ <svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" fill="none" viewBox="0 0 24 24" stroke="currentColor">
63
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
64
+ </svg>
65
+ </div>
66
+ <h3>Carga tu Visión</h3>
67
+ <p>y deja que la magia digital ocurra</p>
68
+ </div>
69
+ </div>
70
+
71
+ <div id="result-area" class="bead-result bead-hidden">
72
+ <div class="bead-result-inner">
73
+ <div class="bead-palette-col">
74
+ <h3 class="bead-palette-title">ADN Cromático</h3>
75
+ <div id="palette-grid" class="bead-palette-grid"></div>
76
+ <button id="reupload-btn" class="bead-reupload-btn">
77
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="currentColor">
78
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
79
+ </svg>
80
+ Cambiar Imagen
81
+ </button>
82
+ </div>
83
+ <div class="bead-canvas-col">
84
+ <canvas id="pattern-canvas" class="bead-canvas"></canvas>
85
+ </div>
86
+ </div>
87
+
88
+ <div class="bead-tunnel-bar">
89
+ <div>
90
+ <h3 class="bead-tunnel-title">Visión de Túnel</h3>
91
+ <p class="bead-tunnel-sub">Tu asistente de precisión fila por fila.</p>
92
+ </div>
93
+ <div class="bead-tunnel-controls">
94
+ <button id="focus-prev" class="bead-tunnel-btn">▲</button>
95
+ <span id="focus-row-display" class="bead-tunnel-display">--</span>
96
+ <button id="focus-next" class="bead-tunnel-btn">▼</button>
97
+ </div>
98
+ </div>
99
+ </div>
100
+ </div>
101
+ </div>
102
+ </div>
103
+ </div>
104
+
105
+ <script>
106
+ const SYMBOLS = "123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
107
+ type RoundCtx = CanvasRenderingContext2D & { roundRect?: (...args: number[]) => void };
108
+
109
+ function getClosestCenterIdx(p: number[], centers: number[][]): number {
110
+ let minDist = Infinity, idx = 0;
111
+ for (let c = 0; c < centers.length; c++) {
112
+ const cc = centers[c] ?? [0,0,0];
113
+ const d = (p[0]-cc[0])**2 + (p[1]-cc[1])**2 + (p[2]-cc[2])**2;
114
+ if (d < minDist) { minDist = d; idx = c; }
115
+ }
116
+ return idx;
117
+ }
118
+
119
+ function updateCenters(clusters: number[][][], colorCount: number, centers: number[][]): void {
120
+ for (let c = 0; c < colorCount; c++) {
121
+ const cluster = clusters[c] ?? [];
122
+ if (cluster.length > 0) {
123
+ let r = 0, g = 0, b = 0;
124
+ for (const p of cluster) { r += p[0] ?? 0; g += p[1] ?? 0; b += p[2] ?? 0; }
125
+ centers[c] = [Math.floor(r/cluster.length), Math.floor(g/cluster.length), Math.floor(b/cluster.length)];
126
+ }
127
+ }
128
+ }
129
+
130
+ function simpleQuantize(imageData: ImageData, colorCount: number): number[][] {
131
+ const pixels = imageData.data;
132
+ const pixelArray: number[][] = [];
133
+ for (let i = 0; i < pixels.length; i += 4 * 10) {
134
+ if (pixels[i + 3] > 128) pixelArray.push([pixels[i], pixels[i + 1], pixels[i + 2]]);
135
+ }
136
+ if (pixelArray.length === 0) return [];
137
+ const centers: number[][] = [];
138
+ for (let i = 0; i < colorCount; i++) centers.push(pixelArray[Math.floor(Math.random() * pixelArray.length)] ?? [0,0,0]);
139
+ for (let iter = 0; iter < 5; iter++) {
140
+ const clusters: number[][][] = Array(colorCount).fill(null).map(() => []);
141
+ for (const p of pixelArray) clusters[getClosestCenterIdx(p, centers)].push(p);
142
+ updateCenters(clusters, colorCount, centers);
143
+ }
144
+ return centers;
145
+ }
146
+
147
+ function findClosestColorIndex(r: number, g: number, b: number, palette: number[][]): number {
148
+ let minDist = Infinity, closestIdx = 0;
149
+ for (let i = 0; i < palette.length; i++) {
150
+ const p = palette[i] ?? [0,0,0];
151
+ const d = (r-p[0])**2 + (g-p[1])**2 + (b-p[2])**2;
152
+ if (d < minDist) { minDist = d; closestIdx = i; }
153
+ }
154
+ return closestIdx;
155
+ }
156
+
157
+ const dropZone = document.getElementById("drop-zone") as HTMLDivElement;
158
+ const imageInput = document.getElementById("image-upload") as HTMLInputElement;
159
+ const resultArea = document.getElementById("result-area") as HTMLDivElement;
160
+ const reuploadBtn = document.getElementById("reupload-btn") as HTMLButtonElement;
161
+ const gridWidthInput = document.getElementById("grid-width") as HTMLInputElement;
162
+ const gridWidthValue = document.getElementById("grid-width-value") as HTMLSpanElement;
163
+ const colorCountInput = document.getElementById("color-count") as HTMLInputElement;
164
+ const colorCountValue = document.getElementById("color-count-value") as HTMLSpanElement;
165
+ const rulersToggle = document.getElementById("show-rulers") as HTMLInputElement;
166
+ const symbolsToggle = document.getElementById("show-symbols") as HTMLInputElement;
167
+ const downloadBtn = document.getElementById("download-btn") as HTMLButtonElement;
168
+ const patternCanvas = document.getElementById("pattern-canvas") as HTMLCanvasElement;
169
+ const paletteGrid = document.getElementById("palette-grid") as HTMLDivElement;
170
+ const focusPrevBtn = document.getElementById("focus-prev") as HTMLButtonElement;
171
+ const focusNextBtn = document.getElementById("focus-next") as HTMLButtonElement;
172
+ const focusDisplay = document.getElementById("focus-row-display") as HTMLSpanElement;
173
+
174
+ let currentImage: HTMLImageElement | null = null;
175
+ let currentFileName = "patron";
176
+ let currentRow = -1;
177
+ let totalRows = 0;
178
+ interface LastData { grid: number[]; palette: number[][]; cols: number; rows: number; }
179
+ let lastData: LastData = { grid: [], palette: [], cols: 0, rows: 0 };
180
+ let debounceTimer: ReturnType<typeof setTimeout>;
181
+
182
+ imageInput?.addEventListener("change", (e) => {
183
+ const files = (e.target as HTMLInputElement).files;
184
+ if (files?.length) handleFile(files[0]);
185
+ });
186
+
187
+ if (dropZone) {
188
+ ["dragover","dragenter"].forEach(evt => dropZone.addEventListener(evt, (e) => { e.preventDefault(); dropZone.classList.add("bead-drop-active"); }));
189
+ ["dragleave","drop"].forEach(evt => dropZone.addEventListener(evt, (e) => { e.preventDefault(); dropZone.classList.remove("bead-drop-active"); }));
190
+ dropZone.addEventListener("drop", (e) => { if (e.dataTransfer?.files.length) handleFile(e.dataTransfer.files[0]); });
191
+ }
192
+
193
+ reuploadBtn?.addEventListener("click", () => {
194
+ dropZone?.classList.remove("bead-hidden");
195
+ resultArea?.classList.add("bead-hidden");
196
+ currentImage = null;
197
+ if (downloadBtn) downloadBtn.disabled = true;
198
+ if (imageInput) imageInput.value = "";
199
+ });
200
+
201
+ function handleFile(file: File) {
202
+ currentFileName = file.name.split(".").slice(0,-1).join(".") || "patron";
203
+ const reader = new FileReader();
204
+ reader.onload = (e) => {
205
+ if (e.target?.result) {
206
+ const img = new Image();
207
+ img.onload = () => { currentImage = img; dropZone?.classList.add("bead-hidden"); resultArea?.classList.remove("bead-hidden"); generatePattern(); };
208
+ img.src = e.target.result as string;
209
+ }
210
+ };
211
+ reader.readAsDataURL(file);
212
+ }
213
+
214
+ gridWidthInput?.addEventListener("input", (e) => { if (gridWidthValue) gridWidthValue.textContent = (e.target as HTMLInputElement).value; debounceGenerate(); });
215
+ colorCountInput?.addEventListener("input", (e) => { if (colorCountValue) colorCountValue.textContent = (e.target as HTMLInputElement).value; debounceGenerate(); });
216
+ rulersToggle?.addEventListener("change", drawGrid);
217
+ symbolsToggle?.addEventListener("change", drawGrid);
218
+
219
+ function debounceGenerate() {
220
+ clearTimeout(debounceTimer);
221
+ debounceTimer = setTimeout(generatePattern, 300);
222
+ }
223
+
224
+ function createGridFromImage(width: number, colors: number): { grid: number[]; palette: number[][] } | null {
225
+ if (!currentImage) return null;
226
+ const height = Math.round(width * (currentImage.height / currentImage.width));
227
+ const tmp = document.createElement("canvas");
228
+ tmp.width = width; tmp.height = height;
229
+ const ctx = tmp.getContext("2d");
230
+ if (!ctx) return null;
231
+ ctx.drawImage(currentImage, 0, 0, width, height);
232
+ const imageData = ctx.getImageData(0, 0, width, height);
233
+ const palette = simpleQuantize(imageData, colors);
234
+ if (!palette.length) return null;
235
+ const grid: number[] = [];
236
+ for (let i = 0; i < imageData.data.length; i += 4)
237
+ grid.push(findClosestColorIndex(imageData.data[i], imageData.data[i+1], imageData.data[i+2], palette));
238
+ return { grid, palette };
239
+ }
240
+ function updatePatternState(grid: number[], palette: number[][], width: number, height: number): void {
241
+ lastData = { grid, palette, cols: width, rows: height };
242
+ totalRows = height;
243
+ currentRow = -1;
244
+ if (focusDisplay) focusDisplay.textContent = "--";
245
+ displayPalette(palette);
246
+ if (downloadBtn) downloadBtn.disabled = false;
247
+ }
248
+
249
+ function generatePattern() {
250
+ if (!currentImage || !patternCanvas) return;
251
+ const width = parseInt(gridWidthInput?.value || "40");
252
+ const colors = parseInt(colorCountInput?.value || "12");
253
+ const result = createGridFromImage(width, colors);
254
+ if (!result) return;
255
+ const height = Math.round(width * (currentImage.height / currentImage.width));
256
+ updatePatternState(result.grid, result.palette, width, height);
257
+ drawGrid();
258
+ }
259
+ interface DrawConfig { S: number; G: number; R: number; ctx: CanvasRenderingContext2D; cols: number; rows: number; palette: number[][]; symbols: boolean; }
260
+ function drawRulers(cfg: DrawConfig): void {
261
+ const { S, G, R, ctx, cols, rows } = cfg;
262
+ ctx.fillStyle="#64748b"; ctx.font="10px monospace"; ctx.textAlign="center"; ctx.textBaseline="middle";
263
+ for (let i=0;i<cols;i++) if ((i+1)%5===0||i===0) ctx.fillText((i+1).toString(), R+G+i*(S+G)+S/2, R/2);
264
+ for (let j=0;j<rows;j++) if ((j+1)%5===0||j===0) ctx.fillText((j+1).toString(), R/2, R+G+j*(S+G)+S/2);
265
+ ctx.strokeStyle="#e2e8f0"; ctx.lineWidth=1; ctx.beginPath();
266
+ ctx.moveTo(R,0); ctx.lineTo(R, (rows*(S+G)+G+R));
267
+ ctx.moveTo(0,R); ctx.lineTo((cols*(S+G)+G+R),R); ctx.stroke();
268
+ }
269
+ function drawCell(cfg: DrawConfig, ci: number, x: number, y: number): void {
270
+ const { S, G, R, ctx, palette, symbols } = cfg;
271
+ const px=R+G+x*(S+G), py=R+G+y*(S+G);
272
+ const c=palette[ci] ?? [0,0,0];
273
+ ctx.fillStyle = symbols ? "#fff" : `rgb(${c[0]},${c[1]},${c[2]})`;
274
+ if (symbols) { ctx.strokeStyle="#cbd5e1"; ctx.lineWidth=1; }
275
+ ctx.beginPath();
276
+ const rCtx = ctx as RoundCtx;
277
+ if (rCtx.roundRect) rCtx.roundRect(px,py,S,S,4); else ctx.rect(px,py,S,S);
278
+ ctx.fill();
279
+ if (symbols) {
280
+ ctx.stroke();
281
+ ctx.fillStyle="#000"; ctx.font="bold 14px sans-serif"; ctx.textAlign="center"; ctx.textBaseline="middle";
282
+ ctx.fillText(SYMBOLS[ci%SYMBOLS.length], px+S/2, py+S/2+1);
283
+ }
284
+ }
285
+ function highlightRow(cfg: DrawConfig, x: number, y: number): void {
286
+ const { S, G, R, ctx } = cfg, px=R+G+x*(S+G), py=R+G+y*(S+G);
287
+ if (y===currentRow) { ctx.strokeStyle="#db2777"; ctx.lineWidth=3; ctx.stroke(); }
288
+ else { ctx.fillStyle="rgba(255,255,255,0.75)"; ctx.fillRect(px,py,S,S); }
289
+ }
290
+ function drawGridCells(cfg: DrawConfig, grid: number[], cols: number): void {
291
+ grid.forEach((ci,idx) => {
292
+ const x=idx%cols, y=Math.floor(idx/cols);
293
+ drawCell(cfg, ci, x, y);
294
+ if (currentRow !== -1) highlightRow(cfg, x, y);
295
+ });
296
+ }
297
+ function prepareDrawConfig(cols: number, rows: number, palette: number[][]): DrawConfig | null {
298
+ if (!patternCanvas) return null;
299
+ const S = 25, G = 2, R = (rulersToggle?.checked) ? 25 : 0;
300
+ patternCanvas.width = cols*(S+G)+G+R;
301
+ patternCanvas.height = rows*(S+G)+G+R;
302
+ const ctx = patternCanvas.getContext("2d");
303
+ return ctx ? { S, G, R, ctx, cols, rows, palette, symbols: symbolsToggle?.checked || false } : null;
304
+ }
305
+ function drawGrid() {
306
+ if (!lastData.grid?.length) return;
307
+ const { grid, cols, rows, palette } = lastData;
308
+ const cfg = prepareDrawConfig(cols, rows, palette);
309
+ if (!cfg) return;
310
+ cfg.ctx.fillStyle = "#fff"; cfg.ctx.fillRect(0,0,cfg.ctx.canvas.width,cfg.ctx.canvas.height);
311
+ if (cfg.R > 0) drawRulers(cfg);
312
+ drawGridCells(cfg, grid, cols);
313
+ }
314
+ function displayPalette(palette: number[][]) {
315
+ if (!paletteGrid) return;
316
+ paletteGrid.innerHTML = "";
317
+ palette.forEach((c, idx) => {
318
+ const [r0=0,g0=0,b0=0] = c;
319
+ const brightness = (r0*299+g0*587+b0*114)/1000;
320
+ const wrap = document.createElement("div"); wrap.className = "bead-palette-item";
321
+ const swatch = document.createElement("div"); swatch.className = "bead-color-swatch";
322
+ swatch.style.backgroundColor = `rgb(${r0},${g0},${b0})`;
323
+ swatch.title = `Color ${idx+1}: RGB(${r0},${g0},${b0})`;
324
+ const sym = document.createElement("span");
325
+ sym.className = "bead-symbol " + (brightness > 128 ? "bead-symbol-dark" : "bead-symbol-light");
326
+ sym.textContent = SYMBOLS[idx%SYMBOLS.length] ?? '';
327
+ swatch.appendChild(sym); wrap.appendChild(swatch); paletteGrid.appendChild(wrap);
328
+ });
329
+ }
330
+
331
+ downloadBtn?.addEventListener("click", async () => {
332
+ if (!patternCanvas || !currentImage) return;
333
+ const { default: JSZip } = await import("jszip");
334
+ const zip = new JSZip();
335
+ const base = currentFileName ? `${currentFileName}_patron_jjlmoya` : "patron_jjlmoya";
336
+ zip.file(`${base}_pattern.webp`, await (await fetch(patternCanvas.toDataURL("image/webp"))).blob());
337
+ zip.file(`${base}_guia.webp`, await (await fetch(generateLegendCanvas().toDataURL("image/webp"))).blob());
338
+ const blob = await zip.generateAsync({ type: "blob" });
339
+ const a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = `${base}.zip`; a.click();
340
+ });
341
+ function generateLegendCanvas(): HTMLCanvasElement {
342
+ const { palette } = lastData;
343
+ const c = document.createElement("canvas"), ctx = c.getContext("2d");
344
+ if (!ctx) return c;
345
+ const rh=40, p=20, hh=60, w=400;
346
+ c.width=w; c.height=hh+palette.length*rh+p;
347
+ ctx.fillStyle="#fff"; ctx.fillRect(0,0,c.width,c.height);
348
+ ctx.fillStyle="#1e293b"; ctx.font="bold 20px sans-serif"; ctx.textAlign="center"; ctx.textBaseline="middle";
349
+ ctx.fillText("Guía Maestra de Ejecución", w/2, 30);
350
+ ctx.font="14px sans-serif"; ctx.fillStyle="#64748b"; ctx.fillText(currentFileName||"Sin título", w/2, 50);
351
+ ctx.textAlign="left";
352
+ palette.forEach((col, idx) => {
353
+ const [r0=0,g0=0,b0=0] = col;
354
+ const y=hh+idx*rh+rh/2;
355
+ ctx.fillStyle=`rgb(${r0},${g0},${b0})`;
356
+ ctx.beginPath();
357
+ const rCtx = ctx as RoundCtx;
358
+ if (rCtx.roundRect) rCtx.roundRect(p, hh+idx*rh+5, 30, 30, 8); else ctx.rect(p, hh+idx*rh+5, 30, 30);
359
+ ctx.fill(); ctx.strokeStyle="#e2e8f0"; ctx.lineWidth=1; ctx.stroke();
360
+ const br=(r0*299+g0*587+b0*114)/1000;
361
+ ctx.fillStyle=br>128?"#000":"#fff"; ctx.font="bold 16px monospace"; ctx.textAlign="center";
362
+ ctx.fillText(SYMBOLS[idx%SYMBOLS.length] ?? '', p+15, y+2);
363
+ ctx.fillStyle="#334155"; ctx.font="bold 16px sans-serif"; ctx.textAlign="left";
364
+ ctx.fillText(`Color ${idx+1}`, p+45, y);
365
+ ctx.fillStyle="#64748b"; ctx.font="14px monospace";
366
+ ctx.fillText(`RGB(${r0},${g0},${b0})`, p+120, y);
367
+ });
368
+ return c;
369
+ }
370
+ focusPrevBtn?.addEventListener("click", () => { currentRow = currentRow > -1 ? currentRow-1 : totalRows-1; if (focusDisplay) focusDisplay.textContent = currentRow===-1?"--":(currentRow+1).toString(); drawGrid(); });
371
+ focusNextBtn?.addEventListener("click", () => { currentRow = currentRow < totalRows-1 ? currentRow+1 : -1; if (focusDisplay) focusDisplay.textContent = currentRow===-1?"--":(currentRow+1).toString(); drawGrid(); });
372
+ </script>
@@ -0,0 +1,61 @@
1
+ import type { BeadPatternGeneratorLocaleContent } from '../index';
2
+
3
+ export const content: BeadPatternGeneratorLocaleContent = {
4
+ slug: 'bead-pattern-generator',
5
+ title: 'Pattern Generator',
6
+ description: 'Create pixel art and bead schemes for Miyuki or Hama from your photos. Color quantization algorithm, tunnel vision mode, and ZIP export.',
7
+ faqTitle: 'Frequently Asked Questions',
8
+ bibliographyTitle: 'Artisan Bibliography',
9
+ ui: {
10
+ title: 'Pattern Generator',
11
+ description: 'From photo to bead scheme.',
12
+ uploadLabel: 'Upload your photo',
13
+ gridSizeLabel: 'Grid size (beads)',
14
+ pixelateBtn: 'Generate Pattern',
15
+ downloadBtn: 'Download Scheme',
16
+ faqTitle: 'FAQ',
17
+ bibliographyTitle: 'References'
18
+ },
19
+ seo: [
20
+ { type: 'title', text: 'Digital Alchemy: Transmute Pixels into Tangible Art', level: 2 },
21
+ { type: 'paragraph', html: 'Welcome to the <strong>Ultimate Pattern Studio</strong>. A <em>chromatic intelligence engine</em> designed for Pixel Art architects, Miyuki masters, and cross-stitch visionaries. Your bridge between the digital and the handmade.' },
22
+ { type: 'card', icon: 'mdi:grid', title: 'The Intelligent Grid', html: 'Our <strong>spatial subsampling</strong> algorithm does not just "shrink" your image. It analyzes the visual structure to map complex pixel groups to individual cells, preserving the integrity of silhouettes and edges like an expert illustrator.' },
23
+ { type: 'card', icon: 'mdi:palette', title: 'K-Means Color Quantization', html: 'We implement a variant of the <strong>K-Means Clustering</strong> algorithm that mathematically finds the "centroid" tones of your image. Astonishing visual fidelity with a minimalist palette of 12, 24 or 32 colors.' },
24
+ { type: 'title', text: 'Mastery in 3 Steps', level: 3 },
25
+ { type: 'card', icon: 'mdi:image-search', title: 'The Perfect Selection', html: 'Look for <strong>high contrast</strong>, dramatic lighting and iconic shapes. Portraits with clean backgrounds, logos, and vector art translate beautifully.' },
26
+ { type: 'card', icon: 'mdi:ruler', title: 'Dimensional Calibration', html: '<strong>Miyuki Delica:</strong> 50 beads ≈ 8cm · <strong>Hama Midi:</strong> 50 beads ≈ 25cm · <strong>Cross-Stitch:</strong> 1 cell = 1 stitch.' },
27
+ { type: 'card', icon: 'mdi:eye-check', title: 'Zen Execution (Tunnel Vision Mode)', html: 'Our <strong>Tunnel Vision</strong> system acts as your personal assistant, dimming visual noise and surgically highlighting only the active row. Absolute concentration.' },
28
+ { type: 'title', text: 'Infinite Canvases', level: 3 },
29
+ { type: 'list', items: [
30
+ '<strong>Textile Jewelry:</strong> Intricate patterns for looms and geometric bracelets.',
31
+ '<strong>Cross-Stitch:</strong> Modern schemes ready to be embroidered pixel by pixel.',
32
+ '<strong>Mosaics:</strong> Large-scale murals using simplified ceramic tesserae.',
33
+ '<strong>Retro Gaming:</strong> 8-bit authentic assets and sprites in seconds.',
34
+ ]},
35
+ { type: 'stats', items: [
36
+ { value: '10–100', label: 'Bead width range', icon: 'mdi:arrow-expand-horizontal' },
37
+ { value: '2–32', label: 'Color palette slots', icon: 'mdi:palette-swatch' },
38
+ { value: 'K-Means', label: 'Quantization algorithm', icon: 'mdi:function-variant' },
39
+ { value: 'ZIP', label: 'Export format (pattern + guide)', icon: 'mdi:zip-box' },
40
+ ], columns: 4 },
41
+ { type: 'paragraph', html: 'In an era of ephemeral screens, creating something physical is a revolutionary act. This tool does not seek to automate art, but to <strong>empower the artisan</strong>. We give you computational precision so your hands can build lasting legacies.' },
42
+ ],
43
+ faq: [
44
+ { question: 'What is color quantization in patterns?', answer: 'It is the process of reducing the thousands of colors in a photo to just a few that correspond to the actual bead colors available (e.g., Miyuki or Hama). We use smart algorithms to maintain visual resemblance with the minimum possible palette.' },
45
+ { question: 'Can I use this pattern for cross-stitch?', answer: 'Yes, the generator creates a grid chart that is perfectly compatible with cross-stitch. You just need to choose a grid size that matches your fabric (Aida 14, 18, etc.).' },
46
+ { question: 'What is the difference between Miyuki and Hama Beads?', answer: 'Miyuki Delica beads are very small, precise glass beads for jewelry. Hama Beads are plastic and are fused with an iron. Our tool lets you adjust the aspect ratio so the pattern does not distort depending on the material used.' },
47
+ { question: 'How does the dithering algorithm work?', answer: 'Dithering creates the illusion of more colors by mixing pixels of different colors in spaced patterns. It helps color gradients look smoother even with a limited bead palette.' },
48
+ ],
49
+ bibliography: [
50
+ { name: 'Scikit-Image: Color Quantization using K-Means', url: 'https://scikit-learn.org/0.23/auto_examples/cluster/plot_color_quantization.html' },
51
+ { name: 'Miyuki Delica Beads Specification', url: 'https://www.miyuki-beads.co.jp/english/seedbeads/delica.html' },
52
+ { name: 'Visgraf Lab: Dithering Algorithms', url: 'https://www.visgraf.impa.br/Courses/ip00/proj/Dithering1/floyd_steinberg_dithering.html' },
53
+ ],
54
+ howTo: [
55
+ { name: 'Upload a clear image', text: 'Select a photo with good contrast and few small details so the pattern is easier to follow.' },
56
+ { name: 'Adjust the grid size', text: 'Define how many beads wide and tall your final piece will be. More beads = more detail but more difficulty.' },
57
+ { name: 'Optimize the color palette', text: 'Reduce the number of colors until they match the beads you have available in your craft kit.' },
58
+ { name: 'Export the guide scheme', text: 'Generate the final pattern with color codes to use as a reference while assembling your beads on the board or thread.' },
59
+ ],
60
+ schemas: []
61
+ };
@@ -0,0 +1,68 @@
1
+ import type { BeadPatternGeneratorLocaleContent } from '../index';
2
+
3
+ export const content: BeadPatternGeneratorLocaleContent = {
4
+ slug: 'generador-patrones-cuentas',
5
+ title: 'Generador de Patrones',
6
+ description: 'Crea esquemas de pixel art y cuentas para Miyuki o Hama desde tus fotos.',
7
+ faqTitle: 'Preguntas Frecuentes',
8
+ bibliographyTitle: 'Bibliografía del Artesano',
9
+ ui: {
10
+ title: 'Generador de Patrones',
11
+ description: 'De fotografía a esquema de cuentas.',
12
+ uploadLabel: 'Sube tu foto',
13
+ gridSizeLabel: 'Tamaño de la cuadrícula (beads)',
14
+ pixelateBtn: 'Generar Patrón',
15
+ downloadBtn: 'Descargar Esquema',
16
+ faqTitle: 'FAQ',
17
+ bibliographyTitle: 'Referencias'
18
+ },
19
+ seo: [
20
+ { type: 'title', text: 'Alquimia Digital: Transmuta Píxeles en Arte Tangible', level: 2 },
21
+ { type: 'paragraph', html: 'Bienvenido al <strong>Estudio Definitivo de Patrones</strong>. Un motor de <em>inteligencia cromática</em> diseñado para arquitectos del Pixel Art, maestros del Miyuki y visionarios del punto de cruz. Tu puente entre lo digital y lo artesanal.' },
22
+ { type: 'card', icon: 'mdi:grid', title: 'La Retícula Inteligente', html: 'Nuestro algoritmo de <strong>submuestreo espacial</strong> no solo "encoge" tu imagen. Analiza la estructura visual para mapear grupos de píxeles complejos a celdas individuales, preservando la integridad de siluetas y bordes como un dibujante experto.' },
23
+ { type: 'card', icon: 'mdi:palette', title: 'Cuantización K-Means', html: 'Implementamos una variante del algoritmo <strong>K-Means Clustering</strong> que encuentra matemáticamente los tonos "centroides" de tu imagen. Fidelidad visual asombrosa con una paleta minimalista de 12, 24 o 32 tonos.' },
24
+ { type: 'title', text: 'Maestría en 3 Pasos', level: 3 },
25
+ { type: 'card', icon: 'mdi:image-search', title: 'La Selección Perfecta', html: 'Busca <strong>alto contraste</strong>, iluminación dramática y formas icónicas. Los retratos con fondo limpio, logotipos y arte vectorial se traducen maravillosamente.' },
26
+ { type: 'card', icon: 'mdi:ruler', title: 'Calibración Dimensional', html: '<strong>Miyuki Delica:</strong> 50 cuentas ≈ 8cm · <strong>Hama Midi:</strong> 50 cuentas ≈ 25cm · <strong>Punto de Cruz:</strong> 1 celda = 1 puntada.' },
27
+ { type: 'card', icon: 'mdi:eye-check', title: 'Ejecución Zen (Modo Visión de Túnel)', html: 'Nuestro sistema <strong>Visión de Túnel</strong> actúa como tu asistente personal, oscureciendo el ruido visual y resaltando quirúrgicamente solo la fila activa. Concentración absoluta.' },
28
+ { type: 'title', text: 'Lienzos Infinitos', level: 3 },
29
+ { type: 'list', items: [
30
+ '<strong>Joyería Textil:</strong> Patrones intrincados para telares y brazaletes geométricos.',
31
+ '<strong>Cross-Stitch:</strong> Esquemas modernos listos para ser bordados pixel a pixel.',
32
+ '<strong>Mosaicos:</strong> Murales de gran escala usando teselas cerámicas simplificadas.',
33
+ '<strong>Retro Gaming:</strong> Assets y sprites con estética 8-bit auténtica en segundos.',
34
+ ]},
35
+ { type: 'paragraph', html: 'En una era de pantallas efímeras, crear algo físico es un acto revolucionario. Esta herramienta no busca automatizar el arte, sino <strong>empoderar al artesano</strong>. Te damos la precisión computacional para que tus manos puedan construir legados duraderos.' },
36
+ { type: 'stats', items: [
37
+ { value: '10–100', label: 'Ancho del patrón (cuentas)', icon: 'mdi:arrow-expand-horizontal' },
38
+ { value: '2–32', label: 'Colores en la paleta', icon: 'mdi:palette-swatch' },
39
+ { value: 'K-Means', label: 'Algoritmo de cuantización', icon: 'mdi:function-variant' },
40
+ { value: 'ZIP', label: 'Exportación (patrón + guía)', icon: 'mdi:zip-box' },
41
+ ], columns: 4 },
42
+ { type: 'glossary', items: [
43
+ { term: 'Miyuki Delica', definition: 'Cuentas de cristal japonesas de alta precisión, cilíndricas y uniformes. Ideales para joyería y patrones geométricos complejos.' },
44
+ { term: 'Hama Beads', definition: 'Cuentas de plástico que se fusionan con calor (plancha). Perfectas para proyectos de pixel art de gran formato.' },
45
+ { term: 'Cuantización de color', definition: 'Proceso matemático para reducir los miles de colores de una imagen a una paleta limitada, manteniendo la fidelidad visual máxima posible.' },
46
+ { term: 'Visión de túnel', definition: 'Modo de trabajo fila a fila que oscurece el resto del patrón para ayudar a concentrarse en la fila activa sin errores.' },
47
+ { term: 'K-Means Clustering', definition: 'Algoritmo de aprendizaje no supervisado que agrupa píxeles por similitud cromática para encontrar los colores "centroide" de la imagen.' },
48
+ ]},
49
+ ],
50
+ faq: [
51
+ { question: '¿Qué es la cuantización de color en patrones?', answer: 'Es el proceso de reducir los miles de colores de una foto a solo unos pocos que correspondan con los colores reales de las cuentas (ej. Miyuki o Hama). Usamos algoritmos inteligentes para mantener el parecido visual con la mínima paleta posible.' },
52
+ { question: '¿Puedo usar este patrón para punto de cruz?', answer: 'Sí, el generador crea una malla cuadriculada (gráfico) que es perfectamente compatible con el punto de cruz. Solo debes elegir un tamaño de rejilla que se adapte a tu tela (Aida 14, 18, etc.).' },
53
+ { question: '¿Qué diferencia hay entre Miyuki y Hama Beads?', answer: 'Las Miyuki Delica son cuentas de cristal muy pequeñas y precisas para joyería. Los Hama Beads son de plástico y se funden con plancha. Nuestra herramienta permite ajustar la relación de aspecto para que el patrón no se deforme según el material usado.' },
54
+ { question: '¿Cómo funciona el algoritmo de tramado (Dithering)?', answer: 'El tramado crea la ilusión de más colores mezclando píxeles de colores diferentes en patrones espaciados. Ayuda a que las degradaciones de color se vean más suaves incluso con una paleta limitada de cuentas.' },
55
+ ],
56
+ bibliography: [
57
+ { name: 'Scikit-Image: Color Quantization using K-Means', url: 'https://scikit-learn.org/0.23/auto_examples/cluster/plot_color_quantization.html' },
58
+ { name: 'Miyuki Delica Beads Specification', url: 'https://www.miyuki-beads.co.jp/english/seedbeads/delica.html' },
59
+ { name: 'Visgraf Lab: Dithering Algorithms', url: 'https://www.visgraf.impa.br/Courses/ip00/proj/Dithering1/floyd_steinberg_dithering.html' },
60
+ ],
61
+ howTo: [
62
+ { name: 'Subir una imagen clara', text: 'Selecciona una foto con buen contraste y pocos detalles pequeños para que el patrón sea más fácil de seguir.' },
63
+ { name: 'Ajustar el tamaño de la rejilla', text: 'Define cuántas cuentas de ancho y alto tendrá tu pieza final. Recuerda que a más tamaño, más detalle pero más dificultad.' },
64
+ { name: 'Optimizar la paleta de colores', text: 'Reduce el número de colores hasta que coincidan con las cuentas que tienes disponibles en tu kit de manualidades.' },
65
+ { name: 'Exportar el esquema guía', text: 'Genera el patrón final con códigos de color para usarlo como referencia mientras montas tus cuentas en la placa o el hilo.' },
66
+ ],
67
+ schemas: []
68
+ };
@@ -0,0 +1,61 @@
1
+ import type { BeadPatternGeneratorLocaleContent } from '../index';
2
+
3
+ export const content: BeadPatternGeneratorLocaleContent = {
4
+ slug: 'generateur-de-modeles-de-perles',
5
+ title: 'Générateur de Modèles',
6
+ description: 'Créez du pixel art et des schémas de perles pour Miyuki ou Hama à partir de vos photos. Algorithme de quantification de couleurs, mode vision tunnel et export ZIP.',
7
+ faqTitle: 'Questions Fréquemment Posées',
8
+ bibliographyTitle: 'Bibliographie de l\'Artisan',
9
+ ui: {
10
+ title: 'Générateur de Modèles',
11
+ description: 'De la photo au schéma de perles.',
12
+ uploadLabel: 'Chargez votre photo',
13
+ gridSizeLabel: 'Taille de grille (perles)',
14
+ pixelateBtn: 'Générer le Modèle',
15
+ downloadBtn: 'Télécharger le Schéma',
16
+ faqTitle: 'FAQ',
17
+ bibliographyTitle: 'Références'
18
+ },
19
+ seo: [
20
+ { type: 'title', text: 'Alchimie Numérique : Transmutez les Pixels en Art Tangible', level: 2 },
21
+ { type: 'paragraph', html: 'Bienvenue dans le <strong>Studio de Modèles Ultime</strong>. Un <em>moteur d\'intelligence chromatique</em> conçu pour les architectes du Pixel Art, les maîtres de la Miyuki et les visionnaires du point de croix. Votre pont entre le numérique et le fait-main.' },
22
+ { type: 'card', icon: 'mdi:grid', title: 'La Grille Intelligente', html: 'Notre algorithme de <strong>sous-échantillonnage spatial</strong> ne se contente pas de "réduire" votre image. Il analyse la structure visuelle pour mapper des groupes de pixels complexes sur des cellules individuelles, préservant l\'intégrité des silhouettes et des contours comme le ferait un illustrateur expert.' },
23
+ { type: 'card', icon: 'mdi:palette', title: 'Quantification de Couleurs K-Means', html: 'Nous implémentons une variante de l\'algorithme <strong>K-Means Clustering</strong> qui trouve mathématiquement les tons "centroïdes" de votre image. Une fidélité visuelle étonnante avec une palette minimaliste de 12, 24 ou 32 couleurs.' },
24
+ { type: 'title', text: 'La Maîtrise en 3 Étapes', level: 3 },
25
+ { type: 'card', icon: 'mdi:image-search', title: 'La Sélection Parfaite', html: 'Recherchez un <strong>contraste élevé</strong>, un éclairage dramatique et des formes iconiques. Les portraits avec des fonds épurés, les logos et l\'art vectoriel se traduisent magnifiquement.' },
26
+ { type: 'card', icon: 'mdi:ruler', title: 'Calibration Dimensionnelle', html: '<strong>Miyuki Delica :</strong> 50 perles ≈ 8 cm · <strong>Hama Midi :</strong> 50 perles ≈ 25 cm · <strong>Point de Croix :</strong> 1 cellule = 1 point.' },
27
+ { type: 'card', icon: 'mdi:eye-check', title: 'Exécution Zen (Mode Vision Tunnel)', html: 'Notre système de <strong>Vision Tunnel</strong> agit comme votre assistant personnel, atténuant le bruit visuel et mettant chirurgicalement en évidence uniquement la rangée active. Une concentration absolue.' },
28
+ { type: 'title', text: 'Canevas Infinis', level: 3 },
29
+ { type: 'list', items: [
30
+ '<strong>Bijouterie Textile :</strong> Modèles complexes pour métiers à tisser et bracelets géométriques.',
31
+ '<strong>Point de Croix :</strong> Schémas modernes prêts à être brodés pixel par pixel.',
32
+ '<strong>Mosaïques :</strong> Murales à grande échelle utilisant des tesselles de céramique simplifiées.',
33
+ '<strong>Retro Gaming :</strong> Sprites et assets 8-bits authentiques en quelques secondes.',
34
+ ]},
35
+ { type: 'stats', items: [
36
+ { value: '10–100', label: 'Largeur en perles', icon: 'mdi:arrow-expand-horizontal' },
37
+ { value: '2–32', label: 'Emplacements de palette', icon: 'mdi:palette-swatch' },
38
+ { value: 'K-Means', label: 'Algorithme de quantification', icon: 'mdi:function-variant' },
39
+ { value: 'ZIP', label: 'Format d\'export (modèle + guide)', icon: 'mdi:zip-box' },
40
+ ], columns: 4 },
41
+ { type: 'paragraph', html: 'À l\'ère des écrans éphémères, créer quelque chose de physique est un acte révolutionnaire. Cet outil ne cherche pas à automatiser l\'art, mais à <strong>donner du pouvoir à l\'artisan</strong>. Nous vous offrons la précision informatique pour que vos mains puissent bâtir des héritages durables.' },
42
+ ],
43
+ faq: [
44
+ { question: 'Qu\'est-ce que la quantification de couleurs dans les modèles ?', answer: 'C\'est le processus de réduction des milliers de couleurs d\'une photo à quelques-unes seulement qui correspondent aux couleurs réelles des perles disponibles (ex: Miyuki ou Hama). Nous utilisons des algorithmes intelligents pour maintenir la ressemblance visuelle avec la palette minimale possible.' },
45
+ { question: 'Puis-je utiliser ce modèle pour le point de croix ?', answer: 'Oui, le générateur crée un diagramme de grille parfaitement compatible avec le point de croix. Il vous suffit de choisir une taille de grille correspondant à votre tissu (Aïda 14, 18, etc.).' },
46
+ { question: 'Quelle est la différence entre les perles Miyuki et Hama ?', answer: 'Les perles Miyuki Delica sont de très petites perles de verre précises pour la bijouterie. Les perles Hama sont en plastique et se fusionnent au fer à repasser. Notre outil vous permet d\'ajuster le rapport d\'aspect pour que le modèle ne se déforme pas selon le matériau utilisé.' },
47
+ { question: 'Comment fonctionne l\'algorithme de tramage (dithering) ?', answer: 'Le tramage crée l\'illusion d\'un plus grand nombre de couleurs en mélangeant des pixels de différentes couleurs dans des motifs espacés. Cela aide les dégradés de couleurs à paraître plus fluides, même avec une palette de perles limitée.' },
48
+ ],
49
+ bibliography: [
50
+ { name: 'Scikit-Image: Quantification de couleurs utilisant K-Means', url: 'https://scikit-learn.org/0.23/auto_examples/cluster/plot_color_quantization.html' },
51
+ { name: 'Spécifications des Perles Miyuki Delica', url: 'https://www.miyuki-beads.co.jp/english/seedbeads/delica.html' },
52
+ { name: 'Visgraf Lab: Algorithmes de Tramage (Dithering)', url: 'https://www.visgraf.impa.br/Courses/ip00/proj/Dithering1/floyd_steinberg_dithering.html' },
53
+ ],
54
+ howTo: [
55
+ { name: 'Charger une image claire', text: 'Sélectionnez une photo avec un bon contraste et peu de petits détails pour que le modèle soit plus facile à suivre.' },
56
+ { name: 'Ajuster la taille de la grille', text: 'Définissez la largeur et la hauteur en perles de votre pièce finale. Plus il y a de perles, plus il y a de détails, mais plus c\'est difficile.' },
57
+ { name: 'Optimiser la palette de couleurs', text: 'Réduisez le nombre de couleurs jusqu\'à ce qu\'elles correspondent aux perles dont vous disposez dans votre kit de loisirs créatifs.' },
58
+ { name: 'Exporter le schéma guide', text: 'Générez le modèle final avec les codes de couleur à utiliser comme référence lors de l\'assemblage de vos perles sur la plaque ou le fil.' },
59
+ ],
60
+ schemas: []
61
+ };
@@ -0,0 +1,37 @@
1
+ import type { CreativeToolEntry, ToolLocaleContent, ToolDefinition } from '../../types';
2
+ import BeadPatternGeneratorComponent from './component.astro';
3
+ import BeadPatternGeneratorSEO from './seo.astro';
4
+ import BeadPatternGeneratorBibliography from './bibliography.astro';
5
+
6
+ export interface BeadPatternGeneratorUI {
7
+ [key: string]: string;
8
+ title: string;
9
+ description: string;
10
+ uploadLabel: string;
11
+ gridSizeLabel: string;
12
+ pixelateBtn: string;
13
+ downloadBtn: string;
14
+ faqTitle: string;
15
+ bibliographyTitle: string;
16
+ }
17
+
18
+ export type BeadPatternGeneratorLocaleContent = ToolLocaleContent<BeadPatternGeneratorUI>;
19
+
20
+ export const beadPatternGenerator: CreativeToolEntry<BeadPatternGeneratorUI> = {
21
+ id: 'bead-pattern-generator',
22
+ icons: { bg: 'mdi:grid', fg: 'mdi:palette' },
23
+ i18n: {
24
+ es: () => import('./i18n/es').then((m) => m.content),
25
+ en: () => import('./i18n/en').then((m) => m.content),
26
+ fr: () => import('./i18n/fr').then((m) => m.content),
27
+ },
28
+ };
29
+
30
+ export { BeadPatternGeneratorComponent, BeadPatternGeneratorSEO, BeadPatternGeneratorBibliography };
31
+
32
+ export const BEAD_PATTERN_GENERATOR_TOOL: ToolDefinition = {
33
+ entry: beadPatternGenerator,
34
+ Component: BeadPatternGeneratorComponent,
35
+ SEOComponent: BeadPatternGeneratorSEO,
36
+ BibliographyComponent: BeadPatternGeneratorBibliography,
37
+ };
@@ -0,0 +1,14 @@
1
+ ---
2
+ import { SEORenderer } from '@jjlmoya/utils-shared';
3
+ import { beadPatternGenerator } 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 beadPatternGenerator.i18n[locale]?.();
12
+ ---
13
+
14
+ {content && <SEORenderer content={{ locale, sections: content.seo }} />}