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