@jjlmoya/utils-developer 1.24.0 → 1.25.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-developer",
3
- "version": "1.24.0",
3
+ "version": "1.25.0",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -28,7 +28,7 @@
28
28
  "check": "astro check",
29
29
  "type-check": "astro check",
30
30
  "test": "vitest run",
31
- "preversion": "npm run lint && npm run test",
31
+ "preversion": "npm run lint && npm run test && npm run build",
32
32
  "postversion": "git push && git push --tags",
33
33
  "patch": "npm version patch",
34
34
  "minor": "npm version minor",
@@ -0,0 +1,60 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { ALL_ENTRIES } from '../entries';
3
+ import type { KnownLocale } from '../types';
4
+
5
+ interface ExpectedCounts {
6
+ seo: number;
7
+ faq: number;
8
+ howTo: number;
9
+ }
10
+
11
+ function countItems(arr: unknown[] | undefined): number {
12
+ return arr?.length ?? 0;
13
+ }
14
+
15
+ async function verifyLocaleParity(
16
+ entry: typeof ALL_ENTRIES[number],
17
+ loc: KnownLocale,
18
+ expected: ExpectedCounts,
19
+ ): Promise<void> {
20
+ const locContent = await entry.i18n[loc]?.();
21
+ expect(locContent, `Locale ${loc} missing content`).toBeDefined();
22
+
23
+ const locSeoCount = countItems(locContent?.seo);
24
+ const locFaqCount = countItems(locContent?.faq);
25
+ const locHowToCount = countItems(locContent?.howTo);
26
+
27
+ expect(
28
+ locSeoCount,
29
+ `Locale ${loc} SEO sections count (${locSeoCount}) must match EN (${expected.seo})`,
30
+ ).toBe(expected.seo);
31
+ expect(
32
+ locFaqCount,
33
+ `Locale ${loc} FAQ items count (${locFaqCount}) must match EN (${expected.faq})`,
34
+ ).toBe(expected.faq);
35
+ expect(
36
+ locHowToCount,
37
+ `Locale ${loc} HowTo steps count (${locHowToCount}) must match EN (${expected.howTo})`,
38
+ ).toBe(expected.howTo);
39
+ }
40
+
41
+ describe('SEO & i18n Structural Parity Suite', () => {
42
+ ALL_ENTRIES.forEach((entry) => {
43
+ describe(`Tool: ${entry.id}`, () => {
44
+ it('all 15 locales should have identical SEO section counts and types as English', async () => {
45
+ const enContent = await entry.i18n.en?.();
46
+ expect(enContent).toBeDefined();
47
+ const expected: ExpectedCounts = {
48
+ seo: countItems(enContent?.seo),
49
+ faq: countItems(enContent?.faq),
50
+ howTo: countItems(enContent?.howTo),
51
+ };
52
+
53
+ const locales = Object.keys(entry.i18n) as KnownLocale[];
54
+ for (const loc of locales) {
55
+ await verifyLocaleParity(entry, loc, expected);
56
+ }
57
+ });
58
+ });
59
+ });
60
+ });
@@ -0,0 +1,124 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { ALL_ENTRIES } from '../entries';
3
+
4
+ const COPY_THRESHOLD = 0.9;
5
+
6
+ const STRUCTURAL_KEYS = new Set([
7
+ '@context',
8
+ '@type',
9
+ 'applicationCategory',
10
+ 'columns',
11
+ 'highlight',
12
+ 'icon',
13
+ 'level',
14
+ 'operatingSystem',
15
+ 'position',
16
+ 'positive',
17
+ 'price',
18
+ 'priceCurrency',
19
+ 'slug',
20
+ 'trend',
21
+ 'type',
22
+ 'url',
23
+ 'value',
24
+ 'variant',
25
+ ]);
26
+
27
+ function normalizeText(value: string): string {
28
+ return value
29
+ .replace(/<[^>]*>/g, ' ')
30
+ .replace(/&(?:amp|lt|gt|quot|apos|nbsp);/gi, ' ')
31
+ .replace(/[\u2018\u2019]/g, "'")
32
+ .replace(/[\u201c\u201d]/g, '"')
33
+ .replace(/\s+/g, ' ')
34
+ .trim()
35
+ .toLocaleLowerCase();
36
+ }
37
+
38
+ function collectText(value: unknown, path: string, parts: string[]): void {
39
+ if (typeof value === 'string') {
40
+ const normalized = normalizeText(value);
41
+ if (normalized.length >= 2) parts.push(normalized);
42
+ return;
43
+ }
44
+
45
+ if (Array.isArray(value)) {
46
+ value.forEach((item, index) => collectText(item, `${path}[${index}]`, parts));
47
+ return;
48
+ }
49
+
50
+ if (!value || typeof value !== 'object') return;
51
+
52
+ Object.entries(value).forEach(([key, child]) => {
53
+ if (STRUCTURAL_KEYS.has(key)) return;
54
+ collectText(child, `${path}.${key}`, parts);
55
+ });
56
+ }
57
+
58
+ function localeCorpus(content: unknown): string {
59
+ if (!content || typeof content !== 'object') return '';
60
+
61
+ const record = content as Record<string, unknown>;
62
+ const parts: string[] = [];
63
+ collectText(record.title, 'title', parts);
64
+ collectText(record.description, 'description', parts);
65
+ collectText(record.faqTitle, 'faqTitle', parts);
66
+ collectText(record.faq, 'faq', parts);
67
+ collectText(record.seo, 'seo', parts);
68
+ collectText(record.schemas, 'schemas', parts);
69
+ return parts.join(' ');
70
+ }
71
+
72
+ function tokenCounts(text: string): Map<string, number> {
73
+ const counts = new Map<string, number>();
74
+ for (const token of text.match(/[\p{L}\p{N}]+/gu) ?? []) {
75
+ counts.set(token, (counts.get(token) ?? 0) + 1);
76
+ }
77
+ return counts;
78
+ }
79
+
80
+ function copySimilarity(left: string, right: string): number {
81
+ const leftCounts = tokenCounts(left);
82
+ const rightCounts = tokenCounts(right);
83
+ const leftTotal = [...leftCounts.values()].reduce((sum, count) => sum + count, 0);
84
+ const rightTotal = [...rightCounts.values()].reduce((sum, count) => sum + count, 0);
85
+ if (leftTotal === 0 || rightTotal === 0) return 0;
86
+
87
+ let shared = 0;
88
+ for (const [token, count] of leftCounts) {
89
+ shared += Math.min(count, rightCounts.get(token) ?? 0);
90
+ }
91
+ return (2 * shared) / (leftTotal + rightTotal);
92
+ }
93
+
94
+ describe('Locales must not copy another locale wholesale', () => {
95
+ ALL_ENTRIES.forEach((entry) => {
96
+ it(`${entry.id} is not at least ${COPY_THRESHOLD * 100}% identical to another locale`, async () => {
97
+ const corpora = new Map<string, string>();
98
+
99
+ for (const [locale, loader] of Object.entries(entry.i18n)) {
100
+ if (!loader) continue;
101
+ corpora.set(locale, localeCorpus(await loader()));
102
+ }
103
+
104
+ const locales = [...corpora.keys()];
105
+ const violations: string[] = [];
106
+
107
+ for (let leftIndex = 0; leftIndex < locales.length; leftIndex += 1) {
108
+ for (let rightIndex = leftIndex + 1; rightIndex < locales.length; rightIndex += 1) {
109
+ const left = locales[leftIndex];
110
+ const right = locales[rightIndex];
111
+ const similarity = copySimilarity(corpora.get(left) ?? '', corpora.get(right) ?? '');
112
+
113
+ if (similarity >= COPY_THRESHOLD) {
114
+ violations.push(`${left} ↔ ${right}: ${(similarity * 100).toFixed(1)}%`);
115
+ }
116
+ }
117
+ }
118
+
119
+ expect(violations, `Locale copy threshold exceeded in ${entry.id}`).toEqual([]);
120
+ });
121
+ });
122
+ });
123
+
124
+
@@ -116,24 +116,12 @@ export const content: ToolLocaleContent<CalculadoraTiempoDatosUI> = {
116
116
  type: 'paragraph',
117
117
  html: 'En la era digital actual, la velocidad de carga de un sitio web no es un lujo, sino una <strong>necesidad absoluta</strong>. Cada milisegundo cuenta cuando se trata de retener usuarios, mejorar el posicionamiento en buscadores y maximizar las conversiones. La realidad es que los usuarios modernos tienen expectativas muy altas: esperan que una página web cargue en menos de 3 segundos.',
118
118
  },
