@motir/design-system 0.1.2 → 0.2.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/dist/components/theme/AppearancePickers.js +10 -4
- package/dist/components/theme/AppearancePickers.js.map +1 -1
- package/dist/components/theme/StyleVignette.d.ts +20 -10
- package/dist/components/theme/StyleVignette.js.map +1 -1
- package/dist/components/ui/Popover.js +8 -1
- package/dist/components/ui/Popover.js.map +1 -1
- package/dist/components/ui/Toast.js +0 -2
- package/dist/components/ui/Toast.js.map +1 -1
- package/dist/components/ui/Tooltip.js +0 -1
- package/dist/components/ui/Tooltip.js.map +1 -1
- package/dist/specimen/TokensSpecimen.js.map +1 -1
- package/package.json +1 -1
- package/theme.css +1438 -607
package/theme.css
CHANGED
|
@@ -222,6 +222,35 @@
|
|
|
222
222
|
--height-input: 44px;
|
|
223
223
|
--height-control: 36px; /* single-line / square small control */
|
|
224
224
|
|
|
225
|
+
/* The space the collapsed nav rail puts AROUND its control: its `px-2`
|
|
226
|
+
gutters (8px a side), its 1px right border, and the 3px of slack the
|
|
227
|
+
shipped rail has always carried. Not the rail's width — the width is this
|
|
228
|
+
PLUS one `--height-control` square, composed at the point of use in
|
|
229
|
+
`components/ui/AppLayout.tsx` so the sum re-resolves under whatever
|
|
230
|
+
[data-style] is in scope rather than freezing the value `:root` happened
|
|
231
|
+
to have.
|
|
232
|
+
|
|
233
|
+
Calibrated so the DEFAULT style is unchanged: 36px + 20px is the 56px the
|
|
234
|
+
rail has always been. A denser style now gets a narrower rail and a roomier
|
|
235
|
+
one a wider rail, which is the density axis working rather than a constant
|
|
236
|
+
ignoring it. Deliberately NOT overridden per style — a style moves the
|
|
237
|
+
CONTROL, and the rail follows it (MOTIR-4232). */
|
|
238
|
+
--width-rail-chrome: 20px;
|
|
239
|
+
|
|
240
|
+
/* The inset a FLOATING rail is held away from the frame on all four sides, so
|
|
241
|
+
the shell canvas reads around it (MOTIR-4253). ZERO here, and that is the
|
|
242
|
+
point: the rail has a shared right EDGE in ten of the eleven styles, and an
|
|
243
|
+
edge is flush by definition. Only a style that turns the rail into a panel
|
|
244
|
+
with a shadow moves it.
|
|
245
|
+
|
|
246
|
+
It is composed into `AppLayout`'s two grid columns rather than applied to
|
|
247
|
+
the rail alone, because the rail FILLS its cell — a margin would shrink the
|
|
248
|
+
rail inside an unchanged column and take the collapsed rail's content box
|
|
249
|
+
below one `--height-control` square, which is exactly the invariant
|
|
250
|
+
MOTIR-4232 established. At `0px` both sums compile to the numbers every
|
|
251
|
+
other style already had. */
|
|
252
|
+
--spacing-rail-inset: 0px;
|
|
253
|
+
|
|
225
254
|
/* ── Interaction ────────────────────────────────────────────────── */
|
|
226
255
|
--transition-duration: 150ms;
|
|
227
256
|
--transition-fast: 100ms;
|
|
@@ -349,7 +378,8 @@
|
|
|
349
378
|
/* ═══════════════════════════════════════════════════════════════════════
|
|
350
379
|
TIER 2 — Named-style overrides ([data-style], Axis 2)
|
|
351
380
|
`warm-editorial` is the base (the Tier-0 defaults above — Notion-sober
|
|
352
|
-
rectangles)
|
|
381
|
+
rectangles). Its explicit block repeats that shape/feel contract so a nested
|
|
382
|
+
base-style scope shadows a foreign active style instead of inheriting it.
|
|
353
383
|
`soft-playful` adopts Figma's pill buttons + larger radius + softer shadows
|
|
354
384
|
+ roomier spacing + a gentler motion feel. Each later style (Swiss,
|
|
355
385
|
Neo-Brutalism, Glassmorphism, …) adds its own `[data-style='<id>']` block
|
|
@@ -357,6 +387,70 @@
|
|
|
357
387
|
motion / type) — NEVER a colour token (that is the data-palette axis).
|
|
358
388
|
═══════════════════════════════════════════════════════════════════════ */
|
|
359
389
|
|
|
390
|
+
[data-style='warm-editorial'] {
|
|
391
|
+
--radius-xs: 4px;
|
|
392
|
+
--radius-sm: 6px;
|
|
393
|
+
--radius-md: 8px;
|
|
394
|
+
--radius-lg: 12px;
|
|
395
|
+
--radius-xl: 16px;
|
|
396
|
+
--radius-pill: 9999px;
|
|
397
|
+
--radius-btn: 8px;
|
|
398
|
+
--radius-card: 12px;
|
|
399
|
+
--radius-input: 8px;
|
|
400
|
+
--radius-modal: 12px;
|
|
401
|
+
--radius-badge: 9999px;
|
|
402
|
+
--radius-control: 6px;
|
|
403
|
+
--radius-kbd: 4px;
|
|
404
|
+
--shadow-flat: none;
|
|
405
|
+
--shadow-subtle: 0 1px 2px rgba(15, 15, 15, 0.04);
|
|
406
|
+
--shadow-card: 0 4px 12px rgba(15, 15, 15, 0.08);
|
|
407
|
+
--shadow-elevated: 0 8px 24px rgba(15, 15, 15, 0.12);
|
|
408
|
+
--shadow-modal: 0 16px 48px -8px rgba(15, 15, 15, 0.16);
|
|
409
|
+
--shadow-hero-mockup: 0 24px 48px -8px rgba(15, 15, 15, 0.2);
|
|
410
|
+
--spacing-xxs: 4px;
|
|
411
|
+
--spacing-xs: 8px;
|
|
412
|
+
--spacing-sm: 12px;
|
|
413
|
+
--spacing-md: 16px;
|
|
414
|
+
--spacing-lg: 20px;
|
|
415
|
+
--spacing-xl: 24px;
|
|
416
|
+
--spacing-2xl: 32px;
|
|
417
|
+
--spacing-3xl: 40px;
|
|
418
|
+
--spacing-section: 64px;
|
|
419
|
+
--spacing-section-lg: 96px;
|
|
420
|
+
--spacing-hero: 120px;
|
|
421
|
+
--spacing-btn-x: 18px;
|
|
422
|
+
--spacing-btn-y: 10px;
|
|
423
|
+
--spacing-btn-x-sm: 12px;
|
|
424
|
+
--spacing-btn-y-sm: 8px;
|
|
425
|
+
--spacing-input-x: 16px;
|
|
426
|
+
--spacing-input-y: 12px;
|
|
427
|
+
--spacing-card-padding: 24px;
|
|
428
|
+
--spacing-control-x: 10px;
|
|
429
|
+
--spacing-control-y: 6px;
|
|
430
|
+
--spacing-icon-btn: 4px;
|
|
431
|
+
--spacing-chip-x: 10px;
|
|
432
|
+
--spacing-chip-y: 2px;
|
|
433
|
+
--spacing-kbd-x: 6px;
|
|
434
|
+
--spacing-kbd-y: 2px;
|
|
435
|
+
--spacing-tooltip-x: 8px;
|
|
436
|
+
--spacing-tooltip-y: 4px;
|
|
437
|
+
/* The rail keeps its shared edge under this style, so it is flush — the
|
|
438
|
+
default's mirror of the Tier-0 value, not an override (MOTIR-4253). */
|
|
439
|
+
--spacing-rail-inset: 0px;
|
|
440
|
+
--height-btn-sm: 32px;
|
|
441
|
+
--height-btn-md: 40px;
|
|
442
|
+
--height-btn-lg: 48px;
|
|
443
|
+
--height-input: 44px;
|
|
444
|
+
--height-control: 36px;
|
|
445
|
+
--transition-duration: 150ms;
|
|
446
|
+
--transition-fast: 100ms;
|
|
447
|
+
--transition-slow: 250ms;
|
|
448
|
+
--hover-scale: 1;
|
|
449
|
+
--active-scale: 0.98;
|
|
450
|
+
--focus-ring-width: 2px;
|
|
451
|
+
--focus-ring-offset: 2px;
|
|
452
|
+
}
|
|
453
|
+
|
|
360
454
|
[data-style='soft-playful'] {
|
|
361
455
|
/* Figma's pill geometry */
|
|
362
456
|
--radius-btn: 9999px; /* pill */
|
|
@@ -548,80 +642,132 @@
|
|
|
548
642
|
they are NOT the bare [data-style] token block and never carry a hue token
|
|
549
643
|
there — the disjoint-axis token-block test still passes. ───────────────── */
|
|
550
644
|
|
|
645
|
+
/* ═══════════════════════════════════════════════════════════════════════
|
|
646
|
+
⚠️ A STYLE CANVAS IS PAINTED ON `body` AND ON `[data-app-shell]`, FROM ONE
|
|
647
|
+
RULE (MOTIR-4230 for 3D / Immersive, MOTIR-4234 for the rest)
|
|
648
|
+
═══════════════════════════════════════════════════════════════════════
|
|
649
|
+
Six of the eleven registered styles express part of their identity as a
|
|
650
|
+
`body`-level CANVAS: glassmorphism's vibrant wash, cybercore's tech grid,
|
|
651
|
+
aurora's drifting ribbons, 3D / Immersive's atmosphere, neumorphism's
|
|
652
|
+
moulded field and retrofuturism's synthwave sky. `body` alone covers the
|
|
653
|
+
anonymous surfaces — auth, the public site, `/tokens` — where nothing is
|
|
654
|
+
stacked over the canvas. On a SIGNED-IN route it covers nothing at all: the
|
|
655
|
+
shell root (`components/ui/AppLayout.tsx`, and the platform-admin
|
|
656
|
+
`AdminShell`) is a `h-dvh overflow-hidden` box carrying `bg-(--el-page-bg)`,
|
|
657
|
+
an OPAQUE fill over the whole viewport. The canvas was painted and then
|
|
658
|
+
hidden, so a user who picked one of these styles got the surface treatment
|
|
659
|
+
on individual panels and a flat frame around them — and glass is the sharpest
|
|
660
|
+
case, because the thing its frosted rail was blurring was that flat fill
|
|
661
|
+
rather than the wash the blur exists to refract.
|
|
662
|
+
|
|
663
|
+
So each of those rules names TWO members, `body` and the `data-app-shell`
|
|
664
|
+
hook both shells carry, and the shell repaints the SAME layers over its own
|
|
665
|
+
page fill. ONE RULE, TWO SELECTORS, DELIBERATELY — a second rule restating a
|
|
666
|
+
gradient stack is a copy that drifts on the first tune, and the whole defect
|
|
667
|
+
was the canvas and the frame disagreeing about what the canvas is. Where the
|
|
668
|
+
body rule fixes `background-attachment`, both members inherit it from the one
|
|
669
|
+
declaration, which is what makes the two pixel-identical rather than merely
|
|
670
|
+
similar: each resolves against the viewport instead of against its own box.
|
|
671
|
+
|
|
672
|
+
A style that paints NO canvas of its own (`warm-editorial`, `soft-playful`,
|
|
673
|
+
`swiss-minimal-flat`, `neo-brutalism`, `hand-drawn-indie`) is untouched by
|
|
674
|
+
all of this and the shell keeps the `--el-page-bg` fill it has always had:
|
|
675
|
+
the hook is a hook, not a style.
|
|
676
|
+
|
|
677
|
+
`tests/theme/styleShellCanvas.test.ts` pins the pairing for EVERY style that
|
|
678
|
+
paints a canvas — derived from the stylesheet, not from a list — and
|
|
679
|
+
`tests/e2e/shell-immersive-atmosphere.spec.ts` is the rendered half.
|
|
680
|
+
═══════════════════════════════════════════════════════════════════════ */
|
|
681
|
+
|
|
551
682
|
/* The canvas: the palette's page bg + three soft corner washes. The hue sources
|
|
552
683
|
are the `--el-*` accent roles (NOT Tier-0 `--color-*`), so the washes re-tint
|
|
553
684
|
with the active palette — the `data-palette` axis (7.3.48 / MOTIR-1065)
|
|
554
685
|
overrides `--el-*`, never `--color-*`, so reading `--el-*` here is what keeps
|
|
555
686
|
the glass orthogonal to the palette. The gradient is what the frosted panels
|
|
556
687
|
blur — without it, the blur is invisible. */
|
|
557
|
-
[data-style='glassmorphism']
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
688
|
+
@scope ([data-style='glassmorphism']) to ([data-style]) {
|
|
689
|
+
body,
|
|
690
|
+
[data-app-shell] {
|
|
691
|
+
background-image:
|
|
692
|
+
radial-gradient(
|
|
693
|
+
85% 80% at 6% -12%,
|
|
694
|
+
color-mix(in srgb, var(--el-accent) var(--glass-wash), transparent),
|
|
695
|
+
transparent 62%
|
|
696
|
+
),
|
|
697
|
+
radial-gradient(
|
|
698
|
+
80% 75% at 110% 4%,
|
|
699
|
+
color-mix(in srgb, var(--el-highlight) var(--glass-wash), transparent),
|
|
700
|
+
transparent 60%
|
|
701
|
+
),
|
|
702
|
+
radial-gradient(
|
|
703
|
+
95% 90% at 50% 120%,
|
|
704
|
+
color-mix(in srgb, var(--el-link) var(--glass-wash), transparent),
|
|
705
|
+
transparent 62%
|
|
706
|
+
),
|
|
707
|
+
radial-gradient(
|
|
708
|
+
60% 60% at 85% 95%,
|
|
709
|
+
color-mix(in srgb, var(--el-accent) var(--glass-wash), transparent),
|
|
710
|
+
transparent 60%
|
|
711
|
+
);
|
|
712
|
+
background-attachment: fixed;
|
|
713
|
+
}
|
|
580
714
|
}
|
|
581
715
|
|
|
582
716
|
/* Frosted container surfaces — cards & popovers float as translucent sheets. */
|
|
583
|
-
[data-style='glassmorphism'] [data-
|
|
584
|
-
[data-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
717
|
+
@scope ([data-style='glassmorphism']) to ([data-style]) {
|
|
718
|
+
[data-surface='card'],
|
|
719
|
+
[data-surface='popover'] {
|
|
720
|
+
/* The translucent fill lets the vibrant canvas through; the linear sheen is
|
|
721
|
+
the light raking across the glass (top-left → fading), the inset RIM is the
|
|
722
|
+
lit glass edge, and the soft drop makes the sheet FLOAT. White light is
|
|
723
|
+
palette-agnostic; the colour still comes only from the palette-derived
|
|
724
|
+
fill/border, so the axes stay disjoint. */
|
|
725
|
+
background-color: color-mix(in srgb, var(--el-page-bg) var(--glass-surface-alpha), transparent);
|
|
726
|
+
background-image: linear-gradient(150deg, var(--glass-sheen), transparent 55%);
|
|
727
|
+
border-color: color-mix(in srgb, var(--el-border) var(--glass-border-alpha), transparent);
|
|
728
|
+
box-shadow:
|
|
729
|
+
inset 0 1px 0 0 var(--glass-rim),
|
|
730
|
+
inset 0 0 0 1px rgba(255, 255, 255, 0.06),
|
|
731
|
+
0 8px 32px -8px rgba(15, 15, 15, 0.18);
|
|
732
|
+
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
|
|
733
|
+
backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
|
|
734
|
+
}
|
|
599
735
|
}
|
|
600
736
|
|
|
601
737
|
/* Modal panel — a touch more opaque + a stronger blur (body-text AA + lift). */
|
|
602
|
-
[data-style='glassmorphism'] [data-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
738
|
+
@scope ([data-style='glassmorphism']) to ([data-style]) {
|
|
739
|
+
[data-surface='modal'] {
|
|
740
|
+
background-color: color-mix(in srgb, var(--el-page-bg) var(--glass-modal-alpha), transparent);
|
|
741
|
+
background-image: linear-gradient(150deg, var(--glass-sheen), transparent 55%);
|
|
742
|
+
border-color: color-mix(in srgb, var(--el-border) var(--glass-border-alpha), transparent);
|
|
743
|
+
box-shadow:
|
|
744
|
+
inset 0 1px 0 0 var(--glass-rim),
|
|
745
|
+
0 28px 70px -16px rgba(15, 15, 15, 0.34);
|
|
746
|
+
-webkit-backdrop-filter: blur(var(--glass-blur-strong)) saturate(var(--glass-saturate));
|
|
747
|
+
backdrop-filter: blur(var(--glass-blur-strong)) saturate(var(--glass-saturate));
|
|
748
|
+
}
|
|
611
749
|
}
|
|
612
750
|
|
|
613
751
|
/* The sidebar rail — frosted chrome over the canvas. */
|
|
614
|
-
[data-style='glassmorphism'] [data-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
752
|
+
@scope ([data-style='glassmorphism']) to ([data-style]) {
|
|
753
|
+
[data-surface='sidebar'] {
|
|
754
|
+
background-color: color-mix(
|
|
755
|
+
in srgb,
|
|
756
|
+
var(--el-sidebar-bg) var(--glass-chrome-alpha),
|
|
757
|
+
transparent
|
|
758
|
+
);
|
|
759
|
+
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
|
|
760
|
+
backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
|
|
761
|
+
}
|
|
618
762
|
}
|
|
619
763
|
|
|
620
764
|
/* Form controls — a translucent fill so inputs read as glass too. */
|
|
621
|
-
[data-style='glassmorphism'] [data-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
765
|
+
@scope ([data-style='glassmorphism']) to ([data-style]) {
|
|
766
|
+
[data-surface='input'] {
|
|
767
|
+
background-color: color-mix(in srgb, var(--el-page-bg) var(--glass-input-alpha), transparent);
|
|
768
|
+
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
|
|
769
|
+
backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
|
|
770
|
+
}
|
|
625
771
|
}
|
|
626
772
|
|
|
627
773
|
/* The MODAL / command-palette BACKDROP — the most important glass surface, since
|
|
@@ -629,10 +775,12 @@
|
|
|
629
775
|
light, palette-derived veil + a page BLUR, so the whole page behind frosts and
|
|
630
776
|
the (also-frosted) modal panel refracts it — the proper glass-modal look.
|
|
631
777
|
Overrides the primitives' `bg-black/40` utility. Palette-derived (no hue). */
|
|
632
|
-
[data-style='glassmorphism'] [data-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
778
|
+
@scope ([data-style='glassmorphism']) to ([data-style]) {
|
|
779
|
+
[data-surface='overlay'] {
|
|
780
|
+
background-color: color-mix(in srgb, var(--el-page-bg) 24%, transparent);
|
|
781
|
+
-webkit-backdrop-filter: blur(8px) saturate(var(--glass-saturate));
|
|
782
|
+
backdrop-filter: blur(8px) saturate(var(--glass-saturate));
|
|
783
|
+
}
|
|
636
784
|
}
|
|
637
785
|
|
|
638
786
|
/* ─────────────────────────────────────────────────────────────────────────
|
|
@@ -711,8 +859,10 @@
|
|
|
711
859
|
descendant rule, not a bare token block, so the disjoint-axis test (which
|
|
712
860
|
inspects token blocks) does not treat it as one — and it sets no colour
|
|
713
861
|
anyway, so the two axes stay disjoint regardless. */
|
|
714
|
-
[data-style='neo-brutalism']
|
|
715
|
-
border
|
|
862
|
+
@scope ([data-style='neo-brutalism']) to ([data-style]) {
|
|
863
|
+
.border {
|
|
864
|
+
border-width: 2px;
|
|
865
|
+
}
|
|
716
866
|
}
|
|
717
867
|
|
|
718
868
|
/* ─────────────────────────────────────────────────────────────────────────
|
|
@@ -795,12 +945,15 @@
|
|
|
795
945
|
so it adapts to the active palette and sits BEHIND content (text never
|
|
796
946
|
renders on it), keeping AA intact. Dark-first — on the dark theme this reads
|
|
797
947
|
as a Tron grid over the near-black canvas. */
|
|
798
|
-
[data-style='cybercore-y2k']
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
948
|
+
@scope ([data-style='cybercore-y2k']) to ([data-style]) {
|
|
949
|
+
body,
|
|
950
|
+
[data-app-shell] {
|
|
951
|
+
background-image:
|
|
952
|
+
linear-gradient(color-mix(in srgb, currentColor 6%, transparent) 1px, transparent 1px),
|
|
953
|
+
linear-gradient(90deg, color-mix(in srgb, currentColor 6%, transparent) 1px, transparent 1px);
|
|
954
|
+
background-size: 32px 32px;
|
|
955
|
+
background-position: center top;
|
|
956
|
+
}
|
|
804
957
|
}
|
|
805
958
|
|
|
806
959
|
/* ─────────────────────────────────────────────────────────────────────────
|
|
@@ -897,31 +1050,34 @@
|
|
|
897
1050
|
`--el-*`, never `--color-*`, which is what keeps the aurora orthogonal to the
|
|
898
1051
|
palette. background-size > 100% gives the gradient room to drift; the whole
|
|
899
1052
|
field eases back and forth over `--aurora-drift-duration`. */
|
|
900
|
-
[data-style='aurora']
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
1053
|
+
@scope ([data-style='aurora']) to ([data-style]) {
|
|
1054
|
+
body,
|
|
1055
|
+
[data-app-shell] {
|
|
1056
|
+
background-image:
|
|
1057
|
+
radial-gradient(
|
|
1058
|
+
58% 75% at 12% 0%,
|
|
1059
|
+
color-mix(in srgb, var(--el-accent) var(--aurora-wash), transparent),
|
|
1060
|
+
transparent 60%
|
|
1061
|
+
),
|
|
1062
|
+
radial-gradient(
|
|
1063
|
+
54% 70% at 92% 8%,
|
|
1064
|
+
color-mix(in srgb, var(--el-highlight) var(--aurora-wash), transparent),
|
|
1065
|
+
transparent 58%
|
|
1066
|
+
),
|
|
1067
|
+
radial-gradient(
|
|
1068
|
+
68% 82% at 62% 104%,
|
|
1069
|
+
color-mix(in srgb, var(--el-link) var(--aurora-wash), transparent),
|
|
1070
|
+
transparent 62%
|
|
1071
|
+
),
|
|
1072
|
+
radial-gradient(
|
|
1073
|
+
48% 64% at 34% 58%,
|
|
1074
|
+
color-mix(in srgb, var(--el-info) var(--aurora-wash-soft), transparent),
|
|
1075
|
+
transparent 55%
|
|
1076
|
+
);
|
|
1077
|
+
background-size: 180% 180%;
|
|
1078
|
+
background-attachment: fixed;
|
|
1079
|
+
animation: aurora-drift var(--aurora-drift-duration) ease-in-out infinite alternate;
|
|
1080
|
+
}
|
|
925
1081
|
}
|
|
926
1082
|
|
|
927
1083
|
@keyframes aurora-drift {
|
|
@@ -942,10 +1098,16 @@
|
|
|
942
1098
|
}
|
|
943
1099
|
|
|
944
1100
|
/* Respect a reduced-motion preference — the aurora settles to a still gradient
|
|
945
|
-
(the identity holds; only the drift stops).
|
|
1101
|
+
(the identity holds; only the drift stops). BOTH members, for the reason the
|
|
1102
|
+
canvas has two: the shell now carries the same `animation` declaration, so an
|
|
1103
|
+
arm that stilled only `body` would leave a reduced-motion user with a drifting
|
|
1104
|
+
frame around a still page — the defect inverted rather than fixed. */
|
|
946
1105
|
@media (prefers-reduced-motion: reduce) {
|
|
947
|
-
[data-style='aurora']
|
|
948
|
-
|
|
1106
|
+
@scope ([data-style='aurora']) to ([data-style]) {
|
|
1107
|
+
body,
|
|
1108
|
+
[data-app-shell] {
|
|
1109
|
+
animation: none;
|
|
1110
|
+
}
|
|
949
1111
|
}
|
|
950
1112
|
}
|
|
951
1113
|
|
|
@@ -955,43 +1117,49 @@
|
|
|
955
1117
|
rather than lifted by a hard shadow. The sheen sits on background-image over
|
|
956
1118
|
the opaque background-color, so it never pulls the surface far enough from its
|
|
957
1119
|
base tone to threaten contrast. */
|
|
958
|
-
[data-style='aurora'] [data-
|
|
959
|
-
[data-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
1120
|
+
@scope ([data-style='aurora']) to ([data-style]) {
|
|
1121
|
+
[data-surface='card'],
|
|
1122
|
+
[data-surface='popover'] {
|
|
1123
|
+
background-color: var(--el-surface);
|
|
1124
|
+
background-image: linear-gradient(
|
|
1125
|
+
180deg,
|
|
1126
|
+
color-mix(in srgb, var(--el-highlight) var(--aurora-sheen), transparent),
|
|
1127
|
+
transparent 42%
|
|
1128
|
+
);
|
|
1129
|
+
box-shadow:
|
|
1130
|
+
var(--shadow-card),
|
|
1131
|
+
0 0 26px -8px color-mix(in srgb, var(--el-accent) var(--aurora-glow), transparent),
|
|
1132
|
+
0 0 40px -14px color-mix(in srgb, var(--el-link) var(--aurora-glow), transparent);
|
|
1133
|
+
}
|
|
970
1134
|
}
|
|
971
1135
|
|
|
972
1136
|
/* Modal panel — opaque, with a deeper glow so the dialog separates cleanly from
|
|
973
1137
|
the drifting canvas behind it (and body text stays on the solid surface). */
|
|
974
|
-
[data-style='aurora'] [data-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
1138
|
+
@scope ([data-style='aurora']) to ([data-style]) {
|
|
1139
|
+
[data-surface='modal'] {
|
|
1140
|
+
background-color: var(--el-surface);
|
|
1141
|
+
background-image: linear-gradient(
|
|
1142
|
+
180deg,
|
|
1143
|
+
color-mix(in srgb, var(--el-highlight) var(--aurora-sheen), transparent),
|
|
1144
|
+
transparent 38%
|
|
1145
|
+
);
|
|
1146
|
+
box-shadow:
|
|
1147
|
+
var(--shadow-modal),
|
|
1148
|
+
0 0 60px -16px color-mix(in srgb, var(--el-accent) var(--aurora-glow-strong), transparent);
|
|
1149
|
+
}
|
|
984
1150
|
}
|
|
985
1151
|
|
|
986
1152
|
/* The sidebar rail — opaque chrome carrying the faint aurora sheen so it reads as
|
|
987
1153
|
part of the lit surface family, not a flat slab. */
|
|
988
|
-
[data-style='aurora'] [data-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
1154
|
+
@scope ([data-style='aurora']) to ([data-style]) {
|
|
1155
|
+
[data-surface='sidebar'] {
|
|
1156
|
+
background-color: var(--el-sidebar-bg);
|
|
1157
|
+
background-image: linear-gradient(
|
|
1158
|
+
180deg,
|
|
1159
|
+
color-mix(in srgb, var(--el-accent) var(--aurora-sheen), transparent),
|
|
1160
|
+
transparent 30%
|
|
1161
|
+
);
|
|
1162
|
+
}
|
|
995
1163
|
}
|
|
996
1164
|
|
|
997
1165
|
/* ─────────────────────────────────────────────────────────────────────────
|
|
@@ -1067,6 +1235,13 @@
|
|
|
1067
1235
|
--height-control: 40px;
|
|
1068
1236
|
--height-input: 46px;
|
|
1069
1237
|
|
|
1238
|
+
/* The rail FLOATS in this style, so it is held off the frame on all four
|
|
1239
|
+
sides and the immersive atmosphere reads around it (MOTIR-4253). The base
|
|
1240
|
+
layer is `0px`, so this is the only override — every other style's rail
|
|
1241
|
+
stays flush against its shared edge, and both of `AppLayout`'s grid sums
|
|
1242
|
+
compile to the numbers they have today. */
|
|
1243
|
+
--spacing-rail-inset: 10px;
|
|
1244
|
+
|
|
1070
1245
|
/* Motion — smooth and floaty. A slower ease for the depth to feel weighty.
|
|
1071
1246
|
The signature INTERACTION is the pointer-parallax TILT below (the standard
|
|
1072
1247
|
"3D card" effect — cards tip toward the cursor), driven by the
|
|
@@ -1085,25 +1260,46 @@
|
|
|
1085
1260
|
never a raw hue), so the style/palette axes stay disjoint and the material-
|
|
1086
1261
|
rule test passes: a palette swap re-tints the atmosphere, a style swap leaves
|
|
1087
1262
|
hues alone. This is what makes the table/list/board pages (no card surfaces of
|
|
1088
|
-
their own) read as immersive too.
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1263
|
+
their own) read as immersive too.
|
|
1264
|
+
|
|
1265
|
+
⚠️ IT IS PAINTED ON THE SHELL CANVAS AS WELL AS ON `body`, AND THAT IS WHY THE
|
|
1266
|
+
SELECTOR LIST HAS TWO MEMBERS (MOTIR-4230). `body` alone covers the anonymous
|
|
1267
|
+
surfaces — auth, the public site, `/tokens` — where nothing is stacked over the
|
|
1268
|
+
canvas. On a SIGNED-IN route the shell root (`components/ui/AppLayout.tsx`, and
|
|
1269
|
+
the platform-admin `AdminShell`) is a `h-dvh` box carrying `bg-(--el-page-bg)`,
|
|
1270
|
+
an OPAQUE fill covering the whole viewport: the body's atmosphere was painted
|
|
1271
|
+
and then hidden behind it, so every card had its 3D tokens while the frame they
|
|
1272
|
+
sit in stayed flat — the style did not read as immersive anywhere a user
|
|
1273
|
+
actually works. The shells therefore carry a `data-app-shell` hook and repaint
|
|
1274
|
+
the SAME layers over their own page fill, which is what the body would have
|
|
1275
|
+
shown. `background-attachment: fixed` on both members is what makes the two
|
|
1276
|
+
pixel-identical: each resolves against the viewport, not against its own box.
|
|
1277
|
+
|
|
1278
|
+
ONE RULE, TWO SELECTORS, DELIBERATELY — a second rule restating this gradient
|
|
1279
|
+
stack is a copy that drifts on the first tune, and the whole defect was the
|
|
1280
|
+
canvas and the frame disagreeing about what the canvas is. `tests/theme/
|
|
1281
|
+
immersiveShellAtmosphere.test.ts` pins the pairing and the hook. */
|
|
1282
|
+
@scope ([data-style='3d-immersive']) to ([data-style]) {
|
|
1283
|
+
body,
|
|
1284
|
+
[data-app-shell] {
|
|
1285
|
+
background-image:
|
|
1286
|
+
radial-gradient(
|
|
1287
|
+
130% 85% at 50% -15%,
|
|
1288
|
+
color-mix(in srgb, var(--el-accent) 9%, transparent),
|
|
1289
|
+
transparent 60%
|
|
1290
|
+
),
|
|
1291
|
+
radial-gradient(
|
|
1292
|
+
95% 75% at 108% 112%,
|
|
1293
|
+
color-mix(in srgb, var(--el-link) 8%, transparent),
|
|
1294
|
+
transparent 55%
|
|
1295
|
+
),
|
|
1296
|
+
radial-gradient(
|
|
1297
|
+
150% 130% at 50% 42%,
|
|
1298
|
+
transparent 52%,
|
|
1299
|
+
color-mix(in srgb, var(--el-text) 8%, transparent)
|
|
1300
|
+
);
|
|
1301
|
+
background-attachment: fixed;
|
|
1302
|
+
}
|
|
1107
1303
|
}
|
|
1108
1304
|
|
|
1109
1305
|
/* EVERY PANEL FLOATS — systematic depth across the WHOLE app, not just the
|
|
@@ -1118,11 +1314,15 @@
|
|
|
1118
1314
|
Per-surface exceptions (the board column's clip-safe shadow, modals) override
|
|
1119
1315
|
below by source order. Modals carry their own deeper shadow via the modal
|
|
1120
1316
|
radius (next rule). */
|
|
1121
|
-
[data-style='3d-immersive']
|
|
1122
|
-
|
|
1317
|
+
@scope ([data-style='3d-immersive']) to ([data-style]) {
|
|
1318
|
+
.rounded-\(--radius-card\) {
|
|
1319
|
+
box-shadow: var(--shadow-card);
|
|
1320
|
+
}
|
|
1123
1321
|
}
|
|
1124
|
-
[data-style='3d-immersive']
|
|
1125
|
-
|
|
1322
|
+
@scope ([data-style='3d-immersive']) to ([data-style]) {
|
|
1323
|
+
.rounded-\(--radius-modal\) {
|
|
1324
|
+
box-shadow: var(--shadow-modal);
|
|
1325
|
+
}
|
|
1126
1326
|
}
|
|
1127
1327
|
|
|
1128
1328
|
/* 3D / Immersive — the POINTER-PARALLAX TILT (the standard "3D card" effect,
|
|
@@ -1147,10 +1347,12 @@
|
|
|
1147
1347
|
never establishes a transform stacking context that could reposition a
|
|
1148
1348
|
non-portaled fixed/absolute descendant. On leave the engine drops the
|
|
1149
1349
|
attribute and the transform eases back to none over 400ms. */
|
|
1150
|
-
[data-style='3d-immersive'] [data-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1350
|
+
@scope ([data-style='3d-immersive']) to ([data-style]) {
|
|
1351
|
+
[data-tilt] {
|
|
1352
|
+
transition:
|
|
1353
|
+
transform 400ms cubic-bezier(0.22, 1, 0.36, 1),
|
|
1354
|
+
box-shadow 260ms ease-out;
|
|
1355
|
+
}
|
|
1154
1356
|
}
|
|
1155
1357
|
/* While the pointer is over the tile the engine sets data-tilt-active='1':
|
|
1156
1358
|
tip it toward the cursor (the per-tile --tilt-rx/ry the engine writes),
|
|
@@ -1159,14 +1361,16 @@
|
|
|
1159
1361
|
establish a 3D context (`preserve-3d`) so the card's slots can sit on
|
|
1160
1362
|
separate translateZ planes and PARALLAX (a flat-plane tilt without this is
|
|
1161
1363
|
the "halfway / flat-3D mix" tell). `position: relative` anchors the glare. */
|
|
1162
|
-
[data-style='3d-immersive'] [data-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1364
|
+
@scope ([data-style='3d-immersive']) to ([data-style]) {
|
|
1365
|
+
[data-tilt][data-tilt-active='1'] {
|
|
1366
|
+
transform: perspective(var(--tilt-perspective, 900px)) rotateX(var(--tilt-rx, 0deg))
|
|
1367
|
+
rotateY(var(--tilt-ry, 0deg));
|
|
1368
|
+
transform-style: preserve-3d;
|
|
1369
|
+
transition:
|
|
1370
|
+
transform 80ms linear,
|
|
1371
|
+
box-shadow 260ms ease-out;
|
|
1372
|
+
box-shadow: var(--shadow-elevated);
|
|
1373
|
+
}
|
|
1170
1374
|
}
|
|
1171
1375
|
|
|
1172
1376
|
/* DEPTH PLANES — the card's slots lift onto different translateZ planes so
|
|
@@ -1175,13 +1379,17 @@
|
|
|
1175
1379
|
content never renders skewed). Only while actively tilting, so a resting
|
|
1176
1380
|
card is perfectly flat. This is the standard layered-parallax technique
|
|
1177
1381
|
(vanilla-tilt / Atropos): perspective + preserve-3d + per-layer translateZ. */
|
|
1178
|
-
[data-style='3d-immersive']
|
|
1179
|
-
|
|
1180
|
-
|
|
1382
|
+
@scope ([data-style='3d-immersive']) to ([data-style]) {
|
|
1383
|
+
[data-tilt][data-tilt-active='1'] [data-tilt-layer='front'] {
|
|
1384
|
+
transform: translateZ(42px);
|
|
1385
|
+
transition: transform 80ms linear;
|
|
1386
|
+
}
|
|
1181
1387
|
}
|
|
1182
|
-
[data-style='3d-immersive']
|
|
1183
|
-
|
|
1184
|
-
|
|
1388
|
+
@scope ([data-style='3d-immersive']) to ([data-style]) {
|
|
1389
|
+
[data-tilt][data-tilt-active='1'] [data-tilt-layer='back'] {
|
|
1390
|
+
transform: translateZ(14px);
|
|
1391
|
+
transition: transform 80ms linear;
|
|
1392
|
+
}
|
|
1185
1393
|
}
|
|
1186
1394
|
|
|
1187
1395
|
/* GLARE — a cursor-tracked specular sweep across the face (the light catching
|
|
@@ -1189,33 +1397,39 @@
|
|
|
1189
1397
|
hue → passes the material-rule test; a palette swap re-tints the sheen). It
|
|
1190
1398
|
rides a high translateZ so it reads as light on the front glass, fades in
|
|
1191
1399
|
only while active, and never eats pointer events. */
|
|
1192
|
-
[data-style='3d-immersive'] [data-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1400
|
+
@scope ([data-style='3d-immersive']) to ([data-style]) {
|
|
1401
|
+
[data-tilt]::after {
|
|
1402
|
+
content: '';
|
|
1403
|
+
position: absolute;
|
|
1404
|
+
inset: 0;
|
|
1405
|
+
border-radius: inherit;
|
|
1406
|
+
background: radial-gradient(
|
|
1407
|
+
circle at var(--tilt-glare-x, 50%) var(--tilt-glare-y, 50%),
|
|
1408
|
+
color-mix(in srgb, var(--el-page-bg) 65%, transparent),
|
|
1409
|
+
transparent 55%
|
|
1410
|
+
);
|
|
1411
|
+
opacity: 0;
|
|
1412
|
+
transition: opacity 200ms ease-out;
|
|
1413
|
+
pointer-events: none;
|
|
1414
|
+
}
|
|
1205
1415
|
}
|
|
1206
|
-
[data-style='3d-immersive'] [data-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1416
|
+
@scope ([data-style='3d-immersive']) to ([data-style]) {
|
|
1417
|
+
[data-tilt][data-tilt-active='1']::after {
|
|
1418
|
+
opacity: 0.45;
|
|
1419
|
+
/* Just above the card FACE but BELOW the popped header/footer planes, so it
|
|
1420
|
+
lights the surface without washing the floating title. */
|
|
1421
|
+
transform: translateZ(2px);
|
|
1422
|
+
}
|
|
1211
1423
|
}
|
|
1212
1424
|
}
|
|
1213
1425
|
|
|
1214
1426
|
/* `position: relative` anchors the glare ::after to each tile. Static (outside
|
|
1215
1427
|
the motion gate) so the anchor is stable; the glare itself only shows while
|
|
1216
1428
|
tilting (above). */
|
|
1217
|
-
[data-style='3d-immersive'] [data-
|
|
1218
|
-
|
|
1429
|
+
@scope ([data-style='3d-immersive']) to ([data-style]) {
|
|
1430
|
+
[data-tilt] {
|
|
1431
|
+
position: relative;
|
|
1432
|
+
}
|
|
1219
1433
|
}
|
|
1220
1434
|
|
|
1221
1435
|
/* ── 3D / Immersive — BOARD COLUMNS ─────────────────────────────────────────
|
|
@@ -1226,33 +1440,37 @@
|
|
|
1226
1440
|
clip/occlusion-safe float shadow (small horizontal bleed, modest drop), and
|
|
1227
1441
|
the scroll row widens its gap + adds bottom room so the shadow shows. Result:
|
|
1228
1442
|
each column floats as its own distinct card. */
|
|
1229
|
-
[data-style='3d-immersive'] [data-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1443
|
+
@scope ([data-style='3d-immersive']) to ([data-style]) {
|
|
1444
|
+
[data-board-col-panel] {
|
|
1445
|
+
box-shadow:
|
|
1446
|
+
inset 0 1px 0 0 rgba(255, 255, 255, 0.55),
|
|
1447
|
+
0 1px 3px rgba(15, 15, 15, 0.08),
|
|
1448
|
+
0 8px 18px -8px rgba(15, 15, 15, 0.22);
|
|
1449
|
+
/* The column is `h-[calc(100dvh - 10.5rem - var(--shell-bottom-clearance))]` —
|
|
1450
|
+
sized to fill the board area. In the `overflow-x-auto` row that
|
|
1451
|
+
exactly-filling height means the column's rounded BOTTOM corner + its drop
|
|
1452
|
+
shadow sit at the overflow-clip boundary and get sheared flat (the reported
|
|
1453
|
+
"sharp bottom corner"). Trim the height by a further 2.5rem so the rounded
|
|
1454
|
+
corner + shadow clear the clip and the column reads as a fully-rounded
|
|
1455
|
+
floating card. Also clip the column's OWN children to its radius so a flush
|
|
1456
|
+
inner surface can't square a corner — the board cards pop on hover but the
|
|
1457
|
+
DragOverlay clone is portaled, so nothing 3D is lost.
|
|
1458
|
+
|
|
1459
|
+
⚠️ This tracks BoardColumn's height and must keep spending the same
|
|
1460
|
+
`--shell-bottom-clearance` the shell sets (MOTIR-2763), not a flat number:
|
|
1461
|
+
a stylesheet cannot read the `showPlanWithAi` prop, so the custom property
|
|
1462
|
+
is the only way this stays conditional on the orb actually mounting. The
|
|
1463
|
+
13rem below is 10.5rem of chrome + this style's own 2.5rem trim. */
|
|
1464
|
+
height: calc(100dvh - 13rem - var(--shell-bottom-clearance, 1.5rem));
|
|
1465
|
+
overflow: hidden;
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
@scope ([data-style='3d-immersive']) to ([data-style]) {
|
|
1469
|
+
[data-board-scroll] {
|
|
1470
|
+
gap: 1.5rem;
|
|
1471
|
+
padding-bottom: 2.25rem;
|
|
1472
|
+
padding-top: 0.25rem;
|
|
1473
|
+
}
|
|
1256
1474
|
}
|
|
1257
1475
|
|
|
1258
1476
|
/* ── 3D / Immersive — CONTROL DEPTH: every control sits on a named plane ────
|
|
@@ -1317,7 +1535,7 @@
|
|
|
1317
1535
|
The `.justify-center` limb is the square icon-button shape: a centred control
|
|
1318
1536
|
on the control radius is a key (the 3-dots trigger, the fullscreen toggle,
|
|
1319
1537
|
every close ×). A row is never centred — it is `w-full` and `text-left`. */
|
|
1320
|
-
[data-style='3d-immersive']
|
|
1538
|
+
@scope ([data-style='3d-immersive']) to ([data-style]) {
|
|
1321
1539
|
:where(
|
|
1322
1540
|
.rounded-\(--radius-btn\),
|
|
1323
1541
|
button.rounded-\(--radius-input\),
|
|
@@ -1333,27 +1551,34 @@
|
|
|
1333
1551
|
[data-depth='flat']
|
|
1334
1552
|
)
|
|
1335
1553
|
) {
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
}
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1554
|
+
box-shadow:
|
|
1555
|
+
0 3px 0 0 var(--el-border-strong),
|
|
1556
|
+
0 5px 10px -3px rgba(15, 15, 15, 0.18);
|
|
1557
|
+
transition:
|
|
1558
|
+
transform 90ms ease-out,
|
|
1559
|
+
box-shadow 90ms ease-out,
|
|
1560
|
+
background-color var(--transition-duration);
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
@scope ([data-style='3d-immersive']) to ([data-style]) {
|
|
1564
|
+
[data-variant='primary'] {
|
|
1565
|
+
box-shadow:
|
|
1566
|
+
0 4px 0 0 var(--el-accent-pressed),
|
|
1567
|
+
0 7px 12px -3px rgba(15, 15, 15, 0.32);
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
@scope ([data-style='3d-immersive']) to ([data-style]) {
|
|
1571
|
+
[data-variant='danger'] {
|
|
1572
|
+
box-shadow:
|
|
1573
|
+
0 4px 0 0 color-mix(in srgb, var(--el-danger), black 32%),
|
|
1574
|
+
0 7px 12px -3px rgba(15, 15, 15, 0.32);
|
|
1575
|
+
}
|
|
1353
1576
|
}
|
|
1354
1577
|
/* Ghost — the deliberately-flat quiet button; no thickness. */
|
|
1355
|
-
[data-style='3d-immersive'] [data-
|
|
1356
|
-
|
|
1578
|
+
@scope ([data-style='3d-immersive']) to ([data-style]) {
|
|
1579
|
+
[data-variant='ghost'] {
|
|
1580
|
+
box-shadow: none;
|
|
1581
|
+
}
|
|
1357
1582
|
}
|
|
1358
1583
|
|
|
1359
1584
|
/* THE FLAT SET — rows, chips and switches, which §4 keeps on their parent's
|
|
@@ -1361,7 +1586,7 @@
|
|
|
1361
1586
|
(0,1,0) wherever the two overlap (a centred control inside an open menu, a
|
|
1362
1587
|
`data-depth='flat'` icon button). A menu row raised inside an already-floating
|
|
1363
1588
|
popover reads as debris, not depth — the overlay is the thing that floats. */
|
|
1364
|
-
[data-style='3d-immersive']
|
|
1589
|
+
@scope ([data-style='3d-immersive']) to ([data-style]) {
|
|
1365
1590
|
:is(
|
|
1366
1591
|
[role='menuitem'],
|
|
1367
1592
|
[role='option'],
|
|
@@ -1370,50 +1595,57 @@
|
|
|
1370
1595
|
[role='switch'],
|
|
1371
1596
|
[data-depth='flat']
|
|
1372
1597
|
),
|
|
1373
|
-
[data-
|
|
1374
|
-
:is(
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
box-shadow: none;
|
|
1598
|
+
:is([data-surface='popover'], [data-menu-surface]) :is(button, a).rounded-\(--radius-control\),
|
|
1599
|
+
:is(button, a).w-full.rounded-\(--radius-control\) {
|
|
1600
|
+
box-shadow: none;
|
|
1601
|
+
}
|
|
1378
1602
|
}
|
|
1379
1603
|
|
|
1380
1604
|
/* THE PRESS — the raised set drops by its thickness and the base edge
|
|
1381
1605
|
compresses, like a real key. Overrides the base `active:scale`. */
|
|
1382
|
-
[data-style='3d-immersive']
|
|
1606
|
+
@scope ([data-style='3d-immersive']) to ([data-style]) {
|
|
1383
1607
|
:where(
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1608
|
+
.rounded-\(--radius-btn\),
|
|
1609
|
+
button.rounded-\(--radius-input\),
|
|
1610
|
+
:is(button, a).rounded-\(--radius-control\).justify-center,
|
|
1611
|
+
[data-depth='key']
|
|
1612
|
+
):active:not(:disabled) {
|
|
1613
|
+
transform: translateY(3px);
|
|
1614
|
+
box-shadow:
|
|
1615
|
+
0 1px 0 0 var(--el-border-strong),
|
|
1616
|
+
0 2px 4px rgba(15, 15, 15, 0.14);
|
|
1617
|
+
}
|
|
1393
1618
|
}
|
|
1394
|
-
[data-style='3d-immersive'] [data-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1619
|
+
@scope ([data-style='3d-immersive']) to ([data-style]) {
|
|
1620
|
+
[data-variant='primary']:active:not(:disabled) {
|
|
1621
|
+
box-shadow:
|
|
1622
|
+
0 1px 0 0 var(--el-accent-pressed),
|
|
1623
|
+
0 2px 5px rgba(15, 15, 15, 0.22);
|
|
1624
|
+
}
|
|
1398
1625
|
}
|
|
1399
|
-
[data-style='3d-immersive'] [data-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1626
|
+
@scope ([data-style='3d-immersive']) to ([data-style]) {
|
|
1627
|
+
[data-variant='danger']:active:not(:disabled) {
|
|
1628
|
+
box-shadow:
|
|
1629
|
+
0 1px 0 0 color-mix(in srgb, var(--el-danger), black 32%),
|
|
1630
|
+
0 2px 5px rgba(15, 15, 15, 0.22);
|
|
1631
|
+
}
|
|
1403
1632
|
}
|
|
1404
|
-
[data-style='3d-immersive'] [data-
|
|
1405
|
-
|
|
1633
|
+
@scope ([data-style='3d-immersive']) to ([data-style]) {
|
|
1634
|
+
[data-variant='ghost']:active:not(:disabled) {
|
|
1635
|
+
box-shadow: none;
|
|
1636
|
+
}
|
|
1406
1637
|
}
|
|
1407
1638
|
|
|
1408
1639
|
/* THE QUIET PRESS — a control with no resting thickness still has to answer the
|
|
1409
1640
|
pointer, or the style reads as flat wherever it is not a key: menu rows, list
|
|
1410
1641
|
rows, nav links, inline affordances, the ghost button. One pixel of travel is
|
|
1411
1642
|
the whole treatment; it is what separates "quiet" from "inert". */
|
|
1412
|
-
[data-style='3d-immersive']
|
|
1643
|
+
@scope ([data-style='3d-immersive']) to ([data-style]) {
|
|
1413
1644
|
:where(:is(button, a).rounded-\(--radius-control\), [data-variant='ghost']):active:not(
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1645
|
+
:disabled
|
|
1646
|
+
) {
|
|
1647
|
+
transform: translateY(1px);
|
|
1648
|
+
}
|
|
1417
1649
|
}
|
|
1418
1650
|
|
|
1419
1651
|
/* THE RECESSED SET — text fields are pressed INTO the surface, not raised off
|
|
@@ -1424,16 +1656,17 @@
|
|
|
1424
1656
|
not a copy of it. `[data-surface='input']` is the Input primitive's shell;
|
|
1425
1657
|
the bare element selectors catch the hand-rolled fields. Checkbox and radio
|
|
1426
1658
|
are excluded: they are chips, not wells. */
|
|
1427
|
-
[data-style='3d-immersive']
|
|
1659
|
+
@scope ([data-style='3d-immersive']) to ([data-style]) {
|
|
1428
1660
|
:where(
|
|
1429
1661
|
[data-surface='input'],
|
|
1430
1662
|
input:not([type='checkbox'], [type='radio'], [type='range'], [type='color']),
|
|
1431
1663
|
textarea,
|
|
1432
1664
|
select
|
|
1433
1665
|
) {
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1666
|
+
box-shadow:
|
|
1667
|
+
inset 0 2px 6px -2px rgba(15, 15, 15, 0.26),
|
|
1668
|
+
inset 0 1px 2px 0 rgba(15, 15, 15, 0.05);
|
|
1669
|
+
}
|
|
1437
1670
|
}
|
|
1438
1671
|
|
|
1439
1672
|
/* Hero controls paint their own shadow in an INLINE style, which beats every
|
|
@@ -1452,18 +1685,20 @@
|
|
|
1452
1685
|
override every shape role. A descendant rule is neither, which is where the
|
|
1453
1686
|
immersive background (which reads `--el-*` for the same reason) already
|
|
1454
1687
|
lives. Custom properties inherit, so every control below `body` sees these. */
|
|
1455
|
-
[data-style='3d-immersive']
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1688
|
+
@scope ([data-style='3d-immersive']) to ([data-style]) {
|
|
1689
|
+
body {
|
|
1690
|
+
--plan-hero-shadow:
|
|
1691
|
+
0 4px 0 0 var(--el-accent-pressed),
|
|
1692
|
+
inset 0 1px 0 color-mix(in srgb, var(--el-accent-text) 38%, transparent),
|
|
1693
|
+
inset 0 0 0 1px color-mix(in srgb, var(--el-accent-text) 18%, transparent),
|
|
1694
|
+
0 9px 18px -5px color-mix(in srgb, var(--el-accent) 75%, transparent),
|
|
1695
|
+
0 0 22px -3px color-mix(in srgb, var(--el-highlight) 50%, transparent);
|
|
1696
|
+
--plan-orb-shadow:
|
|
1697
|
+
0 5px 0 0 var(--el-accent-pressed),
|
|
1698
|
+
inset 0 1px 0 color-mix(in srgb, var(--el-accent-text) 40%, transparent),
|
|
1699
|
+
0 12px 24px -6px color-mix(in srgb, var(--el-accent) 80%, transparent),
|
|
1700
|
+
0 0 28px -2px color-mix(in srgb, var(--el-highlight) 55%, transparent);
|
|
1701
|
+
}
|
|
1467
1702
|
}
|
|
1468
1703
|
|
|
1469
1704
|
/* ── 3D / Immersive — DROPDOWNS / POPOVERS / MENUS unfold in 3D ──────────────
|
|
@@ -1489,22 +1724,129 @@
|
|
|
1489
1724
|
rule above would otherwise flatten any `shadow-(--shadow-elevated)` surface to
|
|
1490
1725
|
--shadow-card). Global + static so menus, tooltips, drag-overlays all keep the
|
|
1491
1726
|
right depth. */
|
|
1492
|
-
[data-style='3d-immersive']
|
|
1493
|
-
|
|
1727
|
+
@scope ([data-style='3d-immersive']) to ([data-style]) {
|
|
1728
|
+
.shadow-\(--shadow-elevated\) {
|
|
1729
|
+
box-shadow: var(--shadow-elevated);
|
|
1730
|
+
}
|
|
1494
1731
|
}
|
|
1495
1732
|
@media (prefers-reduced-motion: no-preference) {
|
|
1496
1733
|
/* The 3D UNFOLD is scoped to actual MENU surfaces (the Popover primitive +
|
|
1497
1734
|
the `data-menu-surface` hook on Combobox / MultiSelect / BoardSwitcher
|
|
1498
1735
|
menus) — NOT every elevated surface, so a drag-overlay or toast doesn't
|
|
1499
1736
|
unexpectedly fold open. */
|
|
1500
|
-
[data-style='3d-immersive'] [data-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1737
|
+
@scope ([data-style='3d-immersive']) to ([data-style]) {
|
|
1738
|
+
[data-surface='popover'],
|
|
1739
|
+
[data-menu-surface] {
|
|
1740
|
+
transform-origin: top center;
|
|
1741
|
+
animation: immersive-menu-open 200ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
1742
|
+
}
|
|
1504
1743
|
}
|
|
1505
1744
|
/* A Radix popover that flips ABOVE its trigger folds from the bottom edge. */
|
|
1506
|
-
[data-style='3d-immersive'] [data-
|
|
1507
|
-
|
|
1745
|
+
@scope ([data-style='3d-immersive']) to ([data-style]) {
|
|
1746
|
+
[data-surface='popover'][data-side='top'] {
|
|
1747
|
+
transform-origin: bottom center;
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
/* 3D / Immersive — THE SHELL CHROME (MOTIR-4253, drawn by MOTIR-4252).
|
|
1753
|
+
docs/styles/3d-immersive.md §4's `shell chrome` row + design/shell/
|
|
1754
|
+
3d-immersive-shell.mock.html, which injects this block verbatim so a frame in
|
|
1755
|
+
the asset cannot flatter a rule the implementation could not write.
|
|
1756
|
+
|
|
1757
|
+
── The defect this closes ─────────────────────────────────────────────────
|
|
1758
|
+
The plane ladder named ten surfaces and stopped before the FRAME: this block
|
|
1759
|
+
carried no [data-surface='header'] and no [data-surface='sidebar'] rule at
|
|
1760
|
+
all, while both hooks are emitted and five of the eleven styles already use
|
|
1761
|
+
them. So roughly a quarter of every signed-in screen rendered byte-identically
|
|
1762
|
+
to the default style — and §4's own `Quiet control / row` row was left
|
|
1763
|
+
asserting that a sidebar nav link is flat *because the surface it sits in
|
|
1764
|
+
floats*, of a surface no rule made float. §4b is the closure rule that keeps
|
|
1765
|
+
the ladder from going silent about a surface again.
|
|
1766
|
+
|
|
1767
|
+
── TWO OBJECTS, TWO PLANES, and the ladder says which ─────────────────────
|
|
1768
|
+
How far a surface floats is how far you can see UNDER its edges. The rail has
|
|
1769
|
+
four edges in the scene, so it can be an object at a distance: --shadow-card.
|
|
1770
|
+
The bar has exactly one — its bottom — so the only thing it can express is a
|
|
1771
|
+
CONTACT, which is --shadow-subtle, the ladder's tightest rung. A bar wearing
|
|
1772
|
+
--shadow-card would out-float the panel it frames on the strength of one
|
|
1773
|
+
visible edge.
|
|
1774
|
+
|
|
1775
|
+
Four things here are DECISIONS rather than syntax:
|
|
1776
|
+
1. box-shadow ONLY on the bar, never `position`. hand-drawn-indie carries the
|
|
1777
|
+
same guard explicitly a few hundred lines below — the sticky header is
|
|
1778
|
+
already a containing block and overriding it breaks the sticky — and the
|
|
1779
|
+
pleasing part is that the property is also the MECHANISM: TopNav's
|
|
1780
|
+
`sticky … z-30` is the stacking context that lets the lid's shadow paint
|
|
1781
|
+
over the region beneath it. A static header's downward shadow is painted
|
|
1782
|
+
behind the next sibling's background and is invisible. The constraint and
|
|
1783
|
+
the mechanism are the same line of the component.
|
|
1784
|
+
2. The rail's shared edge goes `border-right-color: transparent`, NOT
|
|
1785
|
+
`border: none`. The 1px stays in the box, so --width-rail-chrome (the
|
|
1786
|
+
rail's px-2 gutters plus that border) is unchanged. A floating panel has a
|
|
1787
|
+
shadow instead of a shared edge; it does not have a different width.
|
|
1788
|
+
3. Both a11y fallbacks restore a LINE the treatment made transparent, and
|
|
1789
|
+
NEITHER MOVES A PIXEL. The rail's hairline comes back as an `outline` at
|
|
1790
|
+
`outline-offset: -1px` — drawn outside the box, follows the radius, costs
|
|
1791
|
+
no layout — and the bar's `border-bottom` already has its width; only its
|
|
1792
|
+
colour was taken away. This is neumorphism's pattern (its own
|
|
1793
|
+
prefers-contrast / forced-colors blocks) applied to a treatment whose
|
|
1794
|
+
structure rests on a shadow.
|
|
1795
|
+
4. --spacing-rail-inset is `0px` in the base layer, so every other style is
|
|
1796
|
+
untouched and AppLayout's two sums compile to today's numbers everywhere
|
|
1797
|
+
else. That is what makes this a style-axis change rather than a shell one.
|
|
1798
|
+
|
|
1799
|
+
Colour: every value is an existing --el-* or an existing style-axis shadow
|
|
1800
|
+
token. The block adds and changes NO colour token, so the style and palette
|
|
1801
|
+
axes stay disjoint. */
|
|
1802
|
+
@scope ([data-style='3d-immersive']) to ([data-style]) {
|
|
1803
|
+
/* The RAIL — a floating PANEL, inset from the frame on all four sides so the
|
|
1804
|
+
immersive atmosphere reads around it. */
|
|
1805
|
+
[data-surface='sidebar'] {
|
|
1806
|
+
margin: var(--spacing-rail-inset);
|
|
1807
|
+
height: calc(100% - 2 * var(--spacing-rail-inset));
|
|
1808
|
+
border-radius: var(--radius-card);
|
|
1809
|
+
border-right-color: transparent;
|
|
1810
|
+
box-shadow: var(--shadow-card);
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
@scope ([data-style='3d-immersive']) to ([data-style]) {
|
|
1814
|
+
/* The TOP BAR — the LID. No fill (the atmosphere runs under it), no hairline,
|
|
1815
|
+
and a contact shadow cast down onto the region below. It keeps full-bleed
|
|
1816
|
+
edges and square corners: a bar with a radius and a margin is a second
|
|
1817
|
+
floating slab, not a lid. */
|
|
1818
|
+
[data-surface='header'] {
|
|
1819
|
+
background-color: transparent;
|
|
1820
|
+
border-bottom-color: transparent;
|
|
1821
|
+
box-shadow: var(--shadow-subtle);
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
/* Accessibility fallbacks — where STRUCTURE rests on a shadow, restore a solid
|
|
1826
|
+
line. Both are geometry-neutral by construction (see decision 3 above), so a
|
|
1827
|
+
high-contrast or forced-colours user gets the same layout, not a shifted one. */
|
|
1828
|
+
@media (prefers-contrast: more) {
|
|
1829
|
+
@scope ([data-style='3d-immersive']) to ([data-style]) {
|
|
1830
|
+
[data-surface='sidebar'] {
|
|
1831
|
+
outline: 1px solid var(--el-border-strong);
|
|
1832
|
+
outline-offset: -1px;
|
|
1833
|
+
}
|
|
1834
|
+
[data-surface='header'] {
|
|
1835
|
+
border-bottom-color: var(--el-border-strong);
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
@media (forced-colors: active) {
|
|
1840
|
+
@scope ([data-style='3d-immersive']) to ([data-style]) {
|
|
1841
|
+
[data-surface='sidebar'] {
|
|
1842
|
+
outline: 1px solid var(--el-border-strong);
|
|
1843
|
+
outline-offset: -1px;
|
|
1844
|
+
box-shadow: none;
|
|
1845
|
+
}
|
|
1846
|
+
[data-surface='header'] {
|
|
1847
|
+
border-bottom-color: var(--el-border-strong);
|
|
1848
|
+
box-shadow: none;
|
|
1849
|
+
}
|
|
1508
1850
|
}
|
|
1509
1851
|
}
|
|
1510
1852
|
|
|
@@ -1591,52 +1933,62 @@
|
|
|
1591
1933
|
dividers `.border-b` / `.border-t` (table rows, section rules) — the latter
|
|
1592
1934
|
scoped INSIDE `[data-surface]` panels to bound the count to real surfaces (and
|
|
1593
1935
|
their rows are virtualised, so only ~viewport-many overlays ever render). */
|
|
1594
|
-
[data-style='hand-drawn-indie']
|
|
1595
|
-
border
|
|
1936
|
+
@scope ([data-style='hand-drawn-indie']) to ([data-style]) {
|
|
1937
|
+
.border {
|
|
1938
|
+
border-width: 2px;
|
|
1939
|
+
}
|
|
1596
1940
|
}
|
|
1597
|
-
[data-style='hand-drawn-indie']
|
|
1598
|
-
|
|
1941
|
+
@scope ([data-style='hand-drawn-indie']) to ([data-style]) {
|
|
1942
|
+
.border:not(.fixed):not(.absolute):not(.sticky) {
|
|
1943
|
+
position: relative;
|
|
1944
|
+
}
|
|
1599
1945
|
}
|
|
1600
|
-
[data-style='hand-drawn-indie']
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1946
|
+
@scope ([data-style='hand-drawn-indie']) to ([data-style]) {
|
|
1947
|
+
.border::after {
|
|
1948
|
+
content: '';
|
|
1949
|
+
position: absolute;
|
|
1950
|
+
inset: -1px;
|
|
1951
|
+
pointer-events: none;
|
|
1952
|
+
border: 2px solid var(--el-border-strong);
|
|
1953
|
+
border-radius: inherit;
|
|
1954
|
+
filter: url(#hd-rough);
|
|
1955
|
+
}
|
|
1608
1956
|
}
|
|
1609
1957
|
|
|
1610
1958
|
/* Internal grid dividers — make the inner rules as rough as the frame. Scoped
|
|
1611
1959
|
inside framed surfaces; `:not(.border)` so a full-box element isn't handled
|
|
1612
1960
|
twice; `:not(:last-child)` mirrors the common `last:border-b-0` strip so the
|
|
1613
1961
|
final row gets no phantom line. */
|
|
1614
|
-
[data-style='hand-drawn-indie']
|
|
1615
|
-
[data-surface]
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
}
|
|
1619
|
-
[data-style='hand-drawn-indie'] [data-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1962
|
+
@scope ([data-style='hand-drawn-indie']) to ([data-style]) {
|
|
1963
|
+
[data-surface] :is(.border-b, .border-t):not(.border):not(.fixed):not(.absolute):not(.sticky) {
|
|
1964
|
+
position: relative;
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
@scope ([data-style='hand-drawn-indie']) to ([data-style]) {
|
|
1968
|
+
[data-surface] .border-b:not(.border)::after {
|
|
1969
|
+
content: '';
|
|
1970
|
+
position: absolute;
|
|
1971
|
+
left: 0;
|
|
1972
|
+
right: 0;
|
|
1973
|
+
bottom: -1px;
|
|
1974
|
+
height: 0;
|
|
1975
|
+
pointer-events: none;
|
|
1976
|
+
border-bottom: 2px solid var(--el-border-strong);
|
|
1977
|
+
filter: url(#hd-rough);
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
@scope ([data-style='hand-drawn-indie']) to ([data-style]) {
|
|
1981
|
+
[data-surface] .border-t:not(.border)::after {
|
|
1982
|
+
content: '';
|
|
1983
|
+
position: absolute;
|
|
1984
|
+
left: 0;
|
|
1985
|
+
right: 0;
|
|
1986
|
+
top: -1px;
|
|
1987
|
+
height: 0;
|
|
1988
|
+
pointer-events: none;
|
|
1989
|
+
border-top: 2px solid var(--el-border-strong);
|
|
1990
|
+
filter: url(#hd-rough);
|
|
1991
|
+
}
|
|
1640
1992
|
}
|
|
1641
1993
|
/* Suppress the overlay only where the REAL divider is removed: the last row of a
|
|
1642
1994
|
fully-rendered list strips its border via `last:border-b-0`. (We can't use
|
|
@@ -1644,8 +1996,10 @@
|
|
|
1644
1996
|
own `role="rowgroup"`, so it is itself a `:last-child` and would be wrongly
|
|
1645
1997
|
skipped, which left the work-item table header's divider straight.) Same
|
|
1646
1998
|
specificity as the draw rule, placed after, so it wins on the real last row. */
|
|
1647
|
-
[data-style='hand-drawn-indie'] [data-
|
|
1648
|
-
|
|
1999
|
+
@scope ([data-style='hand-drawn-indie']) to ([data-style]) {
|
|
2000
|
+
[data-surface] .last\:border-b-0:last-child::after {
|
|
2001
|
+
content: none;
|
|
2002
|
+
}
|
|
1649
2003
|
}
|
|
1650
2004
|
|
|
1651
2005
|
/* The APP SHELL FRAME — the sidebar rail's right edge + the top bar's bottom edge
|
|
@@ -1659,31 +2013,36 @@
|
|
|
1659
2013
|
ONLY to a STATIC host (the rail) — never the STICKY header, which is already a
|
|
1660
2014
|
containing block (overriding it would break the sticky), mirroring the `.border`
|
|
1661
2015
|
rule's guard. */
|
|
1662
|
-
[data-style='hand-drawn-indie']
|
|
2016
|
+
@scope ([data-style='hand-drawn-indie']) to ([data-style]) {
|
|
1663
2017
|
:is([data-surface='sidebar'], [data-surface='header']):not(.fixed):not(.absolute):not(.sticky) {
|
|
1664
|
-
|
|
1665
|
-
}
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
2018
|
+
position: relative;
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
@scope ([data-style='hand-drawn-indie']) to ([data-style]) {
|
|
2022
|
+
[data-surface='sidebar']::after {
|
|
2023
|
+
content: '';
|
|
2024
|
+
position: absolute;
|
|
2025
|
+
top: 0;
|
|
2026
|
+
bottom: 0;
|
|
2027
|
+
right: -1px;
|
|
2028
|
+
width: 0;
|
|
2029
|
+
pointer-events: none;
|
|
2030
|
+
border-right: 2px solid var(--el-border-strong);
|
|
2031
|
+
filter: url(#hd-rough);
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
@scope ([data-style='hand-drawn-indie']) to ([data-style]) {
|
|
2035
|
+
[data-surface='header']::after {
|
|
2036
|
+
content: '';
|
|
2037
|
+
position: absolute;
|
|
2038
|
+
left: 0;
|
|
2039
|
+
right: 0;
|
|
2040
|
+
bottom: -1px;
|
|
2041
|
+
height: 0;
|
|
2042
|
+
pointer-events: none;
|
|
2043
|
+
border-bottom: 2px solid var(--el-border-strong);
|
|
2044
|
+
filter: url(#hd-rough);
|
|
2045
|
+
}
|
|
1687
2046
|
}
|
|
1688
2047
|
|
|
1689
2048
|
/* ─────────────────────────────────────────────────────────────────────────
|
|
@@ -1795,8 +2154,11 @@
|
|
|
1795
2154
|
panels look MOULDED OUT OF the same material rather than placed on top. Both
|
|
1796
2155
|
the canvas and the surfaces below share `var(--el-surface)`, which is the whole
|
|
1797
2156
|
neumorphic illusion. Palette-derived (no hue of its own). */
|
|
1798
|
-
[data-style='neumorphism']
|
|
1799
|
-
|
|
2157
|
+
@scope ([data-style='neumorphism']) to ([data-style]) {
|
|
2158
|
+
body,
|
|
2159
|
+
[data-app-shell] {
|
|
2160
|
+
background-color: var(--el-surface);
|
|
2161
|
+
}
|
|
1800
2162
|
}
|
|
1801
2163
|
|
|
1802
2164
|
/* Raised container surfaces — cards & popovers swell OUT of the field on a paired
|
|
@@ -1806,45 +2168,53 @@
|
|
|
1806
2168
|
from hue. The faint hairline is kept as the accessibility fallback so structure
|
|
1807
2169
|
doesn't depend on the shadow alone. Opaque `--el-surface` keeps text on the
|
|
1808
2170
|
palette's AA-tuned surface. */
|
|
1809
|
-
[data-style='neumorphism'] [data-
|
|
1810
|
-
[data-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
2171
|
+
@scope ([data-style='neumorphism']) to ([data-style]) {
|
|
2172
|
+
[data-surface='card'],
|
|
2173
|
+
[data-surface='popover'] {
|
|
2174
|
+
background-color: var(--el-surface);
|
|
2175
|
+
border-color: color-mix(in srgb, var(--el-border) 45%, transparent);
|
|
2176
|
+
box-shadow:
|
|
2177
|
+
var(--neu-distance) var(--neu-distance) var(--neu-blur) var(--neu-shadow),
|
|
2178
|
+
calc(-1 * var(--neu-distance)) calc(-1 * var(--neu-distance)) var(--neu-blur) var(--neu-light);
|
|
2179
|
+
}
|
|
1816
2180
|
}
|
|
1817
2181
|
|
|
1818
2182
|
/* Modal panel — the same raised mould, a touch deeper so the dialog separates
|
|
1819
2183
|
from the field behind it (body text stays on the opaque surface). */
|
|
1820
|
-
[data-style='neumorphism'] [data-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
var(--neu-
|
|
1826
|
-
|
|
1827
|
-
var(--neu-
|
|
2184
|
+
@scope ([data-style='neumorphism']) to ([data-style]) {
|
|
2185
|
+
[data-surface='modal'] {
|
|
2186
|
+
background-color: var(--el-surface);
|
|
2187
|
+
border-color: color-mix(in srgb, var(--el-border) 45%, transparent);
|
|
2188
|
+
box-shadow:
|
|
2189
|
+
calc(var(--neu-distance) + 2px) calc(var(--neu-distance) + 2px) calc(var(--neu-blur) + 8px)
|
|
2190
|
+
var(--neu-shadow),
|
|
2191
|
+
calc(-1 * var(--neu-distance)) calc(-1 * var(--neu-distance)) calc(var(--neu-blur) + 4px)
|
|
2192
|
+
var(--neu-light);
|
|
2193
|
+
}
|
|
1828
2194
|
}
|
|
1829
2195
|
|
|
1830
2196
|
/* The sidebar rail — moulded from the same field as the chrome, gently raised so
|
|
1831
2197
|
it reads as part of the one continuous surface. */
|
|
1832
|
-
[data-style='neumorphism'] [data-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
2198
|
+
@scope ([data-style='neumorphism']) to ([data-style]) {
|
|
2199
|
+
[data-surface='sidebar'] {
|
|
2200
|
+
background-color: var(--el-surface);
|
|
2201
|
+
box-shadow:
|
|
2202
|
+
var(--neu-distance) 0 var(--neu-blur) var(--neu-shadow),
|
|
2203
|
+
calc(-1 * var(--neu-distance)) 0 var(--neu-blur) var(--neu-light);
|
|
2204
|
+
}
|
|
1837
2205
|
}
|
|
1838
2206
|
|
|
1839
2207
|
/* Form controls — the inverse mould: a CONCAVE INSET well pressed INTO the field
|
|
1840
2208
|
(the soft-UI input look), the paired shadow flipped to `inset`. */
|
|
1841
|
-
[data-style='neumorphism'] [data-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
var(--neu-
|
|
2209
|
+
@scope ([data-style='neumorphism']) to ([data-style]) {
|
|
2210
|
+
[data-surface='input'] {
|
|
2211
|
+
background-color: var(--el-surface);
|
|
2212
|
+
border-color: color-mix(in srgb, var(--el-border) 45%, transparent);
|
|
2213
|
+
box-shadow:
|
|
2214
|
+
inset var(--neu-distance-sm) var(--neu-distance-sm) var(--neu-blur-sm) var(--neu-shadow),
|
|
2215
|
+
inset calc(-1 * var(--neu-distance-sm)) calc(-1 * var(--neu-distance-sm)) var(--neu-blur-sm)
|
|
2216
|
+
var(--neu-light);
|
|
2217
|
+
}
|
|
1848
2218
|
}
|
|
1849
2219
|
|
|
1850
2220
|
/* Accessibility fallback — when a user asks for more contrast (or forced colours),
|
|
@@ -1852,23 +2222,27 @@
|
|
|
1852
2222
|
moulded surface and flatten the paired shadow to a single readable lift.
|
|
1853
2223
|
Palette-derived (var(--el-border)); no hue. */
|
|
1854
2224
|
@media (prefers-contrast: more) {
|
|
1855
|
-
[data-style='neumorphism'] [data-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
2225
|
+
@scope ([data-style='neumorphism']) to ([data-style]) {
|
|
2226
|
+
[data-surface='card'],
|
|
2227
|
+
[data-surface='popover'],
|
|
2228
|
+
[data-surface='modal'],
|
|
2229
|
+
[data-surface='input'],
|
|
2230
|
+
[data-surface='sidebar'] {
|
|
2231
|
+
border-color: var(--el-border-strong);
|
|
2232
|
+
box-shadow: var(--shadow-card);
|
|
2233
|
+
}
|
|
1862
2234
|
}
|
|
1863
2235
|
}
|
|
1864
2236
|
@media (forced-colors: active) {
|
|
1865
|
-
[data-style='neumorphism'] [data-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
2237
|
+
@scope ([data-style='neumorphism']) to ([data-style]) {
|
|
2238
|
+
[data-surface='card'],
|
|
2239
|
+
[data-surface='popover'],
|
|
2240
|
+
[data-surface='modal'],
|
|
2241
|
+
[data-surface='input'],
|
|
2242
|
+
[data-surface='sidebar'] {
|
|
2243
|
+
border-color: var(--el-border-strong);
|
|
2244
|
+
box-shadow: none;
|
|
2245
|
+
}
|
|
1872
2246
|
}
|
|
1873
2247
|
}
|
|
1874
2248
|
|
|
@@ -1972,62 +2346,92 @@
|
|
|
1972
2346
|
axis overrides `--el-*`, never `--color-*`, which keeps the horizon orthogonal
|
|
1973
2347
|
to the palette. Painted as the body's own background (always behind content),
|
|
1974
2348
|
the proven 3D/Immersive atmosphere approach. */
|
|
1975
|
-
[data-style='retrofuturism']
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
2349
|
+
@scope ([data-style='retrofuturism']) to ([data-style]) {
|
|
2350
|
+
body,
|
|
2351
|
+
[data-app-shell] {
|
|
2352
|
+
background-image:
|
|
2353
|
+
linear-gradient(
|
|
2354
|
+
180deg,
|
|
2355
|
+
transparent 54%,
|
|
2356
|
+
color-mix(in srgb, var(--el-highlight) var(--retro-horizon), transparent) 60.5%,
|
|
2357
|
+
color-mix(in srgb, var(--el-accent) var(--retro-horizon), transparent) 62.5%,
|
|
2358
|
+
transparent 69%
|
|
2359
|
+
),
|
|
2360
|
+
linear-gradient(
|
|
2361
|
+
180deg,
|
|
2362
|
+
color-mix(in srgb, var(--el-link) var(--retro-sky), transparent),
|
|
2363
|
+
transparent 46%
|
|
2364
|
+
),
|
|
2365
|
+
linear-gradient(
|
|
2366
|
+
0deg,
|
|
2367
|
+
color-mix(in srgb, var(--el-accent) var(--retro-ground), transparent),
|
|
2368
|
+
transparent 42%
|
|
2369
|
+
);
|
|
2370
|
+
background-size: 100% 100%;
|
|
2371
|
+
background-attachment: fixed;
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
/* ⚠️ THE SHELL BECOMES A STACKING CONTEXT — the one place the shell-canvas
|
|
2376
|
+
pairing needs more than a second selector (MOTIR-4234). The grid floor below
|
|
2377
|
+
is `position: fixed; z-index: -1`, and both shell roots are `relative` with
|
|
2378
|
+
`z-index: auto`, so they establish NO stacking context: a negative-z-index
|
|
2379
|
+
descendant of the shell would be painted into the ROOT stacking context, at
|
|
2380
|
+
the step BEFORE positioned elements — i.e. behind the shell's own background,
|
|
2381
|
+
which is exactly where `body::after` already is and exactly why the floor was
|
|
2382
|
+
invisible on every signed-in route. `isolation: isolate` makes the shell its
|
|
2383
|
+
own context, so its `::after` resolves INSIDE it: above the shell canvas
|
|
2384
|
+
(step 1) and below all shell content (step 3 onwards), which is the
|
|
2385
|
+
disposition the floor is designed for.
|
|
2386
|
+
|
|
2387
|
+
Scoped to retrofuturism, because it is the only style with a canvas LAYER
|
|
2388
|
+
below content; the shell is left alone under every other style. It creates no
|
|
2389
|
+
containing block, so the `position: fixed` floor still resolves against the
|
|
2390
|
+
viewport and the shell's `overflow: hidden` does not clip it. */
|
|
2391
|
+
@scope ([data-style='retrofuturism']) to ([data-style]) {
|
|
2392
|
+
[data-app-shell] {
|
|
2393
|
+
isolation: isolate;
|
|
2394
|
+
}
|
|
1996
2395
|
}
|
|
1997
2396
|
|
|
1998
2397
|
/* The Outrun GRID FLOOR — a receding neon grid below the horizon. A separate
|
|
1999
|
-
fixed layer (`body::after
|
|
2398
|
+
fixed layer (`body::after`, and `[data-app-shell]::after` for the signed-in
|
|
2399
|
+
shell — the pairing above) so it can be perspective-tilted and masked to fade
|
|
2000
2400
|
into the horizon; `z-index: -1` keeps it BEHIND all app content (above only the
|
|
2001
|
-
|
|
2002
|
-
UI. Lines are `color-mix()` over `--el-*` accents
|
|
2003
|
-
their own); the mask uses the achromatic `black`
|
|
2004
|
-
STATIC depth — like 3D/Immersive's atmosphere it
|
|
2005
|
-
not motion-gated (there is no animation to
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2401
|
+
sky background of whichever of the two is painting), so it textures the floor
|
|
2402
|
+
without ever covering the UI. Lines are `color-mix()` over `--el-*` accents
|
|
2403
|
+
(palette-derived, no hue of their own); the mask uses the achromatic `black`
|
|
2404
|
+
keyword (no hex). The grid is STATIC depth — like 3D/Immersive's atmosphere it
|
|
2405
|
+
carries the identity, so it is not motion-gated (there is no animation to
|
|
2406
|
+
suppress). */
|
|
2407
|
+
@scope ([data-style='retrofuturism']) to ([data-style]) {
|
|
2408
|
+
body::after,
|
|
2409
|
+
[data-app-shell]::after {
|
|
2410
|
+
content: '';
|
|
2411
|
+
position: fixed;
|
|
2412
|
+
left: -30%;
|
|
2413
|
+
right: -30%;
|
|
2414
|
+
bottom: 0;
|
|
2415
|
+
height: 42vh;
|
|
2416
|
+
background-image:
|
|
2417
|
+
repeating-linear-gradient(
|
|
2418
|
+
0deg,
|
|
2419
|
+
color-mix(in srgb, var(--el-highlight) var(--retro-grid), transparent) 0 2px,
|
|
2420
|
+
transparent 2px var(--retro-grid-size)
|
|
2421
|
+
),
|
|
2422
|
+
repeating-linear-gradient(
|
|
2423
|
+
90deg,
|
|
2424
|
+
color-mix(in srgb, var(--el-accent) var(--retro-grid), transparent) 0 2px,
|
|
2425
|
+
transparent 2px var(--retro-grid-size)
|
|
2426
|
+
);
|
|
2427
|
+
background-size: var(--retro-grid-size) var(--retro-grid-size);
|
|
2428
|
+
transform: perspective(300px) rotateX(75deg);
|
|
2429
|
+
transform-origin: bottom center;
|
|
2430
|
+
-webkit-mask-image: linear-gradient(to top, black 2%, transparent 80%);
|
|
2431
|
+
mask-image: linear-gradient(to top, black 2%, transparent 80%);
|
|
2432
|
+
pointer-events: none;
|
|
2433
|
+
z-index: -1;
|
|
2434
|
+
}
|
|
2031
2435
|
}
|
|
2032
2436
|
|
|
2033
2437
|
/* Chrome container surfaces — OPAQUE (text AA is preserved against the base
|
|
@@ -2039,73 +2443,79 @@
|
|
|
2039
2443
|
so they never pull a surface far from its base tone; the inset top/bottom lines
|
|
2040
2444
|
are the lit chrome rim. Applied to EVERY panel via the global card-radius
|
|
2041
2445
|
utility (the 3D/Immersive coverage approach), not just the <Card> primitive. */
|
|
2042
|
-
[data-style='retrofuturism'] [data-
|
|
2043
|
-
[data-
|
|
2044
|
-
[data-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2446
|
+
@scope ([data-style='retrofuturism']) to ([data-style]) {
|
|
2447
|
+
[data-surface='card'],
|
|
2448
|
+
[data-surface='popover'],
|
|
2449
|
+
.rounded-\(--radius-card\) {
|
|
2450
|
+
background-color: var(--el-surface);
|
|
2451
|
+
background-image:
|
|
2452
|
+
linear-gradient(
|
|
2453
|
+
158deg,
|
|
2454
|
+
transparent 36%,
|
|
2455
|
+
color-mix(in srgb, white var(--retro-spec), transparent) 49%,
|
|
2456
|
+
transparent 62%
|
|
2457
|
+
),
|
|
2458
|
+
linear-gradient(
|
|
2459
|
+
180deg,
|
|
2460
|
+
color-mix(in srgb, white var(--retro-bevel-light), transparent) 0%,
|
|
2461
|
+
transparent 16%,
|
|
2462
|
+
transparent 78%,
|
|
2463
|
+
color-mix(in srgb, black var(--retro-bevel-shade), transparent) 100%
|
|
2464
|
+
);
|
|
2465
|
+
box-shadow:
|
|
2466
|
+
var(--shadow-card),
|
|
2467
|
+
inset 0 1px 0 color-mix(in srgb, white 60%, transparent),
|
|
2468
|
+
inset 0 -1px 0 color-mix(in srgb, black 24%, transparent),
|
|
2469
|
+
0 0 28px -8px color-mix(in srgb, var(--el-accent) var(--retro-glow), transparent);
|
|
2470
|
+
}
|
|
2065
2471
|
}
|
|
2066
2472
|
|
|
2067
2473
|
/* Modal panel — the same chrome, with a deeper palette glow so the dialog
|
|
2068
2474
|
separates cleanly from the grid behind it (and body text stays on the solid
|
|
2069
2475
|
surface). Overrides the card-radius rule above for the modal radius. */
|
|
2070
|
-
[data-style='retrofuturism'] [data-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2476
|
+
@scope ([data-style='retrofuturism']) to ([data-style]) {
|
|
2477
|
+
[data-surface='modal'] {
|
|
2478
|
+
background-color: var(--el-surface);
|
|
2479
|
+
background-image:
|
|
2480
|
+
linear-gradient(
|
|
2481
|
+
158deg,
|
|
2482
|
+
transparent 34%,
|
|
2483
|
+
color-mix(in srgb, white var(--retro-spec), transparent) 49%,
|
|
2484
|
+
transparent 64%
|
|
2485
|
+
),
|
|
2486
|
+
linear-gradient(
|
|
2487
|
+
180deg,
|
|
2488
|
+
color-mix(in srgb, white var(--retro-bevel-light), transparent) 0%,
|
|
2489
|
+
transparent 14%,
|
|
2490
|
+
transparent 80%,
|
|
2491
|
+
color-mix(in srgb, black var(--retro-bevel-shade), transparent) 100%
|
|
2492
|
+
);
|
|
2493
|
+
box-shadow:
|
|
2494
|
+
var(--shadow-modal),
|
|
2495
|
+
inset 0 1px 0 color-mix(in srgb, white 60%, transparent),
|
|
2496
|
+
0 0 56px -14px color-mix(in srgb, var(--el-highlight) var(--retro-glow-strong), transparent);
|
|
2497
|
+
}
|
|
2090
2498
|
}
|
|
2091
2499
|
|
|
2092
2500
|
/* The sidebar rail — opaque chrome with a quieter specular + bevel so it reads as
|
|
2093
2501
|
part of the polished-metal family, not a flat slab. */
|
|
2094
|
-
[data-style='retrofuturism'] [data-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2502
|
+
@scope ([data-style='retrofuturism']) to ([data-style]) {
|
|
2503
|
+
[data-surface='sidebar'] {
|
|
2504
|
+
background-color: var(--el-sidebar-bg);
|
|
2505
|
+
background-image:
|
|
2506
|
+
linear-gradient(
|
|
2507
|
+
158deg,
|
|
2508
|
+
transparent 40%,
|
|
2509
|
+
color-mix(in srgb, white var(--retro-sheen), transparent) 50%,
|
|
2510
|
+
transparent 60%
|
|
2511
|
+
),
|
|
2512
|
+
linear-gradient(
|
|
2513
|
+
180deg,
|
|
2514
|
+
color-mix(in srgb, white var(--retro-sheen), transparent),
|
|
2515
|
+
transparent 24%,
|
|
2516
|
+
color-mix(in srgb, black var(--retro-bevel-shade), transparent) 100%
|
|
2517
|
+
);
|
|
2518
|
+
}
|
|
2109
2519
|
}
|
|
2110
2520
|
|
|
2111
2521
|
/* Text inputs (a TYPING field) — a RECESSED chrome well: the bevel inverts (a
|
|
@@ -2113,14 +2523,16 @@
|
|
|
2113
2523
|
metal. Clickable controls (dropdown / select / picker triggers + buttons) get
|
|
2114
2524
|
the RAISED chrome below instead — matching the CTA — so a typing field stays
|
|
2115
2525
|
recessed while everything you click reads as a raised chrome control. */
|
|
2116
|
-
[data-style='retrofuturism'] [data-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2526
|
+
@scope ([data-style='retrofuturism']) to ([data-style]) {
|
|
2527
|
+
[data-surface='input'] {
|
|
2528
|
+
background-color: var(--el-page-bg);
|
|
2529
|
+
background-image: linear-gradient(
|
|
2530
|
+
180deg,
|
|
2531
|
+
color-mix(in srgb, black var(--retro-sheen), transparent) 0%,
|
|
2532
|
+
transparent 34%
|
|
2533
|
+
);
|
|
2534
|
+
box-shadow: inset 0 1px 3px color-mix(in srgb, black var(--retro-glow), transparent);
|
|
2535
|
+
}
|
|
2124
2536
|
}
|
|
2125
2537
|
|
|
2126
2538
|
/* ── EVERY clickable control reads as RETRO CHROME (7.3.40) ──────────────────
|
|
@@ -2151,46 +2563,50 @@
|
|
|
2151
2563
|
/* FILLED CTAs — accent (primary) keeps its hue under a diagonal specular glint +
|
|
2152
2564
|
a vertical metallic sweep + a palette glow. Keyed on the fill class so it
|
|
2153
2565
|
covers the `Button` primitive AND `buttonVariants()` anchors alike. */
|
|
2154
|
-
[data-style='retrofuturism']
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2566
|
+
@scope ([data-style='retrofuturism']) to ([data-style]) {
|
|
2567
|
+
.rounded-\(--radius-btn\).bg-\(--el-accent\) {
|
|
2568
|
+
background-image:
|
|
2569
|
+
linear-gradient(
|
|
2570
|
+
158deg,
|
|
2571
|
+
transparent 38%,
|
|
2572
|
+
color-mix(in srgb, white var(--retro-spec), transparent) 50%,
|
|
2573
|
+
transparent 62%
|
|
2574
|
+
),
|
|
2575
|
+
linear-gradient(
|
|
2576
|
+
180deg,
|
|
2577
|
+
color-mix(in srgb, white var(--retro-spec), var(--el-accent)) 0%,
|
|
2578
|
+
var(--el-accent) 46%,
|
|
2579
|
+
color-mix(in srgb, black var(--retro-bevel-shade), var(--el-accent)) 100%
|
|
2580
|
+
);
|
|
2581
|
+
box-shadow:
|
|
2582
|
+
var(--shadow-subtle),
|
|
2583
|
+
inset 0 1px 0 color-mix(in srgb, white 60%, transparent),
|
|
2584
|
+
0 0 22px -6px color-mix(in srgb, var(--el-accent) var(--retro-glow-strong), transparent);
|
|
2585
|
+
}
|
|
2172
2586
|
}
|
|
2173
2587
|
|
|
2174
2588
|
/* Danger CTAs — the same machined sweep, in the danger hue (so a destructive
|
|
2175
2589
|
button reads as red chrome, not a flat red, matching the accent CTA). */
|
|
2176
|
-
[data-style='retrofuturism']
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2590
|
+
@scope ([data-style='retrofuturism']) to ([data-style]) {
|
|
2591
|
+
.rounded-\(--radius-btn\).bg-\(--el-danger\) {
|
|
2592
|
+
background-image:
|
|
2593
|
+
linear-gradient(
|
|
2594
|
+
158deg,
|
|
2595
|
+
transparent 38%,
|
|
2596
|
+
color-mix(in srgb, white var(--retro-spec), transparent) 50%,
|
|
2597
|
+
transparent 62%
|
|
2598
|
+
),
|
|
2599
|
+
linear-gradient(
|
|
2600
|
+
180deg,
|
|
2601
|
+
color-mix(in srgb, white var(--retro-spec), var(--el-danger)) 0%,
|
|
2602
|
+
var(--el-danger) 46%,
|
|
2603
|
+
color-mix(in srgb, black var(--retro-bevel-shade), var(--el-danger)) 100%
|
|
2604
|
+
);
|
|
2605
|
+
box-shadow:
|
|
2606
|
+
var(--shadow-subtle),
|
|
2607
|
+
inset 0 1px 0 color-mix(in srgb, white 60%, transparent),
|
|
2608
|
+
0 0 22px -6px color-mix(in srgb, var(--el-danger) var(--retro-glow-strong), transparent);
|
|
2609
|
+
}
|
|
2194
2610
|
}
|
|
2195
2611
|
|
|
2196
2612
|
/* NEUTRAL STEEL chrome — every OTHER clickable control: bordered buttons + links
|
|
@@ -2199,67 +2615,446 @@
|
|
|
2199
2615
|
`:not([role='group'])` excludes the segmented TRACK (its own rule below); the
|
|
2200
2616
|
FILLED CTAs are excluded automatically (they carry a fill class, not `.border`).
|
|
2201
2617
|
The gunmetal base is set here because secondary controls ship `bg-transparent`. */
|
|
2202
|
-
[data-style='retrofuturism']
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2618
|
+
@scope ([data-style='retrofuturism']) to ([data-style]) {
|
|
2619
|
+
.rounded-\(--radius-btn\).border:not([role='group']),
|
|
2620
|
+
button.rounded-\(--radius-input\) {
|
|
2621
|
+
background-color: color-mix(in srgb, black 24%, var(--el-surface));
|
|
2622
|
+
background-image:
|
|
2623
|
+
linear-gradient(
|
|
2624
|
+
158deg,
|
|
2625
|
+
transparent 36%,
|
|
2626
|
+
color-mix(in srgb, white var(--retro-spec), transparent) 50%,
|
|
2627
|
+
transparent 64%
|
|
2628
|
+
),
|
|
2629
|
+
linear-gradient(
|
|
2630
|
+
180deg,
|
|
2631
|
+
color-mix(in srgb, white var(--retro-bevel-light), transparent) 0%,
|
|
2632
|
+
transparent 42%,
|
|
2633
|
+
transparent 60%,
|
|
2634
|
+
color-mix(in srgb, black var(--retro-bevel-shade), transparent) 100%
|
|
2635
|
+
);
|
|
2636
|
+
box-shadow:
|
|
2637
|
+
var(--shadow-subtle),
|
|
2638
|
+
inset 0 1px 0 color-mix(in srgb, white 60%, transparent),
|
|
2639
|
+
inset 0 -1px 0 color-mix(in srgb, black 24%, transparent),
|
|
2640
|
+
0 0 18px -8px color-mix(in srgb, var(--el-accent) var(--retro-glow), transparent);
|
|
2641
|
+
}
|
|
2224
2642
|
}
|
|
2225
2643
|
|
|
2226
2644
|
/* SEGMENTED control (the board "Group by" switch + every other `Segmented` use):
|
|
2227
2645
|
the classic metal segmented control — a RECESSED gunmetal TRACK (the
|
|
2228
2646
|
`[role='group']` wrapper) with the ACTIVE segment (`aria-pressed='true'`) raised
|
|
2229
2647
|
out of it as a chrome chip. Inactive segments stay transparent on the track. */
|
|
2230
|
-
[data-style='retrofuturism'] [
|
|
2231
|
-
|
|
2232
|
-
|
|
2648
|
+
@scope ([data-style='retrofuturism']) to ([data-style]) {
|
|
2649
|
+
[role='group'].rounded-\(--radius-btn\) {
|
|
2650
|
+
background-color: color-mix(in srgb, black 15%, var(--el-surface));
|
|
2651
|
+
box-shadow: inset 0 1px 3px color-mix(in srgb, black var(--retro-glow), transparent);
|
|
2652
|
+
}
|
|
2233
2653
|
}
|
|
2234
|
-
[data-style='retrofuturism']
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2654
|
+
@scope ([data-style='retrofuturism']) to ([data-style]) {
|
|
2655
|
+
[role='group'] [aria-pressed='true'] {
|
|
2656
|
+
background-color: color-mix(in srgb, black 24%, var(--el-surface));
|
|
2657
|
+
background-image:
|
|
2658
|
+
linear-gradient(
|
|
2659
|
+
158deg,
|
|
2660
|
+
transparent 36%,
|
|
2661
|
+
color-mix(in srgb, white var(--retro-spec), transparent) 50%,
|
|
2662
|
+
transparent 64%
|
|
2663
|
+
),
|
|
2664
|
+
linear-gradient(
|
|
2665
|
+
180deg,
|
|
2666
|
+
color-mix(in srgb, white var(--retro-bevel-light), transparent) 0%,
|
|
2667
|
+
transparent 42%,
|
|
2668
|
+
transparent 60%,
|
|
2669
|
+
color-mix(in srgb, black var(--retro-bevel-shade), transparent) 100%
|
|
2670
|
+
);
|
|
2671
|
+
box-shadow:
|
|
2672
|
+
var(--shadow-subtle),
|
|
2673
|
+
inset 0 1px 0 color-mix(in srgb, white 60%, transparent),
|
|
2674
|
+
inset 0 -1px 0 color-mix(in srgb, black 24%, transparent);
|
|
2675
|
+
}
|
|
2254
2676
|
}
|
|
2255
2677
|
|
|
2256
2678
|
/* Hover — brighten the steel a touch (the controls' own `hover:bg-*` utilities
|
|
2257
2679
|
are out-specified by the rules above, so restore a clear hover signal). */
|
|
2258
|
-
[data-style='retrofuturism']
|
|
2680
|
+
@scope ([data-style='retrofuturism']) to ([data-style]) {
|
|
2259
2681
|
.rounded-\(--radius-btn\).border:not([role='group']):hover:not(:disabled),
|
|
2260
|
-
|
|
2261
|
-
[
|
|
2262
|
-
|
|
2682
|
+
button.rounded-\(--radius-input\):hover:not(:disabled),
|
|
2683
|
+
[role='group'] [aria-pressed='false']:hover:not(:disabled) {
|
|
2684
|
+
background-color: color-mix(in srgb, black 14%, var(--el-surface));
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
|
|
2688
|
+
/* ─────────────────────────────────────────────────────────────────────────
|
|
2689
|
+
THE HERO AI CONTROL, PER STYLE (drawn by MOTIR-4742, wired by MOTIR-4743).
|
|
2690
|
+
|
|
2691
|
+
The two controls that summon the planning workspace — the header pill
|
|
2692
|
+
(`components/planning/PlanWithAILauncher.tsx`) and the floating M orb
|
|
2693
|
+
(`components/planning/PlanWithAIFab.tsx`) — are the product's headline
|
|
2694
|
+
affordance and the sanctioned exception to the flat-button norm
|
|
2695
|
+
(`design/ai-chat/design-notes.md` § *The hero control is STYLE-AWARE*). Each
|
|
2696
|
+
style gives them its own material. Before this section they rendered
|
|
2697
|
+
byte-identically under all eleven: the hook the design prescribed was emitted
|
|
2698
|
+
nowhere and the fill was painted inline, where no rule can reach it.
|
|
2699
|
+
|
|
2700
|
+
── TWO ATTRIBUTES, AND THE SECOND IS LOAD-BEARING ─────────────────────────
|
|
2701
|
+
`data-ai-cta` distinguishes the two because they do not share a fill recipe:
|
|
2702
|
+
the orb's `radial-gradient` first stop is `--orb-lit-mix`, a guarded contrast
|
|
2703
|
+
knob (MOTIR-3207, `tests/theme/orb-glyph-contrast.test.ts`), and a shared
|
|
2704
|
+
`background-image` would silently overwrite it. So the shape-agnostic
|
|
2705
|
+
properties — border, outer glow, type, ink — are written on
|
|
2706
|
+
`[data-surface='ai-cta']`, and every FILL under `[data-ai-cta='pill']` or
|
|
2707
|
+
`[data-ai-cta='orb']`.
|
|
2708
|
+
|
|
2709
|
+
── THE TWO INLINE-DECLARED PROPERTIES TRAVEL ON CUSTOM PROPERTIES ─────────
|
|
2710
|
+
The components declare exactly two things in an inline `style` prop —
|
|
2711
|
+
`background-image` and `box-shadow` — and an inline declaration beats every
|
|
2712
|
+
stylesheet rule. So BOTH are read through a `var()` seam:
|
|
2713
|
+
`background-image: var(--plan-hero-fill, <the base recipe>)` (MOTIR-4743) and
|
|
2714
|
+
`box-shadow: var(--plan-hero-shadow, <the base look>)` (MOTIR-3522), with the
|
|
2715
|
+
orb's twins beside them.
|
|
2716
|
+
|
|
2717
|
+
`design/ai-chat/design-notes.md` § *The CSS, verbatim* writes each treatment
|
|
2718
|
+
as plain `background-image` / `box-shadow` declarations. Both are transcribed
|
|
2719
|
+
here onto those four variables, because written literally they would be
|
|
2720
|
+
**INERT** — which is the very defect this section exists to close, one
|
|
2721
|
+
property over. It is the same mechanism the design's own row 8 relies on and
|
|
2722
|
+
the only one consistent with its "Warm Editorial has no block by design"
|
|
2723
|
+
decision: painting the base in a `warm-editorial` block instead would give the
|
|
2724
|
+
Tier-0 base a second copy of its own recipe to drift from.
|
|
2725
|
+
|
|
2726
|
+
⚠️ THE SHADOW HALF WAS MEASURED, NOT REASONED. `tests/e2e/hero-ai-control-styles.spec.ts`
|
|
2727
|
+
compares every style against the base in a real browser, and with the shadows
|
|
2728
|
+
written literally exactly ONE control came back byte-identical to the base:
|
|
2729
|
+
the AURORA ORB — the only one whose row sets nothing but a shadow. Every other
|
|
2730
|
+
style also moves a fill, a border or the type, so its rule "worked" while its
|
|
2731
|
+
shadow silently did not. A source-reading review passes that; the render does
|
|
2732
|
+
not.
|
|
2733
|
+
|
|
2734
|
+
A shared `[data-surface='ai-cta']` rule sets BOTH shadow variables, since the
|
|
2735
|
+
design gives the pill and the orb the same shadow and each control reads only
|
|
2736
|
+
its own name. Everything the components do NOT declare inline —
|
|
2737
|
+
`background-color`, `background-size`, `background-position`,
|
|
2738
|
+
`background-repeat`, `border`, `backdrop-filter`, the type properties — is
|
|
2739
|
+
written exactly as the design states it.
|
|
2740
|
+
|
|
2741
|
+
Radius, padding and height are NOT set here: they already flow through
|
|
2742
|
+
`--radius-badge` / `--spacing-btn-x` / `--height-btn-md`, and re-stating them
|
|
2743
|
+
would freeze the one axis that was already working. Palette-derived
|
|
2744
|
+
throughout — `color-mix()` over `--el-*`, no raw hex — so a `data-palette`
|
|
2745
|
+
swap re-tints all eleven treatments. Every label ratio in the design's table
|
|
2746
|
+
was measured on the painted pixel; the tightest is glassmorphism at 4.87:1.
|
|
2747
|
+
───────────────────────────────────────────────────────────────────────── */
|
|
2748
|
+
|
|
2749
|
+
/* 1 · Warm Editorial — NO BLOCK, by design. It is the Tier-0 base, and the
|
|
2750
|
+
components' own `var()` fallbacks ARE its treatment; a block repeating them
|
|
2751
|
+
would be a second copy to drift. */
|
|
2752
|
+
|
|
2753
|
+
/* 2 · Soft / Playful — pillowy stacked shadow, no border. */
|
|
2754
|
+
@scope ([data-style='soft-playful']) to ([data-style]) {
|
|
2755
|
+
[data-surface='ai-cta'] {
|
|
2756
|
+
font-weight: 800;
|
|
2757
|
+
--plan-hero-shadow:
|
|
2758
|
+
0 7px 0 -1px color-mix(in srgb, var(--el-accent) 22%, var(--el-page-bg)),
|
|
2759
|
+
0 14px 22px -6px color-mix(in srgb, var(--el-accent) 55%, transparent);
|
|
2760
|
+
--plan-orb-shadow:
|
|
2761
|
+
0 7px 0 -1px color-mix(in srgb, var(--el-accent) 22%, var(--el-page-bg)),
|
|
2762
|
+
0 14px 22px -6px color-mix(in srgb, var(--el-accent) 55%, transparent);
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
|
|
2766
|
+
/* 3 · Swiss / Minimal-Flat — flat solid, sharp, uppercase, NO shadow. */
|
|
2767
|
+
@scope ([data-style='swiss-minimal-flat']) to ([data-style]) {
|
|
2768
|
+
[data-surface='ai-cta'] {
|
|
2769
|
+
--plan-hero-shadow: none;
|
|
2770
|
+
--plan-orb-shadow: none;
|
|
2771
|
+
font-weight: 700;
|
|
2772
|
+
text-transform: uppercase;
|
|
2773
|
+
letter-spacing: 0.07em;
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
2776
|
+
@scope ([data-style='swiss-minimal-flat']) to ([data-style]) {
|
|
2777
|
+
[data-surface='ai-cta'][data-ai-cta='pill'] {
|
|
2778
|
+
--plan-hero-fill: none;
|
|
2779
|
+
background-color: var(--el-accent);
|
|
2780
|
+
}
|
|
2781
|
+
}
|
|
2782
|
+
|
|
2783
|
+
/* 4 · Glassmorphism — frosted translucency over the rim. 86% is a FLOOR, not a
|
|
2784
|
+
preference: white is the lightest backdrop the light theme can put behind it,
|
|
2785
|
+
so 86% accent composited over anything measures >= 4.87:1. */
|
|
2786
|
+
@scope ([data-style='glassmorphism']) to ([data-style]) {
|
|
2787
|
+
[data-surface='ai-cta'] {
|
|
2788
|
+
border: 1px solid var(--glass-rim);
|
|
2789
|
+
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
|
|
2790
|
+
backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
|
|
2791
|
+
--plan-hero-shadow:
|
|
2792
|
+
inset 0 1px 0 var(--glass-rim),
|
|
2793
|
+
0 8px 22px -6px color-mix(in srgb, var(--el-accent) 45%, transparent);
|
|
2794
|
+
--plan-orb-shadow:
|
|
2795
|
+
inset 0 1px 0 var(--glass-rim),
|
|
2796
|
+
0 8px 22px -6px color-mix(in srgb, var(--el-accent) 45%, transparent);
|
|
2797
|
+
}
|
|
2798
|
+
}
|
|
2799
|
+
@scope ([data-style='glassmorphism']) to ([data-style]) {
|
|
2800
|
+
[data-surface='ai-cta'][data-ai-cta='pill'] {
|
|
2801
|
+
--plan-hero-fill: none;
|
|
2802
|
+
background-color: color-mix(in srgb, var(--el-accent) 86%, transparent);
|
|
2803
|
+
}
|
|
2804
|
+
}
|
|
2805
|
+
@scope ([data-style='glassmorphism']) to ([data-style]) {
|
|
2806
|
+
[data-surface='ai-cta'][data-ai-cta='orb'] {
|
|
2807
|
+
/* Crown-confined: across the whole circle this sheen lightens the 26/56
|
|
2808
|
+
glyph box to 3.17:1 light / 2.67:1 dark and breaks the `--orb-lit-mix`
|
|
2809
|
+
floor the style layer may not touch. Confined, it measures the shipped
|
|
2810
|
+
3.78:1 / 3.10:1 — identical to no sheen at all. */
|
|
2811
|
+
background-size:
|
|
2812
|
+
100% 20%,
|
|
2813
|
+
100% 100%;
|
|
2814
|
+
background-position:
|
|
2815
|
+
top left,
|
|
2816
|
+
top left;
|
|
2817
|
+
background-repeat: no-repeat;
|
|
2818
|
+
--plan-orb-fill:
|
|
2819
|
+
linear-gradient(160deg, var(--glass-sheen), transparent 90%),
|
|
2820
|
+
radial-gradient(
|
|
2821
|
+
circle at 33% 27%,
|
|
2822
|
+
color-mix(in srgb, var(--el-accent-text) var(--orb-lit-mix), var(--el-accent)),
|
|
2823
|
+
var(--el-accent) 56%,
|
|
2824
|
+
color-mix(in srgb, var(--el-accent) 68%, var(--el-highlight))
|
|
2825
|
+
);
|
|
2826
|
+
}
|
|
2827
|
+
}
|
|
2828
|
+
|
|
2829
|
+
/* 5 · Neo-Brutalism — hard 2px border + zero-blur offset shadow. */
|
|
2830
|
+
@scope ([data-style='neo-brutalism']) to ([data-style]) {
|
|
2831
|
+
[data-surface='ai-cta'] {
|
|
2832
|
+
border: 2px solid var(--el-text);
|
|
2833
|
+
--plan-hero-shadow: 4px 4px 0 0 var(--el-text);
|
|
2834
|
+
--plan-orb-shadow: 4px 4px 0 0 var(--el-text);
|
|
2835
|
+
font-weight: 800;
|
|
2836
|
+
text-transform: uppercase;
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2839
|
+
@scope ([data-style='neo-brutalism']) to ([data-style]) {
|
|
2840
|
+
[data-surface='ai-cta'][data-ai-cta='pill'] {
|
|
2841
|
+
--plan-hero-fill: none;
|
|
2842
|
+
background-color: var(--el-accent);
|
|
2843
|
+
}
|
|
2844
|
+
}
|
|
2845
|
+
|
|
2846
|
+
/* 6 · Cybercore / Y2K — dark HUD ground, neon halo, mono label. The mixes toward
|
|
2847
|
+
the achromatic `black` KEYWORD are sanctioned by this style's own material
|
|
2848
|
+
layer, which uses them to read as lightness rather than as a hue, so the
|
|
2849
|
+
palette axis stays disjoint. */
|
|
2850
|
+
@scope ([data-style='cybercore-y2k']) to ([data-style]) {
|
|
2851
|
+
[data-surface='ai-cta'] {
|
|
2852
|
+
border: 1px solid color-mix(in srgb, var(--el-highlight) 70%, transparent);
|
|
2853
|
+
--plan-hero-shadow:
|
|
2854
|
+
0 0 10px color-mix(in srgb, var(--el-highlight) 60%, transparent),
|
|
2855
|
+
0 0 26px -4px color-mix(in srgb, var(--el-accent) 70%, transparent);
|
|
2856
|
+
--plan-orb-shadow:
|
|
2857
|
+
0 0 10px color-mix(in srgb, var(--el-highlight) 60%, transparent),
|
|
2858
|
+
0 0 26px -4px color-mix(in srgb, var(--el-accent) 70%, transparent);
|
|
2859
|
+
font-family: var(--font-mono);
|
|
2860
|
+
letter-spacing: 0.04em;
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2863
|
+
@scope ([data-style='cybercore-y2k']) to ([data-style]) {
|
|
2864
|
+
[data-surface='ai-cta'][data-ai-cta='pill'] {
|
|
2865
|
+
--plan-hero-fill: linear-gradient(
|
|
2866
|
+
135deg,
|
|
2867
|
+
color-mix(in srgb, var(--el-accent) 88%, black),
|
|
2868
|
+
color-mix(in srgb, color-mix(in srgb, var(--el-accent) 70%, var(--el-highlight)) 78%, black)
|
|
2869
|
+
);
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2872
|
+
@scope ([data-style='cybercore-y2k']) to ([data-style]) {
|
|
2873
|
+
[data-surface='ai-cta'][data-ai-cta='orb'] {
|
|
2874
|
+
--plan-orb-fill:
|
|
2875
|
+
linear-gradient(
|
|
2876
|
+
160deg,
|
|
2877
|
+
color-mix(in srgb, var(--el-highlight) 22%, transparent),
|
|
2878
|
+
transparent 55%
|
|
2879
|
+
),
|
|
2880
|
+
radial-gradient(
|
|
2881
|
+
circle at 33% 27%,
|
|
2882
|
+
color-mix(in srgb, var(--el-accent-text) var(--orb-lit-mix), var(--el-accent)),
|
|
2883
|
+
var(--el-accent) 56%,
|
|
2884
|
+
color-mix(in srgb, var(--el-accent) 68%, var(--el-highlight))
|
|
2885
|
+
);
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
|
|
2889
|
+
/* 7 · Aurora — lit from within, colour halo, no hard shadow. */
|
|
2890
|
+
@scope ([data-style='aurora']) to ([data-style]) {
|
|
2891
|
+
[data-surface='ai-cta'] {
|
|
2892
|
+
--plan-hero-shadow:
|
|
2893
|
+
inset 0 1px 0 color-mix(in srgb, var(--el-accent-text) var(--aurora-sheen), transparent),
|
|
2894
|
+
0 0 26px -2px color-mix(in srgb, var(--el-accent) var(--aurora-glow), transparent),
|
|
2895
|
+
0 0 40px -6px color-mix(in srgb, var(--el-highlight) var(--aurora-glow), transparent);
|
|
2896
|
+
--plan-orb-shadow:
|
|
2897
|
+
inset 0 1px 0 color-mix(in srgb, var(--el-accent-text) var(--aurora-sheen), transparent),
|
|
2898
|
+
0 0 26px -2px color-mix(in srgb, var(--el-accent) var(--aurora-glow), transparent),
|
|
2899
|
+
0 0 40px -6px color-mix(in srgb, var(--el-highlight) var(--aurora-glow), transparent);
|
|
2900
|
+
}
|
|
2901
|
+
}
|
|
2902
|
+
@scope ([data-style='aurora']) to ([data-style]) {
|
|
2903
|
+
[data-surface='ai-cta'][data-ai-cta='pill'] {
|
|
2904
|
+
background-size:
|
|
2905
|
+
100% 28%,
|
|
2906
|
+
100% 100%;
|
|
2907
|
+
background-position:
|
|
2908
|
+
top left,
|
|
2909
|
+
top left;
|
|
2910
|
+
background-repeat: no-repeat;
|
|
2911
|
+
--plan-hero-fill:
|
|
2912
|
+
linear-gradient(
|
|
2913
|
+
180deg,
|
|
2914
|
+
color-mix(in srgb, var(--el-accent-text) var(--aurora-sheen), transparent),
|
|
2915
|
+
transparent 90%
|
|
2916
|
+
),
|
|
2917
|
+
linear-gradient(
|
|
2918
|
+
135deg,
|
|
2919
|
+
var(--el-accent),
|
|
2920
|
+
color-mix(in srgb, var(--el-accent) 86%, var(--el-highlight))
|
|
2921
|
+
);
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2924
|
+
|
|
2925
|
+
/* 8 · 3D / Immersive — NOTHING. `--plan-hero-shadow` / `--plan-orb-shadow` own
|
|
2926
|
+
this control's depth and the components read them through a `var()` seam an
|
|
2927
|
+
inline `box-shadow` would otherwise beat (MOTIR-3522). A rule here would
|
|
2928
|
+
either lose to that inline declaration or take a `data-depth="key"` control
|
|
2929
|
+
off the plane ladder it is declared on (docs/styles/3d-immersive.md §4 / §4b).
|
|
2930
|
+
Deliberately empty — recorded so the absence reads as a decision, not an
|
|
2931
|
+
omission. */
|
|
2932
|
+
|
|
2933
|
+
/* 9 · Hand-Drawn / Indie — a drawn ink outline + a hand-placed offset.
|
|
2934
|
+
⚠️ THE `position: relative` IS GUARDED, where the design's transcription
|
|
2935
|
+
states it bare. The orb is `.fixed`, and a fixed element is already a
|
|
2936
|
+
containing block for its own `::after`; re-declaring it `relative` would drop
|
|
2937
|
+
the orb out of fixed positioning entirely. Same `:not(.fixed):not(.absolute)
|
|
2938
|
+
:not(.sticky)` guard this style's own `.border` rule carries, for the same
|
|
2939
|
+
reason it carries it. */
|
|
2940
|
+
@scope ([data-style='hand-drawn-indie']) to ([data-style]) {
|
|
2941
|
+
[data-surface='ai-cta']:not(.fixed):not(.absolute):not(.sticky) {
|
|
2942
|
+
position: relative;
|
|
2943
|
+
}
|
|
2944
|
+
}
|
|
2945
|
+
@scope ([data-style='hand-drawn-indie']) to ([data-style]) {
|
|
2946
|
+
[data-surface='ai-cta'] {
|
|
2947
|
+
font-weight: 700;
|
|
2948
|
+
--plan-hero-shadow: 3px 4px 0 -1px color-mix(in srgb, var(--el-border-strong) 55%, transparent);
|
|
2949
|
+
--plan-orb-shadow: 3px 4px 0 -1px color-mix(in srgb, var(--el-border-strong) 55%, transparent);
|
|
2950
|
+
}
|
|
2951
|
+
}
|
|
2952
|
+
@scope ([data-style='hand-drawn-indie']) to ([data-style]) {
|
|
2953
|
+
[data-surface='ai-cta']::after {
|
|
2954
|
+
content: '';
|
|
2955
|
+
position: absolute;
|
|
2956
|
+
inset: -1px;
|
|
2957
|
+
pointer-events: none;
|
|
2958
|
+
border: 2px solid var(--el-border-strong);
|
|
2959
|
+
border-radius: inherit;
|
|
2960
|
+
filter: url(#hd-rough);
|
|
2961
|
+
}
|
|
2962
|
+
}
|
|
2963
|
+
|
|
2964
|
+
/* 10 · Neumorphism — moulded, NOT raised-and-glowing: no gradient, no glow, and
|
|
2965
|
+
the hairline is KEPT (structure never relies on shadow alone). */
|
|
2966
|
+
@scope ([data-style='neumorphism']) to ([data-style]) {
|
|
2967
|
+
[data-surface='ai-cta'] {
|
|
2968
|
+
border: 1px solid color-mix(in srgb, var(--el-accent) 70%, var(--el-text));
|
|
2969
|
+
--plan-hero-shadow:
|
|
2970
|
+
var(--neu-distance) var(--neu-distance) var(--neu-blur) var(--neu-shadow),
|
|
2971
|
+
calc(var(--neu-distance) * -1) calc(var(--neu-distance) * -1) var(--neu-blur) var(--neu-light);
|
|
2972
|
+
--plan-orb-shadow:
|
|
2973
|
+
var(--neu-distance) var(--neu-distance) var(--neu-blur) var(--neu-shadow),
|
|
2974
|
+
calc(var(--neu-distance) * -1) calc(var(--neu-distance) * -1) var(--neu-blur) var(--neu-light);
|
|
2975
|
+
}
|
|
2976
|
+
}
|
|
2977
|
+
@scope ([data-style='neumorphism']) to ([data-style]) {
|
|
2978
|
+
[data-surface='ai-cta'][data-ai-cta='pill'] {
|
|
2979
|
+
--plan-hero-fill: none;
|
|
2980
|
+
background-color: var(--el-accent);
|
|
2981
|
+
}
|
|
2982
|
+
}
|
|
2983
|
+
@scope ([data-style='neumorphism']) to ([data-style]) {
|
|
2984
|
+
[data-surface='ai-cta'][data-ai-cta='orb'] {
|
|
2985
|
+
--plan-orb-fill: none;
|
|
2986
|
+
background-color: var(--el-accent);
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2989
|
+
|
|
2990
|
+
/* 11 · Retrofuturism — a chrome bevel + a CROWN-CONFINED specular streak.
|
|
2991
|
+
⚠️ THE STREAK IS THE THING THAT HAD TO MOVE, not the bevel. Across the full
|
|
2992
|
+
box the diagonal at `--retro-spec` washes the label to 2.55:1 light / 2.22:1
|
|
2993
|
+
dark, and thinning it does not rescue it (18% still measures 3.67:1 dark, by
|
|
2994
|
+
which point the chrome has stopped reading). Sized to the top band it sits
|
|
2995
|
+
above the cap height, the label sits on the bevel BODY at 6.76:1 / 5.16:1, and
|
|
2996
|
+
the chrome still reads — a machined bevel IS a vertical light-to-dark ramp
|
|
2997
|
+
with a highlight on the crown. The `white` / `black` keywords are this style's
|
|
2998
|
+
own sanctioned lightness mixes, not a hue. */
|
|
2999
|
+
@scope ([data-style='retrofuturism']) to ([data-style]) {
|
|
3000
|
+
[data-surface='ai-cta'] {
|
|
3001
|
+
border: 1px solid color-mix(in srgb, var(--el-accent-text) 30%, transparent);
|
|
3002
|
+
--plan-hero-shadow:
|
|
3003
|
+
inset 0 1px 0 color-mix(in srgb, var(--el-accent-text) var(--retro-bevel-light), transparent),
|
|
3004
|
+
0 0 24px -4px color-mix(in srgb, var(--el-accent) var(--retro-glow), transparent);
|
|
3005
|
+
--plan-orb-shadow:
|
|
3006
|
+
inset 0 1px 0 color-mix(in srgb, var(--el-accent-text) var(--retro-bevel-light), transparent),
|
|
3007
|
+
0 0 24px -4px color-mix(in srgb, var(--el-accent) var(--retro-glow), transparent);
|
|
3008
|
+
}
|
|
3009
|
+
}
|
|
3010
|
+
@scope ([data-style='retrofuturism']) to ([data-style]) {
|
|
3011
|
+
[data-surface='ai-cta'][data-ai-cta='pill'] {
|
|
3012
|
+
background-size:
|
|
3013
|
+
100% 28%,
|
|
3014
|
+
100% 100%;
|
|
3015
|
+
background-position:
|
|
3016
|
+
top left,
|
|
3017
|
+
top left;
|
|
3018
|
+
background-repeat: no-repeat;
|
|
3019
|
+
--plan-hero-fill:
|
|
3020
|
+
linear-gradient(
|
|
3021
|
+
104deg,
|
|
3022
|
+
transparent 30%,
|
|
3023
|
+
color-mix(in srgb, var(--el-accent-text) var(--retro-spec), transparent) 46%,
|
|
3024
|
+
transparent 62%
|
|
3025
|
+
),
|
|
3026
|
+
linear-gradient(
|
|
3027
|
+
180deg,
|
|
3028
|
+
color-mix(in srgb, var(--el-accent) 82%, white) 0%,
|
|
3029
|
+
var(--el-accent) 20%,
|
|
3030
|
+
color-mix(in srgb, var(--el-accent) 84%, black) 100%
|
|
3031
|
+
);
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
3034
|
+
@scope ([data-style='retrofuturism']) to ([data-style]) {
|
|
3035
|
+
[data-surface='ai-cta'][data-ai-cta='orb'] {
|
|
3036
|
+
/* Crown-confined, for the same reason as glass: unbounded it measures
|
|
3037
|
+
3.34:1 light / 2.78:1 dark inside the glyph box. */
|
|
3038
|
+
background-size:
|
|
3039
|
+
100% 26%,
|
|
3040
|
+
100% 100%;
|
|
3041
|
+
background-position:
|
|
3042
|
+
top left,
|
|
3043
|
+
top left;
|
|
3044
|
+
background-repeat: no-repeat;
|
|
3045
|
+
--plan-orb-fill:
|
|
3046
|
+
linear-gradient(
|
|
3047
|
+
180deg,
|
|
3048
|
+
color-mix(in srgb, var(--el-accent-text) var(--retro-bevel-light), transparent),
|
|
3049
|
+
transparent 90%
|
|
3050
|
+
),
|
|
3051
|
+
radial-gradient(
|
|
3052
|
+
circle at 33% 27%,
|
|
3053
|
+
color-mix(in srgb, var(--el-accent-text) var(--orb-lit-mix), var(--el-accent)),
|
|
3054
|
+
var(--el-accent) 56%,
|
|
3055
|
+
color-mix(in srgb, var(--el-accent) 68%, var(--el-highlight))
|
|
3056
|
+
);
|
|
3057
|
+
}
|
|
2263
3058
|
}
|
|
2264
3059
|
|
|
2265
3060
|
/* ─────────────────────────────────────────────────────────────────────────
|
|
@@ -2533,7 +3328,25 @@
|
|
|
2533
3328
|
nobody renders — the base layer IS how the browser reaches these values. */
|
|
2534
3329
|
--el-status-implemented: color-mix(in srgb, var(--color-accent) 75%, var(--color-charcoal));
|
|
2535
3330
|
--el-status-in-review: var(--color-primary); /* brand — differentiate from in-progress */
|
|
2536
|
-
|
|
3331
|
+
/* green — RESOLVED (MOTIR-3991). The bare --color-success (#1aae39) was
|
|
3332
|
+
2.93:1 on --el-card and 2.69:1 on --el-surface in the BASE palette's LIGHT
|
|
3333
|
+
theme — under 1.4.11's 3:1 for a graphical object, and the one palette x
|
|
3334
|
+
theme in which this token missed the bar. --color-success is NOT free to
|
|
3335
|
+
move: the same Tier-0 hue paints --el-success-surface's companion ink, the
|
|
3336
|
+
success toast/notice family and the chart-success step, each held to its
|
|
3337
|
+
own AA bar that pulls opposite to a dot's 3:1 — so the ramp takes one step
|
|
3338
|
+
of the palette's own emphasis ink instead of re-tuning the source. 85% of
|
|
3339
|
+
--color-success toward --color-charcoal gives #1e9c38, 3.58 / 3.29 / 3.58
|
|
3340
|
+
on card / surface / page-bg. --color-charcoal flips with the theme
|
|
3341
|
+
(#37352f light, #e5e5e5 dark), so the same expression DARKENS on white and
|
|
3342
|
+
LIGHTENS on near-black — dark goes 7.29 / 6.62 / 7.29:1. Both inputs stay
|
|
3343
|
+
palette-tuned, so the nine palettes that ride this base value re-skin it as
|
|
3344
|
+
before and each keeps its own --color-success; ΔE2000 to the nearest
|
|
3345
|
+
sibling stays >= 11.3 in all ten palettes x both themes. Why the base and
|
|
3346
|
+
not a [data-palette='motir'] block: same as --el-status-implemented above —
|
|
3347
|
+
<html> carries no data-appearance-scope, so the base layer IS how the
|
|
3348
|
+
browser reaches these values. */
|
|
3349
|
+
--el-status-done: color-mix(in srgb, var(--color-success) 85%, var(--color-charcoal));
|
|
2537
3350
|
--el-status-blocked: var(--color-warning); /* amber — can't proceed (gap fixed) */
|
|
2538
3351
|
--el-status-cancelled: var(--color-steel); /* terminal grey — not red (cancel ≠ error) */
|
|
2539
3352
|
|
|
@@ -2601,10 +3414,27 @@
|
|
|
2601
3414
|
--el-label-4: var(--color-tint-lavender);
|
|
2602
3415
|
--el-label-5: var(--color-tint-sky);
|
|
2603
3416
|
--el-label-6: var(--color-tint-yellow);
|
|
2604
|
-
/* Avatar ramp — KEEP the named keys (peach…yellow):
|
|
2605
|
-
|
|
2606
|
-
(
|
|
2607
|
-
|
|
3417
|
+
/* Avatar ramp — KEEP the named keys (peach…yellow): they are addressed BY NAME, as
|
|
3418
|
+
literal `bg-(--el-avatar-<key>)` class strings, by
|
|
3419
|
+
`app/(authed)/triage/_components/TriageAvatar.tsx` (a hash of the display name
|
|
3420
|
+
indexes a fixed six-entry array — its ORDER is load-bearing too) and by the
|
|
3421
|
+
`--el-avatar-fallback` initial tiles in
|
|
3422
|
+
`app/(authed)/settings/workspace/_components/gitSettingsPrimitives.tsx` and
|
|
3423
|
+
`app/(authed)/settings/project/code-access/_components/CodeAccessSettings.tsx`.
|
|
3424
|
+
`app/tokens/page.tsx` enumerates all seven. Numbering them would break those
|
|
3425
|
+
lookups, and Tailwind cannot rewrite an arbitrary-value class it never sees.
|
|
3426
|
+
⚠️ Until MOTIR-4344 this comment reached the same conclusion from MIGRATION
|
|
3427
|
+
safety instead: a module under `lib/projects/` was said to persist a per-project
|
|
3428
|
+
colour column whose values were these key names, so numbering them would break
|
|
3429
|
+
stored rows (spec §7.1, rung-2). Both halves of that warrant went in MOTIR-2680
|
|
3430
|
+
(commit c73fb9112, 2026-08-11, landed in #2046) —
|
|
3431
|
+
`prisma/migrations/20260811223000_drop_project_preset_avatar/migration.sql` drops
|
|
3432
|
+
the column outright ("The values are LOST, deliberately") and the same change
|
|
3433
|
+
deleted the module. The CONCLUSION survived its warrant; the warrant above is the
|
|
3434
|
+
one that is true today. Neither dead address is repeated here on purpose — a
|
|
3435
|
+
citation is only worth as much as the thing it addresses. The fallback fixes
|
|
3436
|
+
--el-type-* misuse #2 — the mono initials tile keeps its blue but stops
|
|
3437
|
+
borrowing --el-type-task. */
|
|
2608
3438
|
--el-avatar-peach: var(--color-tint-peach);
|
|
2609
3439
|
--el-avatar-rose: var(--color-tint-rose);
|
|
2610
3440
|
--el-avatar-mint: var(--color-tint-mint);
|
|
@@ -2621,8 +3451,9 @@
|
|
|
2621
3451
|
--el-notif-commented: var(--color-info); /* was --el-type-task */
|
|
2622
3452
|
--el-notif-assigned: var(--color-accent-green); /* was --el-type-story */
|
|
2623
3453
|
--el-notif-transitioned: var(--color-accent-teal); /* was --el-type-subtask */
|
|
2624
|
-
/* The EXECUTOR badge on a
|
|
2625
|
-
Subtask MOTIR-2653, per design/
|
|
3454
|
+
/* The EXECUTOR badge on a Workbench row's assignee avatar (Story MOTIR-2649 ·
|
|
3455
|
+
Subtask MOTIR-2653, per design/workbench/design-notes.md §"The agent state"
|
|
3456
|
+
— that area was renamed with the surface by MOTIR-4779).
|
|
2626
3457
|
Same mapping form as --el-notif-mentioned above and for the same reason:
|
|
2627
3458
|
it resolves to the accent fill today, but owning its own slot means a
|
|
2628
3459
|
palette can move agent badges without dragging the CTA with them. The
|
|
@@ -2964,7 +3795,7 @@
|
|
|
2964
3795
|
|
|
2965
3796
|
Not part of the Tier-3 layer above and deliberately not named `--el-*`:
|
|
2966
3797
|
that layer is a COLOUR contract (every member aliases a Tier-0 --color-*
|
|
2967
|
-
and is asserted palette-dependent by tests/theme/paletteTokenCoverage).
|
|
3798
|
+
and is asserted palette-dependent by tests/theme/paletteTokenCoverage.test.ts).
|
|
2968
3799
|
A knob here is a NUMBER inside a color-mix() — one value for every
|
|
2969
3800
|
palette and both themes — so it fails both halves of that contract while
|
|
2970
3801
|
being exactly the thing that has to be expressed once.
|
|
@@ -3026,7 +3857,7 @@
|
|
|
3026
3857
|
`@layer utilities`; this rule is UNLAYERED, and an unlayered declaration
|
|
3027
3858
|
outranks a layered one whatever their specificity. That is the whole of the
|
|
3028
3859
|
mechanism — it does not depend on source order or on this file's position in
|
|
3029
|
-
the `@import` list. Measured, not reasoned: tests/theme/reducedMotionSpinner
|
|
3860
|
+
the `@import` list. Measured, not reasoned: tests/theme/reducedMotionSpinner.test.ts
|
|
3030
3861
|
reads it back off the COMPILED stylesheet for both consumer shapes.
|
|
3031
3862
|
═══════════════════════════════════════════════════════════════════════ */
|
|
3032
3863
|
@media (prefers-reduced-motion: reduce) {
|