tyrell-components 1.0.0-RC11 → 1.0.0-RC13

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 (240) hide show
  1. package/README.md +1 -0
  2. package/css/tyrell-colors-static.css +316 -0
  3. package/css/tyrell-theme.css +1454 -0
  4. package/css/tyrell.css +112 -420
  5. package/dist/tyrell-colors-static.css +316 -0
  6. package/dist/tyrell-theme.css +1454 -0
  7. package/dist/tyrell.css +112 -420
  8. package/dist/tyrell.js +1 -1
  9. package/lib/base/ty-component.d.ts +7 -0
  10. package/lib/base/ty-component.d.ts.map +1 -1
  11. package/lib/base/ty-component.js +27 -0
  12. package/lib/base/ty-component.js.map +1 -1
  13. package/lib/components/button.d.ts +16 -1
  14. package/lib/components/button.d.ts.map +1 -1
  15. package/lib/components/button.js +34 -4
  16. package/lib/components/button.js.map +1 -1
  17. package/lib/components/calendar-month.d.ts +23 -0
  18. package/lib/components/calendar-month.d.ts.map +1 -1
  19. package/lib/components/calendar-month.js +168 -11
  20. package/lib/components/calendar-month.js.map +1 -1
  21. package/lib/components/calendar-navigation.d.ts +31 -5
  22. package/lib/components/calendar-navigation.d.ts.map +1 -1
  23. package/lib/components/calendar-navigation.js +71 -31
  24. package/lib/components/calendar-navigation.js.map +1 -1
  25. package/lib/components/calendar.d.ts +17 -2
  26. package/lib/components/calendar.d.ts.map +1 -1
  27. package/lib/components/calendar.js +83 -12
  28. package/lib/components/calendar.js.map +1 -1
  29. package/lib/components/checkbox.d.ts +14 -3
  30. package/lib/components/checkbox.d.ts.map +1 -1
  31. package/lib/components/checkbox.js +86 -57
  32. package/lib/components/checkbox.js.map +1 -1
  33. package/lib/components/copy.d.ts +14 -3
  34. package/lib/components/copy.d.ts.map +1 -1
  35. package/lib/components/copy.js +47 -25
  36. package/lib/components/copy.js.map +1 -1
  37. package/lib/components/date-picker.d.ts +26 -1
  38. package/lib/components/date-picker.d.ts.map +1 -1
  39. package/lib/components/date-picker.js +151 -32
  40. package/lib/components/date-picker.js.map +1 -1
  41. package/lib/components/input.d.ts +17 -2
  42. package/lib/components/input.d.ts.map +1 -1
  43. package/lib/components/input.js +104 -25
  44. package/lib/components/input.js.map +1 -1
  45. package/lib/components/modal.d.ts.map +1 -1
  46. package/lib/components/modal.js +28 -3
  47. package/lib/components/modal.js.map +1 -1
  48. package/lib/components/option.d.ts +0 -27
  49. package/lib/components/option.d.ts.map +1 -1
  50. package/lib/components/option.js +11 -102
  51. package/lib/components/option.js.map +1 -1
  52. package/lib/components/popup.d.ts.map +1 -1
  53. package/lib/components/popup.js +36 -2
  54. package/lib/components/popup.js.map +1 -1
  55. package/lib/components/radio.d.ts +6 -2
  56. package/lib/components/radio.d.ts.map +1 -1
  57. package/lib/components/radio.js +55 -13
  58. package/lib/components/radio.js.map +1 -1
  59. package/lib/components/scroll-container.d.ts +16 -0
  60. package/lib/components/scroll-container.d.ts.map +1 -1
  61. package/lib/components/scroll-container.js +105 -1
  62. package/lib/components/scroll-container.js.map +1 -1
  63. package/lib/components/select.d.ts +550 -0
  64. package/lib/components/select.d.ts.map +1 -0
  65. package/lib/components/select.js +2115 -0
  66. package/lib/components/select.js.map +1 -0
  67. package/lib/components/selected-tags.d.ts +93 -0
  68. package/lib/components/selected-tags.d.ts.map +1 -0
  69. package/lib/components/selected-tags.js +286 -0
  70. package/lib/components/selected-tags.js.map +1 -0
  71. package/lib/components/switch.d.ts +6 -3
  72. package/lib/components/switch.d.ts.map +1 -1
  73. package/lib/components/switch.js +54 -36
  74. package/lib/components/switch.js.map +1 -1
  75. package/lib/components/tabs.d.ts.map +1 -1
  76. package/lib/components/tabs.js +190 -25
  77. package/lib/components/tabs.js.map +1 -1
  78. package/lib/components/tag.d.ts +9 -1
  79. package/lib/components/tag.d.ts.map +1 -1
  80. package/lib/components/tag.js +41 -5
  81. package/lib/components/tag.js.map +1 -1
  82. package/lib/components/textarea.d.ts +3 -0
  83. package/lib/components/textarea.d.ts.map +1 -1
  84. package/lib/components/textarea.js +61 -6
  85. package/lib/components/textarea.js.map +1 -1
  86. package/lib/components/tooltip.d.ts +4 -2
  87. package/lib/components/tooltip.d.ts.map +1 -1
  88. package/lib/components/tooltip.js +109 -70
  89. package/lib/components/tooltip.js.map +1 -1
  90. package/lib/components/wizard.d.ts.map +1 -1
  91. package/lib/components/wizard.js +22 -3
  92. package/lib/components/wizard.js.map +1 -1
  93. package/lib/index.d.ts +3 -2
  94. package/lib/index.d.ts.map +1 -1
  95. package/lib/index.js +3 -2
  96. package/lib/index.js.map +1 -1
  97. package/lib/styles/button.d.ts +41 -6
  98. package/lib/styles/button.d.ts.map +1 -1
  99. package/lib/styles/button.js +294 -254
  100. package/lib/styles/button.js.map +1 -1
  101. package/lib/styles/calendar-month.d.ts +3 -1
  102. package/lib/styles/calendar-month.d.ts.map +1 -1
  103. package/lib/styles/calendar-month.js +100 -67
  104. package/lib/styles/calendar-month.js.map +1 -1
  105. package/lib/styles/calendar-navigation.d.ts +1 -1
  106. package/lib/styles/calendar-navigation.d.ts.map +1 -1
  107. package/lib/styles/calendar-navigation.js +20 -28
  108. package/lib/styles/calendar-navigation.js.map +1 -1
  109. package/lib/styles/checkbox.d.ts +7 -5
  110. package/lib/styles/checkbox.d.ts.map +1 -1
  111. package/lib/styles/checkbox.js +125 -8
  112. package/lib/styles/checkbox.js.map +1 -1
  113. package/lib/styles/copy.d.ts +3 -1
  114. package/lib/styles/copy.d.ts.map +1 -1
  115. package/lib/styles/copy.js +61 -6
  116. package/lib/styles/copy.js.map +1 -1
  117. package/lib/styles/custom-scrollbar.d.ts +1 -1
  118. package/lib/styles/custom-scrollbar.d.ts.map +1 -1
  119. package/lib/styles/custom-scrollbar.js +2 -2
  120. package/lib/styles/date-picker.d.ts +3 -1
  121. package/lib/styles/date-picker.d.ts.map +1 -1
  122. package/lib/styles/date-picker.js +65 -103
  123. package/lib/styles/date-picker.js.map +1 -1
  124. package/lib/styles/file-upload.d.ts +1 -1
  125. package/lib/styles/file-upload.d.ts.map +1 -1
  126. package/lib/styles/file-upload.js +3 -3
  127. package/lib/styles/input.d.ts +3 -1
  128. package/lib/styles/input.d.ts.map +1 -1
  129. package/lib/styles/input.js +93 -419
  130. package/lib/styles/input.js.map +1 -1
  131. package/lib/styles/multiselect.d.ts +3 -1
  132. package/lib/styles/multiselect.d.ts.map +1 -1
  133. package/lib/styles/multiselect.js +26 -2
  134. package/lib/styles/multiselect.js.map +1 -1
  135. package/lib/styles/option.d.ts +1 -1
  136. package/lib/styles/option.d.ts.map +1 -1
  137. package/lib/styles/option.js +20 -47
  138. package/lib/styles/option.js.map +1 -1
  139. package/lib/styles/radio.d.ts +3 -1
  140. package/lib/styles/radio.d.ts.map +1 -1
  141. package/lib/styles/radio.js +24 -21
  142. package/lib/styles/radio.js.map +1 -1
  143. package/lib/styles/scroll-container.d.ts +1 -1
  144. package/lib/styles/scroll-container.d.ts.map +1 -1
  145. package/lib/styles/scroll-container.js +6 -0
  146. package/lib/styles/scroll-container.js.map +1 -1
  147. package/lib/styles/select-base.d.ts +9 -0
  148. package/lib/styles/select-base.d.ts.map +1 -0
  149. package/lib/styles/select-base.js +973 -0
  150. package/lib/styles/select-base.js.map +1 -0
  151. package/lib/styles/select.d.ts +12 -0
  152. package/lib/styles/select.d.ts.map +1 -0
  153. package/lib/styles/select.js +308 -0
  154. package/lib/styles/select.js.map +1 -0
  155. package/lib/styles/step.d.ts +1 -1
  156. package/lib/styles/step.d.ts.map +1 -1
  157. package/lib/styles/switch.d.ts +3 -1
  158. package/lib/styles/switch.d.ts.map +1 -1
  159. package/lib/styles/switch.js +25 -10
  160. package/lib/styles/switch.js.map +1 -1
  161. package/lib/styles/tab.d.ts +1 -1
  162. package/lib/styles/tab.d.ts.map +1 -1
  163. package/lib/styles/tabs.d.ts +1 -1
  164. package/lib/styles/tabs.d.ts.map +1 -1
  165. package/lib/styles/tabs.js +71 -1
  166. package/lib/styles/tabs.js.map +1 -1
  167. package/lib/styles/tag.d.ts +1 -1
  168. package/lib/styles/tag.d.ts.map +1 -1
  169. package/lib/styles/tag.js +40 -141
  170. package/lib/styles/tag.js.map +1 -1
  171. package/lib/styles/textarea.d.ts +1 -1
  172. package/lib/styles/textarea.d.ts.map +1 -1
  173. package/lib/styles/textarea.js +71 -37
  174. package/lib/styles/textarea.js.map +1 -1
  175. package/lib/styles/tooltip.d.ts +4 -2
  176. package/lib/styles/tooltip.d.ts.map +1 -1
  177. package/lib/styles/tooltip.js +4 -119
  178. package/lib/styles/tooltip.js.map +1 -1
  179. package/lib/styles/wizard.d.ts +1 -1
  180. package/lib/styles/wizard.d.ts.map +1 -1
  181. package/lib/styles/wizard.js +18 -6
  182. package/lib/styles/wizard.js.map +1 -1
  183. package/lib/types/common.d.ts +2 -1
  184. package/lib/types/common.d.ts.map +1 -1
  185. package/lib/types/common.js +15 -1
  186. package/lib/types/common.js.map +1 -1
  187. package/lib/utils/calendar-utils.d.ts +0 -48
  188. package/lib/utils/calendar-utils.d.ts.map +1 -1
  189. package/lib/utils/calendar-utils.js +0 -69
  190. package/lib/utils/calendar-utils.js.map +1 -1
  191. package/lib/utils/flavor-sheet.d.ts +32 -0
  192. package/lib/utils/flavor-sheet.d.ts.map +1 -0
  193. package/lib/utils/flavor-sheet.js +40 -0
  194. package/lib/utils/flavor-sheet.js.map +1 -0
  195. package/lib/utils/icon-registry.d.ts +0 -13
  196. package/lib/utils/icon-registry.d.ts.map +1 -1
  197. package/lib/utils/icon-registry.js +0 -35
  198. package/lib/utils/icon-registry.js.map +1 -1
  199. package/lib/utils/icons.d.ts +10 -0
  200. package/lib/utils/icons.d.ts.map +1 -0
  201. package/lib/utils/icons.js +10 -0
  202. package/lib/utils/icons.js.map +1 -0
  203. package/lib/utils/loader-registry.d.ts +2 -2
  204. package/lib/utils/loader-registry.js +2 -2
  205. package/lib/utils/locale.d.ts +0 -57
  206. package/lib/utils/locale.d.ts.map +1 -1
  207. package/lib/utils/locale.js +0 -89
  208. package/lib/utils/locale.js.map +1 -1
  209. package/lib/utils/number-format.d.ts +0 -21
  210. package/lib/utils/number-format.d.ts.map +1 -1
  211. package/lib/utils/number-format.js +0 -27
  212. package/lib/utils/number-format.js.map +1 -1
  213. package/lib/utils/positioning.d.ts +29 -22
  214. package/lib/utils/positioning.d.ts.map +1 -1
  215. package/lib/utils/positioning.js +23 -87
  216. package/lib/utils/positioning.js.map +1 -1
  217. package/lib/utils/scroll-lock.d.ts.map +1 -1
  218. package/lib/utils/scroll-lock.js +0 -1
  219. package/lib/utils/scroll-lock.js.map +1 -1
  220. package/lib/utils/styles.d.ts +0 -5
  221. package/lib/utils/styles.d.ts.map +1 -1
  222. package/lib/utils/styles.js +0 -7
  223. package/lib/utils/styles.js.map +1 -1
  224. package/lib/version.d.ts +1 -1
  225. package/lib/version.js +1 -1
  226. package/package.json +22 -11
  227. package/css/tyrell-brand.css +0 -913
  228. package/dist/tyrell-brand.css +0 -913
  229. package/lib/components/dropdown.d.ts +0 -424
  230. package/lib/components/dropdown.d.ts.map +0 -1
  231. package/lib/components/dropdown.js +0 -1640
  232. package/lib/components/dropdown.js.map +0 -1
  233. package/lib/components/multiselect.d.ts +0 -397
  234. package/lib/components/multiselect.d.ts.map +0 -1
  235. package/lib/components/multiselect.js +0 -1595
  236. package/lib/components/multiselect.js.map +0 -1
  237. package/lib/styles/dropdown.d.ts +0 -12
  238. package/lib/styles/dropdown.d.ts.map +0 -1
  239. package/lib/styles/dropdown.js +0 -1081
  240. package/lib/styles/dropdown.js.map +0 -1