119
- {
120
- type: 'paragraph',
121
- html: 'El peso de tu sitio web es uno de los factores más directamente relacionados con la velocidad de carga. Esta calculadora te ayuda a entender exactamente cuánto tiempo pierden tus visitantes esperando que tu página se cargue.',
122
- },
123
119
  { type: 'title', text: 'El Impacto de la Velocidad en la Experiencia del Usuario', level: 3 },
124
120
  {
125
121
  type: 'paragraph',
126
122
  html: 'La velocidad de carga afecta directamente a varios aspectos críticos de tu presencia en línea. Los estudios demuestran que el 53% de los visitantes de sitios móviles abandonan una página si tarda más de 3 segundos en cargar.',
127
123
  },
128
- {
129
- type: 'paragraph',
130
- html: 'Las tasas de conversión caen un 7% por cada segundo adicional de latencia. Si tu tienda online pierde 100 ventas al día porque tu sitio tarda 5 segundos en lugar de 2, estás perdiendo decenas de miles de euros anualmente.',
131
- },
132
124
  { type: 'title', text: 'Entendiendo las Diferentes Velocidades de Conexión', level: 3 },
133
- {
134
- type: 'paragraph',
135
- html: 'Las velocidades de conexión varían enormemente según la geografía y la tecnología disponible:',
136
- },
137
125
  {
138
126
  type: 'table',
139
127
  headers: ['Tecnología', 'Velocidad', 'Disponibilidad', 'Tiempo de Carga (5MB)'],
@@ -63,7 +63,9 @@ export const content: ToolLocaleContent<CssBoxShadowGeneratorUI> = {
63
63
  { type: 'stats', columns: 3, items: [{ value: '5', label: 'Schattenebenen pro Element', icon: 'mdi:layers' }, { value: 'Live', label: 'Vorschau bei jeder Änderung', icon: 'mdi:eye' }, { value: '5', label: 'Schnellstart-Presets', icon: 'mdi:star' }] },
64
64
  { type: 'title', text: 'Mehrere Schatten für realistische Tiefe stapeln', level: 3 },
65
65
  { type: 'paragraph', html: 'Echte Schatten sind selten ein einheitlicher Blur. Einen engen Schatten nah am Element mit einem weicheren, breiteren zu stapeln erzeugt natürliche Tiefe. Nutze <strong>+</strong> zum Hinzufügen.' },
66
+ { type: 'title', text: 'Die einzelnen CSS-Steuerelemente verstehen', level: 3 },
66
67
  { type: 'table', headers: ['Steuerung', 'CSS-Wert', 'Effekt'], rows: [['Offset X', 'Erste Länge', 'Horizontale Verschiebung.'], ['Offset Y', 'Zweite Länge', 'Vertikale Verschiebung.'], ['Weichzeichner', 'Dritte Länge', 'Blur-Radius. Größer = weicher.'], ['Ausbreitung', 'Vierte Länge', 'Vergrößert oder verkleinert den Schatten.'], ['Farbe & Deckkraft', 'rgba()', 'Schattenfarbe mit unabhängiger Deckkraft.'], ['Innen', 'inset', 'Rendert den Schatten innerhalb des Elements.']] },
68
+ { type: 'tip', title: 'Arbeite mit einem dezenten Hintergrundraster', html: 'Die Vorschau zeigt ein Punkteraster, damit du die Ausdehnung des Schattens siehst. Mit negativem Spread bleibt er näher am Element.' },
67
69
  { type: 'summary', title: 'Empfohlener Workflow', items: ['Starte mit einem Preset.', 'Füge Ebenen für realistische Tiefe hinzu.', 'Nutze negativen Spread für schwebende Karten.', 'Kopiere das CSS und füge es ein.'] },
68
70
  ],
69
71
  };
