efficient-shell 4.127.0 → 4.132.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/README.md +938 -5
  2. package/dist/{Browser-F5TKMP4K.js → Browser-XPVJ2K7B.js} +13 -10
  3. package/dist/Browser-XPVJ2K7B.js.map +1 -0
  4. package/dist/{Calculator-T3XG7S73.js → Calculator-ZXJPXG7F.js} +12 -9
  5. package/dist/Calculator-ZXJPXG7F.js.map +1 -0
  6. package/dist/ConfirmDialog-MTC7QMUJ.js +4 -0
  7. package/dist/{ConfirmDialog-LJXMB45A.js.map → ConfirmDialog-MTC7QMUJ.js.map} +1 -1
  8. package/dist/{CurrencyConverter-CASQMKLO.js → CurrencyConverter-WSJ2BBSX.js} +12 -9
  9. package/dist/CurrencyConverter-WSJ2BBSX.js.map +1 -0
  10. package/dist/{Documents-YX67YTZS.js → Documents-ONGSXUJI.js} +14 -11
  11. package/dist/Documents-ONGSXUJI.js.map +1 -0
  12. package/dist/{Files-QB63GO6J.js → Files-GQZMFWXQ.js} +17 -14
  13. package/dist/Files-GQZMFWXQ.js.map +1 -0
  14. package/dist/{Notepad-3SUD5XUE.js → Notepad-HM73XAM4.js} +15 -12
  15. package/dist/Notepad-HM73XAM4.js.map +1 -0
  16. package/dist/PdfViewer-M2YGZIYX.js +5 -0
  17. package/dist/{PdfViewer-LSP3YNFC.js.map → PdfViewer-M2YGZIYX.js.map} +1 -1
  18. package/dist/{PomodoroTimer-4EHD2QC4.js → PomodoroTimer-DG3LI5CX.js} +14 -11
  19. package/dist/PomodoroTimer-DG3LI5CX.js.map +1 -0
  20. package/dist/{Preview-4ZHR4SKP.js → Preview-TUSV4UMO.js} +16 -13
  21. package/dist/Preview-TUSV4UMO.js.map +1 -0
  22. package/dist/{Sidebar-J76PV4QF.js → Sidebar-BPU7RCZP.js} +5 -4
  23. package/dist/Sidebar-BPU7RCZP.js.map +1 -0
  24. package/dist/{Spreadsheet-G3SC7XJ5.js → Spreadsheet-3WTXCHYQ.js} +14 -11
  25. package/dist/Spreadsheet-3WTXCHYQ.js.map +1 -0
  26. package/dist/{Stock-TLT4VN2R.js → Stock-YMPOMUAW.js} +12 -9
  27. package/dist/Stock-YMPOMUAW.js.map +1 -0
  28. package/dist/{Weather-FRKX3DAK.js → Weather-NZO3CWLH.js} +12 -9
  29. package/dist/Weather-NZO3CWLH.js.map +1 -0
  30. package/dist/{WorldClock-GKCITO3M.js → WorldClock-HUC6D3AD.js} +12 -9
  31. package/dist/WorldClock-HUC6D3AD.js.map +1 -0
  32. package/dist/apps/index.js +26 -22
  33. package/dist/apps/index.js.map +1 -1
  34. package/dist/brand.css +1 -1
  35. package/dist/chunk-7IN7YZAL.js +547 -0
  36. package/dist/chunk-7IN7YZAL.js.map +1 -0
  37. package/dist/{chunk-YNGPTOL6.js → chunk-7MA5XBWD.js} +3 -3
  38. package/dist/{chunk-YNGPTOL6.js.map → chunk-7MA5XBWD.js.map} +1 -1
  39. package/dist/{chunk-WK7FV7VV.js → chunk-BJ34QOR3.js} +827 -625
  40. package/dist/chunk-BJ34QOR3.js.map +1 -0
  41. package/dist/{chunk-HNH7FOYB.js → chunk-CGXMWHQW.js} +7 -6
  42. package/dist/chunk-CGXMWHQW.js.map +1 -0
  43. package/dist/{chunk-XUCIYMN7.js → chunk-CJYO4RVV.js} +4 -4
  44. package/dist/{chunk-XUCIYMN7.js.map → chunk-CJYO4RVV.js.map} +1 -1
  45. package/dist/{chunk-N3CG5HG5.js → chunk-CLGNTGC5.js} +12 -11
  46. package/dist/chunk-CLGNTGC5.js.map +1 -0
  47. package/dist/chunk-D276EMXS.js +57 -0
  48. package/dist/chunk-D276EMXS.js.map +1 -0
  49. package/dist/{chunk-7KIOEKHS.js → chunk-E3TWIN7Z.js} +21 -135
  50. package/dist/chunk-E3TWIN7Z.js.map +1 -0
  51. package/dist/{chunk-2VM2FRZJ.js → chunk-EENVXIVU.js} +47 -67
  52. package/dist/chunk-EENVXIVU.js.map +1 -0
  53. package/dist/{chunk-YG45DAUT.js → chunk-FONEQRBH.js} +17 -11
  54. package/dist/chunk-FONEQRBH.js.map +1 -0
  55. package/dist/chunk-IPXN646P.js +14 -0
  56. package/dist/chunk-IPXN646P.js.map +1 -0
  57. package/dist/chunk-IRWSRJ4K.js +48 -0
  58. package/dist/chunk-IRWSRJ4K.js.map +1 -0
  59. package/dist/chunk-JMKYYPOF.js +7 -0
  60. package/dist/{chunk-CJZXR7MG.js.map → chunk-JMKYYPOF.js.map} +1 -1
  61. package/dist/{chunk-JFQGPY7W.js → chunk-KWEX4ZAF.js} +20 -17
  62. package/dist/chunk-KWEX4ZAF.js.map +1 -0
  63. package/dist/{chunk-OKEFO6TI.js → chunk-LQTVTOSY.js} +10 -10
  64. package/dist/chunk-LQTVTOSY.js.map +1 -0
  65. package/dist/{chunk-G4KA3SAX.js → chunk-MQPX6ERJ.js} +4 -4
  66. package/dist/{chunk-G4KA3SAX.js.map → chunk-MQPX6ERJ.js.map} +1 -1
  67. package/dist/{chunk-FQB24B42.js → chunk-SEWF4O3H.js} +3 -3
  68. package/dist/{chunk-FQB24B42.js.map → chunk-SEWF4O3H.js.map} +1 -1
  69. package/dist/{chunk-AXWC3332.js → chunk-TFXJRHJV.js} +3 -3
  70. package/dist/{chunk-AXWC3332.js.map → chunk-TFXJRHJV.js.map} +1 -1
  71. package/dist/{chunk-G3HQLQYZ.js → chunk-TQURTLK2.js} +59 -34
  72. package/dist/chunk-TQURTLK2.js.map +1 -0
  73. package/dist/{chunk-6XQHH7D2.js → chunk-TRCWINHF.js} +5 -5
  74. package/dist/{chunk-6XQHH7D2.js.map → chunk-TRCWINHF.js.map} +1 -1
  75. package/dist/{chunk-NPJCMM6V.js → chunk-VWZIGZFF.js} +4 -4
  76. package/dist/{chunk-NPJCMM6V.js.map → chunk-VWZIGZFF.js.map} +1 -1
  77. package/dist/{chunk-HYZ5QMVO.js → chunk-XH3ELPQZ.js} +22 -19
  78. package/dist/chunk-XH3ELPQZ.js.map +1 -0
  79. package/dist/{chunk-PPUZJFCI.js → chunk-ZKHD4J73.js} +8 -7
  80. package/dist/chunk-ZKHD4J73.js.map +1 -0
  81. package/dist/file-intake/index.js +3 -1
  82. package/dist/file-intake/index.js.map +1 -1
  83. package/dist/{index-dQia6NpB.d.ts → index-COwksV6G.d.ts} +699 -199
  84. package/dist/index.d.ts +2 -2
  85. package/dist/index.js +37 -32
  86. package/dist/index.js.map +1 -1
  87. package/dist/map/index.js +5 -3
  88. package/dist/map/index.js.map +1 -1
  89. package/dist/markdown/index.js +4 -3
  90. package/dist/markdown/index.js.map +1 -1
  91. package/dist/page-tabs/index.js +12 -8
  92. package/dist/page-tabs/index.js.map +1 -1
  93. package/dist/shell.css +17 -17
  94. package/dist/themes.css +15 -0
  95. package/dist/ui/index.d.ts +1 -1
  96. package/dist/ui/index.js +17 -13
  97. package/dist/ui/index.js.map +1 -1
  98. package/dist/ui.css +691 -283
  99. package/package.json +1 -1
  100. package/dist/Browser-F5TKMP4K.js.map +0 -1
  101. package/dist/Calculator-T3XG7S73.js.map +0 -1
  102. package/dist/ConfirmDialog-LJXMB45A.js +0 -3
  103. package/dist/CurrencyConverter-CASQMKLO.js.map +0 -1
  104. package/dist/Documents-YX67YTZS.js.map +0 -1
  105. package/dist/Files-QB63GO6J.js.map +0 -1
  106. package/dist/Notepad-3SUD5XUE.js.map +0 -1
  107. package/dist/PdfViewer-LSP3YNFC.js +0 -4
  108. package/dist/PomodoroTimer-4EHD2QC4.js.map +0 -1
  109. package/dist/Preview-4ZHR4SKP.js.map +0 -1
  110. package/dist/Sidebar-J76PV4QF.js.map +0 -1
  111. package/dist/Spreadsheet-G3SC7XJ5.js.map +0 -1
  112. package/dist/Stock-TLT4VN2R.js.map +0 -1
  113. package/dist/Weather-FRKX3DAK.js.map +0 -1
  114. package/dist/WorldClock-GKCITO3M.js.map +0 -1
  115. package/dist/chunk-2VM2FRZJ.js.map +0 -1
  116. package/dist/chunk-3CUSBZWG.js +0 -59
  117. package/dist/chunk-3CUSBZWG.js.map +0 -1
  118. package/dist/chunk-7KIOEKHS.js.map +0 -1
  119. package/dist/chunk-CJZXR7MG.js +0 -7
  120. package/dist/chunk-G3HQLQYZ.js.map +0 -1
  121. package/dist/chunk-HNH7FOYB.js.map +0 -1
  122. package/dist/chunk-HYZ5QMVO.js.map +0 -1
  123. package/dist/chunk-JFQGPY7W.js.map +0 -1
  124. package/dist/chunk-N3CG5HG5.js.map +0 -1
  125. package/dist/chunk-OKEFO6TI.js.map +0 -1
  126. package/dist/chunk-PPUZJFCI.js.map +0 -1
  127. package/dist/chunk-WK7FV7VV.js.map +0 -1
  128. package/dist/chunk-YG45DAUT.js.map +0 -1
