viberoom 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/ui/tokens.js CHANGED
@@ -15,6 +15,7 @@
15
15
  const at = (u, v) => Math.round(u + (v - u) * t);
16
16
  return `#${[at(x, i), at(y, j), at(z, k)].map((v) => v.toString(16).padStart(2, "0")).join("")}`;
17
17
  };
18
+ const NO_SHADOW = "0 0 0 0 transparent";
18
19
 
19
20
  const pop = {
20
21
  primary: "#5b5bf0",
@@ -111,7 +112,7 @@
111
112
  orchid: "#2a1a3a", orchidInk: "#c79bff", orchidMid: "#b58cf0",
112
113
  periwinkle: "#233126", periwinkleDeep: "#2f4234",
113
114
  deep: "#050705", deepGutter: "#0a0d0b", deepInk: "#d7e3d9", deepMuted: "#5b6f61", deepHead: "#c3d1c6", deepMeta: "#7c9282",
114
- lavGrey: "#3a4a3e", lilac: "#7ee0ff", seafoam: "#3ddc84", azure: "#5ab0ff", salmon: "#ff8578", magenta: "#ff7ab8",
115
+ lavGrey: "#5f7a67", lilac: "#7ee0ff", seafoam: "#3ddc84", azure: "#5ab0ff", salmon: "#ff8578", magenta: "#ff7ab8",
115
116
  };
116
117
 
117
118
  const classicDark = {
@@ -133,7 +134,7 @@
133
134
  orchid: "#2e2140", orchidInk: "#c9a3ff", orchidMid: "#b48cf0",
134
135
  periwinkle: "#34365c", periwinkleDeep: "#40437a",
135
136
  deep: "#0f1020", deepGutter: "#0b0c1a", deepInk: "#f4f2ff", deepMuted: "#7c78ad", deepHead: "#e8e6ff", deepMeta: "#a9a5d8",
136
- lavGrey: "#4a4b70", lilac: "#b9a7ff", seafoam: "#8fe3bd", azure: "#9cd2ff", salmon: "#ff9f9f", magenta: "#f2b8ff",
137
+ lavGrey: "#7c78ad", lilac: "#b9a7ff", seafoam: "#8fe3bd", azure: "#9cd2ff", salmon: "#ff9f9f", magenta: "#f2b8ff",
137
138
  };
138
139
 
139
140
  const clay = {
@@ -177,7 +178,7 @@
177
178
  orchid: "#e8e2ec", orchidInk: "#6b4f8c", orchidMid: "#8a6fa8",
178
179
  periwinkle: "#d9dbe6", periwinkleDeep: "#b9bdd0",
179
180
  deep: "#e9e3d6", deepGutter: "#e1dacb", deepInk: "#2b2a27", deepMuted: "#7a7468", deepHead: "#3a3833", deepMeta: "#6f6b62",
180
- lavGrey: "#c9c2b3", lilac: "#5b4a9a", seafoam: "#1f6e5a", azure: "#2a5d8f", salmon: "#a3472f", magenta: "#8a3f7a",
181
+ lavGrey: "#7a7468", lilac: "#5b4a9a", seafoam: "#1f6e5a", azure: "#2a5d8f", salmon: "#a3472f", magenta: "#8a3f7a",
181
182
  };
182
183
 
183
184
  const plush = {
@@ -271,6 +272,7 @@
271
272
  shadow: edge,
272
273
  bevel: "none",
273
274
  shadowRoom: "0px",
275
+ bevelPressed: "none",
274
276
  };
275
277
  };
276
278
 
@@ -284,8 +286,40 @@
284
286
  sbThumbHover: alpha(p.primary, 0.45),
285
287
  });
286
288
 
289
+ const fonts = {
290
+ text: {
291
+ nunito: { label: "Nunito (default)", stack: '"Nunito", "Segoe UI", system-ui, -apple-system, Roboto, sans-serif' },
292
+ inter: { label: "Inter", stack: '"Inter", "Segoe UI", system-ui, -apple-system, Roboto, sans-serif' },
293
+ "noto-sans": { label: "Noto Sans", stack: '"Noto Sans", "Segoe UI", system-ui, -apple-system, Roboto, sans-serif' },
294
+ "open-sans": { label: "Open Sans", stack: '"Open Sans", "Segoe UI", system-ui, -apple-system, Roboto, sans-serif' },
295
+ "source-sans-3": { label: "Source Sans 3", stack: '"Source Sans 3", "Segoe UI", system-ui, -apple-system, Roboto, sans-serif' },
296
+ "ibm-plex-sans": { label: "IBM Plex Sans", stack: '"IBM Plex Sans", "Segoe UI", system-ui, -apple-system, Roboto, sans-serif' },
297
+ manrope: { label: "Manrope", stack: '"Manrope", "Segoe UI", system-ui, -apple-system, Roboto, sans-serif' },
298
+ rubik: { label: "Rubik", stack: '"Rubik", "Segoe UI", system-ui, -apple-system, Roboto, sans-serif' },
299
+ montserrat: { label: "Montserrat", stack: '"Montserrat", "Segoe UI", system-ui, -apple-system, Roboto, sans-serif' },
300
+ "golos-text": { label: "Golos Text", stack: '"Golos Text", "Segoe UI", system-ui, -apple-system, Roboto, sans-serif' },
301
+ "exo-2": { label: "Exo 2", stack: '"Exo 2", "Segoe UI", system-ui, -apple-system, Roboto, sans-serif' },
302
+ comfortaa: { label: "Comfortaa", stack: '"Comfortaa", "Segoe UI", system-ui, -apple-system, Roboto, sans-serif' },
303
+ "ubuntu-sans": { label: "Ubuntu Sans", stack: '"Ubuntu Sans", "Segoe UI", system-ui, -apple-system, Roboto, sans-serif' },
304
+ arial: { label: "Arial / Helvetica (system)", stack: 'Arial, Helvetica, "Liberation Sans", sans-serif' },
305
+ system: { label: "System UI font", stack: 'system-ui, -apple-system, "Segoe UI", Roboto, Cantarell, sans-serif' },
306
+ },
307
+ mono: {
308
+ "jetbrains-mono": { label: "JetBrains Mono (default)", stack: '"JetBrains Mono", ui-monospace, Consolas, Menlo, "DejaVu Sans Mono", monospace' },
309
+ "fira-code": { label: "Fira Code", stack: '"Fira Code", ui-monospace, Consolas, Menlo, "DejaVu Sans Mono", monospace' },
310
+ "source-code-pro": { label: "Source Code Pro", stack: '"Source Code Pro", ui-monospace, Consolas, Menlo, "DejaVu Sans Mono", monospace' },
311
+ "ibm-plex-mono": { label: "IBM Plex Mono", stack: '"IBM Plex Mono", ui-monospace, Consolas, Menlo, "DejaVu Sans Mono", monospace' },
312
+ "pt-mono": { label: "PT Mono", stack: '"PT Mono", ui-monospace, Consolas, Menlo, "DejaVu Sans Mono", monospace' },
313
+ "victor-mono": { label: "Victor Mono", stack: '"Victor Mono", ui-monospace, Consolas, Menlo, "DejaVu Sans Mono", monospace' },
314
+ "anonymous-pro": { label: "Anonymous Pro", stack: '"Anonymous Pro", ui-monospace, Consolas, Menlo, "DejaVu Sans Mono", monospace' },
315
+ "cascadia-code": { label: "Cascadia Code", stack: '"Cascadia Code", ui-monospace, Consolas, Menlo, "DejaVu Sans Mono", monospace' },
316
+ system: { label: "System monospace", stack: 'ui-monospace, Consolas, Menlo, "DejaVu Sans Mono", "Courier New", monospace' },
317
+ },
318
+ };
319
+
287
320
  const shape = {
288
321
  rScale: "1",
322
+ rCtlMin: "0px",
289
323
  rXs: "calc(8px * var(--r-scale))", rSm: "calc(12px * var(--r-scale))", rMd: "calc(14px * var(--r-scale))", rLg: "calc(18px * var(--r-scale))", rXl: "calc(18px * var(--r-scale))", rPill: "calc(99px * var(--r-scale))",
290
324
  };