@@ -63,7 +63,9 @@ export const content: ToolLocaleContent<CssBoxShadowGeneratorUI> = {
63
63
  { type: 'stats', columns: 3, items: [{ value: '5', label: 'Capas de sombra por elemento', icon: 'mdi:layers' }, { value: 'En vivo', label: 'Previsualización con cada cambio', icon: 'mdi:eye' }, { value: '5', label: 'Presets rápidos', icon: 'mdi:star' }] },
64
64
  { type: 'title', text: 'Apila múltiples sombras para profundidad realista', level: 3 },
65
65
  { type: 'paragraph', html: 'Las sombras del mundo real rara vez son un blur uniforme. Apilar una sombra ajustada cerca del elemento con otra más suave y amplia crea profundidad natural. Usa el botón <strong>+</strong> para añadir capas y las pestañas para cambiar entre ellas.' },
66
+ { type: 'title', text: 'Entender cada control', level: 3 },
66
67
  { type: 'table', headers: ['Control', 'Valor CSS', 'Efecto'], rows: [['Offset X', 'Primera longitud', 'Desplazamiento horizontal. Positivo mueve la sombra a la derecha.'], ['Offset Y', 'Segunda longitud', 'Desplazamiento vertical. Positivo mueve la sombra hacia abajo.'], ['Desenfoque', 'Tercera longitud', 'Radio de blur. Más grande crea sombras más suaves.'], ['Expansión', 'Cuarta longitud', 'Expande o encoge la sombra. Negativo encoge.'], ['Color y Opacidad', 'rgba()', 'Color de sombra con control de opacidad independiente.'], ['Interior', 'inset', 'Renderiza la sombra dentro del borde del elemento.']] },
68
+ { type: 'tip', title: 'Usa una cuadrícula de fondo sutil', html: 'La vista previa muestra una cuadrícula de puntos para ver cómo se extiende la sombra. Usa un spread negativo para mantenerla cerca del elemento.' },
67
69
  { type: 'summary', title: 'Flujo de trabajo recomendado', items: ['Empieza con un preset que coincida con tu dirección de diseño.', 'Añade capas para construir profundidad realista.', 'Usa spread negativo en la sombra suave para un efecto de tarjeta flotante.', 'Copia el CSS generado y pégalo en tu stylesheet.'] },
68
70
  ],
69
71
  };