package/dist/ui.css CHANGED
@@ -16,14 +16,31 @@
16
16
  *
17
17
  * Scope — what dark mode actually is here:
18
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
19
+ * - dark overrides of Tailwind UTILITY CLASS NAMES. There are no `dark:`
20
+ * variants in this package; you write the light class and the remap
21
+ * happens to you. Escaped-important classes (.\!bg-blue-50) and alpha
22
22
  * variants (.bg-white\/70) compile to different class names, so each needs
23
23
  * its own rule — that is why the list is long rather than clever.
24
24
  * - the reduce-transparency escape hatch, form-control dark styling, the
25
25
  * coarse-pointer 16px rule that defeats iOS focus-zoom, StatusBadge tones,
26
26
  * and scrollbars.
27
+ *
28
+ * Which element says "dark": `<html data-theme="dark">` — what `useTheme()`
29
+ * stamps — OR `<html class="dark">`, the convention of next-themes, shadcn/ui
30
+ * and Tailwind's own class strategy. Every dark rule is scoped by
31
+ * `:is([data-theme="dark"], .dark)`, which has the specificity of either one
32
+ * alone (0,1,0), so no cascade outcome moved when the alias arrived. An app
33
+ * whose theme switcher sets the class gets the kit's dark mode without a
34
+ * second attribute to keep in step. Keep the two in that one `:is()`; a rule
35
+ * written against only one of them is dark in half the apps.
36
+ * `tests/darkClassAlias.test.ts` holds every dark selector to it.
37
+ *
38
+ * Rules that style BARE ELEMENTS — the dark form controls, the coarse-pointer
39
+ * 16px rule, the dark scrollbars — reach markup the kit did not render. In a
40
+ * portal built on the kit that is the point; in an app with its own design
41
+ * system it restyles the app's inputs. `<html class="rosh-host-elements">`
42
+ * turns those three off and leaves the kit to its own components; see
43
+ * "Bare elements" further down.
27
44
  */
28
45
 
29
46
  @import "./themes.css";
@@ -36,22 +53,29 @@
36
53
  * portal that deletes its own `@theme` block now takes its radius and its
37
54
  * typeface from this package, and one edit here moves all of them.
38
55
  *
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.
56
+ * A consumer's `@theme` wins, WHEREVER it sits relative to this import. That
57
+ * is what `default` buys: Tailwind's own theme is declared `@theme default`,
58
+ * and a default value is replaced by any plain `@theme` declaration of the same
59
+ * key, earlier or later in the cascade, while a later default only replaces an
60
+ * earlier default. So this block overrides Tailwind's defaults (it is later)
61
+ * and yields to the app's own theme (that is not a default at all).
42
62
  *
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.
63
+ * Through 4.127.0 this was a plain `@theme`, and the later declaration won, so
64
+ * import ORDER was the contract: a portal that declared `--font-sans` and then
65
+ * imported the kit below it had its typeface silently reset to the system
66
+ * stack, and its radii to these, with nothing in the diff to explain it. The
67
+ * five EFFICIENT portals all import the kit near the top of `index.css` and
68
+ * declare after it, so for them the change renders nothing. An app that
69
+ * imports its own design system first — a square-cornered one setting every
70
+ * `--radius-*` to 0, say — no longer has to know about the order to keep it.
71
+ * `tests/themeDefaultYields.test.ts` compiles both orders with Tailwind.
48
72
  *
49
73
  * On type: the token is here, the TYPEFACE is not. Shipping a face means
50
74
  * hosting and licensing it, which is a product decision rather than a
51
75
  * packaging one. Until that is made, a portal that sets nothing gets the
52
76
  * system stack — the same thing it got before this block existed.
53
77
  */
