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/NOTICE +5 -3
- package/README.md +90 -19
- package/assets/vendors/grok.svg +3 -0
- package/assets/vendors/hermes.svg +1 -0
- package/dist/agent-health.js +1 -1
- package/dist/hub.js +98 -49
- package/dist/jsonrpc.js +3 -1
- package/dist/look-lint.js +180 -0
- package/dist/looks.js +151 -0
- package/dist/main.js +4 -1
- package/dist/mcp-skills-server.js +87 -0
- package/dist/persona.js +10 -3
- package/dist/recipes.js +110 -8
- package/dist/room-design.js +9 -6
- package/dist/room.js +190 -26
- package/dist/server.js +73 -1
- package/dist/skills.js +23 -2
- package/package.json +1 -1
- package/ui/app.css +23 -16
- package/ui/app.js +198 -66
- package/ui/components.css +96 -115
- package/ui/components.js +8 -3
- package/ui/index.html +3 -2
- package/ui/looks.css +462 -77
- package/ui/theme.css +94 -13
- package/ui/tokens.js +526 -57
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: "#
|
|
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: "#
|
|
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: "#
|
|
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:
|
|
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.
|
|
345
|
-
darkInk: p.
|
|
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.
|
|
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.
|
|
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.
|
|
666
|
+
headBg: alpha(p.deepInk, 0.06),
|
|
577
667
|
headInk: p.deepHead,
|
|
578
668
|
headMeta: p.deepMeta,
|
|
579
|
-
headBtnBg: alpha(p.
|
|
580
|
-
headBtnInk: p.
|
|
581
|
-
bodyBg: alpha(p.
|
|
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.
|
|
584
|
-
sbThumbHover: alpha(p.
|
|
585
|
-
sbFirefox: alpha(p.
|
|
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.
|
|
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:
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
}),
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
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
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
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:
|
|
828
|
-
|
|
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
|
-
|
|
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
|
})();
|