@cube-dev/ui-kit 0.0.0-canary-cfe0821 → 0.0.0-canary-2b2d784

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 (491) hide show
  1. package/dist/_internal/hooks/use-chained-callback.js +1 -1
  2. package/dist/_internal/hooks/use-debounced-value.js +1 -1
  3. package/dist/_internal/hooks/use-deprecation-warning.js +1 -1
  4. package/dist/_internal/hooks/use-event.js +1 -1
  5. package/dist/_internal/hooks/use-is-first-render.js +1 -1
  6. package/dist/_internal/hooks/use-sync-ref.js +1 -1
  7. package/dist/_internal/hooks/use-timer/timer.js +1 -1
  8. package/dist/_internal/hooks/use-timer/use-timer.js +1 -1
  9. package/dist/_internal/hooks/use-warn.js +1 -1
  10. package/dist/components/Block.js +1 -1
  11. package/dist/components/CollectionItem.js +1 -1
  12. package/dist/components/GlobalStyles.js +7 -7
  13. package/dist/components/GlobalStyles.js.map +1 -1
  14. package/dist/components/GridProvider.js +1 -1
  15. package/dist/components/HiddenInput.js +1 -1
  16. package/dist/components/Root.js +1 -1
  17. package/dist/components/actions/Action/Action.js +1 -1
  18. package/dist/components/actions/Banner/Banner.js +1 -1
  19. package/dist/components/actions/Button/Button.js +1 -1
  20. package/dist/components/actions/ButtonGroup/ButtonGroup.js +1 -1
  21. package/dist/components/actions/ButtonSplit/ButtonSplit.js +1 -1
  22. package/dist/components/actions/ButtonSplit/context.js +1 -1
  23. package/dist/components/actions/CommandMenu/CommandMenu.js +1 -1
  24. package/dist/components/actions/CommandMenu/styled.js +1 -1
  25. package/dist/components/actions/ItemAction/ItemAction.js +1 -1
  26. package/dist/components/actions/ItemActionContext.js +1 -1
  27. package/dist/components/actions/ItemButton/ItemButton.js +1 -1
  28. package/dist/components/actions/Link/Link.js +1 -1
  29. package/dist/components/actions/Menu/Menu.js +1 -1
  30. package/dist/components/actions/Menu/MenuItem.js +1 -1
  31. package/dist/components/actions/Menu/MenuSection.js +1 -1
  32. package/dist/components/actions/Menu/MenuTrigger.js +1 -1
  33. package/dist/components/actions/Menu/SubMenuTrigger.js +1 -1
  34. package/dist/components/actions/Menu/SubmenuTriggerContext.js +1 -1
  35. package/dist/components/actions/Menu/context.js +1 -1
  36. package/dist/components/actions/Menu/styled.js +1 -1
  37. package/dist/components/actions/index.js +1 -1
  38. package/dist/components/actions/use-action.js +1 -1
  39. package/dist/components/actions/use-action.js.map +1 -1
  40. package/dist/components/actions/use-anchored-menu.js +1 -1
  41. package/dist/components/actions/use-context-menu.js +1 -1
  42. package/dist/components/content/ActiveZone/ActiveZone.js +1 -1
  43. package/dist/components/content/Alert/Alert.js +1 -1
  44. package/dist/components/content/Alert/use-alert.js +1 -1
  45. package/dist/components/content/Avatar/Avatar.js +1 -1
  46. package/dist/components/content/Badge/Badge.js +1 -1
  47. package/dist/components/content/Card/Card.js +1 -1
  48. package/dist/components/content/Content.js +1 -1
  49. package/dist/components/content/CopyPasteBlock/CopyPasteBlock.js +1 -1
  50. package/dist/components/content/CopySnippet/CopySnippet.js +4 -4
  51. package/dist/components/content/CopySnippet/CopySnippet.js.map +1 -1
  52. package/dist/components/content/Disclosure/Disclosure.js +1 -1
  53. package/dist/components/content/Divider.js +1 -1
  54. package/dist/components/content/Footer.js +1 -1
  55. package/dist/components/content/Header.js +1 -1
  56. package/dist/components/content/HotKeys/HotKeys.js +1 -1
  57. package/dist/components/content/InfoBadge/InfoBadge.js +1 -1
  58. package/dist/components/content/InlineInput/InlineInput.js +1 -1
  59. package/dist/components/content/Item/Item.js +1 -1
  60. package/dist/components/content/ItemBadge/ItemBadge.js +1 -1
  61. package/dist/components/content/ItemCard/ItemCard.js +1 -1
  62. package/dist/components/content/Layout/GridLayout.js +1 -1
  63. package/dist/components/content/Layout/Layout.js +1 -1
  64. package/dist/components/content/Layout/LayoutBlock.js +1 -1
  65. package/dist/components/content/Layout/LayoutCenter.js +1 -1
  66. package/dist/components/content/Layout/LayoutContainer.js +1 -1
  67. package/dist/components/content/Layout/LayoutContent.js +1 -1
  68. package/dist/components/content/Layout/LayoutContext.js +1 -1
  69. package/dist/components/content/Layout/LayoutFlex.js +1 -1
  70. package/dist/components/content/Layout/LayoutFooter.js +1 -1
  71. package/dist/components/content/Layout/LayoutGrid.js +1 -1
  72. package/dist/components/content/Layout/LayoutHeader.js +1 -1
  73. package/dist/components/content/Layout/LayoutPane.js +1 -1
  74. package/dist/components/content/Layout/LayoutPanel.js +1 -1
  75. package/dist/components/content/Layout/LayoutPanelHeader.js +1 -1
  76. package/dist/components/content/Layout/LayoutToolbar.js +1 -1
  77. package/dist/components/content/Layout/hooks/useTinyScrollbar.js +1 -1
  78. package/dist/components/content/Layout/index.js +1 -1
  79. package/dist/components/content/Layout/utils.js +1 -1
  80. package/dist/components/content/Paragraph.js +1 -1
  81. package/dist/components/content/Placeholder/Placeholder.js +1 -1
  82. package/dist/components/content/PrismCode/PrismCode.js +6 -2
  83. package/dist/components/content/PrismCode/PrismCode.js.map +1 -1
  84. package/dist/components/content/PrismCode/prismSetup.js +1 -1
  85. package/dist/components/content/PrismDiffCode/PrismDiffCode.js +1 -1
  86. package/dist/components/content/Result/Result.js +1 -1
  87. package/dist/components/content/Skeleton/Skeleton.js +1 -1
  88. package/dist/components/content/Tag/Tag.js +1 -1
  89. package/dist/components/content/Text.js +1 -1
  90. package/dist/components/content/TextItem/TextItem.js +1 -1
  91. package/dist/components/content/Title.js +1 -1
  92. package/dist/components/content/Tree/Tree.js +1 -1
  93. package/dist/components/content/Tree/TreeNode.js +1 -1
  94. package/dist/components/content/Tree/styled.js +1 -1
  95. package/dist/components/content/Tree/tree-index.js +1 -1
  96. package/dist/components/content/Tree/use-checkbox-tree.js +1 -1
  97. package/dist/components/content/Tree/use-load-data.js +1 -1
  98. package/dist/components/content/highlightText.js +1 -1
  99. package/dist/components/content/use-auto-tooltip.js +1 -1
  100. package/dist/components/fields/Checkbox/Checkbox.js +1 -1
  101. package/dist/components/fields/Checkbox/CheckboxGroup.js +1 -1
  102. package/dist/components/fields/Checkbox/context.js +1 -1
  103. package/dist/components/fields/ComboBox/ComboBox.js +1 -1
  104. package/dist/components/fields/CommandTextArea/CommandTextArea.js +1 -1
  105. package/dist/components/fields/CommandTextArea/caretPosition.js +1 -1
  106. package/dist/components/fields/CommandTextArea/useCaretAnchor.js +1 -1
  107. package/dist/components/fields/DatePicker/DateInput.js +1 -1
  108. package/dist/components/fields/DatePicker/DateInputBase.js +1 -1
  109. package/dist/components/fields/DatePicker/DatePicker.js +1 -1
  110. package/dist/components/fields/DatePicker/DatePickerButton.js +1 -1
  111. package/dist/components/fields/DatePicker/DatePickerElement.js +1 -1
  112. package/dist/components/fields/DatePicker/DatePickerInput.js +1 -1
  113. package/dist/components/fields/DatePicker/DatePickerSegment.js +1 -1
  114. package/dist/components/fields/DatePicker/DateRangePicker.js +1 -1
  115. package/dist/components/fields/DatePicker/DateRangeSeparatedPicker.js +1 -1
  116. package/dist/components/fields/DatePicker/TimeInput.js +1 -1
  117. package/dist/components/fields/DatePicker/parseDate.js +1 -1
  118. package/dist/components/fields/DatePicker/props.js +1 -1
  119. package/dist/components/fields/DatePicker/utils.js +1 -1
  120. package/dist/components/fields/FileInput/FileInput.js +1 -1
  121. package/dist/components/fields/FilterListBox/FilterListBox.js +1 -1
  122. package/dist/components/fields/FilterPicker/FilterPicker.js +1 -3
  123. package/dist/components/fields/FilterPicker/FilterPicker.js.map +1 -1
  124. package/dist/components/fields/Input/Input.js +1 -1
  125. package/dist/components/fields/ListBox/DraggableListBox.js +1 -1
  126. package/dist/components/fields/ListBox/ListBox.js +1 -1
  127. package/dist/components/fields/ListBoxPopover/ListBoxPopover.js +1 -1
  128. package/dist/components/fields/ListBoxPopover/listNavigation.js +1 -1
  129. package/dist/components/fields/ListBoxPopover/useCompositeFocus.js +1 -1
  130. package/dist/components/fields/NumberInput/NumberInput.js +1 -1
  131. package/dist/components/fields/NumberInput/StepButton.js +1 -1
  132. package/dist/components/fields/PasswordInput/PasswordInput.js +1 -1
  133. package/dist/components/fields/Picker/Picker.js +1 -1
  134. package/dist/components/fields/RadioGroup/Radio.js +1 -1
  135. package/dist/components/fields/RadioGroup/RadioGroup.js +1 -1
  136. package/dist/components/fields/RadioGroup/context.js +1 -1
  137. package/dist/components/fields/SearchComboBox/SearchComboBox.js +1 -1
  138. package/dist/components/fields/SearchInput/SearchInput.js +1 -1
  139. package/dist/components/fields/Select/Select.js +1 -1
  140. package/dist/components/fields/Slider/Gradation.js +1 -1
  141. package/dist/components/fields/Slider/HueSlider.js +1 -1
  142. package/dist/components/fields/Slider/RangeSlider.js +1 -1
  143. package/dist/components/fields/Slider/Slider.js +1 -1
  144. package/dist/components/fields/Slider/SliderBase.js +1 -1
  145. package/dist/components/fields/Slider/SliderThumb.js +1 -1
  146. package/dist/components/fields/Slider/SliderTrack.js +1 -1
  147. package/dist/components/fields/Slider/elements.js +1 -1
  148. package/dist/components/fields/Slider/index.js +1 -1
  149. package/dist/components/fields/Switch/Switch.js +1 -1
  150. package/dist/components/fields/TextArea/TextArea.js +1 -1
  151. package/dist/components/fields/TextInput/TextInput.js +1 -1
  152. package/dist/components/fields/TextInput/TextInputBase.js +1 -1
  153. package/dist/components/fields/TextInputMapper/TextInputMapper.js +1 -1
  154. package/dist/components/form/FieldWrapper/FieldWrapper.js +1 -1
  155. package/dist/components/form/Form/Field.js +1 -1
  156. package/dist/components/form/Form/Form.js +1 -1
  157. package/dist/components/form/Form/ResetButton/ResetButton.js +1 -1
  158. package/dist/components/form/Form/SubmitButton/SubmitButton.js +1 -1
  159. package/dist/components/form/Form/SubmitError.js +1 -1
  160. package/dist/components/form/Form/index.js +1 -1
  161. package/dist/components/form/Form/use-field/use-field-props.js +1 -1
  162. package/dist/components/form/Form/use-field/use-field.js +1 -1
  163. package/dist/components/form/Form/use-form.js +1 -1
  164. package/dist/components/form/Form/validation.js +1 -1
  165. package/dist/components/form/Label.js +1 -1
  166. package/dist/components/form/validation/ValidationIndicator.js +1 -1
  167. package/dist/components/form/validation/resolve-validation-props.js +1 -1
  168. package/dist/components/form/validation/use-validation-props.js +1 -1
  169. package/dist/components/form/wrapper.js +1 -1
  170. package/dist/components/helpers/DisplayTransition/DisplayTransition.js +1 -1
  171. package/dist/components/helpers/IconSwitch/IconSwitch.js +1 -1
  172. package/dist/components/layout/Board/Board.js +1 -1
  173. package/dist/components/layout/Board/BoardProvider.js +1 -1
  174. package/dist/components/layout/Board/BoardResponsive.js +1 -1
  175. package/dist/components/layout/Board/Widget.js +1 -1
  176. package/dist/components/layout/Board/WidgetHost.js +1 -1
  177. package/dist/components/layout/Board/board-context.js +1 -1
  178. package/dist/components/layout/Board/board-store.js +1 -1
  179. package/dist/components/layout/Board/grid-core/calculate.js +1 -1
  180. package/dist/components/layout/Board/grid-core/collision.js +1 -1
  181. package/dist/components/layout/Board/grid-core/compactors.js +1 -1
  182. package/dist/components/layout/Board/grid-core/constraints.js +1 -1
  183. package/dist/components/layout/Board/grid-core/layout.js +1 -1
  184. package/dist/components/layout/Board/grid-core/sort.js +1 -1
  185. package/dist/components/layout/Board/index.js +1 -1
  186. package/dist/components/layout/Board/responsive-utils.js +1 -1
  187. package/dist/components/layout/Board/use-board-layout.js +1 -1
  188. package/dist/components/layout/Board/use-board-registry.js +1 -1
  189. package/dist/components/layout/Flex.js +1 -1
  190. package/dist/components/layout/Flow.js +1 -1
  191. package/dist/components/layout/Grid.js +1 -1
  192. package/dist/components/layout/Panel.js +1 -1
  193. package/dist/components/layout/Prefix.js +1 -1
  194. package/dist/components/layout/ResizablePanel.js +1 -1
  195. package/dist/components/layout/Space.js +1 -1
  196. package/dist/components/layout/Suffix.js +1 -1
  197. package/dist/components/navigation/Tabs/DraggableTabList.js +1 -1
  198. package/dist/components/navigation/Tabs/TabButton.js +1 -1
  199. package/dist/components/navigation/Tabs/TabDropIndicator.js +1 -1
  200. package/dist/components/navigation/Tabs/TabPanel.js +1 -1
  201. package/dist/components/navigation/Tabs/TabPicker.js +1 -1
  202. package/dist/components/navigation/Tabs/Tabs.js +1 -1
  203. package/dist/components/navigation/Tabs/TabsAction.js +1 -1
  204. package/dist/components/navigation/Tabs/TabsContext.js +1 -1
  205. package/dist/components/navigation/Tabs/popover-placement.js +1 -1
  206. package/dist/components/navigation/Tabs/styled.js +1 -1
  207. package/dist/components/navigation/Tabs/types.js +1 -1
  208. package/dist/components/navigation/Tabs/use-tab-editing.js +1 -1
  209. package/dist/components/navigation/Tabs/use-tab-indicator.js +1 -1
  210. package/dist/components/organisms/FileTabs/FileTabs.js +1 -1
  211. package/dist/components/organisms/StatsCard/StatsCard.js +1 -1
  212. package/dist/components/other/Calendar/Calendar.js +1 -1
  213. package/dist/components/other/Calendar/CalendarCell.js +1 -1
  214. package/dist/components/other/Calendar/CalendarGrid.js +1 -1
  215. package/dist/components/other/Calendar/RangeCalendar.js +1 -1
  216. package/dist/components/other/CloudLogo/CloudLogo.js +1 -1
  217. package/dist/components/overlays/AlertDialog/AlertDialog.js +1 -1
  218. package/dist/components/overlays/AlertDialog/AlertDialogApiProvider.js +1 -1
  219. package/dist/components/overlays/AlertDialog/AlertDialogZone.js +1 -1
  220. package/dist/components/overlays/Dialog/Dialog.js +2 -3
  221. package/dist/components/overlays/Dialog/Dialog.js.map +1 -1
  222. package/dist/components/overlays/Dialog/DialogContainer.js +1 -1
  223. package/dist/components/overlays/Dialog/DialogForm.js +1 -1
  224. package/dist/components/overlays/Dialog/DialogTrigger.js +1 -1
  225. package/dist/components/overlays/Dialog/context.js +1 -1
  226. package/dist/components/overlays/Dialog/use-dialog-container.js +1 -1
  227. package/dist/components/overlays/Modal/Modal.js +1 -1
  228. package/dist/components/overlays/Modal/OpenTransitionContext.js +1 -1
  229. package/dist/components/overlays/Modal/Overlay.js +1 -1
  230. package/dist/components/overlays/Modal/Popover.js +1 -1
  231. package/dist/components/overlays/Modal/Tray.js +1 -1
  232. package/dist/components/overlays/Modal/Underlay.js +1 -1
  233. package/dist/components/overlays/Notifications/Notification.js +1 -1
  234. package/dist/components/overlays/Notifications/NotificationAction.js +1 -1
  235. package/dist/components/overlays/Notifications/NotificationCard.js +1 -1
  236. package/dist/components/overlays/Notifications/NotificationContext.js +1 -1
  237. package/dist/components/overlays/Notifications/NotificationItem.js +1 -1
  238. package/dist/components/overlays/Notifications/OverlayContainer.js +1 -1
  239. package/dist/components/overlays/Notifications/OverlayProvider.js +1 -1
  240. package/dist/components/overlays/Notifications/PersistentNotificationsList.js +1 -1
  241. package/dist/components/overlays/Notifications/dismissed-storage.js +1 -1
  242. package/dist/components/overlays/Notifications/format-relative-time.js +1 -1
  243. package/dist/components/overlays/Notifications/index.js +1 -1
  244. package/dist/components/overlays/Notifications/use-notification-state.js +1 -1
  245. package/dist/components/overlays/Notifications/use-notifications.js +1 -1
  246. package/dist/components/overlays/Notifications/use-overlay-timers.js +1 -1
  247. package/dist/components/overlays/Notifications/use-persistent-notifications.js +1 -1
  248. package/dist/components/overlays/Notifications/use-persistent-state.js +1 -1
  249. package/dist/components/overlays/Notifications/use-toast-state.js +1 -1
  250. package/dist/components/overlays/Toast/ToastItem.js +1 -1
  251. package/dist/components/overlays/Toast/index.js +1 -1
  252. package/dist/components/overlays/Toast/useProgressToast.js +1 -1
  253. package/dist/components/overlays/Toast/useToast.js +1 -1
  254. package/dist/components/overlays/Tooltip/Tooltip.js +1 -1
  255. package/dist/components/overlays/Tooltip/TooltipProvider.js +1 -1
  256. package/dist/components/overlays/Tooltip/TooltipTrigger.js +1 -1
  257. package/dist/components/overlays/Tooltip/context.js +1 -1
  258. package/dist/components/portal/Portal.js +1 -1
  259. package/dist/components/portal/PortalProvider.js +1 -1
  260. package/dist/components/portal/usePortal.js +1 -1
  261. package/dist/components/shared/DraggableCollection.js +1 -1
  262. package/dist/components/shared/InvalidIcon.js +1 -1
  263. package/dist/components/shared/ValidIcon.js +1 -1
  264. package/dist/components/status/LoadingAnimation/LoadingAnimation.js +1 -1
  265. package/dist/components/status/Spin/Cube.js +1 -1
  266. package/dist/components/status/Spin/InternalSpinner.js +1 -1
  267. package/dist/components/status/Spin/Spin.js +1 -1
  268. package/dist/components/status/Spin/SpinsContainer.js +1 -1
  269. package/dist/data/item-themes.js +5 -3
  270. package/dist/data/item-themes.js.map +1 -1
  271. package/dist/data/themes.js +1 -1
  272. package/dist/i18n/I18nProvider.js +1 -1
  273. package/dist/i18n/createFormatter.js +1 -1
  274. package/dist/i18n/index.js +1 -1
  275. package/dist/i18n/instance.js +1 -1
  276. package/dist/i18n/locales/de-DE/uikit.js +1 -1
  277. package/dist/i18n/locales/en-US/uikit.js +1 -1
  278. package/dist/i18n/locales/es-ES/uikit.js +1 -1
  279. package/dist/i18n/locales/es-MX/uikit.js +1 -1
  280. package/dist/i18n/locales/fr-FR/uikit.js +1 -1
  281. package/dist/i18n/locales/it-IT/uikit.js +1 -1
  282. package/dist/i18n/locales/ja-JP/uikit.js +1 -1
  283. package/dist/i18n/locales/nb-NO/uikit.js +1 -1
  284. package/dist/i18n/locales/pt-BR/uikit.js +1 -1
  285. package/dist/i18n/locales/pt-PT/uikit.js +1 -1
  286. package/dist/i18n/locales/sv-SE/uikit.js +1 -1
  287. package/dist/i18n/locales/vi-VN/uikit.js +1 -1
  288. package/dist/i18n/locales.js +1 -1
  289. package/dist/i18n/useFormatter.js +1 -1
  290. package/dist/i18n/useI18n.js +1 -1
  291. package/dist/icons/AdjustmentsHorizontalIcon.js +1 -1
  292. package/dist/icons/AdjustmentsIcon.js +1 -1
  293. package/dist/icons/AiIcon.js +1 -1
  294. package/dist/icons/AreaChartIcon.js +1 -1
  295. package/dist/icons/BackwardIcon.js +1 -1
  296. package/dist/icons/BarChartIcon.js +1 -1
  297. package/dist/icons/BellFilledIcon.js +1 -1
  298. package/dist/icons/BellIcon.js +1 -1
  299. package/dist/icons/BooleanIcon.js +1 -1
  300. package/dist/icons/CalendarEditIcon.js +1 -1
  301. package/dist/icons/CalendarIcon.js +1 -1
  302. package/dist/icons/CaretDownIcon.js +1 -1
  303. package/dist/icons/CaretUpIcon.js +1 -1
  304. package/dist/icons/ChartAreaStackedIcon.js +1 -1
  305. package/dist/icons/ChartAreaStackedPercentageIcon.js +1 -1
  306. package/dist/icons/ChartBarGroupedHorizontalIcon.js +1 -1
  307. package/dist/icons/ChartBarGroupedIcon.js +1 -1
  308. package/dist/icons/ChartBarHorizontalIcon.js +1 -1
  309. package/dist/icons/ChartBarLineIcon.js +1 -1
  310. package/dist/icons/ChartBarStackedHorizontalIcon.js +1 -1
  311. package/dist/icons/ChartBarStackedIcon.js +1 -1
  312. package/dist/icons/ChartBarStackedPercentageHorizontalIcon.js +1 -1
  313. package/dist/icons/ChartBarStackedPercentageIcon.js +1 -1
  314. package/dist/icons/ChartBoxPlot2Icon.js +1 -1
  315. package/dist/icons/ChartBoxPlotIcon.js +1 -1
  316. package/dist/icons/ChartBubbleIcon.js +1 -1
  317. package/dist/icons/ChartDonut2Icon.js +1 -1
  318. package/dist/icons/ChartFunnelIcon.js +1 -1
  319. package/dist/icons/ChartHeatmapIcon.js +1 -1
  320. package/dist/icons/ChartKPIIcon.js +1 -1
  321. package/dist/icons/ChartPie2Icon.js +1 -1
  322. package/dist/icons/ChartScatterIcon.js +1 -1
  323. package/dist/icons/CheckCircleFilledIcon.js +1 -1
  324. package/dist/icons/CheckCircleIcon.js +1 -1
  325. package/dist/icons/CheckIcon.js +1 -1
  326. package/dist/icons/CircleFilledIcon.js +1 -1
  327. package/dist/icons/ClearIcon.js +1 -1
  328. package/dist/icons/CloseCircleFilledIcon.js +1 -1
  329. package/dist/icons/CloseCircleIcon.js +1 -1
  330. package/dist/icons/CloseIcon.js +1 -1
  331. package/dist/icons/CodeIcon.js +1 -1
  332. package/dist/icons/ColumnTotalIcon.js +1 -1
  333. package/dist/icons/CopyIcon.js +1 -1
  334. package/dist/icons/CountIcon.js +1 -1
  335. package/dist/icons/CubeIcon.js +1 -1
  336. package/dist/icons/CubePauseIcon.js +1 -1
  337. package/dist/icons/CubePlayIcon.js +1 -1
  338. package/dist/icons/CurrencyDollarIcon.js +1 -1
  339. package/dist/icons/DangerIcon.js +1 -1
  340. package/dist/icons/DashboardIcon.js +1 -1
  341. package/dist/icons/DatabaseIcon.js +1 -1
  342. package/dist/icons/DecimalDecreaseIcon.js +1 -1
  343. package/dist/icons/DecimalIncreaseIcon.js +1 -1
  344. package/dist/icons/DirectionIcon.js +1 -1
  345. package/dist/icons/DonutIcon.js +1 -1
  346. package/dist/icons/DownIcon.js +1 -1
  347. package/dist/icons/EditIcon.js +1 -1
  348. package/dist/icons/ExclamationCircleFilledIcon.js +1 -1
  349. package/dist/icons/ExclamationCircleIcon.js +1 -1
  350. package/dist/icons/ExclamationIcon.js +1 -1
  351. package/dist/icons/EyeIcon.js +1 -1
  352. package/dist/icons/EyeInvisibleIcon.js +1 -1
  353. package/dist/icons/FilterIcon.js +1 -1
  354. package/dist/icons/FolderFilledIcon.js +1 -1
  355. package/dist/icons/FolderIcon.js +1 -1
  356. package/dist/icons/FolderOpenFilledIcon.js +1 -1
  357. package/dist/icons/FolderOpenIcon.js +1 -1
  358. package/dist/icons/ForwardIcon.js +1 -1
  359. package/dist/icons/GripVerticalIcon.js +1 -1
  360. package/dist/icons/HierarchyIcon.js +1 -1
  361. package/dist/icons/HierarchyOpenIcon.js +1 -1
  362. package/dist/icons/Icon.js +1 -1
  363. package/dist/icons/InfoCircleIcon.js +1 -1
  364. package/dist/icons/InfoIcon.js +1 -1
  365. package/dist/icons/KeyIcon.js +1 -1
  366. package/dist/icons/LeftIcon.js +1 -1
  367. package/dist/icons/LineChartIcon.js +1 -1
  368. package/dist/icons/LoadingIcon.js +1 -1
  369. package/dist/icons/LockFilledIcon.js +1 -1
  370. package/dist/icons/LockIcon.js +1 -1
  371. package/dist/icons/MoreIcon.js +1 -1
  372. package/dist/icons/NotAllowedIcon.js +1 -1
  373. package/dist/icons/Number123Icon.js +1 -1
  374. package/dist/icons/NumberIcon.js +1 -1
  375. package/dist/icons/PauseCircleFilledIcon.js +1 -1
  376. package/dist/icons/PauseCircleIcon.js +1 -1
  377. package/dist/icons/PauseIcon.js +1 -1
  378. package/dist/icons/PercentageIcon.js +1 -1
  379. package/dist/icons/PieChartIcon.js +1 -1
  380. package/dist/icons/PlayCircleIcon.js +1 -1
  381. package/dist/icons/PlayIcon.js +1 -1
  382. package/dist/icons/PlusIcon.js +1 -1
  383. package/dist/icons/ProgressBarIcon.js +1 -1
  384. package/dist/icons/ReloadIcon.js +1 -1
  385. package/dist/icons/ReportIcon.js +1 -1
  386. package/dist/icons/ReturnIcon.js +1 -1
  387. package/dist/icons/RightIcon.js +1 -1
  388. package/dist/icons/RowTotalsIcon.js +1 -1
  389. package/dist/icons/SchemeIcon.js +1 -1
  390. package/dist/icons/SearchIcon.js +1 -1
  391. package/dist/icons/SemanticQueryIcon.js +1 -1
  392. package/dist/icons/SettingsIcon.js +1 -1
  393. package/dist/icons/ShieldFilledIcon.js +1 -1
  394. package/dist/icons/ShieldIcon.js +1 -1
  395. package/dist/icons/SlashIcon.js +1 -1
  396. package/dist/icons/SparklesIcon.js +1 -1
  397. package/dist/icons/SqlIcon.js +1 -1
  398. package/dist/icons/StatsIcon.js +1 -1
  399. package/dist/icons/StopIcon.js +1 -1
  400. package/dist/icons/StringIcon.js +1 -1
  401. package/dist/icons/SubtotalsIcon.js +1 -1
  402. package/dist/icons/SwitchIcon.js +1 -1
  403. package/dist/icons/TableIcon.js +1 -1
  404. package/dist/icons/ThumbsDownIcon.js +1 -1
  405. package/dist/icons/ThumbsUpIcon.js +1 -1
  406. package/dist/icons/ThunderboltCrossedIcon.js +1 -1
  407. package/dist/icons/ThunderboltFilledIcon.js +1 -1
  408. package/dist/icons/ThunderboltIcon.js +1 -1
  409. package/dist/icons/TimeIcon.js +1 -1
  410. package/dist/icons/TrashIcon.js +1 -1
  411. package/dist/icons/UnlockIcon.js +1 -1
  412. package/dist/icons/UpIcon.js +1 -1
  413. package/dist/icons/UserGroupIcon.js +1 -1
  414. package/dist/icons/UserIcon.js +1 -1
  415. package/dist/icons/UserLockIcon.js +1 -1
  416. package/dist/icons/ViewIcon.js +1 -1
  417. package/dist/icons/WarningFilledIcon.js +1 -1
  418. package/dist/icons/WarningIcon.js +1 -1
  419. package/dist/icons/wrap-icon.js +1 -1
  420. package/dist/index.d.ts +4 -3
  421. package/dist/index.js +5 -4
  422. package/dist/index.js.map +1 -1
  423. package/dist/provider.js +1 -1
  424. package/dist/providers/TrackingProvider.js +1 -1
  425. package/dist/providers/navigationAdapter.default.js +1 -1
  426. package/dist/tokens/base.js +1 -1
  427. package/dist/tokens/colors.d.ts +2 -7
  428. package/dist/tokens/colors.js +16 -8
  429. package/dist/tokens/colors.js.map +1 -1
  430. package/dist/tokens/index.d.ts +5 -17
  431. package/dist/tokens/index.js +23 -13
  432. package/dist/tokens/index.js.map +1 -1
  433. package/dist/tokens/layout.js +1 -1
  434. package/dist/tokens/lazy-styles.js +33 -0
  435. package/dist/tokens/lazy-styles.js.map +1 -0
  436. package/dist/tokens/palette.d.ts +15 -0
  437. package/dist/tokens/palette.js +170 -126
  438. package/dist/tokens/palette.js.map +1 -1
  439. package/dist/tokens/shadows.js +1 -1
  440. package/dist/tokens/sizes.js +1 -1
  441. package/dist/tokens/spacing.js +1 -1
  442. package/dist/tokens/typography.d.ts +1 -0
  443. package/dist/tokens/typography.js +29 -1
  444. package/dist/tokens/typography.js.map +1 -1
  445. package/dist/utils/ResizeSensor.js +1 -1
  446. package/dist/utils/is-dev-env.js +1 -1
  447. package/dist/utils/modules.js +1 -1
  448. package/dist/utils/promise.js +1 -1
  449. package/dist/utils/raf.js +1 -1
  450. package/dist/utils/random.js +1 -1
  451. package/dist/utils/range.js +1 -1
  452. package/dist/utils/react/RenderCache.js +1 -1
  453. package/dist/utils/react/Slots.js +1 -1
  454. package/dist/utils/react/chain.js +1 -1
  455. package/dist/utils/react/forwardRefWithGenerics.js +1 -1
  456. package/dist/utils/react/index.js +1 -1
  457. package/dist/utils/react/interactions.js +1 -1
  458. package/dist/utils/react/isTextOnly.js +1 -1
  459. package/dist/utils/react/mapProps.js +1 -1
  460. package/dist/utils/react/mergeProps.js +1 -1
  461. package/dist/utils/react/nullableValue.js +1 -1
  462. package/dist/utils/react/resolveIcon.js +1 -1
  463. package/dist/utils/react/sharedStore.js +1 -1
  464. package/dist/utils/react/useCombinedRefs.js +1 -1
  465. package/dist/utils/react/useControlledFocusVisible.js +1 -1
  466. package/dist/utils/react/useEventBus.js +1 -1
  467. package/dist/utils/react/useId.js +1 -1
  468. package/dist/utils/react/useIsDarwin.js +1 -1
  469. package/dist/utils/react/useKeySymbols.js +1 -1
  470. package/dist/utils/react/useLayoutEffect.js +1 -1
  471. package/dist/utils/react/useLocalStorage.js +1 -1
  472. package/dist/utils/react/useMergeStyles.js +1 -1
  473. package/dist/utils/react/usePopoverSync.js +1 -1
  474. package/dist/utils/react/useQaProps.js +1 -1
  475. package/dist/utils/react/useViewportSize.js +1 -1
  476. package/dist/utils/react/wrapNodeIfPlain.js +1 -1
  477. package/dist/utils/selection.js +1 -1
  478. package/dist/utils/styles.js +1 -1
  479. package/dist/utils/tree.js +1 -1
  480. package/dist/utils/warnings.js +1 -1
  481. package/dist/version.js +2 -2
  482. package/docs/Colors.md +77 -0
  483. package/docs/Typography.md +1 -1
  484. package/docs/Usage.md +10 -0
  485. package/docs/components/content/CopySnippet.md +1 -1
  486. package/docs/components/content/PrismCode.md +1 -0
  487. package/docs/glaze/api.md +951 -414
  488. package/docs/glaze/methodology.md +512 -212
  489. package/docs/glaze/migration.md +127 -49
  490. package/docs/glaze/okhst.md +170 -0
  491. package/package.json +2 -2