@@ -63,7 +63,9 @@ export const content: ToolLocaleContent<CssBoxShadowGeneratorUI> = {
63
63
  { type: 'stats', columns: 3, items: [{ value: '5', label: 'Couches d\'ombre par élément', icon: 'mdi:layers' }, { value: 'Direct', label: 'Aperçu à chaque modification', icon: 'mdi:eye' }, { value: '5', label: 'Presets rapides', icon: 'mdi:star' }] },
64
64
  { type: 'title', text: 'Superposez plusieurs ombres pour une profondeur réaliste', level: 3 },
65
65
  { type: 'paragraph', html: 'Les ombres réelles sont rarement un flou uniforme. Superposer une ombre serrée près de l\'élément avec une ombre plus douce et large crée une profondeur naturelle. Utilisez <strong>+</strong> pour ajouter des couches.' },
66
+ { type: 'title', text: 'Comprendre chaque réglage', level: 3 },
66
67
  { type: 'table', headers: ['Contrôle', 'Valeur CSS', 'Effet'], rows: [['Offset X', '1re longueur', 'Déplacement horizontal.'], ['Offset Y', '2e longueur', 'Déplacement vertical.'], ['Flou', '3e longueur', 'Rayon de flou.'], ['Expansion', '4e longueur', 'Agrandit ou réduit l\'ombre.'], ['Couleur & Opacité', 'rgba()', 'Couleur avec opacité indépendante.'], ['Intérieur', 'inset', 'Ombre à l\'intérieur de l\'élément.']] },
68
+ { type: 'tip', title: 'Utilisez une grille discrète pour le fond', html: 'La zone d\'aperçu affiche une grille de points pour visualiser l\'extension de l\'ombre. Utilisez une propagation négative pour la rapprocher de l\'élément.' },
67
69
  { type: 'summary', title: 'Workflow recommandé', items: ['Commencez par un preset.', 'Ajoutez des couches pour une profondeur réaliste.', 'Utilisez un spread négatif pour un effet de carte flottante.', 'Copiez le CSS généré et collez-le.'] },
68
70
  ],
69
71
  };
@@ -63,7 +63,9 @@ export const content: ToolLocaleContent<CssBoxShadowGeneratorUI> = {
63
63
  { type: 'stats', columns: 3, items: [{ value: '5', label: 'Lapisan bayangan per elemen', icon: 'mdi:layers' }, { value: 'Live', label: 'Pratinjau setiap perubahan', icon: 'mdi:eye' }, { value: '5', label: 'Preset cepat', icon: 'mdi:star' }] },
64
64
  { type: 'title', text: 'Tumpuk beberapa bayangan untuk kedalaman realistis', level: 3 },
65
65
  { type: 'paragraph', html: 'Bayangan nyata jarang berupa blur seragam. Menumpuk bayangan ketat dekat elemen dengan yang lebih lembut dan lebar menciptakan kedalaman alami. Gunakan <strong>+</strong> untuk menambah lapisan.' },
66
+ { type: 'title', text: 'Memahami setiap kontrol', level: 3 },
66
67
  { type: 'table', headers: ['Kontrol', 'Nilai CSS', 'Efek'], rows: [['Offset X', 'Panjang ke-1', 'Pergeseran horizontal.'], ['Offset Y', 'Panjang ke-2', 'Pergeseran vertikal.'], ['Blur', 'Panjang ke-3', 'Radius blur.'], ['Sebaran', 'Panjang ke-4', 'Memperbesar atau mengecilkan bayangan.'], ['Warna & Opasitas', 'rgba()', 'Warna bayangan dengan opasitas independen.'], ['Dalam', 'inset', 'Bayangan di dalam batas elemen.']] },
68
+ { type: 'tip', title: 'Gunakan kisi latar belakang yang halus', html: 'Area pratinjau menampilkan kisi titik agar Anda dapat melihat jangkauan bayangan. Gunakan spread negatif untuk membuatnya lebih rapat dengan elemen.' },
67
69
  { type: 'summary', title: 'Alur kerja yang direkomendasikan', items: ['Mulai dengan preset.', 'Tambahkan lapisan untuk kedalaman realistis.', 'Gunakan spread negatif untuk efek kartu mengambang.', 'Salin CSS yang dihasilkan dan tempel.'] },
68
70
  ],
69
71
  };
@@ -63,7 +63,9 @@ export const content: ToolLocaleContent<CssBoxShadowGeneratorUI> = {
63
63
  { type: 'stats', columns: 3, items: [{ value: '5', label: 'Strati ombra per elemento', icon: 'mdi:layers' }, { value: 'Live', label: 'Anteprima ad ogni modifica', icon: 'mdi:eye' }, { value: '5', label: 'Preset rapidi', icon: 'mdi:star' }] },
64
64
  { type: 'title', text: 'Sovrapponi più ombre per profondità realistica', level: 3 },
65
65
  { type: 'paragraph', html: 'Le ombre reali non sono mai un blur uniforme. Sovrapporre un\'ombra stretta vicino all\'elemento con una più morbida e ampia crea profondità naturale. Usa <strong>+</strong> per aggiungere strati.' },
66
+ { type: 'title', text: 'Capire ogni controllo', level: 3 },
66
67
  { type: 'table', headers: ['Controllo', 'Valore CSS', 'Effetto'], rows: [['Offset X', '1a lunghezza', 'Spostamento orizzontale.'], ['Offset Y', '2a lunghezza', 'Spostamento verticale.'], ['Sfocatura', '3a lunghezza', 'Raggio di blur.'], ['Espansione', '4a lunghezza', 'Allarga o restringe l\'ombra.'], ['Colore & Opacità', 'rgba()', 'Colore ombra con opacità indipendente.'], ['Interno', 'inset', 'Ombra dentro il bordo dell\'elemento.']] },
68
+ { type: 'tip', title: 'Usa una griglia di sfondo discreta', html: 'L\'anteprima mostra una griglia a punti per vedere quanto si estende l\'ombra. Usa uno spread negativo per mantenerla più vicina all\'elemento.' },
67
69
  { type: 'summary', title: 'Workflow consigliato', items: ['Inizia con un preset.', 'Aggiungi strati per profondità realistica.', 'Usa spread negativo per effetto card fluttuante.', 'Copia il CSS e incollalo.'] },
68
70
  ],
