efficient-shell 0.0.0 → 4.126.3
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/LICENSE +21 -0
- package/README.md +758 -3
- package/dist/Browser-LM4RRZIL.js +478 -0
- package/dist/Browser-LM4RRZIL.js.map +1 -0
- package/dist/Calculator-HCKFTSJR.js +188 -0
- package/dist/Calculator-HCKFTSJR.js.map +1 -0
- package/dist/ConfirmDialog-LJXMB45A.js +3 -0
- package/dist/ConfirmDialog-LJXMB45A.js.map +1 -0
- package/dist/CurrencyConverter-BKKORN6K.js +232 -0
- package/dist/CurrencyConverter-BKKORN6K.js.map +1 -0
- package/dist/Documents-DD3QI5ER.js +437 -0
- package/dist/Documents-DD3QI5ER.js.map +1 -0
- package/dist/Files-BOPCPMJE.js +801 -0
- package/dist/Files-BOPCPMJE.js.map +1 -0
- package/dist/Notepad-KJOLF63O.js +406 -0
- package/dist/Notepad-KJOLF63O.js.map +1 -0
- package/dist/PdfViewer-LSP3YNFC.js +4 -0
- package/dist/PdfViewer-LSP3YNFC.js.map +1 -0
- package/dist/PomodoroTimer-SVJ62RXM.js +629 -0
- package/dist/PomodoroTimer-SVJ62RXM.js.map +1 -0
- package/dist/Preview-5UOC7ZRU.js +2955 -0
- package/dist/Preview-5UOC7ZRU.js.map +1 -0
- package/dist/Sidebar-J76PV4QF.js +290 -0
- package/dist/Sidebar-J76PV4QF.js.map +1 -0
- package/dist/Spreadsheet-J7BH7UHC.js +515 -0
- package/dist/Spreadsheet-J7BH7UHC.js.map +1 -0
- package/dist/Stock-CDUS4VCZ.js +154 -0
- package/dist/Stock-CDUS4VCZ.js.map +1 -0
- package/dist/Weather-EEDBWBPQ.js +427 -0
- package/dist/Weather-EEDBWBPQ.js.map +1 -0
- package/dist/WorldClock-FFFULVTJ.js +198 -0
- package/dist/WorldClock-FFFULVTJ.js.map +1 -0
- package/dist/apps/index.d.ts +267 -0
- package/dist/apps/index.js +147 -0
- package/dist/apps/index.js.map +1 -0
- package/dist/brand.css +2 -0
- package/dist/chunk-2VM2FRZJ.js +215 -0
- package/dist/chunk-2VM2FRZJ.js.map +1 -0
- package/dist/chunk-32W6VY2J.js +549 -0
- package/dist/chunk-32W6VY2J.js.map +1 -0
- package/dist/chunk-3CUSBZWG.js +59 -0
- package/dist/chunk-3CUSBZWG.js.map +1 -0
- package/dist/chunk-3L463T5H.js +26 -0
- package/dist/chunk-3L463T5H.js.map +1 -0
- package/dist/chunk-4TVV2BIU.js +13334 -0
- package/dist/chunk-4TVV2BIU.js.map +1 -0
- package/dist/chunk-6XQHH7D2.js +105 -0
- package/dist/chunk-6XQHH7D2.js.map +1 -0
- package/dist/chunk-7CCHEEYC.js +94 -0
- package/dist/chunk-7CCHEEYC.js.map +1 -0
- package/dist/chunk-7KIOEKHS.js +617 -0
- package/dist/chunk-7KIOEKHS.js.map +1 -0
- package/dist/chunk-AXWC3332.js +1103 -0
- package/dist/chunk-AXWC3332.js.map +1 -0
- package/dist/chunk-BFBWLCO6.js +16 -0
- package/dist/chunk-BFBWLCO6.js.map +1 -0
- package/dist/chunk-CT3HVZRZ.js +26 -0
- package/dist/chunk-CT3HVZRZ.js.map +1 -0
- package/dist/chunk-D7PYW2QS.js +265 -0
- package/dist/chunk-D7PYW2QS.js.map +1 -0
- package/dist/chunk-DMCFOWOJ.js +153 -0
- package/dist/chunk-DMCFOWOJ.js.map +1 -0
- package/dist/chunk-FQB24B42.js +416 -0
- package/dist/chunk-FQB24B42.js.map +1 -0
- package/dist/chunk-GA35NEND.js +56 -0
- package/dist/chunk-GA35NEND.js.map +1 -0
- package/dist/chunk-HNH7FOYB.js +69 -0
- package/dist/chunk-HNH7FOYB.js.map +1 -0
- package/dist/chunk-HYZ5QMVO.js +462 -0
- package/dist/chunk-HYZ5QMVO.js.map +1 -0
- package/dist/chunk-I464GYJJ.js +17 -0
- package/dist/chunk-I464GYJJ.js.map +1 -0
- package/dist/chunk-JFQGPY7W.js +819 -0
- package/dist/chunk-JFQGPY7W.js.map +1 -0
- package/dist/chunk-MGGY74RY.js +14 -0
- package/dist/chunk-MGGY74RY.js.map +1 -0
- package/dist/chunk-N3CG5HG5.js +144 -0
- package/dist/chunk-N3CG5HG5.js.map +1 -0
- package/dist/chunk-QRGFTK2X.js +79 -0
- package/dist/chunk-QRGFTK2X.js.map +1 -0
- package/dist/chunk-SZ4WMHUX.js +7 -0
- package/dist/chunk-SZ4WMHUX.js.map +1 -0
- package/dist/chunk-W6XTG23K.js +3708 -0
- package/dist/chunk-W6XTG23K.js.map +1 -0
- package/dist/chunk-YG45DAUT.js +96 -0
- package/dist/chunk-YG45DAUT.js.map +1 -0
- package/dist/chunk-YNGPTOL6.js +46 -0
- package/dist/chunk-YNGPTOL6.js.map +1 -0
- package/dist/chunk-YNHLQ24V.js +400 -0
- package/dist/chunk-YNHLQ24V.js.map +1 -0
- package/dist/chunk-YNKK7NE3.js +171 -0
- package/dist/chunk-YNKK7NE3.js.map +1 -0
- package/dist/chunk-YPO6RDMX.js +20 -0
- package/dist/chunk-YPO6RDMX.js.map +1 -0
- package/dist/file-intake/index.d.ts +87 -0
- package/dist/file-intake/index.js +3 -0
- package/dist/file-intake/index.js.map +1 -0
- package/dist/index-dQia6NpB.d.ts +5904 -0
- package/dist/index.d.ts +2495 -0
- package/dist/index.js +7460 -0
- package/dist/index.js.map +1 -0
- package/dist/markdown/index.d.ts +118 -0
- package/dist/markdown/index.js +184 -0
- package/dist/markdown/index.js.map +1 -0
- package/dist/markup/index.d.ts +235 -0
- package/dist/markup/index.js +135 -0
- package/dist/markup/index.js.map +1 -0
- package/dist/page-tabs/index.d.ts +154 -0
- package/dist/page-tabs/index.js +706 -0
- package/dist/page-tabs/index.js.map +1 -0
- package/dist/shell.css +105 -0
- package/dist/sounds-NT4DEZGD.js +3 -0
- package/dist/sounds-NT4DEZGD.js.map +1 -0
- package/dist/styles.css +22 -0
- package/dist/themes.css +249 -0
- package/dist/types-BOinJXUf.d.ts +118 -0
- package/dist/ui/index.d.ts +4 -0
- package/dist/ui/index.js +15 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui.css +1518 -0
- package/package.json +172 -3
package/dist/ui.css
ADDED
|
@@ -0,0 +1,1518 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* react-os-shell/ui.css — styles for the UI kit, without the window chrome.
|
|
3
|
+
*
|
|
4
|
+
* Import this when you take `react-os-shell/ui`:
|
|
5
|
+
*
|
|
6
|
+
* import "react-os-shell/ui.css";
|
|
7
|
+
*
|
|
8
|
+
* Import EXACTLY ONE of this and `react-os-shell/styles.css` — the latter is
|
|
9
|
+
* the umbrella over this file plus shell.css, so taking both doubles every rule.
|
|
10
|
+
*
|
|
11
|
+
* You supply Tailwind v4 yourself (`@import "tailwindcss"` in your own entry,
|
|
12
|
+
* which every consumer already does). This file deliberately does NOT import
|
|
13
|
+
* it: a consumer mid-migration off another component library may need the
|
|
14
|
+
* utility and theme layers WITHOUT preflight, whose bare element selectors
|
|
15
|
+
* would otherwise out-specify that library's `:where()`-wrapped reset.
|
|
16
|
+
*
|
|
17
|
+
* Scope — what dark mode actually is here:
|
|
18
|
+
* - the accent theme variants, via themes.css below
|
|
19
|
+
* - [data-theme="dark"] overrides of Tailwind UTILITY CLASS NAMES. There are
|
|
20
|
+
* no `dark:` variants in this package; you write the light class and the
|
|
21
|
+
* remap happens to you. Escaped-important classes (.\!bg-blue-50) and alpha
|
|
22
|
+
* variants (.bg-white\/70) compile to different class names, so each needs
|
|
23
|
+
* its own rule — that is why the list is long rather than clever.
|
|
24
|
+
* - the reduce-transparency escape hatch, form-control dark styling, the
|
|
25
|
+
* coarse-pointer 16px rule that defeats iOS focus-zoom, StatusBadge tones,
|
|
26
|
+
* and scrollbars.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
@import "./themes.css";
|
|
30
|
+
|
|
31
|
+
/*
|
|
32
|
+
* Shape and type, as tokens the consuming app inherits.
|
|
33
|
+
*
|
|
34
|
+
* Every value here equals Tailwind's own default, so adding this changes
|
|
35
|
+
* nothing that renders. What it changes is WHERE the value comes from: a
|
|
36
|
+
* portal that deletes its own `@theme` block now takes its radius and its
|
|
37
|
+
* typeface from this package, and one edit here moves all of them.
|
|
38
|
+
*
|
|
39
|
+
* A consumer's `@theme` still wins — Tailwind merges the blocks and the later
|
|
40
|
+
* declaration replaces this one — so a portal that wants its own shape keeps
|
|
41
|
+
* it by saying so, rather than by this file not having an opinion.
|
|
42
|
+
*
|
|
43
|
+
* "Later" is a REQUIREMENT, not an observation: import this file ABOVE your own
|
|
44
|
+
* `@theme`. A portal that declares `--font-sans` and then imports the kit below
|
|
45
|
+
* it has its typeface silently reset to the system stack, with nothing in the
|
|
46
|
+
* diff to explain it. All five consuming portals import the kit near the top of
|
|
47
|
+
* `index.css` and declare after it, which is why this reads as a no-op today.
|
|
48
|
+
*
|
|
49
|
+
* On type: the token is here, the TYPEFACE is not. Shipping a face means
|
|
50
|
+
* hosting and licensing it, which is a product decision rather than a
|
|
51
|
+
* packaging one. Until that is made, a portal that sets nothing gets the
|
|
52
|
+
* system stack — the same thing it got before this block existed.
|
|
53
|
+
*/
|
|
54
|
+
@theme {
|
|
55
|
+
--radius-sm: 0.25rem;
|
|
56
|
+
--radius-md: 0.375rem;
|
|
57
|
+
--radius-lg: 0.5rem;
|
|
58
|
+
--radius-xl: 0.75rem;
|
|
59
|
+
--radius-2xl: 1rem;
|
|
60
|
+
|
|
61
|
+
--font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
|
62
|
+
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* Tailwind v4 doesn't scan node_modules by default — tell it to scan this
|
|
66
|
+
package's compiled JS so utility classes used in kit components get
|
|
67
|
+
generated when consumers import react-os-shell/ui.css. */
|
|
68
|
+
@source "./**/*.js";
|
|
69
|
+
|
|
70
|
+
/* Tailwind scans every text file it can reach, prose included, so a class
|
|
71
|
+
NAMED in the README, the CHANGELOG or a contributor guide is compiled into
|
|
72
|
+
the bundle as if a component used it. That is not academic: `h-[440px]` and
|
|
73
|
+
`bg-[#abc]`, which .design-sync/conventions.md cites as examples of values
|
|
74
|
+
that render as NOTHING, were both real classes in the shipped stylesheet.
|
|
75
|
+
Worse downstream — harness 52-brand-surface.md treats the compiled bundle as
|
|
76
|
+
the published vocabulary, so a passing mention in prose silently widened the
|
|
77
|
+
organisation's brand contract. Excluding the documentation dropped 21 such
|
|
78
|
+
classes and none the code uses. Excluding a path that does not exist is not
|
|
79
|
+
an error, so these hold for a consumer's node_modules copy too. */
|
|
80
|
+
@source not "../*.md";
|
|
81
|
+
@source not "../docs/**";
|
|
82
|
+
@source not "../.design-sync/**";
|
|
83
|
+
|
|
84
|
+
/* The default `utils/glass.ts` already assumes at `getMenuOpacity()`'s
|
|
85
|
+
fallback. Declared here so a ui-only consumer gets identical glass to a shell
|
|
86
|
+
consumer at rest, and has one knob to dial it. <Layout> overrides it inline
|
|
87
|
+
for shell consumers; reduce-transparency still beats both. */
|
|
88
|
+
:root {
|
|
89
|
+
--menu-opacity: 0.95;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* ── Reduce transparency (Preferences → Customization) ──────────────────────
|
|
93
|
+
* When the user turns on "Reduce transparency", Layout adds this class to
|
|
94
|
+
* <html>. Two effects, applied globally so every present-and-future glass
|
|
95
|
+
* surface is covered without per-component edits:
|
|
96
|
+
* 1. Force every window/taskbar/menu opacity variable fully opaque, so the
|
|
97
|
+
* surfaces that tint via `rgb(... / var(--*-opacity))` become solid. The
|
|
98
|
+
* `!important` beats Layout's inline (non-important) custom-property writes.
|
|
99
|
+
* 2. Strip backdrop-filter everywhere — the blur is the GPU-expensive part on
|
|
100
|
+
* older machines. `!important` overrides both Tailwind `backdrop-blur-*`
|
|
101
|
+
* utilities and inline `backdropFilter` from glassStyle(). glassStyle()
|
|
102
|
+
* also returns a solid background under this class so menus/popups don't
|
|
103
|
+
* read as washed-out translucent panels once the blur is gone. */
|
|
104
|
+
html.rosh-reduce-transparency {
|
|
105
|
+
--active-header-opacity: 1 !important;
|
|
106
|
+
--active-content-opacity: 1 !important;
|
|
107
|
+
--inactive-header-opacity: 1 !important;
|
|
108
|
+
--inactive-content-opacity: 1 !important;
|
|
109
|
+
--menu-opacity: 1 !important;
|
|
110
|
+
--taskbar-opacity: 1 !important;
|
|
111
|
+
}
|
|
112
|
+
html.rosh-reduce-transparency *,
|
|
113
|
+
html.rosh-reduce-transparency *::before,
|
|
114
|
+
html.rosh-reduce-transparency *::after {
|
|
115
|
+
backdrop-filter: none !important;
|
|
116
|
+
-webkit-backdrop-filter: none !important;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* ═══════════════════════════════════════════════════
|
|
120
|
+
The neutral ramp, light
|
|
121
|
+
═══════════════════════════════════════════════════ */
|
|
122
|
+
/*
|
|
123
|
+
* The dark block below has named these twelve roles since 4.16; light named
|
|
124
|
+
* none of them, so `var(--ink)` resolved to nothing outside a dark theme.
|
|
125
|
+
* That is the same shape the --viz-* palette further down already gets right:
|
|
126
|
+
* declared for both themes, read by role.
|
|
127
|
+
*
|
|
128
|
+
* Adding light values is render-neutral for existing consumers — no rule
|
|
129
|
+
* outside a `[data-theme="dark"]` scope reads any of these twelve, in CSS or
|
|
130
|
+
* in TSX. It exists so a surface built WITHOUT the kit's utility classes —
|
|
131
|
+
* a report, an emailed document, a standalone page — can read the same roles
|
|
132
|
+
* the portals do. That surface is governed by harness 52-brand-surface.md and
|
|
133
|
+
* loads `react-os-shell/brand.css`.
|
|
134
|
+
*
|
|
135
|
+
* The values are the Tailwind steps the dark block opposite already remaps:
|
|
136
|
+
* white/gray-50/gray-100 surfaces, gray-100/200/300 lines, gray-900 down to
|
|
137
|
+
* gray-400 ink. Keep the two blocks in step.
|
|
138
|
+
*/
|
|
139
|
+
:root {
|
|
140
|
+
--surface: #ffffff;
|
|
141
|
+
--surface-sunken: #f9fafb;
|
|
142
|
+
--surface-raised: #f3f4f6;
|
|
143
|
+
--line-subtle: #f3f4f6;
|
|
144
|
+
--line: #e5e7eb;
|
|
145
|
+
--line-strong: #d1d5db;
|
|
146
|
+
--ink-faintest: #9ca3af;
|
|
147
|
+
--ink-faint: #6b7280;
|
|
148
|
+
--ink-muted: #4b5563;
|
|
149
|
+
--ink: #374151;
|
|
150
|
+
--ink-strong: #1f2937;
|
|
151
|
+
--ink-strongest: #111827;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/* ═══════════════════════════════════════════════════
|
|
155
|
+
Theme: Dark
|
|
156
|
+
═══════════════════════════════════════════════════ */
|
|
157
|
+
[data-theme="dark"] {
|
|
158
|
+
color-scheme: dark;
|
|
159
|
+
|
|
160
|
+
/*
|
|
161
|
+
* The neutral ramp, as variables.
|
|
162
|
+
*
|
|
163
|
+
* Every rule below reads these rather than a literal, so a consumer that
|
|
164
|
+
* wants a different dark — warmer, higher contrast, its own brand — sets
|
|
165
|
+
* twelve values instead of forking 100 rules. The status hues (red for
|
|
166
|
+
* danger, amber for warning) stay literal on purpose: they mean the same
|
|
167
|
+
* thing in every product, and a consumer redefining "danger" is a different
|
|
168
|
+
* conversation from one restyling its greys.
|
|
169
|
+
*
|
|
170
|
+
* The names say what a step DOES rather than which Tailwind number it
|
|
171
|
+
* replaces, because in a light theme the numbers run the other way round
|
|
172
|
+
* while the roles do not.
|
|
173
|
+
*/
|
|
174
|
+
--surface: #1e1e2e;
|
|
175
|
+
--surface-sunken: #181825;
|
|
176
|
+
--surface-raised: #313244;
|
|
177
|
+
--line-subtle: #2a2a3c;
|
|
178
|
+
--line: #313244;
|
|
179
|
+
--line-strong: #45475a;
|
|
180
|
+
--ink-faintest: #6c7086;
|
|
181
|
+
/* 4.79:1 on --surface. Was #7f849c at 4.44:1 — under AA, and this step
|
|
182
|
+
is what `.text-gray-500` renders as in dark, so it carries real text. */
|
|
183
|
+
--ink-faint: #848aa2;
|
|
184
|
+
/*
|
|
185
|
+
* Currently unreachable, and worth knowing before you set it.
|
|
186
|
+
*
|
|
187
|
+
* Its only consumer is `.text-gray-600` below, which a later rule overrides
|
|
188
|
+
* to `--ink` so short taskbar-button labels stay readable on dark glass.
|
|
189
|
+
* That override is global by design — shell.css's docblock spells out why a
|
|
190
|
+
* bare utility selector cannot live there — so `text-gray-600` renders
|
|
191
|
+
* `--ink` everywhere and this step renders nowhere.
|
|
192
|
+
*
|
|
193
|
+
* Left declared rather than deleted: it names a real rung, and a consumer
|
|
194
|
+
* scoping its own `.text-gray-600` rule can reach it. Deleting it would be a
|
|
195
|
+
* rendering change, which this release deliberately is not.
|
|
196
|
+
*/
|
|
197
|
+
--ink-muted: #9399b2;
|
|
198
|
+
--ink: #a6adc8;
|
|
199
|
+
--ink-strong: #bac2de;
|
|
200
|
+
--ink-strongest: #cdd6f4;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/* ── Chart tokens ───────────────────────────────────────────────────────────
|
|
204
|
+
* Eight categorical slots, a reserved status set, and the chart inks. Declared
|
|
205
|
+
* BELOW the neutral ramp on purpose: the dark chart inks read the ramp, so a
|
|
206
|
+
* consumer restyling its greys moves the grid and axis with them.
|
|
207
|
+
*
|
|
208
|
+
* Deliberately NOT remapped by themes.css. Every other accent token in this
|
|
209
|
+
* package follows the user's chosen theme; these must not. A series colour is
|
|
210
|
+
* an identity encoding — "4xx is amber" — and an identity that changes when
|
|
211
|
+
* someone picks the pink theme has stopped being one. See charts/palette.ts
|
|
212
|
+
* for the slot-order rule and the colour-vision validation these steps passed.
|
|
213
|
+
*
|
|
214
|
+
* The light steps are validated against white, the dark ones against
|
|
215
|
+
* `--surface`. They are the same eight hues re-stepped for the darker band,
|
|
216
|
+
* not a second palette. */
|
|
217
|
+
:root {
|
|
218
|
+
--viz-series-1: #2a78d6;
|
|
219
|
+
--viz-series-2: #eb6834;
|
|
220
|
+
--viz-series-3: #1baf7a;
|
|
221
|
+
--viz-series-4: #eda100;
|
|
222
|
+
--viz-series-5: #e87ba4;
|
|
223
|
+
--viz-series-6: #008300;
|
|
224
|
+
--viz-series-7: #4a3aa7;
|
|
225
|
+
--viz-series-8: #e34948;
|
|
226
|
+
|
|
227
|
+
--viz-good: #1baf7a;
|
|
228
|
+
--viz-neutral: #6b7280;
|
|
229
|
+
--viz-warning: #eda100;
|
|
230
|
+
--viz-serious: #eb6834;
|
|
231
|
+
--viz-critical: #e34948;
|
|
232
|
+
|
|
233
|
+
--viz-grid: #e5e7eb;
|
|
234
|
+
--viz-axis: #d1d5db;
|
|
235
|
+
--viz-label: #6b7280;
|
|
236
|
+
--viz-muted: #9ca3af;
|
|
237
|
+
--viz-surface: #ffffff;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
[data-theme="dark"] {
|
|
241
|
+
--viz-series-1: #3987e5;
|
|
242
|
+
--viz-series-2: #d95926;
|
|
243
|
+
--viz-series-3: #199e70;
|
|
244
|
+
--viz-series-4: #c98500;
|
|
245
|
+
--viz-series-5: #d55181;
|
|
246
|
+
--viz-series-6: #008300;
|
|
247
|
+
--viz-series-7: #9085e9;
|
|
248
|
+
--viz-series-8: #e66767;
|
|
249
|
+
|
|
250
|
+
--viz-good: #199e70;
|
|
251
|
+
--viz-neutral: #7f849c;
|
|
252
|
+
--viz-warning: #c98500;
|
|
253
|
+
--viz-serious: #d95926;
|
|
254
|
+
--viz-critical: #e66767;
|
|
255
|
+
|
|
256
|
+
--viz-grid: var(--line-subtle);
|
|
257
|
+
--viz-axis: var(--line-strong);
|
|
258
|
+
--viz-label: var(--ink-faint);
|
|
259
|
+
--viz-muted: var(--ink-faintest);
|
|
260
|
+
--viz-surface: var(--surface);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/* ── Status tokens ──────────────────────────────────────────────────────────
|
|
264
|
+
* The nine semantic groups (`SemanticGroup` in shell/StatusBadge.tsx), as two
|
|
265
|
+
* registers a caller picks between with one prop rather than by hand-writing
|
|
266
|
+
* colours: SOFT for a badge inside a dense row, SOLID for the same fact in a
|
|
267
|
+
* detail header.
|
|
268
|
+
*
|
|
269
|
+
* WHY THE SOFT FILL IS TRANSPARENT. A status used to be an opaque tint —
|
|
270
|
+
* `bg-green-100` — calibrated against one backdrop: the flat white surface. On
|
|
271
|
+
* a raised panel or a hovered row that tint is still the same flat patch, and
|
|
272
|
+
* it reads as a hole rather than as a badge, because it does not know what is
|
|
273
|
+
* behind it. A wash of the group's own hue at a fixed alpha composites with
|
|
274
|
+
* whatever it sits on, so ONE value is right on `--surface`, `--surface-raised`
|
|
275
|
+
* and a hovered row alike. That is also why each `-soft` is declared once and
|
|
276
|
+
* never re-declared for dark: a transparent wash has no theme, the surface
|
|
277
|
+
* under it does.
|
|
278
|
+
*
|
|
279
|
+
* WHY THE INK IS NOT THE `-text` ROLE. The obvious ink for a status is
|
|
280
|
+
* `--success-text` / `--warning-text` / `--danger-text` from brand.css, which
|
|
281
|
+
* are calibrated to clear 4.5:1 on the FLAT surface. Over the wash they do not,
|
|
282
|
+
* and the worst backdrop (`--surface-raised`) is the one nobody checks:
|
|
283
|
+
*
|
|
284
|
+
* light --success-text #15803d 4.02:1 --warning-text #b45309 3.91:1
|
|
285
|
+
* --danger-text #dc2626 3.59:1 --accent-text #1d4ed8 5.12:1
|
|
286
|
+
* dark --success-text #16a34a 2.82:1 --warning-text #f59e0b 4.58:1
|
|
287
|
+
* --danger-text #f87171 3.86:1 --accent-text #60a5fa 4.00:1
|
|
288
|
+
*
|
|
289
|
+
* Six of those eight are under AA. So the soft tier carries its own ink, moved
|
|
290
|
+
* one step along the same hue until the WORST of the three backdrops clears
|
|
291
|
+
* 4.5:1. The floor it actually achieves is 5.67:1 light and 5.39:1 dark;
|
|
292
|
+
* `tests/statusSoftTier.test.ts` measures every pair on every backdrop and
|
|
293
|
+
* fails the build under 4.5, so the tier cannot drift back under.
|
|
294
|
+
*
|
|
295
|
+
* `draft` and `neutral` share the grey hue and differ by alpha, which is the
|
|
296
|
+
* distinction they have always had (`bg-gray-300` against `bg-gray-100`): a
|
|
297
|
+
* draft is a real state and reads slightly louder than a cancelled one.
|
|
298
|
+
*/
|
|
299
|
+
:root {
|
|
300
|
+
--status-success-soft: rgba(34, 197, 94, 0.16);
|
|
301
|
+
--status-active-soft: rgba(59, 130, 246, 0.16);
|
|
302
|
+
--status-queued-soft: rgba(99, 102, 241, 0.16);
|
|
303
|
+
--status-info-soft: rgba(14, 165, 233, 0.16);
|
|
304
|
+
--status-pending-soft: rgba(234, 179, 8, 0.16);
|
|
305
|
+
--status-warning-soft: rgba(249, 115, 22, 0.16);
|
|
306
|
+
--status-danger-soft: rgba(239, 68, 68, 0.16);
|
|
307
|
+
--status-draft-soft: rgba(107, 114, 128, 0.22);
|
|
308
|
+
--status-neutral-soft: rgba(107, 114, 128, 0.10);
|
|
309
|
+
|
|
310
|
+
/* Light ink: the -800 step of each family. The -700 steps are what the
|
|
311
|
+
`-text` roles use, and they are the ones measured above at 3.59–4.02. */
|
|
312
|
+
--status-success-soft-ink: #166534;
|
|
313
|
+
--status-active-soft-ink: #1e40af;
|
|
314
|
+
--status-queued-soft-ink: #3730a3;
|
|
315
|
+
--status-info-soft-ink: #075985;
|
|
316
|
+
--status-pending-soft-ink: #854d0e;
|
|
317
|
+
--status-warning-soft-ink: #9a3412;
|
|
318
|
+
--status-danger-soft-ink: #991b1b;
|
|
319
|
+
--status-draft-soft-ink: var(--ink-strong);
|
|
320
|
+
--status-neutral-soft-ink: var(--ink);
|
|
321
|
+
|
|
322
|
+
/* The loud register. A saturated -700 fill with white on it, literal in both
|
|
323
|
+
themes for the same reason `--danger` is: a status means the same thing in
|
|
324
|
+
every product, and a solid badge is read at a glance rather than against a
|
|
325
|
+
surface. Banner's `emphasis="solid"` is the same decision one size up. */
|
|
326
|
+
--status-success-solid: #15803d;
|
|
327
|
+
--status-active-solid: #1d4ed8;
|
|
328
|
+
--status-queued-solid: #4338ca;
|
|
329
|
+
--status-info-solid: #0369a1;
|
|
330
|
+
--status-pending-solid: #a16207;
|
|
331
|
+
--status-warning-solid: #c2410c;
|
|
332
|
+
--status-danger-solid: #b91c1c;
|
|
333
|
+
--status-draft-solid: #374151;
|
|
334
|
+
--status-neutral-solid: #4b5563;
|
|
335
|
+
/* 4.92:1 (pending) to 10.31:1 (draft) — the whole solid tier clears AA. */
|
|
336
|
+
--status-on-solid: #ffffff;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/* Only the ink moves. The washes above hold, because they are transparent. */
|
|
340
|
+
[data-theme="dark"] {
|
|
341
|
+
--status-success-soft-ink: #86efac;
|
|
342
|
+
--status-active-soft-ink: #93c5fd;
|
|
343
|
+
--status-queued-soft-ink: #a5b4fc;
|
|
344
|
+
--status-info-soft-ink: #7dd3fc;
|
|
345
|
+
--status-pending-soft-ink: #fde68a;
|
|
346
|
+
--status-warning-soft-ink: #fdba74;
|
|
347
|
+
--status-danger-soft-ink: #fca5a5;
|
|
348
|
+
--status-draft-soft-ink: var(--ink-strongest);
|
|
349
|
+
--status-neutral-soft-ink: var(--ink-strong);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/* ── Timeline accent tokens ─────────────────────────────────────────────────
|
|
353
|
+
* Every colour a `TimelineTrack` paints with, and the ink that reads on top of
|
|
354
|
+
* one. There is ONE of them.
|
|
355
|
+
*
|
|
356
|
+
* "The timeline's blue is not our theme blue; it must be the theme colour, and
|
|
357
|
+
* pure flat blue — no skeuomorphic shading — consistent with everything else"
|
|
358
|
+
* (Henry, 2026-09-14, translated from his Chinese ruling). The bar used to mix
|
|
359
|
+
* three blues that were all nearly right and none of them the theme's: the
|
|
360
|
+
* `blue-500` utilities on the fill and the accent marks, `--status-active-solid`
|
|
361
|
+
* (#1d4ed8, which no accent remap reaches) behind the kind tokens, and
|
|
362
|
+
* `--status-success-solid` behind a completion. The theme's blue is the 600
|
|
363
|
+
* step — what the primary `Button` wears, what a link wears, what an active tab
|
|
364
|
+
* underlines with — so that is what all of them are now.
|
|
365
|
+
*
|
|
366
|
+
* A completion no longer keeps a hue of its own either: the CHECK GLYPH is what
|
|
367
|
+
* says it finished, and shape survives a greyscale print and a colour-blind
|
|
368
|
+
* reader in a way a second hue never did. The same argument already retired the
|
|
369
|
+
* five bespoke kind hues (a violet shipment, an amber DFM, an orange
|
|
370
|
+
* inspection) — a diamond is goods moving, a flask is a test, a document is a
|
|
371
|
+
* drawing revision.
|
|
372
|
+
*
|
|
373
|
+
* THE VALUE, and why it is a chain rather than a token:
|
|
374
|
+
*
|
|
375
|
+
* var(--accent-600, var(--color-blue-600, #2563eb))
|
|
376
|
+
*
|
|
377
|
+
* --accent-600 what `useTheme` stamps inline on <html> when the user
|
|
378
|
+
* picks a custom accent, and what `themes.css` remaps the
|
|
379
|
+
* `blue-600` utilities to under `[data-custom-accent]`. A
|
|
380
|
+
* mark and a primary Button therefore move together.
|
|
381
|
+
* --color-blue-600 Tailwind v4's own theme variable, which is LITERALLY what
|
|
382
|
+
* `.bg-blue-600` resolves to — so with no custom accent the
|
|
383
|
+
* two are the same computed value, not two values that look
|
|
384
|
+
* alike. It is not #2563eb: v4 states the palette in OKLCH
|
|
385
|
+
* and `blue-600` renders rgb(21 93 252).
|
|
386
|
+
* #2563eb the last resort — Tailwind v3's blue-600, and the value
|
|
387
|
+
* `brand.css` gives `--accent-600` — for a consumer that
|
|
388
|
+
* ships neither of the above.
|
|
389
|
+
*
|
|
390
|
+
* `ui.css` cannot declare `--accent-600` itself: a portal takes this file (or
|
|
391
|
+
* `styles.css`) WITHOUT `brand.css`, and declaring a default here would shadow
|
|
392
|
+
* nothing but would also stop `brand.css` from ever being the answer. The
|
|
393
|
+
* fallback chain is the portable form.
|
|
394
|
+
*
|
|
395
|
+
* NO DARK STEP. `.bg-blue-600` and `.border-blue-600` are not remapped for dark
|
|
396
|
+
* — a primary Button is the same fill on a dark page — so neither is the bar.
|
|
397
|
+
* The 600 step measures 3.40:1 on the dark `--surface-sunken` and 4.95:1 on the
|
|
398
|
+
* light one, clearing WCAG 1.4.11 (these are graphics, not text) in both, and
|
|
399
|
+
* `--tl-on-kind` is white ink on it at 5.17:1 either way.
|
|
400
|
+
*
|
|
401
|
+
* The one thing that DOES move in dark is ink the kit paints through
|
|
402
|
+
* `.text-blue-600` — the thumb, a cluster pill, the links — because blue-600 as
|
|
403
|
+
* TEXT on a dark surface is 3.17:1 and fails AA. `ui.css` already remaps that
|
|
404
|
+
* class to #93c5fd for dark, and the timeline inherits it rather than inventing
|
|
405
|
+
* a step of its own. */
|
|
406
|
+
:root {
|
|
407
|
+
/* The one colour. Everything below is an alias, kept as separate names
|
|
408
|
+
because a consumer retuning ONE kind is a conversation worth having in a
|
|
409
|
+
stylesheet rather than a fork of the component. */
|
|
410
|
+
--tl-accent: var(--accent-600, var(--color-blue-600, #2563eb));
|
|
411
|
+
--tl-dfm: var(--tl-accent);
|
|
412
|
+
--tl-shipment: var(--tl-accent);
|
|
413
|
+
--tl-testing: var(--tl-accent);
|
|
414
|
+
--tl-completion: var(--tl-accent);
|
|
415
|
+
--tl-inspection: var(--tl-accent);
|
|
416
|
+
--tl-invoice: var(--tl-accent);
|
|
417
|
+
/* The ink on a filled mark. `--on-accent` is white in both themes by
|
|
418
|
+
`brand.css`'s own contract — the accent fill does not move, so neither does
|
|
419
|
+
what reads on it. */
|
|
420
|
+
--tl-on-kind: var(--on-accent, #ffffff);
|
|
421
|
+
/* The focus ring, and the colour a hot label's name and date take. */
|
|
422
|
+
--tl-focus: var(--tl-accent);
|
|
423
|
+
/* The soft wash behind the current mark, a hovered dot and the mark a thumb
|
|
424
|
+
has just crossed. A box-shadow cannot be a utility class, so the accent is
|
|
425
|
+
mixed down here instead — 16% is the alpha the kit's own `-soft` status
|
|
426
|
+
washes carry, and `color-mix` is the only form of it that can follow a
|
|
427
|
+
custom accent, since `rgba()` cannot take a variable for its channels. */
|
|
428
|
+
--tl-soft: color-mix(in srgb, var(--tl-accent) 16%, transparent);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/* ── Budget overrun hatch ───────────────────────────────────────────────────
|
|
432
|
+
* `BudgetBar`'s third drawing. A run past its wall clock must not be pictured
|
|
433
|
+
* as a full bar: a full solid bar is the calm "finished" shape, and this is the
|
|
434
|
+
* opposite of that. The overrun segment is therefore HATCHED rather than
|
|
435
|
+
* filled — a texture no in-budget state ever wears, so the difference survives
|
|
436
|
+
* a glance, a greyscale screenshot and a colour-blind reader alike.
|
|
437
|
+
*
|
|
438
|
+
* A gradient rather than an image: no asset, no request, and it reads the
|
|
439
|
+
* danger tokens so it follows the theme. `background-color` under it keeps the
|
|
440
|
+
* band visible when a print stylesheet drops background images.
|
|
441
|
+
*/
|
|
442
|
+
.rosh-budget-overrun {
|
|
443
|
+
background-color: var(--status-danger-soft);
|
|
444
|
+
background-image: repeating-linear-gradient(
|
|
445
|
+
135deg,
|
|
446
|
+
var(--status-danger-solid) 0,
|
|
447
|
+
var(--status-danger-solid) 2px,
|
|
448
|
+
transparent 2px,
|
|
449
|
+
transparent 5px
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/* ── Chart motion ───────────────────────────────────────────────────────────
|
|
454
|
+
* Every animation the chart family uses, as classes, so no chart implements
|
|
455
|
+
* motion itself and none of them can forget the reduced-motion rule below.
|
|
456
|
+
*
|
|
457
|
+
* The intro reveal is a WIPE: a white rect inside an SVG <mask>, scaled from
|
|
458
|
+
* nothing to full. Everything under the mask — fill, stroke and point markers
|
|
459
|
+
* alike — appears as it passes, which is what stops a chart assembling in
|
|
460
|
+
* pieces the way a stroke-dashoffset reveal does (that one can only animate
|
|
461
|
+
* strokes, so the fill and the dots arrive on their own schedule).
|
|
462
|
+
*
|
|
463
|
+
* The mask uses objectBoundingBox units, so the rect is 0–1 and one keyframe
|
|
464
|
+
* set works at every chart size.
|
|
465
|
+
*
|
|
466
|
+
* `fill-mode: both` matters on every rule: with a stagger delay the mark would
|
|
467
|
+
* otherwise render at its FINAL state, jump back to the start when its delay
|
|
468
|
+
* elapsed, and animate from there. */
|
|
469
|
+
@keyframes rosh-viz-wipe { from { transform: scaleX(0); } to { transform: scaleX(1); } }
|
|
470
|
+
@keyframes rosh-viz-rise { from { transform: scaleY(0); } to { transform: scaleY(1); } }
|
|
471
|
+
@keyframes rosh-viz-grow { from { transform: scaleX(0); } to { transform: scaleX(1); } }
|
|
472
|
+
@keyframes rosh-viz-sweep { from { opacity: 0; transform: rotate(-14deg) scale(0.94); } to { opacity: 1; transform: none; } }
|
|
473
|
+
@keyframes rosh-viz-fade { from { opacity: 0; } to { opacity: 1; } }
|
|
474
|
+
@keyframes rosh-viz-march { to { stroke-dashoffset: -24; } }
|
|
475
|
+
/* The loading sweep. Translated by twice its own width so the band clears the
|
|
476
|
+
plot at both ends rather than popping at the edges. */
|
|
477
|
+
@keyframes rosh-viz-shimmer { from { transform: translateX(0); } to { transform: translateX(200%); } }
|
|
478
|
+
/* The live-reading ping. Fades as it grows, so the halo is gone before it is
|
|
479
|
+
large enough to reach the neighbouring point. */
|
|
480
|
+
@keyframes rosh-viz-ping { from { transform: scale(1); opacity: 0.45; } to { transform: scale(3.2); opacity: 0; } }
|
|
481
|
+
|
|
482
|
+
.rosh-viz-wipe,
|
|
483
|
+
.rosh-viz-wipe-rtl,
|
|
484
|
+
.rosh-viz-wipe-center {
|
|
485
|
+
transform-box: fill-box;
|
|
486
|
+
animation: rosh-viz-wipe 900ms cubic-bezier(0.22, 1, 0.36, 1) both;
|
|
487
|
+
}
|
|
488
|
+
.rosh-viz-wipe { transform-origin: left; }
|
|
489
|
+
.rosh-viz-wipe-rtl { transform-origin: right; }
|
|
490
|
+
.rosh-viz-wipe-center { transform-origin: center; }
|
|
491
|
+
|
|
492
|
+
/* Grow from the baseline, not the centre — a bar that expands both ways reads
|
|
493
|
+
as a range rather than as a quantity. `fill-box` puts the origin on the rect
|
|
494
|
+
itself rather than on the viewport. */
|
|
495
|
+
.rosh-viz-rise { transform-box: fill-box; transform-origin: bottom; animation: rosh-viz-rise 640ms cubic-bezier(0.22, 1, 0.36, 1) both; }
|
|
496
|
+
/* A horizontal bar grows from its label edge, which is where the eye starts. */
|
|
497
|
+
.rosh-viz-grow { transform-origin: left; animation: rosh-viz-grow 620ms cubic-bezier(0.22, 1, 0.36, 1) both; }
|
|
498
|
+
.rosh-viz-sweep { transform-box: fill-box; transform-origin: center; animation: rosh-viz-sweep 620ms cubic-bezier(0.22, 1, 0.36, 1) both; }
|
|
499
|
+
.rosh-viz-fade { animation: rosh-viz-fade 700ms ease-out both; }
|
|
500
|
+
.rosh-viz-march { animation: rosh-viz-march 1s linear infinite; }
|
|
501
|
+
.rosh-viz-shimmer { animation: rosh-viz-shimmer 1.8s ease-in-out infinite; }
|
|
502
|
+
.rosh-viz-ping { transform-box: fill-box; transform-origin: center; animation: rosh-viz-ping 1.6s cubic-bezier(0, 0, 0.2, 1) infinite; }
|
|
503
|
+
|
|
504
|
+
/* Hover emphasis is a TRANSITION, not a jump. A mark that snaps to grey the
|
|
505
|
+
instant the pointer crosses a band reads as a glitch; 160ms is long enough to
|
|
506
|
+
register as a response and short enough not to lag the pointer. Transitioning
|
|
507
|
+
`filter` too is what makes the glow arrive with the colour rather than after
|
|
508
|
+
it. */
|
|
509
|
+
.rosh-viz-mark { transition: fill 160ms ease-out, fill-opacity 160ms ease-out, opacity 160ms ease-out, filter 200ms ease-out; }
|
|
510
|
+
|
|
511
|
+
/* One rule covers the whole family, present and future. A chart cannot opt out
|
|
512
|
+
of this, because no chart wrote the animation in the first place. */
|
|
513
|
+
@media (prefers-reduced-motion: reduce) {
|
|
514
|
+
.rosh-viz-mark { transition: none !important; }
|
|
515
|
+
.rosh-viz-wipe, .rosh-viz-wipe-rtl, .rosh-viz-wipe-center,
|
|
516
|
+
.rosh-viz-rise, .rosh-viz-grow, .rosh-viz-sweep, .rosh-viz-fade,
|
|
517
|
+
.rosh-viz-march {
|
|
518
|
+
animation: none !important;
|
|
519
|
+
opacity: 1 !important;
|
|
520
|
+
transform: none !important;
|
|
521
|
+
}
|
|
522
|
+
/* These two REST invisible — the shimmer exits at translateX(200%) and the
|
|
523
|
+
ping fades to nothing — so the entrance treatment above would park the
|
|
524
|
+
shimmer band fully lit over the skeleton and pin the ping halo at an
|
|
525
|
+
opacity it never reaches while animating. They stop by disappearing. */
|
|
526
|
+
.rosh-viz-shimmer, .rosh-viz-ping {
|
|
527
|
+
animation: none !important;
|
|
528
|
+
opacity: 0 !important;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/* ── Timeline track ─────────────────────────────────────────────────────────
|
|
533
|
+
* `TimelineTrack`, and through it `MilestoneTimeline` and `ProductionTimeline`.
|
|
534
|
+
*
|
|
535
|
+
* Written as classes here rather than as Tailwind utilities in the component
|
|
536
|
+
* for the same reason the chart family is: the whole card is drawn from ONE
|
|
537
|
+
* geometry object, and a dozen absolutely-positioned bands that have to agree
|
|
538
|
+
* with each other are clearer as named parts than as class strings. It is also
|
|
539
|
+
* what makes the motion impossible to get wrong — no component writes an
|
|
540
|
+
* animation, so none of them can forget the reduced-motion rule at the bottom.
|
|
541
|
+
*
|
|
542
|
+
* Colour comes from the neutral ramp and the `--tl-*` accent tokens, so both
|
|
543
|
+
* themes are covered without a single `[data-theme="dark"]` rule. The two
|
|
544
|
+
* exceptions are deliberate and live in the component: `bg-gray-200` for the
|
|
545
|
+
* rail and `bg-blue-600` for the fill, because those two must follow the dark
|
|
546
|
+
* remap and the user's accent respectively.
|
|
547
|
+
*
|
|
548
|
+
* FLAT, and kept that way by `tests/timelineTokens.test.ts`: no gradient and no
|
|
549
|
+
* drop shadow anywhere in this block. The fill carried a white gloss along its
|
|
550
|
+
* top edge and three elements carried a soft drop shadow (the reveal chip, the
|
|
551
|
+
* bubble and the popover, the scrubber thumb); all of them are gone, and the
|
|
552
|
+
* border or the flat ring that was underneath each one does the separating on
|
|
553
|
+
* its own. The `0 0 0 Npx` box-shadows that remain are RINGS — a halo in the
|
|
554
|
+
* card's own ground, a focus ring, the soft accent wash — which is a flat
|
|
555
|
+
* colour at a radius, not a shadow. Nothing else in the kit wears a gradient
|
|
556
|
+
* fill, and `StatusBadge` wears no shadow at all. */
|
|
557
|
+
|
|
558
|
+
/* The card chrome both timelines wear. Border WIDTH and STYLE only — the colour
|
|
559
|
+
comes from the utility class on the element, which carries the dark remap. */
|
|
560
|
+
.rosh-tl-card {
|
|
561
|
+
border-width: 1px; border-style: solid; border-radius: 12px; padding: 14px 16px 16px;
|
|
562
|
+
}
|
|
563
|
+
.rosh-tl-head { display: flex; align-items: flex-start; gap: 10px; margin-bottom: 14px; }
|
|
564
|
+
/* The sentence: icon, title, facts. It wraps inside itself and may shrink, so
|
|
565
|
+
the controls beside it never get pushed onto a row of their own. */
|
|
566
|
+
.rosh-tl-head-main {
|
|
567
|
+
display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
|
|
568
|
+
flex: 1 1 auto; min-width: 0;
|
|
569
|
+
}
|
|
570
|
+
.rosh-tl-ico { flex: none; width: 15px; height: 15px; align-self: center; margin-right: -3px; }
|
|
571
|
+
.rosh-tl-ico svg { width: 15px; height: 15px; }
|
|
572
|
+
.rosh-tl-title { margin: 0; font-size: 13px; font-weight: 600; letter-spacing: -0.005em; }
|
|
573
|
+
/* `min-width: 0` is what lets a long meta line WRAP rather than push the
|
|
574
|
+
actions onto a row of their own: a flex item's default minimum is its
|
|
575
|
+
content, and five facts about a production order are wider than a card. */
|
|
576
|
+
.rosh-tl-meta { margin: 0; font-size: 12px; font-variant-numeric: tabular-nums; min-width: 0; }
|
|
577
|
+
.rosh-tl-meta b { font-weight: 500; }
|
|
578
|
+
.rosh-tl-head-actions {
|
|
579
|
+
display: inline-flex; align-items: center; gap: 10px; margin-left: auto; flex: none;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/* The play control: a pill, not an icon button — a 20px circle beside an
|
|
583
|
+
uppercase heading is what the two cards used to disagree about. */
|
|
584
|
+
.rosh-tl-play {
|
|
585
|
+
display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 12px 0 10px;
|
|
586
|
+
border-radius: 14px; cursor: pointer; white-space: nowrap;
|
|
587
|
+
font: inherit; font-size: 11.5px; font-weight: 500;
|
|
588
|
+
background: var(--surface-raised); border-width: 1px; border-style: solid;
|
|
589
|
+
transition: color 160ms ease-out, border-color 160ms ease-out;
|
|
590
|
+
}
|
|
591
|
+
.rosh-tl-play svg { width: 9px; height: 9px; fill: currentColor; }
|
|
592
|
+
|
|
593
|
+
.rosh-tl-foot {
|
|
594
|
+
display: flex; align-items: center; gap: 14px 20px; flex-wrap: wrap;
|
|
595
|
+
margin-top: 14px; padding-top: 12px;
|
|
596
|
+
border-top-width: 1px; border-top-style: solid;
|
|
597
|
+
}
|
|
598
|
+
.rosh-tl-status { margin: 0; font-size: 12px; font-variant-numeric: tabular-nums; }
|
|
599
|
+
.rosh-tl-legend { display: flex; gap: 6px; flex-wrap: wrap; margin-left: auto; }
|
|
600
|
+
.rosh-tl-legend > span {
|
|
601
|
+
display: inline-flex; align-items: center; gap: 6px; height: 22px; padding: 0 9px 0 7px;
|
|
602
|
+
border-width: 1px; border-style: solid; border-radius: 11px;
|
|
603
|
+
font-size: 10.5px; background: var(--surface-raised);
|
|
604
|
+
}
|
|
605
|
+
/* The legend's glyphs are the track's glyphs at legend size, so a chip can
|
|
606
|
+
never describe a shape the rail no longer draws. */
|
|
607
|
+
.rosh-tl-glyph {
|
|
608
|
+
width: 10px; height: 10px; flex: none; box-sizing: border-box;
|
|
609
|
+
display: flex; align-items: center; justify-content: center;
|
|
610
|
+
}
|
|
611
|
+
.rosh-tl-glyph.is-ring { border-radius: 50%; border-width: 2px; border-style: solid; }
|
|
612
|
+
.rosh-tl-glyph.is-diamond { border-radius: 2px; transform: rotate(45deg) scale(0.82); }
|
|
613
|
+
.rosh-tl-glyph.is-disc { border-radius: 50%; }
|
|
614
|
+
.rosh-tl-glyph svg { width: 7px; height: 7px; }
|
|
615
|
+
/* Upright and smaller inside a chip's diamond, for the reason the rail's
|
|
616
|
+
diamond gives — at legend size there is even less room in the corners. */
|
|
617
|
+
.rosh-tl-glyph.is-diamond svg { width: 6px; height: 6px; transform: rotate(-45deg); }
|
|
618
|
+
|
|
619
|
+
.rosh-tl-body { display: grid; grid-template-columns: minmax(0, 1fr) 156px; gap: 20px; }
|
|
620
|
+
.rosh-tl-body.is-solo { grid-template-columns: minmax(0, 1fr); }
|
|
621
|
+
/* The caption on an end of the axis — "Start · 20/01/2026". Inside the stage
|
|
622
|
+
rather than flanking it, so it sits over the coordinate it names instead of
|
|
623
|
+
beside a bar whose left edge it could only point at. Mono, like every other
|
|
624
|
+
date the card draws. */
|
|
625
|
+
.rosh-tl-edge {
|
|
626
|
+
position: absolute; white-space: nowrap;
|
|
627
|
+
font-family: var(--font-mono); font-variant-numeric: tabular-nums;
|
|
628
|
+
font-size: 10px; line-height: 1.3; color: var(--ink-muted);
|
|
629
|
+
}
|
|
630
|
+
.rosh-tl-edge.is-end { transform: translateX(-100%); }
|
|
631
|
+
|
|
632
|
+
/* A 10px side inset, so a dot on the first or last day is never clipped. */
|
|
633
|
+
.rosh-tl-stage { position: relative; padding: 0 10px; flex: 1 1 auto; min-width: 0; }
|
|
634
|
+
.rosh-tl-layer { position: absolute; left: 10px; right: 10px; top: 0; bottom: 0; }
|
|
635
|
+
|
|
636
|
+
/* The off-layout copy of every label, measured so the packer has real widths. */
|
|
637
|
+
.rosh-tl-measure {
|
|
638
|
+
position: absolute; left: 0; top: 0; display: flex; gap: 8px;
|
|
639
|
+
visibility: hidden; pointer-events: none;
|
|
640
|
+
}
|
|
641
|
+
.rosh-tl-measure-box { flex: none; display: flex; flex-direction: column; white-space: nowrap; }
|
|
642
|
+
|
|
643
|
+
.rosh-tl-rail { position: absolute; left: 0; right: 0; border-radius: 999px; }
|
|
644
|
+
.rosh-tl-fill {
|
|
645
|
+
position: absolute; left: 0; border-radius: 999px;
|
|
646
|
+
transform-origin: left center;
|
|
647
|
+
}
|
|
648
|
+
/* The notch in the rail where the axis stops keeping time. Painted in the
|
|
649
|
+
card's own ground, so it reads as a cut rather than as a mark. */
|
|
650
|
+
.rosh-tl-cut {
|
|
651
|
+
position: absolute; width: 7px; transform: translateX(-50%);
|
|
652
|
+
background: var(--surface-sunken);
|
|
653
|
+
}
|
|
654
|
+
.rosh-tl-cut i, .rosh-tl-cutglyph i {
|
|
655
|
+
position: absolute; top: 0; width: 1px; height: 100%;
|
|
656
|
+
background: var(--line-strong); transform: skewX(-28deg);
|
|
657
|
+
}
|
|
658
|
+
.rosh-tl-cut i:first-child { left: 2px; }
|
|
659
|
+
.rosh-tl-cut i:last-child { left: 5px; }
|
|
660
|
+
|
|
661
|
+
.rosh-tl-tick { position: absolute; width: 1px; background: var(--line-strong); opacity: 0.85; }
|
|
662
|
+
.rosh-tl-tick.is-week { opacity: 0.5; }
|
|
663
|
+
.rosh-tl-month, .rosh-tl-rulercut span {
|
|
664
|
+
font-family: var(--font-mono); font-variant-numeric: tabular-nums;
|
|
665
|
+
font-size: 10px; line-height: 1.1; color: var(--ink-faint);
|
|
666
|
+
}
|
|
667
|
+
.rosh-tl-month { position: absolute; white-space: nowrap; }
|
|
668
|
+
.rosh-tl-rulercut {
|
|
669
|
+
position: absolute; display: flex; flex-direction: column; align-items: center; gap: 3px;
|
|
670
|
+
transform: translateX(-50%); white-space: nowrap;
|
|
671
|
+
}
|
|
672
|
+
.rosh-tl-cutglyph { position: relative; width: 7px; height: 7px; }
|
|
673
|
+
.rosh-tl-cutglyph i:first-child { left: 1px; }
|
|
674
|
+
.rosh-tl-cutglyph i:last-child { left: 4px; }
|
|
675
|
+
|
|
676
|
+
.rosh-tl-todayline {
|
|
677
|
+
position: absolute; width: 0; border-left: 1px dashed var(--line-strong);
|
|
678
|
+
transform: translateX(-0.5px);
|
|
679
|
+
}
|
|
680
|
+
.rosh-tl-todaytag {
|
|
681
|
+
position: absolute; white-space: nowrap; border-radius: 5px; padding: 1px 6px;
|
|
682
|
+
font-size: 10px; font-weight: 500; letter-spacing: 0.02em;
|
|
683
|
+
color: var(--ink-muted); background: var(--surface-raised);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
/* The list is the accessible reading of the card; the dots are absolutely
|
|
687
|
+
positioned against the layer, so the <li> must not establish a containing
|
|
688
|
+
block of its own. */
|
|
689
|
+
.rosh-tl-nodes { list-style: none; margin: 0; padding: 0; }
|
|
690
|
+
.rosh-tl-nodes > li { position: static; }
|
|
691
|
+
.rosh-tl-node {
|
|
692
|
+
position: absolute; padding: 0; border: 0; background: none; cursor: pointer;
|
|
693
|
+
display: flex; align-items: center; justify-content: center; border-radius: 50%;
|
|
694
|
+
transform: var(--rosh-tl-shift, translate(-50%, -50%))
|
|
695
|
+
rotate(var(--rosh-tl-rot, 0deg)) scale(var(--rosh-tl-sc, 1));
|
|
696
|
+
}
|
|
697
|
+
.rosh-tl-node svg { width: 10px; height: 10px; }
|
|
698
|
+
/* The halo is the CARD's ground, not white: a pure-white ring is a defect on a
|
|
699
|
+
dark card, and this one is right in both themes without a remap. */
|
|
700
|
+
.rosh-tl-node.is-dot {
|
|
701
|
+
width: 10px; height: 10px; background: var(--surface); border-width: 2px; border-style: solid;
|
|
702
|
+
box-shadow: 0 0 0 2px var(--surface-sunken);
|
|
703
|
+
}
|
|
704
|
+
.rosh-tl-node.is-key { width: 16px; height: 16px; box-shadow: 0 0 0 2px var(--surface-sunken); }
|
|
705
|
+
/* "You are here" wears a soft halo as well as the ground ring — it is the one
|
|
706
|
+
mark a reader looks for before reading anything. */
|
|
707
|
+
.rosh-tl-node.is-current {
|
|
708
|
+
width: 14px; height: 14px;
|
|
709
|
+
box-shadow: 0 0 0 2px var(--surface-sunken), 0 0 0 6px var(--tl-soft);
|
|
710
|
+
}
|
|
711
|
+
.rosh-tl-node.is-current svg { width: 9px; height: 9px; }
|
|
712
|
+
.rosh-tl-node.is-diamond { border-radius: 3px; width: 14px; height: 14px; --rosh-tl-rot: 45deg; }
|
|
713
|
+
/* A glyph inside a diamond is turned back upright, and drawn a pixel smaller
|
|
714
|
+
than one in a disc: the square a 14px diamond inscribes is under 10px across,
|
|
715
|
+
so the 10px a disc gives its glyph would touch the rotated corners. */
|
|
716
|
+
.rosh-tl-node.is-diamond svg { width: 9px; height: 9px; transform: rotate(-45deg); }
|
|
717
|
+
.rosh-tl-node.is-ghost {
|
|
718
|
+
width: 10px; height: 10px; background: var(--surface);
|
|
719
|
+
border: 1.5px dashed var(--line-strong); box-shadow: none; cursor: default;
|
|
720
|
+
}
|
|
721
|
+
/* A provisional mark: the kind's shape and glyph drawn hollow, on the best date
|
|
722
|
+
the record holds for something that has not happened — a sample ORDERED,
|
|
723
|
+
where a shipment will be. The outline says what it will be; the empty middle
|
|
724
|
+
says it is not that yet. The inline style sets the border colour. */
|
|
725
|
+
.rosh-tl-node.is-provisional {
|
|
726
|
+
background: var(--surface); border-width: 2px; border-style: solid;
|
|
727
|
+
}
|
|
728
|
+
/* Lit by the consumer — a stepper step hovered above the bar names the marks
|
|
729
|
+
it covers. The same soft wash the current mark wears, at the same radius, so
|
|
730
|
+
"these" and "here" are one vocabulary. */
|
|
731
|
+
.rosh-tl-node.is-lit { box-shadow: 0 0 0 2px var(--surface-sunken), 0 0 0 5px var(--tl-soft); }
|
|
732
|
+
/* The band under the marks a consumer lit: one soft stretch of the accent, the
|
|
733
|
+
rail's own height with room around it, rounded at both ends. */
|
|
734
|
+
.rosh-tl-band { position: absolute; border-radius: 999px; background: var(--tl-soft); pointer-events: none; }
|
|
735
|
+
|
|
736
|
+
/* ── The bare rail (`labels="none"`) ──────────────────────────────────────
|
|
737
|
+
No label lanes. The edge captions flank the rail on its own row, the only
|
|
738
|
+
text over the rail is a mark's caption, and the undated things are a dashed
|
|
739
|
+
cap at the end of a dashed tail. */
|
|
740
|
+
.rosh-tl-flank {
|
|
741
|
+
flex: none; white-space: nowrap; font-size: 11px; font-weight: 500; line-height: 1;
|
|
742
|
+
color: var(--ink-muted);
|
|
743
|
+
}
|
|
744
|
+
/* The caption over a mark — `v3` over a revision. Mono, like every other
|
|
745
|
+
small text the rail draws, and never a pointer target: the mark under it is. */
|
|
746
|
+
.rosh-tl-caption {
|
|
747
|
+
position: absolute; transform: translateX(-50%); white-space: nowrap; pointer-events: none;
|
|
748
|
+
font-family: var(--font-mono); font-variant-numeric: tabular-nums;
|
|
749
|
+
font-size: 10px; line-height: 1.1; color: var(--ink-muted);
|
|
750
|
+
}
|
|
751
|
+
/* The tail: from the end of the axis to the cap. Dashed, because it is not
|
|
752
|
+
time — it is the distance to a date nobody has. */
|
|
753
|
+
.rosh-tl-tail { position: absolute; height: 0; border-top: 2px dashed var(--line-strong); }
|
|
754
|
+
/* The row under a bare production rail: Play, the return button when the thumb
|
|
755
|
+
has been moved, and the status line — one row, because the rail has no
|
|
756
|
+
header corner to put a control in. */
|
|
757
|
+
.rosh-tl-railrow { display: flex; align-items: center; gap: 12px; margin-top: 4px; flex-wrap: wrap; }
|
|
758
|
+
.rosh-tl-railrow .rosh-tl-status { margin: 0; }
|
|
759
|
+
/* The cap: the start anchor's own drawing, at the other end and for the same
|
|
760
|
+
reason, but a real button — its popover is where the undated things are
|
|
761
|
+
named. `.is-lit` still reaches it, so a consumer can light "the end". */
|
|
762
|
+
.rosh-tl-node.is-cap {
|
|
763
|
+
width: 14px; height: 14px; box-sizing: border-box; cursor: default;
|
|
764
|
+
background: var(--surface); border: 2px dashed var(--line-strong);
|
|
765
|
+
box-shadow: 0 0 0 2px var(--surface-sunken);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/* The start anchor. Hollow and dashed, because it is not an event — nothing was
|
|
769
|
+
filed on the day the axis opens, and a solid disc there would claim there
|
|
770
|
+
was. Decoration only: it takes no pointer, so the scrubber's hit strip runs
|
|
771
|
+
straight over it. */
|
|
772
|
+
.rosh-tl-start {
|
|
773
|
+
position: absolute; box-sizing: border-box; pointer-events: none;
|
|
774
|
+
width: 12px; height: 12px; border-radius: 50%;
|
|
775
|
+
transform: translate(-50%, -50%);
|
|
776
|
+
background: var(--surface); border: 1.5px dashed var(--line-strong);
|
|
777
|
+
box-shadow: 0 0 0 2px var(--surface-sunken);
|
|
778
|
+
}
|
|
779
|
+
/* A dated mark landed on the start day. The ring opens out to encircle it
|
|
780
|
+
rather than sitting under it — wider than the 16px key node, and transparent,
|
|
781
|
+
so both are legible and neither has moved off its date. */
|
|
782
|
+
.rosh-tl-start.is-around {
|
|
783
|
+
width: 26px; height: 26px; background: none; box-shadow: none;
|
|
784
|
+
}
|
|
785
|
+
/* A fold: one node standing for a run of marks too close to be drawn apart.
|
|
786
|
+
Same kind throughout, and it is that kind's own shape and glyph with the count
|
|
787
|
+
beside it — the reader should not have to learn a second vocabulary to be told
|
|
788
|
+
there are two shipments rather than one. */
|
|
789
|
+
.rosh-tl-count {
|
|
790
|
+
position: absolute; transform: translate(9px, -14px);
|
|
791
|
+
font-family: var(--font-mono); font-variant-numeric: tabular-nums;
|
|
792
|
+
font-size: 9px; font-weight: 600; line-height: 1; letter-spacing: -0.02em;
|
|
793
|
+
color: var(--tl-accent); pointer-events: none;
|
|
794
|
+
padding: 1px 2px; border-radius: 4px; background: var(--surface-sunken);
|
|
795
|
+
}
|
|
796
|
+
/* Mixed kinds have no glyph that is true of all of them, so the fold is a plain
|
|
797
|
+
`×N` pill instead — wider than a dot, and the only mark on the rail that is
|
|
798
|
+
not a shape the legend explains. */
|
|
799
|
+
.rosh-tl-node.is-fold.is-mixed {
|
|
800
|
+
width: auto; min-width: 22px; height: 16px; padding: 0 5px; border-radius: 8px;
|
|
801
|
+
}
|
|
802
|
+
.rosh-tl-foldnum {
|
|
803
|
+
font-family: var(--font-mono); font-variant-numeric: tabular-nums;
|
|
804
|
+
font-size: 9px; font-weight: 600; line-height: 1; letter-spacing: -0.02em;
|
|
805
|
+
}
|
|
806
|
+
.rosh-tl-node.is-active { box-shadow: 0 0 0 2px var(--surface-sunken), 0 0 0 5px var(--line); }
|
|
807
|
+
/* The flash as the scrubber's thumb crosses a mark. It rides the same 160ms
|
|
808
|
+
transition as the hover, so a fast drag pulses rather than strobes. */
|
|
809
|
+
.rosh-tl-node.is-hit {
|
|
810
|
+
--rosh-tl-sc: 1.5;
|
|
811
|
+
box-shadow: 0 0 0 2px var(--surface-sunken), 0 0 0 5px var(--tl-soft);
|
|
812
|
+
}
|
|
813
|
+
.rosh-tl-node:focus-visible, .rosh-tl-pill:focus-visible, .rosh-tl-thumb:focus-visible, .rosh-tl-more:focus-visible {
|
|
814
|
+
outline: 2px solid var(--tl-focus); outline-offset: 3px;
|
|
815
|
+
}
|
|
816
|
+
.rosh-tl-pulse {
|
|
817
|
+
position: absolute; inset: -1px; border-radius: inherit; pointer-events: none; opacity: 0;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
.rosh-tl-label { position: absolute; display: flex; flex-direction: column; white-space: nowrap; }
|
|
821
|
+
.rosh-tl-label.is-up { justify-content: flex-end; }
|
|
822
|
+
/* The reveal for a label that fits no lane: a chip of its own, so it reads over
|
|
823
|
+
whichever lane label it lands on. */
|
|
824
|
+
.rosh-tl-label.is-reveal {
|
|
825
|
+
z-index: 4; padding: 2px 5px; border-radius: 7px;
|
|
826
|
+
background: var(--surface-raised); border: 1px solid var(--line);
|
|
827
|
+
}
|
|
828
|
+
.rosh-tl-name {
|
|
829
|
+
font-size: 11px; font-weight: 500; line-height: 1.25; color: var(--ink-strong);
|
|
830
|
+
transition: color 160ms cubic-bezier(0, 0, 0.2, 1);
|
|
831
|
+
}
|
|
832
|
+
.rosh-tl-date, .rosh-tl-detail {
|
|
833
|
+
font-family: var(--font-mono); font-variant-numeric: tabular-nums;
|
|
834
|
+
font-size: 10px; line-height: 1.3; color: var(--ink-faint);
|
|
835
|
+
}
|
|
836
|
+
.rosh-tl-detail { font-family: inherit; color: var(--ink-muted); margin-top: 2px; }
|
|
837
|
+
.rosh-tl-label.is-current .rosh-tl-name { font-weight: 600; }
|
|
838
|
+
/* The label of whatever the pointer or the keyboard is on. Colour AND weight,
|
|
839
|
+
because on a card of eleven 11px labels colour alone is not a pointer. */
|
|
840
|
+
.rosh-tl-label.is-hot .rosh-tl-name { color: var(--tl-focus); font-weight: 600; }
|
|
841
|
+
.rosh-tl-label.is-hot .rosh-tl-date { color: var(--tl-focus); }
|
|
842
|
+
/* A phase bracket: the three-sided box under the rail that says "these ran in
|
|
843
|
+
parallel". Border WIDTH and STYLE only — the colour comes from the utility
|
|
844
|
+
class on the element, which carries the dark remap, and a `border:` shorthand
|
|
845
|
+
here would reset it to currentColor and win on being later in the cascade. */
|
|
846
|
+
.rosh-tl-phase {
|
|
847
|
+
position: absolute; height: 8px; pointer-events: none;
|
|
848
|
+
border-style: solid; border-width: 0 1px 1px 1px;
|
|
849
|
+
border-bottom-left-radius: 6px; border-bottom-right-radius: 6px;
|
|
850
|
+
}
|
|
851
|
+
.rosh-tl-phase-label {
|
|
852
|
+
position: absolute; transform: translateX(-50%); white-space: nowrap;
|
|
853
|
+
font-size: 9px; line-height: 1.2; pointer-events: none;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
.rosh-tl-lead, .rosh-tl-cspan {
|
|
857
|
+
position: absolute; height: 1px; background: var(--line-strong); opacity: 0.8;
|
|
858
|
+
}
|
|
859
|
+
.rosh-tl-cspan { opacity: 0.7; }
|
|
860
|
+
|
|
861
|
+
.rosh-tl-pill {
|
|
862
|
+
position: absolute; display: inline-flex; align-items: center; gap: 4px;
|
|
863
|
+
height: 19px; padding: 0 7px; border-radius: 10px; cursor: pointer; white-space: nowrap;
|
|
864
|
+
font: inherit; font-size: 10px; font-weight: 500;
|
|
865
|
+
color: var(--ink); background: var(--surface); border: 1px solid currentColor;
|
|
866
|
+
transition: box-shadow 160ms cubic-bezier(0, 0, 0.2, 1);
|
|
867
|
+
}
|
|
868
|
+
.rosh-tl-pill i { width: 5px; height: 5px; border-radius: 50%; background: currentColor; flex: none; }
|
|
869
|
+
.rosh-tl-pill:hover, .rosh-tl-pill[aria-expanded="true"] { box-shadow: 0 0 0 3px var(--line); }
|
|
870
|
+
|
|
871
|
+
.rosh-tl-bubble, .rosh-tl-popover {
|
|
872
|
+
position: absolute; z-index: 6; min-width: 132px; max-width: 220px;
|
|
873
|
+
display: flex; flex-direction: column;
|
|
874
|
+
background: var(--surface-raised); border: 1px solid var(--line);
|
|
875
|
+
border-radius: 9px; padding: 7px 9px; transform: translateX(-50%);
|
|
876
|
+
}
|
|
877
|
+
/* The arrow is aimed at the dot the bubble describes, which is not the bubble's
|
|
878
|
+
own centre once it has been clamped inside the track. */
|
|
879
|
+
.rosh-tl-bubble::before, .rosh-tl-popover::before {
|
|
880
|
+
content: ""; position: absolute; top: -4px; left: var(--rosh-tl-arrow, 50%);
|
|
881
|
+
width: 7px; height: 7px; margin-left: -3.5px; background: var(--surface-raised);
|
|
882
|
+
border-left: 1px solid var(--line); border-top: 1px solid var(--line);
|
|
883
|
+
transform: rotate(45deg);
|
|
884
|
+
}
|
|
885
|
+
/* A preview is the document in miniature rather than a second line of text, so
|
|
886
|
+
it gets room — 280px, the width at which a chip row and a snippet still read
|
|
887
|
+
on a phone. */
|
|
888
|
+
.rosh-tl-bubble.is-preview { max-width: 280px; min-width: 180px; }
|
|
889
|
+
.rosh-tl-preview {
|
|
890
|
+
display: flex; flex-direction: column; gap: 3px;
|
|
891
|
+
margin-top: 5px; font-size: 11px; color: var(--ink-muted);
|
|
892
|
+
}
|
|
893
|
+
/* The way out of the popover and into the thing it is previewing. */
|
|
894
|
+
.rosh-tl-bubble-open {
|
|
895
|
+
margin-top: 6px; padding: 5px 0 0; width: 100%; text-align: left; cursor: pointer;
|
|
896
|
+
background: none; border: 0; border-top-width: 1px; border-top-style: solid;
|
|
897
|
+
border-top-color: var(--line);
|
|
898
|
+
font: inherit; font-size: 11px; font-weight: 500;
|
|
899
|
+
}
|
|
900
|
+
.rosh-tl-bubble-open:hover { text-decoration: underline; }
|
|
901
|
+
.rosh-tl-bubble-open:focus-visible { outline: 2px solid var(--tl-focus); outline-offset: 2px; }
|
|
902
|
+
|
|
903
|
+
.rosh-tl-popover-head {
|
|
904
|
+
margin: 0 0 5px; font-size: 10px; font-weight: 500; letter-spacing: 0.06em;
|
|
905
|
+
text-transform: uppercase; color: var(--ink-faint);
|
|
906
|
+
}
|
|
907
|
+
.rosh-tl-popover ol { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 3px; }
|
|
908
|
+
.rosh-tl-popover li { display: flex; gap: 8px; justify-content: space-between; font-size: 11px; }
|
|
909
|
+
/* What a FOLD says, which is the same list plus each member's own card: a run
|
|
910
|
+
that is all one date can never be opened, so this is the only place those
|
|
911
|
+
members exist. A column, because a preview under a row is a paragraph and not
|
|
912
|
+
a third column. */
|
|
913
|
+
.rosh-tl-fold-list ol { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
|
|
914
|
+
.rosh-tl-fold-list li { display: flex; flex-direction: column; font-size: 11px; }
|
|
915
|
+
.rosh-tl-foldrow { display: flex; gap: 8px; justify-content: space-between; margin: 0; }
|
|
916
|
+
.rosh-tl-fold-list .rosh-tl-preview { margin-top: 2px; }
|
|
917
|
+
|
|
918
|
+
.rosh-tl-hit { position: absolute; left: -10px; right: -10px; cursor: pointer; touch-action: none; }
|
|
919
|
+
/* The marks stand ABOVE that strip, and this one declaration is the whole of it.
|
|
920
|
+
`ThumbLayer` draws the strip after the list of dots and neither had a z-index,
|
|
921
|
+
so the strip won the paint order and took every pointer event aimed at a mark:
|
|
922
|
+
no hover, no focus, no click, on every track with a thumb — which is every
|
|
923
|
+
production scrubber. 4 is above the strip (auto) and below the thumb (5) and
|
|
924
|
+
the bubble (6), so the disc the reader has hold of still stays on top. The
|
|
925
|
+
strip keeps every pixel no dot is standing on, which is what leaves the drag
|
|
926
|
+
where it was: see the note over the strip in `TimelineTrack.tsx`. */
|
|
927
|
+
.rosh-tl-nodes .rosh-tl-node { z-index: 4; }
|
|
928
|
+
/* The magnet. A drag reports a stop change the moment the pointer crosses the
|
|
929
|
+
halfway line, and these 120ms carry the disc, its chip and the fill there
|
|
930
|
+
together — which is what makes six reports read as six settlings rather than
|
|
931
|
+
as one slide. The whole rule is off under reduced motion, at the bottom. */
|
|
932
|
+
.rosh-tl-thumb {
|
|
933
|
+
position: absolute; width: 18px; height: 18px; border-radius: 50%; z-index: 5;
|
|
934
|
+
background: var(--surface); border: 2px solid currentColor;
|
|
935
|
+
box-shadow: 0 0 0 4px var(--surface-sunken);
|
|
936
|
+
transform: translate(-50%, -50%); touch-action: none; cursor: grab;
|
|
937
|
+
transition: left 120ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
938
|
+
}
|
|
939
|
+
.rosh-tl-fill.is-tweened { transition: width 120ms cubic-bezier(0.22, 1, 0.36, 1); }
|
|
940
|
+
/* While playback carries the thumb, an animation frame sets its position every
|
|
941
|
+
16ms and the magnet's 120ms transitions have to come off: a transition chasing
|
|
942
|
+
a tween trails it by its own duration, which reads as the thumb lagging the
|
|
943
|
+
date on its chip — and a pause would keep sliding for 120ms after the frame
|
|
944
|
+
that froze it, landing somewhere nobody asked for. */
|
|
945
|
+
.rosh-tl-layer.is-gliding .rosh-tl-thumb,
|
|
946
|
+
.rosh-tl-layer.is-gliding .rosh-tl-chip,
|
|
947
|
+
.rosh-tl-layer.is-gliding .rosh-tl-fill.is-tweened { transition: none; }
|
|
948
|
+
/* The label of the mark the thumb is on, as a control: it opens the document
|
|
949
|
+
behind the dot. Styled as the label it replaces, underlined on hover so it
|
|
950
|
+
reads as the link it is. */
|
|
951
|
+
.rosh-tl-open {
|
|
952
|
+
padding: 0; border: 0; background: none; cursor: pointer; text-align: left;
|
|
953
|
+
font: inherit; font-size: 11px; font-weight: 500; line-height: 1.25;
|
|
954
|
+
}
|
|
955
|
+
.rosh-tl-open:hover { text-decoration: underline; }
|
|
956
|
+
.rosh-tl-open:focus-visible { outline: 2px solid var(--tl-focus); outline-offset: 2px; border-radius: 4px; }
|
|
957
|
+
.rosh-tl-thumb:active { cursor: grabbing; }
|
|
958
|
+
.rosh-tl-thumb::after { content: ""; position: absolute; inset: 3px; border-radius: 50%; background: currentColor; }
|
|
959
|
+
.rosh-tl-chip {
|
|
960
|
+
position: absolute; transform: translateX(-50%); z-index: 5; white-space: nowrap;
|
|
961
|
+
transition: left 120ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
962
|
+
font-family: var(--font-mono); font-variant-numeric: tabular-nums;
|
|
963
|
+
font-size: 10.5px; font-weight: 500; padding: 3px 8px; border-radius: 7px;
|
|
964
|
+
background: var(--ink-strongest); color: var(--surface);
|
|
965
|
+
}
|
|
966
|
+
.rosh-tl-chip::after {
|
|
967
|
+
content: ""; position: absolute; bottom: -3px; left: 50%; width: 7px; height: 7px;
|
|
968
|
+
margin-left: -3.5px; border-radius: 1px; background: var(--ink-strongest); transform: rotate(45deg);
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
.rosh-tl-pending { padding-top: 16px; }
|
|
972
|
+
.rosh-tl-pending-head { margin: 0 0 8px; font-size: 10px; font-weight: 500; color: var(--ink-faint); }
|
|
973
|
+
.rosh-tl-ghosts { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 9px; }
|
|
974
|
+
.rosh-tl-ghosts li { display: flex; align-items: center; gap: 9px; font-size: 11px; color: var(--ink-muted); }
|
|
975
|
+
.rosh-tl-ghosts .rosh-tl-node { position: relative; --rosh-tl-shift: translate(0, 0); flex: none; }
|
|
976
|
+
.rosh-tl-ghosts b { font-weight: 500; color: var(--ink-faint); }
|
|
977
|
+
.rosh-tl-more { margin-top: 9px; font-size: 11px; text-decoration: underline; cursor: pointer; }
|
|
978
|
+
|
|
979
|
+
/* The vertical variant: under a 320px track the axis is abandoned rather than
|
|
980
|
+
squeezed, and the connector carries the fill. */
|
|
981
|
+
.rosh-tl-v { list-style: none; margin: 0; padding: 0; }
|
|
982
|
+
.rosh-tl-v > li {
|
|
983
|
+
position: relative; display: grid; grid-template-columns: 16px minmax(0, 1fr);
|
|
984
|
+
gap: 10px; padding-bottom: 14px;
|
|
985
|
+
}
|
|
986
|
+
.rosh-tl-v > li:last-child { padding-bottom: 0; }
|
|
987
|
+
.rosh-tl-v .rosh-tl-node {
|
|
988
|
+
position: relative; --rosh-tl-shift: translate(0, 0);
|
|
989
|
+
justify-self: center; align-self: start; margin-top: 3px;
|
|
990
|
+
}
|
|
991
|
+
.rosh-tl-vline {
|
|
992
|
+
position: absolute; left: 8px; top: 17px; bottom: -1px; width: 1px;
|
|
993
|
+
background: var(--line-strong); transform-origin: top center;
|
|
994
|
+
}
|
|
995
|
+
.rosh-tl-vline.is-on { width: 2px; left: 7.5px; background: currentColor; }
|
|
996
|
+
.rosh-tl-vline.is-dash { background: none; border-left: 1px dashed var(--line-strong); }
|
|
997
|
+
.rosh-tl-vrow { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
|
|
998
|
+
.rosh-tl-v > li[aria-current] .rosh-tl-name { font-weight: 600; }
|
|
999
|
+
.rosh-tl-vgap {
|
|
1000
|
+
position: relative; margin: 0; padding: 0 0 14px 26px; min-height: 20px;
|
|
1001
|
+
font-family: var(--font-mono); font-size: 10px; color: var(--ink-faint);
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
/* ── Timeline motion ────────────────────────────────────────────────────────
|
|
1005
|
+
* Mount budget <= 600ms: the fill draws in 420ms, the dots pop on a stagger
|
|
1006
|
+
* that lands the last one by 520ms, and the labels settle by 580ms. `both` on
|
|
1007
|
+
* every rule — with a delay the mark would otherwise paint its FINAL state and
|
|
1008
|
+
* jump back when its delay elapsed. The draw is `scaleX`, not `width`: the
|
|
1009
|
+
* track is under a ResizeObserver, and animating width relays out every dot on
|
|
1010
|
+
* every frame. */
|
|
1011
|
+
@keyframes rosh-tl-draw { from { transform: scaleX(0); } to { transform: scaleX(1); } }
|
|
1012
|
+
@keyframes rosh-tl-drawy { from { transform: scaleY(0); } to { transform: scaleY(1); } }
|
|
1013
|
+
@keyframes rosh-tl-fade { from { opacity: 0; } to { opacity: 1; } }
|
|
1014
|
+
@keyframes rosh-tl-pop {
|
|
1015
|
+
from {
|
|
1016
|
+
opacity: 0;
|
|
1017
|
+
transform: var(--rosh-tl-shift, translate(-50%, -50%)) rotate(var(--rosh-tl-rot, 0deg)) scale(0.6);
|
|
1018
|
+
}
|
|
1019
|
+
to {
|
|
1020
|
+
opacity: 1;
|
|
1021
|
+
transform: var(--rosh-tl-shift, translate(-50%, -50%)) rotate(var(--rosh-tl-rot, 0deg)) scale(1);
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
@keyframes rosh-tl-pulse { from { transform: scale(1); opacity: 0.35; } to { transform: scale(2.6); opacity: 0; } }
|
|
1025
|
+
|
|
1026
|
+
.rosh-tl-draw { animation: rosh-tl-draw 420ms cubic-bezier(0.22, 1, 0.36, 1) both; }
|
|
1027
|
+
.rosh-tl-drawy { animation: rosh-tl-drawy 420ms cubic-bezier(0.22, 1, 0.36, 1) both; }
|
|
1028
|
+
.rosh-tl-fade { animation: rosh-tl-fade 160ms cubic-bezier(0, 0, 0.2, 1) both; }
|
|
1029
|
+
.rosh-tl-pop { animation: rosh-tl-pop 180ms cubic-bezier(0.22, 1, 0.36, 1) both; }
|
|
1030
|
+
/* Three cycles, then stop. An endless "you are here" ring is the card's most
|
|
1031
|
+
frequent motion, and frequency is what makes motion annoying. */
|
|
1032
|
+
.rosh-tl-pulse.is-on {
|
|
1033
|
+
transform-origin: center;
|
|
1034
|
+
animation: rosh-tl-pulse 2000ms cubic-bezier(0, 0, 0.2, 1) 600ms 3 both;
|
|
1035
|
+
}
|
|
1036
|
+
/* Hover is a TRANSITION, never a keyframe — a keyframe restarts on re-render,
|
|
1037
|
+
and this element re-renders on every pointer move. */
|
|
1038
|
+
.rosh-tl-mark { transition: transform 160ms ease-out, box-shadow 160ms ease-out, background-color 160ms ease-out, border-color 160ms ease-out; }
|
|
1039
|
+
/* Half again as big, with a wash behind it: at 10px a 1.35 dot is a dot that
|
|
1040
|
+
moved, and the point of the hover is that the reader can tell it answered. */
|
|
1041
|
+
.rosh-tl-mark:hover, .rosh-tl-mark:focus-visible {
|
|
1042
|
+
--rosh-tl-sc: 1.5;
|
|
1043
|
+
box-shadow: 0 0 0 2px var(--surface-sunken), 0 0 0 3px var(--tl-soft);
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
/* Zoom: the axis opening around a magnified stretch, and closing again. The
|
|
1047
|
+
transition is armed by the component for the length of ONE change and then
|
|
1048
|
+
taken away, because a positional transition left on permanently eases every
|
|
1049
|
+
dot sideways while a window edge is being dragged — which reads as lag, not
|
|
1050
|
+
as motion. */
|
|
1051
|
+
.rosh-tl-layer.is-zooming .rosh-tl-mark {
|
|
1052
|
+
transition: left 240ms cubic-bezier(0.22, 1, 0.36, 1),
|
|
1053
|
+
transform 160ms ease-out, box-shadow 160ms ease-out,
|
|
1054
|
+
background-color 160ms ease-out, border-color 160ms ease-out;
|
|
1055
|
+
}
|
|
1056
|
+
.rosh-tl-layer.is-zooming .rosh-tl-label,
|
|
1057
|
+
.rosh-tl-layer.is-zooming .rosh-tl-lead,
|
|
1058
|
+
.rosh-tl-layer.is-zooming .rosh-tl-cspan,
|
|
1059
|
+
.rosh-tl-layer.is-zooming .rosh-tl-tick,
|
|
1060
|
+
.rosh-tl-layer.is-zooming .rosh-tl-month,
|
|
1061
|
+
.rosh-tl-layer.is-zooming .rosh-tl-rulercut,
|
|
1062
|
+
.rosh-tl-layer.is-zooming .rosh-tl-cut,
|
|
1063
|
+
.rosh-tl-layer.is-zooming .rosh-tl-todayline,
|
|
1064
|
+
.rosh-tl-layer.is-zooming .rosh-tl-fill {
|
|
1065
|
+
transition: left 240ms cubic-bezier(0.22, 1, 0.36, 1),
|
|
1066
|
+
width 240ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1070
|
+
.rosh-tl-layer.is-zooming .rosh-tl-mark,
|
|
1071
|
+
.rosh-tl-layer.is-zooming .rosh-tl-label,
|
|
1072
|
+
.rosh-tl-layer.is-zooming .rosh-tl-lead,
|
|
1073
|
+
.rosh-tl-layer.is-zooming .rosh-tl-cspan,
|
|
1074
|
+
.rosh-tl-layer.is-zooming .rosh-tl-tick,
|
|
1075
|
+
.rosh-tl-layer.is-zooming .rosh-tl-month,
|
|
1076
|
+
.rosh-tl-layer.is-zooming .rosh-tl-rulercut,
|
|
1077
|
+
.rosh-tl-layer.is-zooming .rosh-tl-cut,
|
|
1078
|
+
.rosh-tl-layer.is-zooming .rosh-tl-todayline,
|
|
1079
|
+
.rosh-tl-layer.is-zooming .rosh-tl-fill { transition: none !important; }
|
|
1080
|
+
.rosh-tl-draw, .rosh-tl-drawy, .rosh-tl-fade, .rosh-tl-pop {
|
|
1081
|
+
animation: none !important;
|
|
1082
|
+
opacity: 1 !important;
|
|
1083
|
+
}
|
|
1084
|
+
/* The two draws rest at full scale; the pop rests at the node's own transform,
|
|
1085
|
+
which carries its translate and rotate, so it cannot be reset to `none`. */
|
|
1086
|
+
.rosh-tl-draw, .rosh-tl-drawy { transform: none !important; }
|
|
1087
|
+
/* The ring RESTS invisible — it ends at opacity 0 — so the entrance treatment
|
|
1088
|
+
above would pin a halo at an opacity it never reaches while animating. */
|
|
1089
|
+
.rosh-tl-pulse.is-on { animation: none !important; opacity: 0 !important; }
|
|
1090
|
+
/* Colour still moves; scale does not. */
|
|
1091
|
+
.rosh-tl-mark { transition: background-color 160ms ease-out, border-color 160ms ease-out !important; }
|
|
1092
|
+
.rosh-tl-mark:hover, .rosh-tl-mark:focus-visible { --rosh-tl-sc: 1; }
|
|
1093
|
+
/* The thumb still MOVES under reduced motion — it just arrives. */
|
|
1094
|
+
.rosh-tl-thumb, .rosh-tl-chip, .rosh-tl-fill.is-tweened { transition: none !important; }
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
/* Backgrounds */
|
|
1098
|
+
[data-theme="dark"] .bg-white { background-color: var(--surface) !important; }
|
|
1099
|
+
[data-theme="dark"] .bg-gray-50 { background-color: var(--surface-sunken) !important; }
|
|
1100
|
+
[data-theme="dark"] .bg-gray-100 { background-color: var(--surface) !important; }
|
|
1101
|
+
[data-theme="dark"] .bg-gray-200 { background-color: var(--surface-raised) !important; }
|
|
1102
|
+
|
|
1103
|
+
/* Text */
|
|
1104
|
+
[data-theme="dark"] .text-gray-900 { color: var(--ink-strongest) !important; }
|
|
1105
|
+
[data-theme="dark"] .text-gray-800 { color: var(--ink-strong) !important; }
|
|
1106
|
+
[data-theme="dark"] .text-gray-700 { color: var(--ink) !important; }
|
|
1107
|
+
[data-theme="dark"] .text-gray-600 { color: var(--ink-muted) !important; }
|
|
1108
|
+
[data-theme="dark"] .text-gray-500 { color: var(--ink-faint) !important; }
|
|
1109
|
+
[data-theme="dark"] .text-gray-400 { color: var(--ink-faintest) !important; }
|
|
1110
|
+
|
|
1111
|
+
/* Borders */
|
|
1112
|
+
[data-theme="dark"] .border-gray-200 { border-color: var(--line) !important; }
|
|
1113
|
+
[data-theme="dark"] .border-gray-300 { border-color: var(--line-strong) !important; }
|
|
1114
|
+
[data-theme="dark"] .border-gray-100 { border-color: var(--line-subtle) !important; }
|
|
1115
|
+
|
|
1116
|
+
/* Rings. `ring-gray-100` is a cut-out rather than a line: it rings a mark in
|
|
1117
|
+
the colour of the `bg-gray-100` surface under it (PageTabBar's unsaved dot,
|
|
1118
|
+
on the tab strip), so it takes that surface's token. Without this it kept
|
|
1119
|
+
its light gray and drew a pale halo on the dark strip. */
|
|
1120
|
+
[data-theme="dark"] .ring-gray-100 { --tw-ring-color: var(--surface) !important; }
|
|
1121
|
+
|
|
1122
|
+
/* Cards, shadows, tables */
|
|
1123
|
+
[data-theme="dark"] .shadow { --tw-shadow-color: rgba(0,0,0,0.4) !important; }
|
|
1124
|
+
[data-theme="dark"] .shadow-lg { --tw-shadow-color: rgba(0,0,0,0.5) !important; }
|
|
1125
|
+
[data-theme="dark"] .shadow-xl { --tw-shadow-color: rgba(0,0,0,0.5) !important; }
|
|
1126
|
+
[data-theme="dark"] .shadow-2xl { --tw-shadow-color: rgba(0,0,0,0.6) !important; }
|
|
1127
|
+
[data-theme="dark"] .divide-gray-200 > :not(:last-child) { border-color: var(--line) !important; }
|
|
1128
|
+
[data-theme="dark"] .divide-gray-100 > :not(:last-child) { border-color: var(--line-subtle) !important; }
|
|
1129
|
+
|
|
1130
|
+
/* Hover states */
|
|
1131
|
+
[data-theme="dark"] .hover\:bg-gray-50:hover { background-color: var(--line-subtle) !important; }
|
|
1132
|
+
[data-theme="dark"] .hover\:bg-gray-100:hover { background-color: var(--surface-raised) !important; }
|
|
1133
|
+
[data-theme="dark"] .hover\:bg-gray-200:hover { background-color: var(--line-strong) !important; }
|
|
1134
|
+
|
|
1135
|
+
/* Blue hover states — used by menu items, profile row, etc.
|
|
1136
|
+
In dark mode swap the near-white bg for a dark blue tint and the dark
|
|
1137
|
+
blue text for a light shade so highlighted items stay readable. */
|
|
1138
|
+
[data-theme="dark"] .bg-blue-50,
|
|
1139
|
+
[data-theme="dark"] .hover\:bg-blue-50:hover { background-color: rgba(59, 130, 246, 0.26) !important; }
|
|
1140
|
+
[data-theme="dark"] .text-blue-700,
|
|
1141
|
+
[data-theme="dark"] .hover\:text-blue-700:hover { color: #bfdbfe !important; }
|
|
1142
|
+
[data-theme="dark"] .text-blue-600 { color: #93c5fd !important; }
|
|
1143
|
+
[data-theme="dark"] .text-blue-800 { color: #bfdbfe !important; }
|
|
1144
|
+
|
|
1145
|
+
/* Bare *-100 highlight backgrounds + *-700 text (status pills, badges, avatars,
|
|
1146
|
+
dialog icons) have no dark override otherwise, so they keep their bright
|
|
1147
|
+
light-mode value and wash out the text on top. Mute the background to a low
|
|
1148
|
+
tint and lighten the text so highlighted content stays readable in dark mode. */
|
|
1149
|
+
[data-theme="dark"] .bg-blue-100 { background-color: rgba(59, 130, 246, 0.22) !important; }
|
|
1150
|
+
|
|
1151
|
+
/* Tailwind's `!` important modifier compiles to distinct class names
|
|
1152
|
+
(`.\!bg-blue-100` etc.), so the EditableGrid selection utilities escape the
|
|
1153
|
+
remaps above: the selection keeps its light-mode background while the cell
|
|
1154
|
+
text goes light, making selected cells unreadable in dark mode. The
|
|
1155
|
+
attribute selector outranks the utility's own !important. */
|
|
1156
|
+
[data-theme="dark"] .\!bg-blue-50 { background-color: rgba(59, 130, 246, 0.26) !important; }
|
|
1157
|
+
[data-theme="dark"] .\!bg-blue-100 { background-color: rgba(59, 130, 246, 0.22) !important; }
|
|
1158
|
+
[data-theme="dark"] .\!bg-blue-200 { background-color: rgba(59, 130, 246, 0.35) !important; }
|
|
1159
|
+
[data-theme="dark"] .\!text-gray-700 { color: var(--ink) !important; }
|
|
1160
|
+
[data-theme="dark"] .bg-green-100 { background-color: rgba(34, 197, 94, 0.18) !important; }
|
|
1161
|
+
[data-theme="dark"] .bg-emerald-100 { background-color: rgba(16, 185, 129, 0.18) !important; }
|
|
1162
|
+
[data-theme="dark"] .bg-sky-100 { background-color: rgba(14, 165, 233, 0.18) !important; }
|
|
1163
|
+
[data-theme="dark"] .bg-amber-100 { background-color: rgba(245, 158, 11, 0.18) !important; }
|
|
1164
|
+
[data-theme="dark"] .bg-yellow-100 { background-color: rgba(234, 179, 8, 0.18) !important; }
|
|
1165
|
+
[data-theme="dark"] .bg-orange-100 { background-color: rgba(249, 115, 22, 0.18) !important; }
|
|
1166
|
+
[data-theme="dark"] .bg-red-100 { background-color: rgba(239, 68, 68, 0.18) !important; }
|
|
1167
|
+
[data-theme="dark"] .bg-indigo-100 { background-color: rgba(99, 102, 241, 0.18) !important; }
|
|
1168
|
+
[data-theme="dark"] .bg-purple-100 { background-color: rgba(168, 85, 247, 0.18) !important; }
|
|
1169
|
+
[data-theme="dark"] .text-green-700 { color: #86efac !important; }
|
|
1170
|
+
/* The /70 form is a separate class, so the remap above misses it. The amber
|
|
1171
|
+
block already covers its own /30../50 tints; green did not, and dark green
|
|
1172
|
+
on the green tint was the result. */
|
|
1173
|
+
[data-theme="dark"] .text-green-700\/70 { color: rgba(134, 239, 172, 0.7) !important; }
|
|
1174
|
+
[data-theme="dark"] .text-emerald-700 { color: #6ee7b7 !important; }
|
|
1175
|
+
[data-theme="dark"] .text-sky-700 { color: #7dd3fc !important; }
|
|
1176
|
+
[data-theme="dark"] .text-amber-700 { color: #fcd34d !important; }
|
|
1177
|
+
[data-theme="dark"] .text-yellow-700 { color: #fde047 !important; }
|
|
1178
|
+
[data-theme="dark"] .text-orange-700 { color: #fdba74 !important; }
|
|
1179
|
+
[data-theme="dark"] .text-red-700 { color: #fca5a5 !important; }
|
|
1180
|
+
[data-theme="dark"] .text-indigo-700 { color: #a5b4fc !important; }
|
|
1181
|
+
[data-theme="dark"] .text-purple-700 { color: #d8b4fe !important; }
|
|
1182
|
+
|
|
1183
|
+
/* Brighten taskbar button text so it stays readable on dark glass.
|
|
1184
|
+
The default override (var(--ink-muted)) is too dim for short button labels. */
|
|
1185
|
+
[data-theme="dark"] .text-gray-600 { color: var(--ink) !important; }
|
|
1186
|
+
|
|
1187
|
+
/* Translucent button backgrounds — Tailwind generates separate classes for the
|
|
1188
|
+
/40 /60 alpha variants (bg-gray-50\/40 etc.) so the bare .bg-gray-50 override
|
|
1189
|
+
doesn't match. Override them explicitly with dark equivalents so taskbar
|
|
1190
|
+
tabs and the start-menu button don't look too bright in Dark mode. */
|
|
1191
|
+
[data-theme="dark"] .bg-gray-50\/40 { background-color: rgba(24, 24, 37, 0.4) !important; }
|
|
1192
|
+
/* /50 and /60 are used as in-window PANEL surfaces (sidebars, form sections,
|
|
1193
|
+
popup cards) — keep them solid in dark so a bright wallpaper can't bleed
|
|
1194
|
+
through the window glass and wash them out. */
|
|
1195
|
+
[data-theme="dark"] .bg-gray-50\/50 { background-color: var(--surface-sunken) !important; }
|
|
1196
|
+
[data-theme="dark"] .bg-gray-50\/60 { background-color: var(--surface-sunken) !important; }
|
|
1197
|
+
[data-theme="dark"] .hover\:bg-gray-50\/50:hover { background-color: var(--surface-sunken) !important; }
|
|
1198
|
+
[data-theme="dark"] .bg-gray-100\/40 { background-color: rgba(30, 30, 46, 0.4) !important; }
|
|
1199
|
+
[data-theme="dark"] .bg-gray-100\/60 { background-color: rgba(30, 30, 46, 0.6) !important; }
|
|
1200
|
+
[data-theme="dark"] .bg-gray-200\/40 { background-color: rgba(49, 50, 68, 0.4) !important; }
|
|
1201
|
+
[data-theme="dark"] .bg-gray-200\/50 { background-color: rgba(49, 50, 68, 0.5) !important; }
|
|
1202
|
+
[data-theme="dark"] .bg-blue-100\/60 { background-color: rgba(59, 130, 246, 0.22) !important; }
|
|
1203
|
+
/* Pale-blue tinted panels (selected rows, inline-reply box) — scale the
|
|
1204
|
+
.bg-blue-50 dark tint (0.26) down through the alpha steps. */
|
|
1205
|
+
[data-theme="dark"] .bg-blue-50\/30 { background-color: rgba(59, 130, 246, 0.10) !important; }
|
|
1206
|
+
[data-theme="dark"] .bg-blue-50\/40 { background-color: rgba(59, 130, 246, 0.12) !important; }
|
|
1207
|
+
[data-theme="dark"] .bg-blue-50\/50 { background-color: rgba(59, 130, 246, 0.15) !important; }
|
|
1208
|
+
[data-theme="dark"] .bg-blue-50\/60 { background-color: rgba(59, 130, 246, 0.18) !important; }
|
|
1209
|
+
[data-theme="dark"] .border-gray-200\/40 { border-color: rgba(69, 71, 90, 0.4) !important; }
|
|
1210
|
+
[data-theme="dark"] .border-gray-300\/40 { border-color: rgba(88, 91, 112, 0.4) !important; }
|
|
1211
|
+
[data-theme="dark"] .border-blue-400\/60 { border-color: rgba(96, 165, 250, 0.45) !important; }
|
|
1212
|
+
[data-theme="dark"] .hover\:bg-gray-200\/40:hover { background-color: rgba(49, 50, 68, 0.4) !important; }
|
|
1213
|
+
|
|
1214
|
+
/* White alpha variants — the bare .bg-white override doesn't match the /50../95
|
|
1215
|
+
alpha classes, so cards (the Widget gallery), the Exposé snapshot title pill
|
|
1216
|
+
and desktop overlays stayed bright white in dark mode while their (remapped,
|
|
1217
|
+
light) text washed out. Map them to the dark window tint. */
|
|
1218
|
+
[data-theme="dark"] .bg-white\/50 { background-color: rgba(30, 30, 46, 0.5) !important; }
|
|
1219
|
+
[data-theme="dark"] .bg-white\/60 { background-color: rgba(30, 30, 46, 0.6) !important; }
|
|
1220
|
+
[data-theme="dark"] .bg-white\/70 { background-color: rgba(30, 30, 46, 0.7) !important; }
|
|
1221
|
+
[data-theme="dark"] .bg-white\/80 { background-color: rgba(30, 30, 46, 0.85) !important; }
|
|
1222
|
+
[data-theme="dark"] .bg-white\/90 { background-color: rgba(30, 30, 46, 0.92) !important; }
|
|
1223
|
+
[data-theme="dark"] .bg-white\/95 { background-color: rgba(30, 30, 46, 0.95) !important; }
|
|
1224
|
+
[data-theme="dark"] .bg-blue-50\/70 { background-color: rgba(59, 130, 246, 0.22) !important; }
|
|
1225
|
+
|
|
1226
|
+
/* Inputs — apply the form-input dark styling, but skip elements that
|
|
1227
|
+
explicitly opt into a transparent background (e.g. the start-menu
|
|
1228
|
+
search) so they keep their parent's frosted-glass tile. */
|
|
1229
|
+
[data-theme="dark"] input:not(.bg-transparent):not([type="checkbox"]):not([type="radio"]):not([type="color"]):not([type="file"]),
|
|
1230
|
+
[data-theme="dark"] select:not(.bg-transparent),
|
|
1231
|
+
[data-theme="dark"] textarea:not(.bg-transparent) {
|
|
1232
|
+
background-color: var(--surface-sunken) !important;
|
|
1233
|
+
border-color: var(--line-strong) !important;
|
|
1234
|
+
color: var(--ink-strongest) !important;
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
/* iOS auto-zooms the viewport when a focused form control's computed
|
|
1238
|
+
font-size is below 16px. On phones/tablets (same breakpoint as
|
|
1239
|
+
useIsMobile) pin text-bearing controls to 16px so focusing them never
|
|
1240
|
+
triggers the zoom. This keeps pinch-zoom working, unlike a
|
|
1241
|
+
`maximum-scale=1` viewport lock. !important beats Tailwind text-*
|
|
1242
|
+
utilities; the :not() list mirrors the dark-input rule above and skips
|
|
1243
|
+
controls that don't take a text caret. */
|
|
1244
|
+
@media (max-width: 767px), (pointer: coarse) {
|
|
1245
|
+
input:not([type="checkbox"]):not([type="radio"]):not([type="color"]):not([type="file"]),
|
|
1246
|
+
select,
|
|
1247
|
+
textarea {
|
|
1248
|
+
font-size: 16px !important;
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
/* Status badges — tone down brightness in dark mode (matches StatusBadge.tsx semantic groups) */
|
|
1253
|
+
[data-theme="dark"] .bg-gray-100.text-gray-800 { background-color: var(--surface-raised) !important; color: var(--ink) !important; }
|
|
1254
|
+
[data-theme="dark"] .bg-gray-300.text-gray-800 { background-color: var(--line-strong) !important; color: var(--ink-strongest) !important; }
|
|
1255
|
+
[data-theme="dark"] .bg-green-100.text-green-800 { background-color: rgba(34, 197, 94, 0.18) !important; color: #86efac !important; }
|
|
1256
|
+
[data-theme="dark"] .bg-blue-100.text-blue-800 { background-color: rgba(59, 130, 246, 0.18) !important; color: #93c5fd !important; }
|
|
1257
|
+
[data-theme="dark"] .bg-indigo-100.text-indigo-800 { background-color: rgba(99, 102, 241, 0.18) !important; color: #a5b4fc !important; }
|
|
1258
|
+
[data-theme="dark"] .bg-sky-100.text-sky-800 { background-color: rgba(14, 165, 233, 0.18) !important; color: #7dd3fc !important; }
|
|
1259
|
+
[data-theme="dark"] .bg-yellow-100.text-yellow-800 { background-color: rgba(234, 179, 8, 0.18) !important; color: #fde68a !important; }
|
|
1260
|
+
[data-theme="dark"] .bg-orange-100.text-orange-800 { background-color: rgba(249, 115, 22, 0.18) !important; color: #fdba74 !important; }
|
|
1261
|
+
[data-theme="dark"] .bg-red-100.text-red-800 { background-color: rgba(239, 68, 68, 0.18) !important; color: #fca5a5 !important; }
|
|
1262
|
+
|
|
1263
|
+
/* ── Extended dark-mode tint families ────────────────────────────────
|
|
1264
|
+
Upstreamed 2026-06-13 from the admin portal's 2026-06-10 audit so all
|
|
1265
|
+
consumers share one source of truth. Every hue used by chips/badges
|
|
1266
|
+
(tag colors, group colors, status accents, deal stages), notice
|
|
1267
|
+
banners, verdict panels, row tints and danger buttons gets the same
|
|
1268
|
+
scale as the families above: -50 → 10% tint, -100 → 18%, -200 → ~30%,
|
|
1269
|
+
borders -200 → 32% / -300 → 45%, and dark inks flip to the
|
|
1270
|
+
-400/-300/-200 light end of their scale.
|
|
1271
|
+
Deliberately NOT remapped: game surfaces that read as intentionally
|
|
1272
|
+
light (Calculator LCD bg-slate-200/text-slate-600/800, 2048/Chess
|
|
1273
|
+
tiles, weather gradients), the Customization theme-preview swatches
|
|
1274
|
+
(bg-pink-50/90, bg-green-50/90, bg-blue-50/90, bg-gray-200/90,
|
|
1275
|
+
bg-pink-200, bg-green-200 — they must show each theme's real colors),
|
|
1276
|
+
and low white/black alphas used as glass highlights on wallpaper. */
|
|
1277
|
+
|
|
1278
|
+
/* Red */
|
|
1279
|
+
[data-theme="dark"] .bg-red-50 { background-color: rgba(239, 68, 68, 0.10) !important; }
|
|
1280
|
+
[data-theme="dark"] .hover\:bg-red-50:hover { background-color: rgba(239, 68, 68, 0.16) !important; }
|
|
1281
|
+
[data-theme="dark"] .active\:bg-red-50:active { background-color: rgba(239, 68, 68, 0.16) !important; }
|
|
1282
|
+
[data-theme="dark"] .bg-red-50\/30 { background-color: rgba(239, 68, 68, 0.06) !important; }
|
|
1283
|
+
[data-theme="dark"] .bg-red-50\/50 { background-color: rgba(239, 68, 68, 0.08) !important; }
|
|
1284
|
+
[data-theme="dark"] .hover\:bg-red-100:hover { background-color: rgba(239, 68, 68, 0.22) !important; }
|
|
1285
|
+
[data-theme="dark"] .border-red-200 { border-color: rgba(239, 68, 68, 0.35) !important; }
|
|
1286
|
+
[data-theme="dark"] .border-red-300,
|
|
1287
|
+
[data-theme="dark"] .hover\:border-red-300:hover { border-color: rgba(239, 68, 68, 0.45) !important; }
|
|
1288
|
+
[data-theme="dark"] .text-red-600,
|
|
1289
|
+
[data-theme="dark"] .hover\:text-red-600:hover { color: #f87171 !important; }
|
|
1290
|
+
[data-theme="dark"] .hover\:text-red-700:hover { color: #fca5a5 !important; }
|
|
1291
|
+
[data-theme="dark"] .text-red-800,
|
|
1292
|
+
[data-theme="dark"] .hover\:text-red-800:hover { color: #fecaca !important; }
|
|
1293
|
+
[data-theme="dark"] .text-red-900 { color: #fecaca !important; }
|
|
1294
|
+
|
|
1295
|
+
/* Amber */
|
|
1296
|
+
[data-theme="dark"] .bg-amber-50 { background-color: rgba(245, 158, 11, 0.10) !important; }
|
|
1297
|
+
[data-theme="dark"] .hover\:bg-amber-50:hover { background-color: rgba(245, 158, 11, 0.14) !important; }
|
|
1298
|
+
[data-theme="dark"] .bg-amber-50\/30 { background-color: rgba(245, 158, 11, 0.06) !important; }
|
|
1299
|
+
[data-theme="dark"] .bg-amber-50\/40 { background-color: rgba(245, 158, 11, 0.07) !important; }
|
|
1300
|
+
[data-theme="dark"] .bg-amber-50\/50 { background-color: rgba(245, 158, 11, 0.08) !important; }
|
|
1301
|
+
[data-theme="dark"] .hover\:bg-amber-100:hover { background-color: rgba(245, 158, 11, 0.24) !important; }
|
|
1302
|
+
[data-theme="dark"] .bg-amber-200 { background-color: rgba(245, 158, 11, 0.28) !important; }
|
|
1303
|
+
[data-theme="dark"] .border-amber-200 { border-color: rgba(245, 158, 11, 0.35) !important; }
|
|
1304
|
+
[data-theme="dark"] .border-amber-300,
|
|
1305
|
+
[data-theme="dark"] .hover\:border-amber-300:hover { border-color: rgba(245, 158, 11, 0.45) !important; }
|
|
1306
|
+
[data-theme="dark"] .text-amber-600,
|
|
1307
|
+
[data-theme="dark"] .hover\:text-amber-600:hover { color: #fbbf24 !important; }
|
|
1308
|
+
[data-theme="dark"] .text-amber-800 { color: #fde68a !important; }
|
|
1309
|
+
[data-theme="dark"] .text-amber-900 { color: #fde68a !important; }
|
|
1310
|
+
|
|
1311
|
+
/* Yellow */
|
|
1312
|
+
[data-theme="dark"] .bg-yellow-50 { background-color: rgba(234, 179, 8, 0.10) !important; }
|
|
1313
|
+
[data-theme="dark"] .bg-yellow-50\/50 { background-color: rgba(234, 179, 8, 0.08) !important; }
|
|
1314
|
+
[data-theme="dark"] .border-yellow-200 { border-color: rgba(234, 179, 8, 0.35) !important; }
|
|
1315
|
+
[data-theme="dark"] .border-yellow-300 { border-color: rgba(234, 179, 8, 0.45) !important; }
|
|
1316
|
+
[data-theme="dark"] .text-yellow-600,
|
|
1317
|
+
[data-theme="dark"] .hover\:text-yellow-600:hover { color: #facc15 !important; }
|
|
1318
|
+
[data-theme="dark"] .text-yellow-900 { color: #fef08a !important; }
|
|
1319
|
+
|
|
1320
|
+
/* Green / emerald */
|
|
1321
|
+
[data-theme="dark"] .bg-green-50 { background-color: rgba(34, 197, 94, 0.10) !important; }
|
|
1322
|
+
[data-theme="dark"] .hover\:bg-green-50:hover { background-color: rgba(34, 197, 94, 0.14) !important; }
|
|
1323
|
+
[data-theme="dark"] .bg-green-50\/40 { background-color: rgba(34, 197, 94, 0.06) !important; }
|
|
1324
|
+
[data-theme="dark"] .border-green-200 { border-color: rgba(34, 197, 94, 0.32) !important; }
|
|
1325
|
+
[data-theme="dark"] .border-green-300,
|
|
1326
|
+
[data-theme="dark"] .hover\:border-green-300:hover { border-color: rgba(34, 197, 94, 0.45) !important; }
|
|
1327
|
+
[data-theme="dark"] .text-green-600,
|
|
1328
|
+
[data-theme="dark"] .hover\:text-green-600:hover { color: #4ade80 !important; }
|
|
1329
|
+
[data-theme="dark"] .text-green-900,
|
|
1330
|
+
[data-theme="dark"] .hover\:text-green-800:hover { color: #bbf7d0 !important; }
|
|
1331
|
+
[data-theme="dark"] .bg-emerald-50 { background-color: rgba(16, 185, 129, 0.10) !important; }
|
|
1332
|
+
[data-theme="dark"] .hover\:bg-emerald-100:hover { background-color: rgba(16, 185, 129, 0.24) !important; }
|
|
1333
|
+
[data-theme="dark"] .border-emerald-200 { border-color: rgba(16, 185, 129, 0.32) !important; }
|
|
1334
|
+
[data-theme="dark"] .border-emerald-300 { border-color: rgba(16, 185, 129, 0.45) !important; }
|
|
1335
|
+
[data-theme="dark"] .text-emerald-600 { color: #34d399 !important; }
|
|
1336
|
+
[data-theme="dark"] .text-emerald-800 { color: #a7f3d0 !important; }
|
|
1337
|
+
|
|
1338
|
+
/* Orange */
|
|
1339
|
+
[data-theme="dark"] .bg-orange-50 { background-color: rgba(249, 115, 22, 0.10) !important; }
|
|
1340
|
+
[data-theme="dark"] .hover\:bg-orange-50:hover { background-color: rgba(249, 115, 22, 0.14) !important; }
|
|
1341
|
+
[data-theme="dark"] .hover\:bg-orange-200:hover { background-color: rgba(249, 115, 22, 0.30) !important; }
|
|
1342
|
+
[data-theme="dark"] .border-orange-200 { border-color: rgba(249, 115, 22, 0.32) !important; }
|
|
1343
|
+
[data-theme="dark"] .border-orange-300,
|
|
1344
|
+
[data-theme="dark"] .hover\:border-orange-300:hover { border-color: rgba(249, 115, 22, 0.45) !important; }
|
|
1345
|
+
[data-theme="dark"] .text-orange-600 { color: #fb923c !important; }
|
|
1346
|
+
[data-theme="dark"] .text-orange-900 { color: #fed7aa !important; }
|
|
1347
|
+
|
|
1348
|
+
/* Sky / teal / cyan */
|
|
1349
|
+
[data-theme="dark"] .bg-sky-50 { background-color: rgba(14, 165, 233, 0.10) !important; }
|
|
1350
|
+
[data-theme="dark"] .border-sky-300 { border-color: rgba(14, 165, 233, 0.45) !important; }
|
|
1351
|
+
[data-theme="dark"] .bg-teal-50 { background-color: rgba(20, 184, 166, 0.10) !important; }
|
|
1352
|
+
[data-theme="dark"] .bg-teal-100 { background-color: rgba(20, 184, 166, 0.18) !important; }
|
|
1353
|
+
[data-theme="dark"] .text-teal-600 { color: #2dd4bf !important; }
|
|
1354
|
+
[data-theme="dark"] .text-teal-700 { color: #5eead4 !important; }
|
|
1355
|
+
[data-theme="dark"] .text-teal-800 { color: #99f6e4 !important; }
|
|
1356
|
+
[data-theme="dark"] .text-cyan-600 { color: #22d3ee !important; }
|
|
1357
|
+
|
|
1358
|
+
/* Purple / violet */
|
|
1359
|
+
[data-theme="dark"] .bg-purple-50 { background-color: rgba(168, 85, 247, 0.10) !important; }
|
|
1360
|
+
[data-theme="dark"] .hover\:bg-purple-50:hover { background-color: rgba(168, 85, 247, 0.14) !important; }
|
|
1361
|
+
[data-theme="dark"] .border-purple-200 { border-color: rgba(168, 85, 247, 0.32) !important; }
|
|
1362
|
+
[data-theme="dark"] .border-purple-300,
|
|
1363
|
+
[data-theme="dark"] .hover\:border-purple-300:hover { border-color: rgba(168, 85, 247, 0.45) !important; }
|
|
1364
|
+
[data-theme="dark"] .text-purple-600 { color: #c084fc !important; }
|
|
1365
|
+
[data-theme="dark"] .text-purple-800,
|
|
1366
|
+
[data-theme="dark"] .text-purple-900 { color: #e9d5ff !important; }
|
|
1367
|
+
[data-theme="dark"] .bg-violet-100 { background-color: rgba(139, 92, 246, 0.18) !important; }
|
|
1368
|
+
[data-theme="dark"] .border-violet-300 { border-color: rgba(139, 92, 246, 0.45) !important; }
|
|
1369
|
+
[data-theme="dark"] .text-violet-600 { color: #a78bfa !important; }
|
|
1370
|
+
[data-theme="dark"] .text-violet-700 { color: #c4b5fd !important; }
|
|
1371
|
+
[data-theme="dark"] .text-violet-800 { color: #ddd6fe !important; }
|
|
1372
|
+
|
|
1373
|
+
/* Pink / rose */
|
|
1374
|
+
[data-theme="dark"] .bg-pink-100 { background-color: rgba(236, 72, 153, 0.18) !important; }
|
|
1375
|
+
[data-theme="dark"] .border-pink-200 { border-color: rgba(236, 72, 153, 0.32) !important; }
|
|
1376
|
+
[data-theme="dark"] .border-pink-300 { border-color: rgba(236, 72, 153, 0.45) !important; }
|
|
1377
|
+
[data-theme="dark"] .text-pink-600 { color: #f472b6 !important; }
|
|
1378
|
+
[data-theme="dark"] .text-pink-800,
|
|
1379
|
+
[data-theme="dark"] .text-pink-900 { color: #fbcfe8 !important; }
|
|
1380
|
+
[data-theme="dark"] .bg-rose-100 { background-color: rgba(244, 63, 94, 0.18) !important; }
|
|
1381
|
+
[data-theme="dark"] .text-rose-700 { color: #fda4af !important; }
|
|
1382
|
+
[data-theme="dark"] .text-rose-800 { color: #fecdd3 !important; }
|
|
1383
|
+
|
|
1384
|
+
/* Indigo / slate */
|
|
1385
|
+
[data-theme="dark"] .bg-indigo-50 { background-color: rgba(99, 102, 241, 0.15) !important; }
|
|
1386
|
+
/* An OPACITY MODIFIER makes a different class name, so the remap above does
|
|
1387
|
+
not reach `bg-indigo-50/40`. Same escape the `!` important-modifier makes,
|
|
1388
|
+
handled for `.\!bg-blue-*` earlier in this sheet, and already handled for
|
|
1389
|
+
amber's own `/30../50` tints below. `/40` and `/50`
|
|
1390
|
+
of a -50 tint both land inside the base tint's own alpha, so they take it
|
|
1391
|
+
rather than a proportional slice: a near-white lavender panel under the dark
|
|
1392
|
+
sheet's light body text is unreadable, which is what shipped. */
|
|
1393
|
+
[data-theme="dark"] .bg-indigo-50\/40 { background-color: rgba(99, 102, 241, 0.12) !important; }
|
|
1394
|
+
[data-theme="dark"] .bg-indigo-50\/50 { background-color: rgba(99, 102, 241, 0.15) !important; }
|
|
1395
|
+
[data-theme="dark"] .hover\:bg-indigo-100:hover { background-color: rgba(99, 102, 241, 0.25) !important; }
|
|
1396
|
+
[data-theme="dark"] .text-indigo-600 { color: #a5b4fc !important; }
|
|
1397
|
+
[data-theme="dark"] .border-indigo-100 { border-color: rgba(99, 102, 241, 0.30) !important; }
|
|
1398
|
+
[data-theme="dark"] .border-indigo-200 { border-color: rgba(99, 102, 241, 0.35) !important; }
|
|
1399
|
+
[data-theme="dark"] .border-indigo-300 { border-color: rgba(99, 102, 241, 0.45) !important; }
|
|
1400
|
+
[data-theme="dark"] .text-slate-700 { color: #cbd5e1 !important; } /* slate-600/800 left for the Calculator LCD */
|
|
1401
|
+
[data-theme="dark"] .border-slate-300 { border-color: rgba(148, 163, 184, 0.35) !important; }
|
|
1402
|
+
|
|
1403
|
+
/* Blue — gaps beyond the accent remaps above. Hover/active steps sit one
|
|
1404
|
+
ladder step above their resting tint (base .26 → active .30; the /30../50
|
|
1405
|
+
alpha ladder steps 0.10/0.12/0.15 → hovers 0.12/0.15/0.18). */
|
|
1406
|
+
[data-theme="dark"] .bg-blue-200 { background-color: rgba(59, 130, 246, 0.35) !important; }
|
|
1407
|
+
[data-theme="dark"] .active\:bg-blue-200:active { background-color: rgba(59, 130, 246, 0.40) !important; }
|
|
1408
|
+
[data-theme="dark"] .bg-blue-200\/60 { background-color: rgba(59, 130, 246, 0.20) !important; }
|
|
1409
|
+
[data-theme="dark"] .hover\:bg-blue-100:hover { background-color: rgba(59, 130, 246, 0.26) !important; }
|
|
1410
|
+
[data-theme="dark"] .active\:bg-blue-50:active { background-color: rgba(59, 130, 246, 0.30) !important; }
|
|
1411
|
+
[data-theme="dark"] .hover\:bg-blue-50\/30:hover { background-color: rgba(59, 130, 246, 0.12) !important; }
|
|
1412
|
+
[data-theme="dark"] .hover\:bg-blue-50\/40:hover { background-color: rgba(59, 130, 246, 0.15) !important; }
|
|
1413
|
+
[data-theme="dark"] .hover\:bg-blue-50\/50:hover { background-color: rgba(59, 130, 246, 0.18) !important; }
|
|
1414
|
+
[data-theme="dark"] .border-blue-100 { border-color: rgba(59, 130, 246, 0.22) !important; }
|
|
1415
|
+
[data-theme="dark"] .border-blue-200,
|
|
1416
|
+
[data-theme="dark"] .hover\:border-blue-200:hover { border-color: rgba(59, 130, 246, 0.32) !important; }
|
|
1417
|
+
[data-theme="dark"] .border-blue-300,
|
|
1418
|
+
[data-theme="dark"] .hover\:border-blue-300:hover { border-color: rgba(59, 130, 246, 0.45) !important; }
|
|
1419
|
+
[data-theme="dark"] .border-blue-300\/50 { border-color: rgba(59, 130, 246, 0.25) !important; }
|
|
1420
|
+
[data-theme="dark"] .text-blue-900 { color: #bfdbfe !important; }
|
|
1421
|
+
[data-theme="dark"] .hover\:text-blue-600:hover { color: #93c5fd !important; }
|
|
1422
|
+
[data-theme="dark"] .hover\:text-blue-800:hover { color: #bfdbfe !important; }
|
|
1423
|
+
/* File-input buttons (file: compiles to ::file-selector-button) */
|
|
1424
|
+
[data-theme="dark"] .file\:bg-blue-50::file-selector-button { background-color: rgba(59, 130, 246, 0.26) !important; }
|
|
1425
|
+
[data-theme="dark"] .file\:text-blue-700::file-selector-button { color: #bfdbfe !important; }
|
|
1426
|
+
[data-theme="dark"] .hover\:file\:bg-blue-100:hover::file-selector-button { background-color: rgba(59, 130, 246, 0.26) !important; }
|
|
1427
|
+
|
|
1428
|
+
/* Gray interaction-state variants (separate class names from the bare remaps) */
|
|
1429
|
+
[data-theme="dark"] .hover\:text-gray-600:hover,
|
|
1430
|
+
[data-theme="dark"] .hover\:text-gray-700:hover { color: var(--ink) !important; }
|
|
1431
|
+
[data-theme="dark"] .hover\:text-gray-800:hover { color: var(--ink-strong) !important; }
|
|
1432
|
+
[data-theme="dark"] .hover\:text-gray-900:hover,
|
|
1433
|
+
[data-theme="dark"] .active\:text-gray-900:active { color: var(--ink-strongest) !important; }
|
|
1434
|
+
[data-theme="dark"] .active\:bg-gray-100:active { background-color: var(--surface-raised) !important; }
|
|
1435
|
+
[data-theme="dark"] .active\:bg-gray-200:active { background-color: var(--line-strong) !important; }
|
|
1436
|
+
[data-theme="dark"] .active\:bg-gray-300:active,
|
|
1437
|
+
[data-theme="dark"] .hover\:bg-gray-300:hover { background-color: #585b70 !important; }
|
|
1438
|
+
[data-theme="dark"] .disabled\:bg-gray-100:disabled { background-color: var(--surface) !important; }
|
|
1439
|
+
[data-theme="dark"] .disabled\:bg-gray-200:disabled { background-color: var(--surface-raised) !important; }
|
|
1440
|
+
[data-theme="dark"] .hover\:bg-gray-200\/50:hover { background-color: rgba(69, 71, 90, 0.5) !important; }
|
|
1441
|
+
[data-theme="dark"] .even\:bg-gray-50\/50:nth-child(even) { background-color: rgba(24, 24, 37, 0.5) !important; }
|
|
1442
|
+
[data-theme="dark"] .border-gray-50,
|
|
1443
|
+
[data-theme="dark"] .divide-gray-50 > :not(:last-child) { border-color: var(--line-subtle) !important; }
|
|
1444
|
+
[data-theme="dark"] .border-gray-200\/50 { border-color: rgba(49, 50, 68, 0.6) !important; }
|
|
1445
|
+
[data-theme="dark"] .hover\:border-gray-200:hover { border-color: var(--surface-raised) !important; }
|
|
1446
|
+
[data-theme="dark"] .hover\:border-gray-300:hover { border-color: var(--line-strong) !important; }
|
|
1447
|
+
|
|
1448
|
+
/* White panel surfaces / solid black ink. hover-to-white means "emphasize",
|
|
1449
|
+
which on dark glass means a solid panel tint; hover-to-black ink flips to
|
|
1450
|
+
brighter, not darker (e.g. the taskbar bell). */
|
|
1451
|
+
[data-theme="dark"] .bg-white\/85 { background-color: rgba(30, 30, 46, 0.85) !important; }
|
|
1452
|
+
[data-theme="dark"] .hover\:bg-white:hover { background-color: var(--surface) !important; }
|
|
1453
|
+
[data-theme="dark"] .text-black { color: var(--ink-strongest) !important; }
|
|
1454
|
+
[data-theme="dark"] .hover\:text-black:hover { color: #ffffff !important; }
|
|
1455
|
+
|
|
1456
|
+
/* Markdown — the clamped note (react-os-shell/markdown, `clamp`).
|
|
1457
|
+
|
|
1458
|
+
The fade MASKS the text rather than laying a coloured gradient over it. A
|
|
1459
|
+
gradient has to know what colour to fade to, and there is no answer that
|
|
1460
|
+
survives a tinted accent theme or dark mode — it shows as a pale band across
|
|
1461
|
+
the bottom of the note. A mask fades the ink itself and needs to know
|
|
1462
|
+
nothing about the surface.
|
|
1463
|
+
|
|
1464
|
+
`.rosmd-fade` is applied only when the clamped content actually OVERFLOWS,
|
|
1465
|
+
measured after layout in the component: a note that is long in characters
|
|
1466
|
+
but short on screen would otherwise get a fade under nothing. */
|
|
1467
|
+
.rosmd-clamp {
|
|
1468
|
+
max-height: 8rem;
|
|
1469
|
+
overflow: hidden;
|
|
1470
|
+
}
|
|
1471
|
+
.rosmd-fade {
|
|
1472
|
+
-webkit-mask-image: linear-gradient(to bottom, black calc(100% - 1.75rem), transparent);
|
|
1473
|
+
mask-image: linear-gradient(to bottom, black calc(100% - 1.75rem), transparent);
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
/* Scrollbar */
|
|
1477
|
+
/* Force visible scrollbars on editable grids (macOS hides them by default) */
|
|
1478
|
+
.grid-scroll::-webkit-scrollbar { width: 10px; height: 10px; }
|
|
1479
|
+
.grid-scroll::-webkit-scrollbar-track { background: #f1f5f9; }
|
|
1480
|
+
.grid-scroll::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 5px; border: 2px solid #f1f5f9; }
|
|
1481
|
+
.grid-scroll::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
|
|
1482
|
+
.grid-scroll::-webkit-scrollbar-corner { background: #f1f5f9; }
|
|
1483
|
+
|
|
1484
|
+
[data-theme="dark"] ::-webkit-scrollbar { width: 8px; height: 8px; }
|
|
1485
|
+
[data-theme="dark"] ::-webkit-scrollbar-track { background: var(--surface-sunken); }
|
|
1486
|
+
[data-theme="dark"] ::-webkit-scrollbar-thumb { background: var(--line-strong); border-radius: 4px; }
|
|
1487
|
+
[data-theme="dark"] ::-webkit-scrollbar-thumb:hover { background: #585b70; }
|
|
1488
|
+
|
|
1489
|
+
/* ── Opacity-modified utilities ───────────────────────────────────────────
|
|
1490
|
+
A Tailwind opacity modifier makes a DIFFERENT class name, so every remap in
|
|
1491
|
+
this sheet — all written against bare class names — stops at the base. The
|
|
1492
|
+
same escape the `!` important-modifier makes for `.\!bg-blue-*` above.
|
|
1493
|
+
|
|
1494
|
+
Everything below is a class whose BASE this sheet already remaps: the colour
|
|
1495
|
+
was already judged to need a dark treatment, and only the modifier escaped.
|
|
1496
|
+
The values are the ones a consumer portal had to hand-write locally and has
|
|
1497
|
+
been shipping, so they are tuned rather than derived: a `/70` of a tint does
|
|
1498
|
+
NOT take 70% of the tint's alpha, because a proportional slice of an already
|
|
1499
|
+
subtle tint disappears. Gray is the exception and does take a real alpha,
|
|
1500
|
+
because there the utility paints a surface rather than a tint over one. */
|
|
1501
|
+
[data-theme="dark"] .bg-gray-50\/70 { background-color: rgba(24, 24, 37, 0.7) !important; }
|
|
1502
|
+
[data-theme="dark"] .bg-gray-50\/80 { background-color: rgba(24, 24, 37, 0.8) !important; }
|
|
1503
|
+
[data-theme="dark"] .bg-gray-50\/95 { background-color: rgba(24, 24, 37, 0.95) !important; }
|
|
1504
|
+
[data-theme="dark"] .bg-gray-100\/90 { background-color: rgba(30, 30, 46, 0.9) !important; }
|
|
1505
|
+
[data-theme="dark"] .bg-gray-200\/80 { background-color: rgba(49, 50, 68, 0.8) !important; }
|
|
1506
|
+
[data-theme="dark"] .hover\:bg-gray-200\/70:hover { background-color: rgba(49, 50, 68, 0.7) !important; }
|
|
1507
|
+
[data-theme="dark"] .hover\:bg-gray-200\/90:hover { background-color: rgba(49, 50, 68, 0.9) !important; }
|
|
1508
|
+
[data-theme="dark"] .border-gray-200\/60 { border-color: rgba(69, 71, 90, 0.6) !important; }
|
|
1509
|
+
[data-theme="dark"] .bg-blue-50\/80 { background-color: rgba(59, 130, 246, 0.24) !important; }
|
|
1510
|
+
[data-theme="dark"] .bg-blue-50\/90 { background-color: rgba(59, 130, 246, 0.26) !important; }
|
|
1511
|
+
[data-theme="dark"] .text-blue-600\/70 { color: rgba(147, 197, 253, 0.7) !important; }
|
|
1512
|
+
[data-theme="dark"] .text-blue-700\/70 { color: rgba(191, 219, 254, 0.7) !important; }
|
|
1513
|
+
[data-theme="dark"] .text-blue-800\/80 { color: rgba(191, 219, 254, 0.8) !important; }
|
|
1514
|
+
[data-theme="dark"] .text-blue-900\/60 { color: rgba(191, 219, 254, 0.6) !important; }
|
|
1515
|
+
[data-theme="dark"] .bg-amber-200\/70 { background-color: rgba(245, 158, 11, 0.20) !important; }
|
|
1516
|
+
[data-theme="dark"] .hover\:bg-amber-50\/70:hover { background-color: rgba(245, 158, 11, 0.12) !important; }
|
|
1517
|
+
[data-theme="dark"] .text-amber-800\/80 { color: rgba(253, 230, 138, 0.8) !important; }
|
|
1518
|
+
[data-theme="dark"] .text-green-700\/80 { color: rgba(134, 239, 172, 0.8) !important; }
|