package/docs/glaze/api.md CHANGED
@@ -6,12 +6,17 @@ Full reference for every public method, option, and type exported by `@tenphi/gl
6
6
 
7
7
  - [Theme creation](#theme-creation)
8
8
  - [Theme methods](#theme-methods)
9
+ - [DTCG](#themedtcgoptions)
10
+ - [DTCG Resolver-Module](#themedtcgresolveroptions)
11
+ - [Tailwind CSS](#themetailwindoptions)
12
+ - [High-contrast pairs](#high-contrast-pairs)
9
13
  - [Color definitions](#color-definitions)
10
14
  - [Standalone color tokens](#standalone-color-tokens)
11
15
  - [Shadows](#shadows)
12
16
  - [Mix colors](#mix-colors)
13
17
  - [Palette](#palette)
14
18
  - [Output formats](#output-formats)
19
+ - [Hue channel splitting](#hue-channel-splitting-splithue)
15
20
  - [Adaptation modes](#adaptation-modes)
16
21
  - [Light / dark scheme mapping](#light--dark-scheme-mapping)
17
22
  - [Configuration](#configuration)
@@ -23,25 +28,41 @@ Full reference for every public method, option, and type exported by `@tenphi/gl
23
28
 
24
29
  ## Theme creation
25
30
 
26
- | Method | Description |
27
- |---|---|
28
- | `glaze(hue, saturation?, config?)` | Create a theme from hue (0–360) and saturation (0–100). Optional `config` overrides the global config for this theme. |
29
- | `glaze({ hue, saturation }, config?)` | Create a theme from an options object, with optional per-theme config override. |
30
- | `glaze.from(data)` | Create a theme from an exported configuration (`theme.export()` snapshot). |
31
- | `glaze.fromHex(hex)` | Create a theme from a hex color (`#rgb` or `#rrggbb`). Extracts hue and saturation. |
32
- | `glaze.fromRgb(r, g, b)` | Create a theme from RGB values (0–255). Extracts hue and saturation. |
31
+ | Method | Description |
32
+ | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
33
+ | `glaze(hue, saturation?, config?)` | Create a theme from hue (0–360) and saturation (0–100). Optional `config` overrides the global config for this theme. |
34
+ | `glaze({ hue, saturation }, config?)` | Create a theme from an options object, with optional per-theme config override. |
35
+ | `glaze.themeFrom(data)` | Create a theme from a `theme.export()` snapshot (`kind: 'theme'`). |
36
+ | `glaze.from(data)` | Compat alias for `glaze.themeFrom`. |
37
+ | `glaze.fromHex(hex)` | Create a theme from a hex color (`#rgb` or `#rrggbb`). Extracts hue and saturation. |
38
+ | `glaze.fromRgb(r, g, b)` | Create a theme from RGB values (0–255). Extracts hue and saturation. |
39
+ | `glaze.paletteFrom(data)` | Create a palette from a `palette.export()` snapshot (`kind: 'palette'`). |
40
+ | `glaze.colorFrom(data)` | Create a color token from a `token.export()` snapshot (`kind: 'color'`). |
41
+ | `glaze.isThemeExport(data)` | Type guard for theme authoring snapshots. |
42
+ | `glaze.isColorTokenExport(data)` | Type guard for color-token authoring snapshots. |
43
+ | `glaze.isPaletteExport(data)` | Type guard for palette authoring snapshots. |
33
44
 
34
45
  ```ts
35
46
  const a = glaze(280, 80);
36
47
  const b = glaze({ hue: 280, saturation: 80 });
37
48
  const c = glaze.fromHex('#7a4dbf');
38
49
  const d = glaze.fromRgb(122, 77, 191);
39
- const e = glaze.from(a.export());
50
+ const e = glaze.themeFrom(a.export());
40
51
 
41
52
  // Per-theme config override:
42
- const rawTheme = glaze(280, 80, { lightLightness: false, darkLightness: false });
53
+ const rawTheme = glaze(280, 80, { lightTone: false, darkTone: false });
43
54
  ```
44
55
 
