@jjlmoya/utils-creative 1.18.0 → 1.20.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jjlmoya/utils-creative",
3
- "version": "1.18.0",
3
+ "version": "1.20.0",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -34,18 +34,28 @@ export async function getStaticPaths() {
34
34
  ]),
35
35
  ) as Partial<Record<KnownLocale, string>>;
36
36
 
37
+ const firstLoader = entry.i18n.en ?? Object.values(entry.i18n)[0];
38
+ const englishSlug = firstLoader ? (await firstLoader()).slug : entry.id;
39
+
37
40
  for (const { locale, content } of localeContents) {
38
- const allToolsNav = await Promise.all(
39
- ALL_TOOLS.map(async ({ entry: navEntry }) => ({
40
- id: navEntry.id,
41
- title: (await navEntry.i18n[locale]!()).title,
42
- href: `/${locale}/${(await navEntry.i18n[locale]!()).slug}`,
43
- isActive: navEntry.id === entry.id,
44
- })),
45
- );
41
+ const allToolsNav = (
42
+ await Promise.all(
43
+ ALL_TOOLS.map(async ({ entry: navEntry }) => {
44
+ const loader = navEntry.i18n[locale] ?? navEntry.i18n.en;
45
+ if (!loader) return null;
46
+ const navContent = await loader();
47
+ return {
48
+ id: navEntry.id,
49
+ title: navContent.title,
50
+ href: `/${locale}/${navContent.slug}`,
51
+ isActive: navEntry.id === entry.id,
52
+ };
53
+ }),
54
+ )
55
+ ).filter(Boolean) as NavItem[];
46
56
  paths.push({
47
57
  params: { locale, slug: content.slug },
48
- props: { Component, locale, content, localeUrls, allToolsNav },
58
+ props: { Component, locale, content, localeUrls, allToolsNav, englishSlug },
49
59
  });
50
60
  }
51
61
  }
@@ -66,11 +76,16 @@ interface Props {
66
76
  content: ToolLocaleContent;
67
77
  localeUrls: Partial<Record<KnownLocale, string>>;
68
78
  allToolsNav: NavItem[];
79
+ englishSlug: string;
69
80
  }
70
81
 
71
- const { Component, locale, content, localeUrls, allToolsNav } = Astro.props;
82
+ const { Component, locale, content, localeUrls, allToolsNav, englishSlug } = Astro.props;
83
+
84
+ const cssFiles = import.meta.glob("../../tool/*/*.css", { query: "?raw", import: "default" });
85
+ const cssKey = Object.keys(cssFiles).find((k) => k.endsWith(`/${englishSlug}.css`));
86
+ const toolCss = cssKey ? await cssFiles[cssKey]() as string : "";
72
87
 
73
- const seoContent: UtilitySEOContent = { locale, sections: content.seo };
88
+ const seoContent: UtilitySEOContent = { locale, sections: content.seo ?? [] };
74
89
 
75
90
  const words = content.title.split(" ");
76
91
  const titleHighlight = words[0] || "";
@@ -89,8 +104,9 @@ const titleBase = words.slice(1).join(" ") || "";
89
104
  tools={allToolsNav}
90
105
  />
91
106
  <Fragment slot="head">
