@motir/design-system 0.1.3 → 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/StyleVignette.d.ts +4 -7
- 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 +707 -25
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,6 +642,43 @@
|
|
|
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)
|
|
@@ -555,7 +686,8 @@
|
|
|
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
688
|
@scope ([data-style='glassmorphism']) to ([data-style]) {
|
|
558
|
-
body
|
|
689
|
+
body,
|
|
690
|
+
[data-app-shell] {
|
|
559
691
|
background-image:
|
|
560
692
|
radial-gradient(
|
|
561
693
|
85% 80% at 6% -12%,
|
|
@@ -814,7 +946,8 @@
|
|
|
814
946
|
renders on it), keeping AA intact. Dark-first — on the dark theme this reads
|
|
815
947
|
as a Tron grid over the near-black canvas. */
|
|
816
948
|
@scope ([data-style='cybercore-y2k']) to ([data-style]) {
|
|
817
|
-
body
|
|
949
|
+
body,
|
|
950
|
+
[data-app-shell] {
|
|
818
951
|
background-image:
|
|
819
952
|
linear-gradient(color-mix(in srgb, currentColor 6%, transparent) 1px, transparent 1px),
|
|
820
953
|
linear-gradient(90deg, color-mix(in srgb, currentColor 6%, transparent) 1px, transparent 1px);
|
|
@@ -918,7 +1051,8 @@
|
|
|
918
1051
|
palette. background-size > 100% gives the gradient room to drift; the whole
|
|
919
1052
|
field eases back and forth over `--aurora-drift-duration`. */
|
|
920
1053
|
@scope ([data-style='aurora']) to ([data-style]) {
|
|
921
|
-
body
|
|
1054
|
+
body,
|
|
1055
|
+
[data-app-shell] {
|
|
922
1056
|
background-image:
|
|
923
1057
|
radial-gradient(
|
|
924
1058
|
58% 75% at 12% 0%,
|
|
@@ -964,10 +1098,14 @@
|
|
|
964
1098
|
}
|
|
965
1099
|
|
|
966
1100
|
/* Respect a reduced-motion preference — the aurora settles to a still gradient
|
|
967
|
-
(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. */
|
|
968
1105
|
@media (prefers-reduced-motion: reduce) {
|
|
969
1106
|
@scope ([data-style='aurora']) to ([data-style]) {
|
|
970
|
-
body
|
|
1107
|
+
body,
|
|
1108
|
+
[data-app-shell] {
|
|
971
1109
|
animation: none;
|
|
972
1110
|
}
|
|
973
1111
|
}
|
|
@@ -1097,6 +1235,13 @@
|
|
|
1097
1235
|
--height-control: 40px;
|
|
1098
1236
|
--height-input: 46px;
|
|
1099
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
|
+
|
|
1100
1245
|
/* Motion — smooth and floaty. A slower ease for the depth to feel weighty.
|
|
1101
1246
|
The signature INTERACTION is the pointer-parallax TILT below (the standard
|
|
1102
1247
|
"3D card" effect — cards tip toward the cursor), driven by the
|
|
@@ -1115,9 +1260,28 @@
|
|
|
1115
1260
|
never a raw hue), so the style/palette axes stay disjoint and the material-
|
|
1116
1261
|
rule test passes: a palette swap re-tints the atmosphere, a style swap leaves
|
|
1117
1262
|
hues alone. This is what makes the table/list/board pages (no card surfaces of
|
|
1118
|
-
their own) read as immersive too.
|
|
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. */
|
|
1119
1282
|
@scope ([data-style='3d-immersive']) to ([data-style]) {
|
|
1120
|
-
body
|
|
1283
|
+
body,
|
|
1284
|
+
[data-app-shell] {
|
|
1121
1285
|
background-image:
|
|
1122
1286
|
radial-gradient(
|
|
1123
1287
|
130% 85% at 50% -15%,
|
|
@@ -1585,6 +1749,107 @@
|
|
|
1585
1749
|
}
|
|
1586
1750
|
}
|
|
1587
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
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1588
1853
|
/* ── Hand-Drawn / Indie (data-style="hand-drawn-indie") — Subtask 7.3.41 ─────
|
|
1589
1854
|
DEFERRED follow-up. A sketchy, friendly, zine-like aesthetic. TWO moves make
|
|
1590
1855
|
it read as genuinely hand-drawn, not just "softly rounded":
|
|
@@ -1890,7 +2155,8 @@
|
|
|
1890
2155
|
the canvas and the surfaces below share `var(--el-surface)`, which is the whole
|
|
1891
2156
|
neumorphic illusion. Palette-derived (no hue of its own). */
|
|
1892
2157
|
@scope ([data-style='neumorphism']) to ([data-style]) {
|
|
1893
|
-
body
|
|
2158
|
+
body,
|
|
2159
|
+
[data-app-shell] {
|
|
1894
2160
|
background-color: var(--el-surface);
|
|
1895
2161
|
}
|
|
1896
2162
|
}
|
|
@@ -2081,7 +2347,8 @@
|
|
|
2081
2347
|
to the palette. Painted as the body's own background (always behind content),
|
|
2082
2348
|
the proven 3D/Immersive atmosphere approach. */
|
|
2083
2349
|
@scope ([data-style='retrofuturism']) to ([data-style]) {
|
|
2084
|
-
body
|
|
2350
|
+
body,
|
|
2351
|
+
[data-app-shell] {
|
|
2085
2352
|
background-image:
|
|
2086
2353
|
linear-gradient(
|
|
2087
2354
|
180deg,
|
|
@@ -2105,16 +2372,41 @@
|
|
|
2105
2372
|
}
|
|
2106
2373
|
}
|
|
2107
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
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2108
2397
|
/* The Outrun GRID FLOOR — a receding neon grid below the horizon. A separate
|
|
2109
|
-
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
|
|
2110
2400
|
into the horizon; `z-index: -1` keeps it BEHIND all app content (above only the
|
|
2111
|
-
|
|
2112
|
-
UI. Lines are `color-mix()` over `--el-*` accents
|
|
2113
|
-
their own); the mask uses the achromatic `black`
|
|
2114
|
-
STATIC depth — like 3D/Immersive's atmosphere it
|
|
2115
|
-
not motion-gated (there is no animation to
|
|
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). */
|
|
2116
2407
|
@scope ([data-style='retrofuturism']) to ([data-style]) {
|
|
2117
|
-
body::after
|
|
2408
|
+
body::after,
|
|
2409
|
+
[data-app-shell]::after {
|
|
2118
2410
|
content: '';
|
|
2119
2411
|
position: fixed;
|
|
2120
2412
|
left: -30%;
|
|
@@ -2393,6 +2685,378 @@
|
|
|
2393
2685
|
}
|
|
2394
2686
|
}
|
|
2395
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
|
+
}
|
|
3058
|
+
}
|
|
3059
|
+
|
|
2396
3060
|
/* ─────────────────────────────────────────────────────────────────────────
|
|
2397
3061
|
Style-preview vignette material canvas (7.3.37 / MOTIR-1050).
|
|
2398
3062
|
The StyleVignette preview specimen carries its OWN axis attribute on a
|
|
@@ -2750,10 +3414,27 @@
|
|
|
2750
3414
|
--el-label-4: var(--color-tint-lavender);
|
|
2751
3415
|
--el-label-5: var(--color-tint-sky);
|
|
2752
3416
|
--el-label-6: var(--color-tint-yellow);
|
|
2753
|
-
/* Avatar ramp — KEEP the named keys (peach…yellow):
|
|
2754
|
-
|
|
2755
|
-
(
|
|
2756
|
-
|
|
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. */
|
|
2757
3438
|
--el-avatar-peach: var(--color-tint-peach);
|
|
2758
3439
|
--el-avatar-rose: var(--color-tint-rose);
|
|
2759
3440
|
--el-avatar-mint: var(--color-tint-mint);
|
|
@@ -2770,8 +3451,9 @@
|
|
|
2770
3451
|
--el-notif-commented: var(--color-info); /* was --el-type-task */
|
|
2771
3452
|
--el-notif-assigned: var(--color-accent-green); /* was --el-type-story */
|
|
2772
3453
|
--el-notif-transitioned: var(--color-accent-teal); /* was --el-type-subtask */
|
|
2773
|
-
/* The EXECUTOR badge on a
|
|
2774
|
-
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).
|
|
2775
3457
|
Same mapping form as --el-notif-mentioned above and for the same reason:
|
|
2776
3458
|
it resolves to the accent fill today, but owning its own slot means a
|
|
2777
3459
|
palette can move agent badges without dragging the CTA with them. The
|
|
@@ -3113,7 +3795,7 @@
|
|
|
3113
3795
|
|
|
3114
3796
|
Not part of the Tier-3 layer above and deliberately not named `--el-*`:
|
|
3115
3797
|
that layer is a COLOUR contract (every member aliases a Tier-0 --color-*
|
|
3116
|
-
and is asserted palette-dependent by tests/theme/paletteTokenCoverage).
|
|
3798
|
+
and is asserted palette-dependent by tests/theme/paletteTokenCoverage.test.ts).
|
|
3117
3799
|
A knob here is a NUMBER inside a color-mix() — one value for every
|
|
3118
3800
|
palette and both themes — so it fails both halves of that contract while
|
|
3119
3801
|
being exactly the thing that has to be expressed once.
|
|
@@ -3175,7 +3857,7 @@
|
|
|
3175
3857
|
`@layer utilities`; this rule is UNLAYERED, and an unlayered declaration
|
|
3176
3858
|
outranks a layered one whatever their specificity. That is the whole of the
|
|
3177
3859
|
mechanism — it does not depend on source order or on this file's position in
|
|
3178
|
-
the `@import` list. Measured, not reasoned: tests/theme/reducedMotionSpinner
|
|
3860
|
+
the `@import` list. Measured, not reasoned: tests/theme/reducedMotionSpinner.test.ts
|
|
3179
3861
|
reads it back off the COMPILED stylesheet for both consumer shapes.
|
|
3180
3862
|
═══════════════════════════════════════════════════════════════════════ */
|
|
3181
3863
|
@media (prefers-reduced-motion: reduce) {
|