@jjlmoya/utils-developer 1.16.0 → 1.18.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 +1 -1
- package/src/category/index.ts +3 -1
- package/src/entries.ts +7 -1
- package/src/index.ts +4 -0
- package/src/tests/locale_completeness.test.ts +2 -2
- package/src/tests/tool_validation.test.ts +2 -2
- package/src/tool/calculadoraTiempoDatos/data-time-calculator-web-speed-impact.css +498 -499
- package/src/tool/jwtDecoder/bibliography.astro +14 -0
- package/src/tool/jwtDecoder/bibliography.ts +12 -0
- package/src/tool/jwtDecoder/component.astro +209 -0
- package/src/tool/jwtDecoder/entry.ts +30 -0
- package/src/tool/jwtDecoder/i18n/de.ts +248 -0
- package/src/tool/jwtDecoder/i18n/en.ts +247 -0
- package/src/tool/jwtDecoder/i18n/es.ts +248 -0
- package/src/tool/jwtDecoder/i18n/fr.ts +248 -0
- package/src/tool/jwtDecoder/i18n/id.ts +248 -0
- package/src/tool/jwtDecoder/i18n/it.ts +248 -0
- package/src/tool/jwtDecoder/i18n/ja.ts +248 -0
- package/src/tool/jwtDecoder/i18n/ko.ts +248 -0
- package/src/tool/jwtDecoder/i18n/nl.ts +247 -0
- package/src/tool/jwtDecoder/i18n/pl.ts +247 -0
- package/src/tool/jwtDecoder/i18n/pt.ts +247 -0
- package/src/tool/jwtDecoder/i18n/ru.ts +248 -0
- package/src/tool/jwtDecoder/i18n/sv.ts +248 -0
- package/src/tool/jwtDecoder/i18n/tr.ts +248 -0
- package/src/tool/jwtDecoder/i18n/zh.ts +248 -0
- package/src/tool/jwtDecoder/index.ts +11 -0
- package/src/tool/jwtDecoder/jwt-decoder-parser-and-claims-inspector.css +358 -0
- package/src/tool/jwtDecoder/logic.ts +96 -0
- package/src/tool/jwtDecoder/seo.astro +15 -0
- package/src/tool/jwtDecoder/ui.ts +38 -0
- package/src/tool/serpPixelSimulator/bibliography.astro +11 -4
- package/src/tool/serpPixelSimulator/index.ts +0 -4
- package/src/tool/serpPixelSimulator/seo.astro +8 -5
- package/src/tool/visualCssGridFlexboxGenerator/bibliography.astro +14 -0
- package/src/tool/visualCssGridFlexboxGenerator/bibliography.ts +20 -0
- package/src/tool/visualCssGridFlexboxGenerator/canvas.ts +103 -0
- package/src/tool/visualCssGridFlexboxGenerator/component.astro +218 -0
- package/src/tool/visualCssGridFlexboxGenerator/entry.ts +30 -0
- package/src/tool/visualCssGridFlexboxGenerator/i18n/de.ts +110 -0
- package/src/tool/visualCssGridFlexboxGenerator/i18n/en.ts +256 -0
- package/src/tool/visualCssGridFlexboxGenerator/i18n/es.ts +110 -0
- package/src/tool/visualCssGridFlexboxGenerator/i18n/fr.ts +110 -0
- package/src/tool/visualCssGridFlexboxGenerator/i18n/id.ts +110 -0
- package/src/tool/visualCssGridFlexboxGenerator/i18n/it.ts +110 -0
- package/src/tool/visualCssGridFlexboxGenerator/i18n/ja.ts +110 -0
- package/src/tool/visualCssGridFlexboxGenerator/i18n/ko.ts +110 -0
- package/src/tool/visualCssGridFlexboxGenerator/i18n/nl.ts +110 -0
- package/src/tool/visualCssGridFlexboxGenerator/i18n/pl.ts +110 -0
- package/src/tool/visualCssGridFlexboxGenerator/i18n/pt.ts +110 -0
- package/src/tool/visualCssGridFlexboxGenerator/i18n/ru.ts +110 -0
- package/src/tool/visualCssGridFlexboxGenerator/i18n/sv.ts +110 -0
- package/src/tool/visualCssGridFlexboxGenerator/i18n/tr.ts +110 -0
- package/src/tool/visualCssGridFlexboxGenerator/i18n/zh.ts +110 -0
- package/src/tool/visualCssGridFlexboxGenerator/index.ts +11 -0
- package/src/tool/visualCssGridFlexboxGenerator/interactions.ts +45 -0
- package/src/tool/visualCssGridFlexboxGenerator/logic.ts +49 -0
- package/src/tool/visualCssGridFlexboxGenerator/presets.ts +63 -0
- package/src/tool/visualCssGridFlexboxGenerator/seo.astro +15 -0
- package/src/tool/visualCssGridFlexboxGenerator/ui.ts +58 -0
- package/src/tool/visualCssGridFlexboxGenerator/visual-css-grid-flexbox-generator.css +816 -0
- package/src/tool/visualCssGridFlexboxGenerator/workspace.ts +192 -0
- package/src/tools.ts +3 -1
|
@@ -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 = 'visuell-css-grid-flexbox-generator';
|
|
7
|
+
const title = 'Visuell CSS Grid & Flexbox Layoutgenerator';
|
|
8
|
+
const description = 'Designa responsiva layouter genom att flytta visuella block, skala om behållaren, justera justering och använda förinställningar - kopiera sedan ren native CSS direkt.';
|
|
9
|
+
|
|
10
|
+
const howTo = [
|
|
11
|
+
{ name: 'Välj en förinställning eller Flexbox / Grid', text: 'Börja med en layoutförinställning (navbar, cards, hero, sidebar, gallery) eller växla mellan Flexbox och Grid manuellt.' },
|
|
12
|
+
{ name: 'Skala om layouten', text: 'Skala om behållaren från det nedre hörnet för att testa hur designen reagerar på tillgängligt utrymme.' },
|
|
13
|
+
{ name: 'Justera justeringskontroller', text: 'Använd reglage och rullgardiner för riktning, radbrytning, mellanrum, kolumner, justify-content, align-items, align-content, bredd, höjd och objektstorlek.' },
|
|
14
|
+
{ name: 'Kopiera produktionsklar CSS', text: 'Kopiera den genererade CSS:en när det visuella resultatet matchar önskad struktur. Inga ramverksberoenden.' },
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const faq = [
|
|
18
|
+
{ question: 'När ska jag använda Flexbox istället för CSS Grid?', answer: 'Använd Flexbox för endimensionella layouter - navigeringsfält, knappgrupper, radbrytande kort, centrerat innehåll. Grid när rader och kolumner spelar roll tillsammans - dashboards, gallerier, formulär, strukturerade sidavsnitt.' },
|
|
19
|
+
{ question: 'Kan denna generator skapa responsiva layouter?', answer: 'Ja. Den genererade CSS:en använder native flex-radbrytning eller upprepade grid-kolumner. Skala om den visuella canvasen för att testa mellanrum och justering vid olika storlekar.' },
|
|
20
|
+
{ question: 'Varför känns justify-content och align-items annorlunda i grid och flex?', answer: 'Flexbox fördelar objekt längs en huvudaxel och tväraxel. Grid placerar först objekt i spår och justerar sedan innehållet inuti dem. Att växla mellan lägena gör skillnaden omedelbart synlig.' },
|
|
21
|
+
{ question: 'Är den genererade CSS:en bunden till ett ramverk?', answer: 'Nej. Resultatet är ren native CSS. Använd den i vanlig HTML, Astro, React, Vue, Svelte, Web Components eller vilket projekt som helst som accepterar standard-CSS.' },
|
|
22
|
+
{ question: 'Vad är layoutförinställningarna till för?', answer: 'Förinställningar snabbar upp vanliga layouter så att du ser realistiska konfigurationer utan att börja från noll. Varje förinställning ställer in läge, riktning, radbrytning, justering och behållarstorlek för ett verkligt mönster.' },
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const ui: VisualCssGridFlexboxGeneratorUI = {
|
|
26
|
+
modeLabel: 'Layoutläge',
|
|
27
|
+
flexMode: 'Flexbox',
|
|
28
|
+
gridMode: 'Grid',
|
|
29
|
+
canvasLabel: 'Interaktiv layoutcanvas',
|
|
30
|
+
addItem: 'Lägg till objekt',
|
|
31
|
+
removeItem: 'Ta bort objekt',
|
|
32
|
+
resetLayout: 'Återställ layout',
|
|
33
|
+
gapLabel: 'Mellanrum',
|
|
34
|
+
columnsLabel: 'Grid-kolumner',
|
|
35
|
+
widthLabel: 'Behållarbredd',
|
|
36
|
+
heightLabel: 'Behållarhöjd',
|
|
37
|
+
justifyLabel: 'Justera',
|
|
38
|
+
alignLabel: 'Justera',
|
|
39
|
+
itemSizeLabel: 'Objektstorlek',
|
|
40
|
+
codeTitle: 'Genererad CSS',
|
|
41
|
+
copyCode: 'Kopiera CSS',
|
|
42
|
+
copied: 'Kopierat!',
|
|
43
|
+
dragHint: 'Skala om canvasen från hörnet - CSS uppdateras live!',
|
|
44
|
+
outputLabel: 'Genererad CSS-utdata',
|
|
45
|
+
justifyStart: 'Start',
|
|
46
|
+
justifyCenter: 'Mitten',
|
|
47
|
+
justifyEnd: 'Slut',
|
|
48
|
+
justifyBetween: 'Space between',
|
|
49
|
+
justifyAround: 'Space around',
|
|
50
|
+
justifyEvenly: 'Space evenly',
|
|
51
|
+
alignStart: 'Start',
|
|
52
|
+
alignCenter: 'Mitten',
|
|
53
|
+
alignEnd: 'Slut',
|
|
54
|
+
alignStretch: 'Sträck',
|
|
55
|
+
alignBaseline: 'Baslinje',
|
|
56
|
+
itemPrefix: 'Block',
|
|
57
|
+
directionLabel: 'Riktning',
|
|
58
|
+
directionRow: 'Rad →',
|
|
59
|
+
directionRowReverse: '← Rad omv',
|
|
60
|
+
directionColumn: 'Kolumn ↓',
|
|
61
|
+
directionColumnReverse: '↑ Kol omv',
|
|
62
|
+
wrapLabel: 'Radbryt',
|
|
63
|
+
wrapNoWrap: 'Ingen radbryt',
|
|
64
|
+
wrapWrap: 'Radbryt',
|
|
65
|
+
wrapWrapReverse: 'Radbryt omv',
|
|
66
|
+
alignContentLabel: 'Justera innehåll',
|
|
67
|
+
alignContentStart: 'Start',
|
|
68
|
+
alignContentCenter: 'Mitten',
|
|
69
|
+
alignContentEnd: 'Slut',
|
|
70
|
+
alignContentBetween: 'Space between',
|
|
71
|
+
alignContentAround: 'Space around',
|
|
72
|
+
alignContentEvenly: 'Space evenly',
|
|
73
|
+
alignContentStretch: 'Sträck',
|
|
74
|
+
presetsLabel: 'Förval',
|
|
75
|
+
presetNavbar: 'Navbar',
|
|
76
|
+
presetCards: 'Kort',
|
|
77
|
+
presetHero: 'Hero',
|
|
78
|
+
presetSidebar: 'Sidopanel',
|
|
79
|
+
presetGallery: 'Galleri',
|
|
80
|
+
shakeLimit: 'Minst 2 objekt krävs!',
|
|
81
|
+
spanHint: 'Dubbelklicka på ett objekt för att ändra kolumnspann (1-3) i Grid-läge',
|
|
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 layoutgenerator FAQ',
|
|
91
|
+
faq,
|
|
92
|
+
bibliographyTitle: 'CSS Grid och Flexbox referenser',
|
|
93
|
+
bibliography,
|
|
94
|
+
howTo,
|
|
95
|
+
schemas: [appSchema, faqSchema, howToSchema],
|
|
96
|
+
seo: [
|
|
97
|
+
{ type: 'title', text: 'Bygg CSS-layouter genom att se beteendet, inte memorera egenskaper', level: 2 },
|
|
98
|
+
{ type: 'paragraph', html: 'CSS Grid och Flexbox är kraftfulla eftersom de beskriver layoutrelationer istället för fasta koordinater. Det svåra är att förutsäga hur <strong>gap</strong>, <strong>justify-content</strong>, <strong>align-items</strong>, riktning, radbrytning, spår och tillgängligt utrymme samverkar. Denna generator förvandlar abstrakta egenskaper till en levande lekplats med förinställningar och realtids-CSS.' },
|
|
99
|
+
{ type: 'stats', columns: 3, items: [{ value: '5', label: 'Snabba layoutförval', icon: 'mdi:view-grid-plus' }, { value: 'Live', label: 'CSS uppdateras vid varje ändring', icon: 'mdi:code-braces' }, { value: '0', label: 'Ramverksberoenden i CSS', icon: 'mdi:language-css3' }] },
|
|
100
|
+
{ type: 'title', text: 'Flexbox vs Grid: välj modell innan du finslipar justering', level: 3 },
|
|
101
|
+
{ type: 'comparative', columns: 2, items: [{ title: 'Flexbox', description: 'Bäst för endimensionella flöden där objekt radar upp sig i rad eller kolumn och bryts naturligt.', icon: 'mdi:format-align-justify', highlight: true, points: ['Navigeringsfält', 'Knappgrupper', 'Radbrytande kort', 'Centrerat innehåll'] }, { title: 'CSS Grid', description: 'Bäst för tvådimensionella strukturer där rader och kolumner definierar kompositionens form.', icon: 'mdi:grid', points: ['Dashboards', 'Gallerier', 'Formulär', 'Redaktionella sektioner'] }] },
|
|
102
|
+
{ type: 'title', text: 'Vad varje kontroll lär dig', level: 3 },
|
|
103
|
+
{ type: 'table', headers: ['Kontroll', 'CSS-egenskap', 'Vad att titta på'], rows: [['Riktning', '<code>flex-direction</code>', 'Hur huvudaxeln flyter - byt från rad till kolumn för att ändra hela layoutlogiken.'], ['Radbryt', '<code>flex-wrap</code>', 'Om objekt stannar på en rad eller flödar till nya rader när utrymmet tar slut.'], ['Mellanrum', '<code>gap</code>', 'Utrymmet mellan objekt utan marginaler som bryts vid kanter.'], ['Justera', '<code>justify-content</code>', 'Hur fritt utrymme fördelas längs huvudaxeln.'], ['Justera', '<code>align-items</code>', 'Hur objekt placeras på tväraxeln.'], ['Justera innehåll', '<code>align-content</code>', 'Hur radbrutna flex-rader eller grid-rader fördelar extra tväraxelutrymme.'], ['Kolumner', '<code>grid-template-columns</code>', 'Hur många lika spår grid:en skapar innan objekt bryts till nästa rad.'], ['Behållarstorlek', '<code>width</code> och <code>min-height</code>', 'Hur samma CSS reagerar när tillgängligt utrymme ändras.']] },
|
|
104
|
+
{ type: 'tip', title: 'Skala först, optimera sedan', html: 'Börja med att skala canvasen till en realistisk storlek. Justera sedan mellanrum och justering. På så sätt fungerar den genererade CSS:en under verkliga förhållanden.' },
|
|
105
|
+
{ type: 'title', text: 'Ren CSS som du kan anpassa', level: 3 },
|
|
106
|
+
{ type: 'code', ariaLabel: 'Exempel på genererad 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: 'Varför visuell skalning är viktig', html: 'Många layoutbuggar uppträder bara när behållaren är smalare, högre eller fylld med olika antal objekt. Att skala medan CSS:en uppdateras live hjälper till att upptäcka besvärlig radbrytning och ömtåliga justeringsval.' },
|
|
108
|
+
{ type: 'summary', title: 'Rekommenderat arbetsflöde', items: ['Välj en förinställning eller Flexbox för endimensionella flöden och Grid för tvådimensionell struktur.', 'Skala canvasen innan du kopierar CSS så att layouten överlever realistiska begränsningar.', 'Använd gap för mellanrum mellan objekt istället för att lägga till marginaler på varje barn.', 'Kopiera den genererade CSS:en som utgångspunkt och lägg sedan till projektspecifika selektorer och media queries.'] },
|
|
109
|
+
],
|
|
110
|
+
};
|
|
@@ -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 = 'gorsel-css-grid-flexbox-olusturucu';
|
|
7
|
+
const title = 'Görsel CSS Grid & Flexbox Düzen Oluşturucu';
|
|
8
|
+
const description = 'Görsel blokları taşıyarak, kapsayıcıyı yeniden boyutlandırarak, hizalamayı ayarlayarak ve hazır ayarları kullanarak duyarlı düzenler tasarlayın - ardından temiz native CSS\'i anında kopyalayın.';
|
|
9
|
+
|
|
10
|
+
const howTo = [
|
|
11
|
+
{ name: 'Hazır ayar veya Flexbox / Grid seçin', text: 'Bir düzen hazır ayarıyla (navbar, cards, hero, sidebar, gallery) başlayın veya Flexbox ile Grid arasında manuel olarak geçiş yapın.' },
|
|
12
|
+
{ name: 'Düzeni yeniden boyutlandırın', text: 'Tasarımın mevcut alana nasıl tepki verdiğini test etmek için kapsayıcıyı alt köşeden yeniden boyutlandırın.' },
|
|
13
|
+
{ name: 'Hizalama kontrollerini ayarlayın', text: 'Yön, kaydırma, boşluk, sütunlar, justify-content, align-items, align-content, genişlik, yükseklik ve öğe boyutu için kaydırıcıları ve seçimleri kullanın.' },
|
|
14
|
+
{ name: 'Üretime hazır CSS kopyalayın', text: 'Görsel sonuç istediğiniz yapıyla eşleştiğinde oluşturulan CSS\'i kopyalayın. Framework bağımlılığı yok.' },
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const faq = [
|
|
18
|
+
{ question: 'Flexbox yerine ne zaman CSS Grid kullanmalıyım?', answer: 'Tek boyutlu düzenler için Flexbox kullanın - gezinme çubukları, düğme grupları, kaydırılan kartlar, ortalanmış içerik. Satır ve sütunların birlikte önemli olduğu durumlarda Grid - panolar, galeriler, formlar, yapılandırılmış sayfa bölümleri.' },
|
|
19
|
+
{ question: 'Bu oluşturucu duyarlı düzenler oluşturabilir mi?', answer: 'Evet. Oluşturulan CSS, yerel flex kaydırma veya tekrarlanan grid sütunları kullanır. Farklı boyutlarda boşluk ve hizalamayı test etmek için görsel tuvali yeniden boyutlandırın.' },
|
|
20
|
+
{ question: 'Justify-content ve align-items neden grid ve flex\'te farklı hissediliyor?', answer: 'Flexbox, öğeleri bir ana eksen ve çapraz eksen boyunca dağıtır. Grid, öğeleri önce izlere yerleştirir, sonra içeriği bunların içinde hizalar. İki mod arasında geçiş yapmak bu farkı hemen görünür kılar.' },
|
|
21
|
+
{ question: 'Oluşturulan CSS bir framework\'e bağlı mı?', answer: 'Hayır. Sonuç saf native CSS\'tir. Düz HTML, Astro, React, Vue, Svelte, Web Components veya standart CSS kabul eden herhangi bir projede kullanın.' },
|
|
22
|
+
{ question: 'Düzen hazır ayarları ne işe yarar?', answer: 'Hazır ayarlar, sıfırdan başlamadan gerçekçi yapılandırmaları görmek için yaygın düzenleri hızlandırır. Her hazır ayar, gerçek bir desen için mod, yön, kaydırma, hizalama ve kapsayıcı boyutunu ayarlar.' },
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const ui: VisualCssGridFlexboxGeneratorUI = {
|
|
26
|
+
modeLabel: 'Düzen modu',
|
|
27
|
+
flexMode: 'Flexbox',
|
|
28
|
+
gridMode: 'Grid',
|
|
29
|
+
canvasLabel: 'Etkileşimli düzen tuvali',
|
|
30
|
+
addItem: 'Öğe ekle',
|
|
31
|
+
removeItem: 'Öğe kaldır',
|
|
32
|
+
resetLayout: 'Düzeni sıfırla',
|
|
33
|
+
gapLabel: 'Boşluk',
|
|
34
|
+
columnsLabel: 'Grid sütunları',
|
|
35
|
+
widthLabel: 'Kapsayıcı genişliği',
|
|
36
|
+
heightLabel: 'Kapsayıcı yüksekliği',
|
|
37
|
+
justifyLabel: 'Hizala',
|
|
38
|
+
alignLabel: 'Hizala',
|
|
39
|
+
itemSizeLabel: 'Öğe boyutu',
|
|
40
|
+
codeTitle: 'Oluşturulan CSS',
|
|
41
|
+
copyCode: 'CSS kopyala',
|
|
42
|
+
copied: 'Kopyalandı!',
|
|
43
|
+
dragHint: 'Tuvali köşeden yeniden boyutlandırın - CSS canlı güncellenir!',
|
|
44
|
+
outputLabel: 'Oluşturulan CSS çıktısı',
|
|
45
|
+
justifyStart: 'Başlangıç',
|
|
46
|
+
justifyCenter: 'Orta',
|
|
47
|
+
justifyEnd: 'Son',
|
|
48
|
+
justifyBetween: 'Space between',
|
|
49
|
+
justifyAround: 'Space around',
|
|
50
|
+
justifyEvenly: 'Space evenly',
|
|
51
|
+
alignStart: 'Başlangıç',
|
|
52
|
+
alignCenter: 'Orta',
|
|
53
|
+
alignEnd: 'Son',
|
|
54
|
+
alignStretch: 'Uzat',
|
|
55
|
+
alignBaseline: 'Taban çizgisi',
|
|
56
|
+
itemPrefix: 'Blok',
|
|
57
|
+
directionLabel: 'Yön',
|
|
58
|
+
directionRow: 'Satır →',
|
|
59
|
+
directionRowReverse: '← Satır ters',
|
|
60
|
+
directionColumn: 'Sütun ↓',
|
|
61
|
+
directionColumnReverse: '↑ Sütun ters',
|
|
62
|
+
wrapLabel: 'Kaydırma',
|
|
63
|
+
wrapNoWrap: 'Kaydırma yok',
|
|
64
|
+
wrapWrap: 'Kaydır',
|
|
65
|
+
wrapWrapReverse: 'Ters kaydır',
|
|
66
|
+
alignContentLabel: 'İçeriği hizala',
|
|
67
|
+
alignContentStart: 'Başlangıç',
|
|
68
|
+
alignContentCenter: 'Orta',
|
|
69
|
+
alignContentEnd: 'Son',
|
|
70
|
+
alignContentBetween: 'Space between',
|
|
71
|
+
alignContentAround: 'Space around',
|
|
72
|
+
alignContentEvenly: 'Space evenly',
|
|
73
|
+
alignContentStretch: 'Uzat',
|
|
74
|
+
presetsLabel: 'Hazır Ayarlar',
|
|
75
|
+
presetNavbar: 'Navbar',
|
|
76
|
+
presetCards: 'Kartlar',
|
|
77
|
+
presetHero: 'Hero',
|
|
78
|
+
presetSidebar: 'Kenar çubuğu',
|
|
79
|
+
presetGallery: 'Galeri',
|
|
80
|
+
shakeLimit: 'En az 2 öğe gerekli!',
|
|
81
|
+
spanHint: 'Grid modunda sütun aralığını (1-3) değiştirmek için öğeye çift tıklayın',
|
|
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 düzen oluşturucu SSS',
|
|
91
|
+
faq,
|
|
92
|
+
bibliographyTitle: 'CSS Grid ve Flexbox referansları',
|
|
93
|
+
bibliography,
|
|
94
|
+
howTo,
|
|
95
|
+
schemas: [appSchema, faqSchema, howToSchema],
|
|
96
|
+
seo: [
|
|
97
|
+
{ type: 'title', text: 'CSS düzenlerini özellikleri ezberleyerek değil, davranışı görerek oluşturun', level: 2 },
|
|
98
|
+
{ type: 'paragraph', html: 'CSS Grid ve Flexbox güçlüdür çünkü sabit koordinatlar yerine düzen ilişkilerini tanımlar. Zor olan, <strong>gap</strong>, <strong>justify-content</strong>, <strong>align-items</strong>, yön, kaydırma, izler ve mevcut alanın nasıl etkileştiğini tahmin etmektir. Bu oluşturucu, soyut özellikleri hazır ayarlar ve gerçek zamanlı CSS ile canlı bir oyun alanına dönüştürür.' },
|
|
99
|
+
{ type: 'stats', columns: 3, items: [{ value: '5', label: 'Hızlı başlangıç hazır ayarı', icon: 'mdi:view-grid-plus' }, { value: 'Canlı', label: 'Her değişiklikte CSS güncellenir', icon: 'mdi:code-braces' }, { value: '0', label: 'CSS\'te framework bağımlılığı', icon: 'mdi:language-css3' }] },
|
|
100
|
+
{ type: 'title', text: 'Flexbox vs Grid: hizalamayı düzeltmeden önce modeli seçin', level: 3 },
|
|
101
|
+
{ type: 'comparative', columns: 2, items: [{ title: 'Flexbox', description: 'Öğelerin bir satır veya sütunda sıralandığı ve doğal olarak kaydığı tek boyutlu akışlar için en iyisi.', icon: 'mdi:format-align-justify', highlight: true, points: ['Gezinme çubukları', 'Düğme grupları', 'Kaydırılan kartlar', 'Ortalanmış içerik'] }, { title: 'CSS Grid', description: 'Satır ve sütunların kompozisyonun şeklini tanımladığı iki boyutlu yapılar için en iyisi.', icon: 'mdi:grid', points: ['Panolar', 'Galeriler', 'Formlar', 'Editoryal bölümler'] }] },
|
|
102
|
+
{ type: 'title', text: 'Her kontrol size ne öğretir', level: 3 },
|
|
103
|
+
{ type: 'table', headers: ['Kontrol', 'CSS özelliği', 'Nelere dikkat etmeli'], rows: [['Yön', '<code>flex-direction</code>', 'Ana eksen nasıl akar - satırdan sütuna geçmek tüm düzen mantığını değiştirir.'], ['Kaydırma', '<code>flex-wrap</code>', 'Öğelerin tek satırda kalıp kalmadığı veya alan bitince yeni satırlara akıp akmadığı.'], ['Boşluk', '<code>gap</code>', 'Kenarlarda kırılmayan öğeler arası boşluk.'], ['Hizala', '<code>justify-content</code>', 'Boş alanın ana eksen boyunca nasıl dağıtıldığı.'], ['Hizala', '<code>align-items</code>', 'Öğelerin çapraz eksende nasıl konumlandığı.'], ['İçeriği hizala', '<code>align-content</code>', 'Kaydırılmış flex satırlarının veya grid satırlarının ek çapraz eksen alanını nasıl dağıttığı.'], ['Sütunlar', '<code>grid-template-columns</code>', 'Grid\'in öğeler başka bir satıra geçmeden önce kaç eşit iz oluşturduğu.'], ['Kapsayıcı boyutu', '<code>width</code> ve <code>min-height</code>', 'Aynı CSS\'in mevcut alan değiştiğinde nasıl tepki verdiği.']] },
|
|
104
|
+
{ type: 'tip', title: 'Önce ölçeklendir, sonra optimize et', html: 'Önce tuvali gerçekçi bir boyuta ölçeklendirin. Ardından boşluk ve hizalamayı ayarlayın. Böylece oluşturulan CSS gerçek koşullarda çalışır.' },
|
|
105
|
+
{ type: 'title', text: 'Uyarlayabileceğiniz temiz CSS', level: 3 },
|
|
106
|
+
{ type: 'code', ariaLabel: 'Örnek oluşturulan 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: 'Görsel ölçeklendirme neden önemlidir', html: 'Birçok düzen hatası yalnızca kapsayıcı daha dar, daha yüksek veya farklı sayıda öğeyle doldurulduğunda ortaya çıkar. CSS canlı güncellenirken ölçeklendirmek, rahatsız edici kaydırmaları ve kırılgan hizalama seçimlerini tespit etmeye yardımcı olur.' },
|
|
108
|
+
{ type: 'summary', title: 'Önerilen iş akışı', items: ['Tek boyutlu akışlar için bir hazır ayar veya Flexbox, iki boyutlu yapı için Grid seçin.', 'Düzenin gerçekçi kısıtlamalara dayanması için CSS\'i kopyalamadan önce tuvali ölçeklendirin.', 'Her çocuğa kenar boşluğu eklemek yerine öğeler arası boşluk için gap kullanın.', 'Oluşturulan CSS\'i başlangıç noktası olarak kopyalayın, ardından projeye özel seçiciler ve medya sorguları ekleyin.'] },
|
|
109
|
+
],
|
|
110
|
+
};
|
|
@@ -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 = 'visual-css-grid-flexbox-generator';
|
|
7
|
+
const title = '可视化CSS Grid与Flexbox布局生成器';
|
|
8
|
+
const description = '通过移动可视化区块、调整容器大小、调整对齐方式并使用预设来设计响应式布局 - 然后即时复制干净的纯CSS。';
|
|
9
|
+
|
|
10
|
+
const howTo = [
|
|
11
|
+
{ name: '选择预设或Flexbox/Grid', text: '从布局预设(navbar、cards、hero、sidebar、gallery)开始,或手动在Flexbox和Grid之间切换。' },
|
|
12
|
+
{ name: '调整布局大小', text: '从右下角调整容器大小,测试设计如何响应可用空间。' },
|
|
13
|
+
{ name: '调整对齐控件', text: '使用滑块和下拉菜单调整方向、换行、间距、列数、justify-content、align-items、align-content、宽度、高度和项目大小。' },
|
|
14
|
+
{ name: '复制生产就绪的CSS', text: '当视觉效果符合目标结构时,复制生成的CSS。无框架依赖。' },
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const faq = [
|
|
18
|
+
{ question: '何时使用Flexbox而不是CSS Grid?', answer: '一维布局使用Flexbox - 导航栏、按钮组、换行卡片、居中内容。当行和列共同重要时使用Grid - 仪表板、画廊、表单、结构化页面区域。' },
|
|
19
|
+
{ question: '这个生成器能创建响应式布局吗?', answer: '可以。生成的CSS使用原生的flex换行或重复grid列。调整可视化画布大小以测试不同尺寸下的间距和对齐。' },
|
|
20
|
+
{ question: '为什么justify-content和align-items在grid和flex中感觉不同?', answer: 'Flexbox沿主轴和交叉轴分布项目。Grid先将项目放入轨道,然后在其中对齐内容。切换两种模式使这种差异立即可见。' },
|
|
21
|
+
{ question: '生成的CSS是否绑定到某个框架?', answer: '不。结果是纯原生CSS。可在纯HTML、Astro、React、Vue、Svelte、Web Components或任何接受标准CSS的项目中使用。' },
|
|
22
|
+
{ question: '布局预设有什么用途?', answer: '预设加速了常见布局,让你无需从零开始就能看到真实的配置。每个预设都根据真实模式设置模式、方向、换行、对齐和容器大小。' },
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const ui: VisualCssGridFlexboxGeneratorUI = {
|
|
26
|
+
modeLabel: '布局模式',
|
|
27
|
+
flexMode: 'Flexbox',
|
|
28
|
+
gridMode: 'Grid',
|
|
29
|
+
canvasLabel: '交互式布局画布',
|
|
30
|
+
addItem: '添加项目',
|
|
31
|
+
removeItem: '删除项目',
|
|
32
|
+
resetLayout: '重置布局',
|
|
33
|
+
gapLabel: '间距',
|
|
34
|
+
columnsLabel: 'Grid列数',
|
|
35
|
+
widthLabel: '容器宽度',
|
|
36
|
+
heightLabel: '容器高度',
|
|
37
|
+
justifyLabel: '水平对齐',
|
|
38
|
+
alignLabel: '垂直对齐',
|
|
39
|
+
itemSizeLabel: '项目大小',
|
|
40
|
+
codeTitle: '生成的CSS',
|
|
41
|
+
copyCode: '复制CSS',
|
|
42
|
+
copied: '已复制!',
|
|
43
|
+
dragHint: '从角落调整画布大小 - CSS实时更新!',
|
|
44
|
+
outputLabel: '生成的CSS输出',
|
|
45
|
+
justifyStart: '起始',
|
|
46
|
+
justifyCenter: '居中',
|
|
47
|
+
justifyEnd: '末尾',
|
|
48
|
+
justifyBetween: 'Space between',
|
|
49
|
+
justifyAround: 'Space around',
|
|
50
|
+
justifyEvenly: 'Space evenly',
|
|
51
|
+
alignStart: '起始',
|
|
52
|
+
alignCenter: '居中',
|
|
53
|
+
alignEnd: '末尾',
|
|
54
|
+
alignStretch: '拉伸',
|
|
55
|
+
alignBaseline: '基线',
|
|
56
|
+
itemPrefix: '区块',
|
|
57
|
+
directionLabel: '方向',
|
|
58
|
+
directionRow: '行 →',
|
|
59
|
+
directionRowReverse: '← 行 反向',
|
|
60
|
+
directionColumn: '列 ↓',
|
|
61
|
+
directionColumnReverse: '↑ 列 反向',
|
|
62
|
+
wrapLabel: '换行',
|
|
63
|
+
wrapNoWrap: '不换行',
|
|
64
|
+
wrapWrap: '换行',
|
|
65
|
+
wrapWrapReverse: '反向换行',
|
|
66
|
+
alignContentLabel: '内容对齐',
|
|
67
|
+
alignContentStart: '起始',
|
|
68
|
+
alignContentCenter: '居中',
|
|
69
|
+
alignContentEnd: '末尾',
|
|
70
|
+
alignContentBetween: 'Space between',
|
|
71
|
+
alignContentAround: 'Space around',
|
|
72
|
+
alignContentEvenly: 'Space evenly',
|
|
73
|
+
alignContentStretch: '拉伸',
|
|
74
|
+
presetsLabel: '预设',
|
|
75
|
+
presetNavbar: '导航栏',
|
|
76
|
+
presetCards: '卡片',
|
|
77
|
+
presetHero: '主视觉',
|
|
78
|
+
presetSidebar: '侧边栏',
|
|
79
|
+
presetGallery: '画廊',
|
|
80
|
+
shakeLimit: '至少需要2个项目!',
|
|
81
|
+
spanHint: '在Grid模式下双击项目可更改列跨度(1-3)',
|
|
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布局生成器常见问题',
|
|
91
|
+
faq,
|
|
92
|
+
bibliographyTitle: 'CSS Grid和Flexbox参考',
|
|
93
|
+
bibliography,
|
|
94
|
+
howTo,
|
|
95
|
+
schemas: [appSchema, faqSchema, howToSchema],
|
|
96
|
+
seo: [
|
|
97
|
+
{ type: 'title', text: '通过观察行为而非记忆属性来构建CSS布局', level: 2 },
|
|
98
|
+
{ type: 'paragraph', html: 'CSS Grid和Flexbox之所以强大,是因为它们描述布局关系而非固定坐标。难点在于预测<strong>gap</strong>、<strong>justify-content</strong>、<strong>align-items</strong>、方向、换行、轨道和可用空间如何相互作用。这个生成器将抽象属性转变为带有预设和实时CSS的活生生的演练场。' },
|
|
99
|
+
{ type: 'stats', columns: 3, items: [{ value: '5', label: '快速启动预设', icon: 'mdi:view-grid-plus' }, { value: '实时', label: '每次更改CSS都更新', icon: 'mdi:code-braces' }, { value: '0', label: 'CSS中的框架依赖', icon: 'mdi:language-css3' }] },
|
|
100
|
+
{ type: 'title', text: 'Flexbox vs Grid:在完善对齐之前选择模型', level: 3 },
|
|
101
|
+
{ type: 'comparative', columns: 2, items: [{ title: 'Flexbox', description: '最适合一维流,项目按行或列排列并自然换行。', icon: 'mdi:format-align-justify', highlight: true, points: ['导航栏', '按钮组', '换行卡片', '居中内容'] }, { title: 'CSS Grid', description: '最适合二维结构,行和列定义组合的形状。', icon: 'mdi:grid', points: ['仪表板', '画廊', '表单', '编辑区域'] }] },
|
|
102
|
+
{ type: 'title', text: '每个控件教给你的知识', level: 3 },
|
|
103
|
+
{ type: 'table', headers: ['控件', 'CSS属性', '关注点'], rows: [['方向', '<code>flex-direction</code>', '主轴如何流动 - 从行切换到列会改变整个布局逻辑。'], ['换行', '<code>flex-wrap</code>', '项目保持在单行还是空间不足时流向新行。'], ['间距', '<code>gap</code>', '项目之间的空间,无需在边缘断裂的边距。'], ['水平对齐', '<code>justify-content</code>', '空闲空间如何沿主轴分布。'], ['垂直对齐', '<code>align-items</code>', '项目如何在交叉轴上放置。'], ['内容对齐', '<code>align-content</code>', '换行的flex行或grid行如何分配额外的交叉轴空间。'], ['列数', '<code>grid-template-columns</code>', '在项目移到下一行之前,grid创建多少个相等的轨道。'], ['容器大小', '<code>width</code>和<code>min-height</code>', '相同的CSS在可用空间变化时如何响应。']] },
|
|
104
|
+
{ type: 'tip', title: '先缩放,再优化', html: '首先将画布缩放到真实大小。然后调整间距和对齐。这样生成的CSS在真实条件下也能工作。' },
|
|
105
|
+
{ type: 'title', text: '可适配的干净CSS', level: 3 },
|
|
106
|
+
{ type: 'code', ariaLabel: '生成的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: '为什么可视化缩放很重要', html: '许多布局错误仅在容器更窄、更高或填充不同数量的项目时才会出现。在CSS实时更新时进行缩放有助于发现尴尬的换行和脆弱的对齐选择。' },
|
|
108
|
+
{ type: 'summary', title: '推荐工作流程', items: ['一维流选择预设或Flexbox,二维结构选择Grid。', '在复制CSS之前缩放画布,使布局经受住现实的约束。', '使用gap作为项目之间的间距,而不是为每个子元素添加边距。', '以生成的CSS为起点进行复制,然后添加项目特定的选择器和媒体查询。'] },
|
|
109
|
+
],
|
|
110
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ToolDefinition } from '../../types';
|
|
2
|
+
import { visualCssGridFlexboxGenerator } from './entry';
|
|
3
|
+
|
|
4
|
+
export * from './entry';
|
|
5
|
+
|
|
6
|
+
export const VISUAL_CSS_GRID_FLEXBOX_GENERATOR_TOOL: ToolDefinition = {
|
|
7
|
+
entry: visualCssGridFlexboxGenerator,
|
|
8
|
+
Component: () => import('./component.astro'),
|
|
9
|
+
SEOComponent: () => import('./seo.astro'),
|
|
10
|
+
BibliographyComponent: () => import('./bibliography.astro'),
|
|
11
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { LayoutMode } from './logic';
|
|
2
|
+
|
|
3
|
+
export function setupSpanHandler(
|
|
4
|
+
canvas: HTMLElement,
|
|
5
|
+
modeFn: () => LayoutMode,
|
|
6
|
+
itemPrefix: string,
|
|
7
|
+
onUpdate: () => void,
|
|
8
|
+
) {
|
|
9
|
+
canvas.addEventListener('dblclick', (e: MouseEvent) => {
|
|
10
|
+
const item = (e.target as HTMLElement).closest('.vcg-item') as HTMLElement | null;
|
|
11
|
+
if (!item || modeFn() !== 'grid') return;
|
|
12
|
+
const cur = Number(item.dataset.span ?? '1');
|
|
13
|
+
const next = cur >= 3 ? 1 : cur + 1;
|
|
14
|
+
item.dataset.span = String(next);
|
|
15
|
+
const label = item.querySelector('span');
|
|
16
|
+
if (label) {
|
|
17
|
+
const num = item.dataset.item;
|
|
18
|
+
label.textContent = next > 1 ? `${itemPrefix} ${num} (${next}x)` : `${itemPrefix} ${num}`;
|
|
19
|
+
}
|
|
20
|
+
onUpdate();
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function spawnConfetti(x: number, y: number) {
|
|
25
|
+
const container = document.createElement('div');
|
|
26
|
+
container.className = 'vcg-confetti-container';
|
|
27
|
+
const colors = ['#2dd4bf', '#fbbf24', '#6366f1', '#f472b6', '#34d399', '#f97316', '#a78bfa', '#22d3ee', '#fde047'];
|
|
28
|
+
for (let i = 0; i < 32; i++) {
|
|
29
|
+
const p = document.createElement('div');
|
|
30
|
+
p.className = 'vcg-confetti-piece';
|
|
31
|
+
p.style.left = `${x}px`;
|
|
32
|
+
p.style.top = `${y}px`;
|
|
33
|
+
p.style.backgroundColor = colors[Math.floor(Math.random() * colors.length)]!;
|
|
34
|
+
p.style.width = `${5 + Math.random() * 7}px`;
|
|
35
|
+
p.style.height = `${5 + Math.random() * 7}px`;
|
|
36
|
+
const a = Math.random() * Math.PI * 2;
|
|
37
|
+
const d = 40 + Math.random() * 80;
|
|
38
|
+
p.style.setProperty('--cx', `${Math.cos(a) * d}px`);
|
|
39
|
+
p.style.setProperty('--cy', `${Math.sin(a) * d - 30}px`);
|
|
40
|
+
p.style.setProperty('--cr', `${Math.random() * 720 - 360}deg`);
|
|
41
|
+
container.append(p);
|
|
42
|
+
}
|
|
43
|
+
document.body.append(container);
|
|
44
|
+
setTimeout(() => container.remove(), 900);
|
|
45
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export type LayoutMode = 'flex' | 'grid';
|
|
2
|
+
|
|
3
|
+
export interface LayoutState {
|
|
4
|
+
mode: LayoutMode;
|
|
5
|
+
gap: number;
|
|
6
|
+
columns: number;
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
itemSize: number;
|
|
10
|
+
justifyContent: string;
|
|
11
|
+
alignItems: string;
|
|
12
|
+
flexDirection: string;
|
|
13
|
+
flexWrap: string;
|
|
14
|
+
alignContent: string;
|
|
15
|
+
itemSpans: Record<string, number>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function buildSpanRules(spans: Record<string, number>): string[] {
|
|
19
|
+
const lines: string[] = [];
|
|
20
|
+
for (const [id, span] of Object.entries(spans)) {
|
|
21
|
+
if (span > 1) {
|
|
22
|
+
lines.push(`.layout-item[data-item="${id}"] {`, ` grid-column: span ${span};`, '}');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return lines;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function buildLayoutCss(state: LayoutState): string {
|
|
29
|
+
const lines: string[] = ['.layout-playground {'];
|
|
30
|
+
|
|
31
|
+
if (state.mode === 'grid') {
|
|
32
|
+
lines.push(' display: grid;', ` grid-template-columns: repeat(${state.columns}, minmax(0, 1fr));`);
|
|
33
|
+
} else {
|
|
34
|
+
lines.push(' display: flex;', ` flex-direction: ${state.flexDirection};`, ` flex-wrap: ${state.flexWrap};`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
lines.push(` gap: ${state.gap}px;`, ` justify-content: ${state.justifyContent};`, ` align-items: ${state.alignItems};`);
|
|
38
|
+
|
|
39
|
+
if (state.mode === 'flex' && state.flexWrap !== 'nowrap') {
|
|
40
|
+
lines.push(` align-content: ${state.alignContent};`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
lines.push(` width: ${state.width}px;`, ` min-height: ${state.height}px;`, '}', '', '.layout-item {', ` min-width: ${state.itemSize}px;`, ` min-height: ${state.itemSize}px;`, '}');
|
|
44
|
+
|
|
45
|
+
if (state.mode === 'grid') {
|
|
46
|
+
lines.push(...buildSpanRules(state.itemSpans));
|
|
47
|
+
}
|
|
48
|
+
return lines.join('\n');
|
|
49
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { LayoutMode } from './logic';
|
|
2
|
+
import { getAllItems, clearPresetActive } from './canvas';
|
|
3
|
+
|
|
4
|
+
type PresetCfg = Partial<{
|
|
5
|
+
mode: LayoutMode;
|
|
6
|
+
gap: number; columns: number; width: number; height: number; itemSize: number;
|
|
7
|
+
justifyContent: string; alignItems: string; flexDirection: string;
|
|
8
|
+
flexWrap: string; alignContent: string;
|
|
9
|
+
}>;
|
|
10
|
+
|
|
11
|
+
export const PRESETS: Record<string, PresetCfg> = {
|
|
12
|
+
navbar: { mode: 'flex', flexDirection: 'row', flexWrap: 'nowrap', gap: 12, justifyContent: 'space-between', alignItems: 'center', alignContent: 'stretch', width: 760, height: 220, itemSize: 80 },
|
|
13
|
+
cards: { mode: 'grid', columns: 3, gap: 24, justifyContent: 'center', alignItems: 'center', width: 760, height: 420, itemSize: 100 },
|
|
14
|
+
hero: { mode: 'flex', flexDirection: 'column', flexWrap: 'nowrap', gap: 20, justifyContent: 'center', alignItems: 'center', alignContent: 'center', width: 600, height: 500, itemSize: 120 },
|
|
15
|
+
sidebar: { mode: 'grid', columns: 2, gap: 16, justifyContent: 'start', alignItems: 'stretch', width: 760, height: 460, itemSize: 100 },
|
|
16
|
+
gallery: { mode: 'grid', columns: 4, gap: 12, justifyContent: 'center', alignItems: 'start', width: 760, height: 400, itemSize: 80 },
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
interface PresetCtrls {
|
|
20
|
+
gapInput: HTMLInputElement;
|
|
21
|
+
columnsInput: HTMLInputElement;
|
|
22
|
+
widthInput: HTMLInputElement;
|
|
23
|
+
heightInput: HTMLInputElement;
|
|
24
|
+
itemSizeInput: HTMLInputElement;
|
|
25
|
+
justifySelect: HTMLSelectElement;
|
|
26
|
+
alignSelect: HTMLSelectElement;
|
|
27
|
+
directionSelect: HTMLSelectElement;
|
|
28
|
+
wrapSelect: HTMLSelectElement;
|
|
29
|
+
alignContentSelect: HTMLSelectElement;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const KEY_MAP: Record<string, keyof PresetCtrls> = {
|
|
33
|
+
gap: 'gapInput', columns: 'columnsInput', width: 'widthInput', height: 'heightInput',
|
|
34
|
+
itemSize: 'itemSizeInput', justifyContent: 'justifySelect', alignItems: 'alignSelect',
|
|
35
|
+
flexDirection: 'directionSelect', flexWrap: 'wrapSelect', alignContent: 'alignContentSelect',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
interface PresetContext {
|
|
39
|
+
canvas: HTMLElement;
|
|
40
|
+
ctrls: PresetCtrls;
|
|
41
|
+
setMode: (m: LayoutMode) => void;
|
|
42
|
+
update: () => void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function applyPreset(name: string, ctx: PresetContext) {
|
|
46
|
+
const cfg = PRESETS[name];
|
|
47
|
+
if (!cfg) return;
|
|
48
|
+
|
|
49
|
+
clearPresetActive();
|
|
50
|
+
const btn = document.querySelector(`[data-preset="${name}"]`);
|
|
51
|
+
if (btn) btn.classList.add('vcg-preset-active');
|
|
52
|
+
|
|
53
|
+
getAllItems(ctx.canvas).forEach((item) => { item.dataset.span = '1'; });
|
|
54
|
+
if (cfg.mode) ctx.setMode(cfg.mode);
|
|
55
|
+
|
|
56
|
+
for (const [key, value] of Object.entries(cfg)) {
|
|
57
|
+
if (key === 'mode') continue;
|
|
58
|
+
const ck = KEY_MAP[key] as keyof PresetCtrls | undefined;
|
|
59
|
+
if (ck && ctx.ctrls[ck]) ctx.ctrls[ck].value = String(value);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
ctx.update();
|
|
63
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { SEORenderer } from '@jjlmoya/utils-shared';
|
|
3
|
+
import { visualCssGridFlexboxGenerator } from './index';
|
|
4
|
+
import type { KnownLocale } from '../../types';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
locale?: KnownLocale;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { locale = 'en' } = Astro.props;
|
|
11
|
+
const content = await visualCssGridFlexboxGenerator.i18n[locale]?.();
|
|
12
|
+
if (!content) return null;
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
{content.seo?.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export interface VisualCssGridFlexboxGeneratorUI extends Record<string, string> {
|
|
2
|
+
modeLabel: string;
|
|
3
|
+
flexMode: string;
|
|
4
|
+
gridMode: string;
|
|
5
|
+
canvasLabel: string;
|
|
6
|
+
addItem: string;
|
|
7
|
+
removeItem: string;
|
|
8
|
+
resetLayout: string;
|
|
9
|
+
gapLabel: string;
|
|
10
|
+
columnsLabel: string;
|
|
11
|
+
widthLabel: string;
|
|
12
|
+
heightLabel: string;
|
|
13
|
+
justifyLabel: string;
|
|
14
|
+
alignLabel: string;
|
|
15
|
+
itemSizeLabel: string;
|
|
16
|
+
codeTitle: string;
|
|
17
|
+
copyCode: string;
|
|
18
|
+
copied: string;
|
|
19
|
+
dragHint: string;
|
|
20
|
+
outputLabel: string;
|
|
21
|
+
justifyStart: string;
|
|
22
|
+
justifyCenter: string;
|
|
23
|
+
justifyEnd: string;
|
|
24
|
+
justifyBetween: string;
|
|
25
|
+
justifyAround: string;
|
|
26
|
+
justifyEvenly: string;
|
|
27
|
+
alignStart: string;
|
|
28
|
+
alignCenter: string;
|
|
29
|
+
alignEnd: string;
|
|
30
|
+
alignStretch: string;
|
|
31
|
+
alignBaseline: string;
|
|
32
|
+
itemPrefix: string;
|
|
33
|
+
directionLabel: string;
|
|
34
|
+
directionRow: string;
|
|
35
|
+
directionRowReverse: string;
|
|
36
|
+
directionColumn: string;
|
|
37
|
+
directionColumnReverse: string;
|
|
38
|
+
wrapLabel: string;
|
|
39
|
+
wrapNoWrap: string;
|
|
40
|
+
wrapWrap: string;
|
|
41
|
+
wrapWrapReverse: string;
|
|
42
|
+
alignContentLabel: string;
|
|
43
|
+
alignContentStart: string;
|
|
44
|
+
alignContentCenter: string;
|
|
45
|
+
alignContentEnd: string;
|
|
46
|
+
alignContentBetween: string;
|
|
47
|
+
alignContentAround: string;
|
|
48
|
+
alignContentEvenly: string;
|
|
49
|
+
alignContentStretch: string;
|
|
50
|
+
presetsLabel: string;
|
|
51
|
+
presetNavbar: string;
|
|
52
|
+
presetCards: string;
|
|
53
|
+
presetHero: string;
|
|
54
|
+
presetSidebar: string;
|
|
55
|
+
presetGallery: string;
|
|
56
|
+
shakeLimit: string;
|
|
57
|
+
spanHint: string;
|
|
58
|
+
}
|