@tartaguti/ui-components 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (392) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE +21 -0
  3. package/README.md +192 -2
  4. package/THIRD-PARTY-NOTICES.md +29 -0
  5. package/custom-elements.json +16731 -0
  6. package/dist/accordion/tt-accordion-item.d.ts +32 -0
  7. package/dist/accordion/tt-accordion-item.d.ts.map +1 -0
  8. package/dist/accordion/tt-accordion-item.js +247 -0
  9. package/dist/accordion/tt-accordion-item.js.map +1 -0
  10. package/dist/accordion/tt-accordion.d.ts +28 -0
  11. package/dist/accordion/tt-accordion.d.ts.map +1 -0
  12. package/dist/accordion/tt-accordion.js +148 -0
  13. package/dist/accordion/tt-accordion.js.map +1 -0
  14. package/dist/bento/tt-bento-item.d.ts +35 -0
  15. package/dist/bento/tt-bento-item.d.ts.map +1 -0
  16. package/dist/bento/tt-bento-item.js +201 -0
  17. package/dist/bento/tt-bento-item.js.map +1 -0
  18. package/dist/bento/tt-bento.d.ts +25 -0
  19. package/dist/bento/tt-bento.d.ts.map +1 -0
  20. package/dist/bento/tt-bento.js +314 -0
  21. package/dist/bento/tt-bento.js.map +1 -0
  22. package/dist/floating.d.ts +61 -0
  23. package/dist/floating.d.ts.map +1 -0
  24. package/dist/floating.js +120 -0
  25. package/dist/floating.js.map +1 -0
  26. package/dist/index.d.ts +92 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +96 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/interaction/tt-draggable.d.ts +29 -0
  31. package/dist/interaction/tt-draggable.d.ts.map +1 -0
  32. package/dist/interaction/tt-draggable.js +66 -0
  33. package/dist/interaction/tt-draggable.js.map +1 -0
  34. package/dist/interaction/tt-droppable.d.ts +37 -0
  35. package/dist/interaction/tt-droppable.d.ts.map +1 -0
  36. package/dist/interaction/tt-droppable.js +108 -0
  37. package/dist/interaction/tt-droppable.js.map +1 -0
  38. package/dist/interaction/tt-resizable.d.ts +45 -0
  39. package/dist/interaction/tt-resizable.d.ts.map +1 -0
  40. package/dist/interaction/tt-resizable.js +210 -0
  41. package/dist/interaction/tt-resizable.js.map +1 -0
  42. package/dist/interaction/tt-reveal.d.ts +54 -0
  43. package/dist/interaction/tt-reveal.d.ts.map +1 -0
  44. package/dist/interaction/tt-reveal.js +302 -0
  45. package/dist/interaction/tt-reveal.js.map +1 -0
  46. package/dist/motion.d.ts +8 -0
  47. package/dist/motion.d.ts.map +1 -0
  48. package/dist/motion.js +49 -0
  49. package/dist/motion.js.map +1 -0
  50. package/dist/overlay.d.ts +7 -0
  51. package/dist/overlay.d.ts.map +1 -0
  52. package/dist/overlay.js +121 -0
  53. package/dist/overlay.js.map +1 -0
  54. package/dist/safe-url.d.ts +14 -0
  55. package/dist/safe-url.d.ts.map +1 -0
  56. package/dist/safe-url.js +46 -0
  57. package/dist/safe-url.js.map +1 -0
  58. package/dist/scroll-lock.d.ts +22 -0
  59. package/dist/scroll-lock.d.ts.map +1 -0
  60. package/dist/scroll-lock.js +56 -0
  61. package/dist/scroll-lock.js.map +1 -0
  62. package/dist/sidebar/tt-menu-group.d.ts +69 -0
  63. package/dist/sidebar/tt-menu-group.d.ts.map +1 -0
  64. package/dist/sidebar/tt-menu-group.js +358 -0
  65. package/dist/sidebar/tt-menu-group.js.map +1 -0
  66. package/dist/sidebar/tt-menu-item.d.ts +70 -0
  67. package/dist/sidebar/tt-menu-item.d.ts.map +1 -0
  68. package/dist/sidebar/tt-menu-item.js +502 -0
  69. package/dist/sidebar/tt-menu-item.js.map +1 -0
  70. package/dist/sidebar/tt-sidebar-footer.d.ts +39 -0
  71. package/dist/sidebar/tt-sidebar-footer.d.ts.map +1 -0
  72. package/dist/sidebar/tt-sidebar-footer.js +205 -0
  73. package/dist/sidebar/tt-sidebar-footer.js.map +1 -0
  74. package/dist/sidebar/tt-sidebar-header.d.ts +31 -0
  75. package/dist/sidebar/tt-sidebar-header.d.ts.map +1 -0
  76. package/dist/sidebar/tt-sidebar-header.js +150 -0
  77. package/dist/sidebar/tt-sidebar-header.js.map +1 -0
  78. package/dist/sidebar/tt-sidebar.d.ts +72 -0
  79. package/dist/sidebar/tt-sidebar.d.ts.map +1 -0
  80. package/dist/sidebar/tt-sidebar.js +539 -0
  81. package/dist/sidebar/tt-sidebar.js.map +1 -0
  82. package/dist/sr-only.d.ts +15 -0
  83. package/dist/sr-only.d.ts.map +1 -0
  84. package/dist/sr-only.js +27 -0
  85. package/dist/sr-only.js.map +1 -0
  86. package/dist/tokens.css +1124 -0
  87. package/dist/tt-alert.d.ts +26 -0
  88. package/dist/tt-alert.d.ts.map +1 -0
  89. package/dist/tt-alert.js +165 -0
  90. package/dist/tt-alert.js.map +1 -0
  91. package/dist/tt-aspect-ratio.d.ts +40 -0
  92. package/dist/tt-aspect-ratio.d.ts.map +1 -0
  93. package/dist/tt-aspect-ratio.js +98 -0
  94. package/dist/tt-aspect-ratio.js.map +1 -0
  95. package/dist/tt-auth.d.ts +75 -0
  96. package/dist/tt-auth.d.ts.map +1 -0
  97. package/dist/tt-auth.js +520 -0
  98. package/dist/tt-auth.js.map +1 -0
  99. package/dist/tt-avatar.d.ts +26 -0
  100. package/dist/tt-avatar.d.ts.map +1 -0
  101. package/dist/tt-avatar.js +169 -0
  102. package/dist/tt-avatar.js.map +1 -0
  103. package/dist/tt-badge.d.ts +36 -0
  104. package/dist/tt-badge.d.ts.map +1 -0
  105. package/dist/tt-badge.js +135 -0
  106. package/dist/tt-badge.js.map +1 -0
  107. package/dist/tt-breadcrumbs.d.ts +22 -0
  108. package/dist/tt-breadcrumbs.d.ts.map +1 -0
  109. package/dist/tt-breadcrumbs.js +144 -0
  110. package/dist/tt-breadcrumbs.js.map +1 -0
  111. package/dist/tt-button-group.d.ts +32 -0
  112. package/dist/tt-button-group.d.ts.map +1 -0
  113. package/dist/tt-button-group.js +132 -0
  114. package/dist/tt-button-group.js.map +1 -0
  115. package/dist/tt-button.d.ts +31 -0
  116. package/dist/tt-button.d.ts.map +1 -0
  117. package/dist/tt-button.js +483 -0
  118. package/dist/tt-button.js.map +1 -0
  119. package/dist/tt-card.d.ts +26 -0
  120. package/dist/tt-card.d.ts.map +1 -0
  121. package/dist/tt-card.js +429 -0
  122. package/dist/tt-card.js.map +1 -0
  123. package/dist/tt-chart.d.ts +36 -0
  124. package/dist/tt-chart.d.ts.map +1 -0
  125. package/dist/tt-chart.js +332 -0
  126. package/dist/tt-chart.js.map +1 -0
  127. package/dist/tt-checkbox-group.d.ts +38 -0
  128. package/dist/tt-checkbox-group.d.ts.map +1 -0
  129. package/dist/tt-checkbox-group.js +243 -0
  130. package/dist/tt-checkbox-group.js.map +1 -0
  131. package/dist/tt-checkbox.d.ts +35 -0
  132. package/dist/tt-checkbox.d.ts.map +1 -0
  133. package/dist/tt-checkbox.js +354 -0
  134. package/dist/tt-checkbox.js.map +1 -0
  135. package/dist/tt-code-block.d.ts +37 -0
  136. package/dist/tt-code-block.d.ts.map +1 -0
  137. package/dist/tt-code-block.js +409 -0
  138. package/dist/tt-code-block.js.map +1 -0
  139. package/dist/tt-collapsible.d.ts +71 -0
  140. package/dist/tt-collapsible.d.ts.map +1 -0
  141. package/dist/tt-collapsible.js +206 -0
  142. package/dist/tt-collapsible.js.map +1 -0
  143. package/dist/tt-combobox.d.ts +107 -0
  144. package/dist/tt-combobox.d.ts.map +1 -0
  145. package/dist/tt-combobox.js +816 -0
  146. package/dist/tt-combobox.js.map +1 -0
  147. package/dist/tt-company-card.d.ts +30 -0
  148. package/dist/tt-company-card.d.ts.map +1 -0
  149. package/dist/tt-company-card.js +382 -0
  150. package/dist/tt-company-card.js.map +1 -0
  151. package/dist/tt-comparison-table.d.ts +59 -0
  152. package/dist/tt-comparison-table.d.ts.map +1 -0
  153. package/dist/tt-comparison-table.js +441 -0
  154. package/dist/tt-comparison-table.js.map +1 -0
  155. package/dist/tt-component-doc.d.ts +63 -0
  156. package/dist/tt-component-doc.d.ts.map +1 -0
  157. package/dist/tt-component-doc.js +585 -0
  158. package/dist/tt-component-doc.js.map +1 -0
  159. package/dist/tt-confirm.d.ts +45 -0
  160. package/dist/tt-confirm.d.ts.map +1 -0
  161. package/dist/tt-confirm.js +151 -0
  162. package/dist/tt-confirm.js.map +1 -0
  163. package/dist/tt-contact-list.d.ts +18 -0
  164. package/dist/tt-contact-list.d.ts.map +1 -0
  165. package/dist/tt-contact-list.js +101 -0
  166. package/dist/tt-contact-list.js.map +1 -0
  167. package/dist/tt-copy-button.d.ts +25 -0
  168. package/dist/tt-copy-button.d.ts.map +1 -0
  169. package/dist/tt-copy-button.js +194 -0
  170. package/dist/tt-copy-button.js.map +1 -0
  171. package/dist/tt-data-table.d.ts +69 -0
  172. package/dist/tt-data-table.d.ts.map +1 -0
  173. package/dist/tt-data-table.js +542 -0
  174. package/dist/tt-data-table.js.map +1 -0
  175. package/dist/tt-divider.d.ts +19 -0
  176. package/dist/tt-divider.d.ts.map +1 -0
  177. package/dist/tt-divider.js +104 -0
  178. package/dist/tt-divider.js.map +1 -0
  179. package/dist/tt-drawer.d.ts +38 -0
  180. package/dist/tt-drawer.d.ts.map +1 -0
  181. package/dist/tt-drawer.js +433 -0
  182. package/dist/tt-drawer.js.map +1 -0
  183. package/dist/tt-dropdown-menu.d.ts +57 -0
  184. package/dist/tt-dropdown-menu.d.ts.map +1 -0
  185. package/dist/tt-dropdown-menu.js +631 -0
  186. package/dist/tt-dropdown-menu.js.map +1 -0
  187. package/dist/tt-empty-state.d.ts +13 -0
  188. package/dist/tt-empty-state.d.ts.map +1 -0
  189. package/dist/tt-empty-state.js +94 -0
  190. package/dist/tt-empty-state.js.map +1 -0
  191. package/dist/tt-example.d.ts +33 -0
  192. package/dist/tt-example.d.ts.map +1 -0
  193. package/dist/tt-example.js +250 -0
  194. package/dist/tt-example.js.map +1 -0
  195. package/dist/tt-file-upload.d.ts +85 -0
  196. package/dist/tt-file-upload.d.ts.map +1 -0
  197. package/dist/tt-file-upload.js +662 -0
  198. package/dist/tt-file-upload.js.map +1 -0
  199. package/dist/tt-filter.d.ts +62 -0
  200. package/dist/tt-filter.d.ts.map +1 -0
  201. package/dist/tt-filter.js +393 -0
  202. package/dist/tt-filter.js.map +1 -0
  203. package/dist/tt-form.d.ts +33 -0
  204. package/dist/tt-form.d.ts.map +1 -0
  205. package/dist/tt-form.js +202 -0
  206. package/dist/tt-form.js.map +1 -0
  207. package/dist/tt-grid.d.ts +22 -0
  208. package/dist/tt-grid.d.ts.map +1 -0
  209. package/dist/tt-grid.js +106 -0
  210. package/dist/tt-grid.js.map +1 -0
  211. package/dist/tt-icon.d.ts +20 -0
  212. package/dist/tt-icon.d.ts.map +1 -0
  213. package/dist/tt-icon.js +510 -0
  214. package/dist/tt-icon.js.map +1 -0
  215. package/dist/tt-image.d.ts +59 -0
  216. package/dist/tt-image.d.ts.map +1 -0
  217. package/dist/tt-image.js +314 -0
  218. package/dist/tt-image.js.map +1 -0
  219. package/dist/tt-inbox.d.ts +45 -0
  220. package/dist/tt-inbox.d.ts.map +1 -0
  221. package/dist/tt-inbox.js +275 -0
  222. package/dist/tt-inbox.js.map +1 -0
  223. package/dist/tt-input.d.ts +43 -0
  224. package/dist/tt-input.d.ts.map +1 -0
  225. package/dist/tt-input.js +476 -0
  226. package/dist/tt-input.js.map +1 -0
  227. package/dist/tt-kbd.d.ts +39 -0
  228. package/dist/tt-kbd.d.ts.map +1 -0
  229. package/dist/tt-kbd.js +145 -0
  230. package/dist/tt-kbd.js.map +1 -0
  231. package/dist/tt-key-values.d.ts +17 -0
  232. package/dist/tt-key-values.d.ts.map +1 -0
  233. package/dist/tt-key-values.js +126 -0
  234. package/dist/tt-key-values.js.map +1 -0
  235. package/dist/tt-knowledge-doc.d.ts +24 -0
  236. package/dist/tt-knowledge-doc.d.ts.map +1 -0
  237. package/dist/tt-knowledge-doc.js +120 -0
  238. package/dist/tt-knowledge-doc.js.map +1 -0
  239. package/dist/tt-layout.d.ts +36 -0
  240. package/dist/tt-layout.d.ts.map +1 -0
  241. package/dist/tt-layout.js +318 -0
  242. package/dist/tt-layout.js.map +1 -0
  243. package/dist/tt-modal.d.ts +32 -0
  244. package/dist/tt-modal.d.ts.map +1 -0
  245. package/dist/tt-modal.js +553 -0
  246. package/dist/tt-modal.js.map +1 -0
  247. package/dist/tt-navbar.d.ts +33 -0
  248. package/dist/tt-navbar.d.ts.map +1 -0
  249. package/dist/tt-navbar.js +390 -0
  250. package/dist/tt-navbar.js.map +1 -0
  251. package/dist/tt-page-header.d.ts +28 -0
  252. package/dist/tt-page-header.d.ts.map +1 -0
  253. package/dist/tt-page-header.js +166 -0
  254. package/dist/tt-page-header.js.map +1 -0
  255. package/dist/tt-page.d.ts +14 -0
  256. package/dist/tt-page.d.ts.map +1 -0
  257. package/dist/tt-page.js +163 -0
  258. package/dist/tt-page.js.map +1 -0
  259. package/dist/tt-pagination.d.ts +27 -0
  260. package/dist/tt-pagination.d.ts.map +1 -0
  261. package/dist/tt-pagination.js +194 -0
  262. package/dist/tt-pagination.js.map +1 -0
  263. package/dist/tt-preview.d.ts +11 -0
  264. package/dist/tt-preview.d.ts.map +1 -0
  265. package/dist/tt-preview.js +68 -0
  266. package/dist/tt-preview.js.map +1 -0
  267. package/dist/tt-product.d.ts +40 -0
  268. package/dist/tt-product.d.ts.map +1 -0
  269. package/dist/tt-product.js +590 -0
  270. package/dist/tt-product.js.map +1 -0
  271. package/dist/tt-profile-card.d.ts +23 -0
  272. package/dist/tt-profile-card.d.ts.map +1 -0
  273. package/dist/tt-profile-card.js +199 -0
  274. package/dist/tt-profile-card.js.map +1 -0
  275. package/dist/tt-progress.d.ts +26 -0
  276. package/dist/tt-progress.d.ts.map +1 -0
  277. package/dist/tt-progress.js +156 -0
  278. package/dist/tt-progress.js.map +1 -0
  279. package/dist/tt-quantity.d.ts +36 -0
  280. package/dist/tt-quantity.d.ts.map +1 -0
  281. package/dist/tt-quantity.js +396 -0
  282. package/dist/tt-quantity.js.map +1 -0
  283. package/dist/tt-radio-group.d.ts +50 -0
  284. package/dist/tt-radio-group.d.ts.map +1 -0
  285. package/dist/tt-radio-group.js +372 -0
  286. package/dist/tt-radio-group.js.map +1 -0
  287. package/dist/tt-rating.d.ts +54 -0
  288. package/dist/tt-rating.d.ts.map +1 -0
  289. package/dist/tt-rating.js +312 -0
  290. package/dist/tt-rating.js.map +1 -0
  291. package/dist/tt-search.d.ts +47 -0
  292. package/dist/tt-search.d.ts.map +1 -0
  293. package/dist/tt-search.js +360 -0
  294. package/dist/tt-search.js.map +1 -0
  295. package/dist/tt-section.d.ts +13 -0
  296. package/dist/tt-section.d.ts.map +1 -0
  297. package/dist/tt-section.js +98 -0
  298. package/dist/tt-section.js.map +1 -0
  299. package/dist/tt-select.d.ts +62 -0
  300. package/dist/tt-select.d.ts.map +1 -0
  301. package/dist/tt-select.js +662 -0
  302. package/dist/tt-select.js.map +1 -0
  303. package/dist/tt-skeleton.d.ts +20 -0
  304. package/dist/tt-skeleton.d.ts.map +1 -0
  305. package/dist/tt-skeleton.js +143 -0
  306. package/dist/tt-skeleton.js.map +1 -0
  307. package/dist/tt-slider.d.ts +74 -0
  308. package/dist/tt-slider.d.ts.map +1 -0
  309. package/dist/tt-slider.js +426 -0
  310. package/dist/tt-slider.js.map +1 -0
  311. package/dist/tt-social-links.d.ts +34 -0
  312. package/dist/tt-social-links.d.ts.map +1 -0
  313. package/dist/tt-social-links.js +242 -0
  314. package/dist/tt-social-links.js.map +1 -0
  315. package/dist/tt-sparkline.d.ts +19 -0
  316. package/dist/tt-sparkline.d.ts.map +1 -0
  317. package/dist/tt-sparkline.js +191 -0
  318. package/dist/tt-sparkline.js.map +1 -0
  319. package/dist/tt-spinner.d.ts +19 -0
  320. package/dist/tt-spinner.d.ts.map +1 -0
  321. package/dist/tt-spinner.js +93 -0
  322. package/dist/tt-spinner.js.map +1 -0
  323. package/dist/tt-stack.d.ts +42 -0
  324. package/dist/tt-stack.d.ts.map +1 -0
  325. package/dist/tt-stack.js +158 -0
  326. package/dist/tt-stack.js.map +1 -0
  327. package/dist/tt-stat.d.ts +17 -0
  328. package/dist/tt-stat.d.ts.map +1 -0
  329. package/dist/tt-stat.js +134 -0
  330. package/dist/tt-stat.js.map +1 -0
  331. package/dist/tt-switch.d.ts +39 -0
  332. package/dist/tt-switch.d.ts.map +1 -0
  333. package/dist/tt-switch.js +254 -0
  334. package/dist/tt-switch.js.map +1 -0
  335. package/dist/tt-switcher.d.ts +23 -0
  336. package/dist/tt-switcher.d.ts.map +1 -0
  337. package/dist/tt-switcher.js +203 -0
  338. package/dist/tt-switcher.js.map +1 -0
  339. package/dist/tt-table-cell.d.ts +26 -0
  340. package/dist/tt-table-cell.d.ts.map +1 -0
  341. package/dist/tt-table-cell.js +121 -0
  342. package/dist/tt-table-cell.js.map +1 -0
  343. package/dist/tt-table-row.d.ts +31 -0
  344. package/dist/tt-table-row.d.ts.map +1 -0
  345. package/dist/tt-table-row.js +72 -0
  346. package/dist/tt-table-row.js.map +1 -0
  347. package/dist/tt-table.d.ts +44 -0
  348. package/dist/tt-table.d.ts.map +1 -0
  349. package/dist/tt-table.js +270 -0
  350. package/dist/tt-table.js.map +1 -0
  351. package/dist/tt-tabs.d.ts +31 -0
  352. package/dist/tt-tabs.d.ts.map +1 -0
  353. package/dist/tt-tabs.js +223 -0
  354. package/dist/tt-tabs.js.map +1 -0
  355. package/dist/tt-tag-list.d.ts +19 -0
  356. package/dist/tt-tag-list.d.ts.map +1 -0
  357. package/dist/tt-tag-list.js +136 -0
  358. package/dist/tt-tag-list.js.map +1 -0
  359. package/dist/tt-text.d.ts +52 -0
  360. package/dist/tt-text.d.ts.map +1 -0
  361. package/dist/tt-text.js +337 -0
  362. package/dist/tt-text.js.map +1 -0
  363. package/dist/tt-textarea.d.ts +43 -0
  364. package/dist/tt-textarea.d.ts.map +1 -0
  365. package/dist/tt-textarea.js +356 -0
  366. package/dist/tt-textarea.js.map +1 -0
  367. package/dist/tt-thread.d.ts +62 -0
  368. package/dist/tt-thread.d.ts.map +1 -0
  369. package/dist/tt-thread.js +374 -0
  370. package/dist/tt-thread.js.map +1 -0
  371. package/dist/tt-toast.d.ts +49 -0
  372. package/dist/tt-toast.d.ts.map +1 -0
  373. package/dist/tt-toast.js +440 -0
  374. package/dist/tt-toast.js.map +1 -0
  375. package/dist/tt-toggle-group.d.ts +73 -0
  376. package/dist/tt-toggle-group.d.ts.map +1 -0
  377. package/dist/tt-toggle-group.js +273 -0
  378. package/dist/tt-toggle-group.js.map +1 -0
  379. package/dist/tt-toggle.d.ts +54 -0
  380. package/dist/tt-toggle.d.ts.map +1 -0
  381. package/dist/tt-toggle.js +232 -0
  382. package/dist/tt-toggle.js.map +1 -0
  383. package/dist/tt-toolbar.d.ts +26 -0
  384. package/dist/tt-toolbar.d.ts.map +1 -0
  385. package/dist/tt-toolbar.js +164 -0
  386. package/dist/tt-toolbar.js.map +1 -0
  387. package/dist/tt-tooltip.d.ts +42 -0
  388. package/dist/tt-tooltip.d.ts.map +1 -0
  389. package/dist/tt-tooltip.js +299 -0
  390. package/dist/tt-tooltip.js.map +1 -0
  391. package/package.json +84 -4
  392. package/vscode.html-custom-data.json +2907 -0
