@orbytes/astrolab 0.4.0-next.1 → 0.4.0-next.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +184 -84
  2. package/bin/pin-gallery.mjs +53 -19
  3. package/defaults.mjs +7 -20
  4. package/docs/PIN-CONTRACT.md +76 -10
  5. package/docs/PIN.md +93 -23
  6. package/index.d.ts +1 -7
  7. package/index.mjs +14 -81
  8. package/package.json +2 -2
  9. package/src/Home.astro +7 -8
  10. package/src/LabHead.astro +1 -1
  11. package/src/chrome/ActionsMenu.astro +97 -0
  12. package/src/chrome/ComponentCard.astro +9 -2
  13. package/src/chrome/Nav.astro +36 -10
  14. package/src/chrome/Panel.astro +17 -4
  15. package/src/chrome/Properties.astro +104 -0
  16. package/src/chrome/SectionsTree.astro +128 -0
  17. package/src/chrome/Shell.astro +20 -6
  18. package/src/chrome/StoryView.astro +103 -162
  19. package/src/chrome/Tree.astro +56 -53
  20. package/src/chrome/ViewportControls.astro +136 -61
  21. package/src/chrome/ViewportStage.astro +26 -3
  22. package/src/chrome/icons.ts +9 -0
  23. package/src/chrome/marks-client.ts +26 -53
  24. package/src/chrome/model.ts +14 -0
  25. package/src/chrome/navbar-client.ts +324 -0
  26. package/src/chrome/params-client.ts +434 -0
  27. package/src/chrome/pins-data.ts +42 -9
  28. package/src/chrome/shell-client.ts +99 -3
  29. package/src/chrome/trees.ts +112 -7
  30. package/src/chrome/viewport-client.ts +68 -242
  31. package/src/chrome/views/Assets.astro +21 -6
  32. package/src/chrome/views/Pages.astro +90 -54
  33. package/src/chrome/views/Placeholder.astro +3 -3
  34. package/src/chrome/views/Tasks.astro +12 -40
  35. package/src/core/LICENSE-astrobook +5 -0
  36. package/src/core/utils/kebab-case.ts +2 -2
  37. package/src/pin/board.mjs +25 -15
  38. package/src/pin/index.mjs +34 -20
  39. package/src/pin/tickets.mjs +6 -5
  40. package/src/pin/toolbar.js +81 -3
  41. package/src/shell/Browse.astro +35 -10
  42. package/src/shell/lab-index.ts +5 -4
  43. package/src/shell/lab-params.ts +113 -6
  44. package/src/shell/live-files.mjs +212 -10
  45. package/src/shell/marks.mjs +17 -41
  46. package/src/ui/components/preview-layout.astro +17 -0
  47. package/src/ui/components/theme-script.astro +4 -3
  48. package/src/ui/lab.css +2167 -566
  49. package/virtual.d.ts +0 -4
  50. package/bin/lab-cull.mjs +0 -401
package/src/ui/lab.css CHANGED
@@ -127,7 +127,7 @@
127
127
  --lab-space-3xl: 24px;
128
128
 
129
129
  --lab-l1-w: 280px;
130
- --lab-l1-rail: 56px;
130
+ --lab-l1-rail: 64px; /* the file's own rail variant; 68 with level 1's 4px padding */
131
131
  --lab-l2-w: 280px;
132
132
  --lab-navbar-h: 60px;
133
133
  --lab-ease: 160ms cubic-bezier(0.2, 0, 0, 1);
