@stnd/lab 1.5.1 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/Lab.svelte +170 -67
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
package/Lab.svelte
CHANGED
|
@@ -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 =
|
|
@@ -159,11 +190,9 @@
|
|
|
159
190
|
|
|
160
191
|
// Collapsible sections
|
|
161
192
|
let sectionsOpen = $state({
|
|
162
|
-
theme:
|
|
163
|
-
colors:
|
|
164
|
-
typography:
|
|
165
|
-
scale: false,
|
|
166
|
-
spacing: false,
|
|
193
|
+
theme: true,
|
|
194
|
+
colors: true,
|
|
195
|
+
typography: true,
|
|
167
196
|
debug: false,
|
|
168
197
|
links: false,
|
|
169
198
|
});
|
|
@@ -223,17 +252,28 @@
|
|
|
223
252
|
let fgToken = $derived(
|
|
224
253
|
scheme === "dark" ? "--color-dark-foreground" : "--color-light-foreground",
|
|
225
254
|
);
|
|
255
|
+
let accentToken = $derived(
|
|
256
|
+
scheme === "dark" ? "--color-dark-accent" : "--color-light-accent",
|
|
257
|
+
);
|
|
258
|
+
const oklchOf = (role) =>
|
|
259
|
+
role === "bg" ? bgOklch : role === "fg" ? fgOklch : accentOklch;
|
|
260
|
+
const tokenOf = (role) =>
|
|
261
|
+
role === "bg" ? bgToken : role === "fg" ? fgToken : accentToken;
|
|
262
|
+
const ROLE_LABEL = { bg: "Background", fg: "Foreground", accent: "Accent" };
|
|
226
263
|
|
|
227
264
|
// Quick panel seeds (scheme-aware)
|
|
228
265
|
let seedBg = $state("");
|
|
229
266
|
let seedFg = $state("");
|
|
230
267
|
let seedBgHex = $state("#ffffff");
|
|
231
268
|
let seedFgHex = $state("#262626");
|
|
269
|
+
let seedAccent = $state("");
|
|
270
|
+
let seedAccentHex = $state("transparent");
|
|
232
271
|
let fontText = $state("");
|
|
233
272
|
let fontHeader = $state("");
|
|
234
273
|
let fontInterface = $state("");
|
|
235
274
|
let fontMono = $state("");
|
|
236
275
|
let fontSize = $state(1);
|
|
276
|
+
/** Font density: the theme-authoring knob. `--line-height` is derived from it, clamped to 1.35–1.75. */
|
|
237
277
|
let lineHeight = $state(1.414);
|
|
238
278
|
let rhythmGap = $state(1);
|
|
239
279
|
let ratio = $state(1.414);
|
|
@@ -355,6 +395,20 @@
|
|
|
355
395
|
bgOklch = { L: parsedBg.L, C: parsedBg.C, h: parsedBg.h };
|
|
356
396
|
fgOklch = { L: parsedFg.L, C: parsedFg.C, h: parsedFg.h };
|
|
357
397
|
|
|
398
|
+
const rawAccent = readVar(accentToken, target);
|
|
399
|
+
const parsedAccent = parseCssColor(
|
|
400
|
+
rawAccent && !/\b(from|light-dark|var)\b/i.test(rawAccent)
|
|
401
|
+
? rawAccent
|
|
402
|
+
: `var(${accentToken}, var(--color-accent))`,
|
|
403
|
+
target,
|
|
404
|
+
);
|
|
405
|
+
seedAccent =
|
|
406
|
+
rawAccent && !/\b(from|light-dark|var|calc|color-mix)\b/i.test(rawAccent)
|
|
407
|
+
? rawAccent
|
|
408
|
+
: parsedAccent.css;
|
|
409
|
+
seedAccentHex = parsedAccent.hex;
|
|
410
|
+
accentOklch = { L: parsedAccent.L, C: parsedAccent.C, h: parsedAccent.h };
|
|
411
|
+
|
|
358
412
|
fontText = readVar("--font-text", target);
|
|
359
413
|
fontHeader = readVar("--font-header", target);
|
|
360
414
|
fontInterface = readVar("--font-interface", target);
|
|
@@ -363,7 +417,10 @@
|
|
|
363
417
|
parseFloat(readVar("--font-text-size", target)) ||
|
|
364
418
|
parseFloat(readVar("--font-size", target)) ||
|
|
365
419
|
1;
|
|
366
|
-
lineHeight =
|
|
420
|
+
lineHeight = Math.min(
|
|
421
|
+
1.75,
|
|
422
|
+
Math.max(1.35, parseFloat(readVar("--density-base", target)) || parseFloat(readVar("--line-height", target)) || 1.414),
|
|
423
|
+
);
|
|
367
424
|
ratio = parseFloat(readVar("--optical-ratio", target)) || 1.414;
|
|
368
425
|
|
|
369
426
|
const rawGap = readVar("--page-padding", target);
|
|
@@ -373,9 +430,8 @@
|
|
|
373
430
|
const pxGap = resolvePx(rawGap);
|
|
374
431
|
gapBody = pxSpace ? pxGap / pxSpace : 1;
|
|
375
432
|
|
|
376
|
-
//
|
|
377
|
-
|
|
378
|
-
rhythmGap = pxSpace ? pxRhythm / pxSpace : 0.5;
|
|
433
|
+
// The rhythm: how much heavier than a text gap the space around a block is (`--rhythm-block-scale`).
|
|
434
|
+
rhythmGap = parseFloat(readVar("--rhythm-block-scale", target)) || 2;
|
|
379
435
|
|
|
380
436
|
// Calculate radius multiplier
|
|
381
437
|
const rawRadius = readVar("--radius", target);
|
|
@@ -398,15 +454,17 @@
|
|
|
398
454
|
"--color-light-foreground",
|
|
399
455
|
"--color-dark-background",
|
|
400
456
|
"--color-dark-foreground",
|
|
457
|
+
"--color-light-accent",
|
|
458
|
+
"--color-dark-accent",
|
|
401
459
|
"--font-text",
|
|
402
460
|
"--font-header",
|
|
403
461
|
"--font-interface",
|
|
404
462
|
"--font-monospace",
|
|
405
463
|
"--font-text-size",
|
|
406
|
-
"--
|
|
464
|
+
"--font-density",
|
|
407
465
|
"--optical-ratio",
|
|
408
466
|
"--page-padding",
|
|
409
|
-
"--rhythm-
|
|
467
|
+
"--rhythm-block-scale",
|
|
410
468
|
"--radius",
|
|
411
469
|
];
|
|
412
470
|
for (const t of tokens) {
|
|
@@ -458,15 +516,17 @@
|
|
|
458
516
|
"--color-light-foreground",
|
|
459
517
|
"--color-dark-background",
|
|
460
518
|
"--color-dark-foreground",
|
|
519
|
+
"--color-light-accent",
|
|
520
|
+
"--color-dark-accent",
|
|
461
521
|
"--font-text",
|
|
462
522
|
"--font-header",
|
|
463
523
|
"--font-interface",
|
|
464
524
|
"--font-monospace",
|
|
465
525
|
"--font-text-size",
|
|
466
|
-
"--
|
|
526
|
+
"--font-density",
|
|
467
527
|
"--optical-ratio",
|
|
468
528
|
"--page-padding",
|
|
469
|
-
"--rhythm-
|
|
529
|
+
"--rhythm-block-scale",
|
|
470
530
|
"--radius",
|
|
471
531
|
];
|
|
472
532
|
const css = tokens
|
|
@@ -670,9 +730,13 @@
|
|
|
670
730
|
};
|
|
671
731
|
|
|
672
732
|
restoreLabState();
|
|
733
|
+
syncHandlerVisibility();
|
|
673
734
|
if (open) view = "open";
|
|
674
735
|
if (pin) pinned = true;
|
|
675
|
-
document.addEventListener("astro:after-swap",
|
|
736
|
+
document.addEventListener("astro:after-swap", () => {
|
|
737
|
+
restoreLabState();
|
|
738
|
+
syncHandlerVisibility();
|
|
739
|
+
});
|
|
676
740
|
|
|
677
741
|
// Global API for dev toolbar compatibility
|
|
678
742
|
window.StandardLab = {
|
|
@@ -705,15 +769,18 @@
|
|
|
705
769
|
if (mod && !e.shiftKey && e.key.toLowerCase() === "s") {
|
|
706
770
|
e.preventDefault();
|
|
707
771
|
view = view === "dormant" ? "open" : "dormant";
|
|
772
|
+
if (view === "open") pinned = true;
|
|
708
773
|
}
|
|
709
774
|
|
|
710
775
|
if (e.shiftKey && mod && e.key.toLowerCase() === "s") {
|
|
711
776
|
e.preventDefault();
|
|
712
777
|
view = view === "dormant" ? "open" : "dormant";
|
|
778
|
+
if (view === "open") pinned = true;
|
|
713
779
|
}
|
|
714
780
|
if (mod && !e.shiftKey && e.key.toLowerCase() === "d") {
|
|
715
781
|
e.preventDefault();
|
|
716
782
|
view = view === "dormant" ? "open" : "dormant";
|
|
783
|
+
if (view === "open") pinned = true;
|
|
717
784
|
}
|
|
718
785
|
if (e.shiftKey && mod && e.key.toLowerCase() === "d") {
|
|
719
786
|
e.preventDefault();
|
|
@@ -752,6 +819,7 @@
|
|
|
752
819
|
}
|
|
753
820
|
|
|
754
821
|
const domObserver = new MutationObserver(() => {
|
|
822
|
+
syncHandlerVisibility();
|
|
755
823
|
requestAnimationFrame(syncFromDOM);
|
|
756
824
|
});
|
|
757
825
|
domObserver.observe(document.documentElement, {
|
|
@@ -761,6 +829,8 @@
|
|
|
761
829
|
"data-color-mode",
|
|
762
830
|
"class",
|
|
763
831
|
"data-lab-scheme",
|
|
832
|
+
"data-lab-handler",
|
|
833
|
+
"data-lab-no-handler",
|
|
764
834
|
],
|
|
765
835
|
});
|
|
766
836
|
|
|
@@ -828,7 +898,7 @@
|
|
|
828
898
|
const imgData = ctx.createImageData(width, height);
|
|
829
899
|
const data = imgData.data;
|
|
830
900
|
|
|
831
|
-
const cur = activePicker
|
|
901
|
+
const cur = oklchOf(activePicker);
|
|
832
902
|
const h = cur.h;
|
|
833
903
|
|
|
834
904
|
// Render 2D grid: X = Chroma (0 to MAX_CHROMA), Y = Lightness (1 at top to 0 at bottom)
|
|
@@ -860,15 +930,19 @@
|
|
|
860
930
|
"--color-light-foreground",
|
|
861
931
|
"--color-dark-background",
|
|
862
932
|
"--color-dark-foreground",
|
|
933
|
+
"--color-light-accent",
|
|
934
|
+
"--color-dark-accent",
|
|
863
935
|
"--font-text",
|
|
864
936
|
"--font-header",
|
|
865
937
|
"--font-interface",
|
|
866
938
|
"--font-monospace",
|
|
867
939
|
"--font-text-size",
|
|
868
940
|
"--font-size",
|
|
941
|
+
"--font-density",
|
|
869
942
|
"--line-height",
|
|
870
943
|
"--optical-ratio",
|
|
871
944
|
"--page-padding",
|
|
945
|
+
"--rhythm-block-scale",
|
|
872
946
|
"--rhythm-gap-new-line",
|
|
873
947
|
"--radius",
|
|
874
948
|
];
|
|
@@ -879,8 +953,16 @@
|
|
|
879
953
|
|
|
880
954
|
if (t) {
|
|
881
955
|
document.documentElement.setAttribute("data-theme", t);
|
|
956
|
+
try {
|
|
957
|
+
document.cookie = `stnd_theme=${encodeURIComponent(t)}; Path=/; Max-Age=31536000; SameSite=Lax`;
|
|
958
|
+
localStorage.setItem("std_theme", t);
|
|
959
|
+
} catch (e) {}
|
|
882
960
|
} else {
|
|
883
961
|
document.documentElement.removeAttribute("data-theme");
|
|
962
|
+
try {
|
|
963
|
+
document.cookie = `stnd_theme=; Path=/; Max-Age=0; SameSite=Lax`;
|
|
964
|
+
localStorage.removeItem("std_theme");
|
|
965
|
+
} catch (e) {}
|
|
884
966
|
}
|
|
885
967
|
sessionStorage.setItem("stnd-theme", t);
|
|
886
968
|
|
|
@@ -909,16 +991,20 @@
|
|
|
909
991
|
const oklchStr = formatOklch(L, C, h);
|
|
910
992
|
const formatted = pickerFormat === "hex" ? hex : oklchStr;
|
|
911
993
|
|
|
912
|
-
const token = role
|
|
994
|
+
const token = tokenOf(role);
|
|
913
995
|
|
|
914
996
|
if (role === "bg") {
|
|
915
997
|
bgOklch = { L, C, h };
|
|
916
998
|
seedBg = formatted;
|
|
917
999
|
seedBgHex = hex;
|
|
918
|
-
} else {
|
|
1000
|
+
} else if (role === "fg") {
|
|
919
1001
|
fgOklch = { L, C, h };
|
|
920
1002
|
seedFg = formatted;
|
|
921
1003
|
seedFgHex = hex;
|
|
1004
|
+
} else {
|
|
1005
|
+
accentOklch = { L, C, h };
|
|
1006
|
+
seedAccent = formatted;
|
|
1007
|
+
seedAccentHex = hex;
|
|
922
1008
|
}
|
|
923
1009
|
|
|
924
1010
|
writeVar(token, formatted);
|
|
@@ -926,7 +1012,7 @@
|
|
|
926
1012
|
|
|
927
1013
|
function onColorText(role, e) {
|
|
928
1014
|
const val = e.target.value;
|
|
929
|
-
const token = role
|
|
1015
|
+
const token = tokenOf(role);
|
|
930
1016
|
const target = getThemeTargetElement();
|
|
931
1017
|
const parsed = parseCssColor(val, target);
|
|
932
1018
|
|
|
@@ -934,17 +1020,21 @@
|
|
|
934
1020
|
seedBg = val;
|
|
935
1021
|
seedBgHex = parsed.hex;
|
|
936
1022
|
bgOklch = { L: parsed.L, C: parsed.C, h: parsed.h };
|
|
937
|
-
} else {
|
|
1023
|
+
} else if (role === "fg") {
|
|
938
1024
|
seedFg = val;
|
|
939
1025
|
seedFgHex = parsed.hex;
|
|
940
1026
|
fgOklch = { L: parsed.L, C: parsed.C, h: parsed.h };
|
|
1027
|
+
} else {
|
|
1028
|
+
seedAccent = val;
|
|
1029
|
+
seedAccentHex = parsed.hex;
|
|
1030
|
+
accentOklch = { L: parsed.L, C: parsed.C, h: parsed.h };
|
|
941
1031
|
}
|
|
942
1032
|
writeVar(token, val);
|
|
943
1033
|
}
|
|
944
1034
|
|
|
945
1035
|
function onSliderInput(role, channel, e) {
|
|
946
1036
|
const val = parseFloat(e.target.value);
|
|
947
|
-
const cur =
|
|
1037
|
+
const cur = { ...oklchOf(role) };
|
|
948
1038
|
cur[channel] = val;
|
|
949
1039
|
updateColorFromOklch(role, cur);
|
|
950
1040
|
}
|
|
@@ -958,7 +1048,7 @@
|
|
|
958
1048
|
const C = (x / rect.width) * MAX_CHROMA;
|
|
959
1049
|
const L = 1 - y / rect.height;
|
|
960
1050
|
|
|
961
|
-
const cur = activePicker
|
|
1051
|
+
const cur = oklchOf(activePicker);
|
|
962
1052
|
updateColorFromOklch(activePicker, { L, C, h: cur.h });
|
|
963
1053
|
}
|
|
964
1054
|
|
|
@@ -1018,7 +1108,7 @@
|
|
|
1018
1108
|
|
|
1019
1109
|
function onLineHeightInput(e) {
|
|
1020
1110
|
lineHeight = parseFloat(e.target.value);
|
|
1021
|
-
writeVar("--
|
|
1111
|
+
writeVar("--font-density", String(lineHeight));
|
|
1022
1112
|
}
|
|
1023
1113
|
|
|
1024
1114
|
function onRatioInput(e) {
|
|
@@ -1033,7 +1123,7 @@
|
|
|
1033
1123
|
|
|
1034
1124
|
function onRhythmGapInput(e) {
|
|
1035
1125
|
rhythmGap = parseFloat(e.target.value);
|
|
1036
|
-
writeVar("--rhythm-
|
|
1126
|
+
writeVar("--rhythm-block-scale", String(rhythmGap));
|
|
1037
1127
|
}
|
|
1038
1128
|
|
|
1039
1129
|
function onRadiusInput(e) {
|
|
@@ -1135,6 +1225,7 @@
|
|
|
1135
1225
|
<!-- ======================================================================
|
|
1136
1226
|
TRIGGER / HANDLER
|
|
1137
1227
|
====================================================================== -->
|
|
1228
|
+
{#if handlerVisible}
|
|
1138
1229
|
<div
|
|
1139
1230
|
class="lab-handler-top m-0"
|
|
1140
1231
|
role="button"
|
|
@@ -1162,6 +1253,7 @@
|
|
|
1162
1253
|
>
|
|
1163
1254
|
<div class="lab-handler-bar"></div>
|
|
1164
1255
|
</div>
|
|
1256
|
+
{/if}
|
|
1165
1257
|
|
|
1166
1258
|
<!-- ======================================================================
|
|
1167
1259
|
SIDE PANEL
|
|
@@ -1316,16 +1408,50 @@
|
|
|
1316
1408
|
</div>
|
|
1317
1409
|
</div>
|
|
1318
1410
|
|
|
1411
|
+
<div class="lab-field">
|
|
1412
|
+
<label class="lab-field-label" for="lab-accent-text">Accent</label>
|
|
1413
|
+
<div class="lab-color-row">
|
|
1414
|
+
<button
|
|
1415
|
+
type="button"
|
|
1416
|
+
class="lab-swatch"
|
|
1417
|
+
class:active={activePicker === "accent"}
|
|
1418
|
+
style="background-color: {seedAccentHex};"
|
|
1419
|
+
title="Toggle OKLCH accent picker"
|
|
1420
|
+
onclick={() => toggleColorPicker("accent")}
|
|
1421
|
+
aria-label="Pick accent color"
|
|
1422
|
+
></button>
|
|
1423
|
+
<input
|
|
1424
|
+
id="lab-accent-text"
|
|
1425
|
+
type="text"
|
|
1426
|
+
class="lab-text-input"
|
|
1427
|
+
value={seedAccent}
|
|
1428
|
+
spellcheck="false"
|
|
1429
|
+
placeholder="e.g. oklch(0.65 0.22 355)"
|
|
1430
|
+
oninput={(e) => onColorText("accent", e)}
|
|
1431
|
+
/>
|
|
1432
|
+
</div>
|
|
1433
|
+
</div>
|
|
1434
|
+
|
|
1319
1435
|
<!-- Custom OKLCH Color Picker Panel -->
|
|
1320
1436
|
{#if activePicker}
|
|
1321
|
-
{@const curOklch = activePicker
|
|
1322
|
-
{@const curHex =
|
|
1323
|
-
|
|
1437
|
+
{@const curOklch = oklchOf(activePicker)}
|
|
1438
|
+
{@const curHex =
|
|
1439
|
+
activePicker === "bg"
|
|
1440
|
+
? seedBgHex
|
|
1441
|
+
: activePicker === "fg"
|
|
1442
|
+
? seedFgHex
|
|
1443
|
+
: seedAccentHex}
|
|
1444
|
+
{@const presets =
|
|
1445
|
+
activePicker === "bg"
|
|
1446
|
+
? BG_PRESETS
|
|
1447
|
+
: activePicker === "fg"
|
|
1448
|
+
? FG_PRESETS
|
|
1449
|
+
: ACCENT_PRESETS}
|
|
1324
1450
|
<div class="lab-picker-panel" transition:slide>
|
|
1325
1451
|
<header class="lab-picker-header">
|
|
1326
1452
|
<div class="lab-picker-title">
|
|
1327
1453
|
<span class="lab-picker-role"
|
|
1328
|
-
>{activePicker
|
|
1454
|
+
>{ROLE_LABEL[activePicker]}</span
|
|
1329
1455
|
>
|
|
1330
1456
|
<span class="lab-picker-hex">{curHex}</span>
|
|
1331
1457
|
</div>
|
|
@@ -1539,20 +1665,6 @@
|
|
|
1539
1665
|
"--font-monospace",
|
|
1540
1666
|
fontMono,
|
|
1541
1667
|
)}
|
|
1542
|
-
</div>
|
|
1543
|
-
{/if}
|
|
1544
|
-
</section>
|
|
1545
|
-
|
|
1546
|
-
<!-- ─── SCALE ─── -->
|
|
1547
|
-
<section class="lab-section">
|
|
1548
|
-
<button class="lab-section-head" onclick={() => toggleSection("scale")}>
|
|
1549
|
-
<span class="lab-section-arrow" class:open={sectionsOpen.scale}
|
|
1550
|
-
>▶</span
|
|
1551
|
-
>
|
|
1552
|
-
<span class="lab-section-title">Scale</span>
|
|
1553
|
-
</button>
|
|
1554
|
-
{#if sectionsOpen.scale}
|
|
1555
|
-
<div class="lab-section-body" transition:slide>
|
|
1556
1668
|
<div class="lab-field">
|
|
1557
1669
|
<label class="lab-field-label" for="lab-font-size"
|
|
1558
1670
|
>Font Size</label
|
|
@@ -1573,16 +1685,16 @@
|
|
|
1573
1685
|
</div>
|
|
1574
1686
|
<div class="lab-field">
|
|
1575
1687
|
<label class="lab-field-label" for="lab-line-height"
|
|
1576
|
-
>
|
|
1688
|
+
>Font density</label
|
|
1577
1689
|
>
|
|
1578
1690
|
<div class="lab-slider-wrapper">
|
|
1579
1691
|
<input
|
|
1580
|
-
id="lab-line-height"
|
|
1692
|
+
id="lab-line-height" title="--font-density: sets --line-height, which stays between 1.35 and 1.75"
|
|
1581
1693
|
type="range"
|
|
1582
1694
|
class="lab-range"
|
|
1583
1695
|
value={lineHeight}
|
|
1584
|
-
min="1"
|
|
1585
|
-
max="
|
|
1696
|
+
min="1.35"
|
|
1697
|
+
max="1.75"
|
|
1586
1698
|
step="0.05"
|
|
1587
1699
|
oninput={onLineHeightInput}
|
|
1588
1700
|
/>
|
|
@@ -1607,23 +1719,6 @@
|
|
|
1607
1719
|
<span class="lab-readout">{ratio.toFixed(2)}</span>
|
|
1608
1720
|
</div>
|
|
1609
1721
|
</div>
|
|
1610
|
-
</div>
|
|
1611
|
-
{/if}
|
|
1612
|
-
</section>
|
|
1613
|
-
|
|
1614
|
-
<!-- ─── SPACING ─── -->
|
|
1615
|
-
<section class="lab-section">
|
|
1616
|
-
<button
|
|
1617
|
-
class="lab-section-head"
|
|
1618
|
-
onclick={() => toggleSection("spacing")}
|
|
1619
|
-
>
|
|
1620
|
-
<span class="lab-section-arrow" class:open={sectionsOpen.spacing}
|
|
1621
|
-
>▶</span
|
|
1622
|
-
>
|
|
1623
|
-
<span class="lab-section-title">Spacing</span>
|
|
1624
|
-
</button>
|
|
1625
|
-
{#if sectionsOpen.spacing}
|
|
1626
|
-
<div class="lab-section-body" transition:slide>
|
|
1627
1722
|
<div class="lab-field">
|
|
1628
1723
|
<label class="lab-field-label" for="lab-gap-body">Body Gap</label>
|
|
1629
1724
|
<div class="lab-slider-wrapper">
|
|
@@ -1642,15 +1737,15 @@
|
|
|
1642
1737
|
</div>
|
|
1643
1738
|
<div class="lab-field">
|
|
1644
1739
|
<label class="lab-field-label" for="lab-rhythm-gap"
|
|
1645
|
-
>
|
|
1740
|
+
>Block rhythm</label
|
|
1646
1741
|
>
|
|
1647
1742
|
<div class="lab-slider-wrapper">
|
|
1648
1743
|
<input
|
|
1649
|
-
id="lab-rhythm-gap"
|
|
1744
|
+
id="lab-rhythm-gap" title="--rhythm-block-scale: the space around tables, cards and figures, as a multiple of the text gap"
|
|
1650
1745
|
type="range"
|
|
1651
1746
|
class="lab-range"
|
|
1652
1747
|
value={rhythmGap}
|
|
1653
|
-
min="
|
|
1748
|
+
min="1"
|
|
1654
1749
|
max="4"
|
|
1655
1750
|
step="0.25"
|
|
1656
1751
|
oninput={onRhythmGapInput}
|
|
@@ -1798,6 +1893,14 @@
|
|
|
1798
1893
|
}
|
|
1799
1894
|
|
|
1800
1895
|
/* ═══════════ HANDLER TRIGGER ═══════════ */
|
|
1896
|
+
:global(:root[data-lab-handler="false"]) .lab-handler,
|
|
1897
|
+
:global(:root[data-lab-handler="false"]) .lab-handler-top,
|
|
1898
|
+
:global(:root[data-lab-no-handler]) .lab-handler,
|
|
1899
|
+
:global(:root[data-lab-no-handler]) .lab-handler-top {
|
|
1900
|
+
display: none !important;
|
|
1901
|
+
pointer-events: none !important;
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1801
1904
|
.lab-handler-top {
|
|
1802
1905
|
position: fixed;
|
|
1803
1906
|
z-index: 200;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stnd/lab",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.1",
|
|
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"
|