291
325
  const type = {
@@ -306,7 +340,9 @@
306
340
  easePop: "cubic-bezier(0.34, 1.56, 0.64, 1)",
307
341
  };
308
342
 
309
- const elementsOf = (p) => ({
343
+ const elementsOf = (p) => {
344
+ const dangerSolid = `0 8px 18px -8px ${alpha(p.roseInk, 0.8)}`;
345
+ return {
310
346
  page: {
311
347
  bg: p.bg,
312
348
  ink: p.ink,
@@ -326,6 +362,7 @@
326
362
  hoverInk: p.primary,
327
363
  onPrimary: p.white,
328
364
  secondaryBg: p.white,
365
+ secondaryHoverBg: p.white,
329
366
  softBg: p.lav,
330
367
  softHoverBg: p.lav2,
331
368
  ghostInk: p.muted,
@@ -335,14 +372,14 @@
335
372
  dangerHoverBg: p.rose2,
336
373
  dangerSolidInk: p.white,
337
374
  dangerSolidHoverBg: p.roseDark,
338
- dangerSolidShadow: `0 8px 18px -8px ${alpha(p.roseInk, 0.8)}`,
375
+ dangerSolidShadow: dangerSolid,
339
376
  spinnerTrack: alpha(p.white, 0.5),
340
377
  spinnerHead: p.white,
341
378
  spinnerTrackQuiet: p.lav2,
342
379
  spinnerHeadQuiet: p.primary,
343
380
  disabledOpacity: "0.45",
344
- darkBg: alpha(p.white, 0.12),
345
- darkInk: p.white,
381
+ darkBg: alpha(p.deepInk, 0.12),
382
+ darkInk: p.deepInk,
346
383
  darkHoverBg: p.primary,
347
384
  warnInk: p.warmInk,
348
385
  warnHoverBg: p.warm,
@@ -356,6 +393,23 @@
356
393
  paperBg: p.white,
357
394
  paperInk: p.ink3,
358
395
  paperHoverBg: p.soft,
396
+ border: "0",
397
+ case: "none",
398
+ tracking: "0px",
399
+ linkUnderline: "none",
400
+ ghostBg: "transparent",
401
+ shadow: "none",
402
+ hoverShadow: "none",
403
+ hoverLift: "0px",
404
+ activeShadow: "none",
405
+ primaryActiveShadow: "var(--shadow-primary)",
406
+ primaryHoverFilter: "brightness(1.05)",
407
+ secondaryHoverShadow: "var(--shadow-tile)",
408
+ secondaryActiveShadow: "var(--shadow-tile)",
409
+ dangerSolidHoverShadow: dangerSolid,
410
+ dangerSolidActiveShadow: dangerSolid,
411
+ okShadow: "none",
412
+ warnShadow: "none",
359
413
  h: "42px",
360
414
  hSm: "34px",
361
415
  hXs: "24px",
@@ -384,6 +438,14 @@
384
438
  inlineInk: p.ink3,
385
439
  inlineOnBg: alpha(p.primary, 0.1),
386
440
  inlineOnInk: p.primary,
441
+ ghostBg: "transparent",
442
+ shadow: "none",
443
+ hoverShadow: "none",
444
+ hoverLift: "0px",
445
+ activeShadow: "none",
446
+ primaryShadow: "var(--shadow-primary)",
447
+ primaryHoverShadow: "var(--shadow-primary)",
448
+ primaryActiveShadow: "var(--shadow-primary)",
387
449
  },
388
450
  badge: {
389
451
  bg: p.lav,
@@ -393,23 +455,37 @@
393
455
  mutedBg: p.softer,
394
456
  mutedInk: p.muted,
395
457
  outlineBorder: p.lav2,
458
+ outlineW: "2px",
396
459
  outlineInk: p.muted,
397
460
  fsXs: "calc(10px * var(--fs-scale))",
461
+ fill: "100%",
462
+ border: "0",
463
+ case: "none",
464
+ tracking: "0px",
465
+ shadow: "none",
398
466
  },
399
467
  chip: {
400
468
  bg: p.lav,
401
469
  ink: p.primary,
402
470
  hoverBg: p.lav2,
471
+ fill: "100%",
472
+ border: "0",
473
+ shadow: "none",
474
+ hoverShadow: "none",
475
+ hoverLift: "0px",
476
+ activeShadow: "none",
477
+ activeDrop: "0px",
403
478
  },
404
479
  fileCard: {
405
480
  bg: p.deep,
406
481
  ink: p.deepInk,
407
- headBg: alpha(p.white, 0.06),
482
+ headBg: alpha(p.deepInk, 0.06),
408
483
  headInk: p.deepHead,
409
484
  headMeta: p.deepMeta,
410
485
  noteInk: p.roseInk,
411
- imageBodyBg: alpha(p.white, 0.04),
486
+ imageBodyBg: alpha(p.deepInk, 0.04),
412
487
  imageBg: p.white,
488
+ shadow: "var(--edge)",
413
489
  },
414
490
  toolCall: {
415
491
  readyBg: p.mint,
@@ -435,6 +511,7 @@
435
511
  hoverInk: p.primary,
436
512
  failedInk: p.roseInk,
437
513
  fs: "calc(11px * var(--fs-scale))",
514
+ shadow: "none",
438
515
  },
439
516
  askCard: {
440
517
  bg: p.warm,
@@ -445,6 +522,7 @@
445
522
  kindFs: "calc(11px * var(--fs-scale))",
446
523
  inputFs: "calc(11px * var(--fs-scale))",
447
524
  outcomeFs: "calc(12px * var(--fs-scale))",
525
+ shadow: "none",
448
526
  },
449
527
  replyNote: {
450
528
  ink: p.muted,
@@ -461,6 +539,7 @@
461
539
  border: p.lav2,
462
540
  hoverBorder: p.periwinkleDeep,
463
541
  onBorder: p.primary,
542
+ shadow: "none",
464
543
  },
465
544
  choice: {
466
545
  bg: p.soft,
@@ -470,6 +549,13 @@
470
549
  onBorder: p.primary,
471
550
  quietInk: p.muted,
472
551
  h: "32px",
552
+ border: "transparent",
553
+ shadow: "none",
554
+ hoverShadow: "none",
555
+ hoverLift: "0px",
556
+ activeShadow: "none",
557
+ activeDrop: "0px",
558
+ onShadow: "none",
473
559
  },
474
560
  control: {
475
561
  knob: p.white,
@@ -487,6 +573,10 @@
487
573
  gradFrom: "72% 66%",
488
574
  gradTo: "68% 52%",
489
575
  emoji: "70% 93%",
576
+ shadow: "none",
577
+ radius: "calc(14px * var(--r-scale))",
578
+ radiusLg: "calc(16px * var(--r-scale))",
579
+ radiusRail: "calc(13px * var(--r-scale))",
490
580
  },
491
581
  pop: {
492
582
  bg: p.white,
@@ -573,21 +663,21 @@
573
663
  gutterInk: p.deepMuted,
574
664
  lineMarkInk: p.honey,
575
665
  lineMarkBg: alpha(p.honey, 0.16),
576
- headBg: alpha(p.white, 0.06),
666
+ headBg: alpha(p.deepInk, 0.06),
577
667
  headInk: p.deepHead,
578
668
  headMeta: p.deepMeta,
579
- headBtnBg: alpha(p.white, 0.12),
580
- headBtnInk: p.white,
581
- bodyBg: alpha(p.white, 0.04),
669
+ headBtnBg: alpha(p.deepInk, 0.12),
670
+ headBtnInk: p.deepInk,
671
+ bodyBg: alpha(p.deepInk, 0.04),
582
672
  sbTrack: alpha(p.black, 0.25),
583
- sbThumb: alpha(p.white, 0.3),
584
- sbThumbHover: alpha(p.white, 0.5),
585
- sbFirefox: alpha(p.white, 0.35),
673
+ sbThumb: alpha(p.deepInk, 0.3),
674
+ sbThumbHover: alpha(p.deepInk, 0.5),
675
+ sbFirefox: alpha(p.deepInk, 0.35),
586
676
  findHitBg: p.honey,
587
677
  findHitInk: p.deep,
588
678
  },
589
679
  syntax: {
590
- comment: p.muted,
680
+ comment: p.deepMuted,
591
681
  punctuation: p.lavGrey,
592
682
  keyword: p.lilac,
593
683
  string: p.seafoam,
@@ -624,6 +714,8 @@
624
714
  avatarDefault: p.primary,
625
715
  avatarGloss: p.white,
626
716
  avatarLabelInk: p.white,
717
+ shadow: NO_SHADOW,
718
+ cardShadow: "var(--shadow-pop)",
627
719
  },
628
720
  logoTile: {
629
721
  bg: p.white,
@@ -636,6 +728,11 @@
636
728
  sm: "30px",
637
729
  md: "36px",
638
730
  lg: "40px",
731
+ shadow: "var(--shadow-tile)",
732
+ radius: "calc(12px * var(--r-scale))",
733
+ radiusSm: "calc(10px * var(--r-scale))",
734
+ radiusLg: "calc(13px * var(--r-scale))",
735
+ radiusBadge: "calc(6px * var(--r-scale))",
639
736
  },
640
737
  unseenLine: {
641
738
  ink: p.pink,
@@ -643,6 +740,10 @@
643
740
  line: `url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='8' viewBox='0 0 16 8'><path d='M0 4 Q4 0 8 4 T16 4' fill='none' stroke='${p.pink.replace("#", "%23")}' stroke-width='1.6'/></svg>") repeat-x center / 16px 8px`,
644
741
  lineH: "8px",
645
742
  lineOpacity: "0.55",
743
+ labelBorder: "0",
744
+ labelCase: "none",
745
+ labelTracking: "0px",
746
+ labelShadow: "none",
646
747
  },
647
748
  numberField: {
648
749
  stepW: "22px",
@@ -661,12 +762,14 @@
661
762
  rowButton: {
662
763
  size: "24px",
663
764
  icon: "14px",
664
- radius: "8px",
765
+ radius: "calc(8px * var(--r-scale))",
665
766
  bg: p.softer,
666
767
  ink: p.muted,
667
768
  hoverBg: p.primary,
668
769
  hoverInk: p.white,
669
770
  disabledOpacity: "0.4",
771
+ shadow: "none",
772
+ activeShadow: "none",
670
773
  },
671
774
  hubRow: {
672
775
  ink: p.muted,
@@ -676,6 +779,9 @@
676
779
  actionInk: p.mintInk,
677
780
  actionHoverBg: p.mint2,
678
781
  faceSize: "18px",
782
+ prefix: "none",
783
+ prefixGap: "0px",
784
+ actionShadow: "var(--edge)",
679
785
  },
680
786
  hero: {
681
787
  ink: p.white,
@@ -699,6 +805,13 @@
699
805
  feature: {
700
806
  emojiBg: alpha(p.white, 0.8),
701
807
  peachBg: p.peach2,
808
+ shadow: "var(--edge)",
809
+ },
810
+ composer: {
811
+ shadow: "none",
812
+ bg: "transparent",
813
+ radius: "0px",
814
+ padX: "0px",
702
815
  },
703
816
  step: {
704
817
  bg: p.white,
@@ -711,7 +824,8 @@
711
824
  browser: {
712
825
  themeColor: p.iris,
713
826
  },
714
- });
827
+ };
828
+ };
715
829
 
716
830
  const popNodes = [
717
831
  { fill: "#e4e6fb", stroke: "#5b5bf0" },
@@ -749,6 +863,8 @@
749
863
  type: { ...type, ...(wants.type || {}) },
750
864
  motion: { ...motion, ...(wants.motion || {}) },
751
865
  elements,
866
+ wants,
867
+ custom: false,
752
868
  };
753
869
  };
754
870
 
@@ -772,48 +888,72 @@
772
888
  code: { headBg: alpha(p.ink, 0.04), headBtnBg: alpha(p.ink, 0.08), headBtnInk: p.ink, bodyBg: alpha(p.ink, 0.02), sbTrack: alpha(p.ink, 0.06), sbThumb: alpha(p.ink, 0.3) },
773
889
  bubble: { mineBg: `linear-gradient(135deg, ${p.primaryLight}, ${p.primaryDeep})`, shadow: `0 1px 2px ${alpha(p.ink, 0.06)}`, mineShadow: "none" },
774
890
  logoTile: { bg: p.white, ink: p.ink },
891
+ btn: { darkHoverBg: p.lav2 },
775
892
  }),
