@hadoku/task-ui-components 0.0.0-stage → 4.20.4

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 (169) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +917 -2
  3. package/dist/app-header.css +288 -0
  4. package/dist/bento.css +143 -0
  5. package/dist/button.css +227 -0
  6. package/dist/components/AppHeader.d.ts +61 -0
  7. package/dist/components/AppHeader.d.ts.map +1 -0
  8. package/dist/components/AppHeader.js +23 -0
  9. package/dist/components/AppHeader.js.map +1 -0
  10. package/dist/components/BentoCard.d.ts +23 -0
  11. package/dist/components/BentoCard.d.ts.map +1 -0
  12. package/dist/components/BentoCard.js +49 -0
  13. package/dist/components/BentoCard.js.map +1 -0
  14. package/dist/components/BentoGrid.d.ts +24 -0
  15. package/dist/components/BentoGrid.d.ts.map +1 -0
  16. package/dist/components/BentoGrid.js +22 -0
  17. package/dist/components/BentoGrid.js.map +1 -0
  18. package/dist/components/Button.d.ts +28 -0
  19. package/dist/components/Button.d.ts.map +1 -0
  20. package/dist/components/Button.js +51 -0
  21. package/dist/components/Button.js.map +1 -0
  22. package/dist/components/ConnectedSettings.d.ts +53 -0
  23. package/dist/components/ConnectedSettings.d.ts.map +1 -0
  24. package/dist/components/ConnectedSettings.js +368 -0
  25. package/dist/components/ConnectedSettings.js.map +1 -0
  26. package/dist/components/ConnectedThemePicker.d.ts +52 -0
  27. package/dist/components/ConnectedThemePicker.d.ts.map +1 -0
  28. package/dist/components/ConnectedThemePicker.js +32 -0
  29. package/dist/components/ConnectedThemePicker.js.map +1 -0
  30. package/dist/components/ContextMenu.d.ts +22 -0
  31. package/dist/components/ContextMenu.d.ts.map +1 -0
  32. package/dist/components/ContextMenu.js +23 -0
  33. package/dist/components/ContextMenu.js.map +1 -0
  34. package/dist/components/HadokuThemePicker.d.ts +27 -0
  35. package/dist/components/HadokuThemePicker.d.ts.map +1 -0
  36. package/dist/components/HadokuThemePicker.js +19 -0
  37. package/dist/components/HadokuThemePicker.js.map +1 -0
  38. package/dist/components/LoadingSkeleton.d.ts +14 -0
  39. package/dist/components/LoadingSkeleton.d.ts.map +1 -0
  40. package/dist/components/LoadingSkeleton.js +62 -0
  41. package/dist/components/LoadingSkeleton.js.map +1 -0
  42. package/dist/components/Modal.d.ts +39 -0
  43. package/dist/components/Modal.d.ts.map +1 -0
  44. package/dist/components/Modal.js +106 -0
  45. package/dist/components/Modal.js.map +1 -0
  46. package/dist/components/NumberInput.d.ts +26 -0
  47. package/dist/components/NumberInput.d.ts.map +1 -0
  48. package/dist/components/NumberInput.js +131 -0
  49. package/dist/components/NumberInput.js.map +1 -0
  50. package/dist/components/Select.d.ts +52 -0
  51. package/dist/components/Select.d.ts.map +1 -0
  52. package/dist/components/Select.js +281 -0
  53. package/dist/components/Select.js.map +1 -0
  54. package/dist/components/SettingsModal.d.ts +69 -0
  55. package/dist/components/SettingsModal.d.ts.map +1 -0
  56. package/dist/components/SettingsModal.js +141 -0
  57. package/dist/components/SettingsModal.js.map +1 -0
  58. package/dist/components/ThemeIcons.d.ts +26 -0
  59. package/dist/components/ThemeIcons.d.ts.map +1 -0
  60. package/dist/components/ThemeIcons.js +74 -0
  61. package/dist/components/ThemeIcons.js.map +1 -0
  62. package/dist/components/ThemePicker.d.ts +28 -0
  63. package/dist/components/ThemePicker.d.ts.map +1 -0
  64. package/dist/components/ThemePicker.js +101 -0
  65. package/dist/components/ThemePicker.js.map +1 -0
  66. package/dist/components/Toast.d.ts +13 -0
  67. package/dist/components/Toast.d.ts.map +1 -0
  68. package/dist/components/Toast.js +70 -0
  69. package/dist/components/Toast.js.map +1 -0
  70. package/dist/components/Toaster.d.ts +19 -0
  71. package/dist/components/Toaster.d.ts.map +1 -0
  72. package/dist/components/Toaster.js +19 -0
  73. package/dist/components/Toaster.js.map +1 -0
  74. package/dist/components/Tooltip.d.ts +49 -0
  75. package/dist/components/Tooltip.d.ts.map +1 -0
  76. package/dist/components/Tooltip.js +143 -0
  77. package/dist/components/Tooltip.js.map +1 -0
  78. package/dist/context-menu.css +11 -0
  79. package/dist/controls.css +137 -0
  80. package/dist/hooks/useReducedMotion.d.ts +7 -0
  81. package/dist/hooks/useReducedMotion.d.ts.map +1 -0
  82. package/dist/hooks/useReducedMotion.js +25 -0
  83. package/dist/hooks/useReducedMotion.js.map +1 -0
  84. package/dist/hooks/useToast.d.ts +18 -0
  85. package/dist/hooks/useToast.d.ts.map +1 -0
  86. package/dist/hooks/useToast.js +28 -0
  87. package/dist/hooks/useToast.js.map +1 -0
  88. package/dist/icon-btn.css +64 -0
  89. package/dist/index.d.ts +49 -0
  90. package/dist/index.d.ts.map +1 -0
  91. package/dist/index.js +75 -0
  92. package/dist/index.js.map +1 -0
  93. package/dist/lib/menuPlacement.d.ts +16 -0
  94. package/dist/lib/menuPlacement.d.ts.map +1 -0
  95. package/dist/lib/menuPlacement.js +19 -0
  96. package/dist/lib/menuPlacement.js.map +1 -0
  97. package/dist/lib/selectMenus.d.ts +30 -0
  98. package/dist/lib/selectMenus.d.ts.map +1 -0
  99. package/dist/lib/selectMenus.js +236 -0
  100. package/dist/lib/selectMenus.js.map +1 -0
  101. package/dist/lib/settingsClient.d.ts +144 -0
  102. package/dist/lib/settingsClient.d.ts.map +1 -0
  103. package/dist/lib/settingsClient.js +157 -0
  104. package/dist/lib/settingsClient.js.map +1 -0
  105. package/dist/lib/themeScope.d.ts +19 -0
  106. package/dist/lib/themeScope.d.ts.map +1 -0
  107. package/dist/lib/themeScope.js +10 -0
  108. package/dist/lib/themeScope.js.map +1 -0
  109. package/dist/lib/titleTooltips.d.ts +38 -0
  110. package/dist/lib/titleTooltips.d.ts.map +1 -0
  111. package/dist/lib/titleTooltips.js +166 -0
  112. package/dist/lib/titleTooltips.js.map +1 -0
  113. package/dist/lib/tooltipPlacement.d.ts +14 -0
  114. package/dist/lib/tooltipPlacement.d.ts.map +1 -0
  115. package/dist/lib/tooltipPlacement.js +34 -0
  116. package/dist/lib/tooltipPlacement.js.map +1 -0
  117. package/dist/modal.css +129 -0
  118. package/dist/number-input.css +131 -0
  119. package/dist/scrollbar.css +165 -0
  120. package/dist/select.css +251 -0
  121. package/dist/styles.css +1866 -0
  122. package/dist/task-ui-components.css +1866 -0
  123. package/dist/theme-picker.css +219 -0
  124. package/dist/toaster.css +159 -0
  125. package/dist/tooltip.css +103 -0
  126. package/dist/types/index.d.ts +37 -0
  127. package/dist/types/index.d.ts.map +1 -0
  128. package/package.json +60 -4
  129. package/src/app-header.css +288 -0
  130. package/src/bento.css +143 -0
  131. package/src/button.css +227 -0
  132. package/src/components/AppHeader.tsx +87 -0
  133. package/src/components/BentoCard.tsx +72 -0
  134. package/src/components/BentoGrid.tsx +48 -0
  135. package/src/components/Button.tsx +81 -0
  136. package/src/components/ConnectedSettings.tsx +545 -0
  137. package/src/components/ConnectedThemePicker.tsx +79 -0
  138. package/src/components/ContextMenu.tsx +48 -0
  139. package/src/components/HadokuThemePicker.tsx +39 -0
  140. package/src/components/LoadingSkeleton.tsx +84 -0
  141. package/src/components/Modal.tsx +171 -0
  142. package/src/components/NumberInput.tsx +161 -0
  143. package/src/components/Select.tsx +359 -0
  144. package/src/components/SettingsModal.tsx +235 -0
  145. package/src/components/ThemeIcons.tsx +127 -0
  146. package/src/components/ThemePicker.tsx +149 -0
  147. package/src/components/Toast.tsx +92 -0
  148. package/src/components/Toaster.tsx +43 -0
  149. package/src/components/Tooltip.tsx +212 -0
  150. package/src/context-menu.css +11 -0
  151. package/src/controls.css +137 -0
  152. package/src/hooks/useReducedMotion.ts +36 -0
  153. package/src/hooks/useToast.ts +57 -0
  154. package/src/icon-btn.css +64 -0
  155. package/src/index.ts +153 -0
  156. package/src/lib/menuPlacement.ts +31 -0
  157. package/src/lib/selectMenus.ts +303 -0
  158. package/src/lib/settingsClient.ts +425 -0
  159. package/src/lib/themeScope.ts +23 -0
  160. package/src/lib/titleTooltips.ts +236 -0
  161. package/src/lib/tooltipPlacement.ts +59 -0
  162. package/src/modal.css +129 -0
  163. package/src/number-input.css +131 -0
  164. package/src/scrollbar.css +165 -0
  165. package/src/select.css +251 -0
  166. package/src/theme-picker.css +219 -0
  167. package/src/toaster.css +159 -0
  168. package/src/tooltip.css +103 -0
  169. package/src/types/index.ts +43 -0
