bedrock-flows 0.7.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.
Files changed (85) hide show
  1. package/auth-schema.sql +8 -0
  2. package/bin/bedrock-flows.mjs +127 -0
  3. package/lib/setup.mjs +262 -0
  4. package/package.json +11 -0
  5. package/template/.storybook/main.js +46 -0
  6. package/template/.storybook/manager-head.html +963 -0
  7. package/template/.storybook/preview-head.html +35 -0
  8. package/template/.storybook/preview.js +23 -0
  9. package/template/CHANGELOG.md +236 -0
  10. package/template/README.md +26 -0
  11. package/template/apps/dashboard/index.html +15 -0
  12. package/template/apps/dashboard/package.json +22 -0
  13. package/template/apps/dashboard/src/App.module.css +1318 -0
  14. package/template/apps/dashboard/src/App.tsx +2716 -0
  15. package/template/apps/dashboard/src/auth-client.ts +17 -0
  16. package/template/apps/dashboard/src/changelog.tsx +92 -0
  17. package/template/apps/dashboard/src/index.css +86 -0
  18. package/template/apps/dashboard/src/main.tsx +15 -0
  19. package/template/apps/dashboard/src/theme.ts +31 -0
  20. package/template/apps/dashboard/src/vite-env.d.ts +4 -0
  21. package/template/apps/dashboard/vite.config.ts +48 -0
  22. package/template/apps/worker/.dev.vars.example +50 -0
  23. package/template/apps/worker/package.json +19 -0
  24. package/template/apps/worker/src/index.ts +295 -0
  25. package/template/apps/worker/tsconfig.json +11 -0
  26. package/template/apps/worker/wrangler.jsonc +29 -0
  27. package/template/bedrock.config.ts +16 -0
  28. package/template/design-system/README.md +97 -0
  29. package/template/design-system/starter-v1/components/button/component.css +42 -0
  30. package/template/design-system/starter-v1/components/button/danger.html +21 -0
  31. package/template/design-system/starter-v1/components/button/default.html +21 -0
  32. package/template/design-system/starter-v1/components/button/disabled.html +21 -0
  33. package/template/design-system/starter-v1/components/button/ghost.html +21 -0
  34. package/template/design-system/starter-v1/components/button/macro.njk +14 -0
  35. package/template/design-system/starter-v1/components/button/primary.html +21 -0
  36. package/template/design-system/starter-v1/components/button/variants.json +30 -0
  37. package/template/design-system/starter-v1/ds.json +3 -0
  38. package/template/design-system/starter-v1/global.css +52 -0
  39. package/template/design-system/starter-v1/style.css +107 -0
  40. package/template/gitignore +8 -0
  41. package/template/package.json +41 -0
  42. package/template/prototypes/F-001-hello/1-welcome.njk +30 -0
  43. package/template/prototypes/F-001-hello/2-form.njk +46 -0
  44. package/template/prototypes/F-001-hello/3-done.njk +29 -0
  45. package/template/prototypes/F-001-hello/meta.json +6 -0
  46. package/template/prototypes/_shared/_auth-gate.njk +54 -0
  47. package/template/prototypes/_shared/delivery.njk +43 -0
  48. package/template/prototypes/_shared/layout.njk +15 -0
  49. package/template/prototypes/_shared/screen.njk +1818 -0
  50. package/template/prototypes/_shared/wireflow.njk +4731 -0
  51. package/template/public/auth-gate.css +150 -0
  52. package/template/public/bedrock/color-inspector.js +284 -0
  53. package/template/public/bedrock/component-overlay.js +219 -0
  54. package/template/public/bedrock/data/bedrock-config.js +45 -0
  55. package/template/public/bedrock/font-size-overlay.js +590 -0
  56. package/template/public/bedrock/grid-overlay.js +379 -0
  57. package/template/public/bedrock/prototype-navigation.js +974 -0
  58. package/template/public/cmdk.js +146 -0
  59. package/template/public/ds-xray.css +112 -0
  60. package/template/public/ds-xray.js +271 -0
  61. package/template/public/favicon.svg +4 -0
  62. package/template/public/icons/bolt-fill.svg +3 -0
  63. package/template/public/icons/bolt.svg +3 -0
  64. package/template/public/icons/caret-down-fill.svg +3 -0
  65. package/template/public/icons/check-double.svg +4 -0
  66. package/template/public/icons/check.svg +3 -0
  67. package/template/public/icons/chevron-left.svg +3 -0
  68. package/template/public/icons/chevron-right.svg +3 -0
  69. package/template/public/icons/circle-info.svg +6 -0
  70. package/template/public/icons/grid.svg +6 -0
  71. package/template/public/icons/message-square-1.svg +3 -0
  72. package/template/public/icons/message-square.svg +3 -0
  73. package/template/public/icons/messages.svg +4 -0
  74. package/template/public/icons/options-horizontal.svg +5 -0
  75. package/template/public/icons/swatches.svg +6 -0
  76. package/template/public/icons/workflow.svg +6 -0
  77. package/template/public/lightbox.js +87 -0
  78. package/template/public/proto-chrome.css +596 -0
  79. package/template/public/screen-comments.css +723 -0
  80. package/template/public/wireflow-client.js +26 -0
  81. package/template/scripts/build-storybooks.mjs +8 -0
  82. package/template/scripts/dev-setup.mjs +15 -0
  83. package/template/scripts/generate-stories.mjs +12 -0
  84. package/template/scripts/generate-variants.mjs +22 -0
  85. package/template/tsconfig.base.json +19 -0