776
893
  }),
777
- plush: look("plush", "3D clayful", "light", plush, {
778
- shape: { rScale: "1.6" },
779
- canvas: (p) => ({
780
- gradCanvas: `radial-gradient(720px 480px at 100% 100%, ${alpha(p.ember, 0.14)}, transparent 70%), radial-gradient(640px 420px at 0% 0%, ${alpha(p.primaryLight, 0.16)}, transparent 70%), linear-gradient(165deg, ${p.canvasTop} 0%, ${p.canvasMid} 55%, ${p.canvasBottom} 100%)`,
781
- canvasPattern: "none",
782
- canvasPatternSize: "0 0",
783
- }),
784
- elevation: (() => {
785
- const p = plush;
786
- const warm = (a) => alpha(p.shadowInk, a);
787
- const light = (a) => alpha(p.white, a);
788
- const dark = (a) => alpha(p.black, a);
789
- const puff = `inset 0 1.5px 2px ${light(0.95)}, inset 1px 0 1px ${light(0.5)}, inset 0 -4px 5px -2px ${warm(0.2)}, inset -1px 0 2px -1px ${warm(0.08)}, 0 0 0 1px ${warm(0.07)}, 0 1px 2px ${warm(0.14)}, 0 6px 12px -4px ${warm(0.3)}`;
790
- const tinted = `inset 0 1.5px 2px ${light(0.6)}, inset 1px 0 1px ${light(0.35)}, inset 0 -4px 5px -2px ${dark(0.16)}, inset -1px 0 2px -1px ${dark(0.06)}, 0 0 0 1px ${warm(0.08)}, 0 1px 2px ${warm(0.16)}, 0 6px 12px -4px ${warm(0.32)}`;
791
- const panel = `inset 0 2px 4px ${light(0.95)}, inset 0 -8px 12px -3px ${warm(0.18)}, inset 2px 0 3px -1px ${light(0.5)}, 0 3px 6px ${warm(0.1)}, 0 16px 32px -8px ${warm(0.28)}`;
792
- const frame = `inset 0 2px 4px ${light(0.95)}, inset 0 -10px 14px -4px ${warm(0.2)}, 0 6px 10px ${warm(0.12)}, 0 30px 60px -16px ${warm(0.4)}`;
793
- const sunk = `inset 0 3px 6px ${warm(0.18)}, inset 0 -1px 2px ${light(0.8)}`;
794
- const lifted = `inset 0 1.5px 2px ${light(0.95)}, inset 1px 0 1px ${light(0.5)}, inset 0 -4px 5px -2px ${warm(0.2)}, inset -1px 0 2px -1px ${warm(0.08)}, 0 0 0 1px ${warm(0.07)}, 0 2px 4px ${warm(0.16)}, 0 10px 18px -5px ${warm(0.34)}`;
795
- return {
894
+ plush: (() => {
895
+ const p = plush;
896
+ const warm = (a) => alpha(p.shadowInk, a);
897
+ const light = (a) => alpha(p.white, a);
898
+ const dark = (a) => alpha(p.black, a);
899
+ const puff = `inset 0 1.5px 2px ${light(0.95)}, inset 1px 0 1px ${light(0.5)}, inset 0 -4px 5px -2px ${warm(0.2)}, inset -1px 0 2px -1px ${warm(0.08)}, 0 0 0 1px ${warm(0.07)}, 0 1px 2px ${warm(0.14)}, 0 6px 12px -4px ${warm(0.3)}`;
900
+ const tinted = `inset 0 1.5px 2px ${light(0.6)}, inset 1px 0 1px ${light(0.35)}, inset 0 -4px 5px -2px ${dark(0.16)}, inset -1px 0 2px -1px ${dark(0.06)}, 0 0 0 1px ${warm(0.08)}, 0 1px 2px ${warm(0.16)}, 0 6px 12px -4px ${warm(0.32)}`;
901
+ const panel = `inset 0 2px 4px ${light(0.95)}, inset 0 -8px 12px -3px ${warm(0.18)}, inset 2px 0 3px -1px ${light(0.5)}, 0 3px 6px ${warm(0.1)}, 0 16px 32px -8px ${warm(0.28)}`;
902
+ const frame = `inset 0 2px 4px ${light(0.95)}, inset 0 -10px 14px -4px ${warm(0.2)}, 0 6px 10px ${warm(0.12)}, 0 30px 60px -16px ${warm(0.4)}`;
903
+ const sunk = `inset 0 3px 6px ${warm(0.18)}, inset 0 -1px 2px ${light(0.8)}`;
904
+ const lifted = `inset 0 1.5px 2px ${light(0.95)}, inset 1px 0 1px ${light(0.5)}, inset 0 -4px 5px -2px ${warm(0.2)}, inset -1px 0 2px -1px ${warm(0.08)}, 0 0 0 1px ${warm(0.07)}, 0 2px 4px ${warm(0.16)}, 0 10px 18px -5px ${warm(0.34)}`;
905
+ return look("plush", "3D clayful", "light", plush, {
906
+ shape: { rScale: "1.6", rCtlMin: "var(--r-pill)" },
907
+ canvas: (p) => ({
908
+ gradCanvas: `radial-gradient(720px 480px at 100% 100%, ${alpha(p.ember, 0.14)}, transparent 70%), radial-gradient(640px 420px at 0% 0%, ${alpha(p.primaryLight, 0.16)}, transparent 70%), linear-gradient(165deg, ${p.canvasTop} 0%, ${p.canvasMid} 55%, ${p.canvasBottom} 100%)`,
909
+ canvasPattern: "none",
910
+ canvasPatternSize: "0 0",
911
+ }),
912
+ elevation: {
796
913
  edge: panel, shadowTile: puff, shadowPop: panel, shadowPrimary: tinted, shadowDialog: frame,
797
914
  shadowInset: sunk, shadowCtl: sunk, shadowCtlHover: lifted, shadowCard: panel, shadowSoft: tinted,
798
915
  shadow: puff, shadow1: panel, shadow2: frame, shadowGlow: tinted,
799
916
  bevel: `linear-gradient(180deg, ${light(0.3)} 0%, ${light(0)} 45%)`,
800
917
  shadowRoom: "20px",
801
- };
802
- })(),
803
- elements: (p) => ({
804
- bubble: {
805
- shadow: `inset 0 2px 3px ${alpha(p.white, 0.95)}, inset 2px 0 2px -1px ${alpha(p.white, 0.6)}, inset 0 -6px 8px -2px ${alpha(p.shadowInk, 0.2)}, inset -2px 0 4px -2px ${alpha(p.shadowInk, 0.1)}, 0 2px 4px ${alpha(p.shadowInk, 0.12)}, 0 10px 18px -4px ${alpha(p.shadowInk, 0.26)}`,
806
- bg: p.soft,
807
- mineBg: p.primary,
808
- mineCtaInk: p.primaryDark,
809
- mineShadow: `inset 0 2px 3px ${alpha(p.white, 0.6)}, inset 2px 0 2px -1px ${alpha(p.white, 0.35)}, inset 0 -6px 8px -2px ${alpha(p.black, 0.16)}, inset -2px 0 4px -2px ${alpha(p.black, 0.06)}, 0 2px 4px ${alpha(p.shadowInk, 0.14)}, 0 10px 18px -4px ${alpha(p.shadowInk, 0.3)}`,
810
918
  },
811
- input: { shadow: `inset 0 3px 6px ${alpha(p.shadowInk, 0.18)}, inset 0 -1px 2px ${alpha(p.white, 0.8)}` },
812
- logoTile: { bg: p.soft, badgeBg: p.soft },
813
- face: { tint: "34%", corner: "0.2" },
814
- btn: { hoverInk: p.primaryDark }, iconButton: { hoverInk: p.primaryDark }, badge: { ink: p.primaryDark }, chip: { ink: p.primaryDark }, toolCall: { pendingInk: p.primaryDark }, toolFold: { hoverInk: p.primaryDark }, numberField: { stepHoverInk: p.primaryDark },
815
- }),
816
- }),
919
+ elements: (p) => ({
920
+ bubble: {
921
+ shadow: `inset 0 2px 3px ${alpha(p.white, 0.95)}, inset 2px 0 2px -1px ${alpha(p.white, 0.6)}, inset 0 -6px 8px -2px ${alpha(p.shadowInk, 0.2)}, inset -2px 0 4px -2px ${alpha(p.shadowInk, 0.1)}, 0 2px 4px ${alpha(p.shadowInk, 0.12)}, 0 10px 18px -4px ${alpha(p.shadowInk, 0.26)}`,
922
+ bg: p.soft,
923
+ mineBg: `linear-gradient(${p.primary}, ${p.primary})`,
924
+ mineCtaInk: p.primaryDark,
925
+ mineShadow: `inset 0 2px 3px ${alpha(p.white, 0.6)}, inset 2px 0 2px -1px ${alpha(p.white, 0.35)}, inset 0 -6px 8px -2px ${alpha(p.black, 0.16)}, inset -2px 0 4px -2px ${alpha(p.black, 0.06)}, 0 2px 4px ${alpha(p.shadowInk, 0.14)}, 0 10px 18px -4px ${alpha(p.shadowInk, 0.3)}`,
926
+ },
927
+ input: { shadow: sunk },
928
+ composer: { shadow: sunk, bg: p.softer, radius: "calc(14px * var(--r-scale))", padX: "12px" },
929
+ logoTile: { bg: p.soft, badgeBg: p.soft, shadow: puff, radius: "calc(8px * var(--r-scale))", radiusSm: "calc(7px * var(--r-scale))", radiusLg: "calc(9px * var(--r-scale))", radiusBadge: "calc(8px * var(--r-scale))" },
930
+ face: { tint: "34%", corner: "0.2", shadow: tinted, cardShadow: tinted },
931
+ btn: {
932
+ hoverInk: p.primaryDark,
933
+ shadow: puff, hoverShadow: lifted, hoverLift: "-1px", activeShadow: sunk,
934
+ primaryActiveShadow: sunk, primaryHoverFilter: "none",
935
+ secondaryHoverShadow: lifted, secondaryActiveShadow: sunk,
936
+ dangerSolidShadow: tinted, dangerSolidHoverShadow: lifted, dangerSolidActiveShadow: sunk,
937
+ okShadow: tinted, warnShadow: tinted,
938
+ ghostBg: p.softer, softBg: p.softer, secondaryBg: p.softer, paperBg: p.softer,
939
+ },
940
+ iconButton: { hoverInk: p.primaryDark, ghostInk: p.muted, shadow: puff, hoverShadow: lifted, hoverLift: "-1px", activeShadow: sunk, primaryShadow: puff, primaryHoverShadow: lifted, primaryActiveShadow: sunk, ghostBg: p.softer },
941
+ rowButton: { shadow: puff, activeShadow: sunk, radius: "8px" },
942
+ badge: { ink: p.primaryDark, shadow: tinted },
943
+ chip: { ink: p.primaryDark, shadow: tinted, hoverShadow: lifted, hoverLift: "-1px", activeShadow: sunk, activeDrop: "1px" },
944
+ choice: { shadow: tinted, hoverShadow: lifted, hoverLift: "-1px", activeShadow: sunk, activeDrop: "1px", onShadow: sunk },
945
+ hubRow: { actionShadow: tinted },
946
+ unseenLine: { labelShadow: tinted },
947
+ lookCard: { shadow: puff },
948
+ fileCard: { shadow: puff },
949
+ askCard: { shadow: puff },
950
+ toolFold: { hoverInk: p.primaryDark, shadow: puff },
951
+ roomMark: { shadow: tinted, radius: "calc(10px * var(--r-scale))", radiusLg: "calc(12px * var(--r-scale))", radiusRail: "calc(10px * var(--r-scale))" },
952
+ feature: { shadow: panel },
953
+ toolCall: { pendingInk: p.primaryDark }, numberField: { stepHoverInk: p.primaryDark },
954
+ }),
955
+ });
956
+ })(),
817
957
  terminal: look("terminal", "Terminal", "dark", terminal, {
818
958
  shape: { rScale: "0" },
819
959
  type: { font: type.mono },
@@ -824,8 +964,13 @@
824
964
  roomMark: { ink: p.ink, gradFrom: "38% 24%", gradTo: "34% 18%", emoji: "35% 20%" },
825
965
  tile: { badgeBg: p.lav2 },
826
966
  logoTile: { bg: p.lav2, ink: p.primary, badgeBg: p.lav2, badgeBorder: p.lav2, badgeInk: p.primary },
827
- bubble: { border: p.lav2, shadow: "none", mineShadow: "none" },
828
- unseenLine: { ink: p.muted, bg: p.soft, line: `repeating-linear-gradient(90deg, ${p.lav2} 0 6px, transparent 6px 12px)`, lineH: "1px", lineOpacity: "1" },
967
+ bubble: { border: p.lav2, shadow: NO_SHADOW, mineShadow: NO_SHADOW },
968
+ btn: { border: "1px solid currentColor", case: "uppercase", tracking: "0.06em", linkUnderline: "underline" },
969
+ badge: { fill: "0%", border: "1px solid currentColor", case: "uppercase", tracking: "0.04em", outlineBorder: "currentColor", outlineW: "1px" },
970
+ chip: { fill: "0%", border: "1px solid currentColor" },
971
+ choice: { border: "currentColor", onBorder: "currentColor" },
972
+ hubRow: { prefix: '">"', prefixGap: "6px" },
973
+ unseenLine: { ink: p.muted, bg: "transparent", line: `repeating-linear-gradient(90deg, ${p.lav2} 0 6px, transparent 6px 12px)`, lineH: "1px", lineOpacity: "1", labelBorder: "1px solid currentColor", labelCase: "uppercase", labelTracking: "0.04em" },
829
974
  }),
830
975
  }),
