@jjlmoya/utils-developer 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +64 -0
- package/src/category/i18n/en.ts +34 -0
- package/src/category/i18n/es.ts +34 -0
- package/src/category/i18n/fr.ts +34 -0
- package/src/category/index.ts +31 -0
- package/src/category/seo.astro +15 -0
- package/src/components/PreviewNavSidebar.astro +116 -0
- package/src/components/PreviewToolbar.astro +143 -0
- package/src/data.ts +11 -0
- package/src/env.d.ts +5 -0
- package/src/index.ts +78 -0
- package/src/layouts/PreviewLayout.astro +117 -0
- package/src/pages/[locale]/[slug].astro +146 -0
- package/src/pages/[locale].astro +251 -0
- package/src/pages/index.astro +4 -0
- package/src/tests/css_specificity_logic.test.ts +39 -0
- package/src/tests/faq_count.test.ts +19 -0
- package/src/tests/locale_completeness.test.ts +42 -0
- package/src/tests/mocks/astro_mock.js +2 -0
- package/src/tests/no_h1_in_components.test.ts +48 -0
- package/src/tests/schemas_fulfillment.test.ts +23 -0
- package/src/tests/seo_length.test.ts +22 -0
- package/src/tests/title_quality.test.ts +55 -0
- package/src/tests/tool_validation.test.ts +17 -0
- package/src/tool/aspectRatio/bibliography.astro +14 -0
- package/src/tool/aspectRatio/component.astro +468 -0
- package/src/tool/aspectRatio/i18n/en.ts +177 -0
- package/src/tool/aspectRatio/i18n/es.ts +177 -0
- package/src/tool/aspectRatio/i18n/fr.ts +177 -0
- package/src/tool/aspectRatio/index.ts +34 -0
- package/src/tool/aspectRatio/seo.astro +14 -0
- package/src/tool/aspectRatio/ui.ts +12 -0
- package/src/tool/calculadoraTiempoDatos/bibliography.astro +14 -0
- package/src/tool/calculadoraTiempoDatos/component.astro +829 -0
- package/src/tool/calculadoraTiempoDatos/i18n/en.ts +150 -0
- package/src/tool/calculadoraTiempoDatos/i18n/es.ts +163 -0
- package/src/tool/calculadoraTiempoDatos/i18n/fr.ts +150 -0
- package/src/tool/calculadoraTiempoDatos/index.ts +33 -0
- package/src/tool/calculadoraTiempoDatos/seo.astro +14 -0
- package/src/tool/calculadoraTiempoDatos/ui.ts +24 -0
- package/src/tool/colorConverter/bibliography.astro +14 -0
- package/src/tool/colorConverter/component.astro +504 -0
- package/src/tool/colorConverter/i18n/en.ts +126 -0
- package/src/tool/colorConverter/i18n/es.ts +126 -0
- package/src/tool/colorConverter/i18n/fr.ts +126 -0
- package/src/tool/colorConverter/index.ts +33 -0
- package/src/tool/colorConverter/seo.astro +14 -0
- package/src/tool/colorConverter/ui.ts +16 -0
- package/src/tool/conversorExcelCsvHtml/bibliography.astro +14 -0
- package/src/tool/conversorExcelCsvHtml/component.astro +595 -0
- package/src/tool/conversorExcelCsvHtml/i18n/en.ts +129 -0
- package/src/tool/conversorExcelCsvHtml/i18n/es.ts +129 -0
- package/src/tool/conversorExcelCsvHtml/i18n/fr.ts +129 -0
- package/src/tool/conversorExcelCsvHtml/index.ts +33 -0
- package/src/tool/conversorExcelCsvHtml/seo.astro +14 -0
- package/src/tool/conversorExcelCsvHtml/ui.ts +14 -0
- package/src/tool/cronGenerator/bibliography.astro +14 -0
- package/src/tool/cronGenerator/component.astro +667 -0
- package/src/tool/cronGenerator/i18n/en.ts +191 -0
- package/src/tool/cronGenerator/i18n/es.ts +191 -0
- package/src/tool/cronGenerator/i18n/fr.ts +191 -0
- package/src/tool/cronGenerator/index.ts +34 -0
- package/src/tool/cronGenerator/seo.astro +14 -0
- package/src/tool/cronGenerator/ui.ts +45 -0
- package/src/tool/cssSpecificityCalculator/bibliography.astro +14 -0
- package/src/tool/cssSpecificityCalculator/component.astro +292 -0
- package/src/tool/cssSpecificityCalculator/i18n/en.ts +168 -0
- package/src/tool/cssSpecificityCalculator/i18n/es.ts +168 -0
- package/src/tool/cssSpecificityCalculator/i18n/fr.ts +168 -0
- package/src/tool/cssSpecificityCalculator/index.ts +34 -0
- package/src/tool/cssSpecificityCalculator/logic.ts +100 -0
- package/src/tool/cssSpecificityCalculator/seo.astro +14 -0
- package/src/tool/cssSpecificityCalculator/ui-controller.ts +57 -0
- package/src/tool/cssSpecificityCalculator/ui.ts +11 -0
- package/src/tool/cssToInlineConverter/bibliography.astro +14 -0
- package/src/tool/cssToInlineConverter/component.astro +308 -0
- package/src/tool/cssToInlineConverter/i18n/en.ts +160 -0
- package/src/tool/cssToInlineConverter/i18n/es.ts +160 -0
- package/src/tool/cssToInlineConverter/i18n/fr.ts +160 -0
- package/src/tool/cssToInlineConverter/index.ts +34 -0
- package/src/tool/cssToInlineConverter/seo.astro +14 -0
- package/src/tool/cssToInlineConverter/ui.ts +12 -0
- package/src/tool/duplicateCssRemover/bibliography.astro +14 -0
- package/src/tool/duplicateCssRemover/component.astro +353 -0
- package/src/tool/duplicateCssRemover/i18n/en.ts +167 -0
- package/src/tool/duplicateCssRemover/i18n/es.ts +167 -0
- package/src/tool/duplicateCssRemover/i18n/fr.ts +167 -0
- package/src/tool/duplicateCssRemover/index.ts +34 -0
- package/src/tool/duplicateCssRemover/seo.astro +14 -0
- package/src/tool/duplicateCssRemover/ui.ts +12 -0
- package/src/tool/generadorSecurityTxt/bibliography.astro +14 -0
- package/src/tool/generadorSecurityTxt/component.astro +473 -0
- package/src/tool/generadorSecurityTxt/i18n/en.ts +173 -0
- package/src/tool/generadorSecurityTxt/i18n/es.ts +173 -0
- package/src/tool/generadorSecurityTxt/i18n/fr.ts +173 -0
- package/src/tool/generadorSecurityTxt/index.ts +33 -0
- package/src/tool/generadorSecurityTxt/seo.astro +14 -0
- package/src/tool/generadorSecurityTxt/ui.ts +27 -0
- package/src/tool/hashGenerator/bibliography.astro +14 -0
- package/src/tool/hashGenerator/component.astro +367 -0
- package/src/tool/hashGenerator/i18n/en.ts +118 -0
- package/src/tool/hashGenerator/i18n/es.ts +118 -0
- package/src/tool/hashGenerator/i18n/fr.ts +118 -0
- package/src/tool/hashGenerator/index.ts +33 -0
- package/src/tool/hashGenerator/md5.ts +138 -0
- package/src/tool/hashGenerator/seo.astro +14 -0
- package/src/tool/hashGenerator/ui.ts +11 -0
- package/src/tool/inspectorCertificadosSsl/bibliography.astro +14 -0
- package/src/tool/inspectorCertificadosSsl/component.astro +580 -0
- package/src/tool/inspectorCertificadosSsl/i18n/en.ts +214 -0
- package/src/tool/inspectorCertificadosSsl/i18n/es.ts +214 -0
- package/src/tool/inspectorCertificadosSsl/i18n/fr.ts +214 -0
- package/src/tool/inspectorCertificadosSsl/index.ts +33 -0
- package/src/tool/inspectorCertificadosSsl/seo.astro +14 -0
- package/src/tool/inspectorCertificadosSsl/ui.ts +29 -0
- package/src/tool/jsonFormatter/bibliography.astro +14 -0
- package/src/tool/jsonFormatter/component.astro +590 -0
- package/src/tool/jsonFormatter/i18n/en.ts +156 -0
- package/src/tool/jsonFormatter/i18n/es.ts +156 -0
- package/src/tool/jsonFormatter/i18n/fr.ts +156 -0
- package/src/tool/jsonFormatter/index.ts +34 -0
- package/src/tool/jsonFormatter/seo.astro +14 -0
- package/src/tool/jsonFormatter/ui.ts +15 -0
- package/src/tool/keycode/bibliography.astro +14 -0
- package/src/tool/keycode/component.astro +540 -0
- package/src/tool/keycode/i18n/en.ts +160 -0
- package/src/tool/keycode/i18n/es.ts +160 -0
- package/src/tool/keycode/i18n/fr.ts +160 -0
- package/src/tool/keycode/index.ts +34 -0
- package/src/tool/keycode/seo.astro +14 -0
- package/src/tool/keycode/ui.ts +10 -0
- package/src/tool/llmCostCalculator/bibliography.astro +14 -0
- package/src/tool/llmCostCalculator/component.astro +488 -0
- package/src/tool/llmCostCalculator/data.ts +99 -0
- package/src/tool/llmCostCalculator/i18n/en.ts +165 -0
- package/src/tool/llmCostCalculator/i18n/es.ts +165 -0
- package/src/tool/llmCostCalculator/i18n/fr.ts +165 -0
- package/src/tool/llmCostCalculator/index.ts +34 -0
- package/src/tool/llmCostCalculator/seo.astro +14 -0
- package/src/tool/llmCostCalculator/ui.ts +11 -0
- package/src/tool/mobileMockupGenerator/app.ts +614 -0
- package/src/tool/mobileMockupGenerator/bibliography.astro +14 -0
- package/src/tool/mobileMockupGenerator/component.astro +1044 -0
- package/src/tool/mobileMockupGenerator/constants.ts +48 -0
- package/src/tool/mobileMockupGenerator/i18n/en.ts +121 -0
- package/src/tool/mobileMockupGenerator/i18n/es.ts +121 -0
- package/src/tool/mobileMockupGenerator/i18n/fr.ts +121 -0
- package/src/tool/mobileMockupGenerator/index.ts +33 -0
- package/src/tool/mobileMockupGenerator/rendering.ts +189 -0
- package/src/tool/mobileMockupGenerator/seo.astro +14 -0
- package/src/tool/mobileMockupGenerator/store.ts +145 -0
- package/src/tool/mobileMockupGenerator/types.ts +41 -0
- package/src/tool/mobileMockupGenerator/ui.ts +48 -0
- package/src/tool/musicalTypography/bibliography.astro +14 -0
- package/src/tool/musicalTypography/component.astro +480 -0
- package/src/tool/musicalTypography/i18n/en.ts +179 -0
- package/src/tool/musicalTypography/i18n/es.ts +179 -0
- package/src/tool/musicalTypography/i18n/fr.ts +179 -0
- package/src/tool/musicalTypography/index.ts +33 -0
- package/src/tool/musicalTypography/seo.astro +14 -0
- package/src/tool/musicalTypography/ui.ts +25 -0
- package/src/tool/placeholderGenerator/bibliography.astro +14 -0
- package/src/tool/placeholderGenerator/component.astro +499 -0
- package/src/tool/placeholderGenerator/i18n/en.ts +176 -0
- package/src/tool/placeholderGenerator/i18n/es.ts +176 -0
- package/src/tool/placeholderGenerator/i18n/fr.ts +176 -0
- package/src/tool/placeholderGenerator/index.ts +34 -0
- package/src/tool/placeholderGenerator/seo.astro +14 -0
- package/src/tool/placeholderGenerator/ui.ts +14 -0
- package/src/tool/promptLibrary/bibliography.astro +14 -0
- package/src/tool/promptLibrary/component.astro +1131 -0
- package/src/tool/promptLibrary/controller.ts +49 -0
- package/src/tool/promptLibrary/i18n/en.ts +131 -0
- package/src/tool/promptLibrary/i18n/es.ts +131 -0
- package/src/tool/promptLibrary/i18n/fr.ts +126 -0
- package/src/tool/promptLibrary/index.ts +33 -0
- package/src/tool/promptLibrary/seo.astro +14 -0
- package/src/tool/promptLibrary/ui.ts +29 -0
- package/src/tool/readabilityCalculator/bibliography.astro +14 -0
- package/src/tool/readabilityCalculator/component.astro +534 -0
- package/src/tool/readabilityCalculator/i18n/en.ts +133 -0
- package/src/tool/readabilityCalculator/i18n/es.ts +133 -0
- package/src/tool/readabilityCalculator/i18n/fr.ts +133 -0
- package/src/tool/readabilityCalculator/index.ts +33 -0
- package/src/tool/readabilityCalculator/seo.astro +14 -0
- package/src/tool/readabilityCalculator/ui.ts +27 -0
- package/src/tool/svgSanitizer/bibliography.astro +14 -0
- package/src/tool/svgSanitizer/component.astro +554 -0
- package/src/tool/svgSanitizer/i18n/en.ts +125 -0
- package/src/tool/svgSanitizer/i18n/es.ts +125 -0
- package/src/tool/svgSanitizer/i18n/fr.ts +125 -0
- package/src/tool/svgSanitizer/index.ts +33 -0
- package/src/tool/svgSanitizer/seo.astro +14 -0
- package/src/tool/svgSanitizer/ui.ts +18 -0
- package/src/tool/svgToCss/bibliography.astro +14 -0
- package/src/tool/svgToCss/component.astro +381 -0
- package/src/tool/svgToCss/i18n/en.ts +174 -0
- package/src/tool/svgToCss/i18n/es.ts +174 -0
- package/src/tool/svgToCss/i18n/fr.ts +174 -0
- package/src/tool/svgToCss/index.ts +34 -0
- package/src/tool/svgToCss/seo.astro +14 -0
- package/src/tool/svgToCss/ui.ts +13 -0
- package/src/tool/urlCleaner/bibliography.astro +14 -0
- package/src/tool/urlCleaner/component.astro +458 -0
- package/src/tool/urlCleaner/i18n/en.ts +116 -0
- package/src/tool/urlCleaner/i18n/es.ts +116 -0
- package/src/tool/urlCleaner/i18n/fr.ts +116 -0
- package/src/tool/urlCleaner/index.ts +33 -0
- package/src/tool/urlCleaner/seo.astro +14 -0
- package/src/tool/urlCleaner/ui.ts +12 -0
- package/src/tool/urlEncoderDecoder/bibliography.astro +14 -0
- package/src/tool/urlEncoderDecoder/component.astro +291 -0
- package/src/tool/urlEncoderDecoder/i18n/en.ts +162 -0
- package/src/tool/urlEncoderDecoder/i18n/es.ts +162 -0
- package/src/tool/urlEncoderDecoder/i18n/fr.ts +162 -0
- package/src/tool/urlEncoderDecoder/index.ts +34 -0
- package/src/tool/urlEncoderDecoder/seo.astro +14 -0
- package/src/tool/urlEncoderDecoder/ui.ts +11 -0
- package/src/tool/utmGenerator/bibliography.astro +14 -0
- package/src/tool/utmGenerator/component.astro +270 -0
- package/src/tool/utmGenerator/i18n/en.ts +117 -0
- package/src/tool/utmGenerator/i18n/es.ts +117 -0
- package/src/tool/utmGenerator/i18n/fr.ts +117 -0
- package/src/tool/utmGenerator/index.ts +33 -0
- package/src/tool/utmGenerator/seo.astro +14 -0
- package/src/tool/utmGenerator/ui.ts +13 -0
- package/src/tools.ts +27 -0
- package/src/types.ts +72 -0
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { KnownLocale } from '../../types';
|
|
3
|
+
import type { MusicalTypographyUI } from './ui';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
locale?: KnownLocale;
|
|
7
|
+
ui?: Record<string, unknown>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { ui } = Astro.props;
|
|
11
|
+
const t = (ui ?? {}) as MusicalTypographyUI;
|
|
12
|
+
|
|
13
|
+
const RATIO_KEYS = [
|
|
14
|
+
{ key: 'ratioSecundaMenor', value: 1.067 },
|
|
15
|
+
{ key: 'ratioSegundaMayor', value: 1.125 },
|
|
16
|
+
{ key: 'ratioTerceraMenor', value: 1.2 },
|
|
17
|
+
{ key: 'ratioTerceraMayor', value: 1.25 },
|
|
18
|
+
{ key: 'ratioCuartaPerfecta', value: 1.333 },
|
|
19
|
+
{ key: 'ratioCuartaAumentada', value: 1.414 },
|
|
20
|
+
{ key: 'ratioQuintaPerfecta', value: 1.5 },
|
|
21
|
+
{ key: 'ratioProporcionAurea', value: 1.618 },
|
|
22
|
+
{ key: 'ratioSextaMayor', value: 1.667 },
|
|
23
|
+
{ key: 'ratioSeptimaMenor', value: 1.778 },
|
|
24
|
+
{ key: 'ratioSeptimaMayor', value: 1.875 },
|
|
25
|
+
{ key: 'ratioOctava', value: 2.0 },
|
|
26
|
+
] as const;
|
|
27
|
+
|
|
28
|
+
const ratios = RATIO_KEYS.map((r) => ({ name: t[r.key] ?? r.key, value: r.value }));
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
<div class="mt-root" id="mt-root" data-i18n={JSON.stringify(t)}>
|
|
32
|
+
<div class="mt-grid">
|
|
33
|
+
<div class="mt-card">
|
|
34
|
+
<div class="mt-card-header">
|
|
35
|
+
<h2 class="mt-card-title">{t.labelConfig}</h2>
|
|
36
|
+
</div>
|
|
37
|
+
<div class="mt-card-body">
|
|
38
|
+
<div class="mt-field">
|
|
39
|
+
<label class="mt-label" for="mt-base-size">{t.labelBaseSize}</label>
|
|
40
|
+
<div class="mt-input-wrap">
|
|
41
|
+
<input
|
|
42
|
+
class="mt-input"
|
|
43
|
+
type="number"
|
|
44
|
+
id="mt-base-size"
|
|
45
|
+
value="16"
|
|
46
|
+
min="8"
|
|
47
|
+
max="32"
|
|
48
|
+
/>
|
|
49
|
+
<span class="mt-input-suffix">px</span>
|
|
50
|
+
</div>
|
|
51
|
+
<p class="mt-hint">{t.hintBaseSize}</p>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<div class="mt-field">
|
|
55
|
+
<label class="mt-label" for="mt-ratio-select">{t.labelRatio}</label>
|
|
56
|
+
<select class="mt-select" id="mt-ratio-select">
|
|
57
|
+
{
|
|
58
|
+
ratios.map((r) => (
|
|
59
|
+
<option value={r.value} selected={r.value === 1.5}>
|
|
60
|
+
{r.name} ({r.value.toFixed(3)})
|
|
61
|
+
</option>
|
|
62
|
+
))
|
|
63
|
+
}
|
|
64
|
+
</select>
|
|
65
|
+
<p class="mt-hint">{t.hintRatio}</p>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<div class="mt-calc-box">
|
|
69
|
+
<h3 class="mt-calc-title">{t.labelCalculated}</h3>
|
|
70
|
+
<div id="mt-scale-preview" class="mt-scale-list"></div>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
<div class="mt-card mt-card-preview">
|
|
76
|
+
<div class="mt-card-header mt-card-header-preview">
|
|
77
|
+
<h2 class="mt-card-title">{t.labelPreview}</h2>
|
|
78
|
+
</div>
|
|
79
|
+
<div class="mt-card-body mt-preview-body">
|
|
80
|
+
<div class="mt-preview-scroll-wrap">
|
|
81
|
+
<div id="mt-type-preview" class="mt-preview-scroll"></div>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
<div class="mt-copy-section">
|
|
85
|
+
<button class="mt-copy-btn" id="mt-copy-btn">
|
|
86
|
+
{t.btnCopyCss}
|
|
87
|
+
</button>
|
|
88
|
+
<p id="mt-feedback" class="mt-feedback">{t.feedbackCopied}</p>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
<script>
|
|
96
|
+
type ScaleItem = { level: number; size: number; rem: number };
|
|
97
|
+
type UI = { previewText: string; previewSubtext: string; feedbackCopied: string; [k: string]: string };
|
|
98
|
+
type Refs = {
|
|
99
|
+
baseSize: HTMLInputElement;
|
|
100
|
+
ratioSelect: HTMLSelectElement;
|
|
101
|
+
scalePreview: HTMLElement;
|
|
102
|
+
typePreview: HTMLElement;
|
|
103
|
+
copyBtn: HTMLElement;
|
|
104
|
+
feedback: HTMLElement;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const LEVELS = [-2, -1, 0, 1, 2, 3, 4, 5];
|
|
108
|
+
|
|
109
|
+
function calcScale(base: number, ratio: number): ScaleItem[] {
|
|
110
|
+
return LEVELS.map((level) => {
|
|
111
|
+
const size = base * Math.pow(ratio, level);
|
|
112
|
+
return { level, size, rem: size / 16 };
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function buildScaleRow(item: ScaleItem): string {
|
|
117
|
+
const cls = item.level === 0 ? ' mt-scale-base' : '';
|
|
118
|
+
return '<div class="mt-scale-row' + cls + '"><span>step-' + item.level + '</span>' +
|
|
119
|
+
'<span>' + item.size.toFixed(2) + 'px\u00a0/\u00a0' + item.rem.toFixed(3) + 'rem</span></div>';
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function buildSubText(item: ScaleItem, subtext: string): string {
|
|
123
|
+
if (item.level < 2) return '';
|
|
124
|
+
return '<p class="mt-sub" style="font-size:' + Math.max(12, item.size * 0.45).toFixed(1) + 'px">' + subtext + '</p>';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function buildPreviewRow(item: ScaleItem, previewText: string, subtext: string): string {
|
|
128
|
+
return '<div class="mt-prev-item">' +
|
|
129
|
+
'<div class="mt-prev-meta"><span>Step ' + item.level + '</span>' +
|
|
130
|
+
'<span>' + item.size.toFixed(1) + 'px</span></div>' +
|
|
131
|
+
'<p class="mt-prev-text" style="font-size:' + item.size.toFixed(2) + 'px;line-height:1.1">' +
|
|
132
|
+
previewText + '</p>' + buildSubText(item, subtext) + '</div>';
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function updateUI(refs: Refs, t: UI): void {
|
|
136
|
+
const base = parseFloat(refs.baseSize.value) || 16;
|
|
137
|
+
const ratio = parseFloat(refs.ratioSelect.value) || 1.5;
|
|
138
|
+
const scale = calcScale(base, ratio);
|
|
139
|
+
refs.scalePreview.innerHTML = scale.slice().reverse().map(buildScaleRow).join('');
|
|
140
|
+
refs.typePreview.innerHTML = scale.slice().reverse()
|
|
141
|
+
.map((item) => buildPreviewRow(item, t.previewText, t.previewSubtext))
|
|
142
|
+
.join('');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function generateCSS(refs: Refs): string {
|
|
146
|
+
const base = parseFloat(refs.baseSize.value) || 16;
|
|
147
|
+
const ratio = parseFloat(refs.ratioSelect.value) || 1.5;
|
|
148
|
+
const scale = calcScale(base, ratio);
|
|
149
|
+
let css = '\n:root {\n --ratio: ' + ratio + ';\n';
|
|
150
|
+
scale.forEach((item) => { css += ' --step-' + item.level + ': ' + item.rem.toFixed(3) + 'rem;\n'; });
|
|
151
|
+
return css + '}\n';
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function bindCopy(refs: Refs): void {
|
|
155
|
+
refs.copyBtn.addEventListener('click', () => {
|
|
156
|
+
const css = generateCSS(refs);
|
|
157
|
+
navigator.clipboard.writeText(css).then(() => {
|
|
158
|
+
refs.feedback.style.opacity = '1';
|
|
159
|
+
setTimeout(() => { refs.feedback.style.opacity = '0'; }, 3000);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function getRefs(root: HTMLElement): Refs {
|
|
165
|
+
return {
|
|
166
|
+
baseSize: root.querySelector<HTMLInputElement>('#mt-base-size')!,
|
|
167
|
+
ratioSelect: root.querySelector<HTMLSelectElement>('#mt-ratio-select')!,
|
|
168
|
+
scalePreview: root.querySelector<HTMLElement>('#mt-scale-preview')!,
|
|
169
|
+
typePreview: root.querySelector<HTMLElement>('#mt-type-preview')!,
|
|
170
|
+
copyBtn: root.querySelector<HTMLElement>('#mt-copy-btn')!,
|
|
171
|
+
feedback: root.querySelector<HTMLElement>('#mt-feedback')!,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function init(): void {
|
|
176
|
+
const root = document.getElementById('mt-root');
|
|
177
|
+
if (!root) return;
|
|
178
|
+
const t = JSON.parse(root.dataset.i18n ?? '{}') as UI;
|
|
179
|
+
const refs = getRefs(root as HTMLElement);
|
|
180
|
+
const upd = () => updateUI(refs, t);
|
|
181
|
+
refs.baseSize.addEventListener('input', upd);
|
|
182
|
+
refs.ratioSelect.addEventListener('change', upd);
|
|
183
|
+
bindCopy(refs);
|
|
184
|
+
upd();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
init();
|
|
188
|
+
</script>
|
|
189
|
+
|
|
190
|
+
<style>
|
|
191
|
+
.mt-root {
|
|
192
|
+
--mt-card-bg: #fff;
|
|
193
|
+
--mt-border: #e2e8f0;
|
|
194
|
+
--mt-text: #1e293b;
|
|
195
|
+
--mt-muted: #64748b;
|
|
196
|
+
--mt-accent: #6366f1;
|
|
197
|
+
--mt-accent-bg: rgba(99, 102, 241, 0.08);
|
|
198
|
+
--mt-input-bg: #f8fafc;
|
|
199
|
+
--mt-input-border: #e2e8f0;
|
|
200
|
+
--mt-calc-bg: rgba(99, 102, 241, 0.05);
|
|
201
|
+
--mt-calc-border: rgba(99, 102, 241, 0.15);
|
|
202
|
+
--mt-preview-bg: #f8fafc;
|
|
203
|
+
--mt-base-color: #6366f1;
|
|
204
|
+
--mt-btn-bg: #4f46e5;
|
|
205
|
+
--mt-btn-hover: #4338ca;
|
|
206
|
+
--mt-feedback-color: #059669;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
:global(.theme-dark) .mt-root {
|
|
210
|
+
--mt-card-bg: #1e293b;
|
|
211
|
+
--mt-border: #334155;
|
|
212
|
+
--mt-text: #f1f5f9;
|
|
213
|
+
--mt-muted: #94a3b8;
|
|
214
|
+
--mt-input-bg: #0f172a;
|
|
215
|
+
--mt-input-border: #475569;
|
|
216
|
+
--mt-calc-bg: rgba(99, 102, 241, 0.1);
|
|
217
|
+
--mt-calc-border: rgba(99, 102, 241, 0.25);
|
|
218
|
+
--mt-preview-bg: rgba(15, 23, 42, 0.5);
|
|
219
|
+
--mt-base-color: #818cf8;
|
|
220
|
+
--mt-btn-bg: #4f46e5;
|
|
221
|
+
--mt-btn-hover: #6366f1;
|
|
222
|
+
--mt-feedback-color: #34d399;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.mt-grid {
|
|
226
|
+
display: grid;
|
|
227
|
+
grid-template-columns: 1fr 1fr;
|
|
228
|
+
gap: 2rem;
|
|
229
|
+
align-items: start;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
@media (max-width: 640px) {
|
|
233
|
+
.mt-grid {
|
|
234
|
+
grid-template-columns: 1fr;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.mt-card {
|
|
239
|
+
background: var(--mt-card-bg);
|
|
240
|
+
border: 1px solid var(--mt-border);
|
|
241
|
+
border-radius: 1rem;
|
|
242
|
+
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.06);
|
|
243
|
+
overflow: hidden;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.mt-card-header {
|
|
247
|
+
display: flex;
|
|
248
|
+
align-items: center;
|
|
249
|
+
gap: 0.75rem;
|
|
250
|
+
padding: 1.25rem 1.5rem;
|
|
251
|
+
border-bottom: 1px solid var(--mt-border);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.mt-card-header-preview {
|
|
255
|
+
background: rgba(236, 72, 153, 0.04);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
:global(.theme-dark) .mt-card-header-preview {
|
|
259
|
+
background: rgba(236, 72, 153, 0.08);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.mt-card-title {
|
|
263
|
+
font-size: 1.125rem;
|
|
264
|
+
font-weight: 700;
|
|
265
|
+
color: var(--mt-text);
|
|
266
|
+
margin: 0;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.mt-card-body {
|
|
270
|
+
padding: 1.5rem;
|
|
271
|
+
display: flex;
|
|
272
|
+
flex-direction: column;
|
|
273
|
+
gap: 1.5rem;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.mt-preview-body {
|
|
277
|
+
flex: 1;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.mt-card-preview {
|
|
281
|
+
display: flex;
|
|
282
|
+
flex-direction: column;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
.mt-label {
|
|
286
|
+
display: block;
|
|
287
|
+
font-size: 0.875rem;
|
|
288
|
+
font-weight: 600;
|
|
289
|
+
color: var(--mt-text);
|
|
290
|
+
margin-bottom: 0.5rem;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.mt-input-wrap {
|
|
294
|
+
position: relative;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.mt-input,
|
|
298
|
+
.mt-select {
|
|
299
|
+
display: block;
|
|
300
|
+
width: 100%;
|
|
301
|
+
padding: 0.625rem 0.75rem;
|
|
302
|
+
background: var(--mt-input-bg);
|
|
303
|
+
border: 1px solid var(--mt-input-border);
|
|
304
|
+
border-radius: 0.625rem;
|
|
305
|
+
color: var(--mt-text);
|
|
306
|
+
font-size: 0.875rem;
|
|
307
|
+
box-sizing: border-box;
|
|
308
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
|
|
309
|
+
transition: border-color 0.15s, box-shadow 0.15s;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.mt-input {
|
|
313
|
+
padding-right: 2.5rem;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.mt-input:focus,
|
|
317
|
+
.mt-select:focus {
|
|
318
|
+
outline: 2px solid var(--mt-accent);
|
|
319
|
+
outline-offset: 2px;
|
|
320
|
+
border-color: var(--mt-accent);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.mt-input-suffix {
|
|
324
|
+
position: absolute;
|
|
325
|
+
right: 0.75rem;
|
|
326
|
+
top: 50%;
|
|
327
|
+
transform: translateY(-50%);
|
|
328
|
+
font-size: 0.875rem;
|
|
329
|
+
color: var(--mt-muted);
|
|
330
|
+
pointer-events: none;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.mt-hint {
|
|
334
|
+
margin: 0.375rem 0 0;
|
|
335
|
+
font-size: 0.75rem;
|
|
336
|
+
color: var(--mt-muted);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
.mt-calc-box {
|
|
340
|
+
background: var(--mt-calc-bg);
|
|
341
|
+
border: 1px solid var(--mt-calc-border);
|
|
342
|
+
border-radius: 0.75rem;
|
|
343
|
+
padding: 1rem;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
.mt-calc-title {
|
|
347
|
+
font-size: 0.6875rem;
|
|
348
|
+
font-weight: 700;
|
|
349
|
+
color: var(--mt-accent);
|
|
350
|
+
text-transform: uppercase;
|
|
351
|
+
letter-spacing: 0.06em;
|
|
352
|
+
margin: 0 0 0.75rem;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.mt-scale-list {
|
|
356
|
+
display: flex;
|
|
357
|
+
flex-direction: column;
|
|
358
|
+
gap: 0.25rem;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
:global(.mt-scale-row) {
|
|
362
|
+
display: flex;
|
|
363
|
+
justify-content: space-between;
|
|
364
|
+
font-size: 0.8125rem;
|
|
365
|
+
color: var(--mt-muted);
|
|
366
|
+
padding: 0.125rem 0;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
:global(.mt-scale-base) {
|
|
370
|
+
color: var(--mt-base-color);
|
|
371
|
+
font-weight: 700;
|
|
372
|
+
background: var(--mt-accent-bg);
|
|
373
|
+
border-radius: 0.25rem;
|
|
374
|
+
padding: 0.125rem 0.375rem;
|
|
375
|
+
margin: 0 -0.375rem;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.mt-preview-scroll-wrap {
|
|
379
|
+
background: var(--mt-preview-bg);
|
|
380
|
+
border: 1px solid var(--mt-border);
|
|
381
|
+
border-radius: 0.75rem;
|
|
382
|
+
height: 18rem;
|
|
383
|
+
overflow: hidden;
|
|
384
|
+
position: relative;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
.mt-preview-scroll {
|
|
388
|
+
position: absolute;
|
|
389
|
+
inset: 0;
|
|
390
|
+
padding: 1rem;
|
|
391
|
+
overflow-y: auto;
|
|
392
|
+
display: flex;
|
|
393
|
+
flex-direction: column;
|
|
394
|
+
gap: 1.5rem;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.mt-preview-scroll::-webkit-scrollbar {
|
|
398
|
+
width: 6px;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
.mt-preview-scroll::-webkit-scrollbar-track {
|
|
402
|
+
background: transparent;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.mt-preview-scroll::-webkit-scrollbar-thumb {
|
|
406
|
+
background: rgba(148, 163, 184, 0.3);
|
|
407
|
+
border-radius: 9999px;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
:global(.mt-prev-item) {
|
|
411
|
+
border-bottom: 1px solid var(--mt-border);
|
|
412
|
+
padding-bottom: 1rem;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
:global(.mt-prev-meta) {
|
|
416
|
+
display: flex;
|
|
417
|
+
align-items: baseline;
|
|
418
|
+
gap: 0.5rem;
|
|
419
|
+
margin-bottom: 0.25rem;
|
|
420
|
+
font-size: 0.75rem;
|
|
421
|
+
color: var(--mt-muted);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
:global(.mt-prev-text) {
|
|
425
|
+
font-weight: 700;
|
|
426
|
+
color: var(--mt-text);
|
|
427
|
+
white-space: nowrap;
|
|
428
|
+
overflow: hidden;
|
|
429
|
+
text-overflow: ellipsis;
|
|
430
|
+
margin: 0;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
:global(.mt-sub) {
|
|
434
|
+
color: var(--mt-muted);
|
|
435
|
+
margin: 0.25rem 0 0;
|
|
436
|
+
white-space: nowrap;
|
|
437
|
+
overflow: hidden;
|
|
438
|
+
text-overflow: ellipsis;
|
|
439
|
+
line-height: 1.4;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
.mt-copy-section {
|
|
443
|
+
display: flex;
|
|
444
|
+
flex-direction: column;
|
|
445
|
+
gap: 0.5rem;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
.mt-copy-btn {
|
|
449
|
+
width: 100%;
|
|
450
|
+
padding: 0.75rem 1.5rem;
|
|
451
|
+
background: var(--mt-btn-bg);
|
|
452
|
+
color: #fff;
|
|
453
|
+
border: none;
|
|
454
|
+
border-radius: 0.75rem;
|
|
455
|
+
font-size: 0.875rem;
|
|
456
|
+
font-weight: 700;
|
|
457
|
+
cursor: pointer;
|
|
458
|
+
box-shadow: 0 4px 12px rgba(79, 70, 229, 0.3);
|
|
459
|
+
transition: background 0.15s, box-shadow 0.15s, transform 0.1s;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
.mt-copy-btn:hover {
|
|
463
|
+
background: var(--mt-btn-hover);
|
|
464
|
+
box-shadow: 0 6px 16px rgba(79, 70, 229, 0.4);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
.mt-copy-btn:active {
|
|
468
|
+
transform: scale(0.98);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
.mt-feedback {
|
|
472
|
+
text-align: center;
|
|
473
|
+
font-size: 0.8125rem;
|
|
474
|
+
font-weight: 600;
|
|
475
|
+
color: var(--mt-feedback-color);
|
|
476
|
+
opacity: 0;
|
|
477
|
+
transition: opacity 0.3s;
|
|
478
|
+
margin: 0;
|
|
479
|
+
}
|
|
480
|
+
</style>
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import type { WithContext, FAQPage, HowToThing, SoftwareApplication } from 'schema-dts';
|
|
2
|
+
import type { ToolLocaleContent } from '../../../types';
|
|
3
|
+
import type { MusicalTypographyUI } from '../ui';
|
|
4
|
+
|
|
5
|
+
const slug = 'musical-typography';
|
|
6
|
+
const title = 'Musical Typography Scale. Modular Scale Calculator';
|
|
7
|
+
const description =
|
|
8
|
+
'Free online tool to create harmonious visual hierarchies using modular scales based on musical proportions. Generates ready-to-use CSS variables for your web design.';
|
|
9
|
+
|
|
10
|
+
const faqData = [
|
|
11
|
+
{
|
|
12
|
+
question: 'What is a typographic modular scale?',
|
|
13
|
+
answer:
|
|
14
|
+
'It is a method for determining font sizes based on a constant mathematical ratio. Just like in music, where notes have harmonic relationships, the modular scale creates a balanced and predictable visual hierarchy.',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
question: 'Why use musical intervals for design?',
|
|
18
|
+
answer:
|
|
19
|
+
'Musical intervals are proportions that the human brain perceives as harmonious. Applying them to text sizes creates a visual structure that feels correct and professional, rather than choosing sizes at random.',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
question: 'What is the Golden Ratio in typography?',
|
|
23
|
+
answer:
|
|
24
|
+
'It is the proportion 1.618, known as the Golden Section. It creates very dramatic and elegant scales where each step in the hierarchy grows exponentially. Perfect for portfolio or art-focused websites.',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
question: 'How do I implement the scale in my CSS file?',
|
|
28
|
+
answer:
|
|
29
|
+
'The tool generates CSS variables (tokens) in :root { --step-N: Xrem; } format. Copy them into your main CSS file and use them with var(--step-N) to maintain typographic consistency across your site.',
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const howToData = [
|
|
34
|
+
{
|
|
35
|
+
name: 'Set the base size',
|
|
36
|
+
text: 'Enter the font size for your body text (usually 16px) which will serve as the fundamental note of your scale.',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'Choose the interval',
|
|
40
|
+
text: 'Select a musical proportion to determine how much space there is between different heading levels.',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'Preview the hierarchy',
|
|
44
|
+
text: 'Watch how the typographic levels behave in real time to verify the visual harmony fits your project.',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'Export the code',
|
|
48
|
+
text: 'Click Copy CSS Variables to get the block ready to paste directly into your workflow.',
|
|
49
|
+
},
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
const faqSchema: WithContext<FAQPage> = {
|
|
53
|
+
'@context': 'https://schema.org',
|
|
54
|
+
'@type': 'FAQPage',
|
|
55
|
+
mainEntity: faqData.map((item) => ({
|
|
56
|
+
'@type': 'Question',
|
|
57
|
+
name: item.question,
|
|
58
|
+
acceptedAnswer: { '@type': 'Answer', text: item.answer },
|
|
59
|
+
})),
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const howToSchema: WithContext<HowToThing> = {
|
|
63
|
+
'@context': 'https://schema.org',
|
|
64
|
+
'@type': 'HowTo',
|
|
65
|
+
name: title,
|
|
66
|
+
description,
|
|
67
|
+
step: howToData.map((step, i) => ({
|
|
68
|
+
'@type': 'HowToStep',
|
|
69
|
+
position: i + 1,
|
|
70
|
+
name: step.name,
|
|
71
|
+
text: step.text,
|
|
72
|
+
})),
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const appSchema: WithContext<SoftwareApplication> = {
|
|
76
|
+
'@context': 'https://schema.org',
|
|
77
|
+
'@type': 'SoftwareApplication',
|
|
78
|
+
name: title,
|
|
79
|
+
description,
|
|
80
|
+
applicationCategory: 'DesignApplication',
|
|
81
|
+
operatingSystem: 'All',
|
|
82
|
+
offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
|
|
83
|
+
inLanguage: 'en',
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const ui: MusicalTypographyUI = {
|
|
87
|
+
labelConfig: 'Configuration',
|
|
88
|
+
labelBaseSize: 'Base Size (px)',
|
|
89
|
+
hintBaseSize: 'Your paragraph text size (usually 16px).',
|
|
90
|
+
labelRatio: 'Musical Scale (Ratio)',
|
|
91
|
+
hintRatio: 'Determines how much the size grows at each step.',
|
|
92
|
+
labelCalculated: 'Calculated Values',
|
|
93
|
+
labelPreview: 'Preview',
|
|
94
|
+
btnCopyCss: 'Copy CSS Variables',
|
|
95
|
+
feedbackCopied: 'Variables copied to clipboard!',
|
|
96
|
+
previewText: 'Musical Typography',
|
|
97
|
+
previewSubtext: 'A perfect harmonic scale for your design.',
|
|
98
|
+
ratioSecundaMenor: 'Minor Second',
|
|
99
|
+
ratioSegundaMayor: 'Major Second',
|
|
100
|
+
ratioTerceraMenor: 'Minor Third',
|
|
101
|
+
ratioTerceraMayor: 'Major Third',
|
|
102
|
+
ratioCuartaPerfecta: 'Perfect Fourth',
|
|
103
|
+
ratioCuartaAumentada: 'Augmented Fourth',
|
|
104
|
+
ratioQuintaPerfecta: 'Perfect Fifth',
|
|
105
|
+
ratioProporcionAurea: 'Golden Ratio',
|
|
106
|
+
ratioSextaMayor: 'Major Sixth',
|
|
107
|
+
ratioSeptimaMenor: 'Minor Seventh',
|
|
108
|
+
ratioSeptimaMayor: 'Major Seventh',
|
|
109
|
+
ratioOctava: 'Octave',
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export const content: ToolLocaleContent<MusicalTypographyUI> = {
|
|
113
|
+
slug,
|
|
114
|
+
title,
|
|
115
|
+
description,
|
|
116
|
+
ui,
|
|
117
|
+
faqTitle: 'Frequently Asked Questions',
|
|
118
|
+
faq: faqData,
|
|
119
|
+
bibliographyTitle: 'References',
|
|
120
|
+
bibliography: [
|
|
121
|
+
{
|
|
122
|
+
name: 'Bringhurst, R. The Elements of Typographic Style',
|
|
123
|
+
url: 'https://en.wikipedia.org/wiki/The_Elements_of_Typographic_Style',
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
name: 'Brown, T. More Meaningful Typography. A List Apart',
|
|
127
|
+
url: 'https://alistapart.com/article/more-meaningful-typography/',
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: 'Physics of Music. Musical Intervals and Scales',
|
|
131
|
+
url: 'https://es.scribd.com/document/199729396/Physics-of-Music-Notes',
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
howTo: howToData,
|
|
135
|
+
schemas: [faqSchema, howToSchema, appSchema],
|
|
136
|
+
seo: [
|
|
137
|
+
{
|
|
138
|
+
type: 'title',
|
|
139
|
+
text: 'The invisible harmony of web design',
|
|
140
|
+
level: 2,
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
type: 'paragraph',
|
|
144
|
+
html: 'The <strong>Musical Typography Scale</strong> applies the mathematics of musical intervals to typographic design. Just as a musical composition is governed by precise proportions, a solid visual design benefits from a mathematical structure that relates all sizes to each other.',
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
type: 'title',
|
|
148
|
+
text: 'How the modular scale works',
|
|
149
|
+
level: 3,
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
type: 'grid',
|
|
153
|
+
columns: 2,
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
type: 'card',
|
|
157
|
+
title: 'The formula',
|
|
158
|
+
html: '<p>The progression is simple: <code>Size = Base × Ratio<sup>n</sup></code>. Step 0 is your base text. Step 1 is a small subtitle. Step 4 or 5 could be your main H1. The same multiplying constant (the ratio) ensures all relationships are consistent.</p>',
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
type: 'card',
|
|
162
|
+
title: 'Why "Musical"',
|
|
163
|
+
html: '<p>The Pythagoreans discovered that dividing a string in simple proportions (1:2, 2:3, 3:4) produced consonant sounds. These intervals, octave, perfect fifth and perfect fourth, are exactly the typographic ratios. You are composing visual music.</p>',
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
type: 'title',
|
|
167
|
+
text: 'Choosing the right ratio',
|
|
168
|
+
level: 3,
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
type: 'tip',
|
|
172
|
+
html: 'For dense interfaces (dashboards, tables) use small ratios like <code>1.125</code> or <code>1.2</code>. For editorial or portfolio sites, use more expressive ratios like <code>1.5</code> or <code>1.618</code>.',
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
type: 'paragraph',
|
|
176
|
+
html: 'Don\'t limit the scale to <code>font-size</code> only. The same CSS variables work for <code>margin</code>, <code>padding</code> and <code>gap</code>. When whitespace follows the same mathematical progression as the text, the design achieves a level of cohesion that everyone feels but few can explain.',
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
};
|