107
+ {toolCss ? <Fragment set:html={`<style is:inline>${toolCss}</style>`} /> : null}
92
108
  {
93
- content.schemas.map((schema: unknown) => (
109
+ ( content.schemas ?? []).map((schema: unknown) => (
94
110
  <script
95
111
  is:inline
96
112
  type="application/ld+json"
@@ -0,0 +1,56 @@
1
+ import type { ToolDefinition } from '../types';
2
+
3
+ export interface ToolExportValidationResult {
4
+ passed: boolean;
5
+ failures: string[];
6
+ }
7
+
8
+ function validateComponentType(
9
+ toolId: string,
10
+ componentName: string,
11
+ component: unknown,
12
+ failures: string[],
13
+ ): void {
14
+ if (typeof component !== 'function') {
15
+ failures.push(`${toolId}: ${componentName} is not a function (${typeof component})`);
16
+ }
17
+ }
18
+
19
+ async function validateComponentExecution(
20
+ toolId: string,
21
+ componentName: string,
22
+ fn: () => Promise<unknown>,
23
+ failures: string[],
24
+ ): Promise<void> {
25
+ try {
26
+ const result = await fn();
27
+ if (!result || typeof result !== 'object') {
28
+ failures.push(`${toolId}: ${componentName} import returned invalid result`);
29
+ }
30
+ } catch (error) {
31
+ failures.push(`${toolId}: ${componentName} execution error - ${error instanceof Error ? error.message : 'unknown'}`);
32
+ }
33
+ }
34
+
35
+ export async function validateToolExports(tools: ToolDefinition[]): Promise<ToolExportValidationResult> {
36
+ const failures: string[] = [];
37
+
38
+ for (const tool of tools) {
39
+ validateComponentType(tool.entry.id, 'Component', tool.Component, failures);
40
+ validateComponentType(tool.entry.id, 'SEOComponent', tool.SEOComponent, failures);
41
+ validateComponentType(tool.entry.id, 'BibliographyComponent', tool.BibliographyComponent, failures);
42
+
43
+ const componentFn = tool.Component as () => Promise<unknown>;
44
+ const seoFn = tool.SEOComponent as () => Promise<unknown>;
45
+ const bibFn = tool.BibliographyComponent as () => Promise<unknown>;
46
+
47
+ await validateComponentExecution(tool.entry.id, 'Component', componentFn, failures);
48
+ await validateComponentExecution(tool.entry.id, 'SEOComponent', seoFn, failures);
49
+ await validateComponentExecution(tool.entry.id, 'BibliographyComponent', bibFn, failures);
50
+ }
51
+
52
+ return {
53
+ passed: failures.length === 0,
54
+ failures,
55
+ };
56
+ }
@@ -0,0 +1,34 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { ALL_TOOLS } from '../tools';
3
+ import { validateToolExports } from './shared-test-helpers';
4
+
5
+ describe('Tool Exports Pattern Validation', () => {
6
+ describe('Component Exports Format', () => {
7
+ ALL_TOOLS.forEach((tool) => {
8
+ it(`${tool.entry.id}: Component should be a lazy-loaded function`, () => {
9
+ expect(typeof tool.Component).toBe('function');
10
+ expect(tool.Component).toBeInstanceOf(Function);
11
+ });
12
+
13
+ it(`${tool.entry.id}: SEOComponent should be a lazy-loaded function`, () => {
14
+ expect(typeof tool.SEOComponent).toBe('function');
15
+ expect(tool.SEOComponent).toBeInstanceOf(Function);
16
+ });
17
+
18
+ it(`${tool.entry.id}: BibliographyComponent should be a lazy-loaded function`, () => {
19
+ expect(typeof tool.BibliographyComponent).toBe('function');
20
+ expect(tool.BibliographyComponent).toBeInstanceOf(Function);
21
+ });
22
+ });
23
+ });
24
+
25
+ describe('Dynamic Import Validation', () => {
26
+ it('all tools must have functional dynamic imports', async () => {
27
+ const result = await validateToolExports(ALL_TOOLS);
28
+ if (!result.passed) {
29
+ throw new Error(`Tool export validation failed:\n${result.failures.join('\n')}`);
30
+ }
31
+ expect(result.passed).toBe(true);
32
+ });
33
+ });
34
+ });
@@ -18,7 +18,7 @@
18
18
  overflow: hidden;
19
19
  border: 1px solid rgba(255,255,255,0.2);
20
20
  }
21
- :global(.theme-dark) .bead-card {
21
+ .theme-dark .bead-card {
22
22
  background: rgba(17,24,39,0.85);
23
23
  border-color: rgba(55,65,81,0.3);
24
24
  }
@@ -53,7 +53,7 @@
53
53
  margin: 0;
54
54
  }
55
55
 
56
- :global(.theme-dark) .bead-header-title h2 {
56
+ .theme-dark .bead-header-title h2 {
57
57
  color: #fff;
58
58
  }
59
59
 
@@ -63,7 +63,7 @@
63
63
  margin: 0.25rem 0 0;
64
64
  }
65
65
 
66
- :global(.theme-dark) .bead-header-title p {
66
+ .theme-dark .bead-header-title p {
67
67
  color: #9ca3af;
68
68
  }
69
69
 
@@ -98,7 +98,7 @@
98
98
  border: 1px solid #e5e7eb;
99
99
  }
100
100
 
101
- :global(.theme-dark) .bead-slider-row {
101
+ .theme-dark .bead-slider-row {
102
102
  background: #1f2937;
103
103
  border-color: #374151;
104
104
  }
@@ -112,7 +112,7 @@
112
112
  background: #d1d5db;
113
113
  }
114
114
 
115
- :global(.theme-dark) .bead-slider {
115
+ .theme-dark .bead-slider {
116
116
  background: #4b5563;
117
117
  }
118
118
 
@@ -149,7 +149,7 @@
149
149
  border: 1px solid #e5e7eb;
150
150
  }
151
151
 
152
- :global(.theme-dark) .bead-options-row {
152
+ .theme-dark .bead-options-row {
153
153
  background: #1f2937;
154
154
  border-color: #374151;
155
155
  }
@@ -165,7 +165,7 @@
165
165
  white-space: nowrap;
166
166
  }
167
167
 
168
- :global(.theme-dark) .bead-checkbox-label {
168
+ .theme-dark .bead-checkbox-label {
169
169
  color: #d1d5db;
170
170
  }
171
171
 
@@ -182,7 +182,7 @@
182
182
  background: #d1d5db;
183
183
  }
184
184
 
185
- :global(.theme-dark) .bead-divider {
185
+ .theme-dark .bead-divider {
186
186
  background: #4b5563;
187
187
  }
188
188
 
@@ -234,7 +234,7 @@
234
234
  transition: border-color 0.2s, background 0.2s, transform 0.2s;
235
235
  cursor: pointer;
236
236
  }
237
- :global(.theme-dark) .bead-drop-zone {
237
+ .theme-dark .bead-drop-zone {
238
238
  border-color: #374151;
239
239
  background: rgba(31,41,55,0.5);
240
240
  }
@@ -284,7 +284,7 @@
284
284
  margin: 0;
285
285
  }
286
286
 
287
- :global(.theme-dark) .bead-drop-content h3 {
287
+ .theme-dark .bead-drop-content h3 {
288
288
  color: #e5e7eb;
289
289
  }
290
290
 
@@ -344,13 +344,13 @@
344
344
  gap: 0.5rem;
345
345
  }
346
346
 
347
- :global(.bead-palette-item) {
347
+ .bead-palette-item {
348
348
  display: flex;
349
349
  flex-direction: column;
350
350
  align-items: center;
351
351
  }
352
352
 
353
- :global(.bead-color-swatch) {
353
+ .bead-color-swatch {
354
354
  width: 100%;
355
355
  aspect-ratio: 1;
356
356
  border-radius: 50%;
@@ -362,21 +362,21 @@
362
362
  justify-content: center;
363
363
  }
364
364
 
365
- :global(.theme-dark .bead-color-swatch) {
365
+ .theme-dark .bead-color-swatch {
366
366
  border-color: #1f2937;
367
367
  outline-color: #374151;
368
368
  }
369
369
 
370
- :global(.bead-symbol) {
370
+ .bead-symbol {
371
371
  font-size: 0.75rem;
372
372
  font-weight: 700;
373
373
  user-select: none;
374
374
  }
375
- :global(.bead-symbol-dark) {
375
+ .bead-symbol-dark {
376
376
  color: #000;
377
377
  }
378
378
 
379
- :global(.bead-symbol-light) {
379
+ .bead-symbol-light {
380
380
  color: #fff;
381
381
  }
382
382
 
@@ -414,7 +414,7 @@
414
414
  text-align: center;
415
415
  }
416
416
 
417
- :global(.theme-dark) .bead-canvas-col {
417
+ .theme-dark .bead-canvas-col {
418
418
  background: #030712;
419
419
  border-color: #1f2937;
420
420
  }
@@ -439,7 +439,7 @@
439
439
  border-radius: 0.75rem;
440
440
  }
441
441
 
442
- :global(.theme-dark) .bead-tunnel-bar {
442
+ .theme-dark .bead-tunnel-bar {
443
443
  background: rgba(79,70,229,0.1);
444
444
  border-color: rgba(99,102,241,0.3);
445
445
  }
@@ -450,7 +450,7 @@
450
450
  margin: 0;
451
451
  }
452
452
 
453
- :global(.theme-dark) .bead-tunnel-title {
453
+ .theme-dark .bead-tunnel-title {
454
454
  color: #c7d2fe;
455
455
  }
456
456
 
@@ -460,7 +460,7 @@
460
460
  margin: 0.25rem 0 0;
461
461
  }
462
462
 
463
- :global(.theme-dark) .bead-tunnel-sub {
463
+ .theme-dark .bead-tunnel-sub {
464
464
  color: #818cf8;
465
465
  }
466
466
 
@@ -484,7 +484,7 @@
484
484
  background: #e0e7ff;
485
485
  }
486
486
 
487
- :global(.theme-dark) .bead-tunnel-btn {
487
+ .theme-dark .bead-tunnel-btn {
488
488
  background: #1f2937;
489
489
  color: #818cf8;
490
490
  }
@@ -500,7 +500,7 @@
500
500
  text-align: center;
501
501
  }
502
502
 
503
- :global(.theme-dark) .bead-tunnel-display {
503
+ .theme-dark .bead-tunnel-display {
504
504
  background: #1f2937;
505
505
  color: #818cf8;
506
506
  border-color: rgba(99,102,241,0.3);
@@ -1,14 +1,19 @@
1
1
  ---
2
- import { SEORenderer } from '@jjlmoya/utils-shared';
3
- import { beadPatternGenerator } from './index';
4
- import type { KnownLocale } from '../../types';
2
+ import { SEORenderer } from "@jjlmoya/utils-shared";
3
+ import { beadPatternGenerator } from "./index";
4
+ import type { KnownLocale } from "../../types";
5
5
 
6
6
  interface Props {
7
7
  locale?: KnownLocale;
8
8
  }
9
9
 
10
- const { locale = 'es' } = Astro.props;
10
+ const { locale = "es" } = Astro.props;
11
11
  const content = await beadPatternGenerator.i18n[locale]?.();
12
+ if (!content) return null;
12
13
  ---
13
14
 
14
- {content && <SEORenderer content={{ locale, sections: content.seo }} />}
15
+ {
16
+ content.seo?.length > 0 && (
17
+ <SEORenderer content={{ locale, sections: content.seo }} />
18
+ )
19
+ }
@@ -25,7 +25,7 @@
25
25
  padding: 1rem;
26
26
  }
27
27
 
28
- :global(.theme-dark) .dr-root {
28
+ .theme-dark .dr-root {
29
29
  --dr-bg: #0f172a;
30
30
  --dr-bg-muted: #1e293b;
31
31
  --dr-bg-card: #1e293b;
@@ -147,7 +147,7 @@
147
147
  font-style: italic;
148
148
  }
149
149
 
150
- :global(.dr-pool-chip) {
150
+ .dr-pool-chip {
151
151
  display: flex;
152
152
  align-items: center;
153
153
  gap: 0.25rem;
@@ -157,13 +157,13 @@
157
157
  padding: 0.2rem 0.5rem 0.2rem 0.75rem;
158
158
  }
159
159
 
160
- :global(.dr-chip-label) {
160
+ .dr-chip-label {
161
161
  font-size: 0.8rem;
162
162
  font-weight: 700;
163
163
  color: var(--dr-primary);
164
164
  }
165
165
 
166
- :global(.dr-chip-remove) {
166
+ .dr-chip-remove {
167
167
  width: 1.125rem;
168
168
  height: 1.125rem;
169
169
  border-radius: 50%;
@@ -180,7 +180,7 @@
180
180
  padding: 0;
181
181
  }
182
182
 
183
- :global(.dr-chip-remove:hover) {
183
+ .dr-chip-remove:hover {
184
184
  background: var(--dr-primary-dark);
185
185
  }
186
186
 
@@ -350,7 +350,7 @@
350
350
  justify-content: center;
351
351
  }
352
352
 
353
- :global(.dr-result-pip) {
353
+ .dr-result-pip {
354
354
  display: flex;
355
355
  flex-direction: column;
356
356
  align-items: center;
@@ -363,24 +363,24 @@
363
363
  transition: border-color 0.2s;
364
364
  }
365
365
 
366
- :global(.dr-pip-max) {
366
+ .dr-pip-max {
367
367
  border-color: var(--dr-success);
368
368
  background: rgba(16, 185, 129, 0.1);
369
369
  }
370
370
 
371
- :global(.dr-pip-min) {
371
+ .dr-pip-min {
372
372
  border-color: var(--dr-danger);
373
373
  background: rgba(239, 68, 68, 0.1);
374
374
  }
375
375
 
376
- :global(.dr-pip-val) {
376
+ .dr-pip-val {
377
377
  font-size: 1.25rem;
378
378
  font-weight: 800;
379
379
  color: var(--dr-text);
380
380
  line-height: 1;
381
381
  }
382
382
 
383
- :global(.dr-pip-die) {
383
+ .dr-pip-die {
384
384
  font-size: 0.65rem;
385
385
  color: var(--dr-text-muted);
386
386
  font-weight: 600;
@@ -431,7 +431,7 @@
431
431
  margin: 1rem 0;
432
432
  }
433
433
 
434
- :global(.dr-history-item) {
434
+ .dr-history-item {
435
435
  display: grid;
436
436
  grid-template-columns: 1fr auto auto;
437
437
  align-items: center;
@@ -455,7 +455,7 @@
455
455
  }
456
456
  }
457
457
 
458
- :global(.dr-hist-expr) {
458
+ .dr-hist-expr {
459
459
  color: var(--dr-text-muted);
460
460
  font-size: 0.75rem;
461
461
  overflow: hidden;
@@ -463,13 +463,13 @@
463
463
  white-space: nowrap;
464
464
  }
465
465
 
466
- :global(.dr-hist-result) {
466
+ .dr-hist-result {
467
467
  font-weight: 800;
468
468
  font-size: 1rem;
469
469
  color: var(--dr-primary);
470
470
  }
471
471
 
472
- :global(.dr-hist-time) {
472
+ .dr-hist-time {
473
473
  font-size: 0.7rem;
474
474
  color: var(--dr-text-muted);
475
475
  }
@@ -9,6 +9,7 @@ interface Props {
9
9
 
10
10
  const { locale = 'es' } = Astro.props;
11
11
  const content = await diceRoller.i18n[locale]?.();
12
+ if (!content) return null;
12
13
  ---
13
14
 
14
- {content && <SEORenderer content={{ locale, sections: content.seo }} />}
15
+ {content.seo?.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}
@@ -16,7 +16,7 @@
16
16
  --excuse-generator-ring: rgba(236, 72, 153, 0.2);
17
17
  }
18
18
 
19
- :global(.theme-dark) .excuse-generator {
19
+ .theme-dark .excuse-generator {
20
20
  --excuse-generator-bg: #0f172a;
21
21
  --excuse-generator-bg-transparent: rgba(15, 23, 42, 0.8);
22
22
  --excuse-generator-bg-muted: #1e293b;
@@ -131,7 +131,7 @@
131
131
  transition-duration: 300ms;
132
132
  }
133
133
 
134
- :global(.theme-dark) .excuse-generator-slot {
134
+ .theme-dark .excuse-generator-slot {
135
135
  background-color: rgba(15, 23, 42, 0.6);
136
136
  }
137
137
 
@@ -233,7 +233,7 @@
233
233
  transition-duration: 300ms;
234
234
  }
235
235
 
236
- :global(.theme-dark) .excuse-generator-btn {
236
+ .theme-dark .excuse-generator-btn {
237
237
  background-color: #f8fafc;
238
238
  color: #0f172a;
239
239
  }
@@ -9,6 +9,7 @@ interface Props {
9
9
 
10
10
  const { locale = 'es' } = Astro.props;
11
11
  const content = await excuseGenerator.i18n[locale]?.();
12
+ if (!content) return null;
12
13
  ---
13
14
 
14
- {content && <SEORenderer content={{ locale, sections: content.seo }} />}
15
+ {content.seo?.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}
@@ -14,7 +14,7 @@
14
14
  --fortune-cookie-error: #f43f5e;
15
15
  }
16
16
 
17
- :global(.theme-dark) .fortune-cookie {
17
+ .theme-dark .fortune-cookie {
18
18
  --fortune-cookie-bg: #0f172a;
19
19
  --fortune-cookie-bg-muted: #1e293b;
20
20
  --fortune-cookie-text: #f8fafc;
@@ -80,7 +80,7 @@
80
80
  animation: fortune-cookie-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
81
81
  }
82
82
 
83
- :global(.theme-dark) .fortune-cookie-instruction {
83
+ .theme-dark .fortune-cookie-instruction {
84
84
  color: #fbbf24;
85
85
  }
86
86
 
@@ -238,7 +238,7 @@
238
238
  transform: rotate(-1deg);
239
239
  }
240
240
 
241
- :global(.fortune-cookie-number) {
241
+ .fortune-cookie-number {
242
242
  font-size: 0.75rem;
243
243
  line-height: 1rem;
244
244
  font-weight: 700;
@@ -300,7 +300,7 @@
300
300
  transition-duration: 150ms;
301
301
  }
302
302
 
303
- :global(.theme-dark) .fortune-cookie-share-btn {
303
+ .theme-dark .fortune-cookie-share-btn {
304
304
  background-color: #1e293b;
305
305
  color: #cbd5e1;
306
306
  }
@@ -309,7 +309,7 @@
309
309
  background-color: #e2e8f0;
310
310
  }
311
311
 
312
- :global(.theme-dark) .fortune-cookie-share-btn:hover {
312
+ .theme-dark .fortune-cookie-share-btn:hover {
313
313
  background-color: #334155;
314
314
  }
315
315
 
@@ -332,7 +332,7 @@
332
332
  40%, 60% { transform: translate3d(4px, 0, 0) rotate(4deg); }
333
333
  }
334
334
 
335
- :global(.fortune-cookie-crumb) {
335
+ .fortune-cookie-crumb {
336
336
  position: absolute;
337
337
  width: 20px;
338
338
  height: 20px;
@@ -9,6 +9,7 @@ interface Props {
9
9
 
10
10
  const { locale = 'es' } = Astro.props;
11
11
  const content = await fortuneCookie.i18n[locale]?.();
12
+ if (!content) return null;
12
13
  ---
13
14
 
14
- {content && <SEORenderer content={{ locale, sections: content.seo }} />}
15
+ {content.seo?.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}
@@ -9,6 +9,7 @@ interface Props {
9
9
 
10
10
  const { locale = 'es' } = Astro.props;
11
11
  const content = await synesthesiaPainter.i18n[locale]?.();
12
+ if (!content) return null;
12
13
  ---
13
14
 
14
- {content && <SEORenderer content={{ locale, sections: content.seo }} />}
15
+ {content.seo?.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}
@@ -11,7 +11,7 @@
11
11
  --sp-header-bg: rgba(255, 255, 255, 0.92);
12
12
  }
13
13
 
14
- :global(.theme-dark) .synesthesia-painter {
14
+ .theme-dark .synesthesia-painter {
15
15
  --sp-bg: #0f172a;
16
16
  --sp-bg-muted: #1e293b;
17
17
  --sp-text: #f8fafc;
@@ -171,7 +171,7 @@
171
171
  color: #ef4444;
172
172
  }
173
173
 
174
- :global(.synesthesia-painter-char) {
174
+ .synesthesia-painter-char {
175
175
  display: inline-block;
176
176
  color: var(--char-color);
177
177
  transition: color 0.3s cubic-bezier(0.4, 0, 0.2, 1);
@@ -190,7 +190,7 @@
190
190
  }
191
191
  }
192
192
 
193
- :global(.synesthesia-painter-viz-layer.dots .synesthesia-painter-char) {
193
+ .synesthesia-painter-viz-layer.dots .synesthesia-painter-char {
194
194
  color: transparent;
195
195
  font-size: 0;
196
196
  width: 1rem;
@@ -201,17 +201,17 @@
201
201
  box-shadow: 0 0 8px var(--char-color);
202
202
  }
203
203
 
204
- :global(.synesthesia-painter-viz-layer.aura .synesthesia-painter-char) {
204
+ .synesthesia-painter-viz-layer.aura .synesthesia-painter-char {
205
205
  color: rgba(255, 255, 255, 0.15);
206
206
  text-shadow: 0 0 18px var(--char-color), 0 0 36px var(--char-color);
207
207
  }
208
208
 
209
- :global(.theme-dark .synesthesia-painter-viz-layer.aura .synesthesia-painter-char) {
209
+ .theme-dark .synesthesia-painter-viz-layer.aura .synesthesia-painter-char {
210
210
  color: rgba(255, 255, 255, 0.85);
211
211
  text-shadow: 0 0 14px var(--char-color), 0 0 28px var(--char-color), 0 0 56px var(--char-color);
212
212
  }
213
213
 
214
- :global(.synesthesia-painter-cursor) {
214
+ .synesthesia-painter-cursor {
215
215
  display: inline-block;
216
216
  width: 2px;
217
217
  height: 2.5rem;
@@ -9,6 +9,7 @@ interface Props {
9
9
 
10
10
  const { locale = 'es' } = Astro.props;
11
11
  const content = await zalgoGenerator.i18n[locale]?.();
12
+ if (!content) return null;
12
13
  ---
13
14
 
14
- {content && <SEORenderer content={{ locale, sections: content.seo }} />}
15
+ {content.seo?.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}
@@ -35,7 +35,7 @@
35
35
  overflow: hidden;
36
36
  }
37
37
 
38
- :global(.theme-dark) .zg-config-card {
38
+ .theme-dark .zg-config-card {
39
39
  background: rgba(24, 24, 27, 0.85);
40
40
  border-color: #27272a;
41
41
  }
@@ -63,7 +63,7 @@
63
63
  margin: 0 0 1.5rem;
64
64
  }
65
65
 
66
- :global(.theme-dark) .zg-config-title {
66
+ .theme-dark .zg-config-title {
67
67
  color: #fff;
68
68
  }
69
69
 
@@ -93,7 +93,7 @@
93
93
  color: #64748b;
94
94
  }
95
95
 
96
- :global(.theme-dark) .zg-field-label {
96
+ .theme-dark .zg-field-label {
97
97
  color: #71717a;
98
98
  }
99
99
 
@@ -113,7 +113,7 @@
113
113
  accent-color: #9333ea;
114
114
  }
115
115
 
116
- :global(.theme-dark) .zg-slider {
116
+ .theme-dark .zg-slider {
117
117
  background: #27272a;
118
118
  }
119
119
 
@@ -140,11 +140,11 @@
140
140
  border-color: rgba(147, 51, 234, 0.15);
141
141
  }
142
142
 
143
- :global(.theme-dark) .zg-toggle-row {
143
+ .theme-dark .zg-toggle-row {
144
144
  background: rgba(9, 9, 11, 0.5);
145
145
  }
146
146
 
147
- :global(.theme-dark) .zg-toggle-row:hover {
147
+ .theme-dark .zg-toggle-row:hover {
148
148
  background: #27272a;
149
149
  }
150
150
 
@@ -154,7 +154,7 @@
154
154
  color: #374151;
155
155
  }
156
156
 
157
- :global(.theme-dark) .zg-toggle-label {
157
+ .theme-dark .zg-toggle-label {
158
158
  color: #d4d4d8;
159
159
  }
160
160
 
@@ -200,7 +200,7 @@
200
200
  transform: translateX(1.25rem);
201
201
  }
202
202
 
203
- :global(.theme-dark) .zg-switch-track {
203
+ .theme-dark .zg-switch-track {
204
204
  background: #3f3f46;
205
205
  }
206
206
 
@@ -226,11 +226,11 @@
226
226
  color: #9333ea;
227
227
  }
228
228
 
229
- :global(.theme-dark) .zg-reset-btn {
229
+ .theme-dark .zg-reset-btn {
230
230
  color: #52525b;
231
231
  }
232
232
 
233
- :global(.theme-dark) .zg-reset-btn:hover {
233
+ .theme-dark .zg-reset-btn:hover {
234
234
  color: #a855f7;
235
235
  }
236
236
 
@@ -249,7 +249,7 @@
249
249
  margin: 0;
250
250
  }
251
251
 
252
- :global(.theme-dark) .zg-warning p {
252
+ .theme-dark .zg-warning p {
253
253
  color: rgba(165, 180, 252, 0.8);
254
254
  }
255
255
 
@@ -269,7 +269,7 @@
269
269
  flex-direction: column;
270
270
  }
271
271
 
272
- :global(.theme-dark) .zg-editor-card {
272
+ .theme-dark .zg-editor-card {
273
273
  background: #09090b;
274
274
  border-color: #27272a;
275
275
  }
@@ -283,7 +283,7 @@
283
283
  background: rgba(248, 250, 252, 0.5);
284
284
  }
285
285
 
286
- :global(.theme-dark) .zg-editor-bar {
286
+ .theme-dark .zg-editor-bar {
287
287
  border-color: #18181b;
288
288
  background: rgba(24, 24, 27, 0.3);
289
289
  }
@@ -301,7 +301,7 @@
301
301
  letter-spacing: 0.05em;
302
302
  }
303
303
 
304
- :global(.theme-dark) .zg-editor-filename {
304
+ .theme-dark .zg-editor-filename {
305
305
  color: #52525b;
306
306
  }
307
307
 
@@ -318,7 +318,7 @@
318
318
  border: none;
319
319
  }
320
320
 
321
- :global(.theme-dark) .zg-textarea {
321
+ .theme-dark .zg-textarea {
322
322
  color: #e4e4e7;
323
323
  }
324
324
 
@@ -326,7 +326,7 @@
326
326
  color: #cbd5e1;
327
327
  }
328
328
 
329
- :global(.theme-dark) .zg-textarea::placeholder {
329
+ .theme-dark .zg-textarea::placeholder {
330
330
  color: #3f3f46;
331
331
  }
332
332
 
@@ -343,7 +343,7 @@
343
343
  color: #94a3b8;
344
344
  }
345
345
 
346
- :global(.theme-dark) .zg-char-count {
346
+ .theme-dark .zg-char-count {
347
347
  color: #3f3f46;
348
348
  }
349
349
 
@@ -353,7 +353,7 @@
353
353
  background: #e2e8f0;
354
354
  }
355
355
 
356
- :global(.theme-dark) .zg-footer-sep {
356
+ .theme-dark .zg-footer-sep {
357
357
  background: #27272a;
358
358
  }
359
359
 
@@ -394,12 +394,12 @@
394
394
  background: #e2e8f0;
395
395
  }
396
396
 
397
- :global(.theme-dark) .zg-clear-btn {
397
+ .theme-dark .zg-clear-btn {
398
398
  background: #18181b;
399
399
  color: #71717a;
400
400
  }
401
401
 
402
- :global(.theme-dark) .zg-clear-btn:hover {
402
+ .theme-dark .zg-clear-btn:hover {
403
403
  background: #27272a;
404
404
  }
405
405
 
@@ -438,7 +438,7 @@
438
438
  overflow: visible;
439
439
  }
440
440
 
441
- :global(.theme-dark) .zg-preview-body {
441
+ .theme-dark .zg-preview-body {
442
442
  background: #18181b;
443
443
  border-color: #27272a;
444
444
  }
@@ -479,7 +479,7 @@
479
479
  transition: color 0.5s;
480
480
  }
481
481
 
482
- :global(.theme-dark) .zg-output-empty {
482
+ .theme-dark .zg-output-empty {
483
483
  color: #27272a;
484
484
  }
485
485
 
@@ -529,7 +529,7 @@
529
529
  letter-spacing: 0.1em;
530
530
  }
531
531
 
532
- :global(.theme-dark) .zg-preview-note {
532
+ .theme-dark .zg-preview-note {
533
533
  color: #52525b;
534
534
  }
535
535
 
@@ -545,6 +545,6 @@
545
545
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
546
546
  }
547
547
 
548
- :global(.theme-dark) .zg-slider::-webkit-slider-thumb {
548
+ .theme-dark .zg-slider::-webkit-slider-thumb {
549
549
  border-color: #18181b;
550
550
  }