831
976
  };
@@ -891,7 +1036,7 @@
891
1036
  { key: "canvas", group: "Paper", label: "Chat paper", hint: "behind the messages", kind: "colour", of: (l) => l.palette.bg, vars: (v) => ({ "--grad-canvas": v, "--grad-page": v, "--canvas-pattern": "none" }) },
892
1037
  { key: "panel", group: "Paper", label: "Panels", hint: "the rooms, the roster, the chat's frame, a vibemate's panel", kind: "colour", of: (l) => l.aliases.panel, vars: (v) => ({ "--panel": v }) },
893
1038
  { key: "bubble", group: "Bubbles", label: "Reply bubble", kind: "colour", of: (l) => l.elements.bubble.bg, vars: (v) => ({ "--bubble-bg": v }) },
894
- { key: "mine", group: "Bubbles", label: "Your bubble", kind: "colour", of: (l) => l.palette.primary, vars: (v) => ({ "--bubble-mine-bg": v }) },
1039
+ { key: "mine", group: "Bubbles", label: "Your bubble", kind: "colour", of: (l) => l.palette.primary, vars: (v) => ({ "--bubble-mine-bg": `linear-gradient(${v}, ${v})` }) },
895
1040
  { key: "ring", group: "Bubbles", label: "Bubble outline", hint: "a hairline around a reply; the bubble's own colour hides it", kind: "colour", of: (l) => hexOr(l.elements.bubble.border, l.elements.bubble.bg), vars: (v) => ({ "--bubble-border": v }) },