54
- @theme {
78
+ @theme default {
55
79
  --radius-sm: 0.25rem;
56
80
  --radius-md: 0.375rem;
57
81
  --radius-lg: 0.5rem;
@@ -125,12 +149,16 @@ html.rosh-reduce-transparency *::after {
125
149
  * That is the same shape the --viz-* palette further down already gets right:
126
150
  * declared for both themes, read by role.
127
151
  *
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`.
152
+ * Adding light values was render-neutral when it landed — no rule outside a
153
+ * `[data-theme="dark"]` scope read any of these twelve. It exists so a surface
154
+ * built WITHOUT the kit's utility classes — a report, an emailed document, a
155
+ * standalone page — can read the same roles the portals do. That surface is
156
+ * governed by harness 52-brand-surface.md and loads `react-os-shell/brand.css`.
157
+ *
158
+ * They are load-bearing in light now: the timeline card is drawn from them in
159
+ * both themes, and the frosted glass is tinted from `--surface` (through
160
+ * `--rosh-glass-surface`, below). Change a light value and those move with it,
161
+ * which is the point — it is how a host gives the kit its own neutrals.
134
162
  *
135
163
  * The values are the Tailwind steps the dark block opposite already remaps:
136
164
  * white/gray-50/gray-100 surfaces, gray-100/200/300 lines, gray-900 down to
@@ -154,7 +182,7 @@ html.rosh-reduce-transparency *::after {
154
182
  /* ═══════════════════════════════════════════════════
155
183
  Theme: Dark
156
184
  ═══════════════════════════════════════════════════ */
157
- [data-theme="dark"] {
185
+ :is([data-theme="dark"], .dark) {
158
186
  color-scheme: dark;
159
187
 
160
188
  /*
@@ -237,7 +265,7 @@ html.rosh-reduce-transparency *::after {
237
265
  --viz-surface: #ffffff;
238
266
  }
239
267
 
240
- [data-theme="dark"] {
268
+ :is([data-theme="dark"], .dark) {
241
269
  --viz-series-1: #3987e5;
242
270
  --viz-series-2: #d95926;
243
271
  --viz-series-3: #199e70;
@@ -337,7 +365,7 @@ html.rosh-reduce-transparency *::after {
337
365
  }
338
366
 
339
367
  /* Only the ink moves. The washes above hold, because they are transparent. */
340
- [data-theme="dark"] {
368
+ :is([data-theme="dark"], .dark) {
341
369
  --status-success-soft-ink: #86efac;
342
370
  --status-active-soft-ink: #93c5fd;
343
371
  --status-queued-soft-ink: #a5b4fc;
@@ -428,6 +456,209 @@ html.rosh-reduce-transparency *::after {
428
456
  --tl-soft: color-mix(in srgb, var(--tl-accent) 16%, transparent);
429
457
  }
430
458
 
459
+ /* ── Focus ──────────────────────────────────────────────────────────────────
460
+ * Every focus indicator the kit draws, as tokens, and the one switch that hands
461
+ * focus to the host app instead (`rosh-host-focus`, the next block down).
462
+ *
463
+ * ON `:focus-visible`, NOT `:focus`. The kit's controls used to ring on
464
+ * `:focus`, which a mouse click satisfies, so every Button, Checkbox and Switch
465
+ * drew its ring when it was clicked — and a host whose own focus rule keys on
466
+ * `:focus-visible`, the state the browser draws its own outline in, could not
467
+ * make the two agree. A text field matches `:focus-visible` on a click as well:
468
+ * it takes typing, so the browser always shows where the caret went. That is why
469
+ * Input, Textarea, SearchableSelect and the rest look exactly as they did. What
470
+ * changed is the controls a click does not type into: they no longer ring on a
471
+ * click, and ring as they always did on Tab.
472
+ *
473
+ * THE COLOURS ARE TOKENS, and each default is character for character what the
474
+ * Tailwind utility it replaced compiles to — `focus:ring-blue-400/30` is
475
+ * `color-mix(in oklab, var(--color-blue-400) 30%, transparent)`. Tailwind gives
476
+ * the token the same `@supports` fallback it gives the utility, because it
477
+ * polyfills `color-mix()` in any CSS it builds, this file included.
478
+ * `tests/focusVisible.test.ts` compiles both and compares them.
479
+ *
480
+ * Nine looks a lot for one ring. It is what the kit actually draws, and folding
481
+ * two of them together would have been a restyle shipped as a refactor:
482
+ *
483
+ * --rosh-focus-ring blue-400 at 30% a field; an outlined or
484
+ * quiet Button; Checkbox; Radio
485
+ * --rosh-focus-ring-strong blue-400 at 40% a filled control: the primary
486
+ * Button, Switch, TileButton,
487
+ * a badge's close
488
+ * --rosh-focus-ring-solid blue-400 a control with no edge of its
489
+ * own: a dialog's close, a
490
+ * calendar day, a table row, a
491
+ * crumb, a step, a menu trigger
492
+ * --rosh-focus-ring-deep blue-500 the chart family (plot, legend,
493
+ * table), a chip's remove, the
494
+ * date-range field
495
+ * --rosh-focus-ring-accent blue-500 HelpCenter's search and the
496
+ * prompt dialog's field: the two
497
+ * rings the accent themes always
498
+ * recoloured (themes.css still does)
499
+ * --rosh-focus-ring-zone #3b82f6 at 45% the upload zones and their
500
+ * thumbnails, which drew theirs
501
+ * inline, in Tailwind v3's blue
502
+ * --rosh-focus-ring-invalid red-400 at 30% an invalid field; ghost-danger
503
+ * --rosh-focus-ring-invalid-strong red-400 at 40% the danger Button
504
+ * --rosh-focus-ring-invalid-solid red-400 the form's error summary
505
+ *
506
+ * No dark step. No focus ring was ever remapped for dark, and the translucent
507
+ * ones composite with whatever surface is under them — the argument the `-soft`
508
+ * status washes make.
509
+ */
510
+ :root {
511
+ --rosh-focus-ring: color-mix(in oklab, var(--color-blue-400) 30%, transparent);
512
+ --rosh-focus-ring-strong: color-mix(in oklab, var(--color-blue-400) 40%, transparent);
513
+ --rosh-focus-ring-solid: var(--color-blue-400);
514
+ --rosh-focus-ring-deep: var(--color-blue-500);
515
+ --rosh-focus-ring-accent: var(--color-blue-500);
516
+ --rosh-focus-ring-zone: rgba(59, 130, 246, 0.45);
517
+ --rosh-focus-ring-invalid: color-mix(in oklab, var(--color-red-400) 30%, transparent);
518
+ --rosh-focus-ring-invalid-strong: color-mix(in oklab, var(--color-red-400) 40%, transparent);
519
+ --rosh-focus-ring-invalid-solid: var(--color-red-400);
520
+ }
521
+
522
+ /*
523
+ * THE MARKERS. A ring is a box-shadow, so the browser's own outline still has
524
+ * to come off the control that wears one — that was the `focus:outline-none`
525
+ * utility. A utility cannot be taken back: a host's `@layer base` rule loses to
526
+ * it whatever the host writes. So the suppression lives here, on the
527
+ * `rosh-focus-ring` class every such control carries, where a selector can stop
528
+ * matching. `rosh-focus-outline` is the same idea for the two chart controls
529
+ * that draw an outline rather than a ring.
530
+ *
531
+ * `@layer components`, for two reasons that pull opposite ways. It has to
532
+ * outrank a host's own `@layer base` focus rule while the kit is drawing — the
533
+ * utility did, and a host that has not opted in must see no change. And it has
534
+ * to lose to a utility the caller passes through `className`, as anything that
535
+ * is not a utility should.
536
+ */
537
+ @layer components {
538
+ .rosh-focus-ring:focus-visible:not(.rosh-host-focus *) {
539
+ outline-style: none;
540
+ }
541
+ .rosh-focus-outline:focus-visible:not(.rosh-host-focus *) {
542
+ outline: 2px solid var(--rosh-focus-ring-deep);
543
+ outline-offset: 2px;
544
+ }
545
+ }
546
+
547
+ /* ── Host-owned focus ───────────────────────────────────────────────────────
548
+ * `<html class="rosh-host-focus">` — or the class on any ancestor of the kit —
549
+ * hands focus to the host app. The kit then draws no ring and no outline of its
550
+ * own, and takes nothing away: whatever `:focus-visible` rule the host has (an
551
+ * outline in its own `--ring`, say, in its `@layer base`) is what every kit
552
+ * control shows. The timeline family's outlines step aside the same way.
553
+ *
554
+ * WHY A CLASS AND NOT JUST TOKENS. A token can make the kit's ring invisible,
555
+ * but it cannot give the outline back. No value of a custom property means
556
+ * "whatever the host's rule says" — `revert-layer` written into a variable
557
+ * reverts the variable, it is never substituted — so the suppression above
558
+ * would still set `outline-style` and still win. A selector that stops matching
559
+ * is the only thing that can step aside, and one class is the smallest one.
560
+ *
561
+ * Under it every ring token is transparent rather than gone, so a ring utility
562
+ * still resolves and simply paints nothing; the box-shadow a control already
563
+ * wears (`shadow-sm`) is untouched. `!important` for the reason the
564
+ * reduce-transparency block gives: it beats the accent themes' remap of
565
+ * `--rosh-focus-ring-accent` and a host's own `:root` value, whichever loads
566
+ * last.
567
+ */
568
+ .rosh-host-focus {
569
+ --rosh-focus-ring: transparent !important;
570
+ --rosh-focus-ring-strong: transparent !important;
571
+ --rosh-focus-ring-solid: transparent !important;
572
+ --rosh-focus-ring-deep: transparent !important;
573
+ --rosh-focus-ring-accent: transparent !important;
574
+ --rosh-focus-ring-zone: transparent !important;
575
+ --rosh-focus-ring-invalid: transparent !important;
576
+ --rosh-focus-ring-invalid-strong: transparent !important;
577
+ --rosh-focus-ring-invalid-solid: transparent !important;
578
+ }
579
+
580
+ /* ── Kit colours ────────────────────────────────────────────────────────────
581
+ * Every colour a kit component used to write as a literal in its TypeScript,
582
+ * as a token a host can reach. `tests/kitColourLiterals.test.ts` walks
583
+ * everything `react-os-shell/ui` reaches and fails on a hex, `rgb()`, `hsl()` or
584
+ * any other colour function written into a string; the allow-list beside it
585
+ * names the few literals that are data rather than chrome.
586
+ *
587
+ * Each default is the literal it replaced, so nothing renders differently.
588
+ * Where a role from the ramp already said the same thing, the component reads
589
+ * the role and no token was added: the frosted glass was always `--surface`
590
+ * (white in light, #1e1e2e in dark) with `--surface-sunken` across its middle
591
+ * in dark, written out as `rgba()` channels.
592
+ *
593
+ * The values that differ in dark are in the block after this one, kept apart
594
+ * so a selector rewrite of the dark theme touches one short block.
595
+ */
596
+ :root {
597
+ /* glassStyle() — every menu, popup, listbox and tooltip in the kit. The
598
+ surface a popup is tinted from (frosted) or painted in (solid), and the
599
+ stop across the middle of its gradient. Both chain to the ramp, so a host
600
+ that restyles `--surface` moves its popups with it; one that wants its
601
+ popups in a colour of their own sets `--rosh-glass-surface` alone. */
602
+ --rosh-glass-surface: var(--surface);
603
+ --rosh-glass-surface-mid: var(--surface);
604
+ /* The frosted edge and its depth: a white hairline and an inner highlight
605
+ over two soft drop shadows. */
606
+ --rosh-glass-border: rgba(255, 255, 255, 0.35);
607
+ --rosh-glass-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.4), inset 0 -1px 0 rgba(255, 255, 255, 0.1),
608
+ 0 8px 32px rgba(0, 0, 0, 0.15), 0 2px 8px rgba(0, 0, 0, 0.08);
609
+ /* The same popup under `rosh-reduce-transparency`, where it is one opaque
610
+ `--rosh-glass-surface` with no blur: a plain hairline and one shadow. */
611
+ --rosh-glass-solid-border: rgba(0, 0, 0, 0.12);
612
+ --rosh-glass-solid-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
613
+
614
+ /* Over a picture — the upload thumbnails. A photograph has no theme, so
615
+ neither has the dark wash white text and glyphs sit on over it: the
616
+ caption strip, and the remove button a shade lighter. */
617
+ --rosh-media-scrim: rgba(0, 0, 0, 0.6);
618
+ --rosh-media-scrim-button: rgba(0, 0, 0, 0.55);
619
+ /* MediaUploadGrid's halo while a file is dragged over it. */
620
+ --rosh-drop-ring: rgba(59, 130, 246, 0.5);
621
+
622
+ /* BrandMark's plates: the light plate and the framed treatment share one
623
+ shadow, and the dark plate takes a deeper one to read as lifted. */
624
+ --rosh-mark-plate-shadow: rgba(0, 0, 0, 0.18);
625
+ --rosh-mark-plate-shadow-strong: rgba(0, 0, 0, 0.25);
626
+
627
+ /* SidebarLayout's resize handle under the pointer, when no custom accent is
628
+ set — the custom accent's `--accent-500` still wins, read where it is set. */
629
+ --rosh-resize-handle: #3b82f6;
630
+
631
+ /* DonutChart's default segment colours, in assignment order, and its track.
632
+ NOT the `--viz-series-*` palette above: the donut predates it and its
633
+ callers have been showing these hues, so this is the same eight in the
634
+ same order. A host that wants one palette across every chart points these
635
+ at the series tokens. The track is a translucent slate, read over either
636
+ theme, which is why nothing here has a dark step. */
637
+ --rosh-donut-1: #3b82f6;
638
+ --rosh-donut-2: #22c55e;
639
+ --rosh-donut-3: #f59e0b;
640
+ --rosh-donut-4: #ef4444;
641
+ --rosh-donut-5: #6366f1;
642
+ --rosh-donut-6: #14b8a6;
643
+ --rosh-donut-7: #a855f7;
644
+ --rosh-donut-8: #64748b;
645
+ --rosh-donut-track: rgba(148, 163, 184, 0.25);
646
+ }
647
+
648
+ /* ── Kit colours, dark ──────────────────────────────────────────────────────
649
+ * Only the glass moves. `--rosh-glass-surface` needs no line here: it reads
650
+ * `--surface`, which the dark ramp already swaps on the same element. Scoped
651
+ * like every dark rule once the kit follows a `dark` class as well as
652
+ * `data-theme` (feat/ui-dark-class): `:is()` has the specificity of either. */
653
+ :is([data-theme="dark"], .dark) {
654
+ --rosh-glass-surface-mid: var(--surface-sunken);
655
+ --rosh-glass-border: rgba(255, 255, 255, 0.08);
656
+ --rosh-glass-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06), inset 0 -1px 0 rgba(0, 0, 0, 0.4),
657
+ 0 8px 32px rgba(0, 0, 0, 0.5), 0 2px 8px rgba(0, 0, 0, 0.3);
658
+ --rosh-glass-solid-border: rgba(255, 255, 255, 0.12);
659
+ --rosh-glass-solid-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
660
+ }
661
+
431
662
  /* ── Budget overrun hatch ───────────────────────────────────────────────────
432
663
  * `BudgetBar`'s third drawing. A run past its wall clock must not be pictured
433
664
  * as a full bar: a full solid bar is the calm "finished" shape, and this is the
@@ -810,7 +1041,11 @@ html.rosh-reduce-transparency *::after {
810
1041
  --rosh-tl-sc: 1.5;
811
1042
  box-shadow: 0 0 0 2px var(--surface-sunken), 0 0 0 5px var(--tl-soft);
812
1043
  }
813
- .rosh-tl-node:focus-visible, .rosh-tl-pill:focus-visible, .rosh-tl-thumb:focus-visible, .rosh-tl-more:focus-visible {
1044
+ /* The track's own focus outline, in the accent. It steps aside under
1045
+ `rosh-host-focus` (see "Host-owned focus") so the host's rule is what a
1046
+ reader sees on these as on every other control. */
1047
+ .rosh-tl-node:focus-visible:not(.rosh-host-focus *), .rosh-tl-pill:focus-visible:not(.rosh-host-focus *),
1048
+ .rosh-tl-thumb:focus-visible:not(.rosh-host-focus *), .rosh-tl-more:focus-visible:not(.rosh-host-focus *) {
814
1049
  outline: 2px solid var(--tl-focus); outline-offset: 3px;
815
1050
  }
816
1051
  .rosh-tl-pulse {
@@ -898,7 +1133,7 @@ html.rosh-reduce-transparency *::after {
898
1133
  font: inherit; font-size: 11px; font-weight: 500;
899
1134
  }
900
1135
  .rosh-tl-bubble-open:hover { text-decoration: underline; }
901
- .rosh-tl-bubble-open:focus-visible { outline: 2px solid var(--tl-focus); outline-offset: 2px; }
1136
+ .rosh-tl-bubble-open:focus-visible:not(.rosh-host-focus *) { outline: 2px solid var(--tl-focus); outline-offset: 2px; }
902
1137
 
903
1138
  .rosh-tl-popover-head {
904
1139
  margin: 0 0 5px; font-size: 10px; font-weight: 500; letter-spacing: 0.06em;
@@ -953,7 +1188,10 @@ html.rosh-reduce-transparency *::after {
953
1188
  font: inherit; font-size: 11px; font-weight: 500; line-height: 1.25;
954
1189
  }
955
1190
  .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; }
1191
+ /* The radius shapes whichever outline is drawn, the host's included, so it is
1192
+ not behind the host-focus guard the outline itself is. */
1193
+ .rosh-tl-open:focus-visible { border-radius: 4px; }
1194
+ .rosh-tl-open:focus-visible:not(.rosh-host-focus *) { outline: 2px solid var(--tl-focus); outline-offset: 2px; }
957
1195
  .rosh-tl-thumb:active { cursor: grabbing; }
958
1196
  .rosh-tl-thumb::after { content: ""; position: absolute; inset: 3px; border-radius: 50%; background: currentColor; }
959
1197
  .rosh-tl-chip {
@@ -1095,140 +1333,173 @@ html.rosh-reduce-transparency *::after {
1095
1333
  }
1096
1334
 
1097
1335
  /* 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; }
1336
+ :is([data-theme="dark"], .dark) .bg-white { background-color: var(--surface) !important; }
1337
+ :is([data-theme="dark"], .dark) .bg-gray-50 { background-color: var(--surface-sunken) !important; }
1338
+ :is([data-theme="dark"], .dark) .bg-gray-100 { background-color: var(--surface) !important; }
1339
+ :is([data-theme="dark"], .dark) .bg-gray-200 { background-color: var(--surface-raised) !important; }
1102
1340
 
1103
1341
  /* 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; }
1342
+ :is([data-theme="dark"], .dark) .text-gray-900 { color: var(--ink-strongest) !important; }
1343
+ :is([data-theme="dark"], .dark) .text-gray-800 { color: var(--ink-strong) !important; }
1344
+ :is([data-theme="dark"], .dark) .text-gray-700 { color: var(--ink) !important; }
1345
+ :is([data-theme="dark"], .dark) .text-gray-600 { color: var(--ink-muted) !important; }
1346
+ :is([data-theme="dark"], .dark) .text-gray-500 { color: var(--ink-faint) !important; }
1347
+ :is([data-theme="dark"], .dark) .text-gray-400 { color: var(--ink-faintest) !important; }
1110
1348
 
1111
1349
  /* 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; }
1350
+ :is([data-theme="dark"], .dark) .border-gray-200 { border-color: var(--line) !important; }
1351
+ :is([data-theme="dark"], .dark) .border-gray-300 { border-color: var(--line-strong) !important; }
1352
+ :is([data-theme="dark"], .dark) .border-gray-100 { border-color: var(--line-subtle) !important; }
1115
1353
 
1116
1354
  /* Rings. `ring-gray-100` is a cut-out rather than a line: it rings a mark in
1117
1355
  the colour of the `bg-gray-100` surface under it (PageTabBar's unsaved dot,
1118
1356
  on the tab strip), so it takes that surface's token. Without this it kept
1119
1357
  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; }
1358
+ :is([data-theme="dark"], .dark) .ring-gray-100 { --tw-ring-color: var(--surface) !important; }
1121
1359
 
1122
1360
  /* 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; }
1361
+ :is([data-theme="dark"], .dark) .shadow { --tw-shadow-color: rgba(0,0,0,0.4) !important; }
1362
+ :is([data-theme="dark"], .dark) .shadow-lg { --tw-shadow-color: rgba(0,0,0,0.5) !important; }
1363
+ :is([data-theme="dark"], .dark) .shadow-xl { --tw-shadow-color: rgba(0,0,0,0.5) !important; }
1364
+ :is([data-theme="dark"], .dark) .shadow-2xl { --tw-shadow-color: rgba(0,0,0,0.6) !important; }
1365
+ :is([data-theme="dark"], .dark) .divide-gray-200 > :not(:last-child) { border-color: var(--line) !important; }
1366
+ :is([data-theme="dark"], .dark) .divide-gray-100 > :not(:last-child) { border-color: var(--line-subtle) !important; }
1129
1367
 
1130
1368
  /* 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; }
1369
+ :is([data-theme="dark"], .dark) .hover\:bg-gray-50:hover { background-color: var(--line-subtle) !important; }
1370
+ :is([data-theme="dark"], .dark) .hover\:bg-gray-100:hover { background-color: var(--surface-raised) !important; }
1371
+ :is([data-theme="dark"], .dark) .hover\:bg-gray-200:hover { background-color: var(--line-strong) !important; }
1372
+
1373
+ /* ── The accent, in dark ───────────────────────────────────────────────────
1374
+ * The kit's accent is Tailwind's blue. In light it is painted with the blue
1375
+ * utilities themselves, so a host that maps `--color-blue-*` onto its own accent
1376
+ * recolours it. In dark these remaps paint it instead, and they used to write
1377
+ * blue as literals, so a host's accent turned blue at night. They read these four
1378
+ * now. Channels rather than colours, because most of them are washes at an alpha
1379
+ * of their own (`rgba(var(--rosh-dark-accent-rgb), 0.26)`).
1380
+ *
1381
+ * Every default is the literal it replaced (blue-500 / blue-400 / blue-300 /
1382
+ * blue-200), so nothing renders differently for a host that sets none.
1383
+ * `tests/darkAccentTokens.test.ts` holds every dark blue rule to them. */
1384
+ :root {
1385
+ --rosh-dark-accent-rgb: 59, 130, 246;
1386
+ --rosh-dark-accent-line-rgb: 96, 165, 250;
1387
+ --rosh-dark-accent-ink-rgb: 147, 197, 253;
1388
+ --rosh-dark-accent-ink-strong-rgb: 191, 219, 254;
1389
+ }
1134
1390
 
1135
1391
  /* Blue hover states — used by menu items, profile row, etc.
1136
1392
  In dark mode swap the near-white bg for a dark blue tint and the dark
1137
1393
  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; }
1394
+ :is([data-theme="dark"], .dark) .bg-blue-50,
1395
+ :is([data-theme="dark"], .dark) .hover\:bg-blue-50:hover { background-color: rgba(var(--rosh-dark-accent-rgb), 0.26) !important; }
1396
+ :is([data-theme="dark"], .dark) .text-blue-700,
1397
+ :is([data-theme="dark"], .dark) .hover\:text-blue-700:hover { color: rgb(var(--rosh-dark-accent-ink-strong-rgb)) !important; }
1398
+ :is([data-theme="dark"], .dark) .text-blue-600 { color: rgb(var(--rosh-dark-accent-ink-rgb)) !important; }
1399
+ :is([data-theme="dark"], .dark) .text-blue-800 { color: rgb(var(--rosh-dark-accent-ink-strong-rgb)) !important; }
1144
1400
 
1145
1401
  /* Bare *-100 highlight backgrounds + *-700 text (status pills, badges, avatars,
1146
1402
  dialog icons) have no dark override otherwise, so they keep their bright
1147
1403
  light-mode value and wash out the text on top. Mute the background to a low
1148
1404
  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; }
1405
+ :is([data-theme="dark"], .dark) .bg-blue-100 { background-color: rgba(var(--rosh-dark-accent-rgb), 0.22) !important; }
1150
1406
 
1151
1407
  /* Tailwind's `!` important modifier compiles to distinct class names
1152
1408
  (`.\!bg-blue-100` etc.), so the EditableGrid selection utilities escape the
1153
1409
  remaps above: the selection keeps its light-mode background while the cell
1154
1410
  text goes light, making selected cells unreadable in dark mode. The
1155
1411
  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; }
1412
+ :is([data-theme="dark"], .dark) .\!bg-blue-50 { background-color: rgba(var(--rosh-dark-accent-rgb), 0.26) !important; }
1413
+ :is([data-theme="dark"], .dark) .\!bg-blue-100 { background-color: rgba(var(--rosh-dark-accent-rgb), 0.22) !important; }
1414
+ :is([data-theme="dark"], .dark) .\!bg-blue-200 { background-color: rgba(var(--rosh-dark-accent-rgb), 0.35) !important; }
1415
+ :is([data-theme="dark"], .dark) .\!text-gray-700 { color: var(--ink) !important; }
1416
+ :is([data-theme="dark"], .dark) .bg-green-100 { background-color: rgba(34, 197, 94, 0.18) !important; }
1417
+ :is([data-theme="dark"], .dark) .bg-emerald-100 { background-color: rgba(16, 185, 129, 0.18) !important; }
1418
+ :is([data-theme="dark"], .dark) .bg-sky-100 { background-color: rgba(14, 165, 233, 0.18) !important; }
1419
+ :is([data-theme="dark"], .dark) .bg-amber-100 { background-color: rgba(245, 158, 11, 0.18) !important; }
1420
+ :is([data-theme="dark"], .dark) .bg-yellow-100 { background-color: rgba(234, 179, 8, 0.18) !important; }
1421
+ :is([data-theme="dark"], .dark) .bg-orange-100 { background-color: rgba(249, 115, 22, 0.18) !important; }
1422
+ :is([data-theme="dark"], .dark) .bg-red-100 { background-color: rgba(239, 68, 68, 0.18) !important; }
1423
+ :is([data-theme="dark"], .dark) .bg-indigo-100 { background-color: rgba(99, 102, 241, 0.18) !important; }
1424
+ :is([data-theme="dark"], .dark) .bg-purple-100 { background-color: rgba(168, 85, 247, 0.18) !important; }
1425
+ :is([data-theme="dark"], .dark) .text-green-700 { color: #86efac !important; }
1170
1426
  /* The /70 form is a separate class, so the remap above misses it. The amber
1171
1427
  block already covers its own /30../50 tints; green did not, and dark green
1172
1428
  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; }
1429
+ :is([data-theme="dark"], .dark) .text-green-700\/70 { color: rgba(134, 239, 172, 0.7) !important; }
1430
+ :is([data-theme="dark"], .dark) .text-emerald-700 { color: #6ee7b7 !important; }
1431
+ :is([data-theme="dark"], .dark) .text-sky-700 { color: #7dd3fc !important; }
1432
+ :is([data-theme="dark"], .dark) .text-amber-700 { color: #fcd34d !important; }
1433
+ :is([data-theme="dark"], .dark) .text-yellow-700 { color: #fde047 !important; }
1434
+ :is([data-theme="dark"], .dark) .text-orange-700 { color: #fdba74 !important; }
1435
+ :is([data-theme="dark"], .dark) .text-red-700 { color: #fca5a5 !important; }
1436
+ :is([data-theme="dark"], .dark) .text-indigo-700 { color: #a5b4fc !important; }
1437
+ :is([data-theme="dark"], .dark) .text-purple-700 { color: #d8b4fe !important; }
1182
1438
 
1183
1439
  /* Brighten taskbar button text so it stays readable on dark glass.
1184
1440
  The default override (var(--ink-muted)) is too dim for short button labels. */
1185
- [data-theme="dark"] .text-gray-600 { color: var(--ink) !important; }
1441
+ :is([data-theme="dark"], .dark) .text-gray-600 { color: var(--ink) !important; }
1186
1442
 
1187
1443
  /* Translucent button backgrounds — Tailwind generates separate classes for the
1188
1444
  /40 /60 alpha variants (bg-gray-50\/40 etc.) so the bare .bg-gray-50 override
1189
1445
  doesn't match. Override them explicitly with dark equivalents so taskbar
1190
1446
  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; }
1447
+ :is([data-theme="dark"], .dark) .bg-gray-50\/40 { background-color: rgba(24, 24, 37, 0.4) !important; }
1192
1448
  /* /50 and /60 are used as in-window PANEL surfaces (sidebars, form sections,
1193
1449
  popup cards) — keep them solid in dark so a bright wallpaper can't bleed
1194
1450
  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; }
1451
+ :is([data-theme="dark"], .dark) .bg-gray-50\/50 { background-color: var(--surface-sunken) !important; }
1452
+ :is([data-theme="dark"], .dark) .bg-gray-50\/60 { background-color: var(--surface-sunken) !important; }
1453
+ :is([data-theme="dark"], .dark) .hover\:bg-gray-50\/50:hover { background-color: var(--surface-sunken) !important; }
1454
+ :is([data-theme="dark"], .dark) .bg-gray-100\/40 { background-color: rgba(30, 30, 46, 0.4) !important; }
1455
+ :is([data-theme="dark"], .dark) .bg-gray-100\/60 { background-color: rgba(30, 30, 46, 0.6) !important; }
1456
+ :is([data-theme="dark"], .dark) .bg-gray-200\/40 { background-color: rgba(49, 50, 68, 0.4) !important; }
1457
+ :is([data-theme="dark"], .dark) .bg-gray-200\/50 { background-color: rgba(49, 50, 68, 0.5) !important; }
1458
+ :is([data-theme="dark"], .dark) .bg-blue-100\/60 { background-color: rgba(var(--rosh-dark-accent-rgb), 0.22) !important; }
1203
1459
  /* Pale-blue tinted panels (selected rows, inline-reply box) — scale the
1204
1460
  .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; }
1461
+ :is([data-theme="dark"], .dark) .bg-blue-50\/30 { background-color: rgba(var(--rosh-dark-accent-rgb), 0.10) !important; }
1462
+ :is([data-theme="dark"], .dark) .bg-blue-50\/40 { background-color: rgba(var(--rosh-dark-accent-rgb), 0.12) !important; }
1463
+ :is([data-theme="dark"], .dark) .bg-blue-50\/50 { background-color: rgba(var(--rosh-dark-accent-rgb), 0.15) !important; }
1464
+ :is([data-theme="dark"], .dark) .bg-blue-50\/60 { background-color: rgba(var(--rosh-dark-accent-rgb), 0.18) !important; }
1465
+ :is([data-theme="dark"], .dark) .border-gray-200\/40 { border-color: rgba(69, 71, 90, 0.4) !important; }
1466
+ :is([data-theme="dark"], .dark) .border-gray-300\/40 { border-color: rgba(88, 91, 112, 0.4) !important; }
1467
+ :is([data-theme="dark"], .dark) .border-blue-400\/60 { border-color: rgba(var(--rosh-dark-accent-line-rgb), 0.45) !important; }
1468
+ :is([data-theme="dark"], .dark) .hover\:bg-gray-200\/40:hover { background-color: rgba(49, 50, 68, 0.4) !important; }
1213
1469
 
1214
1470
  /* White alpha variants — the bare .bg-white override doesn't match the /50../95
1215
1471
  alpha classes, so cards (the Widget gallery), the Exposé snapshot title pill
1216
1472
  and desktop overlays stayed bright white in dark mode while their (remapped,
1217
1473
  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
1474
+ :is([data-theme="dark"], .dark) .bg-white\/50 { background-color: rgba(30, 30, 46, 0.5) !important; }
1475
+ :is([data-theme="dark"], .dark) .bg-white\/60 { background-color: rgba(30, 30, 46, 0.6) !important; }
1476
+ :is([data-theme="dark"], .dark) .bg-white\/70 { background-color: rgba(30, 30, 46, 0.7) !important; }
1477
+ :is([data-theme="dark"], .dark) .bg-white\/80 { background-color: rgba(30, 30, 46, 0.85) !important; }
1478
+ :is([data-theme="dark"], .dark) .bg-white\/90 { background-color: rgba(30, 30, 46, 0.92) !important; }
1479
+ :is([data-theme="dark"], .dark) .bg-white\/95 { background-color: rgba(30, 30, 46, 0.95) !important; }
1480
+ :is([data-theme="dark"], .dark) .bg-blue-50\/70 { background-color: rgba(var(--rosh-dark-accent-rgb), 0.22) !important; }
1481
+
1482
+ /* ── Bare elements ─────────────────────────────────────────────────────────
1483
+ The next three rules style elements by TAG — every input, select and
1484
+ textarea, every scrollbar — so they reach markup the kit never rendered. In
1485
+ a portal built on the kit that is the point: a hand-written input goes dark
1486
+ with the rest. In an app with its own design system it is a restyle of that
1487
+ app's controls (and on a tablet, a 13px console field jumping to 16px).
1488
+
1489
+ `<html class="rosh-host-elements">` turns all three off. The gate is
1490
+ `:where(:root:not(.rosh-host-elements) *)` on the ELEMENT, not on the dark
1491
+ ancestor, so it holds wherever the theme is stamped, and `:where()` adds no
1492
+ specificity — without the class, every one of these matches exactly what it
1493
+ matched before. The kit's own controls stay dark under the class: their
1494
+ utility classes (bg-white, border-gray-300, text-gray-800) are remapped like
1495
+ any other.
1496
+
1497
+ Inputs — apply the form-input dark styling, but skip elements that
1227
1498
  explicitly opt into a transparent background (e.g. the start-menu
1228
1499
  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) {
1500
+ :is([data-theme="dark"], .dark) input:not(.bg-transparent):not([type="checkbox"]):not([type="radio"]):not([type="color"]):not([type="file"]):where(:root:not(.rosh-host-elements) *),
1501
+ :is([data-theme="dark"], .dark) select:not(.bg-transparent):where(:root:not(.rosh-host-elements) *),
1502
+ :is([data-theme="dark"], .dark) textarea:not(.bg-transparent):where(:root:not(.rosh-host-elements) *) {
1232
1503
  background-color: var(--surface-sunken) !important;
1233
1504
  border-color: var(--line-strong) !important;
1234
1505
  color: var(--ink-strongest) !important;
@@ -1242,23 +1513,23 @@ html.rosh-reduce-transparency *::after {
1242
1513
  utilities; the :not() list mirrors the dark-input rule above and skips
1243
1514
  controls that don't take a text caret. */
1244
1515
  @media (max-width: 767px), (pointer: coarse) {
1245
- input:not([type="checkbox"]):not([type="radio"]):not([type="color"]):not([type="file"]),
1246
- select,
1247
- textarea {
1516
+ input:not([type="checkbox"]):not([type="radio"]):not([type="color"]):not([type="file"]):where(:root:not(.rosh-host-elements) *),
1517
+ select:where(:root:not(.rosh-host-elements) *),
1518
+ textarea:where(:root:not(.rosh-host-elements) *) {
1248
1519
  font-size: 16px !important;
1249
1520
  }
1250
1521
  }
1251
1522
 
1252
1523
  /* 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; }
1524
+ :is([data-theme="dark"], .dark) .bg-gray-100.text-gray-800 { background-color: var(--surface-raised) !important; color: var(--ink) !important; }
1525
+ :is([data-theme="dark"], .dark) .bg-gray-300.text-gray-800 { background-color: var(--line-strong) !important; color: var(--ink-strongest) !important; }
1526
+ :is([data-theme="dark"], .dark) .bg-green-100.text-green-800 { background-color: rgba(34, 197, 94, 0.18) !important; color: #86efac !important; }
1527
+ :is([data-theme="dark"], .dark) .bg-blue-100.text-blue-800 { background-color: rgba(var(--rosh-dark-accent-rgb), 0.18) !important; color: rgb(var(--rosh-dark-accent-ink-rgb)) !important; }
1528
+ :is([data-theme="dark"], .dark) .bg-indigo-100.text-indigo-800 { background-color: rgba(99, 102, 241, 0.18) !important; color: #a5b4fc !important; }
1529
+ :is([data-theme="dark"], .dark) .bg-sky-100.text-sky-800 { background-color: rgba(14, 165, 233, 0.18) !important; color: #7dd3fc !important; }
1530
+ :is([data-theme="dark"], .dark) .bg-yellow-100.text-yellow-800 { background-color: rgba(234, 179, 8, 0.18) !important; color: #fde68a !important; }
1531
+ :is([data-theme="dark"], .dark) .bg-orange-100.text-orange-800 { background-color: rgba(249, 115, 22, 0.18) !important; color: #fdba74 !important; }
1532
+ :is([data-theme="dark"], .dark) .bg-red-100.text-red-800 { background-color: rgba(239, 68, 68, 0.18) !important; color: #fca5a5 !important; }
1262
1533
 
1263
1534
  /* ── Extended dark-mode tint families ────────────────────────────────
1264
1535
  Upstreamed 2026-06-13 from the admin portal's 2026-06-10 audit so all
@@ -1276,113 +1547,113 @@ html.rosh-reduce-transparency *::after {
1276
1547
  and low white/black alphas used as glass highlights on wallpaper. */
1277
1548
 
1278
1549
  /* 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; }
1550
+ :is([data-theme="dark"], .dark) .bg-red-50 { background-color: rgba(239, 68, 68, 0.10) !important; }
1551
+ :is([data-theme="dark"], .dark) .hover\:bg-red-50:hover { background-color: rgba(239, 68, 68, 0.16) !important; }
1552
+ :is([data-theme="dark"], .dark) .active\:bg-red-50:active { background-color: rgba(239, 68, 68, 0.16) !important; }
1553
+ :is([data-theme="dark"], .dark) .bg-red-50\/30 { background-color: rgba(239, 68, 68, 0.06) !important; }
1554
+ :is([data-theme="dark"], .dark) .bg-red-50\/50 { background-color: rgba(239, 68, 68, 0.08) !important; }
1555
+ :is([data-theme="dark"], .dark) .hover\:bg-red-100:hover { background-color: rgba(239, 68, 68, 0.22) !important; }
1556
+ :is([data-theme="dark"], .dark) .border-red-200 { border-color: rgba(239, 68, 68, 0.35) !important; }
1557
+ :is([data-theme="dark"], .dark) .border-red-300,
1558
+ :is([data-theme="dark"], .dark) .hover\:border-red-300:hover { border-color: rgba(239, 68, 68, 0.45) !important; }
1559
+ :is([data-theme="dark"], .dark) .text-red-600,
1560
+ :is([data-theme="dark"], .dark) .hover\:text-red-600:hover { color: #f87171 !important; }
1561
+ :is([data-theme="dark"], .dark) .hover\:text-red-700:hover { color: #fca5a5 !important; }
1562
+ :is([data-theme="dark"], .dark) .text-red-800,
1563
+ :is([data-theme="dark"], .dark) .hover\:text-red-800:hover { color: #fecaca !important; }
1564
+ :is([data-theme="dark"], .dark) .text-red-900 { color: #fecaca !important; }
1294
1565
 
1295
1566
  /* 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; }
1567
+ :is([data-theme="dark"], .dark) .bg-amber-50 { background-color: rgba(245, 158, 11, 0.10) !important; }
1568
+ :is([data-theme="dark"], .dark) .hover\:bg-amber-50:hover { background-color: rgba(245, 158, 11, 0.14) !important; }
1569
+ :is([data-theme="dark"], .dark) .bg-amber-50\/30 { background-color: rgba(245, 158, 11, 0.06) !important; }
1570
+ :is([data-theme="dark"], .dark) .bg-amber-50\/40 { background-color: rgba(245, 158, 11, 0.07) !important; }
1571
+ :is([data-theme="dark"], .dark) .bg-amber-50\/50 { background-color: rgba(245, 158, 11, 0.08) !important; }
1572
+ :is([data-theme="dark"], .dark) .hover\:bg-amber-100:hover { background-color: rgba(245, 158, 11, 0.24) !important; }
1573
+ :is([data-theme="dark"], .dark) .bg-amber-200 { background-color: rgba(245, 158, 11, 0.28) !important; }
1574
+ :is([data-theme="dark"], .dark) .border-amber-200 { border-color: rgba(245, 158, 11, 0.35) !important; }
1575
+ :is([data-theme="dark"], .dark) .border-amber-300,
1576
+ :is([data-theme="dark"], .dark) .hover\:border-amber-300:hover { border-color: rgba(245, 158, 11, 0.45) !important; }
1577
+ :is([data-theme="dark"], .dark) .text-amber-600,
1578
+ :is([data-theme="dark"], .dark) .hover\:text-amber-600:hover { color: #fbbf24 !important; }
1579
+ :is([data-theme="dark"], .dark) .text-amber-800 { color: #fde68a !important; }
1580
+ :is([data-theme="dark"], .dark) .text-amber-900 { color: #fde68a !important; }
1310
1581
 
1311
1582
  /* 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; }
1583
+ :is([data-theme="dark"], .dark) .bg-yellow-50 { background-color: rgba(234, 179, 8, 0.10) !important; }
1584
+ :is([data-theme="dark"], .dark) .bg-yellow-50\/50 { background-color: rgba(234, 179, 8, 0.08) !important; }
1585
+ :is([data-theme="dark"], .dark) .border-yellow-200 { border-color: rgba(234, 179, 8, 0.35) !important; }
1586
+ :is([data-theme="dark"], .dark) .border-yellow-300 { border-color: rgba(234, 179, 8, 0.45) !important; }
1587
+ :is([data-theme="dark"], .dark) .text-yellow-600,
1588
+ :is([data-theme="dark"], .dark) .hover\:text-yellow-600:hover { color: #facc15 !important; }
1589
+ :is([data-theme="dark"], .dark) .text-yellow-900 { color: #fef08a !important; }
1319
1590
 
1320
1591
  /* 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; }
1592
+ :is([data-theme="dark"], .dark) .bg-green-50 { background-color: rgba(34, 197, 94, 0.10) !important; }
1593
+ :is([data-theme="dark"], .dark) .hover\:bg-green-50:hover { background-color: rgba(34, 197, 94, 0.14) !important; }
1594
+ :is([data-theme="dark"], .dark) .bg-green-50\/40 { background-color: rgba(34, 197, 94, 0.06) !important; }
1595
+ :is([data-theme="dark"], .dark) .border-green-200 { border-color: rgba(34, 197, 94, 0.32) !important; }
1596
+ :is([data-theme="dark"], .dark) .border-green-300,
1597
+ :is([data-theme="dark"], .dark) .hover\:border-green-300:hover { border-color: rgba(34, 197, 94, 0.45) !important; }
1598
+ :is([data-theme="dark"], .dark) .text-green-600,
1599
+ :is([data-theme="dark"], .dark) .hover\:text-green-600:hover { color: #4ade80 !important; }
1600
+ :is([data-theme="dark"], .dark) .text-green-900,
1601
+ :is([data-theme="dark"], .dark) .hover\:text-green-800:hover { color: #bbf7d0 !important; }
1602
+ :is([data-theme="dark"], .dark) .bg-emerald-50 { background-color: rgba(16, 185, 129, 0.10) !important; }
1603
+ :is([data-theme="dark"], .dark) .hover\:bg-emerald-100:hover { background-color: rgba(16, 185, 129, 0.24) !important; }
1604
+ :is([data-theme="dark"], .dark) .border-emerald-200 { border-color: rgba(16, 185, 129, 0.32) !important; }
1605
+ :is([data-theme="dark"], .dark) .border-emerald-300 { border-color: rgba(16, 185, 129, 0.45) !important; }
1606
+ :is([data-theme="dark"], .dark) .text-emerald-600 { color: #34d399 !important; }
1607
+ :is([data-theme="dark"], .dark) .text-emerald-800 { color: #a7f3d0 !important; }
1337
1608
 
1338
1609
  /* 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; }
1610
+ :is([data-theme="dark"], .dark) .bg-orange-50 { background-color: rgba(249, 115, 22, 0.10) !important; }
1611
+ :is([data-theme="dark"], .dark) .hover\:bg-orange-50:hover { background-color: rgba(249, 115, 22, 0.14) !important; }
1612
+ :is([data-theme="dark"], .dark) .hover\:bg-orange-200:hover { background-color: rgba(249, 115, 22, 0.30) !important; }
1613
+ :is([data-theme="dark"], .dark) .border-orange-200 { border-color: rgba(249, 115, 22, 0.32) !important; }
1614
+ :is([data-theme="dark"], .dark) .border-orange-300,
1615
+ :is([data-theme="dark"], .dark) .hover\:border-orange-300:hover { border-color: rgba(249, 115, 22, 0.45) !important; }
1616
+ :is([data-theme="dark"], .dark) .text-orange-600 { color: #fb923c !important; }
1617
+ :is([data-theme="dark"], .dark) .text-orange-900 { color: #fed7aa !important; }
1347
1618
 
1348
1619
  /* 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; }
1620
+ :is([data-theme="dark"], .dark) .bg-sky-50 { background-color: rgba(14, 165, 233, 0.10) !important; }
1621
+ :is([data-theme="dark"], .dark) .border-sky-300 { border-color: rgba(14, 165, 233, 0.45) !important; }
1622
+ :is([data-theme="dark"], .dark) .bg-teal-50 { background-color: rgba(20, 184, 166, 0.10) !important; }
1623
+ :is([data-theme="dark"], .dark) .bg-teal-100 { background-color: rgba(20, 184, 166, 0.18) !important; }
1624
+ :is([data-theme="dark"], .dark) .text-teal-600 { color: #2dd4bf !important; }
1625
+ :is([data-theme="dark"], .dark) .text-teal-700 { color: #5eead4 !important; }
1626
+ :is([data-theme="dark"], .dark) .text-teal-800 { color: #99f6e4 !important; }
1627
+ :is([data-theme="dark"], .dark) .text-cyan-600 { color: #22d3ee !important; }
1357
1628
 
1358
1629
  /* 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; }
1630
+ :is([data-theme="dark"], .dark) .bg-purple-50 { background-color: rgba(168, 85, 247, 0.10) !important; }
1631
+ :is([data-theme="dark"], .dark) .hover\:bg-purple-50:hover { background-color: rgba(168, 85, 247, 0.14) !important; }
1632
+ :is([data-theme="dark"], .dark) .border-purple-200 { border-color: rgba(168, 85, 247, 0.32) !important; }
1633
+ :is([data-theme="dark"], .dark) .border-purple-300,
1634
+ :is([data-theme="dark"], .dark) .hover\:border-purple-300:hover { border-color: rgba(168, 85, 247, 0.45) !important; }
1635
+ :is([data-theme="dark"], .dark) .text-purple-600 { color: #c084fc !important; }
1636
+ :is([data-theme="dark"], .dark) .text-purple-800,
1637
+ :is([data-theme="dark"], .dark) .text-purple-900 { color: #e9d5ff !important; }
1638
+ :is([data-theme="dark"], .dark) .bg-violet-100 { background-color: rgba(139, 92, 246, 0.18) !important; }
1639
+ :is([data-theme="dark"], .dark) .border-violet-300 { border-color: rgba(139, 92, 246, 0.45) !important; }
1640
+ :is([data-theme="dark"], .dark) .text-violet-600 { color: #a78bfa !important; }
1641
+ :is([data-theme="dark"], .dark) .text-violet-700 { color: #c4b5fd !important; }
1642
+ :is([data-theme="dark"], .dark) .text-violet-800 { color: #ddd6fe !important; }
1372
1643
 
1373
1644
  /* 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; }
1645
+ :is([data-theme="dark"], .dark) .bg-pink-100 { background-color: rgba(236, 72, 153, 0.18) !important; }
1646
+ :is([data-theme="dark"], .dark) .border-pink-200 { border-color: rgba(236, 72, 153, 0.32) !important; }
1647
+ :is([data-theme="dark"], .dark) .border-pink-300 { border-color: rgba(236, 72, 153, 0.45) !important; }
1648
+ :is([data-theme="dark"], .dark) .text-pink-600 { color: #f472b6 !important; }
1649
+ :is([data-theme="dark"], .dark) .text-pink-800,
1650
+ :is([data-theme="dark"], .dark) .text-pink-900 { color: #fbcfe8 !important; }
1651
+ :is([data-theme="dark"], .dark) .bg-rose-100 { background-color: rgba(244, 63, 94, 0.18) !important; }
1652
+ :is([data-theme="dark"], .dark) .text-rose-700 { color: #fda4af !important; }
1653
+ :is([data-theme="dark"], .dark) .text-rose-800 { color: #fecdd3 !important; }
1383
1654
 
1384
1655
  /* Indigo / slate */
1385
- [data-theme="dark"] .bg-indigo-50 { background-color: rgba(99, 102, 241, 0.15) !important; }
1656
+ :is([data-theme="dark"], .dark) .bg-indigo-50 { background-color: rgba(99, 102, 241, 0.15) !important; }
1386
1657
  /* An OPACITY MODIFIER makes a different class name, so the remap above does
1387
1658
  not reach `bg-indigo-50/40`. Same escape the `!` important-modifier makes,
1388
1659
  handled for `.\!bg-blue-*` earlier in this sheet, and already handled for
@@ -1390,68 +1661,68 @@ html.rosh-reduce-transparency *::after {
1390
1661
  of a -50 tint both land inside the base tint's own alpha, so they take it
1391
1662
  rather than a proportional slice: a near-white lavender panel under the dark
1392
1663
  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; }
1664
+ :is([data-theme="dark"], .dark) .bg-indigo-50\/40 { background-color: rgba(99, 102, 241, 0.12) !important; }
1665
+ :is([data-theme="dark"], .dark) .bg-indigo-50\/50 { background-color: rgba(99, 102, 241, 0.15) !important; }
1666
+ :is([data-theme="dark"], .dark) .hover\:bg-indigo-100:hover { background-color: rgba(99, 102, 241, 0.25) !important; }
1667
+ :is([data-theme="dark"], .dark) .text-indigo-600 { color: #a5b4fc !important; }
1668
+ :is([data-theme="dark"], .dark) .border-indigo-100 { border-color: rgba(99, 102, 241, 0.30) !important; }
1669
+ :is([data-theme="dark"], .dark) .border-indigo-200 { border-color: rgba(99, 102, 241, 0.35) !important; }
1670
+ :is([data-theme="dark"], .dark) .border-indigo-300 { border-color: rgba(99, 102, 241, 0.45) !important; }
1671
+ :is([data-theme="dark"], .dark) .text-slate-700 { color: #cbd5e1 !important; } /* slate-600/800 left for the Calculator LCD */
1672
+ :is([data-theme="dark"], .dark) .border-slate-300 { border-color: rgba(148, 163, 184, 0.35) !important; }
1402
1673
 
1403
1674
  /* Blue — gaps beyond the accent remaps above. Hover/active steps sit one
1404
1675
  ladder step above their resting tint (base .26 → active .30; the /30../50
1405
1676
  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; }
1677
+ :is([data-theme="dark"], .dark) .bg-blue-200 { background-color: rgba(var(--rosh-dark-accent-rgb), 0.35) !important; }
1678
+ :is([data-theme="dark"], .dark) .active\:bg-blue-200:active { background-color: rgba(var(--rosh-dark-accent-rgb), 0.40) !important; }
1679
+ :is([data-theme="dark"], .dark) .bg-blue-200\/60 { background-color: rgba(var(--rosh-dark-accent-rgb), 0.20) !important; }
1680
+ :is([data-theme="dark"], .dark) .hover\:bg-blue-100:hover { background-color: rgba(var(--rosh-dark-accent-rgb), 0.26) !important; }
1681
+ :is([data-theme="dark"], .dark) .active\:bg-blue-50:active { background-color: rgba(var(--rosh-dark-accent-rgb), 0.30) !important; }
1682
+ :is([data-theme="dark"], .dark) .hover\:bg-blue-50\/30:hover { background-color: rgba(var(--rosh-dark-accent-rgb), 0.12) !important; }
1683
+ :is([data-theme="dark"], .dark) .hover\:bg-blue-50\/40:hover { background-color: rgba(var(--rosh-dark-accent-rgb), 0.15) !important; }
1684
+ :is([data-theme="dark"], .dark) .hover\:bg-blue-50\/50:hover { background-color: rgba(var(--rosh-dark-accent-rgb), 0.18) !important; }
1685
+ :is([data-theme="dark"], .dark) .border-blue-100 { border-color: rgba(var(--rosh-dark-accent-rgb), 0.22) !important; }
1686
+ :is([data-theme="dark"], .dark) .border-blue-200,
1687
+ :is([data-theme="dark"], .dark) .hover\:border-blue-200:hover { border-color: rgba(var(--rosh-dark-accent-rgb), 0.32) !important; }
1688
+ :is([data-theme="dark"], .dark) .border-blue-300,
1689
+ :is([data-theme="dark"], .dark) .hover\:border-blue-300:hover { border-color: rgba(var(--rosh-dark-accent-rgb), 0.45) !important; }
1690
+ :is([data-theme="dark"], .dark) .border-blue-300\/50 { border-color: rgba(var(--rosh-dark-accent-rgb), 0.25) !important; }
1691
+ :is([data-theme="dark"], .dark) .text-blue-900 { color: rgb(var(--rosh-dark-accent-ink-strong-rgb)) !important; }
1692
+ :is([data-theme="dark"], .dark) .hover\:text-blue-600:hover { color: rgb(var(--rosh-dark-accent-ink-rgb)) !important; }
1693
+ :is([data-theme="dark"], .dark) .hover\:text-blue-800:hover { color: rgb(var(--rosh-dark-accent-ink-strong-rgb)) !important; }
1423
1694
  /* 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; }
1695
+ :is([data-theme="dark"], .dark) .file\:bg-blue-50::file-selector-button { background-color: rgba(var(--rosh-dark-accent-rgb), 0.26) !important; }
1696
+ :is([data-theme="dark"], .dark) .file\:text-blue-700::file-selector-button { color: rgb(var(--rosh-dark-accent-ink-strong-rgb)) !important; }
1697
+ :is([data-theme="dark"], .dark) .hover\:file\:bg-blue-100:hover::file-selector-button { background-color: rgba(var(--rosh-dark-accent-rgb), 0.26) !important; }
1427
1698
 
1428
1699
  /* 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; }
1700
+ :is([data-theme="dark"], .dark) .hover\:text-gray-600:hover,
1701
+ :is([data-theme="dark"], .dark) .hover\:text-gray-700:hover { color: var(--ink) !important; }
1702
+ :is([data-theme="dark"], .dark) .hover\:text-gray-800:hover { color: var(--ink-strong) !important; }
1703
+ :is([data-theme="dark"], .dark) .hover\:text-gray-900:hover,
1704
+ :is([data-theme="dark"], .dark) .active\:text-gray-900:active { color: var(--ink-strongest) !important; }
1705
+ :is([data-theme="dark"], .dark) .active\:bg-gray-100:active { background-color: var(--surface-raised) !important; }
1706
+ :is([data-theme="dark"], .dark) .active\:bg-gray-200:active { background-color: var(--line-strong) !important; }
1707
+ :is([data-theme="dark"], .dark) .active\:bg-gray-300:active,
1708
+ :is([data-theme="dark"], .dark) .hover\:bg-gray-300:hover { background-color: #585b70 !important; }
1709
+ :is([data-theme="dark"], .dark) .disabled\:bg-gray-100:disabled { background-color: var(--surface) !important; }
1710
+ :is([data-theme="dark"], .dark) .disabled\:bg-gray-200:disabled { background-color: var(--surface-raised) !important; }
1711
+ :is([data-theme="dark"], .dark) .hover\:bg-gray-200\/50:hover { background-color: rgba(69, 71, 90, 0.5) !important; }
1712
+ :is([data-theme="dark"], .dark) .even\:bg-gray-50\/50:nth-child(even) { background-color: rgba(24, 24, 37, 0.5) !important; }
1713
+ :is([data-theme="dark"], .dark) .border-gray-50,
1714
+ :is([data-theme="dark"], .dark) .divide-gray-50 > :not(:last-child) { border-color: var(--line-subtle) !important; }
1715
+ :is([data-theme="dark"], .dark) .border-gray-200\/50 { border-color: rgba(49, 50, 68, 0.6) !important; }
1716
+ :is([data-theme="dark"], .dark) .hover\:border-gray-200:hover { border-color: var(--surface-raised) !important; }
1717
+ :is([data-theme="dark"], .dark) .hover\:border-gray-300:hover { border-color: var(--line-strong) !important; }
1447
1718
 
1448
1719
  /* White panel surfaces / solid black ink. hover-to-white means "emphasize",
1449
1720
  which on dark glass means a solid panel tint; hover-to-black ink flips to
1450
1721
  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; }
1722
+ :is([data-theme="dark"], .dark) .bg-white\/85 { background-color: rgba(30, 30, 46, 0.85) !important; }
1723
+ :is([data-theme="dark"], .dark) .hover\:bg-white:hover { background-color: var(--surface) !important; }
1724
+ :is([data-theme="dark"], .dark) .text-black { color: var(--ink-strongest) !important; }
1725
+ :is([data-theme="dark"], .dark) .hover\:text-black:hover { color: #ffffff !important; }
1455
1726
 
1456
1727
  /* Markdown — the clamped note (react-os-shell/markdown, `clamp`).
1457
1728
 
@@ -1473,6 +1744,142 @@ html.rosh-reduce-transparency *::after {
1473
1744
  mask-image: linear-gradient(to bottom, black calc(100% - 1.75rem), transparent);
1474
1745
  }
1475
1746
 
1747
+ /* ═══════════════════════════════════════════════════
1748
+ Toasts and the notification card (shell/toast.ts)
1749
+ ═══════════════════════════════════════════════════
1750
+ * These were `style="…"` strings and an `onmouseenter` attribute inside an
1751
+ * innerHTML template, which a strict Content-Security-Policy refuses: the
1752
+ * attribute is inline script, and a `style-src` carrying a nonce drops style
1753
+ * attributes that arrive through markup. toast.ts now builds plain nodes and
1754
+ * names these classes; the only style it still writes is the enter/leave
1755
+ * motion, through the CSSOM, which CSP does not govern.
1756
+ *
1757
+ * Every value is the one the inline styles had. The colours are tokens so a
1758
+ * host can restyle a toast without forking it, and they are declared ONCE,
1759
+ * under :root, on purpose: the toast has always been a light glass card in
1760
+ * both themes (none of the dark remaps ever reached an inline style), and a
1761
+ * dark block here would be a redesign, not a refactor.
1762
+ *
1763
+ * The glass reads `--menu-opacity` live, exactly as the old `getMenuOpacity()`
1764
+ * read it at creation: Layout writes it on <html>, reduce-transparency pins it
1765
+ * to 1 and strips the blur. The stops stay `rgba()` on purpose. A gradient
1766
+ * whose stops are all legacy sRGB colours blends in sRGB; one written with
1767
+ * `color-mix()` blends in Oklab, which moved the glass by a couple of levels
1768
+ * per channel — measured, not guessed. The corners read the radius tokens with
1769
+ * today's sizes as the fallback, so a square-cornered host gets square toasts.
1770
+ */
1771
+ :root {
1772
+ /* Channels, not a colour: the glass gradient needs them inside rgba(). */
1773
+ --rosh-toast-glass: 255, 255, 255;
1774
+ --rosh-toast-ink: rgb(55 65 81);
1775
+ --rosh-toast-dismiss: rgb(156 163 175);
1776
+ --rosh-toast-dismiss-hover: rgb(75 85 99);
1777
+ --rosh-toast-success: #22c55e;
1778
+ --rosh-toast-error: #ef4444;
1779
+ --rosh-toast-warning: #f59e0b;
1780
+ /* info and loading share the working blue; so does the notification card. */
1781
+ --rosh-toast-info: #3b82f6;
1782
+ }
1783
+
1784
+ .rosh-toast,
1785
+ .rosh-notif {
1786
+ background: linear-gradient(135deg,
1787
+ rgba(var(--rosh-toast-glass), calc(var(--menu-opacity, 0.95) * 0.85)) 0%,
1788
+ rgba(var(--rosh-toast-glass), calc(var(--menu-opacity, 0.95) * 0.65)) 50%,
1789
+ rgba(var(--rosh-toast-glass), calc(var(--menu-opacity, 0.95) * 0.75)) 100%);
1790
+ backdrop-filter: blur(40px) saturate(1.8);
1791
+ -webkit-backdrop-filter: blur(40px) saturate(1.8);
1792
+ border: 1px solid rgba(var(--rosh-toast-glass), 0.35);
1793
+ box-shadow:
1794
+ inset 0 1px 0 rgba(var(--rosh-toast-glass), 0.4),
1795
+ 0 8px 32px rgba(0, 0, 0, 0.15),
1796
+ 0 2px 8px rgba(0, 0, 0, 0.08);
1797
+ display: flex;
1798
+ }
1799
+
1800
+ /* toast.success / error / warning / info / promise — top-centre or bottom, brief. */
1801
+ .rosh-toast {
1802
+ padding: 8px 20px;
1803
+ border-radius: var(--radius-xl, 0.75rem);
1804
+ transition: opacity 300ms ease, transform 300ms ease;
1805
+ align-items: center;
1806
+ gap: 8px;
1807
+ font-size: 13px;
1808
+ font-weight: 500;
1809
+ color: var(--rosh-toast-ink);
1810
+ max-width: min(90vw, 460px);
1811
+ }
1812
+ .rosh-tone-success { color: var(--rosh-toast-success); }
1813
+ .rosh-tone-error { color: var(--rosh-toast-error); }
1814
+ .rosh-tone-warning { color: var(--rosh-toast-warning); }
1815
+ .rosh-tone-info,
1816
+ .rosh-tone-loading { color: var(--rosh-toast-info); }
1817
+
1818
+ /* toast.notify — the top-right card that stays. */
1819
+ .rosh-notif {
1820
+ min-width: 280px;
1821
+ max-width: 380px;
1822
+ padding: 12px 16px;
1823
+ border-radius: var(--radius-2xl, 1rem);
1824
+ transition: opacity 300ms cubic-bezier(0.4, 0, 0.2, 1), transform 300ms cubic-bezier(0.4, 0, 0.2, 1);
1825
+ align-items: flex-start;
1826
+ gap: 12px;
1827
+ }
1828
+ .rosh-notif-badge {
1829
+ width: 36px;
1830
+ height: 36px;
1831
+ /* 10px beside the card's 12px; clamps to 0 when the host's corners are square. */
1832
+ border-radius: calc(var(--radius-xl, 0.75rem) - 2px);
1833
+ background: color-mix(in srgb, var(--rosh-toast-info) 15%, transparent);
1834
+ color: var(--rosh-toast-info);
1835
+ display: flex;
1836
+ align-items: center;
1837
+ justify-content: center;
1838
+ flex-shrink: 0;
1839
+ }
1840
+ .rosh-notif-body { flex: 1; min-width: 0; }
1841
+ .rosh-notif-title {
1842
+ font-size: 11px;
1843
+ font-weight: 600;
1844
+ color: var(--rosh-toast-info);
1845
+ text-transform: uppercase;
1846
+ letter-spacing: 0.05em;
1847
+ margin-bottom: 2px;
1848
+ }
1849
+ .rosh-notif-msg {
1850
+ font-size: 13px;
1851
+ font-weight: 500;
1852
+ color: var(--rosh-toast-ink);
1853
+ line-height: 1.4;
1854
+ }
1855
+ .rosh-notif-close {
1856
+ flex-shrink: 0;
1857
+ padding: 4px;
1858
+ color: var(--rosh-toast-dismiss);
1859
+ font-size: 18px;
1860
+ line-height: 1;
1861
+ transition: color 0.2s;
1862
+ }
1863
+ .rosh-notif-close:hover { color: var(--rosh-toast-dismiss-hover); }
1864
+
1865
+ /* PopupMenu's entrances. They were `<style>` elements rendered inside each
1866
+ menu, which a `style-src` without 'unsafe-inline' (or with a nonce, which
1867
+ makes the browser ignore 'unsafe-inline') refuses — the menu then opened
1868
+ with no animation and a violation report. PopupMenu still NAMES them in its
1869
+ inline `animation`, which is a CSSOM write and not governed by CSP. */
1870
+ @keyframes popup-in {
1871
+ from { opacity: 0; transform: scale(0.96); }
1872
+ to { opacity: 1; transform: scale(1); }
1873
+ }
1874
+ @keyframes popup-submenu-in {
1875
+ from { opacity: 0; transform: translateX(-4px); }
1876
+ to { opacity: 1; transform: translateX(0); }
1877
+ }
1878
+ @keyframes popup-submenu-in-left {
1879
+ from { opacity: 0; transform: translateX(4px); }
1880
+ to { opacity: 1; transform: translateX(0); }
1881
+ }
1882
+
1476
1883
  /* Scrollbar */
1477
1884
  /* Force visible scrollbars on editable grids (macOS hides them by default) */
1478
1885
  .grid-scroll::-webkit-scrollbar { width: 10px; height: 10px; }
@@ -1481,10 +1888,11 @@ html.rosh-reduce-transparency *::after {
1481
1888
  .grid-scroll::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
1482
1889
  .grid-scroll::-webkit-scrollbar-corner { background: #f1f5f9; }
1483
1890
 
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; }
1891
+ /* Page-wide, so gated like the other bare-element rules above. */
1892
+ :is([data-theme="dark"], .dark) :where(:root:not(.rosh-host-elements) *)::-webkit-scrollbar { width: 8px; height: 8px; }
1893
+ :is([data-theme="dark"], .dark) :where(:root:not(.rosh-host-elements) *)::-webkit-scrollbar-track { background: var(--surface-sunken); }
1894
+ :is([data-theme="dark"], .dark) :where(:root:not(.rosh-host-elements) *)::-webkit-scrollbar-thumb { background: var(--line-strong); border-radius: 4px; }
1895
+ :is([data-theme="dark"], .dark) :where(:root:not(.rosh-host-elements) *)::-webkit-scrollbar-thumb:hover { background: #585b70; }
1488
1896
 
1489
1897
  /* ── Opacity-modified utilities ───────────────────────────────────────────
1490
1898
  A Tailwind opacity modifier makes a DIFFERENT class name, so every remap in
@@ -1498,21 +1906,21 @@ html.rosh-reduce-transparency *::after {
1498
1906
  NOT take 70% of the tint's alpha, because a proportional slice of an already
1499
1907
  subtle tint disappears. Gray is the exception and does take a real alpha,
1500
1908
  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; }
1909
+ :is([data-theme="dark"], .dark) .bg-gray-50\/70 { background-color: rgba(24, 24, 37, 0.7) !important; }
1910
+ :is([data-theme="dark"], .dark) .bg-gray-50\/80 { background-color: rgba(24, 24, 37, 0.8) !important; }
1911
+ :is([data-theme="dark"], .dark) .bg-gray-50\/95 { background-color: rgba(24, 24, 37, 0.95) !important; }
1912
+ :is([data-theme="dark"], .dark) .bg-gray-100\/90 { background-color: rgba(30, 30, 46, 0.9) !important; }
1913
+ :is([data-theme="dark"], .dark) .bg-gray-200\/80 { background-color: rgba(49, 50, 68, 0.8) !important; }
1914
+ :is([data-theme="dark"], .dark) .hover\:bg-gray-200\/70:hover { background-color: rgba(49, 50, 68, 0.7) !important; }
1915
+ :is([data-theme="dark"], .dark) .hover\:bg-gray-200\/90:hover { background-color: rgba(49, 50, 68, 0.9) !important; }
1916
+ :is([data-theme="dark"], .dark) .border-gray-200\/60 { border-color: rgba(69, 71, 90, 0.6) !important; }
1917
+ :is([data-theme="dark"], .dark) .bg-blue-50\/80 { background-color: rgba(var(--rosh-dark-accent-rgb), 0.24) !important; }
1918
+ :is([data-theme="dark"], .dark) .bg-blue-50\/90 { background-color: rgba(var(--rosh-dark-accent-rgb), 0.26) !important; }
1919
+ :is([data-theme="dark"], .dark) .text-blue-600\/70 { color: rgba(var(--rosh-dark-accent-ink-rgb), 0.7) !important; }
1920
+ :is([data-theme="dark"], .dark) .text-blue-700\/70 { color: rgba(var(--rosh-dark-accent-ink-strong-rgb), 0.7) !important; }
1921
+ :is([data-theme="dark"], .dark) .text-blue-800\/80 { color: rgba(var(--rosh-dark-accent-ink-strong-rgb), 0.8) !important; }
1922
+ :is([data-theme="dark"], .dark) .text-blue-900\/60 { color: rgba(var(--rosh-dark-accent-ink-strong-rgb), 0.6) !important; }
1923
+ :is([data-theme="dark"], .dark) .bg-amber-200\/70 { background-color: rgba(245, 158, 11, 0.20) !important; }
1924
+ :is([data-theme="dark"], .dark) .hover\:bg-amber-50\/70:hover { background-color: rgba(245, 158, 11, 0.12) !important; }
1925
+ :is([data-theme="dark"], .dark) .text-amber-800\/80 { color: rgba(253, 230, 138, 0.8) !important; }
1926
+ :is([data-theme="dark"], .dark) .text-green-700\/80 { color: rgba(134, 239, 172, 0.8) !important; }