@stnd/lab 1.6.0 → 1.7.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 +12 -0
- package/Lab.svelte +335 -171
- package/README.md +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
package/Lab.svelte
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
} from "./color.js";
|
|
25
25
|
|
|
26
26
|
// Sidebar transition parameters (defaults)
|
|
27
|
-
let sidebarDuration = $state(
|
|
27
|
+
let sidebarDuration = $state(220); // milliseconds
|
|
28
28
|
let customEasing = $state(bezier(0.22, 1, 0.36, 1));
|
|
29
29
|
|
|
30
30
|
// =========================================================================
|
|
@@ -57,6 +57,15 @@
|
|
|
57
57
|
{ label: "Deep Slate", value: "oklch(0.19 0.015 255)" },
|
|
58
58
|
{ label: "Carbon", value: "#141413" },
|
|
59
59
|
];
|
|
60
|
+
const ACCENT_PRESETS = [
|
|
61
|
+
{ label: "Signal Red", value: "oklch(0.6 0.2 27)" },
|
|
62
|
+
{ label: "Amber", value: "oklch(0.5 0.12 55)" },
|
|
63
|
+
{ label: "Forest", value: "oklch(0.5 0.12 150)" },
|
|
64
|
+
{ label: "Teal", value: "oklch(0.6 0.1 195)" },
|
|
65
|
+
{ label: "Ocean", value: "oklch(0.55 0.15 245)" },
|
|
66
|
+
{ label: "Violet", value: "oklch(0.5 0.18 300)" },
|
|
67
|
+
{ label: "Candy Pink", value: "oklch(0.65 0.22 355)" },
|
|
68
|
+
];
|
|
60
69
|
const FG_PRESETS = [
|
|
61
70
|
{ label: "Warm Ink", value: "#262626" },
|
|
62
71
|
{ label: "Espresso", value: "#4a341c" },
|
|
@@ -72,7 +81,28 @@
|
|
|
72
81
|
// =========================================================================
|
|
73
82
|
// Props. `open` opens the panel on mount and `pin` keeps it open: the Studio
|
|
74
83
|
// page embeds the Lab that way, so it works in production too.
|
|
75
|
-
|
|
84
|
+
// `showHandler` controls whether the floating edge pastille/handler is rendered.
|
|
85
|
+
let { open = false, pin = false, showHandler = true } = $props();
|
|
86
|
+
|
|
87
|
+
let handlerVisible = $state(
|
|
88
|
+
showHandler &&
|
|
89
|
+
(typeof document === "undefined" ||
|
|
90
|
+
(document.documentElement.getAttribute("data-lab-handler") !== "false" &&
|
|
91
|
+
!document.documentElement.hasAttribute("data-lab-no-handler"))),
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
function syncHandlerVisibility() {
|
|
95
|
+
if (
|
|
96
|
+
showHandler === false ||
|
|
97
|
+
(typeof document !== "undefined" &&
|
|
98
|
+
(document.documentElement.getAttribute("data-lab-handler") === "false" ||
|
|
99
|
+
document.documentElement.hasAttribute("data-lab-no-handler")))
|
|
100
|
+
) {
|
|
101
|
+
handlerVisible = false;
|
|
102
|
+
} else {
|
|
103
|
+
handlerVisible = true;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
76
106
|
|
|
77
107
|
// One Lab per page. The dev module mounts it on every page and the Studio
|
|
78
108
|
// mounts its own; whichever comes second renders nothing and asks the first
|
|
@@ -89,10 +119,11 @@
|
|
|
89
119
|
let sidebarEl = $state();
|
|
90
120
|
|
|
91
121
|
// Custom OKLCH color picker state
|
|
92
|
-
let activePicker = $state(null); // null | "bg" | "fg"
|
|
122
|
+
let activePicker = $state(null); // null | "bg" | "fg" | "accent"
|
|
93
123
|
let pickerFormat = $state("oklch"); // "oklch" | "hex"
|
|
94
124
|
let bgOklch = $state({ L: 1, C: 0, h: 0 });
|
|
95
125
|
let fgOklch = $state({ L: 0.2, C: 0, h: 0 });
|
|
126
|
+
let accentOklch = $state({ L: 0.6, C: 0.15, h: 30 });
|
|
96
127
|
let pickerCanvasEl = $state();
|
|
97
128
|
let isDraggingPlane = $state(false);
|
|
98
129
|
const hasEyeDropper =
|
|
@@ -126,12 +157,27 @@
|
|
|
126
157
|
}
|
|
127
158
|
}
|
|
128
159
|
|
|
160
|
+
let openTimer;
|
|
161
|
+
|
|
129
162
|
function openLab() {
|
|
130
163
|
clearTimeout(closeTimer);
|
|
164
|
+
clearTimeout(openTimer);
|
|
131
165
|
view = "open";
|
|
132
166
|
}
|
|
133
167
|
|
|
168
|
+
function hoverOpenLab() {
|
|
169
|
+
if (typeof window !== "undefined" && !window.matchMedia("(hover: hover) and (pointer: fine)").matches) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
clearTimeout(closeTimer);
|
|
173
|
+
clearTimeout(openTimer);
|
|
174
|
+
openTimer = setTimeout(() => {
|
|
175
|
+
view = "open";
|
|
176
|
+
}, 120);
|
|
177
|
+
}
|
|
178
|
+
|
|
134
179
|
function requestCloseLab() {
|
|
180
|
+
clearTimeout(openTimer);
|
|
135
181
|
if (pinned || activePicker || isDraggingPlane) return;
|
|
136
182
|
// Keep open if user is interacting with inputs or popovers inside
|
|
137
183
|
if (
|
|
@@ -146,24 +192,38 @@
|
|
|
146
192
|
clearTimeout(closeTimer);
|
|
147
193
|
closeTimer = setTimeout(() => {
|
|
148
194
|
view = "dormant";
|
|
149
|
-
},
|
|
195
|
+
}, 280);
|
|
150
196
|
}
|
|
151
197
|
|
|
152
198
|
function cancelCloseLab() {
|
|
199
|
+
clearTimeout(openTimer);
|
|
153
200
|
clearTimeout(closeTimer);
|
|
154
201
|
}
|
|
155
202
|
|
|
203
|
+
function togglePinned() {
|
|
204
|
+
pinned = !pinned;
|
|
205
|
+
try {
|
|
206
|
+
sessionStorage.setItem("stnd-pinned", String(pinned));
|
|
207
|
+
} catch {}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function collapseLab() {
|
|
211
|
+
pinned = false;
|
|
212
|
+
try {
|
|
213
|
+
sessionStorage.setItem("stnd-pinned", "false");
|
|
214
|
+
} catch {}
|
|
215
|
+
view = "dormant";
|
|
216
|
+
}
|
|
217
|
+
|
|
156
218
|
let modified = $state(new Set());
|
|
157
219
|
let version = $state("");
|
|
158
220
|
let viewportW = $state(0);
|
|
159
221
|
|
|
160
222
|
// Collapsible sections
|
|
161
223
|
let sectionsOpen = $state({
|
|
162
|
-
theme:
|
|
163
|
-
colors:
|
|
164
|
-
typography:
|
|
165
|
-
scale: false,
|
|
166
|
-
spacing: false,
|
|
224
|
+
theme: true,
|
|
225
|
+
colors: true,
|
|
226
|
+
typography: true,
|
|
167
227
|
debug: false,
|
|
168
228
|
links: false,
|
|
169
229
|
});
|
|
@@ -223,17 +283,28 @@
|
|
|
223
283
|
let fgToken = $derived(
|
|
224
284
|
scheme === "dark" ? "--color-dark-foreground" : "--color-light-foreground",
|
|
225
285
|
);
|
|
286
|
+
let accentToken = $derived(
|
|
287
|
+
scheme === "dark" ? "--color-dark-accent" : "--color-light-accent",
|
|
288
|
+
);
|
|
289
|
+
const oklchOf = (role) =>
|
|
290
|
+
role === "bg" ? bgOklch : role === "fg" ? fgOklch : accentOklch;
|
|
291
|
+
const tokenOf = (role) =>
|
|
292
|
+
role === "bg" ? bgToken : role === "fg" ? fgToken : accentToken;
|
|
293
|
+
const ROLE_LABEL = { bg: "Background", fg: "Foreground", accent: "Accent" };
|
|
226
294
|
|
|
227
295
|
// Quick panel seeds (scheme-aware)
|
|
228
296
|
let seedBg = $state("");
|
|
229
297
|
let seedFg = $state("");
|
|
230
298
|
let seedBgHex = $state("#ffffff");
|
|
231
299
|
let seedFgHex = $state("#262626");
|
|
300
|
+
let seedAccent = $state("");
|
|
301
|
+
let seedAccentHex = $state("transparent");
|
|
232
302
|
let fontText = $state("");
|
|
233
303
|
let fontHeader = $state("");
|
|
234
304
|
let fontInterface = $state("");
|
|
235
305
|
let fontMono = $state("");
|
|
236
306
|
let fontSize = $state(1);
|
|
307
|
+
/** Font density: the theme-authoring knob. `--line-height` is derived from it, clamped to 1.35–1.75. */
|
|
237
308
|
let lineHeight = $state(1.414);
|
|
238
309
|
let rhythmGap = $state(1);
|
|
239
310
|
let ratio = $state(1.414);
|
|
@@ -355,6 +426,20 @@
|
|
|
355
426
|
bgOklch = { L: parsedBg.L, C: parsedBg.C, h: parsedBg.h };
|
|
356
427
|
fgOklch = { L: parsedFg.L, C: parsedFg.C, h: parsedFg.h };
|
|
357
428
|
|
|
429
|
+
const rawAccent = readVar(accentToken, target);
|
|
430
|
+
const parsedAccent = parseCssColor(
|
|
431
|
+
rawAccent && !/\b(from|light-dark|var)\b/i.test(rawAccent)
|
|
432
|
+
? rawAccent
|
|
433
|
+
: `var(${accentToken}, var(--color-accent))`,
|
|
434
|
+
target,
|
|
435
|
+
);
|
|
436
|
+
seedAccent =
|
|
437
|
+
rawAccent && !/\b(from|light-dark|var|calc|color-mix)\b/i.test(rawAccent)
|
|
438
|
+
? rawAccent
|
|
439
|
+
: parsedAccent.css;
|
|
440
|
+
seedAccentHex = parsedAccent.hex;
|
|
441
|
+
accentOklch = { L: parsedAccent.L, C: parsedAccent.C, h: parsedAccent.h };
|
|
442
|
+
|
|
358
443
|
fontText = readVar("--font-text", target);
|
|
359
444
|
fontHeader = readVar("--font-header", target);
|
|
360
445
|
fontInterface = readVar("--font-interface", target);
|
|
@@ -363,7 +448,10 @@
|
|
|
363
448
|
parseFloat(readVar("--font-text-size", target)) ||
|
|
364
449
|
parseFloat(readVar("--font-size", target)) ||
|
|
365
450
|
1;
|
|
366
|
-
lineHeight =
|
|
451
|
+
lineHeight = Math.min(
|
|
452
|
+
1.75,
|
|
453
|
+
Math.max(1.35, parseFloat(readVar("--density-base", target)) || parseFloat(readVar("--line-height", target)) || 1.414),
|
|
454
|
+
);
|
|
367
455
|
ratio = parseFloat(readVar("--optical-ratio", target)) || 1.414;
|
|
368
456
|
|
|
369
457
|
const rawGap = readVar("--page-padding", target);
|
|
@@ -373,9 +461,8 @@
|
|
|
373
461
|
const pxGap = resolvePx(rawGap);
|
|
374
462
|
gapBody = pxSpace ? pxGap / pxSpace : 1;
|
|
375
463
|
|
|
376
|
-
//
|
|
377
|
-
|
|
378
|
-
rhythmGap = pxSpace ? pxRhythm / pxSpace : 0.5;
|
|
464
|
+
// The rhythm: how much heavier than a text gap the space around a block is (`--rhythm-block-scale`).
|
|
465
|
+
rhythmGap = parseFloat(readVar("--rhythm-block-scale", target)) || 2;
|
|
379
466
|
|
|
380
467
|
// Calculate radius multiplier
|
|
381
468
|
const rawRadius = readVar("--radius", target);
|
|
@@ -398,15 +485,17 @@
|
|
|
398
485
|
"--color-light-foreground",
|
|
399
486
|
"--color-dark-background",
|
|
400
487
|
"--color-dark-foreground",
|
|
488
|
+
"--color-light-accent",
|
|
489
|
+
"--color-dark-accent",
|
|
401
490
|
"--font-text",
|
|
402
491
|
"--font-header",
|
|
403
492
|
"--font-interface",
|
|
404
493
|
"--font-monospace",
|
|
405
494
|
"--font-text-size",
|
|
406
|
-
"--
|
|
495
|
+
"--font-density",
|
|
407
496
|
"--optical-ratio",
|
|
408
497
|
"--page-padding",
|
|
409
|
-
"--rhythm-
|
|
498
|
+
"--rhythm-block-scale",
|
|
410
499
|
"--radius",
|
|
411
500
|
];
|
|
412
501
|
for (const t of tokens) {
|
|
@@ -458,15 +547,17 @@
|
|
|
458
547
|
"--color-light-foreground",
|
|
459
548
|
"--color-dark-background",
|
|
460
549
|
"--color-dark-foreground",
|
|
550
|
+
"--color-light-accent",
|
|
551
|
+
"--color-dark-accent",
|
|
461
552
|
"--font-text",
|
|
462
553
|
"--font-header",
|
|
463
554
|
"--font-interface",
|
|
464
555
|
"--font-monospace",
|
|
465
556
|
"--font-text-size",
|
|
466
|
-
"--
|
|
557
|
+
"--font-density",
|
|
467
558
|
"--optical-ratio",
|
|
468
559
|
"--page-padding",
|
|
469
|
-
"--rhythm-
|
|
560
|
+
"--rhythm-block-scale",
|
|
470
561
|
"--radius",
|
|
471
562
|
];
|
|
472
563
|
const css = tokens
|
|
@@ -670,9 +761,13 @@
|
|
|
670
761
|
};
|
|
671
762
|
|
|
672
763
|
restoreLabState();
|
|
764
|
+
syncHandlerVisibility();
|
|
673
765
|
if (open) view = "open";
|
|
674
766
|
if (pin) pinned = true;
|
|
675
|
-
document.addEventListener("astro:after-swap",
|
|
767
|
+
document.addEventListener("astro:after-swap", () => {
|
|
768
|
+
restoreLabState();
|
|
769
|
+
syncHandlerVisibility();
|
|
770
|
+
});
|
|
676
771
|
|
|
677
772
|
// Global API for dev toolbar compatibility
|
|
678
773
|
window.StandardLab = {
|
|
@@ -700,20 +795,40 @@
|
|
|
700
795
|
// Keyboard shortcuts
|
|
701
796
|
function onKey(e) {
|
|
702
797
|
const mod = navigator.platform.includes("Mac") ? e.metaKey : e.ctrlKey;
|
|
798
|
+
const el = document.activeElement;
|
|
799
|
+
const typing =
|
|
800
|
+
!!el && (["INPUT", "TEXTAREA", "SELECT"].includes(el.tagName) || el.isContentEditable);
|
|
801
|
+
|
|
802
|
+
// Right-sidebar shortcut `]` when not typing
|
|
803
|
+
if (!typing && !e.metaKey && !e.ctrlKey && !e.altKey && e.key === "]") {
|
|
804
|
+
e.preventDefault();
|
|
805
|
+
if (view === "dormant") {
|
|
806
|
+
view = "open";
|
|
807
|
+
pinned = true;
|
|
808
|
+
} else if (!pinned) {
|
|
809
|
+
pinned = true;
|
|
810
|
+
} else {
|
|
811
|
+
collapseLab();
|
|
812
|
+
}
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
703
815
|
|
|
704
816
|
// Toggle lab with Cmd/Ctrl+S (override default save shortcut)
|
|
705
817
|
if (mod && !e.shiftKey && e.key.toLowerCase() === "s") {
|
|
706
818
|
e.preventDefault();
|
|
707
819
|
view = view === "dormant" ? "open" : "dormant";
|
|
820
|
+
if (view === "open") pinned = true;
|
|
708
821
|
}
|
|
709
822
|
|
|
710
823
|
if (e.shiftKey && mod && e.key.toLowerCase() === "s") {
|
|
711
824
|
e.preventDefault();
|
|
712
825
|
view = view === "dormant" ? "open" : "dormant";
|
|
826
|
+
if (view === "open") pinned = true;
|
|
713
827
|
}
|
|
714
828
|
if (mod && !e.shiftKey && e.key.toLowerCase() === "d") {
|
|
715
829
|
e.preventDefault();
|
|
716
830
|
view = view === "dormant" ? "open" : "dormant";
|
|
831
|
+
if (view === "open") pinned = true;
|
|
717
832
|
}
|
|
718
833
|
if (e.shiftKey && mod && e.key.toLowerCase() === "d") {
|
|
719
834
|
e.preventDefault();
|
|
@@ -744,6 +859,7 @@
|
|
|
744
859
|
if (view !== "open" || pinned) return;
|
|
745
860
|
if (sidebarEl && e.composedPath().includes(sidebarEl)) return;
|
|
746
861
|
if (
|
|
862
|
+
e.target.closest?.(".lab-open-btn") ||
|
|
747
863
|
e.target.closest?.(".lab-handler") ||
|
|
748
864
|
e.target.closest?.(".lab-handler-top")
|
|
749
865
|
)
|
|
@@ -752,6 +868,7 @@
|
|
|
752
868
|
}
|
|
753
869
|
|
|
754
870
|
const domObserver = new MutationObserver(() => {
|
|
871
|
+
syncHandlerVisibility();
|
|
755
872
|
requestAnimationFrame(syncFromDOM);
|
|
756
873
|
});
|
|
757
874
|
domObserver.observe(document.documentElement, {
|
|
@@ -761,6 +878,8 @@
|
|
|
761
878
|
"data-color-mode",
|
|
762
879
|
"class",
|
|
763
880
|
"data-lab-scheme",
|
|
881
|
+
"data-lab-handler",
|
|
882
|
+
"data-lab-no-handler",
|
|
764
883
|
],
|
|
765
884
|
});
|
|
766
885
|
|
|
@@ -786,13 +905,12 @@
|
|
|
786
905
|
// EFFECTS
|
|
787
906
|
// =========================================================================
|
|
788
907
|
|
|
789
|
-
//
|
|
790
|
-
// Shrink the page when the sidebar is open (like DevTools)
|
|
908
|
+
// Shrink the page only when the Lab is pinned open on wide screens
|
|
791
909
|
$effect(() => {
|
|
792
910
|
if (duplicate) return;
|
|
793
911
|
if (typeof document === "undefined") return;
|
|
794
912
|
const el = document.documentElement;
|
|
795
|
-
if (view === "open") {
|
|
913
|
+
if (view === "open" && pinned) {
|
|
796
914
|
el.classList.add("lab-open");
|
|
797
915
|
} else {
|
|
798
916
|
el.classList.remove("lab-open");
|
|
@@ -828,7 +946,7 @@
|
|
|
828
946
|
const imgData = ctx.createImageData(width, height);
|
|
829
947
|
const data = imgData.data;
|
|
830
948
|
|
|
831
|
-
const cur = activePicker
|
|
949
|
+
const cur = oklchOf(activePicker);
|
|
832
950
|
const h = cur.h;
|
|
833
951
|
|
|
834
952
|
// Render 2D grid: X = Chroma (0 to MAX_CHROMA), Y = Lightness (1 at top to 0 at bottom)
|
|
@@ -860,15 +978,19 @@
|
|
|
860
978
|
"--color-light-foreground",
|
|
861
979
|
"--color-dark-background",
|
|
862
980
|
"--color-dark-foreground",
|
|
981
|
+
"--color-light-accent",
|
|
982
|
+
"--color-dark-accent",
|
|
863
983
|
"--font-text",
|
|
864
984
|
"--font-header",
|
|
865
985
|
"--font-interface",
|
|
866
986
|
"--font-monospace",
|
|
867
987
|
"--font-text-size",
|
|
868
988
|
"--font-size",
|
|
989
|
+
"--font-density",
|
|
869
990
|
"--line-height",
|
|
870
991
|
"--optical-ratio",
|
|
871
992
|
"--page-padding",
|
|
993
|
+
"--rhythm-block-scale",
|
|
872
994
|
"--rhythm-gap-new-line",
|
|
873
995
|
"--radius",
|
|
874
996
|
];
|
|
@@ -917,16 +1039,20 @@
|
|
|
917
1039
|
const oklchStr = formatOklch(L, C, h);
|
|
918
1040
|
const formatted = pickerFormat === "hex" ? hex : oklchStr;
|
|
919
1041
|
|
|
920
|
-
const token = role
|
|
1042
|
+
const token = tokenOf(role);
|
|
921
1043
|
|
|
922
1044
|
if (role === "bg") {
|
|
923
1045
|
bgOklch = { L, C, h };
|
|
924
1046
|
seedBg = formatted;
|
|
925
1047
|
seedBgHex = hex;
|
|
926
|
-
} else {
|
|
1048
|
+
} else if (role === "fg") {
|
|
927
1049
|
fgOklch = { L, C, h };
|
|
928
1050
|
seedFg = formatted;
|
|
929
1051
|
seedFgHex = hex;
|
|
1052
|
+
} else {
|
|
1053
|
+
accentOklch = { L, C, h };
|
|
1054
|
+
seedAccent = formatted;
|
|
1055
|
+
seedAccentHex = hex;
|
|
930
1056
|
}
|
|
931
1057
|
|
|
932
1058
|
writeVar(token, formatted);
|
|
@@ -934,7 +1060,7 @@
|
|
|
934
1060
|
|
|
935
1061
|
function onColorText(role, e) {
|
|
936
1062
|
const val = e.target.value;
|
|
937
|
-
const token = role
|
|
1063
|
+
const token = tokenOf(role);
|
|
938
1064
|
const target = getThemeTargetElement();
|
|
939
1065
|
const parsed = parseCssColor(val, target);
|
|
940
1066
|
|
|
@@ -942,17 +1068,21 @@
|
|
|
942
1068
|
seedBg = val;
|
|
943
1069
|
seedBgHex = parsed.hex;
|
|
944
1070
|
bgOklch = { L: parsed.L, C: parsed.C, h: parsed.h };
|
|
945
|
-
} else {
|
|
1071
|
+
} else if (role === "fg") {
|
|
946
1072
|
seedFg = val;
|
|
947
1073
|
seedFgHex = parsed.hex;
|
|
948
1074
|
fgOklch = { L: parsed.L, C: parsed.C, h: parsed.h };
|
|
1075
|
+
} else {
|
|
1076
|
+
seedAccent = val;
|
|
1077
|
+
seedAccentHex = parsed.hex;
|
|
1078
|
+
accentOklch = { L: parsed.L, C: parsed.C, h: parsed.h };
|
|
949
1079
|
}
|
|
950
1080
|
writeVar(token, val);
|
|
951
1081
|
}
|
|
952
1082
|
|
|
953
1083
|
function onSliderInput(role, channel, e) {
|
|
954
1084
|
const val = parseFloat(e.target.value);
|
|
955
|
-
const cur =
|
|
1085
|
+
const cur = { ...oklchOf(role) };
|
|
956
1086
|
cur[channel] = val;
|
|
957
1087
|
updateColorFromOklch(role, cur);
|
|
958
1088
|
}
|
|
@@ -966,7 +1096,7 @@
|
|
|
966
1096
|
const C = (x / rect.width) * MAX_CHROMA;
|
|
967
1097
|
const L = 1 - y / rect.height;
|
|
968
1098
|
|
|
969
|
-
const cur = activePicker
|
|
1099
|
+
const cur = oklchOf(activePicker);
|
|
970
1100
|
updateColorFromOklch(activePicker, { L, C, h: cur.h });
|
|
971
1101
|
}
|
|
972
1102
|
|
|
@@ -1026,7 +1156,7 @@
|
|
|
1026
1156
|
|
|
1027
1157
|
function onLineHeightInput(e) {
|
|
1028
1158
|
lineHeight = parseFloat(e.target.value);
|
|
1029
|
-
writeVar("--
|
|
1159
|
+
writeVar("--font-density", String(lineHeight));
|
|
1030
1160
|
}
|
|
1031
1161
|
|
|
1032
1162
|
function onRatioInput(e) {
|
|
@@ -1041,7 +1171,7 @@
|
|
|
1041
1171
|
|
|
1042
1172
|
function onRhythmGapInput(e) {
|
|
1043
1173
|
rhythmGap = parseFloat(e.target.value);
|
|
1044
|
-
writeVar("--rhythm-
|
|
1174
|
+
writeVar("--rhythm-block-scale", String(rhythmGap));
|
|
1045
1175
|
}
|
|
1046
1176
|
|
|
1047
1177
|
function onRadiusInput(e) {
|
|
@@ -1141,59 +1271,81 @@
|
|
|
1141
1271
|
|
|
1142
1272
|
{#if !duplicate}
|
|
1143
1273
|
<!-- ======================================================================
|
|
1144
|
-
TRIGGER
|
|
1274
|
+
TOP-RIGHT STICKY FLASK TRIGGER (Right Sidebar Minimized Mode)
|
|
1145
1275
|
====================================================================== -->
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
aria-label="Open Design Lab"
|
|
1151
|
-
onmouseenter={openLab}
|
|
1152
|
-
onmouseleave={requestCloseLab}
|
|
1153
|
-
onclick={() => (view = view === "dormant" ? "open" : "dormant")}
|
|
1154
|
-
onkeydown={(e) => e.key === "Enter" && (view = "open")}
|
|
1155
|
-
>
|
|
1156
|
-
<div class="lab-dot"></div>
|
|
1157
|
-
</div>
|
|
1158
|
-
|
|
1159
|
-
<div
|
|
1160
|
-
class="lab-handler m-0"
|
|
1161
|
-
class:scrolling={isScrolling}
|
|
1276
|
+
{#if handlerVisible && view === "dormant"}
|
|
1277
|
+
<button
|
|
1278
|
+
type="button"
|
|
1279
|
+
class="item lab-open-btn m-0"
|
|
1162
1280
|
class:modified={modCount > 0}
|
|
1163
|
-
onmouseenter={openLab}
|
|
1164
|
-
onmouseleave={requestCloseLab}
|
|
1165
|
-
role="button"
|
|
1166
|
-
tabindex="0"
|
|
1167
1281
|
aria-label="Open Design Lab"
|
|
1168
|
-
|
|
1169
|
-
|
|
1282
|
+
title="Open Design Lab ( ] )"
|
|
1283
|
+
onpointerenter={hoverOpenLab}
|
|
1284
|
+
onpointerleave={requestCloseLab}
|
|
1285
|
+
onclick={() => {
|
|
1286
|
+
clearTimeout(openTimer);
|
|
1287
|
+
view = "open";
|
|
1288
|
+
pinned = true;
|
|
1289
|
+
try {
|
|
1290
|
+
sessionStorage.setItem("stnd-pinned", "true");
|
|
1291
|
+
} catch {}
|
|
1292
|
+
}}
|
|
1170
1293
|
>
|
|
1171
|
-
<
|
|
1172
|
-
|
|
1294
|
+
<svg
|
|
1295
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
1296
|
+
width="1.25em"
|
|
1297
|
+
height="1.25em"
|
|
1298
|
+
viewBox="0 0 256 256"
|
|
1299
|
+
fill="currentColor"
|
|
1300
|
+
aria-hidden="true"
|
|
1301
|
+
>
|
|
1302
|
+
<path
|
|
1303
|
+
d="M221.6,199.77,160,96.92V40h8a8,8,0,0,0,0-16H88a8,8,0,0,0,0,16h8V96.92L34.4,199.77A16,16,0,0,0,48.13,224H207.87a16,16,0,0,0,13.73-24.23ZM110.86,103.25A7.93,7.93,0,0,0,112,99.14V40h32V99.14a7.93,7.93,0,0,0,1.14,4.11L183.36,167c-12.19,2.31-26.07-.37-41.19-8.19-19-9.82-35.87-12.48-50.25-8.23ZM48.13,208l24.51-40.91c11.79-4.76,25.75-2.66,43.1,6.31,15.65,8.09,30.12,11.66,43.18,10.67a63.36,63.36,0,0,0,15.46-3L207.87,208Z"
|
|
1304
|
+
/>
|
|
1305
|
+
</svg>
|
|
1306
|
+
{#if modCount > 0}
|
|
1307
|
+
<span class="lab-mod-dot" title="{modCount} active override(s)"></span>
|
|
1308
|
+
{/if}
|
|
1309
|
+
</button>
|
|
1310
|
+
{/if}
|
|
1173
1311
|
|
|
1174
1312
|
<!-- ======================================================================
|
|
1175
|
-
|
|
1313
|
+
RIGHT SIDEBAR PANE
|
|
1176
1314
|
====================================================================== -->
|
|
1177
1315
|
{#if view === "open"}
|
|
1178
1316
|
<div
|
|
1179
1317
|
bind:this={sidebarEl}
|
|
1180
1318
|
transition:fly={{
|
|
1181
|
-
x:
|
|
1319
|
+
x: 340,
|
|
1182
1320
|
duration: sidebarDuration,
|
|
1183
1321
|
easing: customEasing,
|
|
1184
1322
|
opacity: false,
|
|
1185
1323
|
}}
|
|
1186
|
-
class="lab-sidebar no-rhythm mt-0"
|
|
1324
|
+
class="pane lab-sidebar no-rhythm mt-0"
|
|
1325
|
+
data-pinned={pinned ? "" : undefined}
|
|
1187
1326
|
role="dialog"
|
|
1188
1327
|
tabindex="-1"
|
|
1189
1328
|
aria-label="Design Lab"
|
|
1190
|
-
|
|
1191
|
-
|
|
1329
|
+
onpointerenter={cancelCloseLab}
|
|
1330
|
+
onpointerleave={requestCloseLab}
|
|
1192
1331
|
>
|
|
1193
1332
|
<!-- ═══════════ HEADER ═══════════ -->
|
|
1194
1333
|
<header class="lab-header">
|
|
1195
1334
|
<div class="lab-header-title">
|
|
1196
|
-
<
|
|
1335
|
+
<svg
|
|
1336
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
1337
|
+
width="1.15em"
|
|
1338
|
+
height="1.15em"
|
|
1339
|
+
viewBox="0 0 256 256"
|
|
1340
|
+
fill="currentColor"
|
|
1341
|
+
class="lab-flask-mark"
|
|
1342
|
+
aria-hidden="true"
|
|
1343
|
+
>
|
|
1344
|
+
<path
|
|
1345
|
+
d="M221.6,199.77,160,96.92V40h8a8,8,0,0,0,0-16H88a8,8,0,0,0,0,16h8V96.92L34.4,199.77A16,16,0,0,0,48.13,224H207.87a16,16,0,0,0,13.73-24.23ZM110.86,103.25A7.93,7.93,0,0,0,112,99.14V40h32V99.14a7.93,7.93,0,0,0,1.14,4.11L183.36,167c-12.19,2.31-26.07-.37-41.19-8.19-19-9.82-35.87-12.48-50.25-8.23ZM48.13,208l24.51-40.91c11.79-4.76,25.75-2.66,43.1,6.31,15.65,8.09,30.12,11.66,43.18,10.67a63.36,63.36,0,0,0,15.46-3L207.87,208Z"
|
|
1346
|
+
/>
|
|
1347
|
+
</svg>
|
|
1348
|
+
<h2>stnd lab</h2>
|
|
1197
1349
|
{#if version}<span class="lab-version">v{version}</span>{/if}
|
|
1198
1350
|
</div>
|
|
1199
1351
|
<div class="lab-header-meta">
|
|
@@ -1216,16 +1368,17 @@
|
|
|
1216
1368
|
|
|
1217
1369
|
<button
|
|
1218
1370
|
class="smaller icon ghost"
|
|
1219
|
-
onclick={
|
|
1220
|
-
aria-label={pinned ? "Unpin Lab" : "Pin Lab"}
|
|
1221
|
-
title={pinned ? "
|
|
1371
|
+
onclick={togglePinned}
|
|
1372
|
+
aria-label={pinned ? "Unpin Lab (hover peek mode)" : "Pin Lab open"}
|
|
1373
|
+
title={pinned ? "Pinned open — click to switch to hover peek" : "Hover peek — click to pin open"}
|
|
1222
1374
|
>
|
|
1223
1375
|
{pinned ? "●" : "○"}
|
|
1224
1376
|
</button>
|
|
1225
1377
|
<button
|
|
1226
1378
|
class="smaller icon ghost"
|
|
1227
|
-
onclick={
|
|
1228
|
-
aria-label="
|
|
1379
|
+
onclick={collapseLab}
|
|
1380
|
+
aria-label="Minimize Lab to top-right icon"
|
|
1381
|
+
title="Collapse Lab ( ] )"
|
|
1229
1382
|
>
|
|
1230
1383
|
✕
|
|
1231
1384
|
</button>
|
|
@@ -1324,16 +1477,50 @@
|
|
|
1324
1477
|
</div>
|
|
1325
1478
|
</div>
|
|
1326
1479
|
|
|
1480
|
+
<div class="lab-field">
|
|
1481
|
+
<label class="lab-field-label" for="lab-accent-text">Accent</label>
|
|
1482
|
+
<div class="lab-color-row">
|
|
1483
|
+
<button
|
|
1484
|
+
type="button"
|
|
1485
|
+
class="lab-swatch"
|
|
1486
|
+
class:active={activePicker === "accent"}
|
|
1487
|
+
style="background-color: {seedAccentHex};"
|
|
1488
|
+
title="Toggle OKLCH accent picker"
|
|
1489
|
+
onclick={() => toggleColorPicker("accent")}
|
|
1490
|
+
aria-label="Pick accent color"
|
|
1491
|
+
></button>
|
|
1492
|
+
<input
|
|
1493
|
+
id="lab-accent-text"
|
|
1494
|
+
type="text"
|
|
1495
|
+
class="lab-text-input"
|
|
1496
|
+
value={seedAccent}
|
|
1497
|
+
spellcheck="false"
|
|
1498
|
+
placeholder="e.g. oklch(0.65 0.22 355)"
|
|
1499
|
+
oninput={(e) => onColorText("accent", e)}
|
|
1500
|
+
/>
|
|
1501
|
+
</div>
|
|
1502
|
+
</div>
|
|
1503
|
+
|
|
1327
1504
|
<!-- Custom OKLCH Color Picker Panel -->
|
|
1328
1505
|
{#if activePicker}
|
|
1329
|
-
{@const curOklch = activePicker
|
|
1330
|
-
{@const curHex =
|
|
1331
|
-
|
|
1506
|
+
{@const curOklch = oklchOf(activePicker)}
|
|
1507
|
+
{@const curHex =
|
|
1508
|
+
activePicker === "bg"
|
|
1509
|
+
? seedBgHex
|
|
1510
|
+
: activePicker === "fg"
|
|
1511
|
+
? seedFgHex
|
|
1512
|
+
: seedAccentHex}
|
|
1513
|
+
{@const presets =
|
|
1514
|
+
activePicker === "bg"
|
|
1515
|
+
? BG_PRESETS
|
|
1516
|
+
: activePicker === "fg"
|
|
1517
|
+
? FG_PRESETS
|
|
1518
|
+
: ACCENT_PRESETS}
|
|
1332
1519
|
<div class="lab-picker-panel" transition:slide>
|
|
1333
1520
|
<header class="lab-picker-header">
|
|
1334
1521
|
<div class="lab-picker-title">
|
|
1335
1522
|
<span class="lab-picker-role"
|
|
1336
|
-
>{activePicker
|
|
1523
|
+
>{ROLE_LABEL[activePicker]}</span
|
|
1337
1524
|
>
|
|
1338
1525
|
<span class="lab-picker-hex">{curHex}</span>
|
|
1339
1526
|
</div>
|
|
@@ -1547,20 +1734,6 @@
|
|
|
1547
1734
|
"--font-monospace",
|
|
1548
1735
|
fontMono,
|
|
1549
1736
|
)}
|
|
1550
|
-
</div>
|
|
1551
|
-
{/if}
|
|
1552
|
-
</section>
|
|
1553
|
-
|
|
1554
|
-
<!-- ─── SCALE ─── -->
|
|
1555
|
-
<section class="lab-section">
|
|
1556
|
-
<button class="lab-section-head" onclick={() => toggleSection("scale")}>
|
|
1557
|
-
<span class="lab-section-arrow" class:open={sectionsOpen.scale}
|
|
1558
|
-
>▶</span
|
|
1559
|
-
>
|
|
1560
|
-
<span class="lab-section-title">Scale</span>
|
|
1561
|
-
</button>
|
|
1562
|
-
{#if sectionsOpen.scale}
|
|
1563
|
-
<div class="lab-section-body" transition:slide>
|
|
1564
1737
|
<div class="lab-field">
|
|
1565
1738
|
<label class="lab-field-label" for="lab-font-size"
|
|
1566
1739
|
>Font Size</label
|
|
@@ -1581,16 +1754,16 @@
|
|
|
1581
1754
|
</div>
|
|
1582
1755
|
<div class="lab-field">
|
|
1583
1756
|
<label class="lab-field-label" for="lab-line-height"
|
|
1584
|
-
>
|
|
1757
|
+
>Font density</label
|
|
1585
1758
|
>
|
|
1586
1759
|
<div class="lab-slider-wrapper">
|
|
1587
1760
|
<input
|
|
1588
|
-
id="lab-line-height"
|
|
1761
|
+
id="lab-line-height" title="--font-density: sets --line-height, which stays between 1.35 and 1.75"
|
|
1589
1762
|
type="range"
|
|
1590
1763
|
class="lab-range"
|
|
1591
1764
|
value={lineHeight}
|
|
1592
|
-
min="1"
|
|
1593
|
-
max="
|
|
1765
|
+
min="1.35"
|
|
1766
|
+
max="1.75"
|
|
1594
1767
|
step="0.05"
|
|
1595
1768
|
oninput={onLineHeightInput}
|
|
1596
1769
|
/>
|
|
@@ -1615,23 +1788,6 @@
|
|
|
1615
1788
|
<span class="lab-readout">{ratio.toFixed(2)}</span>
|
|
1616
1789
|
</div>
|
|
1617
1790
|
</div>
|
|
1618
|
-
</div>
|
|
1619
|
-
{/if}
|
|
1620
|
-
</section>
|
|
1621
|
-
|
|
1622
|
-
<!-- ─── SPACING ─── -->
|
|
1623
|
-
<section class="lab-section">
|
|
1624
|
-
<button
|
|
1625
|
-
class="lab-section-head"
|
|
1626
|
-
onclick={() => toggleSection("spacing")}
|
|
1627
|
-
>
|
|
1628
|
-
<span class="lab-section-arrow" class:open={sectionsOpen.spacing}
|
|
1629
|
-
>▶</span
|
|
1630
|
-
>
|
|
1631
|
-
<span class="lab-section-title">Spacing</span>
|
|
1632
|
-
</button>
|
|
1633
|
-
{#if sectionsOpen.spacing}
|
|
1634
|
-
<div class="lab-section-body" transition:slide>
|
|
1635
1791
|
<div class="lab-field">
|
|
1636
1792
|
<label class="lab-field-label" for="lab-gap-body">Body Gap</label>
|
|
1637
1793
|
<div class="lab-slider-wrapper">
|
|
@@ -1650,15 +1806,15 @@
|
|
|
1650
1806
|
</div>
|
|
1651
1807
|
<div class="lab-field">
|
|
1652
1808
|
<label class="lab-field-label" for="lab-rhythm-gap"
|
|
1653
|
-
>
|
|
1809
|
+
>Block rhythm</label
|
|
1654
1810
|
>
|
|
1655
1811
|
<div class="lab-slider-wrapper">
|
|
1656
1812
|
<input
|
|
1657
|
-
id="lab-rhythm-gap"
|
|
1813
|
+
id="lab-rhythm-gap" title="--rhythm-block-scale: the space around tables, cards and figures, as a multiple of the text gap"
|
|
1658
1814
|
type="range"
|
|
1659
1815
|
class="lab-range"
|
|
1660
1816
|
value={rhythmGap}
|
|
1661
|
-
min="
|
|
1817
|
+
min="1"
|
|
1662
1818
|
max="4"
|
|
1663
1819
|
step="0.25"
|
|
1664
1820
|
oninput={onRhythmGapInput}
|
|
@@ -1790,10 +1946,12 @@
|
|
|
1790
1946
|
STYLES — Fully scoped, isolated from design tokens
|
|
1791
1947
|
====================================================================== -->
|
|
1792
1948
|
<style>
|
|
1793
|
-
/* ═══════════ PAGE SHRINK (
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1949
|
+
/* ═══════════ PAGE SHRINK (only when pinned on wide screens) ═══════════ */
|
|
1950
|
+
@media (min-width: 1025px) {
|
|
1951
|
+
:global(html.lab-open) {
|
|
1952
|
+
margin-right: calc(var(--lab-sidebar-w, 304px) + var(--window-inset, 8px) * 2);
|
|
1953
|
+
transition: margin-right var(--transition);
|
|
1954
|
+
}
|
|
1797
1955
|
}
|
|
1798
1956
|
:global(html:not(.lab-open)) {
|
|
1799
1957
|
transition: margin-right var(--transition);
|
|
@@ -1802,81 +1960,82 @@
|
|
|
1802
1960
|
/* ═══════════ DESIGN CONSTANTS ═══════════ */
|
|
1803
1961
|
:root {
|
|
1804
1962
|
--lab-font-size: 12px;
|
|
1805
|
-
--lab-sidebar-w:
|
|
1963
|
+
--lab-sidebar-w: 304px;
|
|
1806
1964
|
}
|
|
1807
1965
|
|
|
1808
|
-
/* ═══════════
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
right: 0;
|
|
1814
|
-
padding: var(--space-2);
|
|
1815
|
-
cursor: pointer;
|
|
1816
|
-
opacity: 0.1;
|
|
1817
|
-
transition: opacity var(--transition);
|
|
1818
|
-
display: none;
|
|
1819
|
-
}
|
|
1820
|
-
.lab-handler-top:hover {
|
|
1821
|
-
opacity: 1;
|
|
1822
|
-
}
|
|
1823
|
-
.lab-dot {
|
|
1824
|
-
width: var(--space-d4);
|
|
1825
|
-
height: var(--space-d4);
|
|
1826
|
-
background: var(--color-foreground);
|
|
1827
|
-
border-radius: 50%;
|
|
1966
|
+
/* ═══════════ TOP-RIGHT STICKY FLASK TRIGGER ═══════════ */
|
|
1967
|
+
:global(:root[data-lab-handler="false"]) .lab-open-btn,
|
|
1968
|
+
:global(:root[data-lab-no-handler]) .lab-open-btn {
|
|
1969
|
+
display: none !important;
|
|
1970
|
+
pointer-events: none !important;
|
|
1828
1971
|
}
|
|
1829
1972
|
|
|
1830
|
-
.lab-
|
|
1973
|
+
.lab-open-btn {
|
|
1831
1974
|
position: fixed;
|
|
1832
|
-
z-index:
|
|
1833
|
-
top:
|
|
1834
|
-
right:
|
|
1835
|
-
|
|
1836
|
-
padding-right: 20px;
|
|
1837
|
-
cursor: normal;
|
|
1838
|
-
display: flex;
|
|
1839
|
-
flex-direction: row;
|
|
1975
|
+
z-index: 9991;
|
|
1976
|
+
top: calc(var(--window-inset, 8px) + env(safe-area-inset-top, 0px));
|
|
1977
|
+
right: calc(var(--window-inset, 8px) + env(safe-area-inset-right, 0px));
|
|
1978
|
+
display: inline-flex;
|
|
1840
1979
|
align-items: center;
|
|
1841
|
-
|
|
1980
|
+
justify-content: center;
|
|
1981
|
+
width: calc(var(--space) * 1.5);
|
|
1982
|
+
height: calc(var(--space) * 1.5);
|
|
1983
|
+
padding: 0;
|
|
1984
|
+
border: none;
|
|
1985
|
+
border-radius: var(--radius-sm);
|
|
1986
|
+
background: var(--color-surface-raised);
|
|
1987
|
+
color: var(--color-muted);
|
|
1988
|
+
box-shadow: var(--shadow), var(--pane-edge);
|
|
1989
|
+
cursor: pointer;
|
|
1990
|
+
transition:
|
|
1991
|
+
color var(--transition-fast),
|
|
1992
|
+
background var(--transition-fast),
|
|
1993
|
+
transform var(--transition-fast),
|
|
1994
|
+
box-shadow var(--transition-fast);
|
|
1842
1995
|
}
|
|
1843
|
-
.lab-
|
|
1844
|
-
|
|
1996
|
+
.lab-open-btn:hover {
|
|
1997
|
+
color: var(--color-foreground);
|
|
1998
|
+
background: var(--color-surface-overlay);
|
|
1999
|
+
box-shadow: var(--shadow-lg), var(--pane-edge);
|
|
1845
2000
|
}
|
|
1846
|
-
.lab-
|
|
1847
|
-
|
|
1848
|
-
height: var(--space-2);
|
|
1849
|
-
background: oklch(from var(--color-foreground) l c h / 0);
|
|
1850
|
-
border-radius: var(--radius);
|
|
1851
|
-
box-shadow: var(--shadow-inset) var(--shadow-ring);
|
|
1852
|
-
transition: all var(--transition);
|
|
1853
|
-
cursor: normal;
|
|
1854
|
-
backdrop-filter: var(--filter-glass);
|
|
2001
|
+
.lab-open-btn.modified {
|
|
2002
|
+
color: var(--color-accent);
|
|
1855
2003
|
}
|
|
1856
|
-
.lab-
|
|
1857
|
-
|
|
2004
|
+
.lab-mod-dot {
|
|
2005
|
+
position: absolute;
|
|
2006
|
+
top: 4px;
|
|
2007
|
+
right: 4px;
|
|
2008
|
+
width: 6px;
|
|
2009
|
+
height: 6px;
|
|
2010
|
+
border-radius: 50%;
|
|
2011
|
+
background: var(--color-accent);
|
|
1858
2012
|
}
|
|
1859
2013
|
|
|
1860
|
-
/* ═══════════ SIDEBAR ═══════════ */
|
|
2014
|
+
/* ═══════════ RIGHT SIDEBAR PANE (Reveal Dev Panel geometry) ═══════════ */
|
|
1861
2015
|
.lab-sidebar {
|
|
1862
2016
|
position: fixed;
|
|
1863
2017
|
z-index: 9992;
|
|
1864
|
-
top:
|
|
1865
|
-
right:
|
|
2018
|
+
top: calc(var(--window-inset, 8px) + env(safe-area-inset-top, 0px));
|
|
2019
|
+
right: calc(var(--window-inset, 8px) + env(safe-area-inset-right, 0px));
|
|
1866
2020
|
width: var(--lab-sidebar-w);
|
|
1867
|
-
height:
|
|
2021
|
+
height: calc(
|
|
2022
|
+
100dvh - 2 * var(--window-inset, 8px) - env(safe-area-inset-top, 0px) -
|
|
2023
|
+
env(safe-area-inset-bottom, 0px)
|
|
2024
|
+
);
|
|
2025
|
+
margin: 0;
|
|
2026
|
+
padding: 0;
|
|
1868
2027
|
|
|
1869
2028
|
color: var(--color-foreground);
|
|
1870
2029
|
font: var(--lab-font-size) / 1.5 var(--font-monospace);
|
|
1871
|
-
border-
|
|
2030
|
+
border-radius: var(--pane-radius, 9px);
|
|
1872
2031
|
display: flex;
|
|
1873
2032
|
flex-direction: column;
|
|
1874
|
-
box-shadow: var(--shadow-
|
|
1875
|
-
background: var(--color-surface);
|
|
1876
|
-
backdrop-filter: var(--filter-
|
|
2033
|
+
box-shadow: var(--shadow-overlay), var(--pane-edge);
|
|
2034
|
+
background: var(--color-surface-raised);
|
|
2035
|
+
backdrop-filter: var(--filter-glass);
|
|
2036
|
+
overflow: hidden;
|
|
1877
2037
|
/* Ensure the sidebar stays fully opaque and never animates opacity */
|
|
1878
2038
|
opacity: 1 !important;
|
|
1879
|
-
/* Explicitly disable opacity animation while preserving other transitions */
|
|
1880
2039
|
transition:
|
|
1881
2040
|
transform var(--transition),
|
|
1882
2041
|
box-shadow var(--transition),
|
|
@@ -1891,8 +2050,6 @@
|
|
|
1891
2050
|
/* Reset children to Lab font, allowing explicit font-family previews to win */
|
|
1892
2051
|
.lab-sidebar * {
|
|
1893
2052
|
box-sizing: border-box;
|
|
1894
|
-
/* Disable opacity transitions on child elements to prevent fading when opening the lab.
|
|
1895
|
-
Other property transitions for children remain allowed outside of opacity. */
|
|
1896
2053
|
transition: opacity 0s !important;
|
|
1897
2054
|
}
|
|
1898
2055
|
.lab-sidebar *:not([style*="font-family"]) {
|
|
@@ -1905,19 +2062,22 @@
|
|
|
1905
2062
|
flex-wrap: wrap;
|
|
1906
2063
|
align-items: center;
|
|
1907
2064
|
gap: 0;
|
|
1908
|
-
padding: var(--space-
|
|
2065
|
+
padding: var(--space-d4) var(--space-d2);
|
|
1909
2066
|
border-bottom: var(--border);
|
|
1910
2067
|
flex-shrink: 0;
|
|
1911
|
-
|
|
1912
2068
|
background: var(--color-surface-raised);
|
|
1913
2069
|
}
|
|
1914
2070
|
.lab-header-title {
|
|
1915
2071
|
display: flex;
|
|
1916
2072
|
align-items: center;
|
|
1917
|
-
gap:
|
|
2073
|
+
gap: var(--space-d5);
|
|
1918
2074
|
flex: 1;
|
|
1919
2075
|
min-width: 0;
|
|
1920
2076
|
}
|
|
2077
|
+
.lab-flask-mark {
|
|
2078
|
+
color: var(--color-accent);
|
|
2079
|
+
flex-shrink: 0;
|
|
2080
|
+
}
|
|
1921
2081
|
.lab-header h2 {
|
|
1922
2082
|
font-size: var(--scale-d2) !important;
|
|
1923
2083
|
line-height: 1 !important;
|
|
@@ -2276,11 +2436,15 @@
|
|
|
2276
2436
|
/* ═══════════ RESPONSIVE ═══════════ */
|
|
2277
2437
|
@media (max-width: 480px) {
|
|
2278
2438
|
.lab-sidebar {
|
|
2439
|
+
top: 0;
|
|
2440
|
+
right: 0;
|
|
2279
2441
|
width: 100vw;
|
|
2442
|
+
height: 100dvh;
|
|
2443
|
+
border-radius: 0;
|
|
2280
2444
|
}
|
|
2281
2445
|
}
|
|
2282
2446
|
@media print {
|
|
2283
|
-
.lab-
|
|
2447
|
+
.lab-open-btn,
|
|
2284
2448
|
.lab-sidebar {
|
|
2285
2449
|
display: none !important;
|
|
2286
2450
|
}
|
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stnd/lab",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"svelte": "./Lab.svelte",
|
|
6
6
|
"author": "Francis Fontaine",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"bezier-easing": "^3.0.1",
|
|
18
18
|
"svelte": "5.57.2",
|
|
19
|
-
"@stnd/ui": "0.16.
|
|
19
|
+
"@stnd/ui": "0.16.1"
|
|
20
20
|
},
|
|
21
21
|
"peerDependencies": {
|
|
22
22
|
"astro": ">=5.0.0"
|