@@ -0,0 +1,1124 @@
1
+ /* =========================================
2
+ Tartaguti Design Tokens
3
+
4
+ Single source of truth for the design system. Every tt-* component
5
+ reads these; nothing hardcodes a colour.
6
+
7
+ Setup (once per app):
8
+ import "@tartaguti/ui-components/tokens.css";
9
+
10
+ Dark mode: toggle the `dark` class on <html>. Custom properties are
11
+ inherited properties, so they cross shadow boundaries automatically -
12
+ no per-component prop needed.
13
+
14
+ Theming: override any token on :root (or on any subtree) and every
15
+ component below it updates.
16
+
17
+ ---------------------------------------------------------------------
18
+ The palette is neutral ON PURPOSE, with ONE brand accent.
19
+
20
+ Surfaces, text and borders are greyscale. Colour means something:
21
+ destructive, success and warning are red, green and amber, and
22
+ --tt-brand (indigo by default) marks where the keyboard IS (the focus
23
+ ring) and nothing else in the library. A base with a loud brand forces
24
+ every consumer to fight it, so the accent is spent like money: an app
25
+ sets --tt-brand on :root and every focus ring takes its colour.
26
+
27
+ The status colours and the brand are one family (Oct 2026): every one
28
+ is inside sRGB, so no screen clips it into a different colour, and
29
+ every one passes 4.5:1 as text on the card, on the page and on its own
30
+ 13% badge tint over any surface (the card, the page, --tt-muted, the
31
+ subtle tints), in both themes. The old warning sat outside sRGB and
32
+ was clipped to a muddy brown; the old light-mode badge text failed AA
33
+ for all three.
34
+
35
+ Named themes are preset blocks of overrides, the same shape as the
36
+ .dark block: <html data-theme="soft">, or any subtree. Neutral, the
37
+ values on :root, is still the default. See Themes below.
38
+ ========================================================================= */
39
+
40
+ :root {
41
+ /* ---- Surfaces ---- */
42
+ /* The page sits slightly below its cards. When both were pure white,
43
+ a card only existed because of its border and light mode read flat
44
+ next to dark mode, where 0.205 already floats above 0.145. */
45
+ --tt-background: oklch(0.976 0 0);
46
+ --tt-foreground: oklch(0.145 0 0);
47
+
48
+ --tt-card: oklch(1 0 0);
49
+ --tt-card-foreground: oklch(0.145 0 0);
50
+
51
+ --tt-popover: oklch(1 0 0);
52
+ --tt-popover-foreground: oklch(0.145 0 0);
53
+
54
+ /* ---- Intent ---- */
55
+ --tt-primary: oklch(0.205 0 0);
56
+ --tt-primary-foreground: oklch(0.985 0 0);
57
+
58
+ --tt-secondary: oklch(0.97 0 0);
59
+ --tt-secondary-foreground: oklch(0.205 0 0);
60
+
61
+ /* muted = a static surface that is deliberately quiet: table headers,
62
+ disabled fields, code chips. */
63
+ --tt-muted: oklch(0.97 0 0);
64
+
65
+ /* Was 0.556, which measured about 4.4:1 on the page grey. Most of the
66
+ text using it is 12px, so it needs the margin, not the minimum. */
67
+ --tt-muted-foreground: oklch(0.52 0 0);
68
+ /* Table headers, separate from muted so a colour theme can tint them
69
+ without tinting every muted surface. Muted text, as in shadcn. */
70
+ --tt-table-header: var(--tt-muted);
71
+ --tt-table-header-foreground: var(--tt-muted-foreground);
72
+
73
+ /* accent = the surface something takes ON INTERACTION: hover, the
74
+ highlighted option in a list, the active menu item. It used to be
75
+ identical to --tt-muted, so hovering a table row landed on exactly
76
+ the header's colour and the two stopped separating. */
77
+ --tt-accent: oklch(0.955 0 0);
78
+ --tt-accent-foreground: oklch(0.205 0 0);
79
+
80
+ --tt-destructive: oklch(0.51 0.18 25);
81
+ --tt-destructive-foreground: oklch(0.985 0 0);
82
+
83
+ /* The one accent. Indigo, at the same lightness as the status colours
84
+ so none of them shouts over the others. Text on it is white here and
85
+ near-black in dark mode, where the fill is light. */
86
+ --tt-brand: oklch(0.51 0.2 277);
87
+ --tt-brand-foreground: oklch(0.985 0 0);
88
+
89
+ /* ---- Lines & focus ---- */
90
+ --tt-border: oklch(0.922 0 0);
91
+
92
+ /* --tt-input is the BORDER of a field; --tt-field is its surface.
93
+ Without the second one, inputs inherited the page grey and vanished
94
+ when placed outside a card. */
95
+ --tt-input: oklch(0.922 0 0);
96
+ --tt-field: oklch(1 0 0);
97
+
98
+ /*
99
+ THE focus ring. One recipe, every component:
100
+
101
+ :focus-visible {
102
+ outline: var(--tt-ring-width) solid var(--tt-ring);
103
+ outline-offset: var(--tt-ring-offset);
104
+ }
105
+
106
+ outline, never box-shadow. An outline takes no space in layout, cannot
107
+ be clipped by an ancestor's overflow:hidden, and follows border-radius.
108
+ A box-shadow ring fails all three. Components used to split three ways
109
+ between an outline, a box-shadow ring at four different opacities, and
110
+ a ring drawn on some inner element, so focus changed appearance
111
+ depending on what you tabbed onto.
112
+
113
+ The colour is 3:1 against its surround, which WCAG requires of any
114
+ non-text UI element. The old 0.708 grey measured about 2.3:1 on white,
115
+ which is a focus indicator you cannot actually see.
116
+
117
+ ---------------------------------------------------------------------
118
+ BORDERED controls negate the offset instead:
119
+
120
+ .control:focus-within {
121
+ border-color: var(--tt-ring);
122
+ outline: var(--tt-ring-width) solid var(--tt-ring);
123
+ outline-offset: calc(var(--tt-ring-width) * -1);
124
+ }
125
+
126
+ A button has no border, so a ring sitting 2px outside it reads as one
127
+ indicator. A field already has a border, so the same ring outside is a
128
+ SECOND one with a gap between them, and the field looks like it grew on
129
+ focus. Negating the offset draws the ring over the border instead: one
130
+ crisp 2px edge, no halo, no apparent growth, and no layout shift.
131
+
132
+ Still an outline, not a box-shadow, for all the reasons above. The
133
+ indicator is not optional - a text field with no visible focus state is
134
+ unusable by keyboard - so the fix is to draw it better, never to remove
135
+ it.
136
+ */
137
+ /*
138
+ The ring is the brand colour: the one place the accent appears in
139
+ every component. It used to double as the HOVER border of fields and
140
+ cards too, which would have painted every hovered input indigo, so
141
+ hover now has its own neutral, --tt-border-strong (the old ring grey).
142
+
143
+ A subtree that sets its own --tt-brand must set --tt-ring: var(--tt-brand)
144
+ beside it. A custom property resolves its var() where it is DECLARED,
145
+ so the ring declared here keeps the root's brand otherwise.
146
+ */
147
+ --tt-ring: var(--tt-brand);
148
+ --tt-border-strong: oklch(0.55 0 0);
149
+ --tt-ring-width: 2px;
150
+ --tt-ring-offset: 2px;
151
+
152
+ /* ---- Disabled ----
153
+ How far a disabled control fades. Was 0.55 on some controls, 0.5 on
154
+ others and 0.4 on one, so two disabled things side by side did not
155
+ match. Fields do NOT use this: a disabled field keeps full opacity and
156
+ takes the --tt-muted surface instead, so its text stays readable. */
157
+ --tt-opacity-disabled: 0.5;
158
+
159
+ /* ---- Scrim behind overlays ---- */
160
+ --tt-overlay: oklch(0.145 0 0 / 42%);
161
+
162
+ /* ---- Sidebar ---- */
163
+ --tt-sidebar: oklch(1 0 0);
164
+ --tt-sidebar-foreground: oklch(0.145 0 0);
165
+ --tt-sidebar-primary: oklch(0.205 0 0);
166
+ --tt-sidebar-primary-foreground: oklch(0.985 0 0);
167
+ --tt-sidebar-accent: oklch(0.955 0 0);
168
+ --tt-sidebar-accent-foreground: oklch(0.205 0 0);
169
+ --tt-sidebar-border: oklch(0.922 0 0);
170
+ --tt-sidebar-ring: var(--tt-brand);
171
+
172
+ /* ---- Charts ---- */
173
+ --tt-chart-1: oklch(0.87 0 0);
174
+ --tt-chart-2: oklch(0.556 0 0);
175
+ --tt-chart-3: oklch(0.439 0 0);
176
+ --tt-chart-4: oklch(0.371 0 0);
177
+ --tt-chart-5: oklch(0.269 0 0);
178
+
179
+ /* ---- Radius ----
180
+
181
+ On the unit rhythm, same as spacing. That is not tidiness, it is what
182
+ makes the concentric rule below possible: a radius and a padding can
183
+ only subtract cleanly from each other if they step by the same amount.
184
+ The old scale went 4, 6, 8, 10 while spacing went 4, 8, 12, 16, so the
185
+ two could never line up.
186
+
187
+ Roughly: sm for chips and tags, md for controls, lg for cards, xl for
188
+ panels, 2xl for bento tiles and anything full-bleed. */
189
+
190
+ /* ---- Three shape types, and only three ----
191
+
192
+ Every corner in the system is one of these. Naming the KINDS rather
193
+ than only the sizes is what stops a layout drifting: two shapes are
194
+ allowed to differ because they are different types, never because
195
+ nobody decided.
196
+
197
+ FIXED a constant radius off the scale below. The default.
198
+ Picked from the size ramp: sm for chips, md for
199
+ controls, lg for cards, xl for panels, 2xl for tiles.
200
+
201
+ CAPSULE radius = half the height, which --tt-radius-full gets
202
+ by overshooting. For pills and anything whose shape
203
+ should track its own height: badges, tags, avatars,
204
+ progress tracks.
205
+
206
+ CONCENTRIC radius = parent radius - padding, published by the
207
+ container as --tt-radius-nested and read by the child.
208
+ For a child that FILLS its parent's padding box.
209
+
210
+ A concentric shape ALWAYS carries a fixed fallback:
211
+
212
+ border-radius: var(--tt-radius-nested, var(--tt-radius-lg));
213
+
214
+ so the same component is correct nested and correct standing alone.
215
+ Writing it without the fallback is a bug, not a shortcut: when no
216
+ container defines the property the whole declaration is invalid and
217
+ the corner silently goes square. That is not hypothetical, it is how
218
+ tt-product's row lost its left corners.
219
+
220
+ See the Concentric corners note below for the full reasoning. */
221
+
222
+ --tt-radius-sm: calc(var(--tt-unit) * 1); /* 4px */
223
+ --tt-radius-md: calc(var(--tt-unit) * 2); /* 8px */
224
+ --tt-radius-lg: calc(var(--tt-unit) * 3); /* 12px */
225
+ --tt-radius-xl: calc(var(--tt-unit) * 4); /* 16px */
226
+ --tt-radius-2xl: calc(var(--tt-unit) * 6); /* 24px */
227
+ --tt-radius-full: 9999px;
228
+
229
+ /* The unqualified default, for anything that has not picked a rung. */
230
+ --tt-radius: var(--tt-radius-md);
231
+
232
+ /* ---- Concentric corners ----
233
+
234
+ A child inset inside a rounded parent needs a SMALLER radius, or the
235
+ two curves are not concentric and the corner reads as two unrelated
236
+ arcs stacked. The rule is inner = outer - inset.
237
+
238
+ A container publishes this for its children:
239
+
240
+ .card {
241
+ padding: var(--_pad);
242
+ border-radius: var(--tt-radius-lg);
243
+ --tt-radius-nested: max(
244
+ var(--tt-radius-sm),
245
+ calc(var(--tt-radius-lg) - var(--_pad))
246
+ );
247
+ }
248
+
249
+ and a child reads `var(--tt-radius-nested, var(--tt-radius))`, so it
250
+ is correct inside a container and unchanged outside one. Custom
251
+ properties inherit through shadow roots, so this crosses component
252
+ boundaries with no props and no coordination.
253
+
254
+ IMPORTANT: this is for a child that FILLS the padding box, so its
255
+ corner really does sit one inset in from the parent's - a bento tile's
256
+ content, a full-width image, a code block, a nested panel. It is not
257
+ for every descendant. A button floating in the middle of a card is
258
+ nowhere near the corner, so it keeps its own radius from its size ramp;
259
+ giving it the nested value would make it tighter for no reason.
260
+
261
+ The floor matters. When padding is larger than the radius the
262
+ subtraction goes negative, which is an invalid radius, and the
263
+ relationship stops meaning anything visually anyway. Floor it at sm.
264
+
265
+ Note this only does visible work when the parent radius EXCEEDS its
266
+ padding, which is the large-radius case: a 24px bento tile with 16px
267
+ padding gives its children 8px. A tight 8px card with 12px padding
268
+ just floors, which is the correct answer for it.
269
+
270
+ There is deliberately NO default value here. A container defines it or
271
+ nothing does, which is what lets a consumer write
272
+
273
+ border-radius: var(--tt-radius-nested, var(--tt-radius-2xl));
274
+
275
+ and get the concentric value when nested and its own full radius when
276
+ standing alone. A global default would always resolve, the fallback
277
+ would never fire, and a top-level tile would silently take the tight
278
+ nested corner it was never inside anything to earn. */
279
+
280
+ /* ---- Typography ---- */
281
+ --tt-font-sans:
282
+ "Inter",
283
+ ui-sans-serif,
284
+ system-ui,
285
+ -apple-system,
286
+ BlinkMacSystemFont,
287
+ "Segoe UI",
288
+ sans-serif;
289
+
290
+ /* Page-level headings: tt-text h1 to h3, tt-page and tt-page-header.
291
+ The sans by default, so nothing changes until a theme gives
292
+ headings a face of their own. Like the ring, it resolves where it
293
+ is declared: a subtree that sets --tt-font-sans sets this beside it. */
294
+ --tt-font-display: var(--tt-font-sans);
295
+
296
+ --tt-font-mono:
297
+ "JetBrains Mono",
298
+ "Fira Code",
299
+ ui-monospace,
300
+ "SFMono-Regular",
301
+ Consolas,
302
+ "Liberation Mono",
303
+ monospace;
304
+
305
+ --tt-text-xs: 0.75rem;
306
+ --tt-text-sm: 0.875rem;
307
+ --tt-text-md: 1rem;
308
+ --tt-text-lg: 1.125rem;
309
+ --tt-text-xl: 1.25rem;
310
+ --tt-text-2xl: 1.5rem;
311
+ --tt-text-3xl: 1.875rem;
312
+
313
+ /* Line height was hardcoded per component and had drifted to 1.4, 1.45,
314
+ 1.5 and 1.6 for the same kind of text. Four named steps instead:
315
+ tight headings and numbers, where the size does the spacing
316
+ snug labels and single-line UI text
317
+ normal default body copy
318
+ relaxed long prose, docs paragraphs */
319
+ --tt-leading-tight: 1.2;
320
+ --tt-leading-snug: 1.4;
321
+ --tt-leading-normal: 1.5;
322
+ --tt-leading-relaxed: 1.65;
323
+
324
+ /* Weight had no tokens and drifted the same way: 550, 600, 650 and 700
325
+ for the same kind of title. Four roles:
326
+ regular body copy, and the quiet half of a two-part value
327
+ medium labels, menu items, links: UI text that must not shout
328
+ semibold component titles and anything selected or current
329
+ bold page-level display text: page headings, big numbers
330
+ tt-button keeps its own 550, an optical choice between medium and
331
+ semibold that no other component shares. */
332
+ --tt-weight-regular: 400;
333
+ --tt-weight-medium: 500;
334
+ --tt-weight-semibold: 600;
335
+ --tt-weight-bold: 650;
336
+
337
+ /* ---- The unit ----
338
+
339
+ Every measurement in the system is a multiple of this. Spacing, radius
340
+ and control heights all derive from it below, which means density is
341
+ ONE knob: a spacious theme sets --tt-unit and the whole layout breathes
342
+ without a single component changing.
343
+
344
+ It is in rem rather than px on purpose, so the whole system still
345
+ scales with a user's root font size.
346
+
347
+ The scale numbering already was the multiplier - --tt-space-4 was 1rem,
348
+ which is 4 units - so switching to calc() changed no value anywhere.
349
+ That was luck worth keeping: never add a step that breaks it. */
350
+
351
+ --tt-unit: 0.25rem; /* 4px at the default root size */
352
+
353
+ /* ---- Spacing ---- */
354
+ --tt-space-1: calc(var(--tt-unit) * 1);
355
+ --tt-space-2: calc(var(--tt-unit) * 2);
356
+ --tt-space-3: calc(var(--tt-unit) * 3);
357
+ --tt-space-4: calc(var(--tt-unit) * 4);
358
+ --tt-space-5: calc(var(--tt-unit) * 5);
359
+ --tt-space-6: calc(var(--tt-unit) * 6);
360
+ --tt-space-8: calc(var(--tt-unit) * 8);
361
+ --tt-space-10: calc(var(--tt-unit) * 10);
362
+ --tt-space-12: calc(var(--tt-unit) * 12);
363
+
364
+ /* ---- Control heights ----
365
+ On the same rhythm, so a control sits on the grid its padding uses. */
366
+ --tt-control-sm: calc(var(--tt-unit) * 8); /* 32px */
367
+ --tt-control-md: calc(var(--tt-unit) * 9); /* 36px */
368
+ --tt-control-lg: calc(var(--tt-unit) * 10); /* 40px */
369
+
370
+ /* ---- Elevation ----
371
+
372
+ Height above the page, as four rungs. Each rung pairs a SURFACE with
373
+ a SHADOW, and they must move together: a shadow with no surface
374
+ change reads as a smudge, and a surface change with no shadow reads
375
+ as a colour swatch.
376
+
377
+ rung surface shadow used by
378
+ 0 --tt-background none the page itself
379
+ 1 --tt-card --tt-elevation-1 cards, stat tiles
380
+ 2 --tt-popover --tt-elevation-2 dropdowns, tooltips, toasts
381
+ 3 --tt-popover --tt-elevation-3 modals, drawers
382
+
383
+ Rungs 2 and 3 share a surface on purpose: once something is floating
384
+ over a scrim, the shadow alone carries the difference.
385
+
386
+ Reach for a rung, not a shadow. "Raise this to 2" is one decision;
387
+ picking a shadow by hand is how tt-card, tt-tooltip and tt-search all
388
+ ended up on --tt-shadow-md for three unrelated reasons. */
389
+
390
+ --tt-elevation-1: var(--tt-shadow-sm);
391
+ --tt-elevation-2: var(--tt-shadow-lg);
392
+ --tt-elevation-3: var(--tt-shadow-overlay);
393
+
394
+ /* ---- Shadows ----
395
+ The raw values the rungs above are built from. Prefer a rung. */
396
+ --tt-shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
397
+
398
+ --tt-shadow-md:
399
+ 0 4px 6px -1px rgb(0 0 0 / 0.08),
400
+ 0 2px 4px -2px rgb(0 0 0 / 0.08);
401
+
402
+ --tt-shadow-lg:
403
+ 0 10px 15px -3px rgb(0 0 0 / 0.08),
404
+ 0 4px 6px -4px rgb(0 0 0 / 0.08);
405
+
406
+ --tt-shadow-overlay:
407
+ 0 12px 32px rgb(0 0 0 / 0.18),
408
+ 0 2px 8px rgb(0 0 0 / 0.1);
409
+
410
+ /* The edge of something pinned while content scrolls UNDER it, such as
411
+ a sticky table column. Cast sideways, which no rung above does. */
412
+ --tt-shadow-pinned: 2px 0 4px -2px rgb(0 0 0 / 0.08);
413
+
414
+ /* ---- Motion ----
415
+
416
+ Two groups, and the split matters.
417
+
418
+ The durations and easings in the first group are the CHARACTER knob.
419
+ Every transition and every one-shot entrance in the library reads one
420
+ of them; nothing hardcodes a duration. A theme overrides these four
421
+ values and the whole system changes feel at once - calmer, snappier,
422
+ springier - with no component touched.
423
+
424
+ The looping group is different. A spinner is not character, it is
425
+ feedback, and it survives the reduced-motion block at the bottom of
426
+ this file: a frozen spinner reads as "hung", not as "working".
427
+
428
+ Durations are deliberately short. Motion in an interface is feedback,
429
+ not decoration, and anything past about 400ms starts being something
430
+ the user waits for rather than something that confirms what they did. */
431
+
432
+ --tt-duration-fast: 100ms; /* hover, focus, colour */
433
+ --tt-duration-normal: 150ms; /* the default for anything interactive */
434
+ --tt-duration-slow: 250ms; /* disclosure, popovers, entrances */
435
+ --tt-duration-slower: 400ms; /* full surfaces: drawers, sheets, modals */
436
+
437
+ /* Looping feedback. Exempt from the reduced-motion block below. */
438
+ --tt-duration-spin: 0.6s;
439
+
440
+ /* Easings.
441
+
442
+ in = leaving. Accelerates away, because the user has stopped caring
443
+ about a thing that is on its way out.
444
+ out = entering. Decelerates in, because the eye tracks it and wants
445
+ to see where it lands.
446
+ The plain --tt-ease is symmetric, for anything that changes in place
447
+ rather than arriving or leaving: a colour, a background, a border. */
448
+
449
+ --tt-ease: cubic-bezier(0.4, 0, 0.2, 1);
450
+ --tt-ease-in: cubic-bezier(0.7, 0, 0.84, 0);
451
+ --tt-ease-out: cubic-bezier(0.16, 1, 0.3, 1);
452
+
453
+ /* A damped spring, sampled from 1 - e^(-10t)cos(10t).
454
+
455
+ It overshoots by about 6% around a quarter of the way through, then
456
+ settles. That single soft overshoot is what makes a control feel
457
+ physical. Anything bouncier reads as a toy, which is why this is one
458
+ overshoot and not three.
459
+
460
+ Use it for things that should feel picked up and put down: a toggle
461
+ landing, a panel snapping open, a tile lifting. Never for opacity,
462
+ where overshoot is invisible and only costs time. */
463
+
464
+ --tt-ease-spring: linear(
465
+ 0,
466
+ 0.468 5%,
467
+ 0.801 10%,
468
+ 0.984 15%,
469
+ 1.056 20%,
470
+ 1.066 25%,
471
+ 1.049 30%,
472
+ 1.028 35%,
473
+ 1.012 40%,
474
+ 0.998 50%,
475
+ 0.998 60%,
476
+ 0.999 70%,
477
+ 1
478
+ );
479
+
480
+ /* ---- Z-index ---- */
481
+ --tt-z-dropdown: 1000;
482
+ --tt-z-sticky: 1100;
483
+ --tt-z-overlay: 1200;
484
+ --tt-z-drawer: 1250;
485
+ --tt-z-modal: 1300;
486
+ --tt-z-toast: 1400;
487
+
488
+ /* ---- Code surface ----
489
+ Intentionally theme-independent: code blocks stay dark in both
490
+ themes so a single syntax palette stays readable. Override these
491
+ if you need a light code surface. */
492
+ --tt-code-background: #0d1117;
493
+ --tt-code-surface: #161b22;
494
+ --tt-code-border: #30363d;
495
+ --tt-code-foreground: #e6edf3;
496
+ --tt-code-muted: #8b949e;
497
+
498
+ --tt-code-comment: #8b949e;
499
+ --tt-code-punctuation: #c9d1d9;
500
+ --tt-code-constant: #79c0ff;
501
+ --tt-code-string: #a5d6ff;
502
+ --tt-code-operator: #d2a8ff;
503
+ --tt-code-keyword: #ff7b72;
504
+ --tt-code-function: #d2a8ff;
505
+ --tt-code-regex: #ffa657;
506
+
507
+ /* :root. See the header for how these were chosen.
508
+
509
+ Green and amber sit a little darker than the brand and red. At the
510
+ same oklch lightness they carry more luminance, which is what WCAG
511
+ measures, and their badge text fell to 4.2:1 on the page grey and
512
+ on --tt-muted. Matched by contrast instead, with chroma pulled in to
513
+ stay inside sRGB at the lower lightness. */
514
+ --tt-success: oklch(0.48 0.115 155);
515
+ --tt-warning: oklch(0.5 0.12 55);
516
+
517
+ /* Text on a success or warning FILL, so every colour that can be an
518
+ accent has a readable pair, like brand and destructive: a success
519
+ button, a warning switch. White here (5.9:1 and 6:1), near-black
520
+ in dark mode, where the fills are light. */
521
+ --tt-success-foreground: oklch(0.985 0 0);
522
+ --tt-warning-foreground: oklch(0.985 0 0);
523
+
524
+ /* ---- Subtle tints ----
525
+ A quiet surface in each accent's hue: a callout, a highlighted card,
526
+ a neutral badge given an accent. One lightness and chroma for all
527
+ four, so none is louder than the others. 0.018 is as much chroma as
528
+ indigo holds inside sRGB this light; more is clipped, like the old
529
+ warning was.
530
+
531
+ Text on them is --tt-foreground. Muted text and every accent colour
532
+ still pass 4.5:1 as text on them, in both themes (4.8:1 at worst),
533
+ and so does a status badge sitting on one.
534
+
535
+ Like the ring, they do not follow a re-pointed --tt-brand on their
536
+ own: a custom property resolves its var() where it is declared, so a
537
+ subtree that sets --tt-brand sets --tt-brand-subtle beside it. */
538
+ --tt-brand-subtle: oklch(0.96 0.018 277);
539
+ --tt-success-subtle: oklch(0.96 0.018 155);
540
+ --tt-warning-subtle: oklch(0.96 0.018 55);
541
+ --tt-destructive-subtle: oklch(0.96 0.018 25);
542
+
543
+ /* ---- Content hues ----
544
+ Colour an app picks for content, not for meaning: a tag, a callout,
545
+ a highlighted panel. No component reads them; the builder offers
546
+ them as accents. Each is a kit like the status colours (fill, text
547
+ on it, quiet tint), built by the same rules: inside sRGB, 4.5:1 as
548
+ text on every surface and every tint, in both themes. Hues sit
549
+ between the ones already spoken for, so none reads as a status. */
550
+ --tt-teal: oklch(0.49 0.08 185);
551
+ --tt-teal-foreground: oklch(0.985 0 0);
552
+ --tt-teal-subtle: oklch(0.96 0.018 185);
553
+
554
+ --tt-blue: oklch(0.5 0.135 250);
555
+ --tt-blue-foreground: oklch(0.985 0 0);
556
+ --tt-blue-subtle: oklch(0.96 0.018 250);
557
+
558
+ --tt-purple: oklch(0.51 0.2 305);
559
+ --tt-purple-foreground: oklch(0.985 0 0);
560
+ --tt-purple-subtle: oklch(0.96 0.018 305);
561
+
562
+ --tt-pink: oklch(0.51 0.19 350);
563
+ --tt-pink-foreground: oklch(0.985 0 0);
564
+ --tt-pink-subtle: oklch(0.96 0.018 350);
565
+
566
+ /* ---- Constants ----
567
+ The same in every theme and mode, on purpose: text over a photo or
568
+ a fill that does not change with the theme. Everywhere else, a
569
+ -foreground pair is the right answer, because it flips with the
570
+ surface. Never redeclared below. */
571
+ --tt-white: oklch(1 0 0);
572
+ }
573
+
574
+ /* =========================================
575
+ Baseline
576
+
577
+ The only rule in this file that is not a variable, and it is here
578
+ for one reason: -webkit-tap-highlight-color is an INHERITED
579
+ property, exactly like the custom properties above. Inherited
580
+ properties cross shadow boundaries, so declaring it once on :root
581
+ reaches inside all 58 components. An ordinary CSS rule could not.
582
+
583
+ What it fixes: on Android Chrome and iOS Safari, tapping anything
584
+ clickable flashes a translucent blue-grey box over it. It exists so
585
+ a plain HTML link gives some feedback on touch, but every control
586
+ here already has its own hover, active and focus-visible states, so
587
+ the flash is a second uninvited highlight on top of them. Removing
588
+ it costs nothing: the control's own states still show the tap.
589
+
590
+ user-select is deliberately NOT set here. It is inherited too, so
591
+ putting it on :root would make body copy unselectable, and not
592
+ being able to copy text off a page is a real loss. It belongs on
593
+ individual controls instead.
594
+ ========================================= */
595
+
596
+ :root {
597
+ -webkit-tap-highlight-color: transparent;
598
+
599
+ /* ---- The canvas, and the one thing that must never disagree ----
600
+
601
+ color-scheme decides what the BROWSER paints: the canvas behind the
602
+ page, form controls, scrollbars, the caret. The tokens decide what WE
603
+ paint. When those two answers differ, the browser's dark canvas shows
604
+ around the edges of a light app, which reads as a black border around
605
+ everything.
606
+
607
+ That happened twice. First when nothing set the .dark class at all
608
+ and the page asked the browser to follow the OS. Then again when a
609
+ theme TOGGLE was added: the class started following the user's saved
610
+ choice while the canvas was still following the OS, so choosing light
611
+ on a machine set to dark brought the black border straight back.
612
+
613
+ Binding it to the same class that drives the tokens makes the
614
+ disagreement impossible rather than merely fixed. One class, one
615
+ owner, both answers.
616
+
617
+ Do NOT declare a two-value color-scheme in a page that has a theme
618
+ toggle. That hands the canvas back to the OS and reopens this. */
619
+ color-scheme: light;
620
+ }
621
+
622
+ /* =========================================
623
+ Dark theme
624
+ ========================================= */
625
+
626
+ .dark {
627
+ /* Pairs with color-scheme on :root above. See the note there: this is
628
+ what stops the browser's canvas and our tokens ever disagreeing. */
629
+ color-scheme: dark;
630
+
631
+ --tt-background: oklch(0.145 0 0);
632
+ --tt-foreground: oklch(0.985 0 0);
633
+
634
+ --tt-card: oklch(0.205 0 0);
635
+ --tt-card-foreground: oklch(0.985 0 0);
636
+
637
+ --tt-popover: oklch(0.205 0 0);
638
+ --tt-popover-foreground: oklch(0.985 0 0);
639
+
640
+ --tt-primary: oklch(0.922 0 0);
641
+ --tt-primary-foreground: oklch(0.205 0 0);
642
+
643
+ --tt-secondary: oklch(0.269 0 0);
644
+ --tt-secondary-foreground: oklch(0.985 0 0);
645
+
646
+ --tt-muted: oklch(0.269 0 0);
647
+ --tt-muted-foreground: oklch(0.72 0 0);
648
+ /* Table headers, separate from muted so a colour theme can tint them
649
+ without tinting every muted surface. Muted text, as in shadcn. */
650
+ --tt-table-header: var(--tt-muted);
651
+ --tt-table-header-foreground: var(--tt-muted-foreground);
652
+
653
+ /* Lifts on interaction, same as light mode. Dark surfaces get lighter
654
+ on hover rather than darker: the eye reads "closer to the light". */
655
+ --tt-accent: oklch(0.3 0 0);
656
+ --tt-accent-foreground: oklch(0.985 0 0);
657
+
658
+ /* Light fills in dark mode, so text on them is dark. White on this red
659
+ was 2.4:1: every danger button failed contrast in dark mode. */
660
+ --tt-destructive: oklch(0.73 0.15 24);
661
+ --tt-destructive-foreground: oklch(0.145 0 0);
662
+
663
+ --tt-brand: oklch(0.74 0.13 277);
664
+ --tt-brand-foreground: oklch(0.145 0 0);
665
+
666
+ --tt-border: oklch(1 0 0 / 10%);
667
+ --tt-input: oklch(1 0 0 / 15%);
668
+ --tt-field: oklch(0.205 0 0);
669
+ --tt-ring: var(--tt-brand);
670
+ --tt-border-strong: oklch(0.68 0 0);
671
+
672
+ --tt-overlay: oklch(0 0 0 / 62%);
673
+
674
+ --tt-sidebar: oklch(0.205 0 0);
675
+ --tt-sidebar-foreground: oklch(0.985 0 0);
676
+
677
+ /* Was a saturated blue while light mode was neutral black, so the
678
+ active sidebar item changed brand between themes. A token pair
679
+ should mean the same thing in both. */
680
+ --tt-sidebar-primary: oklch(0.922 0 0);
681
+ --tt-sidebar-primary-foreground: oklch(0.205 0 0);
682
+
683
+ --tt-sidebar-accent: oklch(0.3 0 0);
684
+ --tt-sidebar-accent-foreground: oklch(0.985 0 0);
685
+ --tt-sidebar-border: oklch(1 0 0 / 10%);
686
+ --tt-sidebar-ring: var(--tt-brand);
687
+
688
+ --tt-shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.3);
689
+
690
+ --tt-shadow-md:
691
+ 0 4px 6px -1px rgb(0 0 0 / 0.4),
692
+ 0 2px 4px -2px rgb(0 0 0 / 0.4);
693
+
694
+ --tt-shadow-lg:
695
+ 0 10px 15px -3px rgb(0 0 0 / 0.45),
696
+ 0 4px 6px -4px rgb(0 0 0 / 0.45);
697
+
698
+ --tt-shadow-overlay:
699
+ 0 12px 32px rgb(0 0 0 / 0.5),
700
+ 0 2px 8px rgb(0 0 0 / 0.35);
701
+
702
+ --tt-shadow-pinned: 2px 0 4px -2px rgb(0 0 0 / 0.4);
703
+
704
+ /* .dark - lighter, so they stay readable on a dark surface */
705
+ --tt-success: oklch(0.77 0.13 155);
706
+ --tt-warning: oklch(0.8 0.13 70);
707
+
708
+ /* Light fills, so dark text, the same as brand and destructive. */
709
+ --tt-success-foreground: oklch(0.145 0 0);
710
+ --tt-warning-foreground: oklch(0.145 0 0);
711
+
712
+ /* Lifted above the card to the same lightness as --tt-accent, in the
713
+ same hues as the fills above. A tint is also a hover and selected
714
+ surface (a builder colour theme points --tt-accent at one), so it
715
+ has to lift the way the grey one does. At 0.26 it sat below
716
+ --tt-secondary, and a hovered secondary button went darker. */
717
+ --tt-brand-subtle: oklch(0.3 0.045 277);
718
+ --tt-success-subtle: oklch(0.3 0.045 155);
719
+ --tt-warning-subtle: oklch(0.3 0.045 70);
720
+ --tt-destructive-subtle: oklch(0.3 0.045 24);
721
+
722
+ --tt-teal: oklch(0.76 0.12 185);
723
+ --tt-teal-foreground: oklch(0.145 0 0);
724
+ --tt-teal-subtle: oklch(0.3 0.045 185);
725
+
726
+ --tt-blue: oklch(0.75 0.12 250);
727
+ --tt-blue-foreground: oklch(0.145 0 0);
728
+ --tt-blue-subtle: oklch(0.3 0.045 250);
729
+
730
+ --tt-purple: oklch(0.75 0.13 305);
731
+ --tt-purple-foreground: oklch(0.145 0 0);
732
+ --tt-purple-subtle: oklch(0.3 0.045 305);
733
+
734
+ --tt-pink: oklch(0.76 0.13 350);
735
+ --tt-pink-foreground: oklch(0.145 0 0);
736
+ --tt-pink-subtle: oklch(0.3 0.045 350);
737
+ }
738
+
739
+ /* =========================================
740
+ Density
741
+
742
+ One attribute on <html> remaps the spacing and control-height tokens,
743
+ so every component tightens or loosens together:
744
+
745
+ <html data-density="compact">
746
+
747
+ Data-heavy screens want compact; a marketing page or a settings form
748
+ reads better spacious. This works for free because nothing hardcodes a
749
+ pixel value: components only ever read --tt-space-* and --tt-control-*.
750
+ ========================================= */
751
+
752
+ [data-density="compact"] {
753
+ --tt-space-1: 0.125rem;
754
+ --tt-space-2: 0.375rem;
755
+ --tt-space-3: 0.5rem;
756
+ --tt-space-4: 0.75rem;
757
+ --tt-space-5: 0.875rem;
758
+ --tt-space-6: 1rem;
759
+ --tt-space-8: 1.25rem;
760
+ --tt-space-10: 1.5rem;
761
+ --tt-space-12: 2rem;
762
+
763
+ --tt-control-sm: 1.75rem;
764
+ --tt-control-md: 2rem;
765
+ --tt-control-lg: 2.25rem;
766
+ }
767
+
768
+ [data-density="spacious"] {
769
+ --tt-space-1: 0.375rem;
770
+ --tt-space-2: 0.625rem;
771
+ --tt-space-3: 1rem;
772
+ --tt-space-4: 1.25rem;
773
+ --tt-space-5: 1.5rem;
774
+ --tt-space-6: 2rem;
775
+ --tt-space-8: 2.5rem;
776
+ --tt-space-10: 3rem;
777
+ --tt-space-12: 4rem;
778
+
779
+ --tt-control-sm: 2.25rem;
780
+ --tt-control-md: 2.75rem;
781
+ --tt-control-lg: 3rem;
782
+ }
783
+
784
+ /* =========================================
785
+ Themes
786
+
787
+ One attribute changes the personality of every component at once:
788
+
789
+ <html data-theme="soft"> soft, sharp, editorial, bold
790
+
791
+ or on any element, for a subtree. A theme is a block of overrides of
792
+ tokens that already exist, exactly like .dark and density, so no
793
+ component knows themes exist.
794
+
795
+ Four rules keep them composable, and styles.test.ts checks each one:
796
+
797
+ 1. A theme never touches spacing, control heights or durations. Those
798
+ belong to density, touch and reduced motion, and a theme that set
799
+ them would quietly win over the user's own setting.
800
+
801
+ 2. Every token defined as var(--tt-x) on :root is redeclared beside
802
+ --tt-x. A custom property resolves its var() where it is DECLARED,
803
+ so on a subtree, --tt-radius would keep the root's medium radius
804
+ and --tt-ring the root's brand.
805
+
806
+ 3. Anything .dark changes, a theme changes again under .dark: the
807
+ second block per theme. Both selectors are needed, one for a theme
808
+ inside a dark page and one for <html class="dark" data-theme>.
809
+
810
+ 4. Brand pairs keep AA in both modes: text on the brand, the brand as
811
+ text on every surface and tint, the ring at 3:1.
812
+
813
+ Themes compose, like density does: a theme on a subtree changes what
814
+ it declares and inherits the rest from the page. "neutral" is the one
815
+ that declares everything any theme touches, at the :root values, so
816
+ <div data-theme="neutral"> is a clean slate inside a themed page.
817
+ ========================================= */
818
+
819
+ [data-theme="neutral"] {
820
+ --tt-radius-sm: calc(var(--tt-unit) * 1);
821
+ --tt-radius-md: calc(var(--tt-unit) * 2);
822
+ --tt-radius-lg: calc(var(--tt-unit) * 3);
823
+ --tt-radius-xl: calc(var(--tt-unit) * 4);
824
+ --tt-radius-2xl: calc(var(--tt-unit) * 6);
825
+ --tt-radius: var(--tt-radius-md);
826
+
827
+ --tt-shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
828
+ --tt-shadow-md:
829
+ 0 4px 6px -1px rgb(0 0 0 / 0.08),
830
+ 0 2px 4px -2px rgb(0 0 0 / 0.08);
831
+ --tt-shadow-lg:
832
+ 0 10px 15px -3px rgb(0 0 0 / 0.08),
833
+ 0 4px 6px -4px rgb(0 0 0 / 0.08);
834
+ --tt-shadow-overlay:
835
+ 0 12px 32px rgb(0 0 0 / 0.18),
836
+ 0 2px 8px rgb(0 0 0 / 0.1);
837
+ --tt-shadow-pinned: 2px 0 4px -2px rgb(0 0 0 / 0.08);
838
+ --tt-elevation-1: var(--tt-shadow-sm);
839
+ --tt-elevation-2: var(--tt-shadow-lg);
840
+ --tt-elevation-3: var(--tt-shadow-overlay);
841
+
842
+ --tt-font-display: var(--tt-font-sans);
843
+ --tt-weight-medium: 500;
844
+ --tt-weight-semibold: 600;
845
+ --tt-weight-bold: 650;
846
+ --tt-leading-snug: 1.4;
847
+ --tt-leading-normal: 1.5;
848
+ --tt-leading-relaxed: 1.65;
849
+ --tt-text-2xl: 1.5rem;
850
+ --tt-text-3xl: 1.875rem;
851
+
852
+ --tt-brand: oklch(0.51 0.2 277);
853
+ --tt-brand-foreground: oklch(0.985 0 0);
854
+ --tt-brand-subtle: oklch(0.96 0.018 277);
855
+ --tt-ring: var(--tt-brand);
856
+ --tt-sidebar-ring: var(--tt-brand);
857
+ --tt-primary: oklch(0.205 0 0);
858
+ --tt-primary-foreground: oklch(0.985 0 0);
859
+ --tt-sidebar-primary: oklch(0.205 0 0);
860
+ --tt-sidebar-primary-foreground: oklch(0.985 0 0);
861
+
862
+ --tt-border: oklch(0.922 0 0);
863
+ --tt-input: oklch(0.922 0 0);
864
+ --tt-border-strong: oklch(0.55 0 0);
865
+ --tt-sidebar-border: oklch(0.922 0 0);
866
+ }
867
+
868
+ .dark [data-theme="neutral"],
869
+ .dark[data-theme="neutral"] {
870
+ --tt-shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.3);
871
+ --tt-shadow-md:
872
+ 0 4px 6px -1px rgb(0 0 0 / 0.4),
873
+ 0 2px 4px -2px rgb(0 0 0 / 0.4);
874
+ --tt-shadow-lg:
875
+ 0 10px 15px -3px rgb(0 0 0 / 0.45),
876
+ 0 4px 6px -4px rgb(0 0 0 / 0.45);
877
+ --tt-shadow-overlay:
878
+ 0 12px 32px rgb(0 0 0 / 0.5),
879
+ 0 2px 8px rgb(0 0 0 / 0.35);
880
+ --tt-shadow-pinned: 2px 0 4px -2px rgb(0 0 0 / 0.4);
881
+
882
+ --tt-brand: oklch(0.74 0.13 277);
883
+ --tt-brand-foreground: oklch(0.145 0 0);
884
+ --tt-brand-subtle: oklch(0.3 0.045 277);
885
+ --tt-primary: oklch(0.922 0 0);
886
+ --tt-primary-foreground: oklch(0.205 0 0);
887
+ --tt-sidebar-primary: oklch(0.922 0 0);
888
+ --tt-sidebar-primary-foreground: oklch(0.205 0 0);
889
+
890
+ --tt-border: oklch(1 0 0 / 10%);
891
+ --tt-input: oklch(1 0 0 / 15%);
892
+ --tt-border-strong: oklch(0.68 0 0);
893
+ --tt-sidebar-border: oklch(1 0 0 / 10%);
894
+ }
895
+
896
+ /* ---- Soft: rounder corners, diffuse shadows, lighter type ----
897
+ Every rung of the radius scale steps up, still on the unit. The small
898
+ one only by half a unit: it rounds an 18px checkbox, and at 8px the
899
+ box reads as a circle, which is a radio button. Shadows spread wider
900
+ at lower opacity, so a card floats instead of sitting on an edge. */
901
+
902
+ [data-theme="soft"] {
903
+ --tt-radius-sm: calc(var(--tt-unit) * 1.5);
904
+ --tt-radius-md: calc(var(--tt-unit) * 3);
905
+ --tt-radius-lg: calc(var(--tt-unit) * 5);
906
+ --tt-radius-xl: calc(var(--tt-unit) * 6);
907
+ --tt-radius-2xl: calc(var(--tt-unit) * 8);
908
+ --tt-radius: var(--tt-radius-md);
909
+
910
+ --tt-shadow-sm:
911
+ 0 1px 3px 0 rgb(0 0 0 / 0.04),
912
+ 0 2px 8px -2px rgb(0 0 0 / 0.05);
913
+ --tt-shadow-md:
914
+ 0 4px 12px -2px rgb(0 0 0 / 0.06),
915
+ 0 2px 4px -2px rgb(0 0 0 / 0.04);
916
+ --tt-shadow-lg:
917
+ 0 12px 28px -6px rgb(0 0 0 / 0.1),
918
+ 0 4px 10px -4px rgb(0 0 0 / 0.05);
919
+ --tt-shadow-overlay:
920
+ 0 24px 48px -12px rgb(0 0 0 / 0.22),
921
+ 0 8px 16px -8px rgb(0 0 0 / 0.08);
922
+ --tt-shadow-pinned: 4px 0 8px -4px rgb(0 0 0 / 0.08);
923
+ --tt-elevation-1: var(--tt-shadow-sm);
924
+ --tt-elevation-2: var(--tt-shadow-lg);
925
+ --tt-elevation-3: var(--tt-shadow-overlay);
926
+
927
+ --tt-weight-semibold: 560;
928
+ --tt-weight-bold: 600;
929
+ }
930
+
931
+ .dark [data-theme="soft"],
932
+ .dark[data-theme="soft"] {
933
+ --tt-shadow-sm:
934
+ 0 1px 3px 0 rgb(0 0 0 / 0.3),
935
+ 0 2px 8px -2px rgb(0 0 0 / 0.3);
936
+ --tt-shadow-md:
937
+ 0 4px 12px -2px rgb(0 0 0 / 0.35),
938
+ 0 2px 4px -2px rgb(0 0 0 / 0.3);
939
+ --tt-shadow-lg:
940
+ 0 12px 28px -6px rgb(0 0 0 / 0.45),
941
+ 0 4px 10px -4px rgb(0 0 0 / 0.3);
942
+ --tt-shadow-overlay:
943
+ 0 24px 48px -12px rgb(0 0 0 / 0.55),
944
+ 0 8px 16px -8px rgb(0 0 0 / 0.3);
945
+ --tt-shadow-pinned: 4px 0 8px -4px rgb(0 0 0 / 0.4);
946
+ }
947
+
948
+ /* ---- Sharp: square corners, crisp borders, flat ----
949
+ 0px, not 0: components subtract padding from these inside calc(),
950
+ and a unitless 0 there is a number, which makes the whole calc
951
+ invalid. Capsules (--tt-radius-full) stay round: an avatar is still
952
+ a circle. Cards lose their shadow and lean on a darker border; the
953
+ field border reaches 3:1, so an input's edge is visible on its own.
954
+ Floating things keep a tight shadow, or a menu would merge with the
955
+ page under it. */
956
+
957
+ [data-theme="sharp"] {
958
+ --tt-radius-sm: 0px;
959
+ --tt-radius-md: 0px;
960
+ --tt-radius-lg: 0px;
961
+ --tt-radius-xl: 0px;
962
+ --tt-radius-2xl: 0px;
963
+ --tt-radius: var(--tt-radius-md);
964
+
965
+ --tt-shadow-sm: none;
966
+ --tt-shadow-md: 0 1px 2px 0 rgb(0 0 0 / 0.08);
967
+ --tt-shadow-lg: 0 2px 6px -1px rgb(0 0 0 / 0.12);
968
+ --tt-shadow-overlay: 0 8px 24px -4px rgb(0 0 0 / 0.24);
969
+ --tt-shadow-pinned: 1px 0 0 0 rgb(0 0 0 / 0.1);
970
+ --tt-elevation-1: var(--tt-shadow-sm);
971
+ --tt-elevation-2: var(--tt-shadow-lg);
972
+ --tt-elevation-3: var(--tt-shadow-overlay);
973
+
974
+ --tt-border: oklch(0.86 0 0);
975
+ --tt-input: oklch(0.64 0 0);
976
+ --tt-border-strong: oklch(0.45 0 0);
977
+ --tt-sidebar-border: oklch(0.86 0 0);
978
+ }
979
+
980
+ .dark [data-theme="sharp"],
981
+ .dark[data-theme="sharp"] {
982
+ --tt-shadow-sm: none;
983
+ --tt-shadow-md: 0 1px 2px 0 rgb(0 0 0 / 0.4);
984
+ --tt-shadow-lg: 0 2px 6px -1px rgb(0 0 0 / 0.5);
985
+ --tt-shadow-overlay: 0 8px 24px -4px rgb(0 0 0 / 0.6);
986
+ --tt-shadow-pinned: 1px 0 0 0 rgb(0 0 0 / 0.4);
987
+
988
+ --tt-border: oklch(1 0 0 / 18%);
989
+ --tt-input: oklch(1 0 0 / 34%);
990
+ --tt-border-strong: oklch(0.8 0 0);
991
+ --tt-sidebar-border: oklch(1 0 0 / 18%);
992
+ }
993
+
994
+ /* ---- Editorial: serif headings, more air, ink-blue accent ----
995
+ A system serif stack, so the theme costs no download: Iowan Old
996
+ Style and Charter on Apple, Sitka and Cambria on Windows, Charter or
997
+ Noto Serif on Linux. Body text stays sans, which is what keeps forms
998
+ and tables readable. More line height and larger top headings do
999
+ the airiness; spacing is left to density. Corners tighten towards
1000
+ print. */
1001
+
1002
+ [data-theme="editorial"] {
1003
+ --tt-radius-sm: calc(var(--tt-unit) * 0.5);
1004
+ --tt-radius-md: calc(var(--tt-unit) * 1);
1005
+ --tt-radius-lg: calc(var(--tt-unit) * 1.5);
1006
+ --tt-radius-xl: calc(var(--tt-unit) * 2);
1007
+ --tt-radius-2xl: calc(var(--tt-unit) * 3);
1008
+ --tt-radius: var(--tt-radius-md);
1009
+
1010
+ --tt-font-display:
1011
+ "Iowan Old Style",
1012
+ Charter,
1013
+ "Bitstream Charter",
1014
+ ui-serif,
1015
+ "Sitka Text",
1016
+ Cambria,
1017
+ Georgia,
1018
+ "Noto Serif",
1019
+ serif;
1020
+ --tt-leading-snug: 1.45;
1021
+ --tt-leading-normal: 1.6;
1022
+ --tt-leading-relaxed: 1.8;
1023
+ --tt-text-2xl: 1.625rem;
1024
+ --tt-text-3xl: 2.25rem;
1025
+
1026
+ --tt-brand: oklch(0.47 0.1 245);
1027
+ --tt-brand-foreground: oklch(0.985 0 0);
1028
+ --tt-brand-subtle: oklch(0.96 0.018 245);
1029
+ --tt-ring: var(--tt-brand);
1030
+ --tt-sidebar-ring: var(--tt-brand);
1031
+ }
1032
+
1033
+ .dark [data-theme="editorial"],
1034
+ .dark[data-theme="editorial"] {
1035
+ --tt-brand: oklch(0.76 0.09 245);
1036
+ --tt-brand-foreground: oklch(0.145 0 0);
1037
+ --tt-brand-subtle: oklch(0.3 0.045 245);
1038
+ }
1039
+
1040
+ /* ---- Bold: a louder brand that fills the primary, heavier type ----
1041
+ The one theme that spends the accent on every filled control: primary
1042
+ buttons, checked boxes and switches take the brand. Aliases, so they
1043
+ follow the brand into dark mode with no second copy. The display face
1044
+ is the landing page's Space Grotesk when the app has loaded it, and
1045
+ the sans otherwise. */
1046
+
1047
+ [data-theme="bold"] {
1048
+ --tt-font-display: "Space Grotesk Variable", "Space Grotesk", var(--tt-font-sans);
1049
+ --tt-weight-medium: 550;
1050
+ --tt-weight-semibold: 700;
1051
+ --tt-weight-bold: 800;
1052
+
1053
+ --tt-brand: oklch(0.47 0.25 277);
1054
+ --tt-brand-foreground: oklch(0.985 0 0);
1055
+ --tt-ring: var(--tt-brand);
1056
+ --tt-sidebar-ring: var(--tt-brand);
1057
+ --tt-primary: var(--tt-brand);
1058
+ --tt-primary-foreground: var(--tt-brand-foreground);
1059
+ --tt-sidebar-primary: var(--tt-brand);
1060
+ --tt-sidebar-primary-foreground: var(--tt-brand-foreground);
1061
+ }
1062
+
1063
+ .dark [data-theme="bold"],
1064
+ .dark[data-theme="bold"] {
1065
+ --tt-brand: oklch(0.69 0.15 277);
1066
+ --tt-brand-foreground: oklch(0.145 0 0);
1067
+ }
1068
+
1069
+ /* =========================================
1070
+ Touch
1071
+
1072
+ A mouse pointer is precise; a fingertip is roughly 44px across, which
1073
+ is the minimum target size both Apple and Google publish. Rather than
1074
+ guessing at screen width, this keys off the actual input device, so a
1075
+ touchscreen laptop gets the larger controls too.
1076
+ ========================================= */
1077
+
1078
+ @media (pointer: coarse) {
1079
+ :root {
1080
+ --tt-control-sm: 2.5rem;
1081
+ --tt-control-md: 2.75rem;
1082
+ --tt-control-lg: 3rem;
1083
+ }
1084
+ }
1085
+
1086
+ /* =========================================
1087
+ Reduced motion
1088
+
1089
+ One block for the whole library. Every transition and every one-shot
1090
+ entrance reads a duration token, so zeroing the tokens here stops all of
1091
+ them at once. This replaced 31 separate @media blocks scattered through
1092
+ the components, which was 31 chances to forget one and one more thing to
1093
+ remember on every new component.
1094
+
1095
+ 1ms rather than 0ms, deliberately. A zero-duration transition never fires
1096
+ transitionend, so any code that waits on one hangs forever. Nothing in the
1097
+ library does that today; this costs nothing and means it stays safe when
1098
+ something eventually does.
1099
+
1100
+ Two things are not zeroed:
1101
+
1102
+ - --tt-duration-spin is slowed, not stopped. A spinner is feedback, not
1103
+ decoration, and freezing it says "hung" rather than "working", which is
1104
+ worse for everyone including the people this query is for.
1105
+ - The skeleton shimmer cannot be handled from a token at all: an infinite
1106
+ animation at 1ms strobes rather than stops. tt-skeleton keeps its own
1107
+ block and switches the shimmer off properly.
1108
+
1109
+ The spring is swapped rather than shortened. Overshoot is precisely the
1110
+ movement being asked about, so a 1ms spring is still the wrong shape.
1111
+ ========================================= */
1112
+
1113
+ @media (prefers-reduced-motion: reduce) {
1114
+ :root {
1115
+ --tt-duration-fast: 1ms;
1116
+ --tt-duration-normal: 1ms;
1117
+ --tt-duration-slow: 1ms;
1118
+ --tt-duration-slower: 1ms;
1119
+
1120
+ --tt-ease-spring: var(--tt-ease-out);
1121
+
1122
+ --tt-duration-spin: 1.6s;
1123
+ }
1124
+ }