@stnd/lab 1.2.0 → 1.3.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/CHANGELOG.md +11 -0
- package/Lab.svelte +40 -18
- package/README.md +22 -0
- package/package.json +2 -2
- package/old.standard.lab.js +0 -1545
package/old.standard.lab.js
DELETED
|
@@ -1,1545 +0,0 @@
|
|
|
1
|
-
(function () {
|
|
2
|
-
/*!
|
|
3
|
-
* Standard System Inspector - Minimal Debug Interface
|
|
4
|
-
*
|
|
5
|
-
* A minimalist debug interface for the Standard Framework
|
|
6
|
-
* Philosophy: Felt, not seen. Present when needed, invisible when not.
|
|
7
|
-
*
|
|
8
|
-
* HTMX-COMPATIBLE VERSION: Includes a .refresh() method and stable IDs
|
|
9
|
-
* to work with htmx:load events and hx-preserve.
|
|
10
|
-
*
|
|
11
|
-
* @version @VERSION_PLACEHOLDER@
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
class StandardLab {
|
|
15
|
-
constructor() {
|
|
16
|
-
this.state = "dormant"; // dormant, quick, full
|
|
17
|
-
this.badge = null;
|
|
18
|
-
this.panel = null;
|
|
19
|
-
this.currentTab = "typography";
|
|
20
|
-
this.googleFontsLoaded = new Set();
|
|
21
|
-
this.colorScheme = this.detectColorScheme();
|
|
22
|
-
this.modifiedTokens = new Set();
|
|
23
|
-
this.position = this.loadPosition();
|
|
24
|
-
this.version = document.documentElement.dataset.standardVersion;
|
|
25
|
-
this.customThemes = []; // Store custom themes added by users
|
|
26
|
-
|
|
27
|
-
// Slider configuration for numeric tokens
|
|
28
|
-
this.sliderConfig = {
|
|
29
|
-
"--optical-ratio": { min: 1, max: 2, step: 0.01, unit: "" },
|
|
30
|
-
"--line-height": { min: 1, max: 2, step: 0.05, unit: "" },
|
|
31
|
-
"--font-size": { min: 0, max: 2, step: 0.05, unit: "em" },
|
|
32
|
-
"--ratio-golden": { min: 1, max: 2, step: 0.01, unit: "" },
|
|
33
|
-
"--ratio-silver": { min: 1, max: 3, step: 0.01, unit: "" },
|
|
34
|
-
"--ratio-wholestep": { min: 1, max: 3, step: 0.01, unit: "" },
|
|
35
|
-
"--ratio-halfstep": { min: 1, max: 2, step: 0.01, unit: "" },
|
|
36
|
-
"--font-weight": { min: 100, max: 900, step: 100, unit: "" },
|
|
37
|
-
"--font-weight-bold": { min: 100, max: 900, step: 100, unit: "" },
|
|
38
|
-
"--radius": { min: 0, max: 2, step: 0.05, unit: "rem" },
|
|
39
|
-
"--line-width": { min: 0, max: 10, step: 0.5, unit: "px" },
|
|
40
|
-
"--base": { min: 0.5, max: 3, step: 0.05, unit: "rem" },
|
|
41
|
-
"--base-gap": { min: 0, max: 3, step: 0.05, unit: "rem" },
|
|
42
|
-
"--base-block-gap": { min: 0, max: 3, step: 0.05, unit: "rem" },
|
|
43
|
-
"--body-padding": { min: 0, max: 5, step: 0.25, unit: "rem" },
|
|
44
|
-
"--gap-body": { min: 0, max: 4, step: 0.25, unit: "rem" },
|
|
45
|
-
"--gap": { min: 0, max: 3, step: 0.05, unit: "rem" },
|
|
46
|
-
"--grid-gap": { min: 0, max: 3, step: 0.05, unit: "rem" },
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
this.coreTokens = {
|
|
50
|
-
typography: [
|
|
51
|
-
"--font-text",
|
|
52
|
-
"--font-header",
|
|
53
|
-
"--font-monospace",
|
|
54
|
-
"--font-interface",
|
|
55
|
-
"--font-size",
|
|
56
|
-
"--optical-ratio",
|
|
57
|
-
"--line-height",
|
|
58
|
-
"--font-weight",
|
|
59
|
-
"--font-weight-bold",
|
|
60
|
-
"--font-letter-spacing",
|
|
61
|
-
"--font-header-letter-spacing",
|
|
62
|
-
"--font-weight-header",
|
|
63
|
-
],
|
|
64
|
-
layout: [
|
|
65
|
-
"--line-width",
|
|
66
|
-
"--line-width-xs",
|
|
67
|
-
"--line-width-sm",
|
|
68
|
-
"--line-width-md",
|
|
69
|
-
"--line-width-lg",
|
|
70
|
-
"--line-width-xl",
|
|
71
|
-
"--base",
|
|
72
|
-
"--base-gap",
|
|
73
|
-
"--base-block-gap",
|
|
74
|
-
"--body-padding",
|
|
75
|
-
"--gap",
|
|
76
|
-
"--grid-gap",
|
|
77
|
-
],
|
|
78
|
-
colors: this.getColorTokens(),
|
|
79
|
-
advanced: [
|
|
80
|
-
"--ratio-golden",
|
|
81
|
-
"--ratio-silver",
|
|
82
|
-
"--ratio-wholestep",
|
|
83
|
-
"--ratio-halfstep",
|
|
84
|
-
"--radius",
|
|
85
|
-
"--filter-blur",
|
|
86
|
-
"--transition",
|
|
87
|
-
"--line-width",
|
|
88
|
-
"--shadow",
|
|
89
|
-
"--border",
|
|
90
|
-
],
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
// NEW: CSS Variable configuration for StandardLab styling
|
|
94
|
-
this.cssVars = {
|
|
95
|
-
"--stnd-lab-bg":
|
|
96
|
-
"color-mix( in srgb, var(--color-dark-background) 90%, var(--color-dark-foreground))",
|
|
97
|
-
"--stnd-lab-text":
|
|
98
|
-
"color-mix( in srgb, var(--color-dark-background) 10%, var(--color-dark-foreground))",
|
|
99
|
-
"--stnd-lab-text-size": "12px",
|
|
100
|
-
"--stnd-lab-opacity": "1",
|
|
101
|
-
"--stnd-lab-accent": "var(--color-accent)",
|
|
102
|
-
"--stnd-lab-border": "var(--border)",
|
|
103
|
-
"--stnd-lab-shadow": "var(--shadow)",
|
|
104
|
-
"--stnd-lab-blur": "var(--filter-blur)",
|
|
105
|
-
"--stnd-lab-font-family": "var(--font-monospace)",
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
this.defaults = {};
|
|
109
|
-
this.initCSSVars();
|
|
110
|
-
this.init();
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// NEW: Initialize CSS variables on the root element
|
|
114
|
-
initCSSVars() {
|
|
115
|
-
Object.entries(this.cssVars).forEach(([varName, value]) => {
|
|
116
|
-
document.documentElement.style.setProperty(varName, value);
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Set a custom CSS variable for StandardLab styling
|
|
122
|
-
* @param {string} varName - Variable name (e.g., "--stnd-lab-bg")
|
|
123
|
-
* @param {string} value - CSS value
|
|
124
|
-
*
|
|
125
|
-
* @example
|
|
126
|
-
* window.StandardLab.setCSSVar("--stnd-lab-bg", "#1a1a1a");
|
|
127
|
-
* window.StandardLab.setCSSVar("--stnd-lab-text", "#fff");
|
|
128
|
-
* window.StandardLab.setCSSVar("--stnd-lab-text-size", "14px");
|
|
129
|
-
* window.StandardLab.setCSSVar("--stnd-lab-opacity", "0.95");
|
|
130
|
-
*/
|
|
131
|
-
setCSSVar(varName, value) {
|
|
132
|
-
this.cssVars[varName] = value;
|
|
133
|
-
document.documentElement.style.setProperty(varName, value);
|
|
134
|
-
|
|
135
|
-
// Refresh panel if it's open to apply new styles
|
|
136
|
-
if (this.state !== "dormant" && this.panel) {
|
|
137
|
-
this.refresh();
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Get a custom CSS variable value
|
|
143
|
-
* @param {string} varName - Variable name
|
|
144
|
-
* @returns {string} CSS value
|
|
145
|
-
*/
|
|
146
|
-
getCSSVar(varName) {
|
|
147
|
-
return this.cssVars[varName] || "";
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
init() {
|
|
151
|
-
this.storeDefaults();
|
|
152
|
-
this.createBadge();
|
|
153
|
-
this.initKeyboardShortcuts();
|
|
154
|
-
|
|
155
|
-
this.restoreTheme();
|
|
156
|
-
this.restoreDebugMode(); // NEW: Restore debug mode from sessionStorage
|
|
157
|
-
this.restorePanelState(); // NEW: Restore panel state from sessionStorage
|
|
158
|
-
|
|
159
|
-
// Check if debug mode is already active OR if blueprint theme is active
|
|
160
|
-
const currentTheme = document.documentElement.getAttribute("data-theme");
|
|
161
|
-
const isDebugActive =
|
|
162
|
-
document.documentElement.classList.contains("stnd-debug");
|
|
163
|
-
const isBlueprintActive = currentTheme === "blueprint";
|
|
164
|
-
|
|
165
|
-
if (isDebugActive || isBlueprintActive) {
|
|
166
|
-
this.injectGridDebugOverlays();
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (window.matchMedia) {
|
|
170
|
-
window
|
|
171
|
-
.matchMedia("(prefers-color-scheme: dark)")
|
|
172
|
-
.addEventListener("change", (e) => {
|
|
173
|
-
this.colorScheme = e.matches ? "dark" : "light";
|
|
174
|
-
if (this.state === "full") {
|
|
175
|
-
this.refreshTokenList();
|
|
176
|
-
}
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
this.initModificationTracking();
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
restoreTheme() {
|
|
183
|
-
// Use sessionStorage instead of localStorage for tab-specific persistence
|
|
184
|
-
const savedTheme = sessionStorage.getItem("stnd-theme");
|
|
185
|
-
|
|
186
|
-
if (savedTheme) {
|
|
187
|
-
document.documentElement.setAttribute("data-theme", savedTheme);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// NEW: Restore debug mode from sessionStorage
|
|
192
|
-
restoreDebugMode() {
|
|
193
|
-
const savedDebugMode = sessionStorage.getItem("stnd-debug-mode");
|
|
194
|
-
|
|
195
|
-
if (savedDebugMode === "true") {
|
|
196
|
-
document.documentElement.classList.add("stnd-debug");
|
|
197
|
-
} else if (savedDebugMode === "false") {
|
|
198
|
-
document.documentElement.classList.remove("stnd-debug");
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// NEW: Save debug mode to sessionStorage
|
|
203
|
-
saveDebugMode(isEnabled) {
|
|
204
|
-
sessionStorage.setItem("stnd-debug-mode", isEnabled.toString());
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// NEW: Restore panel state from sessionStorage
|
|
208
|
-
restorePanelState() {
|
|
209
|
-
const savedPanelState = sessionStorage.getItem("stnd-panel-state");
|
|
210
|
-
|
|
211
|
-
if (savedPanelState === "quick") {
|
|
212
|
-
this.openQuick();
|
|
213
|
-
} else if (savedPanelState === "full") {
|
|
214
|
-
this.openFull();
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// NEW: Save panel state to sessionStorage
|
|
219
|
-
savePanelState(state) {
|
|
220
|
-
if (state === "dormant") {
|
|
221
|
-
sessionStorage.removeItem("stnd-panel-state");
|
|
222
|
-
} else {
|
|
223
|
-
sessionStorage.setItem("stnd-panel-state", state);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Emit a window event when the panel state changes so external clients
|
|
228
|
-
// (for example the dev toolbar) can stay in sync with the Lab visibility.
|
|
229
|
-
dispatchStateEvent() {
|
|
230
|
-
try {
|
|
231
|
-
if (
|
|
232
|
-
typeof window !== "undefined" &&
|
|
233
|
-
typeof window.dispatchEvent === "function"
|
|
234
|
-
) {
|
|
235
|
-
window.dispatchEvent(
|
|
236
|
-
new CustomEvent("standardlab:state", {
|
|
237
|
-
detail: { state: this.state },
|
|
238
|
-
}),
|
|
239
|
-
);
|
|
240
|
-
}
|
|
241
|
-
} catch (e) {
|
|
242
|
-
console.warn("[StandardLab] dispatchStateEvent failed", e);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Add a custom theme to the theme selector
|
|
248
|
-
* @param {string} value - Theme attribute value (e.g., "custom_dark")
|
|
249
|
-
* @param {string} label - Display label (e.g., "Custom Dark")
|
|
250
|
-
*
|
|
251
|
-
* @example
|
|
252
|
-
* window.StandardLab.addTheme("my-theme", "My Theme");
|
|
253
|
-
*/
|
|
254
|
-
addTheme(value, label) {
|
|
255
|
-
// Check if theme already exists
|
|
256
|
-
const exists = this.customThemes.some((theme) => theme.value === value);
|
|
257
|
-
if (!exists) {
|
|
258
|
-
this.customThemes.push({ value, label });
|
|
259
|
-
|
|
260
|
-
// Refresh panel if it's open
|
|
261
|
-
if (this.state === "quick") {
|
|
262
|
-
this.createQuickPanel();
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
storeDefaults() {
|
|
268
|
-
const allTokens = [
|
|
269
|
-
...this.coreTokens.typography,
|
|
270
|
-
...this.coreTokens.layout,
|
|
271
|
-
...this.coreTokens.colors,
|
|
272
|
-
...this.coreTokens.advanced,
|
|
273
|
-
];
|
|
274
|
-
allTokens.forEach((token) => {
|
|
275
|
-
const value = getComputedStyle(document.documentElement)
|
|
276
|
-
.getPropertyValue(token)
|
|
277
|
-
.trim();
|
|
278
|
-
if (value) {
|
|
279
|
-
this.defaults[token] = value;
|
|
280
|
-
}
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
getColorTokens() {
|
|
285
|
-
const scheme = this.colorScheme === "light" ? "light" : "dark";
|
|
286
|
-
return [
|
|
287
|
-
`--color-${scheme}-background`,
|
|
288
|
-
`--color-${scheme}-foreground`,
|
|
289
|
-
`--color-${scheme}-accent`,
|
|
290
|
-
`--color-${scheme}-red`,
|
|
291
|
-
`--color-${scheme}-orange`,
|
|
292
|
-
`--color-${scheme}-yellow`,
|
|
293
|
-
`--color-${scheme}-green`,
|
|
294
|
-
`--color-${scheme}-cyan`,
|
|
295
|
-
`--color-${scheme}-blue`,
|
|
296
|
-
`--color-${scheme}-magenta`,
|
|
297
|
-
];
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
detectColorScheme() {
|
|
301
|
-
if (
|
|
302
|
-
window.matchMedia &&
|
|
303
|
-
window.matchMedia("(prefers-color-scheme: dark)").matches
|
|
304
|
-
) {
|
|
305
|
-
return "dark";
|
|
306
|
-
}
|
|
307
|
-
return "light";
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
loadPosition() {
|
|
311
|
-
const saved = localStorage.getItem("standard-lab-position");
|
|
312
|
-
if (saved) {
|
|
313
|
-
return JSON.parse(saved);
|
|
314
|
-
}
|
|
315
|
-
return { bottom: 16, right: 16 };
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
savePosition() {
|
|
319
|
-
localStorage.setItem(
|
|
320
|
-
"standard-lab-position",
|
|
321
|
-
JSON.stringify(this.position),
|
|
322
|
-
);
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
createBadge() {
|
|
326
|
-
if (this.badge) return;
|
|
327
|
-
this.badge = document.createElement("button");
|
|
328
|
-
this.badge.id = "standard-lab-badge-container";
|
|
329
|
-
this.badge.className = "standard-lab-badge";
|
|
330
|
-
this.badge.setAttribute("aria-label", "Open Standard Inspector");
|
|
331
|
-
this.badge.setAttribute("title", "Standard Inspector (Shift+Cmd+S)");
|
|
332
|
-
this.badge.setAttribute("style", "color:var(--color-subtle);");
|
|
333
|
-
this.badge.style.bottom = `${this.position.bottom}px`;
|
|
334
|
-
this.badge.style.right = `${this.position.right}px`;
|
|
335
|
-
this.badge.innerHTML = ``;
|
|
336
|
-
this.badge.addEventListener("click", () => this.openQuick());
|
|
337
|
-
this.makeDraggable(this.badge);
|
|
338
|
-
|
|
339
|
-
// Append to body
|
|
340
|
-
document.body.appendChild(this.badge);
|
|
341
|
-
|
|
342
|
-
this.updateBadge();
|
|
343
|
-
this.injectStyles();
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
updateBadge() {
|
|
347
|
-
if (!this.badge) return;
|
|
348
|
-
const count = this.modifiedTokens.size;
|
|
349
|
-
if (count > 0) {
|
|
350
|
-
this.badge.setAttribute("data-modified", count);
|
|
351
|
-
} else {
|
|
352
|
-
this.badge.removeAttribute("data-modified");
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
makeDraggable(element) {
|
|
357
|
-
let isDragging = false,
|
|
358
|
-
startX,
|
|
359
|
-
startY,
|
|
360
|
-
initialBottom,
|
|
361
|
-
initialRight;
|
|
362
|
-
const onMouseDown = (e) => {
|
|
363
|
-
if (
|
|
364
|
-
e.target !== element &&
|
|
365
|
-
!element.classList.contains("standard-lab-panel-header")
|
|
366
|
-
)
|
|
367
|
-
return;
|
|
368
|
-
isDragging = true;
|
|
369
|
-
startX = e.clientX;
|
|
370
|
-
startY = e.clientY;
|
|
371
|
-
const rect = element.getBoundingClientRect();
|
|
372
|
-
initialBottom = window.innerHeight - rect.bottom;
|
|
373
|
-
initialRight = window.innerWidth - rect.right;
|
|
374
|
-
document.addEventListener("mousemove", onMouseMove);
|
|
375
|
-
document.addEventListener("mouseup", onMouseUp);
|
|
376
|
-
element.style.cursor = "grabbing";
|
|
377
|
-
e.preventDefault();
|
|
378
|
-
};
|
|
379
|
-
const onMouseMove = (e) => {
|
|
380
|
-
if (!isDragging) return;
|
|
381
|
-
const deltaX = startX - e.clientX;
|
|
382
|
-
const deltaY = e.clientY - startY;
|
|
383
|
-
this.position.right = Math.max(0, initialRight + deltaX);
|
|
384
|
-
this.position.bottom = Math.max(0, initialBottom - deltaY);
|
|
385
|
-
element.style.right = `${this.position.right}px`;
|
|
386
|
-
element.style.bottom = `${this.position.bottom}px`;
|
|
387
|
-
};
|
|
388
|
-
const onMouseUp = () => {
|
|
389
|
-
isDragging = false;
|
|
390
|
-
element.style.cursor = "";
|
|
391
|
-
document.removeEventListener("mousemove", onMouseMove);
|
|
392
|
-
document.removeEventListener("mouseup", onMouseUp);
|
|
393
|
-
this.savePosition();
|
|
394
|
-
};
|
|
395
|
-
element.addEventListener("mousedown", onMouseDown);
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
openQuick() {
|
|
399
|
-
if (this.state === "quick") {
|
|
400
|
-
this.close();
|
|
401
|
-
return;
|
|
402
|
-
}
|
|
403
|
-
this.state = "quick";
|
|
404
|
-
this.savePanelState("quick");
|
|
405
|
-
this.createQuickPanel();
|
|
406
|
-
this.dispatchStateEvent();
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
openFull() {
|
|
410
|
-
this.state = "full";
|
|
411
|
-
this.savePanelState("full");
|
|
412
|
-
this.createFullPanel();
|
|
413
|
-
this.dispatchStateEvent();
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
close() {
|
|
417
|
-
if (this.panel) {
|
|
418
|
-
this.panel.style.opacity = "0";
|
|
419
|
-
this.panel.style.transform = "translateY(10px)";
|
|
420
|
-
setTimeout(() => {
|
|
421
|
-
if (this.panel) {
|
|
422
|
-
this.panel.remove();
|
|
423
|
-
this.panel = null;
|
|
424
|
-
}
|
|
425
|
-
}, 200);
|
|
426
|
-
}
|
|
427
|
-
this.state = "dormant";
|
|
428
|
-
this.savePanelState("dormant");
|
|
429
|
-
this.dispatchStateEvent();
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
/**
|
|
433
|
-
* Public method to refresh the panel's UI.
|
|
434
|
-
* Ideal for calling after HTMX swaps change the page state (e.g., theme).
|
|
435
|
-
*/
|
|
436
|
-
refresh() {
|
|
437
|
-
console.log("refresh");
|
|
438
|
-
this.storeDefaults();
|
|
439
|
-
|
|
440
|
-
this.badge = null;
|
|
441
|
-
this.init();
|
|
442
|
-
|
|
443
|
-
// Re-inject overlays if debug is active
|
|
444
|
-
if (document.documentElement.classList.contains("stnd-debug")) {
|
|
445
|
-
this.removeGridDebugOverlays();
|
|
446
|
-
this.injectGridDebugOverlays();
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
// Do nothing if the panel is closed
|
|
450
|
-
if (this.state === "dormant") {
|
|
451
|
-
return;
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
// Re-run the appropriate panel creation method to rebuild the UI
|
|
455
|
-
if (this.state === "quick") {
|
|
456
|
-
this.createQuickPanel();
|
|
457
|
-
} else if (this.state === "full") {
|
|
458
|
-
const showModifiedOnly =
|
|
459
|
-
this.panel?.querySelector('[data-filter="modified"]')?.checked ||
|
|
460
|
-
false;
|
|
461
|
-
this.createFullPanel();
|
|
462
|
-
this.switchTab(this.currentTab);
|
|
463
|
-
const modifiedFilter = this.panel?.querySelector(
|
|
464
|
-
'[data-filter="modified"]',
|
|
465
|
-
);
|
|
466
|
-
if (modifiedFilter) {
|
|
467
|
-
modifiedFilter.checked = showModifiedOnly;
|
|
468
|
-
this.refreshTokenList(showModifiedOnly);
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
/** Read current seed / token value, resolving computed style */
|
|
474
|
-
_seedValue(token) {
|
|
475
|
-
return (
|
|
476
|
-
document.documentElement.style.getPropertyValue(token).trim() ||
|
|
477
|
-
getComputedStyle(document.documentElement)
|
|
478
|
-
.getPropertyValue(token)
|
|
479
|
-
.trim()
|
|
480
|
-
);
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
createQuickPanel() {
|
|
484
|
-
if (this.panel) this.panel.remove();
|
|
485
|
-
this.panel = document.createElement("div");
|
|
486
|
-
this.panel.id = "standard-lab-panel-container";
|
|
487
|
-
this.panel.className = "standard-lab-panel standard-lab-panel-quick";
|
|
488
|
-
this.panel.style.bottom = `calc(${this.position.bottom}px + var(--space-2))`;
|
|
489
|
-
this.panel.style.right = `${this.position.right}px`;
|
|
490
|
-
|
|
491
|
-
const debugActive =
|
|
492
|
-
document.documentElement.classList.contains("stnd-debug");
|
|
493
|
-
const modifiedCount = this.modifiedTokens.size;
|
|
494
|
-
|
|
495
|
-
// Current seed colors
|
|
496
|
-
const lightBg = this._seedValue("--color-light-background");
|
|
497
|
-
const lightFg = this._seedValue("--color-light-foreground");
|
|
498
|
-
const lightBgHex = this.extractHexColor(lightBg);
|
|
499
|
-
const lightFgHex = this.extractHexColor(lightFg);
|
|
500
|
-
|
|
501
|
-
// Current fonts
|
|
502
|
-
const fontText = this._seedValue("--font-text");
|
|
503
|
-
const fontHeader = this._seedValue("--font-header");
|
|
504
|
-
|
|
505
|
-
// Current ratio & padding
|
|
506
|
-
const ratio = this._seedValue("--optical-ratio");
|
|
507
|
-
const ratioNum = this.extractNumericValue(ratio) || 1.414;
|
|
508
|
-
const gapBody = this._seedValue("--gap-body");
|
|
509
|
-
const gapNum = this.extractNumericValue(gapBody) || 1;
|
|
510
|
-
|
|
511
|
-
this.panel.innerHTML = `
|
|
512
|
-
<header class="standard-lab-panel-header">
|
|
513
|
-
<div style="display:inline-flex;align-items:center;gap:6px;">
|
|
514
|
-
<h2 style="color:var(--color-dark-foreground);display:inline-block;">Lab v${this.version}</h2>
|
|
515
|
-
<div class="standard-lab-viewport-info" style="display:inline-block;">${window.innerWidth}px</div>
|
|
516
|
-
</div>
|
|
517
|
-
<button class="standard-lab-btn-icon" data-action="close" title="Close (Esc)">×</button>
|
|
518
|
-
</header>
|
|
519
|
-
|
|
520
|
-
<!-- Theme -->
|
|
521
|
-
<section class="standard-lab-section">
|
|
522
|
-
<label class="standard-lab-label">Theme</label>
|
|
523
|
-
<select class="standard-lab-select" data-action="theme">
|
|
524
|
-
${this.getThemeOptions()}
|
|
525
|
-
</select>
|
|
526
|
-
</section>
|
|
527
|
-
|
|
528
|
-
<!-- Seed Colors -->
|
|
529
|
-
<section class="standard-lab-section">
|
|
530
|
-
<label class="standard-lab-label">Colors</label>
|
|
531
|
-
<div class="standard-lab-seed-row">
|
|
532
|
-
<input type="color" class="standard-lab-color-swatch" data-seed="--color-light-background" value="${lightBgHex}" title="Light background">
|
|
533
|
-
<input type="text" class="standard-lab-seed-input" data-seed="--color-light-background" value="${lightBg}" spellcheck="false" placeholder="background">
|
|
534
|
-
<span class="standard-lab-seed-tag">bg</span>
|
|
535
|
-
</div>
|
|
536
|
-
<div class="standard-lab-seed-row">
|
|
537
|
-
<input type="color" class="standard-lab-color-swatch" data-seed="--color-light-foreground" value="${lightFgHex}" title="Light foreground">
|
|
538
|
-
<input type="text" class="standard-lab-seed-input" data-seed="--color-light-foreground" value="${lightFg}" spellcheck="false" placeholder="foreground">
|
|
539
|
-
<span class="standard-lab-seed-tag">fg</span>
|
|
540
|
-
</div>
|
|
541
|
-
</section>
|
|
542
|
-
|
|
543
|
-
<!-- Typography -->
|
|
544
|
-
<section class="standard-lab-section">
|
|
545
|
-
<label class="standard-lab-label">Typography</label>
|
|
546
|
-
<div class="standard-lab-font-row">
|
|
547
|
-
<span class="standard-lab-font-tag">Body</span>
|
|
548
|
-
<input type="text" class="standard-lab-seed-input" data-seed="--font-text" value="${fontText}" spellcheck="false" placeholder="Font family">
|
|
549
|
-
</div>
|
|
550
|
-
<div class="standard-lab-font-row">
|
|
551
|
-
<span class="standard-lab-font-tag">Head</span>
|
|
552
|
-
<input type="text" class="standard-lab-seed-input" data-seed="--font-header" value="${fontHeader}" spellcheck="false" placeholder="Font family">
|
|
553
|
-
</div>
|
|
554
|
-
</section>
|
|
555
|
-
|
|
556
|
-
<!-- Scale -->
|
|
557
|
-
<section class="standard-lab-section">
|
|
558
|
-
<label class="standard-lab-label">Scale</label>
|
|
559
|
-
<div class="standard-lab-quick-slider">
|
|
560
|
-
<span class="standard-lab-font-tag">Ratio</span>
|
|
561
|
-
<input type="range" class="standard-lab-slider-input" data-seed="--optical-ratio"
|
|
562
|
-
value="${ratioNum}" min="1" max="2" step="0.01">
|
|
563
|
-
<span class="standard-lab-slider-readout" data-readout="--optical-ratio">${ratioNum}</span>
|
|
564
|
-
</div>
|
|
565
|
-
<div class="standard-lab-quick-slider">
|
|
566
|
-
<span class="standard-lab-font-tag">Gap</span>
|
|
567
|
-
<input type="range" class="standard-lab-slider-input" data-seed="--gap-body"
|
|
568
|
-
value="${gapNum}" min="0" max="4" step="0.25">
|
|
569
|
-
<span class="standard-lab-slider-readout" data-readout="--gap-body">${gapNum}rem</span>
|
|
570
|
-
</div>
|
|
571
|
-
</section>
|
|
572
|
-
|
|
573
|
-
<!-- Debug -->
|
|
574
|
-
<section class="standard-lab-section">
|
|
575
|
-
<label class="standard-lab-toggle">
|
|
576
|
-
<input type="checkbox" ${debugActive ? "checked" : ""} data-toggle="debug">
|
|
577
|
-
<span>Debug Mode</span>
|
|
578
|
-
<kbd>⇧⌘D</kbd>
|
|
579
|
-
</label>
|
|
580
|
-
</section>
|
|
581
|
-
|
|
582
|
-
<!-- Actions -->
|
|
583
|
-
<div class="standard-lab-actions">
|
|
584
|
-
<button class="standard-lab-btn" data-action="edit-tokens">
|
|
585
|
-
${modifiedCount > 0 ? `All Tokens (${modifiedCount})` : "All Tokens"}
|
|
586
|
-
</button>
|
|
587
|
-
${modifiedCount > 0 ? `<button class="standard-lab-btn standard-lab-btn-secondary" data-action="reset">Reset</button>` : ""}
|
|
588
|
-
<button class="standard-lab-btn standard-lab-btn-secondary" data-action="copy">Copy CSS</button>
|
|
589
|
-
</div>
|
|
590
|
-
`;
|
|
591
|
-
this.attachQuickPanelListeners();
|
|
592
|
-
document.body.appendChild(this.panel);
|
|
593
|
-
requestAnimationFrame(() => {
|
|
594
|
-
this.panel.style.opacity = "1";
|
|
595
|
-
this.panel.style.transform = "translateY(0)";
|
|
596
|
-
});
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
getThemeOptions() {
|
|
600
|
-
const defaultThemes = [
|
|
601
|
-
{ value: "", label: "Empty" },
|
|
602
|
-
{ value: "default", label: "Default" },
|
|
603
|
-
|
|
604
|
-
{ value: "system", label: "System" },
|
|
605
|
-
{ value: "claude", label: "Claude" },
|
|
606
|
-
{ value: "bauhaus", label: "Bauhaus" },
|
|
607
|
-
{ value: "venetian", label: "Venetian" },
|
|
608
|
-
{ value: "newspaper", label: "Newspaper" },
|
|
609
|
-
{ value: "didone", label: "Didone" },
|
|
610
|
-
{ value: "transitional", label: "Transitional" },
|
|
611
|
-
|
|
612
|
-
{ value: "english-oldstyle", label: "English Oldstyle" },
|
|
613
|
-
|
|
614
|
-
{ value: "paper", label: "Paper" },
|
|
615
|
-
{ value: "swiss", label: "Swiss" },
|
|
616
|
-
{ value: "kernel", label: "Kernel" },
|
|
617
|
-
{ value: "forest", label: "Forest" },
|
|
618
|
-
{ value: "book", label: "Book" },
|
|
619
|
-
{ value: "occult", label: "Occult" },
|
|
620
|
-
{ value: "baroque", label: "Baroque" },
|
|
621
|
-
{ value: "botanist", label: "Botanist" },
|
|
622
|
-
{ value: "chalky", label: "Chalky" },
|
|
623
|
-
{ value: "minimal", label: "Minimal" },
|
|
624
|
-
{ value: "marauder", label: "Marauder" },
|
|
625
|
-
{ value: "blueprint", label: "Blueprint" },
|
|
626
|
-
{ value: "custom_1", label: "Custom 1" },
|
|
627
|
-
{ value: "custom_2", label: "Custom 2" },
|
|
628
|
-
{ value: "custom_3", label: "Custom 3" },
|
|
629
|
-
];
|
|
630
|
-
|
|
631
|
-
// Merge default themes with custom themes
|
|
632
|
-
const allThemes = [...defaultThemes, ...this.customThemes];
|
|
633
|
-
|
|
634
|
-
const current = document.documentElement.getAttribute("data-theme") || "";
|
|
635
|
-
return allThemes
|
|
636
|
-
.map(
|
|
637
|
-
(theme) =>
|
|
638
|
-
`<option value="${theme.value}" ${current === theme.value ? "selected" : ""}>${theme.label}</option>`,
|
|
639
|
-
)
|
|
640
|
-
.join("");
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
attachQuickPanelListeners() {
|
|
644
|
-
// Close / minimize / full-panel
|
|
645
|
-
this.panel
|
|
646
|
-
.querySelector('[data-action="close"]')
|
|
647
|
-
?.addEventListener("click", () => this.close());
|
|
648
|
-
this.panel
|
|
649
|
-
.querySelector('[data-action="edit-tokens"]')
|
|
650
|
-
?.addEventListener("click", () => this.openFull());
|
|
651
|
-
this.panel
|
|
652
|
-
.querySelector('[data-action="reset"]')
|
|
653
|
-
?.addEventListener("click", () => this.resetAll());
|
|
654
|
-
this.panel
|
|
655
|
-
.querySelector('[data-action="copy"]')
|
|
656
|
-
?.addEventListener("click", () => this.copyCSS());
|
|
657
|
-
|
|
658
|
-
// Debug toggle
|
|
659
|
-
this.panel
|
|
660
|
-
.querySelector('[data-toggle="debug"]')
|
|
661
|
-
?.addEventListener("change", (e) => {
|
|
662
|
-
const isEnabled = e.target.checked;
|
|
663
|
-
document.documentElement.classList.toggle("stnd-debug", isEnabled);
|
|
664
|
-
this.saveDebugMode(isEnabled);
|
|
665
|
-
|
|
666
|
-
const currentTheme =
|
|
667
|
-
document.documentElement.getAttribute("data-theme");
|
|
668
|
-
const isBlueprintActive = currentTheme === "blueprint";
|
|
669
|
-
|
|
670
|
-
if (isEnabled || isBlueprintActive) {
|
|
671
|
-
this.injectGridDebugOverlays();
|
|
672
|
-
} else {
|
|
673
|
-
this.removeGridDebugOverlays();
|
|
674
|
-
}
|
|
675
|
-
window.toast?.info(
|
|
676
|
-
`Debug mode ${e.target.checked ? "enabled" : "disabled"}`,
|
|
677
|
-
);
|
|
678
|
-
});
|
|
679
|
-
|
|
680
|
-
// Theme selector
|
|
681
|
-
this.panel
|
|
682
|
-
.querySelector('[data-action="theme"]')
|
|
683
|
-
?.addEventListener("change", (e) => {
|
|
684
|
-
const theme = e.target.value;
|
|
685
|
-
if (theme) {
|
|
686
|
-
document.documentElement.setAttribute("data-theme", theme);
|
|
687
|
-
} else {
|
|
688
|
-
document.documentElement.removeAttribute("data-theme");
|
|
689
|
-
}
|
|
690
|
-
sessionStorage.setItem("stnd-theme", theme);
|
|
691
|
-
this.handleBlueprintDebugException(theme);
|
|
692
|
-
|
|
693
|
-
// Re-read seed values after theme switch and update controls
|
|
694
|
-
this._syncQuickSeedInputs();
|
|
695
|
-
window.toast?.info(`Theme: ${theme || "Default"}`);
|
|
696
|
-
});
|
|
697
|
-
|
|
698
|
-
// Seed color pickers (swatch)
|
|
699
|
-
this.panel
|
|
700
|
-
.querySelectorAll(".standard-lab-color-swatch")
|
|
701
|
-
.forEach((swatch) => {
|
|
702
|
-
swatch.addEventListener("input", (e) => {
|
|
703
|
-
const token = e.target.dataset.seed;
|
|
704
|
-
const color = e.target.value;
|
|
705
|
-
this.updateToken(token, color);
|
|
706
|
-
// Sync companion text input
|
|
707
|
-
const textInput = this.panel.querySelector(
|
|
708
|
-
`.standard-lab-seed-input[data-seed="${token}"]`,
|
|
709
|
-
);
|
|
710
|
-
if (textInput) textInput.value = color;
|
|
711
|
-
});
|
|
712
|
-
});
|
|
713
|
-
|
|
714
|
-
// Seed color text inputs
|
|
715
|
-
this.panel
|
|
716
|
-
.querySelectorAll(
|
|
717
|
-
'.standard-lab-seed-input[data-seed^="--color-light-"]',
|
|
718
|
-
)
|
|
719
|
-
.forEach((input) => {
|
|
720
|
-
input.addEventListener("input", (e) => {
|
|
721
|
-
const token = e.target.dataset.seed;
|
|
722
|
-
const value = e.target.value;
|
|
723
|
-
this.updateToken(token, value);
|
|
724
|
-
// Sync companion swatch
|
|
725
|
-
const swatch = this.panel.querySelector(
|
|
726
|
-
`.standard-lab-color-swatch[data-seed="${token}"]`,
|
|
727
|
-
);
|
|
728
|
-
if (swatch && this.isColorValue(value)) {
|
|
729
|
-
swatch.value = this.extractHexColor(value);
|
|
730
|
-
}
|
|
731
|
-
});
|
|
732
|
-
});
|
|
733
|
-
|
|
734
|
-
// Font family inputs
|
|
735
|
-
this.panel
|
|
736
|
-
.querySelectorAll('.standard-lab-seed-input[data-seed^="--font-"]')
|
|
737
|
-
.forEach((input) => {
|
|
738
|
-
input.addEventListener("input", (e) => {
|
|
739
|
-
const token = e.target.dataset.seed;
|
|
740
|
-
const value = e.target.value;
|
|
741
|
-
this.updateToken(token, value);
|
|
742
|
-
this.loadGoogleFont(value);
|
|
743
|
-
});
|
|
744
|
-
});
|
|
745
|
-
|
|
746
|
-
// Quick sliders (ratio, gap)
|
|
747
|
-
this.panel
|
|
748
|
-
.querySelectorAll(
|
|
749
|
-
".standard-lab-quick-slider .standard-lab-slider-input",
|
|
750
|
-
)
|
|
751
|
-
.forEach((slider) => {
|
|
752
|
-
slider.addEventListener("input", (e) => {
|
|
753
|
-
const token = e.target.dataset.seed;
|
|
754
|
-
const num = e.target.value;
|
|
755
|
-
const config = this.sliderConfig[token];
|
|
756
|
-
const unit = config?.unit || "";
|
|
757
|
-
const fullValue = `${num}${unit}`;
|
|
758
|
-
this.updateToken(token, fullValue);
|
|
759
|
-
// Sync readout
|
|
760
|
-
const readout = this.panel.querySelector(
|
|
761
|
-
`.standard-lab-slider-readout[data-readout="${token}"]`,
|
|
762
|
-
);
|
|
763
|
-
if (readout) readout.textContent = fullValue || num;
|
|
764
|
-
});
|
|
765
|
-
});
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
/** Re-read seed values from computed style and sync quick-panel inputs */
|
|
769
|
-
_syncQuickSeedInputs() {
|
|
770
|
-
if (!this.panel) return;
|
|
771
|
-
// Small delay so theme CSS cascades before we read computed values
|
|
772
|
-
requestAnimationFrame(() => {
|
|
773
|
-
const seeds = [
|
|
774
|
-
"--color-light-background",
|
|
775
|
-
"--color-light-foreground",
|
|
776
|
-
"--font-text",
|
|
777
|
-
"--font-header",
|
|
778
|
-
];
|
|
779
|
-
seeds.forEach((token) => {
|
|
780
|
-
const val = this._seedValue(token);
|
|
781
|
-
const textInput = this.panel?.querySelector(
|
|
782
|
-
`.standard-lab-seed-input[data-seed="${token}"]`,
|
|
783
|
-
);
|
|
784
|
-
if (textInput) textInput.value = val;
|
|
785
|
-
const swatch = this.panel?.querySelector(
|
|
786
|
-
`.standard-lab-color-swatch[data-seed="${token}"]`,
|
|
787
|
-
);
|
|
788
|
-
if (swatch && this.isColorValue(val)) {
|
|
789
|
-
swatch.value = this.extractHexColor(val);
|
|
790
|
-
}
|
|
791
|
-
});
|
|
792
|
-
// Sliders
|
|
793
|
-
["--optical-ratio", "--gap-body"].forEach((token) => {
|
|
794
|
-
const val = this._seedValue(token);
|
|
795
|
-
const num = this.extractNumericValue(val);
|
|
796
|
-
const slider = this.panel?.querySelector(
|
|
797
|
-
`.standard-lab-slider-input[data-seed="${token}"]`,
|
|
798
|
-
);
|
|
799
|
-
if (slider) slider.value = num;
|
|
800
|
-
const readout = this.panel?.querySelector(
|
|
801
|
-
`.standard-lab-slider-readout[data-readout="${token}"]`,
|
|
802
|
-
);
|
|
803
|
-
if (readout) {
|
|
804
|
-
const config = this.sliderConfig[token];
|
|
805
|
-
readout.textContent = `${num}${config?.unit || ""}`;
|
|
806
|
-
}
|
|
807
|
-
});
|
|
808
|
-
});
|
|
809
|
-
}
|
|
810
|
-
/**
|
|
811
|
-
* Handle the blueprint theme exception for debug grid overlay
|
|
812
|
-
* @param {string} theme - Current theme name
|
|
813
|
-
*/
|
|
814
|
-
handleBlueprintDebugException(theme) {
|
|
815
|
-
const isBlueprint = theme === "blueprint";
|
|
816
|
-
const hasDebugClass =
|
|
817
|
-
document.documentElement.classList.contains("stnd-debug");
|
|
818
|
-
|
|
819
|
-
if (isBlueprint && !hasDebugClass) {
|
|
820
|
-
// Blueprint theme activated - show grid overlays
|
|
821
|
-
this.injectGridDebugOverlays();
|
|
822
|
-
} else if (!isBlueprint && !hasDebugClass) {
|
|
823
|
-
// Non-blueprint theme and debug not active - remove overlays
|
|
824
|
-
this.removeGridDebugOverlays();
|
|
825
|
-
}
|
|
826
|
-
// If debug is explicitly active, let it control the overlays
|
|
827
|
-
}
|
|
828
|
-
createFullPanel() {
|
|
829
|
-
if (this.panel) this.panel.remove();
|
|
830
|
-
this.panel = document.createElement("div");
|
|
831
|
-
this.panel.id = "standard-lab-panel-container";
|
|
832
|
-
this.panel.className = "standard-lab-panel standard-lab-panel-full";
|
|
833
|
-
this.panel.style.bottom = `calc(${this.position.bottom}px + var(--space-2))`;
|
|
834
|
-
this.panel.style.right = `${this.position.right}px`;
|
|
835
|
-
const modifiedCount = this.modifiedTokens.size;
|
|
836
|
-
const showModifiedOnly = modifiedCount > 0;
|
|
837
|
-
this.panel.innerHTML = `
|
|
838
|
-
<header class="standard-lab-panel-header" style="cursor: grab;">
|
|
839
|
-
<h2 style="display:inline-block;">Token Editor</h2>
|
|
840
|
-
<div class="standard-lab-header-actions">
|
|
841
|
-
<button class="standard-lab-btn-icon" data-action="minimize" title="Minimize">−</button>
|
|
842
|
-
<button class="standard-lab-btn-icon" data-action="close" title="Close (Esc)">×</button>
|
|
843
|
-
</div>
|
|
844
|
-
</header>
|
|
845
|
-
<nav class="standard-lab-tabs">
|
|
846
|
-
<button class="standard-lab-tab" data-tab="typography" aria-selected="true">Typography</button>
|
|
847
|
-
<button class="standard-lab-tab" data-tab="layout">Layout</button>
|
|
848
|
-
<button class="standard-lab-tab" data-tab="colors">Colors</button>
|
|
849
|
-
<button class="standard-lab-tab" data-tab="advanced">Advanced</button>
|
|
850
|
-
</nav>
|
|
851
|
-
${modifiedCount > 0 ? `<div class="standard-lab-filter"><label class="standard-lab-toggle"><input type="checkbox" ${showModifiedOnly ? "checked" : ""} data-filter="modified"><span>Show modified only (${modifiedCount})</span></label></div>` : ""}
|
|
852
|
-
<div class="standard-lab-content">${this.renderTokenList("typography", showModifiedOnly)}</div>
|
|
853
|
-
<div class="standard-lab-actions">
|
|
854
|
-
<button class="standard-lab-btn standard-lab-btn-secondary" data-action="reset">Reset All</button>
|
|
855
|
-
<button class="standard-lab-btn" data-action="copy">Copy CSS</button>
|
|
856
|
-
</div>
|
|
857
|
-
`;
|
|
858
|
-
this.attachFullPanelListeners();
|
|
859
|
-
this.makeDraggable(
|
|
860
|
-
this.panel.querySelector(".standard-lab-panel-header"),
|
|
861
|
-
);
|
|
862
|
-
document.body.appendChild(this.panel);
|
|
863
|
-
requestAnimationFrame(() => {
|
|
864
|
-
this.panel.style.opacity = "1";
|
|
865
|
-
this.panel.style.transform = "translateY(0)";
|
|
866
|
-
});
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
renderTokenList(category, modifiedOnly = false) {
|
|
870
|
-
const tokens = this.coreTokens[category] || [];
|
|
871
|
-
let filteredTokens = modifiedOnly
|
|
872
|
-
? tokens.filter((token) => this.modifiedTokens.has(token))
|
|
873
|
-
: tokens;
|
|
874
|
-
if (filteredTokens.length === 0) {
|
|
875
|
-
return `<div class="standard-lab-empty">No tokens to display</div>`;
|
|
876
|
-
}
|
|
877
|
-
return `<div class="standard-lab-token-list">${filteredTokens.map((token) => this.renderTokenRow(token)).join("")}</div>`;
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
/**
|
|
881
|
-
* Check if a value is numeric (with optional unit)
|
|
882
|
-
*/
|
|
883
|
-
isNumericValue(value) {
|
|
884
|
-
if (!value) return false;
|
|
885
|
-
// Match patterns like: 1.5, 1.5rem, 16px, 1.618, 500
|
|
886
|
-
return /^-?\d+(\.\d+)?(rem|em|px|%|ch|vh|vw)?$/.test(value.trim());
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
/**
|
|
890
|
-
* Extract numeric portion from a CSS value
|
|
891
|
-
*/
|
|
892
|
-
extractNumericValue(value) {
|
|
893
|
-
if (!value) return 0;
|
|
894
|
-
const match = value.match(/^(-?\d+(\.\d+)?)/);
|
|
895
|
-
return match ? parseFloat(match[1]) : 0;
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
/**
|
|
899
|
-
* Extract unit from a CSS value (rem, px, em, etc.)
|
|
900
|
-
*/
|
|
901
|
-
extractUnit(value) {
|
|
902
|
-
if (!value) return "";
|
|
903
|
-
const match = value.match(/[a-z%]+$/i);
|
|
904
|
-
return match ? match[0] : "";
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
renderTokenRow(token) {
|
|
908
|
-
const value = this.getPropertyValue(token);
|
|
909
|
-
const isModified = this.modifiedTokens.has(token);
|
|
910
|
-
const isColor = token.includes("color") && this.isColorValue(value);
|
|
911
|
-
const sliderConfig = this.sliderConfig[token];
|
|
912
|
-
|
|
913
|
-
// Detect if this is a slider-enabled numeric token
|
|
914
|
-
const isSlider = sliderConfig && this.isNumericValue(value);
|
|
915
|
-
|
|
916
|
-
if (isSlider) {
|
|
917
|
-
const numericValue = this.extractNumericValue(value);
|
|
918
|
-
return `
|
|
919
|
-
<div class="standard-lab-token-row ${isModified ? "is-modified" : ""}">
|
|
920
|
-
<div class="standard-lab-slider-container">
|
|
921
|
-
<label class="standard-lab-slider-label">${token}</label>
|
|
922
|
-
<div class="standard-lab-slider-controls">
|
|
923
|
-
<input
|
|
924
|
-
type="range"
|
|
925
|
-
class="standard-lab-slider-input"
|
|
926
|
-
data-token="${token}"
|
|
927
|
-
value="${numericValue}"
|
|
928
|
-
min="${sliderConfig.min}"
|
|
929
|
-
max="${sliderConfig.max}"
|
|
930
|
-
step="${sliderConfig.step}"
|
|
931
|
-
title="${token}">
|
|
932
|
-
<input
|
|
933
|
-
type="text"
|
|
934
|
-
class="standard-lab-text-input standard-lab-slider-value"
|
|
935
|
-
data-token="${token}"
|
|
936
|
-
value="${value}"
|
|
937
|
-
spellcheck="false">
|
|
938
|
-
</div>
|
|
939
|
-
</div>
|
|
940
|
-
<button class="standard-lab-btn-icon standard-lab-btn-reset" data-action="reset-token" data-token="${token}" title="Reset" ${!isModified ? "disabled" : ""}>↺</button>
|
|
941
|
-
</div>
|
|
942
|
-
`;
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
if (isColor) {
|
|
946
|
-
return `
|
|
947
|
-
<div class="standard-lab-token-row ${isModified ? "is-modified" : ""}">
|
|
948
|
-
<div class="standard-lab-color-container">
|
|
949
|
-
<label class="standard-lab-color-label">${token}</label>
|
|
950
|
-
<div class="standard-lab-color-controls">
|
|
951
|
-
<input
|
|
952
|
-
type="color"
|
|
953
|
-
class="standard-lab-color-input"
|
|
954
|
-
data-token="${token}"
|
|
955
|
-
value="${this.extractHexColor(value)}"
|
|
956
|
-
title="Pick color">
|
|
957
|
-
<input
|
|
958
|
-
type="text"
|
|
959
|
-
class="standard-lab-text-input standard-lab-color-value"
|
|
960
|
-
data-token="${token}"
|
|
961
|
-
value="${value}"
|
|
962
|
-
placeholder="${token}"
|
|
963
|
-
spellcheck="false">
|
|
964
|
-
</div>
|
|
965
|
-
</div>
|
|
966
|
-
<button class="standard-lab-btn-icon standard-lab-btn-reset" data-action="reset-token" data-token="${token}" title="Reset" ${!isModified ? "disabled" : ""}>↺</button>
|
|
967
|
-
</div>
|
|
968
|
-
`;
|
|
969
|
-
}
|
|
970
|
-
|
|
971
|
-
// Default text input (for font names, etc.)
|
|
972
|
-
return `
|
|
973
|
-
<div class="standard-lab-token-row ${isModified ? "is-modified" : ""}">
|
|
974
|
-
<div class="standard-lab-text-container">
|
|
975
|
-
<label class="standard-lab-text-label">${token}</label>
|
|
976
|
-
<input type="text" class="standard-lab-text-input" data-token="${token}" value="${value}" placeholder="${token}" spellcheck="false">
|
|
977
|
-
</div>
|
|
978
|
-
<button class="standard-lab-btn-icon standard-lab-btn-reset" data-action="reset-token" data-token="${token}" title="Reset" ${!isModified ? "disabled" : ""}>↺</button>
|
|
979
|
-
</div>
|
|
980
|
-
`;
|
|
981
|
-
}
|
|
982
|
-
|
|
983
|
-
attachFullPanelListeners() {
|
|
984
|
-
this.panel
|
|
985
|
-
.querySelector('[data-action="close"]')
|
|
986
|
-
?.addEventListener("click", () => this.close());
|
|
987
|
-
this.panel
|
|
988
|
-
.querySelector('[data-action="minimize"]')
|
|
989
|
-
?.addEventListener("click", () => this.openQuick());
|
|
990
|
-
this.panel.querySelectorAll(".standard-lab-tab").forEach((tab) => {
|
|
991
|
-
tab.addEventListener("click", (e) =>
|
|
992
|
-
this.switchTab(e.target.dataset.tab),
|
|
993
|
-
);
|
|
994
|
-
});
|
|
995
|
-
this.panel
|
|
996
|
-
.querySelector('[data-filter="modified"]')
|
|
997
|
-
?.addEventListener("change", (e) =>
|
|
998
|
-
this.refreshTokenList(e.target.checked),
|
|
999
|
-
);
|
|
1000
|
-
|
|
1001
|
-
// Slider input listeners
|
|
1002
|
-
this.panel
|
|
1003
|
-
.querySelectorAll(".standard-lab-slider-input")
|
|
1004
|
-
.forEach((slider) => {
|
|
1005
|
-
slider.addEventListener("input", (e) => {
|
|
1006
|
-
const token = e.target.dataset.token;
|
|
1007
|
-
const numericValue = e.target.value;
|
|
1008
|
-
const config = this.sliderConfig[token];
|
|
1009
|
-
const unit =
|
|
1010
|
-
config.unit || this.extractUnit(this.getPropertyValue(token));
|
|
1011
|
-
const fullValue = `${numericValue}${unit}`;
|
|
1012
|
-
|
|
1013
|
-
this.updateToken(token, fullValue);
|
|
1014
|
-
|
|
1015
|
-
// Update the text input to reflect slider value
|
|
1016
|
-
const textInput = this.panel.querySelector(
|
|
1017
|
-
`.standard-lab-slider-value[data-token="${token}"]`,
|
|
1018
|
-
);
|
|
1019
|
-
if (textInput) textInput.value = fullValue;
|
|
1020
|
-
});
|
|
1021
|
-
});
|
|
1022
|
-
|
|
1023
|
-
// Text input for sliders (allows manual override)
|
|
1024
|
-
this.panel
|
|
1025
|
-
.querySelectorAll(".standard-lab-slider-value")
|
|
1026
|
-
.forEach((input) => {
|
|
1027
|
-
input.addEventListener("input", (e) => {
|
|
1028
|
-
const token = e.target.dataset.token;
|
|
1029
|
-
const value = e.target.value;
|
|
1030
|
-
const numericValue = this.extractNumericValue(value);
|
|
1031
|
-
|
|
1032
|
-
this.updateToken(token, value);
|
|
1033
|
-
|
|
1034
|
-
// Sync slider position
|
|
1035
|
-
const slider = this.panel.querySelector(
|
|
1036
|
-
`.standard-lab-slider-input[data-token="${token}"]`,
|
|
1037
|
-
);
|
|
1038
|
-
if (slider) slider.value = numericValue;
|
|
1039
|
-
});
|
|
1040
|
-
});
|
|
1041
|
-
|
|
1042
|
-
// Regular text inputs
|
|
1043
|
-
this.panel
|
|
1044
|
-
.querySelectorAll(
|
|
1045
|
-
".standard-lab-text-input:not(.standard-lab-slider-value):not(.standard-lab-color-value)",
|
|
1046
|
-
)
|
|
1047
|
-
.forEach((input) => {
|
|
1048
|
-
input.addEventListener("input", (e) =>
|
|
1049
|
-
this.updateToken(e.target.dataset.token, e.target.value),
|
|
1050
|
-
);
|
|
1051
|
-
});
|
|
1052
|
-
|
|
1053
|
-
// Color pickers
|
|
1054
|
-
this.panel
|
|
1055
|
-
.querySelectorAll(".standard-lab-color-input")
|
|
1056
|
-
.forEach((input) => {
|
|
1057
|
-
input.addEventListener("input", (e) => {
|
|
1058
|
-
const token = e.target.dataset.token;
|
|
1059
|
-
const color = e.target.value;
|
|
1060
|
-
this.updateToken(token, color);
|
|
1061
|
-
const textInput = this.panel.querySelector(
|
|
1062
|
-
`.standard-lab-color-value[data-token="${token}"]`,
|
|
1063
|
-
);
|
|
1064
|
-
if (textInput) textInput.value = color;
|
|
1065
|
-
});
|
|
1066
|
-
});
|
|
1067
|
-
|
|
1068
|
-
// Color text inputs
|
|
1069
|
-
this.panel
|
|
1070
|
-
.querySelectorAll(".standard-lab-color-value")
|
|
1071
|
-
.forEach((input) => {
|
|
1072
|
-
input.addEventListener("input", (e) => {
|
|
1073
|
-
const token = e.target.dataset.token;
|
|
1074
|
-
const value = e.target.value;
|
|
1075
|
-
this.updateToken(token, value);
|
|
1076
|
-
const colorInput = this.panel.querySelector(
|
|
1077
|
-
`.standard-lab-color-input[data-token="${token}"]`,
|
|
1078
|
-
);
|
|
1079
|
-
if (colorInput && this.isColorValue(value)) {
|
|
1080
|
-
colorInput.value = this.extractHexColor(value);
|
|
1081
|
-
}
|
|
1082
|
-
});
|
|
1083
|
-
});
|
|
1084
|
-
|
|
1085
|
-
// Reset buttons
|
|
1086
|
-
this.panel
|
|
1087
|
-
.querySelectorAll('[data-action="reset-token"]')
|
|
1088
|
-
.forEach((btn) => {
|
|
1089
|
-
btn.addEventListener("click", (e) =>
|
|
1090
|
-
this.resetToken(e.target.dataset.token),
|
|
1091
|
-
);
|
|
1092
|
-
});
|
|
1093
|
-
this.panel
|
|
1094
|
-
.querySelector('[data-action="reset"]')
|
|
1095
|
-
?.addEventListener("click", () => this.resetAll());
|
|
1096
|
-
this.panel
|
|
1097
|
-
.querySelector('[data-action="copy"]')
|
|
1098
|
-
?.addEventListener("click", () => this.copyCSS());
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
|
-
switchTab(category) {
|
|
1102
|
-
this.currentTab = category;
|
|
1103
|
-
this.panel.querySelectorAll(".standard-lab-tab").forEach((tab) => {
|
|
1104
|
-
tab.setAttribute("aria-selected", tab.dataset.tab === category);
|
|
1105
|
-
});
|
|
1106
|
-
const showModifiedOnly =
|
|
1107
|
-
this.panel.querySelector('[data-filter="modified"]')?.checked || false;
|
|
1108
|
-
this.refreshTokenList(showModifiedOnly);
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
|
-
refreshTokenList(modifiedOnly = false) {
|
|
1112
|
-
const content = this.panel?.querySelector(".standard-lab-content");
|
|
1113
|
-
if (!content) return;
|
|
1114
|
-
content.innerHTML = this.renderTokenList(this.currentTab, modifiedOnly);
|
|
1115
|
-
this.attachFullPanelListeners();
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
updateToken(token, value) {
|
|
1119
|
-
document.documentElement.style.setProperty(token, value);
|
|
1120
|
-
if (value !== this.defaults[token]) {
|
|
1121
|
-
this.modifiedTokens.add(token);
|
|
1122
|
-
} else {
|
|
1123
|
-
this.modifiedTokens.delete(token);
|
|
1124
|
-
}
|
|
1125
|
-
this.updateBadge();
|
|
1126
|
-
const resetBtn = this.panel?.querySelector(
|
|
1127
|
-
`[data-action="reset-token"][data-token="${token}"]`,
|
|
1128
|
-
);
|
|
1129
|
-
if (resetBtn) resetBtn.disabled = !this.modifiedTokens.has(token);
|
|
1130
|
-
const row = this.panel?.querySelector(
|
|
1131
|
-
`.standard-lab-token-row:has([data-token="${token}"])`,
|
|
1132
|
-
);
|
|
1133
|
-
if (row)
|
|
1134
|
-
row.classList.toggle("is-modified", this.modifiedTokens.has(token));
|
|
1135
|
-
if (
|
|
1136
|
-
token.includes("font-") &&
|
|
1137
|
-
!token.includes("font-size") &&
|
|
1138
|
-
!token.includes("font-ratio")
|
|
1139
|
-
) {
|
|
1140
|
-
this.loadGoogleFont(value);
|
|
1141
|
-
}
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
resetToken(token) {
|
|
1145
|
-
const defaultValue = this.defaults[token];
|
|
1146
|
-
if (defaultValue) {
|
|
1147
|
-
document.documentElement.style.setProperty(token, defaultValue);
|
|
1148
|
-
this.modifiedTokens.delete(token);
|
|
1149
|
-
const input = this.panel?.querySelector(`[data-token="${token}"]`);
|
|
1150
|
-
if (input) input.value = defaultValue;
|
|
1151
|
-
this.updateBadge();
|
|
1152
|
-
this.refreshTokenList();
|
|
1153
|
-
window.toast?.info(`Reset ${token}`);
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
|
-
resetAll() {
|
|
1158
|
-
if (!confirm("Reset all tokens to defaults?")) return;
|
|
1159
|
-
document.documentElement.removeAttribute("style");
|
|
1160
|
-
this.modifiedTokens.clear();
|
|
1161
|
-
this.googleFontsLoaded.clear();
|
|
1162
|
-
this.updateBadge();
|
|
1163
|
-
if (this.state === "full") {
|
|
1164
|
-
this.refreshTokenList();
|
|
1165
|
-
}
|
|
1166
|
-
window.toast?.info("All tokens reset");
|
|
1167
|
-
}
|
|
1168
|
-
|
|
1169
|
-
copyCSS() {
|
|
1170
|
-
const allTokens = [
|
|
1171
|
-
...this.coreTokens.typography,
|
|
1172
|
-
...this.coreTokens.layout,
|
|
1173
|
-
...this.coreTokens.colors,
|
|
1174
|
-
...this.coreTokens.advanced,
|
|
1175
|
-
];
|
|
1176
|
-
const css = `:root {\n${allTokens.map((token) => ` ${token}: ${this.getPropertyValue(token)};`).join("\n")}\n}`;
|
|
1177
|
-
navigator.clipboard
|
|
1178
|
-
.writeText(css)
|
|
1179
|
-
.then(() => window.toast?.success("CSS copied to clipboard!"))
|
|
1180
|
-
.catch(() => {
|
|
1181
|
-
const textarea = document.createElement("textarea");
|
|
1182
|
-
textarea.value = css;
|
|
1183
|
-
textarea.style.position = "fixed";
|
|
1184
|
-
textarea.style.opacity = "0";
|
|
1185
|
-
document.body.appendChild(textarea);
|
|
1186
|
-
textarea.select();
|
|
1187
|
-
document.execCommand("copy");
|
|
1188
|
-
document.body.removeChild(textarea);
|
|
1189
|
-
window.toast?.success("CSS copied to clipboard!");
|
|
1190
|
-
});
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
getPropertyValue(token) {
|
|
1194
|
-
const value = getComputedStyle(document.documentElement)
|
|
1195
|
-
.getPropertyValue(token)
|
|
1196
|
-
.trim();
|
|
1197
|
-
return (
|
|
1198
|
-
value || document.documentElement.style.getPropertyValue(token).trim()
|
|
1199
|
-
);
|
|
1200
|
-
}
|
|
1201
|
-
|
|
1202
|
-
isColorValue(value) {
|
|
1203
|
-
return value
|
|
1204
|
-
? value.startsWith("#") ||
|
|
1205
|
-
value.startsWith("rgb") ||
|
|
1206
|
-
value.startsWith("hsl") ||
|
|
1207
|
-
value.startsWith("oklch") ||
|
|
1208
|
-
value.includes("color-mix")
|
|
1209
|
-
: false;
|
|
1210
|
-
}
|
|
1211
|
-
|
|
1212
|
-
extractHexColor(value) {
|
|
1213
|
-
if (!value) return "#000000";
|
|
1214
|
-
const hexMatch = value.match(/#[0-9a-f]{6}/i);
|
|
1215
|
-
if (hexMatch) return hexMatch[0];
|
|
1216
|
-
const rgbMatch = value.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
|
|
1217
|
-
if (rgbMatch) {
|
|
1218
|
-
const [, r, g, b] = rgbMatch;
|
|
1219
|
-
return (
|
|
1220
|
-
"#" +
|
|
1221
|
-
[r, g, b]
|
|
1222
|
-
.map((x) => parseInt(x).toString(16).padStart(2, "0"))
|
|
1223
|
-
.join("")
|
|
1224
|
-
);
|
|
1225
|
-
}
|
|
1226
|
-
const colorMap = {
|
|
1227
|
-
white: "#ffffff",
|
|
1228
|
-
black: "#000000",
|
|
1229
|
-
red: "#ff0000",
|
|
1230
|
-
green: "#00ff00",
|
|
1231
|
-
blue: "#0000ff",
|
|
1232
|
-
yellow: "#ffff00",
|
|
1233
|
-
orange: "#ffa500",
|
|
1234
|
-
purple: "#800080",
|
|
1235
|
-
pink: "#ffc0cb",
|
|
1236
|
-
cyan: "#00ffff",
|
|
1237
|
-
};
|
|
1238
|
-
return colorMap[value.toLowerCase()] || "#000000";
|
|
1239
|
-
}
|
|
1240
|
-
|
|
1241
|
-
loadGoogleFont(fontName) {
|
|
1242
|
-
const cleanName = fontName.split(",")[0].trim().replace(/['"]/g, "");
|
|
1243
|
-
const systemFonts = [
|
|
1244
|
-
"system-ui",
|
|
1245
|
-
"sans-serif",
|
|
1246
|
-
"serif",
|
|
1247
|
-
"monospace",
|
|
1248
|
-
"-apple-system",
|
|
1249
|
-
"BlinkMacSystemFont",
|
|
1250
|
-
];
|
|
1251
|
-
if (
|
|
1252
|
-
systemFonts.includes(cleanName) ||
|
|
1253
|
-
this.googleFontsLoaded.has(cleanName)
|
|
1254
|
-
)
|
|
1255
|
-
return;
|
|
1256
|
-
const fontUrl = `https://fonts.googleapis.com/css2?family=${cleanName.replace(/ /g, "+")}:wght@300;400;500;600;700;900&display=swap`;
|
|
1257
|
-
if (
|
|
1258
|
-
!document.querySelector(`link[href*="${cleanName.replace(/ /g, "+")}"]`)
|
|
1259
|
-
) {
|
|
1260
|
-
const link = document.createElement("link");
|
|
1261
|
-
link.rel = "stylesheet";
|
|
1262
|
-
link.href = fontUrl;
|
|
1263
|
-
document.head.appendChild(link);
|
|
1264
|
-
this.googleFontsLoaded.add(cleanName);
|
|
1265
|
-
}
|
|
1266
|
-
}
|
|
1267
|
-
|
|
1268
|
-
showNotification(message, type = "info") {
|
|
1269
|
-
const notification = document.createElement("div");
|
|
1270
|
-
notification.className = `standard-lab-notification standard-lab-notification-${type}`;
|
|
1271
|
-
notification.textContent = message;
|
|
1272
|
-
document.body.appendChild(notification);
|
|
1273
|
-
requestAnimationFrame(() => {
|
|
1274
|
-
notification.style.opacity = "1";
|
|
1275
|
-
notification.style.transform = "translateY(0)";
|
|
1276
|
-
});
|
|
1277
|
-
setTimeout(() => {
|
|
1278
|
-
notification.style.opacity = "0";
|
|
1279
|
-
notification.style.transform = "translateY(-10px)";
|
|
1280
|
-
setTimeout(() => notification.remove(), 200);
|
|
1281
|
-
}, 2500);
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
initKeyboardShortcuts() {
|
|
1285
|
-
document.addEventListener("keydown", (e) => {
|
|
1286
|
-
const isMac = navigator.platform.indexOf("Mac") > -1;
|
|
1287
|
-
if (
|
|
1288
|
-
e.shiftKey &&
|
|
1289
|
-
(isMac ? e.metaKey : e.ctrlKey) &&
|
|
1290
|
-
e.key.toLowerCase() === "s"
|
|
1291
|
-
) {
|
|
1292
|
-
e.preventDefault();
|
|
1293
|
-
this.state === "dormant" ? this.openQuick() : this.close();
|
|
1294
|
-
}
|
|
1295
|
-
if (
|
|
1296
|
-
e.shiftKey &&
|
|
1297
|
-
(isMac ? e.metaKey : e.ctrlKey) &&
|
|
1298
|
-
e.key.toLowerCase() === "d"
|
|
1299
|
-
) {
|
|
1300
|
-
e.preventDefault();
|
|
1301
|
-
|
|
1302
|
-
document.documentElement.classList.toggle("stnd-debug");
|
|
1303
|
-
const isActive =
|
|
1304
|
-
document.documentElement.classList.contains("stnd-debug");
|
|
1305
|
-
|
|
1306
|
-
// UPDATED: Save debug mode to sessionStorage
|
|
1307
|
-
this.saveDebugMode(isActive);
|
|
1308
|
-
|
|
1309
|
-
if (isActive) {
|
|
1310
|
-
this.injectGridDebugOverlays();
|
|
1311
|
-
} else {
|
|
1312
|
-
this.removeGridDebugOverlays();
|
|
1313
|
-
}
|
|
1314
|
-
|
|
1315
|
-
window.toast?.info(`Debug mode ${isActive ? "enabled" : "disabled"}`);
|
|
1316
|
-
}
|
|
1317
|
-
if (e.key === "Escape" && this.state !== "dormant") {
|
|
1318
|
-
this.close();
|
|
1319
|
-
}
|
|
1320
|
-
});
|
|
1321
|
-
}
|
|
1322
|
-
|
|
1323
|
-
initModificationTracking() {
|
|
1324
|
-
const allTokens = [
|
|
1325
|
-
...this.coreTokens.typography,
|
|
1326
|
-
...this.coreTokens.layout,
|
|
1327
|
-
...this.coreTokens.colors,
|
|
1328
|
-
...this.coreTokens.advanced,
|
|
1329
|
-
];
|
|
1330
|
-
allTokens.forEach((token) => {
|
|
1331
|
-
const currentValue = this.getPropertyValue(token);
|
|
1332
|
-
const defaultValue = this.defaults[token];
|
|
1333
|
-
if (currentValue && defaultValue && currentValue !== defaultValue) {
|
|
1334
|
-
this.modifiedTokens.add(token);
|
|
1335
|
-
}
|
|
1336
|
-
});
|
|
1337
|
-
this.updateBadge();
|
|
1338
|
-
}
|
|
1339
|
-
|
|
1340
|
-
/**
|
|
1341
|
-
* Inject grid debug overlays into all .prose containers
|
|
1342
|
-
*/
|
|
1343
|
-
injectGridDebugOverlays() {
|
|
1344
|
-
const proseContainers = document.querySelectorAll(".prose");
|
|
1345
|
-
|
|
1346
|
-
proseContainers.forEach((prose) => {
|
|
1347
|
-
// Check if overlay already exists
|
|
1348
|
-
if (prose.querySelector(".grid-debug-overlay")) {
|
|
1349
|
-
return;
|
|
1350
|
-
}
|
|
1351
|
-
|
|
1352
|
-
const overlay = document.createElement("div");
|
|
1353
|
-
overlay.className = "grid-debug-overlay";
|
|
1354
|
-
overlay.setAttribute("aria-hidden", "true");
|
|
1355
|
-
|
|
1356
|
-
// Add 7 spans for grid columns
|
|
1357
|
-
for (let i = 0; i < 7; i++) {
|
|
1358
|
-
overlay.appendChild(document.createElement("span"));
|
|
1359
|
-
}
|
|
1360
|
-
|
|
1361
|
-
// Insert as first child
|
|
1362
|
-
prose.insertBefore(overlay, prose.firstChild);
|
|
1363
|
-
});
|
|
1364
|
-
}
|
|
1365
|
-
|
|
1366
|
-
/**
|
|
1367
|
-
* Remove grid debug overlays from all .prose containers
|
|
1368
|
-
*/
|
|
1369
|
-
removeGridDebugOverlays() {
|
|
1370
|
-
document
|
|
1371
|
-
.querySelectorAll(".grid-debug-overlay")
|
|
1372
|
-
.forEach((overlay) => overlay.remove());
|
|
1373
|
-
}
|
|
1374
|
-
|
|
1375
|
-
injectStyles() {
|
|
1376
|
-
if (document.getElementById("standard-lab-styles")) return;
|
|
1377
|
-
const styles = document.createElement("style");
|
|
1378
|
-
styles.id = "standard-lab-styles";
|
|
1379
|
-
styles.textContent = `
|
|
1380
|
-
.standard-lab-badge { font-family: var(--stnd-lab-font-family); position: fixed; width: var(--space-d2); height: var(--space-d2); border-radius: 50%; background: var(--color-surface); border: var(--border); box-shadow: var(--shadow); backdrop-filter: var(--filter-blur); cursor: pointer; z-index: var(--z-toast); line-height:1.5; display: flex; align-items: baseline; justify-content: center; color: var(--color-foreground); transition: all 0.2s ease; padding: 0; margin:0; }
|
|
1381
|
-
.standard-lab-badge:hover { box-shadow: var(--shadow); transform: scale(1.05); color: var(--color-dark-accent); }
|
|
1382
|
-
.standard-lab-badge:active { transform: scale(0.95); }
|
|
1383
|
-
.standard-lab-badge[data-modified]::after { content: attr(data-modified); position: absolute; top: -4px; right: -4px; width: 20px; height: 20px; border-radius: 50%; background: var(--color-accent); color: var(--color-background); font-size: var(--scale-d3); display: flex; align-items: center; justify-content: center; font-weight: 600; border: var(--border); box-shadow:var(--shadow); }
|
|
1384
|
-
.standard-lab-panel { position: fixed; background: var(--stnd-lab-bg); color: var(--stnd-lab-text); font-size: var(--stnd-lab-text-size); border: var(--stnd-lab-border); border-radius: 6px; box-shadow: var(--stnd-lab-shadow); backdrop-filter: var(--stnd-lab-blur); z-index: var(--z-toast); opacity: 0; transform: translateY(10px); transition: opacity 0.2s ease, transform 0.2s ease; display: flex; flex-direction: column; }
|
|
1385
|
-
.standard-lab-panel * { font-family: var(--stnd-lab-font-family) !important; font-size: inherit; line-height:1.5; margin:0; }
|
|
1386
|
-
.standard-lab-panel :is(label, input) { margin:0; }
|
|
1387
|
-
.standard-lab-panel-quick { max-width: 300px; max-height: 80vh; overflow-y: auto; }
|
|
1388
|
-
.standard-lab-panel-full { max-width: 360px; max-height: 600px; }
|
|
1389
|
-
.standard-lab-panel-header { padding: 6px; border-bottom: var(--stnd-lab-border); display: flex; align-items: center; justify-content: space-between; flex-shrink: 0; }
|
|
1390
|
-
.standard-lab-panel-header h2 { margin: 0; font-size: calc(var(--stnd-lab-text-size) * 1.167); font-weight: 600; letter-spacing: -0.02em; }
|
|
1391
|
-
.standard-lab-viewport-info { font-size: calc(var(--stnd-lab-text-size) * 0.917); font-weight: 400; color: var(--stnd-lab-text); opacity: 0.7; margin-top: 2px; font-family: var(--font-monospace); }
|
|
1392
|
-
.standard-lab-header-actions { display: flex; gap: 6px; }
|
|
1393
|
-
.standard-lab-section { margin:0;padding: 4px; border-bottom: var(--stnd-lab-border); }
|
|
1394
|
-
.standard-lab-section:last-of-type { border-bottom: none; }
|
|
1395
|
-
.standard-lab-label { display: block; font-size: calc(var(--stnd-lab-text-size) * 0.917); font-weight: 600; color: var(--stnd-lab-text); opacity: 0.7; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 6px; }
|
|
1396
|
-
.standard-lab-toggle { display: flex; align-items: center; gap: 6px; cursor: pointer; user-select: none; }
|
|
1397
|
-
.standard-lab-toggle input[type="checkbox"] { width: 16px; height: 16px; cursor: pointer; margin: 0; }
|
|
1398
|
-
.standard-lab-toggle span { flex: 1; font-size: calc(var(--stnd-lab-text-size) * 1.167); }
|
|
1399
|
-
.standard-lab-toggle kbd { font-size: calc(var(--stnd-lab-text-size) * 1); font-family: var(--font-monospace); color: var(--stnd-lab-text); opacity: 0.6; padding: 2px 6px; background: rgba(0,0,0,0.1); border-radius: 6px; }
|
|
1400
|
-
.standard-lab-select { width: 100%; padding: 6px 3px; margin:0 ; background: var(--stnd-lab-bg); border: var(--stnd-lab-border); border-radius: 6px; font-size: calc(var(--stnd-lab-text-size) * 1.167); color: var(--stnd-lab-text); cursor: pointer; }
|
|
1401
|
-
.standard-lab-actions { padding: 6px; border-top: var(--stnd-lab-border); display: flex; flex-direction: column; gap: 6px; flex-shrink: 0; }
|
|
1402
|
-
.standard-lab-btn { font-size: calc(var(--stnd-lab-text-size) * 1.167); width: 100%; padding: 6px 3px; background: var(--stnd-lab-accent); color: var(--stnd-lab-bg); border: var(--stnd-lab-border); border-radius: 6px; font-weight: 600; cursor: pointer; transition: opacity 0.2s ease; }
|
|
1403
|
-
.standard-lab-btn:hover { opacity: 0.9; }
|
|
1404
|
-
.standard-lab-btn:active { opacity: 0.8; }
|
|
1405
|
-
.standard-lab-btn-secondary { background: rgba(255,255,255,0.1); color: var(--stnd-lab-text); border: var(--stnd-lab-border); }
|
|
1406
|
-
.standard-lab-btn-icon { font-size: calc(var(--stnd-lab-text-size) * 1.667); width: 24px; height: 24px; padding: 0; background: transparent; border: none; color: var(--stnd-lab-text); line-height: 1; cursor: pointer; border-radius: 6px; transition: all 0.2s ease; display: inline-flex; align-items: center; justify-content: center; opacity: 0.7; }
|
|
1407
|
-
.standard-lab-btn-icon:hover { background: rgba(255,255,255,0.1); color: var(--stnd-lab-text); opacity: 1; }
|
|
1408
|
-
.standard-lab-tabs { display: flex; border-bottom: var(--stnd-lab-border); flex-shrink: 0; }
|
|
1409
|
-
.standard-lab-tab { flex: 1; padding: 6px; background: transparent; border: none; border-bottom: 2px solid transparent; font-size: calc(var(--stnd-lab-text-size) * 1.333); font-weight: 500; color: var(--stnd-lab-text); opacity: 0.6; cursor: pointer; transition: all 0.2s ease; }
|
|
1410
|
-
.standard-lab-tab:hover { color: var(--stnd-lab-text); opacity: 1; background: rgba(255,255,255,0.05); }
|
|
1411
|
-
.standard-lab-tab[aria-selected="true"] { color: var(--stnd-lab-accent); opacity: 1; border-bottom-color: var(--stnd-lab-accent); font-weight: 600; }
|
|
1412
|
-
.standard-lab-filter { padding: 6px 6px; background: rgba(255,255,255,0.05); border-bottom: var(--stnd-lab-border); flex-shrink: 0; }
|
|
1413
|
-
.standard-lab-content { flex: 1; overflow-y: auto; padding: 6px; }
|
|
1414
|
-
.standard-lab-content::-webkit-scrollbar { width: 6px; }
|
|
1415
|
-
.standard-lab-content::-webkit-scrollbar-track { background: transparent; }
|
|
1416
|
-
.standard-lab-content::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.2); border-radius: 6px; }
|
|
1417
|
-
.standard-lab-content::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.3); }
|
|
1418
|
-
.standard-lab-token-list { display: flex; flex-direction: column; gap: 3px; }
|
|
1419
|
-
.standard-lab-token-row { display: grid; grid-template-columns: 1fr auto; gap: 6px; align-items: start; padding: 6px; border-radius: 6px; transition: background 0.2s ease; }
|
|
1420
|
-
.standard-lab-token-row:hover { background: rgba(255,255,255,0.05); }
|
|
1421
|
-
.standard-lab-token-row.is-modified { background: rgba(var(--stnd-lab-accent-rgb, 255), 0.05); }
|
|
1422
|
-
.standard-lab-text-input { padding: 0; background: transparent; border: 1px solid transparent; border-radius: 6px; font-size: var(--stnd-lab-text-size); font-family: var(--font-monospace); color: var(--stnd-lab-text); min-width: 0; transition: all 0.2s ease; }
|
|
1423
|
-
.standard-lab-text-input:focus { outline: none; background: rgba(255,255,255,0.05); border-color: var(--stnd-lab-accent); }
|
|
1424
|
-
.standard-lab-color-container { width: 100%; }
|
|
1425
|
-
.standard-lab-color-label { display: block; font-size: calc(var(--stnd-lab-text-size) * 0.917); font-family: var(--font-monospace); color: var(--stnd-lab-text); opacity: 0.7; margin-bottom: 6px; }
|
|
1426
|
-
.standard-lab-color-controls { display: flex; gap: 6px; align-items: center; }
|
|
1427
|
-
.standard-lab-color-input { width: 40px; height: 40px; border: var(--stnd-lab-border); border-radius: 6px; cursor: pointer; padding: 4px; background: rgba(255,255,255,0.05); flex-shrink: 0; }
|
|
1428
|
-
.standard-lab-color-input::-webkit-color-swatch-wrapper { padding: 0; }
|
|
1429
|
-
.standard-lab-color-input::-webkit-color-swatch { border: none; border-radius: 6px; }
|
|
1430
|
-
.standard-lab-color-value { flex: 1; min-width: 0; padding: 0; font-family: var(--font-monospace); font-size: var(--stnd-lab-text-size); }
|
|
1431
|
-
.standard-lab-text-container { width: 100%; }
|
|
1432
|
-
.standard-lab-text-label { display: block; font-size: calc(var(--stnd-lab-text-size) * 0.917); font-family: var(--font-monospace); color: var(--stnd-lab-text); opacity: 0.7; margin-bottom: 6px; }
|
|
1433
|
-
.standard-lab-slider-container { width: 100%; }
|
|
1434
|
-
.standard-lab-slider-label { display: block; font-size: calc(var(--stnd-lab-text-size) * 0.917); font-family: var(--font-monospace); color: var(--stnd-lab-text); opacity: 0.7; margin-bottom: 6px; }
|
|
1435
|
-
.standard-lab-slider-controls { display: flex; gap: 6px; align-items: center; }
|
|
1436
|
-
.standard-lab-slider-input { flex: 1; height: 6px; -webkit-appearance: none; appearance: none; background: rgba(255,255,255,0.1); border-radius: 6px; outline: none; cursor: pointer; }
|
|
1437
|
-
.standard-lab-slider-input::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 16px; height: 16px; background: var(--stnd-lab-accent); border-radius: 50%; cursor: grab; transition: transform 0.1s ease; }
|
|
1438
|
-
.standard-lab-slider-input::-webkit-slider-thumb:active { cursor: grabbing; transform: scale(1.2); }
|
|
1439
|
-
.standard-lab-slider-input::-moz-range-thumb { width: 16px; height: 16px; background: var(--stnd-lab-accent); border-radius: 50%; border: none; cursor: grab; transition: transform 0.1s ease; }
|
|
1440
|
-
.standard-lab-slider-input::-moz-range-thumb:active { cursor: grabbing; transform: scale(1.2); }
|
|
1441
|
-
.standard-lab-slider-value { width: 80px; flex-shrink: 0; text-align: center; padding: 6px 8px; font-size: var(--stnd-lab-text-size); }
|
|
1442
|
-
.standard-lab-btn-reset { opacity: 0; transition: opacity 0.2s ease; }
|
|
1443
|
-
.standard-lab-token-row:hover .standard-lab-btn-reset:not(:disabled) { opacity: 1; }
|
|
1444
|
-
.standard-lab-btn-reset:disabled { opacity: 0; cursor: not-allowed; }
|
|
1445
|
-
.standard-lab-seed-row { display: flex; align-items: center; gap: 6px; margin-bottom: 4px; }
|
|
1446
|
-
.standard-lab-seed-row:last-child { margin-bottom: 0; }
|
|
1447
|
-
.standard-lab-color-swatch { width: 28px; height: 28px; border: var(--stnd-lab-border); border-radius: 4px; cursor: pointer; padding: 2px; background: rgba(255,255,255,0.05); flex-shrink: 0; }
|
|
1448
|
-
.standard-lab-color-swatch::-webkit-color-swatch-wrapper { padding: 0; }
|
|
1449
|
-
.standard-lab-color-swatch::-webkit-color-swatch { border: none; border-radius: 2px; }
|
|
1450
|
-
.standard-lab-seed-input { flex: 1; min-width: 0; padding: 2px 4px; background: transparent; border: 1px solid transparent; border-radius: 4px; font-family: var(--stnd-lab-font-family); font-size: var(--stnd-lab-text-size); color: var(--stnd-lab-text); transition: border-color 0.2s ease; }
|
|
1451
|
-
.standard-lab-seed-input:focus { outline: none; background: rgba(255,255,255,0.05); border-color: var(--stnd-lab-accent); }
|
|
1452
|
-
.standard-lab-seed-tag { font-size: calc(var(--stnd-lab-text-size) * 0.833); text-transform: uppercase; letter-spacing: 0.05em; opacity: 0.5; flex-shrink: 0; width: 20px; text-align: right; }
|
|
1453
|
-
.standard-lab-font-row { display: flex; align-items: center; gap: 6px; margin-bottom: 4px; }
|
|
1454
|
-
.standard-lab-font-row:last-child { margin-bottom: 0; }
|
|
1455
|
-
.standard-lab-font-tag { font-size: calc(var(--stnd-lab-text-size) * 0.833); text-transform: uppercase; letter-spacing: 0.05em; opacity: 0.5; flex-shrink: 0; width: 32px; }
|
|
1456
|
-
.standard-lab-quick-slider { display: flex; align-items: center; gap: 6px; margin-bottom: 4px; }
|
|
1457
|
-
.standard-lab-quick-slider:last-child { margin-bottom: 0; }
|
|
1458
|
-
.standard-lab-quick-slider .standard-lab-slider-input { flex: 1; }
|
|
1459
|
-
.standard-lab-slider-readout { font-family: var(--stnd-lab-font-family); font-size: var(--stnd-lab-text-size); opacity: 0.8; flex-shrink: 0; width: 48px; text-align: right; }
|
|
1460
|
-
.standard-lab-empty { padding: 6px; text-align: center; color: var(--stnd-lab-text); opacity: 0.6; font-size: calc(var(--stnd-lab-text-size) * 1.167); }
|
|
1461
|
-
.standard-lab-notification { position: fixed; top: 6px; left: 50%; transform: translateX(-50%) translateY(-10px); background: var(--stnd-lab-bg); border: var(--stnd-lab-border); border-radius: 6px; padding: 8px 16px; box-shadow: var(--stnd-lab-shadow); z-index: calc(var(--z-toast) + 1); font-size: calc(var(--stnd-lab-text-size) * 1.167); opacity: 0; transition: opacity 0.2s ease, transform 0.2s ease; pointer-events: none; }
|
|
1462
|
-
.standard-lab-notification-success { background: var(--stnd-lab-accent); color: var(--stnd-lab-bg); border-color: var(--stnd-lab-accent); }
|
|
1463
|
-
.standard-lab-notification-info { background: var(--stnd-lab-accent); color: var(--stnd-lab-bg); border-color: var(--stnd-lab-accent); }
|
|
1464
|
-
@media (prefers-color-scheme: dark) { .standard-lab-panel { box-shadow: var(--stnd-lab-shadow); } }
|
|
1465
|
-
@media (max-width: 768px) { .standard-lab-panel-quick, .standard-lab-panel-full { width: calc(100vw - 32px); max-width: 360px; } }
|
|
1466
|
-
@media print { .standard-lab-badge, .standard-lab-panel, .standard-lab-notification { display: none !important; } }
|
|
1467
|
-
`;
|
|
1468
|
-
document.head.appendChild(styles);
|
|
1469
|
-
}
|
|
1470
|
-
}
|
|
1471
|
-
|
|
1472
|
-
// ================================================================
|
|
1473
|
-
// AUTO-INITIALIZATION AND ASTRO CLIENTROUTER INTEGRATION
|
|
1474
|
-
// ================================================================
|
|
1475
|
-
|
|
1476
|
-
if (typeof window !== "undefined") {
|
|
1477
|
-
const setupStandardLab = () => {
|
|
1478
|
-
// Ensure body exists before initializing
|
|
1479
|
-
if (!document.body) {
|
|
1480
|
-
setTimeout(setupStandardLab, 10);
|
|
1481
|
-
return;
|
|
1482
|
-
}
|
|
1483
|
-
|
|
1484
|
-
if (!window.StandardLab) {
|
|
1485
|
-
window.StandardLab = new StandardLab();
|
|
1486
|
-
console.log("[StandardLab] ✓ Initialized");
|
|
1487
|
-
}
|
|
1488
|
-
|
|
1489
|
-
// Handle HTMX swaps - Setup here to ensure document.body exists
|
|
1490
|
-
if (document.body) {
|
|
1491
|
-
document.body.addEventListener("htmx:afterSettle", () => {
|
|
1492
|
-
console.log("[StandardLab] ⚡ HTMX swap detected");
|
|
1493
|
-
if (window.StandardLab) {
|
|
1494
|
-
window.StandardLab.refresh();
|
|
1495
|
-
}
|
|
1496
|
-
});
|
|
1497
|
-
}
|
|
1498
|
-
};
|
|
1499
|
-
|
|
1500
|
-
if (document.readyState === "loading") {
|
|
1501
|
-
document.addEventListener("DOMContentLoaded", setupStandardLab);
|
|
1502
|
-
} else {
|
|
1503
|
-
setupStandardLab();
|
|
1504
|
-
}
|
|
1505
|
-
|
|
1506
|
-
// Handle Astro ClientRouter page swaps - MUST check root container
|
|
1507
|
-
document.addEventListener("astro:after-swap", () => {
|
|
1508
|
-
console.log("[StandardLab] 🔄 Page swap detected");
|
|
1509
|
-
|
|
1510
|
-
if (!window.StandardLab) {
|
|
1511
|
-
console.log("[StandardLab] ⚠️ Instance not found, reinitializing");
|
|
1512
|
-
window.StandardLab = new StandardLab();
|
|
1513
|
-
return;
|
|
1514
|
-
}
|
|
1515
|
-
|
|
1516
|
-
// Check if badge exists in the new DOM
|
|
1517
|
-
const badge = document.getElementById("standard-lab-badge-container");
|
|
1518
|
-
|
|
1519
|
-
// If badge is gone, recreate it
|
|
1520
|
-
if (!badge) {
|
|
1521
|
-
console.log("[StandardLab] 🔨 Recreating badge");
|
|
1522
|
-
window.StandardLab.badge = null;
|
|
1523
|
-
window.StandardLab.createBadge();
|
|
1524
|
-
}
|
|
1525
|
-
|
|
1526
|
-
// If panel was open, we might want to close it or re-open it.
|
|
1527
|
-
// For now, let's ensure state is consistent.
|
|
1528
|
-
// If the panel element is missing from DOM but state says it's open, close it to reset.
|
|
1529
|
-
const panel = document.getElementById("standard-lab-panel-container");
|
|
1530
|
-
if (window.StandardLab.state !== "dormant" && !panel) {
|
|
1531
|
-
window.StandardLab.state = "dormant";
|
|
1532
|
-
window.StandardLab.panel = null;
|
|
1533
|
-
}
|
|
1534
|
-
|
|
1535
|
-
// Always restore state from storage
|
|
1536
|
-
window.StandardLab.restoreTheme();
|
|
1537
|
-
window.StandardLab.restoreDebugMode();
|
|
1538
|
-
|
|
1539
|
-
// Re-inject debug overlays if needed
|
|
1540
|
-
if (document.documentElement.classList.contains("stnd-debug")) {
|
|
1541
|
-
window.StandardLab.injectGridDebugOverlays();
|
|
1542
|
-
}
|
|
1543
|
-
});
|
|
1544
|
-
}
|
|
1545
|
-
})();
|