69
71
  };
@@ -63,7 +63,9 @@ export const content: ToolLocaleContent<CssBoxShadowGeneratorUI> = {
63
63
  { type: 'stats', columns: 3, items: [{ value: '5', label: '要素あたりの影レイヤー', icon: 'mdi:layers' }, { value: 'ライブ', label: '変更ごとにプレビュー更新', icon: 'mdi:eye' }, { value: '5', label: 'クイックプリセット', icon: 'mdi:star' }] },
64
64
  { type: 'title', text: '複数の影を重ねてリアルな奥行きを', level: 3 },
65
65
  { type: 'paragraph', html: '実際の影は均一なぼかしではありません。要素の近くにタイトな影を、より柔らかく広い影と重ねることで自然な奥行きが生まれます。<strong>+</strong>でレイヤーを追加できます。' },
66
+ { type: 'title', text: '各コントロールを理解する', level: 3 },
66
67
  { type: 'table', headers: ['コントロール', 'CSS値', '効果'], rows: [['オフセットX', '1番目の長さ', '水平方向の変位。'], ['オフセットY', '2番目の長さ', '垂直方向の変位。'], ['ぼかし', '3番目の長さ', 'ぼかしの半径。'], ['スプレッド', '4番目の長さ', '影を拡大または縮小。'], ['色と不透明度', 'rgba()', '独立した不透明度の影の色。'], ['内側', 'inset', '要素の境界内に影を描画。']] },
68
+ { type: 'tip', title: '控えめな背景グリッドを使う', html: 'プレビューにはドットグリッドが表示され、影の広がりを確認できます。負のスプレッドを使うと影を要素の近くに保てます。' },
67
69
  { type: 'summary', title: '推奨ワークフロー', items: ['プリセットから始める。', 'レイヤーを追加してリアルな奥行きを。', '浮遊カード効果には負のスプレッドを使用。', '生成されたCSSをコピーして貼り付け。'] },
68
70
  ],
69
71
  };
@@ -63,7 +63,9 @@ export const content: ToolLocaleContent<CssBoxShadowGeneratorUI> = {
63
63
  { type: 'stats', columns: 3, items: [{ value: '5', label: '요소당 그림자 레이어', icon: 'mdi:layers' }, { value: '실시간', label: '변경 시 미리보기 업데이트', icon: 'mdi:eye' }, { value: '5', label: '빠른 프리셋', icon: 'mdi:star' }] },
64
64
  { type: 'title', text: '현실적인 깊이를 위해 여러 그림자 쌓기', level: 3 },
65
65
  { type: 'paragraph', html: '실제 그림자는 균일한 블러가 아닙니다. 요소 가까이에 타이트한 그림자를 더 부드럽고 넓은 그림자와 쌓으면 자연스러운 깊이가 만들어집니다. <strong>+</strong>로 레이어를 추가하세요.' },
66
+ { type: 'title', text: '각 CSS 제어 항목 이해하기', level: 3 },
66
67
  { type: 'table', headers: ['컨트롤', 'CSS 값', '효과'], rows: [['오프셋 X', '첫 번째 길이', '수평 변위.'], ['오프셋 Y', '두 번째 길이', '수직 변위.'], ['블러', '세 번째 길이', '블러 반경.'], ['스프레드', '네 번째 길이', '그림자 확대 또는 축소.'], ['색상 및 불투명도', 'rgba()', '독립적인 불투명도의 그림자 색상.'], ['내부', 'inset', '요소 경계 내부에 그림자.']] },
68
+ { type: 'tip', title: '은은한 배경 격자 사용하기', html: '미리보기 영역의 점 격자로 그림자의 확장 범위를 확인할 수 있습니다. 음수 spread를 사용하면 그림자를 요소 가까이에 둘 수 있습니다.' },
67
69
  { type: 'summary', title: '권장 워크플로우', items: ['프리셋으로 시작하세요.', '현실적인 깊이를 위해 레이어를 추가하세요.', '떠 있는 카드 효과에 음수 스프레드를 사용하세요.', '생성된 CSS를 복사하여 붙여넣으세요.'] },
68
70
  ],
69
71
  };