@@ -0,0 +1,1866 @@
1
+ /* Tooltip styles for @wolffm/task-ui-components.
2
+ *
3
+ * import '@wolffm/task-ui-components/tooltip.css'
4
+ *
5
+ * `inverse` paints body text colour as the surface and the page background
6
+ * as the text — the same pair as body copy, flipped, so its contrast is the
7
+ * theme's own text contrast in every theme.
8
+ */
9
+
10
+ .hdk-tooltip {
11
+ --_tip-bg: var(--color-text);
12
+ --_tip-fg: var(--color-bg);
13
+ --_tip-border: transparent;
14
+ /* Overwritten inline once placed: where the anchor's centre falls. */
15
+ --hdk-tooltip-arrow: 50%;
16
+
17
+ position: fixed;
18
+ z-index: 1100;
19
+ max-width: min(280px, calc(100vw - 16px));
20
+ padding: 6px 10px;
21
+ font-size: var(--hdk-text-xs);
22
+ font-weight: var(--font-weight-normal);
23
+ line-height: 1.4;
24
+ text-align: left;
25
+ white-space: normal;
26
+ overflow-wrap: break-word;
27
+ color: var(--_tip-fg);
28
+ background-color: var(--_tip-bg);
29
+ border: 1px solid var(--_tip-border);
30
+ border-radius: var(--hdk-radius);
31
+ box-shadow: var(--hdk-shadow-md);
32
+ /* A fade, never a slide: a transform puts the bubble on its own layer, so
33
+ * its 1px border smears across rows mid-slide and visibly snaps back into
34
+ * place when the transition ends. */
35
+ opacity: 0;
36
+ transition: opacity var(--transition-fast);
37
+ }
38
+
39
+ .hdk-tooltip--visible {
40
+ opacity: 1;
41
+ }
42
+
43
+ .hdk-tooltip--card {
44
+ --_tip-bg: var(--color-bg-card);
45
+ --_tip-fg: var(--color-text);
46
+ --_tip-border: var(--color-border);
47
+
48
+ box-shadow: var(--hdk-shadow-lg);
49
+ }
50
+
51
+ /* A `title=` string from installTitleTooltips: plain text, and its line
52
+ * breaks are real (a native title shows them too). */
53
+ .hdk-tooltip--plain {
54
+ white-space: pre-line;
55
+ }
56
+
57
+ .hdk-tooltip--primary {
58
+ --_tip-bg: var(--color-primary);
59
+ --_tip-fg: var(--color-on-primary);
60
+ }
61
+
62
+ /* ── arrow ────────────────────────────────────────────────────────────── */
63
+
64
+ /* A rotated square sharing the bubble's fill and border; only its two outer
65
+ * edges carry the border, so it reads as one shape with the bubble. */
66
+ .hdk-tooltip::after {
67
+ content: '';
68
+ position: absolute;
69
+ width: 8px;
70
+ height: 8px;
71
+ background-color: var(--_tip-bg);
72
+ border: 1px solid var(--_tip-border);
73
+ border-top-color: transparent;
74
+ border-left-color: transparent;
75
+ }
76
+
77
+ .hdk-tooltip--top::after {
78
+ top: 100%;
79
+ left: var(--hdk-tooltip-arrow);
80
+ translate: -50% -50%;
81
+ rotate: 45deg;
82
+ }
83
+
84
+ .hdk-tooltip--bottom::after {
85
+ bottom: 100%;
86
+ left: var(--hdk-tooltip-arrow);
87
+ translate: -50% 50%;
88
+ rotate: -135deg;
89
+ }
90
+
91
+ .hdk-tooltip--left::after {
92
+ left: 100%;
93
+ top: var(--hdk-tooltip-arrow);
94
+ translate: -50% -50%;
95
+ rotate: -45deg;
96
+ }
97
+
98
+ .hdk-tooltip--right::after {
99
+ right: 100%;
100
+ top: var(--hdk-tooltip-arrow);
101
+ translate: 50% -50%;
102
+ rotate: 135deg;
103
+ }
104
+ /**
105
+ * ThemePicker Component Styles
106
+ * Beautiful theme picker with light/dark pill design
107
+ *
108
+ * Required CSS Variables (should be defined by @wolffm/themes):
109
+ * --color-text
110
+ * --color-primary
111
+ * --color-primary-hover
112
+ * --color-primary-bg
113
+ * --color-bg-card
114
+ * --color-text-secondary
115
+ * --hdk-shadow-focus-sm
116
+ * --hdk-shadow-md
117
+ * --hdk-shadow-lg
118
+ * --transition-fast (e.g., 0.15s ease)
119
+ * --hdk-space-sm (e.g., 8px)
120
+ * --hdk-space-md (e.g., 12px)
121
+ * --hdk-space-lg (e.g., 16px)
122
+ * --hdk-text-lg (e.g., 18px)
123
+ * --font-weight-semibold (e.g., 600)
124
+ */
125
+
126
+ /* The round 32px icon button on the AppHeader bar — the theme picker trigger,
127
+ * the settings gear, and any link an app puts in the actions row.
128
+ *
129
+ * Defined ONCE. The picker and the gear each used to carry their own copy and
130
+ * the two had already drifted (padding 8px vs --hdk-space-sm, a ring on every
131
+ * mouse click vs on :focus-visible, a min-size on one only). `.theme-toggle-btn`
132
+ * and `.settings-toggle-btn` survive as hooks with no values of their own.
133
+ *
134
+ * Lives in its own file because ThemePicker renders outside the header too
135
+ * (HadokuThemePicker on a video overlay), so its trigger cannot depend on
136
+ * app-header.css. The chain is app-header.css -> theme-picker.css -> this.
137
+ *
138
+ * Works on an <a>: child apps ship without a preflight, so a bare anchor would
139
+ * render UA blue, visited purple and underlined. */
140
+
141
+ .app-header__icon-btn {
142
+ display: inline-flex;
143
+ align-items: center;
144
+ justify-content: center;
145
+ flex-shrink: 0;
146
+ box-sizing: border-box;
147
+ width: 32px;
148
+ height: 32px;
149
+ min-width: 32px;
150
+ min-height: 32px;
151
+ padding: 0;
152
+ margin: 0;
153
+ border: none;
154
+ border-radius: 50%;
155
+ background: none;
156
+ color: var(--color-text);
157
+ font: inherit;
158
+ font-size: var(--hdk-text-lg);
159
+ line-height: 1;
160
+ text-decoration: none;
161
+ cursor: pointer;
162
+ opacity: 0.7;
163
+ transition:
164
+ background-color var(--transition-fast),
165
+ opacity var(--transition-fast);
166
+ }
167
+
168
+ /* Same colour visited or not, and no hover recolour from a host stylesheet's
169
+ * `a:hover` — the hover signal is the background below. */
170
+
171
+ .app-header__icon-btn:link,
172
+ .app-header__icon-btn:visited,
173
+ .app-header__icon-btn:hover,
174
+ .app-header__icon-btn:active {
175
+ color: var(--color-text);
176
+ text-decoration: none;
177
+ }
178
+
179
+ .app-header__icon-btn:hover {
180
+ background-color: var(--color-primary-hover);
181
+ opacity: 1;
182
+ }
183
+
184
+ /* :focus-visible, not :focus — :focus rings on every mouse click. Full
185
+ * opacity too, so a keyboard user never lands on the dimmed resting state. */
186
+
187
+ .app-header__icon-btn:focus-visible {
188
+ outline: none;
189
+ opacity: 1;
190
+ box-shadow: 0 0 0 2px var(--color-primary);
191
+ }
192
+
193
+ .theme-picker {
194
+ position: relative;
195
+ }
196
+
197
+ /* Theme toggle button: styled by the shared .app-header__icon-btn
198
+ * (icon-btn.css). `.theme-toggle-btn` stays on the element as a hook only —
199
+ * giving it values again would be the second copy that drifts. */
200
+
201
+ /* Theme picker overlay - covers entire viewport for click-outside detection */
202
+
203
+ .theme-picker__overlay {
204
+ position: fixed;
205
+ top: 0;
206
+ left: 0;
207
+ right: 0;
208
+ bottom: 0;
209
+ z-index: 99;
210
+ background: transparent;
211
+ }
212
+
213
+ /* Theme picker dropdown - positioned relative to picker button */
214
+
215
+ .theme-picker__dropdown {
216
+ position: absolute;
217
+ top: 0;
218
+ left: calc(100% + var(--hdk-space-md));
219
+ z-index: 100;
220
+ display: flex;
221
+ flex-direction: column;
222
+ align-items: flex-start;
223
+ gap: var(--hdk-space-sm);
224
+ }
225
+
226
+ /* Inner row: pill tower + settings icon side-by-side */
227
+
228
+ .theme-picker__main {
229
+ display: flex;
230
+ align-items: flex-start;
231
+ gap: var(--hdk-space-md);
232
+ }
233
+
234
+ /* Open-left modifier: dropdown opens to the left of the toggle */
235
+
236
+ .theme-picker--open-left .theme-picker__dropdown {
237
+ left: auto;
238
+ right: calc(100% + var(--hdk-space-md));
239
+ }
240
+
241
+ .theme-picker--open-left .theme-picker__main {
242
+ flex-direction: row-reverse;
243
+ }
244
+
245
+ /* Theme pill tower - has the shadow */
246
+
247
+ .theme-picker__pills {
248
+ display: flex;
249
+ flex-direction: column;
250
+ gap: 0; /* No gap - pancake stack */
251
+ border-radius: 12px;
252
+ overflow: hidden;
253
+ box-shadow: var(--hdk-shadow-lg); /* Shadow on pill tower only */
254
+ }
255
+
256
+ /* Theme pill - contains light and dark buttons */
257
+
258
+ .theme-pill {
259
+ display: flex;
260
+ gap: 0;
261
+ background: transparent;
262
+ border: none;
263
+ transition: all var(--transition-fast, 0.15s ease);
264
+ }
265
+
266
+ /* Separator line between pills */
267
+
268
+ .theme-pill:not(:last-child) {
269
+ border-bottom: 1px solid rgba(0, 0, 0, 0.08);
270
+ }
271
+
272
+ /* Theme pill buttons */
273
+
274
+ .theme-pill__btn {
275
+ flex: 1;
276
+ display: flex;
277
+ align-items: center;
278
+ justify-content: center;
279
+ padding: calc(var(--hdk-space-lg) * 1.2); /* 20% bigger total (10% + 10%) */
280
+ border: none;
281
+ background: transparent;
282
+ cursor: pointer;
283
+ transition: all var(--transition-fast, 0.15s ease);
284
+ position: relative;
285
+ overflow: visible; /* Allow border to show */
286
+ }
287
+
288
+ /* The light/dark pills are the mode toggle itself: they must always LOOK light
289
+ and dark so the control reads correctly whichever theme is active. These
290
+ literals are deliberate and are the one place theme tokens must not be used. */
291
+
292
+ /* Light button default style */
293
+
294
+ .theme-pill__btn--light {
295
+ background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
296
+ color: #1e293b;
297
+ border-right: 1px solid rgba(0, 0, 0, 0.1);
298
+ }
299
+
300
+ /* Dark button default style - more cyber with darker background and blue tint */
301
+
302
+ .theme-pill__btn--dark {
303
+ background: linear-gradient(135deg, #0f172a 0%, #020617 100%);
304
+ color: #64748b;
305
+ border-left: 1px solid rgba(100, 116, 139, 0.1);
306
+ }
307
+
308
+ /* First pill top-left corner rounding - rounded corner instead of full circle */
309
+
310
+ .theme-pill:first-child .theme-pill__btn--light {
311
+ border-top-left-radius: 12px;
312
+ }
313
+
314
+ /* First pill top-right corner rounding */
315
+
316
+ .theme-pill:first-child .theme-pill__btn--dark {
317
+ border-top-right-radius: 12px;
318
+ }
319
+
320
+ /* Last pill bottom-left corner rounding */
321
+
322
+ .theme-pill:last-child .theme-pill__btn--light {
323
+ border-bottom-left-radius: 12px;
324
+ }
325
+
326
+ /* Last pill bottom-right corner rounding */
327
+
328
+ .theme-pill:last-child .theme-pill__btn--dark {
329
+ border-bottom-right-radius: 12px;
330
+ }
331
+
332
+ .theme-pill__btn--light:hover {
333
+ background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
334
+ color: var(--color-primary);
335
+ }
336
+
337
+ .theme-pill__btn--dark:hover {
338
+ background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
339
+ color: #cbd5e1;
340
+ }
341
+
342
+ /* Active states - with proper border radius that doesn't clip, 10% bigger border */
343
+
344
+ .theme-pill__btn--light.active {
345
+ background: linear-gradient(135deg, #ffffff 0%, #f0f9ff 100%);
346
+ color: var(--color-primary);
347
+ box-shadow: inset 0 0 0 2.2px var(--color-primary); /* Uses theme primary color */
348
+ font-weight: var(--font-weight-semibold, 600);
349
+ }
350
+
351
+ .theme-pill__btn--dark.active {
352
+ background: linear-gradient(135deg, #020617 0%, #000000 100%);
353
+ color: var(--color-primary); /* Use theme primary color */
354
+ box-shadow: inset 0 0 0 2.2px var(--color-primary); /* Use theme primary color */
355
+ font-weight: var(--font-weight-semibold, 600);
356
+ /* Add glow effect for cyber look */
357
+ filter: brightness(1.1);
358
+ }
359
+
360
+ /* Icon container - reduced by ~15% (24px -> 20px) */
361
+
362
+ .theme-pill__icon {
363
+ display: flex;
364
+ align-items: center;
365
+ justify-content: center;
366
+ width: 20px;
367
+ height: 20px;
368
+ }
369
+
370
+ .theme-pill__icon svg {
371
+ width: 100%;
372
+ height: 100%;
373
+ }
374
+
375
+ /* Settings icon button - circular, separate from pill tower */
376
+
377
+ .theme-picker__settings-icon {
378
+ display: flex;
379
+ align-items: center;
380
+ justify-content: center;
381
+ width: 44px;
382
+ height: 44px;
383
+ padding: 0;
384
+ border: none;
385
+ background: var(--color-bg-card);
386
+ color: var(--color-text-secondary);
387
+ cursor: pointer;
388
+ transition: all var(--transition-fast, 0.15s ease);
389
+ border-radius: 50%; /* Circular */
390
+ box-shadow: var(--hdk-shadow-md);
391
+ flex-shrink: 0;
392
+ }
393
+
394
+ .theme-picker__settings-icon:hover {
395
+ background: var(--color-primary-bg);
396
+ color: var(--color-on-primary-bg);
397
+ transform: rotate(45deg);
398
+ box-shadow: var(--hdk-shadow-lg);
399
+ }
400
+
401
+ .theme-picker__settings-icon svg {
402
+ width: 20px;
403
+ height: 20px;
404
+ }
405
+ /* AppHeader + ConnectedSettings styles for @wolffm/task-ui-components.
406
+ *
407
+ * All colors come from @wolffm/themes semantic tokens so the header themes with
408
+ * everything else. One import is all an app needs:
409
+ * import '@wolffm/task-ui-components/app-header.css'
410
+ *
411
+ * This used to say "import alongside theme-picker.css", and that convention
412
+ * failed exactly the way conventions do. AppHeader renders the theme picker
413
+ * unconditionally now — it is not a prop — so an app that loads this file
414
+ * without the picker's gets the right component with none of its styling. The
415
+ * command station shipped like that after its old wrapper (the only importer of
416
+ * theme-picker.css on that site) was deleted: correct markup, no CSS.
417
+ *
418
+ * Pulling it in here makes the pair inseparable, which is the same reason the
419
+ * component itself stopped being a prop. */
420
+ /**
421
+ * ThemePicker Component Styles
422
+ * Beautiful theme picker with light/dark pill design
423
+ *
424
+ * Required CSS Variables (should be defined by @wolffm/themes):
425
+ * --color-text
426
+ * --color-primary
427
+ * --color-primary-hover
428
+ * --color-primary-bg
429
+ * --color-bg-card
430
+ * --color-text-secondary
431
+ * --hdk-shadow-focus-sm
432
+ * --hdk-shadow-md
433
+ * --hdk-shadow-lg
434
+ * --transition-fast (e.g., 0.15s ease)
435
+ * --hdk-space-sm (e.g., 8px)
436
+ * --hdk-space-md (e.g., 12px)
437
+ * --hdk-space-lg (e.g., 16px)
438
+ * --hdk-text-lg (e.g., 18px)
439
+ * --font-weight-semibold (e.g., 600)
440
+ */
441
+ /* The round 32px icon button on the AppHeader bar — the theme picker trigger,
442
+ * the settings gear, and any link an app puts in the actions row.
443
+ *
444
+ * Defined ONCE. The picker and the gear each used to carry their own copy and
445
+ * the two had already drifted (padding 8px vs --hdk-space-sm, a ring on every
446
+ * mouse click vs on :focus-visible, a min-size on one only). `.theme-toggle-btn`
447
+ * and `.settings-toggle-btn` survive as hooks with no values of their own.
448
+ *
449
+ * Lives in its own file because ThemePicker renders outside the header too
450
+ * (HadokuThemePicker on a video overlay), so its trigger cannot depend on
451
+ * app-header.css. The chain is app-header.css -> theme-picker.css -> this.
452
+ *
453
+ * Works on an <a>: child apps ship without a preflight, so a bare anchor would
454
+ * render UA blue, visited purple and underlined. */
455
+ .app-header__icon-btn {
456
+ display: inline-flex;
457
+ align-items: center;
458
+ justify-content: center;
459
+ flex-shrink: 0;
460
+ box-sizing: border-box;
461
+ width: 32px;
462
+ height: 32px;
463
+ min-width: 32px;
464
+ min-height: 32px;
465
+ padding: 0;
466
+ margin: 0;
467
+ border: none;
468
+ border-radius: 50%;
469
+ background: none;
470
+ color: var(--color-text);
471
+ font: inherit;
472
+ font-size: var(--hdk-text-lg);
473
+ line-height: 1;
474
+ text-decoration: none;
475
+ cursor: pointer;
476
+ opacity: 0.7;
477
+ transition:
478
+ background-color var(--transition-fast),
479
+ opacity var(--transition-fast);
480
+ }
481
+ /* Same colour visited or not, and no hover recolour from a host stylesheet's
482
+ * `a:hover` — the hover signal is the background below. */
483
+ .app-header__icon-btn:link,
484
+ .app-header__icon-btn:visited,
485
+ .app-header__icon-btn:hover,
486
+ .app-header__icon-btn:active {
487
+ color: var(--color-text);
488
+ text-decoration: none;
489
+ }
490
+ .app-header__icon-btn:hover {
491
+ background-color: var(--color-primary-hover);
492
+ opacity: 1;
493
+ }
494
+ /* :focus-visible, not :focus — :focus rings on every mouse click. Full
495
+ * opacity too, so a keyboard user never lands on the dimmed resting state. */
496
+ .app-header__icon-btn:focus-visible {
497
+ outline: none;
498
+ opacity: 1;
499
+ box-shadow: 0 0 0 2px var(--color-primary);
500
+ }
501
+ .theme-picker {
502
+ position: relative;
503
+ }
504
+ /* Theme toggle button: styled by the shared .app-header__icon-btn
505
+ * (icon-btn.css). `.theme-toggle-btn` stays on the element as a hook only —
506
+ * giving it values again would be the second copy that drifts. */
507
+ /* Theme picker overlay - covers entire viewport for click-outside detection */
508
+ .theme-picker__overlay {
509
+ position: fixed;
510
+ top: 0;
511
+ left: 0;
512
+ right: 0;
513
+ bottom: 0;
514
+ z-index: 99;
515
+ background: transparent;
516
+ }
517
+ /* Theme picker dropdown - positioned relative to picker button */
518
+ .theme-picker__dropdown {
519
+ position: absolute;
520
+ top: 0;
521
+ left: calc(100% + var(--hdk-space-md));
522
+ z-index: 100;
523
+ display: flex;
524
+ flex-direction: column;
525
+ align-items: flex-start;
526
+ gap: var(--hdk-space-sm);
527
+ }
528
+ /* Inner row: pill tower + settings icon side-by-side */
529
+ .theme-picker__main {
530
+ display: flex;
531
+ align-items: flex-start;
532
+ gap: var(--hdk-space-md);
533
+ }
534
+ /* Open-left modifier: dropdown opens to the left of the toggle */
535
+ .theme-picker--open-left .theme-picker__dropdown {
536
+ left: auto;
537
+ right: calc(100% + var(--hdk-space-md));
538
+ }
539
+ .theme-picker--open-left .theme-picker__main {
540
+ flex-direction: row-reverse;
541
+ }
542
+ /* Theme pill tower - has the shadow */
543
+ .theme-picker__pills {
544
+ display: flex;
545
+ flex-direction: column;
546
+ gap: 0; /* No gap - pancake stack */
547
+ border-radius: 12px;
548
+ overflow: hidden;
549
+ box-shadow: var(--hdk-shadow-lg); /* Shadow on pill tower only */
550
+ }
551
+ /* Theme pill - contains light and dark buttons */
552
+ .theme-pill {
553
+ display: flex;
554
+ gap: 0;
555
+ background: transparent;
556
+ border: none;
557
+ transition: all var(--transition-fast, 0.15s ease);
558
+ }
559
+ /* Separator line between pills */
560
+ .theme-pill:not(:last-child) {
561
+ border-bottom: 1px solid rgba(0, 0, 0, 0.08);
562
+ }
563
+ /* Theme pill buttons */
564
+ .theme-pill__btn {
565
+ flex: 1;
566
+ display: flex;
567
+ align-items: center;
568
+ justify-content: center;
569
+ padding: calc(var(--hdk-space-lg) * 1.2); /* 20% bigger total (10% + 10%) */
570
+ border: none;
571
+ background: transparent;
572
+ cursor: pointer;
573
+ transition: all var(--transition-fast, 0.15s ease);
574
+ position: relative;
575
+ overflow: visible; /* Allow border to show */
576
+ }
577
+ /* The light/dark pills are the mode toggle itself: they must always LOOK light
578
+ and dark so the control reads correctly whichever theme is active. These
579
+ literals are deliberate and are the one place theme tokens must not be used. */
580
+ /* Light button default style */
581
+ .theme-pill__btn--light {
582
+ background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
583
+ color: #1e293b;
584
+ border-right: 1px solid rgba(0, 0, 0, 0.1);
585
+ }
586
+ /* Dark button default style - more cyber with darker background and blue tint */
587
+ .theme-pill__btn--dark {
588
+ background: linear-gradient(135deg, #0f172a 0%, #020617 100%);
589
+ color: #64748b;
590
+ border-left: 1px solid rgba(100, 116, 139, 0.1);
591
+ }
592
+ /* First pill top-left corner rounding - rounded corner instead of full circle */
593
+ .theme-pill:first-child .theme-pill__btn--light {
594
+ border-top-left-radius: 12px;
595
+ }
596
+ /* First pill top-right corner rounding */
597
+ .theme-pill:first-child .theme-pill__btn--dark {
598
+ border-top-right-radius: 12px;
599
+ }
600
+ /* Last pill bottom-left corner rounding */
601
+ .theme-pill:last-child .theme-pill__btn--light {
602
+ border-bottom-left-radius: 12px;
603
+ }
604
+ /* Last pill bottom-right corner rounding */
605
+ .theme-pill:last-child .theme-pill__btn--dark {
606
+ border-bottom-right-radius: 12px;
607
+ }
608
+ .theme-pill__btn--light:hover {
609
+ background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
610
+ color: var(--color-primary);
611
+ }
612
+ .theme-pill__btn--dark:hover {
613
+ background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
614
+ color: #cbd5e1;
615
+ }
616
+ /* Active states - with proper border radius that doesn't clip, 10% bigger border */
617
+ .theme-pill__btn--light.active {
618
+ background: linear-gradient(135deg, #ffffff 0%, #f0f9ff 100%);
619
+ color: var(--color-primary);
620
+ box-shadow: inset 0 0 0 2.2px var(--color-primary); /* Uses theme primary color */
621
+ font-weight: var(--font-weight-semibold, 600);
622
+ }
623
+ .theme-pill__btn--dark.active {
624
+ background: linear-gradient(135deg, #020617 0%, #000000 100%);
625
+ color: var(--color-primary); /* Use theme primary color */
626
+ box-shadow: inset 0 0 0 2.2px var(--color-primary); /* Use theme primary color */
627
+ font-weight: var(--font-weight-semibold, 600);
628
+ /* Add glow effect for cyber look */
629
+ filter: brightness(1.1);
630
+ }
631
+ /* Icon container - reduced by ~15% (24px -> 20px) */
632
+ .theme-pill__icon {
633
+ display: flex;
634
+ align-items: center;
635
+ justify-content: center;
636
+ width: 20px;
637
+ height: 20px;
638
+ }
639
+ .theme-pill__icon svg {
640
+ width: 100%;
641
+ height: 100%;
642
+ }
643
+ /* Settings icon button - circular, separate from pill tower */
644
+ .theme-picker__settings-icon {
645
+ display: flex;
646
+ align-items: center;
647
+ justify-content: center;
648
+ width: 44px;
649
+ height: 44px;
650
+ padding: 0;
651
+ border: none;
652
+ background: var(--color-bg-card);
653
+ color: var(--color-text-secondary);
654
+ cursor: pointer;
655
+ transition: all var(--transition-fast, 0.15s ease);
656
+ border-radius: 50%; /* Circular */
657
+ box-shadow: var(--hdk-shadow-md);
658
+ flex-shrink: 0;
659
+ }
660
+ .theme-picker__settings-icon:hover {
661
+ background: var(--color-primary-bg);
662
+ color: var(--color-on-primary-bg);
663
+ transform: rotate(45deg);
664
+ box-shadow: var(--hdk-shadow-lg);
665
+ }
666
+ .theme-picker__settings-icon svg {
667
+ width: 20px;
668
+ height: 20px;
669
+ }
670
+ /* ── Header layout ─────────────────────────────────────────────────────────
671
+ * Title left, action bar right, vertically centered. Full width of whatever
672
+ * column the app places it in. */
673
+ .app-header {
674
+ display: flex;
675
+ align-items: center;
676
+ justify-content: space-between;
677
+ gap: 1rem;
678
+ padding-bottom: 1rem;
679
+ margin-bottom: 1.5rem;
680
+ border-bottom: 1px solid var(--color-border);
681
+ }
682
+ /* Body text, NOT --color-primary. `primary` is a fill colour: sanctioned only
683
+ * under --color-on-primary. As bare text on --color-bg it clears the 3:1
684
+ * large-text floor in just 12 of 18 themes (izakaya-light 1.92, nature-light
685
+ * 2.15, ocean-light 2.28, lavender-light 2.53, pink-light 2.56,
686
+ * strawberry-light 2.61). --color-text clears it everywhere at 8.87–17.58.
687
+ * The brand accent lives in the border-bottom instead. */
688
+ .app-header__title {
689
+ margin: 0;
690
+ color: var(--color-text);
691
+ font-size: var(--hdk-text-lg);
692
+ font-weight: 700;
693
+ line-height: 1.2;
694
+ /* A flex item floors at its min-content width, so without `min-width: 0` a
695
+ * long title pushed the actions off a 320px screen instead of giving way. */
696
+ min-width: 0;
697
+ overflow: hidden;
698
+ text-overflow: ellipsis;
699
+ white-space: nowrap;
700
+ }
701
+ /* Never shrinks: the controls keep their 32px hit areas and the title is what
702
+ * gives way. 0.5rem is enough between icons — the round hit areas already
703
+ * space them visually. */
704
+ .app-header__actions {
705
+ display: flex;
706
+ align-items: center;
707
+ flex-shrink: 0;
708
+ gap: 0.5rem;
709
+ }
710
+ /* In the header the theme picker sits directly left of the settings gear, and
711
+ * the base ThemePicker opens its dropdown SIDEWAYS (which suits a vertical /
712
+ * sidebar placement). Here that would cover the gear — so scope it to drop
713
+ * straight DOWN, right-aligned, opening into the header away from the gear. */
714
+ .app-header .theme-picker__dropdown,
715
+ .app-header .theme-picker--open-left .theme-picker__dropdown {
716
+ top: calc(100% + 8px);
717
+ left: auto;
718
+ right: 0;
719
+ }
720
+ /* ── ConnectedSettings: gear toggle ────────────────────────────────────────
721
+ * The gear itself is a .app-header__icon-btn (icon-btn.css, via
722
+ * theme-picker.css), the same rules as the picker trigger beside it. */
723
+ .settings-popout {
724
+ position: relative;
725
+ display: inline-flex;
726
+ }
727
+ /* Full-viewport click-catcher, same trick ThemePicker uses. */
728
+ .settings-popout__overlay {
729
+ position: fixed;
730
+ inset: 0;
731
+ z-index: 99;
732
+ background: transparent;
733
+ }
734
+ .settings-popout__panel {
735
+ position: absolute;
736
+ top: calc(100% + 8px);
737
+ right: 0;
738
+ z-index: 100;
739
+ /* These apps ship without a Tailwind preflight, so box-sizing is content-box
740
+ * unless a rule says otherwise: without this, `width: 300px` renders 334px
741
+ * (+32 padding +2 border) and `max-width` clamps the CONTENT box, leaving the
742
+ * popout 2px wider than a 320px viewport. */
743
+ box-sizing: border-box;
744
+ width: 300px;
745
+ max-width: calc(100vw - 32px);
746
+ /* Cap height so an app that adds its own preferences section stays on screen;
747
+ * the popout scrolls rather than overflowing the viewport. */
748
+ max-height: calc(100vh - 80px);
749
+ overflow-y: auto;
750
+ display: flex;
751
+ flex-direction: column;
752
+ gap: 14px;
753
+ padding: 16px;
754
+ border-radius: 12px;
755
+ background: var(--color-bg-card);
756
+ border: 1px solid var(--color-border);
757
+ box-shadow: 0 8px 28px rgba(0, 0, 0, 0.18);
758
+ color: var(--color-text);
759
+ }
760
+ /* Divider above the optional app-specific settings section. */
761
+ .settings-popout__app {
762
+ border-top: 1px solid var(--color-border);
763
+ padding-top: 12px;
764
+ }
765
+ .settings-popout__header {
766
+ font-weight: 600;
767
+ font-size: 0.95rem;
768
+ color: var(--color-text);
769
+ }
770
+ .settings-popout__row {
771
+ display: flex;
772
+ align-items: center;
773
+ justify-content: space-between;
774
+ gap: 10px;
775
+ }
776
+ .settings-popout__row--stack {
777
+ flex-direction: column;
778
+ align-items: stretch;
779
+ gap: 8px;
780
+ }
781
+ .settings-popout__label {
782
+ font-size: 0.8rem;
783
+ color: var(--color-text-secondary);
784
+ }
785
+ .settings-popout__value {
786
+ font-size: 0.85rem;
787
+ font-weight: 500;
788
+ color: var(--color-text);
789
+ }
790
+ /* Label + control pair on the right of a row. `min-width: 0` is load-bearing:
791
+ * as a flex item of __row it otherwise floors at its own min-content width, and
792
+ * that includes the input's default `size=20` intrinsic width (195px in Chrome).
793
+ * Pair + gap + Save came to 251.4px against a 300px row that also has to hold
794
+ * the label — so the name row overflowed the panel the moment Edit was clicked,
795
+ * and __panel's `overflow-y: auto` computes overflow-x to `auto`, turning that
796
+ * into a horizontal scrollbar rather than a clip. */
797
+ .settings-popout__inline {
798
+ display: flex;
799
+ align-items: center;
800
+ justify-content: flex-end;
801
+ gap: 6px;
802
+ /* Claim the row's free space rather than shrink-wrapping, so the input below
803
+ * has something to grow into now that its basis is 0. */
804
+ flex: 1 1 auto;
805
+ min-width: 0;
806
+ }
807
+ .settings-popout__tier {
808
+ font-size: 0.78rem;
809
+ font-weight: 600;
810
+ padding: 2px 10px;
811
+ border-radius: 999px;
812
+ background: var(--color-bg-alt);
813
+ color: var(--color-text);
814
+ border: 1px solid var(--color-border);
815
+ }
816
+ .settings-popout__tier--admin {
817
+ background: var(--color-primary-bg);
818
+ color: var(--color-on-primary-bg);
819
+ border-color: var(--color-primary);
820
+ }
821
+ .settings-popout__input {
822
+ /* `1 1 0` + `width: 0`, not `1 1 auto`: the basis must not come from the
823
+ * input's `size`-derived intrinsic width, or the row cannot shrink below it. */
824
+ flex: 1 1 0;
825
+ width: 0;
826
+ min-width: 0;
827
+ box-sizing: border-box;
828
+ font-size: 0.85rem;
829
+ padding: 5px 8px;
830
+ border-radius: 7px;
831
+ border: 1px solid var(--color-border);
832
+ background: var(--color-bg);
833
+ color: var(--color-text);
834
+ }
835
+ .settings-popout__input:focus-visible {
836
+ outline: none;
837
+ border-color: var(--color-primary);
838
+ box-shadow: 0 0 0 2px var(--color-primary-bg);
839
+ }
840
+ .settings-popout__btn {
841
+ font-size: 0.78rem;
842
+ padding: 4px 10px;
843
+ border-radius: 7px;
844
+ border: 1px solid var(--color-border);
845
+ background: var(--color-bg);
846
+ color: var(--color-text);
847
+ cursor: pointer;
848
+ white-space: nowrap;
849
+ text-decoration: none;
850
+ transition:
851
+ background-color 0.15s ease,
852
+ border-color 0.15s ease;
853
+ }
854
+ .settings-popout__btn:hover:not(:disabled) {
855
+ background: var(--color-bg-alt);
856
+ }
857
+ .settings-popout__btn--primary {
858
+ background: var(--color-primary);
859
+ border-color: var(--color-primary);
860
+ color: var(--color-on-primary);
861
+ }
862
+ .settings-popout__btn--primary:hover:not(:disabled) {
863
+ background: var(--color-primary-dark);
864
+ }
865
+ .settings-popout__btn:disabled {
866
+ opacity: 0.6;
867
+ cursor: default;
868
+ }
869
+ /* Content pill — a chunked "volume slider": segments 1..maxLevel, everything
870
+ * up to and including the selected level reads as filled. */
871
+ .settings-popout__pill {
872
+ display: flex;
873
+ gap: 3px;
874
+ width: 100%;
875
+ }
876
+ .settings-popout__seg {
877
+ flex: 1 1 0;
878
+ padding: 7px 0;
879
+ font-size: 0.8rem;
880
+ font-weight: 600;
881
+ border: 1px solid var(--color-border);
882
+ background: var(--color-bg-alt);
883
+ color: var(--color-text-secondary);
884
+ cursor: pointer;
885
+ transition:
886
+ background-color 0.12s ease,
887
+ color 0.12s ease;
888
+ }
889
+ .settings-popout__seg:first-child {
890
+ border-radius: 8px 0 0 8px;
891
+ }
892
+ .settings-popout__seg:last-child {
893
+ border-radius: 0 8px 8px 0;
894
+ }
895
+ .settings-popout__seg--filled {
896
+ background: var(--color-primary);
897
+ border-color: var(--color-primary);
898
+ color: var(--color-on-primary);
899
+ }
900
+ .settings-popout__seg:disabled {
901
+ cursor: default;
902
+ }
903
+ /* Inline sign-in: label above, input + submit on one line, error below. */
904
+ .settings-popout__key-form {
905
+ display: flex;
906
+ flex-direction: column;
907
+ gap: 6px;
908
+ width: 100%;
909
+ }
910
+ .settings-popout__error {
911
+ font-size: 0.75rem;
912
+ line-height: 1.35;
913
+ padding: 4px 8px;
914
+ border-radius: 6px;
915
+ background: var(--color-danger-bg);
916
+ color: var(--color-on-danger-bg);
917
+ }
918
+ .settings-popout__hint {
919
+ font-size: 0.72rem;
920
+ line-height: 1.35;
921
+ color: var(--color-text-tertiary);
922
+ }
923
+ /**
924
+ * Toast Notification Styles
925
+ */
926
+
927
+ .toast-container {
928
+ position: fixed;
929
+ z-index: 10000;
930
+ display: flex;
931
+ gap: 12px;
932
+ pointer-events: none;
933
+ max-width: 500px;
934
+ }
935
+
936
+ /* Position variants */
937
+ .toast-container--top-left {
938
+ top: 20px;
939
+ left: 20px;
940
+ flex-direction: column;
941
+ }
942
+
943
+ .toast-container--top-center {
944
+ top: 20px;
945
+ left: 50%;
946
+ transform: translateX(-50%);
947
+ flex-direction: column;
948
+ width: calc(100% - 40px);
949
+ }
950
+
951
+ .toast-container--top-right {
952
+ top: 20px;
953
+ right: 20px;
954
+ flex-direction: column;
955
+ }
956
+
957
+ .toast-container--bottom-left {
958
+ bottom: 20px;
959
+ left: 20px;
960
+ flex-direction: column-reverse;
961
+ }
962
+
963
+ .toast-container--bottom-center {
964
+ bottom: 20px;
965
+ left: 50%;
966
+ transform: translateX(-50%);
967
+ flex-direction: column-reverse;
968
+ width: calc(100% - 40px);
969
+ }
970
+
971
+ .toast-container--bottom-right {
972
+ bottom: 20px;
973
+ right: 20px;
974
+ flex-direction: column-reverse;
975
+ }
976
+
977
+ .toast {
978
+ display: flex;
979
+ align-items: center;
980
+ gap: 12px;
981
+ padding: 14px 20px;
982
+ background: var(--color-bg-card);
983
+ border: 1px solid var(--color-border-light);
984
+ border-radius: var(--hdk-radius-lg);
985
+ box-shadow: var(--hdk-shadow-md);
986
+ min-width: 280px;
987
+ max-width: 100%;
988
+ opacity: 0;
989
+ transform: translateY(30px) scale(0.9);
990
+ transition:
991
+ opacity 0.5s ease-out,
992
+ transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1),
993
+ box-shadow 0.3s ease;
994
+ pointer-events: auto;
995
+ }
996
+
997
+ .toast--visible {
998
+ opacity: 1;
999
+ transform: translateY(0) scale(1);
1000
+ }
1001
+
1002
+ .toast__icon {
1003
+ flex-shrink: 0;
1004
+ }
1005
+
1006
+ .toast__message {
1007
+ flex: 1;
1008
+ font-size: 14px;
1009
+ color: var(--color-text);
1010
+ line-height: 1.4;
1011
+ }
1012
+
1013
+ /* Toast type variants - using colored shadows for subtle emphasis */
1014
+ .toast--success {
1015
+ box-shadow:
1016
+ 0 0 0 1px var(--success-color, #10b981),
1017
+ 0 4px 12px rgba(16, 185, 129, 0.15),
1018
+ 0 2px 4px rgba(0, 0, 0, 0.1);
1019
+ }
1020
+
1021
+ .toast--success .toast__icon {
1022
+ color: var(--success-color, #10b981);
1023
+ }
1024
+
1025
+ .toast--error {
1026
+ box-shadow:
1027
+ 0 0 0 1px var(--error-color, #ef4444),
1028
+ 0 4px 12px rgba(239, 68, 68, 0.15),
1029
+ 0 2px 4px rgba(0, 0, 0, 0.1);
1030
+ }
1031
+
1032
+ .toast--error .toast__icon {
1033
+ color: var(--error-color, #ef4444);
1034
+ }
1035
+
1036
+ .toast--warning {
1037
+ box-shadow:
1038
+ 0 0 0 1px var(--warning-color, #f59e0b),
1039
+ 0 4px 12px rgba(245, 158, 11, 0.15),
1040
+ 0 2px 4px rgba(0, 0, 0, 0.1);
1041
+ }
1042
+
1043
+ .toast--warning .toast__icon {
1044
+ color: var(--warning-color, #f59e0b);
1045
+ }
1046
+
1047
+ .toast--info {
1048
+ box-shadow:
1049
+ 0 0 0 1px var(--info-color, var(--accent-color, #3b82f6)),
1050
+ 0 4px 12px rgba(59, 130, 246, 0.15),
1051
+ 0 2px 4px rgba(0, 0, 0, 0.1);
1052
+ }
1053
+
1054
+ .toast--info .toast__icon {
1055
+ color: var(--info-color, var(--accent-color, #3b82f6));
1056
+ }
1057
+
1058
+ /* Mobile adjustments */
1059
+ @media (max-width: 768px) {
1060
+ .toast-container {
1061
+ max-width: none;
1062
+ }
1063
+
1064
+ .toast-container--top-left,
1065
+ .toast-container--top-right,
1066
+ .toast-container--bottom-left,
1067
+ .toast-container--bottom-right {
1068
+ left: 10px;
1069
+ right: 10px;
1070
+ }
1071
+
1072
+ .toast-container--top-center,
1073
+ .toast-container--bottom-center {
1074
+ width: calc(100% - 20px);
1075
+ }
1076
+
1077
+ .toast {
1078
+ min-width: unset;
1079
+ padding: 12px 16px;
1080
+ }
1081
+ }
1082
+ /**
1083
+ * ContextMenu Component Styles
1084
+ * Generic context menu with dynamic positioning
1085
+ */
1086
+
1087
+ .context-menu {
1088
+ position: fixed;
1089
+ left: var(--context-menu-x, 0);
1090
+ top: var(--context-menu-y, 0);
1091
+ z-index: var(--context-menu-z-index, 1000);
1092
+ }
1093
+ /* Select / dropdown styles for @wolffm/task-ui-components.
1094
+ *
1095
+ * import '@wolffm/task-ui-components/select.css'
1096
+ * import '@wolffm/task-ui-components/scrollbar.css' (the open list uses it)
1097
+ *
1098
+ * The trigger variants differ only in their resting surface; the open list
1099
+ * is one look everywhere, so a page mixing variants still has one menu.
1100
+ */
1101
+
1102
+ .hdk-select {
1103
+ position: relative;
1104
+ display: inline-flex;
1105
+ min-width: 0;
1106
+ vertical-align: middle;
1107
+ }
1108
+
1109
+ .hdk-select__trigger {
1110
+ display: inline-flex;
1111
+ align-items: center;
1112
+ gap: 8px;
1113
+ width: 100%;
1114
+ min-width: 0;
1115
+ font: inherit;
1116
+ color: var(--color-text);
1117
+ text-align: left;
1118
+ background-color: var(--color-bg-card);
1119
+ border: 1px solid var(--color-border);
1120
+ border-radius: var(--hdk-radius);
1121
+ cursor: pointer;
1122
+ transition:
1123
+ border-color var(--transition-fast),
1124
+ background-color var(--transition-fast),
1125
+ box-shadow var(--transition-fast);
1126
+ }
1127
+
1128
+ .hdk-select__trigger:hover:not(:disabled) {
1129
+ border-color: var(--color-text-muted);
1130
+ }
1131
+
1132
+ .hdk-select__trigger:focus-visible,
1133
+ .hdk-select--open .hdk-select__trigger {
1134
+ outline: none;
1135
+ border-color: var(--color-primary);
1136
+ box-shadow: var(--hdk-shadow-focus-sm);
1137
+ }
1138
+
1139
+ .hdk-select__trigger:disabled {
1140
+ opacity: 0.5;
1141
+ cursor: not-allowed;
1142
+ }
1143
+
1144
+ .hdk-select__value {
1145
+ flex: 1;
1146
+ min-width: 0;
1147
+ overflow: hidden;
1148
+ text-overflow: ellipsis;
1149
+ white-space: nowrap;
1150
+ }
1151
+
1152
+ .hdk-select__value--placeholder {
1153
+ color: var(--color-text-tertiary);
1154
+ }
1155
+
1156
+ .hdk-select__chevron {
1157
+ flex-shrink: 0;
1158
+ color: var(--color-text-tertiary);
1159
+ transition: transform var(--transition-fast);
1160
+ }
1161
+
1162
+ .hdk-select--open .hdk-select__chevron {
1163
+ transform: rotate(180deg);
1164
+ }
1165
+
1166
+ /* ── trigger variants ─────────────────────────────────────────────────── */
1167
+
1168
+ .hdk-select--filled .hdk-select__trigger {
1169
+ background-color: var(--color-bg-hover);
1170
+ border-color: transparent;
1171
+ }
1172
+
1173
+ .hdk-select--filled .hdk-select__trigger:hover:not(:disabled) {
1174
+ border-color: var(--color-border);
1175
+ }
1176
+
1177
+ .hdk-select--ghost .hdk-select__trigger {
1178
+ background-color: transparent;
1179
+ border-color: transparent;
1180
+ }
1181
+
1182
+ .hdk-select--ghost .hdk-select__trigger:hover:not(:disabled) {
1183
+ background-color: var(--color-bg-hover);
1184
+ border-color: transparent;
1185
+ }
1186
+
1187
+ .hdk-select--underline .hdk-select__trigger {
1188
+ background-color: transparent;
1189
+ border-color: transparent;
1190
+ border-bottom-color: var(--color-border);
1191
+ border-radius: 0;
1192
+ }
1193
+
1194
+ .hdk-select--underline .hdk-select__trigger:focus-visible,
1195
+ .hdk-select--underline.hdk-select--open .hdk-select__trigger {
1196
+ border-color: transparent;
1197
+ border-bottom-color: var(--color-primary);
1198
+ box-shadow: 0 1px 0 0 var(--color-primary);
1199
+ }
1200
+
1201
+ /* ── sizes ────────────────────────────────────────────────────────────── */
1202
+
1203
+ .hdk-select--sm .hdk-select__trigger {
1204
+ font-size: var(--hdk-text-xs);
1205
+ padding: 4px 8px;
1206
+ min-height: 28px;
1207
+ }
1208
+
1209
+ .hdk-select--md .hdk-select__trigger {
1210
+ font-size: var(--hdk-text-sm);
1211
+ padding: 7px 10px;
1212
+ min-height: 34px;
1213
+ }
1214
+
1215
+ .hdk-select--lg .hdk-select__trigger {
1216
+ font-size: var(--hdk-text-base);
1217
+ padding: 10px 12px;
1218
+ min-height: 42px;
1219
+ }
1220
+
1221
+ /* ── open list ────────────────────────────────────────────────────────── */
1222
+
1223
+ .hdk-select__menu {
1224
+ position: fixed;
1225
+ z-index: 1000;
1226
+ margin: 0;
1227
+ padding: 4px;
1228
+ list-style: none;
1229
+ overflow-y: auto;
1230
+ max-width: min(420px, calc(100vw - 16px));
1231
+ background-color: var(--color-bg-card);
1232
+ color: var(--color-text);
1233
+ border: 1px solid var(--color-border);
1234
+ border-radius: var(--hdk-radius-lg);
1235
+ box-shadow: var(--hdk-shadow-lg);
1236
+ }
1237
+
1238
+ .hdk-select__menu:focus {
1239
+ outline: none;
1240
+ }
1241
+
1242
+ .hdk-select--sm .hdk-select__menu {
1243
+ font-size: var(--hdk-text-xs);
1244
+ }
1245
+
1246
+ .hdk-select--md .hdk-select__menu {
1247
+ font-size: var(--hdk-text-sm);
1248
+ }
1249
+
1250
+ .hdk-select--lg .hdk-select__menu {
1251
+ font-size: var(--hdk-text-base);
1252
+ }
1253
+
1254
+ .hdk-select__option {
1255
+ display: flex;
1256
+ align-items: center;
1257
+ gap: 8px;
1258
+ padding: 7px 10px;
1259
+ border-radius: var(--hdk-radius);
1260
+ cursor: pointer;
1261
+ }
1262
+
1263
+ .hdk-select__option--active {
1264
+ background-color: var(--color-bg-hover);
1265
+ }
1266
+
1267
+ .hdk-select__option--selected {
1268
+ background-color: var(--color-primary-bg);
1269
+ color: var(--color-on-primary-bg);
1270
+ }
1271
+
1272
+ .hdk-select__option--selected.hdk-select__option--active {
1273
+ box-shadow: inset 0 0 0 1px var(--color-primary);
1274
+ }
1275
+
1276
+ /* An <optgroup> label in a list installSelectMenus builds from a native select. */
1277
+ .hdk-select__group {
1278
+ padding: 8px 10px 2px;
1279
+ font-size: 0.85em;
1280
+ font-weight: var(--font-weight-semibold);
1281
+ color: var(--color-text-secondary);
1282
+ cursor: default;
1283
+ }
1284
+
1285
+ .hdk-select__option[aria-disabled='true'] {
1286
+ opacity: 0.45;
1287
+ cursor: not-allowed;
1288
+ }
1289
+
1290
+ .hdk-select__option-text {
1291
+ display: flex;
1292
+ flex-direction: column;
1293
+ flex: 1;
1294
+ min-width: 0;
1295
+ }
1296
+
1297
+ .hdk-select__option-label {
1298
+ overflow: hidden;
1299
+ text-overflow: ellipsis;
1300
+ white-space: nowrap;
1301
+ }
1302
+
1303
+ .hdk-select__option-desc {
1304
+ font-size: 0.85em;
1305
+ color: var(--color-text-tertiary);
1306
+ }
1307
+
1308
+ .hdk-select__option--selected .hdk-select__option-desc {
1309
+ color: inherit;
1310
+ opacity: 0.85;
1311
+ }
1312
+
1313
+ .hdk-select__check {
1314
+ flex-shrink: 0;
1315
+ }
1316
+
1317
+ /* ── native ───────────────────────────────────────────────────────────── */
1318
+
1319
+ /* Doubled class so the room for the chevron beats each size's `padding`
1320
+ * shorthand above, which would otherwise draw the chevron over the text. */
1321
+ .hdk-select__trigger.hdk-select__native {
1322
+ appearance: none;
1323
+ padding-right: 32px;
1324
+ }
1325
+
1326
+ .hdk-select--sm .hdk-select__trigger.hdk-select__native {
1327
+ padding-right: 26px;
1328
+ }
1329
+
1330
+ .hdk-select__chevron--native {
1331
+ position: absolute;
1332
+ right: 10px;
1333
+ top: 50%;
1334
+ translate: 0 -50%;
1335
+ pointer-events: none;
1336
+ }
1337
+
1338
+ /* The OS popup takes option colours from the <select>; without these, a dark
1339
+ * theme can render dark-on-dark options in Chromium on Windows. */
1340
+ .hdk-select__native option {
1341
+ background-color: var(--color-bg-card);
1342
+ color: var(--color-text);
1343
+ }
1344
+ /* Themed scrollbars for @wolffm/task-ui-components.
1345
+ *
1346
+ * Without this, every scroller renders the platform's bar: a grey Windows
1347
+ * gutter on a coffee or cyberpunk surface. `color-scheme` (set per theme in
1348
+ * @wolffm/themes) only gets the bar to the right light/dark mode, not the
1349
+ * theme's colours.
1350
+ *
1351
+ * import '@wolffm/task-ui-components/scrollbar.css'
1352
+ * <div className="task-app-container hdk-scroll">…</div>
1353
+ *
1354
+ * `.hdk-scroll` styles the element it is on AND every scroller inside it, so
1355
+ * one class on an app's root covers the whole app — hadoku_site puts it on
1356
+ * <html>, which covers every micro-frontend at once. The thumb is the theme's
1357
+ * primary colour.
1358
+ *
1359
+ * Every selector is wrapped in `:where()`, so all of this has ZERO
1360
+ * specificity: it is a fallback for scrollers nobody styled. Any rule an app
1361
+ * writes for a scroller — `scrollbar-width: none` on a carousel, its own
1362
+ * `::-webkit-scrollbar` thumb — wins, whatever order the sheets load in.
1363
+ * Modifiers sit on the same element:
1364
+ *
1365
+ * --subtle neutral thumb instead of the accent
1366
+ * --thin narrower bar (WebKit/Chromium only; see Firefox below)
1367
+ * --rail visible track, so the scroll extent reads at a glance
1368
+ * --autohide thumb appears only while that scroller is hovered or focused
1369
+ *
1370
+ * The colours are declared on EVERY element under the root (`.hdk-scroll *`),
1371
+ * not once on it. A custom property inherits its RESOLVED value, so a thumb
1372
+ * colour set only on the root would carry the root's theme down into a nested
1373
+ * `[data-theme]` scope: a cyberpunk preview inside a coffee app would scroll
1374
+ * with coffee's accent. Declared per element, each scroller resolves the
1375
+ * tokens of the theme it actually sits in. Sizes are theme-independent and
1376
+ * simply inherit.
1377
+ *
1378
+ * Two engines, two mechanisms. Firefox only knows the standard
1379
+ * `scrollbar-color`/`scrollbar-width`, and only ever gets `auto` width —
1380
+ * `--thin` included. Under overlay scrollbars (GTK's default on Linux)
1381
+ * `thin` draws the idle thumb as a ~1px hairline that antialiases
1382
+ * unevenly along its length and only widens on hover; `auto` idles at a solid
1383
+ * 2px and hovers at 6px, and with classic scrollbars it is a 6px thumb, the
1384
+ * same as the WebKit default below. Chromium and Safari support the
1385
+ * `::-webkit-scrollbar` pseudo-elements, which are the only way to get a
1386
+ * rounded, inset thumb — but Chromium 121+ IGNORES them whenever
1387
+ * `scrollbar-color` or `scrollbar-width` is non-auto, and `scrollbar-color`
1388
+ * inherits. So the WebKit branch resets both to `auto`, or a host page that
1389
+ * sets `scrollbar-color` on <html> would silently switch all of this off.
1390
+ *
1391
+ * Everything sits in `@layer hdk-fallbacks`. A rule in a cascade layer loses
1392
+ * to every unlayered rule and to every layer declared after it — whatever
1393
+ * either's specificity — and Tailwind ships its utilities in layers. Unlayered,
1394
+ * even a zero-specificity rule here would beat `bg-primary`. The page must
1395
+ * declare the layer FIRST, before any other, so it ranks lowest:
1396
+ *
1397
+ * @layer hdk-fallbacks, base, theme, utilities;
1398
+ *
1399
+ * hadoku_site's global.css does that. A sheet that never names it just gets
1400
+ * these rules below its own unlayered ones, which is also correct.
1401
+ */
1402
+
1403
+ @layer hdk-fallbacks {
1404
+ :where(.hdk-scroll) {
1405
+ --hdk-scrollbar-size: 10px;
1406
+ --hdk-scrollbar-pad: 2px;
1407
+ }
1408
+
1409
+ :where(.hdk-scroll),
1410
+ :where(.hdk-scroll *) {
1411
+ --hdk-scrollbar-thumb: var(--color-primary);
1412
+ --hdk-scrollbar-thumb-hover: var(--color-primary-dark);
1413
+ --hdk-scrollbar-track: transparent;
1414
+ }
1415
+
1416
+ :where(.hdk-scroll--thin) {
1417
+ --hdk-scrollbar-size: 6px;
1418
+ --hdk-scrollbar-pad: 1px;
1419
+ }
1420
+
1421
+ :where(.hdk-scroll--subtle),
1422
+ :where(.hdk-scroll--subtle *) {
1423
+ --hdk-scrollbar-thumb: var(--color-border);
1424
+ --hdk-scrollbar-thumb-hover: var(--color-text-muted);
1425
+ }
1426
+
1427
+ :where(.hdk-scroll--rail) {
1428
+ --hdk-scrollbar-size: 12px;
1429
+ --hdk-scrollbar-pad: 3px;
1430
+ }
1431
+
1432
+ :where(.hdk-scroll--rail),
1433
+ :where(.hdk-scroll--rail *) {
1434
+ --hdk-scrollbar-track: var(--color-bg-hover);
1435
+ }
1436
+
1437
+ :where(.hdk-scroll--autohide),
1438
+ :where(.hdk-scroll--autohide *) {
1439
+ --hdk-scrollbar-thumb: transparent;
1440
+ }
1441
+
1442
+ /* On the hovered/focused element itself, so only that scroller's thumb shows —
1443
+ * not every scroller inside an autohide root at once. */
1444
+ :where(.hdk-scroll--autohide:hover),
1445
+ :where(.hdk-scroll--autohide:focus-within),
1446
+ :where(.hdk-scroll--autohide :hover),
1447
+ :where(.hdk-scroll--autohide :focus-within) {
1448
+ --hdk-scrollbar-thumb: var(--color-primary);
1449
+ }
1450
+
1451
+ @supports not selector(::-webkit-scrollbar) {
1452
+ :where(.hdk-scroll),
1453
+ :where(.hdk-scroll *) {
1454
+ scrollbar-width: auto;
1455
+ scrollbar-color: var(--hdk-scrollbar-thumb) var(--hdk-scrollbar-track);
1456
+ }
1457
+ }
1458
+
1459
+ @supports selector(::-webkit-scrollbar) {
1460
+ :where(.hdk-scroll),
1461
+ :where(.hdk-scroll *) {
1462
+ scrollbar-width: auto;
1463
+ scrollbar-color: auto;
1464
+ }
1465
+
1466
+ :where(.hdk-scroll)::-webkit-scrollbar,
1467
+ :where(.hdk-scroll *)::-webkit-scrollbar {
1468
+ width: var(--hdk-scrollbar-size);
1469
+ height: var(--hdk-scrollbar-size);
1470
+ background-color: var(--hdk-scrollbar-track);
1471
+ }
1472
+
1473
+ :where(.hdk-scroll)::-webkit-scrollbar-track,
1474
+ :where(.hdk-scroll *)::-webkit-scrollbar-track {
1475
+ background-color: var(--hdk-scrollbar-track);
1476
+ border-radius: 999px;
1477
+ }
1478
+
1479
+ /* The transparent border plus padding-box clip is what insets the thumb
1480
+ * from the track edge — a margin does nothing on a scrollbar part. */
1481
+ :where(.hdk-scroll)::-webkit-scrollbar-thumb,
1482
+ :where(.hdk-scroll *)::-webkit-scrollbar-thumb {
1483
+ background-color: var(--hdk-scrollbar-thumb);
1484
+ background-clip: padding-box;
1485
+ border: var(--hdk-scrollbar-pad) solid transparent;
1486
+ border-radius: 999px;
1487
+ min-height: 32px;
1488
+ min-width: 32px;
1489
+ }
1490
+
1491
+ :where(.hdk-scroll)::-webkit-scrollbar-thumb:hover,
1492
+ :where(.hdk-scroll *)::-webkit-scrollbar-thumb:hover,
1493
+ :where(.hdk-scroll)::-webkit-scrollbar-thumb:active,
1494
+ :where(.hdk-scroll *)::-webkit-scrollbar-thumb:active {
1495
+ background-color: var(--hdk-scrollbar-thumb-hover);
1496
+ }
1497
+
1498
+ :where(.hdk-scroll)::-webkit-scrollbar-corner,
1499
+ :where(.hdk-scroll *)::-webkit-scrollbar-corner {
1500
+ background-color: transparent;
1501
+ }
1502
+
1503
+ :where(.hdk-scroll)::-webkit-scrollbar-button,
1504
+ :where(.hdk-scroll *)::-webkit-scrollbar-button {
1505
+ display: none;
1506
+ }
1507
+ }
1508
+ }
1509
+ /* Button styles for @wolffm/task-ui-components.
1510
+ *
1511
+ * import '@wolffm/task-ui-components/button.css'
1512
+ *
1513
+ * Each family modifier maps its six theme tokens onto private `--_btn-*`
1514
+ * properties, and each variant paints from those. That keeps the matrix at
1515
+ * 5 + 5 rules instead of 25, and every pairing below is a sanctioned one:
1516
+ * `on-<f>` over `<f>`, `on-<f>-bg` over `<f>-bg` (THEME_USAGE_GUIDE.md).
1517
+ */
1518
+
1519
+ .hdk-btn {
1520
+ --_btn-c: var(--color-primary);
1521
+ --_btn-c-dark: var(--color-primary-dark);
1522
+ --_btn-on: var(--color-on-primary);
1523
+ --_btn-tint: var(--color-primary-bg);
1524
+ --_btn-on-tint: var(--color-on-primary-bg);
1525
+ --_btn-hover: var(--color-primary-hover);
1526
+
1527
+ display: inline-flex;
1528
+ align-items: center;
1529
+ justify-content: center;
1530
+ gap: 6px;
1531
+ font: inherit;
1532
+ font-weight: var(--font-weight-semibold);
1533
+ line-height: 1.2;
1534
+ white-space: nowrap;
1535
+ border: 1px solid transparent;
1536
+ border-radius: var(--hdk-radius);
1537
+ cursor: pointer;
1538
+ user-select: none;
1539
+ transition:
1540
+ background-color var(--transition-fast),
1541
+ border-color var(--transition-fast),
1542
+ color var(--transition-fast),
1543
+ box-shadow var(--transition-fast),
1544
+ transform var(--transition-fast);
1545
+ }
1546
+
1547
+ .hdk-btn:focus-visible {
1548
+ outline: none;
1549
+ box-shadow: var(--hdk-shadow-focus);
1550
+ }
1551
+
1552
+ .hdk-btn:active:not(:disabled) {
1553
+ transform: translateY(1px);
1554
+ }
1555
+
1556
+ .hdk-btn:disabled {
1557
+ opacity: 0.5;
1558
+ cursor: not-allowed;
1559
+ }
1560
+
1561
+ /* A loading button is disabled for clicks but is not "unavailable", so it
1562
+ * keeps full opacity — the spinner is the signal. */
1563
+ .hdk-btn--loading:disabled {
1564
+ opacity: 1;
1565
+ cursor: progress;
1566
+ }
1567
+
1568
+ /* ── families ─────────────────────────────────────────────────────────── */
1569
+
1570
+ .hdk-btn--success {
1571
+ --_btn-c: var(--color-success);
1572
+ --_btn-c-dark: var(--color-success-dark);
1573
+ --_btn-on: var(--color-on-success);
1574
+ --_btn-tint: var(--color-success-bg);
1575
+ --_btn-on-tint: var(--color-on-success-bg);
1576
+ --_btn-hover: var(--color-success-hover);
1577
+ }
1578
+
1579
+ .hdk-btn--warning {
1580
+ --_btn-c: var(--color-warning);
1581
+ --_btn-c-dark: var(--color-warning-dark);
1582
+ --_btn-on: var(--color-on-warning);
1583
+ --_btn-tint: var(--color-warning-bg);
1584
+ --_btn-on-tint: var(--color-on-warning-bg);
1585
+ --_btn-hover: var(--color-warning-hover);
1586
+ }
1587
+
1588
+ .hdk-btn--danger {
1589
+ --_btn-c: var(--color-danger);
1590
+ --_btn-c-dark: var(--color-danger-dark);
1591
+ --_btn-on: var(--color-on-danger);
1592
+ --_btn-tint: var(--color-danger-bg);
1593
+ --_btn-on-tint: var(--color-on-danger-bg);
1594
+ --_btn-hover: var(--color-danger-hover);
1595
+ }
1596
+
1597
+ .hdk-btn--neutral {
1598
+ --_btn-c: var(--color-neutral);
1599
+ --_btn-c-dark: var(--color-neutral-dark);
1600
+ --_btn-on: var(--color-on-neutral);
1601
+ --_btn-tint: var(--color-neutral-bg);
1602
+ --_btn-on-tint: var(--color-on-neutral-bg);
1603
+ --_btn-hover: var(--color-neutral-hover);
1604
+ }
1605
+
1606
+ /* ── variants ─────────────────────────────────────────────────────────── */
1607
+
1608
+ .hdk-btn--solid {
1609
+ background-color: var(--_btn-c);
1610
+ color: var(--_btn-on);
1611
+ }
1612
+
1613
+ .hdk-btn--solid:hover:not(:disabled) {
1614
+ background-color: var(--_btn-c-dark);
1615
+ }
1616
+
1617
+ /* The task app's own `.pill-btn--active` look, as a reusable treatment. */
1618
+ .hdk-btn--gradient {
1619
+ background: linear-gradient(180deg, var(--_btn-c) 0%, var(--_btn-c-dark) 100%);
1620
+ color: var(--_btn-on);
1621
+ box-shadow: var(--hdk-shadow-sm);
1622
+ }
1623
+
1624
+ .hdk-btn--gradient:hover:not(:disabled) {
1625
+ box-shadow: var(--hdk-shadow-md);
1626
+ }
1627
+
1628
+ .hdk-btn--soft {
1629
+ background-color: var(--_btn-tint);
1630
+ color: var(--_btn-on-tint);
1631
+ }
1632
+
1633
+ .hdk-btn--soft:hover:not(:disabled) {
1634
+ border-color: var(--_btn-c);
1635
+ }
1636
+
1637
+ .hdk-btn--outline {
1638
+ background-color: transparent;
1639
+ border-color: var(--_btn-c);
1640
+ color: var(--color-text);
1641
+ }
1642
+
1643
+ .hdk-btn--outline:hover:not(:disabled) {
1644
+ background-color: var(--_btn-tint);
1645
+ color: var(--_btn-on-tint);
1646
+ }
1647
+
1648
+ .hdk-btn--ghost {
1649
+ background-color: transparent;
1650
+ color: var(--color-text-secondary);
1651
+ }
1652
+
1653
+ .hdk-btn--ghost:hover:not(:disabled) {
1654
+ background-color: var(--_btn-tint);
1655
+ color: var(--_btn-on-tint);
1656
+ }
1657
+
1658
+ /* ── sizes ────────────────────────────────────────────────────────────── */
1659
+
1660
+ .hdk-btn--sm {
1661
+ font-size: var(--hdk-text-xs);
1662
+ padding: 4px 10px;
1663
+ min-height: 28px;
1664
+ }
1665
+
1666
+ .hdk-btn--md {
1667
+ font-size: var(--hdk-text-sm);
1668
+ padding: 7px 14px;
1669
+ min-height: 34px;
1670
+ }
1671
+
1672
+ .hdk-btn--lg {
1673
+ font-size: var(--hdk-text-base);
1674
+ padding: 10px 18px;
1675
+ min-height: 42px;
1676
+ }
1677
+
1678
+ .hdk-btn--icon-only {
1679
+ padding: 0;
1680
+ aspect-ratio: 1;
1681
+ }
1682
+
1683
+ .hdk-btn--icon-only.hdk-btn--sm {
1684
+ width: 28px;
1685
+ }
1686
+
1687
+ .hdk-btn--icon-only.hdk-btn--md {
1688
+ width: 34px;
1689
+ }
1690
+
1691
+ .hdk-btn--icon-only.hdk-btn--lg {
1692
+ width: 42px;
1693
+ }
1694
+
1695
+ /* ── shapes ───────────────────────────────────────────────────────────── */
1696
+
1697
+ .hdk-btn--pill {
1698
+ border-radius: 999px;
1699
+ }
1700
+
1701
+ .hdk-btn--square {
1702
+ border-radius: var(--hdk-radius-sm);
1703
+ }
1704
+
1705
+ .hdk-btn--block {
1706
+ display: flex;
1707
+ width: 100%;
1708
+ }
1709
+
1710
+ /* ── loading ──────────────────────────────────────────────────────────── */
1711
+
1712
+ .hdk-btn__spinner {
1713
+ width: 1em;
1714
+ height: 1em;
1715
+ border: 2px solid currentColor;
1716
+ border-right-color: transparent;
1717
+ border-radius: 50%;
1718
+ animation: hdk-btn-spin 0.7s linear infinite;
1719
+ }
1720
+
1721
+ @keyframes hdk-btn-spin {
1722
+ to {
1723
+ transform: rotate(360deg);
1724
+ }
1725
+ }
1726
+
1727
+ @media (prefers-reduced-motion: reduce) {
1728
+ .hdk-btn__spinner {
1729
+ animation-duration: 2s;
1730
+ }
1731
+
1732
+ .hdk-btn:active:not(:disabled) {
1733
+ transform: none;
1734
+ }
1735
+ }
1736
+ /* NumberInput styles for @wolffm/task-ui-components.
1737
+ *
1738
+ * import '@wolffm/task-ui-components/number-input.css'
1739
+ *
1740
+ * One bordered box holding the field and a stacked up/down stepper, so the
1741
+ * focus ring and the border belong to the whole control, as on <Select>.
1742
+ */
1743
+
1744
+ .hdk-number {
1745
+ display: inline-flex;
1746
+ align-items: stretch;
1747
+ min-width: 0;
1748
+ vertical-align: middle;
1749
+ color: var(--color-text);
1750
+ background-color: var(--color-bg-card);
1751
+ border: 1px solid var(--color-border);
1752
+ border-radius: var(--hdk-radius);
1753
+ overflow: hidden;
1754
+ transition:
1755
+ border-color var(--transition-fast),
1756
+ box-shadow var(--transition-fast);
1757
+ }
1758
+
1759
+ .hdk-number:hover:not(.hdk-number--disabled) {
1760
+ border-color: var(--color-text-muted);
1761
+ }
1762
+
1763
+ .hdk-number:focus-within {
1764
+ border-color: var(--color-primary);
1765
+ box-shadow: var(--hdk-shadow-focus-sm);
1766
+ }
1767
+
1768
+ .hdk-number--disabled {
1769
+ opacity: 0.5;
1770
+ }
1771
+
1772
+ .hdk-number__input {
1773
+ flex: 1;
1774
+ width: 100%;
1775
+ min-width: 0;
1776
+ font: inherit;
1777
+ color: inherit;
1778
+ background: transparent;
1779
+ border: 0;
1780
+ appearance: textfield;
1781
+ }
1782
+
1783
+ .hdk-number__input:focus {
1784
+ outline: none;
1785
+ }
1786
+
1787
+ .hdk-number__input::-webkit-inner-spin-button,
1788
+ .hdk-number__input::-webkit-outer-spin-button {
1789
+ appearance: none;
1790
+ margin: 0;
1791
+ }
1792
+
1793
+ .hdk-number__steps {
1794
+ display: flex;
1795
+ flex-direction: column;
1796
+ flex-shrink: 0;
1797
+ border-left: 1px solid var(--color-border);
1798
+ }
1799
+
1800
+ .hdk-number__step {
1801
+ display: flex;
1802
+ flex: 1;
1803
+ align-items: center;
1804
+ justify-content: center;
1805
+ padding: 0;
1806
+ color: var(--color-text-secondary);
1807
+ background: transparent;
1808
+ border: 0;
1809
+ cursor: pointer;
1810
+ transition:
1811
+ background-color var(--transition-fast),
1812
+ color var(--transition-fast);
1813
+ }
1814
+
1815
+ .hdk-number__step + .hdk-number__step {
1816
+ border-top: 1px solid var(--color-border);
1817
+ }
1818
+
1819
+ .hdk-number__step:hover:not(:disabled) {
1820
+ color: var(--color-on-primary-bg);
1821
+ background-color: var(--color-primary-bg);
1822
+ }
1823
+
1824
+ .hdk-number__step:disabled {
1825
+ opacity: 0.4;
1826
+ cursor: not-allowed;
1827
+ }
1828
+
1829
+ .hdk-number--sm {
1830
+ font-size: var(--hdk-text-xs);
1831
+ min-height: 28px;
1832
+ }
1833
+
1834
+ .hdk-number--sm .hdk-number__input {
1835
+ padding: 4px 8px;
1836
+ }
1837
+
1838
+ .hdk-number--sm .hdk-number__step {
1839
+ width: 18px;
1840
+ }
1841
+
1842
+ .hdk-number--md {
1843
+ font-size: var(--hdk-text-sm);
1844
+ min-height: 34px;
1845
+ }
1846
+
1847
+ .hdk-number--md .hdk-number__input {
1848
+ padding: 7px 10px;
1849
+ }
1850
+
1851
+ .hdk-number--md .hdk-number__step {
1852
+ width: 22px;
1853
+ }
1854
+
1855
+ .hdk-number--lg {
1856
+ font-size: var(--hdk-text-base);
1857
+ min-height: 42px;
1858
+ }
1859
+
1860
+ .hdk-number--lg .hdk-number__input {
1861
+ padding: 10px 12px;
1862
+ }
1863
+
1864
+ .hdk-number--lg .hdk-number__step {
1865
+ width: 26px;
1866
+ }