@jjlmoya/utils-developer 1.17.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.
Files changed (60) hide show
  1. package/package.json +1 -1
  2. package/src/category/index.ts +2 -1
  3. package/src/entries.ts +7 -1
  4. package/src/index.ts +3 -0
  5. package/src/tests/locale_completeness.test.ts +2 -2
  6. package/src/tests/tool_validation.test.ts +2 -2
  7. package/src/tool/calculadoraTiempoDatos/data-time-calculator-web-speed-impact.css +498 -499
  8. package/src/tool/cssBoxShadowGenerator/bibliography.astro +14 -0
  9. package/src/tool/cssBoxShadowGenerator/bibliography.ts +14 -0
  10. package/src/tool/cssBoxShadowGenerator/box-shadow-generator.css +376 -0
  11. package/src/tool/cssBoxShadowGenerator/component.astro +234 -0
  12. package/src/tool/cssBoxShadowGenerator/entry.ts +29 -0
  13. package/src/tool/cssBoxShadowGenerator/i18n/de.ts +69 -0
  14. package/src/tool/cssBoxShadowGenerator/i18n/en.ts +71 -0
  15. package/src/tool/cssBoxShadowGenerator/i18n/es.ts +69 -0
  16. package/src/tool/cssBoxShadowGenerator/i18n/fr.ts +69 -0
  17. package/src/tool/cssBoxShadowGenerator/i18n/id.ts +69 -0
  18. package/src/tool/cssBoxShadowGenerator/i18n/it.ts +69 -0
  19. package/src/tool/cssBoxShadowGenerator/i18n/ja.ts +69 -0
  20. package/src/tool/cssBoxShadowGenerator/i18n/ko.ts +69 -0
  21. package/src/tool/cssBoxShadowGenerator/i18n/nl.ts +69 -0
  22. package/src/tool/cssBoxShadowGenerator/i18n/pl.ts +69 -0
  23. package/src/tool/cssBoxShadowGenerator/i18n/pt.ts +69 -0
  24. package/src/tool/cssBoxShadowGenerator/i18n/ru.ts +69 -0
  25. package/src/tool/cssBoxShadowGenerator/i18n/sv.ts +69 -0
  26. package/src/tool/cssBoxShadowGenerator/i18n/tr.ts +69 -0
  27. package/src/tool/cssBoxShadowGenerator/i18n/zh.ts +69 -0
  28. package/src/tool/cssBoxShadowGenerator/index.ts +9 -0
  29. package/src/tool/cssBoxShadowGenerator/logic.ts +35 -0
  30. package/src/tool/cssBoxShadowGenerator/seo.astro +15 -0
  31. package/src/tool/cssBoxShadowGenerator/ui.ts +23 -0
  32. package/src/tool/visualCssGridFlexboxGenerator/bibliography.astro +14 -0
  33. package/src/tool/visualCssGridFlexboxGenerator/bibliography.ts +20 -0
  34. package/src/tool/visualCssGridFlexboxGenerator/canvas.ts +103 -0
  35. package/src/tool/visualCssGridFlexboxGenerator/component.astro +218 -0
  36. package/src/tool/visualCssGridFlexboxGenerator/entry.ts +30 -0
  37. package/src/tool/visualCssGridFlexboxGenerator/i18n/de.ts +110 -0
  38. package/src/tool/visualCssGridFlexboxGenerator/i18n/en.ts +256 -0
  39. package/src/tool/visualCssGridFlexboxGenerator/i18n/es.ts +110 -0
  40. package/src/tool/visualCssGridFlexboxGenerator/i18n/fr.ts +110 -0
  41. package/src/tool/visualCssGridFlexboxGenerator/i18n/id.ts +110 -0
  42. package/src/tool/visualCssGridFlexboxGenerator/i18n/it.ts +110 -0
  43. package/src/tool/visualCssGridFlexboxGenerator/i18n/ja.ts +110 -0
  44. package/src/tool/visualCssGridFlexboxGenerator/i18n/ko.ts +110 -0
  45. package/src/tool/visualCssGridFlexboxGenerator/i18n/nl.ts +110 -0
  46. package/src/tool/visualCssGridFlexboxGenerator/i18n/pl.ts +110 -0
  47. package/src/tool/visualCssGridFlexboxGenerator/i18n/pt.ts +110 -0
  48. package/src/tool/visualCssGridFlexboxGenerator/i18n/ru.ts +110 -0
  49. package/src/tool/visualCssGridFlexboxGenerator/i18n/sv.ts +110 -0
  50. package/src/tool/visualCssGridFlexboxGenerator/i18n/tr.ts +110 -0
  51. package/src/tool/visualCssGridFlexboxGenerator/i18n/zh.ts +110 -0
  52. package/src/tool/visualCssGridFlexboxGenerator/index.ts +11 -0
  53. package/src/tool/visualCssGridFlexboxGenerator/interactions.ts +45 -0
  54. package/src/tool/visualCssGridFlexboxGenerator/logic.ts +49 -0
  55. package/src/tool/visualCssGridFlexboxGenerator/presets.ts +63 -0
  56. package/src/tool/visualCssGridFlexboxGenerator/seo.astro +15 -0
  57. package/src/tool/visualCssGridFlexboxGenerator/ui.ts +58 -0
  58. package/src/tool/visualCssGridFlexboxGenerator/visual-css-grid-flexbox-generator.css +816 -0
  59. package/src/tool/visualCssGridFlexboxGenerator/workspace.ts +192 -0
  60. package/src/tools.ts +3 -1
