@stnd/lab 1.6.0 → 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 +6 -0
- package/Lab.svelte +162 -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
|
];
|
|
@@ -917,16 +991,20 @@
|
|
|
917
991
|
const oklchStr = formatOklch(L, C, h);
|
|
918
992
|
const formatted = pickerFormat === "hex" ? hex : oklchStr;
|
|
919
993
|
|
|
920
|
-
const token = role
|
|
994
|
+
const token = tokenOf(role);
|
|
921
995
|
|
|
922
996
|
if (role === "bg") {
|
|
923
997
|
bgOklch = { L, C, h };
|
|
924
998
|
seedBg = formatted;
|
|
925
999
|
seedBgHex = hex;
|
|
926
|
-
} else {
|
|
1000
|
+
} else if (role === "fg") {
|
|
927
1001
|
fgOklch = { L, C, h };
|
|
928
1002
|
seedFg = formatted;
|
|
929
1003
|
seedFgHex = hex;
|
|
1004
|
+
} else {
|
|
1005
|
+
accentOklch = { L, C, h };
|
|
1006
|
+
seedAccent = formatted;
|
|
1007
|
+
seedAccentHex = hex;
|
|
930
1008
|
}
|
|
931
1009
|
|
|
932
1010
|
writeVar(token, formatted);
|
|
@@ -934,7 +1012,7 @@
|
|
|
934
1012
|
|
|
935
1013
|
function onColorText(role, e) {
|
|
936
1014
|
const val = e.target.value;
|
|
937
|
-
const token = role
|
|
1015
|
+
const token = tokenOf(role);
|
|
938
1016
|
const target = getThemeTargetElement();
|
|
939
1017
|
const parsed = parseCssColor(val, target);
|
|
940
1018
|
|
|
@@ -942,17 +1020,21 @@
|
|
|
942
1020
|
seedBg = val;
|
|
943
1021
|
seedBgHex = parsed.hex;
|
|
944
1022
|
bgOklch = { L: parsed.L, C: parsed.C, h: parsed.h };
|
|
945
|
-
} else {
|
|
1023
|
+
} else if (role === "fg") {
|
|
946
1024
|
seedFg = val;
|
|
947
1025
|
seedFgHex = parsed.hex;
|
|
948
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 };
|
|
949
1031
|
}
|
|
950
1032
|
writeVar(token, val);
|
|
951
1033
|
}
|
|
952
1034
|
|
|
953
1035
|
function onSliderInput(role, channel, e) {
|
|
954
1036
|
const val = parseFloat(e.target.value);
|
|
955
|
-
const cur =
|
|
1037
|
+
const cur = { ...oklchOf(role) };
|
|
956
1038
|
cur[channel] = val;
|
|
957
1039
|
updateColorFromOklch(role, cur);
|
|
958
1040
|
}
|
|
@@ -966,7 +1048,7 @@
|
|
|
966
1048
|
const C = (x / rect.width) * MAX_CHROMA;
|
|
967
1049
|
const L = 1 - y / rect.height;
|
|
968
1050
|
|
|
969
|
-
const cur = activePicker
|
|
1051
|
+
const cur = oklchOf(activePicker);
|
|
970
1052
|
updateColorFromOklch(activePicker, { L, C, h: cur.h });
|
|
971
1053
|
}
|
|
972
1054
|
|
|
@@ -1026,7 +1108,7 @@
|
|
|
1026
1108
|
|
|
1027
1109
|
function onLineHeightInput(e) {
|
|
1028
1110
|
lineHeight = parseFloat(e.target.value);
|
|
1029
|
-
writeVar("--
|
|
1111
|
+
writeVar("--font-density", String(lineHeight));
|
|
1030
1112
|
}
|
|
1031
1113
|
|
|
1032
1114
|
function onRatioInput(e) {
|
|
@@ -1041,7 +1123,7 @@
|
|
|
1041
1123
|
|
|
1042
1124
|
function onRhythmGapInput(e) {
|
|
1043
1125
|
rhythmGap = parseFloat(e.target.value);
|
|
1044
|
-
writeVar("--rhythm-
|
|
1126
|
+
writeVar("--rhythm-block-scale", String(rhythmGap));
|
|
1045
1127
|
}
|
|
1046
1128
|
|
|
1047
1129
|
function onRadiusInput(e) {
|
|
@@ -1143,6 +1225,7 @@
|
|
|
1143
1225
|
<!-- ======================================================================
|
|
1144
1226
|
TRIGGER / HANDLER
|
|
1145
1227
|
====================================================================== -->
|
|
1228
|
+
{#if handlerVisible}
|
|
1146
1229
|
<div
|
|
1147
1230
|
class="lab-handler-top m-0"
|
|
1148
1231
|
role="button"
|
|
@@ -1170,6 +1253,7 @@
|
|
|
1170
1253
|
>
|
|
1171
1254
|
<div class="lab-handler-bar"></div>
|
|
1172
1255
|
</div>
|
|
1256
|
+
{/if}
|
|
1173
1257
|
|
|
1174
1258
|
<!-- ======================================================================
|
|
1175
1259
|
SIDE PANEL
|
|
@@ -1324,16 +1408,50 @@
|
|
|
1324
1408
|
</div>
|
|
1325
1409
|
</div>
|
|
1326
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
|
+
|
|
1327
1435
|
<!-- Custom OKLCH Color Picker Panel -->
|
|
1328
1436
|
{#if activePicker}
|
|
1329
|
-
{@const curOklch = activePicker
|
|
1330
|
-
{@const curHex =
|
|
1331
|
-
|
|
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}
|
|
1332
1450
|
<div class="lab-picker-panel" transition:slide>
|
|
1333
1451
|
<header class="lab-picker-header">
|
|
1334
1452
|
<div class="lab-picker-title">
|
|
1335
1453
|
<span class="lab-picker-role"
|
|
1336
|
-
>{activePicker
|
|
1454
|
+
>{ROLE_LABEL[activePicker]}</span
|
|
1337
1455
|
>
|
|
1338
1456
|
<span class="lab-picker-hex">{curHex}</span>
|
|
1339
1457
|
</div>
|
|
@@ -1547,20 +1665,6 @@
|
|
|
1547
1665
|
"--font-monospace",
|
|
1548
1666
|
fontMono,
|
|
1549
1667
|
)}
|
|
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
1668
|
<div class="lab-field">
|
|
1565
1669
|
<label class="lab-field-label" for="lab-font-size"
|
|
1566
1670
|
>Font Size</label
|
|
@@ -1581,16 +1685,16 @@
|
|
|
1581
1685
|
</div>
|
|
1582
1686
|
<div class="lab-field">
|
|
1583
1687
|
<label class="lab-field-label" for="lab-line-height"
|
|
1584
|
-
>
|
|
1688
|
+
>Font density</label
|
|
1585
1689
|
>
|
|
1586
1690
|
<div class="lab-slider-wrapper">
|
|
1587
1691
|
<input
|
|
1588
|
-
id="lab-line-height"
|
|
1692
|
+
id="lab-line-height" title="--font-density: sets --line-height, which stays between 1.35 and 1.75"
|
|
1589
1693
|
type="range"
|
|
1590
1694
|
class="lab-range"
|
|
1591
1695
|
value={lineHeight}
|
|
1592
|
-
min="1"
|
|
1593
|
-
max="
|
|
1696
|
+
min="1.35"
|
|
1697
|
+
max="1.75"
|
|
1594
1698
|
step="0.05"
|
|
1595
1699
|
oninput={onLineHeightInput}
|
|
1596
1700
|
/>
|
|
@@ -1615,23 +1719,6 @@
|
|
|
1615
1719
|
<span class="lab-readout">{ratio.toFixed(2)}</span>
|
|
1616
1720
|
</div>
|
|
1617
1721
|
</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
1722
|
<div class="lab-field">
|
|
1636
1723
|
<label class="lab-field-label" for="lab-gap-body">Body Gap</label>
|
|
1637
1724
|
<div class="lab-slider-wrapper">
|
|
@@ -1650,15 +1737,15 @@
|
|
|
1650
1737
|
</div>
|
|
1651
1738
|
<div class="lab-field">
|
|
1652
1739
|
<label class="lab-field-label" for="lab-rhythm-gap"
|
|
1653
|
-
>
|
|
1740
|
+
>Block rhythm</label
|
|
1654
1741
|
>
|
|
1655
1742
|
<div class="lab-slider-wrapper">
|
|
1656
1743
|
<input
|
|
1657
|
-
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"
|
|
1658
1745
|
type="range"
|
|
1659
1746
|
class="lab-range"
|
|
1660
1747
|
value={rhythmGap}
|
|
1661
|
-
min="
|
|
1748
|
+
min="1"
|
|
1662
1749
|
max="4"
|
|
1663
1750
|
step="0.25"
|
|
1664
1751
|
oninput={onRhythmGapInput}
|
|
@@ -1806,6 +1893,14 @@
|
|
|
1806
1893
|
}
|
|
1807
1894
|
|
|
1808
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
|
+
|
|
1809
1904
|
.lab-handler-top {
|
|
1810
1905
|
position: fixed;
|
|
1811
1906
|
z-index: 200;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stnd/lab",
|
|
3
|
-
"version": "1.6.
|
|
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"
|