@@ -266,12 +266,77 @@ body.lab-shell {
266
266
 
267
267
  State lives on <html>, set before paint by the inline script in ../chrome/Shell.astro so a
268
268
  collapsed sidebar never flashes open:
269
- data-lab-l1="rail" level 1 is the 56px icon strip
269
+ data-lab-l1="rail" level 1 is the 64px icon rail
270
270
  data-lab-l2="closed" level 2 is gone; the stage gets its width
271
- data-lab-peek level 2 is closed but showing over the stage, for as long as the
272
- pointer stays on it — it overlays rather than pushes, because pushing
273
- would resize the preview and trip its breakpoints
271
+ data-lab-peek level 2 is closed but showing over the stage, below the navbar, for
272
+ as long as the pointer stays on it — it overlays rather than pushes,
273
+ because pushing would resize the preview and trip its breakpoints
274
+ data-lab-sort="name" the Sections tree is sorted by name rather than by slot
274
275
  --------------------------------------------------------------------------------------------- */
276
+
277
+ /* The sidebars' own tokens (Figma `Astrolab`, 2026-09-24): the floating surfaces' edge and
278
+ shadow, the rail's hover on a group it holds, and the Sections tree's slot badge. Dark values
279
+ are the file's own Dark mode where it defines them. */
280
+ :root {
281
+ --lab-border-secondary-alt: rgba(0, 0, 0, 0.1); /* Colors/Border/border-secondary_alt */
282
+ --lab-shadow-lg-3: 0 12px 16px -4px rgba(0, 0, 0, 0.08), 0 4px 6px -2px rgba(0, 0, 0, 0.03),
283
+ 0 2px 2px -1px rgba(0, 0, 0, 0.04); /* Shadows/shadow-lg */
284
+ --lab-shadow-xl: 0 20px 24px -4px rgba(0, 0, 0, 0.08), 0 8px 8px -4px rgba(0, 0, 0, 0.03),
285
+ 0 3px 3px -1.5px rgba(0, 0, 0, 0.04); /* Shadows/shadow-xl */
286
+ --lab-bg-active-hover: #f4ebff; /* Component colors/Utility/Brand/utility-brand-100 */
287
+ --lab-tip-bg: #0a0a0a; /* Colors/Background/bg-primary-solid */
288
+ --lab-tip-text: #ffffff;
289
+ --lab-tip-sub: #d4d4d4; /* tooltip-supporting-text */
290
+
291
+ --lab-slot-bg: #f0fdf4; /* utility-green-50 */
292
+ --lab-slot-border: #bbf7d0; /* utility-green-200 */
293
+ --lab-slot-text: #15803d; /* utility-green-700 */
294
+ --lab-slot-none-bg: #fafafa; /* utility-neutral-50 */
295
+ --lab-slot-none-border: #e5e5e5; /* utility-neutral-200 */
296
+ --lab-slot-none-text: #404040; /* utility-neutral-700 */
297
+ --lab-slot-here-bg: #f9f5ff; /* utility-brand-50 */
298
+ --lab-slot-here-border: #e9d7fe; /* utility-brand-200 */
299
+ --lab-slot-here-text: #7f56d9; /* utility-brand-600 */
300
+ }
301
+
302
+ :root.dark {
303
+ --lab-border-secondary-alt: rgba(255, 255, 255, 0.1);
304
+ --lab-shadow-lg-3: 0 16px 32px -8px rgba(0, 0, 0, 0.7);
305
+ --lab-shadow-xl: 0 20px 40px -8px rgba(0, 0, 0, 0.75);
306
+ --lab-bg-active-hover: #2c1c5f;
307
+ --lab-tip-bg: #fafafa;
308
+ --lab-tip-text: #0a0a0a;
309
+ --lab-tip-sub: #404040;
310
+
311
+ --lab-slot-bg: #052e16;
312
+ --lab-slot-border: #166534;
313
+ --lab-slot-text: #86efac;
314
+ --lab-slot-none-bg: #171717;
315
+ --lab-slot-none-border: #404040;
316
+ --lab-slot-none-text: #d4d4d4;
317
+ --lab-slot-here-bg: #2c1c5f;
318
+ --lab-slot-here-border: #53389e;
319
+ --lab-slot-here-text: #d6bbfb;
320
+ }
321
+
322
+ /* In the dark sidebars the file's Dark mode keeps "you are here" as a violet fill and turns the
323
+ brand-coloured text neutral (Figma `Lab / Story / dark`). Scoped to the two sidebars, so the
324
+ navbar and the views keep the lab-wide tokens. */
325
+ :root.dark .lab-l1,
326
+ :root.dark .lab-l2 {
327
+ --lab-bg-active: #2c1c5f; /* utility-brand-50, dark */
328
+ --lab-text-brand-primary: #fafafa; /* text-brand-primary (900), dark */
329
+ --lab-text-brand-secondary: #d4d4d4; /* text-brand-secondary (700), dark */
330
+ }
331
+
332
+ /* The coffee footer is lime primitives in the file, so it stays a light slab in the dark sidebar —
333
+ left that way on purpose while its link waits on a decision. */
334
+ :root.dark .lab-l2__footer {
335
+ --lab-coffee-bg: #ecfccb;
336
+ --lab-coffee-text: #3f6212;
337
+ --lab-coffee-icon: #65a30d;
338
+ }
339
+
275
340
  .lab-app {
276
341
  display: flex;
277
342
  height: 100vh;
@@ -310,7 +375,7 @@ body.lab-shell {
310
375
  display: flex;
311
376
  align-items: center;
312
377
  gap: var(--lab-space-md);
313
- padding: var(--lab-space-2xl) var(--lab-space-lg) var(--lab-space-2xl) var(--lab-space-2xl);
378
+ padding: var(--lab-space-xl) var(--lab-space-lg) var(--lab-space-xl) var(--lab-space-2xl);
314
379
  }
315
380
 
316
381
  .lab-brand {
@@ -376,7 +441,7 @@ body.lab-shell {
376
441
  }
377
442
 
378
443
  .lab-l1__nav .lab-divider {
379
- margin: 1px 0;
444
+ margin: 0;
380
445
  }
381
446
 
382
447
  /* One row of level 1. Figma `_Nav item base`: 8px padding, 12px between text and trailing icon,
@@ -478,7 +543,7 @@ body.lab-shell {
478
543
  .lab-nav-group__items {
479
544
  display: flex;
480
545
  flex-direction: column;
481
- padding-bottom: var(--lab-space-xs);
546
+ padding-bottom: 2px;
482
547
  }
483
548
 
484
549
  .lab-nav-group[data-collapsed] > .lab-nav-group__items {
@@ -510,6 +575,118 @@ body.lab-shell {
510
575
  display: block;
511
576
  }
512
577
 
578
+ :root[data-lab-l1="rail"] .lab-tip__title .lab-l1-btn__expand {
579
+ display: inline;
580
+ }
581
+
582
+ /* The header's two buttons — collapse, then the lab's theme — are 32px ghost buttons with a 16px
583
+ icon at text-quaternary, not the kit's paler fg-quaternary: an icon-only control needs 3:1. */
584
+ .lab-icon-btn.lab-l1__btn {
585
+ color: var(--lab-text-quaternary);
586
+ }
587
+
588
+ .lab-icon-btn.lab-l1__btn:hover {
589
+ color: var(--lab-text-secondary);
590
+ }
591
+
592
+ .lab-icon-btn.lab-l1__btn .lab-icon {
593
+ width: 16px;
594
+ height: 16px;
595
+ }
596
+
597
+ /* The moon is a toggle for the lab's dark mode. It stays a ghost button either way. */
598
+ .lab-icon-btn.lab-theme-btn[aria-pressed="true"] {
599
+ background-color: transparent;
600
+ color: var(--lab-text-quaternary);
601
+ }
602
+
603
+ .lab-icon-btn.lab-theme-btn:hover {
604
+ background-color: var(--lab-bg-hover);
605
+ color: var(--lab-text-secondary);
606
+ }
607
+
608
+ /* ---- tooltips (Figma `Tooltip`, with supporting text) ------------------------------------- */
609
+ /* A shortcut tooltip that shows at once on hover or keyboard focus — `title` takes a second and
610
+ cannot hold three lines. The tooltip is inside its button, which names itself with aria-label
611
+ and points at the tooltip with aria-describedby. */
612
+ .lab-tipped {
613
+ position: relative;
614
+ }
615
+
616
+ .lab-tip {
617
+ position: absolute;
618
+ z-index: 20;
619
+ display: flex;
620
+ flex-direction: column;
621
+ gap: 2px;
622
+ width: max-content;
623
+ max-width: 296px;
624
+ padding: var(--lab-space-lg);
625
+ border-radius: var(--lab-radius-md);
626
+ background-color: var(--lab-tip-bg);
627
+ box-shadow: var(--lab-shadow-lg-3);
628
+ color: var(--lab-tip-sub);
629
+ font-size: 12px;
630
+ line-height: 18px;
631
+ font-weight: 500;
632
+ text-align: left;
633
+ white-space: nowrap;
634
+ pointer-events: none;
635
+ visibility: hidden;
636
+ opacity: 0;
637
+ transition: opacity 120ms ease, visibility 0s linear 120ms;
638
+ }
639
+
640
+ .lab-tip__title {
641
+ color: var(--lab-tip-text);
642
+ font-weight: 600;
643
+ }
644
+
645
+ /* The arrow: a 6px notch on the side facing the button. */
646
+ .lab-tip::before {
647
+ content: "";
648
+ position: absolute;
649
+ width: 10px;
650
+ height: 10px;
651
+ background-color: inherit;
652
+ transform: rotate(45deg);
653
+ border-radius: 1px;
654
+ }
655
+
656
+ .lab-tipped:hover > .lab-tip,
657
+ .lab-tipped:focus-visible > .lab-tip {
658
+ visibility: visible;
659
+ opacity: 1;
660
+ transition-delay: 300ms, 300ms;
661
+ transition-duration: 120ms, 0s;
662
+ }
663
+
664
+ /* Below its button, arrow up (level 1 open). */
665
+ .lab-tip--l1 {
666
+ top: calc(100% + 8px);
667
+ left: 50%;
668
+ transform: translateX(-50%);
669
+ }
670
+
671
+ .lab-tip--l1::before {
672
+ top: -4px;
673
+ left: calc(50% - 5px);
674
+ }
675
+
676
+ /* Right of its button, arrow left (the rail's expand button, the navbar's panel button). */
677
+ :root[data-lab-l1="rail"] .lab-tip--l1,
678
+ .lab-tip--panel {
679
+ top: 0;
680
+ left: calc(100% + 10px);
681
+ transform: none;
682
+ }
683
+
684
+ :root[data-lab-l1="rail"] .lab-tip--l1::before,
685
+ .lab-tip--panel::before {
686
+ top: 11px;
687
+ left: -4px;
688
+ }
689
+
513
690
  /* The navbar's own "open the panel" button exists only while the panel is closed. */
514
691
  .lab-navbar .lab-navbar__panel-btn {
515
692
  display: none;
@@ -555,51 +732,73 @@ body.lab-shell {
555
732
  height: 18px;
556
733
  }
557
734
 
558
- .lab-theme-btn .lab-icon--moon,
559
- :root.dark .lab-theme-btn .lab-icon--sun {
560
- display: none;
561
- }
562
-
563
- :root.dark .lab-theme-btn .lab-icon--moon {
564
- display: block;
565
- }
566
-
567
735
  /* ---- level 1 as a rail -------------------------------------------------------------------- */
736
+ /* Figma `Lab / Rail + flyout`: a 64px card (68 with level 1's padding), bordered all round and
737
+ lifted, with the logomark, the theme moon and the expand button stacked in its header. */
568
738
  :root[data-lab-l1="rail"] .lab-l1__card {
569
739
  width: var(--lab-l1-rail);
740
+ border: 1px solid var(--lab-border-secondary);
741
+ box-shadow: var(--lab-shadow-xs);
570
742
  }
571
743
 
572
744
  :root[data-lab-l1="rail"] .lab-l1__header {
573
745
  flex-direction: column;
574
- padding: var(--lab-space-xl) 0 var(--lab-space-lg);
746
+ gap: var(--lab-space-lg);
747
+ padding: var(--lab-space-2xl) 0;
575
748
  }
576
749
 
577
750
  :root[data-lab-l1="rail"] .lab-brand {
578
751
  flex: none;
579
752
  }
580
753
 
754
+ /* Under the logomark the moon comes first, then the expand button. */
755
+ :root[data-lab-l1="rail"] .lab-l1__header .lab-theme-btn {
756
+ order: 1;
757
+ }
758
+
759
+ :root[data-lab-l1="rail"] .lab-l1__header [data-lab-l1-btn] {
760
+ order: 2;
761
+ }
762
+
581
763
  :root[data-lab-l1="rail"] .lab-l1 .lab-brand__name,
582
764
  :root[data-lab-l1="rail"] .lab-l1 .lab-nav-item__label,
583
765
  :root[data-lab-l1="rail"] .lab-l1 .lab-nav-item__count,
584
- :root[data-lab-l1="rail"] .lab-l1 .lab-nav-group__chevron,
585
- :root[data-lab-l1="rail"] .lab-l1__header .lab-theme-btn {
766
+ :root[data-lab-l1="rail"] .lab-l1 .lab-nav-group__chevron {
586
767
  display: none;
587
768
  }
588
769
 
589
770
  :root[data-lab-l1="rail"] .lab-l1__nav,
590
771
  :root[data-lab-l1="rail"] .lab-l1__footer {
591
- padding-left: var(--lab-space-md);
592
- padding-right: var(--lab-space-md);
772
+ align-items: center;
773
+ padding-left: var(--lab-space-lg);
774
+ padding-right: var(--lab-space-lg);
593
775
  overflow: visible;
594
776
  }
595
777
 
778
+ :root[data-lab-l1="rail"] .lab-l1__footer {
779
+ padding-bottom: var(--lab-space-xl);
780
+ }
781
+
782
+ :root[data-lab-l1="rail"] .lab-l1__nav,
783
+ :root[data-lab-l1="rail"] .lab-l1__footer {
784
+ gap: 2px;
785
+ }
786
+
596
787
  :root[data-lab-l1="rail"] .lab-l1 .lab-nav-item {
597
788
  justify-content: center;
598
- padding: var(--lab-space-md) 0;
789
+ width: 36px;
790
+ margin: 0;
791
+ padding: var(--lab-space-md);
792
+ }
793
+
794
+ /* The rail has no dividers between its groups — the icons carry the grouping. */
795
+ :root[data-lab-l1="rail"] .lab-l1__nav > .lab-divider {
796
+ display: none;
599
797
  }
600
798
 
601
799
  /* In the rail a group is its icon; its items open as a flyout beside it, on hover or on focus,
602
- with no script. The flyout is the same list the expanded nav shows, repositioned. */
800
+ with no script. The flyout is the same list the expanded nav shows, repositioned, under the
801
+ group's name so it reads as the group rather than as a context menu for the icon. */
603
802
  :root[data-lab-l1="rail"] .lab-nav-group {
604
803
  position: relative;
605
804
  }
@@ -609,33 +808,53 @@ body.lab-shell {
609
808
  background-color: var(--lab-bg-active);
610
809
  }
611
810
 
811
+ /* ...and still answers the pointer: one step deeper on hover, since it is already tinted. */
812
+ :root[data-lab-l1="rail"] .lab-l1__nav .lab-nav-group > .lab-nav-item[aria-current="true"]:hover {
813
+ background-color: var(--lab-bg-active-hover);
814
+ }
815
+
612
816
  :root[data-lab-l1="rail"] .lab-l1__nav .lab-nav-group > .lab-nav-item[aria-current="true"] > .lab-icon {
613
817
  color: var(--lab-fg-brand);
614
818
  }
615
819
 
820
+ .lab-nav-group__flyout-label {
821
+ display: none;
822
+ margin: 0;
823
+ padding: var(--lab-space-sm) var(--lab-space-md) 2px;
824
+ color: var(--lab-text-quaternary);
825
+ font-size: 12px;
826
+ line-height: 18px;
827
+ font-weight: 600;
828
+ }
829
+
830
+ :root[data-lab-l1="rail"] .lab-nav-group__flyout-label {
831
+ display: block;
832
+ }
833
+
834
+ /* 8px right of the rail's edge: the group is 36px wide, centred in the 62px inside the card. */
616
835
  :root[data-lab-l1="rail"] .lab-nav-group > .lab-nav-group__items,
617
836
  :root[data-lab-l1="rail"] .lab-nav-group[data-collapsed] > .lab-nav-group__items {
618
837
  display: none;
619
838
  position: absolute;
620
839
  top: -5px;
621
- left: calc(100% + 10px);
840
+ left: calc(100% + 13px + 1px + 8px);
622
841
  z-index: 10;
623
- min-width: 200px;
842
+ min-width: 216px;
624
843
  padding: var(--lab-space-xs);
625
844
  background-color: var(--lab-bg-primary);
626
- border: 1px solid var(--lab-border-secondary);
845
+ border: 1px solid var(--lab-border-secondary-alt);
627
846
  border-radius: var(--lab-radius-md);
628
- box-shadow: var(--lab-shadow-lg);
847
+ box-shadow: var(--lab-shadow-lg-3);
629
848
  }
630
849
 
631
- /* A bridge across the 10px gap, so the pointer can travel from the icon to the flyout. */
850
+ /* A bridge across the gap, so the pointer can travel from the icon to the flyout. */
632
851
  :root[data-lab-l1="rail"] .lab-nav-group__items::before {
633
852
  content: "";
634
853
  position: absolute;
635
854
  top: 0;
636
855
  bottom: 0;
637
- left: -12px;
638
- width: 12px;
856
+ left: -24px;
857
+ width: 24px;
639
858
  }
640
859
 
641
860
  :root[data-lab-l1="rail"] .lab-nav-group:hover > .lab-nav-group__items,
@@ -645,6 +864,8 @@ body.lab-shell {
645
864
 
646
865
  :root[data-lab-l1="rail"] .lab-l1 .lab-nav-group__items .lab-nav-item {
647
866
  justify-content: flex-start;
867
+ width: 100%;
868
+ margin: 1px 0;
648
869
  padding: var(--lab-space-md) var(--lab-space-md);
649
870
  }
650
871
 
@@ -688,18 +909,21 @@ body.lab-shell {
688
909
  border-right: 1px solid var(--lab-border-secondary);
689
910
  }
690
911
 
912
+ /* The tighter padding, 12 / 8, from the design pass (2026-09-24): the header's contents sit 12px
913
+ from the panel's edges, the tree 8px (plus its own 4). */
691
914
  .lab-l2__header {
692
915
  flex: none;
693
916
  display: flex;
694
917
  flex-direction: column;
695
918
  gap: var(--lab-space-lg);
696
- padding: var(--lab-space-md) var(--lab-space-2xl);
919
+ padding: var(--lab-space-md) var(--lab-space-lg);
697
920
  }
698
921
 
699
922
  .lab-l2__crumbs-row {
700
923
  display: flex;
701
924
  align-items: center;
702
925
  gap: var(--lab-space-md);
926
+ min-height: 20px;
703
927
  padding-top: var(--lab-space-xl);
704
928
  }
705
929
 
@@ -707,7 +931,7 @@ body.lab-shell {
707
931
  flex: 1;
708
932
  min-height: 0;
709
933
  overflow-y: auto;
710
- padding: 0 var(--lab-space-xl) var(--lab-space-xl);
934
+ padding: 0 var(--lab-space-md) var(--lab-space-xl);
711
935
  scrollbar-width: thin;
712
936
  }
713
937
 
@@ -735,14 +959,18 @@ body.lab-shell {
735
959
  display: none;
736
960
  }
737
961
 
738
- /* Peek: the closed panel, shown over the stage for as long as the pointer is on it. */
962
+ /* Peek: the closed panel, shown over the STAGE for as long as the pointer is on it — below the
963
+ navbar, never over it, so the navbar's own panel button stays in reach (Figma `Lab / Peek`).
964
+ --lab-peek-top is the navbar's measured height (../chrome/shell-client.ts); it floats over a
965
+ stage of the same grey, so it keeps its border and lifts with shadow-xl. */
739
966
  :root[data-lab-l2="closed"][data-lab-peek] .lab-l2 {
740
967
  display: flex;
741
968
  position: absolute;
742
- top: 0;
969
+ top: var(--lab-peek-top, var(--lab-navbar-h));
743
970
  bottom: 0;
744
971
  left: calc(var(--lab-l1-w) + var(--lab-space-xs));
745
- box-shadow: var(--lab-shadow-lg);
972
+ height: auto;
973
+ box-shadow: var(--lab-shadow-xl);
746
974
  animation: lab-peek-in var(--lab-ease);
747
975
  }
748
976
 
@@ -757,11 +985,12 @@ body.lab-shell {
757
985
  }
758
986
  }
759
987
 
760
- /* The strip along the stage's left edge that opens the peek. Only exists while level 2 is closed. */
988
+ /* The strip along the stage's left edge that opens the peek. Only exists while level 2 is closed,
989
+ and like the peek it starts under the navbar. */
761
990
  .lab-peek-zone {
762
991
  display: none;
763
992
  position: absolute;
764
- top: 0;
993
+ top: var(--lab-peek-top, var(--lab-navbar-h));
765
994
  bottom: 0;
766
995
  left: 0;
767
996
  z-index: 3;
@@ -789,6 +1018,8 @@ body.lab-shell {
789
1018
  gap: var(--lab-space-md);
790
1019
  min-width: 0;
791
1020
  color: var(--lab-text-quaternary);
1021
+ font-size: 12px;
1022
+ line-height: 18px;
792
1023
  font-weight: 600;
793
1024
  white-space: nowrap;
794
1025
  }
@@ -828,8 +1059,24 @@ body.lab-shell {
828
1059
  --lab-chevron-right: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.33' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 12l4-4-4-4'/%3E%3C/svg%3E");
829
1060
  }
830
1061
 
1062
+ /* Level 2's own hide / keep-open button. It overhangs the 20px crumbs row rather than growing it,
1063
+ so the header keeps the design's rhythm. */
831
1064
  .lab-l2__close {
832
- margin-left: auto;
1065
+ margin: -6px -6px -6px auto;
1066
+ }
1067
+
1068
+ .lab-l2__close .lab-icon {
1069
+ width: 16px;
1070
+ height: 16px;
1071
+ }
1072
+
1073
+ .lab-l2__close-dock,
1074
+ :root[data-lab-peek] .lab-l2__close-hide {
1075
+ display: none;
1076
+ }
1077
+
1078
+ :root[data-lab-peek] .lab-l2__close-dock {
1079
+ display: block;
833
1080
  }
834
1081
 
835
1082
  /* ---- inputs (Figma `Input`) ---------------------------------------------------------------- */
@@ -901,12 +1148,17 @@ body.lab-shell {
901
1148
  height: 16px;
902
1149
  }
903
1150
 
904
- /* ---- menus (popover) ----------------------------------------------------------------------- */
1151
+ /* ---- menus (popover) -----------------------------------------------------------------------
1152
+ Figma `Astrolab` › Menu: every navbar dropdown, the sidebars' filter and sort menus, and the one
1153
+ hover panel (Properties). A row is 6px in from the menu's edge with 8px of padding inside, a 16px
1154
+ leading slot (`__mark`: an icon, a tick or a checkbox), the label (`__text`), and a trailing hint
1155
+ or chevron. Section labels are text-tertiary, not violet, because violet means "you are here";
1156
+ row hover is bg-hover, because the kit's #fafafa is invisible on a white menu. */
905
1157
  .lab-menu {
906
1158
  margin: 0;
907
1159
  min-width: 220px;
908
1160
  max-width: 320px;
909
- padding: var(--lab-space-xs);
1161
+ padding: var(--lab-space-xs) 0;
910
1162
  border: 1px solid var(--lab-border-secondary);
911
1163
  border-radius: var(--lab-radius-md);
912
1164
  background-color: var(--lab-bg-primary);
@@ -920,8 +1172,8 @@ body.lab-shell {
920
1172
 
921
1173
  .lab-menu__label {
922
1174
  margin: 0;
923
- padding: var(--lab-space-sm) var(--lab-space-md) var(--lab-space-xs);
924
- color: var(--lab-text-quaternary);
1175
+ padding: var(--lab-space-sm) var(--lab-space-xl) 2px;
1176
+ color: var(--lab-text-tertiary);
925
1177
  font-size: 12px;
926
1178
  line-height: 18px;
927
1179
  font-weight: 600;
@@ -931,14 +1183,15 @@ body.lab-shell {
931
1183
  display: flex;
932
1184
  align-items: center;
933
1185
  gap: var(--lab-space-md);
934
- width: 100%;
935
- padding: var(--lab-space-sm) var(--lab-space-md);
1186
+ width: calc(100% - 12px);
1187
+ margin: 1px 6px;
1188
+ padding: var(--lab-space-md) var(--lab-space-sm) var(--lab-space-md) 10px;
936
1189
  border: 0;
937
1190
  border-radius: var(--lab-radius-sm);
938
1191
  background: transparent;
939
1192
  color: var(--lab-text-secondary);
940
1193
  font: inherit;
941
- font-weight: 500;
1194
+ font-weight: 600;
942
1195
  text-align: left;
943
1196
  text-decoration: none;
944
1197
  cursor: pointer;
@@ -949,6 +1202,15 @@ body.lab-shell {
949
1202
  color: var(--lab-text-primary);
950
1203
  }
951
1204
 
1205
+ .lab-menu__item[aria-disabled="true"] {
1206
+ cursor: default;
1207
+ opacity: 0.6;
1208
+ }
1209
+
1210
+ .lab-menu__item[aria-disabled="true"]:hover {
1211
+ background-color: transparent;
1212
+ }
1213
+
952
1214
  .lab-menu__item > .lab-icon {
953
1215
  width: 16px;
954
1216
  height: 16px;
@@ -959,6 +1221,7 @@ body.lab-shell {
959
1221
  color: var(--lab-text-primary);
960
1222
  }
961
1223
 
1224
+ /* A tick on the right — the sidebars' filter menus. */
962
1225
  .lab-menu__item .lab-menu__tick {
963
1226
  margin-left: auto;
964
1227
  color: var(--lab-fg-brand);
@@ -969,10 +1232,56 @@ body.lab-shell {
969
1232
  visibility: visible;
970
1233
  }
971
1234
 
1235
+ /* The 16px leading slot. A tick in it marks the current choice (menuitemradio); a checkbox box in
1236
+ it marks a switch (menuitemcheckbox); anything else is the row's icon. */
1237
+ .lab-menu__mark {
1238
+ flex: none;
1239
+ display: grid;
1240
+ place-items: center;
1241
+ width: 16px;
1242
+ height: 16px;
1243
+ color: var(--lab-text-quaternary);
1244
+ }
1245
+
1246
+ .lab-menu__mark > .lab-icon {
1247
+ width: 16px;
1248
+ height: 16px;
1249
+ }
1250
+
1251
+ .lab-menu__mark .lab-menu__tick {
1252
+ margin: 0;
1253
+ }
1254
+
1255
+ .lab-menu__text {
1256
+ flex: 1;
1257
+ min-width: 0;
1258
+ overflow: hidden;
1259
+ text-overflow: ellipsis;
1260
+ white-space: nowrap;
1261
+ }
1262
+
1263
+ .lab-menu__sub {
1264
+ color: var(--lab-text-quaternary);
1265
+ font-weight: 500;
1266
+ }
1267
+
972
1268
  .lab-menu__hint {
1269
+ flex: none;
973
1270
  margin-left: auto;
1271
+ padding-right: var(--lab-space-xs);
974
1272
  color: var(--lab-text-quaternary);
1273
+ font-family: inherit;
975
1274
  font-size: 12px;
1275
+ line-height: 18px;
1276
+ font-weight: 500;
1277
+ font-variant-numeric: tabular-nums;
1278
+ }
1279
+
1280
+ .lab-menu__trail {
1281
+ flex: none;
1282
+ width: 16px;
1283
+ height: 16px;
1284
+ color: var(--lab-text-quaternary);
976
1285
  }
977
1286
 
978
1287
  .lab-menu hr {
@@ -982,77 +1291,230 @@ body.lab-shell {
982
1291
  background-color: var(--lab-border-secondary);
983
1292
  }
984
1293
 
985
- /* ---------------------------------------------------------------------------------------------
986
- The tree (Figma `Tree view`, sm): 32px rows 4px apart, a 16px connector column per level,
987
- connector lines in neutral-300 with a curved end on the last child.
988
- --------------------------------------------------------------------------------------------- */
989
- .lab-tree,
990
- .lab-tree__group {
991
- display: flex;
992
- flex-direction: column;
993
- gap: var(--lab-space-xs);
1294
+ .lab-menu__note {
994
1295
  margin: 0;
995
- padding: 0;
996
- list-style: none;
1296
+ padding: var(--lab-space-md) var(--lab-space-xl);
1297
+ color: var(--lab-text-quaternary);
1298
+ font-size: 12px;
1299
+ line-height: 18px;
997
1300
  }
998
1301
 
999
- .lab-tree {
1000
- padding: var(--lab-space-xs);
1302
+ /* The checkbox a switch row draws in its slot (Figma `_Checkbox base`, sm), for rows that are
1303
+ buttons with role="menuitemcheckbox" rather than real inputs. */
1304
+ .lab-menu__box {
1305
+ width: 16px;
1306
+ height: 16px;
1307
+ border: 1px solid var(--lab-border-primary);
1308
+ border-radius: var(--lab-radius-xs);
1309
+ background-color: var(--lab-bg-primary);
1001
1310
  }
1002
1311
 
1003
- .lab-tree__node {
1004
- position: relative;
1005
- --lab-indent: calc(var(--lab-d, 0) * 24px);
1312
+ .lab-menu__item[aria-checked="true"] .lab-menu__box {
1313
+ border-color: var(--lab-brand-solid);
1314
+ background: var(--lab-brand-solid) no-repeat center / 12px;
1315
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
1006
1316
  }
1007
1317
 
1008
- .lab-tree__node > details > summary {
1009
- list-style: none;
1318
+ /* The real checkboxes in the Properties panel — the responsive marks. */
1319
+ .lab-checkbox {
1320
+ width: 16px;
1321
+ height: 16px;
1322
+ margin: 0;
1323
+ accent-color: var(--lab-brand-solid);
1324
+ cursor: pointer;
1010
1325
  }
1011
1326
 
1012
- .lab-tree__node > details > summary::-webkit-details-marker {
1013
- display: none;
1327
+ .lab-menu__item:has(.lab-checkbox:disabled) {
1328
+ cursor: default;
1329
+ opacity: 0.7;
1014
1330
  }
1015
1331
 
1016
- .lab-tree__row {
1017
- position: relative;
1018
- display: flex;
1019
- align-items: center;
1020
- gap: var(--lab-space-sm);
1021
- min-height: 32px;
1022
- padding: var(--lab-space-sm) var(--lab-space-md);
1023
- padding-left: calc(var(--lab-space-md) + var(--lab-indent));
1024
- border-radius: var(--lab-radius-sm);
1025
- color: var(--lab-text-tertiary);
1026
- font-weight: 600;
1027
- text-decoration: none;
1028
- cursor: pointer;
1029
- user-select: none;
1030
- transition: background-color var(--lab-ease), color var(--lab-ease);
1332
+ .lab-menu--variants {
1333
+ width: 256px;
1031
1334
  }
1032
1335
 
1033
- .lab-tree__row:hover {
1034
- background-color: var(--lab-bg-hover);
1035
- color: var(--lab-text-primary);
1336
+ .lab-menu--presets {
1337
+ width: 304px;
1036
1338
  }
1037
1339
 
1038
- .lab-tree__row[aria-current="page"] {
1039
- background-color: var(--lab-bg-active);
1040
- color: var(--lab-text-brand-primary);
1340
+ /* The design's 312px, grown to fit "Copy link to this exact size" and its size beside it. */
1341
+ .lab-menu--actions {
1342
+ width: max-content;
1343
+ min-width: 312px;
1344
+ max-width: 360px;
1041
1345
  }
1042
1346
 
1043
- .lab-tree__row[aria-current="page"] > .lab-icon {
1044
- color: var(--lab-fg-brand);
1347
+ .lab-menu--components {
1348
+ width: 288px;
1045
1349
  }
1046
1350
 
1047
- .lab-tree__row[aria-disabled="true"] {
1048
- color: var(--lab-text-quaternary);
1049
- cursor: default;
1351
+ /* The zoom row in the presets menu (Figma `Story tab`, Type=Tab): a segmented choice. It fills the
1352
+ menu between its 12px margins and splits that into seven equal segments. Sized by its labels
1353
+ instead, it came to 283px, which with both margins is 5px more than the 304px menu holds, so the
1354
+ menu scrolled sideways. */
1355
+ .lab-zoom {
1356
+ display: flex;
1357
+ gap: 2px;
1358
+ margin: 0 var(--lab-space-lg) var(--lab-space-md);
1359
+ padding: 2px;
1360
+ border: 1px solid var(--lab-border-secondary);
1361
+ border-radius: var(--lab-radius-md);
1362
+ background-color: var(--lab-bg-secondary);
1050
1363
  }
1051
1364
 
1052
- .lab-tree__row > .lab-icon {
1053
- width: 16px;
1054
- height: 16px;
1055
- color: var(--lab-fg-quaternary);
1365
+ .lab-zoom__btn {
1366
+ flex: 1 1 0;
1367
+ min-width: 0;
1368
+ display: inline-flex;
1369
+ align-items: center;
1370
+ justify-content: center;
1371
+ height: 32px;
1372
+ padding: var(--lab-space-sm) 0;
1373
+ border: 1px solid transparent;
1374
+ border-radius: var(--lab-radius-sm);
1375
+ background: transparent;
1376
+ color: var(--lab-text-quaternary);
1377
+ font: inherit;
1378
+ font-weight: 600;
1379
+ font-variant-numeric: tabular-nums;
1380
+ cursor: pointer;
1381
+ }
1382
+
1383
+ .lab-zoom__btn:hover {
1384
+ color: var(--lab-text-secondary);
1385
+ }
1386
+
1387
+ .lab-zoom__btn[aria-checked="true"] {
1388
+ border-color: var(--lab-border-primary);
1389
+ background-color: var(--lab-bg-primary);
1390
+ box-shadow: var(--lab-shadow-xs);
1391
+ color: var(--lab-text-secondary);
1392
+ }
1393
+
1394
+ /* ---- the Properties panel (Figma `Menu`, Type=Properties) -----------------------------------
1395
+ Not a menu: the eye's hover panel, a manual popover placed by ../chrome/navbar-client.ts. */
1396
+ .lab-props {
1397
+ width: 288px;
1398
+ }
1399
+
1400
+ .lab-props__facts {
1401
+ display: flex;
1402
+ flex-direction: column;
1403
+ gap: 2px;
1404
+ padding: var(--lab-space-xs) var(--lab-space-xl) var(--lab-space-md);
1405
+ }
1406
+
1407
+ .lab-props__fact {
1408
+ display: flex;
1409
+ align-items: center;
1410
+ gap: var(--lab-space-md);
1411
+ margin: 0;
1412
+ color: var(--lab-text-tertiary);
1413
+ }
1414
+
1415
+ .lab-props__dot {
1416
+ flex: none;
1417
+ width: 16px;
1418
+ height: 16px;
1419
+ background: radial-gradient(circle, var(--lab-fg-quaternary) 3.5px, transparent 4px);
1420
+ }
1421
+
1422
+ .lab-props__dot--live {
1423
+ background: radial-gradient(circle, var(--lab-success-dot) 3.5px, transparent 4px);
1424
+ }
1425
+
1426
+ .lab-props__pin {
1427
+ color: var(--lab-pin-open);
1428
+ }
1429
+
1430
+ .lab-props__source {
1431
+ margin: 0;
1432
+ padding: var(--lab-space-xs) var(--lab-space-xl) var(--lab-space-md);
1433
+ color: var(--lab-text-tertiary);
1434
+ overflow-wrap: anywhere;
1435
+ }
1436
+
1437
+ .lab-props__note {
1438
+ margin: 0;
1439
+ padding: var(--lab-space-md) var(--lab-space-xl);
1440
+ color: var(--lab-text-quaternary);
1441
+ font-size: 12px;
1442
+ line-height: 18px;
1443
+ }
1444
+
1445
+ /* ---------------------------------------------------------------------------------------------
1446
+ The tree (Figma `Tree view`, sm, as condensed on 2026-09-24): no icons, 12px text, 30px rows
1447
+ with no gap between them, a 16px connector column per level, connector lines in neutral-300
1448
+ with a curved end on the last child.
1449
+ --------------------------------------------------------------------------------------------- */
1450
+ .lab-tree,
1451
+ .lab-tree__group {
1452
+ display: flex;
1453
+ flex-direction: column;
1454
+ margin: 0;
1455
+ padding: 0;
1456
+ list-style: none;
1457
+ }
1458
+
1459
+ .lab-tree {
1460
+ padding: var(--lab-space-xs);
1461
+ }
1462
+
1463
+ .lab-tree__node {
1464
+ position: relative;
1465
+ --lab-indent: calc(var(--lab-d, 0) * 24px);
1466
+ }
1467
+
1468
+ .lab-tree__node > details > summary {
1469
+ list-style: none;
1470
+ }
1471
+
1472
+ .lab-tree__node > details > summary::-webkit-details-marker {
1473
+ display: none;
1474
+ }
1475
+
1476
+ .lab-tree__row {
1477
+ position: relative;
1478
+ display: flex;
1479
+ align-items: center;
1480
+ gap: var(--lab-space-sm);
1481
+ min-height: 30px;
1482
+ padding: var(--lab-space-sm) var(--lab-space-md);
1483
+ padding-left: calc(var(--lab-space-md) + var(--lab-indent));
1484
+ border-radius: var(--lab-radius-sm);
1485
+ color: var(--lab-text-tertiary);
1486
+ font-size: 12px;
1487
+ line-height: 18px;
1488
+ font-weight: 600;
1489
+ text-decoration: none;
1490
+ cursor: pointer;
1491
+ user-select: none;
1492
+ transition: background-color var(--lab-ease), color var(--lab-ease);
1493
+ }
1494
+
1495
+ .lab-tree__row:hover {
1496
+ background-color: var(--lab-bg-hover);
1497
+ color: var(--lab-text-primary);
1498
+ }
1499
+
1500
+ .lab-tree__row[aria-current="page"] {
1501
+ background-color: var(--lab-bg-active);
1502
+ color: var(--lab-text-brand-primary);
1503
+ }
1504
+
1505
+ .lab-tree__row[aria-current="page"] > .lab-icon {
1506
+ color: var(--lab-fg-brand);
1507
+ }
1508
+
1509
+ .lab-tree__row[aria-disabled="true"] {
1510
+ color: var(--lab-text-quaternary);
1511
+ cursor: default;
1512
+ }
1513
+
1514
+ .lab-tree__row > .lab-icon {
1515
+ width: 16px;
1516
+ height: 16px;
1517
+ color: var(--lab-fg-quaternary);
1056
1518
  }
1057
1519
 
1058
1520
  .lab-tree__label {
@@ -1124,7 +1586,7 @@ details:not([open]) > summary .lab-tree__chevron {
1124
1586
  .lab-tree__group > .lab-tree__node::before {
1125
1587
  content: "";
1126
1588
  position: absolute;
1127
- top: -4px;
1589
+ top: 0;
1128
1590
  bottom: 0;
1129
1591
  left: calc(var(--lab-space-md) + var(--lab-indent) - 24px + 7.5px);
1130
1592
  width: 1px;
@@ -1134,7 +1596,7 @@ details:not([open]) > summary .lab-tree__chevron {
1134
1596
 
1135
1597
  .lab-tree__group > .lab-tree__node:last-child::before {
1136
1598
  bottom: auto;
1137
- height: 20px;
1599
+ height: 15px;
1138
1600
  width: 9px;
1139
1601
  background: none;
1140
1602
  border-left: 1px solid var(--lab-fg-muted);
@@ -1157,6 +1619,171 @@ details:not([open]) > summary .lab-tree__chevron {
1157
1619
  color: var(--lab-text-brand-secondary);
1158
1620
  }
1159
1621
 
1622
+ /* ---- the Sections tree (Figma `Sections tree`, ../chrome/SectionsTree.astro) ---------------- */
1623
+ /* The count and the Sort by button, above the rows. */
1624
+ .lab-stree-head {
1625
+ display: flex;
1626
+ align-items: center;
1627
+ justify-content: space-between;
1628
+ padding: var(--lab-space-xs) var(--lab-space-xs) var(--lab-space-xs) calc(var(--lab-space-xs) + var(--lab-space-md));
1629
+ }
1630
+
1631
+ .lab-stree-head__count {
1632
+ color: var(--lab-text-quaternary);
1633
+ font-size: 12px;
1634
+ line-height: 18px;
1635
+ font-weight: 500;
1636
+ }
1637
+
1638
+ .lab-stree-head__sort {
1639
+ display: inline-flex;
1640
+ align-items: center;
1641
+ gap: var(--lab-space-xs);
1642
+ height: 28px;
1643
+ padding: 0 var(--lab-space-sm) 0 var(--lab-space-md);
1644
+ border: 0;
1645
+ border-radius: var(--lab-radius-sm);
1646
+ background: transparent;
1647
+ color: var(--lab-text-quaternary);
1648
+ font: inherit;
1649
+ font-size: 12px;
1650
+ line-height: 18px;
1651
+ font-weight: 500;
1652
+ cursor: pointer;
1653
+ }
1654
+
1655
+ .lab-stree-head__sort b {
1656
+ color: var(--lab-text-secondary);
1657
+ font-weight: 600;
1658
+ }
1659
+
1660
+ .lab-stree-head__sort .lab-icon {
1661
+ width: 12px;
1662
+ height: 12px;
1663
+ transition: transform var(--lab-ease);
1664
+ }
1665
+
1666
+ .lab-stree-head__sort:hover,
1667
+ .lab-stree-head:has(+ .lab-menu:popover-open) .lab-stree-head__sort {
1668
+ background-color: var(--lab-bg-sunken);
1669
+ }
1670
+
1671
+ .lab-stree-head:has(+ .lab-menu:popover-open) .lab-stree-head__sort .lab-icon {
1672
+ transform: rotate(180deg);
1673
+ }
1674
+
1675
+ .lab-stree-head__by-name,
1676
+ :root[data-lab-sort="name"] .lab-stree-head__by-slot {
1677
+ display: none;
1678
+ }
1679
+
1680
+ :root[data-lab-sort="name"] .lab-stree-head__by-name {
1681
+ display: inline;
1682
+ }
1683
+
1684
+ /* The sort menu (Figma `Menu`, Type=Sort): 200px, the tick leading its row. */
1685
+ .lab-menu.lab-menu--sort {
1686
+ min-width: 200px;
1687
+ width: 200px;
1688
+ padding: var(--lab-space-xs) 0;
1689
+ border-color: var(--lab-border-secondary-alt);
1690
+ box-shadow: var(--lab-shadow-lg-3);
1691
+ }
1692
+
1693
+ .lab-menu--sort .lab-menu__item {
1694
+ width: calc(100% - 12px);
1695
+ margin: 1px var(--lab-space-sm);
1696
+ padding: var(--lab-space-md) var(--lab-space-sm) var(--lab-space-md) 10px;
1697
+ color: var(--lab-text-secondary);
1698
+ font-weight: 600;
1699
+ }
1700
+
1701
+ .lab-menu--sort .lab-menu__item .lab-menu__tick {
1702
+ order: -1;
1703
+ margin-left: 0;
1704
+ }
1705
+
1706
+ /* The rows. A remembered "Name" sort is drawn by `order` from the first paint; the script then
1707
+ moves the rows to match (../chrome/shell-client.ts). */
1708
+ .lab-stree > li {
1709
+ order: var(--lab-o-slot, 0);
1710
+ }
1711
+
1712
+ :root[data-lab-sort="name"] .lab-stree > li {
1713
+ order: var(--lab-o-name, 0);
1714
+ }
1715
+
1716
+ .lab-stree__heading {
1717
+ margin: var(--lab-space-xs) 0;
1718
+ padding: var(--lab-space-lg) 0 var(--lab-space-xs) var(--lab-space-md);
1719
+ border-top: 1px solid var(--lab-border-secondary);
1720
+ color: var(--lab-text-quaternary);
1721
+ font-size: 12px;
1722
+ line-height: 18px;
1723
+ font-weight: 500;
1724
+ }
1725
+
1726
+ .lab-stree .lab-tree__row {
1727
+ gap: var(--lab-space-sm);
1728
+ }
1729
+
1730
+ /* The row you are on reads violet, without a fill — the badge carries "you are here". */
1731
+ .lab-stree .lab-tree__row[aria-current="page"] {
1732
+ background-color: transparent;
1733
+ color: var(--lab-text-brand-secondary);
1734
+ }
1735
+
1736
+ .lab-stree .lab-tree__row[aria-current="page"]:hover {
1737
+ background-color: var(--lab-bg-hover);
1738
+ }
1739
+
1740
+ /* A section's versions: the connector runs straight down the badge column, no curve. */
1741
+ .lab-stree .lab-tree__group > .lab-tree__node::before,
1742
+ .lab-stree .lab-tree__group > .lab-tree__node:last-child::before {
1743
+ top: 0;
1744
+ bottom: 0;
1745
+ height: auto;
1746
+ width: 1px;
1747
+ border: 0;
1748
+ border-radius: 0;
1749
+ background-color: var(--lab-fg-muted);
1750
+ }
1751
+
1752
+ .lab-stree .lab-tree__group > .lab-tree__node > .lab-tree__row {
1753
+ gap: var(--lab-space-md);
1754
+ }
1755
+
1756
+ /* The slot badge (Figma `Slot badge`): green with its slot, a grey dash when no page mounts it,
1757
+ violet on the row you are on. Two digits widen it. */
1758
+ .lab-slot {
1759
+ flex: none;
1760
+ display: inline-grid;
1761
+ place-items: center;
1762
+ min-width: 20px;
1763
+ height: 16px;
1764
+ padding: 0 3px;
1765
+ border: 1px solid var(--lab-slot-border);
1766
+ border-radius: var(--lab-radius-xs);
1767
+ background-color: var(--lab-slot-bg);
1768
+ color: var(--lab-slot-text);
1769
+ font-size: 12px;
1770
+ line-height: 14px;
1771
+ font-weight: 600;
1772
+ font-variant-numeric: tabular-nums;
1773
+ }
1774
+
1775
+ .lab-slot[data-state="unslotted"] {
1776
+ border-color: var(--lab-slot-none-border);
1777
+ background-color: var(--lab-slot-none-bg);
1778
+ color: var(--lab-slot-none-text);
1779
+ }
1780
+
1781
+ .lab-slot[data-state="current"] {
1782
+ border-color: var(--lab-slot-here-border);
1783
+ background-color: var(--lab-slot-here-bg);
1784
+ color: var(--lab-slot-here-text);
1785
+ }
1786
+
1160
1787
  /* ---------------------------------------------------------------------------------------------
1161
1788
  Main: the navbar above, the content below.
1162
1789
  --------------------------------------------------------------------------------------------- */
@@ -1235,175 +1862,239 @@ details:not([open]) > summary .lab-tree__chevron {
1235
1862
  background-color: var(--lab-border-secondary);
1236
1863
  }
1237
1864
 
1238
- /* The pills after a component's name are one button: the trigger for its status menu. */
1239
- .lab-status-btn {
1240
- display: inline-flex;
1241
- align-items: center;
1242
- flex-wrap: wrap;
1243
- gap: var(--lab-space-xs);
1244
- padding: 2px 4px;
1245
- margin: -2px -4px;
1246
- border: 0;
1247
- border-radius: var(--lab-radius-md);
1248
- background: transparent;
1249
- color: var(--lab-fg-quaternary);
1250
- font: inherit;
1251
- cursor: pointer;
1865
+ /* ---- the navbar of a component or a page (Figma `Astrolab` Lab / Components Navbar) --------
1866
+ One grid, `.lab-nb`, laid out by a container query on the navbar's own width — the sidebars
1867
+ change the main area, never the viewport, so a media query would read the wrong number.
1868
+ ONE row, 59px, when the navbar is 1040px wide or more:
1869
+ identity · variants · previous / next · device switch + Viewport field · Comment · Parameters · ⋯
1870
+ otherwise TWO rows, 99px, the viewport row always last:
1871
+ identity · variants · previous / next · ⋯ / device switch + Viewport field | Comment · Parameters
1872
+ and under 552px THREE or FOUR rows — "The narrow navbar", after the Viewport field's rules.
1873
+ The query's number is the navbar's content box: 1040 less its 16px side padding. Row 1 needs
1874
+ about 468px and row 2 about 480 to 520px (Parameters is the difference), so the two rows hold
1875
+ down to a 552px navbar. Where row 2 still runs out (the long "phone landscape" band, the panel
1876
+ button taking its 44px), Comment and Parameters wrap under the viewport group, to the right,
1877
+ rather than overflow. Every control is 32px tall; the device switch's outer border makes the
1878
+ one-row bar 34px of content, which is where 59 comes from.
1879
+ The page must never scroll sideways. Every layout fits down to a navbar of about 200px (measured
1880
+ 2026-09-24); below that nothing can, and the navbar clips its right edge instead
1881
+ (`overflow-x: clip` — the menus and the Properties panel are popovers in the top layer, so they
1882
+ are not clipped with it). */
1883
+ .lab-navbar:has(> .lab-nb) {
1884
+ container: lab-navbar / inline-size;
1885
+ align-items: flex-start;
1886
+ flex-wrap: nowrap;
1887
+ gap: var(--lab-space-lg);
1888
+ min-height: 0;
1889
+ padding: var(--lab-space-lg) var(--lab-space-xl);
1890
+ overflow-x: clip;
1252
1891
  }
1253
1892
 
1254
- .lab-status-btn:hover {
1255
- background-color: var(--lab-bg-hover);
1893
+ .lab-navbar:has(> .lab-nb) .lab-navbar__panel-btn {
1894
+ margin-left: 0;
1256
1895
  }
1257
1896
 
1258
- .lab-status-menu {
1259
- width: 320px;
1897
+ .lab-nb {
1898
+ flex: 1;
1899
+ min-width: 0;
1900
+ display: grid;
1901
+ grid-template-columns: minmax(0, 1fr) auto auto;
1902
+ grid-template-areas:
1903
+ "id variants actions"
1904
+ "tools tools tools";
1905
+ align-items: center;
1906
+ gap: var(--lab-space-md);
1260
1907
  }
1261
1908
 
1262
- .lab-status-menu__fact,
1263
- .lab-status-menu__note {
1264
- margin: 0;
1265
- padding: 0 var(--lab-space-md) var(--lab-space-sm);
1266
- color: var(--lab-text-tertiary);
1267
- font-size: 13px;
1268
- line-height: 18px;
1269
- overflow-wrap: anywhere;
1270
- }
1909
+ @container lab-navbar (min-width: 1008px) {
1910
+ .lab-nb {
1911
+ grid-template-columns: minmax(0, 1fr) auto auto auto;
1912
+ grid-template-areas: "id variants tools actions";
1913
+ column-gap: var(--lab-space-lg);
1914
+ }
1271
1915
 
1272
- .lab-status-menu__note {
1273
- padding-top: var(--lab-space-sm);
1274
- color: var(--lab-text-quaternary);
1275
- font-size: 12px;
1276
- }
1277
-
1278
- .lab-menu__check {
1279
- cursor: pointer;
1280
- }
1916
+ .lab-nb .lab-nb__id {
1917
+ padding-right: 0;
1918
+ }
1281
1919
 
1282
- .lab-menu__check input {
1283
- width: 16px;
1284
- height: 16px;
1285
- margin: 0;
1286
- accent-color: var(--lab-brand-solid);
1920
+ .lab-nb .lab-nb__tools {
1921
+ column-gap: var(--lab-space-lg);
1922
+ }
1287
1923
  }
1288
1924
 
1289
- .lab-menu__check:has(input:disabled) {
1290
- cursor: default;
1291
- opacity: 0.7;
1925
+ .lab-nb__id {
1926
+ grid-area: id;
1927
+ display: flex;
1928
+ flex-wrap: wrap;
1929
+ align-items: center;
1930
+ gap: var(--lab-space-xs) 10px;
1931
+ min-width: 0;
1932
+ padding-right: var(--lab-space-xs);
1292
1933
  }
1293
1934
 
1294
- /* Story tabs (the Untitled "button minimal" tab). */
1295
- .lab-tabs {
1935
+ .lab-nb__line {
1296
1936
  display: flex;
1297
1937
  align-items: center;
1298
- flex-wrap: wrap;
1299
- gap: var(--lab-space-xs);
1938
+ gap: var(--lab-space-md);
1939
+ min-width: 0;
1940
+ max-width: 100%;
1300
1941
  }
1301
1942
 
1302
- .lab-tab {
1303
- display: inline-flex;
1304
- align-items: center;
1305
- min-height: 32px;
1306
- padding: var(--lab-space-sm) var(--lab-space-lg);
1307
- border-radius: var(--lab-radius-sm);
1308
- color: var(--lab-text-quaternary);
1943
+ .lab-nb__name {
1944
+ margin: 0;
1945
+ min-width: 0;
1946
+ overflow: hidden;
1947
+ color: var(--lab-text-primary);
1948
+ font-size: 16px;
1949
+ line-height: 24px;
1309
1950
  font-weight: 600;
1310
- text-decoration: none;
1951
+ text-overflow: ellipsis;
1311
1952
  white-space: nowrap;
1312
- transition: background-color var(--lab-ease), color var(--lab-ease);
1313
1953
  }
1314
1954
 
1315
- .lab-tab:hover {
1316
- background-color: var(--lab-bg-hover);
1317
- color: var(--lab-text-secondary);
1955
+ /* The version (`V1`) or, on a page, the route (`/services/web-design`). */
1956
+ .lab-nb__tag {
1957
+ flex: none;
1958
+ padding: 0 var(--lab-space-sm);
1959
+ border: 1px solid var(--lab-border-primary);
1960
+ border-radius: var(--lab-radius-sm);
1961
+ color: var(--lab-text-tertiary);
1962
+ font-size: 12px;
1963
+ line-height: 18px;
1964
+ font-weight: 500;
1965
+ white-space: nowrap;
1318
1966
  }
1319
1967
 
1320
- .lab-tab[aria-current="page"] {
1321
- background-color: var(--lab-bg-active);
1322
- color: var(--lab-text-brand-primary);
1968
+ .lab-nb__tag--route {
1969
+ flex-shrink: 1;
1970
+ min-width: 0;
1971
+ overflow: hidden;
1972
+ text-overflow: ellipsis;
1973
+ background-color: var(--lab-bg-secondary);
1323
1974
  }
1324
1975
 
1325
- .lab-tabs__count {
1976
+ /* A page's second identity line: its source file, then the components it mounts. */
1977
+ .lab-nb__facts {
1978
+ display: flex;
1979
+ align-items: center;
1980
+ gap: var(--lab-space-md);
1981
+ min-width: 0;
1982
+ max-width: 100%;
1326
1983
  color: var(--lab-text-quaternary);
1327
- font-weight: 500;
1328
- font-variant-numeric: tabular-nums;
1984
+ font-size: 12px;
1985
+ line-height: 18px;
1329
1986
  }
1330
1987
 
1331
- a.lab-menu__item[aria-checked] .lab-menu__tick {
1332
- margin-left: auto;
1988
+ .lab-nb__source {
1989
+ display: inline-flex;
1990
+ align-items: center;
1991
+ gap: var(--lab-space-xs);
1992
+ min-width: 0;
1993
+ overflow: hidden;
1994
+ white-space: nowrap;
1995
+ text-overflow: ellipsis;
1333
1996
  }
1334
1997
 
1335
- .lab-content {
1998
+ .lab-nb__source .lab-icon {
1999
+ width: 14px;
2000
+ height: 14px;
2001
+ }
2002
+
2003
+ /* The components chip: 22px to look at, 32px to hit. */
2004
+ .lab-nb__chip {
1336
2005
  position: relative;
1337
- flex: 1;
1338
- min-height: 0;
1339
- overflow: auto;
2006
+ flex: none;
2007
+ display: inline-flex;
2008
+ align-items: center;
2009
+ gap: var(--lab-space-xs);
2010
+ padding: 1px var(--lab-space-xs) 1px var(--lab-space-sm);
2011
+ border: 1px solid var(--lab-border-primary);
2012
+ border-radius: var(--lab-radius-sm);
2013
+ background: transparent;
2014
+ color: var(--lab-text-secondary);
2015
+ font: inherit;
2016
+ font-size: 12px;
2017
+ line-height: 18px;
2018
+ font-weight: 500;
2019
+ white-space: nowrap;
2020
+ cursor: pointer;
1340
2021
  }
1341
2022
 
1342
- .lab-page {
1343
- max-width: 1280px;
1344
- padding: var(--lab-space-3xl) var(--lab-space-3xl) 48px;
2023
+ .lab-nb__chip::after {
2024
+ content: "";
2025
+ position: absolute;
2026
+ inset: -5px 0;
1345
2027
  }
1346
2028
 
1347
- .lab-page__head {
1348
- margin-bottom: var(--lab-space-3xl);
2029
+ .lab-nb__chip:hover {
2030
+ background-color: var(--lab-bg-secondary);
1349
2031
  }
1350
2032
 
1351
- .lab-page__title {
1352
- margin: 0 0 var(--lab-space-xs);
1353
- color: var(--lab-text-primary);
1354
- font-size: 24px;
1355
- line-height: 32px;
1356
- font-weight: 600;
1357
- letter-spacing: -0.01em;
2033
+ .lab-nb__chip .lab-icon {
2034
+ width: 12px;
2035
+ height: 12px;
2036
+ color: var(--lab-text-quaternary);
1358
2037
  }
1359
2038
 
1360
- .lab-page__lede {
1361
- max-width: 68ch;
1362
- margin: 0;
1363
- color: var(--lab-text-tertiary);
1364
- font-size: 15px;
1365
- line-height: 24px;
2039
+ .lab-nb__variants {
2040
+ grid-area: variants;
2041
+ display: flex;
2042
+ align-items: center;
2043
+ gap: var(--lab-space-md);
1366
2044
  }
1367
2045
 
1368
- .lab-section-title {
2046
+ .lab-nb__step {
1369
2047
  display: flex;
1370
- align-items: baseline;
2048
+ }
2049
+
2050
+ /* On two rows the space between the viewport group and the overlays is space-between's, so the gap
2051
+ is only the least it may be before the overlays wrap: 8, which is what keeps Parameters on the row
2052
+ at 552px. The one-row bar sizes this to its content, where the gap is the spacing, and sets 12. */
2053
+ .lab-nb__tools {
2054
+ grid-area: tools;
2055
+ display: flex;
2056
+ flex-wrap: wrap;
2057
+ align-items: center;
2058
+ justify-content: space-between;
1371
2059
  gap: var(--lab-space-md);
1372
- margin: var(--lab-space-3xl) 0 var(--lab-space-lg);
1373
- color: var(--lab-text-primary);
1374
- font-size: 16px;
1375
- line-height: 24px;
1376
- font-weight: 600;
2060
+ min-width: 0;
1377
2061
  }
1378
2062
 
1379
- .lab-section-title small {
1380
- color: var(--lab-text-quaternary);
1381
- font-size: 14px;
1382
- font-weight: 500;
2063
+ .lab-nb__viewport {
2064
+ display: flex;
2065
+ align-items: center;
2066
+ gap: var(--lab-space-md);
1383
2067
  }
1384
2068
 
1385
- .lab-section-title a {
1386
- color: inherit;
1387
- text-decoration: none;
2069
+ /* margin-left: auto keeps the pair at the right when it wraps onto a line of its own. */
2070
+ .lab-nb__overlays {
2071
+ display: flex;
2072
+ align-items: center;
2073
+ gap: var(--lab-space-xs);
2074
+ margin-left: auto;
1388
2075
  }
1389
2076
 
1390
- .lab-section-title a:hover {
1391
- color: var(--lab-text-brand-secondary);
2077
+ .lab-nb__actions {
2078
+ grid-area: actions;
2079
+ display: flex;
1392
2080
  }
1393
2081
 
1394
- /* ---- buttons ------------------------------------------------------------------------------- */
1395
- .lab-btn {
2082
+ /* The navbar's buttons (Figma `Tool button`): 32px, ghost at rest. Icons at rest are
2083
+ text-quaternary (#737373, 4.7:1) rather than the kit's #a3a3a3 — an icon-only button needs 3:1.
2084
+ Outline for a standalone action, Pressed for a toggle that is on, and the blue Pins pressed for
2085
+ Comment while pin mode is on, because pins are blue. */
2086
+ .lab-tool {
2087
+ flex: none;
1396
2088
  display: inline-flex;
1397
2089
  align-items: center;
1398
2090
  justify-content: center;
1399
2091
  gap: var(--lab-space-xs);
1400
- min-height: 36px;
1401
- padding: var(--lab-space-md) var(--lab-space-lg);
1402
- border: 1px solid var(--lab-border-primary);
2092
+ height: 32px;
2093
+ padding: 7px;
2094
+ border: 1px solid transparent;
1403
2095
  border-radius: var(--lab-radius-md);
1404
- background-color: var(--lab-bg-primary);
1405
- box-shadow: var(--lab-shadow-xs);
1406
- color: var(--lab-text-secondary);
2096
+ background: transparent;
2097
+ color: var(--lab-text-quaternary);
1407
2098
  font: inherit;
1408
2099
  font-weight: 600;
1409
2100
  white-space: nowrap;
@@ -1412,117 +2103,619 @@ a.lab-menu__item[aria-checked] .lab-menu__tick {
1412
2103
  transition: background-color var(--lab-ease), color var(--lab-ease), border-color var(--lab-ease);
1413
2104
  }
1414
2105
 
1415
- .lab-btn:hover {
1416
- background-color: var(--lab-bg-hover);
1417
- color: var(--lab-text-primary);
1418
- }
1419
-
1420
- .lab-btn > .lab-icon {
2106
+ .lab-tool .lab-icon {
1421
2107
  width: 16px;
1422
2108
  height: 16px;
1423
- color: var(--lab-fg-quaternary);
1424
2109
  }
1425
2110
 
1426
- .lab-btn--icon {
1427
- width: 36px;
1428
- padding: 0;
2111
+ .lab-tool__label {
2112
+ padding: 0 2px;
2113
+ color: var(--lab-text-tertiary);
1429
2114
  }
1430
2115
 
1431
- .lab-btn--sm {
1432
- min-height: 32px;
1433
- padding: var(--lab-space-sm) var(--lab-space-md);
1434
- font-size: 13px;
2116
+ .lab-tool:hover {
2117
+ background-color: var(--lab-bg-secondary);
2118
+ color: var(--lab-text-secondary);
1435
2119
  }
1436
2120
 
1437
- .lab-btn--sm.lab-btn--icon {
2121
+ .lab-tool:hover .lab-tool__label {
2122
+ color: var(--lab-text-secondary);
2123
+ }
2124
+
2125
+ .lab-tool--icon {
1438
2126
  width: 32px;
1439
2127
  padding: 0;
1440
2128
  }
1441
2129
 
1442
- .lab-btn[aria-pressed="true"],
1443
- .lab-btn[data-active] {
1444
- background-color: var(--lab-bg-active);
1445
- border-color: color-mix(in srgb, var(--lab-fg-brand) 55%, var(--lab-border-primary));
1446
- color: var(--lab-text-brand-primary);
2130
+ .lab-tool--outline {
2131
+ border-color: var(--lab-border-primary);
2132
+ background-color: var(--lab-bg-primary);
2133
+ box-shadow: var(--lab-shadow-xs);
1447
2134
  }
1448
2135
 
1449
- .lab-btn[aria-pressed="true"] > .lab-icon,
1450
- .lab-btn[data-active] > .lab-icon {
1451
- color: var(--lab-fg-brand);
2136
+ .lab-tool--outline .lab-tool__label {
2137
+ color: var(--lab-text-secondary);
1452
2138
  }
1453
2139
 
1454
- .lab-btn:disabled,
1455
- .lab-btn[aria-disabled="true"] {
1456
- opacity: 0.5;
1457
- cursor: not-allowed;
2140
+ .lab-tool[aria-pressed="true"],
2141
+ .lab-tool[data-pinned] {
2142
+ border-color: var(--lab-border-secondary);
2143
+ background-color: var(--lab-bg-hover);
2144
+ color: var(--lab-text-secondary);
1458
2145
  }
1459
2146
 
1460
- /* A row of joined buttons — the device switch. */
1461
- .lab-seg {
1462
- display: inline-flex;
1463
- border: 1px solid var(--lab-border-primary);
1464
- border-radius: var(--lab-radius-md);
1465
- box-shadow: var(--lab-shadow-xs);
1466
- overflow: hidden;
1467
- background-color: var(--lab-bg-primary);
2147
+ .lab-tool[aria-pressed="true"]:hover,
2148
+ .lab-tool[data-pinned]:hover {
2149
+ border-color: var(--lab-border-primary);
1468
2150
  }
1469
2151
 
1470
- .lab-seg > .lab-btn {
1471
- border: 0;
1472
- border-radius: 0;
1473
- box-shadow: none;
2152
+ .lab-tool--pins[aria-pressed="true"] {
2153
+ border-color: var(--lab-pins-border);
2154
+ background-color: var(--lab-pins-bg);
2155
+ color: var(--lab-pins-text);
1474
2156
  }
1475
2157
 
1476
- .lab-seg > .lab-btn + .lab-btn {
1477
- border-left: 1px solid var(--lab-border-primary);
2158
+ .lab-tool--pins[aria-pressed="true"] .lab-tool__label {
2159
+ color: var(--lab-pins-text);
1478
2160
  }
1479
2161
 
1480
- .lab-seg > .lab-btn[data-active] {
1481
- background-color: var(--lab-bg-hover);
1482
- color: var(--lab-text-primary);
2162
+ .lab-tool--pins[aria-pressed="true"]:hover {
2163
+ border-color: var(--lab-pins-border);
2164
+ background-color: var(--lab-pins-bg-hover);
1483
2165
  }
1484
2166
 
1485
- .lab-seg > .lab-btn[data-active] > .lab-icon {
1486
- color: var(--lab-text-secondary);
2167
+ .lab-tool:disabled {
2168
+ opacity: 0.45;
2169
+ cursor: default;
1487
2170
  }
1488
2171
 
1489
- .lab-select {
1490
- min-height: 36px;
1491
- padding: var(--lab-space-md) 32px var(--lab-space-md) var(--lab-space-lg);
2172
+ .lab-tool:disabled:hover {
2173
+ background-color: transparent;
2174
+ }
2175
+
2176
+ /* The variant dropdown (Figma `Story tab`, Type=Dropdown) — at every count. */
2177
+ .lab-variant-btn {
2178
+ display: inline-flex;
2179
+ align-items: center;
2180
+ gap: var(--lab-space-sm);
2181
+ max-width: 260px;
2182
+ height: 32px;
2183
+ padding: var(--lab-space-sm) 10px var(--lab-space-sm) var(--lab-space-lg);
1492
2184
  border: 1px solid var(--lab-border-primary);
1493
2185
  border-radius: var(--lab-radius-md);
1494
- background: var(--lab-bg-primary) no-repeat right 10px center / 16px;
1495
- background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23a3a3a3' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
2186
+ background-color: var(--lab-bg-primary);
1496
2187
  box-shadow: var(--lab-shadow-xs);
1497
2188
  color: var(--lab-text-secondary);
1498
2189
  font: inherit;
1499
2190
  font-weight: 600;
1500
- appearance: none;
2191
+ white-space: nowrap;
1501
2192
  cursor: pointer;
1502
2193
  }
1503
2194
 
1504
- .lab-select--sm {
1505
- min-height: 32px;
1506
- padding-top: var(--lab-space-sm);
1507
- padding-bottom: var(--lab-space-sm);
1508
- font-size: 13px;
2195
+ .lab-variant-btn:hover {
2196
+ background-color: var(--lab-bg-secondary);
1509
2197
  }
1510
2198
 
1511
- /* ---- pills (Figma/Untitled `Badge`, sm) ---------------------------------------------------- */
1512
- .lab-pill {
1513
- display: inline-flex;
1514
- align-items: center;
1515
- gap: var(--lab-space-xs);
1516
- padding: 1px 8px;
1517
- border: 1px solid var(--lab-neutral-border);
1518
- border-radius: var(--lab-radius-full);
1519
- background-color: var(--lab-neutral-bg);
1520
- color: var(--lab-neutral-text);
1521
- font-size: 12px;
1522
- line-height: 18px;
1523
- font-weight: 500;
1524
- white-space: nowrap;
1525
- }
2199
+ .lab-variant-btn__name {
2200
+ min-width: 0;
2201
+ overflow: hidden;
2202
+ text-overflow: ellipsis;
2203
+ }
2204
+
2205
+ .lab-variant-btn__count {
2206
+ color: var(--lab-text-quaternary);
2207
+ font-weight: 500;
2208
+ font-variant-numeric: tabular-nums;
2209
+ }
2210
+
2211
+ .lab-variant-btn .lab-icon {
2212
+ width: 16px;
2213
+ height: 16px;
2214
+ color: var(--lab-text-quaternary);
2215
+ }
2216
+
2217
+ /* The device switch (Figma `Device switch`): four 34 × 32 segments. The active one is violet —
2218
+ "you are here"; a width that is none of the four presses none. */
2219
+ .lab-devsw {
2220
+ flex: none;
2221
+ display: inline-flex;
2222
+ border: 1px solid var(--lab-border-primary);
2223
+ border-radius: var(--lab-radius-md);
2224
+ background-color: var(--lab-bg-primary);
2225
+ box-shadow: var(--lab-shadow-xs);
2226
+ overflow: hidden;
2227
+ }
2228
+
2229
+ .lab-devsw__btn {
2230
+ display: grid;
2231
+ place-items: center;
2232
+ width: 34px;
2233
+ height: 32px;
2234
+ padding: 0;
2235
+ border: 0;
2236
+ border-right: 1px solid var(--lab-border-primary);
2237
+ background: transparent;
2238
+ color: var(--lab-text-quaternary);
2239
+ cursor: pointer;
2240
+ }
2241
+
2242
+ .lab-devsw__btn:last-child {
2243
+ border-right: 0;
2244
+ }
2245
+
2246
+ .lab-devsw__btn:hover {
2247
+ background-color: var(--lab-bg-secondary);
2248
+ color: var(--lab-text-secondary);
2249
+ }
2250
+
2251
+ .lab-devsw__btn[aria-pressed="true"] {
2252
+ background-color: var(--lab-bg-active);
2253
+ color: var(--lab-brand-solid);
2254
+ }
2255
+
2256
+ :root.dark .lab-devsw__btn[aria-pressed="true"] {
2257
+ color: var(--lab-fg-brand);
2258
+ }
2259
+
2260
+ .lab-shell .lab-devsw__btn:focus-visible {
2261
+ box-shadow: inset 0 0 0 2px var(--lab-fg-brand);
2262
+ }
2263
+
2264
+ /* The Viewport field (Figma `Viewport field`): W × H typed in place, then the band and zoom as the
2265
+ button that opens the presets. While a frame edge is dragged the numbers follow it, in violet. */
2266
+ .lab-vpf {
2267
+ flex: none;
2268
+ display: inline-flex;
2269
+ align-items: center;
2270
+ gap: var(--lab-space-xs);
2271
+ height: 32px;
2272
+ padding-left: 10px;
2273
+ border: 1px solid var(--lab-border-primary);
2274
+ border-radius: var(--lab-radius-md);
2275
+ background-color: var(--lab-bg-primary);
2276
+ box-shadow: var(--lab-shadow-xs);
2277
+ overflow: hidden;
2278
+ color: var(--lab-text-secondary);
2279
+ font-weight: 500;
2280
+ font-variant-numeric: tabular-nums;
2281
+ transition: border-color var(--lab-ease), box-shadow var(--lab-ease);
2282
+ }
2283
+
2284
+ .lab-vpf:has(.lab-vpf__num:focus) {
2285
+ border-color: var(--lab-fg-brand);
2286
+ box-shadow: var(--lab-shadow-xs), 0 0 0 3px color-mix(in srgb, var(--lab-fg-brand) 22%, transparent);
2287
+ }
2288
+
2289
+ .lab-vpf__num {
2290
+ width: 36px;
2291
+ padding: 0;
2292
+ border: 0;
2293
+ outline: none;
2294
+ background: transparent;
2295
+ color: var(--lab-text-secondary);
2296
+ font: inherit;
2297
+ appearance: textfield;
2298
+ -moz-appearance: textfield;
2299
+ }
2300
+
2301
+ .lab-vpf__num--w {
2302
+ text-align: right;
2303
+ }
2304
+
2305
+ .lab-vpf__num::-webkit-outer-spin-button,
2306
+ .lab-vpf__num::-webkit-inner-spin-button {
2307
+ -webkit-appearance: none;
2308
+ margin: 0;
2309
+ }
2310
+
2311
+ .lab-shell .lab-vpf__num:focus-visible {
2312
+ box-shadow: none;
2313
+ color: var(--lab-text-brand-secondary);
2314
+ }
2315
+
2316
+ .lab-vpf[data-dragging] .lab-vpf__num {
2317
+ color: var(--lab-text-brand-secondary);
2318
+ }
2319
+
2320
+ .lab-vpf__x {
2321
+ color: var(--lab-text-quaternary);
2322
+ }
2323
+
2324
+ .lab-vpf__menu {
2325
+ display: inline-flex;
2326
+ align-items: center;
2327
+ gap: var(--lab-space-sm);
2328
+ height: 100%;
2329
+ padding: 0 var(--lab-space-md) 0 10px;
2330
+ border: 0;
2331
+ border-left: 1px solid var(--lab-border-secondary);
2332
+ background: transparent;
2333
+ font: inherit;
2334
+ white-space: nowrap;
2335
+ cursor: pointer;
2336
+ }
2337
+
2338
+ .lab-vpf__menu:hover {
2339
+ background-color: var(--lab-bg-secondary);
2340
+ }
2341
+
2342
+ .lab-shell .lab-vpf__menu:focus-visible {
2343
+ box-shadow: inset 0 0 0 2px var(--lab-fg-brand);
2344
+ }
2345
+
2346
+ .lab-vpf__band {
2347
+ color: var(--lab-text-secondary);
2348
+ font-weight: 600;
2349
+ }
2350
+
2351
+ .lab-vpf__zoom {
2352
+ color: var(--lab-text-quaternary);
2353
+ font-weight: 500;
2354
+ }
2355
+
2356
+ .lab-vpf__menu .lab-icon {
2357
+ width: 16px;
2358
+ height: 16px;
2359
+ color: var(--lab-text-quaternary);
2360
+ }
2361
+
2362
+ /* ---- the narrow navbar (no Figma frame: the design stops at two rows) --------------------------
2363
+ Under 520px of content box — a navbar under 552px — row 1's variants and ⋯ squeeze the name to
2364
+ nothing and row 2 is wider than the bar. So the identity gets a row of its own, with ⋯:
2365
+ identity · ⋯ / variants · previous / next | Comment · Parameters / device switch + Viewport field
2366
+ THREE rows, and FOUR under about 400px, where the device switch and the Viewport field stop fitting
2367
+ side by side and stack. Nothing is folded into a menu: the four device icons stay in view (the
2368
+ ruling that condensed the viewport group kept them), and so does every other control. Comment
2369
+ drops its word and keeps its icon, its tooltip and its accessible name, which is what lets the
2370
+ variants row hold at 300px. The name, the variant and the band shorten with an ellipsis; the
2371
+ name's title carries it in full. */
2372
+ @container lab-navbar (width < 520px) {
2373
+ .lab-nb {
2374
+ grid-template-columns: minmax(0, 1fr) auto;
2375
+ grid-template-areas:
2376
+ "id actions"
2377
+ "variants overlays"
2378
+ "viewport viewport";
2379
+ }
2380
+
2381
+ /* Its two halves become grid items of their own; nothing reads this element's box. */
2382
+ .lab-nb__tools {
2383
+ display: contents;
2384
+ }
2385
+
2386
+ .lab-nb__viewport {
2387
+ grid-area: viewport;
2388
+ flex-wrap: wrap;
2389
+ min-width: 0;
2390
+ }
2391
+
2392
+ .lab-nb__overlays {
2393
+ grid-area: overlays;
2394
+ }
2395
+
2396
+ .lab-nb__variants,
2397
+ .lab-variant-btn,
2398
+ .lab-vpf__menu {
2399
+ min-width: 0;
2400
+ }
2401
+
2402
+ .lab-vpf {
2403
+ flex-shrink: 1;
2404
+ min-width: 0;
2405
+ }
2406
+
2407
+ .lab-vpf__band {
2408
+ min-width: 0;
2409
+ overflow: hidden;
2410
+ text-overflow: ellipsis;
2411
+ }
2412
+
2413
+ .lab-tool--pins .lab-tool__label {
2414
+ position: absolute;
2415
+ width: 1px;
2416
+ height: 1px;
2417
+ overflow: hidden;
2418
+ clip: rect(0 0 0 0);
2419
+ white-space: nowrap;
2420
+ }
2421
+ }
2422
+
2423
+ .lab-content {
2424
+ position: relative;
2425
+ flex: 1;
2426
+ min-height: 0;
2427
+ overflow: auto;
2428
+ }
2429
+
2430
+ /* A listing's content (Figma `Lab / Listing`): Main's own padding, 32 on top and at the sides,
2431
+ 48 at the foot. No dot grid — a listing is a page of cards, not a stage. */
2432
+ .lab-page {
2433
+ max-width: 1280px;
2434
+ padding: 32px 32px 48px;
2435
+ }
2436
+
2437
+ /* ---- the listing title bar (Figma `Navbar`, listing: one row, title · counts · filter chips) -- */
2438
+ .lab-navbar.lab-navbar--listing {
2439
+ min-height: 0;
2440
+ padding: var(--lab-space-lg) var(--lab-space-xl);
2441
+ }
2442
+
2443
+ .lab-listing-bar {
2444
+ display: flex;
2445
+ align-items: center;
2446
+ flex-wrap: wrap;
2447
+ gap: var(--lab-space-md) var(--lab-space-lg);
2448
+ flex: 1 1 auto;
2449
+ min-width: 0;
2450
+ min-height: 38px;
2451
+ }
2452
+
2453
+ .lab-listing-bar__title {
2454
+ margin: 0;
2455
+ color: var(--lab-text-primary);
2456
+ font-size: 16px;
2457
+ line-height: 24px;
2458
+ font-weight: 600;
2459
+ white-space: nowrap;
2460
+ }
2461
+
2462
+ .lab-listing-bar__counts {
2463
+ margin: 0;
2464
+ min-width: 0;
2465
+ color: var(--lab-text-quaternary);
2466
+ font-size: 14px;
2467
+ line-height: 20px;
2468
+ font-weight: 500;
2469
+ overflow: hidden;
2470
+ text-overflow: ellipsis;
2471
+ white-space: nowrap;
2472
+ }
2473
+
2474
+ .lab-listing-bar > .lab-chips,
2475
+ .lab-listing-bar__end {
2476
+ margin-left: auto;
2477
+ }
2478
+
2479
+ /* Filter chips: a segmented control whose choices set the same filter as level 2's menu. */
2480
+ .lab-chips {
2481
+ display: inline-flex;
2482
+ align-items: center;
2483
+ gap: 2px;
2484
+ padding: 2px;
2485
+ border: 1px solid var(--lab-border-secondary);
2486
+ border-radius: var(--lab-radius-md);
2487
+ background-color: var(--lab-bg-secondary);
2488
+ }
2489
+
2490
+ .lab-chip {
2491
+ display: inline-flex;
2492
+ align-items: center;
2493
+ height: 32px;
2494
+ padding: var(--lab-space-sm) 10px;
2495
+ border: 1px solid transparent;
2496
+ border-radius: var(--lab-radius-sm);
2497
+ background: transparent;
2498
+ color: var(--lab-text-quaternary);
2499
+ font: inherit;
2500
+ font-weight: 600;
2501
+ white-space: nowrap;
2502
+ cursor: pointer;
2503
+ transition: background-color var(--lab-ease), color var(--lab-ease);
2504
+ }
2505
+
2506
+ .lab-chip:hover {
2507
+ color: var(--lab-text-secondary);
2508
+ }
2509
+
2510
+ .lab-chip[aria-checked="true"] {
2511
+ border-color: var(--lab-border-primary);
2512
+ background-color: var(--lab-bg-primary);
2513
+ box-shadow: var(--lab-shadow-xs);
2514
+ color: var(--lab-text-secondary);
2515
+ }
2516
+
2517
+ /* What waits on you — the review count on Tasks. Amber, the design's colour for it. */
2518
+ .lab-pill.lab-pill--waiting {
2519
+ border-color: var(--lab-warning-border);
2520
+ background-color: var(--lab-warning-bg);
2521
+ color: var(--lab-warning-text);
2522
+ }
2523
+
2524
+ .lab-pill.lab-pill--waiting::before {
2525
+ content: "";
2526
+ width: 6px;
2527
+ height: 6px;
2528
+ border-radius: 50%;
2529
+ background-color: var(--lab-warning-dot);
2530
+ }
2531
+
2532
+ /* ---- the Tasks views' layout (no level 2 — main takes the width beside level 1) ------------ */
2533
+ /* In the lab the board scrolls sideways inside itself, which makes the board — not the page — the
2534
+ sticky container of its column heads. Their offset below the page's toolbar then pushed every
2535
+ head ~70px down its own column; inside the board they stick at its top. */
2536
+ .pin-board.pin-board--lab .board .col-head {
2537
+ top: 0;
2538
+ }
2539
+
2540
+ .lab-page__head {
2541
+ margin-bottom: var(--lab-space-3xl);
2542
+ }
2543
+
2544
+ .lab-page__title {
2545
+ margin: 0 0 var(--lab-space-xs);
2546
+ color: var(--lab-text-primary);
2547
+ font-size: 24px;
2548
+ line-height: 32px;
2549
+ font-weight: 600;
2550
+ letter-spacing: -0.01em;
2551
+ }
2552
+
2553
+ .lab-page__lede {
2554
+ max-width: 68ch;
2555
+ margin: 0;
2556
+ color: var(--lab-text-tertiary);
2557
+ font-size: 15px;
2558
+ line-height: 24px;
2559
+ }
2560
+
2561
+ .lab-section-title {
2562
+ display: flex;
2563
+ align-items: baseline;
2564
+ gap: var(--lab-space-md);
2565
+ margin: var(--lab-space-3xl) 0 var(--lab-space-lg);
2566
+ color: var(--lab-text-primary);
2567
+ font-size: 16px;
2568
+ line-height: 24px;
2569
+ font-weight: 600;
2570
+ }
2571
+
2572
+ .lab-section-title small {
2573
+ color: var(--lab-text-quaternary);
2574
+ font-size: 14px;
2575
+ font-weight: 500;
2576
+ }
2577
+
2578
+ .lab-section-title a {
2579
+ color: inherit;
2580
+ text-decoration: none;
2581
+ }
2582
+
2583
+ .lab-section-title a:hover {
2584
+ color: var(--lab-text-brand-secondary);
2585
+ }
2586
+
2587
+ /* ---- buttons ------------------------------------------------------------------------------- */
2588
+ .lab-btn {
2589
+ display: inline-flex;
2590
+ align-items: center;
2591
+ justify-content: center;
2592
+ gap: var(--lab-space-xs);
2593
+ min-height: 36px;
2594
+ padding: var(--lab-space-md) var(--lab-space-lg);
2595
+ border: 1px solid var(--lab-border-primary);
2596
+ border-radius: var(--lab-radius-md);
2597
+ background-color: var(--lab-bg-primary);
2598
+ box-shadow: var(--lab-shadow-xs);
2599
+ color: var(--lab-text-secondary);
2600
+ font: inherit;
2601
+ font-weight: 600;
2602
+ white-space: nowrap;
2603
+ text-decoration: none;
2604
+ cursor: pointer;
2605
+ transition: background-color var(--lab-ease), color var(--lab-ease), border-color var(--lab-ease);
2606
+ }
2607
+
2608
+ .lab-btn:hover {
2609
+ background-color: var(--lab-bg-hover);
2610
+ color: var(--lab-text-primary);
2611
+ }
2612
+
2613
+ .lab-btn > .lab-icon {
2614
+ width: 16px;
2615
+ height: 16px;
2616
+ color: var(--lab-fg-quaternary);
2617
+ }
2618
+
2619
+ .lab-btn--icon {
2620
+ width: 36px;
2621
+ padding: 0;
2622
+ }
2623
+
2624
+ .lab-btn--sm {
2625
+ min-height: 32px;
2626
+ padding: var(--lab-space-sm) var(--lab-space-md);
2627
+ font-size: 13px;
2628
+ }
2629
+
2630
+ .lab-btn--sm.lab-btn--icon {
2631
+ width: 32px;
2632
+ padding: 0;
2633
+ }
2634
+
2635
+ .lab-btn[aria-pressed="true"],
2636
+ .lab-btn[data-active] {
2637
+ background-color: var(--lab-bg-active);
2638
+ border-color: color-mix(in srgb, var(--lab-fg-brand) 55%, var(--lab-border-primary));
2639
+ color: var(--lab-text-brand-primary);
2640
+ }
2641
+
2642
+ .lab-btn[aria-pressed="true"] > .lab-icon,
2643
+ .lab-btn[data-active] > .lab-icon {
2644
+ color: var(--lab-fg-brand);
2645
+ }
2646
+
2647
+ .lab-btn:disabled,
2648
+ .lab-btn[aria-disabled="true"] {
2649
+ opacity: 0.5;
2650
+ cursor: not-allowed;
2651
+ }
2652
+
2653
+ /* A row of joined buttons — the device switch. */
2654
+ .lab-seg {
2655
+ display: inline-flex;
2656
+ border: 1px solid var(--lab-border-primary);
2657
+ border-radius: var(--lab-radius-md);
2658
+ box-shadow: var(--lab-shadow-xs);
2659
+ overflow: hidden;
2660
+ background-color: var(--lab-bg-primary);
2661
+ }
2662
+
2663
+ .lab-seg > .lab-btn {
2664
+ border: 0;
2665
+ border-radius: 0;
2666
+ box-shadow: none;
2667
+ }
2668
+
2669
+ .lab-seg > .lab-btn + .lab-btn {
2670
+ border-left: 1px solid var(--lab-border-primary);
2671
+ }
2672
+
2673
+ .lab-seg > .lab-btn[data-active] {
2674
+ background-color: var(--lab-bg-hover);
2675
+ color: var(--lab-text-primary);
2676
+ }
2677
+
2678
+ .lab-seg > .lab-btn[data-active] > .lab-icon {
2679
+ color: var(--lab-text-secondary);
2680
+ }
2681
+
2682
+ .lab-select {
2683
+ min-height: 36px;
2684
+ padding: var(--lab-space-md) 32px var(--lab-space-md) var(--lab-space-lg);
2685
+ border: 1px solid var(--lab-border-primary);
2686
+ border-radius: var(--lab-radius-md);
2687
+ background: var(--lab-bg-primary) no-repeat right 10px center / 16px;
2688
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23a3a3a3' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
2689
+ box-shadow: var(--lab-shadow-xs);
2690
+ color: var(--lab-text-secondary);
2691
+ font: inherit;
2692
+ font-weight: 600;
2693
+ appearance: none;
2694
+ cursor: pointer;
2695
+ }
2696
+
2697
+ .lab-select--sm {
2698
+ min-height: 32px;
2699
+ padding-top: var(--lab-space-sm);
2700
+ padding-bottom: var(--lab-space-sm);
2701
+ font-size: 13px;
2702
+ }
2703
+
2704
+ /* ---- pills (Figma/Untitled `Badge`, sm) ---------------------------------------------------- */
2705
+ .lab-pill {
2706
+ display: inline-flex;
2707
+ align-items: center;
2708
+ gap: var(--lab-space-xs);
2709
+ padding: 1px 8px;
2710
+ border: 1px solid var(--lab-neutral-border);
2711
+ border-radius: var(--lab-radius-full);
2712
+ background-color: var(--lab-neutral-bg);
2713
+ color: var(--lab-neutral-text);
2714
+ font-size: 12px;
2715
+ line-height: 18px;
2716
+ font-weight: 500;
2717
+ white-space: nowrap;
2718
+ }
1526
2719
 
1527
2720
  .lab-pill--live {
1528
2721
  border-color: var(--lab-success-border);
@@ -1557,7 +2750,6 @@ a.lab-menu__item[aria-checked] .lab-menu__tick {
1557
2750
  color: var(--lab-warning-text);
1558
2751
  }
1559
2752
 
1560
- .lab-pill--marked,
1561
2753
  .lab-pill--error {
1562
2754
  border-color: var(--lab-error-border);
1563
2755
  background-color: var(--lab-error-bg);
@@ -1813,429 +3005,838 @@ a.lab-slots__name:hover {
1813
3005
  /* VS Code and staging, over the thumbnail's corner, above the card's own link. */
1814
3006
  .lab-card__links {
1815
3007
  position: absolute;
1816
- top: var(--lab-space-md);
1817
- right: var(--lab-space-md);
3008
+ top: var(--lab-space-md);
3009
+ right: var(--lab-space-md);
3010
+ z-index: 2;
3011
+ display: flex;
3012
+ gap: var(--lab-space-xs);
3013
+ opacity: 0;
3014
+ transition: opacity var(--lab-ease);
3015
+ }
3016
+
3017
+ .lab-card:hover .lab-card__links,
3018
+ .lab-card:focus-within .lab-card__links {
3019
+ opacity: 1;
3020
+ }
3021
+
3022
+ .lab-card__links .lab-icon-btn {
3023
+ width: 28px;
3024
+ height: 28px;
3025
+ background-color: var(--lab-bg-primary);
3026
+ border: 1px solid var(--lab-border-secondary);
3027
+ box-shadow: var(--lab-shadow-xs);
3028
+ }
3029
+
3030
+ .lab-card__links .lab-icon-btn .lab-icon {
3031
+ width: 16px;
3032
+ height: 16px;
3033
+ }
3034
+
3035
+ .lab-empty {
3036
+ padding: 48px var(--lab-space-3xl);
3037
+ border: 1px dashed var(--lab-border-primary);
3038
+ border-radius: var(--lab-radius-xl);
3039
+ color: var(--lab-text-quaternary);
3040
+ text-align: center;
3041
+ }
3042
+
3043
+ .lab-empty strong {
3044
+ display: block;
3045
+ margin-bottom: var(--lab-space-xs);
3046
+ color: var(--lab-text-primary);
3047
+ font-size: 16px;
3048
+ line-height: 24px;
3049
+ }
3050
+
3051
+ /* ---------------------------------------------------------------------------------------------
3052
+ The viewport stage — the story or page in an <iframe> whose logical size you set. Zoom is a
3053
+ transform on the wrapper, so the frame's own width (and so its breakpoints) never changes.
3054
+ --------------------------------------------------------------------------------------------- */
3055
+ .lab-vp {
3056
+ position: absolute;
3057
+ inset: 0;
3058
+ }
3059
+
3060
+ /* The preview's own light/dark, apart from the chrome's (decided 2026-09-24). The iframe's
3061
+ color-scheme is what `prefers-color-scheme` reads inside it, so it is pinned here — or a dark
3062
+ chrome would quietly turn every preview dark — and the preview theme switch sets it. */
3063
+ .lab-vp-wrapper iframe,
3064
+ .lab-card__thumb iframe {
3065
+ color-scheme: light;
3066
+ }
3067
+
3068
+ .lab-vp-wrapper iframe[data-theme="dark"] {
3069
+ color-scheme: dark;
3070
+ }
3071
+
3072
+ /* The live size while a handle is being dragged. */
3073
+ .lab-vp-live {
3074
+ position: absolute;
3075
+ right: 22px;
3076
+ bottom: 22px;
3077
+ z-index: 2;
3078
+ display: none;
3079
+ padding: 2px 8px;
3080
+ border-radius: var(--lab-radius-sm);
3081
+ background-color: var(--lab-text-primary);
3082
+ color: var(--lab-bg-primary);
3083
+ font-size: 12px;
3084
+ line-height: 18px;
3085
+ font-weight: 600;
3086
+ font-variant-numeric: tabular-nums;
3087
+ pointer-events: none;
3088
+ }
3089
+
3090
+ .lab-vp[data-dragging] .lab-vp-live {
3091
+ display: block;
3092
+ }
3093
+
3094
+ .lab-vp-stage {
3095
+ position: absolute;
3096
+ inset: 0;
3097
+ display: flex;
3098
+ overflow: auto;
3099
+ background-color: var(--lab-bg-secondary);
3100
+ background-image: radial-gradient(color-mix(in srgb, var(--lab-fg-quaternary) 45%, transparent) 1px, transparent 1px);
3101
+ background-size: 16px 16px;
3102
+ }
3103
+
3104
+ .lab-vp-box {
3105
+ position: relative;
3106
+ flex: none;
3107
+ margin: auto;
3108
+ padding: var(--lab-space-xl);
3109
+ box-sizing: content-box;
3110
+ overflow: hidden;
3111
+ }
3112
+
3113
+ .lab-vp-wrapper {
3114
+ position: relative;
3115
+ transform-origin: 0 0;
3116
+ border-radius: 2px;
3117
+ outline: 1px solid var(--lab-border-primary);
3118
+ background-color: #fff;
3119
+ box-shadow: var(--lab-shadow-lg);
3120
+ }
3121
+
3122
+ .lab-vp-wrapper iframe {
3123
+ display: block;
3124
+ width: 100%;
3125
+ height: 100%;
3126
+ border: 0;
3127
+ background-color: #fff;
3128
+ }
3129
+
3130
+ .lab-vp[data-dragging] .lab-vp-stage {
3131
+ user-select: none;
3132
+ }
3133
+
3134
+ .lab-vp-handle {
3135
+ position: absolute;
3136
+ touch-action: none;
3137
+ }
3138
+
3139
+ .lab-vp-handle::after {
3140
+ content: "";
3141
+ position: absolute;
3142
+ border-radius: var(--lab-radius-full);
3143
+ background-color: var(--lab-text-quaternary);
3144
+ transition: background-color var(--lab-ease);
3145
+ }
3146
+
3147
+ .lab-vp-handle:hover::after,
3148
+ .lab-vp[data-dragging="x"] .lab-vp-handle--e::after,
3149
+ .lab-vp[data-dragging="y"] .lab-vp-handle--s::after,
3150
+ .lab-vp[data-dragging="xy"] .lab-vp-handle--se::after {
3151
+ background-color: var(--lab-fg-brand);
3152
+ }
3153
+
3154
+ .lab-vp-handle--e {
3155
+ top: 16px;
3156
+ right: 0;
3157
+ bottom: 16px;
3158
+ width: 16px;
3159
+ cursor: ew-resize;
3160
+ }
3161
+
3162
+ .lab-vp-handle--e::after {
3163
+ top: 50%;
3164
+ left: 5px;
3165
+ width: 5px;
3166
+ height: 40px;
3167
+ transform: translateY(-50%);
3168
+ }
3169
+
3170
+ .lab-vp-handle--s {
3171
+ left: 16px;
3172
+ right: 16px;
3173
+ bottom: 0;
3174
+ height: 16px;
3175
+ cursor: ns-resize;
3176
+ }
3177
+
3178
+ .lab-vp-handle--s::after {
3179
+ left: 50%;
3180
+ top: 5px;
3181
+ width: 40px;
3182
+ height: 5px;
3183
+ transform: translateX(-50%);
3184
+ }
3185
+
3186
+ .lab-vp-handle--se {
3187
+ right: 0;
3188
+ bottom: 0;
3189
+ width: 20px;
3190
+ height: 20px;
3191
+ cursor: nwse-resize;
3192
+ }
3193
+
3194
+ .lab-vp-handle--se::after {
3195
+ top: 4px;
3196
+ left: 4px;
3197
+ width: 9px;
3198
+ height: 9px;
3199
+ }
3200
+
3201
+ /* ---- the pins on the frame, and the Comment hint --------------------------------------------
3202
+ Figma `Pin`: 24px at every zoom, its bottom-left corner the anchor, set on the element's top-left.
3203
+ Open pins are blue and resolved pins grey (ruled). The number and the ring are bg-primary, which
3204
+ passes on the blue in both themes where white would not in dark. Drawn by
3205
+ ../chrome/navbar-client.ts, in the chrome over the frame — never inside it. */
3206
+ :root {
3207
+ --lab-pin-open: #2563eb; /* Component colors/Utility/Blue/utility-blue-600 */
3208
+ --lab-pin-resolved: #737373; /* Colors/Foreground/fg-quaternary_hover */
3209
+ --lab-pins-bg: #eff6ff; /* utility-blue-50 — Comment while pin mode is on */
3210
+ --lab-pins-bg-hover: #dbeafe; /* utility-blue-100 */
3211
+ --lab-pins-border: #bfdbfe; /* utility-blue-200 */
3212
+ --lab-pins-text: #1d4ed8; /* utility-blue-700 */
3213
+ }
3214
+
3215
+ :root.dark {
3216
+ --lab-pin-open: #60a5fa; /* utility-blue-600, Dark mode */
3217
+ --lab-pin-resolved: #a3a3a3;
3218
+ --lab-pins-bg: #172554;
3219
+ --lab-pins-bg-hover: #1e3a8a;
3220
+ --lab-pins-border: #1e40af;
3221
+ --lab-pins-text: #93c5fd;
3222
+ }
3223
+
3224
+ .lab-vp-pins {
3225
+ position: absolute;
3226
+ inset: 0;
3227
+ z-index: 1;
3228
+ pointer-events: none;
3229
+ }
3230
+
3231
+ .lab-pin {
3232
+ position: absolute;
3233
+ display: grid;
3234
+ place-items: center;
3235
+ width: 24px;
3236
+ height: 24px;
3237
+ border: 2px solid var(--lab-bg-primary);
3238
+ border-radius: 12px 12px 12px 2px;
3239
+ background-color: var(--lab-pin-open);
3240
+ color: var(--lab-bg-primary);
3241
+ font-size: 12px;
3242
+ line-height: 18px;
3243
+ font-weight: 600;
3244
+ font-variant-numeric: tabular-nums;
3245
+ text-decoration: none;
3246
+ filter: drop-shadow(0 4px 3px rgba(0, 0, 0, 0.1)) drop-shadow(0 2px 2px rgba(0, 0, 0, 0.06));
3247
+ pointer-events: auto;
3248
+ cursor: pointer;
3249
+ }
3250
+
3251
+ .lab-pin[data-state="resolved"] {
3252
+ background-color: var(--lab-pin-resolved);
3253
+ }
3254
+
3255
+ /* While Comment is picking: what a click does, and how to stop. */
3256
+ .lab-vp-hint {
3257
+ position: absolute;
3258
+ top: var(--lab-space-sm);
3259
+ left: 50%;
3260
+ z-index: 3;
3261
+ margin: 0;
3262
+ padding: 3px var(--lab-space-lg);
3263
+ border-radius: var(--lab-radius-full);
3264
+ background-color: var(--lab-text-primary);
3265
+ color: var(--lab-bg-primary);
3266
+ font-size: 12px;
3267
+ line-height: 18px;
3268
+ font-weight: 600;
3269
+ white-space: nowrap;
3270
+ transform: translateX(-50%);
3271
+ pointer-events: none;
3272
+ }
3273
+
3274
+
3275
+ /* ---- the parameters drawer ---------------------------------------------------------------------
3276
+ On the stage's right edge, present only when the story registered parameters through
3277
+ @orbytes/astrolab/params. Built from the Figma components Parameters drawer (20074:3489), Drawer
3278
+ group (20072:259700) and Param row (20072:259490); the markup is ../chrome/params-client.ts.
3279
+ It overlays the canvas and never resizes it — resizing would change the preview's width and trip
3280
+ its breakpoints. Every rule here is anchored on .lab-params. */
3281
+ .lab-params {
3282
+ --lab-params-shadow: 0 20px 24px -4px rgba(0, 0, 0, 0.08), 0 8px 8px -4px rgba(0, 0, 0, 0.03),
3283
+ 0 3px 3px -1.5px rgba(0, 0, 0, 0.04); /* Shadows/shadow-xl */
3284
+ --lab-params-track: #e5e5e5; /* Colors/Background/bg-quaternary — the slider's empty track */
3285
+ --lab-params-switch-off: #f5f5f5; /* Colors/Background/bg-tertiary — a switch that is off */
3286
+ --lab-params-swatch-edge: rgba(0, 0, 0, 0.1); /* Component colors/Alpha/alpha-black-10 */
3287
+ --lab-params-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23a3a3a3' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
3288
+ position: absolute;
3289
+ top: 0;
3290
+ right: 0;
3291
+ bottom: 0;
1818
3292
  z-index: 2;
1819
3293
  display: flex;
1820
- gap: var(--lab-space-xs);
1821
- opacity: 0;
1822
- transition: opacity var(--lab-ease);
3294
+ flex-direction: column;
3295
+ width: 340px;
3296
+ max-width: 100%;
3297
+ background-color: var(--lab-bg-primary);
3298
+ border-left: 1px solid var(--lab-border-secondary);
3299
+ box-shadow: var(--lab-params-shadow);
3300
+ color: var(--lab-text-secondary);
3301
+ font-size: 14px;
3302
+ line-height: 20px;
1823
3303
  }
1824
3304
 
1825
- .lab-card:hover .lab-card__links,
1826
- .lab-card:focus-within .lab-card__links {
1827
- opacity: 1;
3305
+ :root.dark .lab-params {
3306
+ --lab-params-shadow: var(--lab-shadow-lg);
3307
+ --lab-params-track: #333333;
3308
+ --lab-params-switch-off: #262626;
3309
+ --lab-params-swatch-edge: rgba(255, 255, 255, 0.14);
1828
3310
  }
1829
3311
 
1830
- .lab-card__links .lab-icon-btn {
1831
- width: 28px;
1832
- height: 28px;
1833
- background-color: var(--lab-bg-primary);
1834
- border: 1px solid var(--lab-border-secondary);
1835
- box-shadow: var(--lab-shadow-xs);
3312
+ .lab-params[hidden] {
3313
+ display: none;
1836
3314
  }
1837
3315
 
1838
- .lab-card__links .lab-icon-btn .lab-icon {
1839
- width: 16px;
1840
- height: 16px;
3316
+ /* The header: title, the control count, close. */
3317
+ .lab-params .vpp__header {
3318
+ display: flex;
3319
+ flex: none;
3320
+ align-items: center;
3321
+ justify-content: space-between;
3322
+ gap: var(--lab-space-md);
3323
+ padding: var(--lab-space-lg) var(--lab-space-lg) var(--lab-space-lg) var(--lab-space-xl);
3324
+ border-bottom: 1px solid var(--lab-border-secondary);
1841
3325
  }
1842
3326
 
1843
- .lab-empty {
1844
- padding: 48px var(--lab-space-3xl);
1845
- border: 1px dashed var(--lab-border-primary);
1846
- border-radius: var(--lab-radius-xl);
1847
- color: var(--lab-text-quaternary);
1848
- text-align: center;
3327
+ .lab-params .vpp__heading {
3328
+ display: flex;
3329
+ align-items: center;
3330
+ gap: var(--lab-space-md);
3331
+ min-width: 0;
1849
3332
  }
1850
3333
 
1851
- .lab-empty strong {
1852
- display: block;
1853
- margin-bottom: var(--lab-space-xs);
3334
+ .lab-params .vpp__heading-title {
3335
+ margin: 0;
1854
3336
  color: var(--lab-text-primary);
1855
3337
  font-size: 16px;
1856
3338
  line-height: 24px;
3339
+ font-weight: 600;
1857
3340
  }
1858
3341
 
1859
- /* ---------------------------------------------------------------------------------------------
1860
- The viewport stage — the story or page in an <iframe> whose logical size you set. Zoom is a
1861
- transform on the wrapper, so the frame's own width (and so its breakpoints) never changes.
1862
- --------------------------------------------------------------------------------------------- */
1863
- .lab-vp-size {
1864
- display: inline-flex;
1865
- align-items: center;
1866
- gap: var(--lab-space-xs);
1867
- padding: 0 var(--lab-space-xs) 0 var(--lab-space-md);
1868
- min-height: 32px;
1869
- border: 1px solid var(--lab-border-primary);
1870
- border-radius: var(--lab-radius-md);
1871
- background-color: var(--lab-bg-primary);
1872
- box-shadow: var(--lab-shadow-xs);
1873
- color: var(--lab-text-quaternary);
1874
- font-size: 13px;
3342
+ .lab-params .vpp__count {
3343
+ padding: 2px 8px;
3344
+ border: 1px solid var(--lab-neutral-border);
3345
+ border-radius: var(--lab-radius-full);
3346
+ background-color: var(--lab-neutral-bg);
3347
+ color: var(--lab-neutral-text);
3348
+ font-size: 12px;
3349
+ line-height: 18px;
3350
+ font-weight: 500;
1875
3351
  font-variant-numeric: tabular-nums;
1876
3352
  }
1877
3353
 
1878
- .lab-vp-size input {
1879
- width: 3.4em;
3354
+ /* Close, Reset and Copy: the ghost tool button, 32 × 32. At rest the icon is #737373 (4.7:1), not
3355
+ the lighter fg-quaternary — an icon-only button needs 3:1. */
3356
+ .lab-params .vpp__icon-btn {
3357
+ display: inline-grid;
3358
+ flex: none;
3359
+ place-items: center;
3360
+ width: 32px;
3361
+ height: 32px;
1880
3362
  padding: 0;
1881
3363
  border: 0;
1882
- outline: none;
3364
+ border-radius: var(--lab-radius-md);
1883
3365
  background: transparent;
1884
- color: var(--lab-text-primary);
1885
- font: inherit;
1886
- font-weight: 600;
1887
- text-align: center;
1888
- appearance: textfield;
1889
- -moz-appearance: textfield;
3366
+ color: var(--lab-text-quaternary);
3367
+ cursor: pointer;
3368
+ transition: background-color var(--lab-ease), color var(--lab-ease);
1890
3369
  }
1891
3370
 
1892
- .lab-shell .lab-vp-size input:focus-visible {
1893
- box-shadow: none;
1894
- color: var(--lab-text-brand-secondary);
3371
+ .lab-params .vpp__icon-btn:hover {
3372
+ background-color: var(--lab-bg-hover);
3373
+ color: var(--lab-text-secondary);
1895
3374
  }
1896
3375
 
1897
- .lab-vp-size:focus-within {
1898
- border-color: var(--lab-fg-brand);
3376
+ /* The body scrolls; the header and footer stay put. */
3377
+ .lab-params .vpp__scroll {
3378
+ flex: 1 1 auto;
3379
+ min-height: 0;
3380
+ padding: 0 var(--lab-space-xl);
3381
+ overflow-y: auto;
3382
+ overscroll-behavior: contain;
1899
3383
  }
1900
3384
 
1901
- .lab-vp-size input::-webkit-outer-spin-button,
1902
- .lab-vp-size input::-webkit-inner-spin-button {
1903
- -webkit-appearance: none;
1904
- margin: 0;
3385
+ /* One registered group — a <details>. */
3386
+ .lab-params .vpp__group {
3387
+ position: relative;
3388
+ padding: var(--lab-space-lg) 0;
1905
3389
  }
1906
3390
 
1907
- .lab-vp-size .lab-icon-btn {
1908
- width: 26px;
1909
- height: 26px;
3391
+ .lab-params .vpp__group + .vpp__group {
3392
+ border-top: 1px solid var(--lab-border-secondary);
1910
3393
  }
1911
3394
 
1912
- .lab-vp-size .lab-icon-btn .lab-icon {
1913
- width: 14px;
1914
- height: 14px;
3395
+ .lab-params .vpp__summary {
3396
+ display: flex;
3397
+ align-items: center;
3398
+ gap: var(--lab-space-md);
3399
+ min-height: 32px;
3400
+ border-radius: var(--lab-radius-sm);
3401
+ list-style: none;
3402
+ cursor: pointer;
1915
3403
  }
1916
3404
 
1917
- .lab-vp-band {
1918
- font-variant-numeric: tabular-nums;
3405
+ .lab-params .vpp__summary::-webkit-details-marker {
3406
+ display: none;
1919
3407
  }
1920
3408
 
1921
- .lab-vp {
1922
- position: absolute;
1923
- inset: 0;
3409
+ .lab-params .vpp__group[open] > .vpp__summary {
3410
+ padding-right: 72px; /* room for Reset and Copy, which sit over this row */
1924
3411
  }
1925
3412
 
1926
- /* The preview's own light/dark, apart from the chrome's (decided 2026-09-24). The iframe's
1927
- color-scheme is what `prefers-color-scheme` reads inside it, so it is pinned here — or a dark
1928
- chrome would quietly turn every preview dark — and the preview theme switch sets it. */
1929
- .lab-vp-wrapper iframe,
1930
- .lab-card__thumb iframe {
1931
- color-scheme: light;
3413
+ /* With a source link the header row is 38 tall: the title on top, the link tucked under it. */
3414
+ .lab-params .vpp__group[open][data-source] > .vpp__summary {
3415
+ min-height: 38px;
1932
3416
  }
1933
3417
 
1934
- .lab-vp-wrapper iframe[data-theme="dark"] {
1935
- color-scheme: dark;
3418
+ .lab-params .vpp__group[open][data-source] .vpp__title {
3419
+ align-self: flex-start;
3420
+ }
3421
+
3422
+ .lab-params .vpp__chev {
3423
+ color: var(--lab-fg-quaternary);
3424
+ transition: transform var(--lab-ease);
1936
3425
  }
1937
3426
 
1938
- .lab-preview-theme-btn .lab-icon--moon,
1939
- .lab-preview-theme-btn[data-theme="dark"] .lab-icon--sun {
3427
+ .lab-params .vpp__group:not([open]) .vpp__chev {
3428
+ transform: rotate(-90deg);
3429
+ }
3430
+
3431
+ .lab-params .vpp__title {
3432
+ flex: 1 1 auto;
3433
+ min-width: 0;
3434
+ color: var(--lab-text-primary);
3435
+ font-size: 14px;
3436
+ line-height: 20px;
3437
+ font-weight: 600;
3438
+ overflow-wrap: anywhere;
3439
+ }
3440
+
3441
+ .lab-params .vpp__summary-count {
3442
+ flex: none;
3443
+ color: var(--lab-text-quaternary);
3444
+ font-size: 12px;
3445
+ line-height: 18px;
3446
+ font-weight: 500;
3447
+ }
3448
+
3449
+ .lab-params .vpp__group[open] .vpp__summary-count {
1940
3450
  display: none;
1941
3451
  }
1942
3452
 
1943
- .lab-preview-theme-btn[data-theme="dark"] .lab-icon--moon {
3453
+ .lab-params .vpp__source {
3454
+ position: relative;
1944
3455
  display: block;
3456
+ width: fit-content;
3457
+ max-width: calc(100% - 96px);
3458
+ margin: -18px 0 0 24px;
3459
+ overflow: hidden;
3460
+ color: var(--lab-text-brand-secondary);
3461
+ font-size: 12px;
3462
+ line-height: 18px;
3463
+ font-weight: 500;
3464
+ text-decoration: none;
3465
+ text-overflow: ellipsis;
3466
+ white-space: nowrap;
1945
3467
  }
1946
3468
 
1947
- /* The live size while a handle is being dragged. */
1948
- .lab-vp-live {
3469
+ .lab-params .vpp__source:hover {
3470
+ text-decoration: underline;
3471
+ }
3472
+
3473
+ .lab-params .vpp__actions {
1949
3474
  position: absolute;
1950
- right: 22px;
1951
- bottom: 22px;
1952
- z-index: 2;
1953
- display: none;
1954
- padding: 2px 8px;
1955
- border-radius: var(--lab-radius-sm);
1956
- background-color: var(--lab-text-primary);
1957
- color: var(--lab-bg-primary);
3475
+ top: var(--lab-space-lg);
3476
+ right: 0;
3477
+ display: flex;
3478
+ }
3479
+
3480
+ .lab-params .vpp__group[data-source] > .vpp__actions {
3481
+ top: calc(var(--lab-space-lg) + 3px); /* centred on the 38px header row */
3482
+ }
3483
+
3484
+ .lab-params .vpp__note {
3485
+ margin: var(--lab-space-md) 0 0;
3486
+ padding-left: 24px;
3487
+ color: var(--lab-text-tertiary);
1958
3488
  font-size: 12px;
1959
3489
  line-height: 18px;
1960
- font-weight: 600;
1961
- font-variant-numeric: tabular-nums;
1962
- pointer-events: none;
1963
3490
  }
1964
3491
 
1965
- .lab-vp[data-dragging] .lab-vp-live {
1966
- display: block;
3492
+ .lab-params .vpp__controls {
3493
+ display: flex;
3494
+ flex-direction: column;
3495
+ gap: var(--lab-space-md);
3496
+ margin-top: var(--lab-space-md);
3497
+ padding-top: var(--lab-space-xs);
1967
3498
  }
1968
3499
 
1969
- .lab-vp-stage {
1970
- position: absolute;
1971
- inset: 0;
3500
+ .lab-params .vpp__status {
3501
+ margin: var(--lab-space-md) 0 0;
3502
+ color: var(--lab-text-tertiary);
3503
+ font-size: 12px;
3504
+ line-height: 18px;
3505
+ white-space: pre-wrap;
3506
+ overflow-wrap: anywhere;
3507
+ }
3508
+
3509
+ /* One row per control: the label on the left, the control in a fixed 144px column so every
3510
+ control starts on one edge. The label wraps rather than truncates. */
3511
+ .lab-params .vpp__row {
1972
3512
  display: flex;
1973
- overflow: auto;
1974
- background-color: var(--lab-bg-secondary);
1975
- background-image: radial-gradient(color-mix(in srgb, var(--lab-fg-quaternary) 45%, transparent) 1px, transparent 1px);
1976
- background-size: 16px 16px;
3513
+ align-items: center;
3514
+ gap: var(--lab-space-lg);
3515
+ min-height: 32px;
1977
3516
  }
1978
3517
 
1979
- .lab-vp-box {
1980
- position: relative;
1981
- flex: none;
1982
- margin: auto;
1983
- padding: var(--lab-space-xl);
1984
- box-sizing: content-box;
1985
- overflow: hidden;
3518
+ .lab-params .vpp__label {
3519
+ display: flex;
3520
+ flex: 1 1 0;
3521
+ align-items: center;
3522
+ gap: var(--lab-space-xs);
3523
+ min-width: 0;
1986
3524
  }
1987
3525
 
1988
- .lab-vp-wrapper {
1989
- position: relative;
1990
- transform-origin: 0 0;
1991
- border-radius: 2px;
1992
- outline: 1px solid var(--lab-border-primary);
1993
- background-color: #fff;
1994
- box-shadow: var(--lab-shadow-lg);
3526
+ .lab-params .vpp__label-text {
3527
+ max-width: 136px;
3528
+ color: var(--lab-text-secondary);
3529
+ overflow-wrap: anywhere;
1995
3530
  }
1996
3531
 
1997
- .lab-vp-wrapper iframe {
1998
- display: block;
1999
- width: 100%;
2000
- height: 100%;
2001
- border: 0;
2002
- background-color: #fff;
3532
+ .lab-params .vpp__help {
3533
+ width: 12px;
3534
+ height: 12px;
3535
+ color: var(--lab-fg-quaternary);
2003
3536
  }
2004
3537
 
2005
- .lab-vp[data-dragging] .lab-vp-stage {
2006
- user-select: none;
3538
+ .lab-params .vpp__control {
3539
+ display: flex;
3540
+ flex: none;
3541
+ align-items: center;
3542
+ width: 144px;
2007
3543
  }
2008
3544
 
2009
- .lab-vp-handle {
2010
- position: absolute;
2011
- touch-action: none;
3545
+ .lab-params .vpp__row--range .vpp__control {
3546
+ gap: var(--lab-space-md);
2012
3547
  }
2013
3548
 
2014
- .lab-vp-handle::after {
2015
- content: "";
2016
- position: absolute;
2017
- border-radius: var(--lab-radius-full);
2018
- background-color: var(--lab-border-primary);
2019
- transition: background-color var(--lab-ease);
3549
+ /* Slider — unitless tuning values only. Track 6px, fill brand, a 16px handle. */
3550
+ .lab-params .vpp__range {
3551
+ flex: 1 1 0;
3552
+ min-width: 0;
3553
+ height: 20px;
3554
+ margin: 0;
3555
+ background: transparent;
3556
+ cursor: pointer;
3557
+ -webkit-appearance: none;
3558
+ appearance: none;
2020
3559
  }
2021
3560
 
2022
- .lab-vp-handle:hover::after,
2023
- .lab-vp[data-dragging="x"] .lab-vp-handle--e::after,
2024
- .lab-vp[data-dragging="y"] .lab-vp-handle--s::after,
2025
- .lab-vp[data-dragging="xy"] .lab-vp-handle--se::after {
2026
- background-color: var(--lab-fg-brand);
3561
+ .lab-params .vpp__range::-webkit-slider-runnable-track {
3562
+ height: 6px;
3563
+ border-radius: var(--lab-radius-full);
3564
+ background: linear-gradient(
3565
+ to right,
3566
+ var(--lab-brand-solid) var(--lab-params-fill, 0%),
3567
+ var(--lab-params-track) var(--lab-params-fill, 0%)
3568
+ );
2027
3569
  }
2028
3570
 
2029
- .lab-vp-handle--e {
2030
- top: 16px;
2031
- right: 0;
2032
- bottom: 16px;
3571
+ .lab-params .vpp__range::-webkit-slider-thumb {
3572
+ box-sizing: border-box;
2033
3573
  width: 16px;
2034
- cursor: ew-resize;
3574
+ height: 16px;
3575
+ margin-top: -5px;
3576
+ border: 2px solid var(--lab-brand-solid);
3577
+ border-radius: 50%;
3578
+ background-color: #ffffff;
3579
+ box-shadow: var(--lab-shadow-sm);
3580
+ -webkit-appearance: none;
3581
+ appearance: none;
2035
3582
  }
2036
3583
 
2037
- .lab-vp-handle--e::after {
2038
- top: 50%;
2039
- left: 5px;
2040
- width: 5px;
2041
- height: 40px;
2042
- transform: translateY(-50%);
3584
+ .lab-params .vpp__range::-moz-range-track {
3585
+ height: 6px;
3586
+ border-radius: var(--lab-radius-full);
3587
+ background-color: var(--lab-params-track);
2043
3588
  }
2044
3589
 
2045
- .lab-vp-handle--s {
2046
- left: 16px;
2047
- right: 16px;
2048
- bottom: 0;
3590
+ .lab-params .vpp__range::-moz-range-progress {
3591
+ height: 6px;
3592
+ border-radius: var(--lab-radius-full);
3593
+ background-color: var(--lab-brand-solid);
3594
+ }
3595
+
3596
+ .lab-params .vpp__range::-moz-range-thumb {
3597
+ box-sizing: border-box;
3598
+ width: 16px;
2049
3599
  height: 16px;
2050
- cursor: ns-resize;
3600
+ border: 2px solid var(--lab-brand-solid);
3601
+ border-radius: 50%;
3602
+ background-color: #ffffff;
3603
+ box-shadow: var(--lab-shadow-sm);
2051
3604
  }
2052
3605
 
2053
- .lab-vp-handle--s::after {
2054
- left: 50%;
2055
- top: 5px;
2056
- width: 40px;
2057
- height: 5px;
2058
- transform: translateX(-50%);
3606
+ .lab-params .vpp__range:focus-visible {
3607
+ box-shadow: none;
2059
3608
  }
2060
3609
 
2061
- .lab-vp-handle--se {
2062
- right: 0;
2063
- bottom: 0;
2064
- width: 20px;
2065
- height: 20px;
2066
- cursor: nwse-resize;
3610
+ .lab-params .vpp__range:focus-visible::-webkit-slider-thumb {
3611
+ box-shadow: var(--lab-focus-ring);
2067
3612
  }
2068
3613
 
2069
- .lab-vp-handle--se::after {
2070
- top: 4px;
2071
- left: 4px;
2072
- width: 9px;
2073
- height: 9px;
3614
+ .lab-params .vpp__range:focus-visible::-moz-range-thumb {
3615
+ box-shadow: var(--lab-focus-ring);
2074
3616
  }
2075
3617
 
2076
- /* The parameters panel: a drawer on the stage's right edge, present only when the story
2077
- registered parameters through @orbytes/astrolab/params. */
2078
- .lab-params {
2079
- position: absolute;
2080
- top: 0;
2081
- right: 0;
2082
- bottom: 0;
2083
- z-index: 2;
2084
- display: flex;
2085
- flex-direction: column;
2086
- gap: var(--lab-space-lg);
2087
- width: 340px;
2088
- padding: var(--lab-space-xl);
2089
- overflow: auto;
2090
- background-color: var(--lab-bg-primary);
2091
- border-left: 1px solid var(--lab-border-secondary);
2092
- box-shadow: var(--lab-shadow-lg);
2093
- font-size: 13px;
3618
+ .lab-params .vpp__readout {
3619
+ min-width: 40px;
3620
+ color: var(--lab-text-primary);
3621
+ font-size: 12px;
2094
3622
  line-height: 18px;
3623
+ font-weight: 500;
3624
+ font-variant-numeric: tabular-nums;
3625
+ text-align: right;
3626
+ white-space: nowrap;
2095
3627
  }
2096
3628
 
2097
- .lab-params[hidden] {
2098
- display: none;
3629
+ /* The outline field every other control sits in: 32px, border-primary, radius 8, shadow-xs. */
3630
+ .lab-params .vpp__field,
3631
+ .lab-params .vpp__select {
3632
+ box-sizing: border-box;
3633
+ flex: 1 1 0;
3634
+ min-width: 0;
3635
+ height: 32px;
3636
+ border: 1px solid var(--lab-border-primary);
3637
+ border-radius: var(--lab-radius-md);
3638
+ background-color: var(--lab-bg-primary);
3639
+ box-shadow: var(--lab-shadow-xs);
3640
+ color: var(--lab-text-primary);
3641
+ font: inherit;
3642
+ transition: border-color var(--lab-ease), box-shadow var(--lab-ease);
2099
3643
  }
2100
3644
 
2101
- .lab-params .vpp__group + .vpp__group {
2102
- padding-top: var(--lab-space-lg);
2103
- border-top: 1px solid var(--lab-border-secondary);
3645
+ .lab-params .vpp__field {
3646
+ display: flex;
3647
+ align-items: center;
3648
+ overflow: hidden;
2104
3649
  }
2105
3650
 
2106
- .lab-params .vpp__head {
2107
- display: flex;
2108
- flex-wrap: wrap;
2109
- align-items: baseline;
2110
- gap: var(--lab-space-xs) var(--lab-space-lg);
3651
+ .lab-params .vpp__field:focus-within,
3652
+ .lab-params .vpp__select:focus-visible {
3653
+ border-color: var(--lab-fg-brand);
3654
+ box-shadow: var(--lab-shadow-xs), 0 0 0 3px color-mix(in srgb, var(--lab-fg-brand) 22%, transparent);
2111
3655
  }
2112
3656
 
2113
- .lab-params .vpp__toggle {
2114
- display: inline-flex;
2115
- align-items: baseline;
2116
- gap: var(--lab-space-sm);
2117
- padding: 0;
3657
+ .lab-params .vpp__select {
3658
+ padding: 0 30px 0 12px;
3659
+ background: var(--lab-bg-primary) var(--lab-params-chevron) no-repeat right 10px center / 16px;
3660
+ font-weight: 500;
3661
+ text-overflow: ellipsis;
3662
+ cursor: pointer;
3663
+ -webkit-appearance: none;
3664
+ appearance: none;
3665
+ }
3666
+
3667
+ /* Number + unit — every size. The value is typed (↑ / ↓ step it); the unit is a select joined
3668
+ on its right. */
3669
+ .lab-params .vpp__number {
3670
+ flex: 1 1 0;
3671
+ min-width: 0;
3672
+ height: 100%;
3673
+ padding: 0 8px 0 12px;
2118
3674
  border: 0;
2119
3675
  background: transparent;
2120
3676
  color: var(--lab-text-primary);
2121
3677
  font: inherit;
2122
- font-weight: 600;
2123
- cursor: pointer;
3678
+ font-weight: 500;
3679
+ font-variant-numeric: tabular-nums;
3680
+ -moz-appearance: textfield;
3681
+ appearance: textfield;
2124
3682
  }
2125
3683
 
2126
- .lab-params .vpp__chev {
2127
- display: inline-block;
2128
- color: var(--lab-fg-quaternary);
2129
- transition: transform var(--lab-ease);
3684
+ .lab-params .vpp__number::-webkit-inner-spin-button,
3685
+ .lab-params .vpp__number::-webkit-outer-spin-button {
3686
+ margin: 0;
3687
+ -webkit-appearance: none;
2130
3688
  }
2131
3689
 
2132
- .lab-params .vpp__group[data-collapsed] .vpp__chev {
2133
- transform: rotate(-90deg);
3690
+ .lab-params .vpp__number:focus-visible {
3691
+ outline: none;
3692
+ box-shadow: none;
2134
3693
  }
2135
3694
 
2136
- .lab-params .vpp__group[data-collapsed] .vpp__body {
2137
- display: none;
3695
+ .lab-params .vpp__unit {
3696
+ flex: none;
3697
+ width: 64px;
3698
+ height: 100%;
3699
+ padding: 0 22px 0 10px;
3700
+ border: 0;
3701
+ border-left: 1px solid var(--lab-border-primary);
3702
+ border-radius: 0;
3703
+ background: transparent var(--lab-params-chevron) no-repeat right 6px center / 16px;
3704
+ color: var(--lab-text-secondary);
3705
+ font: inherit;
3706
+ cursor: pointer;
3707
+ -webkit-appearance: none;
3708
+ appearance: none;
2138
3709
  }
2139
3710
 
2140
- .lab-params .vpp__source {
2141
- color: var(--lab-text-quaternary);
3711
+ .lab-params .vpp__unit:focus-visible {
3712
+ outline: none;
3713
+ box-shadow: none;
3714
+ background-color: var(--lab-bg-hover);
2142
3715
  }
2143
3716
 
2144
- .lab-params .vpp__note {
2145
- flex-basis: 100%;
2146
- margin: 0;
2147
- color: var(--lab-text-quaternary);
3717
+ .lab-params .vpp__unit:disabled {
3718
+ background-image: none;
3719
+ color: var(--lab-text-secondary);
3720
+ opacity: 1;
3721
+ cursor: default;
2148
3722
  }
2149
3723
 
2150
- .lab-params .vpp__body {
2151
- display: flex;
2152
- flex-direction: column;
2153
- gap: var(--lab-space-lg);
2154
- margin-top: var(--lab-space-sm);
3724
+ /* Colour — the native picker drawn as a 20px swatch, and the hex beside it. */
3725
+ .lab-params .vpp__field--color {
3726
+ gap: var(--lab-space-md);
3727
+ padding: 0 10px 0 6px;
3728
+ cursor: pointer;
2155
3729
  }
2156
3730
 
2157
- .lab-params .vpp__controls {
2158
- display: flex;
2159
- flex-direction: column;
2160
- gap: var(--lab-space-lg);
3731
+ .lab-params .vpp__swatch {
3732
+ box-sizing: border-box;
3733
+ flex: none;
3734
+ width: 20px;
3735
+ height: 20px;
3736
+ padding: 0;
3737
+ border: 1px solid var(--lab-params-swatch-edge);
3738
+ border-radius: var(--lab-radius-full);
3739
+ background: none;
3740
+ overflow: hidden;
3741
+ cursor: pointer;
3742
+ -webkit-appearance: none;
3743
+ appearance: none;
2161
3744
  }
2162
3745
 
2163
- .lab-params .vpp__control {
2164
- display: grid;
2165
- grid-template-columns: 1fr auto;
2166
- gap: var(--lab-space-xs) var(--lab-space-md);
2167
- cursor: pointer;
3746
+ .lab-params .vpp__swatch::-webkit-color-swatch-wrapper {
3747
+ padding: 0;
2168
3748
  }
2169
3749
 
2170
- .lab-params .vpp__control input[type="range"] {
2171
- grid-column: 1 / -1;
2172
- width: 100%;
2173
- margin: 0;
2174
- accent-color: var(--lab-brand-solid);
3750
+ .lab-params .vpp__swatch::-webkit-color-swatch {
3751
+ border: 0;
3752
+ border-radius: var(--lab-radius-full);
2175
3753
  }
2176
3754
 
2177
- .lab-params .vpp__control--toggle {
2178
- display: flex;
2179
- align-items: center;
2180
- gap: var(--lab-space-md);
2181
- color: var(--lab-text-primary);
3755
+ .lab-params .vpp__swatch::-moz-color-swatch {
3756
+ border: 0;
3757
+ border-radius: var(--lab-radius-full);
2182
3758
  }
2183
3759
 
2184
- .lab-params .vpp__control--toggle input {
2185
- accent-color: var(--lab-brand-solid);
3760
+ .lab-params .vpp__swatch:focus-visible {
3761
+ outline: none;
3762
+ box-shadow: none;
2186
3763
  }
2187
3764
 
2188
- .lab-params .vpp__select,
2189
- .lab-params .vpp__swatch {
2190
- grid-column: 1 / -1;
2191
- width: 100%;
2192
- border: 1px solid var(--lab-border-primary);
2193
- border-radius: var(--lab-radius-sm);
2194
- background-color: var(--lab-bg-primary);
3765
+ .lab-params .vpp__hex {
3766
+ flex: 1 1 0;
3767
+ min-width: 0;
3768
+ overflow: hidden;
2195
3769
  color: var(--lab-text-primary);
2196
- font: inherit;
3770
+ font-variant-numeric: tabular-nums;
3771
+ text-overflow: ellipsis;
3772
+ text-transform: uppercase;
3773
+ white-space: nowrap;
2197
3774
  }
2198
3775
 
2199
- .lab-params .vpp__select {
2200
- padding: 4px 6px;
3776
+ /* Toggle — <input type="checkbox" role="switch">, the kit's switch at sm: 36 × 20. */
3777
+ .lab-params .vpp__switch {
3778
+ position: relative;
3779
+ box-sizing: border-box;
3780
+ flex: none;
3781
+ width: 36px;
3782
+ height: 20px;
3783
+ margin: 0;
3784
+ border: 0.5px solid var(--lab-border-secondary);
3785
+ border-radius: var(--lab-radius-full);
3786
+ background-color: var(--lab-params-switch-off);
3787
+ cursor: pointer;
3788
+ transition: background-color var(--lab-ease);
3789
+ -webkit-appearance: none;
3790
+ appearance: none;
2201
3791
  }
2202
3792
 
2203
- .lab-params .vpp__swatch {
2204
- height: 28px;
2205
- padding: 0;
2206
- cursor: pointer;
3793
+ .lab-params .vpp__switch::before {
3794
+ content: "";
3795
+ position: absolute;
3796
+ top: 1.5px;
3797
+ left: 1.5px;
3798
+ width: 16px;
3799
+ height: 16px;
3800
+ border-radius: 50%;
3801
+ background-color: #ffffff;
3802
+ box-shadow: var(--lab-shadow-sm);
3803
+ transition: transform var(--lab-ease);
2207
3804
  }
2208
3805
 
2209
- .lab-params .vpp__label {
2210
- color: var(--lab-text-tertiary);
3806
+ .lab-params .vpp__switch:checked {
3807
+ border-color: transparent;
3808
+ background-color: var(--lab-brand-solid);
2211
3809
  }
2212
3810
 
2213
- .lab-params .vpp__value {
2214
- color: var(--lab-text-primary);
2215
- font-variant-numeric: tabular-nums;
2216
- font-weight: 600;
3811
+ .lab-params .vpp__switch:checked::before {
3812
+ transform: translateX(16px);
2217
3813
  }
2218
3814
 
2219
- .lab-params .vpp__actions {
3815
+ /* The footer: one line saying what the last action did. Hidden until there is something to say. */
3816
+ .lab-params .vpp__footer {
2220
3817
  display: flex;
2221
- gap: var(--lab-space-sm);
3818
+ flex: none;
3819
+ align-items: center;
3820
+ gap: var(--lab-space-md);
3821
+ padding: 10px var(--lab-space-xl);
3822
+ border-top: 1px solid var(--lab-border-secondary);
2222
3823
  }
2223
3824
 
2224
- .lab-params .vpp__button {
2225
- padding: 4px 10px;
2226
- border: 1px solid var(--lab-border-primary);
2227
- border-radius: var(--lab-radius-sm);
2228
- background-color: var(--lab-bg-primary);
2229
- color: var(--lab-text-secondary);
2230
- font: inherit;
2231
- font-weight: 600;
2232
- cursor: pointer;
3825
+ .lab-params .vpp__footer[hidden] {
3826
+ display: none;
2233
3827
  }
2234
3828
 
2235
- .lab-params .vpp__status {
3829
+ .lab-params .vpp__footer-line {
3830
+ flex: 1 1 0;
3831
+ min-width: 0;
2236
3832
  margin: 0;
3833
+ overflow: hidden;
2237
3834
  color: var(--lab-text-quaternary);
2238
- white-space: pre-wrap;
3835
+ font-size: 12px;
3836
+ line-height: 18px;
3837
+ font-weight: 500;
3838
+ text-overflow: ellipsis;
3839
+ white-space: nowrap;
2239
3840
  }
2240
3841
 
2241
3842
  /* ---- the asset grid ------------------------------------------------------------------------ */