@echodial/deck 0.1.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.
Files changed (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1327 -0
  3. package/bin/deck.mjs +219 -0
  4. package/build.mjs +338 -0
  5. package/dist/api-buckets.json +10486 -0
  6. package/dist/api.json +57896 -0
  7. package/dist/brand/deck-apple-touch-icon.png +0 -0
  8. package/dist/brand/deck-logo-dark.svg +1 -0
  9. package/dist/brand/deck-logo-light.svg +1 -0
  10. package/dist/brand/deck-logo-stacked.svg +1 -0
  11. package/dist/brand/deck-logo.svg +1 -0
  12. package/dist/brand/deck-mark.svg +7 -0
  13. package/dist/brand/deck-og.png +0 -0
  14. package/dist/collisions.json +223 -0
  15. package/dist/deck-adapters.js +482 -0
  16. package/dist/deck-adapters.min.js +23 -0
  17. package/dist/deck-extras.js +823 -0
  18. package/dist/deck-extras.min.js +5 -0
  19. package/dist/deck-icons.svg +184 -0
  20. package/dist/deck.bundle.js +2638 -0
  21. package/dist/deck.bundle.min.js +86 -0
  22. package/dist/deck.css +7904 -0
  23. package/dist/deck.esm.js +2644 -0
  24. package/dist/deck.js +1331 -0
  25. package/dist/deck.min.css +32 -0
  26. package/dist/deck.min.js +60 -0
  27. package/dist/layers/anchor.css +192 -0
  28. package/dist/layers/buttons.css +210 -0
  29. package/dist/layers/charts.css +337 -0
  30. package/dist/layers/combobox.css +248 -0
  31. package/dist/layers/components.css +868 -0
  32. package/dist/layers/container.css +199 -0
  33. package/dist/layers/datagrid.css +335 -0
  34. package/dist/layers/datepicker.css +305 -0
  35. package/dist/layers/forms.css +401 -0
  36. package/dist/layers/gradients.css +302 -0
  37. package/dist/layers/inputs.css +522 -0
  38. package/dist/layers/layers.css +87 -0
  39. package/dist/layers/layout.css +235 -0
  40. package/dist/layers/logical.css +226 -0
  41. package/dist/layers/media.css +400 -0
  42. package/dist/layers/mobile.css +245 -0
  43. package/dist/layers/motion.css +379 -0
  44. package/dist/layers/nav.css +771 -0
  45. package/dist/layers/perf.css +234 -0
  46. package/dist/layers/print.css +198 -0
  47. package/dist/layers/reset.css +103 -0
  48. package/dist/layers/space3d.css +280 -0
  49. package/dist/layers/toasts.css +201 -0
  50. package/dist/layers/tokens.css +229 -0
  51. package/dist/layers/type.css +114 -0
  52. package/dist/layers/utilities.css +241 -0
  53. package/dist/sizes.json +52 -0
  54. package/dist/usage.json +6340 -0
  55. package/package.json +94 -0
  56. package/src/00-layers.css +87 -0
  57. package/src/01-tokens.css +229 -0
  58. package/src/02-reset.css +103 -0
  59. package/src/03-type.css +114 -0
  60. package/src/04-layout.css +235 -0
  61. package/src/05-buttons.css +210 -0
  62. package/src/06-forms.css +401 -0
  63. package/src/07-components.css +868 -0
  64. package/src/08-mobile.css +245 -0
  65. package/src/09-utilities.css +241 -0
  66. package/src/10-datepicker.css +305 -0
  67. package/src/11-combobox.css +248 -0
  68. package/src/12-datagrid.css +335 -0
  69. package/src/13-toasts.css +201 -0
  70. package/src/14-charts.css +337 -0
  71. package/src/16-motion.css +379 -0
  72. package/src/18-container.css +199 -0
  73. package/src/19-logical.css +226 -0
  74. package/src/20-gradients.css +302 -0
  75. package/src/21-space3d.css +280 -0
  76. package/src/22-nav.css +771 -0
  77. package/src/23-inputs.css +522 -0
  78. package/src/24-media.css +400 -0
  79. package/src/25-anchor.css +192 -0
  80. package/src/26-perf.css +234 -0
  81. package/src/99-print.css +198 -0
  82. package/src/brand/deck-apple-touch-icon.png +0 -0
  83. package/src/brand/deck-logo-dark.svg +1 -0
  84. package/src/brand/deck-logo-light.svg +1 -0
  85. package/src/brand/deck-logo-stacked.svg +1 -0
  86. package/src/brand/deck-logo.svg +1 -0
  87. package/src/brand/deck-mark.svg +7 -0
  88. package/src/brand/deck-og.png +0 -0
  89. package/src/brand/sources.json +7 -0
  90. package/src/deck-icons.svg +184 -0
  91. package/src/js/deck-adapters.js +482 -0
  92. package/src/js/deck-extras.js +823 -0
  93. package/src/js/deck.js +1331 -0
@@ -0,0 +1,226 @@
1
+ /* ==========================================================================
2
+ Deck — 19 Logical properties and direction
3
+ The framework is written in logical properties end to end: inline-size,
4
+ block-size, inset-inline-start, padding-block, margin-inline, border-start-
5
+ start-radius. That means Deck already lays out correctly in Arabic, Hebrew,
6
+ Urdu, and Farsi with nothing but dir="rtl" on the html element.
7
+
8
+ What logical properties cannot fix by themselves is content: icons that
9
+ point somewhere, characters that are inherently one-directional, and the few
10
+ places where a physical direction is genuinely meant. That is what this file
11
+ handles.
12
+ ========================================================================== */
13
+
14
+ @layer deck.utilities {
15
+
16
+ /* --- Logical spacing utilities -------------------------------------------
17
+ The short physical-looking names in the utility layer (.mt-4, .px-3) are
18
+ already logical underneath — .mt-4 sets margin-block-start. These are the
19
+ explicitly named versions for when the distinction matters in review.
20
+ ---------------------------------------------------------------------- */
21
+ .mis-auto { margin-inline-start: auto; }
22
+ .mie-auto { margin-inline-end: auto; }
23
+
24
+ /* Same seven steps as every other spacing family: 0 1 2 3 4 6 8. */
25
+ .mis-0 { margin-inline-start: 0; }
26
+ .mis-1 { margin-inline-start: var(--space-1); }
27
+ .mis-2 { margin-inline-start: var(--space-2); }
28
+ .mis-3 { margin-inline-start: var(--space-3); }
29
+ .mis-4 { margin-inline-start: var(--space-4); }
30
+ .mis-6 { margin-inline-start: var(--space-6); }
31
+ .mis-8 { margin-inline-start: var(--space-8); }
32
+ .mie-0 { margin-inline-end: 0; }
33
+ .mie-1 { margin-inline-end: var(--space-1); }
34
+ .mie-2 { margin-inline-end: var(--space-2); }
35
+ .mie-3 { margin-inline-end: var(--space-3); }
36
+ .mie-4 { margin-inline-end: var(--space-4); }
37
+ .mie-6 { margin-inline-end: var(--space-6); }
38
+ .mie-8 { margin-inline-end: var(--space-8); }
39
+
40
+
41
+ .pis-0 { padding-inline-start: 0; }
42
+ .pis-1 { padding-inline-start: var(--space-1); }
43
+ .pis-2 { padding-inline-start: var(--space-2); }
44
+ .pis-3 { padding-inline-start: var(--space-3); }
45
+ .pis-4 { padding-inline-start: var(--space-4); }
46
+ .pis-6 { padding-inline-start: var(--space-6); }
47
+ .pis-8 { padding-inline-start: var(--space-8); }
48
+ .pie-0 { padding-inline-end: 0; }
49
+ .pie-1 { padding-inline-end: var(--space-1); }
50
+ .pie-2 { padding-inline-end: var(--space-2); }
51
+ .pie-3 { padding-inline-end: var(--space-3); }
52
+ .pie-4 { padding-inline-end: var(--space-4); }
53
+ .pie-6 { padding-inline-end: var(--space-6); }
54
+ .pie-8 { padding-inline-end: var(--space-8); }
55
+
56
+ .pbs-0 { padding-block-start: 0; }
57
+ .pbs-1 { padding-block-start: var(--space-1); }
58
+ .pbs-2 { padding-block-start: var(--space-2); }
59
+ .pbs-3 { padding-block-start: var(--space-3); }
60
+ .pbs-4 { padding-block-start: var(--space-4); }
61
+ .pbs-6 { padding-block-start: var(--space-6); }
62
+ .pbs-8 { padding-block-start: var(--space-8); }
63
+ .pbe-0 { padding-block-end: 0; }
64
+ .pbe-1 { padding-block-end: var(--space-1); }
65
+ .pbe-2 { padding-block-end: var(--space-2); }
66
+ .pbe-3 { padding-block-end: var(--space-3); }
67
+ .pbe-4 { padding-block-end: var(--space-4); }
68
+ .pbe-6 { padding-block-end: var(--space-6); }
69
+ .pbe-8 { padding-block-end: var(--space-8); }
70
+
71
+ .bis { border-inline-start: 1px solid var(--line); }
72
+ .bie { border-inline-end: 1px solid var(--line); }
73
+
74
+ .min-is-0 { min-inline-size: 0; }
75
+
76
+ .inset-is-0 { inset-inline-start: 0; }
77
+ .inset-ie-0 { inset-inline-end: 0; }
78
+ .inset-bs-0 { inset-block-start: 0; }
79
+ .inset-be-0 { inset-block-end: 0; }
80
+
81
+ /* Radius on the leading and trailing edges, not left and right */
82
+ .r-start { border-start-start-radius: var(--r-md); border-end-start-radius: var(--r-md); }
83
+ .r-end { border-start-end-radius: var(--r-md); border-end-end-radius: var(--r-md); }
84
+
85
+ /* --- Writing mode --------------------------------------------------------
86
+ Vertical text for a table header that would otherwise need a 40 character
87
+ column, or for a sidebar label.
88
+ ---------------------------------------------------------------------- */
89
+ .writing-vertical {
90
+ writing-mode: vertical-rl;
91
+ text-orientation: mixed;
92
+ }
93
+ .writing-vertical-lr { writing-mode: vertical-lr; }
94
+ .writing-upright { text-orientation: upright; letter-spacing: .1em; }
95
+ .writing-sideways { writing-mode: sideways-lr; }
96
+ .writing-normal { writing-mode: horizontal-tb; }
97
+
98
+ /* A rotated column header that still measures and aligns correctly */
99
+ .th-vertical {
100
+ writing-mode: vertical-rl;
101
+ rotate: 180deg;
102
+ white-space: nowrap;
103
+ block-size: 8rem;
104
+ padding-block: var(--space-3);
105
+ text-align: start;
106
+ }
107
+
108
+ /* --- Explicit direction --------------------------------------------------
109
+ Sometimes a value has one true direction regardless of the page. An order
110
+ number, an account number, a URL, and a code block read left to right even
111
+ on an Arabic page. Isolate them so they do not scramble the text around
112
+ them.
113
+ ---------------------------------------------------------------------- */
114
+ .dir-ltr { direction: ltr; unicode-bidi: isolate; }
115
+ .dir-rtl { direction: rtl; unicode-bidi: isolate; }
116
+ .bidi-isolate { unicode-bidi: isolate; }
117
+ .bidi-plaintext { unicode-bidi: plaintext; }
118
+
119
+ /* Identifiers read left to right in every language: an order number, a commit
120
+ hash, an API key, a part code. Keep them LTR wherever they appear, and keep
121
+ them from scrambling the text around them. */
122
+ .mono, code, kbd, samp, .nums { unicode-bidi: isolate; }
123
+ .code-ltr { direction: ltr; unicode-bidi: isolate; text-align: start; }
124
+ }
125
+
126
+ @layer deck.rtl {
127
+
128
+ /* --- Icons that point ----------------------------------------------------
129
+ An arrow, a chevron, a reply glyph, and a back button all mean "the
130
+ direction of reading". They must mirror. A checkmark, a clock, a wrench,
131
+ and a logo must not. Mark the ones that point.
132
+ ---------------------------------------------------------------------- */
133
+ [dir="rtl"] .mirror-rtl { scale: -1 1; }
134
+
135
+ /* The pointing icons in Deck's own sprite, mirrored automatically */
136
+ [dir="rtl"] :is(
137
+ .icon:has(use[href$="#chevron-left"]),
138
+ .icon:has(use[href$="#chevron-right"]),
139
+ .icon:has(use[href$="#arrow-left"]),
140
+ .icon:has(use[href$="#arrow-right"]),
141
+ .icon:has(use[href$="#arrow-up-right"]),
142
+ .icon:has(use[href$="#external"]),
143
+ .icon:has(use[href$="#log-out"]),
144
+ .icon:has(use[href$="#send"]),
145
+ .icon:has(use[href$="#trend-up"]),
146
+ .icon:has(use[href$="#trend-down"])
147
+ ) { scale: -1 1; }
148
+
149
+ /* Things that must never mirror, even inside a flipped ancestor */
150
+ [dir="rtl"] .no-flip { scale: 1 1; }
151
+
152
+ /* --- Direction-aware motion ---------------------------------------------
153
+ The entrance animations move toward the reader. In RTL the start edge is
154
+ on the right, so the keyframes flip too.
155
+ ---------------------------------------------------------------------- */
156
+ @media (prefers-reduced-motion: no-preference) {
157
+ [dir="rtl"] .enter-start { animation-name: dk-in-start-rtl; }
158
+ [dir="rtl"] .enter-end { animation-name: dk-in-end-rtl; }
159
+ @keyframes dk-in-start-rtl { from { opacity: 0; translate: calc(var(--travel) * 1.6) 0; } }
160
+ @keyframes dk-in-end-rtl { from { opacity: 0; translate: calc(var(--travel) * -1.6) 0; } }
161
+
162
+ [dir="rtl"] .icon-follow { transition: translate var(--dur-2) var(--ease-spring); }
163
+ [dir="rtl"] .btn:hover .icon-follow,
164
+ [dir="rtl"] .list-row:hover .icon-follow,
165
+ [dir="rtl"] .card-link:hover .icon-follow { translate: -3px 0; }
166
+
167
+ [dir="rtl"] .sweep { background-position: 100% 100%; }
168
+ [dir="rtl"] .marquee-track { animation-name: dk-marquee-rtl; }
169
+ @keyframes dk-marquee-rtl { to { translate: 100% 0; } }
170
+ }
171
+
172
+ /* --- Components that hard-code a direction ------------------------------- */
173
+
174
+ /* The select arrow is drawn with background-position from the right edge */
175
+ [dir="rtl"] .select {
176
+ background-position: 19px calc(50% + 1px), 14px calc(50% + 1px);
177
+ background-image: linear-gradient(135deg, transparent 50%, currentColor 50%),
178
+ linear-gradient(45deg, currentColor 50%, transparent 50%);
179
+ padding-inline: var(--space-10) var(--space-3);
180
+ }
181
+
182
+ /* Search icon sits on the start edge, which is the right in RTL */
183
+ [dir="rtl"] .search > .icon { inset-inline-start: var(--space-3); }
184
+
185
+ /* Pinned grid columns and their shadows. Which edge is "start" is already
186
+ logical in both branches of 12-datagrid; only the offsets are physical,
187
+ so mirroring the four variables flips the scroll-state path and the
188
+ class-based fallback at the same time. */
189
+ [dir="rtl"] .dg-wrap {
190
+ --dg-pin-shadow-start: -6px 0 8px -6px oklch(20% .03 var(--hue-neutral) / .28);
191
+ --dg-pin-shadow-end: 6px 0 8px -6px oklch(20% .03 var(--hue-neutral) / .28);
192
+ --dg-pin-clip-start: inset(0 0 0 -12px);
193
+ --dg-pin-clip-end: inset(0 -12px 0 0);
194
+ }
195
+
196
+ /* Switch knob travels toward the end edge */
197
+ [dir="rtl"] .switch input::after { translate: 0 0; }
198
+ [dir="rtl"] .switch input:checked::after { translate: -18px 0; }
199
+
200
+ /* Toast swipe and chart bars grow from the start edge, and transform-origin
201
+ has no logical form.
202
+
203
+ FINDINGS 59: .progress::-webkit-progress-value is also in this list and
204
+ does nothing. A pseudo-element is not valid inside :is(), and :is() parses
205
+ forgivingly -- the browser drops the invalid item and keeps the rest, so
206
+ this rule serialises as [dir="rtl"] :is(.chart-bar-fill, .toast-timer).
207
+ No warning, no failed rule, no visual difference. It is harmless here
208
+ because the progress fill animates inline-size rather than a transform,
209
+ and because the engine mirrors <progress> under dir="rtl" on its own.
210
+
211
+ Due for deletion. If a vendor pseudo-element ever does need a logical
212
+ flip, it has to be its own rule -- grouping it will fail silently. */
213
+ [dir="rtl"] :is(.chart-bar-fill, .progress::-webkit-progress-value, .toast-timer) {
214
+ transform-origin: right center;
215
+ }
216
+ [dir="rtl"] .scroll-progress { transform-origin: right center; }
217
+
218
+ /* Breadcrumb separator */
219
+ [dir="rtl"] .breadcrumb li + li::before { content: "\\\\"; }
220
+
221
+ /* Timeline rail and dots */
222
+ [dir="rtl"] .timeline-item:not(:last-child)::before { inset-inline-start: 10px; }
223
+
224
+ /* Mixed-direction pages: a block that is LTR inside an RTL document */
225
+ [dir="ltr"] [dir="rtl"], [dir="rtl"] [dir="ltr"] { unicode-bidi: isolate; }
226
+ }
@@ -0,0 +1,302 @@
1
+ /* ==========================================================================
2
+ Deck — 20 Gradients
3
+ Every gradient here is built from the brand hue, so the hue slider retunes
4
+ them along with everything else. Interpolation is in oklab or oklch, which
5
+ avoids the grey dead zone you get when sRGB blends two saturated colors.
6
+ ========================================================================== */
7
+
8
+ @layer deck.effects {
9
+
10
+ /* --- Surface washes ------------------------------------------------------
11
+ Quiet. These sit behind content and must never fight it.
12
+ ---------------------------------------------------------------------- */
13
+ .g-surface {
14
+ background-image: linear-gradient(in oklab 180deg,
15
+ color-mix(in oklab, var(--brand-500) 7%, transparent),
16
+ transparent 60%);
17
+ }
18
+ .g-sunken {
19
+ background-image: linear-gradient(in oklab 180deg,
20
+ transparent,
21
+ color-mix(in oklab, var(--ink-900) 5%, transparent));
22
+ }
23
+ .g-brand {
24
+ background-image: linear-gradient(in oklab 135deg,
25
+ var(--brand-600), oklch(from var(--brand-600) l c calc(h + 34)));
26
+ color: var(--text-on-brand);
27
+ }
28
+ .g-brand-soft {
29
+ background-image: linear-gradient(in oklab 135deg,
30
+ var(--brand-soft), color-mix(in oklab, var(--brand-soft) 40%, var(--surface)));
31
+ }
32
+ .g-dark {
33
+ background-image: linear-gradient(in oklab 160deg, var(--ink-900), var(--ink-1000));
34
+ color: var(--ink-100);
35
+ }
36
+
37
+ /* --- Aurora / mesh -------------------------------------------------------
38
+ Three soft radial blooms. No image, no SVG filter, no blur — blur on a
39
+ large area is expensive and this reads the same.
40
+ ---------------------------------------------------------------------- */
41
+ .g-mesh {
42
+ background-color: var(--bg);
43
+ background-image:
44
+ radial-gradient(closest-side at 18% 22%,
45
+ oklch(72% .14 var(--hue-brand) / .30), transparent),
46
+ radial-gradient(closest-side at 82% 12%,
47
+ oklch(76% .13 calc(var(--hue-brand) + 62) / .24), transparent),
48
+ radial-gradient(closest-side at 62% 88%,
49
+ oklch(70% .12 calc(var(--hue-brand) - 48) / .22), transparent);
50
+ background-size: 70% 70%, 60% 60%, 78% 78%;
51
+ background-repeat: no-repeat;
52
+ }
53
+ .g-mesh-warm {
54
+ --hue-brand: 34;
55
+ }
56
+ .g-mesh-subtle {
57
+ background-image:
58
+ radial-gradient(closest-side at 20% 0%, oklch(74% .10 var(--hue-brand) / .18), transparent),
59
+ radial-gradient(closest-side at 90% 30%, oklch(78% .09 calc(var(--hue-brand) + 70) / .14), transparent);
60
+ background-size: 80% 80%, 70% 70%;
61
+ background-repeat: no-repeat;
62
+ }
63
+
64
+ /* Slow drift for a hero. Off under reduced motion. */
65
+ @media (prefers-reduced-motion: no-preference) {
66
+ .g-mesh-drift {
67
+ animation: g-drift 22s var(--ease-in-out) infinite alternate;
68
+ }
69
+ @keyframes g-drift {
70
+ to { background-position: 8% 6%, -6% 10%, 4% -8%; }
71
+ }
72
+ }
73
+
74
+ /* --- Text gradients ------------------------------------------------------
75
+ background-clip on the text. Always keep a real color underneath so the
76
+ text is still readable if the clip fails.
77
+ ---------------------------------------------------------------------- */
78
+ .g-text {
79
+ color: var(--brand-700);
80
+ background-image: linear-gradient(in oklab 92deg,
81
+ var(--brand-600), oklch(from var(--brand-500) l c calc(h + 52)));
82
+ -webkit-background-clip: text;
83
+ background-clip: text;
84
+ -webkit-text-fill-color: transparent;
85
+ /* keeps descenders from clipping in some engines */
86
+ padding-block-end: .06em;
87
+ }
88
+ .g-text-shine {
89
+ color: var(--text);
90
+ background-image: linear-gradient(in oklab 92deg,
91
+ var(--text) 0 40%, var(--brand-400) 50%, var(--text) 60% 100%);
92
+ background-size: 260% 100%;
93
+ -webkit-background-clip: text;
94
+ background-clip: text;
95
+ -webkit-text-fill-color: transparent;
96
+ }
97
+ @media (prefers-reduced-motion: no-preference) {
98
+ .g-text-shine { animation: g-shine 4.5s var(--ease-in-out) infinite; }
99
+ @keyframes g-shine { to { background-position: -160% 0; } }
100
+ }
101
+
102
+ /* --- Gradient borders ----------------------------------------------------
103
+ Two backgrounds with different clip boxes. Works with any border-radius,
104
+ unlike border-image, and leaves the inside a real surface color.
105
+ ---------------------------------------------------------------------- */
106
+ .g-border {
107
+ --g-width: 1.5px;
108
+ border: var(--g-width) solid transparent;
109
+ background:
110
+ linear-gradient(var(--surface), var(--surface)) padding-box,
111
+ linear-gradient(in oklab var(--g-angle, 135deg),
112
+ var(--brand-500), oklch(from var(--brand-400) l c calc(h + 70))) border-box;
113
+ }
114
+ .g-border-soft {
115
+ background:
116
+ linear-gradient(var(--surface), var(--surface)) padding-box,
117
+ linear-gradient(in oklab 160deg,
118
+ color-mix(in oklab, var(--brand-400) 60%, transparent),
119
+ transparent 70%) border-box;
120
+ }
121
+ /* The angle is a registered property, so it can animate */
122
+ @media (prefers-reduced-motion: no-preference) {
123
+ .g-border-spin { animation: g-spin 6s linear infinite; }
124
+ @keyframes g-spin { to { --g-angle: 495deg; } }
125
+ }
126
+
127
+ /* A single glowing hairline along the top edge — good for a nav or a modal */
128
+ .g-hairline {
129
+ position: relative;
130
+ }
131
+ .g-hairline::before {
132
+ content: "";
133
+ position: absolute;
134
+ inset-block-start: 0;
135
+ inset-inline: 0;
136
+ block-size: 1px;
137
+ background: linear-gradient(90deg, transparent,
138
+ color-mix(in oklab, var(--brand-400) 70%, transparent) 30% 70%, transparent);
139
+ }
140
+
141
+ /* --- Scrims ---------------------------------------------------------------
142
+ A caption over a photo needs a legible floor. A flat 50% black overlay
143
+ dulls the whole image; an eased scrim only darkens where the text sits.
144
+ ---------------------------------------------------------------------- */
145
+ .g-scrim {
146
+ position: relative;
147
+ isolation: isolate;
148
+ }
149
+ .g-scrim::after {
150
+ content: "";
151
+ position: absolute;
152
+ inset: 0;
153
+ z-index: -1;
154
+ background: var(--scrim);
155
+ pointer-events: none;
156
+ }
157
+ .g-scrim-top::after {
158
+ background: linear-gradient(to bottom,
159
+ oklch(12% .02 var(--hue-neutral) / .72), transparent 62%);
160
+ }
161
+ .g-scrim > * { position: relative; }
162
+
163
+ /* --- Fade masks ----------------------------------------------------------
164
+ Content that runs off the edge should fade, not be cut with a hard line.
165
+ ---------------------------------------------------------------------- */
166
+ .g-fade-inline {
167
+ mask-image: linear-gradient(90deg, transparent, #000 var(--fade, 4%),
168
+ #000 calc(100% - var(--fade, 4%)), transparent);
169
+ -webkit-mask-image: linear-gradient(90deg, transparent, #000 var(--fade, 4%),
170
+ #000 calc(100% - var(--fade, 4%)), transparent);
171
+ }
172
+ .g-fade-end {
173
+ mask-image: linear-gradient(90deg, #000 70%, transparent);
174
+ -webkit-mask-image: linear-gradient(90deg, #000 70%, transparent);
175
+ }
176
+ .g-fade-block {
177
+ mask-image: linear-gradient(180deg, #000 60%, transparent);
178
+ -webkit-mask-image: linear-gradient(180deg, #000 60%, transparent);
179
+ }
180
+ /* Long text that is collapsed: fade the bottom instead of a hard cut */
181
+ .g-fade-more {
182
+ max-block-size: var(--collapsed, 9rem);
183
+ overflow: hidden;
184
+ mask-image: linear-gradient(180deg, #000 65%, transparent);
185
+ -webkit-mask-image: linear-gradient(180deg, #000 65%, transparent);
186
+ }
187
+ .g-fade-more.is-open {
188
+ max-block-size: none;
189
+ mask-image: none;
190
+ -webkit-mask-image: none;
191
+ }
192
+ .scroller { mask-image: linear-gradient(90deg, #000 92%, transparent); -webkit-mask-image: linear-gradient(90deg, #000 92%, transparent); }
193
+
194
+ /* --- Sheen ---------------------------------------------------------------
195
+ A highlight that sweeps across on hover. --sheen is registered, so it
196
+ animates as a real percentage instead of jumping.
197
+ ---------------------------------------------------------------------- */
198
+ .g-sheen {
199
+ position: relative;
200
+ overflow: hidden;
201
+ isolation: isolate;
202
+ }
203
+ .g-sheen::after {
204
+ content: "";
205
+ position: absolute;
206
+ inset-block: 0;
207
+ inset-inline-start: var(--sheen);
208
+ inline-size: 40%;
209
+ background: linear-gradient(105deg, transparent,
210
+ oklch(100% 0 0 / .32) 45%, oklch(100% 0 0 / .32) 55%, transparent);
211
+ pointer-events: none;
212
+ transition: --sheen var(--dur-5) var(--ease-out);
213
+ }
214
+ @media (prefers-reduced-motion: no-preference) {
215
+ .g-sheen:hover::after { --sheen: 110%; }
216
+ }
217
+
218
+ /* --- Status gradients ----------------------------------------------------- */
219
+ .g-good { background-image: linear-gradient(in oklab 135deg, var(--good-600), oklch(from var(--good-600) l c calc(h - 26))); color: var(--text-on-good); }
220
+ .g-warn { background-image: linear-gradient(in oklab 135deg, var(--warn-500), oklch(from var(--warn-500) l c calc(h + 22))); color: var(--text-on-warn); }
221
+ .g-bad { background-image: linear-gradient(in oklab 135deg, var(--bad-600), oklch(from var(--bad-600) l c calc(h - 18))); color: var(--text-on-bad); }
222
+
223
+ /* --- Conic accents -------------------------------------------------------- */
224
+ .g-conic {
225
+ background-image: conic-gradient(from var(--g-angle, 0deg) in oklch longer hue,
226
+ oklch(70% .16 0), oklch(70% .16 360));
227
+ }
228
+ .g-ring {
229
+ --thickness: 3px;
230
+ border-radius: var(--r-full);
231
+ background: conic-gradient(from var(--g-angle, 0deg),
232
+ transparent, var(--brand-500), transparent 62%);
233
+ mask: radial-gradient(farthest-side, transparent calc(100% - var(--thickness)), #000 calc(100% - var(--thickness) + 1px));
234
+ -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - var(--thickness)), #000 calc(100% - var(--thickness) + 1px));
235
+ }
236
+ @media (prefers-reduced-motion: no-preference) {
237
+ .g-ring-spin { animation: g-spin-360 1.6s linear infinite; }
238
+ @keyframes g-spin-360 { to { --g-angle: 360deg; } }
239
+ }
240
+
241
+ /* --- Structural patterns --------------------------------------------------
242
+ Not gradients as decoration — gradients standing in for an image. These
243
+ cost nothing to download and retint automatically.
244
+ ---------------------------------------------------------------------- */
245
+ .g-grid-lines {
246
+ background-image:
247
+ linear-gradient(to right, var(--line) 1px, transparent 1px),
248
+ linear-gradient(to bottom, var(--line) 1px, transparent 1px);
249
+ background-size: var(--cell, 24px) var(--cell, 24px);
250
+ }
251
+ .g-dots {
252
+ background-image: radial-gradient(var(--line-strong) 1px, transparent 1px);
253
+ background-size: var(--cell, 18px) var(--cell, 18px);
254
+ }
255
+ .g-stripes {
256
+ background-image: repeating-linear-gradient(45deg,
257
+ color-mix(in oklab, var(--ink-500) 8%, transparent) 0 6px,
258
+ transparent 6px 12px);
259
+ }
260
+ /* Hatching for a "pending" or "unavailable" block in a schedule */
261
+ .g-hatch {
262
+ background-image: repeating-linear-gradient(-45deg,
263
+ color-mix(in oklab, var(--warn-500) 22%, transparent) 0 5px,
264
+ transparent 5px 10px);
265
+ }
266
+
267
+ /* --- Skeleton and shimmer, unified with the palette ---------------------- */
268
+ .g-shimmer {
269
+ background-image: linear-gradient(100deg,
270
+ transparent 20%,
271
+ color-mix(in oklab, var(--brand-200) 55%, transparent) 45%,
272
+ transparent 70%);
273
+ background-size: 250% 100%;
274
+ background-repeat: no-repeat;
275
+ }
276
+ @media (prefers-reduced-motion: no-preference) {
277
+ .g-shimmer { animation: dk-shimmer 1.6s var(--ease-in-out) infinite; }
278
+ }
279
+
280
+ /* --- Chart fills ---------------------------------------------------------
281
+ Area charts read better with a gradient falling off to nothing.
282
+ ---------------------------------------------------------------------- */
283
+ .chart-area-g {
284
+ fill: url(#deck-area-gradient);
285
+ stroke: none;
286
+ }
287
+ .g-bar-fill {
288
+ background-image: linear-gradient(in oklab 180deg,
289
+ var(--series, var(--c1)),
290
+ color-mix(in oklab, var(--series, var(--c1)) 70%, var(--ink-900)));
291
+ }
292
+
293
+ /* --- Fallback ------------------------------------------------------------
294
+ Engines without oklch get the plain brand color rather than a broken
295
+ gradient. The declarations above simply do not apply.
296
+ ---------------------------------------------------------------------- */
297
+ @supports not (color: oklch(50% .1 200)) {
298
+ .g-brand, .g-good, .g-warn, .g-bad { background-image: none; background-color: var(--brand-600); }
299
+ .g-text { -webkit-text-fill-color: currentColor; background-image: none; }
300
+ .g-mesh { background-image: none; }
301
+ }
302
+ }