56
+ Authoring restore triad (parallel to `.export()` on each instance):
57
+
58
+ | Export | Restore |
59
+ | ------ | ------- |
60
+ | `theme.export()` | `glaze.themeFrom()` |
61
+ | `token.export()` | `glaze.colorFrom()` |
62
+ | `palette.export()` | `glaze.paletteFrom()` |
63
+
64
+ Every snapshot includes `kind` + `version` (`GLAZE_EXPORT_VERSION`, currently `1`). Legacy snapshots without those fields still restore. Wrong `kind`, or a `version` outside `1..=GLAZE_EXPORT_VERSION`, throws.
65
+
45
66
  The optional `config` parameter is a `GlazeConfigOverride` — see [Per-instance config override](#per-instance-config-override).
46
67
 
47
68
  ---
@@ -50,38 +71,41 @@ The optional `config` parameter is a `GlazeConfigOverride` — see [Per-instance
50
71
 
51
72
  A `GlazeTheme` exposes:
52
73
 
53
- | Method | Description |
54
- |---|---|
55
- | `theme.hue` (readonly) | The hue seed (0–360). |
56
- | `theme.saturation` (readonly) | The saturation seed (0–100). |
57
- | `theme.colors(defs)` | Add/replace colors (additive merge — adds new, overwrites existing by name, doesn't remove others). |
58
- | `theme.color(name)` | Get a color definition by name. |
59
- | `theme.color(name, def)` | Set a single color definition. |
60
- | `theme.remove(name \| names[])` | Remove one or more color definitions. |
61
- | `theme.has(name)` | Check if a color is defined. |
62
- | `theme.list()` | List all defined color names. |
63
- | `theme.reset()` | Clear all color definitions. |
64
- | `theme.export()` | Export the theme configuration as a JSON-safe object. |
65
- | `theme.extend(options)` | Create a child theme inheriting all color definitions (see [`extend`](#themeextendoptions) below). |
66
- | `theme.resolve()` | Resolve all colors and return a `Map<string, ResolvedColor>`. |
67
- | `theme.tokens(options?)` | Export as a flat token map grouped by scheme variant. |
68
- | `theme.tasty(options?)` | Export as Tasty style-to-state bindings. |
69
- | `theme.json(options?)` | Export as plain JSON. |
70
- | `theme.css(options?)` | Export as CSS custom property declarations. |
74
+ | Method | Description |
75
+ | ------------------------------- | --------------------------------------------------------------------------------------------------- |
76
+ | `theme.hue` (readonly) | The hue seed (0–360). |
77
+ | `theme.saturation` (readonly) | The saturation seed (0–100). |
78
+ | `theme.colors(defs)` | Add/replace colors (additive merge — adds new, overwrites existing by name, doesn't remove others). |
79
+ | `theme.color(name)` | Get a color definition by name. |
80
+ | `theme.color(name, def)` | Set a single color definition. |
81
+ | `theme.remove(name \| names[])` | Remove one or more color definitions. |
82
+ | `theme.has(name)` | Check if a color is defined. |
83
+ | `theme.list()` | List all defined color names. |
84
+ | `theme.reset()` | Clear all color definitions. |
85
+ | `theme.export(override?)` | Export the theme configuration as a JSON-safe object (optional config override at export time). |
86
+ | `theme.extend(options)` | Create a child theme inheriting all color definitions (see [`extend`](#themeextendoptions) below). |
87
+ | `theme.resolve()` | Resolve all colors and return a `Map<string, ResolvedColor>`. |
88
+ | `theme.tokens(options?)` | Export as a flat token map grouped by scheme variant. |
89
+ | `theme.tasty(options?)` | Export as [Tasty](https://tasty.style) style-to-state bindings. |
90
+ | `theme.json(options?)` | Export as plain JSON. |
91
+ | `theme.css(options?)` | Export as CSS custom property declarations. |
92
+ | `theme.dtcg(options?)` | Export one W3C DTCG token tree per scheme. |
93
+ | `theme.dtcgResolver(options?)` | Export one DTCG Resolver-Module document containing every scheme. |
94
+ | `theme.tailwind(options?)` | Export a Tailwind CSS v4 theme and scheme overrides. |
71
95
 
72
96
  ### `theme.colors(defs)`
73
97
 
74
98
  ```ts
75
- theme.colors({ surface: { lightness: 97 } });
76
- theme.colors({ text: { lightness: 30 } });
99
+ theme.colors({ surface: { tone: 97 } });
100
+ theme.colors({ text: { tone: 30 } });
77
101
  // Both 'surface' and 'text' are now defined.
78
102
  ```
79
103
 
80
104
  ### `theme.color(name) / theme.color(name, def)`
81
105
 
82
106
  ```ts
83
- theme.color('surface', { lightness: 97, saturation: 0.75 }); // set
84
- const def = theme.color('surface'); // get
107
+ theme.color('surface', { tone: 97, saturation: 0.75 }); // set
108
+ const def = theme.color('surface'); // get
85
109
  ```
86
110
 
87
111
  ### `theme.extend(options)`
@@ -91,73 +115,87 @@ Creates a new theme inheriting all color definitions, optionally replacing the h
91
115
  ```ts
92
116
  const danger = primary.extend({
93
117
  hue: 23,
94
- colors: { 'accent-fill': { lightness: 48, mode: 'fixed' } },
118
+ colors: { 'accent-fill': { tone: 48, mode: 'fixed' } },
95
119
  });
96
120
 
97
- // Inherit parent's config override and tighten the dark window further:
98
- const highSat = base.extend({ config: { darkLightness: [10, 100] } });
121
+ // Inherit parent's config override and widen the dark window further:
122
+ const highSat = base.extend({ config: { darkTone: [10, 100] } });
99
123
  ```
100
124
 
101
125
  `GlazeExtendOptions`:
102
126
 
103
- | Field | Type | Description |
104
- |---|---|---|
105
- | `hue` | `number` | Replace the hue seed. Defaults to the parent's hue. |
106
- | `saturation` | `number` | Replace the saturation seed. Defaults to the parent's saturation. |
107
- | `colors` | `ColorMap` | Per-theme overrides (additive merge over the inherited map). |
108
- | `config` | `GlazeConfigOverride` | Config override for the child. Shallow-merged with the parent's override — child fields win. |
127
+ | Field | Type | Description |
128
+ | ------------ | --------------------- | -------------------------------------------------------------------------------------------- |
129
+ | `hue` | `number` | Replace the hue seed. Defaults to the parent's hue. |
130
+ | `saturation` | `number` | Replace the saturation seed. Defaults to the parent's saturation. |
131
+ | `colors` | `ColorMap` | Per-theme overrides (additive merge over the inherited map). |
132
+ | `config` | `GlazeConfigOverride` | Config override for the child. Shallow-merged with the parent's override — child fields win. |
109
133
 
110
134
  Colors marked with `inherit: false` on the parent are **not** copied into the child.
111
135
 
136
+ ### `theme.resolve()`
137
+
138
+ Resolves the dependency graph and returns a
139
+ `Map<string, ResolvedColor>`. Export methods call it automatically; use it
140
+ directly for tests, diagnostics, or a custom output pipeline.
141
+
142
+ ```ts
143
+ const resolved = theme.resolve();
144
+ const surface = resolved.get('surface');
145
+ // surface?.light.t is canonical tone on 0–1.
146
+ ```
147
+
112
148
  ### `theme.tokens(options?)`
113
149
 
114
150
  Flat token map grouped by scheme variant.
115
151
 
116
152
  ```ts
117
- theme.tokens()
118
- // → { light: { surface: 'okhsl(...)' }, dark: { surface: 'okhsl(...)' } }
153
+ theme.tokens();
154
+ // → { light: { surface: 'oklch(...)' }, dark: { surface: 'oklch(...)' } }
119
155
  ```
120
156
 
121
157
  `GlazeJsonOptions`:
122
158
 
123
- | Option | Default | Description |
124
- |---|---|---|
125
- | `format` | `'okhsl'` | Output color format. One of `'okhsl' \| 'rgb' \| 'hsl' \| 'oklch'`. |
126
- | `modes` | `{ dark: true, highContrast: false }` (or global config) | Which scheme variants to include. |
159
+ | Option | Default | Description |
160
+ | -------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
161
+ | `format` | `'oklch'` | Output color format. One of `'rgb' \| 'hsl' \| 'oklch'`. `'okhsl'` and `'okhst'` throw — use `tasty()` for those. |
162
+ | `modes` | `{ dark: true, highContrast: false }` (or global config) | Which scheme variants to include. |
127
163
 
128
164
  ### `theme.tasty(options?)`
129
165
 
130
- Tasty style-to-state bindings for the [Tasty style system](https://tasty.style/docs). Uses `#name` color token keys and state aliases (`''`, `@dark`, etc.).
166
+ Style-to-state bindings for the [Tasty](https://tasty.style) style system. Uses `#name` color token keys and state aliases. By default the dark and high-contrast variants are keyed by media-query states (`'@media(prefers-color-scheme: dark)'`, `'@media(prefers-contrast: more)'`) so tokens work without registering custom states.
131
167
 
132
168
  ```ts
133
- theme.tasty()
169
+ theme.tasty();
134
170
  // → {
135
- // '#surface': { '': 'okhsl(...)', '@dark': 'okhsl(...)' },
171
+ // '#surface': { '': 'oklch(...)', '@media(prefers-color-scheme: dark)': 'oklch(...)' },
136
172
  // ...
137
173
  // }
138
174
  ```
139
175
 
140
176
  `GlazeTokenOptions`:
141
177
 
142
- | Option | Default | Description |
143
- |---|---|---|
144
- | `format` | `'okhsl'` | Output color format. |
145
- | `modes` | global config | Which scheme variants to include. |
146
- | `states.dark` | `'@dark'` (or global config) | State alias for dark mode tokens. |
147
- | `states.highContrast` | `'@high-contrast'` (or global config) | State alias for high-contrast tokens. |
148
- | `prefix` | (palette only) | See [Palette](#palette). |
178
+ | Option | Default | Description |
179
+ | --------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
180
+ | `format` | `'oklch'` | Output color format. `'okhsl'` and `'okhst'` are also supported here ([Tasty](https://tasty.style)-only spaces). |
181
+ | `modes` | global config | Which scheme variants to include. |
182
+ | `states.dark` | `'@media(prefers-color-scheme: dark)'` (or global config) | State alias for dark mode tokens. |
183
+ | `states.highContrast` | `'@media(prefers-contrast: more)'` (or global config) | State alias for high-contrast tokens. |
184
+ | `splitHue` | `false` | Emit hue as a separate custom property (`$name-hue` token + `var()` in `oklch` values). Requires `format: 'oklch'` and every color to be pastel. |
185
+ | `name` | `'theme'` | Base name for the theme-level hue var (`$theme-hue` / `--theme-hue`). Palette export auto-derives this from the theme name. |
186
+ | `prefix` | (palette only) | See [Palette](#palette). |
149
187
 
150
- When both `dark` and `highContrast` modes are enabled, dark high-contrast variants are emitted under the combined key `<dark> & <highContrast>` (e.g. `'@dark & @high-contrast'`).
188
+ When both `dark` and `highContrast` modes are enabled, dark high-contrast variants are emitted under the combined key `<dark> & <highContrast>` (e.g. `'@media(prefers-color-scheme: dark) & @media(prefers-contrast: more)'`).
151
189
 
152
190
  ### `theme.json(options?)`
153
191
 
154
192
  Per-color JSON map.
155
193
 
156
194
  ```ts
157
- theme.json()
195
+ theme.json();
158
196
  // → {
159
- // surface: { light: 'okhsl(...)', dark: 'okhsl(...)' },
160
- // text: { light: 'okhsl(...)', dark: 'okhsl(...)' },
197
+ // surface: { light: 'oklch(...)', dark: 'oklch(...)' },
198
+ // text: { light: 'oklch(...)', dark: 'oklch(...)' },
161
199
  // }
162
200
  ```
163
201
 
@@ -170,8 +208,8 @@ CSS custom property declaration strings, grouped by scheme variant.
170
208
  ```ts
171
209
  theme.css();
172
210
  // → {
173
- // light: '--surface-color: rgb(...);\n--text-color: rgb(...);',
174
- // dark: '--surface-color: rgb(...);\n--text-color: rgb(...);',
211
+ // light: '--surface-color: oklch(...);\n--text-color: oklch(...);',
212
+ // dark: '--surface-color: oklch(...);\n--text-color: oklch(...);',
175
213
  // lightContrast: '...',
176
214
  // darkContrast: '...',
177
215
  // }
@@ -179,23 +217,188 @@ theme.css();
179
217
 
180
218
  `GlazeCssOptions`:
181
219
 
182
- | Option | Default | Description |
183
- |---|---|---|
184
- | `format` | `'rgb'` | Output color format. |
185
- | `suffix` | `'-color'` | Suffix appended to each CSS property name. Pass `''` for bare property names. |
220
+ | Option | Default | Description |
221
+ | ---------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
222
+ | `format` | `'oklch'` | Output color format. One of `'rgb' \| 'hsl' \| 'oklch'`. `'okhsl'` and `'okhst'` throw — use `tasty()` for those. |
223
+ | `suffix` | `'-color'` | Suffix appended to each CSS property name. Pass `''` for bare property names. |
224
+ | `splitHue` | `false` | Emit hue as a separate `--*-hue` custom property referenced via `var()` in `oklch` color values. Requires `format: 'oklch'` and every color to be pastel. Shadow/mix colors stay inline (blended hue; they do not follow `--hue` rotation). |
225
+ | `name` | `'theme'` | Base name for the theme-level hue var (`--theme-hue`). Palette export auto-derives this from the theme name. |
186
226
 
187
227
  `GlazeCssResult` always contains all four keys (`light`, `dark`, `lightContrast`, `darkContrast`); empty if no colors are defined for that variant.
188
228
 
189
- ### `theme.export()`
229
+ ### `theme.dtcg(options?)`
230
+
231
+ W3C [Design Tokens Format Module (2025.10)](https://www.designtokens.org/) documents — the vendor-neutral JSON format consumed by Figma, Tokens Studio, Style Dictionary v4+, Terrazzo, Penpot, and every DTCG-compatible tool. Returns one spec-conformant token tree per scheme variant.
232
+
233
+ ```ts
234
+ theme.dtcg();
235
+ // → {
236
+ // light: {
237
+ // surface: {
238
+ // $type: 'color',
239
+ // $value: { colorSpace: 'srgb', components: [0.96, 0.94, 0.98], hex: '#f5f0fa' },
240
+ // },
241
+ // },
242
+ // dark: {
243
+ // surface: {
244
+ // $type: 'color',
245
+ // $value: { colorSpace: 'srgb', components: [0.16, 0.14, 0.2], hex: '#292333' },
246
+ // },
247
+ // },
248
+ // }
249
+ ```
250
+
251
+ Write each document to its own `.tokens.json` file — one file per scheme is the most tool-compatible convention (one per Style Dictionary theme / Tokens Studio set / Figma variable mode).
252
+
253
+ `GlazeDtcgOptions`:
254
+
255
+ | Option | Default | Description |
256
+ | ------------ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
257
+ | `colorSpace` | `'srgb'` | Color space for `$value`. `'srgb'` emits gamma sRGB `components` (0–1) plus a `hex` hint — universally understood. `'oklch'` emits `[L, C, H]` components with no hex — Glaze-native, wide-gamut. |
258
+ | `modes` | global config | Which scheme variants to include. `light` is always present. |
259
+
260
+ `alpha` is included on `$value` only when the color's opacity is below 1. `$type` is always `'color'`.
261
+
262
+ ### `theme.dtcgResolver(options?)`
263
+
264
+ A single W3C [DTCG Resolver-Module](https://www.designtokens.org/) document describing **every scheme variant in one file** — an alternative to `dtcg()`'s per-scheme files for tools that resolve sets + modifiers (e.g. Dispersa). The light document becomes `sets.base.sources[0]` (the default context); each other variant becomes a context override on a single `scheme` modifier.
265
+
266
+ ```ts
267
+ theme.dtcgResolver({ modes: { highContrast: true } });
268
+ // → {
269
+ // version: '2025.10',
270
+ // sets: {
271
+ // base: {
272
+ // sources: [
273
+ // {
274
+ // surface: {
275
+ // $type: 'color',
276
+ // $value: { colorSpace: 'srgb', components: [0.96, 0.94, 0.98], hex: '#f5f0fa' },
277
+ // },
278
+ // },
279
+ // ],
280
+ // },
281
+ // },
282
+ // modifiers: {
283
+ // scheme: {
284
+ // default: 'light',
285
+ // contexts: {
286
+ // light: [],
287
+ // dark: [
288
+ // {
289
+ // surface: {
290
+ // $type: 'color',
291
+ // $value: { colorSpace: 'srgb', components: [0.16, 0.14, 0.2], hex: '#292333' },
292
+ // },
293
+ // },
294
+ // ],
295
+ // lightContrast: [ /* … */ ],
296
+ // darkContrast: [ /* … */ ],
297
+ // },
298
+ // },
299
+ // },
300
+ // resolutionOrder: [
301
+ // { $ref: '#/sets/base' },
302
+ // { $ref: '#/modifiers/scheme' },
303
+ // ],
304
+ // }
305
+ ```
306
+
307
+ **Why one modifier with four contexts.** Glaze resolves `darkContrast` independently — it is not `dark` + `lightContrast` layered. The resolver model composes modifiers additively (last in `resolutionOrder` wins on conflict), so two independent modifiers (`scheme` × `contrast`) would produce wrong values for the dark + high-contrast permutation. One `scheme` modifier with a context per variant keeps every resolved value exact. Choose `dtcgResolver()` when you want single-file theming and feed it to a resolver tool; choose `dtcg()` for maximum per-file tool compatibility.
308
+
309
+ `GlazeDtcgResolverOptions` (extends `GlazeDtcgOptions`, so `modes` and `colorSpace` pass through):
310
+
311
+ | Option | Default | Description |
312
+ | -------------- | ------------- | ----------------------------------------------------------------------------------------------------------------- |
313
+ | `colorSpace` | `'srgb'` | Same as `dtcg()` — flows through to every source and context. |
314
+ | `modes` | global config | Which scheme variants to emit as contexts. `light` is always present (the default); absent variants are omitted. |
315
+ | `setName` | `'base'` | Name of the single set holding the default (light) token tree. |
316
+ | `modifierName` | `'scheme'` | Name of the modifier describing the scheme axis. |
317
+ | `contextNames` | identity | Override the four context names (`light` / `dark` / `lightContrast` / `darkContrast`) — e.g. `{ dark: 'night' }`. |
318
+ | `version` | `'2025.10'` | Resolver document version. |
319
+
320
+ ### `theme.tailwind(options?)`
321
+
322
+ A Tailwind CSS v4 `@theme` block (light baseline) plus dark / high-contrast overrides under configurable selectors. Returns a single ready-to-paste CSS string. The `--color-*` namespace auto-generates `bg-*` / `text-*` / `border-*` utilities.
323
+
324
+ ```css
325
+ @theme {
326
+ --color-surface: oklch(0.96 0.01 280);
327
+ --color-text: oklch(0.3 0.05 280);
328
+ }
329
+ .dark {
330
+ --color-surface: oklch(0.16 0.01 280);
331
+ --color-text: oklch(0.85 0.05 280);
332
+ }
333
+ .high-contrast {
334
+ --color-surface: oklch(0.98 0.01 280);
335
+ --color-text: oklch(0.1 0.05 280);
336
+ }
337
+ .dark.high-contrast {
338
+ --color-surface: oklch(0.05 0.01 280);
339
+ --color-text: oklch(0.95 0.05 280);
340
+ }
341
+ ```
342
+
343
+ `GlazeTailwindOptions`:
344
+
345
+ | Option | Default | Description |
346
+ | ---------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
347
+ | `format` | `'oklch'` | Output color format for the values. |
348
+ | `namespace` | `'color-'` | CSS custom property namespace, forming `--<namespace><name>` (e.g. `--color-surface`). Named `namespace` to avoid clashing with the palette theme-prefix option. |
349
+ | `darkSelector` | `'.dark'` | Selector wrapping the dark overrides. Pass an at-rule like `'@media (prefers-color-scheme: dark)'` to drive dark mode from the OS preference (it nests `:root` automatically). |
350
+ | `highContrastSelector` | `'.high-contrast'` | Selector wrapping the light high-contrast overrides. The combined dark + high-contrast block uses `${darkSelector}${highContrastSelector}` (e.g. `.dark.high-contrast`). |
351
+ | `modes` | global config | Which scheme variants to include. The `@theme` block (light) is always emitted when colors exist. |
352
+
353
+ ### `theme.export(override?)`
190
354
 
191
355
  ```ts
192
356
  const snapshot = theme.export();
193
- // → { hue: 280, saturation: 80, colors: { surface: { ... }, ... } }
357
+ // → {
358
+ // kind: 'theme',
359
+ // version: 1,
360
+ // hue: 280,
361
+ // saturation: 80,
362
+ // colors: { surface: { ... }, ... },
363
+ // config: { lightTone: {...}, darkTone: {...}, pastel: false, ... },
364
+ // }
365
+
366
+ const restored = glaze.themeFrom(snapshot);
367
+ ```
368
+
369
+ Returns a deep-cloned, JSON-safe authoring snapshot (definitions + frozen
370
+ effective config — not resolved color strings). Freezes
371
+ `getConfig() ∪ instance local ∪ override` at call time. Restored themes pin
372
+ that freeze as their local override. Distinct from `theme.json()`, which
373
+ emits resolved color strings.
374
+
375
+ ---
376
+
377
+ ## High-contrast pairs
194
378
 
195
- const restored = glaze.from(snapshot);
379
+ `HCPair<T>` means either one value used in both ordinary and high-contrast
380
+ schemes, or an explicit `[normal, highContrast]` pair:
381
+
382
+ ```ts
383
+ type HCPair<T> = T | [T, T];
384
+ ```
385
+
386
+ It is used by `tone`, `contrast`, shadow `intensity`, and mix `value`:
387
+
388
+ ```ts
389
+ tone: '-8'; // -8 in normal and HC
390
+ tone: ['-8', '-16']; // -8 normal, -16 HC
391
+ contrast: {
392
+ apca: 'content';
393
+ } // preset with automatic HC enhancement
394
+ contrast: {
395
+ apca: ['content', 'body'];
396
+ } // explicit normal/HC targets
196
397
  ```
197
398
 
198
- The export contains only the configuration not resolved color values. Resolved values are recomputed on demand.
399
+ For `contrast`, the pair may wrap the whole spec or live inside the selected
400
+ metric. An explicit HC value disables automatic APCA enhancement or WCAG preset
401
+ promotion for that color.
199
402
 
200
403
  ---
201
404
 
@@ -209,88 +412,233 @@ type ColorDef = RegularColorDef | ShadowColorDef | MixColorDef;
209
412
 
210
413
  ### `RegularColorDef`
211
414
 
212
- | Field | Type | Description |
213
- |---|---|---|
214
- | `lightness` | `HCPair<number \| RelativeValue>` | Number = absolute (0–100). String (`'+N'`/`'-N'`) = relative to base's lightness (requires `base`). Optional HC pair `[normal, hc]`. |
215
- | `saturation` | `number` | Saturation factor applied to the seed saturation (0–1). Default: `1`. |
216
- | `hue` | `number \| RelativeValue` | Number = absolute (0–360). String (`'+N'`/`'-N'`) = relative to the **theme seed hue** (never to a base color). |
217
- | `base` | `string` | Name of another color in the same theme — makes this a *dependent* color. |
218
- | `contrast` | `HCPair<MinContrast>` | WCAG contrast floor against `base`. Requires `base`. |
219
- | `mode` | `'auto' \| 'fixed' \| 'static'` | Adaptation mode. Default: `'auto'`. See [Adaptation modes](#adaptation-modes). |
220
- | `opacity` | `number` | Fixed alpha 0–1. Output includes alpha in the CSS value. Combining with `contrast` is not recommended (a `console.warn` is emitted). |
221
- | `inherit` | `boolean` | Whether this color is inherited by child themes via `extend()`. Default: `true`. Set to `false` to make the color local to the current theme. |
415
+ | Field | Type | Description |
416
+ | ------------ | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
417
+ | `tone` | `HCPair<ToneValue>` | Number = absolute (0–100). `'+N'`/`'-N'` = a signed **tone delta** from the base (requires `base`). `'max'`/`'min'` = forced to the scheme's tone extreme (no `base`). Optional HC pair `[normal, hc]`. |
418
+ | `saturation` | `number` | Saturation factor applied to the seed saturation (0–1). Default: `1`. |
419
+ | `hue` | `number \| RelativeValue` | Number = absolute (0–360). String (`'+N'`/`'-N'`) = relative to the **theme seed hue** (never to a base color). |
420
+ | `base` | `string` | Name of another color in the same theme — makes this a _dependent_ color. |
421
+ | `contrast` | `HCPair<ContrastSpec>` | Contrast floor against `base`. Requires `base`. See [`contrast`](#contrast-floor). |
422
+ | `mode` | `'auto' \| 'fixed' \| 'static'` | Adaptation mode. Default: `'auto'`. See [Adaptation modes](#adaptation-modes). |
423
+ | `autoFlip` | `boolean` | Flip out-of-bounds results (relative `tone` overshoot / unmet `contrast`) to the opposite side instead of clamping. Default: the global `autoFlip` (`true`). See [`autoFlip`](#autoflip). |
424
+ | `opacity` | `number` | Fixed alpha 0–1. Output includes alpha in the CSS value. Combining with `contrast` is not recommended (a `console.warn` is emitted). |
425
+ | `pastel` | `boolean` | Per-color override for the hue-independent "safe" chroma limit used in OKHSL↔sRGB conversions (luminance, contrast solving, output formatting). Falls through to the per-theme / per-token `pastel` override when omitted. Default: unset. See [Per-color `pastel`](#per-color-pastel). |
426
+ | `role` | `RoleInput` | Semantic role against `base` (`'text'` / `'surface'` / `'border'` or an alias). Fixes APCA contrast polarity. Resolved via: explicit `role` → name inference → opposite of the base's role → `'text'`. See [Roles](#roles). |
427
+ | `inherit` | `boolean` | Whether this color is inherited by child themes via `extend()`. Default: `true`. Set to `false` to make the color local to the current theme. |
428
+
429
+ #### Tone values
430
+
431
+ `tone` (0–100) replaces authored OKHSL lightness with a contrast-shaped axis.
432
+ Equal tone differences give equal WCAG contrast for neutrals; chromatic results
433
+ can drift in measured luminance. See [OKHST in Glaze](okhst.md). To port old
434
+ `lightness` values, see [migration.md](migration.md).
435
+
436
+ | Form | Example | Meaning |
437
+ | ------------------- | ----------------------- | --------------------------------------------------------------------------------------------- |
438
+ | Number (absolute) | `tone: 45` | Absolute tone 0–100. |
439
+ | String (tone delta) | `tone: '-52'` | Signed difference from the base color's resolved tone (requires `base`). |
440
+ | Extreme | `tone: 'max'` / `'min'` | Force to the scheme's highest (`'max'` = 100) or lowest (`'min'` = 0) tone. No `base` needed. |
441
+ | HC pair | `tone: ['-7', '-20']` | `[normal, high-contrast]`. A single value applies to both. |
442
+
443
+ **Absolute tone** on a dependent color (`base` set) positions the color independently. In dark mode it is tone-mapped (inverted + windowed) on its own. The `contrast` solver acts as a safety net.
222
444
 
223
- #### Lightness values
445
+ **A tone delta** applies a signed difference to the base color's resolved tone.
446
+ It gives an exact contrast step for neutrals and a stable visual progression for
447
+ chromatic colors. In dark mode with `mode: 'auto'`, it is anchored to the
448
+ base's per-scheme tone. If `base + delta` falls outside `[0, 100]`, the result
449
+ is clamped to the boundary, or — with `autoFlip` (default on) — mirrored to the
450
+ other side of the base. If the mirrored target is also out of range, the original
451
+ delta is kept and clamped on the authored side.
224
452
 
225
- | Form | Example | Meaning |
226
- |---|---|---|
227
- | Number (absolute) | `lightness: 45` | Absolute lightness 0–100. |
228
- | String (relative) | `lightness: '-52'` | Relative to base color's lightness (requires `base`). |
229
- | HC pair | `lightness: ['-7', '-20']` | `[normal, high-contrast]`. A single value applies to both. |
453
+ **Extreme tone** (`'max'` / `'min'`) forces the color to the scheme's tone extreme without a contrast hack or a magic number. `'max'` resolves to author tone 100 and `'min'` to 0; both flow through scheme mapping like an absolute tone, so under `mode: 'auto'` they invert in dark (`'max'` is lightest in light, darkest in dark). Use `mode: 'static'` to pin the same extreme across schemes, or `mode: 'fixed'` to keep the same end without inverting. No `base` required.
230
454
 
231
- **Absolute lightness** on a dependent color (`base` set) positions the color independently. In dark mode it is dark-mapped on its own. The `contrast` solver acts as a safety net.
455
+ A dependent color with `base` but no `tone` inherits the base's tone (equivalent to a delta of 0).
232
456
 
233
- **Relative lightness** applies a signed delta to the base color's resolved lightness. In dark mode with `mode: 'auto'`, the sign flips automatically so a `'-52'` light-mode offset becomes a `+52` dark-mode offset.
457
+ #### `autoFlip`
234
458
 
235
- A dependent color with `base` but no `lightness` inherits the base's lightness (equivalent to a delta of 0).
459
+ `autoFlip` governs what happens when a result would fall outside its valid range:
236
460
 
237
- #### `contrast` (WCAG floor)
461
+ - **Relative `tone` overshoot:** when `base ± delta` exceeds `[0, 100]`, `autoFlip` mirrors the delta to the other side of the base (e.g. `'+30'` becomes `'-30'`) instead of clamping to the boundary. If the mirrored target is also outside `[0, 100]`, the original delta is kept and clamped on the authored side.
462
+ - **`contrast` direction:** when the requested tone direction can't meet the floor, `autoFlip` lets the solver try the opposite side (the same behavior as the global `autoFlip`).
463
+
464
+ `autoFlip` defaults to the global `autoFlip` (`true`). Set `autoFlip: false` on a color to clamp instead of mirror — useful when you want a relative offset to stay on the authored side of the base, or to keep an unmet contrast pinned to one direction's extreme.
465
+
466
+ #### `contrast` (floor)
467
+
468
+ ```ts
469
+ type ContrastPreset = 'AA' | 'AAA' | 'AA-large' | 'AAA-large';
470
+ type ContrastSpec =
471
+ | number // bare WCAG ratio
472
+ | ContrastPreset // named WCAG preset
473
+ | { wcag: HCPair<number | ContrastPreset> }
474
+ | { apca: HCPair<number> }; // APCA Lc target
475
+ ```
476
+
477
+ | Preset | WCAG ratio |
478
+ | ------------- | ---------- |
479
+ | `'AA-large'` | 3 |
480
+ | `'AA'` | 4.5 |
481
+ | `'AAA-large'` | 4.5 |
482
+ | `'AAA'` | 7 |
483
+
484
+ A bare number or preset means **WCAG**. Use `{ wcag }` / `{ apca }` to pick the metric explicitly. The `[normal, highContrast]` pair may live at the outer level (`[4.5, 7]`, `[{ wcag: 4.5 }, { wcag: 7 }]`) or inside the metric (`{ wcag: [4.5, 7] }`, `{ apca: [45, 60] }`).
238
485
 
239
486
  ```ts
240
- type MinContrast = number | 'AA' | 'AAA' | 'AA-large' | 'AAA-large';
487
+ contrast: 4.5; // WCAG 4.5
488
+ contrast: 'AAA'; // WCAG 7
489
+ contrast: {
490
+ wcag: 6;
491
+ } // WCAG 6
492
+ contrast: {
493
+ wcag: [4.5, 7];
494
+ } // WCAG 4.5 normal / 7 high-contrast (explicit)
495
+ contrast: {
496
+ apca: 60;
497
+ } // APCA Lc 60 normal / 75 high-contrast (auto)
498
+ contrast: {
499
+ apca: [45, 60];
500
+ } // APCA Lc 45 normal / 60 high-contrast (explicit)
501
+ contrast: {
502
+ apca: 'content';
503
+ } // APCA preset -> Lc 60 normal / 75 high-contrast (auto)
504
+ contrast: {
505
+ apca: ['content', 'body'];
506
+ } // Lc 60 normal / 75 high-contrast (explicit)
241
507
  ```
242
508
 
243
- | Preset | Ratio |
244
- |---|---|
245
- | `'AA-large'` | 3 |
246
- | `'AA'` | 4.5 |
247
- | `'AAA-large'` | 4.5 |
248
- | `'AAA'` | 7 |
509
+ **WCAG HC auto-promotion:** a bare WCAG preset (no `[normal, hc]` pair at either
510
+ the outer `contrast` or inner `wcag` level) is automatically promoted to its
511
+ spec-defined "Enhanced" successor in high-contrast mode — `AA` `AAA` (4.5 → 7)
512
+ and `AA-large` `AAA-large` (3 → 4.5), per WCAG SC 1.4.3 → 1.4.6. `AAA` and
513
+ `AAA-large` are already the top WCAG tier and are left unchanged; bare numeric
514
+ targets have no successor tier and are also left unchanged. An explicit HC value
515
+ via either pair overrides and skips the promotion.
516
+
517
+ **APCA Enhanced Level (HC auto-boost):** a bare APCA scalar (no `[normal, hc]`
518
+ pair at either the outer `contrast` or inner `apca` level) is automatically
519
+ boosted by **+15 Lc** in high-contrast mode, the APCA analog of WCAG's
520
+ AAA-over-AA step. On by default; an explicit HC value via either pair
521
+ overrides it and skips the boost. The enhanced target is clamped to 106 Lc.
522
+ For large/bold text (where APCA caps contrast at Lc 90 to avoid glare), pass
523
+ an explicit HC pair to hold that ceiling.
524
+
525
+ APCA preset keywords (Bronze Simple Mode conformance levels, role-independent):
526
+ `'preferred'` (Lc 90), `'body'` (75), `'content'` (60, ~AA), `'large'` (45, ~3:1),
527
+ `'non-text'` (30), `'min'` (15, point of invisibility).
528
+
529
+ The floor is applied independently per scheme. If the preferred `tone` already
530
+ satisfies it, the tone is kept; otherwise the solver uses the tone-shaped scale
531
+ for a closed-form WCAG seed and fast search until the target is met.
249
532
 
250
- You can also pass any numeric ratio directly (e.g., `contrast: 4.5`, `contrast: 11`). The constraint is applied independently for each schemeif the `lightness` already satisfies the floor it's kept, otherwise the solver adjusts lightness until the target is met.
533
+ By default, the solver crosses to the opposite side of the base color when the requested tone direction cannot satisfy the floor. This is controlled per-color by [`autoFlip`](#autoflip) (which defaults to the global `autoFlip`). Set `glaze.configure({ autoFlip: false })` or `autoFlip: false` on a single colorto keep strict directionality: unmet colors pin to that direction's 0 or 100 tone extreme instead of falling back to the original requested value.
251
534
 
252
- By default, `autoFlip` lets the solver cross to the opposite side of the base color when the requested lightness direction cannot satisfy contrast. Set `glaze.configure({ autoFlip: false })` to keep strict directionality: unmet colors pin to that direction's 0 or 100 lightness extreme instead of falling back to the original requested value.
535
+ **Full tone spectrum in HC mode:** in high-contrast variants the `lightTone` and `darkTone` window constraints are bypassed entirely (the window is forced to `[0, 100]`). Colors can reach the full range, maximizing perceivable contrast.
253
536
 
254
- **Full lightness spectrum in HC mode:** in high-contrast variants the `lightLightness` and `darkLightness` window constraints are bypassed entirely. Colors can reach the full 0–100 range, maximizing perceivable contrast.
537
+ **Chromatic drift (verification):** tone is contrast-uniform for grays. A chromatic swatch at a given tone shares its OKHSL lightness with the equivalent gray but drifts in real luminance, so a contrast-floored color may land slightly under its gray-tone expectation. Glaze measures the resolved result against the base and emits a deduped advisory `console.warn` when it drifts below the target. See [Contrast verification](okhst.md#contrast-verification).
255
538
 
256
539
  #### Per-color hue override
257
540
 
258
541
  ```ts
259
542
  const theme = glaze(280, 80);
260
543
  theme.colors({
261
- surface: { lightness: 97 },
262
- gradientEnd: { lightness: 90, hue: '+20' }, // 280 + 20 = 300
263
- warning: { lightness: 60, hue: 40 }, // absolute
544
+ surface: { tone: 97 },
545
+ gradientEnd: { tone: 90, hue: '+20' }, // 280 + 20 = 300
546
+ warning: { tone: 60, hue: 40 }, // absolute
264
547
  });
265
548
  ```
266
549
 
267
550
  Relative hue is always relative to the **theme seed hue**, not to a base color.
268
551
 
552
+ #### Per-color `pastel`
553
+
554
+ `pastel: true` on a single color def overrides the per-theme / per-token `pastel` override for that color only. It toggles the hue-independent "safe" chroma limit used in every OKHSL↔sRGB conversion that touches this color: luminance calculations during contrast solving, gamut clamping during sRGB blend / mix edges, and output formatting. The effective flag is carried on the resolved variant (`ResolvedColorVariant.pastel`) so formatting matches the gamut mapping applied during resolution.
555
+
556
+ ```ts
557
+ const theme = glaze(280, 80);
558
+ theme.colors({
559
+ plain: { tone: 50, saturation: 1 },
560
+ soft: { tone: 50, saturation: 1, pastel: true },
561
+ });
562
+ // theme.resolve().get('soft')!.light.pastel === true
563
+ // theme.css().light contains different rgb() triples for `--plain` and `--soft`
564
+ ```
565
+
566
+ Omit the field to inherit the theme/token `pastel` override (default `false`) — useful for keeping the default behavior while opting a single accent into the pastel gamut.
567
+
568
+ The flag is part of the def object, so `extend()` copies it through to child themes alongside the rest of the def. Override it again on the child to flip a single color back:
569
+
570
+ ```ts
571
+ const parent = glaze(280, 80);
572
+ parent.colors({ soft: { tone: 50, saturation: 1, pastel: true } });
573
+
574
+ const child = parent.extend({
575
+ colors: { soft: { tone: 50, saturation: 1, pastel: false } },
576
+ });
577
+ // child.resolve().get('soft')!.light.pastel === false
578
+ ```
579
+
580
+ > **Note:** Per-color `pastel` is also supported on `ShadowColorDef` and `MixColorDef` (see the tables above). For shadows the math itself happens in OKHSL space, so the flag mainly controls the gamut-mapped output formatting and any luminance verification for that variant.
581
+ >
582
+ > Standalone `glaze.color()` tokens accept the same `pastel` field on both the structured (`GlazeColorInput`) and value-shorthand (`GlazeColorOverrides`) forms, and it survives the `export()` / `glaze.colorFrom()` round-trip.
583
+
584
+ #### Roles
585
+
586
+ A color's `role` describes how it is used against its `base` and fixes **APCA contrast polarity** — which side is the foreground vs the background. APCA is asymmetric (`|apca(a,b)| ≠ |apca(b,a)|`), so the role picks the correct argument order; WCAG is symmetric and unaffected.
587
+
588
+ | Role | Polarity | Use | Aliases (name inference) |
589
+ | ----------- | -------- | ---------------------------------------------------- | ----------------------------------------------------------------- |
590
+ | `'text'` | fg | Text / icons / foreground content | `text`, `fg`, `foreground`, `content`, `ink`, `label`, `stroke` |
591
+ | `'border'` | fg | Non-text spot elements (borders, dividers, outlines) | `border`, `divider`, `outline`, `separator`, `hairline`, `rule` |
592
+ | `'surface'` | bg | Backgrounds / fills | `surface`, `bg`, `background`, `fill`, `canvas`, `paper`, `layer` |
593
+
594
+ Resolution chain (per color):
595
+
596
+ 1. Explicit `role` (normalized from an alias) wins.
597
+ 2. Else, when `inferRole` is enabled (default), infer from the color name — the **last** recognized token wins (`button-text` → `text`, `input-bg` → `surface`, `card-outline` → `border`).
598
+ 3. Else, the opposite of the base's role (a `surface` base ⇒ this is `text`).
599
+ 4. Else, `'text'` (foreground) — i.e. the base is treated as the background.
600
+
601
+ ```ts
602
+ const theme = glaze(280, 60);
603
+ theme.colors({
604
+ surface: { tone: 90 },
605
+ text: { base: 'surface', contrast: { apca: 'content' } }, // inferred text
606
+ border: { base: 'surface', tone: '-10' }, // inferred border
607
+ });
608
+ // role fixes APCA polarity; set `pastel: true` explicitly if a border
609
+ // needs the hue-independent safe chroma limit.
610
+ ```
611
+
612
+ Disable name inference with `glaze.configure({ inferRole: false })` (the base-opposite and foreground-default fallbacks still apply).
613
+
269
614
  ### `ShadowColorDef`
270
615
 
271
- | Field | Type | Description |
272
- |---|---|---|
273
- | `type` | `'shadow'` | Discriminator. |
274
- | `bg` | `string` | Background color name — must reference a non-shadow color in the same theme. |
275
- | `fg` | `string` | Optional foreground color name for tinting and intensity modulation. Must reference a non-shadow color. Omit for an achromatic shadow at full user-specified intensity. |
276
- | `intensity` | `HCPair<number>` | Shadow intensity, 0–100. Supports HC pairs. |
277
- | `tuning` | `ShadowTuning` | Per-color tuning overrides. Merged field-by-field with the global `shadowTuning`. |
278
- | `inherit` | `boolean` | Inheritance flag, default `true`. |
616
+ | Field | Type | Description |
617
+ | ----------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
618
+ | `type` | `'shadow'` | Discriminator. |
619
+ | `bg` | `string` | Background color name — must reference a non-shadow color in the same theme. |
620
+ | `fg` | `string` | Optional foreground color name for tinting and intensity modulation. Must reference a non-shadow color. Omit for an achromatic shadow at full user-specified intensity. |
621
+ | `intensity` | `HCPair<number>` | Shadow intensity, 0–100. Supports HC pairs. |
622
+ | `tuning` | `ShadowTuning` | Per-color tuning overrides. Merged field-by-field with the global `shadowTuning`. |
623
+ | `pastel` | `boolean` | Per-color `pastel` override. See [Per-color `pastel`](#per-color-pastel). |
624
+ | `inherit` | `boolean` | Inheritance flag, default `true`. |
279
625
 
280
626
  See [Shadows](#shadows) below for the algorithm and tuning details.
281
627
 
282
628
  ### `MixColorDef`
283
629
 
284
- | Field | Type | Description |
285
- |---|---|---|
286
- | `type` | `'mix'` | Discriminator. |
287
- | `base` | `string` | "From" color name. |
288
- | `target` | `string` | "To" color name. |
289
- | `value` | `HCPair<number>` | Mix ratio 0–100 (0 = pure base, 100 = pure target). In `'transparent'` blend, this becomes the target's opacity. Supports HC pairs. |
290
- | `blend` | `'opaque' \| 'transparent'` | Default `'opaque'`. |
291
- | `space` | `'okhsl' \| 'srgb'` | Interpolation space for opaque blending. Default `'okhsl'`. Ignored for `'transparent'` (always composites in linear sRGB). |
292
- | `contrast` | `HCPair<MinContrast>` | Optional WCAG floor against `base`. The solver adjusts the mix ratio (opaque) or opacity (transparent). |
293
- | `inherit` | `boolean` | Inheritance flag, default `true`. |
630
+ | Field | Type | Description |
631
+ | ---------- | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
632
+ | `type` | `'mix'` | Discriminator. |
633
+ | `base` | `string` | "From" color name. |
634
+ | `target` | `string` | "To" color name. |
635
+ | `value` | `HCPair<number>` | Mix ratio 0–100 (0 = pure base, 100 = pure target). In `'transparent'` blend, this becomes the target's opacity. Supports HC pairs. |
636
+ | `blend` | `'opaque' \| 'transparent'` | Default `'opaque'`. |
637
+ | `space` | `'okhsl' \| 'srgb'` | Interpolation space for opaque blending. Default `'okhsl'`. Ignored for `'transparent'` (always composites in linear sRGB). |
638
+ | `contrast` | `HCPair<ContrastSpec>` | Optional contrast floor against `base` (WCAG or APCA — see [`contrast`](#contrast-floor)). The solver adjusts the mix ratio (opaque) or opacity (transparent). |
639
+ | `pastel` | `boolean` | Per-color `pastel` override. See [Per-color `pastel`](#per-color-pastel). |
640
+ | `role` | `RoleInput` | Semantic role of the mixed result against `base`. Same semantics as `RegularColorDef.role` (see [Roles](#roles)). |
641
+ | `inherit` | `boolean` | Inheritance flag, default `true`. |
294
642
 
295
643
  See [Mix colors](#mix-colors) below.
296
644
 
@@ -310,130 +658,149 @@ glaze.color(color: GlazeFromInput | GlazeColorInput | GlazeColorValue, config?:
310
658
 
311
659
  `glaze.color()` accepts **four input shapes**, discriminated by structure:
312
660
 
313
- | Shape | Example | Notes |
314
- |---|---|---|
315
- | **Bare string** | `'#26fcb2'` | Hex or CSS color function (`rgb()`, `hsl()`, `okhsl()`, `oklch()`). |
316
- | **Value object** | `{ h: 152, s: 0.95, l: 0.74 }` | OKHSL, `{ r, g, b }` (sRGB 0–255), or `{ l, c, h }` (OKLCh). |
317
- | **`{ from, ...overrides }`** | `{ from: '#1a1a2e', base: bg, contrast: 'AA' }` | Value + color overrides in one object. |
318
- | **Structured** | `{ hue: 152, saturation: 95, lightness: 74 }` | Full theme-style token (hue/saturation/lightness all in 0–100). |
661
+ | Shape | Example | Notes |
662
+ | ---------------------------- | ----------------------------------------------- | ----------------------------------------------------------------------------------- |
663
+ | **Bare string** | `'#26fcb2'` | Hex or CSS color function (`rgb()`, `hsl()`, `okhsl()`, `okhst()`, `oklch()`). |
664
+ | **Value object** | `{ h: 152, s: 0.95, l: 0.74 }` | OKHSL, OKHST (`{ h, s, t }`), `{ r, g, b }` (sRGB 0–255), or `{ l, c, h }` (OKLCh). |
665
+ | **`{ from, ...overrides }`** | `{ from: '#1a1a2e', base: bg, contrast: 'AA' }` | Value + color overrides in one object. |
666
+ | **Structured** | `{ hue: 152, saturation: 95, tone: 74 }` | Full theme-style token (hue/saturation in 0–100, tone in 0–100). |
319
667
 
320
668
  `GlazeColorValue` (bare string or value-object forms) accepts:
321
669
 
322
- | Form | Example | Notes |
323
- |---|---|---|
324
- | Hex | `'#26fcb2'`, `'#26fcb2ff'`, `'#abc'` | 3, 6, or 8 digits. Alpha is dropped with a `console.warn` — use `opacity` instead. |
325
- | `rgb()` | `'rgb(38 252 178)'`, `'rgb(38 252 178 / 0.8)'` | Modern space syntax. Alpha dropped with warning. |
326
- | `hsl()` | `'hsl(152 97% 57%)'` | Modern space syntax. Alpha dropped with warning. |
327
- | `okhsl()` | `'okhsl(152 95% 74%)'` | Glaze's own emit format. Alpha dropped with warning. |
328
- | `oklch()` | `'oklch(0.85 0.18 152)'` | Glaze's own emit format. Alpha dropped with warning. |
329
- | `OkhslColor` object | `{ h: 152, s: 0.95, l: 0.74 }` | Glaze's native shape (h: 0–360, s/l: 0–1). Passing 0–100 for `s`/`l` throws with a hint to use the structured form. |
330
- | `RgbColor` object | `{ r: 38, g: 252, b: 178 }` | sRGB 0–255. RGB tuple `[r, g, b]` is not supported use this object form. |
331
- | `OklchColor` object | `{ l: 0.85, c: 0.18, h: 152 }` | OKLCh (L/C: 0–1, H: degrees), same semantics as `oklch()` strings. |
332
-
333
- `GlazeColorInput` (structured form) is `{ hue, saturation, lightness, ... }`:
334
-
335
- | Field | Type | Description |
336
- |---|---|---|
337
- | `hue` | `number` | 0–360. |
338
- | `saturation` | `number` | 0–100. |
339
- | `lightness` | `HCPair<number>` | 0–100, optional HC pair. |
340
- | `saturationFactor` | `number` | Multiplier on the seed (0–1). Default: `1`. |
341
- | `mode` | `AdaptationMode` | Default: `'auto'`. |
342
- | `opacity` | `number` | Fixed alpha 0–1. |
343
- | `base` | `GlazeColorToken \| GlazeColorValue` | Optional dependency. See [Pairing colors](#pairing-colors). |
344
- | `contrast` | `HCPair<MinContrast>` | WCAG floor against `base`. Without `base`, anchored to the literal seed. |
345
- | `name` | `string` | Debug label for warnings; doesn't change output keys. Reserved names (`'value'`, `'seed'`, `'externalBase'`) are rejected. |
670
+ | Form | Example | Notes |
671
+ | ------------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
672
+ | Hex | `'#26fcb2'`, `'#26fcb2ff'`, `'#abc'` | 3, 6, or 8 digits. Alpha is dropped with a `console.warn` — use `opacity` instead. |
673
+ | `rgb()` | `'rgb(38 252 178)'`, `'rgb(38 252 178 / 0.8)'` | Modern space syntax. Alpha dropped with warning. |
674
+ | `hsl()` | `'hsl(152 97% 57%)'` | Modern space syntax. Alpha dropped with warning. |
675
+ | `okhsl()` | `'okhsl(152 95% 74%)'` | Glaze's own emit format. Alpha dropped with warning. |
676
+ | `okhst()` | `'okhst(152 95% 70%)'` | OKHST tone input (third value is tone 0–100). Not native CSS; it can also be serialized by [Tasty](https://tasty.style) exports. Alpha dropped with warning. |
677
+ | `oklch()` | `'oklch(0.85 0.18 152)'` | Glaze's own emit format. Alpha dropped with warning. |
678
+ | `OkhslColor` object | `{ h: 152, s: 0.95, l: 0.74 }` | OKHSL shape (h: 0–360, s/l: 0–1). Passing 0–100 for `s`/`l` throws with a hint to use the structured form. |
679
+ | `OkhstColor` object | `{ h: 152, s: 0.95, t: 0.70 }` | Direct OKHST input shape (h: 0–360, s/t: 0–1). The `t` key disambiguates it from `{ h, s, l }`. |
680
+ | `RgbColor` object | `{ r: 38, g: 252, b: 178 }` | sRGB 0–255. RGB tuple `[r, g, b]` is not supported — use this object form. |
681
+ | `OklchColor` object | `{ l: 0.85, c: 0.18, h: 152 }` | OKLCh (L/C: 0–1, H: degrees), same semantics as `oklch()` strings. |
682
+
683
+ `GlazeColorInput` (structured form) is `{ hue, saturation, tone, ... }`:
684
+
685
+ | Field | Type | Description |
686
+ | ------------------ | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
687
+ | `hue` | `number` | 0–360. |
688
+ | `saturation` | `number` | 0–100. |
689
+ | `tone` | `HCPair<number \| ExtremeValue>` | 0–100 (contrast-shaped) or `'max'`/`'min'`, optional HC pair. |
690
+ | `saturationFactor` | `number` | Multiplier on the seed (0–1). Default: `1`. |
691
+ | `mode` | `AdaptationMode` | Default: `'auto'`. |
692
+ | `autoFlip` | `boolean` | Flip out-of-bounds results instead of clamping. Default: global `autoFlip`. |
693
+ | `opacity` | `number` | Fixed alpha 0–1. |
694
+ | `base` | `GlazeColorToken \| GlazeColorValue` | Optional dependency. See [Pairing colors](#pairing-colors). |
695
+ | `contrast` | `HCPair<ContrastSpec>` | Contrast floor against `base` (WCAG or APCA). Without `base`, anchored to the literal seed. |
696
+ | `pastel` | `boolean` | Per-color `pastel` override. Falls through to the per-theme / per-token `pastel` override when omitted. See [Per-color `pastel`](#per-color-pastel). |
697
+ | `role` | `RoleInput` | Semantic role against `base` / the seed (see [Roles](#roles)). Fixes APCA polarity. |
698
+ | `name` | `string` | Debug label for warnings; doesn't change output keys. Reserved names (`'value'`, `'seed'`, `'externalBase'`) are rejected. |
346
699
 
347
700
  `GlazeFromInput` (from form) is `{ from: GlazeColorValue, ...colorOverrides }`:
348
701
 
349
- | Field | Notes |
350
- |---|---|
351
- | `from` | **Required.** The source color value — same forms as `GlazeColorValue`. |
352
- | `hue` | Number (absolute 0–360) or `'+N'`/`'-N'` (relative to seed, never to `base`). |
353
- | `saturation` | Override seed saturation (0–100). |
354
- | `lightness` | Number (absolute 0–100) or `'+N'`/`'-N'`. Without `base`, relative anchors to the seed; with `base`, anchors to `base`'s lightness per scheme. |
355
- | `saturationFactor` | Multiplier on the seed (0–1). |
356
- | `mode` | `'auto'` (default) / `'fixed'` / `'static'`. |
357
- | `contrast` | WCAG floor. Without `base`, anchored to the literal seed; with `base`, solved per scheme. |
358
- | `base` | `GlazeColorToken` or raw `GlazeColorValue`. See [Pairing colors](#pairing-colors). |
359
- | `opacity` | Fixed alpha 0–1. Combining with `contrast` is not recommended `console.warn` is emitted. |
360
- | `name` | Debug label only surfaces in warnings/errors. Does not change output keys. |
702
+ | Field | Notes |
703
+ | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
704
+ | `from` | **Required.** The source color value — same forms as `GlazeColorValue`. |
705
+ | `hue` | Number (absolute 0–360) or `'+N'`/`'-N'` (relative to seed, never to `base`). |
706
+ | `saturation` | Override seed saturation (0–100). |
707
+ | `tone` | Number (absolute 0–100), `'+N'`/`'-N'`, or `'max'`/`'min'`. Without `base`, relative anchors to the seed; with `base`, anchors to `base`'s tone per scheme. |
708
+ | `saturationFactor` | Multiplier on the seed (0–1). |
709
+ | `mode` | `'auto'` (default) / `'fixed'` / `'static'`. |
710
+ | `autoFlip` | Flip out-of-bounds results instead of clamping. Default: global `autoFlip`. |
711
+ | `contrast` | Contrast floor (WCAG or APCA). Without `base`, anchored to the literal seed; with `base`, solved per scheme. |
712
+ | `base` | `GlazeColorToken` or raw `GlazeColorValue`. See [Pairing colors](#pairing-colors). |
713
+ | `opacity` | Fixed alpha 0–1. Combining with `contrast` is not recommended `console.warn` is emitted. |
714
+ | `pastel` | Per-color `pastel` override. Falls through to the per-theme / per-token `pastel` override when omitted. See [Per-color `pastel`](#per-color-pastel). |
715
+ | `role` | Semantic role against `base` / the seed (see [Roles](#roles)). Fixes APCA polarity. |
716
+ | `name` | Debug label only — surfaces in warnings/errors. Does not change output keys. |
361
717
 
362
718
  Named CSS colors (`'red'`, `'blueviolet'`) are not supported.
363
719
 
364
720
  ### Defaults
365
721
 
366
- Every input form defaults to `mode: 'auto'` so the resolved token adapts between light and dark like an ordinary theme color. The config snapshot taken at create time differs by input form:
722
+ Every input form defaults to `mode: 'auto'` so the resolved token adapts between light and dark like an ordinary theme color. Tokens store a **sparse local** config override; omitted fields fall through to the live global at resolve time (same as themes). Authoring `.export(override?)` freezes `getConfig() ∪ local ∪ override` at call time.
367
723
 
368
724
  - **Value-shorthand** (bare strings, value objects, and `{ from, ...overrides }`):
369
- - Light variant preserves the input lightness exactly (`lightLightness: false`).
370
- - All other config fields (`darkLightness`, `darkDesaturation`, `darkCurve`, `autoFlip`) snapshot from `globalConfig` at create time.
371
- - **Structured input** (`{ hue, saturation, lightness, ... }`):
372
- - Both lightness windows snapshot from `globalConfig` at create time (same as a theme color).
373
- - All fields are **snapshotted at color-creation time** later `glaze.configure()` calls don't retroactively change existing tokens.
725
+ - Light variant preserves the input tone exactly (`lightTone: false` as a local default).
726
+ - Other omitted fields track the live global config.
727
+ - **Structured input** (`{ hue, saturation, tone, ... }`):
728
+ - Omitted tone windows and other fields track the live global config (same as a theme color).
729
+ - `pastel` is instance-only set via the config override or per-color `pastel`, not `glaze.configure()`.
374
730
 
375
731
  ```ts
376
732
  // Bare string — adapts automatically
377
- glaze.color('#26fcb2')
733
+ glaze.color('#26fcb2');
378
734
 
379
735
  // Value-object — same behavior
380
- glaze.color({ h: 152, s: 0.95, l: 0.74 })
736
+ glaze.color({ h: 152, s: 0.95, l: 0.74 });
737
+
738
+ // OKHST value-object — tone axis
739
+ glaze.color({ h: 152, s: 0.95, t: 0.7 });
381
740
 
382
741
  // From form — value + color overrides
383
- glaze.color({ from: '#1a1a2e', hue: '+20', contrast: 'AA' })
742
+ glaze.color({ from: '#1a1a2e', hue: '+20', contrast: 'AA' });
384
743
 
385
- // Structured form — explicit hue/saturation/lightness (0–100)
386
- glaze.color({ hue: 152, saturation: 95, lightness: 74 })
744
+ // Structured form — explicit hue/saturation/tone (0–100)
745
+ glaze.color({ hue: 152, saturation: 95, tone: 74 });
387
746
  ```
388
747
 
389
748
  ### Token methods
390
749
 
391
750
  A `GlazeColorToken` exposes:
392
751
 
393
- | Method | Description |
394
- |---|---|
395
- | `token.resolve()` | Resolve as a `ResolvedColor` (light/dark/lightContrast/darkContrast variants). |
396
- | `token.token(options?)` | Flat token map (no color-name key). Options: `format`, `modes`, `states`. |
397
- | `token.tasty(options?)` | Tasty state map (no color-name key). Same options as `token.token`. |
398
- | `token.json(options?)` | JSON map (no color-name key). Options: `format`, `modes`. |
752
+ | Method | Description |
753
+ | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
754
+ | `token.resolve()` | Resolve as a `ResolvedColor` (light/dark/lightContrast/darkContrast variants). |
755
+ | `token.token(options?)` | Flat token map (no color-name key). Options: `format`, `modes`, `states`. |
756
+ | `token.tasty(options?)` | [Tasty](https://tasty.style) state map (no color-name key). Same options as `token.token`. |
757
+ | `token.json(options?)` | JSON map (no color-name key). Options: `format`, `modes`. |
399
758
  | `token.css({ name, format?, suffix? })` | CSS custom property declarations grouped by scheme variant. `name` is **required** and becomes the variable identifier (`'brand'` → `--brand-color`). Defaults: `format: 'rgb'`, `suffix: '-color'` (matches `theme.css`). |
400
- | `token.export()` | JSON-safe snapshot pass to `glaze.colorFrom(...)` to rehydrate. |
759
+ | `token.dtcg(options?)` | DTCG color tokens, one per scheme variant (no color-name key). Each entry is a full `{ $type: 'color', $value }` token. Options: `colorSpace` (`'srgb'` \| `'oklch'`), `modes`. |
760
+ | `token.dtcgResolver({ name, ... })` | A single DTCG Resolver-Module document for this color, keyed by `name` across all scheme variants. `name` is **required**. Same options as `theme.dtcgResolver()` plus `name`. |
761
+ | `token.tailwind({ name, ... })` | Tailwind v4 `@theme` block + dark / high-contrast overrides for this color. `name` is **required** (forms `--color-<name>`). Same options as `theme.tailwind()` plus `name`. |
762
+ | `token.export(override?)` | JSON-safe snapshot — freezes effective config at call time; pass to `glaze.colorFrom(...)` to rehydrate. Optional `override` merges over the instance local (and nested `base` exports). |
401
763
 
402
764
  ### Per-instance config override
403
765
 
404
- The optional `config` second argument (`GlazeConfigOverride`) overrides the resolve-relevant global config fields for a single token or theme. Fields that are omitted fall through to the live global config at create time (and are snapshotted). Pass `false` for a lightness window to disable clamping entirely — equivalent to `[0, 100]`.
766
+ The optional `config` argument (`GlazeConfigOverride`) overrides
767
+ resolve-relevant fields for a token or theme. A tone window can be
768
+ `[lo, hi]`, `{ lo, hi, eps }`, or `false` (full range). Both themes and
769
+ standalone tokens keep a sparse local override — omitted fields (except
770
+ instance-only `pastel`) fall through to the live global at resolve time.
405
771
 
406
772
  `GlazeConfigOverride`:
407
773
 
408
- | Field | Default (from global) | Description |
409
- |---|---|---|
410
- | `lightLightness` | `[10, 100]` | Light window `[lo, hi]` or `false` (disable clamping = `[0, 100]`). |
411
- | `darkLightness` | `[15, 95]` | Dark window `[lo, hi]` or `false` (disable clamping). |
412
- | `darkDesaturation` | `0.1` | Saturation reduction in dark scheme (0–1). |
413
- | `darkCurve` | `0.5` | Möbius beta for dark `auto`-inversion (0–1). Accepts `[normal, hc]` pair. |
414
- | `autoFlip` | `true` | When solving `contrast`, allow the solver to switch lightness direction if the requested side can't meet the target. |
415
- | `shadowTuning` | `undefined` | Default shadow tuning (meaningful for themes; harmless on color tokens). |
774
+ | Field | Default (from global / fixed) | Description |
775
+ | ------------------ | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
776
+ | `lightTone` | `[10, 100]` | Light tone window: `[lo, hi]`, `{ lo, hi, eps }`, or `false` (disable clamping). |
777
+ | `darkTone` | `[15, 95]` | Dark tone window: `[lo, hi]`, `{ lo, hi, eps }`, or `false` (disable clamping). |
778
+ | `darkDesaturation` | `0.1` | Saturation reduction in dark scheme (0–1). |
779
+ | `autoFlip` | `true` | Default for each color's `autoFlip`: when solving `contrast` (or applying a relative `tone` that overshoots), allow crossing to the opposite side instead of clamping. |
780
+ | `pastel` | `false` (instance-only) | Theme/token-level pastel default for colors that omit per-color `pastel`. Not available on `glaze.configure()`. |
781
+ | `inferRole` | `true` | Infer color `role` from the name when unset. |
782
+ | `shadowTuning` | `undefined` | Default shadow tuning (meaningful for themes; harmless on color tokens). |
416
783
 
417
784
  Config overrides apply to both `glaze.color()` tokens and `glaze()` themes:
418
785
 
419
786
  ```ts
420
- // Standalone color — preserve raw lightness in both schemes
421
- glaze.color('#26fcb2', { darkLightness: false })
787
+ // Standalone color — preserve raw tone in both schemes
788
+ glaze.color('#26fcb2', { darkTone: false });
422
789
 
423
790
  // Restore the #000 → white dark flip (full dark range)
424
791
  glaze.color('#000000', {
425
- lightLightness: false,
426
- darkLightness: [15, 100],
427
- })
792
+ lightTone: false,
793
+ darkTone: [15, 100],
794
+ });
428
795
 
429
796
  // Structured form with config override
430
- glaze.color({ hue: 152, saturation: 95, lightness: 74 }, { darkLightness: false })
797
+ glaze.color({ hue: 152, saturation: 95, tone: 74 }, { darkTone: false, pastel: true });
431
798
 
432
799
  // Theme with config override
433
- const rawTheme = glaze(280, 80, { lightLightness: false })
800
+ const rawTheme = glaze(280, 80, { lightTone: false, pastel: true });
434
801
  ```
435
802
 
436
- The override is **snapshotted at create time** so later `glaze.configure()` calls don't change already-created tokens or themes (for non-overridden fields, the snapshot captured the global value at creation time; for themes, non-overridden fields are re-read from the live global at resolve time — see [Theme config override](#theme-config-override)).
803
+ See [Theme config override](#theme-config-override).
437
804
 
438
805
  ### Theme config override
439
806
 
@@ -443,24 +810,31 @@ When a theme is created with a `GlazeConfigOverride`, the override is **merged o
443
810
  - Fields you didn't override still react to later `glaze.configure()` calls.
444
811
 
445
812
  ```ts
446
- const t = glaze(280, 80, { lightLightness: [0, 50] });
447
- t.colors({ text: { lightness: 50, saturation: 1 } });
448
- // text.light.l 0.25 — always, regardless of global lightLightness changes.
813
+ const t = glaze(280, 80, { lightTone: [0, 50] });
814
+ t.colors({ text: { tone: 50, saturation: 1 } });
815
+ // text.light lands inside the [0, 50] window — always, regardless of
816
+ // global lightTone changes.
449
817
  // text.dark.s reacts to glaze.configure({ darkDesaturation }) since it's not overridden.
450
818
  ```
451
819
 
452
820
  `extend` inherits the parent's override and shallow-merges the child's:
453
821
 
454
822
  ```ts
455
- const child = t.extend({ config: { darkLightness: false } });
456
- // child: lightLightness: [0, 50] (inherited) + darkLightness: false (added)
823
+ const child = t.extend({ config: { darkTone: false } });
824
+ // child: lightTone { lo: 0, hi: 50 } (inherited) + darkTone: false (added)
457
825
  ```
458
826
 
459
- `theme.export()` includes `config`; `glaze.from(data)` restores it.
827
+ `theme.export(override?)` freezes `getConfig() ∪ instance local ∪ override` at
828
+ call time. Restoring via `glaze.themeFrom(data)` (or the compat alias
829
+ `glaze.from`) pins that freeze as the restored theme's local override — matching
830
+ standalone color-token behavior.
460
831
 
461
832
  ### `glaze.colorFrom(data)`
462
833
 
463
- Inverse of `token.export()`. The exported snapshot includes the original input, all overrides (with any `base` token recursively serialized), and the full effective config — so later `glaze.configure()` calls don't change rehydrated tokens.
834
+ Inverse of `token.export()`. The exported snapshot includes the original input,
835
+ all overrides (with any `base` token recursively serialized), and the effective
836
+ config freeze from export time — so later `glaze.configure()` calls don't change
837
+ rehydrated tokens.
464
838
 
465
839
  ```ts
466
840
  const text = glaze.color({ from: '#1a1a1a', contrast: 'AA' });
@@ -473,7 +847,7 @@ Both value-form and structured-form tokens round-trip.
473
847
 
474
848
  ### Pairing colors
475
849
 
476
- Set `base` to anchor a standalone color to another standalone color or raw value. The WCAG contrast solver and relative `lightness` offsets switch their anchor from the literal seed to the base's resolved variant per scheme — so the same text color automatically lands at AA against its background in light, dark, and high-contrast modes.
850
+ Set `base` to anchor a standalone color to another standalone color or raw value. The contrast solver and relative `tone` offsets switch their anchor from the literal seed to the base's resolved variant per scheme — so the same text color automatically lands at AA against its background in light, dark, and high-contrast modes.
477
851
 
478
852
  ```ts
479
853
  const bg = glaze.color('#1a1a2e');
@@ -481,10 +855,11 @@ const bg = glaze.color('#1a1a2e');
481
855
  // Text guaranteed AA against `bg` in every scheme.
482
856
  const text = glaze.color({ from: '#ffffff', base: bg, contrast: 'AA' });
483
857
 
484
- // Border 8 lightness units lighter than `bg` in each scheme.
485
- const border = glaze.color({ from: '#000000',
858
+ // Border 8 tone units lighter than `bg` in each scheme.
859
+ const border = glaze.color({
860
+ from: '#000000',
486
861
  base: bg,
487
- lightness: '+8',
862
+ tone: '+8',
488
863
  mode: 'fixed',
489
864
  });
490
865
 
@@ -495,11 +870,11 @@ const text2 = glaze.color({ from: '#ffffff', base: '#1a1a2e', contrast: 'AA' });
495
870
  Behavior with `base`:
496
871
 
497
872
  - `contrast` is solved per scheme against `base`'s resolved variant (light / dark / lightContrast / darkContrast).
498
- - Relative `lightness: '+N'` / `'-N'` is anchored to `base`'s lightness per scheme (matches theme behavior).
873
+ - Relative `tone: '+N'` / `'-N'` is anchored to `base`'s tone per scheme (matches theme behavior).
499
874
  - Relative `hue: '+N'` / `'-N'` still anchors to the **seed** (the value passed to `glaze.color()`), not the base.
500
875
  - `mode` works as a per-pair knob.
501
876
  - The base token's `.resolve()` is called lazily on the first resolve of the dependent and the result is captured by reference; later mutations to the base don't apply.
502
- - **Structured bases are resolved at full range for linking math**: when a value/`from` color links to a base created via the structured form, the contrast/lightness anchor uses the raw input lightness (not the windowed output). This ensures the anchor matches what you intended, not what the light window remapped it to. The base's own `.resolve()` output is unaffected.
877
+ - **Structured bases are resolved at full range for linking math**: when a value/`from` color links to a base created via the structured form, the contrast/tone anchor uses the raw input tone (not the windowed output). This ensures the anchor matches what you intended, not what the light window remapped it to. The base's own `.resolve()` output is unaffected.
503
878
  - When the contrast target is physically unreachable, `glaze` emits a single `console.warn` per `(name, scheme, target)` triple and returns the closest passing variant. Use the `name` override to make the warning identifiable.
504
879
 
505
880
  Chains compose:
@@ -522,8 +897,8 @@ The `name` override appears in `console.warn` / Error messages but **does not**
522
897
 
523
898
  ```ts
524
899
  theme.colors({
525
- surface: { lightness: 95 },
526
- text: { base: 'surface', lightness: '-52', contrast: 'AAA' },
900
+ surface: { tone: 95 },
901
+ text: { base: 'surface', tone: '-52', contrast: 'AAA' },
527
902
 
528
903
  'shadow-sm': { type: 'shadow', bg: 'surface', fg: 'text', intensity: 5 },
529
904
  'shadow-md': { type: 'shadow', bg: 'surface', fg: 'text', intensity: 10 },
@@ -562,20 +937,22 @@ theme.colors({
562
937
 
563
938
  Fine-tune behavior per-color or globally via `glaze.configure({ shadowTuning })`. Per-color `tuning` is merged field-by-field with the global one.
564
939
 
565
- | Parameter | Default | Description |
566
- |---|---|---|
567
- | `saturationFactor` | `0.18` | Fraction of fg saturation kept in pigment. |
568
- | `maxSaturation` | `0.25` | Upper clamp on pigment saturation. |
569
- | `lightnessFactor` | `0.25` | Multiplier for bg lightness → pigment lightness. |
570
- | `lightnessBounds` | `[0.05, 0.20]` | Clamp range for pigment lightness. |
571
- | `minGapTarget` | `0.05` | Target minimum gap between pigment and bg lightness. |
572
- | `alphaMax` | `1.0` | Asymptotic maximum alpha. |
573
- | `bgHueBlend` | `0.2` | Blend weight pulling pigment hue toward bg hue. `0` = pure fg hue, `1` = pure bg hue. |
940
+ | Parameter | Default | Description |
941
+ | ------------------ | -------------- | ------------------------------------------------------------------------------------- |
942
+ | `saturationFactor` | `0.18` | Fraction of fg saturation kept in pigment. |
943
+ | `maxSaturation` | `0.25` | Upper clamp on pigment saturation. |
944
+ | `lightnessFactor` | `0.25` | Multiplier for bg lightness → pigment lightness. |
945
+ | `lightnessBounds` | `[0.05, 0.20]` | Clamp range for pigment lightness. |
946
+ | `minGapTarget` | `0.05` | Target minimum gap between pigment and bg lightness. |
947
+ | `alphaMax` | `1.0` | Asymptotic maximum alpha. |
948
+ | `bgHueBlend` | `0.2` | Blend weight pulling pigment hue toward bg hue. `0` = pure fg hue, `1` = pure bg hue. |
574
949
 
575
950
  ```ts
576
951
  theme.colors({
577
952
  'shadow-soft': {
578
- type: 'shadow', bg: 'surface', intensity: 10,
953
+ type: 'shadow',
954
+ bg: 'surface',
955
+ intensity: 10,
579
956
  tuning: { alphaMax: 0.3, saturationFactor: 0.1 },
580
957
  },
581
958
  });
@@ -603,12 +980,12 @@ const css = glaze.format(v, 'oklch');
603
980
 
604
981
  `GlazeShadowInput`:
605
982
 
606
- | Field | Type | Description |
607
- |---|---|---|
608
- | `bg` | `GlazeColorValue` | Background. Any `GlazeColorValue` form. Alpha components dropped with warning. |
609
- | `fg` | `GlazeColorValue` | Optional foreground. Same forms as `bg`. |
610
- | `intensity` | `number` | 0–100. |
611
- | `tuning` | `ShadowTuning` | Optional. |
983
+ | Field | Type | Description |
984
+ | ----------- | ----------------- | ------------------------------------------------------------------------------ |
985
+ | `bg` | `GlazeColorValue` | Background. Any `GlazeColorValue` form. Alpha components dropped with warning. |
986
+ | `fg` | `GlazeColorValue` | Optional foreground. Same forms as `bg`. |
987
+ | `intensity` | `number` | 0–100. |
988
+ | `tuning` | `ShadowTuning` | Optional. |
612
989
 
613
990
  ### Fixed opacity (regular colors)
614
991
 
@@ -616,7 +993,7 @@ For a simple fixed-alpha color (no shadow algorithm), use `opacity` on a regular
616
993
 
617
994
  ```ts
618
995
  theme.colors({
619
- overlay: { lightness: 0, opacity: 0.5 },
996
+ overlay: { tone: 0, opacity: 0.5 },
620
997
  });
621
998
  // → 'oklch(0 0 0 / 0.5)'
622
999
  ```
@@ -631,9 +1008,9 @@ Produces a solid color by interpolating between `base` and `target`:
631
1008
 
632
1009
  ```ts
633
1010
  theme.colors({
634
- surface: { lightness: 95 },
635
- accent: { lightness: 30 },
636
- tint: { type: 'mix', base: 'surface', target: 'accent', value: 30 },
1011
+ surface: { tone: 95 },
1012
+ accent: { tone: 30 },
1013
+ tint: { type: 'mix', base: 'surface', target: 'accent', value: 30 },
637
1014
  });
638
1015
  ```
639
1016
 
@@ -647,11 +1024,14 @@ Produces the target color with controlled opacity — useful for hover overlays:
647
1024
 
648
1025
  ```ts
649
1026
  theme.colors({
650
- surface: { lightness: 95 },
651
- black: { lightness: 0, saturation: 0 },
1027
+ surface: { tone: 95 },
1028
+ black: { tone: 0, saturation: 0 },
652
1029
  hover: {
653
- type: 'mix', base: 'surface', target: 'black',
654
- value: 8, blend: 'transparent',
1030
+ type: 'mix',
1031
+ base: 'surface',
1032
+ target: 'black',
1033
+ value: 8,
1034
+ blend: 'transparent',
655
1035
  },
656
1036
  });
657
1037
  // hover → black with alpha = 0.08
@@ -661,10 +1041,10 @@ The output color has `h`, `s`, `l` from the target and `alpha = value / 100`.
661
1041
 
662
1042
  ### Blend space (opaque only)
663
1043
 
664
- | `space` | Behavior | Best for |
665
- |---|---|---|
666
- | `'okhsl'` (default) | Perceptually uniform OKHSL interpolation. | Design token derivation. |
667
- | `'srgb'` | Linear sRGB channel interpolation. | Matching browser compositing of CSS color-mix / overlay. |
1044
+ | `space` | Behavior | Best for |
1045
+ | ------------------- | ----------------------------------------- | -------------------------------------------------------- |
1046
+ | `'okhsl'` (default) | Perceptually uniform OKHSL interpolation. | Design token derivation. |
1047
+ | `'srgb'` | Linear sRGB channel interpolation. | Matching browser compositing of CSS color-mix / overlay. |
668
1048
 
669
1049
  Transparent blending always composites in linear sRGB (matches browser alpha compositing).
670
1050
 
@@ -695,10 +1075,22 @@ Mix colors can reference other mix colors:
695
1075
 
696
1076
  ```ts
697
1077
  theme.colors({
698
- white: { lightness: 100, saturation: 0 },
699
- black: { lightness: 0, saturation: 0 },
700
- gray: { type: 'mix', base: 'white', target: 'black', value: 50, space: 'srgb' },
701
- lightGray: { type: 'mix', base: 'white', target: 'gray', value: 50, space: 'srgb' },
1078
+ white: { tone: 100, saturation: 0 },
1079
+ black: { tone: 0, saturation: 0 },
1080
+ gray: {
1081
+ type: 'mix',
1082
+ base: 'white',
1083
+ target: 'black',
1084
+ value: 50,
1085
+ space: 'srgb',
1086
+ },
1087
+ lightGray: {
1088
+ type: 'mix',
1089
+ base: 'white',
1090
+ target: 'gray',
1091
+ value: 50,
1092
+ space: 'srgb',
1093
+ },
702
1094
  });
703
1095
  ```
704
1096
 
@@ -720,35 +1112,67 @@ const palette = glaze.palette(
720
1112
 
721
1113
  `GlazePaletteOptions`:
722
1114
 
723
- | Option | Description |
724
- |---|---|
1115
+ | Option | Description |
1116
+ | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
725
1117
  | `primary` | Name of the primary theme. The primary's tokens are duplicated **without** prefix in all exports, providing convenient short aliases alongside the prefixed versions. Throws if the name doesn't match any theme. |
726
1118
 
727
1119
  A `GlazePalette` exposes:
728
1120
 
729
- | Method | Description |
730
- |---|---|
731
- | `palette.tokens(options?)` | Flat token map grouped by scheme variant. |
732
- | `palette.tasty(options?)` | Tasty style-to-state bindings. |
733
- | `palette.json(options?)` | Per-theme JSON map (no prefix needed — keyed by theme name). |
734
- | `palette.css(options?)` | CSS custom property declaration strings. |
1121
+ | Method | Description |
1122
+ | -------------------------------- | ---------------------------------------------------------------------------------------------------- |
1123
+ | `palette.list()` | Theme names in insertion order. |
1124
+ | `palette.primary` | Primary theme name, if set. |
1125
+ | `palette.theme(name)` | Get a live theme instance by name. |
1126
+ | `palette.themes()` | Shallow copy of the theme map (same instances the palette holds). |
1127
+ | `palette.export(override?)` | Authoring snapshot — restorable via `glaze.paletteFrom()`. Optional override forwarded to themes. |
1128
+ | `palette.tokens(options?)` | Flat token map grouped by scheme variant. |
1129
+ | `palette.tasty(options?)` | [Tasty](https://tasty.style) style-to-state bindings. |
1130
+ | `palette.json(options?)` | Per-theme **resolved** color JSON (not restorable as authoring config). |
1131
+ | `palette.css(options?)` | CSS custom property declaration strings. |
1132
+ | `palette.dtcg(options?)` | Per-scheme W3C DTCG token trees. |
1133
+ | `palette.dtcgResolver(options?)` | One DTCG Resolver-Module document for every scheme. |
1134
+ | `palette.tailwind(options?)` | One Tailwind CSS v4 theme with scheme overrides. |
1135
+
1136
+ ### `palette.export(override?)` / `glaze.paletteFrom()`
1137
+
1138
+ ```ts
1139
+ const snapshot = palette.export();
1140
+ // → {
1141
+ // kind: 'palette',
1142
+ // version: 1,
1143
+ // primary: 'brand',
1144
+ // themes: { brand: { kind: 'theme', ... }, danger: { ... } },
1145
+ // }
1146
+
1147
+ const restored = glaze.paletteFrom(JSON.parse(JSON.stringify(snapshot)));
1148
+ const brand = restored.theme('brand')!;
1149
+ ```
1150
+
1151
+ Optional `override` is forwarded to each nested `theme.export(override)`.
1152
+ Config snapshot vs resolved output: use `export()` / `paletteFrom()` to
1153
+ persist and restore the authoring graph (themes, color defs, relations). Use
1154
+ `json()` / `tokens()` / `css()` / … to emit resolved color strings for apps
1155
+ and design tools.
735
1156
 
736
1157
  ### `GlazePaletteExportOptions`
737
1158
 
738
1159
  Shared by `tokens`, `tasty`, and `css`:
739
1160
 
740
- | Option | Default | Description |
741
- |---|---|---|
742
- | `prefix` | `true` (= `"<themeName>-"`) | `false` disables prefixing. Or pass a custom map: `{ primary: 'brand-', danger: 'error-' }`. |
743
- | `primary` | inherits from palette creation | `string` to override, `false` to disable for this call. |
1161
+ | Option | Default | Description |
1162
+ | --------- | ------------------------------ | -------------------------------------------------------------------------------------------- |
1163
+ | `prefix` | `true` (= `"<themeName>-"`) | `false` disables prefixing. Or pass a custom map: `{ primary: 'brand-', danger: 'error-' }`. |
1164
+ | `primary` | inherits from palette creation | `string` to override, `false` to disable for this call. |
744
1165
 
745
1166
  Each export method also accepts its own format/options shape:
746
1167
 
747
- | Method | Additional options |
748
- |---|---|
749
- | `palette.tokens(options?)` | `format`, `modes` |
750
- | `palette.tasty(options?)` | `format`, `modes`, `states` |
751
- | `palette.css(options?)` | `format`, `suffix` |
1168
+ | Method | Additional options |
1169
+ | -------------------------------- | ----------------------------------------- |
1170
+ | `palette.tokens(options?)` | `format`, `modes` |
1171
+ | `palette.tasty(options?)` | `format`, `modes`, `states` |
1172
+ | `palette.css(options?)` | `format`, `suffix` |
1173
+ | `palette.dtcg(options?)` | `colorSpace`, `modes` |
1174
+ | `palette.dtcgResolver(options?)` | `colorSpace`, `modes`, resolver names |
1175
+ | `palette.tailwind(options?)` | `format`, `modes`, selectors, `namespace` |
752
1176
 
753
1177
  `palette.css()` does not accept `modes`; it always returns all four CSS strings (`light`, `dark`, `lightContrast`, `darkContrast`).
754
1178
 
@@ -759,8 +1183,8 @@ By default all palette tokens are prefixed:
759
1183
  ```ts
760
1184
  palette.tokens();
761
1185
  // → {
762
- // light: { 'primary-surface': 'okhsl(...)', 'danger-surface': 'okhsl(...)' },
763
- // dark: { 'primary-surface': 'okhsl(...)', 'danger-surface': 'okhsl(...)' },
1186
+ // light: { 'primary-surface': 'oklch(...)', 'danger-surface': 'oklch(...)' },
1187
+ // dark: { 'primary-surface': 'oklch(...)', 'danger-surface': 'oklch(...)' },
764
1188
  // }
765
1189
  ```
766
1190
 
@@ -796,10 +1220,10 @@ const palette = glaze.palette(
796
1220
  palette.tokens();
797
1221
  // → {
798
1222
  // light: {
799
- // 'primary-surface': 'okhsl(...)',
800
- // 'danger-surface': 'okhsl(...)',
801
- // 'success-surface': 'okhsl(...)',
802
- // 'surface': 'okhsl(...)', // unprefixed alias
1223
+ // 'primary-surface': 'oklch(...)',
1224
+ // 'danger-surface': 'oklch(...)',
1225
+ // 'success-surface': 'oklch(...)',
1226
+ // 'surface': 'oklch(...)', // unprefixed alias
803
1227
  // },
804
1228
  // }
805
1229
  ```
@@ -825,8 +1249,8 @@ JSON export groups by theme name (no prefix needed):
825
1249
  ```ts
826
1250
  palette.json();
827
1251
  // → {
828
- // primary: { surface: { light: 'okhsl(...)', dark: 'okhsl(...)' } },
829
- // danger: { surface: { light: 'okhsl(...)', dark: 'okhsl(...)' } },
1252
+ // primary: { surface: { light: 'oklch(...)', dark: 'oklch(...)' } },
1253
+ // danger: { surface: { light: 'oklch(...)', dark: 'oklch(...)' } },
830
1254
  // }
831
1255
  ```
832
1256
 
@@ -845,29 +1269,98 @@ const stylesheet = `
845
1269
  `palette.css()` accepts the same `GlazeCssOptions` as `theme.css()` plus `GlazePaletteExportOptions`.
846
1270
  It does not accept `modes`; all four result fields are always returned.
847
1271
 
1272
+ ### `palette.dtcg()`
1273
+
1274
+ DTCG export for a palette. Prefix defaults to `true` and the palette-level `primary` is honored (the primary theme's tokens are duplicated without prefix as aliases).
1275
+
1276
+ ```ts
1277
+ palette.dtcg();
1278
+ // → {
1279
+ // light: {
1280
+ // 'primary-surface': { $type: 'color', $value: { ... } },
1281
+ // 'surface': { $type: 'color', $value: { ... } }, // unprefixed alias
1282
+ // 'danger-surface': { $type: 'color', $value: { ... } },
1283
+ // },
1284
+ // dark: { ... },
1285
+ // }
1286
+ ```
1287
+
1288
+ Accepts `GlazeDtcgOptions` plus `GlazePaletteExportOptions`.
1289
+
1290
+ ### `palette.dtcgResolver()`
1291
+
1292
+ Resolver-Module export for a palette. Same as `theme.dtcgResolver()` but merges every theme (with prefix / `primary` aliasing) into the single `sets.base` source and each `scheme` context. Prefix defaults to `true`; the palette-level `primary` is honored.
1293
+
1294
+ ```ts
1295
+ palette.dtcgResolver();
1296
+ // → {
1297
+ // version: '2025.10',
1298
+ // sets: { base: { sources: [ { 'primary-surface': {…}, 'surface': {…}, 'danger-surface': {…} } ] } },
1299
+ // modifiers: { scheme: { default: 'light', contexts: { light: [], dark: [ {…} ] } } },
1300
+ // resolutionOrder: [ { $ref: '#/sets/base' }, { $ref: '#/modifiers/scheme' } ],
1301
+ // }
1302
+ ```
1303
+
1304
+ Accepts `GlazeDtcgResolverOptions` plus `GlazePaletteExportOptions`.
1305
+
1306
+ ### `palette.tailwind()`
1307
+
1308
+ Tailwind export for a palette. All themes are merged into a single `@theme` block (plus dark / high-contrast overrides), so each color is reachable as a Tailwind utility. Prefix defaults to `true`.
1309
+
1310
+ ```ts
1311
+ const css = palette.tailwind();
1312
+ // @theme {
1313
+ // --color-primary-surface: oklch(...);
1314
+ // --color-surface: oklch(...); /* unprefixed alias */
1315
+ // --color-danger-surface: oklch(...);
1316
+ // }
1317
+ // .dark { ... }
1318
+ ```
1319
+
1320
+ Accepts `GlazeTailwindOptions` plus `GlazePaletteExportOptions`. The palette `prefix` option (theme prefixing) is separate from `GlazeTailwindOptions.namespace` (the `--color-*` CSS namespace).
1321
+
848
1322
  ---
849
1323
 
850
1324
  ## Output formats
851
1325
 
852
1326
  Control the color format with the `format` option on any export method:
853
1327
 
854
- | Format | Output (alpha = 1) | Output (alpha < 1) | Notes |
855
- |---|---|---|---|
856
- | `'okhsl'` (default for tokens/tasty/json) | `okhsl(H S% L%)` | `okhsl(H S% L% / A)` | Glaze's native format, not a CSS function. |
857
- | `'rgb'` (default for css) | `rgb(R G B)` | `rgb(R G B / A)` | Rounded integers, modern space syntax. |
858
- | `'hsl'` | `hsl(H S% L%)` | `hsl(H S% L% / A)` | Modern space syntax. |
859
- | `'oklch'` | `oklch(L C H)` | `oklch(L C H / A)` | OKLab-based LCH. |
1328
+ | Format | Output (alpha = 1) | Output (alpha < 1) | Notes |
1329
+ | --------------------------------------------- | ------------------ | -------------------- | --------------------------------------------------------------------------------------------- |
1330
+ | `'oklch'` (default for CSS-string exports) | `oklch(L C H)` | `oklch(L C H / A)` | OKLab-based LCH. Native CSS. Required for `splitHue`. |
1331
+ | `'rgb'` | `rgb(R G B)` | `rgb(R G B / A)` | Rounded integers, modern space syntax. |
1332
+ | `'hsl'` | `hsl(H S% L%)` | `hsl(H S% L% / A)` | Modern space syntax. |
1333
+ | `'okhsl'` | `okhsl(H S% L%)` | `okhsl(H S% L% / A)` | Glaze's native format, not a CSS function. **[Tasty](https://tasty.style)-only** (`tasty()`, `token()`, `.tasty()`). |
1334
+ | `'okhst'` | `okhst(H S% T%)` | `okhst(H S% T% / A)` | OKHST tone axis. **[Tasty](https://tasty.style)-only** — same restriction as `okhsl`. |
860
1335
 
861
1336
  ```ts
862
- theme.tokens(); // 'okhsl(280 60% 97%)'
863
- theme.tokens({ format: 'rgb' }); // 'rgb(244 240 250)'
864
- theme.tokens({ format: 'hsl' }); // 'hsl(270.5 45.2% 95.8%)'
865
- theme.tokens({ format: 'oklch' }); // 'oklch(0.965 0.0123 280)'
1337
+ theme.tokens(); // 'oklch(0.965 0.0123 280)' (default)
1338
+ theme.tokens({ format: 'rgb' }); // 'rgb(244 240 250)'
1339
+ theme.tasty(); // 'oklch(0.965 0.0123 280)' (default)
1340
+ theme.tasty({ format: 'okhst' }); // 'okhst(280 60% 97%)'
866
1341
  ```
867
1342
 
868
1343
  All numeric output strips trailing zeros for cleaner CSS (e.g. `95` not `95.0`).
869
1344
 
870
- The `format` option works on every export: `theme.tokens()`, `theme.tasty()`, `theme.json()`, `theme.css()`, the same on `palette`, and on `token.token()` / `.tasty()` / `.json()` / `.css()`.
1345
+ The `format` option works on CSS-string exports: `theme.tokens()`, `theme.tasty()`, `theme.json()`, `theme.css()`, `theme.tailwind()`, the same on `palette`, and on `token.token()` / `.tasty()` / `.json()` / `.css()` / `.tailwind()`. **`okhsl` and `okhst` throw on non-[Tasty](https://tasty.style) exports** (`tokens`, `json`, `css`, `tailwind`) — they are not native CSS color spaces.
1346
+
1347
+ ### Hue channel splitting (`splitHue`)
1348
+
1349
+ On `theme.css()`, `theme.tasty()`, `palette.css()`, `palette.tasty()`, and standalone `color.css()` with `format: 'oklch'`, set `splitHue: true` to emit hue as its own custom property so consumers can re-skin at runtime:
1350
+
1351
+ ```css
1352
+ /* theme.css({ format: 'oklch', splitHue: true, name: 'brand' }) */
1353
+ --brand-hue: 240;
1354
+ --accent-hue: calc(var(--brand-hue) + 20);
1355
+ --surface-color: oklch(0.52 0.06 var(--brand-hue));
1356
+ --accent-color: oklch(0.62 0.03 var(--accent-hue));
1357
+ ```
1358
+
1359
+ **Requirements:** every exported color must be pastel (`pastel: true` on the theme/token override or per-color). Pastel mode bounds chroma by the hue-independent safe chroma at each lightness, so emitted `C` stays in sRGB for any rotated hue. Non-pastel palettes throw rather than emit values that would clip under rotation.
1360
+
1361
+ **Limitations:** `oklch` only (native CSS `var()` in the hue slot). Shadow and mix colors stay inline (blended hue). Standalone `.token()` / `.tasty()` do not support `splitHue` (return shape cannot carry the `$name-hue` declaration).
1362
+
1363
+ `theme.dtcg()` / `theme.dtcgResolver()` / `palette.dtcg()` / `palette.dtcgResolver()` ignore `format` — DTCG emits structured `$value` objects, not CSS strings. Use the `colorSpace` option (`'srgb'` or `'oklch'`) to pick the color representation instead.
871
1364
 
872
1365
  ---
873
1366
 
@@ -875,87 +1368,84 @@ The `format` option works on every export: `theme.tokens()`, `theme.tasty()`, `t
875
1368
 
876
1369
  `mode` controls how a color adapts across schemes:
877
1370
 
878
- | Mode | Behavior |
879
- |---|---|
880
- | `'auto'` (default) | Full adaptation. Light dark inversion via the Möbius curve. High-contrast boost. |
881
- | `'fixed'` | Color stays recognizable. Lightness is *mapped* (not inverted) into the dark window. Use for brand buttons, CTAs, status banners. |
882
- | `'static'` | No adaptation. Same value in every scheme. |
1371
+ | Mode | Behavior |
1372
+ | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- |
1373
+ | `'auto'` (default) | Full adaptation. Dark uses **dark tone inversion** (`100 − t`) and then remaps into the dark tone window. High-contrast uses the full range. |
1374
+ | `'fixed'` | Color stays recognizable. Tone is _mapped_ (not inverted) into the dark window. Use for brand buttons, CTAs, status banners. |
1375
+ | `'static'` | No adaptation. Same tone in every scheme. |
883
1376
 
884
- ### How relative lightness adapts
1377
+ ### How relative tone adapts
885
1378
 
886
- **`auto`** — relative lightness sign flips in dark scheme:
1379
+ **`auto`** — the offset is anchored to the base's per-scheme tone:
887
1380
 
888
1381
  ```
889
- Light: surface L=97, text lightness='-52' → L=45 (dark text on light bg)
890
- Dark: surface inverts to L≈20 (Möbius), sign flips L=20+52=72
891
- contrast solver may push further (light text on dark bg)
1382
+ Light: surface tone=97, text tone='-52' → tone 45 (dark text on light bg)
1383
+ Dark: surface inverts to a low tone; the '-52' offset re-anchors to the
1384
+ base's light tone and maps into the dark window (light text on dark bg)
892
1385
  ```
893
1386
 
894
- **`fixed`** — lightness is mapped (not inverted), relative sign preserved:
1387
+ **`fixed`** — tone is mapped (not inverted), relative sign preserved:
895
1388
 
896
1389
  ```
897
- Light: accent-fill L=52, accent-text lightness='+48' → L=100 (white on brand)
898
- Dark: accent-fill maps to L≈51.6, sign preserved → L≈99.6
1390
+ Light: accent-fill tone=52, accent-text tone='+20' → lighter than the fill
1391
+ Dark: accent-fill maps into the dark window, sign preserved
899
1392
  ```
900
1393
 
901
- **`static`**no adaptation, same value in every scheme.
1394
+ Offsets that would push past `[0, 100]` clamp to the boundary, or with `autoFlip` (default on) — mirror to the other side of the base. If the mirror also overshoots, the original side is kept and clamped. Set `autoFlip: false` to keep the authored side and clamp instead.
1395
+
1396
+ **`static`** — no adaptation, same tone in every scheme.
902
1397
 
903
1398
  ---
904
1399
 
905
1400
  ## Light / dark scheme mapping
906
1401
 
907
- ### Light scheme lightness
1402
+ The mapping is a single tone pipeline; there is no Möbius curve. See
1403
+ [Scheme adaptation](okhst.md#scheme-adaptation) for the product-level model and
1404
+ the [canonical OKHST specification](https://github.com/tenphi/okhst) for the
1405
+ transfer math.
908
1406
 
909
- Absolute lightness values (root colors and dependent colors with absolute lightness) are mapped linearly within the configured `lightLightness` window:
1407
+ ### Light scheme
910
1408
 
911
- ```ts
912
- const [lo, hi] = lightLightness; // default: [10, 100]
913
- const mappedL = (lightness * (hi - lo)) / 100 + lo;
914
- ```
915
-
916
- Both `auto` and `fixed` modes use the same linear formula. `static` mode and HC variants bypass the mapping (identity: `mappedL = l`).
1409
+ An authored tone (0–100) is remapped into the `lightTone` **tone window**.
1410
+ The window's `lo`/`hi` are OKHSL-lightness boundaries (0–100); authored tone is
1411
+ positioned within the corresponding tone interval and converted to final OKHSL
1412
+ lightness. `static` mode and HC variants use the full range.
917
1413
 
918
- | Color | Raw L | Mapped L (default `[10, 100]`) |
919
- |---|---|---|
920
- | surface (L=97) | 97 | 97.3 |
921
- | accent-fill (L=52) | 52 | 56.8 |
922
- | near-black (L=0) | 0 | 10 |
1414
+ ```
1415
+ window = lightTone // default [10, 100]
1416
+ finalTone = remap(authorTone, window)
1417
+ finalL = fromTone(finalTone) // OKHSL lightness
1418
+ ```
923
1419
 
924
- ### Dark scheme — lightness
1420
+ ### Dark scheme
925
1421
 
926
- **`auto`** — inverted with a Möbius transformation within the configured window:
1422
+ **`auto`** — invert the tone, then remap into the dark window:
927
1423
 
928
- ```ts
929
- const [lo, hi] = darkLightness; // default: [15, 95]
930
- const t = (100 - lightness) / 100;
931
- const invertedL = lo + (hi - lo) * t / (t + darkCurve * (1 - t));
932
- // darkCurve default: 0.5
1424
+ ```
1425
+ window = darkTone // default [15, 95]
1426
+ inverted = 100 - authorTone
1427
+ finalTone = remap(inverted, window)
933
1428
  ```
934
1429
 
935
- The `darkCurve` parameter (default `0.5`, range 0–1) controls how much the dark-mode inversion expands lightness deltas. Lower values produce stronger expansion; `1` gives linear (legacy) behavior. Accepts `[normal, highContrast]` pairs (e.g. `darkCurve: [0.5, 0.3]`).
936
-
937
- Unlike a power curve, the Möbius transformation provides **proportional expansion** — small and large deltas are scaled by similar ratios, preserving the visual hierarchy of the light theme.
1430
+ The inversion preserves authored tone spacing without a fitted curve. This is
1431
+ exactly contrast-even for neutrals and approximate for chromatic colors. The
1432
+ ordinary light/dark asymmetry lives in the two windows' `(lo, hi, eps)` values
1433
+ (`eps` defaults to the reference `0.05`).
938
1434
 
939
- **`fixed`** — mapped without inversion (not affected by `darkCurve`):
1435
+ **`fixed`** — remap into the dark window without inversion:
940
1436
 
941
- ```ts
942
- const mappedL = (lightness * (hi - lo)) / 100 + lo;
1437
+ ```
1438
+ finalTone = remap(authorTone, darkTone)
943
1439
  ```
944
1440
 
945
- | Color | Light L | Auto (curve=0.5) | Auto (curve=1, linear) | Fixed (mapped) |
946
- |---|---|---|---|---|
947
- | surface (L=97) | 97 | 19.7 | 17.4 | 92.6 |
948
- | accent-fill (L=52) | 52 | 66.9 | 53.4 | 56.6 |
949
- | accent-text (L=100) | 100 | 15 | 15 | 95 |
950
-
951
- In high-contrast variants the `darkLightness` window is bypassed — `auto` uses the Möbius curve over the full `[0, 100]` range, `fixed` uses identity.
1441
+ In high-contrast variants both windows are bypassed (forced to the full `[0, 100]` range): `auto` still inverts, `fixed`/`static` do not.
952
1442
 
953
1443
  ### Dark scheme — saturation
954
1444
 
955
1445
  `darkDesaturation` reduces saturation for all colors in dark scheme:
956
1446
 
957
1447
  ```ts
958
- S_dark = S_light * (1 - darkDesaturation) // default: 0.1
1448
+ S_dark = S_light * (1 - darkDesaturation); // default: 0.1
959
1449
  ```
960
1450
 
961
1451
  `static` mode skips desaturation.
@@ -966,13 +1456,12 @@ S_dark = S_light * (1 - darkDesaturation) // default: 0.1
966
1456
 
967
1457
  ```ts
968
1458
  glaze.configure({
969
- lightLightness: [10, 100], // or false to disable clamping
970
- darkLightness: [15, 95], // or false to disable clamping
1459
+ lightTone: [10, 100], // [lo, hi]; or { lo, hi, eps } / false to disable clamping
1460
+ darkTone: [15, 95], // [lo, hi]; or { lo, hi, eps } / false to disable clamping
971
1461
  darkDesaturation: 0.1,
972
- darkCurve: 0.5, // or [normal, hc] pair
973
1462
  states: {
974
- dark: '@dark',
975
- highContrast: '@high-contrast',
1463
+ dark: '@media(prefers-color-scheme: dark)',
1464
+ highContrast: '@media(prefers-contrast: more)',
976
1465
  },
977
1466
  modes: {
978
1467
  dark: true,
@@ -985,28 +1474,33 @@ glaze.configure({
985
1474
  });
986
1475
  ```
987
1476
 
1477
+ A `ToneWindow` is `[lo, hi]` (OKHSL-lightness boundaries, reference eps — the
1478
+ common form), `{ lo, hi, eps }` (advanced: explicit per-scheme render eps), or
1479
+ `false` for the full range `[0, 100]` at the reference eps. `false` removes the
1480
+ boundaries, not the tone transfer.
1481
+
988
1482
  `GlazeConfig`:
989
1483
 
990
- | Field | Default | Description |
991
- |---|---|---|
992
- | `lightLightness` | `[10, 100]` | Light scheme lightness window `[lo, hi]`, or `false` to disable clamping (equivalent to `[0, 100]`). Bypassed in HC. |
993
- | `darkLightness` | `[15, 95]` | Dark scheme lightness window, or `false` to disable clamping. Bypassed in HC. |
994
- | `darkDesaturation` | `0.1` | Saturation reduction in dark scheme (0–1). |
995
- | `darkCurve` | `0.5` | Möbius beta for dark `auto`-inversion (0–1). Accepts `[normal, hc]` pair. |
996
- | `states.dark` | `'@dark'` | State alias for dark mode tokens (Tasty export). |
997
- | `states.highContrast` | `'@high-contrast'` | State alias for HC tokens. |
998
- | `modes.dark` | `true` | Include dark variants in exports. |
999
- | `modes.highContrast` | `false` | Include HC variants. |
1000
- | `shadowTuning` | `undefined` | Default tuning for all shadow colors. Per-color tuning merges field-by-field. |
1001
- | `autoFlip` | `true` | When solving `contrast`, allow the solver to switch away from the requested lightness direction if that side can't meet the target. With `false`, only the requested direction is considered; unmet contrasts pin the lightness to that direction's extreme (and emit a warning). |
1002
-
1003
- | Method | Description |
1004
- |---|---|
1484
+ | Field | Default | Description |
1485
+ | --------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1486
+ | `lightTone` | `[10, 100]` | Light scheme tone window: `[lo, hi]`, `{ lo, hi, eps }`, or `false` to disable clamping. Bypassed in HC. |
1487
+ | `darkTone` | `[15, 95]` | Dark scheme tone window: `[lo, hi]`, `{ lo, hi, eps }`, or `false` to disable clamping. Bypassed in HC. |
1488
+ | `darkDesaturation` | `0.1` | Saturation reduction in dark scheme (0–1). |
1489
+ | `states.dark` | `'@media(prefers-color-scheme: dark)'` | State alias for dark mode tokens ([Tasty](https://tasty.style) export). Defaults to a media query so tokens react to the OS preference without registering custom states. |
1490
+ | `states.highContrast` | `'@media(prefers-contrast: more)'` | State alias for HC tokens ([Tasty](https://tasty.style) export). |
1491
+ | `modes.dark` | `true` | Include dark variants in exports. |
1492
+ | `modes.highContrast` | `false` | Include HC variants. |
1493
+ | `shadowTuning` | `undefined` | Default tuning for all shadow colors. Per-color tuning merges field-by-field. |
1494
+ | `autoFlip` | `true` | Default for each color's `autoFlip`. When solving `contrast` (or applying a relative `tone` that overshoots `[0, 100]`), allow crossing to the opposite side instead of clamping. With `false`, only the requested direction is considered; unmet contrasts pin the tone to that direction's extreme (and emit a warning) and overshooting offsets clamp to the boundary. Override per color via [`autoFlip`](#autoflip). |
1495
+ | `inferRole` | `true` | Infer each color's [`role`](#roles) from its name when no explicit `role` is set. Set to `false` to opt out of name-based inference (the base-opposite and foreground-default fallbacks still apply). |
1496
+
1497
+ | Method | Description |
1498
+ | ------------------------- | ----------------------------------------------------------------------------------- |
1005
1499
  | `glaze.configure(config)` | Merge into the global config. Bumps a config version that invalidates theme caches. |
1006
- | `glaze.getConfig()` | Snapshot the current resolved config (shallow copy). |
1007
- | `glaze.resetConfig()` | Reset to defaults (also bumps the version counter). |
1500
+ | `glaze.getConfig()` | Snapshot the current resolved config (shallow copy). |
1501
+ | `glaze.resetConfig()` | Reset to defaults (also bumps the version counter). |
1008
1502
 
1009
- Standalone `glaze.color()` tokens snapshot the resolve-relevant fields at create time, so later `configure()` calls don't change already-created tokens. Themes merge the live global at resolve time for fields not overridden via `GlazeConfigOverride`.
1503
+ Themes and standalone color tokens keep a sparse local `GlazeConfigOverride` and merge the live global at resolve time for omitted fields. Authoring `.export(override?)` freezes the effective merge at call time; restored instances pin that freeze. `pastel` is instance-only (theme/token override or per-color) — not set via `configure()`.
1010
1504
 
1011
1505
  ---
1012
1506
 
@@ -1036,30 +1530,38 @@ Resolution priority (highest first):
1036
1530
 
1037
1531
  ## Validation
1038
1532
 
1039
- | Condition | Behavior |
1040
- |---|---|
1041
- | `contrast` without `base` in a **theme** color | Validation error |
1042
- | Relative `lightness` without `base` in a **theme** color | Validation error |
1043
- | `contrast` without `base` in `glaze.color()` | Anchors against the literal seed (no error) |
1044
- | Relative `lightness` without `base` in `glaze.color()` | Anchors against the literal seed (no error) |
1045
- | `lightness` resolves outside 0–100 | Clamp silently |
1046
- | `saturation` outside 0–1 | Clamp silently |
1047
- | Circular `base` references | Validation error |
1048
- | `base` references non-existent name | Validation error |
1049
- | Shadow `bg` references non-existent color | Validation error |
1050
- | Shadow `fg` references non-existent color | Validation error |
1051
- | Shadow `bg` references another shadow color | Validation error |
1052
- | Shadow `fg` references another shadow color | Validation error |
1053
- | Regular color `base` references a shadow color | Validation error |
1054
- | Shadow `intensity` outside 0–100 | Clamp silently |
1055
- | `contrast` + `opacity` combined | `console.warn` |
1056
- | Mix `base` references non-existent color | Validation error |
1057
- | Mix `target` references non-existent color | Validation error |
1058
- | Mix `base` references a shadow color | Validation error |
1059
- | Mix `target` references a shadow color | Validation error |
1060
- | Mix `value` outside 0–100 | Clamp silently |
1061
- | Circular references involving mix colors | Validation error |
1062
- | Contrast target physically unreachable | `console.warn` (deduped per `(name, scheme, target)`); closest passing variant returned |
1533
+ Invalid definitions throw before resolution when Glaze cannot produce a
1534
+ well-defined dependency graph. Recoverable numeric bounds are clamped. A
1535
+ physically unreachable contrast floor or a potentially misleading
1536
+ contrast/opacity combination emits `console.warn` and returns the closest
1537
+ available result.
1538
+
1539
+ | Condition | Behavior |
1540
+ | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
1541
+ | `contrast` without `base` in a **theme** color | Validation error |
1542
+ | Relative `tone` without `base` in a **theme** color | Validation error |
1543
+ | `contrast` without `base` in `glaze.color()` | Anchors against the literal seed (no error) |
1544
+ | Relative `tone` without `base` in `glaze.color()` | Anchors against the literal seed (no error) |
1545
+ | Relative `tone` overshoots `[0, 100]` | Mirror to the other side of the base (`autoFlip` on, default), or clamp to the boundary (`autoFlip` off). If the mirror also overshoots, clamp on the authored side. |
1546
+ | `tone` resolves outside 0–100 | Clamp silently |
1547
+ | `'max'` / `'min'` without `base` | Allowed resolves to the scheme's tone extreme (root color) |
1548
+ | `saturation` outside 0–1 | Clamp silently |
1549
+ | Circular `base` references | Validation error |
1550
+ | `base` references non-existent name | Validation error |
1551
+ | Shadow `bg` references non-existent color | Validation error |
1552
+ | Shadow `fg` references non-existent color | Validation error |
1553
+ | Shadow `bg` references another shadow color | Validation error |
1554
+ | Shadow `fg` references another shadow color | Validation error |
1555
+ | Regular color `base` references a shadow color | Validation error |
1556
+ | Shadow `intensity` outside 0–100 | Clamp silently |
1557
+ | `contrast` + `opacity` combined | `console.warn` |
1558
+ | Mix `base` references non-existent color | Validation error |
1559
+ | Mix `target` references non-existent color | Validation error |
1560
+ | Mix `base` references a shadow color | Validation error |
1561
+ | Mix `target` references a shadow color | Validation error |
1562
+ | Mix `value` outside 0–100 | Clamp silently |
1563
+ | Circular references involving mix colors | Validation error |
1564
+ | Contrast target physically unreachable | `console.warn` (deduped per `(name, scheme, target)`); closest passing variant returned |
1063
1565
 
1064
1566
  ---
1065
1567
 
@@ -1085,19 +1587,19 @@ import {
1085
1587
  } from '@tenphi/glaze';
1086
1588
  ```
1087
1589
 
1088
- | Function | Description |
1089
- |---|---|
1090
- | `okhslToLinearSrgb(h, s, l)` | OKHSL (h: 0–360, s/l: 0–1) → linear sRGB tuple. |
1091
- | `okhslToSrgb(h, s, l)` | OKHSL → gamma-encoded sRGB tuple (0–1 per channel). |
1092
- | `okhslToOklab([h, s, l])` | OKHSL → OKLab `[L, a, b]`. |
1093
- | `oklabToOkhsl([L, a, b])` | OKLab → OKHSL. |
1094
- | `srgbToOkhsl([r, g, b])` | Gamma sRGB (0–1) → OKHSL. |
1095
- | `hslToSrgb(h, s, l)` | CSS HSL → sRGB tuple. |
1096
- | `parseHex(hex)` | Parse `#rgb` / `#rrggbb` to sRGB tuple. Returns `null` on invalid input. |
1097
- | `parseHexAlpha(hex)` | Parse `#rgb` / `#rrggbb` / `#rrggbbaa`; returns `[r, g, b, a?]`. |
1098
- | `relativeLuminanceFromLinearRgb(rgb)` | WCAG relative luminance from linear sRGB. |
1099
- | `contrastRatioFromLuminance(yA, yB)` | WCAG contrast ratio from two luminances. |
1100
- | `gamutClampedLuminance(linearRgb)` | Relative luminance with channel clamping for out-of-gamut colors. |
1590
+ | Function | Description |
1591
+ | ------------------------------------- | ------------------------------------------------------------------------ |
1592
+ | `okhslToLinearSrgb(h, s, l)` | OKHSL (h: 0–360, s/l: 0–1) → linear sRGB tuple. |
1593
+ | `okhslToSrgb(h, s, l)` | OKHSL → gamma-encoded sRGB tuple (0–1 per channel). |
1594
+ | `okhslToOklab([h, s, l])` | OKHSL → OKLab `[L, a, b]`. |
1595
+ | `oklabToOkhsl([L, a, b])` | OKLab → OKHSL. |
1596
+ | `srgbToOkhsl([r, g, b])` | Gamma sRGB (0–1) → OKHSL. |
1597
+ | `hslToSrgb(h, s, l)` | CSS HSL → sRGB tuple. |
1598
+ | `parseHex(hex)` | Parse `#rgb` / `#rrggbb` to sRGB tuple. Returns `null` on invalid input. |
1599
+ | `parseHexAlpha(hex)` | Parse `#rgb` / `#rrggbb` / `#rrggbbaa`; returns `[r, g, b, a?]`. |
1600
+ | `relativeLuminanceFromLinearRgb(rgb)` | WCAG relative luminance from linear sRGB. |
1601
+ | `contrastRatioFromLuminance(yA, yB)` | WCAG contrast ratio from two luminances. |
1602
+ | `gamutClampedLuminance(linearRgb)` | Relative luminance with channel clamping for out-of-gamut colors. |
1101
1603
 
1102
1604
  ### Format writers
1103
1605
 
@@ -1105,42 +1607,77 @@ import {
1105
1607
  import { formatOkhsl, formatRgb, formatHsl, formatOklch } from '@tenphi/glaze';
1106
1608
 
1107
1609
  formatOkhsl(280, 60, 95); // 'okhsl(280 60% 95%)'
1108
- formatRgb(280, 60, 95); // 'rgb(244 240 250)'
1109
- formatHsl(280, 60, 95); // 'hsl(280 60% 95%)'
1610
+ formatRgb(280, 60, 95); // 'rgb(244 240 250)'
1611
+ formatHsl(280, 60, 95); // 'hsl(280 60% 95%)'
1110
1612
  formatOklch(280, 60, 95); // 'oklch(0.95 ... 280)'
1111
1613
  ```
1112
1614
 
1113
1615
  To attach an alpha component, use `glaze.format(variant, format)` on a `ResolvedColorVariant` (which carries the `alpha` channel) instead of these raw writers.
1114
1616
 
1617
+ ### OKHST tone utilities
1618
+
1619
+ ```ts
1620
+ import {
1621
+ toTone,
1622
+ fromTone,
1623
+ toneFromY,
1624
+ yFromTone,
1625
+ okhstToOkhsl,
1626
+ okhslToOkhst,
1627
+ variantToOkhsl,
1628
+ REF_EPS,
1629
+ } from '@tenphi/glaze';
1630
+ ```
1631
+
1632
+ | Function | Description |
1633
+ | ------------------------------------------- | ------------------------------------------------------------------------- |
1634
+ | `toTone(l, eps?)` | OKHSL lightness (0–1) → tone (0–100). Defaults to `REF_EPS`. |
1635
+ | `fromTone(t, eps?)` | Tone (0–100) → OKHSL lightness (0–1). Inverse of `toTone`. |
1636
+ | `toneFromY(y, eps?)` / `yFromTone(t, eps?)` | Same transfer in luminance space (0–1). |
1637
+ | `okhstToOkhsl({ h, s, t })` | OKHST → OKHSL (`{ h, s, l }`). |
1638
+ | `okhslToOkhst({ h, s, l })` | OKHSL → OKHST (`{ h, s, t }`). |
1639
+ | `variantToOkhsl(variant)` | `ResolvedColorVariant` (stores `t`) → `{ h, s, l, alpha }` for rendering. |
1640
+ | `REF_EPS` | Reference epsilon (`0.05`) for the canonical tone axis. |
1641
+
1642
+ `ResolvedColorVariant` stores `{ h, s, t, alpha }` (tone, not lightness). Use
1643
+ `variantToOkhsl(variant).l` to recover OKHSL lightness. See
1644
+ [OKHST in Glaze](okhst.md) for the model.
1645
+
1115
1646
  ### Contrast solver
1116
1647
 
1117
1648
  ```ts
1118
1649
  import {
1119
- findLightnessForContrast,
1650
+ findToneForContrast,
1120
1651
  findValueForMixContrast,
1652
+ resolveContrastForMode,
1121
1653
  resolveMinContrast,
1654
+ apcaContrast,
1122
1655
  } from '@tenphi/glaze';
1123
1656
  ```
1124
1657
 
1125
- | Function | Description |
1126
- |---|---|
1127
- | `findLightnessForContrast(opts)` | Binary-search for the OKHSL lightness that meets a WCAG contrast floor against a base color. Returns `{ lightness, contrast, met, branch }`. |
1128
- | `findValueForMixContrast(opts)` | Same, but searches for a mix `value` (0–1) that meets a contrast floor between a base and a target. |
1129
- | `resolveMinContrast(value)` | Resolves a `MinContrast` (preset or number) to a numeric ratio. |
1130
-
1131
- `findLightnessForContrast` options:
1132
-
1133
- | Option | Default | Description |
1134
- |---|---|---|
1135
- | `hue` | — | Candidate hue (0–360). |
1136
- | `saturation` | — | Candidate saturation (0–1). |
1137
- | `preferredLightness` | | Preferred candidate lightness (0–1). Kept if it already meets the target. |
1138
- | `baseLinearRgb` | | Base color as linear sRGB tuple. |
1139
- | `contrast` | — | WCAG floor (`MinContrast`). |
1140
- | `lightnessRange` | `[0, 1]` | Search bounds. |
1141
- | `epsilon` | `1e-4` | Convergence threshold. |
1142
- | `maxIterations` | `14` | Max binary-search iterations per branch. |
1143
- | `initialDirection` | higher-contrast side | Direction to search first (`'lighter'` or `'darker'`). Theme resolution sets this from the requested lightness relative to the base color. |
1144
- | `flip` | `false` | When `true`, try the opposite direction if the initial one doesn't meet the target. When `false`, only the initial direction is searched — unmet contrasts pin the result to that direction's extreme. |
1145
-
1146
- Result: `{ lightness, contrast, met, branch: 'lighter' | 'darker' | 'preferred', flipped? }`. `flipped: true` indicates the initial direction failed and the opposite direction satisfied the target.
1658
+ | Function | Description |
1659
+ | ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1660
+ | `findToneForContrast(opts)` | Binary-search for the tone (0–1) that meets a contrast floor (WCAG or APCA) against a base color. Returns `{ tone, contrast, met, branch, flipped? }`. |
1661
+ | `findValueForMixContrast(opts)` | Same, but searches for a mix `value` (0–1) that meets a contrast floor between a base and a target. |
1662
+ | `resolveContrastForMode(spec, isHC, polarity?, outerExplicitHC?)` | Resolves a `ContrastSpec` to `{ metric: 'wcag' \| 'apca', target }` for the requested mode (picks the normal or HC entry of any pair). In HC, applies the metric's auto-enhancement unless `outerExplicitHC` is set or the inner metric pair carries an explicit HC value: APCA +15 Lc (clamped to 106); WCAG AA → AAA / AA-large → AAA-large (AAA-family and bare numbers unchanged). |
1663
+ | `resolveMinContrast(value)` | Resolves a `MinContrast` (WCAG preset or number) to a numeric ratio. |
1664
+ | `apcaContrast(yText, yBg)` | APCA Lc magnitude (0–106) for two relative luminances. |
1665
+
1666
+ Exported constants: `APCA_PRESETS`, `APCA_HC_ENHANCEMENT` (`15`, the Enhanced Level delta), `APCA_MAX_LC` (`106`).
1667
+
1668
+ `findToneForContrast` options:
1669
+
1670
+ | Option | Default | Description |
1671
+ | ------------------ | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
1672
+ | `hue` | — | Candidate hue (0–360). |
1673
+ | `saturation` | | Candidate saturation (01). |
1674
+ | `preferredTone` | | Preferred candidate tone (0–1). Kept if it already meets the target. |
1675
+ | `baseLinearRgb` | | Base color as linear sRGB tuple. |
1676
+ | `contrast` | | `ResolvedContrast` (`{ metric, target }`). |
1677
+ | `toneRange` | `[0, 1]` | Search bounds in tone. |
1678
+ | `epsilon` | `1e-4` | Convergence threshold. |
1679
+ | `maxIterations` | `18` | Max binary-search iterations per branch. |
1680
+ | `initialDirection` | higher-contrast side | Direction to search first (`'lighter'` or `'darker'`). |
1681
+ | `flip` | `false` | When `true`, try the opposite direction if the initial one doesn't meet the target. When `false`, only the initial direction is searched — unmet contrasts pin the result to that direction's extreme. |
1682
+
1683
+ Result: `{ tone, contrast, met, branch: 'lighter' | 'darker' | 'preferred', flipped? }`. `flipped: true` indicates the initial direction failed and the opposite direction satisfied the target.