896
1041
  { key: "ink", group: "Ink", label: "Words", kind: "colour", of: (l) => l.palette.ink, vars: (v) => ({ "--ink": v, "--ink-2": v, "--bubble-ink": v }) },
897
1042
  { key: "muted", group: "Ink", label: "Quiet words", hint: "times, hints, labels", kind: "colour", of: (l) => l.palette.muted, vars: (v) => ({ "--muted": v, "--faint": v }) },
@@ -902,5 +1047,329 @@
902
1047
  { key: "corners", group: "Shape", label: "Corners", hint: "0 is square; every corner in the window scales with it", kind: "scale", min: 0, max: 1.5, step: 0.05, of: (l) => l.shape.rScale, vars: (v) => ({ "--r-scale": v }) },
903
1048
  ];
904
1049
 
905
- globalThis.VIBEROOM_TOKENS = { looks, current: looks.classic, active, diagrams, adjustables, cssGroups, cssVars, alpha, mix, kebab };
1050
+
1051
+ const SPEC_ID = /^[a-z][a-z0-9-]{0,30}$/;
1052
+ const HEX6 = /^#[0-9a-f]{6}$/i;
1053
+ const COLOUR_VALUE = /^(#[0-9a-f]{6}|#[0-9a-f]{8}|rgba?\([\d\s.,%]+\)|transparent|currentColor)$/i;
1054
+ const UNSAFE = /url\(|expression\(|javascript:|[<>{};\\@]/i;
1055
+ const FONT_STACK = /^[\w\s"',.-]+$/;
1056
+ const isColourDefault = (v) => COLOUR_VALUE.test(String(v).trim());
1057
+
1058
+ const hueOf = (token, palette, where) => {
1059
+ const t = String(token).trim();
1060
+ if (t.startsWith("$")) {
1061
+ const name = t.slice(1);
1062
+ if (!(name in palette)) throw new Error(`${where}: no hue "${name}" in the palette`);
1063
+ return palette[name];
1064
+ }
1065
+ if (HEX6.test(t)) return t.toLowerCase();
1066
+ throw new Error(`${where}: "${t}" is neither a hue of the palette ($name) nor a colour (#rrggbb)`);
1067
+ };
1068
+ const numberIn = (token, where, min, max) => {
1069
+ const n = Number(String(token).trim());
1070
+ if (!Number.isFinite(n) || n < min || n > max) throw new Error(`${where}: "${token}" is not a number between ${min} and ${max}`);
1071
+ return n;
1072
+ };
1073
+ const resolveValue = (raw, palette, where) => {
1074
+ if (typeof raw !== "string") throw new Error(`${where}: a value is a string`);
1075
+ const v = raw.trim();
1076
+ if (!v) throw new Error(`${where}: an empty value`);
1077
+ if (v.length > 400) throw new Error(`${where}: a value is at most 400 characters`);
1078
+ if (UNSAFE.test(v)) throw new Error(`${where}: a value may not carry url(), expression(), javascript: or < > { } ; \\ @`);
1079
+ const out = v
1080
+ .replace(/\b(alpha|mix)\(([^()]*)\)/g, (m, fn, args) => {
1081
+ const parts = args.split(",").map((s) => s.trim());
1082
+ if (fn === "alpha") {
1083
+ if (parts.length !== 2) throw new Error(`${where}: alpha takes a hue and a number: alpha($ink, 0.2)`);
1084
+ return alpha(hueOf(parts[0], palette, where), numberIn(parts[1], where, 0, 1));
1085
+ }
1086
+ if (parts.length !== 3) throw new Error(`${where}: mix takes two hues and a number: mix($white, $ink, 0.5)`);
1087
+ return mix(hueOf(parts[0], palette, where), hueOf(parts[1], palette, where), numberIn(parts[2], where, 0, 1));
1088
+ })
1089
+ .replace(/\$([a-zA-Z][a-zA-Z0-9]*)/g, (m, name) => hueOf(`$${name}`, palette, where));
1090
+ if (/\$/.test(out)) throw new Error(`${where}: a stray $ (a hue is $name)`);
1091
+ return out;
1092
+ };
1093
+ const checked = (defaultValue, value, where) => {
1094
+ if (isColourDefault(defaultValue) && !COLOUR_VALUE.test(value)) throw new Error(`${where}: a colour (the look has ${defaultValue}), not "${value}"`);
1095
+ return value;
1096
+ };
1097
+ const flatSection = (spec, section, defaults, baseWants, palette) => {
1098
+ const out = { ...(baseWants || {}) };
1099
+ for (const [key, raw] of Object.entries(spec || {})) {
1100
+ if (!(key in defaults)) throw new Error(`${section}.${key}: no such token (${Object.keys(defaults).join(", ")})`);
1101
+ out[key] = checked(defaults[key], resolveValue(raw, palette, `${section}.${key}`), `${section}.${key}`);
1102
+ }
1103
+ return out;
1104
+ };
1105
+
1106
+ function make(spec) {
1107
+ if (!spec || typeof spec !== "object" || Array.isArray(spec)) throw new Error("a look spec is an object");
1108
+ const id = String(spec.id ?? "").trim();
1109
+ if (!SPEC_ID.test(id)) throw new Error("id: a short lower-case id of letters, digits and hyphens (1-31), starting with a letter");
1110
+ if (looks[id] && !looks[id].custom) throw new Error(`id "${id}" belongs to a look viberoom ships; pick another`);
1111
+ const label = String(spec.label ?? "").trim();
1112
+ if (!label || label.length > 40) throw new Error("label: 1-40 characters, the name the picker shows");
1113
+ const baseId = spec.extends === undefined || spec.extends === null || spec.extends === "" ? "classic" : String(spec.extends);
1114
+ const base = looks[baseId];
1115
+ if (!base) throw new Error(`extends: no look "${baseId}" (${Object.keys(looks).filter((k) => !looks[k].custom).join(", ")})`);
1116
+ if (base.custom) throw new Error(`extends: "${baseId}" is a look of the human's own; a look extends one viberoom ships`);
1117
+ const scheme = spec.scheme === undefined || spec.scheme === null || spec.scheme === "" ? base.scheme : String(spec.scheme);
1118
+ if (scheme !== "light" && scheme !== "dark") throw new Error('scheme: "light" or "dark"');
1119
+ for (const key of Object.keys(spec)) {
1120
+ if (!["id", "label", "scheme", "extends", "author", "created", "updatedAt", "palette", "shape", "type", "motion", "elevation", "canvas", "elements"].includes(key)) throw new Error(`${key}: not a part of a look spec`);
1121
+ }
1122
+ const section = (name) => {
1123
+ const v = spec[name];
1124
+ if (v === undefined || v === null) return {};
1125
+ if (typeof v !== "object" || Array.isArray(v)) throw new Error(`${name}: an object of keys and values`);
1126
+ return v;
1127
+ };
1128
+ const palette = { ...base.palette };
1129
+ for (const [name, raw] of Object.entries(section("palette"))) {
1130
+ if (!(name in palette)) throw new Error(`palette.${name}: no such hue (${Object.keys(palette).join(", ")})`);
1131
+ const v = resolveValue(raw, palette, `palette.${name}`);
1132
+ if (!HEX6.test(v)) throw new Error(`palette.${name}: a hue is a flat colour #rrggbb, not "${v}"`);
1133
+ palette[name] = v.toLowerCase();
1134
+ }
1135
+ const baseWants = base.wants || {};
1136
+ const typeWants = { ...(baseWants.type || {}) };
1137
+ for (const [key, raw] of Object.entries(section("type"))) {
1138
+ if (!(key in type)) throw new Error(`type.${key}: no such token (${Object.keys(type).join(", ")})`);
1139
+ const v = resolveValue(raw, palette, `type.${key}`);
1140
+ if (key === "font" || key === "mono") {
1141
+ const table = key === "font" ? fonts.text : fonts.mono;
1142
+ if (table[v]) typeWants[key] = table[v].stack;
1143
+ else if (FONT_STACK.test(v)) typeWants[key] = v;
1144
+ else throw new Error(`type.${key}: a font id (${Object.keys(table).join(", ")}) or a family stack`);
1145
+ } else typeWants[key] = v;
1146
+ }
1147
+ const specCanvas = section("canvas");
1148
+ const specElements = section("elements");
1149
+ const wants = {
1150
+ shape: flatSection(section("shape"), "shape", shape, baseWants.shape, palette),
1151
+ type: typeWants,
1152
+ motion: flatSection(section("motion"), "motion", motion, baseWants.motion, palette),
1153
+ elevation: flatSection(section("elevation"), "elevation", elevationOf(palette), baseWants.elevation, palette),
1154
+ canvas: (p) => flatSection(specCanvas, "canvas", canvasOf(p), baseWants.canvas ? baseWants.canvas(p) : {}, p),
1155
+ elements: (p) => {
1156
+ const defaults = elementsOf(p);
1157
+ const out = {};
1158
+ const fromBase = baseWants.elements ? baseWants.elements(p) : {};
1159
+ for (const [group, parts] of Object.entries(fromBase)) out[group] = { ...parts };
1160
+ for (const [group, parts] of Object.entries(specElements)) {
1161
+ if (!(group in defaults)) throw new Error(`elements.${group}: no such element (${Object.keys(defaults).join(", ")})`);
1162
+ if (!parts || typeof parts !== "object" || Array.isArray(parts)) throw new Error(`elements.${group}: an object of keys and values`);
1163
+ out[group] = { ...(out[group] || {}) };
1164
+ for (const [key, raw] of Object.entries(parts)) {
1165
+ if (!(key in defaults[group])) throw new Error(`elements.${group}.${key}: no such token (${Object.keys(defaults[group]).join(", ")})`);
1166
+ out[group][key] = checked(defaults[group][key], resolveValue(raw, p, `elements.${group}.${key}`), `elements.${group}.${key}`);
1167
+ }
1168
+ }
1169
+ return out;
1170
+ },
1171
+ };
1172
+ const made = look(id, label, scheme, palette, wants);
1173
+ made.custom = true;
1174
+ made.extends = base.id;
1175
+ made.author = spec.author === undefined || spec.author === null ? "" : String(spec.author).slice(0, 40);
1176
+ return made;
1177
+ }
1178
+
1179
+ const docs = {
1180
+ sections: {
1181
+ palette: "the hues of the look, named after what they look like, never after what they are for; every element is derived from them, so a hue changed here reaches every place that wears it",
1182
+ shape: "corners: rScale multiplies every radius in the window (0 square, 1 as designed, 1.6 very round); rCtlMin is the least radius of a button, an icon button or a chip (0px keeps each one's own, 99px makes every control a pill); rXs…rPill are the named radii, each ONE length or calc() (they are used inside calc() and max(), so a multi-corner value breaks every corner)",
1183
+ type: "text: font and mono are the family stacks (in a spec: an id from fonts, or a stack), lineHeight the running text's, fsScale multiplies every text size, fsXs…fs2xl are the named sizes",
1184
+ motion: "the durations (tFast, tBase, tSlow; 0ms for none) and easings of the transitions",
1185
+ elevation: "the shadows and the light: edge (a hair under a panel), shadowTile (a raised tile), shadowPop (a floating balloon), shadowPrimary (the glow under the accent), shadowDialog, shadowCard, shadowCtl and shadowCtlHover (a control at rest and under the pointer), shadowInset (a sunken thing), shadowSoft (a small raised thing), shadow / shadow1 / shadow2 (older names), bevel (a light laid over a raised surface: a gradient, or none), bevelPressed (the same when pressed), shadowRoom (room around a message for its shadow, 0px unless the shadows reach out)",
1186
+ canvas: "the chat's paper: gradCanvas (the paint behind the messages: a colour, or gradients), canvasPattern and canvasPatternSize (a pattern over it, or none / 0 0), gradPage (the paper behind the panels), sbW / sbThumb / sbThumbHover (the scrollbar)",
1187
+ elements: "every UI element and what it wears: the group is the element, the key the part (bg, ink, hoverBg, border, shadow, radius…); a key ending in Ink is written on the key of the same stem ending in Bg (or on bg), and the pair must read at 3:1 or better; a key whose value is none / 0px / transparent is structure a look may switch on (a border, a shadow, a lift, capitals)",
1188
+ },
1189
+ palette: {
1190
+ primary: "the one accent: buttons, links, your bubble, the ring on your face",
1191
+ primaryLight: "the accent a step lighter (the start of its gradient)",
1192
+ primaryDeep: "the accent a step deeper (the end of its gradient)",
1193
+ primaryDark: "the accent's darkest step: the accent as an ink on its own tint",
1194
+ violet: "the far end of the welcome hero's sweep",
1195
+ iris: "the colour the browser paints its own window frame with",
1196
+ ink: "the words",
1197
+ ink2: "the words in a bubble",
1198
+ ink3: "quieter words: a button's, labels",
1199
+ indigo: "the human's initials on their face; a vendor's mark",
1200
+ muted: "quiet words: times, hints",
1201
+ faint: "the faintest words and dots",
1202
+ placeholder: "a field's placeholder",
1203
+ grey: "a face with no colour of its own",
1204
+ white: "the paper of panels, cards and bubbles (on a dark look: the dark paper)",
1205
+ black: "black, for shadows cast in black",
1206
+ bg: "the paper behind everything",
1207
+ soft: "the paper a step off the panel's: fields, tiles",
1208
+ softer: "the paper two steps off: buttons at rest",
1209
+ lav: "the accent's tint: tiles, chips, hover",
1210
+ lav2: "the accent's deeper tint: borders, a pressed chip",
1211
+ canvasTop: "the chat paper's sweep, top",
1212
+ canvasMid: "the chat paper's sweep, middle",
1213
+ canvasBottom: "the chat paper's sweep, bottom",
1214
+ shadowInk: "the colour shadows are cast in",
1215
+ mint: "green, pale: done, ready",
1216
+ mint2: "green, pale, a step deeper: hover on a green thing",
1217
+ mintTint: "green, the faintest tint",
1218
+ mintInk: "green as an ink on the pale green",
1219
+ forest: "green, dark: words on the hero's green bubble",
1220
+ green: "green, bright: the ready dot",
1221
+ warm: "yellow, pale: thinking, attention, a note",
1222
+ warm2: "yellow, pale, a step deeper: hover on a note",
1223
+ warmInk: "yellow as an ink on the pale yellow",
1224
+ bronze: "yellow, dark: words on the hero's yellow bubble",
1225
+ amber: "amber, bright: the thinking dot, a warning",
1226
+ honey: "honey: the number and the found line in code",
1227
+ spark: "the spark on the hero",
1228
+ peach: "peach, pale: a welcome tile",
1229
+ peach2: "peach, pale, a step deeper",
1230
+ orange: "orange: attention",
1231
+ orangeLight: "orange, light: the attention gradient's start",
1232
+ orangeDeep: "orange, deep: the attention gradient's end",
1233
+ orangeDark: "orange, dark: the attention ink",
1234
+ ember: "ember: unread, a glow",
1235
+ emberDeep: "ember, deep: the unread gradient's end",
1236
+ rose: "red, pale: an error, a danger button",
1237
+ rose2: "red, pale, a step deeper: hover on a danger button",
1238
+ roseInk: "red as an ink: errors, the danger ink, a failed call",
1239
+ roseDark: "red, dark: a solid danger button pressed",
1240
+ pink: "pink: the unseen line",
1241
+ pinkTint: "pink, the faintest tint: the unseen line's label",
1242
+ sky: "blue, pale: waiting, info",
1243
+ blue: "blue: info",
1244
+ blueDeep: "blue, deep: the waiting ink",
1245
+ blueSoft: "blue, soft: the waiting dot",
1246
+ orchid: "violet, pale: writing",
1247
+ orchidInk: "violet as an ink: the writing ink",
1248
+ orchidMid: "violet, mid: the writing dot",
1249
+ periwinkle: "periwinkle: your ticks on the timeline",
1250
+ periwinkleDeep: "periwinkle, deep: your ticks in view",
1251
+ deep: "the dark surface of code blocks and the file viewer",
1252
+ deepGutter: "the gutter of the dark surface",
1253
+ deepInk: "the words on the dark surface",
1254
+ deepMuted: "the quiet words on the dark surface",
1255
+ deepHead: "the head of the dark surface",
1256
+ deepMeta: "the meta words in that head",
1257
+ lavGrey: "code: punctuation",
1258
+ lilac: "code: keywords",
1259
+ seafoam: "code: strings",
1260
+ azure: "code: functions",
1261
+ salmon: "code: tags",
1262
+ magenta: "code: attributes",
1263
+ },
1264
+ elements: {
1265
+ page: "the page itself: its paper, its ink, the selection",
1266
+ focus: "the ring around whatever the keyboard is on",
1267
+ link: "a path or a link inside a message",
1268
+ btn: "button: a button with words; the colours of every kind (primary, secondary, soft, ghost, danger, danger-solid, dark, warn, inverse, link, ok, paper), its sizes, and its structure (border: width style colour; case; tracking; ghostBg; the shadows at rest / on hover / pressed; hoverLift: negative lifts)",
1269
+ iconButton: "icon-button: a button that is only a glyph; kinds primary, danger, ghost, inline; its sizes and structure (ghostBg, shadows, hoverLift)",
1270
+ badge: "badge: a small label that states a fact; the state tones come from the room's meaning colours; fill (100% filled, 0% an outline in its ink), border, case, tracking, shadow",
1271
+ chip: "chip: a small tag that names a thing; fill, border, shadow, hoverLift, activeDrop",
1272
+ fileCard: "file-card: a fragment of a file or a picture under the message that names it (the dark card)",
1273
+ toolCall: "tool-call: a chip that opens to the call; the status colours",
1274
+ toolFold: "tool-fold: the calls of a finished reply, in one line",
1275
+ askCard: "ask-card: a decision the room puts in front of you (a permission, a proposal)",
1276
+ replyNote: "reply-note: a line the hub attaches to a reply, inside its bubble",
1277
+ lookCard: "look-card: a look as a small picture of itself (the picker)",
1278
+ choice: "choice: one option of a few, as a pill; border, shadows, hoverLift, activeDrop",
1279
+ control: "switches and checkboxes",
1280
+ tile: "the small marks that are not types yet: the ? badge of the life ring, the count pills",
1281
+ roomMark: "room-mark: a room's square mark in its own hue (ink, the recipe of the gradient, shadow, radius / radiusLg / radiusRail)",
1282
+ pop: "the little speech balloons the rail and the participants raise",
1283
+ dialog: "the glass behind a dialog",
1284
+ scrim: "the dark glass behind a picture or a diagram",
1285
+ toast: "a toast: the ink with the paper's colour on it",
1286
+ rail: "the main menu",
1287
+ timeline: "the strips beside the chat",
1288
+ bubble: "the message balloon: everyone else's (bg, ink, border, shadow) and your own (mineBg: always an image, a flat colour as linear-gradient(c, c); mineInk, mineShadow and the parts inside it)",
1289
+ quote: "a quoted message inside a balloon",
1290
+ attach: "pictures and file chips in a message",
1291
+ table: "a table in a message",
1292
+ code: "the file viewer and every code block",
1293
+ syntax: "the colours of the code itself",
1294
+ diagram: "a mermaid diagram opened full screen",
1295
+ input: "text fields (bg, focusBorder, shadow: none, or a sunken recipe)",
1296
+ profile: "the human's own card",
1297
+ face: "face: a participant's face (paper and tint of the tile, corner, rings, shadow, cardShadow)",
1298
+ logoTile: "logo-tile: a vendor's mark on a tile (bg, ink, the badge in a face's corner, shadow, radius / radiusSm / radiusLg / radiusBadge)",
1299
+ unseenLine: "unseen-line: the line above which a vibemate has seen nothing (ink, bg, the line's drawing, the label's border / case / tracking / shadow)",
1300
+ numberField: "number-field: a number with its own steps",
1301
+ adjustRow: "adjust-row: one thing the human may adjust in a look, in Settings",
1302
+ rowButton: "row-button: a small action on a vibemate's row",
1303
+ hubRow: "hub-row: a line the hub writes (ink, tones, the one that leads somewhere: actionBg / actionShadow; prefix / prefixGap: what stands before the line, none or a prompt)",
1304
+ hero: "the welcome screen's hero",
1305
+ feature: "the welcome screen's feature tiles (emojiBg, shadow)",
1306
+ composer: "the box you write in (shadow, bg, radius, padX: flat and bare, or sunk into the paper)",
1307
+ step: "the welcome screen's numbered steps",
1308
+ tpl: "the template list in Open a room",
1309
+ browser: "the colour the browser paints its window frame with",
1310
+ },
1311
+ };
1312
+
1313
+ const KIND_SYNTAX = {
1314
+ border: 'a CSS border: width, style and colour, e.g. "2px solid $ink" (a width alone draws nothing); 0 for none',
1315
+ shadow: 'a CSS box-shadow list, e.g. "3px 4px 0 $ink" or "0 2px 6px alpha($ink, 0.2)", or none',
1316
+ radius: "ONE length or calc(), e.g. \"calc(14px * var(--r-scale))\": the named radii are used inside calc() and max(), so a multi-corner value (four lengths, or a slash) breaks every corner",
1317
+ lift: 'a length the thing moves on hover: NEGATIVE lifts it ("-2px"), positive pushes it down; 0px for none',
1318
+ drop: 'a length the thing sinks when pressed ("1px"); 0px for none',
1319
+ fill: 'a percentage of the tone\'s colour: "100%" filled, "0%" an outline in the ink',
1320
+ prefix: 'a quoted string written before the line, e.g. "\">\"", or none',
1321
+ case: "none, uppercase, lowercase or capitalize",
1322
+ tracking: 'a letter-spacing length, e.g. "0.06em" or 0px',
1323
+ scale: 'a number as a string ("1", "0.5", "1.6")',
1324
+ length: 'a CSS length, e.g. "12px"',
1325
+ gradient: "a CSS gradient image (linear-gradient / radial-gradient), a flat colour, or none",
1326
+ bevel: "a gradient image laid over a raised face, or none",
1327
+ font: "a font id from fonts (text or mono), or a family stack",
1328
+ };
1329
+ const kindOf = (section, key, value) => {
1330
+ const v = String(value);
1331
+ if (COLOUR_VALUE.test(v)) return "colour";
1332
+ if (section === "shape") return key === "rScale" ? "scale" : "radius";
1333
+ if (section === "type") return key === "font" || key === "mono" ? "font" : /^fs/.test(key) ? "length" : "scale";
1334
+ if (section === "elevation") return /^bevel/.test(key) ? "bevel" : key === "shadowRoom" ? "length" : "shadow";
1335
+ if (section === "canvas") return /^grad|Pattern$/.test(key) ? "gradient" : "length";
1336
+ if (/[Rr]adius/.test(key)) return "radius";
1337
+ if (/[Ss]hadow$/.test(key)) return "shadow";
1338
+ if (key === "border" || /Border$/.test(key)) return "border";
1339
+ if (/Lift$/.test(key)) return "lift";
1340
+ if (/Drop$/.test(key)) return "drop";
1341
+ if (key === "fill") return "fill";
1342
+ if (key === "prefix") return "prefix";
1343
+ if (key === "case" || /Case$/.test(key)) return "case";
1344
+ if (/[Tt]racking$/.test(key)) return "tracking";
1345
+ if (/Bg$|^bg$|Ink$|^ink$|Color$/.test(key) || /gradient\(/.test(v)) return "gradient";
1346
+ return null;
1347
+ };
1348
+
1349
+ function describe() {
1350
+ const l = looks.classic;
1351
+ const keysOf = (obj) => Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, String(v)]));
1352
+ const kinds = {};
1353
+ const structural = (kind) => kind && kind !== "colour" && kind !== "gradient";
1354
+ for (const section of ["shape", "type", "elevation", "canvas"]) for (const [k, v] of Object.entries(l[section])) { const kind = kindOf(section, k, v); if (structural(kind)) kinds[`${section}.${k}`] = kind; }
1355
+ for (const [g, parts] of Object.entries(l.elements)) for (const [k, v] of Object.entries(parts)) { const kind = kindOf("elements", k, v); if (structural(kind)) kinds[`elements.${g}.${k}`] = kind; }
1356
+ return {
1357
+ syntax: "A look spec is { id, label, scheme?, extends?, palette?, shape?, type?, motion?, elevation?, canvas?, elements? }. It extends one of the looks viberoom ships (VibeClassic unless said) and changes only the keys it gives. A value is a string: a colour \"#1a2b3c\"; \"$name\" for a hue of the palette; \"alpha($ink, 0.14)\" for a hue at an alpha; \"mix($white, $ink, 0.5)\" for a blend; anything else (a shadow, a size, a gradient, a font) as CSS, with those forms inside it. A key the look does not have is refused; a colour key takes only a colour; no url(), no braces.",
1358
+ sections: docs.sections,
1359
+ palette: Object.fromEntries(Object.keys(l.palette).map((n) => [n, { doc: docs.palette[n] || "", classic: l.palette[n] }])),
1360
+ shape: keysOf(l.shape),
1361
+ type: keysOf(l.type),
1362
+ motion: keysOf(l.motion),
1363
+ elevation: keysOf(l.elevation),
1364
+ canvas: keysOf(l.canvas),
1365
+ elements: Object.fromEntries(Object.entries(l.elements).map(([g, parts]) => [g, { doc: docs.elements[g] || "", keys: keysOf(parts) }])),
1366
+ kinds,
1367
+ kindSyntax: KIND_SYNTAX,
1368
+ fonts: { text: Object.keys(fonts.text), mono: Object.keys(fonts.mono) },
1369
+ looks: Object.values(looks).map((x) => ({ id: x.id, label: x.label, scheme: x.scheme, custom: !!x.custom, extends: x.extends || null })),
1370
+ adjustables: adjustables.map((a) => ({ key: a.key, group: a.group, label: a.label, hint: a.hint || "", kind: a.kind, min: a.min, max: a.max, step: a.step })),
1371
+ };
1372
+ }
1373
+
1374
+ globalThis.VIBEROOM_TOKENS = { looks, current: looks.classic, active, diagrams, adjustables, cssGroups, cssVars, alpha, mix, kebab, fonts, make, describe, docs, kindOf };
906
1375
  })();