@@ -63,7 +63,9 @@ export const content: ToolLocaleContent<CssBoxShadowGeneratorUI> = {
63
63
  { type: 'stats', columns: 3, items: [{ value: '5', label: 'Schaduwlagen per element', icon: 'mdi:layers' }, { value: 'Live', label: 'Voorbeeld bij elke wijziging', icon: 'mdi:eye' }, { value: '5', label: 'Snelle presets', icon: 'mdi:star' }] },
64
64
  { type: 'title', text: 'Stapel meerdere schaduwen voor realistische diepte', level: 3 },
65
65
  { type: 'paragraph', html: 'Echte schaduwen zijn zelden uniform. Een strakke schaduw dicht bij het element stapelen met een zachtere, bredere creëert natuurlijke diepte. Gebruik <strong>+</strong> om lagen toe te voegen.' },
66
+ { type: 'title', text: 'Elke CSS-instelling begrijpen', level: 3 },
66
67
  { type: 'table', headers: ['Bediening', 'CSS-waarde', 'Effect'], rows: [['Offset X', '1e lengte', 'Horizontale verplaatsing.'], ['Offset Y', '2e lengte', 'Verticale verplaatsing.'], ['Vervaging', '3e lengte', 'Blur-radius.'], ['Spreiding', '4e lengte', 'Vergroot of verkleint de schaduw.'], ['Kleur & Dekking', 'rgba()', 'Schaduwkleur met onafhankelijke dekking.'], ['Binnen', 'inset', 'Schaduw binnen de elementrand.']] },
68
+ { type: 'tip', title: 'Gebruik een subtiel achtergrondraster', html: 'Het voorbeeld toont een raster met punten zodat je de uitloop van de schaduw ziet. Gebruik een negatieve spread om de schaduw dichter bij het element te houden.' },
67
69
  { type: 'summary', title: 'Aanbevolen workflow', items: ['Begin met een preset.', 'Voeg lagen toe voor realistische diepte.', 'Gebruik negatieve spread voor zwevende kaarten.', 'Kopieer de CSS en plak deze.'] },
68
70
  ],
69
71
  };
@@ -63,7 +63,9 @@ export const content: ToolLocaleContent<CssBoxShadowGeneratorUI> = {
63
63
  { type: 'stats', columns: 3, items: [{ value: '5', label: 'Warstw cienia na element', icon: 'mdi:layers' }, { value: 'Na żywo', label: 'Podgląd przy każdej zmianie', icon: 'mdi:eye' }, { value: '5', label: 'Szybkie presety', icon: 'mdi:star' }] },
64
64
  { type: 'title', text: 'Układaj wiele cieni dla realistycznej głębi', level: 3 },
65
65
  { type: 'paragraph', html: 'Prawdziwe cienie rzadko są jednolitym rozmyciem. Nakładanie ciasnego cienia blisko elementu z bardziej miękkim, szerszym tworzy naturalną głębię. Użyj <strong>+</strong> do dodawania warstw.' },
66
+ { type: 'title', text: 'Zrozumienie każdego ustawienia', level: 3 },
66
67
  { type: 'table', headers: ['Kontrolka', 'Wartość CSS', 'Efekt'], rows: [['Offset X', '1. długość', 'Przesunięcie poziome.'], ['Offset Y', '2. długość', 'Przesunięcie pionowe.'], ['Rozmycie', '3. długość', 'Promień rozmycia.'], ['Rozprz.', '4. długość', 'Powiększa lub zmniejsza cień.'], ['Kolor i przezrocz.', 'rgba()', 'Kolor cienia z niezależną przezroczystością.'], ['Wewn.', 'inset', 'Cień wewnątrz krawędzi elementu.']] },
68
+ { type: 'tip', title: 'Użyj delikatnej siatki tła', html: 'Podgląd pokazuje siatkę punktów, dzięki której widać zasięg cienia. Ujemny spread utrzymuje cień bliżej elementu.' },
67
69
  { type: 'summary', title: 'Zalecany workflow', items: ['Zacznij od presetu.', 'Dodaj warstwy dla realistycznej głębi.', 'Użyj ujemnego spread dla efektu unoszenia karty.', 'Skopiuj wygenerowany CSS i wklej.'] },
68
70
  ],
69
71
  };