@@ -0,0 +1,103 @@
1
+ import type { LayoutMode, LayoutState } from './logic';
2
+
3
+ export function getAllItems(canvas: HTMLElement): HTMLElement[] {
4
+ return Array.from(canvas.querySelectorAll<HTMLElement>('.vcg-item'));
5
+ }
6
+
7
+ export function getItemSpans(canvas: HTMLElement): Record<string, number> {
8
+ const spans: Record<string, number> = {};
9
+ for (const item of getAllItems(canvas)) {
10
+ const s = Number(item.dataset.span ?? '1');
11
+ if (s > 1) spans[item.dataset.item ?? ''] = s;
12
+ }
13
+ return spans;
14
+ }
15
+
16
+ export function refreshItemLabels(canvas: HTMLElement, itemPrefix: string) {
17
+ getAllItems(canvas).forEach((item, index) => {
18
+ item.dataset.item = String(index + 1);
19
+ const label = item.querySelector('span');
20
+ if (label) label.textContent = `${itemPrefix} ${index + 1}`;
21
+ });
22
+ }
23
+
24
+ export function makeItem(index: number, itemPrefix: string): HTMLElement {
25
+ const item = document.createElement('div');
26
+ const label = document.createElement('span');
27
+ item.className = 'vcg-item vcg-enter';
28
+ item.dataset.item = String(index);
29
+ item.dataset.span = '1';
30
+ label.textContent = `${itemPrefix} ${index}`;
31
+ item.append(label);
32
+ item.addEventListener('animationend', () => item.classList.remove('vcg-enter'));
33
+ return item;
34
+ }
35
+
36
+ function paintGridItem(item: HTMLElement, state: LayoutState) {
37
+ const span = Number(item.dataset.span ?? '1');
38
+ item.style.minWidth = `${state.itemSize}px`;
39
+ item.style.minHeight = `${state.itemSize}px`;
40
+ item.style.gridColumn = span > 1 ? `span ${span}` : '';
41
+ item.classList.toggle('vcg-spanned', span > 1);
42
+ }
43
+
44
+ function paintFlexItem(item: HTMLElement, state: LayoutState) {
45
+ item.style.minWidth = `${state.itemSize}px`;
46
+ item.style.minHeight = `${state.itemSize}px`;
47
+ item.style.gridColumn = '';
48
+ item.classList.remove('vcg-spanned');
49
+ }
50
+
51
+ export function paintCanvas(canvas: HTMLElement, state: LayoutState) {
52
+ canvas.style.display = state.mode;
53
+ canvas.style.gap = `${state.gap}px`;
54
+ canvas.style.justifyContent = state.justifyContent;
55
+ canvas.style.alignItems = state.alignItems;
56
+ canvas.style.width = `${state.width}px`;
57
+ canvas.style.minHeight = `${state.height}px`;
58
+
59
+ if (state.mode === 'grid') {
60
+ canvas.style.gridTemplateColumns = `repeat(${state.columns}, minmax(0, 1fr))`;
61
+ canvas.style.flexDirection = '';
62
+ canvas.style.flexWrap = '';
63
+ canvas.style.alignContent = '';
64
+ } else {
65
+ canvas.style.gridTemplateColumns = '';
66
+ canvas.style.flexDirection = state.flexDirection;
67
+ canvas.style.flexWrap = state.flexWrap;
68
+ canvas.style.alignContent = state.flexWrap !== 'nowrap' ? state.alignContent : '';
69
+ }
70
+
71
+ const painter = state.mode === 'grid' ? paintGridItem : paintFlexItem;
72
+ getAllItems(canvas).forEach((item) => painter(item, state));
73
+ }
74
+
75
+ export function toggleVisibility(mode: LayoutMode, wrapValue: string) {
76
+ document.querySelectorAll<HTMLElement>('.vcg-grid-only').forEach((el) => {
77
+ el.classList.toggle('vcg-hidden', mode !== 'grid');
78
+ });
79
+ document.querySelectorAll<HTMLElement>('.vcg-flex-only').forEach((el) => {
80
+ el.classList.toggle('vcg-hidden', mode !== 'flex');
81
+ });
82
+ const acw = document.getElementById('vcg-align-content-wrap');
83
+ if (acw && mode === 'flex') {
84
+ acw.classList.toggle('vcg-hidden', wrapValue === 'nowrap');
85
+ }
86
+ }
87
+
88
+ export function setLabelText(id: string, text: string) {
89
+ const el = document.getElementById(id);
90
+ if (el) el.textContent = text;
91
+ }
92
+
93
+ export function updateLabels(state: LayoutState) {
94
+ setLabelText('vcg-gap-value', `${state.gap}px`);
95
+ setLabelText('vcg-columns-value', String(state.columns));
96
+ setLabelText('vcg-width-value', `${state.width}px`);
97
+ setLabelText('vcg-height-value', `${state.height}px`);
98
+ setLabelText('vcg-item-size-value', `${state.itemSize}px`);
99
+ }
100
+
101
+ export function clearPresetActive() {
102
+ document.querySelectorAll('.vcg-preset-button').forEach((b) => b.classList.remove('vcg-preset-active'));
103
+ }
@@ -0,0 +1,218 @@
1
+ ---
2
+ import { Icon } from 'astro-icon/components';
3
+ import type { VisualCssGridFlexboxGeneratorUI } from './ui';
4
+ import './visual-css-grid-flexbox-generator.css';
5
+
6
+ interface Props {
7
+ ui?: Record<string, unknown>;
8
+ }
9
+
10
+ const { ui } = Astro.props;
11
+ const t = (ui ?? {}) as VisualCssGridFlexboxGeneratorUI;
12
+ ---
13
+
14
+ <div id="vcg-root" class="vcg-root" data-i18n={JSON.stringify(t)}>
15
+ <section class="vcg-card">
16
+ <div class="vcg-stage-panel">
17
+ <div class="vcg-toolbar">
18
+ <div class="vcg-mode" role="group" aria-label={t.modeLabel}>
19
+ <button id="vcg-flex" class="vcg-mode-button vcg-active" type="button" aria-pressed="true">
20
+ <Icon name="mdi:format-align-justify" />
21
+ {t.flexMode}
22
+ </button>
23
+ <button id="vcg-grid" class="vcg-mode-button" type="button" aria-pressed="false">
24
+ <Icon name="mdi:grid" />
25
+ {t.gridMode}
26
+ </button>
27
+ </div>
28
+ <div class="vcg-actions">
29
+ <button id="vcg-add" class="vcg-icon-button" type="button" aria-label={t.addItem} title={t.addItem}>
30
+ <Icon name="mdi:plus" />
31
+ </button>
32
+ <button id="vcg-remove" class="vcg-icon-button" type="button" aria-label={t['removeItem']} title={t['removeItem']}>
33
+ <Icon name="mdi:minus" />
34
+ </button>
35
+ <button id="vcg-reset" class="vcg-icon-button" type="button" aria-label={t.resetLayout} title={t.resetLayout}>
36
+ <Icon name="mdi:restore" />
37
+ </button>
38
+ </div>
39
+ </div>
40
+
41
+ <div class="vcg-presets" id="vcg-presets" role="group" aria-label={t.presetsLabel}>
42
+ <button class="vcg-preset-button" data-preset="navbar" type="button">
43
+ <Icon name="mdi:menu" />{t.presetNavbar}
44
+ </button>
45
+ <button class="vcg-preset-button" data-preset="cards" type="button">
46
+ <Icon name="mdi:view-dashboard" />{t.presetCards}
47
+ </button>
48
+ <button class="vcg-preset-button" data-preset="hero" type="button">
49
+ <Icon name="mdi:image-area" />{t.presetHero}
50
+ </button>
51
+ <button class="vcg-preset-button" data-preset="sidebar" type="button">
52
+ <Icon name="mdi:page-layout-sidebar-left" />{t.presetSidebar}
53
+ </button>
54
+ <button class="vcg-preset-button" data-preset="gallery" type="button">
55
+ <Icon name="mdi:view-gallery" />{t.presetGallery}
56
+ </button>
57
+ </div>
58
+
59
+ <div class="vcg-canvas-shell" id="vcg-canvas-shell">
60
+ <div id="vcg-canvas" class="vcg-canvas" aria-label={t.canvasLabel}>
61
+ <div class="vcg-item" data-item="1"><span>{t.itemPrefix} 1</span></div>
62
+ <div class="vcg-item" data-item="2"><span>{t.itemPrefix} 2</span></div>
63
+ <div class="vcg-item" data-item="3"><span>{t.itemPrefix} 3</span></div>
64
+ <div class="vcg-item" data-item="4"><span>{t.itemPrefix} 4</span></div>
65
+ <div class="vcg-item" data-item="5"><span>{t.itemPrefix} 5</span></div>
66
+ </div>
67
+ </div>
68
+ </div>
69
+
70
+ <div class="vcg-control-panel">
71
+ <div class="vcg-slider-grid">
72
+ <label class="vcg-control" for="vcg-gap">
73
+ <span>{t.gapLabel}</span>
74
+ <strong id="vcg-gap-value">24px</strong>
75
+ <input id="vcg-gap" type="range" min="0" max="80" value="24" />
76
+ </label>
77
+
78
+ <label class="vcg-control vcg-grid-only vcg-hidden" id="vcg-columns-wrap" for="vcg-columns">
79
+ <span>{t.columnsLabel}</span>
80
+ <strong id="vcg-columns-value">3</strong>
81
+ <input id="vcg-columns" type="range" min="1" max="8" value="3" />
82
+ </label>
83
+
84
+ <label class="vcg-control" for="vcg-width">
85
+ <span>{t.widthLabel}</span>
86
+ <strong id="vcg-width-value">760px</strong>
87
+ <input id="vcg-width" type="range" min="280" max="1200" value="760" />
88
+ </label>
89
+
90
+ <label class="vcg-control" for="vcg-height">
91
+ <span>{t.heightLabel}</span>
92
+ <strong id="vcg-height-value">380px</strong>
93
+ <input id="vcg-height" type="range" min="180" max="700" value="380" />
94
+ </label>
95
+
96
+ <label class="vcg-control" for="vcg-item-size">
97
+ <span>{t.itemSizeLabel}</span>
98
+ <strong id="vcg-item-size-value">104px</strong>
99
+ <input id="vcg-item-size" type="range" min="56" max="200" value="104" />
100
+ </label>
101
+ </div>
102
+
103
+ <div class="vcg-select-grid">
104
+ <label class="vcg-select-wrap vcg-flex-only" id="vcg-direction-wrap" for="vcg-direction">
105
+ <span>{t.directionLabel}</span>
106
+ <select id="vcg-direction" class="vcg-select">
107
+ <option value="row" selected>{t.directionRow}</option>
108
+ <option value="row-reverse">{t.directionRowReverse}</option>
109
+ <option value="column">{t.directionColumn}</option>
110
+ <option value="column-reverse">{t.directionColumnReverse}</option>
111
+ </select>
112
+ </label>
113
+
114
+ <label class="vcg-select-wrap vcg-flex-only" id="vcg-wrap-wrap" for="vcg-wrap">
115
+ <span>{t.wrapLabel}</span>
116
+ <select id="vcg-wrap" class="vcg-select">
117
+ <option value="nowrap">{t.wrapNoWrap}</option>
118
+ <option value="wrap" selected>{t.wrapWrap}</option>
119
+ <option value="wrap-reverse">{t.wrapWrapReverse}</option>
120
+ </select>
121
+ </label>
122
+
123
+ <label class="vcg-select-wrap" for="vcg-justify">
124
+ <span>{t.justifyLabel}</span>
125
+ <select id="vcg-justify" class="vcg-select">
126
+ <option value="start">{t.justifyStart}</option>
127
+ <option value="center" selected>{t.justifyCenter}</option>
128
+ <option value="end">{t.justifyEnd}</option>
129
+ <option value="space-between">{t.justifyBetween}</option>
130
+ <option value="space-around">{t.justifyAround}</option>
131
+ <option value="space-evenly">{t.justifyEvenly}</option>
132
+ </select>
133
+ </label>
134
+
135
+ <label class="vcg-select-wrap" for="vcg-align">
136
+ <span>{t.alignLabel}</span>
137
+ <select id="vcg-align" class="vcg-select">
138
+ <option value="start">{t.alignStart}</option>
139
+ <option value="center" selected>{t.alignCenter}</option>
140
+ <option value="end">{t.alignEnd}</option>
141
+ <option value="stretch">{t.alignStretch}</option>
142
+ <option value="baseline">{t.alignBaseline}</option>
143
+ </select>
144
+ </label>
145
+
146
+ <label class="vcg-select-wrap vcg-flex-only" id="vcg-align-content-wrap" for="vcg-align-content">
147
+ <span>{t.alignContentLabel}</span>
148
+ <select id="vcg-align-content" class="vcg-select">
149
+ <option value="start">{t.alignContentStart}</option>
150
+ <option value="center">{t.alignContentCenter}</option>
151
+ <option value="end">{t.alignContentEnd}</option>
152
+ <option value="space-between">{t.alignContentBetween}</option>
153
+ <option value="space-around">{t.alignContentAround}</option>
154
+ <option value="space-evenly">{t.alignContentEvenly}</option>
155
+ <option value="stretch" selected>{t.alignContentStretch}</option>
156
+ </select>
157
+ </label>
158
+ </div>
159
+
160
+ <div class="vcg-code-panel" id="vcg-code-panel">
161
+ <div class="vcg-code-header">
162
+ <h2>{t.codeTitle}</h2>
163
+ <button id="vcg-copy" class="vcg-copy-button" type="button" aria-label={t.copyCode} title={t.copyCode}>
164
+ <Icon name="mdi:content-copy" />
165
+ </button>
166
+ </div>
167
+ <pre id="vcg-code" class="vcg-code" aria-label={t.outputLabel}></pre>
168
+ </div>
169
+ </div>
170
+ </section>
171
+ </div>
172
+
173
+ <script>
174
+ import type { VisualCssGridFlexboxGeneratorUI } from './ui';
175
+ import { createWorkspace, setupSpanHandler, spawnConfetti } from './workspace';
176
+
177
+ const root = document.getElementById('vcg-root') as HTMLElement;
178
+ const t = JSON.parse(root.dataset.i18n ?? '{}') as VisualCssGridFlexboxGeneratorUI;
179
+ const ws = createWorkspace(root, t);
180
+
181
+ setupSpanHandler(ws.canvas, () => ws.readState().mode, t.itemPrefix, () => ws.update());
182
+
183
+ [
184
+ ws.gapInput, ws.columnsInput, ws.widthInput, ws.heightInput, ws.itemSizeInput,
185
+ ws.justifySelect, ws.alignSelect, ws.directionSelect, ws.wrapSelect, ws.alignContentSelect,
186
+ ].forEach((el) => {
187
+ el.addEventListener('input', () => ws.update());
188
+ el.addEventListener('change', () => ws.update());
189
+ });
190
+
191
+ document.getElementById('vcg-flex')?.addEventListener('click', () => ws.setMode('flex'));
192
+ document.getElementById('vcg-grid')?.addEventListener('click', () => ws.setMode('grid'));
193
+ document.getElementById('vcg-add')?.addEventListener('click', () => ws.addItem());
194
+ document.getElementById('vcg-remove')?.addEventListener('click', () => ws['removeItem']());
195
+ document.getElementById('vcg-reset')?.addEventListener('click', () => ws.resetLayout());
196
+
197
+ document.querySelectorAll('.vcg-preset-button').forEach((btn) => {
198
+ btn.addEventListener('click', () => {
199
+ const preset = (btn as HTMLElement).dataset.preset;
200
+ if (preset) ws.applyPreset(preset);
201
+ });
202
+ });
203
+
204
+ const copyBtn = document.getElementById('vcg-copy') as HTMLButtonElement | null;
205
+ copyBtn?.addEventListener('click', async () => {
206
+ const rect = copyBtn.getBoundingClientRect();
207
+ await navigator.clipboard.writeText(ws.code.textContent ?? '');
208
+ copyBtn.dataset.copied = t.copied;
209
+ copyBtn.classList.add('vcg-copied');
210
+ spawnConfetti(rect.left + rect.width / 2, rect.top);
211
+ window.setTimeout(() => {
212
+ delete copyBtn.dataset.copied;
213
+ copyBtn.classList.remove('vcg-copied');
214
+ }, 1400);
215
+ });
216
+
217
+ ws.update();
218
+ </script>
@@ -0,0 +1,30 @@
1
+ import type { DeveloperToolEntry, ToolLocaleContent } from '../../types';
2
+
3
+ import type { VisualCssGridFlexboxGeneratorUI } from './ui';
4
+
5
+ export type VisualCssGridFlexboxGeneratorLocaleContent = ToolLocaleContent<VisualCssGridFlexboxGeneratorUI>;
6
+
7
+ export const visualCssGridFlexboxGenerator: DeveloperToolEntry<VisualCssGridFlexboxGeneratorUI> = {
8
+ id: 'visual-css-grid-flexbox-generator',
9
+ icons: {
10
+ bg: 'mdi:view-grid-plus',
11
+ fg: 'mdi:drag-variant',
12
+ },
13
+ i18n: {
14
+ de: async () => (await import('./i18n/de')).content,
15
+ en: async () => (await import('./i18n/en')).content,
16
+ es: async () => (await import('./i18n/es')).content,
17
+ fr: async () => (await import('./i18n/fr')).content,
18
+ id: async () => (await import('./i18n/id')).content,
19
+ it: async () => (await import('./i18n/it')).content,
20
+ ja: async () => (await import('./i18n/ja')).content,
21
+ ko: async () => (await import('./i18n/ko')).content,
22
+ nl: async () => (await import('./i18n/nl')).content,
23
+ pl: async () => (await import('./i18n/pl')).content,
24
+ pt: async () => (await import('./i18n/pt')).content,
25
+ ru: async () => (await import('./i18n/ru')).content,
26
+ sv: async () => (await import('./i18n/sv')).content,
27
+ tr: async () => (await import('./i18n/tr')).content,
28
+ zh: async () => (await import('./i18n/zh')).content,
29
+ },
30
+ };
@@ -0,0 +1,110 @@
1
+ import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+ import type { VisualCssGridFlexboxGeneratorUI } from '../ui';
4
+ import { bibliography } from '../bibliography';
5
+
6
+ const slug = 'visueller-css-grid-flexbox-generator';
7
+ const title = 'Visueller CSS Grid & Flexbox Layout Generator';
8
+ const description = 'Gestalte responsive Layouts durch Verschieben visueller Blöcke, skalieren des Containers, anpassen der Ausrichtung und Verwenden von Presets - dann sauberes natives CSS sofort kopieren.';
9
+
10
+ const howTo = [
11
+ { name: 'Preset oder Flexbox / Grid wählen', text: 'Beginne mit einem Layout-Preset (Navbar, Cards, Hero, Sidebar, Gallery) oder wechsle manuell zwischen Flexbox und Grid.' },
12
+ { name: 'Layout skalieren', text: 'Ziehe den Container an der unteren Ecke, um zu testen, wie das Design auf verfügbaren Platz reagiert.' },
13
+ { name: 'Ausrichtung anpassen', text: 'Nutze Schieberegler und Dropdowns für Richtung, Wrap, Abstand, Spalten, justify-content, align-items, align-content, Breite, Höhe und Item-Größe.' },
14
+ { name: 'Produktionsreifes CSS kopieren', text: 'Kopiere das generierte CSS, wenn das visuelle Ergebnis der gewünschten Struktur entspricht. Keine Framework-Abhängigkeiten.' },
15
+ ];
16
+
17
+ const faq = [
18
+ { question: 'Wann sollte ich Flexbox statt CSS Grid verwenden?', answer: 'Nutze Flexbox bei eindimensionalen Layouts - Navigationsleisten, Button-Gruppen, umfließende Cards, zentrierte Inhalte. Grid eignet sich, wenn Zeilen und Spalten gemeinsam wichtig sind - Dashboards, Galerien, Formulare, strukturierte Seitenbereiche.' },
19
+ { question: 'Kann dieser Generator responsive Layouts erstellen?', answer: 'Ja. Das generierte CSS verwendet natives Flex-Wrapping oder Grid-Repeat-Spalten. Skaliere die visuelle Leinwand, um Abstände und Ausrichtung bei verschiedenen Größen zu testen.' },
20
+ { question: 'Warum fühlen sich justify-content und align-items in Grid und Flex unterschiedlich an?', answer: 'Flexbox verteilt Elemente entlang einer Haupt- und Querachse. Grid platziert Elemente zuerst in Tracks und richtet dann den Inhalt darin aus. Der Wechsel zwischen beiden Modi macht diesen Unterschied sofort sichtbar.' },
21
+ { question: 'Ist das generierte CSS an ein Framework gebunden?', answer: 'Nein. Das Ergebnis ist reines natives CSS. Verwende es in einfachem HTML, Astro, React, Vue, Svelte, Web Components oder jedem Projekt mit Standard-CSS.' },
22
+ { question: 'Wozu dienen die Layout-Presets?', answer: 'Presets beschleunigen gängige Layouts, damit du realistische Konfigurationen siehst, ohne bei Null anzufangen. Jedes Preset setzt Modus, Richtung, Wrap, Ausrichtung und Containergröße für ein praxisnahes Muster.' },
23
+ ];
24
+
25
+ const ui: VisualCssGridFlexboxGeneratorUI = {
26
+ modeLabel: 'Layout-Modus',
27
+ flexMode: 'Flexbox',
28
+ gridMode: 'Grid',
29
+ canvasLabel: 'Interaktive Layout-Leinwand',
30
+ addItem: 'Element hinzufügen',
31
+ removeItem: 'Element entfernen',
32
+ resetLayout: 'Layout zurücksetzen',
33
+ gapLabel: 'Abstand',
34
+ columnsLabel: 'Grid-Spalten',
35
+ widthLabel: 'Container-Breite',
36
+ heightLabel: 'Container-Höhe',
37
+ justifyLabel: 'Ausrichten',
38
+ alignLabel: 'Ausrichten',
39
+ itemSizeLabel: 'Elementgröße',
40
+ codeTitle: 'Generiertes CSS',
41
+ copyCode: 'CSS kopieren',
42
+ copied: 'Kopiert!',
43
+ dragHint: 'Canvas an der Ecke skalieren - CSS aktualisiert sich live!',
44
+ outputLabel: 'Generierte CSS-Ausgabe',
45
+ justifyStart: 'Start',
46
+ justifyCenter: 'Mitte',
47
+ justifyEnd: 'Ende',
48
+ justifyBetween: 'Space between',
49
+ justifyAround: 'Space around',
50
+ justifyEvenly: 'Space evenly',
51
+ alignStart: 'Start',
52
+ alignCenter: 'Mitte',
53
+ alignEnd: 'Ende',
54
+ alignStretch: 'Strecken',
55
+ alignBaseline: 'Basislinie',
56
+ itemPrefix: 'Block',
57
+ directionLabel: 'Richtung',
58
+ directionRow: 'Reihe →',
59
+ directionRowReverse: '← Reihe rev',
60
+ directionColumn: 'Spalte ↓',
61
+ directionColumnReverse: '↑ Spalte rev',
62
+ wrapLabel: 'Umbruch',
63
+ wrapNoWrap: 'Kein Umbruch',
64
+ wrapWrap: 'Umbruch',
65
+ wrapWrapReverse: 'Umbruch rev',
66
+ alignContentLabel: 'Inhalt ausr.',
67
+ alignContentStart: 'Start',
68
+ alignContentCenter: 'Mitte',
69
+ alignContentEnd: 'Ende',
70
+ alignContentBetween: 'Space between',
71
+ alignContentAround: 'Space around',
72
+ alignContentEvenly: 'Space evenly',
73
+ alignContentStretch: 'Strecken',
74
+ presetsLabel: 'Presets',
75
+ presetNavbar: 'Navbar',
76
+ presetCards: 'Cards',
77
+ presetHero: 'Hero',
78
+ presetSidebar: 'Sidebar',
79
+ presetGallery: 'Galerie',
80
+ shakeLimit: 'Mindestens 2 Elemente nötig!',
81
+ spanHint: 'Doppelklick auf ein Element ändert die Spaltenspanne (1-3) im Grid-Modus',
82
+ };
83
+
84
+ const faqSchema: WithContext<FAQPage> = { '@context': 'https://schema.org', '@type': 'FAQPage', mainEntity: faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })) };
85
+ const howToSchema: WithContext<HowTo> = { '@context': 'https://schema.org', '@type': 'HowTo', name: title, description, step: howTo.map((step, index) => ({ '@type': 'HowToStep', position: index + 1, name: step.name, text: step.text })) };
86
+ const appSchema: WithContext<SoftwareApplication> = { '@context': 'https://schema.org', '@type': 'SoftwareApplication', name: title, description, applicationCategory: 'DeveloperApplication', operatingSystem: 'Any', offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' } };
87
+
88
+ export const content: ToolLocaleContent<VisualCssGridFlexboxGeneratorUI> = {
89
+ slug, title, description, ui,
90
+ faqTitle: 'CSS-Layout-Generator FAQ',
91
+ faq,
92
+ bibliographyTitle: 'CSS Grid und Flexbox Referenzen',
93
+ bibliography,
94
+ howTo,
95
+ schemas: [appSchema, faqSchema, howToSchema],
96
+ seo: [
97
+ { type: 'title', text: 'Erstelle CSS-Layouts durch Beobachten des Verhaltens, nicht durch Auswendiglernen', level: 2 },
98
+ { type: 'paragraph', html: 'CSS Grid und Flexbox sind mächtig, weil sie Layout-Beziehungen statt fester Koordinaten beschreiben. Schwierig ist vorherzusagen, wie <strong>gap</strong>, <strong>justify-content</strong>, <strong>align-items</strong>, Richtung, Umbruch, Tracks und verfügbarer Platz interagieren. Dieser Generator verwandelt abstrakte Eigenschaften in einen Live-Spielplatz mit Presets und Echtzeit-CSS.' },
99
+ { type: 'stats', columns: 3, items: [{ value: '5', label: 'Schnellstart-Layout-Presets', icon: 'mdi:view-grid-plus' }, { value: 'Live', label: 'CSS aktualisiert bei jeder Änderung', icon: 'mdi:code-braces' }, { value: '0', label: 'Framework-Abhängigkeiten im CSS', icon: 'mdi:language-css3' }] },
100
+ { type: 'title', text: 'Flexbox vs Grid: Modell wählen vor Ausrichtung optimieren', level: 3 },
101
+ { type: 'comparative', columns: 2, items: [{ title: 'Flexbox', description: 'Am besten für eindimensionale Flüsse, wo Elemente in einer Reihe oder Spalte ausgerichtet sind und natürlich umbrechen.', icon: 'mdi:format-align-justify', highlight: true, points: ['Navigation', 'Button-Gruppen', 'Umfließende Cards', 'Zentrierter Inhalt'] }, { title: 'CSS Grid', description: 'Am besten für zweidimensionale Strukturen, wo Reihen und Spalten die Form der Komposition definieren.', icon: 'mdi:grid', points: ['Dashboards', 'Galerien', 'Formulare', 'Redaktionelle Bereiche'] }] },
102
+ { type: 'title', text: 'Was jede Steuerung lehrt', level: 3 },
103
+ { type: 'table', headers: ['Steuerung', 'CSS-Eigenschaft', 'Worauf achten'], rows: [['Richtung', '<code>flex-direction</code>', 'Wie die Hauptachse fließt - von Reihe zu Spalte wechseln ändert die gesamte Layout-Logik.'], ['Umbruch', '<code>flex-wrap</code>', 'Ob Elemente in einer Zeile bleiben oder bei Platzmangel umbrechen.'], ['Abstand', '<code>gap</code>', 'Der Raum zwischen Elementen ohne Ränder, die an Kanten brechen.'], ['Ausrichten', '<code>justify-content</code>', 'Wie freier Raum entlang der Hauptachse verteilt wird.'], ['Ausrichten', '<code>align-items</code>', 'Wie Elemente auf der Querachse sitzen.'], ['Inhalt ausr.', '<code>align-content</code>', 'Wie umgebrochene Flex-Zeilen oder Grid-Zeilen zusätzlichen Querachsen-Raum verteilen.'], ['Spalten', '<code>grid-template-columns</code>', 'Wie viele gleiche Tracks das Grid erstellt, bevor Elemente in die nächste Zeile umbrechen.'], ['Container-Größe', '<code>width</code> und <code>min-height</code>', 'Wie dasselbe CSS bei verändertem Platz reagiert.']] },
104
+ { type: 'tip', title: 'Zuerst skalieren, dann optimieren', html: 'Skaliere die Leinwand zuerst auf eine realistische Größe. Dann Abstand und Ausrichtung anpassen. So funktioniert das generierte CSS unter echten Bedingungen.' },
105
+ { type: 'title', text: 'Sauberes CSS zum Anpassen', level: 3 },
106
+ { type: 'code', ariaLabel: 'Beispiel generiertes CSS', code: '.layout-playground {\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n gap: 24px;\n justify-content: center;\n align-items: center;\n}' },
107
+ { type: 'diagnostic', variant: 'info', title: 'Warum visuelles Skalieren wichtig ist', html: 'Viele Layout-Bugs erscheinen nur, wenn der Container schmaler, höher oder mit anderer Elementanzahl gefüllt ist. Das Skalieren während der Live-CSS-Aktualisierung hilft, ungünstige Umbrüche und ausrichtungsbedingte Probleme zu erkennen.' },
108
+ { type: 'summary', title: 'Empfohlener Workflow', items: ['Preset wählen oder Flexbox für eindimensionale und Grid für zweidimensionale Strukturen.', 'Leinwand vor dem Kopieren skalieren, damit das Layout realistischen Einschränkungen standhält.', 'Abstand für Zwischenräume nutzen statt Ränder an jedem Kind.' , 'Generiertes CSS als Ausgangspunkt kopieren, dann projektspezifische Selektoren und Media Queries hinzufügen.'] },
109
+ ],
110
+ };