@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,558 @@
1
+ /* ── zalgo generator ───────────────────────────────────── */
2
+ .zg-section {
3
+ max-width: 64rem;
4
+ margin: 0 auto;
5
+ padding: 3rem 1rem;
6
+ position: relative;
7
+ }
8
+
9
+ .zg-grid {
10
+ display: grid;
11
+ grid-template-columns: 1fr 2fr;
12
+ gap: 2rem;
13
+ align-items: start;
14
+ }
15
+
16
+ @media (max-width: 768px) {
17
+ .zg-grid {
18
+ grid-template-columns: 1fr;
19
+ }
20
+ }
21
+
22
+ /* left panel */
23
+ .zg-left {
24
+ display: flex;
25
+ flex-direction: column;
26
+ gap: 1rem;
27
+ }
28
+
29
+ .zg-config-card {
30
+ position: relative;
31
+ background: rgba(255, 255, 255, 0.85);
32
+ backdrop-filter: blur(12px);
33
+ border: 1px solid #e2e8f0;
34
+ border-radius: 1.5rem;
35
+ padding: 1.5rem;
36
+ box-shadow: 0 20px 40px -10px rgba(0, 0, 0, 0.1);
37
+ overflow: hidden;
38
+ }
39
+
40
+ .theme-dark .zg-config-card {
41
+ background: rgba(24, 24, 27, 0.85);
42
+ border-color: #27272a;
43
+ }
44
+
45
+ .zg-config-hover-bg {
46
+ position: absolute;
47
+ inset: 0;
48
+ background: linear-gradient(135deg, rgba(147, 51, 234, 0.05), transparent);
49
+ opacity: 0;
50
+ pointer-events: none;
51
+ transition: opacity 0.5s;
52
+ }
53
+
54
+ .zg-config-card:hover .zg-config-hover-bg {
55
+ opacity: 1;
56
+ }
57
+
58
+ .zg-config-title {
59
+ font-size: 1.1rem;
60
+ font-weight: 700;
61
+ color: #0f172a;
62
+ display: flex;
63
+ align-items: center;
64
+ gap: 0.5rem;
65
+ margin: 0 0 1.5rem;
66
+ }
67
+
68
+ .theme-dark .zg-config-title {
69
+ color: #fff;
70
+ }
71
+
72
+ .zg-config-body {
73
+ display: flex;
74
+ flex-direction: column;
75
+ gap: 2rem;
76
+ }
77
+
78
+ .zg-field {
79
+ display: flex;
80
+ flex-direction: column;
81
+ gap: 0.75rem;
82
+ }
83
+
84
+ .zg-field-header {
85
+ display: flex;
86
+ justify-content: space-between;
87
+ align-items: center;
88
+ }
89
+
90
+ .zg-field-label {
91
+ font-size: 0.7rem;
92
+ font-weight: 700;
93
+ text-transform: uppercase;
94
+ letter-spacing: 0.1em;
95
+ color: #64748b;
96
+ }
97
+
98
+ .theme-dark .zg-field-label {
99
+ color: #71717a;
100
+ }
101
+
102
+ .zg-intensity-val {
103
+ font-size: 0.875rem;
104
+ font-weight: 700;
105
+ color: #9333ea;
106
+ }
107
+
108
+ .zg-slider {
109
+ width: 100%;
110
+ height: 0.375rem;
111
+ border-radius: 999px;
112
+ background: #e2e8f0;
113
+ appearance: none;
114
+ cursor: pointer;
115
+ accent-color: #9333ea;
116
+ }
117
+
118
+ .theme-dark .zg-slider {
119
+ background: #27272a;
120
+ }
121
+
122
+ .zg-toggles {
123
+ display: flex;
124
+ flex-direction: column;
125
+ gap: 0.75rem;
126
+ }
127
+
128
+ .zg-toggle-row {
129
+ display: flex;
130
+ align-items: center;
131
+ justify-content: space-between;
132
+ padding: 0.75rem;
133
+ background: #f8fafc;
134
+ border-radius: 1rem;
135
+ cursor: pointer;
136
+ border: 1px solid transparent;
137
+ transition: background 0.15s, border-color 0.15s;
138
+ }
139
+
140
+ .zg-toggle-row:hover {
141
+ background: #f1f5f9;
142
+ border-color: rgba(147, 51, 234, 0.15);
143
+ }
144
+
145
+ .theme-dark .zg-toggle-row {
146
+ background: rgba(9, 9, 11, 0.5);
147
+ }
148
+
149
+ .theme-dark .zg-toggle-row:hover {
150
+ background: #27272a;
151
+ }
152
+
153
+ .zg-toggle-label {
154
+ font-size: 0.875rem;
155
+ font-weight: 500;
156
+ color: #374151;
157
+ }
158
+
159
+ .theme-dark .zg-toggle-label {
160
+ color: #d4d4d8;
161
+ }
162
+
163
+ /* toggle switch */
164
+ .zg-switch {
165
+ position: relative;
166
+ display: inline-flex;
167
+ align-items: center;
168
+ }
169
+
170
+ .zg-switch-input {
171
+ position: absolute;
172
+ opacity: 0;
173
+ width: 0;
174
+ height: 0;
175
+ }
176
+
177
+ .zg-switch-track {
178
+ width: 2.5rem;
179
+ height: 1.25rem;
180
+ background: #d1d5db;
181
+ border-radius: 999px;
182
+ transition: background 0.2s;
183
+ position: relative;
184
+ }
185
+
186
+ .zg-switch-track::after {
187
+ content: '';
188
+ position: absolute;
189
+ top: 2px;
190
+ left: 2px;
191
+ width: 1rem;
192
+ height: 1rem;
193
+ background: #fff;
194
+ border-radius: 50%;
195
+ transition: transform 0.2s;
196
+ }
197
+
198
+ .zg-switch-input:checked+.zg-switch-track {
199
+ background: #9333ea;
200
+ }
201
+
202
+ .zg-switch-input:checked+.zg-switch-track::after {
203
+ transform: translateX(1.25rem);
204
+ }
205
+
206
+ .theme-dark .zg-switch-track {
207
+ background: #3f3f46;
208
+ }
209
+
210
+ .zg-reset-btn {
211
+ display: flex;
212
+ align-items: center;
213
+ justify-content: center;
214
+ gap: 0.5rem;
215
+ width: 100%;
216
+ padding: 0.75rem;
217
+ background: transparent;
218
+ border: none;
219
+ cursor: pointer;
220
+ font-size: 0.7rem;
221
+ font-weight: 700;
222
+ text-transform: uppercase;
223
+ letter-spacing: 0.1em;
224
+ color: #94a3b8;
225
+ transition: color 0.15s;
226
+ }
227
+
228
+ .zg-reset-btn:hover {
229
+ color: #9333ea;
230
+ }
231
+
232
+ .theme-dark .zg-reset-btn {
233
+ color: #52525b;
234
+ }
235
+
236
+ .theme-dark .zg-reset-btn:hover {
237
+ color: #a855f7;
238
+ }
239
+
240
+ .zg-warning {
241
+ padding: 1rem;
242
+ background: rgba(99, 102, 241, 0.05);
243
+ border: 1px solid rgba(99, 102, 241, 0.1);
244
+ border-radius: 1rem;
245
+ }
246
+
247
+ .zg-warning p {
248
+ font-size: 0.65rem;
249
+ line-height: 1.6;
250
+ color: #4338ca;
251
+ font-weight: 500;
252
+ margin: 0;
253
+ }
254
+
255
+ .theme-dark .zg-warning p {
256
+ color: rgba(165, 180, 252, 0.8);
257
+ }
258
+
259
+ /* right panel */
260
+ .zg-right {
261
+ display: flex;
262
+ flex-direction: column;
263
+ gap: 2rem;
264
+ }
265
+
266
+ .zg-editor-card {
267
+ background: #fff;
268
+ border: 1px solid #e2e8f0;
269
+ border-radius: 1.5rem;
270
+ overflow: hidden;
271
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.1);
272
+ display: flex;
273
+ flex-direction: column;
274
+ }
275
+
276
+ .theme-dark .zg-editor-card {
277
+ background: #09090b;
278
+ border-color: #27272a;
279
+ }
280
+
281
+ .zg-editor-bar {
282
+ display: flex;
283
+ align-items: center;
284
+ justify-content: space-between;
285
+ padding: 1rem 1.5rem;
286
+ border-bottom: 1px solid #f1f5f9;
287
+ background: rgba(248, 250, 252, 0.5);
288
+ }
289
+
290
+ .theme-dark .zg-editor-bar {
291
+ border-color: #18181b;
292
+ background: rgba(24, 24, 27, 0.3);
293
+ }
294
+
295
+ .zg-editor-bar-left {
296
+ display: flex;
297
+ align-items: center;
298
+ gap: 0.75rem;
299
+ }
300
+
301
+ .zg-editor-filename {
302
+ font-size: 0.7rem;
303
+ color: #94a3b8;
304
+ text-transform: uppercase;
305
+ letter-spacing: 0.05em;
306
+ }
307
+
308
+ .theme-dark .zg-editor-filename {
309
+ color: #52525b;
310
+ }
311
+
312
+ .zg-textarea {
313
+ width: 100%;
314
+ padding: 2rem;
315
+ background: transparent;
316
+ color: #1e293b;
317
+ font-size: 1.125rem;
318
+ font-weight: 500;
319
+ outline: none;
320
+ resize: none;
321
+ min-height: 10rem;
322
+ border: none;
323
+ }
324
+
325
+ .theme-dark .zg-textarea {
326
+ color: #e4e4e7;
327
+ }
328
+
329
+ .zg-textarea::placeholder {
330
+ color: #cbd5e1;
331
+ }
332
+
333
+ .theme-dark .zg-textarea::placeholder {
334
+ color: #3f3f46;
335
+ }
336
+
337
+ .zg-editor-footer {
338
+ display: flex;
339
+ align-items: center;
340
+ gap: 1rem;
341
+ padding: 0 2rem 2rem;
342
+ justify-content: flex-end;
343
+ }
344
+
345
+ .zg-char-count {
346
+ font-size: 0.65rem;
347
+ color: #94a3b8;
348
+ }
349
+
350
+ .theme-dark .zg-char-count {
351
+ color: #3f3f46;
352
+ }
353
+
354
+ .zg-footer-sep {
355
+ height: 1rem;
356
+ width: 1px;
357
+ background: #e2e8f0;
358
+ }
359
+
360
+ .theme-dark .zg-footer-sep {
361
+ background: #27272a;
362
+ }
363
+
364
+ .zg-copy-btn {
365
+ display: flex;
366
+ align-items: center;
367
+ gap: 0.5rem;
368
+ padding: 0.75rem 1.5rem;
369
+ background: #9333ea;
370
+ color: #fff;
371
+ border: none;
372
+ border-radius: 0.75rem;
373
+ font-weight: 700;
374
+ cursor: pointer;
375
+ box-shadow: 0 4px 14px rgba(147, 51, 234, 0.25);
376
+ transition: background 0.15s, transform 0.1s;
377
+ }
378
+
379
+ .zg-copy-btn:hover {
380
+ background: #7c3aed;
381
+ }
382
+
383
+ .zg-copy-btn:active {
384
+ transform: scale(0.95);
385
+ }
386
+
387
+ .zg-clear-btn {
388
+ padding: 0.75rem;
389
+ background: #f1f5f9;
390
+ color: #64748b;
391
+ border: none;
392
+ border-radius: 0.75rem;
393
+ cursor: pointer;
394
+ transition: background 0.15s;
395
+ }
396
+
397
+ .zg-clear-btn:hover {
398
+ background: #e2e8f0;
399
+ }
400
+
401
+ .theme-dark .zg-clear-btn {
402
+ background: #18181b;
403
+ color: #71717a;
404
+ }
405
+
406
+ .theme-dark .zg-clear-btn:hover {
407
+ background: #27272a;
408
+ }
409
+
410
+ /* preview */
411
+ .zg-preview-wrap {
412
+ position: relative;
413
+ }
414
+
415
+ .zg-preview-badge {
416
+ position: absolute;
417
+ top: -0.75rem;
418
+ left: -0.5rem;
419
+ z-index: 10;
420
+ background: #9333ea;
421
+ color: #fff;
422
+ font-size: 0.6rem;
423
+ font-weight: 900;
424
+ padding: 0.2rem 0.5rem;
425
+ border-radius: 0.25rem;
426
+ letter-spacing: 0.1em;
427
+ text-transform: uppercase;
428
+ box-shadow: 0 4px 8px rgba(147, 51, 234, 0.3);
429
+ }
430
+
431
+ .zg-preview-body {
432
+ background: #fff;
433
+ border: 1px solid #e2e8f0;
434
+ border-radius: 1.5rem;
435
+ padding: 2rem;
436
+ box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.04);
437
+ transition: border-color 0.5s, box-shadow 0.5s;
438
+ min-height: 15rem;
439
+ display: flex;
440
+ align-items: center;
441
+ justify-content: center;
442
+ position: relative;
443
+ overflow: visible;
444
+ }
445
+
446
+ .theme-dark .zg-preview-body {
447
+ background: #18181b;
448
+ border-color: #27272a;
449
+ }
450
+
451
+ .zg-preview-dots {
452
+ position: absolute;
453
+ inset: 0;
454
+ pointer-events: none;
455
+ border-radius: 1.5rem;
456
+ overflow: hidden;
457
+ opacity: 0.03;
458
+ background-image: radial-gradient(circle at 2px 2px, currentcolor 1px, transparent 0);
459
+ background-size: 24px 24px;
460
+ }
461
+
462
+ .zg-output {
463
+ font-size: 2rem;
464
+ font-weight: 700;
465
+ text-align: center;
466
+ max-width: 100%;
467
+ line-height: 1.5;
468
+ z-index: 10;
469
+ transition: color 0.3s;
470
+ overflow-wrap: anywhere;
471
+ color: var(--zalgo-color, inherit);
472
+ }
473
+
474
+ @media (min-width: 768px) {
475
+ .zg-output {
476
+ font-size: 3rem;
477
+ }
478
+ }
479
+
480
+ .zg-output-empty {
481
+ color: #e2e8f0;
482
+ font-style: italic;
483
+ font-weight: 500;
484
+ transition: color 0.5s;
485
+ }
486
+
487
+ .theme-dark .zg-output-empty {
488
+ color: #27272a;
489
+ }
490
+
491
+ .zg-glitch-overlay {
492
+ position: absolute;
493
+ inset: 0;
494
+ pointer-events: none;
495
+ opacity: 0;
496
+ transition: opacity 0.3s;
497
+ }
498
+
499
+ .zg-glitch-line {
500
+ position: absolute;
501
+ left: 0;
502
+ width: 100%;
503
+ height: 1px;
504
+ }
505
+
506
+ .zg-glitch-line-1 {
507
+ top: 25%;
508
+ background: rgba(147, 51, 234, 0.2);
509
+ animation: zg-pulse 2s infinite;
510
+ }
511
+
512
+ .zg-glitch-line-2 {
513
+ top: 66%;
514
+ background: rgba(99, 102, 241, 0.2);
515
+ animation: zg-pulse 2s 1s infinite;
516
+ }
517
+
518
+ @keyframes zg-pulse {
519
+
520
+ 0%,
521
+ 100% {
522
+ opacity: 0.3
523
+ }
524
+
525
+ 50% {
526
+ opacity: 1
527
+ }
528
+ }
529
+
530
+ .zg-preview-note {
531
+ margin-top: 1rem;
532
+ text-align: center;
533
+ font-size: 0.65rem;
534
+ color: #94a3b8;
535
+ text-transform: uppercase;
536
+ letter-spacing: 0.1em;
537
+ }
538
+
539
+ .theme-dark .zg-preview-note {
540
+ color: #52525b;
541
+ }
542
+
543
+ /* slider thumb */
544
+ .zg-slider::-webkit-slider-thumb {
545
+ -webkit-appearance: none;
546
+ appearance: none;
547
+ width: 18px;
548
+ height: 18px;
549
+ background: #9333ea;
550
+ border-radius: 50%;
551
+ cursor: pointer;
552
+ border: 3px solid #fff;
553
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
554
+ }
555
+
556
+ .theme-dark .zg-slider::-webkit-slider-thumb {
557
+ border-color: #18181b;
558
+ }
package/src/tools.ts ADDED
@@ -0,0 +1,4 @@
1
+ import type { ToolDefinition } from './types';
2
+
3
+ export const ALL_TOOLS: ToolDefinition[] = [];
4
+
package/src/types.ts ADDED
@@ -0,0 +1,72 @@
1
+ import type { SEOSection } from '@jjlmoya/utils-shared';
2
+ import type { WithContext, Thing } from 'schema-dts';
3
+
4
+ export type { SEOSection };
5
+
6
+ export type KnownLocale =
7
+ | 'ar' | 'da' | 'de' | 'en' | 'es' | 'fi'
8
+ | 'fr' | 'it' | 'ja' | 'ko' | 'nb' | 'nl'
9
+ | 'pl' | 'pt' | 'ru' | 'sv' | 'tr' | 'zh';
10
+
11
+ export interface FAQItem {
12
+ question: string;
13
+ answer: string;
14
+ }
15
+
16
+ export interface BibliographyEntry {
17
+ name: string;
18
+ url: string;
19
+ }
20
+
21
+ export interface HowToStep {
22
+ name: string;
23
+ text: string;
24
+ }
25
+
26
+ export interface ToolLocaleContent<TUI extends Record<string, string> = Record<string, string>> {
27
+ slug: string;
28
+ title: string;
29
+ description: string;
30
+ ui: TUI;
31
+ seo: SEOSection[];
32
+ faqTitle?: string;
33
+ faq: FAQItem[];
34
+ bibliographyTitle?: string;
35
+ bibliography: BibliographyEntry[];
36
+ howTo: HowToStep[];
37
+ schemas: WithContext<Thing>[];
38
+ }
39
+
40
+ export interface CategoryLocaleContent {
41
+ slug: string;
42
+ title: string;
43
+ description: string;
44
+ seo: SEOSection[];
45
+ }
46
+
47
+ export type LocaleLoader<T> = () => Promise<T>;
48
+
49
+ export type LocaleMap<T> = Partial<Record<KnownLocale, LocaleLoader<T>>>;
50
+
51
+ export interface CreativeToolEntry<TUI extends Record<string, string> = Record<string, string>> {
52
+ id: string;
53
+ icons: {
54
+ bg: string;
55
+ fg: string;
56
+ };
57
+ i18n: LocaleMap<ToolLocaleContent<TUI>>;
58
+ }
59
+
60
+ export interface CreativeCategoryEntry {
61
+ icon: string;
62
+ tools: CreativeToolEntry[];
63
+ i18n: LocaleMap<CategoryLocaleContent>;
64
+ }
65
+
66
+ export interface ToolDefinition {
67
+ entry: CreativeToolEntry;
68
+ Component: unknown;
69
+ SEOComponent: unknown;
70
+ BibliographyComponent: unknown;
71
+ }
72
+