@@ -63,7 +63,9 @@ export const content: ToolLocaleContent<CssBoxShadowGeneratorUI> = {
63
63
  { type: 'stats', columns: 3, items: [{ value: '5', label: 'Camadas de sombra por elemento', icon: 'mdi:layers' }, { value: 'Ao vivo', label: 'Pré-visualização a cada mudança', icon: 'mdi:eye' }, { value: '5', label: 'Presets rápidos', icon: 'mdi:star' }] },
64
64
  { type: 'title', text: 'Empilhe múltiplas sombras para profundidade realista', level: 3 },
65
65
  { type: 'paragraph', html: 'Sombras reais raramente são um blur uniforme. Empilhar uma sombra apertada perto do elemento com uma mais suave e ampla cria profundidade natural. Use <strong>+</strong> para adicionar camadas.' },
66
+ { type: 'title', text: 'Compreender cada controlo', level: 3 },
66
67
  { type: 'table', headers: ['Controle', 'Valor CSS', 'Efeito'], rows: [['Offset X', '1º comprimento', 'Deslocamento horizontal.'], ['Offset Y', '2º comprimento', 'Deslocamento vertical.'], ['Desfoque', '3º comprimento', 'Raio de blur.'], ['Expansão', '4º comprimento', 'Expande ou encolhe a sombra.'], ['Cor e Opacidade', 'rgba()', 'Cor da sombra com opacidade independente.'], ['Interno', 'inset', 'Sombra dentro da borda do elemento.']] },
68
+ { type: 'tip', title: 'Use uma grelha de fundo discreta', html: 'A pré-visualização mostra uma grelha de pontos para revelar a extensão da sombra. Use um spread negativo para a manter mais próxima do elemento.' },
67
69
  { type: 'summary', title: 'Workflow recomendado', items: ['Comece com um preset.', 'Adicione camadas para profundidade realista.', 'Use spread negativo para efeito de card flutuante.', 'Copie o CSS gerado e cole.'] },
68
70
  ],
69
71
  };
@@ -63,7 +63,9 @@ export const content: ToolLocaleContent<CssBoxShadowGeneratorUI> = {
63
63
  { type: 'stats', columns: 3, items: [{ value: '5', label: 'Слоёв тени на элемент', icon: 'mdi:layers' }, { value: 'Live', label: 'Предпросмотр при каждом изменении', icon: 'mdi:eye' }, { value: '5', label: 'Быстрых пресетов', icon: 'mdi:star' }] },
64
64
  { type: 'title', text: 'Накладывайте несколько теней для реалистичной глубины', level: 3 },
65
65
  { type: 'paragraph', html: 'Реальные тени редко бывают равномерным размытием. Наложение плотной тени близко к элементу с более мягкой и широкой создаёт естественную глубину. Используйте <strong>+</strong> для добавления слоёв.' },
66
+ { type: 'title', text: 'Разбор каждого параметра', level: 3 },
66
67
  { type: 'table', headers: ['Элемент', 'Значение CSS', 'Эффект'], rows: [['Смещ. X', '1-я длина', 'Горизонтальное смещение.'], ['Смещ. Y', '2-я длина', 'Вертикальное смещение.'], ['Размытие', '3-я длина', 'Радиус размытия.'], ['Размах', '4-я длина', 'Увеличивает или уменьшает тень.'], ['Цвет и прозрач.', 'rgba()', 'Цвет тени с независимой прозрачностью.'], ['Внутр.', 'inset', 'Тень внутри границы элемента.']] },
68
+ { type: 'tip', title: 'Используйте ненавязчивую фоновую сетку', html: 'В области предварительного просмотра видна точечная сетка, показывающая размер тени. Отрицательный spread удерживает ее ближе к элементу.' },
67
69
  { type: 'summary', title: 'Рекомендуемый рабочий процесс', items: ['Начните с пресета.', 'Добавьте слои для реалистичной глубины.', 'Используйте отрицательный spread для эффекта парящей карточки.', 'Скопируйте сгенерированный CSS и вставьте.'] },
68
70
  ],
69
71
  };
@@ -63,7 +63,9 @@ export const content: ToolLocaleContent<CssBoxShadowGeneratorUI> = {
63
63
  { type: 'stats', columns: 3, items: [{ value: '5', label: 'Skugglager per element', icon: 'mdi:layers' }, { value: 'Live', label: 'Förhandsvisning vid varje ändring', icon: 'mdi:eye' }, { value: '5', label: 'Snabba förval', icon: 'mdi:star' }] },
64
64
  { type: 'title', text: 'Stapla flera skuggor för realistiskt djup', level: 3 },
65
65
  { type: 'paragraph', html: 'Verkliga skuggor är sällan enhetlig oskärpa. Att stapla en tight skugga nära elementet med en mjukare, bredare skapar naturligt djup. Använd <strong>+</strong> för att lägga till lager.' },
66
+ { type: 'title', text: 'Förstå varje CSS-kontroll', level: 3 },
66
67
  { type: 'table', headers: ['Kontroll', 'CSS-värde', 'Effekt'], rows: [['Offset X', '1:a längd', 'Horisontell förskjutning.'], ['Offset Y', '2:a längd', 'Vertikal förskjutning.'], ['Oskärpa', '3:e längd', 'Oskärperadie.'], ['Spridning', '4:e längd', 'Expanderar eller krymper skuggan.'], ['Färg & Opacitet', 'rgba()', 'Skuggfärg med oberoende opacitet.'], ['Inre', 'inset', 'Skugga innanför elementkanten.']] },
68
+ { type: 'tip', title: 'Använd ett diskret bakgrundsrutnät', html: 'Förhandsvisningen visar ett punktnät så att du ser hur långt skuggan sträcker sig. Ett negativt spread håller den närmare elementet.' },
67
69
  { type: 'summary', title: 'Rekommenderat arbetsflöde', items: ['Börja med en förinställning.', 'Lägg till lager för realistiskt djup.', 'Använd negativ spread för svävande kort.', 'Kopiera CSS:en och klistra in.'] },
68
70
  ],
