unit.gl 0.2.3 → 0.3.1
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/README.md +329 -1
- package/css/unit.gl.css +35819 -6
- package/css/unit.gl.docs.css +4156 -0
- package/css/unit.gl.docs.min.css +2 -0
- package/css/unit.gl.min.css +1 -1
- package/js/unit.gl.demo.js +708 -0
- package/js/unit.gl.demo.js.map +1 -0
- package/js/unit.gl.js +25 -0
- package/js/unit.gl.js.map +1 -1
- package/package.json +16 -3
- package/scss/classes/_docs.scss +4690 -0
- package/scss/classes/_index.scss +1 -0
- package/scss/classes/_utilities.scss +1488 -0
- package/scss/docs.scss +11 -0
- package/scss/formats.scss +27 -0
- package/scss/functions/_density.scss +311 -0
- package/scss/functions/_index.scss +3 -0
- package/scss/functions/_scale.scss +211 -1
- package/scss/guide.scss +22 -0
- package/scss/maps/_density.scss +141 -0
- package/scss/maps/_device.scss +13 -20
- package/scss/maps/_index.scss +6 -0
- package/scss/maps/_scale.scss +47 -4
- package/scss/mixins/_device.scss +1 -3
- package/scss/mixins/_display.scss +256 -0
- package/scss/mixins/_format.scss +75 -0
- package/scss/mixins/_index.scss +2 -1
- package/scss/mixins/_unit.scss +115 -6
- package/scss/mixins/_utilities.scss +303 -0
- package/scss/mixins/_view.scss +41 -11
- package/scss/tags/_global.scss +0 -3
- package/scss/tags/_unit.scss +1 -3
- package/scss/utilities.scss +20 -0
- package/scss/variables/_format.scss +80 -0
- package/scss/variables/_index.scss +4 -0
- package/scss/variables/_scale.scss +434 -63
- package/scss/variables/_view.scss +222 -64
- package/ts/demo.ts +889 -0
- package/ts/index.d.ts +72 -0
- package/ts/index.ts +45 -0
- package/scss/mixins/_paper.scss +0 -52
package/ts/demo.ts
ADDED
|
@@ -0,0 +1,889 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Demo Site JavaScript
|
|
3
|
+
* ====================
|
|
4
|
+
*
|
|
5
|
+
* This file contains all JavaScript functionality for the unit.gl demo/docs site.
|
|
6
|
+
* It is separate from the library code and should be loaded after unit.gl.js.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Utility Functions
|
|
11
|
+
// ============================================================================
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Shorthand for matchMedia queries
|
|
15
|
+
*/
|
|
16
|
+
function mq(query: string): boolean {
|
|
17
|
+
try {
|
|
18
|
+
return window.matchMedia(query).matches;
|
|
19
|
+
} catch (_) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Set text content of an element by ID
|
|
26
|
+
*/
|
|
27
|
+
function setText(id: string, value: string | number): void {
|
|
28
|
+
const el = document.getElementById(id);
|
|
29
|
+
if (el) el.textContent = String(value);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Format number with specified decimal places
|
|
34
|
+
*/
|
|
35
|
+
function fmt(n: number, digits = 2): string {
|
|
36
|
+
return (Math.round(n * (10 ** digits)) / (10 ** digits)).toFixed(digits);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get viewport dimensions
|
|
41
|
+
*/
|
|
42
|
+
function getViewport(): { width: number; height: number } {
|
|
43
|
+
const width = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);
|
|
44
|
+
const height = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0);
|
|
45
|
+
return { width, height };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ============================================================================
|
|
49
|
+
// Theme Toggle (base.html.jinja)
|
|
50
|
+
// ============================================================================
|
|
51
|
+
|
|
52
|
+
function initThemeToggle(): void {
|
|
53
|
+
const themeToggle = document.querySelector('[data-toggle="theme"]');
|
|
54
|
+
const html = document.documentElement;
|
|
55
|
+
|
|
56
|
+
// Check for saved theme or system preference
|
|
57
|
+
const savedTheme = localStorage.getItem('theme');
|
|
58
|
+
const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
59
|
+
|
|
60
|
+
if (savedTheme) {
|
|
61
|
+
html.setAttribute('data-theme', savedTheme);
|
|
62
|
+
} else if (systemPrefersDark) {
|
|
63
|
+
html.setAttribute('data-theme', 'dark');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
themeToggle?.addEventListener('click', function () {
|
|
67
|
+
const currentTheme = html.getAttribute('data-theme');
|
|
68
|
+
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
|
69
|
+
html.setAttribute('data-theme', newTheme);
|
|
70
|
+
localStorage.setItem('theme', newTheme);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ============================================================================
|
|
75
|
+
// Grid Toggle (base.html.jinja)
|
|
76
|
+
// ============================================================================
|
|
77
|
+
|
|
78
|
+
function initGridToggle(): void {
|
|
79
|
+
document.querySelectorAll('.grid-controls button').forEach(btn => {
|
|
80
|
+
btn.addEventListener('click', function (this: HTMLButtonElement) {
|
|
81
|
+
const gridType = this.dataset.toggle;
|
|
82
|
+
const grid = document.querySelector(`[data-grid="${gridType}"]`);
|
|
83
|
+
if (grid) {
|
|
84
|
+
grid.classList.toggle('active');
|
|
85
|
+
this.classList.toggle('active');
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ============================================================================
|
|
92
|
+
// Mobile Navigation Toggle
|
|
93
|
+
// ============================================================================
|
|
94
|
+
|
|
95
|
+
function initMobileNav(): void {
|
|
96
|
+
const toggle = document.getElementById('nav-mobile-toggle');
|
|
97
|
+
const menu = document.getElementById('nav-menu');
|
|
98
|
+
const navWrapper = document.querySelector('.nav-wrapper');
|
|
99
|
+
|
|
100
|
+
if (!toggle || !menu) return;
|
|
101
|
+
|
|
102
|
+
toggle.addEventListener('click', function () {
|
|
103
|
+
const isOpen = menu.classList.toggle('is-open');
|
|
104
|
+
navWrapper?.classList.toggle('nav-open', isOpen);
|
|
105
|
+
this.setAttribute('aria-expanded', String(isOpen));
|
|
106
|
+
|
|
107
|
+
// Prevent body scroll when menu is open
|
|
108
|
+
document.body.classList.toggle('nav-open', isOpen);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Close menu when clicking a link
|
|
112
|
+
menu.querySelectorAll('a').forEach(link => {
|
|
113
|
+
link.addEventListener('click', () => {
|
|
114
|
+
menu.classList.remove('is-open');
|
|
115
|
+
navWrapper?.classList.remove('nav-open');
|
|
116
|
+
toggle.setAttribute('aria-expanded', 'false');
|
|
117
|
+
document.body.classList.remove('nav-open');
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Close menu on escape
|
|
122
|
+
document.addEventListener('keydown', function (e) {
|
|
123
|
+
if (e.key === 'Escape' && menu.classList.contains('is-open')) {
|
|
124
|
+
menu.classList.remove('is-open');
|
|
125
|
+
navWrapper?.classList.remove('nav-open');
|
|
126
|
+
toggle.setAttribute('aria-expanded', 'false');
|
|
127
|
+
document.body.classList.remove('nav-open');
|
|
128
|
+
toggle.focus();
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// Close menu when resizing to desktop
|
|
133
|
+
const mediaQuery = window.matchMedia('(min-width: 768px)');
|
|
134
|
+
mediaQuery.addEventListener('change', (e) => {
|
|
135
|
+
if (e.matches && menu.classList.contains('is-open')) {
|
|
136
|
+
menu.classList.remove('is-open');
|
|
137
|
+
navWrapper?.classList.remove('nav-open');
|
|
138
|
+
toggle.setAttribute('aria-expanded', 'false');
|
|
139
|
+
document.body.classList.remove('nav-open');
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ============================================================================
|
|
145
|
+
// Dropdown Menu (base.html.jinja)
|
|
146
|
+
// ============================================================================
|
|
147
|
+
|
|
148
|
+
function initDropdown(): void {
|
|
149
|
+
const dropdown = document.getElementById('nav-dropdown');
|
|
150
|
+
const toggle = dropdown?.querySelector('.nav__dropdown-toggle') as HTMLElement | null;
|
|
151
|
+
|
|
152
|
+
toggle?.addEventListener('click', function (e) {
|
|
153
|
+
e.stopPropagation();
|
|
154
|
+
const isOpen = dropdown!.classList.toggle('open');
|
|
155
|
+
this.setAttribute('aria-expanded', String(isOpen));
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Close dropdown when clicking outside
|
|
159
|
+
document.addEventListener('click', function (e) {
|
|
160
|
+
if (dropdown && !dropdown.contains(e.target as Node)) {
|
|
161
|
+
dropdown.classList.remove('open');
|
|
162
|
+
toggle?.setAttribute('aria-expanded', 'false');
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Close dropdown when pressing Escape
|
|
167
|
+
document.addEventListener('keydown', function (e) {
|
|
168
|
+
if (e.key === 'Escape' && dropdown?.classList.contains('open')) {
|
|
169
|
+
dropdown.classList.remove('open');
|
|
170
|
+
toggle?.setAttribute('aria-expanded', 'false');
|
|
171
|
+
toggle?.focus();
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ============================================================================
|
|
177
|
+
// Layers Demo (layers.html.jinja)
|
|
178
|
+
// ============================================================================
|
|
179
|
+
|
|
180
|
+
function initLayersDemo(): void {
|
|
181
|
+
// Expose functions globally for onclick handlers
|
|
182
|
+
(window as any).toggleLayer = function (id: string): void {
|
|
183
|
+
const layer = document.getElementById('layer-' + id);
|
|
184
|
+
const btn = document.getElementById('btn-' + id);
|
|
185
|
+
|
|
186
|
+
layer?.classList.toggle('layer-box--hidden');
|
|
187
|
+
btn?.classList.toggle('layer-btn--active');
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
(window as any).showAll = function (): void {
|
|
191
|
+
document.querySelectorAll('.layer-box').forEach(el => {
|
|
192
|
+
el.classList.remove('layer-box--hidden');
|
|
193
|
+
});
|
|
194
|
+
document.querySelectorAll('.layer-btn').forEach(el => {
|
|
195
|
+
if (el.id.startsWith('btn-')) {
|
|
196
|
+
el.classList.add('layer-btn--active');
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
(window as any).hideAll = function (): void {
|
|
202
|
+
document.querySelectorAll('.layer-box').forEach(el => {
|
|
203
|
+
el.classList.add('layer-box--hidden');
|
|
204
|
+
});
|
|
205
|
+
document.querySelectorAll('.layer-btn').forEach(el => {
|
|
206
|
+
if (el.id.startsWith('btn-')) {
|
|
207
|
+
el.classList.remove('layer-btn--active');
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ============================================================================
|
|
214
|
+
// Device Detection Demo (test_device.html.jinja)
|
|
215
|
+
// ============================================================================
|
|
216
|
+
|
|
217
|
+
function initDeviceDemo(): void {
|
|
218
|
+
if (!document.getElementById('dev-width')) return;
|
|
219
|
+
|
|
220
|
+
function classifyOrientation(width: number, height: number): string {
|
|
221
|
+
if (mq('(orientation: portrait)')) return 'portrait';
|
|
222
|
+
if (mq('(orientation: landscape)')) return 'landscape';
|
|
223
|
+
return width >= height ? 'landscape' : 'portrait';
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function getColorScheme(): string {
|
|
227
|
+
if (mq('(prefers-color-scheme: dark)')) return 'dark';
|
|
228
|
+
if (mq('(prefers-color-scheme: light)')) return 'light';
|
|
229
|
+
return 'no-preference';
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function getReducedMotion(): string {
|
|
233
|
+
if (mq('(prefers-reduced-motion: reduce)')) return 'reduce';
|
|
234
|
+
if (mq('(prefers-reduced-motion: no-preference)')) return 'no-preference';
|
|
235
|
+
return 'unknown';
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function getPrefersContrast(): string {
|
|
239
|
+
if (mq('(prefers-contrast: more)')) return 'more';
|
|
240
|
+
if (mq('(prefers-contrast: less)')) return 'less';
|
|
241
|
+
if (mq('(prefers-contrast: custom)')) return 'custom';
|
|
242
|
+
if (mq('(prefers-contrast: no-preference)')) return 'no-preference';
|
|
243
|
+
return 'unknown';
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function getForcedColors(): string {
|
|
247
|
+
if (mq('(forced-colors: active)')) return 'active';
|
|
248
|
+
if (mq('(forced-colors: none)')) return 'none';
|
|
249
|
+
return 'unknown';
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function getHover(): string {
|
|
253
|
+
if (mq('(hover: hover)')) return 'hover';
|
|
254
|
+
if (mq('(hover: none)')) return 'none';
|
|
255
|
+
return 'unknown';
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function getPointer(): string {
|
|
259
|
+
if (mq('(pointer: fine)')) return 'fine';
|
|
260
|
+
if (mq('(pointer: coarse)')) return 'coarse';
|
|
261
|
+
if (mq('(pointer: none)')) return 'none';
|
|
262
|
+
return 'unknown';
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function getAnyHover(): string {
|
|
266
|
+
if (mq('(any-hover: hover)')) return 'hover';
|
|
267
|
+
if (mq('(any-hover: none)')) return 'none';
|
|
268
|
+
return 'unknown';
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function getAnyPointer(): string {
|
|
272
|
+
if (mq('(any-pointer: fine)')) return 'fine';
|
|
273
|
+
if (mq('(any-pointer: coarse)')) return 'coarse';
|
|
274
|
+
if (mq('(any-pointer: none)')) return 'none';
|
|
275
|
+
return 'unknown';
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function getColorGamut(): string {
|
|
279
|
+
if (mq('(color-gamut: rec2020)')) return 'rec2020';
|
|
280
|
+
if (mq('(color-gamut: p3)')) return 'p3';
|
|
281
|
+
if (mq('(color-gamut: srgb)')) return 'srgb';
|
|
282
|
+
return 'unknown';
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function getDisplayMode(): string {
|
|
286
|
+
if (mq('(display-mode: fullscreen)')) return 'fullscreen';
|
|
287
|
+
if (mq('(display-mode: standalone)')) return 'standalone';
|
|
288
|
+
if (mq('(display-mode: minimal-ui)')) return 'minimal-ui';
|
|
289
|
+
if (mq('(display-mode: browser)')) return 'browser';
|
|
290
|
+
return 'unknown';
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function getReducedTransparency(): string {
|
|
294
|
+
if (mq('(prefers-reduced-transparency: reduce)')) return 'reduce';
|
|
295
|
+
if (mq('(prefers-reduced-transparency: no-preference)')) return 'no-preference';
|
|
296
|
+
return 'unknown';
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function getReducedData(): string {
|
|
300
|
+
if (mq('(prefers-reduced-data: reduce)')) return 'reduce';
|
|
301
|
+
if (mq('(prefers-reduced-data: no-preference)')) return 'no-preference';
|
|
302
|
+
return 'unknown';
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function scoreDeviceMatch(
|
|
306
|
+
{ width, dpr }: { width: number; dpr: number },
|
|
307
|
+
row: HTMLElement
|
|
308
|
+
): { match: boolean; label: string } {
|
|
309
|
+
const min = Number(row.dataset.min || '0');
|
|
310
|
+
const max = Number(row.dataset.max || '0');
|
|
311
|
+
const targetDpr = Number(row.dataset.dpr || '1');
|
|
312
|
+
|
|
313
|
+
const inRange = width >= min && width <= max;
|
|
314
|
+
const dprDelta = Math.abs((dpr || 1) - targetDpr);
|
|
315
|
+
const dprOk = dprDelta <= 0.6;
|
|
316
|
+
|
|
317
|
+
if (!inRange) return { match: false, label: '—' };
|
|
318
|
+
if (!dprOk) return { match: true, label: 'width' };
|
|
319
|
+
return { match: true, label: 'width + dpr' };
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function updateUI(): void {
|
|
323
|
+
const { width, height } = getViewport();
|
|
324
|
+
const dpr = Number(window.devicePixelRatio || 1);
|
|
325
|
+
|
|
326
|
+
setText('dev-width', width);
|
|
327
|
+
setText('dev-height', height);
|
|
328
|
+
setText('dev-dpr', dpr.toFixed(2));
|
|
329
|
+
setText('dev-orientation', classifyOrientation(width, height));
|
|
330
|
+
setText('dev-touch', (navigator.maxTouchPoints || 0));
|
|
331
|
+
|
|
332
|
+
setText('mf-hover', getHover());
|
|
333
|
+
setText('mf-pointer', getPointer());
|
|
334
|
+
setText('mf-any-hover', getAnyHover());
|
|
335
|
+
setText('mf-any-pointer', getAnyPointer());
|
|
336
|
+
|
|
337
|
+
setText('mf-scheme', getColorScheme());
|
|
338
|
+
setText('mf-motion', getReducedMotion());
|
|
339
|
+
setText('mf-contrast', getPrefersContrast());
|
|
340
|
+
setText('mf-forced', getForcedColors());
|
|
341
|
+
|
|
342
|
+
setText('mf-gamut', getColorGamut());
|
|
343
|
+
setText('mf-display', getDisplayMode());
|
|
344
|
+
setText('mf-transparency', getReducedTransparency());
|
|
345
|
+
setText('mf-data', getReducedData());
|
|
346
|
+
|
|
347
|
+
const filterEl = document.getElementById('dev-filter') as HTMLInputElement | null;
|
|
348
|
+
const filterValue = String(filterEl?.value || '').trim().toLowerCase();
|
|
349
|
+
|
|
350
|
+
let matches = 0;
|
|
351
|
+
document.querySelectorAll<HTMLElement>('.device-row').forEach(row => {
|
|
352
|
+
const key = String(row.dataset.key || '').toLowerCase();
|
|
353
|
+
const visible = !filterValue || key.includes(filterValue);
|
|
354
|
+
row.classList.toggle('is-filtered', !visible);
|
|
355
|
+
|
|
356
|
+
const badge = row.querySelector('[data-role="match"]');
|
|
357
|
+
if (!visible) {
|
|
358
|
+
row.classList.remove('is-match');
|
|
359
|
+
if (badge) badge.textContent = '—';
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const result = scoreDeviceMatch({ width, dpr }, row);
|
|
364
|
+
row.classList.toggle('is-match', result.match);
|
|
365
|
+
if (badge) badge.textContent = result.label;
|
|
366
|
+
if (result.match) matches += 1;
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
setText('dev-matches', matches);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
window.addEventListener('resize', updateUI);
|
|
373
|
+
window.addEventListener('orientationchange', updateUI);
|
|
374
|
+
|
|
375
|
+
const filterEl = document.getElementById('dev-filter');
|
|
376
|
+
if (filterEl) filterEl.addEventListener('input', updateUI);
|
|
377
|
+
|
|
378
|
+
updateUI();
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// ============================================================================
|
|
382
|
+
// Q Scale Demo (test_qscale.html.jinja)
|
|
383
|
+
// ============================================================================
|
|
384
|
+
|
|
385
|
+
function initQScaleDemo(): void {
|
|
386
|
+
if (!document.getElementById('qs-root')) return;
|
|
387
|
+
|
|
388
|
+
const qSteps = [0, 1, 2, 4, 6, 8, 12, 16, 20, 24, 32, 40, 48, 56, 64, 72, 80, 96, 112, 128, 144, 160, 192, 224, 256];
|
|
389
|
+
const slider = document.getElementById('qs-range') as HTMLInputElement | null;
|
|
390
|
+
|
|
391
|
+
function rootPx(): number {
|
|
392
|
+
const raw = getComputedStyle(document.documentElement).fontSize;
|
|
393
|
+
const value = Number.parseFloat(raw);
|
|
394
|
+
return Number.isFinite(value) ? value : 16;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function toPx(step: number): number {
|
|
398
|
+
return (step * rootPx()) / 16;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function toRem(step: number): number {
|
|
402
|
+
return step / 16;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function setStep(step: number): void {
|
|
406
|
+
const stepNum = Number(step);
|
|
407
|
+
const px = toPx(stepNum);
|
|
408
|
+
const rem = toRem(stepNum);
|
|
409
|
+
|
|
410
|
+
setText('qs-root', fmt(rootPx(), 2));
|
|
411
|
+
setText('qs-step', stepNum);
|
|
412
|
+
setText('qs-px', fmt(px, 2));
|
|
413
|
+
setText('qs-rem', fmt(rem, 4));
|
|
414
|
+
|
|
415
|
+
const className = `.p_q${stepNum}`;
|
|
416
|
+
const classEl = document.getElementById('qs-class');
|
|
417
|
+
if (classEl) classEl.textContent = className;
|
|
418
|
+
|
|
419
|
+
// Visual: box size responds to the step
|
|
420
|
+
const size = Math.max(8, px);
|
|
421
|
+
const pad = Math.max(2, px / 4);
|
|
422
|
+
const gap = Math.max(2, px / 6);
|
|
423
|
+
|
|
424
|
+
const box = document.getElementById('qs-box');
|
|
425
|
+
if (box) {
|
|
426
|
+
box.style.width = `${size}px`;
|
|
427
|
+
box.style.height = `${size}px`;
|
|
428
|
+
box.style.padding = `${pad}px`;
|
|
429
|
+
box.style.gap = `${gap}px`;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
setText('qs-w', `${fmt(size, 0)}px`);
|
|
433
|
+
setText('qs-h', `${fmt(size, 0)}px`);
|
|
434
|
+
setText('qs-pad', `${fmt(pad, 0)}px`);
|
|
435
|
+
setText('qs-gap', `${fmt(gap, 0)}px`);
|
|
436
|
+
|
|
437
|
+
setText('qs-u-step', stepNum);
|
|
438
|
+
setText('qs-u-step2', stepNum);
|
|
439
|
+
setText('qs-u-step3', stepNum);
|
|
440
|
+
setText('qs-u-step4', stepNum);
|
|
441
|
+
setText('qs-u-step5', stepNum);
|
|
442
|
+
|
|
443
|
+
// Update slider position
|
|
444
|
+
if (slider) {
|
|
445
|
+
const sliderIndex = qSteps.indexOf(stepNum);
|
|
446
|
+
if (sliderIndex >= 0) {
|
|
447
|
+
slider.value = String(sliderIndex);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// UI state - update chip active states
|
|
452
|
+
document.querySelectorAll<HTMLElement>('.qscale-chip').forEach(btn => {
|
|
453
|
+
btn.classList.toggle('active', Number(btn.dataset.step) === stepNum);
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Slider event listener
|
|
458
|
+
if (slider) {
|
|
459
|
+
slider.addEventListener('input', (e) => {
|
|
460
|
+
const index = parseInt((e.target as HTMLInputElement).value);
|
|
461
|
+
if (index >= 0 && index < qSteps.length) {
|
|
462
|
+
setStep(qSteps[index]);
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Chip click handlers (for chips defined in template)
|
|
468
|
+
document.querySelectorAll<HTMLElement>('.qscale-chip').forEach(chip => {
|
|
469
|
+
chip.addEventListener('click', () => {
|
|
470
|
+
const step = parseInt(chip.dataset.step || '16');
|
|
471
|
+
setStep(step);
|
|
472
|
+
});
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
// Initialize chips dynamically if container exists
|
|
476
|
+
const chipRow = document.getElementById('qscale-chip-row');
|
|
477
|
+
if (chipRow) {
|
|
478
|
+
qSteps.forEach(step => {
|
|
479
|
+
const btn = document.createElement('button');
|
|
480
|
+
btn.className = 'qscale-chip' + (step === 16 ? ' active' : '');
|
|
481
|
+
btn.textContent = String(step);
|
|
482
|
+
btn.dataset.step = String(step);
|
|
483
|
+
btn.addEventListener('click', () => setStep(step));
|
|
484
|
+
chipRow.appendChild(btn);
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Grid visualization for qscale page
|
|
489
|
+
function createQScaleGrid(canvasId: string, scale: number, className: string): void {
|
|
490
|
+
const canvas = document.getElementById(canvasId);
|
|
491
|
+
if (!canvas) return;
|
|
492
|
+
|
|
493
|
+
canvas.innerHTML = '';
|
|
494
|
+
const width = canvas.offsetWidth;
|
|
495
|
+
|
|
496
|
+
for (let x = scale; x < width; x += scale) {
|
|
497
|
+
const line = document.createElement('div');
|
|
498
|
+
line.className = 'grid-line ' + className;
|
|
499
|
+
|
|
500
|
+
// Mark LCM alignment points
|
|
501
|
+
if (x % 20 === 0) {
|
|
502
|
+
line.classList.add('lcm');
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
line.style.left = x + 'px';
|
|
506
|
+
canvas.appendChild(line);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Initialize grids if they exist on this page
|
|
511
|
+
createQScaleGrid('qs-type-grid', 4, 'type');
|
|
512
|
+
createQScaleGrid('qs-line-grid', 5, 'line-scale');
|
|
513
|
+
|
|
514
|
+
// Combined grid showing only LCM points
|
|
515
|
+
const combinedCanvas = document.getElementById('qs-combined-grid');
|
|
516
|
+
if (combinedCanvas) {
|
|
517
|
+
combinedCanvas.innerHTML = '';
|
|
518
|
+
const width = combinedCanvas.offsetWidth;
|
|
519
|
+
for (let x = 20; x < width; x += 20) {
|
|
520
|
+
const line = document.createElement('div');
|
|
521
|
+
line.className = 'grid-line lcm';
|
|
522
|
+
line.style.left = x + 'px';
|
|
523
|
+
combinedCanvas.appendChild(line);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Initial render
|
|
528
|
+
setStep(16);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// ============================================================================
|
|
532
|
+
// Density Demo (density.html.jinja)
|
|
533
|
+
// ============================================================================
|
|
534
|
+
|
|
535
|
+
function initDensityDemo(): void {
|
|
536
|
+
if (!document.getElementById('current-dpr')) return;
|
|
537
|
+
|
|
538
|
+
function updateDeviceInfo(): void {
|
|
539
|
+
const dpr = window.devicePixelRatio || 1;
|
|
540
|
+
const dpi = Math.round(dpr * 96);
|
|
541
|
+
|
|
542
|
+
setText('current-dpr', dpr.toFixed(2) + '×');
|
|
543
|
+
setText('current-dpi', dpi + 'dpi');
|
|
544
|
+
|
|
545
|
+
// Determine bucket
|
|
546
|
+
let bucket = 'mdpi';
|
|
547
|
+
if (dpr >= 4) bucket = 'xxxhdpi';
|
|
548
|
+
else if (dpr >= 3) bucket = 'xxhdpi';
|
|
549
|
+
else if (dpr >= 2) bucket = 'xhdpi';
|
|
550
|
+
else if (dpr >= 1.5) bucket = 'hdpi';
|
|
551
|
+
else if (dpr < 1) bucket = 'ldpi';
|
|
552
|
+
|
|
553
|
+
setText('current-bucket', bucket);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function updateCalculator(): void {
|
|
557
|
+
const input = document.getElementById('calc-q') as HTMLInputElement | null;
|
|
558
|
+
const q = parseFloat(input?.value || '0') || 0;
|
|
559
|
+
const mm = q * 0.25;
|
|
560
|
+
const inches = mm / 25.4;
|
|
561
|
+
const pt = mm / 0.3528;
|
|
562
|
+
const rem = q * 0.0625;
|
|
563
|
+
|
|
564
|
+
setText('calc-mm', mm.toFixed(2) + 'mm');
|
|
565
|
+
setText('calc-in', inches.toFixed(3) + 'in');
|
|
566
|
+
setText('calc-pt', pt.toFixed(2) + 'pt');
|
|
567
|
+
setText('calc-rem', rem.toFixed(4) + 'rem');
|
|
568
|
+
setText('calc-px1', q + 'px');
|
|
569
|
+
setText('calc-px2', (q * 2) + 'px');
|
|
570
|
+
setText('calc-px3', (q * 3) + 'px');
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const calcInput = document.getElementById('calc-q');
|
|
574
|
+
calcInput?.addEventListener('input', updateCalculator);
|
|
575
|
+
|
|
576
|
+
// Initialize
|
|
577
|
+
updateDeviceInfo();
|
|
578
|
+
updateCalculator();
|
|
579
|
+
|
|
580
|
+
// Update on DPR change
|
|
581
|
+
if (window.matchMedia) {
|
|
582
|
+
const checkDPR = (): void => {
|
|
583
|
+
const mqQuery = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);
|
|
584
|
+
mqQuery.addEventListener('change', () => {
|
|
585
|
+
updateDeviceInfo();
|
|
586
|
+
checkDPR();
|
|
587
|
+
}, { once: true });
|
|
588
|
+
};
|
|
589
|
+
checkDPR();
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// ============================================================================
|
|
594
|
+
// Breakpoints Demo (test_breakpoints.html.jinja)
|
|
595
|
+
// ============================================================================
|
|
596
|
+
|
|
597
|
+
function initBreakpointsDemo(): void {
|
|
598
|
+
if (!document.getElementById('bp-width')) return;
|
|
599
|
+
|
|
600
|
+
const breakpoints = [
|
|
601
|
+
{ key: 'ul', min: 4320 },
|
|
602
|
+
{ key: 'xl', min: 2880 },
|
|
603
|
+
{ key: 'lg', min: 2160 },
|
|
604
|
+
{ key: 'md', min: 1440 },
|
|
605
|
+
{ key: 'sm', min: 720 },
|
|
606
|
+
{ key: 'xs', min: 540 },
|
|
607
|
+
{ key: 'ss', min: 360 },
|
|
608
|
+
{ key: 'us', min: 240 },
|
|
609
|
+
];
|
|
610
|
+
|
|
611
|
+
function updateBreakpointUI(): void {
|
|
612
|
+
const width = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);
|
|
613
|
+
const active = breakpoints.find(bp => width >= bp.min) || breakpoints[breakpoints.length - 1];
|
|
614
|
+
|
|
615
|
+
setText('bp-width', String(width));
|
|
616
|
+
setText('bp-active', active.key);
|
|
617
|
+
setText('bp-rule', `(min-width: ${active.min}px)`);
|
|
618
|
+
|
|
619
|
+
document.querySelectorAll<HTMLElement>('.bp-row').forEach(row => {
|
|
620
|
+
row.classList.toggle('active', row.dataset.bp === active.key);
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
window.addEventListener('resize', updateBreakpointUI);
|
|
625
|
+
updateBreakpointUI();
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// ============================================================================
|
|
629
|
+
// Paper Demo (test_paper.html.jinja)
|
|
630
|
+
// ============================================================================
|
|
631
|
+
|
|
632
|
+
function initPaperDemo(): void {
|
|
633
|
+
const select = document.getElementById('paper-format') as HTMLSelectElement | null;
|
|
634
|
+
const preview = document.getElementById('paper-preview') as HTMLElement | null;
|
|
635
|
+
const container = document.getElementById('paper-preview-container') as HTMLElement | null;
|
|
636
|
+
const title = document.getElementById('paper-title');
|
|
637
|
+
const outW = document.getElementById('paper-w');
|
|
638
|
+
const outH = document.getElementById('paper-h');
|
|
639
|
+
const code = document.getElementById('paper-code');
|
|
640
|
+
const codeOr = document.getElementById('paper-code-or');
|
|
641
|
+
const scaleDisplay = document.getElementById('scale-display');
|
|
642
|
+
|
|
643
|
+
if (!select || !preview) return;
|
|
644
|
+
|
|
645
|
+
let orientation = 'portrait';
|
|
646
|
+
|
|
647
|
+
// Reference size for scaling (A0 is the largest common format)
|
|
648
|
+
const maxRefSize = 1189; // A0 height in mm
|
|
649
|
+
const containerMaxPx = 400; // Max pixel size for preview
|
|
650
|
+
|
|
651
|
+
function currentDims(): { key: string; w: number; h: number } {
|
|
652
|
+
const opt = select?.selectedOptions?.[0];
|
|
653
|
+
if (!opt) return { key: 'q04', w: 180, h: 270 };
|
|
654
|
+
|
|
655
|
+
const key = opt.value;
|
|
656
|
+
const w = Number((opt as HTMLOptionElement).dataset.w);
|
|
657
|
+
const h = Number((opt as HTMLOptionElement).dataset.h);
|
|
658
|
+
return { key, w, h };
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
function apply(): void {
|
|
662
|
+
if (!preview || !select || !container) return;
|
|
663
|
+
|
|
664
|
+
const { key, w, h } = currentDims();
|
|
665
|
+
const pw = orientation === 'landscape' ? h : w;
|
|
666
|
+
const ph = orientation === 'landscape' ? w : h;
|
|
667
|
+
|
|
668
|
+
// Calculate scale factor based on container size
|
|
669
|
+
const maxDim = Math.max(pw, ph);
|
|
670
|
+
const scale = containerMaxPx / maxRefSize;
|
|
671
|
+
const scaledW = pw * scale;
|
|
672
|
+
const scaledH = ph * scale;
|
|
673
|
+
|
|
674
|
+
// Apply actual pixel dimensions
|
|
675
|
+
preview.style.width = `${scaledW}px`;
|
|
676
|
+
preview.style.height = `${scaledH}px`;
|
|
677
|
+
preview.style.aspectRatio = 'auto';
|
|
678
|
+
|
|
679
|
+
// Update scale indicator
|
|
680
|
+
const displayScale = (maxRefSize / maxDim).toFixed(1);
|
|
681
|
+
if (scaleDisplay) scaleDisplay.textContent = `Scale: 1:${displayScale}`;
|
|
682
|
+
|
|
683
|
+
if (title) title.textContent = key;
|
|
684
|
+
if (outW) outW.textContent = String(pw);
|
|
685
|
+
if (outH) outH.textContent = String(ph);
|
|
686
|
+
if (code) code.textContent = key;
|
|
687
|
+
if (codeOr) codeOr.textContent = orientation;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
document.querySelectorAll<HTMLElement>('.paper-orient__btn').forEach(btn => {
|
|
691
|
+
btn.addEventListener('click', () => {
|
|
692
|
+
orientation = btn.dataset.orient || 'portrait';
|
|
693
|
+
document.querySelectorAll('.paper-orient__btn').forEach(b => b.classList.toggle('active', b === btn));
|
|
694
|
+
apply();
|
|
695
|
+
});
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
select.addEventListener('change', apply);
|
|
699
|
+
apply();
|
|
700
|
+
|
|
701
|
+
// Comparison stacks - render sheets at relative scale
|
|
702
|
+
function renderComparisonStacks(): void {
|
|
703
|
+
const scaleFactor = 0.5; // pixels per mm
|
|
704
|
+
|
|
705
|
+
document.querySelectorAll<HTMLElement>('.comparison-sheet').forEach(sheet => {
|
|
706
|
+
const w = Number(sheet.dataset.w || 0);
|
|
707
|
+
const h = Number(sheet.dataset.h || 0);
|
|
708
|
+
sheet.style.width = `${w * scaleFactor}px`;
|
|
709
|
+
sheet.style.height = `${h * scaleFactor}px`;
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
renderComparisonStacks();
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// ============================================================================
|
|
717
|
+
// Baseline Grid Demo (guide_baseline.html.jinja)
|
|
718
|
+
// ============================================================================
|
|
719
|
+
|
|
720
|
+
function initBaselineDemo(): void {
|
|
721
|
+
// Expose toggle function globally
|
|
722
|
+
(window as any).toggleBaseline = function (): void {
|
|
723
|
+
document.querySelectorAll('.guide--baseline, .guide--baseline_custom').forEach(el => {
|
|
724
|
+
el.classList.toggle('guide--baseline--hidden');
|
|
725
|
+
});
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// ============================================================================
|
|
730
|
+
// Hybrid Scale Demo (test_hybrid_scale.html.jinja)
|
|
731
|
+
// ============================================================================
|
|
732
|
+
|
|
733
|
+
function initHybridScaleDemo(): void {
|
|
734
|
+
const slider = document.getElementById('scaleSlider') as HTMLInputElement | null;
|
|
735
|
+
const sliderValue = document.getElementById('sliderValue');
|
|
736
|
+
const chips = document.querySelectorAll<HTMLElement>('.chip');
|
|
737
|
+
const previewBox = document.getElementById('previewBox');
|
|
738
|
+
|
|
739
|
+
if (!slider) return;
|
|
740
|
+
|
|
741
|
+
// Metric elements
|
|
742
|
+
const typeQ = document.getElementById('typeQ');
|
|
743
|
+
const typePx = document.getElementById('typePx');
|
|
744
|
+
const typeMm = document.getElementById('typeMm');
|
|
745
|
+
const typeRem = document.getElementById('typeRem');
|
|
746
|
+
|
|
747
|
+
const lineQ = document.getElementById('lineQ');
|
|
748
|
+
const linePx = document.getElementById('linePx');
|
|
749
|
+
const lineMm = document.getElementById('lineMm');
|
|
750
|
+
const lineRem = document.getElementById('lineRem');
|
|
751
|
+
|
|
752
|
+
const lcmValue = document.getElementById('lcmValue');
|
|
753
|
+
const lcmMultiple = document.getElementById('lcmMultiple');
|
|
754
|
+
const lcmAligned = document.getElementById('lcmAligned');
|
|
755
|
+
|
|
756
|
+
function updateScale(value: number): void {
|
|
757
|
+
const typeVal = value * 4;
|
|
758
|
+
const lineVal = value * 5;
|
|
759
|
+
const lcmVal = Math.ceil(Math.max(typeVal, lineVal) / 20) * 20;
|
|
760
|
+
const isAligned = typeVal % 20 === 0 && lineVal % 20 === 0;
|
|
761
|
+
|
|
762
|
+
// Update slider
|
|
763
|
+
slider!.value = String(value);
|
|
764
|
+
if (sliderValue) sliderValue.textContent = String(value);
|
|
765
|
+
|
|
766
|
+
// Update chips
|
|
767
|
+
chips.forEach(chip => {
|
|
768
|
+
chip.classList.toggle('active', parseInt(chip.dataset.value || '0') === value);
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
// Update metrics
|
|
772
|
+
if (typeQ) typeQ.textContent = typeVal + 'Q';
|
|
773
|
+
if (typePx) typePx.textContent = typeVal + 'px';
|
|
774
|
+
if (typeMm) typeMm.textContent = (typeVal / 4).toFixed(1) + 'mm';
|
|
775
|
+
if (typeRem) typeRem.textContent = (typeVal / 16).toFixed(3) + 'rem';
|
|
776
|
+
|
|
777
|
+
if (lineQ) lineQ.textContent = lineVal + 'Q';
|
|
778
|
+
if (linePx) linePx.textContent = lineVal + 'px';
|
|
779
|
+
if (lineMm) lineMm.textContent = (lineVal / 4).toFixed(2) + 'mm';
|
|
780
|
+
if (lineRem) lineRem.textContent = (lineVal / 16).toFixed(3) + 'rem';
|
|
781
|
+
|
|
782
|
+
if (lcmValue) lcmValue.textContent = lcmVal + 'Q';
|
|
783
|
+
if (lcmMultiple) lcmMultiple.textContent = (lcmVal / 20) + '× LCM';
|
|
784
|
+
|
|
785
|
+
if (lcmAligned) {
|
|
786
|
+
if (typeVal === lineVal && typeVal % 20 === 0) {
|
|
787
|
+
lcmAligned.textContent = '✓ Perfect Alignment';
|
|
788
|
+
lcmAligned.classList.add('aligned');
|
|
789
|
+
} else {
|
|
790
|
+
lcmAligned.textContent = 'Next: ' + lcmVal + 'Q';
|
|
791
|
+
lcmAligned.classList.remove('aligned');
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// Update preview box
|
|
796
|
+
if (previewBox) {
|
|
797
|
+
const size = Math.min(Math.max(typeVal * 2, 40), 200);
|
|
798
|
+
previewBox.style.width = size + 'px';
|
|
799
|
+
previewBox.style.height = size + 'px';
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// Event listeners
|
|
804
|
+
slider.addEventListener('input', (e) => updateScale(parseInt((e.target as HTMLInputElement).value)));
|
|
805
|
+
|
|
806
|
+
chips.forEach(chip => {
|
|
807
|
+
chip.addEventListener('click', () => {
|
|
808
|
+
updateScale(parseInt(chip.dataset.value || '4'));
|
|
809
|
+
});
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
// Initialize
|
|
813
|
+
updateScale(4);
|
|
814
|
+
|
|
815
|
+
// Grid visualization
|
|
816
|
+
function createGrid(canvasId: string, scale: number, className: string, showLCM = false): void {
|
|
817
|
+
const canvas = document.getElementById(canvasId);
|
|
818
|
+
if (!canvas) return;
|
|
819
|
+
|
|
820
|
+
canvas.innerHTML = '';
|
|
821
|
+
const width = canvas.offsetWidth;
|
|
822
|
+
|
|
823
|
+
for (let x = scale; x < width; x += scale) {
|
|
824
|
+
const line = document.createElement('div');
|
|
825
|
+
line.className = 'grid-line ' + className;
|
|
826
|
+
|
|
827
|
+
if (showLCM && x % 20 === 0) {
|
|
828
|
+
line.classList.add('lcm');
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
line.style.left = x + 'px';
|
|
832
|
+
canvas.appendChild(line);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
function createCombinedGrid(canvasId: string): void {
|
|
837
|
+
const canvas = document.getElementById(canvasId);
|
|
838
|
+
if (!canvas) return;
|
|
839
|
+
|
|
840
|
+
canvas.innerHTML = '';
|
|
841
|
+
const width = canvas.offsetWidth;
|
|
842
|
+
|
|
843
|
+
// Add LCM lines (20Q intervals)
|
|
844
|
+
for (let x = 20; x < width; x += 20) {
|
|
845
|
+
const line = document.createElement('div');
|
|
846
|
+
line.className = 'grid-line lcm';
|
|
847
|
+
line.style.left = x + 'px';
|
|
848
|
+
canvas.appendChild(line);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// Initialize grid visualizations if containers exist
|
|
853
|
+
function initGrids(): void {
|
|
854
|
+
createGrid('type-grid-canvas', 4, 'type-line', false);
|
|
855
|
+
createGrid('line-grid-canvas', 5, 'line-line', false);
|
|
856
|
+
createCombinedGrid('combined-grid-canvas');
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
initGrids();
|
|
860
|
+
|
|
861
|
+
// Redraw grids on window resize
|
|
862
|
+
let resizeTimeout: ReturnType<typeof setTimeout>;
|
|
863
|
+
window.addEventListener('resize', () => {
|
|
864
|
+
clearTimeout(resizeTimeout);
|
|
865
|
+
resizeTimeout = setTimeout(initGrids, 150);
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// ============================================================================
|
|
870
|
+
// Initialize All Demos
|
|
871
|
+
// ============================================================================
|
|
872
|
+
|
|
873
|
+
document.addEventListener('DOMContentLoaded', function () {
|
|
874
|
+
// Core functionality (always runs)
|
|
875
|
+
initThemeToggle();
|
|
876
|
+
initGridToggle();
|
|
877
|
+
initMobileNav();
|
|
878
|
+
initDropdown();
|
|
879
|
+
|
|
880
|
+
// Page-specific demos (only run if relevant elements exist)
|
|
881
|
+
initLayersDemo();
|
|
882
|
+
initDeviceDemo();
|
|
883
|
+
initQScaleDemo();
|
|
884
|
+
initDensityDemo();
|
|
885
|
+
initBreakpointsDemo();
|
|
886
|
+
initPaperDemo();
|
|
887
|
+
initBaselineDemo();
|
|
888
|
+
initHybridScaleDemo();
|
|
889
|
+
});
|