@cube-dev/ui-kit 0.171.0 → 0.172.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/CHANGELOG.md +21 -0
- package/dist/README.md +1 -1
- package/dist/_internal/hooks/use-chained-callback.js +1 -1
- package/dist/_internal/hooks/use-debounced-value.js +1 -1
- package/dist/_internal/hooks/use-deprecation-warning.js +1 -1
- package/dist/_internal/hooks/use-event.js +1 -1
- package/dist/_internal/hooks/use-is-first-render.js +1 -1
- package/dist/_internal/hooks/use-sync-ref.js +1 -1
- package/dist/_internal/hooks/use-timer/timer.js +1 -1
- package/dist/_internal/hooks/use-timer/use-timer.js +1 -1
- package/dist/_internal/hooks/use-warn.js +1 -1
- package/dist/components/Block.js +1 -1
- package/dist/components/CollectionItem.js +1 -1
- package/dist/components/GlobalStyles.js +6 -6
- package/dist/components/GlobalStyles.js.map +1 -1
- package/dist/components/GridProvider.js +1 -1
- package/dist/components/HiddenInput.js +1 -1
- package/dist/components/Root.js +2 -2
- package/dist/components/Root.js.map +1 -1
- package/dist/components/actions/Action/Action.js +1 -1
- package/dist/components/actions/Banner/Banner.js +1 -1
- package/dist/components/actions/Banner/Banner.js.map +1 -1
- package/dist/components/actions/Button/Button.d.ts +1 -0
- package/dist/components/actions/Button/Button.js +1 -1
- package/dist/components/actions/ButtonGroup/ButtonGroup.js +1 -1
- package/dist/components/actions/ButtonSplit/ButtonSplit.js +1 -1
- package/dist/components/actions/ButtonSplit/context.js +1 -1
- package/dist/components/actions/CommandMenu/CommandMenu.js +1 -1
- package/dist/components/actions/CommandMenu/styled.js +1 -1
- package/dist/components/actions/ItemAction/ItemAction.js +1 -1
- package/dist/components/actions/ItemActionContext.js +1 -1
- package/dist/components/actions/ItemButton/ItemButton.js +1 -1
- package/dist/components/actions/Link/Link.js +1 -1
- package/dist/components/actions/Menu/Menu.js +1 -1
- package/dist/components/actions/Menu/MenuItem.js +1 -1
- package/dist/components/actions/Menu/MenuSection.js +1 -1
- package/dist/components/actions/Menu/MenuTrigger.js +1 -1
- package/dist/components/actions/Menu/SubMenuTrigger.js +1 -1
- package/dist/components/actions/Menu/SubmenuTriggerContext.js +1 -1
- package/dist/components/actions/Menu/context.js +1 -1
- package/dist/components/actions/Menu/styled.js +1 -1
- package/dist/components/actions/Menu/styled.js.map +1 -1
- package/dist/components/actions/index.js +1 -1
- package/dist/components/actions/use-action.js +1 -1
- package/dist/components/actions/use-anchored-menu.js +1 -1
- package/dist/components/actions/use-context-menu.js +1 -1
- package/dist/components/content/ActiveZone/ActiveZone.js +1 -1
- package/dist/components/content/Alert/Alert.js +1 -1
- package/dist/components/content/Alert/use-alert.js +1 -1
- package/dist/components/content/Avatar/Avatar.js +1 -1
- package/dist/components/content/Badge/Badge.js +1 -1
- package/dist/components/content/Card/Card.js +1 -1
- package/dist/components/content/Content.js +1 -1
- package/dist/components/content/CopyPasteBlock/CopyPasteBlock.js +1 -1
- package/dist/components/content/CopySnippet/CopySnippet.js +1 -1
- package/dist/components/content/Disclosure/Disclosure.js +1 -1
- package/dist/components/content/Divider.js +1 -1
- package/dist/components/content/Footer.js +1 -1
- package/dist/components/content/Header.js +1 -1
- package/dist/components/content/HotKeys/HotKeys.js +1 -1
- package/dist/components/content/InfoBadge/InfoBadge.js +1 -1
- package/dist/components/content/InlineInput/InlineInput.js +1 -1
- package/dist/components/content/Item/Item.js +1 -1
- package/dist/components/content/ItemBadge/ItemBadge.js +1 -1
- package/dist/components/content/ItemCard/ItemCard.js +1 -1
- package/dist/components/content/Layout/GridLayout.js +1 -1
- package/dist/components/content/Layout/Layout.js +1 -1
- package/dist/components/content/Layout/LayoutBlock.js +1 -1
- package/dist/components/content/Layout/LayoutCenter.js +1 -1
- package/dist/components/content/Layout/LayoutContainer.js +1 -1
- package/dist/components/content/Layout/LayoutContent.js +1 -1
- package/dist/components/content/Layout/LayoutContext.js +1 -1
- package/dist/components/content/Layout/LayoutFlex.js +1 -1
- package/dist/components/content/Layout/LayoutFooter.js +1 -1
- package/dist/components/content/Layout/LayoutGrid.js +1 -1
- package/dist/components/content/Layout/LayoutHeader.js +1 -1
- package/dist/components/content/Layout/LayoutPane.js +1 -1
- package/dist/components/content/Layout/LayoutPanel.js +1 -1
- package/dist/components/content/Layout/LayoutPanelHeader.js +1 -1
- package/dist/components/content/Layout/LayoutToolbar.js +1 -1
- package/dist/components/content/Layout/hooks/useTinyScrollbar.js +1 -1
- package/dist/components/content/Layout/index.js +1 -1
- package/dist/components/content/Layout/utils.js +1 -1
- package/dist/components/content/Paragraph.js +1 -1
- package/dist/components/content/Placeholder/Placeholder.js +1 -1
- package/dist/components/content/PrismCode/PrismCode.js +1 -1
- package/dist/components/content/PrismCode/prismSetup.js +1 -1
- package/dist/components/content/PrismDiffCode/PrismDiffCode.js +1 -1
- package/dist/components/content/Result/Result.js +1 -1
- package/dist/components/content/Skeleton/Skeleton.js +1 -1
- package/dist/components/content/Tag/Tag.js +1 -1
- package/dist/components/content/Text.js +1 -1
- package/dist/components/content/TextItem/TextItem.js +1 -1
- package/dist/components/content/Title.js +1 -1
- package/dist/components/content/Tree/Tree.js +1 -1
- package/dist/components/content/Tree/TreeNode.js +1 -1
- package/dist/components/content/Tree/styled.js +1 -1
- package/dist/components/content/Tree/tree-index.js +1 -1
- package/dist/components/content/Tree/use-checkbox-tree.js +1 -1
- package/dist/components/content/Tree/use-load-data.js +1 -1
- package/dist/components/content/highlightText.js +1 -1
- package/dist/components/content/use-auto-tooltip.js +1 -1
- package/dist/components/data/DataTable/DataTable.js +1 -1
- package/dist/components/data/ItemTable/ItemTable.js +1 -1
- package/dist/components/data/ItemTable/ItemTableBulkBar.js +1 -1
- package/dist/components/data/ItemTable/ItemTableDragPreview.js +1 -1
- package/dist/components/data/ItemTable/ItemTableFooter.js +1 -1
- package/dist/components/data/ItemTable/ItemTableToolbar.js +1 -1
- package/dist/components/data/TableBase/ColumnResizer.js +1 -1
- package/dist/components/data/TableBase/RowCollection.js +1 -1
- package/dist/components/data/TableBase/TableHeaderCell.js +1 -1
- package/dist/components/data/TableBase/TableRow.js +1 -1
- package/dist/components/data/TableBase/TableView.js +1 -1
- package/dist/components/data/TableBase/column-menu.js +1 -1
- package/dist/components/data/TableBase/column-tint.js +2 -2
- package/dist/components/data/TableBase/column-tint.js.map +1 -1
- package/dist/components/data/TableBase/row-menu.js +1 -1
- package/dist/components/data/TableBase/styled.d.ts +2 -1
- package/dist/components/data/TableBase/styled.js +1 -1
- package/dist/components/data/TableBase/table-tree.js +1 -1
- package/dist/components/data/TableBase/types.d.ts +3 -3
- package/dist/components/data/TableBase/types.js +1 -1
- package/dist/components/data/TableBase/types.js.map +1 -1
- package/dist/components/data/TableBase/use-cell-selection.d.ts +1 -0
- package/dist/components/data/TableBase/use-cell-selection.js +0 -0
- package/dist/components/data/TableBase/use-column-order.js +1 -1
- package/dist/components/data/TableBase/use-container-width.js +1 -1
- package/dist/components/data/TableBase/use-row-move-animation.js +1 -1
- package/dist/components/data/TableBase/use-scrollability.js +1 -1
- package/dist/components/data/TableBase/use-table-columns.js +1 -1
- package/dist/components/data/TableBase/use-table-search.js +1 -1
- package/dist/components/data/TableBase/use-table-selection.js +1 -1
- package/dist/components/data/TableBase/use-table-sort.js +1 -1
- package/dist/components/data/TableBase/use-table-sorts.js +1 -1
- package/dist/components/data/TableBase/use-table-storage.js +1 -1
- package/dist/components/data/TableBase/use-table-tree-state.js +1 -1
- package/dist/components/fields/Checkbox/Checkbox.js +1 -1
- package/dist/components/fields/Checkbox/Checkbox.js.map +1 -1
- package/dist/components/fields/Checkbox/CheckboxGroup.js +1 -1
- package/dist/components/fields/Checkbox/context.js +1 -1
- package/dist/components/fields/ColorInput/ColorInput.js +1 -1
- package/dist/components/fields/ColorPicker/ColorPicker.js +1 -1
- package/dist/components/fields/ColorSwatch/ColorSwatch.js +1 -1
- package/dist/components/fields/ColorSwatchGroup/ColorSwatchGroup.js +2 -2
- package/dist/components/fields/ColorSwatchGroup/ColorSwatchGroup.js.map +1 -1
- package/dist/components/fields/ComboBox/ComboBox.js +1 -1
- package/dist/components/fields/CommandTextArea/CommandTextArea.js +1 -1
- package/dist/components/fields/CommandTextArea/caretPosition.js +1 -1
- package/dist/components/fields/CommandTextArea/useCaretAnchor.js +1 -1
- package/dist/components/fields/DatePicker/DateInput.js +1 -1
- package/dist/components/fields/DatePicker/DateInputBase.js +1 -1
- package/dist/components/fields/DatePicker/DatePicker.js +1 -1
- package/dist/components/fields/DatePicker/DatePickerButton.js +1 -1
- package/dist/components/fields/DatePicker/DatePickerElement.js +1 -1
- package/dist/components/fields/DatePicker/DatePickerInput.js +1 -1
- package/dist/components/fields/DatePicker/DatePickerSegment.js +1 -1
- package/dist/components/fields/DatePicker/DateRangePicker.js +1 -1
- package/dist/components/fields/DatePicker/DateRangeSeparatedPicker.js +1 -1
- package/dist/components/fields/DatePicker/MonthPicker.js +1 -1
- package/dist/components/fields/DatePicker/PeriodPicker.js +1 -1
- package/dist/components/fields/DatePicker/QuarterPicker.js +1 -1
- package/dist/components/fields/DatePicker/TimeInput.js +1 -1
- package/dist/components/fields/DatePicker/WeekPicker.js +1 -1
- package/dist/components/fields/DatePicker/YearPicker.js +1 -1
- package/dist/components/fields/DatePicker/parseDate.js +1 -1
- package/dist/components/fields/DatePicker/period.js +1 -1
- package/dist/components/fields/DatePicker/props.js +1 -1
- package/dist/components/fields/DatePicker/utils.js +1 -1
- package/dist/components/fields/FileInput/FileInput.js +1 -1
- package/dist/components/fields/FilterListBox/FilterListBox.js +1 -1
- package/dist/components/fields/FilterPicker/FilterPicker.js +1 -1
- package/dist/components/fields/Input/Input.js +1 -1
- package/dist/components/fields/ListBox/DraggableListBox.js +1 -1
- package/dist/components/fields/ListBox/ListBox.js +1 -1
- package/dist/components/fields/ListBoxPopover/ListBoxPopover.js +1 -1
- package/dist/components/fields/ListBoxPopover/index.d.ts +1 -0
- package/dist/components/fields/ListBoxPopover/listNavigation.js +1 -1
- package/dist/components/fields/ListBoxPopover/useCompositeFocus.d.ts +2 -0
- package/dist/components/fields/ListBoxPopover/useCompositeFocus.js +1 -1
- package/dist/components/fields/NumberInput/NumberInput.js +1 -1
- package/dist/components/fields/NumberInput/StepButton.js +1 -1
- package/dist/components/fields/PasswordInput/PasswordInput.js +1 -1
- package/dist/components/fields/Picker/Picker.js +1 -1
- package/dist/components/fields/RadioGroup/Radio.js +1 -1
- package/dist/components/fields/RadioGroup/RadioGroup.js +1 -1
- package/dist/components/fields/RadioGroup/context.js +1 -1
- package/dist/components/fields/SearchComboBox/SearchComboBox.js +1 -1
- package/dist/components/fields/SearchInput/SearchInput.js +1 -1
- package/dist/components/fields/Select/Select.js +1 -1
- package/dist/components/fields/Select/Select.js.map +1 -1
- package/dist/components/fields/Slider/Gradation.js +1 -1
- package/dist/components/fields/Slider/HueSlider.js +1 -1
- package/dist/components/fields/Slider/RangeSlider.js +1 -1
- package/dist/components/fields/Slider/Slider.js +1 -1
- package/dist/components/fields/Slider/SliderBase.js +1 -1
- package/dist/components/fields/Slider/SliderThumb.js +1 -1
- package/dist/components/fields/Slider/SliderTrack.js +1 -1
- package/dist/components/fields/Slider/elements.js +1 -1
- package/dist/components/fields/Slider/index.js +1 -1
- package/dist/components/fields/Switch/Switch.js +1 -1
- package/dist/components/fields/TextArea/TextArea.js +1 -1
- package/dist/components/fields/TextInput/TextInput.js +1 -1
- package/dist/components/fields/TextInput/TextInputBase.js +1 -1
- package/dist/components/fields/TextInput/useAutoSizeTextArea.js +1 -1
- package/dist/components/fields/TextInputMapper/TextInputMapper.js +1 -1
- package/dist/components/fields/color/ColorPanel.js +1 -1
- package/dist/components/fields/color/channels.js +1 -1
- package/dist/components/fields/color/color.js +2 -2
- package/dist/components/fields/color/color.js.map +1 -1
- package/dist/components/fields/color/context.js +1 -1
- package/dist/components/form/FieldWrapper/FieldWrapper.js +1 -1
- package/dist/components/form/Form/Field.js +1 -1
- package/dist/components/form/Form/Form.js +1 -1
- package/dist/components/form/Form/ResetButton/ResetButton.js +1 -1
- package/dist/components/form/Form/SubmitButton/SubmitButton.js +1 -1
- package/dist/components/form/Form/SubmitError.js +1 -1
- package/dist/components/form/Form/index.js +1 -1
- package/dist/components/form/Form/use-field/use-field-props.js +1 -1
- package/dist/components/form/Form/use-field/use-field.js +1 -1
- package/dist/components/form/Form/use-form.js +1 -1
- package/dist/components/form/Form/validation.js +1 -1
- package/dist/components/form/Label.js +1 -1
- package/dist/components/form/validation/ValidationIndicator.js +1 -1
- package/dist/components/form/validation/resolve-validation-props.js +1 -1
- package/dist/components/form/validation/use-validation-props.js +1 -1
- package/dist/components/form/wrapper.js +1 -1
- package/dist/components/helpers/DisplayTransition/DisplayTransition.js +1 -1
- package/dist/components/helpers/IconSwitch/IconSwitch.js +1 -1
- package/dist/components/layout/Board/Board.js +1 -1
- package/dist/components/layout/Board/Board.js.map +1 -1
- package/dist/components/layout/Board/BoardProvider.js +1 -1
- package/dist/components/layout/Board/BoardResponsive.js +1 -1
- package/dist/components/layout/Board/Widget.js +1 -1
- package/dist/components/layout/Board/WidgetHost.js +1 -1
- package/dist/components/layout/Board/WidgetHost.js.map +1 -1
- package/dist/components/layout/Board/board-context.js +1 -1
- package/dist/components/layout/Board/board-store.js +1 -1
- package/dist/components/layout/Board/grid-core/calculate.js +1 -1
- package/dist/components/layout/Board/grid-core/collision-modes.js +1 -1
- package/dist/components/layout/Board/grid-core/collision.js +1 -1
- package/dist/components/layout/Board/grid-core/compactors.js +1 -1
- package/dist/components/layout/Board/grid-core/constraints.js +1 -1
- package/dist/components/layout/Board/grid-core/group-move.js +1 -1
- package/dist/components/layout/Board/grid-core/layout.js +1 -1
- package/dist/components/layout/Board/grid-core/placement.js +1 -1
- package/dist/components/layout/Board/grid-core/sort.js +1 -1
- package/dist/components/layout/Board/index.js +1 -1
- package/dist/components/layout/Board/responsive-utils.js +1 -1
- package/dist/components/layout/Board/use-board-layout.js +1 -1
- package/dist/components/layout/Board/use-board-registry.js +1 -1
- package/dist/components/layout/Board/use-board-select-modifier-key.js +1 -1
- package/dist/components/layout/Board/use-board-selection.js +1 -1
- package/dist/components/layout/Flex.js +1 -1
- package/dist/components/layout/Flow.js +1 -1
- package/dist/components/layout/Grid.js +1 -1
- package/dist/components/layout/Panel.js +1 -1
- package/dist/components/layout/Prefix.js +1 -1
- package/dist/components/layout/ResizablePanel.js +1 -1
- package/dist/components/layout/Space.js +1 -1
- package/dist/components/layout/Suffix.js +1 -1
- package/dist/components/navigation/Pagination/Pagination.js +1 -1
- package/dist/components/navigation/Pagination/use-pagination.js +1 -1
- package/dist/components/navigation/Tabs/DraggableTabList.js +1 -1
- package/dist/components/navigation/Tabs/TabButton.js +1 -1
- package/dist/components/navigation/Tabs/TabDropIndicator.js +1 -1
- package/dist/components/navigation/Tabs/TabPanel.js +1 -1
- package/dist/components/navigation/Tabs/TabPicker.js +1 -1
- package/dist/components/navigation/Tabs/Tabs.js +1 -1
- package/dist/components/navigation/Tabs/TabsAction.js +1 -1
- package/dist/components/navigation/Tabs/TabsContext.js +1 -1
- package/dist/components/navigation/Tabs/popover-placement.js +1 -1
- package/dist/components/navigation/Tabs/styled.js +1 -1
- package/dist/components/navigation/Tabs/types.js +1 -1
- package/dist/components/navigation/Tabs/use-tab-editing.js +1 -1
- package/dist/components/navigation/Tabs/use-tab-indicator.js +1 -1
- package/dist/components/organisms/StatsCard/StatsCard.js +1 -1
- package/dist/components/other/Calendar/Calendar.js +1 -1
- package/dist/components/other/Calendar/CalendarCell.js +1 -1
- package/dist/components/other/Calendar/CalendarGrid.js +1 -1
- package/dist/components/other/Calendar/CalendarHeader.js +1 -1
- package/dist/components/other/Calendar/CalendarPanel.js +1 -1
- package/dist/components/other/Calendar/PeriodCalendar.js +1 -1
- package/dist/components/other/Calendar/PeriodGrid.js +1 -1
- package/dist/components/other/Calendar/RangeCalendar.js +1 -1
- package/dist/components/other/Calendar/styled.js +1 -1
- package/dist/components/other/CubeLogo/CubeLogo.d.ts +2 -2
- package/dist/components/other/CubeLogo/CubeLogo.js +12 -12
- package/dist/components/other/CubeLogo/CubeLogo.js.map +1 -1
- package/dist/components/other/NoDataIcon/NoDataIcon.js +1 -1
- package/dist/components/overlays/AlertDialog/AlertDialog.js +1 -1
- package/dist/components/overlays/AlertDialog/AlertDialogApiProvider.js +1 -1
- package/dist/components/overlays/AlertDialog/AlertDialogZone.js +1 -1
- package/dist/components/overlays/Dialog/Dialog.js +1 -1
- package/dist/components/overlays/Dialog/DialogContainer.js +1 -1
- package/dist/components/overlays/Dialog/DialogForm.js +1 -1
- package/dist/components/overlays/Dialog/DialogTrigger.js +1 -1
- package/dist/components/overlays/Dialog/context.js +1 -1
- package/dist/components/overlays/Dialog/use-dialog-container.js +1 -1
- package/dist/components/overlays/Modal/Modal.d.ts +2 -1
- package/dist/components/overlays/Modal/Modal.js +1 -1
- package/dist/components/overlays/Modal/OpenTransitionContext.js +1 -1
- package/dist/components/overlays/Modal/Overlay.d.ts +1 -0
- package/dist/components/overlays/Modal/Overlay.js +1 -1
- package/dist/components/overlays/Modal/Popover.js +1 -1
- package/dist/components/overlays/Modal/Tray.js +1 -1
- package/dist/components/overlays/Modal/Underlay.js +1 -1
- package/dist/components/overlays/Modal/types.d.ts +1 -0
- package/dist/components/overlays/Notifications/Notification.js +1 -1
- package/dist/components/overlays/Notifications/NotificationAction.js +1 -1
- package/dist/components/overlays/Notifications/NotificationCard.js +1 -1
- package/dist/components/overlays/Notifications/NotificationContext.d.ts +2 -0
- package/dist/components/overlays/Notifications/NotificationContext.js +1 -1
- package/dist/components/overlays/Notifications/NotificationItem.js +1 -1
- package/dist/components/overlays/Notifications/OverlayContainer.js +1 -1
- package/dist/components/overlays/Notifications/OverlayProvider.js +1 -1
- package/dist/components/overlays/Notifications/PersistentNotificationsList.js +1 -1
- package/dist/components/overlays/Notifications/dismissed-storage.js +1 -1
- package/dist/components/overlays/Notifications/format-relative-time.js +1 -1
- package/dist/components/overlays/Notifications/index.js +1 -1
- package/dist/components/overlays/Notifications/use-notification-state.js +1 -1
- package/dist/components/overlays/Notifications/use-notifications.js +1 -1
- package/dist/components/overlays/Notifications/use-overlay-timers.js +1 -1
- package/dist/components/overlays/Notifications/use-persistent-notifications.js +1 -1
- package/dist/components/overlays/Notifications/use-persistent-state.js +1 -1
- package/dist/components/overlays/Notifications/use-toast-state.js +1 -1
- package/dist/components/overlays/Toast/ToastItem.js +1 -1
- package/dist/components/overlays/Toast/index.js +1 -1
- package/dist/components/overlays/Toast/useProgressToast.js +1 -1
- package/dist/components/overlays/Toast/useToast.js +1 -1
- package/dist/components/overlays/Tooltip/Tooltip.js +1 -1
- package/dist/components/overlays/Tooltip/Tooltip.js.map +1 -1
- package/dist/components/overlays/Tooltip/TooltipProvider.js +1 -1
- package/dist/components/overlays/Tooltip/TooltipTrigger.js +1 -1
- package/dist/components/overlays/Tooltip/context.js +1 -1
- package/dist/components/portal/Portal.js +1 -1
- package/dist/components/portal/PortalProvider.d.ts +2 -0
- package/dist/components/portal/PortalProvider.js +1 -1
- package/dist/components/portal/index.d.ts +1 -0
- package/dist/components/portal/usePortal.js +1 -1
- package/dist/components/shared/DraggableCollection.js +1 -1
- package/dist/components/shared/InvalidIcon.js +1 -1
- package/dist/components/shared/ValidIcon.js +1 -1
- package/dist/components/status/LoadingAnimation/LoadingAnimation.js +1 -1
- package/dist/components/status/Spin/Cube.js +1 -1
- package/dist/components/status/Spin/InternalSpinner.js +1 -1
- package/dist/components/status/Spin/Spin.js +1 -1
- package/dist/components/status/Spin/SpinsContainer.js +1 -1
- package/dist/data/item-themes.js +1 -1
- package/dist/data/item-themes.js.map +1 -1
- package/dist/data/themes.js +1 -1
- package/dist/data/themes.js.map +1 -1
- package/dist/eslint-plugin/defaults.generated.js +1 -1
- package/dist/eslint-plugin/index.js +1 -1
- package/dist/eslint-plugin/rules/no-redundant-default-prop.js +1 -1
- package/dist/i18n/I18nProvider.js +1 -1
- package/dist/i18n/createFormatter.js +1 -1
- package/dist/i18n/index.js +1 -1
- package/dist/i18n/instance.js +1 -1
- package/dist/i18n/locales/de-DE/uikit.js +1 -1
- package/dist/i18n/locales/en-US/uikit.js +1 -1
- package/dist/i18n/locales/es-ES/uikit.js +1 -1
- package/dist/i18n/locales/es-MX/uikit.js +1 -1
- package/dist/i18n/locales/fr-FR/uikit.js +1 -1
- package/dist/i18n/locales/it-IT/uikit.js +1 -1
- package/dist/i18n/locales/ja-JP/uikit.js +1 -1
- package/dist/i18n/locales/nb-NO/uikit.js +1 -1
- package/dist/i18n/locales/pt-BR/uikit.js +1 -1
- package/dist/i18n/locales/pt-PT/uikit.js +1 -1
- package/dist/i18n/locales/sv-SE/uikit.js +1 -1
- package/dist/i18n/locales/vi-VN/uikit.js +1 -1
- package/dist/i18n/locales.js +1 -1
- package/dist/i18n/useFormatter.js +1 -1
- package/dist/i18n/useI18n.js +1 -1
- package/dist/icons/AdjustmentsHorizontalIcon.js +1 -1
- package/dist/icons/AdjustmentsIcon.js +1 -1
- package/dist/icons/AiIcon.js +1 -1
- package/dist/icons/AreaChartIcon.js +1 -1
- package/dist/icons/ArrowNarrowDownIcon.js +1 -1
- package/dist/icons/ArrowNarrowUpIcon.js +1 -1
- package/dist/icons/BackwardIcon.js +1 -1
- package/dist/icons/BarChartIcon.js +1 -1
- package/dist/icons/BellFilledIcon.js +1 -1
- package/dist/icons/BellIcon.js +1 -1
- package/dist/icons/BooleanIcon.js +1 -1
- package/dist/icons/CalendarEditIcon.js +1 -1
- package/dist/icons/CalendarIcon.js +1 -1
- package/dist/icons/CaretDownIcon.js +1 -1
- package/dist/icons/CaretUpIcon.js +1 -1
- package/dist/icons/ChartAreaStackedIcon.js +1 -1
- package/dist/icons/ChartAreaStackedPercentageIcon.js +1 -1
- package/dist/icons/ChartBarGroupedHorizontalIcon.js +1 -1
- package/dist/icons/ChartBarGroupedIcon.js +1 -1
- package/dist/icons/ChartBarHorizontalIcon.js +1 -1
- package/dist/icons/ChartBarLineIcon.js +1 -1
- package/dist/icons/ChartBarStackedHorizontalIcon.js +1 -1
- package/dist/icons/ChartBarStackedIcon.js +1 -1
- package/dist/icons/ChartBarStackedPercentageHorizontalIcon.js +1 -1
- package/dist/icons/ChartBarStackedPercentageIcon.js +1 -1
- package/dist/icons/ChartBoxPlot2Icon.js +1 -1
- package/dist/icons/ChartBoxPlotIcon.js +1 -1
- package/dist/icons/ChartBubbleIcon.js +1 -1
- package/dist/icons/ChartDonut2Icon.js +1 -1
- package/dist/icons/ChartFunnelIcon.js +1 -1
- package/dist/icons/ChartHeatmapIcon.js +1 -1
- package/dist/icons/ChartKPIIcon.js +1 -1
- package/dist/icons/ChartPie2Icon.js +1 -1
- package/dist/icons/ChartScatterIcon.js +1 -1
- package/dist/icons/CheckCircleFilledIcon.js +1 -1
- package/dist/icons/CheckCircleIcon.js +1 -1
- package/dist/icons/CheckIcon.js +1 -1
- package/dist/icons/CircleFilledIcon.js +1 -1
- package/dist/icons/ClearIcon.js +1 -1
- package/dist/icons/CloseCircleFilledIcon.js +1 -1
- package/dist/icons/CloseCircleIcon.js +1 -1
- package/dist/icons/CloseIcon.js +1 -1
- package/dist/icons/CodeIcon.js +1 -1
- package/dist/icons/ColumnTotalIcon.js +1 -1
- package/dist/icons/CopyIcon.js +1 -1
- package/dist/icons/CountIcon.js +1 -1
- package/dist/icons/CubeIcon.js +1 -1
- package/dist/icons/CubePauseIcon.js +1 -1
- package/dist/icons/CubePlayIcon.js +1 -1
- package/dist/icons/CurrencyDollarIcon.js +1 -1
- package/dist/icons/DangerIcon.js +1 -1
- package/dist/icons/DashboardIcon.js +1 -1
- package/dist/icons/DatabaseIcon.js +1 -1
- package/dist/icons/DecimalDecreaseIcon.js +1 -1
- package/dist/icons/DecimalIncreaseIcon.js +1 -1
- package/dist/icons/DirectionIcon.js +1 -1
- package/dist/icons/DonutIcon.js +1 -1
- package/dist/icons/DownIcon.js +1 -1
- package/dist/icons/EditIcon.js +1 -1
- package/dist/icons/ExclamationCircleFilledIcon.js +1 -1
- package/dist/icons/ExclamationCircleIcon.js +1 -1
- package/dist/icons/ExclamationIcon.js +1 -1
- package/dist/icons/EyeIcon.js +1 -1
- package/dist/icons/EyeInvisibleIcon.js +1 -1
- package/dist/icons/FilterIcon.js +1 -1
- package/dist/icons/FolderFilledIcon.js +1 -1
- package/dist/icons/FolderIcon.js +1 -1
- package/dist/icons/FolderOpenFilledIcon.js +1 -1
- package/dist/icons/FolderOpenIcon.js +1 -1
- package/dist/icons/ForwardIcon.js +1 -1
- package/dist/icons/GripVerticalIcon.js +1 -1
- package/dist/icons/HierarchyIcon.js +1 -1
- package/dist/icons/HierarchyOpenIcon.js +1 -1
- package/dist/icons/Icon.js +1 -1
- package/dist/icons/InfoCircleIcon.js +1 -1
- package/dist/icons/InfoIcon.js +1 -1
- package/dist/icons/KeyIcon.js +1 -1
- package/dist/icons/LeftIcon.js +1 -1
- package/dist/icons/LineChartIcon.js +1 -1
- package/dist/icons/LoadingIcon.js +1 -1
- package/dist/icons/LockFilledIcon.js +1 -1
- package/dist/icons/LockIcon.js +1 -1
- package/dist/icons/MoreIcon.js +1 -1
- package/dist/icons/NotAllowedIcon.js +1 -1
- package/dist/icons/Number123Icon.js +1 -1
- package/dist/icons/NumberIcon.js +1 -1
- package/dist/icons/PauseCircleFilledIcon.js +1 -1
- package/dist/icons/PauseCircleIcon.js +1 -1
- package/dist/icons/PauseIcon.js +1 -1
- package/dist/icons/PercentageIcon.js +1 -1
- package/dist/icons/PieChartIcon.js +1 -1
- package/dist/icons/PipetteIcon.js +1 -1
- package/dist/icons/PlayCircleIcon.js +1 -1
- package/dist/icons/PlayIcon.js +1 -1
- package/dist/icons/PlusIcon.js +1 -1
- package/dist/icons/ProgressBarIcon.js +1 -1
- package/dist/icons/ReloadIcon.js +1 -1
- package/dist/icons/ReportIcon.js +1 -1
- package/dist/icons/ReturnIcon.js +1 -1
- package/dist/icons/RightIcon.js +1 -1
- package/dist/icons/RowTotalsIcon.js +1 -1
- package/dist/icons/SchemeIcon.js +1 -1
- package/dist/icons/SearchIcon.js +1 -1
- package/dist/icons/SemanticQueryIcon.js +1 -1
- package/dist/icons/SettingsIcon.js +1 -1
- package/dist/icons/ShieldFilledIcon.js +1 -1
- package/dist/icons/ShieldIcon.js +1 -1
- package/dist/icons/SlashIcon.js +1 -1
- package/dist/icons/SparklesIcon.js +1 -1
- package/dist/icons/SqlIcon.js +1 -1
- package/dist/icons/StatsIcon.js +1 -1
- package/dist/icons/StopIcon.js +1 -1
- package/dist/icons/StringIcon.js +1 -1
- package/dist/icons/SubtotalsIcon.js +1 -1
- package/dist/icons/SwitchIcon.js +1 -1
- package/dist/icons/TableIcon.js +1 -1
- package/dist/icons/ThumbsDownIcon.js +1 -1
- package/dist/icons/ThumbsUpIcon.js +1 -1
- package/dist/icons/ThunderboltCrossedIcon.js +1 -1
- package/dist/icons/ThunderboltFilledIcon.js +1 -1
- package/dist/icons/ThunderboltIcon.js +1 -1
- package/dist/icons/TimeIcon.js +1 -1
- package/dist/icons/TrashIcon.js +1 -1
- package/dist/icons/UnlockIcon.js +1 -1
- package/dist/icons/UpIcon.js +1 -1
- package/dist/icons/UserGroupIcon.js +1 -1
- package/dist/icons/UserIcon.js +1 -1
- package/dist/icons/UserLockIcon.js +1 -1
- package/dist/icons/ViewIcon.js +1 -1
- package/dist/icons/WarningFilledIcon.js +1 -1
- package/dist/icons/WarningIcon.js +1 -1
- package/dist/icons/wrap-icon.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -3
- package/dist/probe/canonicalize.js +1 -1
- package/dist/probe/css.js +1 -1
- package/dist/probe/index.js +1 -1
- package/dist/provider.js +1 -1
- package/dist/providers/TrackingProvider.js +1 -1
- package/dist/providers/navigationAdapter.default.js +1 -1
- package/dist/tokens/all-tokens.js +1 -1
- package/dist/tokens/base.js +1 -1
- package/dist/tokens/color-seed.js +1 -1
- package/dist/tokens/color-theme.d.ts +2 -2
- package/dist/tokens/color-theme.js +2 -2
- package/dist/tokens/color-theme.js.map +1 -1
- package/dist/tokens/colors.d.ts +3 -3
- package/dist/tokens/colors.js +4 -4
- package/dist/tokens/colors.js.map +1 -1
- package/dist/tokens/layout.js +1 -1
- package/dist/tokens/lazy-styles.js +1 -1
- package/dist/tokens/palette-config.d.ts +1 -1
- package/dist/tokens/palette-config.js +1 -1
- package/dist/tokens/palette-config.js.map +1 -1
- package/dist/tokens/palette.d.ts +5 -5
- package/dist/tokens/palette.js +11 -11
- package/dist/tokens/palette.js.map +1 -1
- package/dist/tokens/resolve.d.ts +2 -2
- package/dist/tokens/resolve.js +9 -9
- package/dist/tokens/resolve.js.map +1 -1
- package/dist/tokens/shadows.d.ts +1 -1
- package/dist/tokens/shadows.js +2 -2
- package/dist/tokens/shadows.js.map +1 -1
- package/dist/tokens/sizes.js +1 -1
- package/dist/tokens/spacing.js +1 -1
- package/dist/tokens/typography.js +1 -1
- package/dist/utils/ResizeSensor.js +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/is-dev-env.js +1 -1
- package/dist/utils/modules.js +1 -1
- package/dist/utils/promise.js +1 -1
- package/dist/utils/raf.js +1 -1
- package/dist/utils/random.js +1 -1
- package/dist/utils/range.js +1 -1
- package/dist/utils/react/RenderCache.js +1 -1
- package/dist/utils/react/Slots.js +1 -1
- package/dist/utils/react/chain.js +1 -1
- package/dist/utils/react/disabledProps.js +1 -1
- package/dist/utils/react/forwardRefWithGenerics.js +1 -1
- package/dist/utils/react/index.d.ts +1 -1
- package/dist/utils/react/index.js +2 -2
- package/dist/utils/react/interactions.js +1 -1
- package/dist/utils/react/isTextOnly.js +1 -1
- package/dist/utils/react/mapProps.js +1 -1
- package/dist/utils/react/mergeProps.js +1 -1
- package/dist/utils/react/nullableValue.js +1 -1
- package/dist/utils/react/resolveIcon.js +1 -1
- package/dist/utils/react/sharedStore.js +1 -1
- package/dist/utils/react/useBufferedValue.js +1 -1
- package/dist/utils/react/useCombinedRefs.js +1 -1
- package/dist/utils/react/useControlledFocusVisible.js +1 -1
- package/dist/utils/react/useEventBus.js +1 -1
- package/dist/utils/react/useId.js +1 -1
- package/dist/utils/react/useIsDarwin.js +1 -1
- package/dist/utils/react/useKeySymbols.js +1 -1
- package/dist/utils/react/useLayoutEffect.js +1 -1
- package/dist/utils/react/useLocalStorage.js +1 -1
- package/dist/utils/react/useMergeStyles.js +1 -1
- package/dist/utils/react/usePopoverSync.js +1 -1
- package/dist/utils/react/useQaProps.js +1 -1
- package/dist/utils/react/{useSchema.d.ts → useScheme.d.ts} +16 -16
- package/dist/utils/react/{useSchema.js → useScheme.js} +30 -30
- package/dist/utils/react/{useSchema.js.map → useScheme.js.map} +1 -1
- package/dist/utils/react/useViewportSize.js +1 -1
- package/dist/utils/react/wrapNodeIfPlain.js +1 -1
- package/dist/utils/selection.js +1 -1
- package/dist/utils/styles.js +1 -1
- package/dist/utils/tree.js +1 -1
- package/dist/utils/warnings.js +1 -1
- package/dist/version.js +2 -2
- package/docs/Colors.md +2 -2
- package/docs/Introduction.md +1 -1
- package/docs/Theming.md +19 -19
- package/docs/Usage.md +19 -19
- package/docs/components/content/ItemCard.md +1 -1
- package/docs/components/content/PrismCode.md +2 -2
- package/docs/components/data/DataTable.md +1 -1
- package/docs/components/fields/Picker.md +1 -1
- package/docs/components/organisms/StatsCard.md +1 -1
- package/docs/components/other/CubeLogo.md +9 -9
- package/docs/components/overlays/Tooltip.md +3 -3
- package/docs/components/overlays/TooltipProvider.md +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"palette.js","names":[],"sources":["../../src/tokens/palette.ts"],"sourcesContent":["import {\n apcaContrast,\n glaze,\n okhslToLinearSrgb,\n variantToOkhsl,\n} from '@tenphi/glaze';\n\nimport { lazyStyles } from './lazy-styles';\nimport {\n getPaletteConfig,\n getPaletteVersion,\n resolvePaletteConfig,\n SURFACE_SATURATION_SHARE,\n} from './palette-config';\n\nimport type {\n ColorMap,\n ContrastSpec,\n GlazeColorValue,\n GlazeConfigOverride,\n GlazePalette,\n GlazeTheme,\n} from '@tenphi/glaze';\nimport type { Styles, Tokens } from '@tenphi/tasty';\nimport type {\n PaletteConfig,\n ResolvedPaletteConfig,\n ResolvedThemeSeed,\n} from './palette-config';\n\n/** Which resolved schema variant {@link renderPaletteTokens} should return. */\nexport interface RenderPaletteOptions extends PaletteConfig {\n /** Color schema to resolve. Default: `'light'`. */\n schema?: 'light' | 'dark';\n /** Resolve the high-contrast variant of that schema. Default: `false`. */\n highContrast?: boolean;\n}\n\n/**\n * Glaze-generated color palette for the Cube UI Kit.\n *\n * Produces light, dark, and high-contrast color variants from a single\n * source of truth. Every color token is emitted as a tasty state map:\n *\n * '#surface': { '': 'oklch(...)', '@dark': 'oklch(...)', '@hc': 'oklch(...)' }\n *\n * The `@dark` and `@hc` state aliases are wired up globally in\n * `src/components/Root.tsx` (see `setGlobalPredefinedStates`).\n *\n * This module owns the *recipe* — which colors exist and how each one is\n * positioned relative to its base. The *seeds* it is built from (hue,\n * saturation, pastel, contrast level) live in `./palette-config.ts` and are\n * tunable at runtime; `buildPalette()` below is a pure function of them.\n */\n\n// ============================================================================\n// Recipe constants\n// ============================================================================\n\n// Relative tone ramp from a white surface.\n// Tone is a contrast-uniform 0–100 scale (OKHST). A relative tone delta of -N\n// yields a fixed WCAG contrast step against the base regardless of where the\n// base sits on the scale. These constants form the primary text ramp; the\n// surface-2 / surface-3 variants reuse the same absolute targets via a small\n// offset because their bases are shifted down from `surface`.\nconst TEXT_TONE = -98; // relative tone delta from white surface (~cr 21)\nconst TEXT_SOFT_TONE = -72; // relative tone delta from white surface (~cr 9)\nconst TEXT_SOFT2_TONE = -52; // relative tone delta from white surface (~cr 4.5)\nconst SURFACE_2_TEXT_OFFSET = 2;\nconst SURFACE_3_TEXT_OFFSET = 4;\n\n// Syntax highlighting keeps its own fixed hue reference (see the `code-*` block\n// below). This is the kit's default brand hue, frozen as a literal: string\n// literals were historically purple because the brand is, but they must not\n// follow a re-seeded brand hue — a green brand would collide `code-string` with\n// `code-number` (156°) and make strings and numbers indistinguishable.\nconst CODE_STRING_HUE = 280.3;\n\n/**\n * Saturation factor of the neutral `surface`.\n *\n * Shared with the code theme, which mirrors `surface` to solve its contrast floors\n * against the real page background — the two must not drift apart. Shared with the\n * config too, which needs it to work out the share of the accent zone's chroma the\n * chrome takes when no base COLOR names one outright; it lives there because the\n * config cannot import from here, and is re-exported under this name because that is\n * what the recipe calls it.\n */\nconst SURFACE_SATURATION = SURFACE_SATURATION_SHARE;\n\nconst TINTED_SURFACE_SATURATION = 0.2;\nconst TINTED_SURFACE_TONE_OFFSET = 2;\n\n/**\n * How far `surfaceMode: 'tinted'` moves the neutral ramp off the end of the tone\n * scale.\n *\n * Small on purpose. Two tones is below the threshold where a page reads as\n * \"grey\" rather than \"white\", and it is not lightness this buys: chroma needs\n * distance from the extreme to exist at all, so at tone 100 a light surface is\n * white whatever saturation it asks for. Two tones is the cheapest room in which\n * the base hue becomes visible.\n *\n * Everything under `surface` is positioned relative to it, so the shift carries\n * the whole ladder — and the text ramp's contrast floors re-solve against the new\n * background instead of drifting.\n */\nconst TINTED_SURFACE_TONE_SHIFT = 2;\n\n/** Tone of the neutral `surface`, per {@link ResolvedPaletteConfig.surfaceMode}. */\nfunction surfaceTone(config: ResolvedPaletteConfig): number {\n return config.surfaceMode === 'tinted'\n ? 100 - TINTED_SURFACE_TONE_SHIFT\n : 100;\n}\n\n/**\n * Tone of a TINTED `surface` — a status theme's, or a runtime tint's —\n * as `[light, highContrast]`.\n *\n * A tinted surface is a banner sitting *on* the page, so its tone is authored as\n * an offset from the page's rather than as an absolute. Under\n * `surfaceMode: 'tinted'` the page itself moves down to 98, which is exactly where\n * these used to sit: a `note` banner would come out the same tone as the surface\n * behind it and stop reading as a banner at all. Anchoring both on\n * {@link surfaceTone} keeps the separation the offset was chosen for.\n *\n * The text ramps on these themes are relative (`base: 'surface'`), so they follow\n * on their own — only the absolute tone needs the anchor.\n */\nfunction tintedSurfaceTone(config: ResolvedPaletteConfig): [number, number] {\n const page = surfaceTone(config);\n\n return [\n page - TINTED_SURFACE_TONE_OFFSET,\n page - TINTED_SURFACE_TONE_OFFSET * 2,\n ];\n}\n\n/**\n * Rescale the base-zone saturation factors onto the base zone's own seed.\n *\n * Every color's `saturation` is a 0–1 factor of its **theme's** seed, and the\n * neutral family shares a theme instance with the accent one — it has to, since\n * `accent-surface` is positioned against `surface`. A separate base seed therefore\n * cannot be a second theme; it arrives as a scale over the authored factors.\n *\n * The scale is anchored on `surface`: dividing by `SURFACE_SATURATION` turns each\n * factor into its share *of the surface's tint*, and multiplying by\n * `baseSaturation / saturation` re-expresses that share against the base seed.\n * `surface` therefore lands on exactly `baseSaturation` on the 0–100 scale, and\n * every sibling keeps its proportion to it. At the shipped\n * `baseSaturation = saturation × 0.12` the scale is `1` and nothing moves.\n *\n * Clamped because Glaze reads the factor as 0–1, which is also why the proportions\n * converge at the top: `surface-inverse` (the highest at `0.475`) saturates first,\n * around `baseSaturation: 25`.\n *\n * A palette seed of `0` leaves no chroma for any factor to scale, so the base zone\n * is grey there whatever it asks for — the ceiling belongs to the theme instance,\n * not to this scale.\n */\nfunction baseSaturationScale(config: ResolvedPaletteConfig): number {\n return config.saturation > 0\n ? config.baseSaturation / (SURFACE_SATURATION * config.saturation)\n : 0;\n}\n\n/**\n * How far a pinned brand FILL has to separate from the page it sits on — APCA Lc 25, in\n * both tiers.\n *\n * The base is `surface`, and `roleToPolarity('surface')` gives this the `bg` polarity,\n * so Glaze solves `apcaContrast(surface, fill)`.\n *\n * **This number is page separation and nothing else**, and getting that wrong is what\n * made the dark schema unusable. It reads differently by schema — in light `surface` IS\n * white, so the same measurement happens to be the white-label pair, which invited the\n * value to be set for the LABEL at Lc 45 (`large` text) with 60 in high contrast. In\n * dark the page is near-black and the same number is a demand that a filled shape reach\n * text-grade contrast against it. Nothing in the palette meets that: measured on the\n * emitted tokens, the SHIPPED `accent-surface` — the white-anchored ladder every primary\n * button used before color seeds existed — sits at **Lc 25.5** off the dark page, and at\n * **Lc 19.3** in dark high contrast, where the ladder darkens the fill toward its label.\n * So 45/60 held a color-seeded fill to 1.8x and 3.1x what the design system's own button\n * achieves.\n *\n * Because a floor can only LIGHTEN in dark, that surplus flattened the tone axis: every\n * seed below the floor solved to the same fill. Measured across the axis at one hue, the\n * dark fill was pinned at tone 66 for every seed from 5 to 65 — a brand's whole dark half\n * collapsed onto one lavender — and in dark high contrast the floor met\n * {@link ACCENT_LABEL_LC}'s ceiling and left a window of a single value. At Lc 25 the\n * same sweep tracks the seed from tone 47 up, and 47 is where the shipped fill sits in\n * dark, so the floor now coincides with the palette's own reference point instead of\n * overshooting it.\n *\n * The label is not left unguarded by this — it never depended on this number. The white\n * `type=\"primary\"` label is guaranteed by {@link accentToneCeiling}, which caps the seed\n * against pure white across all four variants, with {@link ACCENT_LABEL_MARGIN} covering\n * the lightening this floor can still add. A LOWER floor lightens less, so it makes that\n * margin safer rather than riskier.\n *\n * What the floor cannot do is invent contrast that a dark page has no room for. A brand\n * darker than the shipped fill's own dark tone still lands on the floor, because a fill\n * beneath it is not a shape on that page. The fix is that the floor now sits where the\n * design system already put it, not two tiers above.\n *\n * Still a FLOOR, not a target: a brand already past it is emitted exactly as given —\n * which is why light is untouched by this change. A dark brand on a white page measures\n * Lc 100+, so this never bound there; the ceiling is what light answers to.\n *\n * Why APCA and not a WCAG ratio. WCAG 2.x is polarity-blind, so one number means two\n * very different things: the previous `3` measured Lc 56 in light but only Lc 23 in\n * dark (12 hues, spread under 2 Lc — hue is not a factor, polarity is). That is 2.4x\n * stricter in light than in dark, which is why light brands kept getting crushed while\n * dark ones sailed through under the same rule. An Lc is one number that means one\n * thing in both schemas.\n *\n * The pair is written out with BOTH entries equal, and that is the whole reason it is a\n * pair: stating one level would let APCA's automatic +15 Lc enhancement fire in high\n * contrast, which is the escalation this must not make. High contrast is a request for\n * separation over brand, but not for separation from the PAGE — the same fill carries a\n * white label, and driving it off the page drives the label off it. The shipped ladder\n * makes the same trade and lands the other side of it, at Lc 19.3: *less* page separation\n * in high contrast than in normal, because it darkens the fill toward its label. Here the\n * label is protected by {@link accentToneCeiling} instead, which searches the\n * high-contrast variants too, so the tier can leave this number alone.\n */\nconst ACCENT_FILL_CONTRAST: ContrastSpec = { apca: [25, 25] };\n\n/**\n * Floors for the two brand TEXT tokens: rest, then hover — APCA `content` and `body`.\n *\n * Also \"from white\", in the same sense as {@link ACCENT_FILL_CONTRAST}: both solve\n * against `accent-selected-fill`, which is `oklch(0.975 0.0037 284.4)` in light — white\n * for every practical purpose, and the surface these labels actually sit on. These are\n * foreground spots, so they keep the default `fg` polarity and Glaze solves\n * `apcaContrast(text, base)`.\n *\n * Lc 60 is APCA's `content` tier, the floor for ordinary body text, which is what a\n * link is. The hover takes `body`/75 so the rest→hover intensify stays visible; one\n * preset step apart is what keeps the two from solving onto the same color.\n *\n * The HC entries are spelled `85` and `92` rather than left to Glaze's +15, for the\n * same reason as {@link ACCENT_FILL_CONTRAST}, and they stay apart so the rest→hover\n * intensify survives the tier that needs it most. They replace the hand-measured\n * `[4.5, 9]` pair, whose `9` existed only because a WCAG ratio that high is unreachable\n * for a saturated hue against a chromatic base: `#FFD400` in dark high contrast used to\n * pin to pure black and come out a hover link *less* readable than its rest state. An\n * Lc target is reachable in both schemas because it is polarity-aware, so the\n * pathological case has no equivalent here.\n */\nconst ACCENT_TEXT_CONTRAST: ContrastSpec = { apca: [60, 85] };\nconst ACCENT_TEXT_HOVER_CONTRAST: ContrastSpec = { apca: [75, 92] };\n\n/**\n * Tone steps of the brand fill ramp, measured from `accent-surface`.\n *\n * They reproduce the white-anchored `-49 / -52 / -55 / -58` ladder exactly once the\n * fill is pinned, because those four deltas share an anchor and differ by 3 / 6 / 9.\n */\nconst ACCENT_RAMP = {\n surface2: '-3',\n surface3: '-6',\n hover: '-9',\n} as const;\n\n/**\n * How far the hover brand text sits past the rest one, in tone.\n *\n * Both are pinned to the caller's tone otherwise, and a pair at the same tone behind\n * the same floor resolves to one color — which would silently delete the rest→hover\n * intensify that `accent-text` exists for. Tone is contrast-uniform, so one step is\n * one step in either schema.\n */\nconst ACCENT_TEXT_HOVER_STEP = 6;\n\n/**\n * The caller's brand color, or `null` for the shipped derivation.\n *\n * Carries the literal — which is what Glaze's `from` consumes — alongside its tone,\n * because {@link ACCENT_TEXT_HOVER_STEP} needs the number to compute a step past it.\n */\ntype AccentSeed = { color: GlazeColorValue; tone: number } | null;\n\n/**\n * The white label's own floor on the brand fill, as an APCA Lc.\n *\n * The same 45 as {@link ACCENT_FILL_CONTRAST}, and deliberately so: it is the same\n * surface being constrained, just against the other thing that has to survive on it.\n *\n * Two floors rather than one because in dark they pull opposite ways, and dropping\n * either one produces the mirror image of the other's failure. The fill has to be dark\n * enough for the `#white` label every `type=\"primary\"` item paints on it, and light\n * enough to separate from a dark page. In light mode the page IS white, so both\n * collapse into the single measurement Glaze already makes and this cap never fires.\n *\n * Measured, in dark, with only the page floor: `#FFFFFF` clears it at WCAG 14.4 while\n * the label lands on **Lc 0** — the label is exactly its own fill. With only the white\n * floor the failure mirrors: `#111827` puts the fill at **Lc 0.0 against the page**, a\n * blazing white label on a shape that is not there. The border does not stand in for\n * the fill here — it is deliberately low-contrast — so the fill has to carry it.\n */\nconst ACCENT_LABEL_LC = 45;\n\n/**\n * How far above {@link ACCENT_LABEL_LC} the ceiling actually searches.\n *\n * The ceiling is computed on the bare seed, but the emitted `accent-surface` then goes\n * through the page floor, which can only LIGHTEN — and a lighter fill is a weaker white\n * label, so the solve eats into the margin the ceiling just established. Measured worst\n * case across 3072 hue/chroma/tone/schema/tier combinations is 1.8 Lc, in dark high\n * contrast where the page floor pushes hardest; 3 covers it with room.\n */\nconst ACCENT_LABEL_MARGIN = 3;\n\n/**\n * Keyed on the palette VERSION as well as the seed, because the search resolves\n * through Glaze's global settings — the dark tone window among them. A caller that\n * runs `glaze.configure(...)` and then `invalidatePaletteTokens()` has changed the\n * answer without changing the seed, and a cache keyed on the color alone would hand\n * back a cap computed against the old settings. Versioning it is what makes the\n * invalidation API mean what it says.\n *\n * Bounded for the same reason `colorSeed`'s cache is: a color picker drag resolves a\n * distinct seed per frame, and stale versions accumulate keys nobody reads again.\n */\nconst accentCapCache = new Map<string, number>();\nconst ACCENT_CAP_CACHE_LIMIT = 256;\n\n/** APCA Lc of pure white on one resolved variant. */\nfunction labelLcOf(variant: Parameters<typeof variantToOkhsl>[0]): number {\n const { h, s, l } = variantToOkhsl(variant);\n const gamma = (c: number) =>\n c <= 0.0031308 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;\n const y = (rgb: number[]) => {\n const [r, g, b] = rgb.map((c) => Math.max(0, Math.min(1, gamma(c))));\n\n return 0.2126 * r ** 2.4 + 0.7152 * g ** 2.4 + 0.0722 * b ** 2.4;\n };\n\n return Math.abs(\n apcaContrast(y([1, 1, 1]), y(okhslToLinearSrgb(h, s, l, false))),\n );\n}\n\n/**\n * Pull a brand tone down until a white label survives on every variant of the fill.\n *\n * The search runs against Glaze's own fixed-mode resolution — the same mapping\n * `accent-surface` goes through — rather than reimplementing the dark tone window, and\n * checks all four variants so the cap is a property of the seed and not of one schema.\n *\n * Only ever lowers, so a brand already dark enough comes back untouched.\n */\nfunction capAccentTone(hue: number, saturation: number, tone: number): number {\n return Math.min(tone, accentToneCeiling(hue, saturation));\n}\n\n/**\n * The lightest tone at this hue and chroma that a white label still survives on.\n *\n * A property of the hue/chroma pair alone, NOT of the tone being asked for — which is\n * what makes it cacheable, and what the first cut got wrong: it only searched when the\n * requested tone already failed, so a dark tone probed first cached \"no ceiling\" and\n * every later light tone at the same hue escaped uncapped.\n */\nfunction accentToneCeiling(hue: number, saturation: number): number {\n const key = `${hue}|${saturation}|${getPaletteVersion()}`;\n const cached = accentCapCache.get(key);\n\n if (cached !== undefined) return cached;\n\n const labelLc = (candidate: number): number => {\n // `from` takes an `OkhstColor` directly, so the seed never becomes a string —\n // which skips the writers' scale question and the two decimals `okhst()` rounds\n // to. Both components arrive on the palette's 0–100 authoring scale and\n // `OkhstColor` wants factors, so both are divided.\n const resolved = glaze\n .color({\n from: { h: hue, s: saturation / 100, t: candidate / 100 },\n mode: 'fixed',\n })\n .resolve();\n\n return Math.min(\n labelLcOf(resolved.light),\n labelLcOf(resolved.dark),\n labelLcOf(resolved.lightContrast),\n labelLcOf(resolved.darkContrast),\n );\n };\n\n let ceiling = 100;\n\n const target = ACCENT_LABEL_LC + ACCENT_LABEL_MARGIN;\n\n if (labelLc(100) < target) {\n // Monotone in tone — a lighter fill is a weaker white label. Bisect the boundary,\n // always across the full axis so the answer is the hue's own ceiling.\n let low = 0;\n let high = 100;\n\n for (let i = 0; i < 24; i++) {\n const mid = (low + high) / 2;\n\n if (labelLc(mid) >= target) low = mid;\n else high = mid;\n }\n\n ceiling = low;\n }\n\n if (accentCapCache.size >= ACCENT_CAP_CACHE_LIMIT) accentCapCache.clear();\n accentCapCache.set(key, ceiling);\n\n return ceiling;\n}\n\n/**\n * A color seed as the three numbers Glaze consumes, with its tone capped.\n *\n * Takes the RESOLVED components rather than the caller's literal because the tone is\n * capped — {@link capAccentTone} only ever lowers it — and a capped tone means rebuilding\n * the color regardless. Passing `from: <the original string>` would emit the tone the\n * white label cannot survive.\n *\n * Shared by the accent zone and by any status theme on the color path: they resolve their\n * components differently, but a fill is a fill once the numbers are in hand.\n */\nfunction cappedAccent(\n hue: number,\n saturation: number,\n tone: number,\n): AccentSeed {\n const capped = capAccentTone(hue, saturation, tone);\n\n return {\n color: { h: hue, s: saturation / 100, t: capped / 100 },\n tone: capped,\n };\n}\n\n/**\n * Opaque stand-in for the BASE selected fill used by outline / outline-2 / clear Item\n * types (`#surface|#surface-2|#surface-3` + `#accent-surface.09`).\n *\n * Anchors the `accent-text*` contrast. `value: 9` matches the `.09` alpha overlay;\n * `space: 'srgb'` approximates CSS two-layer compositing. Inherited so colored themes\n * re-resolve against their own `surface` + `accent-surface` (default `surface-2` /\n * `surface-3` are `inherit: false` and can't be the mix base). Primary's tinted\n * `surface` makes this slightly harder than default clear-selected, covering\n * outline / outline-2 selected fills as well.\n *\n * The same in both accent arrangements — it is defined by its two ends, and both of\n * those move with the seed on their own.\n */\nconst ACCENT_SELECTED_FILL: ColorMap = {\n 'accent-selected-fill': {\n type: 'mix',\n base: 'surface',\n target: 'accent-surface',\n value: 9,\n space: 'srgb',\n },\n};\n\n/**\n * Syntax highlighting for `PrismCode`, as its own theme.\n *\n * A small palette of *adaptive* colored tokens. Each one is anchored to `surface`\n * with a relative tone plus an `['AA','AAA']` readability floor, so it stays legible\n * in light, dark and high contrast. Hues mirror the long-standing PrismCode\n * reference (pink keywords / pink functions / purple strings / green numbers / gray\n * comments); `code-attribute` keeps cyan for HTML attribute names, CSS properties\n * and selectors, while HTML/XML tag names reuse `code-keyword`. Diff insertion and\n * deletion reuse the `success-*` / `danger-*` ramps instead.\n *\n * Three things are deliberately static here. Every hue is an absolute literal, so a\n * re-seeded brand cannot rotate the syntax palette — a green brand would otherwise\n * collide `code-string` with `code-number` (156°). These factors are relative to\n * the *code* theme's own seed (`themes.code.saturation`), not the palette-level one,\n * so tuning the app's saturation cannot wash out a code block. And `pastel` is held\n * off here whatever the palette does, for the same reason: it lowers the chroma\n * ceiling hard enough to take `code-keyword` from ~0.19 to ~0.07, which is the\n * difference between distinguishable syntax and mud. Between them, these mean the\n * emitted `code-*` values are a function of the code saturation alone.\n *\n * That separate seed is the only reason these live outside the default theme: a\n * theme colour can never exceed its own seed, and four of these sit at factor `1.0`,\n * so they could not hold their chroma inside a default theme seeded below 80.\n */\nconst CODE_COLORS: ColorMap = {\n 'code-comment': {\n base: 'surface',\n hue: 280,\n saturation: 0.1,\n tone: '-50',\n contrast: ['AA', 'AAA'],\n },\n 'code-punctuation': {\n base: 'surface',\n hue: 348,\n saturation: 0.4,\n tone: '-50',\n contrast: ['AA', 'AAA'],\n },\n 'code-keyword': {\n base: 'surface',\n hue: 348,\n saturation: 1,\n tone: '-54',\n contrast: ['AA', 'AAA'],\n },\n 'code-string': {\n base: 'surface',\n hue: CODE_STRING_HUE,\n saturation: 1,\n tone: '-50',\n contrast: ['AA', 'AAA'],\n },\n 'code-number': {\n base: 'surface',\n hue: 156,\n saturation: 0.9,\n tone: '-50',\n contrast: ['AA', 'AAA'],\n },\n 'code-function': {\n base: 'surface',\n hue: 348,\n saturation: 1,\n tone: '-54',\n contrast: ['AA', 'AAA'],\n },\n 'code-attribute': {\n base: 'surface',\n hue: 200,\n saturation: 1,\n tone: '-50',\n contrast: ['AA', 'AAA'],\n },\n};\n\n/**\n * Build the standalone theme the `code-*` tokens live in.\n *\n * Its seed is the code saturation, so {@link CODE_COLORS} keeps its authored factors\n * verbatim and resolves identically at any palette saturation. The mirrored `surface`\n * is an implementation detail — it exists only as the contrast base, and is filtered\n * out of the emitted tokens by {@link pickCodeTokens}.\n *\n * `instanceConfig` arrives with `pastel` pinned off, so the mirror is non-pastel too\n * even when the app is. Harmless: at saturation factor 0.12 the pastel ceiling moves\n * the mirror's chroma and leaves its tone — the axis every `code-*` contrast floor is\n * solved against — bit-identical.\n */\nfunction buildCodeTheme(\n config: ResolvedPaletteConfig,\n instanceConfig: GlazeConfigOverride | undefined,\n): GlazeTheme {\n const codeSaturation = config.themes.code.saturation;\n const theme = glaze(config.hue, codeSaturation, instanceConfig);\n\n // Re-express the default theme's `surface` as a factor of the *code* seed, so it\n // resolves to the same colour the code actually sits on.\n //\n // The BASE seed, not the palette one: the real `surface` takes its chroma from\n // the base zone, and a mirror that tracked the palette seed would solve every\n // syntax contrast floor against a background the page does not have. `surface`\n // lands on exactly `baseSaturation` on the 0–100 scale, so the ratio of the two\n // seeds *is* the factor — there is no `SURFACE_SATURATION` left to apply.\n let mirrorFactor = config.baseSaturation / codeSaturation;\n\n if (mirrorFactor > 1) {\n // Glaze would clamp this silently and then solve every contrast floor against a\n // background the page does not have, so say so.\n //\n // Closer to reach than it used to be: a `baseColor` can push `baseSaturation` to\n // `MAX_BASE_SATURATION`, so any code saturation under 50 now trips it where it\n // previously took a `baseSaturation` above 80. `Slate` sits at 55 — a five-point\n // margin — so treat that number as load-bearing when tuning the preset.\n //\n // Deliberately NOT deduped once-per-process. It fires per token-cache miss, so a\n // drag over a misconfigured pair is noisy — but the flag would also have to\n // survive `resetPaletteConfig`, which is what a caller does to get back to a\n // config that warns again legitimately. The narrow noise is the better trade.\n // eslint-disable-next-line no-console\n console.warn(\n `[cube-ui-kit] themes.code.saturation (${codeSaturation}) is too low for ` +\n `baseSaturation ${config.baseSaturation}: the mirrored code surface would ` +\n `need a factor of ${mirrorFactor.toFixed(3)}. Clamped to 1; raise the code ` +\n `saturation to keep syntax contrast accurate.`,\n );\n mirrorFactor = 1;\n }\n\n theme.colors({\n // Tone as well as chroma: under `surfaceMode: 'tinted'` the page moved two\n // tones off the extreme, and the mirror exists to be the page.\n surface: { tone: surfaceTone(config), saturation: mirrorFactor },\n ...CODE_COLORS,\n });\n\n return theme;\n}\n\n/** What {@link buildPalette} produces: the themed palette plus the code theme. */\ninterface BuiltPalette {\n palette: GlazePalette;\n codeTheme: GlazeTheme;\n}\n\n/** Drop the mirrored `surface`, keeping only the `code-*` tokens. */\nfunction pickCodeTokens<T>(tokens: Record<string, T>): Record<string, T> {\n const out: Record<string, T> = {};\n\n for (const name of Object.keys(tokens)) {\n if (name.startsWith('code-') || name.startsWith('#code-')) {\n out[name] = tokens[name];\n }\n }\n\n return out;\n}\n\n// ============================================================================\n// Global Glaze configuration\n// ============================================================================\n\nglaze.configure({\n states: {\n dark: '@dark',\n highContrast: '@hc',\n },\n modes: {\n dark: true,\n highContrast: true,\n },\n darkTone: [14, 95],\n darkDesaturation: 0,\n});\n\n/**\n * The config-free half of a colored theme's overrides — everything anchored to\n * the theme's own `surface` with a *relative* tone, so it follows wherever\n * {@link tintedSurfaceTone} puts that surface.\n *\n * - `border` — bumped saturation so OUTLINE-variant borders pick up the theme\n * hue (used by `#<theme>-border` in `item-themes.ts`). Mirrors the\n * default-theme `border` shape (`base: 'surface'`, tone window) but with\n * higher saturation. Glaze's `extend({ colors })` redefines each listed color\n * from scratch, so we restate the full definition here.\n */\nconst TINTED_SURFACE_RAMP: ColorMap = {\n border: {\n base: 'surface',\n tone: ['-10', '-30'],\n saturation: 0.3,\n },\n 'surface-text': {\n base: 'surface',\n tone: `${TEXT_TONE - TINTED_SURFACE_TONE_OFFSET}`,\n saturation: 0.25,\n contrast: ['AA', 'AAA'],\n },\n 'surface-text-soft': {\n base: 'surface',\n tone: `${TEXT_SOFT_TONE - TINTED_SURFACE_TONE_OFFSET}`,\n saturation: 0.25,\n contrast: ['AA', 'AAA'],\n },\n 'surface-text-soft-2': {\n base: 'surface',\n tone: `${TEXT_SOFT2_TONE - TINTED_SURFACE_TONE_OFFSET}`,\n saturation: 0.25,\n contrast: ['AA', 'AAA'],\n },\n};\n\n/**\n * Per-colored-theme overrides on top of the default theme: a `surface` bumped in\n * saturation so the banner background is visibly tinted, plus\n * {@link TINTED_SURFACE_RAMP}.\n *\n * A function of the config because the surface's tone is — see\n * {@link tintedSurfaceTone}.\n */\nfunction tintedSurfaceOverride(config: ResolvedPaletteConfig): ColorMap {\n return {\n surface: {\n tone: tintedSurfaceTone(config),\n saturation: TINTED_SURFACE_SATURATION,\n },\n ...TINTED_SURFACE_RAMP,\n };\n}\n\n/**\n * A tinted surface, a banding step, and text guaranteed to read on both.\n *\n * The recipe behind {@link getColorTheme} in `./color-theme.ts`, which builds\n * one-off themes at runtime from an arbitrary hue. It lives here so it shares\n * `TINTED_SURFACE_SATURATION`, the tone offsets and the text ramp with\n * {@link tintedSurfaceOverride} above rather than forking them — a runtime tint\n * and a built-in theme's `surface` should be the same colour for the same hue,\n * which also means both have to follow the page when `surfaceMode` moves it.\n *\n * Three colours, because that is what banded, readable table column needs:\n * `surface` for even rows, `surface-2` one tone step down for odd rows and\n * pinned totals, and `surface-2-text`.\n *\n * The text is anchored to `surface-2`, not `surface`. `surface-2` has the lower\n * contrast headroom in BOTH schemas — a darker background under dark text in\n * light, a lighter one under light text in dark — so solving the floor there\n * clears it on both bands. The neutral ramp's own `surface-2-text` is shaped the\n * same way for the same reason.\n */\nexport function tintRecipe(config: ResolvedPaletteConfig): ColorMap {\n return {\n surface: {\n tone: tintedSurfaceTone(config),\n saturation: TINTED_SURFACE_SATURATION,\n },\n 'surface-2': {\n base: 'surface',\n // Mirrors the neutral ramp's step from `surface` to `surface-2`: enough to\n // read as banding down a column, not enough to read as two colours.\n tone: ['-2', '-4'],\n saturation: TINTED_SURFACE_SATURATION,\n },\n 'surface-2-text': {\n base: 'surface-2',\n tone: `${TEXT_TONE - TINTED_SURFACE_TONE_OFFSET - SURFACE_2_TEXT_OFFSET}`,\n saturation: 0.25,\n // The whole point: Glaze binary-searches the tone per schema until the floor\n // is met, so a caller cannot persist an unreadable pair.\n contrast: ['AA', 'AAA'],\n },\n /**\n * The softer step, for a tinted column HEADER.\n *\n * A neutral header is deliberately muted (`HeadRow` publishes `#dark-03`), so\n * a tinted one taking the full-strength body text read markedly darker than\n * the headers either side of it — measured at 16:1 against their 4.9:1. This\n * keeps the two consistent, and the `AA` floor still applies.\n */\n 'surface-2-text-soft': {\n base: 'surface-2',\n tone: `${TEXT_SOFT_TONE - TINTED_SURFACE_TONE_OFFSET - SURFACE_2_TEXT_OFFSET}`,\n saturation: 0.25,\n contrast: ['AA', 'AAA'],\n },\n };\n}\n\n// ============================================================================\n// The accent system\n// ============================================================================\n\n/**\n * The brand family, in one of two arrangements.\n *\n * **`null`** — the shipped one, verbatim. Every fill hangs off a fixed white\n * `accent-surface-text` at a relative tone delta behind an `['AA','AAA']` floor, and\n * the text pair hangs off `accent-selected-fill` at `-49`. Those floors are\n * load-bearing rather than decorative: relaxing them moves the shipped `accent-text`\n * light tone from 38.76 to 48.63 and the `accent-surface` high-contrast tone from\n * 36.08 to 51.00. Nothing on this path may change — `palette.test.ts` snapshots it.\n *\n * **A color** — the caller handed us one, and the job is to render *that*. Glaze's\n * `from` takes the value directly: it supplies the hue, the tone, and an absolute\n * saturation that does not answer to the theme seed, and it reproduces the value\n * exactly in the light/normal-contrast variant. The floors drop to\n * {@link ACCENT_FILL_CONTRAST} on top, because anchored the shipped way every accent\n * lands at roughly tone 50 whatever went in, and a 4.5 floor then crushes anything\n * light besides.\n *\n * Two details worth keeping straight:\n *\n * - **The ramp carries no floor.** Re-anchored onto the fill, an `['AA','AAA']` floor\n * is measured against the *fill* rather than white and collapses all three steps to\n * near-black. A plain tone step is what the relationship actually is.\n * - **The text pair stays `mode: 'auto'`.** Link text has to invert on a dark page, so\n * exactness belongs to the fill; the text is a brand-toned companion of it.\n */\nfunction accentFillColors(accent: AccentSeed): ColorMap {\n if (accent == null) {\n return {\n // ---- Accent system (theme-aware, inherited by colored themes) ----\n // Everything here is anchored to a fixed white \"accent-surface-text\" via\n // `mode: 'fixed'` + relative tone deltas, so accent colors stay visually\n // consistent across light/dark/high-contrast schemas (the brand color does\n // not flip). The solid fills are white-text-on-brand backgrounds, so they\n // keep an `['AA','AAA']` contrast floor even though the chosen tone deltas\n // already exceed it. This leaves room for a future low-contrast scale.\n 'accent-surface-text': { tone: 100, mode: 'fixed' },\n 'accent-surface': {\n base: 'accent-surface-text',\n tone: '-49',\n contrast: ['AA', 'AAA'],\n mode: 'fixed',\n },\n 'accent-surface-2': {\n base: 'accent-surface-text',\n tone: '-52',\n contrast: ['AA', 'AAA'],\n mode: 'fixed',\n },\n 'accent-surface-3': {\n base: 'accent-surface-text',\n tone: '-55',\n contrast: ['AA', 'AAA'],\n mode: 'fixed',\n },\n // Hover variant of `accent-surface` — a *fixed*-mode darker shade used as\n // the hover fill for solid PRIMARY-type buttons. Anchored to the same\n // accent-surface-text so it stays in the same hue family. The relative tone\n // lands a few steps darker than the pressed state in both schemas.\n 'accent-surface-hover': {\n base: 'accent-surface-text',\n tone: '-58',\n contrast: ['AA', 'AAA'],\n mode: 'fixed',\n },\n // Border for accent surfaces — a small relative tone step away from the\n // brand fill so it stays in the brand hue family and does not flip in dark\n // mode. No contrast prop needed; the delta is chosen directly on the tone\n // scale.\n 'accent-surface-border': {\n base: 'accent-surface',\n tone: '+13',\n mode: 'fixed',\n },\n };\n }\n\n return {\n // Still a hard white root, and still the PRIMARY label: every `type=\"primary\"`\n // item in `src/data/item-themes.ts` writes `#white` directly. That is exactly what\n // ACCENT_FILL_CONTRAST protects — and in light, where `surface` IS white, the\n // floor below measures that label pair directly.\n 'accent-surface-text': { tone: 100, mode: 'fixed' },\n 'accent-surface': {\n from: accent.color,\n base: 'surface',\n contrast: ACCENT_FILL_CONTRAST,\n mode: 'fixed',\n },\n 'accent-surface-2': {\n base: 'accent-surface',\n tone: ACCENT_RAMP.surface2,\n mode: 'fixed',\n },\n 'accent-surface-3': {\n base: 'accent-surface',\n tone: ACCENT_RAMP.surface3,\n mode: 'fixed',\n },\n 'accent-surface-hover': {\n base: 'accent-surface',\n tone: ACCENT_RAMP.hover,\n mode: 'fixed',\n },\n // `'+13'` overshoots 100 on a light brand and `autoFlip` mirrors it to `-13`,\n // which is the right answer either way: a border on a light fill has to be the\n // darker of the two (`#FFD400` → `#d3af00`).\n 'accent-surface-border': {\n base: 'accent-surface',\n tone: '+13',\n mode: 'fixed',\n },\n };\n}\n\n/**\n * The brand FOREGROUNDS — text on a neutral surface, and the icon.\n *\n * Split from {@link accentFillColors} because the two have different audiences: the\n * fill ramp is shared with the standalone `special` theme, which builds its own\n * purpose-made subset and must not gain tokens it does not consume. Only the default\n * theme (and the colored themes that inherit from it) takes these.\n *\n * These stay `mode: 'auto'` in both arrangements — a link has to invert on a dark page,\n * so exactness belongs to the fill and these are its brand-toned companions.\n */\nfunction accentTextColors(accent: AccentSeed): ColorMap {\n if (accent == null) {\n return {\n ...ACCENT_SELECTED_FILL,\n // Stronger brand text for HOVERED selected outline/clear labels and LINK\n // hover. Same anchor + preferred tone as `accent-text-soft`, but a higher\n // `contrast: [6, 11]` floor against `accent-selected-fill` so it reads as a\n // clear step up from the soft rest color while staying saturated (a bare\n // `AAA`/`7` floor over-darkens light and desaturates dark). The HC pair keeps\n // it at/above the soft variant (which auto-promotes AA→AAA in HC).\n // `mode: 'auto'` (default) keeps dark-mode text readable on dark surfaces.\n 'accent-text': {\n base: 'accent-selected-fill',\n tone: '-49',\n saturation: 1,\n contrast: [6, 11],\n },\n // Rest brand text for selected outline/clear labels and LINK base color.\n // Anchored to `accent-selected-fill` with `contrast: 'AA'` — the measured\n // floor for every BASE state of those Item types (surface / outline /\n // outline-2 / clear selected fills). Sits visibly less prominent than\n // `accent-text` (lighter in light, darker in dark) so the rest→hover\n // intensify is real.\n 'accent-text-soft': {\n base: 'accent-selected-fill',\n tone: '-49',\n saturation: 1,\n contrast: ['AA', 'AAA'],\n },\n 'accent-icon': {\n base: 'surface',\n tone: '-38',\n saturation: 0.9375,\n },\n };\n }\n\n return {\n ...ACCENT_SELECTED_FILL,\n // The rest link color IS the brand, which is the visible payoff of a color seed;\n // the hover one steps past it so the intensify survives. See\n // ACCENT_TEXT_HOVER_STEP.\n // The hover text is the one place the derived NUMBER is still needed: `from`\n // carries the tone, but a step past it has to be computed.\n 'accent-text': {\n from: accent.color,\n base: 'accent-selected-fill',\n tone: Math.max(0, accent.tone - ACCENT_TEXT_HOVER_STEP),\n contrast: ACCENT_TEXT_HOVER_CONTRAST,\n },\n 'accent-text-soft': {\n from: accent.color,\n base: 'accent-selected-fill',\n contrast: ACCENT_TEXT_CONTRAST,\n },\n 'accent-icon': {\n from: accent.color,\n base: 'surface',\n contrast: ACCENT_TEXT_CONTRAST,\n },\n };\n}\n\n/** Every accent token the default theme carries — fills, foregrounds, and the mix. */\nfunction accentColors(accent: AccentSeed): ColorMap {\n return {\n ...accentFillColors(accent),\n ...accentTextColors(accent),\n };\n}\n\n// ============================================================================\n// Palette construction\n// ============================================================================\n\n/**\n * Build the full palette from a resolved config.\n *\n * Pure: it reads nothing but its argument (and the global Glaze config, which\n * the caller sets first), so re-running it with the same config yields the same\n * tokens. Theme instances are immutable with respect to their hue / saturation\n * seed and a `GlazePalette` has no mutation API, so re-seeding means rebuilding.\n */\nfunction buildPalette(\n config: ResolvedPaletteConfig,\n options: {\n /**\n * Carry `contrastLevel` on the theme instances instead of relying on the\n * global Glaze config. Used when rendering a palette the app is not running,\n * so a preview cannot disturb the live one. The resolved values are the same\n * either way; only which schema variants get *emitted* differs, and callers\n * that isolate pick their variant explicitly.\n */\n isolateContrastLevel?: boolean;\n } = {},\n): BuiltPalette {\n const {\n hue,\n baseHue,\n saturation,\n accentColor,\n accentTone,\n accentSaturation,\n pastel,\n themes,\n contrastLevel,\n } = config;\n\n // Built from the RESOLVED components, not the literal — see `cappedAccent`. Handing\n // Glaze the original string would pin `accent-surface` to an uncapped tone while its\n // `-2`, `-3` and hover siblings stepped off the capped one, splitting the ramp so a\n // primary button changed weight on hover.\n const accent: AccentSeed =\n accentColor !== null && accentTone !== null && accentSaturation !== null\n ? cappedAccent(hue, accentSaturation, accentTone)\n : null;\n\n // The base zone's tone anchor and its own chroma share, applied to every color\n // that carries `hue: baseHue` below — that prop is the boundary of the zone.\n const baseTone = surfaceTone(config);\n const baseScale = baseSaturationScale(config);\n const baseChroma = (factor: number) => Math.min(1, factor * baseScale);\n\n // The one override the code theme shares with the rest of the palette.\n const sharedOverrides: GlazeConfigOverride = options.isolateContrastLevel\n ? { contrastLevel }\n : {};\n\n // `pastel` is instance-level in Glaze (not settable via `glaze.configure`).\n // Setting it on the two root themes is enough: `extend({ config })` merges\n // with the parent's override, so the derived themes inherit it. When it is\n // off we omit the field entirely rather than pass `false` — the two are\n // equivalent, and omitting keeps the default output provably untouched.\n const overrides: GlazeConfigOverride = {\n ...sharedOverrides,\n ...(pastel ? { pastel: true } : null),\n };\n const instanceConfig: GlazeConfigOverride | undefined = Object.keys(overrides)\n .length\n ? overrides\n : undefined;\n\n // `pastel` deliberately does *not* reach the code theme: syntax colors answer\n // to `themes.code.saturation` and nothing else. Pinned to `false` rather than\n // omitted — the same value Glaze defaults to, but here it is the point of the\n // override, not an absence. See {@link buildCodeTheme}.\n const codeInstanceConfig: GlazeConfigOverride = {\n ...sharedOverrides,\n pastel: false,\n };\n\n // --------------------------------------------------------------------------\n // Default theme (neutral, primary in palette → exported unprefixed)\n // --------------------------------------------------------------------------\n\n const defaultTheme = glaze(hue, saturation, instanceConfig);\n\n defaultTheme.colors({\n // ---- Surfaces (neutral, very low saturation) ----\n surface: {\n tone: baseTone,\n saturation: baseChroma(SURFACE_SATURATION),\n hue: baseHue,\n },\n 'surface-2': {\n hue: baseHue,\n base: 'surface',\n tone: ['-2', '-4'],\n saturation: baseChroma(0.1),\n inherit: false,\n },\n 'surface-3': {\n hue: baseHue,\n base: 'surface',\n tone: ['-4', '-8'],\n saturation: baseChroma(0.1),\n inherit: false,\n },\n 'surface-4': {\n hue: baseHue,\n base: 'surface',\n tone: ['-6', '-12'],\n saturation: baseChroma(0.1),\n inherit: false,\n },\n\n // ---- Text on surfaces ----\n // Tone is the contrast-uniform OKHST axis, so a relative tone delta directly\n // encodes a WCAG contrast step. `surface-text` sits at tone 2 (a -98 delta\n // from white), giving the same near-black appearance as the legacy `#dark`.\n // We still keep an `['AA','AAA']` contrast floor on every text-on-surface\n // color so a future low-contrast scale can shift the ramp down without ever\n // breaking readability.\n 'surface-text': {\n hue: baseHue,\n base: 'surface',\n tone: `${TEXT_TONE - 2}`,\n saturation: baseChroma(0.2),\n contrast: ['AA', 'AAA'],\n },\n 'surface-text-soft': {\n hue: baseHue,\n base: 'surface',\n tone: `${TEXT_SOFT_TONE - 2}`,\n saturation: baseChroma(0.2),\n contrast: ['AA', 'AAA'],\n inherit: false,\n },\n 'surface-text-soft-2': {\n hue: baseHue,\n base: 'surface',\n tone: `${TEXT_SOFT2_TONE - 2}`,\n saturation: baseChroma(0.2),\n contrast: ['AA', 'AAA'],\n inherit: false,\n },\n 'surface-2-text': {\n hue: baseHue,\n base: 'surface-2',\n tone: `${TEXT_TONE + SURFACE_2_TEXT_OFFSET}`,\n saturation: baseChroma(0.2),\n contrast: ['AA', 'AAA'],\n inherit: false,\n },\n 'surface-2-text-soft': {\n hue: baseHue,\n base: 'surface-2',\n tone: `${TEXT_SOFT_TONE + SURFACE_2_TEXT_OFFSET}`,\n saturation: baseChroma(0.2),\n contrast: ['AA', 'AAA'],\n inherit: false,\n },\n 'surface-3-text': {\n hue: baseHue,\n base: 'surface-3',\n tone: `${TEXT_TONE + SURFACE_3_TEXT_OFFSET}`,\n saturation: baseChroma(0.2),\n contrast: ['AA', 'AAA'],\n inherit: false,\n },\n 'surface-3-text-soft': {\n hue: baseHue,\n base: 'surface-3',\n tone: `${TEXT_SOFT_TONE + SURFACE_3_TEXT_OFFSET}`,\n saturation: baseChroma(0.2),\n contrast: ['AA', 'AAA'],\n inherit: false,\n },\n\n // ---- Other neutral UI primitives (default-only) ----\n border: {\n hue: baseHue,\n base: 'surface',\n tone: ['-10', '-30'],\n saturation: baseChroma(0.175),\n },\n placeholder: {\n hue: baseHue,\n base: 'surface',\n tone: '-33',\n saturation: baseChroma(0.175),\n inherit: false,\n },\n focus: {\n base: 'surface',\n tone: '-29',\n saturation: 0.8625,\n inherit: false,\n },\n disabled: {\n tone: 80.8,\n saturation: 0.4,\n inherit: false,\n },\n // Disabled fill chip + text — both adaptive (mode 'auto') and positioned\n // with relative tone deltas against `surface` so the disabled state has the\n // same perceived intensity in light, dark, and high-contrast schemas. No\n // numeric contrast prop is needed: tone is already on a WCAG-uniform scale.\n //\n // Tone deltas reproduce the legacy palette's disabled appearance exactly:\n // the chip sits ~cr 1.11 vs `surface` (a faint greyed pill for\n // OUTLINE/SECONDARY/item disabled states) and the label ~cr 2.02 vs\n // `surface` — intentionally sub-AA so it reads as disabled while staying the\n // same softness clear/link disabled labels had before. PRIMARY-style buttons\n // use the brand-tinted `accent-disabled-*` variants below instead. Both\n // tokens stay default-only (`inherit: false`) — colored themes inherit their\n // own disabled chip via the accent variants.\n 'disabled-surface': {\n hue: baseHue,\n base: 'surface',\n tone: '-3.5',\n saturation: baseChroma(0.2),\n inherit: false,\n },\n 'disabled-surface-text': {\n base: 'surface',\n tone: '-23',\n saturation: 0.3,\n inherit: false,\n },\n\n // Fixed-mode \"always dark\" surface for elements that intentionally stay\n // inverted regardless of schema (tooltips, code blocks, popovers with their\n // own dark theme, etc.). `mode: 'fixed'` bypasses the dark-schema inversion\n // so the color reads as a dark surface in light, dark, and high-contrast.\n // Pair with `#white` (built-in) for foreground text.\n 'surface-inverse': {\n hue: baseHue,\n tone: 12,\n saturation: baseChroma(0.475),\n mode: 'fixed',\n inherit: false,\n },\n\n // ---- Accent system (theme-aware, inherited by colored themes) ----\n // Two arrangements, one per seeding mode — see `accentColors`.\n ...accentColors(accent),\n\n // Brand-tinted disabled chip + label for PRIMARY-style buttons (solid brand\n // fill). The chip is schema-symmetric (`mode: 'fixed'`) so the muted state\n // reads the same weight in light/dark/HC; saturation is bumped so it stays\n // identifiable as a muted brand color.\n //\n // The label rides the extreme away from the chip (`tone: 'max'`, ~cr 1.7):\n // deliberately faint, so it reads as disabled rather than as live text.\n // `'max'` is the authored intent — it was temporarily hand-approximated as a\n // relative `tone: '+15'` with `autoFlip: false`, because Glaze < 1.2.0\n // re-mapped the extreme through the dark tone window, compressing the\n // base-to-extreme span and dropping the dark label's contrast. Glaze 1.2.0\n // (tenphi/glaze#82) instead replays the light schema's base→extreme tone\n // shift against the base's resolved dark tone — same-signed under\n // `mode: 'fixed'` — so `'max'` holds its intended separation in every schema\n // and the approximation is no longer needed. Inherited per theme.\n //\n // The special theme keeps its own relative `+18` pair: its `surface` is a\n // fixed dark tone, so an extreme there would resolve to white and read as\n // live text rather than disabled.\n 'accent-disabled-surface': {\n base: 'surface',\n tone: '-13',\n saturation: 0.5,\n mode: 'fixed',\n },\n 'accent-disabled-surface-text': {\n base: 'accent-disabled-surface',\n tone: 'max',\n saturation: 0.4,\n mode: 'fixed',\n },\n\n // The disabled counterpart of `accent-text-soft`, for the SELECTED disabled\n // state of the non-solid types (`outline`, `outline-2`, `clear`).\n //\n // Those types express `selected & disabled` by keeping the enabled selected\n // CHIP and fading only the LABEL, so this is the one token the state needs.\n // It is the neutral `disabled-surface-text` geometry — the same `-23` tone\n // delta against `surface`, adaptive, so it reads exactly as disabled as\n // every other disabled label in light, dark and HC — carrying brand chroma\n // instead of neutral: roughly 2x `disabled-surface-text` and comfortably\n // under `accent-text-soft`, which is the live label it must not be mistaken\n // for. Selection therefore survives as a hue on a label of unchanged\n // paleness.\n //\n // The pair above (`accent-disabled-surface` / `-text`) stays what it always\n // was: the muted chip for a PRIMARY button, whose enabled state is an opaque\n // brand fill under `#white`. Borrowing it here got the state backwards — its\n // mid-tone chip is a step DOWN from a solid fill but a step UP from a 9%\n // tint, and its `tone: 'max'` label resolves to literal white in light mode.\n 'accent-disabled-text': {\n base: 'surface',\n tone: '-23',\n saturation: 0.6,\n },\n\n // ---- Isometric cube faces ----\n // The three shading steps of the kit's isometric cube artwork: the lit top\n // face, the mid side, and the shadowed side. Shared by `LoadingAnimation`\n // and `NoDataIcon` so both read as the same object under the same light.\n //\n // Two things are deliberate here.\n //\n // **Neutral chroma.** These used to take a fraction of the *brand* seed\n // saturation (0.3 / 0.62 / 0.66), which put the shadowed face at chroma\n // 0.0676 — eight times `border` — so the animation read as a purple gradient\n // next to a monochrome `CubeLogo`. They now take `baseChroma(0.2)`, the same\n // normalised share the neutral chrome (`border`, `placeholder`, the text\n // ramp) takes, which lands them at 0.0059 / 0.0161 / 0.0248: a tint that\n // follows a re-seeded brand hue without announcing it.\n //\n // **Contrast, not tone, is the spec.** A relative tone delta is uniform on\n // the OKHST scale but the dark schema resolves it inside the `darkTone`\n // window, which compressed the ramp to ~75% of its light span — measurably\n // flatter, which is exactly how it looked. Glaze has no per-color\n // `darkTone`, so the fix is to state the intent as a WCAG floor against\n // `surface` and let each schema solve for it: the authored `tone: '-2'` is\n // deliberately short of every floor, so all three faces are pinned by the\n // ratio in every schema rather than by a delta that means different things\n // in each. Measured on the emitted tokens, light comes out 1.201 / 1.653 /\n // 2.409 and dark 1.212 / 1.666 / 2.424 — within 1% of each other, against\n // 1.063 / 1.320 / 1.915 vs 1.053 / 1.264 / 1.735 before.\n //\n // WCAG rather than APCA, against the grain of the accent tokens above:\n // APCA's low-contrast clamp scores every step of a ramp this subtle as\n // Lc 0, so it cannot express the difference between these three faces at\n // all. Polarity-blindness — the reason APCA wins for text — costs nothing\n // for a decorative fill whose only job is to separate from the page.\n //\n // The high-contrast entries roughly double each step's distance from the\n // page (1.351 / 2.107 / 3.211) instead of leaving HC identical to the\n // normal tier, which is what an unconstrained tone delta gave.\n 'loading-face-1': {\n hue: baseHue,\n base: 'surface',\n tone: '-2',\n saturation: baseChroma(0.2),\n contrast: [1.2, 1.35],\n inherit: false,\n },\n 'loading-face-2': {\n hue: baseHue,\n base: 'surface',\n tone: '-2',\n saturation: baseChroma(0.2),\n contrast: [1.65, 2.1],\n inherit: false,\n },\n 'loading-face-3': {\n hue: baseHue,\n base: 'surface',\n tone: '-2',\n saturation: baseChroma(0.2),\n contrast: [2.4, 3.2],\n inherit: false,\n },\n\n // ---- Shadows (default-only) ----\n 'shadow-sm': {\n type: 'shadow',\n bg: 'surface',\n fg: 'surface-text',\n intensity: 5,\n inherit: false,\n },\n 'shadow-md': {\n type: 'shadow',\n bg: 'surface',\n fg: 'surface-text',\n intensity: 10,\n inherit: false,\n },\n 'shadow-lg': {\n type: 'shadow',\n bg: 'surface',\n fg: 'surface-text',\n intensity: 15,\n inherit: false,\n },\n\n // Backdrop overlay (translucent)\n overlay: { tone: 10, opacity: 0.5, inherit: false, hue: baseHue },\n });\n\n // --------------------------------------------------------------------------\n // Colored themes\n // --------------------------------------------------------------------------\n //\n // `primary` is the brand theme: it shares both the accent hue and the palette\n // seed saturation with `default`, and differs only in carrying a tinted surface.\n // Each status theme adds its own hue on top.\n const tintedSurface = tintedSurfaceOverride(config);\n const primaryTheme = defaultTheme.extend({\n colors: tintedSurface,\n });\n\n /**\n * One status theme's overrides, on whichever path its own seed is on.\n *\n * **Its own color** — the family becomes that color, exactly as the accent zone's does,\n * so the softened APCA floors in `accentColors`' color arm come with it rather than\n * being restated here. The theme's Glaze seed is the color's chroma (see\n * `ResolvedThemeSeed.saturation`), so the tinted surface, the border and the text ramp\n * re-solve at their shipped factors OF that chroma — the banner keeps its proportion to\n * the button instead of drifting relative to a fill whose chroma arrives absolute.\n *\n * **No color of its own, but the ACCENT has one** — the accent color's tone is taken\n * back out. The tone is the brand's, and only the brand's. Inherited, a light brand\n * would put `#danger-accent-surface` at tone 88 in a red hue — a pale pink danger\n * button, which is not a danger button. A status theme carries a *meaning* its hue\n * exists to signal, so absent an instruction of its own it keeps the white-anchored\n * derivation that lands every hue at a comparable weight. `extend({ colors })`\n * redefines each listed color from scratch, so restating the null arrangement is enough\n * to undo it.\n *\n * **Neither** — the tinted-surface override itself, so the shipped palette is provably\n * untouched.\n */\n const statusColors = (theme: ResolvedThemeSeed): ColorMap => {\n if (theme.color !== null && theme.colorTone !== null) {\n return {\n ...tintedSurface,\n ...accentColors(\n cappedAccent(theme.hue, theme.saturation, theme.colorTone),\n ),\n };\n }\n\n return accent == null\n ? tintedSurface\n : { ...tintedSurface, ...accentColors(null) };\n };\n\n const successTheme = defaultTheme.extend({\n hue: themes.success.hue,\n saturation: themes.success.saturation,\n colors: statusColors(themes.success),\n });\n const dangerTheme = defaultTheme.extend({\n hue: themes.danger.hue,\n saturation: themes.danger.saturation,\n colors: statusColors(themes.danger),\n });\n const warningTheme = defaultTheme.extend({\n hue: themes.warning.hue,\n saturation: themes.warning.saturation,\n colors: statusColors(themes.warning),\n });\n const noteTheme = defaultTheme.extend({\n hue: themes.note.hue,\n saturation: themes.note.saturation,\n colors: statusColors(themes.note),\n });\n\n // --------------------------------------------------------------------------\n // Special theme (fixed-mode, NOT inherited from the default theme)\n // --------------------------------------------------------------------------\n //\n // Standalone theme for `special`-variant components (hero CTAs, banners, etc.)\n // that intentionally sit on a dark surface regardless of the active schema.\n //\n // Every token here is `mode: 'fixed'` so the resolved value is identical in\n // light, dark, and high-contrast. The shape is purpose-built (not a full\n // mirror of the default theme) — only what `SPECIAL_*_STYLES` in\n // `src/data/item-themes.ts` consumes is emitted. It follows the *primary*\n // seed, since it is the brand-on-dark theme.\n //\n // Token rundown:\n // - `surface` — dark L≈12 backdrop (same value as `#surface-inverse`).\n // - `accent-surface` / `accent-surface-2` / `accent-surface-3` —\n // brand PRIMARY fill ramp (default → hover → pressed). Mirrors the\n // `#primary-accent-surface` / `-2` / `-3` ramp on the colored themes so\n // `SPECIAL_PRIMARY_STYLES.fill` can use the same shape.\n // - `accent-surface-hover` — legacy alias kept around for the\n // `#special-hover` color shortcut in `src/tokens/colors.ts`. Item\n // themes themselves no longer reference it.\n // - `accent-surface-text` — fixed white (= built-in `#white`), exposed\n // for explicit references.\n // - `accent-text` — dark brand foreground readable on white. Used as\n // CLEAR-variant text on the always-white pill, and as the\n // pressed/focused border on the brand primary fill. Matches the\n // legacy `#fixed-primary-text` alias (= `#primary-accent-surface-hover`).\n // - `accent-disabled-surface` / `accent-disabled-surface-text` —\n // brand-tinted disabled chip + label, positioned with relative tone\n // deltas against the fixed dark `surface` so the disabled state is\n // schema-symmetric.\n\n const specialTheme = glaze(hue, saturation, instanceConfig);\n\n specialTheme.colors({\n surface: { tone: 12, saturation: 0.475, mode: 'fixed' },\n\n // The same fill ramp as the default theme, and for the same reason: this IS the\n // brand CTA — `SPECIAL_PRIMARY_STYLES.fill` mirrors `#primary-accent-surface` — so\n // leaving it on the white-anchored derivation would give a yellow brand a\n // dark-purple hero button.\n //\n // Only the fills: `accent-selected-fill`, `accent-text-soft` and `accent-icon` are\n // not part of this theme's purpose-built shape, and adding them would grow the\n // emitted token set.\n ...accentFillColors(accent),\n\n // Special's `accent-text` is \"dark brand, readable on a WHITE pill\" (the CLEAR\n // selected fill) — not on `surface`, which here is the fixed dark backdrop. It\n // stays anchored to `accent-surface-text`, which is a hard white root in both\n // arrangements, so the `-58` delta keeps its meaning either way.\n 'accent-text': {\n base: 'accent-surface-text',\n tone: '-58',\n contrast: ['AA', 'AAA'],\n mode: 'fixed',\n },\n\n 'accent-disabled-surface': {\n base: 'surface',\n tone: '+4',\n saturation: 0.5,\n mode: 'fixed',\n },\n 'accent-disabled-surface-text': {\n base: 'accent-disabled-surface',\n tone: '+18',\n saturation: 0.4,\n mode: 'fixed',\n },\n });\n\n // --------------------------------------------------------------------------\n // Palette composition\n // --------------------------------------------------------------------------\n //\n // `purple` is the legacy alias for `primary` and resolves to identical tokens\n // (same theme instance, different prefix → no token-value collision).\n //\n // `default` is emitted with an empty prefix (custom map in `getPaletteTokens`),\n // so its tokens appear unprefixed (`#surface`, `#border`, …). All other themes\n // are prefixed with `<themeName>-`.\n return {\n palette: glaze.palette({\n default: defaultTheme,\n primary: primaryTheme,\n purple: primaryTheme,\n success: successTheme,\n danger: dangerTheme,\n warning: warningTheme,\n note: noteTheme,\n special: specialTheme,\n }),\n // Kept out of the palette on purpose: it has to emit `code-*` unprefixed, and\n // its mirrored `surface` would collide with the default theme's.\n codeTheme: buildCodeTheme(config, codeInstanceConfig),\n };\n}\n\n// ============================================================================\n// Tasty-formatted token export\n// ============================================================================\n\nconst TOKEN_PREFIXES = {\n default: '',\n primary: 'primary-',\n purple: 'purple-',\n success: 'success-',\n danger: 'danger-',\n warning: 'warning-',\n note: 'note-',\n special: 'special-',\n};\n\nlet paletteCache: GlazePalette | null = null;\nlet codeThemeCache: GlazeTheme | null = null;\nlet paletteTokensCache: Styles | null = null;\nlet cachedVersion = -1;\n\n/**\n * Whether *we* are the ones holding a manual level in Glaze's global config.\n *\n * `contrastLevel` is the one palette option that has to be global: a global\n * level pins `modes.highContrast: false` so no high-contrast tier is emitted at\n * all (which is the point — a manual level already carries the preference),\n * whereas a per-instance level would leave the tier in place and report its own\n * values there. `palette.tasty({ modes })` cannot override that either.\n *\n * Being global means it is shared with the host app, so we only write it when\n * asked. A host that sets its own `glaze.configure({ contrastLevel })` and never\n * touches `setPaletteConfig({ contrastLevel })` keeps its level — without this\n * flag we would silently reset it to `'auto'` on the first token read.\n */\nlet ownsContrastLevel = false;\n\nfunction applyContrastLevel(contrastLevel: number | 'auto') {\n // Nothing to do: we hold no level, and the config does not ask for one.\n if (contrastLevel === 'auto' && !ownsContrastLevel) return;\n\n // Always explicit — `configure()` never clears a field by omission, so\n // returning to `'auto'` has to say so.\n glaze.configure({ contrastLevel });\n ownsContrastLevel = contrastLevel !== 'auto';\n}\n\nfunction refresh() {\n const version = getPaletteVersion();\n\n if (paletteTokensCache && cachedVersion === version) return;\n\n const config = getPaletteConfig();\n\n applyContrastLevel(config.contrastLevel);\n\n const built = buildPalette(config);\n\n paletteCache = built.palette;\n paletteTokensCache = {\n ...built.palette.tasty({ prefix: TOKEN_PREFIXES, format: 'oklch' }),\n ...pickCodeTokens(built.codeTheme.tasty({ format: 'oklch' })),\n } as Styles;\n codeThemeCache = built.codeTheme;\n cachedVersion = version;\n}\n\n/**\n * Resolve Glaze palette tokens against the current palette config.\n *\n * Memoized against the config version (see `./palette-config.ts`), so repeated\n * reads are free and `setPaletteConfig()` invalidates them. Resolution is\n * deferred rather than done at module import, so host apps can call\n * `setPaletteConfig(...)` / `glaze.configure(...)` after importing\n * `@cube-dev/ui-kit` and still affect the first paint.\n *\n * Keys use `#name` syntax; values are state maps:\n * '#surface': { '': 'oklch(...)', '@dark': 'oklch(...)', '@hc': 'oklch(...)' }\n */\nexport function getPaletteTokens(): Styles {\n refresh();\n\n return paletteTokensCache!;\n}\n\n/** The live `GlazePalette`. Rebuilt whenever the palette config changes. */\nexport function getPalette(): GlazePalette {\n refresh();\n\n return paletteCache!;\n}\n\n/**\n * The live theme behind the `code-*` tokens. Separate from {@link getPalette}\n * because it carries its own saturation seed — see {@link CODE_COLORS}.\n */\nexport function getCodeTheme(): GlazeTheme {\n refresh();\n\n return codeThemeCache!;\n}\n\n// ============================================================================\n// One-variant render (for previewing a palette on a region)\n// ============================================================================\n\nconst VARIANT_KEY = {\n 'light:false': 'light',\n 'light:true': 'lightContrast',\n 'dark:false': 'dark',\n 'dark:true': 'darkContrast',\n} as const;\n\n/**\n * Single-entry memo: all four variants of the last config rendered.\n *\n * Keyed on the palette version as well as the config, because the config is not\n * the only input — `buildPalette` also reads Glaze's own global config\n * (`darkTone`, `darkDesaturation`, the state map). A host that drives Glaze\n * directly and then calls `invalidatePaletteTokens()` bumps the version without\n * touching the config, and the region previews have to follow the document.\n */\nlet renderKey: string | null = null;\nlet renderVariants: Record<string, Record<string, string>> = {};\n\n/**\n * Resolve one schema variant of a palette to flat, literal color values.\n *\n * Unlike {@link getPaletteTokens}, which emits state maps (`@dark` / `@hc`) for\n * the whole document, this collapses the palette to the single variant you ask\n * for. That is what makes a **region** preview possible: applied through a tasty\n * `tokens` prop, the values override the inherited ones for that subtree only, so\n * a dark or high-contrast theme can be shown inside a light page.\n *\n * Prefer `renderColorTokens()` from `./colors` unless you specifically want the\n * palette without the legacy aliases.\n */\nexport function renderPaletteTokens(\n options: RenderPaletteOptions = {},\n): Tokens {\n const { schema = 'light', highContrast = false, ...config } = options;\n const resolved = resolvePaletteConfig(config);\n const key = `${getPaletteVersion()}:${JSON.stringify(resolved)}`;\n\n if (renderKey !== key) {\n // Force the *global* level to `'auto'` for the duration of the export.\n // Glaze suppresses high-contrast output whenever a global level is set, and\n // that check reads the global config — so without this, a preview's variants\n // would depend on whatever contrast mode the surrounding app happens to be\n // in. The preview's own level rides on the theme instances instead\n // (`isolateContrastLevel`), so the values stay correct. Restored in `finally`;\n // resolution is synchronous, so nothing can observe the window.\n const previousLevel = glaze.getConfig().contrastLevel;\n\n try {\n glaze.configure({ contrastLevel: 'auto' });\n\n const built = buildPalette(resolved, { isolateContrastLevel: true });\n const modes = { dark: true, highContrast: true };\n const themed = built.palette.tokens({\n prefix: TOKEN_PREFIXES,\n format: 'oklch',\n modes,\n });\n const code = built.codeTheme.tokens({ format: 'oklch', modes });\n\n // Built into a local and published together, so a throw part-way through\n // cannot leave a half-filled map sitting under the previous key.\n const variants: typeof renderVariants = {};\n\n for (const name of Object.keys(themed)) {\n variants[name] = {\n ...themed[name],\n ...pickCodeTokens(code[name] ?? {}),\n };\n }\n\n renderVariants = variants;\n renderKey = key;\n } finally {\n glaze.configure({ contrastLevel: previousLevel });\n }\n }\n\n const variant = VARIANT_KEY[`${schema}:${highContrast}`];\n // The fallback is for `contrastLevel: 100` only. There the normal variants\n // already *are* the high-contrast ones, so Glaze emits a single light/dark set\n // rather than duplicating it — and `highContrast` correctly resolves to the same\n // colors. At every other level the contrast variants are present and genuinely\n // escalated, so the fallback is not taken.\n const flat = renderVariants[variant] ?? renderVariants[schema];\n const out: Tokens = {};\n\n for (const name of Object.keys(flat)) out[`#${name}`] = flat[name];\n\n return out;\n}\n\n/**\n * Lazy proxy of {@link getPaletteTokens}. Prefer `getPaletteTokens()` in new\n * code. Property / enumeration access always reflects the current palette\n * config.\n */\nexport const PALETTE_TOKENS: Styles = lazyStyles(getPaletteTokens);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"palette.js","names":[],"sources":["../../src/tokens/palette.ts"],"sourcesContent":["import {\n apcaContrast,\n glaze,\n okhslToLinearSrgb,\n variantToOkhsl,\n} from '@tenphi/glaze';\n\nimport { lazyStyles } from './lazy-styles';\nimport {\n getPaletteConfig,\n getPaletteVersion,\n resolvePaletteConfig,\n SURFACE_SATURATION_SHARE,\n} from './palette-config';\n\nimport type {\n ColorMap,\n ContrastSpec,\n GlazeColorValue,\n GlazeConfigOverride,\n GlazePalette,\n GlazeTheme,\n} from '@tenphi/glaze';\nimport type { Styles, Tokens } from '@tenphi/tasty';\nimport type {\n PaletteConfig,\n ResolvedPaletteConfig,\n ResolvedThemeSeed,\n} from './palette-config';\n\n/** Which resolved scheme variant {@link renderPaletteTokens} should return. */\nexport interface RenderPaletteOptions extends PaletteConfig {\n /** Color scheme to resolve. Default: `'light'`. */\n scheme?: 'light' | 'dark';\n /** Resolve the high-contrast variant of that scheme. Default: `false`. */\n highContrast?: boolean;\n}\n\n/**\n * Glaze-generated color palette for the Cube UI Kit.\n *\n * Produces light, dark, and high-contrast color variants from a single\n * source of truth. Every color token is emitted as a tasty state map:\n *\n * '#surface': { '': 'oklch(...)', '@dark': 'oklch(...)', '@hc': 'oklch(...)' }\n *\n * The `@dark` and `@hc` state aliases are wired up globally in\n * `src/components/Root.tsx` (see `setGlobalPredefinedStates`).\n *\n * This module owns the *recipe* — which colors exist and how each one is\n * positioned relative to its base. The *seeds* it is built from (hue,\n * saturation, pastel, contrast level) live in `./palette-config.ts` and are\n * tunable at runtime; `buildPalette()` below is a pure function of them.\n */\n\n// ============================================================================\n// Recipe constants\n// ============================================================================\n\n// Relative tone ramp from a white surface.\n// Tone is a contrast-uniform 0–100 scale (OKHST). A relative tone delta of -N\n// yields a fixed WCAG contrast step against the base regardless of where the\n// base sits on the scale. These constants form the primary text ramp; the\n// surface-2 / surface-3 variants reuse the same absolute targets via a small\n// offset because their bases are shifted down from `surface`.\nconst TEXT_TONE = -98; // relative tone delta from white surface (~cr 21)\nconst TEXT_SOFT_TONE = -72; // relative tone delta from white surface (~cr 9)\nconst TEXT_SOFT2_TONE = -52; // relative tone delta from white surface (~cr 4.5)\nconst SURFACE_2_TEXT_OFFSET = 2;\nconst SURFACE_3_TEXT_OFFSET = 4;\n\n// Syntax highlighting keeps its own fixed hue reference (see the `code-*` block\n// below). This is the kit's default brand hue, frozen as a literal: string\n// literals were historically purple because the brand is, but they must not\n// follow a re-seeded brand hue — a green brand would collide `code-string` with\n// `code-number` (156°) and make strings and numbers indistinguishable.\nconst CODE_STRING_HUE = 280.3;\n\n/**\n * Saturation factor of the neutral `surface`.\n *\n * Shared with the code theme, which mirrors `surface` to solve its contrast floors\n * against the real page background — the two must not drift apart. Shared with the\n * config too, which needs it to work out the share of the accent zone's chroma the\n * chrome takes when no base COLOR names one outright; it lives there because the\n * config cannot import from here, and is re-exported under this name because that is\n * what the recipe calls it.\n */\nconst SURFACE_SATURATION = SURFACE_SATURATION_SHARE;\n\nconst TINTED_SURFACE_SATURATION = 0.2;\nconst TINTED_SURFACE_TONE_OFFSET = 2;\n\n/**\n * How far `surfaceMode: 'tinted'` moves the neutral ramp off the end of the tone\n * scale.\n *\n * Small on purpose. Two tones is below the threshold where a page reads as\n * \"grey\" rather than \"white\", and it is not lightness this buys: chroma needs\n * distance from the extreme to exist at all, so at tone 100 a light surface is\n * white whatever saturation it asks for. Two tones is the cheapest room in which\n * the base hue becomes visible.\n *\n * Everything under `surface` is positioned relative to it, so the shift carries\n * the whole ladder — and the text ramp's contrast floors re-solve against the new\n * background instead of drifting.\n */\nconst TINTED_SURFACE_TONE_SHIFT = 2;\n\n/** Tone of the neutral `surface`, per {@link ResolvedPaletteConfig.surfaceMode}. */\nfunction surfaceTone(config: ResolvedPaletteConfig): number {\n return config.surfaceMode === 'tinted'\n ? 100 - TINTED_SURFACE_TONE_SHIFT\n : 100;\n}\n\n/**\n * Tone of a TINTED `surface` — a status theme's, or a runtime tint's —\n * as `[light, highContrast]`.\n *\n * A tinted surface is a banner sitting *on* the page, so its tone is authored as\n * an offset from the page's rather than as an absolute. Under\n * `surfaceMode: 'tinted'` the page itself moves down to 98, which is exactly where\n * these used to sit: a `note` banner would come out the same tone as the surface\n * behind it and stop reading as a banner at all. Anchoring both on\n * {@link surfaceTone} keeps the separation the offset was chosen for.\n *\n * The text ramps on these themes are relative (`base: 'surface'`), so they follow\n * on their own — only the absolute tone needs the anchor.\n */\nfunction tintedSurfaceTone(config: ResolvedPaletteConfig): [number, number] {\n const page = surfaceTone(config);\n\n return [\n page - TINTED_SURFACE_TONE_OFFSET,\n page - TINTED_SURFACE_TONE_OFFSET * 2,\n ];\n}\n\n/**\n * Rescale the base-zone saturation factors onto the base zone's own seed.\n *\n * Every color's `saturation` is a 0–1 factor of its **theme's** seed, and the\n * neutral family shares a theme instance with the accent one — it has to, since\n * `accent-surface` is positioned against `surface`. A separate base seed therefore\n * cannot be a second theme; it arrives as a scale over the authored factors.\n *\n * The scale is anchored on `surface`: dividing by `SURFACE_SATURATION` turns each\n * factor into its share *of the surface's tint*, and multiplying by\n * `baseSaturation / saturation` re-expresses that share against the base seed.\n * `surface` therefore lands on exactly `baseSaturation` on the 0–100 scale, and\n * every sibling keeps its proportion to it. At the shipped\n * `baseSaturation = saturation × 0.12` the scale is `1` and nothing moves.\n *\n * Clamped because Glaze reads the factor as 0–1, which is also why the proportions\n * converge at the top: `surface-inverse` (the highest at `0.475`) saturates first,\n * around `baseSaturation: 25`.\n *\n * A palette seed of `0` leaves no chroma for any factor to scale, so the base zone\n * is grey there whatever it asks for — the ceiling belongs to the theme instance,\n * not to this scale.\n */\nfunction baseSaturationScale(config: ResolvedPaletteConfig): number {\n return config.saturation > 0\n ? config.baseSaturation / (SURFACE_SATURATION * config.saturation)\n : 0;\n}\n\n/**\n * How far a pinned brand FILL has to separate from the page it sits on — APCA Lc 25, in\n * both tiers.\n *\n * The base is `surface`, and `roleToPolarity('surface')` gives this the `bg` polarity,\n * so Glaze solves `apcaContrast(surface, fill)`.\n *\n * **This number is page separation and nothing else**, and getting that wrong is what\n * made the dark scheme unusable. It reads differently by scheme — in light `surface` IS\n * white, so the same measurement happens to be the white-label pair, which invited the\n * value to be set for the LABEL at Lc 45 (`large` text) with 60 in high contrast. In\n * dark the page is near-black and the same number is a demand that a filled shape reach\n * text-grade contrast against it. Nothing in the palette meets that: measured on the\n * emitted tokens, the SHIPPED `accent-surface` — the white-anchored ladder every primary\n * button used before color seeds existed — sits at **Lc 25.5** off the dark page, and at\n * **Lc 19.3** in dark high contrast, where the ladder darkens the fill toward its label.\n * So 45/60 held a color-seeded fill to 1.8x and 3.1x what the design system's own button\n * achieves.\n *\n * Because a floor can only LIGHTEN in dark, that surplus flattened the tone axis: every\n * seed below the floor solved to the same fill. Measured across the axis at one hue, the\n * dark fill was pinned at tone 66 for every seed from 5 to 65 — a brand's whole dark half\n * collapsed onto one lavender — and in dark high contrast the floor met\n * {@link ACCENT_LABEL_LC}'s ceiling and left a window of a single value. At Lc 25 the\n * same sweep tracks the seed from tone 47 up, and 47 is where the shipped fill sits in\n * dark, so the floor now coincides with the palette's own reference point instead of\n * overshooting it.\n *\n * The label is not left unguarded by this — it never depended on this number. The white\n * `type=\"primary\"` label is guaranteed by {@link accentToneCeiling}, which caps the seed\n * against pure white across all four variants, with {@link ACCENT_LABEL_MARGIN} covering\n * the lightening this floor can still add. A LOWER floor lightens less, so it makes that\n * margin safer rather than riskier.\n *\n * What the floor cannot do is invent contrast that a dark page has no room for. A brand\n * darker than the shipped fill's own dark tone still lands on the floor, because a fill\n * beneath it is not a shape on that page. The fix is that the floor now sits where the\n * design system already put it, not two tiers above.\n *\n * Still a FLOOR, not a target: a brand already past it is emitted exactly as given —\n * which is why light is untouched by this change. A dark brand on a white page measures\n * Lc 100+, so this never bound there; the ceiling is what light answers to.\n *\n * Why APCA and not a WCAG ratio. WCAG 2.x is polarity-blind, so one number means two\n * very different things: the previous `3` measured Lc 56 in light but only Lc 23 in\n * dark (12 hues, spread under 2 Lc — hue is not a factor, polarity is). That is 2.4x\n * stricter in light than in dark, which is why light brands kept getting crushed while\n * dark ones sailed through under the same rule. An Lc is one number that means one\n * thing in both schemes.\n *\n * The pair is written out with BOTH entries equal, and that is the whole reason it is a\n * pair: stating one level would let APCA's automatic +15 Lc enhancement fire in high\n * contrast, which is the escalation this must not make. High contrast is a request for\n * separation over brand, but not for separation from the PAGE — the same fill carries a\n * white label, and driving it off the page drives the label off it. The shipped ladder\n * makes the same trade and lands the other side of it, at Lc 19.3: *less* page separation\n * in high contrast than in normal, because it darkens the fill toward its label. Here the\n * label is protected by {@link accentToneCeiling} instead, which searches the\n * high-contrast variants too, so the tier can leave this number alone.\n */\nconst ACCENT_FILL_CONTRAST: ContrastSpec = { apca: [25, 25] };\n\n/**\n * Floors for the two brand TEXT tokens: rest, then hover — APCA `content` and `body`.\n *\n * Also \"from white\", in the same sense as {@link ACCENT_FILL_CONTRAST}: both solve\n * against `accent-selected-fill`, which is `oklch(0.975 0.0037 284.4)` in light — white\n * for every practical purpose, and the surface these labels actually sit on. These are\n * foreground spots, so they keep the default `fg` polarity and Glaze solves\n * `apcaContrast(text, base)`.\n *\n * Lc 60 is APCA's `content` tier, the floor for ordinary body text, which is what a\n * link is. The hover takes `body`/75 so the rest→hover intensify stays visible; one\n * preset step apart is what keeps the two from solving onto the same color.\n *\n * The HC entries are spelled `85` and `92` rather than left to Glaze's +15, for the\n * same reason as {@link ACCENT_FILL_CONTRAST}, and they stay apart so the rest→hover\n * intensify survives the tier that needs it most. They replace the hand-measured\n * `[4.5, 9]` pair, whose `9` existed only because a WCAG ratio that high is unreachable\n * for a saturated hue against a chromatic base: `#FFD400` in dark high contrast used to\n * pin to pure black and come out a hover link *less* readable than its rest state. An\n * Lc target is reachable in both schemes because it is polarity-aware, so the\n * pathological case has no equivalent here.\n */\nconst ACCENT_TEXT_CONTRAST: ContrastSpec = { apca: [60, 85] };\nconst ACCENT_TEXT_HOVER_CONTRAST: ContrastSpec = { apca: [75, 92] };\n\n/**\n * Tone steps of the brand fill ramp, measured from `accent-surface`.\n *\n * They reproduce the white-anchored `-49 / -52 / -55 / -58` ladder exactly once the\n * fill is pinned, because those four deltas share an anchor and differ by 3 / 6 / 9.\n */\nconst ACCENT_RAMP = {\n surface2: '-3',\n surface3: '-6',\n hover: '-9',\n} as const;\n\n/**\n * How far the hover brand text sits past the rest one, in tone.\n *\n * Both are pinned to the caller's tone otherwise, and a pair at the same tone behind\n * the same floor resolves to one color — which would silently delete the rest→hover\n * intensify that `accent-text` exists for. Tone is contrast-uniform, so one step is\n * one step in either scheme.\n */\nconst ACCENT_TEXT_HOVER_STEP = 6;\n\n/**\n * The caller's brand color, or `null` for the shipped derivation.\n *\n * Carries the literal — which is what Glaze's `from` consumes — alongside its tone,\n * because {@link ACCENT_TEXT_HOVER_STEP} needs the number to compute a step past it.\n */\ntype AccentSeed = { color: GlazeColorValue; tone: number } | null;\n\n/**\n * The white label's own floor on the brand fill, as an APCA Lc.\n *\n * The same 45 as {@link ACCENT_FILL_CONTRAST}, and deliberately so: it is the same\n * surface being constrained, just against the other thing that has to survive on it.\n *\n * Two floors rather than one because in dark they pull opposite ways, and dropping\n * either one produces the mirror image of the other's failure. The fill has to be dark\n * enough for the `#white` label every `type=\"primary\"` item paints on it, and light\n * enough to separate from a dark page. In light mode the page IS white, so both\n * collapse into the single measurement Glaze already makes and this cap never fires.\n *\n * Measured, in dark, with only the page floor: `#FFFFFF` clears it at WCAG 14.4 while\n * the label lands on **Lc 0** — the label is exactly its own fill. With only the white\n * floor the failure mirrors: `#111827` puts the fill at **Lc 0.0 against the page**, a\n * blazing white label on a shape that is not there. The border does not stand in for\n * the fill here — it is deliberately low-contrast — so the fill has to carry it.\n */\nconst ACCENT_LABEL_LC = 45;\n\n/**\n * How far above {@link ACCENT_LABEL_LC} the ceiling actually searches.\n *\n * The ceiling is computed on the bare seed, but the emitted `accent-surface` then goes\n * through the page floor, which can only LIGHTEN — and a lighter fill is a weaker white\n * label, so the solve eats into the margin the ceiling just established. Measured worst\n * case across 3072 hue/chroma/tone/scheme/tier combinations is 1.8 Lc, in dark high\n * contrast where the page floor pushes hardest; 3 covers it with room.\n */\nconst ACCENT_LABEL_MARGIN = 3;\n\n/**\n * Keyed on the palette VERSION as well as the seed, because the search resolves\n * through Glaze's global settings — the dark tone window among them. A caller that\n * runs `glaze.configure(...)` and then `invalidatePaletteTokens()` has changed the\n * answer without changing the seed, and a cache keyed on the color alone would hand\n * back a cap computed against the old settings. Versioning it is what makes the\n * invalidation API mean what it says.\n *\n * Bounded for the same reason `colorSeed`'s cache is: a color picker drag resolves a\n * distinct seed per frame, and stale versions accumulate keys nobody reads again.\n */\nconst accentCapCache = new Map<string, number>();\nconst ACCENT_CAP_CACHE_LIMIT = 256;\n\n/** APCA Lc of pure white on one resolved variant. */\nfunction labelLcOf(variant: Parameters<typeof variantToOkhsl>[0]): number {\n const { h, s, l } = variantToOkhsl(variant);\n const gamma = (c: number) =>\n c <= 0.0031308 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;\n const y = (rgb: number[]) => {\n const [r, g, b] = rgb.map((c) => Math.max(0, Math.min(1, gamma(c))));\n\n return 0.2126 * r ** 2.4 + 0.7152 * g ** 2.4 + 0.0722 * b ** 2.4;\n };\n\n return Math.abs(\n apcaContrast(y([1, 1, 1]), y(okhslToLinearSrgb(h, s, l, false))),\n );\n}\n\n/**\n * Pull a brand tone down until a white label survives on every variant of the fill.\n *\n * The search runs against Glaze's own fixed-mode resolution — the same mapping\n * `accent-surface` goes through — rather than reimplementing the dark tone window, and\n * checks all four variants so the cap is a property of the seed and not of one scheme.\n *\n * Only ever lowers, so a brand already dark enough comes back untouched.\n */\nfunction capAccentTone(hue: number, saturation: number, tone: number): number {\n return Math.min(tone, accentToneCeiling(hue, saturation));\n}\n\n/**\n * The lightest tone at this hue and chroma that a white label still survives on.\n *\n * A property of the hue/chroma pair alone, NOT of the tone being asked for — which is\n * what makes it cacheable, and what the first cut got wrong: it only searched when the\n * requested tone already failed, so a dark tone probed first cached \"no ceiling\" and\n * every later light tone at the same hue escaped uncapped.\n */\nfunction accentToneCeiling(hue: number, saturation: number): number {\n const key = `${hue}|${saturation}|${getPaletteVersion()}`;\n const cached = accentCapCache.get(key);\n\n if (cached !== undefined) return cached;\n\n const labelLc = (candidate: number): number => {\n // `from` takes an `OkhstColor` directly, so the seed never becomes a string —\n // which skips the writers' scale question and the two decimals `okhst()` rounds\n // to. Both components arrive on the palette's 0–100 authoring scale and\n // `OkhstColor` wants factors, so both are divided.\n const resolved = glaze\n .color({\n from: { h: hue, s: saturation / 100, t: candidate / 100 },\n mode: 'fixed',\n })\n .resolve();\n\n return Math.min(\n labelLcOf(resolved.light),\n labelLcOf(resolved.dark),\n labelLcOf(resolved.lightContrast),\n labelLcOf(resolved.darkContrast),\n );\n };\n\n let ceiling = 100;\n\n const target = ACCENT_LABEL_LC + ACCENT_LABEL_MARGIN;\n\n if (labelLc(100) < target) {\n // Monotone in tone — a lighter fill is a weaker white label. Bisect the boundary,\n // always across the full axis so the answer is the hue's own ceiling.\n let low = 0;\n let high = 100;\n\n for (let i = 0; i < 24; i++) {\n const mid = (low + high) / 2;\n\n if (labelLc(mid) >= target) low = mid;\n else high = mid;\n }\n\n ceiling = low;\n }\n\n if (accentCapCache.size >= ACCENT_CAP_CACHE_LIMIT) accentCapCache.clear();\n accentCapCache.set(key, ceiling);\n\n return ceiling;\n}\n\n/**\n * A color seed as the three numbers Glaze consumes, with its tone capped.\n *\n * Takes the RESOLVED components rather than the caller's literal because the tone is\n * capped — {@link capAccentTone} only ever lowers it — and a capped tone means rebuilding\n * the color regardless. Passing `from: <the original string>` would emit the tone the\n * white label cannot survive.\n *\n * Shared by the accent zone and by any status theme on the color path: they resolve their\n * components differently, but a fill is a fill once the numbers are in hand.\n */\nfunction cappedAccent(\n hue: number,\n saturation: number,\n tone: number,\n): AccentSeed {\n const capped = capAccentTone(hue, saturation, tone);\n\n return {\n color: { h: hue, s: saturation / 100, t: capped / 100 },\n tone: capped,\n };\n}\n\n/**\n * Opaque stand-in for the BASE selected fill used by outline / outline-2 / clear Item\n * types (`#surface|#surface-2|#surface-3` + `#accent-surface.09`).\n *\n * Anchors the `accent-text*` contrast. `value: 9` matches the `.09` alpha overlay;\n * `space: 'srgb'` approximates CSS two-layer compositing. Inherited so colored themes\n * re-resolve against their own `surface` + `accent-surface` (default `surface-2` /\n * `surface-3` are `inherit: false` and can't be the mix base). Primary's tinted\n * `surface` makes this slightly harder than default clear-selected, covering\n * outline / outline-2 selected fills as well.\n *\n * The same in both accent arrangements — it is defined by its two ends, and both of\n * those move with the seed on their own.\n */\nconst ACCENT_SELECTED_FILL: ColorMap = {\n 'accent-selected-fill': {\n type: 'mix',\n base: 'surface',\n target: 'accent-surface',\n value: 9,\n space: 'srgb',\n },\n};\n\n/**\n * Syntax highlighting for `PrismCode`, as its own theme.\n *\n * A small palette of *adaptive* colored tokens. Each one is anchored to `surface`\n * with a relative tone plus an `['AA','AAA']` readability floor, so it stays legible\n * in light, dark and high contrast. Hues mirror the long-standing PrismCode\n * reference (pink keywords / pink functions / purple strings / green numbers / gray\n * comments); `code-attribute` keeps cyan for HTML attribute names, CSS properties\n * and selectors, while HTML/XML tag names reuse `code-keyword`. Diff insertion and\n * deletion reuse the `success-*` / `danger-*` ramps instead.\n *\n * Three things are deliberately static here. Every hue is an absolute literal, so a\n * re-seeded brand cannot rotate the syntax palette — a green brand would otherwise\n * collide `code-string` with `code-number` (156°). These factors are relative to\n * the *code* theme's own seed (`themes.code.saturation`), not the palette-level one,\n * so tuning the app's saturation cannot wash out a code block. And `pastel` is held\n * off here whatever the palette does, for the same reason: it lowers the chroma\n * ceiling hard enough to take `code-keyword` from ~0.19 to ~0.07, which is the\n * difference between distinguishable syntax and mud. Between them, these mean the\n * emitted `code-*` values are a function of the code saturation alone.\n *\n * That separate seed is the only reason these live outside the default theme: a\n * theme colour can never exceed its own seed, and four of these sit at factor `1.0`,\n * so they could not hold their chroma inside a default theme seeded below 80.\n */\nconst CODE_COLORS: ColorMap = {\n 'code-comment': {\n base: 'surface',\n hue: 280,\n saturation: 0.1,\n tone: '-50',\n contrast: ['AA', 'AAA'],\n },\n 'code-punctuation': {\n base: 'surface',\n hue: 348,\n saturation: 0.4,\n tone: '-50',\n contrast: ['AA', 'AAA'],\n },\n 'code-keyword': {\n base: 'surface',\n hue: 348,\n saturation: 1,\n tone: '-54',\n contrast: ['AA', 'AAA'],\n },\n 'code-string': {\n base: 'surface',\n hue: CODE_STRING_HUE,\n saturation: 1,\n tone: '-50',\n contrast: ['AA', 'AAA'],\n },\n 'code-number': {\n base: 'surface',\n hue: 156,\n saturation: 0.9,\n tone: '-50',\n contrast: ['AA', 'AAA'],\n },\n 'code-function': {\n base: 'surface',\n hue: 348,\n saturation: 1,\n tone: '-54',\n contrast: ['AA', 'AAA'],\n },\n 'code-attribute': {\n base: 'surface',\n hue: 200,\n saturation: 1,\n tone: '-50',\n contrast: ['AA', 'AAA'],\n },\n};\n\n/**\n * Build the standalone theme the `code-*` tokens live in.\n *\n * Its seed is the code saturation, so {@link CODE_COLORS} keeps its authored factors\n * verbatim and resolves identically at any palette saturation. The mirrored `surface`\n * is an implementation detail — it exists only as the contrast base, and is filtered\n * out of the emitted tokens by {@link pickCodeTokens}.\n *\n * `instanceConfig` arrives with `pastel` pinned off, so the mirror is non-pastel too\n * even when the app is. Harmless: at saturation factor 0.12 the pastel ceiling moves\n * the mirror's chroma and leaves its tone — the axis every `code-*` contrast floor is\n * solved against — bit-identical.\n */\nfunction buildCodeTheme(\n config: ResolvedPaletteConfig,\n instanceConfig: GlazeConfigOverride | undefined,\n): GlazeTheme {\n const codeSaturation = config.themes.code.saturation;\n const theme = glaze(config.hue, codeSaturation, instanceConfig);\n\n // Re-express the default theme's `surface` as a factor of the *code* seed, so it\n // resolves to the same colour the code actually sits on.\n //\n // The BASE seed, not the palette one: the real `surface` takes its chroma from\n // the base zone, and a mirror that tracked the palette seed would solve every\n // syntax contrast floor against a background the page does not have. `surface`\n // lands on exactly `baseSaturation` on the 0–100 scale, so the ratio of the two\n // seeds *is* the factor — there is no `SURFACE_SATURATION` left to apply.\n let mirrorFactor = config.baseSaturation / codeSaturation;\n\n if (mirrorFactor > 1) {\n // Glaze would clamp this silently and then solve every contrast floor against a\n // background the page does not have, so say so.\n //\n // Closer to reach than it used to be: a `baseColor` can push `baseSaturation` to\n // `MAX_BASE_SATURATION`, so any code saturation under 50 now trips it where it\n // previously took a `baseSaturation` above 80. `Slate` sits at 55 — a five-point\n // margin — so treat that number as load-bearing when tuning the preset.\n //\n // Deliberately NOT deduped once-per-process. It fires per token-cache miss, so a\n // drag over a misconfigured pair is noisy — but the flag would also have to\n // survive `resetPaletteConfig`, which is what a caller does to get back to a\n // config that warns again legitimately. The narrow noise is the better trade.\n // eslint-disable-next-line no-console\n console.warn(\n `[cube-ui-kit] themes.code.saturation (${codeSaturation}) is too low for ` +\n `baseSaturation ${config.baseSaturation}: the mirrored code surface would ` +\n `need a factor of ${mirrorFactor.toFixed(3)}. Clamped to 1; raise the code ` +\n `saturation to keep syntax contrast accurate.`,\n );\n mirrorFactor = 1;\n }\n\n theme.colors({\n // Tone as well as chroma: under `surfaceMode: 'tinted'` the page moved two\n // tones off the extreme, and the mirror exists to be the page.\n surface: { tone: surfaceTone(config), saturation: mirrorFactor },\n ...CODE_COLORS,\n });\n\n return theme;\n}\n\n/** What {@link buildPalette} produces: the themed palette plus the code theme. */\ninterface BuiltPalette {\n palette: GlazePalette;\n codeTheme: GlazeTheme;\n}\n\n/** Drop the mirrored `surface`, keeping only the `code-*` tokens. */\nfunction pickCodeTokens<T>(tokens: Record<string, T>): Record<string, T> {\n const out: Record<string, T> = {};\n\n for (const name of Object.keys(tokens)) {\n if (name.startsWith('code-') || name.startsWith('#code-')) {\n out[name] = tokens[name];\n }\n }\n\n return out;\n}\n\n// ============================================================================\n// Global Glaze configuration\n// ============================================================================\n\nglaze.configure({\n states: {\n dark: '@dark',\n highContrast: '@hc',\n },\n modes: {\n dark: true,\n highContrast: true,\n },\n darkTone: [14, 95],\n darkDesaturation: 0,\n});\n\n/**\n * The config-free half of a colored theme's overrides — everything anchored to\n * the theme's own `surface` with a *relative* tone, so it follows wherever\n * {@link tintedSurfaceTone} puts that surface.\n *\n * - `border` — bumped saturation so OUTLINE-variant borders pick up the theme\n * hue (used by `#<theme>-border` in `item-themes.ts`). Mirrors the\n * default-theme `border` shape (`base: 'surface'`, tone window) but with\n * higher saturation. Glaze's `extend({ colors })` redefines each listed color\n * from scratch, so we restate the full definition here.\n */\nconst TINTED_SURFACE_RAMP: ColorMap = {\n border: {\n base: 'surface',\n tone: ['-10', '-30'],\n saturation: 0.3,\n },\n 'surface-text': {\n base: 'surface',\n tone: `${TEXT_TONE - TINTED_SURFACE_TONE_OFFSET}`,\n saturation: 0.25,\n contrast: ['AA', 'AAA'],\n },\n 'surface-text-soft': {\n base: 'surface',\n tone: `${TEXT_SOFT_TONE - TINTED_SURFACE_TONE_OFFSET}`,\n saturation: 0.25,\n contrast: ['AA', 'AAA'],\n },\n 'surface-text-soft-2': {\n base: 'surface',\n tone: `${TEXT_SOFT2_TONE - TINTED_SURFACE_TONE_OFFSET}`,\n saturation: 0.25,\n contrast: ['AA', 'AAA'],\n },\n};\n\n/**\n * Per-colored-theme overrides on top of the default theme: a `surface` bumped in\n * saturation so the banner background is visibly tinted, plus\n * {@link TINTED_SURFACE_RAMP}.\n *\n * A function of the config because the surface's tone is — see\n * {@link tintedSurfaceTone}.\n */\nfunction tintedSurfaceOverride(config: ResolvedPaletteConfig): ColorMap {\n return {\n surface: {\n tone: tintedSurfaceTone(config),\n saturation: TINTED_SURFACE_SATURATION,\n },\n ...TINTED_SURFACE_RAMP,\n };\n}\n\n/**\n * A tinted surface, a banding step, and text guaranteed to read on both.\n *\n * The recipe behind {@link getColorTheme} in `./color-theme.ts`, which builds\n * one-off themes at runtime from an arbitrary hue. It lives here so it shares\n * `TINTED_SURFACE_SATURATION`, the tone offsets and the text ramp with\n * {@link tintedSurfaceOverride} above rather than forking them — a runtime tint\n * and a built-in theme's `surface` should be the same colour for the same hue,\n * which also means both have to follow the page when `surfaceMode` moves it.\n *\n * Three colours, because that is what banded, readable table column needs:\n * `surface` for even rows, `surface-2` one tone step down for odd rows and\n * pinned totals, and `surface-2-text`.\n *\n * The text is anchored to `surface-2`, not `surface`. `surface-2` has the lower\n * contrast headroom in BOTH schemes — a darker background under dark text in\n * light, a lighter one under light text in dark — so solving the floor there\n * clears it on both bands. The neutral ramp's own `surface-2-text` is shaped the\n * same way for the same reason.\n */\nexport function tintRecipe(config: ResolvedPaletteConfig): ColorMap {\n return {\n surface: {\n tone: tintedSurfaceTone(config),\n saturation: TINTED_SURFACE_SATURATION,\n },\n 'surface-2': {\n base: 'surface',\n // Mirrors the neutral ramp's step from `surface` to `surface-2`: enough to\n // read as banding down a column, not enough to read as two colours.\n tone: ['-2', '-4'],\n saturation: TINTED_SURFACE_SATURATION,\n },\n 'surface-2-text': {\n base: 'surface-2',\n tone: `${TEXT_TONE - TINTED_SURFACE_TONE_OFFSET - SURFACE_2_TEXT_OFFSET}`,\n saturation: 0.25,\n // The whole point: Glaze binary-searches the tone per scheme until the floor\n // is met, so a caller cannot persist an unreadable pair.\n contrast: ['AA', 'AAA'],\n },\n /**\n * The softer step, for a tinted column HEADER.\n *\n * A neutral header is deliberately muted (`HeadRow` publishes `#dark-03`), so\n * a tinted one taking the full-strength body text read markedly darker than\n * the headers either side of it — measured at 16:1 against their 4.9:1. This\n * keeps the two consistent, and the `AA` floor still applies.\n */\n 'surface-2-text-soft': {\n base: 'surface-2',\n tone: `${TEXT_SOFT_TONE - TINTED_SURFACE_TONE_OFFSET - SURFACE_2_TEXT_OFFSET}`,\n saturation: 0.25,\n contrast: ['AA', 'AAA'],\n },\n };\n}\n\n// ============================================================================\n// The accent system\n// ============================================================================\n\n/**\n * The brand family, in one of two arrangements.\n *\n * **`null`** — the shipped one, verbatim. Every fill hangs off a fixed white\n * `accent-surface-text` at a relative tone delta behind an `['AA','AAA']` floor, and\n * the text pair hangs off `accent-selected-fill` at `-49`. Those floors are\n * load-bearing rather than decorative: relaxing them moves the shipped `accent-text`\n * light tone from 38.76 to 48.63 and the `accent-surface` high-contrast tone from\n * 36.08 to 51.00. Nothing on this path may change — `palette.test.ts` snapshots it.\n *\n * **A color** — the caller handed us one, and the job is to render *that*. Glaze's\n * `from` takes the value directly: it supplies the hue, the tone, and an absolute\n * saturation that does not answer to the theme seed, and it reproduces the value\n * exactly in the light/normal-contrast variant. The floors drop to\n * {@link ACCENT_FILL_CONTRAST} on top, because anchored the shipped way every accent\n * lands at roughly tone 50 whatever went in, and a 4.5 floor then crushes anything\n * light besides.\n *\n * Two details worth keeping straight:\n *\n * - **The ramp carries no floor.** Re-anchored onto the fill, an `['AA','AAA']` floor\n * is measured against the *fill* rather than white and collapses all three steps to\n * near-black. A plain tone step is what the relationship actually is.\n * - **The text pair stays `mode: 'auto'`.** Link text has to invert on a dark page, so\n * exactness belongs to the fill; the text is a brand-toned companion of it.\n */\nfunction accentFillColors(accent: AccentSeed): ColorMap {\n if (accent == null) {\n return {\n // ---- Accent system (theme-aware, inherited by colored themes) ----\n // Everything here is anchored to a fixed white \"accent-surface-text\" via\n // `mode: 'fixed'` + relative tone deltas, so accent colors stay visually\n // consistent across light/dark/high-contrast schemes (the brand color does\n // not flip). The solid fills are white-text-on-brand backgrounds, so they\n // keep an `['AA','AAA']` contrast floor even though the chosen tone deltas\n // already exceed it. This leaves room for a future low-contrast scale.\n 'accent-surface-text': { tone: 100, mode: 'fixed' },\n 'accent-surface': {\n base: 'accent-surface-text',\n tone: '-49',\n contrast: ['AA', 'AAA'],\n mode: 'fixed',\n },\n 'accent-surface-2': {\n base: 'accent-surface-text',\n tone: '-52',\n contrast: ['AA', 'AAA'],\n mode: 'fixed',\n },\n 'accent-surface-3': {\n base: 'accent-surface-text',\n tone: '-55',\n contrast: ['AA', 'AAA'],\n mode: 'fixed',\n },\n // Hover variant of `accent-surface` — a *fixed*-mode darker shade used as\n // the hover fill for solid PRIMARY-type buttons. Anchored to the same\n // accent-surface-text so it stays in the same hue family. The relative tone\n // lands a few steps darker than the pressed state in both schemes.\n 'accent-surface-hover': {\n base: 'accent-surface-text',\n tone: '-58',\n contrast: ['AA', 'AAA'],\n mode: 'fixed',\n },\n // Border for accent surfaces — a small relative tone step away from the\n // brand fill so it stays in the brand hue family and does not flip in dark\n // mode. No contrast prop needed; the delta is chosen directly on the tone\n // scale.\n 'accent-surface-border': {\n base: 'accent-surface',\n tone: '+13',\n mode: 'fixed',\n },\n };\n }\n\n return {\n // Still a hard white root, and still the PRIMARY label: every `type=\"primary\"`\n // item in `src/data/item-themes.ts` writes `#white` directly. That is exactly what\n // ACCENT_FILL_CONTRAST protects — and in light, where `surface` IS white, the\n // floor below measures that label pair directly.\n 'accent-surface-text': { tone: 100, mode: 'fixed' },\n 'accent-surface': {\n from: accent.color,\n base: 'surface',\n contrast: ACCENT_FILL_CONTRAST,\n mode: 'fixed',\n },\n 'accent-surface-2': {\n base: 'accent-surface',\n tone: ACCENT_RAMP.surface2,\n mode: 'fixed',\n },\n 'accent-surface-3': {\n base: 'accent-surface',\n tone: ACCENT_RAMP.surface3,\n mode: 'fixed',\n },\n 'accent-surface-hover': {\n base: 'accent-surface',\n tone: ACCENT_RAMP.hover,\n mode: 'fixed',\n },\n // `'+13'` overshoots 100 on a light brand and `autoFlip` mirrors it to `-13`,\n // which is the right answer either way: a border on a light fill has to be the\n // darker of the two (`#FFD400` → `#d3af00`).\n 'accent-surface-border': {\n base: 'accent-surface',\n tone: '+13',\n mode: 'fixed',\n },\n };\n}\n\n/**\n * The brand FOREGROUNDS — text on a neutral surface, and the icon.\n *\n * Split from {@link accentFillColors} because the two have different audiences: the\n * fill ramp is shared with the standalone `special` theme, which builds its own\n * purpose-made subset and must not gain tokens it does not consume. Only the default\n * theme (and the colored themes that inherit from it) takes these.\n *\n * These stay `mode: 'auto'` in both arrangements — a link has to invert on a dark page,\n * so exactness belongs to the fill and these are its brand-toned companions.\n */\nfunction accentTextColors(accent: AccentSeed): ColorMap {\n if (accent == null) {\n return {\n ...ACCENT_SELECTED_FILL,\n // Stronger brand text for HOVERED selected outline/clear labels and LINK\n // hover. Same anchor + preferred tone as `accent-text-soft`, but a higher\n // `contrast: [6, 11]` floor against `accent-selected-fill` so it reads as a\n // clear step up from the soft rest color while staying saturated (a bare\n // `AAA`/`7` floor over-darkens light and desaturates dark). The HC pair keeps\n // it at/above the soft variant (which auto-promotes AA→AAA in HC).\n // `mode: 'auto'` (default) keeps dark-mode text readable on dark surfaces.\n 'accent-text': {\n base: 'accent-selected-fill',\n tone: '-49',\n saturation: 1,\n contrast: [6, 11],\n },\n // Rest brand text for selected outline/clear labels and LINK base color.\n // Anchored to `accent-selected-fill` with `contrast: 'AA'` — the measured\n // floor for every BASE state of those Item types (surface / outline /\n // outline-2 / clear selected fills). Sits visibly less prominent than\n // `accent-text` (lighter in light, darker in dark) so the rest→hover\n // intensify is real.\n 'accent-text-soft': {\n base: 'accent-selected-fill',\n tone: '-49',\n saturation: 1,\n contrast: ['AA', 'AAA'],\n },\n 'accent-icon': {\n base: 'surface',\n tone: '-38',\n saturation: 0.9375,\n },\n };\n }\n\n return {\n ...ACCENT_SELECTED_FILL,\n // The rest link color IS the brand, which is the visible payoff of a color seed;\n // the hover one steps past it so the intensify survives. See\n // ACCENT_TEXT_HOVER_STEP.\n // The hover text is the one place the derived NUMBER is still needed: `from`\n // carries the tone, but a step past it has to be computed.\n 'accent-text': {\n from: accent.color,\n base: 'accent-selected-fill',\n tone: Math.max(0, accent.tone - ACCENT_TEXT_HOVER_STEP),\n contrast: ACCENT_TEXT_HOVER_CONTRAST,\n },\n 'accent-text-soft': {\n from: accent.color,\n base: 'accent-selected-fill',\n contrast: ACCENT_TEXT_CONTRAST,\n },\n 'accent-icon': {\n from: accent.color,\n base: 'surface',\n contrast: ACCENT_TEXT_CONTRAST,\n },\n };\n}\n\n/** Every accent token the default theme carries — fills, foregrounds, and the mix. */\nfunction accentColors(accent: AccentSeed): ColorMap {\n return {\n ...accentFillColors(accent),\n ...accentTextColors(accent),\n };\n}\n\n// ============================================================================\n// Palette construction\n// ============================================================================\n\n/**\n * Build the full palette from a resolved config.\n *\n * Pure: it reads nothing but its argument (and the global Glaze config, which\n * the caller sets first), so re-running it with the same config yields the same\n * tokens. Theme instances are immutable with respect to their hue / saturation\n * seed and a `GlazePalette` has no mutation API, so re-seeding means rebuilding.\n */\nfunction buildPalette(\n config: ResolvedPaletteConfig,\n options: {\n /**\n * Carry `contrastLevel` on the theme instances instead of relying on the\n * global Glaze config. Used when rendering a palette the app is not running,\n * so a preview cannot disturb the live one. The resolved values are the same\n * either way; only which scheme variants get *emitted* differs, and callers\n * that isolate pick their variant explicitly.\n */\n isolateContrastLevel?: boolean;\n } = {},\n): BuiltPalette {\n const {\n hue,\n baseHue,\n saturation,\n accentColor,\n accentTone,\n accentSaturation,\n pastel,\n themes,\n contrastLevel,\n } = config;\n\n // Built from the RESOLVED components, not the literal — see `cappedAccent`. Handing\n // Glaze the original string would pin `accent-surface` to an uncapped tone while its\n // `-2`, `-3` and hover siblings stepped off the capped one, splitting the ramp so a\n // primary button changed weight on hover.\n const accent: AccentSeed =\n accentColor !== null && accentTone !== null && accentSaturation !== null\n ? cappedAccent(hue, accentSaturation, accentTone)\n : null;\n\n // The base zone's tone anchor and its own chroma share, applied to every color\n // that carries `hue: baseHue` below — that prop is the boundary of the zone.\n const baseTone = surfaceTone(config);\n const baseScale = baseSaturationScale(config);\n const baseChroma = (factor: number) => Math.min(1, factor * baseScale);\n\n // The one override the code theme shares with the rest of the palette.\n const sharedOverrides: GlazeConfigOverride = options.isolateContrastLevel\n ? { contrastLevel }\n : {};\n\n // `pastel` is instance-level in Glaze (not settable via `glaze.configure`).\n // Setting it on the two root themes is enough: `extend({ config })` merges\n // with the parent's override, so the derived themes inherit it. When it is\n // off we omit the field entirely rather than pass `false` — the two are\n // equivalent, and omitting keeps the default output provably untouched.\n const overrides: GlazeConfigOverride = {\n ...sharedOverrides,\n ...(pastel ? { pastel: true } : null),\n };\n const instanceConfig: GlazeConfigOverride | undefined = Object.keys(overrides)\n .length\n ? overrides\n : undefined;\n\n // `pastel` deliberately does *not* reach the code theme: syntax colors answer\n // to `themes.code.saturation` and nothing else. Pinned to `false` rather than\n // omitted — the same value Glaze defaults to, but here it is the point of the\n // override, not an absence. See {@link buildCodeTheme}.\n const codeInstanceConfig: GlazeConfigOverride = {\n ...sharedOverrides,\n pastel: false,\n };\n\n // --------------------------------------------------------------------------\n // Default theme (neutral, primary in palette → exported unprefixed)\n // --------------------------------------------------------------------------\n\n const defaultTheme = glaze(hue, saturation, instanceConfig);\n\n defaultTheme.colors({\n // ---- Surfaces (neutral, very low saturation) ----\n surface: {\n tone: baseTone,\n saturation: baseChroma(SURFACE_SATURATION),\n hue: baseHue,\n },\n 'surface-2': {\n hue: baseHue,\n base: 'surface',\n tone: ['-2', '-4'],\n saturation: baseChroma(0.1),\n inherit: false,\n },\n 'surface-3': {\n hue: baseHue,\n base: 'surface',\n tone: ['-4', '-8'],\n saturation: baseChroma(0.1),\n inherit: false,\n },\n 'surface-4': {\n hue: baseHue,\n base: 'surface',\n tone: ['-6', '-12'],\n saturation: baseChroma(0.1),\n inherit: false,\n },\n\n // ---- Text on surfaces ----\n // Tone is the contrast-uniform OKHST axis, so a relative tone delta directly\n // encodes a WCAG contrast step. `surface-text` sits at tone 2 (a -98 delta\n // from white), giving the same near-black appearance as the legacy `#dark`.\n // We still keep an `['AA','AAA']` contrast floor on every text-on-surface\n // color so a future low-contrast scale can shift the ramp down without ever\n // breaking readability.\n 'surface-text': {\n hue: baseHue,\n base: 'surface',\n tone: `${TEXT_TONE - 2}`,\n saturation: baseChroma(0.2),\n contrast: ['AA', 'AAA'],\n },\n 'surface-text-soft': {\n hue: baseHue,\n base: 'surface',\n tone: `${TEXT_SOFT_TONE - 2}`,\n saturation: baseChroma(0.2),\n contrast: ['AA', 'AAA'],\n inherit: false,\n },\n 'surface-text-soft-2': {\n hue: baseHue,\n base: 'surface',\n tone: `${TEXT_SOFT2_TONE - 2}`,\n saturation: baseChroma(0.2),\n contrast: ['AA', 'AAA'],\n inherit: false,\n },\n 'surface-2-text': {\n hue: baseHue,\n base: 'surface-2',\n tone: `${TEXT_TONE + SURFACE_2_TEXT_OFFSET}`,\n saturation: baseChroma(0.2),\n contrast: ['AA', 'AAA'],\n inherit: false,\n },\n 'surface-2-text-soft': {\n hue: baseHue,\n base: 'surface-2',\n tone: `${TEXT_SOFT_TONE + SURFACE_2_TEXT_OFFSET}`,\n saturation: baseChroma(0.2),\n contrast: ['AA', 'AAA'],\n inherit: false,\n },\n 'surface-3-text': {\n hue: baseHue,\n base: 'surface-3',\n tone: `${TEXT_TONE + SURFACE_3_TEXT_OFFSET}`,\n saturation: baseChroma(0.2),\n contrast: ['AA', 'AAA'],\n inherit: false,\n },\n 'surface-3-text-soft': {\n hue: baseHue,\n base: 'surface-3',\n tone: `${TEXT_SOFT_TONE + SURFACE_3_TEXT_OFFSET}`,\n saturation: baseChroma(0.2),\n contrast: ['AA', 'AAA'],\n inherit: false,\n },\n\n // ---- Other neutral UI primitives (default-only) ----\n border: {\n hue: baseHue,\n base: 'surface',\n tone: ['-10', '-30'],\n saturation: baseChroma(0.175),\n },\n placeholder: {\n hue: baseHue,\n base: 'surface',\n tone: '-33',\n saturation: baseChroma(0.175),\n inherit: false,\n },\n focus: {\n base: 'surface',\n tone: '-29',\n saturation: 0.8625,\n inherit: false,\n },\n disabled: {\n tone: 80.8,\n saturation: 0.4,\n inherit: false,\n },\n // Disabled fill chip + text — both adaptive (mode 'auto') and positioned\n // with relative tone deltas against `surface` so the disabled state has the\n // same perceived intensity in light, dark, and high-contrast schemes. No\n // numeric contrast prop is needed: tone is already on a WCAG-uniform scale.\n //\n // Tone deltas reproduce the legacy palette's disabled appearance exactly:\n // the chip sits ~cr 1.11 vs `surface` (a faint greyed pill for\n // OUTLINE/SECONDARY/item disabled states) and the label ~cr 2.02 vs\n // `surface` — intentionally sub-AA so it reads as disabled while staying the\n // same softness clear/link disabled labels had before. PRIMARY-style buttons\n // use the brand-tinted `accent-disabled-*` variants below instead. Both\n // tokens stay default-only (`inherit: false`) — colored themes inherit their\n // own disabled chip via the accent variants.\n 'disabled-surface': {\n hue: baseHue,\n base: 'surface',\n tone: '-3.5',\n saturation: baseChroma(0.2),\n inherit: false,\n },\n 'disabled-surface-text': {\n base: 'surface',\n tone: '-23',\n saturation: 0.3,\n inherit: false,\n },\n\n // Fixed-mode \"always dark\" surface for elements that intentionally stay\n // inverted regardless of scheme (tooltips, code blocks, popovers with their\n // own dark theme, etc.). `mode: 'fixed'` bypasses the dark-scheme inversion\n // so the color reads as a dark surface in light, dark, and high-contrast.\n // Pair with `#white` (built-in) for foreground text.\n 'surface-inverse': {\n hue: baseHue,\n tone: 12,\n saturation: baseChroma(0.475),\n mode: 'fixed',\n inherit: false,\n },\n\n // ---- Accent system (theme-aware, inherited by colored themes) ----\n // Two arrangements, one per seeding mode — see `accentColors`.\n ...accentColors(accent),\n\n // Brand-tinted disabled chip + label for PRIMARY-style buttons (solid brand\n // fill). The chip is scheme-symmetric (`mode: 'fixed'`) so the muted state\n // reads the same weight in light/dark/HC; saturation is bumped so it stays\n // identifiable as a muted brand color.\n //\n // The label rides the extreme away from the chip (`tone: 'max'`, ~cr 1.7):\n // deliberately faint, so it reads as disabled rather than as live text.\n // `'max'` is the authored intent — it was temporarily hand-approximated as a\n // relative `tone: '+15'` with `autoFlip: false`, because Glaze < 1.2.0\n // re-mapped the extreme through the dark tone window, compressing the\n // base-to-extreme span and dropping the dark label's contrast. Glaze 1.2.0\n // (tenphi/glaze#82) instead replays the light scheme's base→extreme tone\n // shift against the base's resolved dark tone — same-signed under\n // `mode: 'fixed'` — so `'max'` holds its intended separation in every scheme\n // and the approximation is no longer needed. Inherited per theme.\n //\n // The special theme keeps its own relative `+18` pair: its `surface` is a\n // fixed dark tone, so an extreme there would resolve to white and read as\n // live text rather than disabled.\n 'accent-disabled-surface': {\n base: 'surface',\n tone: '-13',\n saturation: 0.5,\n mode: 'fixed',\n },\n 'accent-disabled-surface-text': {\n base: 'accent-disabled-surface',\n tone: 'max',\n saturation: 0.4,\n mode: 'fixed',\n },\n\n // The disabled counterpart of `accent-text-soft`, for the SELECTED disabled\n // state of the non-solid types (`outline`, `outline-2`, `clear`).\n //\n // Those types express `selected & disabled` by keeping the enabled selected\n // CHIP and fading only the LABEL, so this is the one token the state needs.\n // It is the neutral `disabled-surface-text` geometry — the same `-23` tone\n // delta against `surface`, adaptive, so it reads exactly as disabled as\n // every other disabled label in light, dark and HC — carrying brand chroma\n // instead of neutral: roughly 2x `disabled-surface-text` and comfortably\n // under `accent-text-soft`, which is the live label it must not be mistaken\n // for. Selection therefore survives as a hue on a label of unchanged\n // paleness.\n //\n // The pair above (`accent-disabled-surface` / `-text`) stays what it always\n // was: the muted chip for a PRIMARY button, whose enabled state is an opaque\n // brand fill under `#white`. Borrowing it here got the state backwards — its\n // mid-tone chip is a step DOWN from a solid fill but a step UP from a 9%\n // tint, and its `tone: 'max'` label resolves to literal white in light mode.\n 'accent-disabled-text': {\n base: 'surface',\n tone: '-23',\n saturation: 0.6,\n },\n\n // ---- Isometric cube faces ----\n // The three shading steps of the kit's isometric cube artwork: the lit top\n // face, the mid side, and the shadowed side. Shared by `LoadingAnimation`\n // and `NoDataIcon` so both read as the same object under the same light.\n //\n // Two things are deliberate here.\n //\n // **Neutral chroma.** These used to take a fraction of the *brand* seed\n // saturation (0.3 / 0.62 / 0.66), which put the shadowed face at chroma\n // 0.0676 — eight times `border` — so the animation read as a purple gradient\n // next to a monochrome `CubeLogo`. They now take `baseChroma(0.2)`, the same\n // normalised share the neutral chrome (`border`, `placeholder`, the text\n // ramp) takes, which lands them at 0.0059 / 0.0161 / 0.0248: a tint that\n // follows a re-seeded brand hue without announcing it.\n //\n // **Contrast, not tone, is the spec.** A relative tone delta is uniform on\n // the OKHST scale but the dark scheme resolves it inside the `darkTone`\n // window, which compressed the ramp to ~75% of its light span — measurably\n // flatter, which is exactly how it looked. Glaze has no per-color\n // `darkTone`, so the fix is to state the intent as a WCAG floor against\n // `surface` and let each scheme solve for it: the authored `tone: '-2'` is\n // deliberately short of every floor, so all three faces are pinned by the\n // ratio in every scheme rather than by a delta that means different things\n // in each. Measured on the emitted tokens, light comes out 1.201 / 1.653 /\n // 2.409 and dark 1.212 / 1.666 / 2.424 — within 1% of each other, against\n // 1.063 / 1.320 / 1.915 vs 1.053 / 1.264 / 1.735 before.\n //\n // WCAG rather than APCA, against the grain of the accent tokens above:\n // APCA's low-contrast clamp scores every step of a ramp this subtle as\n // Lc 0, so it cannot express the difference between these three faces at\n // all. Polarity-blindness — the reason APCA wins for text — costs nothing\n // for a decorative fill whose only job is to separate from the page.\n //\n // The high-contrast entries roughly double each step's distance from the\n // page (1.351 / 2.107 / 3.211) instead of leaving HC identical to the\n // normal tier, which is what an unconstrained tone delta gave.\n 'loading-face-1': {\n hue: baseHue,\n base: 'surface',\n tone: '-2',\n saturation: baseChroma(0.2),\n contrast: [1.2, 1.35],\n inherit: false,\n },\n 'loading-face-2': {\n hue: baseHue,\n base: 'surface',\n tone: '-2',\n saturation: baseChroma(0.2),\n contrast: [1.65, 2.1],\n inherit: false,\n },\n 'loading-face-3': {\n hue: baseHue,\n base: 'surface',\n tone: '-2',\n saturation: baseChroma(0.2),\n contrast: [2.4, 3.2],\n inherit: false,\n },\n\n // ---- Shadows (default-only) ----\n 'shadow-sm': {\n type: 'shadow',\n bg: 'surface',\n fg: 'surface-text',\n intensity: 5,\n inherit: false,\n },\n 'shadow-md': {\n type: 'shadow',\n bg: 'surface',\n fg: 'surface-text',\n intensity: 10,\n inherit: false,\n },\n 'shadow-lg': {\n type: 'shadow',\n bg: 'surface',\n fg: 'surface-text',\n intensity: 15,\n inherit: false,\n },\n\n // Backdrop overlay (translucent)\n overlay: { tone: 10, opacity: 0.5, inherit: false, hue: baseHue },\n });\n\n // --------------------------------------------------------------------------\n // Colored themes\n // --------------------------------------------------------------------------\n //\n // `primary` is the brand theme: it shares both the accent hue and the palette\n // seed saturation with `default`, and differs only in carrying a tinted surface.\n // Each status theme adds its own hue on top.\n const tintedSurface = tintedSurfaceOverride(config);\n const primaryTheme = defaultTheme.extend({\n colors: tintedSurface,\n });\n\n /**\n * One status theme's overrides, on whichever path its own seed is on.\n *\n * **Its own color** — the family becomes that color, exactly as the accent zone's does,\n * so the softened APCA floors in `accentColors`' color arm come with it rather than\n * being restated here. The theme's Glaze seed is the color's chroma (see\n * `ResolvedThemeSeed.saturation`), so the tinted surface, the border and the text ramp\n * re-solve at their shipped factors OF that chroma — the banner keeps its proportion to\n * the button instead of drifting relative to a fill whose chroma arrives absolute.\n *\n * **No color of its own, but the ACCENT has one** — the accent color's tone is taken\n * back out. The tone is the brand's, and only the brand's. Inherited, a light brand\n * would put `#danger-accent-surface` at tone 88 in a red hue — a pale pink danger\n * button, which is not a danger button. A status theme carries a *meaning* its hue\n * exists to signal, so absent an instruction of its own it keeps the white-anchored\n * derivation that lands every hue at a comparable weight. `extend({ colors })`\n * redefines each listed color from scratch, so restating the null arrangement is enough\n * to undo it.\n *\n * **Neither** — the tinted-surface override itself, so the shipped palette is provably\n * untouched.\n */\n const statusColors = (theme: ResolvedThemeSeed): ColorMap => {\n if (theme.color !== null && theme.colorTone !== null) {\n return {\n ...tintedSurface,\n ...accentColors(\n cappedAccent(theme.hue, theme.saturation, theme.colorTone),\n ),\n };\n }\n\n return accent == null\n ? tintedSurface\n : { ...tintedSurface, ...accentColors(null) };\n };\n\n const successTheme = defaultTheme.extend({\n hue: themes.success.hue,\n saturation: themes.success.saturation,\n colors: statusColors(themes.success),\n });\n const dangerTheme = defaultTheme.extend({\n hue: themes.danger.hue,\n saturation: themes.danger.saturation,\n colors: statusColors(themes.danger),\n });\n const warningTheme = defaultTheme.extend({\n hue: themes.warning.hue,\n saturation: themes.warning.saturation,\n colors: statusColors(themes.warning),\n });\n const noteTheme = defaultTheme.extend({\n hue: themes.note.hue,\n saturation: themes.note.saturation,\n colors: statusColors(themes.note),\n });\n\n // --------------------------------------------------------------------------\n // Special theme (fixed-mode, NOT inherited from the default theme)\n // --------------------------------------------------------------------------\n //\n // Standalone theme for `special`-variant components (hero CTAs, banners, etc.)\n // that intentionally sit on a dark surface regardless of the active scheme.\n //\n // Every token here is `mode: 'fixed'` so the resolved value is identical in\n // light, dark, and high-contrast. The shape is purpose-built (not a full\n // mirror of the default theme) — only what `SPECIAL_*_STYLES` in\n // `src/data/item-themes.ts` consumes is emitted. It follows the *primary*\n // seed, since it is the brand-on-dark theme.\n //\n // Token rundown:\n // - `surface` — dark L≈12 backdrop (same value as `#surface-inverse`).\n // - `accent-surface` / `accent-surface-2` / `accent-surface-3` —\n // brand PRIMARY fill ramp (default → hover → pressed). Mirrors the\n // `#primary-accent-surface` / `-2` / `-3` ramp on the colored themes so\n // `SPECIAL_PRIMARY_STYLES.fill` can use the same shape.\n // - `accent-surface-hover` — legacy alias kept around for the\n // `#special-hover` color shortcut in `src/tokens/colors.ts`. Item\n // themes themselves no longer reference it.\n // - `accent-surface-text` — fixed white (= built-in `#white`), exposed\n // for explicit references.\n // - `accent-text` — dark brand foreground readable on white. Used as\n // CLEAR-variant text on the always-white pill, and as the\n // pressed/focused border on the brand primary fill. Matches the\n // legacy `#fixed-primary-text` alias (= `#primary-accent-surface-hover`).\n // - `accent-disabled-surface` / `accent-disabled-surface-text` —\n // brand-tinted disabled chip + label, positioned with relative tone\n // deltas against the fixed dark `surface` so the disabled state is\n // scheme-symmetric.\n\n const specialTheme = glaze(hue, saturation, instanceConfig);\n\n specialTheme.colors({\n surface: { tone: 12, saturation: 0.475, mode: 'fixed' },\n\n // The same fill ramp as the default theme, and for the same reason: this IS the\n // brand CTA — `SPECIAL_PRIMARY_STYLES.fill` mirrors `#primary-accent-surface` — so\n // leaving it on the white-anchored derivation would give a yellow brand a\n // dark-purple hero button.\n //\n // Only the fills: `accent-selected-fill`, `accent-text-soft` and `accent-icon` are\n // not part of this theme's purpose-built shape, and adding them would grow the\n // emitted token set.\n ...accentFillColors(accent),\n\n // Special's `accent-text` is \"dark brand, readable on a WHITE pill\" (the CLEAR\n // selected fill) — not on `surface`, which here is the fixed dark backdrop. It\n // stays anchored to `accent-surface-text`, which is a hard white root in both\n // arrangements, so the `-58` delta keeps its meaning either way.\n 'accent-text': {\n base: 'accent-surface-text',\n tone: '-58',\n contrast: ['AA', 'AAA'],\n mode: 'fixed',\n },\n\n 'accent-disabled-surface': {\n base: 'surface',\n tone: '+4',\n saturation: 0.5,\n mode: 'fixed',\n },\n 'accent-disabled-surface-text': {\n base: 'accent-disabled-surface',\n tone: '+18',\n saturation: 0.4,\n mode: 'fixed',\n },\n });\n\n // --------------------------------------------------------------------------\n // Palette composition\n // --------------------------------------------------------------------------\n //\n // `purple` is the legacy alias for `primary` and resolves to identical tokens\n // (same theme instance, different prefix → no token-value collision).\n //\n // `default` is emitted with an empty prefix (custom map in `getPaletteTokens`),\n // so its tokens appear unprefixed (`#surface`, `#border`, …). All other themes\n // are prefixed with `<themeName>-`.\n return {\n palette: glaze.palette({\n default: defaultTheme,\n primary: primaryTheme,\n purple: primaryTheme,\n success: successTheme,\n danger: dangerTheme,\n warning: warningTheme,\n note: noteTheme,\n special: specialTheme,\n }),\n // Kept out of the palette on purpose: it has to emit `code-*` unprefixed, and\n // its mirrored `surface` would collide with the default theme's.\n codeTheme: buildCodeTheme(config, codeInstanceConfig),\n };\n}\n\n// ============================================================================\n// Tasty-formatted token export\n// ============================================================================\n\nconst TOKEN_PREFIXES = {\n default: '',\n primary: 'primary-',\n purple: 'purple-',\n success: 'success-',\n danger: 'danger-',\n warning: 'warning-',\n note: 'note-',\n special: 'special-',\n};\n\nlet paletteCache: GlazePalette | null = null;\nlet codeThemeCache: GlazeTheme | null = null;\nlet paletteTokensCache: Styles | null = null;\nlet cachedVersion = -1;\n\n/**\n * Whether *we* are the ones holding a manual level in Glaze's global config.\n *\n * `contrastLevel` is the one palette option that has to be global: a global\n * level pins `modes.highContrast: false` so no high-contrast tier is emitted at\n * all (which is the point — a manual level already carries the preference),\n * whereas a per-instance level would leave the tier in place and report its own\n * values there. `palette.tasty({ modes })` cannot override that either.\n *\n * Being global means it is shared with the host app, so we only write it when\n * asked. A host that sets its own `glaze.configure({ contrastLevel })` and never\n * touches `setPaletteConfig({ contrastLevel })` keeps its level — without this\n * flag we would silently reset it to `'auto'` on the first token read.\n */\nlet ownsContrastLevel = false;\n\nfunction applyContrastLevel(contrastLevel: number | 'auto') {\n // Nothing to do: we hold no level, and the config does not ask for one.\n if (contrastLevel === 'auto' && !ownsContrastLevel) return;\n\n // Always explicit — `configure()` never clears a field by omission, so\n // returning to `'auto'` has to say so.\n glaze.configure({ contrastLevel });\n ownsContrastLevel = contrastLevel !== 'auto';\n}\n\nfunction refresh() {\n const version = getPaletteVersion();\n\n if (paletteTokensCache && cachedVersion === version) return;\n\n const config = getPaletteConfig();\n\n applyContrastLevel(config.contrastLevel);\n\n const built = buildPalette(config);\n\n paletteCache = built.palette;\n paletteTokensCache = {\n ...built.palette.tasty({ prefix: TOKEN_PREFIXES, format: 'oklch' }),\n ...pickCodeTokens(built.codeTheme.tasty({ format: 'oklch' })),\n } as Styles;\n codeThemeCache = built.codeTheme;\n cachedVersion = version;\n}\n\n/**\n * Resolve Glaze palette tokens against the current palette config.\n *\n * Memoized against the config version (see `./palette-config.ts`), so repeated\n * reads are free and `setPaletteConfig()` invalidates them. Resolution is\n * deferred rather than done at module import, so host apps can call\n * `setPaletteConfig(...)` / `glaze.configure(...)` after importing\n * `@cube-dev/ui-kit` and still affect the first paint.\n *\n * Keys use `#name` syntax; values are state maps:\n * '#surface': { '': 'oklch(...)', '@dark': 'oklch(...)', '@hc': 'oklch(...)' }\n */\nexport function getPaletteTokens(): Styles {\n refresh();\n\n return paletteTokensCache!;\n}\n\n/** The live `GlazePalette`. Rebuilt whenever the palette config changes. */\nexport function getPalette(): GlazePalette {\n refresh();\n\n return paletteCache!;\n}\n\n/**\n * The live theme behind the `code-*` tokens. Separate from {@link getPalette}\n * because it carries its own saturation seed — see {@link CODE_COLORS}.\n */\nexport function getCodeTheme(): GlazeTheme {\n refresh();\n\n return codeThemeCache!;\n}\n\n// ============================================================================\n// One-variant render (for previewing a palette on a region)\n// ============================================================================\n\nconst VARIANT_KEY = {\n 'light:false': 'light',\n 'light:true': 'lightContrast',\n 'dark:false': 'dark',\n 'dark:true': 'darkContrast',\n} as const;\n\n/**\n * Single-entry memo: all four variants of the last config rendered.\n *\n * Keyed on the palette version as well as the config, because the config is not\n * the only input — `buildPalette` also reads Glaze's own global config\n * (`darkTone`, `darkDesaturation`, the state map). A host that drives Glaze\n * directly and then calls `invalidatePaletteTokens()` bumps the version without\n * touching the config, and the region previews have to follow the document.\n */\nlet renderKey: string | null = null;\nlet renderVariants: Record<string, Record<string, string>> = {};\n\n/**\n * Resolve one scheme variant of a palette to flat, literal color values.\n *\n * Unlike {@link getPaletteTokens}, which emits state maps (`@dark` / `@hc`) for\n * the whole document, this collapses the palette to the single variant you ask\n * for. That is what makes a **region** preview possible: applied through a tasty\n * `tokens` prop, the values override the inherited ones for that subtree only, so\n * a dark or high-contrast theme can be shown inside a light page.\n *\n * Prefer `renderColorTokens()` from `./colors` unless you specifically want the\n * palette without the legacy aliases.\n */\nexport function renderPaletteTokens(\n options: RenderPaletteOptions = {},\n): Tokens {\n const { scheme = 'light', highContrast = false, ...config } = options;\n const resolved = resolvePaletteConfig(config);\n const key = `${getPaletteVersion()}:${JSON.stringify(resolved)}`;\n\n if (renderKey !== key) {\n // Force the *global* level to `'auto'` for the duration of the export.\n // Glaze suppresses high-contrast output whenever a global level is set, and\n // that check reads the global config — so without this, a preview's variants\n // would depend on whatever contrast mode the surrounding app happens to be\n // in. The preview's own level rides on the theme instances instead\n // (`isolateContrastLevel`), so the values stay correct. Restored in `finally`;\n // resolution is synchronous, so nothing can observe the window.\n const previousLevel = glaze.getConfig().contrastLevel;\n\n try {\n glaze.configure({ contrastLevel: 'auto' });\n\n const built = buildPalette(resolved, { isolateContrastLevel: true });\n const modes = { dark: true, highContrast: true };\n const themed = built.palette.tokens({\n prefix: TOKEN_PREFIXES,\n format: 'oklch',\n modes,\n });\n const code = built.codeTheme.tokens({ format: 'oklch', modes });\n\n // Built into a local and published together, so a throw part-way through\n // cannot leave a half-filled map sitting under the previous key.\n const variants: typeof renderVariants = {};\n\n for (const name of Object.keys(themed)) {\n variants[name] = {\n ...themed[name],\n ...pickCodeTokens(code[name] ?? {}),\n };\n }\n\n renderVariants = variants;\n renderKey = key;\n } finally {\n glaze.configure({ contrastLevel: previousLevel });\n }\n }\n\n const variant = VARIANT_KEY[`${scheme}:${highContrast}`];\n // The fallback is for `contrastLevel: 100` only. There the normal variants\n // already *are* the high-contrast ones, so Glaze emits a single light/dark set\n // rather than duplicating it — and `highContrast` correctly resolves to the same\n // colors. At every other level the contrast variants are present and genuinely\n // escalated, so the fallback is not taken.\n const flat = renderVariants[variant] ?? renderVariants[scheme];\n const out: Tokens = {};\n\n for (const name of Object.keys(flat)) out[`#${name}`] = flat[name];\n\n return out;\n}\n\n/**\n * Lazy proxy of {@link getPaletteTokens}. Prefer `getPaletteTokens()` in new\n * code. Property / enumeration access always reflects the current palette\n * config.\n */\nexport const PALETTE_TOKENS: Styles = lazyStyles(getPaletteTokens);\n"],"mappings":"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"}
|
package/dist/tokens/resolve.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ interface ResolveTokenOptions {
|
|
|
7
7
|
*
|
|
8
8
|
* Pass an element when the value has to reflect a *local* override — a subtree
|
|
9
9
|
* carrying its own `tokens` prop (`renderColorTokens()`), or one under a
|
|
10
|
-
* `data-
|
|
10
|
+
* `data-scheme` / `data-contrast` attribute that differs from the document's.
|
|
11
11
|
* The element's own document and view are used, so a node inside a same-origin
|
|
12
12
|
* iframe resolves against that iframe.
|
|
13
13
|
*/
|
|
@@ -79,7 +79,7 @@ declare function resolveTokenValues<T extends string>(tokens: readonly T[], opti
|
|
|
79
79
|
declare function resolvePresetValues(preset: string, options?: ResolveTokenOptions): ResolvedPreset;
|
|
80
80
|
/**
|
|
81
81
|
* {@link resolveTokenValue} as a hook: re-renders when the palette is re-seeded
|
|
82
|
-
* or the
|
|
82
|
+
* or the scheme / contrast tier flips.
|
|
83
83
|
*
|
|
84
84
|
* ```tsx
|
|
85
85
|
* const accent = useTokenValue('#purple');
|
package/dist/tokens/resolve.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
/** @license MIT | @cube-dev/ui-kit v0.
|
|
1
|
+
/** @license MIT | @cube-dev/ui-kit v0.172.0 | Cube Dev Team */
|
|
2
2
|
import { useLayoutEffect as useLayoutEffect$1 } from "../utils/react/useLayoutEffect.js";
|
|
3
|
-
import {
|
|
3
|
+
import { subscribeScheme } from "../utils/react/useScheme.js";
|
|
4
4
|
import { warn } from "../utils/warnings.js";
|
|
5
5
|
import { getPaletteVersion, usePaletteVersion } from "./palette-config.js";
|
|
6
6
|
import { getTokens } from "./all-tokens.js";
|
|
@@ -220,11 +220,11 @@ function resolvePresetValues(preset, options = {}) {
|
|
|
220
220
|
}
|
|
221
221
|
/**
|
|
222
222
|
* A token's resolved value changes for two reasons: the palette was re-seeded
|
|
223
|
-
* (`setPaletteConfig()`, covered by `usePaletteVersion`), or the
|
|
223
|
+
* (`setPaletteConfig()`, covered by `usePaletteVersion`), or the scheme /
|
|
224
224
|
* contrast tier flipped. This store covers the second.
|
|
225
225
|
*
|
|
226
|
-
* The watching itself belongs to `
|
|
227
|
-
* which owns the definition of both axes — the `data-
|
|
226
|
+
* The watching itself belongs to `subscribeScheme()` (`src/utils/react/useScheme.ts`),
|
|
227
|
+
* which owns the definition of both axes — the `data-scheme` / `data-contrast`
|
|
228
228
|
* attributes and the media queries they fall back to are the same strings
|
|
229
229
|
* `<Root>` registers `@dark` and `@hc` from. This store only counts the changes,
|
|
230
230
|
* so there is one observer for the document rather than one per concern.
|
|
@@ -238,7 +238,7 @@ function onAppearanceChange() {
|
|
|
238
238
|
}
|
|
239
239
|
function subscribeAppearance(listener) {
|
|
240
240
|
appearanceListeners.add(listener);
|
|
241
|
-
if (appearanceListeners.size === 1) stopWatchingAppearance =
|
|
241
|
+
if (appearanceListeners.size === 1) stopWatchingAppearance = subscribeScheme(onAppearanceChange);
|
|
242
242
|
return () => {
|
|
243
243
|
appearanceListeners.delete(listener);
|
|
244
244
|
if (!appearanceListeners.size) {
|
|
@@ -264,7 +264,7 @@ function getServerAppearanceVersion() {
|
|
|
264
264
|
return HYDRATING;
|
|
265
265
|
}
|
|
266
266
|
/**
|
|
267
|
-
* Re-render on a
|
|
267
|
+
* Re-render on a scheme / contrast change. Returns the version rather than the
|
|
268
268
|
* state so the snapshot is a primitive — the same reason `usePaletteVersion`
|
|
269
269
|
* does, see `src/tokens/palette-config.ts`.
|
|
270
270
|
*/
|
|
@@ -273,7 +273,7 @@ function useAppearanceVersion() {
|
|
|
273
273
|
}
|
|
274
274
|
/**
|
|
275
275
|
* The value is read out of the DOM, so it re-resolves whenever the palette is
|
|
276
|
-
* re-seeded or the
|
|
276
|
+
* re-seeded or the scheme / contrast tier flips, and it is `fallback ?? null`
|
|
277
277
|
* during SSR. The layout effect is what covers the first commit: a consumer
|
|
278
278
|
* rendered in the same pass as `<Root>` reads before the token block lands.
|
|
279
279
|
*/
|
|
@@ -318,7 +318,7 @@ function isSameRecord(a, b) {
|
|
|
318
318
|
}
|
|
319
319
|
/**
|
|
320
320
|
* {@link resolveTokenValue} as a hook: re-renders when the palette is re-seeded
|
|
321
|
-
* or the
|
|
321
|
+
* or the scheme / contrast tier flips.
|
|
322
322
|
*
|
|
323
323
|
* ```tsx
|
|
324
324
|
* const accent = useTokenValue('#purple');
|