69
71
  };
@@ -63,7 +63,9 @@ export const content: ToolLocaleContent<CssBoxShadowGeneratorUI> = {
63
63
  { type: 'stats', columns: 3, items: [{ value: '5', label: 'Öğe başına gölge katmanı', icon: 'mdi:layers' }, { value: 'Canlı', label: 'Her değişiklikte önizleme', icon: 'mdi:eye' }, { value: '5', label: 'Hızlı hazır ayarlar', icon: 'mdi:star' }] },
64
64
  { type: 'title', text: 'Gerçekçi derinlik için birden fazla gölge istifleyin', level: 3 },
65
65
  { type: 'paragraph', html: 'Gerçek gölgeler nadiren tekdüze bulanıklıktır. Öğeye yakın sıkı bir gölgeyi daha yumuşak, daha geniş bir gölgeyle istiflemek doğal derinlik oluşturur. Katman eklemek için <strong>+</strong> kullanın.' },
66
+ { type: 'title', text: 'Her CSS kontrolünü anlama', level: 3 },
66
67
  { type: 'table', headers: ['Kontrol', 'CSS Değeri', 'Etki'], rows: [['Ofset X', '1. uzunluk', 'Yatay kaydırma.'], ['Ofset Y', '2. uzunluk', 'Dikey kaydırma.'], ['Bulanıklık', '3. uzunluk', 'Bulanıklık yarıçapı.'], ['Yayılma', '4. uzunluk', 'Gölgeyi büyütür veya küçültür.'], ['Renk & Opaklık', 'rgba()', 'Bağımsız opaklıklı gölge rengi.'], ['İç', 'inset', 'Öğe kenarının içinde gölge.']] },
68
+ { type: 'tip', title: 'Sade bir arka plan ızgarası kullanın', html: 'Önizleme alanındaki noktalı ızgara gölgenin ne kadar yayıldığını gösterir. Negatif spread, gölgeyi öğeye daha yakın tutar.' },
67
69
  { type: 'summary', title: 'Önerilen İş Akışı', items: ['Bir hazır ayarla başlayın.', 'Gerçekçi derinlik için katman ekleyin.', 'Yüzen kart efekti için negatif spread kullanın.', 'Oluşturulan CSS\'i kopyalayıp yapıştırın.'] },
68
70
  ],
69
71
  };
@@ -63,7 +63,9 @@ export const content: ToolLocaleContent<CssBoxShadowGeneratorUI> = {
63
63
  { type: 'stats', columns: 3, items: [{ value: '5', label: '每个元素的阴影图层', icon: 'mdi:layers' }, { value: '实时', label: '每次更改时预览更新', icon: 'mdi:eye' }, { value: '5', label: '快速预设', icon: 'mdi:star' }] },
64
64
  { type: 'title', text: '堆叠多个阴影实现逼真深度', level: 3 },
65
65
  { type: 'paragraph', html: '真实阴影很少是均匀模糊的。将紧贴元素的紧密阴影与更柔和、更宽的阴影堆叠在一起,创造自然深度。使用<strong>+</strong>添加图层。' },
66
+ { type: 'title', text: '理解每个 CSS 控件', level: 3 },
66
67
  { type: 'table', headers: ['控件', 'CSS值', '效果'], rows: [['X偏移', '第一个长度', '水平位移。'], ['Y偏移', '第二个长度', '垂直位移。'], ['模糊', '第三个长度', '模糊半径。'], ['扩散', '第四个长度', '扩大或缩小阴影。'], ['颜色和不透明度', 'rgba()', '带独立不透明度的阴影颜色。'], ['内阴影', 'inset', '在元素边框内渲染阴影。']] },
68
+ { type: 'tip', title: '使用低调的背景网格', html: '预览区域显示点状网格,便于观察阴影的延伸范围。使用负 spread 可以让阴影更贴近元素。' },
67
69
  { type: 'summary', title: '推荐工作流程', items: ['从预设开始。', '添加图层实现逼真深度。', '使用负spread实现悬浮卡片效果。', '复制生成的CSS并粘贴。'] },
68
70
  ],
69
71
  };
@@ -122,5 +122,10 @@ export const content: ToolLocaleContent<PromptLibraryUI> = {
122
122
  type: 'paragraph',
123
123
  html: 'Utilisez la notation <strong>[VARIABLE]</strong> dans vos prompts pour cr\u00e9er des champs \u00e0 remplir dynamiquement. Quand vous ouvrez une carte, des champs apparaissent pour chaque variable d\u00e9finie.',
124
124
  },
125
+ { type: 'title', text: 'Partager vos prompts', level: 3 },
126
+ {
127
+ type: 'paragraph',
128
+ html: 'Chaque prompt peut être partagé par une URL. Le bouton de partage génère un lien qui ouvre le formulaire prérempli avec le contenu du prompt, afin de pouvoir le réutiliser ou le transmettre facilement.',
129
+ },
125
130
  ],
126
131
  };