@@ -0,0 +1,1454 @@
1
+ /* =====================================================================
2
+ * tyrell-theme.css — the OKLCH theme engine for Tyrell Components
3
+ * =====================================================================
4
+ *
5
+ * Opt-in. Load AFTER `tyrell.css`. Override a few variables to rebrand —
6
+ * or seed a color directly, declare a named theme, scope one to a
7
+ * subtree. `tyrell.css` alone is correct and accessible without this
8
+ * file; this is what turns it into a theming ENGINE rather than a fixed
9
+ * palette: auto-contrast text, seed ingestion, named/scoped themes,
10
+ * animated theme transitions. See CSS_GUIDE.md → Color Customization.
11
+ *
12
+ * <link rel="stylesheet" href=".../tyrell.css">
13
+ * <link rel="stylesheet" href=".../tyrell-theme.css">
14
+ *
15
+ * :root {
16
+ * --ty-brand-hue: 200; // teal. Try 260 indigo, 30 orange, 145 emerald.
17
+ * --ty-brand-chroma: 0.13;
18
+ * }
19
+ *
20
+ * =====================================================================
21
+ * THE FORMULA
22
+ * =====================================================================
23
+ *
24
+ * Every color in the library is one formula on five axes:
25
+ *
26
+ * oklch(
27
+ * L = L-curve[shade] × flavor-l-factor ← Tier 3 × Tier 5
28
+ * C = flavor-chroma × saturation-curve[shade] ← Tier 2 × Tier 4
29
+ * H = flavor-hue ← Tier 2
30
+ * )
31
+ *
32
+ * Pick a flavor (primary / success / warning / danger /
33
+ * neutral). Pick a shade (strong / bold / base / soft / faint). That's
34
+ * the cell; the formula gives you its color.
35
+ *
36
+ * FLAVOR axis — which semantic role (carries its own hue, chroma,
37
+ * l-factor seeds)
38
+ * SHADE axis — 5 emphasis stops shared across flavors
39
+ * (strong / bold / base / soft / faint)
40
+ * HUE axis — what color family (0°–360°)
41
+ * CHROMA axis — how saturated (0 = grey, ~0.4 = max vivid)
42
+ * L axis — how dark or light (0 = black, 1 = white)
43
+ * Light mode: low L = more emphasis.
44
+ * Dark mode: high L = more emphasis (curve flips).
45
+ *
46
+ * Worked example — `--ty-color-warning-bold` in LIGHT mode:
47
+ *
48
+ * L = 0.46 × 1 = 0.46 ← l-bold × warning-l-factor
49
+ * C = 0.26 × 1.00 = 0.26 ← warning-chroma × c-bold-mult
50
+ * H = 75° ← warning-hue
51
+ * → oklch(0.46 0.26 75) (a punchy amber for +1 emphasis text)
52
+ *
53
+ * Want warning a notch darker without touching anything else? Set
54
+ * `--ty-warning-l-factor: 0.85` and the new L becomes 0.46 × 0.85 =
55
+ * 0.39. Every warning shade shifts in step; primary / success / danger
56
+ * are untouched.
57
+ *
58
+ * =====================================================================
59
+ * FILE LAYOUT
60
+ * =====================================================================
61
+ *
62
+ * SECTION 1 — KNOBS
63
+ * The variables you might want to override. Six tiers from drop-in
64
+ * rebrand (Tier 1) to surgical per-flavor tweak (Tier 5) plus solid
65
+ * buttons (Tier 6).
66
+ *
67
+ * SECTION 2 — IMPLEMENTATION
68
+ * The oklch() expressions that wire the knobs through to the actual
69
+ * color tokens. Rarely needs editing — Section 1 is the contract.
70
+ *
71
+ * Browser support: oklch() is in every evergreen browser since mid-2023.
72
+ * Tier 6 (solid) additionally uses OKLCH relative color — oklch(from …) —
73
+ * which lands later: Chrome 119, Safari 16.4, Firefox 128 (mid-2024).
74
+ * Solid fills have no fallback below those versions.
75
+ *
76
+ * ===================================================================== */
77
+
78
+ /* =====================================================================
79
+ * SECTION 1 — KNOBS
80
+ * =====================================================================
81
+ *
82
+ * Five tiers, ordered by how often you'd touch them. Override any of
83
+ * these in your own `:root` (or `html.dark` for per-mode tuning) —
84
+ * your declaration wins by source order.
85
+ *
86
+ * Tier 1 — SEEDS brand-hue + chroma. Rebrand in 2 lines.
87
+ * Tier 2 — PER-FLAVOR ANCHORS each flavor's hue + chroma.
88
+ * Tier 3 — L-CURVE 5 lightness stops for the emphasis ramp.
89
+ * Tier 4 — SATURATION CURVE per-shade chroma multipliers.
90
+ * Tier 5 — PER-FLAVOR L-FACTOR per-flavor lightness multiplier — push
91
+ * one flavor up or down without touching
92
+ * the others. Default 1.
93
+ * Tier 6 — SOLID FILLS solid-button system: hover/active/tone
94
+ * L offsets + per-theme dim/desaturate.
95
+ * ===================================================================== */
96
+
97
+ :root {
98
+ /* ----------------------------------------------------------------
99
+ * Tier 1 — SEEDS
100
+ * Brand-hue + chroma drive primary, neutral, surfaces, inputs,
101
+ * solid buttons. One slider rebrands the page.
102
+ * --------------------------------------------------------------*/
103
+
104
+ --ty-brand-hue: 45;
105
+ --ty-brand-chroma: 0.125; /* restrained default; the loud 0.2 read as "bollywood" */
106
+
107
+ /* Surface tint. 0 = monochrome surfaces (default, no brand leak).
108
+ * Bump to ~0.005 (light) / ~0.012 (dark) to warm surfaces toward brand. */
109
+ --ty-surface-chroma: 0;
110
+
111
+ /* ----------------------------------------------------------------
112
+ * Tier 2 — PER-FLAVOR ANCHORS
113
+ * Semantic colors (success/warning/danger/neutral) have their own
114
+ * hue + chroma seeds so they keep meaning across brand changes.
115
+ * Override any individually:
116
+ * --ty-success-hue: 160; (more teal)
117
+ * --ty-warning-chroma: 0.2; (more vivid)
118
+ * --------------------------------------------------------------*/
119
+
120
+ --ty-success-hue: 145;
121
+ --ty-success-chroma: calc(var(--ty-brand-chroma) * 1.08);
122
+
123
+ --ty-warning-hue: 75;
124
+ --ty-warning-chroma: calc(var(--ty-brand-chroma) * 2);
125
+
126
+ --ty-danger-hue: 25;
127
+ --ty-danger-chroma: calc(var(--ty-brand-chroma) * 1.31);
128
+
129
+ /* Neutral is ACHROMATIC by default — greys do not follow the brand.
130
+ * Most of an interface is monochrome; a rebrand should not drift it.
131
+ * To warm greys toward the brand (the pre-2026-08 behavior), opt in:
132
+ * --ty-neutral-hue: var(--ty-brand-hue);
133
+ * --ty-neutral-chroma: calc(var(--ty-brand-chroma) * 0.04); */
134
+ --ty-neutral-hue: 0;
135
+ --ty-neutral-chroma: 0;
136
+
137
+ /* ----------------------------------------------------------------
138
+ * Tier 3 — L-CURVE (light mode)
139
+ * 5 lightness stops per flavor for the emphasis ramp.
140
+ * Lower L = darker = more emphasis in light mode.
141
+ * Plus 3 bg L stops for the soft-tint backgrounds.
142
+ * --------------------------------------------------------------*/
143
+
144
+ --ty-l-strong: 0.38; /* ++ max emphasis */
145
+ --ty-l-bold: 0.46; /* + high emphasis */
146
+ --ty-l-base: 0.54; /* body */
147
+ --ty-l-soft: 0.72; /* - low emphasis */
148
+ --ty-l-faint: 0.88; /* -- min emphasis */
149
+
150
+ --ty-l-bg-base: 0.96;
151
+ --ty-l-bg-bold: 0.92;
152
+ --ty-l-bg-soft: 0.98;
153
+
154
+ /* Text ladder (--ty-text-*) and surface borders — historically
155
+ * hardcoded hexes in tyrell.css that a rebrand never touched; now
156
+ * engine-wired at the SAME lightness the hexes had (measured via
157
+ * oklch(from #hex l c h)), tinted by the neutral seeds. At the
158
+ * default --ty-neutral-chroma (≈0.005) the tint is imperceptible. */
159
+ --ty-l-text-strong: 0; /* was #000 */
160
+ --ty-l-text-bold: 0; /* was #000 */
161
+ --ty-l-text-base: 0.21; /* was #111827 */
162
+ --ty-l-text-soft: 0.37; /* was #374151 */
163
+ --ty-l-text-faint: 0.71; /* was #9ca3af */
164
+
165
+ --ty-l-border-content: 0.93; /* was #e5e7eb */
166
+ --ty-l-border-elevated: 0.93;
167
+ --ty-l-border-floating: 0.93;
168
+
169
+ /* Generic border ladder — borders are their OWN family, not ink.
170
+ * Historically --ty-border-* aliased the neutral text ramp; that tied
171
+ * app chrome to the text emphasis curve (and to --ty-neutral-l-factor),
172
+ * so tuning text moved every border. Defaults preserve the exact L the
173
+ * aliases produced. Borders sit relative to the surfaces they separate;
174
+ * tune them here without touching text. */
175
+ --ty-l-border-strong: 0.46; /* was color-neutral-bold */
176
+ --ty-l-border-bold: 0.64;
177
+ --ty-l-border: 0.76; /* was color-neutral-soft */
178
+ --ty-l-border-soft: 0.88; /* was color-neutral-faint */
179
+ --ty-l-border-faint: 0.92;
180
+
181
+ /* Surface ladder + remaining per-mode absolutes, as L dials. These were
182
+ * whole oklch() declarations swapped per mode; as numeric dials they are
183
+ * (a) consistent with every other family and (b) interpolable — which is
184
+ * what lets theme/mode switches ANIMATE (see THEME TRANSITIONS at the
185
+ * end of this file). */
186
+ --ty-l-surface-canvas: 0.985;
187
+ --ty-l-surface-content: 1;
188
+ --ty-l-surface-elevated: 1;
189
+ --ty-l-surface-floating: 1;
190
+ --ty-l-surface-input: 1;
191
+
192
+ --ty-l-solid-neutral: 0.4; /* the neutral "ink" fill — dark in BOTH modes */
193
+ /* neutral+ is FULL-CONTRAST ink — near-black on light, near-white on dark
194
+ (mode block below) — not "anchor + shared offset" like other tones: the
195
+ max-emphasis monochrome button is an absolute, not a nudge. Matches the
196
+ static tyrell.css light value (#000) that predates this dial. */
197
+ --ty-l-solid-neutral-strong: 0.15;
198
+ --ty-l-bg-neutral-strong: 0.25; /* opposes the mode: dark bg on light page */
199
+ --ty-l-bg-neutral-faint: 0.99; /* follows the mode */
200
+ --ty-c-bg-neutral-faint-mult: 0.4;
201
+
202
+ --ty-focus-ring-alpha: 5%; /* focus ring mix strength (dark needs more) */
203
+
204
+ /* ----------------------------------------------------------------
205
+ * Tier 4 — SATURATION CURVE
206
+ * Per-shade chroma multipliers. Each shade's chroma =
207
+ * flavor-chroma × multiplier. Drops near the extremes so
208
+ * near-white and near-black can hold their tint.
209
+ * --------------------------------------------------------------*/
210
+
211
+ --ty-c-strong-mult: 0.77;
212
+ --ty-c-bold-mult: 1;
213
+ --ty-c-base-mult: 0.92;
214
+ --ty-c-soft-mult: 0.77;
215
+ --ty-c-faint-mult: 0.46;
216
+
217
+ --ty-c-bg-base-mult: 0.15;
218
+ --ty-c-bg-bold-mult: 0.38;
219
+ --ty-c-bg-soft-mult: 0.08;
220
+
221
+ /* ----------------------------------------------------------------
222
+ * Tier 5 — PER-FLAVOR L-FACTOR
223
+ * Multiplies the L-curve values for that flavor only. Default 1 =
224
+ * no change. Useful when one flavor's brand-default sits too close
225
+ * to brand-hue on the wheel and the buttons blend.
226
+ *
227
+ * Example: brand-hue: 47° (gold). Warning sits at 75° (amber).
228
+ * Both render as similar warm browns at brand-chroma. Drop warning
229
+ * a notch and it darkens enough to stand apart:
230
+ * --ty-warning-l-factor: 0.85;
231
+ *
232
+ * NOTE: the L-curve flips between light and dark mode, so a factor
233
+ * < 1 darkens in light mode but DIMS in dark mode. If you want
234
+ * symmetric "more emphatic" in both modes, set the factor here for
235
+ * light AND set its inverse (1 / factor, roughly) in `html.dark`.
236
+ * --------------------------------------------------------------*/
237
+
238
+ --ty-primary-l-factor: 1;
239
+ --ty-success-l-factor: 1;
240
+ --ty-warning-l-factor: 1.15;
241
+ --ty-danger-l-factor: 1;
242
+ --ty-neutral-l-factor: 1;
243
+
244
+ /* ----------------------------------------------------------------
245
+ * Tier 6 — SOLID FILLS
246
+ * Solid buttons are their OWN system, not the emphasis ramp. Each
247
+ * flavor's fill defaults to --ty-color-{flavor}; the knobs below
248
+ * derive its hover / active / tone+ / tone- and per-theme adjust.
249
+ * Math lives in SECTION 2 (search "Tier 6 — SOLID"); these are the
250
+ * dials.
251
+ *
252
+ * Axis A — interaction & tone, OKLCH L offsets on the fill.
253
+ * Axis B — per-theme adjust applied to the base fill (dim/desaturate).
254
+ *
255
+ * NOTE: like Tier 5, the emphasis curve flips for dark, so these L
256
+ * offsets are sign-flipped in `html.dark` below (darken in light =
257
+ * lighten in dark). Keep the two blocks in sync.
258
+ * --------------------------------------------------------------*/
259
+
260
+ /* Axis A — interaction (light: darken on press, so offsets are negative) */
261
+ --ty-solid-hover-l: -0.04; /* :hover */
262
+ --ty-solid-active-l: -0.08; /* :active */
263
+ --ty-solid-strong-l: -0.06; /* tone+ */
264
+ --ty-solid-soft-l: 0.1; /* tone- */
265
+
266
+ /* Axis B — per-theme adjust (light reads fine → identity) */
267
+ --ty-solid-l: 0; /* lightness offset — negative dims */
268
+ --ty-solid-c: 1; /* chroma multiplier — <1 desaturates */
269
+ --ty-solid-h: 0; /* hue offset (deg) */
270
+
271
+ /* Axis C — per-flavor solid anchor lift (added to the base fill's L,
272
+ * tones/hover/active then derive from the lifted anchor). The solid
273
+ * ladder's "darker = stronger" is FALSE for the yellow family: darkened
274
+ * hue-75 is brown, so warning's whole ladder shifts UP instead — soft
275
+ * 0.82 pale amber, base 0.72 vivid orange, strong 0.66 deep orange, all
276
+ * black-text (Material treats amber the same way). Other flavors: 0. */
277
+ --ty-primary-solid-l: 0;
278
+ --ty-success-solid-l: 0;
279
+ --ty-warning-solid-l: 0.1;
280
+ --ty-danger-solid-l: 0;
281
+ }
282
+
283
+ /* Dark mode overrides for the L-curve and saturation curve. Tier 1 + 2
284
+ * seeds and Tier 5 L-factors carry through unchanged from :root. */
285
+ html.dark,
286
+ html[data-theme="dark"],
287
+ [data-ty-theme].dark,
288
+ [data-ty-theme][data-theme="dark"] {
289
+ /* Tier 3 dark — inverted curve. Higher L = lighter text = more
290
+ * emphasis on a dark background. */
291
+ --ty-l-strong: 0.72;
292
+ --ty-l-bold: 0.66;
293
+ --ty-l-base: 0.62;
294
+ --ty-l-soft: 0.46;
295
+ --ty-l-faint: 0.3;
296
+
297
+ --ty-l-bg-base: 0.22;
298
+ --ty-l-bg-bold: 0.26;
299
+ --ty-l-bg-soft: 0.19;
300
+
301
+ /* Text ladder + surface borders dark — L matched to the hexes
302
+ * tyrell.css used to hardcode (#fff/#f9fafb/#d6d6d6/#7f8590/#555a63,
303
+ * borders #030712/#414141/#494949). */
304
+ --ty-l-text-strong: 1;
305
+ --ty-l-text-bold: 0.985;
306
+ --ty-l-text-base: 0.88;
307
+ --ty-l-text-soft: 0.62;
308
+ --ty-l-text-faint: 0.47;
309
+
310
+ --ty-l-border-content: 0.13;
311
+ --ty-l-border-elevated: 0.375;
312
+ --ty-l-border-floating: 0.41;
313
+
314
+ --ty-l-border-strong: 0.66;
315
+ --ty-l-border-bold: 0.46;
316
+ --ty-l-border: 0.38;
317
+ --ty-l-border-soft: 0.3;
318
+ --ty-l-border-faint: 0.3;
319
+
320
+ /* Surface ladder + absolutes dark — hand-tuned values, now as dials. */
321
+ --ty-l-surface-canvas: 0.12;
322
+ --ty-l-surface-content: 0.16;
323
+ --ty-l-surface-elevated: 0.19;
324
+ --ty-l-surface-floating: 0.22;
325
+ --ty-l-surface-input: 0.18;
326
+
327
+ --ty-l-solid-neutral: 0.18;
328
+ --ty-l-solid-neutral-strong: 0.88; /* inverted ink: white button, black text */
329
+ --ty-l-bg-neutral-strong: 0.95;
330
+ --ty-l-bg-neutral-faint: 0.15;
331
+ --ty-c-bg-neutral-faint-mult: 2;
332
+
333
+ /* Tier 5 dark — neutral inks pulled down a notch against dark surfaces. */
334
+ --ty-neutral-l-factor: 0.8;
335
+
336
+ --ty-focus-ring-alpha: 20%;
337
+
338
+ /* Tier 4 dark — dim shades hold more chroma so they don't grey out;
339
+ * strong shades trim chroma so bright tones don't over-pop. */
340
+ --ty-c-strong-mult: 0.77;
341
+ --ty-c-bold-mult: 1;
342
+ --ty-c-base-mult: 0.92;
343
+ --ty-c-soft-mult: 0.77;
344
+ --ty-c-faint-mult: 0.5;
345
+
346
+ --ty-c-bg-base-mult: 0.38;
347
+ --ty-c-bg-bold-mult: 0.54;
348
+ --ty-c-bg-soft-mult: 0.23;
349
+
350
+ /* Tier 6 dark — interaction offsets flip sign (press LIGHTENS on dark),
351
+ * and the per-theme adjust dims + desaturates the fills against the
352
+ * dark canvas. (Computed solid tokens are declared once at html:root.) */
353
+ /* Bigger than light mode's 0.04/0.08: small L deltas at the dark end of
354
+ * an emissive screen read much weaker than the same delta mid-scale, so
355
+ * dark hover needs more push to be felt at all. */
356
+ --ty-solid-hover-l: 0.07; /* :hover */
357
+ --ty-solid-active-l: 0.11; /* :active */
358
+ --ty-solid-strong-l: 0.06; /* tone+ */
359
+ --ty-solid-soft-l: -0.1; /* tone- */
360
+
361
+ --ty-solid-l: -0.2; /* dim against dark canvas */
362
+ --ty-solid-c: 0.7; /* desaturate against dark canvas */
363
+ --ty-solid-h: 0; /* hue offset */
364
+
365
+ /* Axis C — dark warning fills are already dim enough not to brown out
366
+ * (and browns read intentional on dark); no lift needed. */
367
+ --ty-warning-solid-l: 0;
368
+ }
369
+
370
+ /* =====================================================================
371
+ * SECTION 2 — IMPLEMENTATION
372
+ * =====================================================================
373
+ *
374
+ * The oklch() expressions that turn Section 1's knobs into the actual
375
+ * --ty-color-*, --ty-bg-*, --ty-border-*, --ty-surface-*, --ty-input-*,
376
+ * and --ty-solid-* tokens that components read.
377
+ *
378
+ * Pattern for each flavor's 5-shade fg ramp + 3-shade bg ramp:
379
+ *
380
+ * --ty-color-{flavor}-{shade}: oklch(
381
+ * calc(var(--ty-l-{shade}) * var(--ty-{flavor}-l-factor)) ← L
382
+ * calc(var(--ty-{flavor}-chroma) * var(--ty-c-{shade}-mult)) ← C
383
+ * var(--ty-{flavor}-hue) ← H
384
+ * );
385
+ *
386
+ * Formulas are declared ONCE, at `html:root` (specificity 0,1,1 — NOT
387
+ * plain `:root` (0,1,0)): they out-rank tyrell.css's hardcoded light
388
+ * tokens by specificity and its hardcoded `html.dark` hexes (0,1,1) by
389
+ * source order, since the brand layer loads after. Dark mode needs no
390
+ * formula duplicates — the dials they read (Tier 3/4, SECTION 1) are
391
+ * what flip per mode. The `html.dark` block at the END of this file
392
+ * holds only genuine per-mode DATA (surface ladder, hand-pinned
393
+ * absolutes) — never formulas. If you add a token here, do NOT mirror
394
+ * it there unless its VALUE is mode-specific.
395
+ * ===================================================================== */
396
+
397
+ html:root,
398
+ [data-ty-theme] {
399
+ /* ----------------------------------------------------------------
400
+ * Flavor color ramps (fg + bg + border)
401
+ *
402
+ * SEED INGESTION — every flavor resolves ONE seed color, and every
403
+ * ramp formula reads that seed's c + h channels via relative color
404
+ * syntax; L always comes from the mode-flipped curve. Two ways in:
405
+ *
406
+ * numbers (default) — the Tier 1/2 hue+chroma dials construct the
407
+ * seed: --ty-brand-hue: 200; --ty-brand-chroma: 0.13;
408
+ *
409
+ * a color — set the seed directly and the dials are bypassed:
410
+ * --ty-primary-seed: #76467c;
411
+ * --ty-danger-seed: crimson;
412
+ *
413
+ * The seed's LIGHTNESS is discarded by design: a color's identity
414
+ * here is hue + chroma; where each shade sits per mode is the
415
+ * L-curve's job. That is what makes dark mode, tones and contrast
416
+ * correct for ANY seed — light or dark. (Your exact pixel may
417
+ * appear nowhere; pin --ty-solid-X etc. if you need it literally.)
418
+ * --------------------------------------------------------------*/
419
+ --ty-primary-seed-resolved: var(
420
+ --ty-primary-seed,
421
+ oklch(0.5 var(--ty-brand-chroma) var(--ty-brand-hue))
422
+ );
423
+ --ty-success-seed-resolved: var(
424
+ --ty-success-seed,
425
+ oklch(0.5 var(--ty-success-chroma) var(--ty-success-hue))
426
+ );
427
+ --ty-warning-seed-resolved: var(
428
+ --ty-warning-seed,
429
+ oklch(0.5 var(--ty-warning-chroma) var(--ty-warning-hue))
430
+ );
431
+ --ty-danger-seed-resolved: var(
432
+ --ty-danger-seed,
433
+ oklch(0.5 var(--ty-danger-chroma) var(--ty-danger-hue))
434
+ );
435
+ --ty-neutral-seed-resolved: var(
436
+ --ty-neutral-seed,
437
+ oklch(0.5 var(--ty-neutral-chroma) var(--ty-neutral-hue))
438
+ );
439
+
440
+ /* Primary */
441
+ --ty-color-primary-strong: oklch(
442
+ from var(--ty-primary-seed-resolved)
443
+ calc(var(--ty-l-strong) * var(--ty-primary-l-factor))
444
+ calc(c * var(--ty-c-strong-mult)) h
445
+ );
446
+ --ty-color-primary-bold: oklch(
447
+ from var(--ty-primary-seed-resolved)
448
+ calc(var(--ty-l-bold) * var(--ty-primary-l-factor))
449
+ calc(c * var(--ty-c-bold-mult)) h
450
+ );
451
+ --ty-color-primary: oklch(
452
+ from var(--ty-primary-seed-resolved)
453
+ calc(var(--ty-l-base) * var(--ty-primary-l-factor))
454
+ calc(c * var(--ty-c-base-mult)) h
455
+ );
456
+ --ty-color-primary-soft: oklch(
457
+ from var(--ty-primary-seed-resolved)
458
+ calc(var(--ty-l-soft) * var(--ty-primary-l-factor))
459
+ calc(c * var(--ty-c-soft-mult)) h
460
+ );
461
+ --ty-color-primary-faint: oklch(
462
+ from var(--ty-primary-seed-resolved)
463
+ calc(var(--ty-l-faint) * var(--ty-primary-l-factor))
464
+ calc(c * var(--ty-c-faint-mult)) h
465
+ );
466
+ --ty-bg-primary: oklch(
467
+ from var(--ty-primary-seed-resolved)
468
+ calc(var(--ty-l-bg-base) * var(--ty-primary-l-factor))
469
+ calc(c * var(--ty-c-bg-base-mult)) h
470
+ );
471
+ --ty-bg-primary-bold: oklch(
472
+ from var(--ty-primary-seed-resolved)
473
+ calc(var(--ty-l-bg-bold) * var(--ty-primary-l-factor))
474
+ calc(c * var(--ty-c-bg-bold-mult)) h
475
+ );
476
+ --ty-bg-primary-soft: oklch(
477
+ from var(--ty-primary-seed-resolved)
478
+ calc(var(--ty-l-bg-soft) * var(--ty-primary-l-factor))
479
+ calc(c * var(--ty-c-bg-soft-mult)) h
480
+ );
481
+ --ty-border-primary: var(--ty-color-primary-soft);
482
+
483
+ /* Success */
484
+ --ty-color-success-strong: oklch(
485
+ from var(--ty-success-seed-resolved)
486
+ calc(var(--ty-l-strong) * var(--ty-success-l-factor))
487
+ calc(c * var(--ty-c-strong-mult)) h
488
+ );
489
+ --ty-color-success-bold: oklch(
490
+ from var(--ty-success-seed-resolved)
491
+ calc(var(--ty-l-bold) * var(--ty-success-l-factor))
492
+ calc(c * var(--ty-c-bold-mult)) h
493
+ );
494
+ --ty-color-success: oklch(
495
+ from var(--ty-success-seed-resolved)
496
+ calc(var(--ty-l-base) * var(--ty-success-l-factor))
497
+ calc(c * var(--ty-c-base-mult)) h
498
+ );
499
+ --ty-color-success-soft: oklch(
500
+ from var(--ty-success-seed-resolved)
501
+ calc(var(--ty-l-soft) * var(--ty-success-l-factor))
502
+ calc(c * var(--ty-c-soft-mult)) h
503
+ );
504
+ --ty-color-success-faint: oklch(
505
+ from var(--ty-success-seed-resolved)
506
+ calc(var(--ty-l-faint) * var(--ty-success-l-factor))
507
+ calc(c * var(--ty-c-faint-mult)) h
508
+ );
509
+ --ty-bg-success: oklch(
510
+ from var(--ty-success-seed-resolved)
511
+ calc(var(--ty-l-bg-base) * var(--ty-success-l-factor))
512
+ calc(c * var(--ty-c-bg-base-mult)) h
513
+ );
514
+ --ty-bg-success-bold: oklch(
515
+ from var(--ty-success-seed-resolved)
516
+ calc(var(--ty-l-bg-bold) * var(--ty-success-l-factor))
517
+ calc(c * var(--ty-c-bg-bold-mult)) h
518
+ );
519
+ --ty-bg-success-soft: oklch(
520
+ from var(--ty-success-seed-resolved)
521
+ calc(var(--ty-l-bg-soft) * var(--ty-success-l-factor))
522
+ calc(c * var(--ty-c-bg-soft-mult)) h
523
+ );
524
+ --ty-border-success: var(--ty-color-success-soft);
525
+
526
+ /* Danger */
527
+ --ty-color-danger-strong: oklch(
528
+ from var(--ty-danger-seed-resolved)
529
+ calc(var(--ty-l-strong) * var(--ty-danger-l-factor))
530
+ calc(c * var(--ty-c-strong-mult)) h
531
+ );
532
+ --ty-color-danger-bold: oklch(
533
+ from var(--ty-danger-seed-resolved)
534
+ calc(var(--ty-l-bold) * var(--ty-danger-l-factor))
535
+ calc(c * var(--ty-c-bold-mult)) h
536
+ );
537
+ --ty-color-danger: oklch(
538
+ from var(--ty-danger-seed-resolved)
539
+ calc(var(--ty-l-base) * var(--ty-danger-l-factor))
540
+ calc(c * var(--ty-c-base-mult)) h
541
+ );
542
+ --ty-color-danger-soft: oklch(
543
+ from var(--ty-danger-seed-resolved)
544
+ calc(var(--ty-l-soft) * var(--ty-danger-l-factor))
545
+ calc(c * var(--ty-c-soft-mult)) h
546
+ );
547
+ --ty-color-danger-faint: oklch(
548
+ from var(--ty-danger-seed-resolved)
549
+ calc(var(--ty-l-faint) * var(--ty-danger-l-factor))
550
+ calc(c * var(--ty-c-faint-mult)) h
551
+ );
552
+ --ty-bg-danger: oklch(
553
+ from var(--ty-danger-seed-resolved)
554
+ calc(var(--ty-l-bg-base) * var(--ty-danger-l-factor))
555
+ calc(c * var(--ty-c-bg-base-mult)) h
556
+ );
557
+ --ty-bg-danger-bold: oklch(
558
+ from var(--ty-danger-seed-resolved)
559
+ calc(var(--ty-l-bg-bold) * var(--ty-danger-l-factor))
560
+ calc(c * var(--ty-c-bg-bold-mult)) h
561
+ );
562
+ --ty-bg-danger-soft: oklch(
563
+ from var(--ty-danger-seed-resolved)
564
+ calc(var(--ty-l-bg-soft) * var(--ty-danger-l-factor))
565
+ calc(c * var(--ty-c-bg-soft-mult)) h
566
+ );
567
+ --ty-border-danger: var(--ty-color-danger-soft);
568
+
569
+ /* Warning */
570
+ --ty-color-warning-strong: oklch(
571
+ from var(--ty-warning-seed-resolved)
572
+ calc(var(--ty-l-strong) * var(--ty-warning-l-factor))
573
+ calc(c * var(--ty-c-strong-mult)) h
574
+ );
575
+ --ty-color-warning-bold: oklch(
576
+ from var(--ty-warning-seed-resolved)
577
+ calc(var(--ty-l-bold) * var(--ty-warning-l-factor))
578
+ calc(c * var(--ty-c-bold-mult)) h
579
+ );
580
+ --ty-color-warning: oklch(
581
+ from var(--ty-warning-seed-resolved)
582
+ calc(var(--ty-l-base) * var(--ty-warning-l-factor))
583
+ calc(c * var(--ty-c-base-mult)) h
584
+ );
585
+ --ty-color-warning-soft: oklch(
586
+ from var(--ty-warning-seed-resolved)
587
+ calc(var(--ty-l-soft) * var(--ty-warning-l-factor))
588
+ calc(c * var(--ty-c-soft-mult)) h
589
+ );
590
+ --ty-color-warning-faint: oklch(
591
+ from var(--ty-warning-seed-resolved)
592
+ calc(var(--ty-l-faint) * var(--ty-warning-l-factor))
593
+ calc(c * var(--ty-c-faint-mult)) h
594
+ );
595
+ --ty-bg-warning: oklch(
596
+ from var(--ty-warning-seed-resolved)
597
+ calc(var(--ty-l-bg-base) * var(--ty-warning-l-factor))
598
+ calc(c * var(--ty-c-bg-base-mult)) h
599
+ );
600
+ --ty-bg-warning-bold: oklch(
601
+ from var(--ty-warning-seed-resolved)
602
+ calc(var(--ty-l-bg-bold) * var(--ty-warning-l-factor))
603
+ calc(c * var(--ty-c-bg-bold-mult)) h
604
+ );
605
+ --ty-bg-warning-soft: oklch(
606
+ from var(--ty-warning-seed-resolved)
607
+ calc(var(--ty-l-bg-soft) * var(--ty-warning-l-factor))
608
+ calc(c * var(--ty-c-bg-soft-mult)) h
609
+ );
610
+ --ty-border-warning: var(--ty-color-warning-soft);
611
+
612
+ /* Neutral — same L curve, near-zero chroma so the grey scale stays grey. */
613
+ --ty-color-neutral-strong: oklch(
614
+ from var(--ty-neutral-seed-resolved)
615
+ calc(var(--ty-l-strong) * var(--ty-neutral-l-factor))
616
+ calc(c * var(--ty-c-strong-mult)) h
617
+ );
618
+ --ty-color-neutral-bold: oklch(
619
+ from var(--ty-neutral-seed-resolved)
620
+ calc(var(--ty-l-bold) * var(--ty-neutral-l-factor))
621
+ calc(c * var(--ty-c-bold-mult)) h
622
+ );
623
+ --ty-color-neutral: oklch(
624
+ from var(--ty-neutral-seed-resolved)
625
+ calc(var(--ty-l-base) * var(--ty-neutral-l-factor))
626
+ calc(c * var(--ty-c-base-mult)) h
627
+ );
628
+ --ty-color-neutral-soft: oklch(
629
+ from var(--ty-neutral-seed-resolved)
630
+ calc(var(--ty-l-soft) * var(--ty-neutral-l-factor))
631
+ calc(c * var(--ty-c-soft-mult)) h
632
+ );
633
+ --ty-color-neutral-faint: oklch(
634
+ from var(--ty-neutral-seed-resolved)
635
+ calc(var(--ty-l-faint) * var(--ty-neutral-l-factor))
636
+ calc(c * var(--ty-c-faint-mult)) h
637
+ );
638
+ --ty-bg-neutral: oklch(
639
+ from var(--ty-neutral-seed-resolved)
640
+ calc(var(--ty-l-bg-base) * var(--ty-neutral-l-factor))
641
+ calc(c * var(--ty-c-bg-base-mult)) h
642
+ );
643
+ --ty-bg-neutral-bold: oklch(
644
+ from var(--ty-neutral-seed-resolved)
645
+ calc(var(--ty-l-bg-bold) * var(--ty-neutral-l-factor))
646
+ calc(c * var(--ty-c-bg-bold-mult)) h
647
+ );
648
+ --ty-bg-neutral-soft: oklch(
649
+ from var(--ty-neutral-seed-resolved)
650
+ calc(var(--ty-l-bg-soft) * var(--ty-neutral-l-factor))
651
+ calc(c * var(--ty-c-bg-soft-mult)) h
652
+ );
653
+ --ty-bg-neutral-strong: oklch(
654
+ from var(--ty-neutral-seed-resolved) var(--ty-l-bg-neutral-strong)
655
+ calc(c * 2) h
656
+ );
657
+ --ty-bg-neutral-faint: oklch(
658
+ from var(--ty-neutral-seed-resolved) var(--ty-l-bg-neutral-faint)
659
+ calc(c * var(--ty-c-bg-neutral-faint-mult)) h
660
+ );
661
+
662
+ /* ----------------------------------------------------------------
663
+ * Global neutral borders — own family, own L ladder (Tier 3).
664
+ * Tinted by the neutral seeds like everything grey, but NOT tied to
665
+ * the text emphasis curve or --ty-neutral-l-factor. Per-flavor
666
+ * accent borders (--ty-border-{flavor}) stay ink aliases — a colored
667
+ * border SHOULD track its flavor's ink.
668
+ * --------------------------------------------------------------*/
669
+ --ty-border-strong: oklch(
670
+ from var(--ty-neutral-seed-resolved) var(--ty-l-border-strong) c h
671
+ );
672
+ --ty-border: oklch(
673
+ from var(--ty-neutral-seed-resolved) var(--ty-l-border) c h
674
+ );
675
+ --ty-border-bold: oklch(
676
+ from var(--ty-neutral-seed-resolved) var(--ty-l-border-bold) c h
677
+ );
678
+ --ty-border-soft: oklch(
679
+ from var(--ty-neutral-seed-resolved) var(--ty-l-border-soft) c h
680
+ );
681
+ --ty-border-faint: oklch(
682
+ from var(--ty-neutral-seed-resolved) var(--ty-l-border-faint) c h
683
+ );
684
+
685
+ /* ----------------------------------------------------------------
686
+ * Text ladder + surface borders — engine-wired versions of tokens
687
+ * tyrell.css hardcodes as hexes. Own L dials (Tier 3), neutral
688
+ * hue/chroma tint. These out-rank the base hexes the same way every
689
+ * other token here does; a rebrand now reaches body text too.
690
+ * --------------------------------------------------------------*/
691
+ --ty-text-strong: oklch(
692
+ from var(--ty-neutral-seed-resolved) var(--ty-l-text-strong) c h
693
+ );
694
+ --ty-text-bold: oklch(
695
+ from var(--ty-neutral-seed-resolved) var(--ty-l-text-bold) c h
696
+ );
697
+ --ty-text: oklch(
698
+ from var(--ty-neutral-seed-resolved) var(--ty-l-text-base) c h
699
+ );
700
+ --ty-text-soft: oklch(
701
+ from var(--ty-neutral-seed-resolved) var(--ty-l-text-soft) c h
702
+ );
703
+ --ty-text-faint: oklch(
704
+ from var(--ty-neutral-seed-resolved) var(--ty-l-text-faint) c h
705
+ );
706
+
707
+ --ty-content-border: oklch(
708
+ from var(--ty-neutral-seed-resolved) var(--ty-l-border-content) c h
709
+ );
710
+ --ty-elevated-border: oklch(
711
+ from var(--ty-neutral-seed-resolved) var(--ty-l-border-elevated) c h
712
+ );
713
+ --ty-floating-border: oklch(
714
+ from var(--ty-neutral-seed-resolved) var(--ty-l-border-floating) c h
715
+ );
716
+
717
+ /* ----------------------------------------------------------------
718
+ * Surfaces — monochrome by default; --ty-surface-chroma warms them.
719
+ * --------------------------------------------------------------*/
720
+ --ty-surface-canvas: oklch(
721
+ from var(--ty-primary-seed-resolved) var(--ty-l-surface-canvas)
722
+ var(--ty-surface-chroma) h
723
+ );
724
+ --ty-surface-content: oklch(
725
+ from var(--ty-primary-seed-resolved) var(--ty-l-surface-content)
726
+ var(--ty-surface-chroma) h
727
+ );
728
+ --ty-surface-elevated: oklch(
729
+ from var(--ty-primary-seed-resolved) var(--ty-l-surface-elevated)
730
+ var(--ty-surface-chroma) h
731
+ );
732
+ --ty-surface-floating: oklch(
733
+ from var(--ty-primary-seed-resolved) var(--ty-l-surface-floating)
734
+ var(--ty-surface-chroma) h
735
+ );
736
+ --ty-surface-input: oklch(
737
+ from var(--ty-primary-seed-resolved) var(--ty-l-surface-input)
738
+ var(--ty-surface-chroma) h
739
+ );
740
+
741
+ /* ----------------------------------------------------------------
742
+ * Inputs — tied to neutral ramp so form controls retint with brand.
743
+ * --------------------------------------------------------------*/
744
+ --ty-input-bg: var(--ty-surface-input);
745
+ --ty-input-color: var(--ty-color-neutral-strong);
746
+ --ty-input-border: var(--ty-color-neutral-faint);
747
+ --ty-input-border-hover: var(--ty-color-neutral-soft);
748
+ --ty-input-border-focus: var(--ty-color-primary);
749
+ --ty-input-placeholder: var(--ty-color-neutral-soft);
750
+ --ty-input-disabled-bg: var(--ty-color-neutral-faint);
751
+ --ty-input-disabled-border: var(--ty-color-neutral-faint);
752
+ --ty-input-disabled-color: var(--ty-color-neutral-bold);
753
+ --ty-input-shadow-focus: color-mix(
754
+ in oklab,
755
+ var(--ty-color-primary) var(--ty-focus-ring-alpha),
756
+ transparent
757
+ );
758
+ }
759
+
760
+ /* ----------------------------------------------------------------
761
+ * Tier 6 — SOLID (implementation)
762
+ * Solid is its OWN system, NOT the text/emphasis ramp. The dials
763
+ * (--ty-solid-hover-l / -active-l / -strong-l / -soft-l and
764
+ * --ty-solid-l / -c / -h) live in SECTION 1, Tier 6. Below is the
765
+ * derivation only: base fill bakes in the per-theme adjust (Axis B);
766
+ * hover/active/strong/soft offset the base by Axis A. Override any
767
+ * single --ty-solid-{flavor}-{state} to recolor just that segment.
768
+ *
769
+ * Declared at `html:root` (specificity 0,1,1) — NOT plain `:root` (0,1,0) —
770
+ * on purpose: it must out-rank tyrell.css's hardcoded dark solids at
771
+ * `html.dark` (0,1,1). It wins light by specificity over base's `html{}`,
772
+ * and wins dark by source order over base's `html.dark` (brand loads after).
773
+ * That's what lets dark solids re-tint with --ty-brand-hue. Declared ONCE —
774
+ * the oklch(from var(--ty-color-*)) formulas resolve per-mode automatically,
775
+ * so no dark duplicate is needed.
776
+ * --------------------------------------------------------------*/
777
+ html:root,
778
+ [data-ty-theme] {
779
+ /* base fill per flavor (= flavor color, theme-adjusted by Axis B) */
780
+ --ty-solid-primary: oklch(
781
+ from var(--ty-color-primary)
782
+ calc(l + var(--ty-solid-l) + var(--ty-primary-solid-l, 0))
783
+ calc(c * var(--ty-solid-c)) calc(h + var(--ty-solid-h))
784
+ );
785
+ --ty-solid-success: oklch(
786
+ from var(--ty-color-success)
787
+ calc(l + var(--ty-solid-l) + var(--ty-success-solid-l, 0))
788
+ calc(c * var(--ty-solid-c)) calc(h + var(--ty-solid-h))
789
+ );
790
+ --ty-solid-danger: oklch(
791
+ from var(--ty-color-danger)
792
+ calc(l + var(--ty-solid-l) + var(--ty-danger-solid-l, 0))
793
+ calc(c * var(--ty-solid-c)) calc(h + var(--ty-solid-h))
794
+ );
795
+ --ty-solid-warning: oklch(
796
+ from var(--ty-color-warning)
797
+ calc(l + var(--ty-solid-l) + var(--ty-warning-solid-l, 0))
798
+ calc(c * var(--ty-solid-c)) calc(h + var(--ty-solid-h))
799
+ );
800
+ /* neutral is a deliberate dark grey fill, not the neutral text color */
801
+ --ty-solid-neutral: oklch(
802
+ from var(--ty-neutral-seed-resolved) var(--ty-l-solid-neutral)
803
+ calc(c * var(--ty-c-strong-mult)) h
804
+ );
805
+
806
+ /* ----------------------------------------------------------------
807
+ * SOLID FOREGROUNDS — auto-contrast.
808
+ *
809
+ * Each fill gets a foreground picked from its OWN lightness, so a
810
+ * rebrand (--ty-brand-hue / --ty-brand-chroma / any L-curve override)
811
+ * can't strand white text on a pale fill. Branchless, no JS:
812
+ *
813
+ * clamp(0, (threshold - l) * 1000, 1) -> 1 (white) when the fill
814
+ * is darker than the threshold, 0 (black) when it's lighter.
815
+ *
816
+ * One token per FILL, not per flavor — base / -soft (tone-) / -strong
817
+ * (tone+) each carry a different L, so each needs its own decision.
818
+ * Declared once: the oklch(from ...) formulas re-resolve per mode,
819
+ * because html.dark redefines the fills these read.
820
+ *
821
+ * TO REVERT to the old fixed-white behaviour, override in your app:
822
+ * :root { --ty-solid-primary-fg: white; ... }
823
+ * or set --ty-solid-fg-threshold: 1 to force white everywhere.
824
+ * --------------------------------------------------------------*/
825
+
826
+ /* Crossover point. Fills lighter than this get black text. ~0.58-0.62
827
+ is the sRGB break-even; higher = prefers white, lower = prefers black. */
828
+ --ty-solid-fg-threshold: 0.62;
829
+
830
+ --ty-solid-primary-fg: oklch(
831
+ from var(--ty-solid-primary)
832
+ clamp(0, (var(--ty-solid-fg-threshold) - l) * 1000, 1) 0 0
833
+ );
834
+ --ty-solid-primary-soft-fg: oklch(
835
+ from var(--ty-solid-primary-soft)
836
+ clamp(0, (var(--ty-solid-fg-threshold) - l) * 1000, 1) 0 0
837
+ );
838
+ --ty-solid-primary-strong-fg: oklch(
839
+ from var(--ty-solid-primary-strong)
840
+ clamp(0, (var(--ty-solid-fg-threshold) - l) * 1000, 1) 0 0
841
+ );
842
+
843
+ --ty-solid-success-fg: oklch(
844
+ from var(--ty-solid-success)
845
+ clamp(0, (var(--ty-solid-fg-threshold) - l) * 1000, 1) 0 0
846
+ );
847
+ --ty-solid-success-soft-fg: oklch(
848
+ from var(--ty-solid-success-soft)
849
+ clamp(0, (var(--ty-solid-fg-threshold) - l) * 1000, 1) 0 0
850
+ );
851
+ --ty-solid-success-strong-fg: oklch(
852
+ from var(--ty-solid-success-strong)
853
+ clamp(0, (var(--ty-solid-fg-threshold) - l) * 1000, 1) 0 0
854
+ );
855
+
856
+ --ty-solid-danger-fg: oklch(
857
+ from var(--ty-solid-danger)
858
+ clamp(0, (var(--ty-solid-fg-threshold) - l) * 1000, 1) 0 0
859
+ );
860
+ --ty-solid-danger-soft-fg: oklch(
861
+ from var(--ty-solid-danger-soft)
862
+ clamp(0, (var(--ty-solid-fg-threshold) - l) * 1000, 1) 0 0
863
+ );
864
+ --ty-solid-danger-strong-fg: oklch(
865
+ from var(--ty-solid-danger-strong)
866
+ clamp(0, (var(--ty-solid-fg-threshold) - l) * 1000, 1) 0 0
867
+ );
868
+
869
+ --ty-solid-warning-fg: oklch(
870
+ from var(--ty-solid-warning)
871
+ clamp(0, (var(--ty-solid-fg-threshold) - l) * 1000, 1) 0 0
872
+ );
873
+ --ty-solid-warning-soft-fg: oklch(
874
+ from var(--ty-solid-warning-soft)
875
+ clamp(0, (var(--ty-solid-fg-threshold) - l) * 1000, 1) 0 0
876
+ );
877
+ --ty-solid-warning-strong-fg: oklch(
878
+ from var(--ty-solid-warning-strong)
879
+ clamp(0, (var(--ty-solid-fg-threshold) - l) * 1000, 1) 0 0
880
+ );
881
+
882
+ --ty-solid-neutral-fg: oklch(
883
+ from var(--ty-solid-neutral)
884
+ clamp(0, (var(--ty-solid-fg-threshold) - l) * 1000, 1) 0 0
885
+ );
886
+ --ty-solid-neutral-soft-fg: oklch(
887
+ from var(--ty-solid-neutral-soft)
888
+ clamp(0, (var(--ty-solid-fg-threshold) - l) * 1000, 1) 0 0
889
+ );
890
+ --ty-solid-neutral-strong-fg: oklch(
891
+ from var(--ty-solid-neutral-strong)
892
+ clamp(0, (var(--ty-solid-fg-threshold) - l) * 1000, 1) 0 0
893
+ );
894
+
895
+ /* derived states — axis 2 offsets on each base fill */
896
+ --ty-solid-primary-hover: oklch(
897
+ from var(--ty-solid-primary) calc(l + var(--ty-solid-hover-l)) c h
898
+ );
899
+ --ty-solid-primary-active: oklch(
900
+ from var(--ty-solid-primary) calc(l + var(--ty-solid-active-l)) c h
901
+ );
902
+ --ty-solid-primary-strong: oklch(
903
+ from var(--ty-solid-primary) calc(l + var(--ty-solid-strong-l)) c h
904
+ );
905
+ --ty-solid-primary-soft: oklch(
906
+ from var(--ty-solid-primary) calc(l + var(--ty-solid-soft-l)) c h
907
+ );
908
+
909
+ --ty-solid-success-hover: oklch(
910
+ from var(--ty-solid-success) calc(l + var(--ty-solid-hover-l)) c h
911
+ );
912
+ --ty-solid-success-active: oklch(
913
+ from var(--ty-solid-success) calc(l + var(--ty-solid-active-l)) c h
914
+ );
915
+ --ty-solid-success-strong: oklch(
916
+ from var(--ty-solid-success) calc(l + var(--ty-solid-strong-l)) c h
917
+ );
918
+ --ty-solid-success-soft: oklch(
919
+ from var(--ty-solid-success) calc(l + var(--ty-solid-soft-l)) c h
920
+ );
921
+
922
+ --ty-solid-danger-hover: oklch(
923
+ from var(--ty-solid-danger) calc(l + var(--ty-solid-hover-l)) c h
924
+ );
925
+ --ty-solid-danger-active: oklch(
926
+ from var(--ty-solid-danger) calc(l + var(--ty-solid-active-l)) c h
927
+ );
928
+ --ty-solid-danger-strong: oklch(
929
+ from var(--ty-solid-danger) calc(l + var(--ty-solid-strong-l)) c h
930
+ );
931
+ --ty-solid-danger-soft: oklch(
932
+ from var(--ty-solid-danger) calc(l + var(--ty-solid-soft-l)) c h
933
+ );
934
+
935
+ --ty-solid-warning-hover: oklch(
936
+ from var(--ty-solid-warning) calc(l + var(--ty-solid-hover-l)) c h
937
+ );
938
+ --ty-solid-warning-active: oklch(
939
+ from var(--ty-solid-warning) calc(l + var(--ty-solid-active-l)) c h
940
+ );
941
+ --ty-solid-warning-strong: oklch(
942
+ from var(--ty-solid-warning) calc(l + var(--ty-solid-strong-l)) c h
943
+ );
944
+ --ty-solid-warning-soft: oklch(
945
+ from var(--ty-solid-warning) calc(l + var(--ty-solid-soft-l)) c h
946
+ );
947
+
948
+ --ty-solid-neutral-hover: oklch(
949
+ from var(--ty-solid-neutral) calc(l + var(--ty-solid-hover-l)) c h
950
+ );
951
+ --ty-solid-neutral-active: oklch(
952
+ from var(--ty-solid-neutral) calc(l + var(--ty-solid-active-l)) c h
953
+ );
954
+ /* neutral+ reads its own absolute L dial, not anchor + shared offset —
955
+ it's the full-contrast ink endpoint (see --ty-l-solid-neutral-strong). */
956
+ --ty-solid-neutral-strong: oklch(
957
+ from var(--ty-neutral-seed-resolved) var(--ty-l-solid-neutral-strong)
958
+ calc(c * var(--ty-c-strong-mult)) h
959
+ );
960
+ --ty-solid-neutral-soft: oklch(
961
+ from var(--ty-solid-neutral) calc(l + var(--ty-solid-soft-l)) c h
962
+ );
963
+ }
964
+
965
+ /* =====================================================================
966
+ * SECTION 3 — THEME TRANSITIONS
967
+ * =====================================================================
968
+ *
969
+ * Every dial above is a plain number (or percentage) — and every color
970
+ * in the system is a pure function of those dials. Registering the
971
+ * dials as typed custom properties makes them INTERPOLABLE, so a theme
972
+ * or mode switch (html.dark, html.love, a [data-ty-theme] pack) does
973
+ * not snap: the dials animate, and every derived color — buttons,
974
+ * borders, surfaces, text, focus rings — crossfades through OKLCH
975
+ * space frame by frame. No JS, no repaint tricks; it falls out of the
976
+ * dials-not-colors architecture.
977
+ *
978
+ * Opt out (or retune) per app:
979
+ * :root { --ty-theme-transition: 0s; } no animation
980
+ * :root { --ty-theme-transition: 1.2s; } slower crossfade
981
+ * Reduced-motion users never get the animation.
982
+ *
983
+ * Browsers without @property simply skip registration — themes still
984
+ * work, switches just snap (the pre-TC37 behavior).
985
+ * ===================================================================== */
986
+
987
+ @property --ty-brand-hue {
988
+ syntax: "<number>";
989
+ inherits: true;
990
+ initial-value: 45;
991
+ }
992
+ @property --ty-brand-chroma {
993
+ syntax: "<number>";
994
+ inherits: true;
995
+ initial-value: 0.125;
996
+ }
997
+ @property --ty-surface-chroma {
998
+ syntax: "<number>";
999
+ inherits: true;
1000
+ initial-value: 0;
1001
+ }
1002
+ @property --ty-success-hue {
1003
+ syntax: "<number>";
1004
+ inherits: true;
1005
+ initial-value: 145;
1006
+ }
1007
+ @property --ty-success-chroma {
1008
+ syntax: "<number>";
1009
+ inherits: true;
1010
+ initial-value: 0.135;
1011
+ }
1012
+ @property --ty-warning-hue {
1013
+ syntax: "<number>";
1014
+ inherits: true;
1015
+ initial-value: 75;
1016
+ }
1017
+ @property --ty-warning-chroma {
1018
+ syntax: "<number>";
1019
+ inherits: true;
1020
+ initial-value: 0.25;
1021
+ }
1022
+ @property --ty-danger-hue {
1023
+ syntax: "<number>";
1024
+ inherits: true;
1025
+ initial-value: 25;
1026
+ }
1027
+ @property --ty-danger-chroma {
1028
+ syntax: "<number>";
1029
+ inherits: true;
1030
+ initial-value: 0.164;
1031
+ }
1032
+ @property --ty-neutral-hue {
1033
+ syntax: "<number>";
1034
+ inherits: true;
1035
+ initial-value: 0;
1036
+ }
1037
+ @property --ty-neutral-chroma {
1038
+ syntax: "<number>";
1039
+ inherits: true;
1040
+ initial-value: 0;
1041
+ }
1042
+ @property --ty-l-strong {
1043
+ syntax: "<number>";
1044
+ inherits: true;
1045
+ initial-value: 0.38;
1046
+ }
1047
+ @property --ty-l-bold {
1048
+ syntax: "<number>";
1049
+ inherits: true;
1050
+ initial-value: 0.46;
1051
+ }
1052
+ @property --ty-l-base {
1053
+ syntax: "<number>";
1054
+ inherits: true;
1055
+ initial-value: 0.54;
1056
+ }
1057
+ @property --ty-l-soft {
1058
+ syntax: "<number>";
1059
+ inherits: true;
1060
+ initial-value: 0.72;
1061
+ }
1062
+ @property --ty-l-faint {
1063
+ syntax: "<number>";
1064
+ inherits: true;
1065
+ initial-value: 0.88;
1066
+ }
1067
+ @property --ty-l-bg-base {
1068
+ syntax: "<number>";
1069
+ inherits: true;
1070
+ initial-value: 0.96;
1071
+ }
1072
+ @property --ty-l-bg-bold {
1073
+ syntax: "<number>";
1074
+ inherits: true;
1075
+ initial-value: 0.92;
1076
+ }
1077
+ @property --ty-l-bg-soft {
1078
+ syntax: "<number>";
1079
+ inherits: true;
1080
+ initial-value: 0.98;
1081
+ }
1082
+ @property --ty-l-text-strong {
1083
+ syntax: "<number>";
1084
+ inherits: true;
1085
+ initial-value: 0;
1086
+ }
1087
+ @property --ty-l-text-bold {
1088
+ syntax: "<number>";
1089
+ inherits: true;
1090
+ initial-value: 0;
1091
+ }
1092
+ @property --ty-l-text-base {
1093
+ syntax: "<number>";
1094
+ inherits: true;
1095
+ initial-value: 0.21;
1096
+ }
1097
+ @property --ty-l-text-soft {
1098
+ syntax: "<number>";
1099
+ inherits: true;
1100
+ initial-value: 0.37;
1101
+ }
1102
+ @property --ty-l-text-faint {
1103
+ syntax: "<number>";
1104
+ inherits: true;
1105
+ initial-value: 0.71;
1106
+ }
1107
+ @property --ty-l-border-content {
1108
+ syntax: "<number>";
1109
+ inherits: true;
1110
+ initial-value: 0.93;
1111
+ }
1112
+ @property --ty-l-border-elevated {
1113
+ syntax: "<number>";
1114
+ inherits: true;
1115
+ initial-value: 0.93;
1116
+ }
1117
+ @property --ty-l-border-floating {
1118
+ syntax: "<number>";
1119
+ inherits: true;
1120
+ initial-value: 0.93;
1121
+ }
1122
+ @property --ty-l-border-strong {
1123
+ syntax: "<number>";
1124
+ inherits: true;
1125
+ initial-value: 0.46;
1126
+ }
1127
+ @property --ty-l-border-bold {
1128
+ syntax: "<number>";
1129
+ inherits: true;
1130
+ initial-value: 0.64;
1131
+ }
1132
+ @property --ty-l-border {
1133
+ syntax: "<number>";
1134
+ inherits: true;
1135
+ initial-value: 0.76;
1136
+ }
1137
+ @property --ty-l-border-soft {
1138
+ syntax: "<number>";
1139
+ inherits: true;
1140
+ initial-value: 0.88;
1141
+ }
1142
+ @property --ty-l-border-faint {
1143
+ syntax: "<number>";
1144
+ inherits: true;
1145
+ initial-value: 0.92;
1146
+ }
1147
+ @property --ty-l-surface-canvas {
1148
+ syntax: "<number>";
1149
+ inherits: true;
1150
+ initial-value: 0.985;
1151
+ }
1152
+ @property --ty-l-surface-content {
1153
+ syntax: "<number>";
1154
+ inherits: true;
1155
+ initial-value: 1;
1156
+ }
1157
+ @property --ty-l-surface-elevated {
1158
+ syntax: "<number>";
1159
+ inherits: true;
1160
+ initial-value: 1;
1161
+ }
1162
+ @property --ty-l-surface-floating {
1163
+ syntax: "<number>";
1164
+ inherits: true;
1165
+ initial-value: 1;
1166
+ }
1167
+ @property --ty-l-surface-input {
1168
+ syntax: "<number>";
1169
+ inherits: true;
1170
+ initial-value: 1;
1171
+ }
1172
+ @property --ty-l-solid-neutral {
1173
+ syntax: "<number>";
1174
+ inherits: true;
1175
+ initial-value: 0.4;
1176
+ }
1177
+ @property --ty-l-solid-neutral-strong {
1178
+ syntax: "<number>";
1179
+ inherits: true;
1180
+ initial-value: 0.15;
1181
+ }
1182
+ @property --ty-l-bg-neutral-strong {
1183
+ syntax: "<number>";
1184
+ inherits: true;
1185
+ initial-value: 0.25;
1186
+ }
1187
+ @property --ty-l-bg-neutral-faint {
1188
+ syntax: "<number>";
1189
+ inherits: true;
1190
+ initial-value: 0.99;
1191
+ }
1192
+ @property --ty-c-bg-neutral-faint-mult {
1193
+ syntax: "<number>";
1194
+ inherits: true;
1195
+ initial-value: 0.4;
1196
+ }
1197
+ @property --ty-c-strong-mult {
1198
+ syntax: "<number>";
1199
+ inherits: true;
1200
+ initial-value: 0.77;
1201
+ }
1202
+ @property --ty-c-bold-mult {
1203
+ syntax: "<number>";
1204
+ inherits: true;
1205
+ initial-value: 1;
1206
+ }
1207
+ @property --ty-c-base-mult {
1208
+ syntax: "<number>";
1209
+ inherits: true;
1210
+ initial-value: 0.92;
1211
+ }
1212
+ @property --ty-c-soft-mult {
1213
+ syntax: "<number>";
1214
+ inherits: true;
1215
+ initial-value: 0.77;
1216
+ }
1217
+ @property --ty-c-faint-mult {
1218
+ syntax: "<number>";
1219
+ inherits: true;
1220
+ initial-value: 0.46;
1221
+ }
1222
+ @property --ty-c-bg-base-mult {
1223
+ syntax: "<number>";
1224
+ inherits: true;
1225
+ initial-value: 0.15;
1226
+ }
1227
+ @property --ty-c-bg-bold-mult {
1228
+ syntax: "<number>";
1229
+ inherits: true;
1230
+ initial-value: 0.38;
1231
+ }
1232
+ @property --ty-c-bg-soft-mult {
1233
+ syntax: "<number>";
1234
+ inherits: true;
1235
+ initial-value: 0.08;
1236
+ }
1237
+ @property --ty-primary-l-factor {
1238
+ syntax: "<number>";
1239
+ inherits: true;
1240
+ initial-value: 1;
1241
+ }
1242
+ @property --ty-success-l-factor {
1243
+ syntax: "<number>";
1244
+ inherits: true;
1245
+ initial-value: 1;
1246
+ }
1247
+ @property --ty-warning-l-factor {
1248
+ syntax: "<number>";
1249
+ inherits: true;
1250
+ initial-value: 1.15;
1251
+ }
1252
+ @property --ty-danger-l-factor {
1253
+ syntax: "<number>";
1254
+ inherits: true;
1255
+ initial-value: 1;
1256
+ }
1257
+ @property --ty-neutral-l-factor {
1258
+ syntax: "<number>";
1259
+ inherits: true;
1260
+ initial-value: 1;
1261
+ }
1262
+ /* Typography + button-depth dials (defined in tyrell.css, mode-flipped
1263
+ there). Registered here so they interpolate on theme switch: with a
1264
+ variable font, text weight crossfades along with the colors, and the
1265
+ solid-button depth chrome (border offset) glides rather than snaps. */
1266
+ @property --ty-weight-action {
1267
+ syntax: "<number>";
1268
+ inherits: true;
1269
+ initial-value: 510;
1270
+ }
1271
+ @property --ty-weight-label {
1272
+ syntax: "<number>";
1273
+ inherits: true;
1274
+ initial-value: 440;
1275
+ }
1276
+ @property --ty-button-depth {
1277
+ syntax: "<number>";
1278
+ inherits: true;
1279
+ initial-value: 0.35;
1280
+ }
1281
+ @property --ty-button-border-l {
1282
+ syntax: "<number>";
1283
+ inherits: true;
1284
+ initial-value: -0.08;
1285
+ }
1286
+ @property --ty-button-border-l-neutral {
1287
+ syntax: "<number>";
1288
+ inherits: true;
1289
+ initial-value: -0.25;
1290
+ }
1291
+ @property --ty-primary-solid-l {
1292
+ syntax: "<number>";
1293
+ inherits: true;
1294
+ initial-value: 0;
1295
+ }
1296
+ @property --ty-success-solid-l {
1297
+ syntax: "<number>";
1298
+ inherits: true;
1299
+ initial-value: 0;
1300
+ }
1301
+ @property --ty-warning-solid-l {
1302
+ syntax: "<number>";
1303
+ inherits: true;
1304
+ initial-value: 0.1;
1305
+ }
1306
+ @property --ty-danger-solid-l {
1307
+ syntax: "<number>";
1308
+ inherits: true;
1309
+ initial-value: 0;
1310
+ }
1311
+ @property --ty-solid-hover-l {
1312
+ syntax: "<number>";
1313
+ inherits: true;
1314
+ initial-value: -0.04;
1315
+ }
1316
+ @property --ty-solid-active-l {
1317
+ syntax: "<number>";
1318
+ inherits: true;
1319
+ initial-value: -0.08;
1320
+ }
1321
+ @property --ty-solid-strong-l {
1322
+ syntax: "<number>";
1323
+ inherits: true;
1324
+ initial-value: -0.06;
1325
+ }
1326
+ @property --ty-solid-soft-l {
1327
+ syntax: "<number>";
1328
+ inherits: true;
1329
+ initial-value: 0.1;
1330
+ }
1331
+ @property --ty-solid-l {
1332
+ syntax: "<number>";
1333
+ inherits: true;
1334
+ initial-value: 0;
1335
+ }
1336
+ @property --ty-solid-c {
1337
+ syntax: "<number>";
1338
+ inherits: true;
1339
+ initial-value: 1;
1340
+ }
1341
+ @property --ty-solid-h {
1342
+ syntax: "<number>";
1343
+ inherits: true;
1344
+ initial-value: 0;
1345
+ }
1346
+ @property --ty-solid-fg-threshold {
1347
+ syntax: "<number>";
1348
+ inherits: true;
1349
+ initial-value: 0.62;
1350
+ }
1351
+ @property --ty-focus-ring-alpha {
1352
+ syntax: "<percentage>";
1353
+ inherits: true;
1354
+ initial-value: 5%;
1355
+ }
1356
+
1357
+ @property --ty-primary-seed-resolved {
1358
+ syntax: "<color>";
1359
+ inherits: true;
1360
+ initial-value: #808080;
1361
+ }
1362
+ @property --ty-success-seed-resolved {
1363
+ syntax: "<color>";
1364
+ inherits: true;
1365
+ initial-value: #808080;
1366
+ }
1367
+ @property --ty-warning-seed-resolved {
1368
+ syntax: "<color>";
1369
+ inherits: true;
1370
+ initial-value: #808080;
1371
+ }
1372
+ @property --ty-danger-seed-resolved {
1373
+ syntax: "<color>";
1374
+ inherits: true;
1375
+ initial-value: #808080;
1376
+ }
1377
+ @property --ty-neutral-seed-resolved {
1378
+ syntax: "<color>";
1379
+ inherits: true;
1380
+ initial-value: #808080;
1381
+ }
1382
+
1383
+ @media (prefers-reduced-motion: no-preference) {
1384
+ html,
1385
+ [data-ty-theme] {
1386
+ transition-property:
1387
+ --ty-brand-hue, --ty-brand-chroma, --ty-surface-chroma, --ty-success-hue,
1388
+ --ty-success-chroma, --ty-warning-hue, --ty-warning-chroma,
1389
+ --ty-danger-hue, --ty-danger-chroma, --ty-neutral-hue,
1390
+ --ty-neutral-chroma, --ty-l-strong, --ty-l-bold, --ty-l-base, --ty-l-soft,
1391
+ --ty-l-faint, --ty-l-bg-base, --ty-l-bg-bold, --ty-l-bg-soft,
1392
+ --ty-l-text-strong, --ty-l-text-bold, --ty-l-text-base, --ty-l-text-soft,
1393
+ --ty-l-text-faint, --ty-l-border-content, --ty-l-border-elevated,
1394
+ --ty-l-border-floating, --ty-l-border-strong, --ty-l-border-bold,
1395
+ --ty-l-border, --ty-l-border-soft, --ty-l-border-faint,
1396
+ --ty-l-surface-canvas, --ty-l-surface-content, --ty-l-surface-elevated,
1397
+ --ty-l-surface-floating, --ty-l-surface-input, --ty-l-solid-neutral,
1398
+ --ty-l-solid-neutral-strong, --ty-l-bg-neutral-strong,
1399
+ --ty-l-bg-neutral-faint, --ty-c-bg-neutral-faint-mult, --ty-c-strong-mult,
1400
+ --ty-c-bold-mult, --ty-c-base-mult, --ty-c-soft-mult, --ty-c-faint-mult,
1401
+ --ty-c-bg-base-mult, --ty-c-bg-bold-mult, --ty-c-bg-soft-mult,
1402
+ --ty-primary-l-factor, --ty-success-l-factor, --ty-warning-l-factor,
1403
+ --ty-danger-l-factor, --ty-neutral-l-factor, --ty-primary-solid-l,
1404
+ --ty-success-solid-l, --ty-warning-solid-l, --ty-danger-solid-l,
1405
+ --ty-solid-hover-l, --ty-solid-active-l, --ty-solid-strong-l,
1406
+ --ty-solid-soft-l, --ty-solid-l, --ty-solid-c, --ty-solid-h,
1407
+ --ty-solid-fg-threshold, --ty-focus-ring-alpha, --ty-weight-action,
1408
+ --ty-weight-label, --ty-button-depth, --ty-button-border-l,
1409
+ --ty-button-border-l-neutral;
1410
+ transition-duration: var(--ty-theme-transition, 0.45s);
1411
+ transition-timing-function: ease;
1412
+ }
1413
+ }
1414
+
1415
+ /* Interactive components (button, input, switch, tabs, …) each carry their
1416
+ * OWN short transition — background-color/border-color/color at 0.15–0.2s —
1417
+ * for snappy hover/focus/active feedback. CSS transitions fire on ANY
1418
+ * computed-value change, not just interaction, so a theme switch also
1419
+ * (re-)triggers every one of those, racing the coordinated 0.45s dial
1420
+ * crossfade above at a different speed on every element — hundreds of
1421
+ * mistimed transitions instead of one wash, which reads as jittery rather
1422
+ * than smooth.
1423
+ *
1424
+ * `.ty-theme-switching` is an opt-in escape hatch: add it to `<html>` (or
1425
+ * a scoped `[data-ty-theme]` root) for the duration of a theme switch to
1426
+ * silence local transitions on its descendants — NOT on the switching
1427
+ * element itself, so the dial crossfade above is untouched. Five lines of
1428
+ * JS around your theme toggle:
1429
+ *
1430
+ * root.classList.add('ty-theme-switching');
1431
+ * root.classList.toggle('dark'); // or flip the theme pack
1432
+ * setTimeout(() => root.classList.remove('ty-theme-switching'),
1433
+ * parseFloat(getComputedStyle(root)
1434
+ * .getPropertyValue('--ty-theme-transition')) * 1000 || 450);
1435
+ *
1436
+ * Two mechanisms, because Shadow DOM blocks the first from reaching the
1437
+ * second: the `*` rule below only matches LIGHT-DOM elements (your own
1438
+ * markup, Tailwind-transitioned nav items, etc.) — it cannot reach inside
1439
+ * any ty-* component's shadow root, style encapsulation blocks a document
1440
+ * stylesheet's universal selector from crossing that boundary. Every ty-*
1441
+ * component's OWN local transition is instead wired to the inheriting
1442
+ * custom property `--ty-local-transition` (unset by default; components
1443
+ * fall back to their normal value) — custom properties DO inherit through
1444
+ * shadow boundaries, so setting it once here reaches every component's
1445
+ * internal styles for free. Purely additive — nothing here runs unless you
1446
+ * add the class yourself. */
1447
+ .ty-theme-switching {
1448
+ --ty-local-transition: none;
1449
+ }
1450
+ .ty-theme-switching *,
1451
+ .ty-theme-switching *::before,
1452
+ .ty-theme-switching *::after {
1453
+ transition: none !important;
1454
+ }