@@ -0,0 +1,590 @@
1
+ /**
2
+ * Typography Debug Overlay Utility
3
+ *
4
+ * Two modes:
5
+ * 1. Font Size Mode (Ctrl+Shift+F) - Shows font info on hover
6
+ * 2. Distance Mode (Ctrl+Shift+D) - Shows vertical distance between elements
7
+ *
8
+ * State is persisted in localStorage
9
+ */
10
+ (function() {
11
+ const STORAGE_KEY_FONT = 'typographyOverlayFont';
12
+ const STORAGE_KEY_DISTANCE = 'typographyOverlayDistance';
13
+ const TOOLTIP_ID = 'typography-tooltip';
14
+ const TOAST_ID = 'typography-toast';
15
+
16
+ let fontModeEnabled = false;
17
+ let distanceModeEnabled = false;
18
+ let tooltip = null;
19
+ let distanceOverlays = [];
20
+
21
+ // Text elements to track
22
+ const TEXT_SELECTORS = 'h1, h2, h3, h4, h5, h6, p, span, a, li, td, th, label, button, input, textarea, blockquote, figcaption, cite';
23
+ const BLOCK_SELECTORS = 'h1, h2, h3, h4, h5, h6, p, ul, ol, blockquote, figure, pre, table, hr, div.o-prose > *';
24
+
25
+ // Inject styles
26
+ function injectStyles() {
27
+ if (document.getElementById('typography-overlay-styles')) return;
28
+
29
+ const style = document.createElement('style');
30
+ style.id = 'typography-overlay-styles';
31
+ style.textContent = `
32
+ #${TOOLTIP_ID} {
33
+ position: fixed;
34
+ background: #2196F3;
35
+ color: white;
36
+ font-size: 11px;
37
+ font-family: monospace;
38
+ padding: 4px 8px;
39
+ border-radius: 3px;
40
+ z-index: 99999;
41
+ pointer-events: none;
42
+ white-space: nowrap;
43
+ box-shadow: 0 2px 8px rgba(0,0,0,0.2);
44
+ opacity: 0;
45
+ transition: opacity 0.15s ease;
46
+ }
47
+ #${TOOLTIP_ID}.visible {
48
+ opacity: 1;
49
+ }
50
+ #${TOOLTIP_ID}.distance-mode {
51
+ background: #FF5722;
52
+ }
53
+ .typography-highlight {
54
+ outline: 1px dashed #2196F3 !important;
55
+ outline-offset: 1px;
56
+ }
57
+ .distance-marker {
58
+ position: absolute;
59
+ pointer-events: none;
60
+ z-index: 99998;
61
+ }
62
+ .distance-marker__line {
63
+ position: absolute;
64
+ left: 50%;
65
+ width: 2px;
66
+ background: #FF5722;
67
+ transform: translateX(-50%);
68
+ }
69
+ .distance-marker__label {
70
+ position: absolute;
71
+ left: 50%;
72
+ transform: translateX(-50%);
73
+ background: #FF5722;
74
+ color: white;
75
+ font-size: 10px;
76
+ font-family: monospace;
77
+ padding: 2px 6px;
78
+ border-radius: 3px;
79
+ white-space: nowrap;
80
+ }
81
+ .distance-marker__tags {
82
+ opacity: 0.75;
83
+ font-size: 9px;
84
+ }
85
+ .distance-marker__bracket {
86
+ position: absolute;
87
+ left: calc(50% - 8px);
88
+ width: 16px;
89
+ height: 6px;
90
+ border: 2px solid #FF5722;
91
+ border-top: none;
92
+ }
93
+ .distance-marker__bracket--top {
94
+ border: 2px solid #FF5722;
95
+ border-bottom: none;
96
+ }
97
+ .distance-element-highlight {
98
+ outline: 2px solid #FF5722 !important;
99
+ outline-offset: 0px;
100
+ }
101
+ #${TOAST_ID} {
102
+ position: fixed;
103
+ bottom: 20px;
104
+ left: 50%;
105
+ transform: translateX(-50%) translateY(100px);
106
+ background: #1A1A2E;
107
+ color: white;
108
+ font-size: 13px;
109
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
110
+ padding: 10px 20px;
111
+ border-radius: 6px;
112
+ z-index: 100000;
113
+ pointer-events: none;
114
+ box-shadow: 0 4px 12px rgba(0,0,0,0.3);
115
+ opacity: 0;
116
+ transition: transform 0.3s ease, opacity 0.3s ease;
117
+ }
118
+ #${TOAST_ID}.visible {
119
+ opacity: 1;
120
+ transform: translateX(-50%) translateY(0);
121
+ }
122
+ #${TOAST_ID} .toast-status {
123
+ font-weight: 600;
124
+ }
125
+ #${TOAST_ID} .toast-status--on {
126
+ color: #4CAF50;
127
+ }
128
+ #${TOAST_ID} .toast-status--off {
129
+ color: #F44336;
130
+ }
131
+ `;
132
+ document.head.appendChild(style);
133
+ }
134
+
135
+ // Create tooltip element
136
+ function createTooltip() {
137
+ if (tooltip) return;
138
+
139
+ tooltip = document.createElement('div');
140
+ tooltip.id = TOOLTIP_ID;
141
+ document.body.appendChild(tooltip);
142
+ }
143
+
144
+ // Show toast notification
145
+ let toastTimeout = null;
146
+ function showToast(message, isEnabled) {
147
+ injectStyles();
148
+ let toast = document.getElementById(TOAST_ID);
149
+ if (!toast) {
150
+ toast = document.createElement('div');
151
+ toast.id = TOAST_ID;
152
+ document.body.appendChild(toast);
153
+ }
154
+
155
+ const statusClass = isEnabled ? 'toast-status--on' : 'toast-status--off';
156
+ const statusText = isEnabled ? 'ON' : 'OFF';
157
+ toast.innerHTML = `${message}: <span class="toast-status ${statusClass}">${statusText}</span>`;
158
+
159
+ // Clear any existing timeout
160
+ if (toastTimeout) {
161
+ clearTimeout(toastTimeout);
162
+ }
163
+
164
+ // Show toast
165
+ requestAnimationFrame(() => {
166
+ toast.classList.add('visible');
167
+ });
168
+
169
+ // Hide after 2 seconds
170
+ toastTimeout = setTimeout(() => {
171
+ toast.classList.remove('visible');
172
+ }, 2000);
173
+ }
174
+
175
+ // Get font info for an element
176
+ function getFontInfo(element) {
177
+ const style = window.getComputedStyle(element);
178
+ const fontSize = style.fontSize;
179
+ const fontWeight = style.fontWeight;
180
+ const lineHeight = style.lineHeight;
181
+ const fontFamily = style.fontFamily.split(',')[0].replace(/['"]/g, '').trim();
182
+
183
+ const weightNames = {
184
+ '100': 'Thin',
185
+ '200': 'ExtraLight',
186
+ '300': 'Light',
187
+ '400': 'Regular',
188
+ '500': 'Medium',
189
+ '600': 'SemiBold',
190
+ '700': 'Bold',
191
+ '800': 'ExtraBold',
192
+ '900': 'Black'
193
+ };
194
+ const weightName = weightNames[fontWeight] || fontWeight;
195
+
196
+ return {
197
+ size: fontSize,
198
+ weight: weightName,
199
+ lineHeight: lineHeight,
200
+ family: fontFamily
201
+ };
202
+ }
203
+
204
+ // Format line height for display
205
+ function formatLineHeight(lineHeight, fontSize) {
206
+ if (lineHeight === 'normal') return 'normal';
207
+
208
+ if (lineHeight.endsWith('px')) {
209
+ const lhPx = parseFloat(lineHeight);
210
+ const fsPx = parseFloat(fontSize);
211
+ const ratio = (lhPx / fsPx).toFixed(2);
212
+ return `${lineHeight} (${ratio})`;
213
+ }
214
+ return lineHeight;
215
+ }
216
+
217
+ // Font mode mouse move handler
218
+ function handleFontMouseMove(e) {
219
+ if (!fontModeEnabled || !tooltip) return;
220
+
221
+ const element = document.elementFromPoint(e.clientX, e.clientY);
222
+ if (!element || element.closest('#protoNav') || !element.matches(TEXT_SELECTORS)) {
223
+ tooltip.classList.remove('visible');
224
+ document.querySelectorAll('.typography-highlight').forEach(el => {
225
+ el.classList.remove('typography-highlight');
226
+ });
227
+ return;
228
+ }
229
+
230
+ document.querySelectorAll('.typography-highlight').forEach(el => {
231
+ el.classList.remove('typography-highlight');
232
+ });
233
+
234
+ element.classList.add('typography-highlight');
235
+
236
+ const info = getFontInfo(element);
237
+ const tagName = element.tagName.toLowerCase();
238
+
239
+ tooltip.className = '';
240
+ tooltip.id = TOOLTIP_ID;
241
+ tooltip.innerHTML = `
242
+ <strong>&lt;${tagName}&gt;</strong> ${info.size} · ${info.weight}<br>
243
+ line-height: ${formatLineHeight(info.lineHeight, info.size)}<br>
244
+ ${info.family}
245
+ `;
246
+ tooltip.classList.add('visible');
247
+
248
+ positionTooltip(e.clientX, e.clientY);
249
+ }
250
+
251
+ // Position tooltip near cursor
252
+ function positionTooltip(x, y) {
253
+ if (!tooltip) return;
254
+
255
+ const tooltipRect = tooltip.getBoundingClientRect();
256
+ let posX = x + 15;
257
+ let posY = y + 15;
258
+
259
+ if (posX + tooltipRect.width > window.innerWidth) {
260
+ posX = x - tooltipRect.width - 10;
261
+ }
262
+ if (posY + tooltipRect.height > window.innerHeight) {
263
+ posY = y - tooltipRect.height - 10;
264
+ }
265
+
266
+ tooltip.style.left = posX + 'px';
267
+ tooltip.style.top = posY + 'px';
268
+ }
269
+
270
+ // Clear distance overlays
271
+ function clearDistanceOverlays() {
272
+ distanceOverlays.forEach(el => el.remove());
273
+ distanceOverlays = [];
274
+ document.querySelectorAll('.distance-element-highlight').forEach(el => {
275
+ el.classList.remove('distance-element-highlight');
276
+ });
277
+ }
278
+
279
+ // Create distance marker between two elements
280
+ function createDistanceMarker(el1, el2, container) {
281
+ const rect1 = el1.getBoundingClientRect();
282
+ const rect2 = el2.getBoundingClientRect();
283
+ const containerRect = container.getBoundingClientRect();
284
+
285
+ // Calculate the gap between bottom of el1 and top of el2
286
+ const gap = rect2.top - rect1.bottom;
287
+
288
+ if (gap <= 0) return; // Elements overlap or touch
289
+
290
+ const marker = document.createElement('div');
291
+ marker.className = 'distance-marker';
292
+
293
+ // Position marker
294
+ const markerLeft = Math.max(rect1.left, rect2.left) + Math.min(rect1.width, rect2.width) / 2;
295
+ marker.style.left = (markerLeft + window.scrollX) + 'px';
296
+ marker.style.top = (rect1.bottom + window.scrollY) + 'px';
297
+ marker.style.width = '40px';
298
+ marker.style.height = gap + 'px';
299
+
300
+ // Add line
301
+ const line = document.createElement('div');
302
+ line.className = 'distance-marker__line';
303
+ line.style.top = '0';
304
+ line.style.height = gap + 'px';
305
+ marker.appendChild(line);
306
+
307
+ // Add top bracket
308
+ const bracketTop = document.createElement('div');
309
+ bracketTop.className = 'distance-marker__bracket distance-marker__bracket--top';
310
+ bracketTop.style.top = '0';
311
+ marker.appendChild(bracketTop);
312
+
313
+ // Add bottom bracket
314
+ const bracketBottom = document.createElement('div');
315
+ bracketBottom.className = 'distance-marker__bracket';
316
+ bracketBottom.style.bottom = '0';
317
+ marker.appendChild(bracketBottom);
318
+
319
+ // Add label with element tags
320
+ const tag1 = el1.tagName.toLowerCase();
321
+ const tag2 = el2.tagName.toLowerCase();
322
+ const label = document.createElement('div');
323
+ label.className = 'distance-marker__label';
324
+ label.style.top = (gap / 2 - 10) + 'px';
325
+ label.innerHTML = `<span class="distance-marker__tags">${tag1}+${tag2}</span> ${Math.round(gap)}px`;
326
+ marker.appendChild(label);
327
+
328
+ document.body.appendChild(marker);
329
+ distanceOverlays.push(marker);
330
+
331
+ // Highlight elements
332
+ el1.classList.add('distance-element-highlight');
333
+ el2.classList.add('distance-element-highlight');
334
+ }
335
+
336
+ // Find block-level elements and show distances
337
+ function showDistances() {
338
+ clearDistanceOverlays();
339
+
340
+ // Find prose containers and content areas
341
+ const containers = document.querySelectorAll('.o-prose, .c-article, .c-detail-section, .c-article__section, main');
342
+
343
+ containers.forEach(container => {
344
+ // Get direct children that are block elements
345
+ const children = container.querySelectorAll(':scope > h1, :scope > h2, :scope > h3, :scope > h4, :scope > h5, :scope > h6, :scope > p, :scope > ul, :scope > ol, :scope > blockquote, :scope > figure, :scope > pre, :scope > table, :scope > div:not([class*="marker"])');
346
+
347
+ const blockElements = Array.from(children).filter(el => {
348
+ const style = window.getComputedStyle(el);
349
+ return style.display === 'block' || style.display === 'list-item';
350
+ });
351
+
352
+ // Create markers between consecutive elements
353
+ for (let i = 0; i < blockElements.length - 1; i++) {
354
+ createDistanceMarker(blockElements[i], blockElements[i + 1], container);
355
+ }
356
+ });
357
+
358
+ // Also check within o-prose for nested elements
359
+ document.querySelectorAll('.o-prose').forEach(prose => {
360
+ const elements = prose.querySelectorAll('h1, h2, h3, h4, h5, h6, p, ul, ol, blockquote, figure');
361
+ const blockElements = Array.from(elements).filter(el => {
362
+ const parent = el.parentElement;
363
+ // Only direct children of prose or other block elements
364
+ return parent === prose || parent.matches('li, blockquote, figure');
365
+ });
366
+
367
+ for (let i = 0; i < blockElements.length - 1; i++) {
368
+ // Check if they're siblings or close in the DOM
369
+ const el1 = blockElements[i];
370
+ const el2 = blockElements[i + 1];
371
+
372
+ // Skip if already has a marker nearby
373
+ const rect1 = el1.getBoundingClientRect();
374
+ const rect2 = el2.getBoundingClientRect();
375
+ const gap = rect2.top - rect1.bottom;
376
+
377
+ if (gap > 0 && gap < 200) {
378
+ // Check if marker already exists for this gap
379
+ const existingMarker = distanceOverlays.find(m => {
380
+ const mTop = parseFloat(m.style.top);
381
+ return Math.abs(mTop - (rect1.bottom + window.scrollY)) < 5;
382
+ });
383
+
384
+ if (!existingMarker) {
385
+ createDistanceMarker(el1, el2, prose);
386
+ }
387
+ }
388
+ }
389
+ });
390
+ }
391
+
392
+ // Distance mode mouse move handler (shows info about hovered gap)
393
+ function handleDistanceMouseMove(e) {
394
+ if (!distanceModeEnabled || !tooltip) return;
395
+
396
+ // Find if we're hovering over a distance marker label
397
+ const label = document.elementFromPoint(e.clientX, e.clientY);
398
+
399
+ if (label && label.closest('#protoNav')) {
400
+ tooltip.classList.remove('visible');
401
+ return;
402
+ }
403
+
404
+ if (label && label.classList.contains('distance-marker__label')) {
405
+ tooltip.className = '';
406
+ tooltip.id = TOOLTIP_ID;
407
+ tooltip.classList.add('distance-mode', 'visible');
408
+ tooltip.innerHTML = `Distance: <strong>${label.textContent}</strong>`;
409
+ positionTooltip(e.clientX, e.clientY);
410
+ } else {
411
+ tooltip.classList.remove('visible');
412
+ }
413
+ }
414
+
415
+ // Enable font mode
416
+ function enableFontMode() {
417
+ fontModeEnabled = true;
418
+ disableDistanceMode(true); // Disable other mode but don't save
419
+ injectStyles();
420
+ createTooltip();
421
+ document.addEventListener('mousemove', handleFontMouseMove);
422
+ localStorage.setItem(STORAGE_KEY_FONT, 'true');
423
+ console.log('[Typography Overlay] Font Size Mode ON (Ctrl+Shift+F)');
424
+
425
+ window.dispatchEvent(new CustomEvent('typographyOverlayToggled', {
426
+ detail: { mode: 'font', enabled: true }
427
+ }));
428
+ }
429
+
430
+ // Disable font mode
431
+ function disableFontMode(skipSave) {
432
+ fontModeEnabled = false;
433
+ document.removeEventListener('mousemove', handleFontMouseMove);
434
+
435
+ if (tooltip) {
436
+ tooltip.classList.remove('visible');
437
+ }
438
+
439
+ document.querySelectorAll('.typography-highlight').forEach(el => {
440
+ el.classList.remove('typography-highlight');
441
+ });
442
+
443
+ if (!skipSave) {
444
+ localStorage.setItem(STORAGE_KEY_FONT, 'false');
445
+ console.log('[Typography Overlay] Font Size Mode OFF');
446
+ }
447
+
448
+ window.dispatchEvent(new CustomEvent('typographyOverlayToggled', {
449
+ detail: { mode: 'font', enabled: false }
450
+ }));
451
+ }
452
+
453
+ // Get scroll container (prototype wrapper or window)
454
+ function getScrollContainer() {
455
+ return document.getElementById('protoSiteWrapper') || window;
456
+ }
457
+
458
+ // Enable distance mode
459
+ function enableDistanceMode() {
460
+ distanceModeEnabled = true;
461
+ disableFontMode(true); // Disable other mode but don't save
462
+ injectStyles();
463
+ createTooltip();
464
+ showDistances();
465
+ document.addEventListener('mousemove', handleDistanceMouseMove);
466
+ window.addEventListener('scroll', showDistances, true);
467
+ window.addEventListener('resize', showDistances);
468
+ // Also listen on prototype wrapper if it exists
469
+ const protoWrapper = document.getElementById('protoSiteWrapper');
470
+ if (protoWrapper) {
471
+ protoWrapper.addEventListener('scroll', showDistances);
472
+ }
473
+ localStorage.setItem(STORAGE_KEY_DISTANCE, 'true');
474
+ console.log('[Typography Overlay] Distance Mode ON (Ctrl+Shift+D)');
475
+
476
+ window.dispatchEvent(new CustomEvent('typographyOverlayToggled', {
477
+ detail: { mode: 'distance', enabled: true }
478
+ }));
479
+ }
480
+
481
+ // Disable distance mode
482
+ function disableDistanceMode(skipSave) {
483
+ distanceModeEnabled = false;
484
+ document.removeEventListener('mousemove', handleDistanceMouseMove);
485
+ window.removeEventListener('scroll', showDistances, true);
486
+ window.removeEventListener('resize', showDistances);
487
+ // Also remove from prototype wrapper if it exists
488
+ const protoWrapper = document.getElementById('protoSiteWrapper');
489
+ if (protoWrapper) {
490
+ protoWrapper.removeEventListener('scroll', showDistances);
491
+ }
492
+ clearDistanceOverlays();
493
+
494
+ if (tooltip) {
495
+ tooltip.classList.remove('visible');
496
+ }
497
+
498
+ if (!skipSave) {
499
+ localStorage.setItem(STORAGE_KEY_DISTANCE, 'false');
500
+ console.log('[Typography Overlay] Distance Mode OFF');
501
+ }
502
+
503
+ window.dispatchEvent(new CustomEvent('typographyOverlayToggled', {
504
+ detail: { mode: 'distance', enabled: false }
505
+ }));
506
+ }
507
+
508
+ // Toggle font mode
509
+ function toggleFontMode() {
510
+ if (fontModeEnabled) {
511
+ disableFontMode();
512
+ showToast('Font Size Mode', false);
513
+ } else {
514
+ enableFontMode();
515
+ showToast('Font Size Mode', true);
516
+ }
517
+ }
518
+
519
+ // Toggle distance mode
520
+ function toggleDistanceMode() {
521
+ if (distanceModeEnabled) {
522
+ disableDistanceMode();
523
+ showToast('Distance Mode', false);
524
+ } else {
525
+ enableDistanceMode();
526
+ showToast('Distance Mode', true);
527
+ }
528
+ }
529
+
530
+ // Expose global API
531
+ window.TypographyOverlay = {
532
+ toggleFontMode: toggleFontMode,
533
+ toggleDistanceMode: toggleDistanceMode,
534
+ enableFontMode: enableFontMode,
535
+ disableFontMode: disableFontMode,
536
+ enableDistanceMode: enableDistanceMode,
537
+ disableDistanceMode: disableDistanceMode,
538
+ isFontModeEnabled: function() { return fontModeEnabled; },
539
+ isDistanceModeEnabled: function() { return distanceModeEnabled; },
540
+ refreshDistances: showDistances
541
+ };
542
+
543
+ // Legacy API
544
+ window.FontSizeOverlay = {
545
+ toggle: toggleFontMode,
546
+ enable: enableFontMode,
547
+ disable: disableFontMode,
548
+ isEnabled: function() { return fontModeEnabled; }
549
+ };
550
+
551
+ // Listen for keyboard shortcuts
552
+ document.addEventListener('keydown', function(e) {
553
+ // Ctrl+Shift+F for font size mode
554
+ if (e.ctrlKey && e.shiftKey && (e.key === 'F' || e.key === 'f')) {
555
+ e.preventDefault();
556
+ toggleFontMode();
557
+ }
558
+ // Ctrl+Shift+D for distance mode
559
+ if (e.ctrlKey && e.shiftKey && (e.key === 'D' || e.key === 'd')) {
560
+ e.preventDefault();
561
+ toggleDistanceMode();
562
+ }
563
+ });
564
+
565
+ // Restore state from localStorage on page load
566
+ document.addEventListener('DOMContentLoaded', function() {
567
+ const savedFont = localStorage.getItem(STORAGE_KEY_FONT);
568
+ const savedDistance = localStorage.getItem(STORAGE_KEY_DISTANCE);
569
+
570
+ if (savedFont === 'true') {
571
+ enableFontMode();
572
+ } else if (savedDistance === 'true') {
573
+ enableDistanceMode();
574
+ }
575
+ });
576
+
577
+ // If DOM is already loaded
578
+ if (document.readyState !== 'loading') {
579
+ const savedFont = localStorage.getItem(STORAGE_KEY_FONT);
580
+ const savedDistance = localStorage.getItem(STORAGE_KEY_DISTANCE);
581
+
582
+ if (savedFont === 'true') {
583
+ enableFontMode();
584
+ } else if (savedDistance === 'true') {
585
+ enableDistanceMode();
586
+ }
587
+ }
588
+
589
+ console.log('[Typography Overlay] Ready. Ctrl+Shift+F for font sizes, Ctrl+Shift+D for distances.');
590
+ })();