@sincpro/web-ui 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +157 -2
- package/dist/components/assist/AssistMark.d.ts +64 -0
- package/dist/components/assist/AssistMark.js +108 -0
- package/dist/components/assist/AssistPending.d.ts +24 -0
- package/dist/components/assist/AssistPending.js +26 -0
- package/dist/components/assist/AssistSuggestion.d.ts +42 -0
- package/dist/components/assist/AssistSuggestion.js +55 -0
- package/dist/components/assist/ChangeReview.d.ts +56 -0
- package/dist/components/assist/ChangeReview.js +121 -0
- package/dist/components/assist/assist.d.ts +77 -0
- package/dist/components/assist/assist.js +87 -0
- package/dist/components/assist/index.d.ts +5 -0
- package/dist/components/assist/index.js +7 -0
- package/dist/components/chatter/Chatter.d.ts +161 -0
- package/dist/components/chatter/Chatter.js +232 -0
- package/dist/components/chatter/Messages.d.ts +66 -0
- package/dist/components/chatter/Messages.js +43 -0
- package/dist/components/chatter/Timeline.d.ts +21 -0
- package/dist/components/chatter/Timeline.js +30 -0
- package/dist/components/chatter/index.d.ts +3 -0
- package/dist/components/chatter/index.js +4 -0
- package/dist/components/form/Form.d.ts +52 -0
- package/dist/components/form/Form.js +49 -0
- package/dist/components/form/Sheet.d.ts +43 -0
- package/dist/components/form/Sheet.js +83 -0
- package/dist/components/form/index.d.ts +2 -0
- package/dist/components/form/index.js +4 -0
- package/dist/components/frame/Chart.d.ts +13 -0
- package/dist/components/frame/Chart.js +24 -0
- package/dist/components/frame/FrameView.d.ts +33 -0
- package/dist/components/frame/FrameView.js +125 -0
- package/dist/components/frame/index.d.ts +3 -0
- package/dist/components/frame/index.js +3 -0
- package/dist/components/frame/model.d.ts +43 -0
- package/dist/components/frame/model.js +101 -0
- package/dist/components/index.d.ts +10 -0
- package/dist/components/index.js +11 -0
- package/dist/components/lines/LineItems.d.ts +74 -0
- package/dist/components/lines/LineItems.js +229 -0
- package/dist/components/lines/index.d.ts +1 -0
- package/dist/components/lines/index.js +2 -0
- package/dist/components/lines/lines.d.ts +23 -0
- package/dist/components/lines/lines.js +26 -0
- package/dist/components/listing/ColumnsMenu.d.ts +20 -0
- package/dist/components/listing/ColumnsMenu.js +46 -0
- package/dist/components/listing/GroupingLevels.d.ts +35 -0
- package/dist/components/listing/GroupingLevels.js +45 -0
- package/dist/components/listing/Listing.d.ts +27 -0
- package/dist/components/listing/Listing.js +86 -0
- package/dist/components/listing/ListingBar.d.ts +73 -0
- package/dist/components/listing/ListingBar.js +105 -0
- package/dist/components/listing/Pagination.d.ts +36 -0
- package/dist/components/listing/Pagination.js +69 -0
- package/dist/components/listing/RecordCard.d.ts +33 -0
- package/dist/components/listing/RecordCard.js +22 -0
- package/dist/components/listing/RecordLink.d.ts +7 -0
- package/dist/components/listing/RecordLink.js +22 -0
- package/dist/components/listing/Row.d.ts +106 -0
- package/dist/components/listing/Row.js +122 -0
- package/dist/components/listing/SortMenu.d.ts +62 -0
- package/dist/components/listing/SortMenu.js +105 -0
- package/dist/components/listing/ViewSwitcher.d.ts +24 -0
- package/dist/components/listing/ViewSwitcher.js +16 -0
- package/dist/components/listing/columns.d.ts +47 -0
- package/dist/components/listing/columns.js +72 -0
- package/dist/components/listing/grouping.d.ts +35 -0
- package/dist/components/listing/grouping.js +47 -0
- package/dist/components/listing/index.d.ts +17 -0
- package/dist/components/listing/index.js +20 -0
- package/dist/components/listing/modelColumns.d.ts +50 -0
- package/dist/components/listing/modelColumns.js +103 -0
- package/dist/components/listing/pages.d.ts +78 -0
- package/dist/components/listing/pages.js +135 -0
- package/dist/components/listing/selection.d.ts +21 -0
- package/dist/components/listing/selection.js +43 -0
- package/dist/components/listing/set.d.ts +48 -0
- package/dist/components/listing/set.js +24 -0
- package/dist/components/listing/tree.d.ts +28 -0
- package/dist/components/listing/tree.js +23 -0
- package/dist/components/navigation/CommandPalette.d.ts +40 -0
- package/dist/components/navigation/CommandPalette.js +114 -0
- package/dist/components/navigation/NavigationTree.d.ts +31 -0
- package/dist/components/navigation/NavigationTree.js +79 -0
- package/dist/components/navigation/commands.d.ts +30 -0
- package/dist/components/navigation/commands.js +30 -0
- package/dist/components/navigation/index.d.ts +3 -0
- package/dist/components/navigation/index.js +4 -0
- package/dist/components/record/Breadcrumb.d.ts +42 -0
- package/dist/components/record/Breadcrumb.js +98 -0
- package/dist/components/record/DirtyActions.d.ts +26 -0
- package/dist/components/record/DirtyActions.js +22 -0
- package/dist/components/record/Pager.d.ts +18 -0
- package/dist/components/record/Pager.js +33 -0
- package/dist/components/record/RecordHeader.d.ts +71 -0
- package/dist/components/record/RecordHeader.js +72 -0
- package/dist/components/record/RecordSkeleton.d.ts +3 -0
- package/dist/components/record/RecordSkeleton.js +21 -0
- package/dist/components/record/SmartButtons.d.ts +35 -0
- package/dist/components/record/SmartButtons.js +38 -0
- package/dist/components/record/StatusBar.d.ts +51 -0
- package/dist/components/record/StatusBar.js +168 -0
- package/dist/components/record/ThumbBar.d.ts +36 -0
- package/dist/components/record/ThumbBar.js +86 -0
- package/dist/components/record/index.d.ts +6 -0
- package/dist/components/record/index.js +8 -0
- package/dist/components/search/CriteriaBuilder.d.ts +58 -0
- package/dist/components/search/CriteriaBuilder.js +278 -0
- package/dist/components/search/CriteriaChips.d.ts +22 -0
- package/dist/components/search/CriteriaChips.js +64 -0
- package/dist/components/search/FilterBuilder.d.ts +142 -0
- package/dist/components/search/FilterBuilder.js +421 -0
- package/dist/components/search/JoinWord.d.ts +8 -0
- package/dist/components/search/JoinWord.js +47 -0
- package/dist/components/search/JsonEditor.d.ts +19 -0
- package/dist/components/search/JsonEditor.js +78 -0
- package/dist/components/search/SearchBar.d.ts +78 -0
- package/dist/components/search/SearchBar.js +387 -0
- package/dist/components/search/criteria.d.ts +116 -0
- package/dist/components/search/criteria.js +159 -0
- package/dist/components/search/datePeriods.d.ts +122 -0
- package/dist/components/search/datePeriods.js +134 -0
- package/dist/components/search/dateRanges.d.ts +54 -0
- package/dist/components/search/dateRanges.js +89 -0
- package/dist/components/search/index.d.ts +9 -0
- package/dist/components/search/index.js +20 -0
- package/dist/components/search/model.d.ts +54 -0
- package/dist/components/search/model.js +37 -0
- package/dist/components/search/query.d.ts +166 -0
- package/dist/components/search/query.js +436 -0
- package/dist/components/shell/AppFrame.d.ts +14 -0
- package/dist/components/shell/AppFrame.js +80 -0
- package/dist/components/shell/TopBar.d.ts +73 -0
- package/dist/components/shell/TopBar.js +54 -0
- package/dist/components/shell/headerSlot.d.ts +17 -0
- package/dist/components/shell/headerSlot.js +21 -0
- package/dist/components/shell/index.d.ts +2 -0
- package/dist/components/shell/index.js +3 -0
- package/dist/foundations/aria.d.ts +14 -0
- package/dist/foundations/aria.js +18 -0
- package/dist/foundations/calendar.d.ts +28 -0
- package/dist/foundations/calendar.js +183 -0
- package/dist/foundations/copy.d.ts +619 -0
- package/dist/foundations/copy.es.d.ts +2 -0
- package/dist/foundations/copy.es.js +377 -0
- package/dist/foundations/copy.js +472 -0
- package/dist/foundations/density.d.ts +16 -0
- package/dist/foundations/density.js +21 -0
- package/dist/foundations/grouping.d.ts +281 -0
- package/dist/foundations/grouping.js +0 -0
- package/dist/foundations/index.d.ts +14 -0
- package/dist/foundations/index.js +30 -0
- package/dist/foundations/layout.d.ts +9 -0
- package/dist/foundations/layout.js +9 -0
- package/dist/foundations/pivot.d.ts +222 -0
- package/dist/foundations/pivot.js +198 -0
- package/dist/foundations/pivotSheet.d.ts +61 -0
- package/dist/foundations/pivotSheet.js +0 -0
- package/dist/foundations/slots.d.ts +67 -0
- package/dist/foundations/slots.js +63 -0
- package/dist/foundations/sticky.d.ts +53 -0
- package/dist/foundations/sticky.js +150 -0
- package/dist/foundations/surface.d.ts +9 -0
- package/dist/foundations/surface.js +26 -0
- package/dist/foundations/theme.d.ts +42 -0
- package/dist/foundations/theme.js +88 -0
- package/dist/foundations/types.d.ts +23 -0
- package/dist/foundations/types.js +50 -0
- package/dist/foundations/variants.d.ts +19 -0
- package/dist/foundations/variants.js +48 -0
- package/dist/foundations/xlsx.d.ts +86 -0
- package/dist/foundations/xlsx.js +0 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +19 -0
- package/dist/primitives/block/Block.d.ts +66 -0
- package/dist/primitives/block/Block.js +34 -0
- package/dist/primitives/block/Line.d.ts +18 -0
- package/dist/primitives/block/Line.js +45 -0
- package/dist/primitives/block/SectionGrid.d.ts +30 -0
- package/dist/primitives/block/SectionGrid.js +29 -0
- package/dist/primitives/block/Steps.d.ts +76 -0
- package/dist/primitives/block/Steps.js +43 -0
- package/dist/primitives/block/index.d.ts +4 -0
- package/dist/primitives/block/index.js +7 -0
- package/dist/primitives/button/Button.d.ts +137 -0
- package/dist/primitives/button/Button.js +234 -0
- package/dist/primitives/button/index.d.ts +1 -0
- package/dist/primitives/button/index.js +2 -0
- package/dist/primitives/cell/CellStack.d.ts +47 -0
- package/dist/primitives/cell/CellStack.js +33 -0
- package/dist/primitives/cell/index.d.ts +1 -0
- package/dist/primitives/cell/index.js +3 -0
- package/dist/primitives/choice/ComboBox.d.ts +16 -0
- package/dist/primitives/choice/ComboBox.js +52 -0
- package/dist/primitives/choice/RadioGroup.d.ts +18 -0
- package/dist/primitives/choice/RadioGroup.js +50 -0
- package/dist/primitives/choice/SearchField.d.ts +24 -0
- package/dist/primitives/choice/SearchField.js +27 -0
- package/dist/primitives/choice/SegmentedControl.d.ts +16 -0
- package/dist/primitives/choice/SegmentedControl.js +69 -0
- package/dist/primitives/choice/Slider.d.ts +16 -0
- package/dist/primitives/choice/Slider.js +46 -0
- package/dist/primitives/choice/Switch.d.ts +14 -0
- package/dist/primitives/choice/Switch.js +65 -0
- package/dist/primitives/choice/ThemeSwitcher.d.ts +10 -0
- package/dist/primitives/choice/ThemeSwitcher.js +25 -0
- package/dist/primitives/choice/index.d.ts +8 -0
- package/dist/primitives/choice/index.js +11 -0
- package/dist/primitives/choice/options.d.ts +28 -0
- package/dist/primitives/choice/options.js +6 -0
- package/dist/primitives/date/Calendar.d.ts +31 -0
- package/dist/primitives/date/Calendar.js +110 -0
- package/dist/primitives/date/DatePicker.d.ts +34 -0
- package/dist/primitives/date/DatePicker.js +99 -0
- package/dist/primitives/date/index.d.ts +2 -0
- package/dist/primitives/date/index.js +4 -0
- package/dist/primitives/dialog/Dialog.d.ts +118 -0
- package/dist/primitives/dialog/Dialog.js +121 -0
- package/dist/primitives/dialog/index.d.ts +1 -0
- package/dist/primitives/dialog/index.js +2 -0
- package/dist/primitives/disclosure/Disclosure.d.ts +36 -0
- package/dist/primitives/disclosure/Disclosure.js +52 -0
- package/dist/primitives/disclosure/index.d.ts +1 -0
- package/dist/primitives/disclosure/index.js +2 -0
- package/dist/primitives/field/Checks.d.ts +57 -0
- package/dist/primitives/field/Checks.js +72 -0
- package/dist/primitives/field/DropZone.d.ts +42 -0
- package/dist/primitives/field/DropZone.js +52 -0
- package/dist/primitives/field/Field.d.ts +166 -0
- package/dist/primitives/field/Field.js +240 -0
- package/dist/primitives/field/Suggest.d.ts +48 -0
- package/dist/primitives/field/Suggest.js +43 -0
- package/dist/primitives/field/aria.d.ts +61 -0
- package/dist/primitives/field/aria.js +70 -0
- package/dist/primitives/field/context.d.ts +30 -0
- package/dist/primitives/field/context.js +13 -0
- package/dist/primitives/field/index.d.ts +7 -0
- package/dist/primitives/field/index.js +11 -0
- package/dist/primitives/field/layout.d.ts +24 -0
- package/dist/primitives/field/layout.js +10 -0
- package/dist/primitives/field/mode.d.ts +9 -0
- package/dist/primitives/field/mode.js +13 -0
- package/dist/primitives/hint/Hint.d.ts +31 -0
- package/dist/primitives/hint/Hint.js +42 -0
- package/dist/primitives/hint/index.d.ts +1 -0
- package/dist/primitives/hint/index.js +1 -0
- package/dist/primitives/index.d.ts +19 -0
- package/dist/primitives/index.js +20 -0
- package/dist/primitives/menu/Menu.d.ts +100 -0
- package/dist/primitives/menu/Menu.js +59 -0
- package/dist/primitives/menu/index.d.ts +1 -0
- package/dist/primitives/menu/index.js +2 -0
- package/dist/primitives/notice/Callout.d.ts +49 -0
- package/dist/primitives/notice/Callout.js +28 -0
- package/dist/primitives/notice/Notice.d.ts +45 -0
- package/dist/primitives/notice/Notice.js +61 -0
- package/dist/primitives/notice/Toast.d.ts +55 -0
- package/dist/primitives/notice/Toast.js +142 -0
- package/dist/primitives/notice/index.d.ts +3 -0
- package/dist/primitives/notice/index.js +5 -0
- package/dist/primitives/overlay/Popover.d.ts +6 -0
- package/dist/primitives/overlay/Popover.js +27 -0
- package/dist/primitives/overlay/PopoverTrigger.d.ts +55 -0
- package/dist/primitives/overlay/PopoverTrigger.js +38 -0
- package/dist/primitives/overlay/Tooltip.d.ts +38 -0
- package/dist/primitives/overlay/Tooltip.js +74 -0
- package/dist/primitives/overlay/index.d.ts +3 -0
- package/dist/primitives/overlay/index.js +5 -0
- package/dist/primitives/picker/Picker.d.ts +48 -0
- package/dist/primitives/picker/Picker.js +150 -0
- package/dist/primitives/picker/index.d.ts +1 -0
- package/dist/primitives/picker/index.js +2 -0
- package/dist/primitives/surface/Peek.d.ts +60 -0
- package/dist/primitives/surface/Peek.js +71 -0
- package/dist/primitives/surface/index.d.ts +1 -0
- package/dist/primitives/surface/index.js +2 -0
- package/dist/primitives/table/CellEditor.d.ts +14 -0
- package/dist/primitives/table/CellEditor.js +83 -0
- package/dist/primitives/table/DataTable.d.ts +135 -0
- package/dist/primitives/table/DataTable.js +244 -0
- package/dist/primitives/table/EditableTable.d.ts +96 -0
- package/dist/primitives/table/EditableTable.js +423 -0
- package/dist/primitives/table/PivotTable.d.ts +14 -0
- package/dist/primitives/table/PivotTable.js +0 -0
- package/dist/primitives/table/ReportTable.d.ts +38 -0
- package/dist/primitives/table/ReportTable.js +106 -0
- package/dist/primitives/table/RowActions.d.ts +16 -0
- package/dist/primitives/table/RowActions.js +28 -0
- package/dist/primitives/table/SheetTable.d.ts +70 -0
- package/dist/primitives/table/SheetTable.js +197 -0
- package/dist/primitives/table/Table.d.ts +106 -0
- package/dist/primitives/table/Table.js +116 -0
- package/dist/primitives/table/index.d.ts +7 -0
- package/dist/primitives/table/index.js +9 -0
- package/dist/primitives/tabs/Tabs.d.ts +91 -0
- package/dist/primitives/tabs/Tabs.js +56 -0
- package/dist/primitives/tabs/index.d.ts +1 -0
- package/dist/primitives/tabs/index.js +1 -0
- package/dist/primitives/tag/Tag.d.ts +36 -0
- package/dist/primitives/tag/Tag.js +68 -0
- package/dist/primitives/tag/Tags.d.ts +57 -0
- package/dist/primitives/tag/Tags.js +104 -0
- package/dist/primitives/tag/TagsField.d.ts +32 -0
- package/dist/primitives/tag/TagsField.js +182 -0
- package/dist/primitives/tag/choices.d.ts +38 -0
- package/dist/primitives/tag/choices.js +26 -0
- package/dist/primitives/tag/index.d.ts +4 -0
- package/dist/primitives/tag/index.js +8 -0
- package/dist/primitives/toolbar/Toolbar.d.ts +13 -0
- package/dist/primitives/toolbar/Toolbar.js +36 -0
- package/dist/primitives/toolbar/index.d.ts +1 -0
- package/dist/primitives/toolbar/index.js +2 -0
- package/dist/primitives/value/Copyable.d.ts +37 -0
- package/dist/primitives/value/Copyable.js +50 -0
- package/dist/primitives/value/Fecha.d.ts +12 -0
- package/dist/primitives/value/Fecha.js +80 -0
- package/dist/primitives/value/Meter.d.ts +29 -0
- package/dist/primitives/value/Meter.js +46 -0
- package/dist/primitives/value/Metric.d.ts +42 -0
- package/dist/primitives/value/Metric.js +38 -0
- package/dist/primitives/value/Person.d.ts +90 -0
- package/dist/primitives/value/Person.js +208 -0
- package/dist/primitives/value/Relation.d.ts +69 -0
- package/dist/primitives/value/Relation.js +57 -0
- package/dist/primitives/value/Totals.d.ts +26 -0
- package/dist/primitives/value/Totals.js +4 -0
- package/dist/primitives/value/Value.d.ts +77 -0
- package/dist/primitives/value/Value.js +119 -0
- package/dist/primitives/value/index.d.ts +8 -0
- package/dist/primitives/value/index.js +9 -0
- package/dist/tokens.css +1025 -0
- package/dist/views/index.d.ts +2 -0
- package/dist/views/index.js +3 -0
- package/dist/views/list/List.d.ts +113 -0
- package/dist/views/list/List.js +97 -0
- package/dist/views/list/ListView.d.ts +6 -0
- package/dist/views/list/ListView.js +269 -0
- package/dist/views/list/columns.d.ts +18 -0
- package/dist/views/list/columns.js +27 -0
- package/dist/views/list/derive.d.ts +76 -0
- package/dist/views/list/derive.js +181 -0
- package/dist/views/list/index.d.ts +2 -0
- package/dist/views/list/index.js +4 -0
- package/dist/views/list/presentations.d.ts +39 -0
- package/dist/views/list/presentations.js +103 -0
- package/dist/views/list/types.d.ts +272 -0
- package/dist/views/list/types.js +1 -0
- package/dist/views/list/useNarrowList.d.ts +17 -0
- package/dist/views/list/useNarrowList.js +32 -0
- package/dist/views/record/Record.d.ts +126 -0
- package/dist/views/record/Record.js +141 -0
- package/dist/views/record/index.d.ts +1 -0
- package/dist/views/record/index.js +2 -0
- package/package.json +109 -4
package/dist/tokens.css
ADDED
|
@@ -0,0 +1,1025 @@
|
|
|
1
|
+
/* Latin and latin-ext only. A face that covers á, é, ñ in one file and drops
|
|
2
|
+
* them in the next is why a sentence looks like two fonts. Cyrillic and Greek
|
|
3
|
+
* stay out: they are not this product. */
|
|
4
|
+
@import "@fontsource/ibm-plex-sans/latin-400.css";
|
|
5
|
+
@import "@fontsource/ibm-plex-sans/latin-ext-400.css";
|
|
6
|
+
@import "@fontsource/ibm-plex-sans/latin-500.css";
|
|
7
|
+
@import "@fontsource/ibm-plex-sans/latin-ext-500.css";
|
|
8
|
+
@import "@fontsource/ibm-plex-sans/latin-600.css";
|
|
9
|
+
@import "@fontsource/ibm-plex-sans/latin-ext-600.css";
|
|
10
|
+
@import "@fontsource/ibm-plex-mono/latin-400.css";
|
|
11
|
+
@import "@fontsource/ibm-plex-mono/latin-ext-400.css";
|
|
12
|
+
@import "@fontsource/ibm-plex-mono/latin-500.css";
|
|
13
|
+
@import "@fontsource/ibm-plex-mono/latin-ext-500.css";
|
|
14
|
+
|
|
15
|
+
@import "tailwindcss";
|
|
16
|
+
|
|
17
|
+
/* The variants React Aria's states are styled with — `pressed:`, `selected:`,
|
|
18
|
+
* `focus-visible:`, `placement-bottom:` — read from its data attributes. Declared here so a
|
|
19
|
+
* consumer that imports this file gets them without configuring anything. */
|
|
20
|
+
@plugin "tailwindcss-react-aria-components";
|
|
21
|
+
|
|
22
|
+
/* The components are written with Tailwind classes, so Tailwind has to read them. The path
|
|
23
|
+
* is relative to THIS file: in the repo it is src/, once published it is the package dist/
|
|
24
|
+
* inside node_modules — which Tailwind skips unless told. Importing this file is the whole
|
|
25
|
+
* setup a consumer needs. */
|
|
26
|
+
@source ".";
|
|
27
|
+
|
|
28
|
+
/* ==================================================================== *
|
|
29
|
+
* LIGHT THEME
|
|
30
|
+
*
|
|
31
|
+
* Every contrast ratio below is MEASURED (WCAG 2.1, relative luminance
|
|
32
|
+
* formula) against `--color-ground` = #FAF9F7. The number is written next to
|
|
33
|
+
* each token because a token without its number invites someone to use it
|
|
34
|
+
* where it cannot be read.
|
|
35
|
+
*
|
|
36
|
+
* Tailwind v4: there is no tailwind.config.js and no postcss config. It all
|
|
37
|
+
* lives here.
|
|
38
|
+
* ==================================================================== */
|
|
39
|
+
@theme {
|
|
40
|
+
/* WARM white, not pure white: #FFFFFF on a full screen tires the eye and
|
|
41
|
+
cheapens the piece. It is called `ground` and not `base` because
|
|
42
|
+
`text-base` is already a Tailwind font size and the name would clash. */
|
|
43
|
+
--color-ground: #faf9f7;
|
|
44
|
+
|
|
45
|
+
/* The raised surface IS white — that is what separates it from the ground.
|
|
46
|
+
Elevation here is done with a hairline border, not with a shadow: a shadow
|
|
47
|
+
on a light background is dirty grey. */
|
|
48
|
+
--color-surface: #ffffff;
|
|
49
|
+
|
|
50
|
+
/* The two hairlines. WARM grey — a neutral grey against a warm ground looks
|
|
51
|
+
bluish. They are not text: 1.26:1 and 1.49:1 on the ground, which is
|
|
52
|
+
exactly what a line that separates without shouting should measure. */
|
|
53
|
+
--color-line: #e4dfd7; /* 1.26:1 — common separator */
|
|
54
|
+
--color-line-strong: #d5cec2; /* 1.49:1 — border of a box, table header */
|
|
55
|
+
|
|
56
|
+
/* Text: warm near-black. 16.96:1 on the ground, 17.85:1 on white. */
|
|
57
|
+
--color-ink: #1a1714;
|
|
58
|
+
|
|
59
|
+
/* Faint text — labels, metadata, units. 5.56:1 on the ground and 5.85:1 on
|
|
60
|
+
white: it passes AA for small text, which is exactly where it is used. The
|
|
61
|
+
#8A8178 from the dark theme gave 3.63:1 here and was discarded. */
|
|
62
|
+
--color-muted: #6b6459;
|
|
63
|
+
|
|
64
|
+
/* ---------------------------------------------------------------- *
|
|
65
|
+
* THE GOLD — and the rule that governs it on light.
|
|
66
|
+
*
|
|
67
|
+
* #C89B3C on #FAF9F7 gives 2.43:1. That is NOT enough for small text
|
|
68
|
+
* (4.5:1), nor for large text (3:1), nor even for an icon or a border that
|
|
69
|
+
* has to mean something (1.4.11, 3:1).
|
|
70
|
+
*
|
|
71
|
+
* So on light the gold is used in TWO places and nowhere else:
|
|
72
|
+
* · as the BACKGROUND of the primary action, with `ink` text on top → 6.98:1
|
|
73
|
+
* · as a purely decorative 1px rule, next to nothing that has to be read.
|
|
74
|
+
*
|
|
75
|
+
* Never as a text color, never as the color of an icon that informs, never
|
|
76
|
+
* as the border that marks a state. If the gold needs to say something, it
|
|
77
|
+
* is the dark text on top of it that says it.
|
|
78
|
+
* ---------------------------------------------------------------- */
|
|
79
|
+
--color-accent: #c89b3c; /* 2.43:1 on the ground → NEVER text */
|
|
80
|
+
--color-accent-hover: #b88a2e; /* on light the hover DARKENS: ink on top gives 5.71:1 */
|
|
81
|
+
|
|
82
|
+
/* The text ON the gold. It is its own token and not `ink` because `ink` flips
|
|
83
|
+
to a light colour in the dark theme, and light text on gold is 2.4:1: the
|
|
84
|
+
primary action would stop being readable the moment the theme changed. The
|
|
85
|
+
gold button is dark-on-gold in BOTH themes. */
|
|
86
|
+
--color-on-accent: #1a1714; /* 6.98:1 on the gold */
|
|
87
|
+
|
|
88
|
+
/* Data blue. On light the brand blue reads in full, without lightening it:
|
|
89
|
+
13.52:1. In the dark theme it had to be lightened to #3D5CA8 so that a 6px
|
|
90
|
+
bar would exist; here that problem does not exist. */
|
|
91
|
+
--color-data: #1b2a4a; /* 13.52:1 */
|
|
92
|
+
|
|
93
|
+
/* ---------------------------------------------------------------- *
|
|
94
|
+
* THE ACTION ROLES — «it is in only 2 colors and does not have many
|
|
95
|
+
* calls to action», and that was true.
|
|
96
|
+
*
|
|
97
|
+
* The interface had grey and gold. Gold at 2.43:1 cannot say anything, so
|
|
98
|
+
* everything that was an action ended up drawn with `data` (#1B2A4A) — a
|
|
99
|
+
* blue so dark that next to `ink` (#1A1714) it IS INDISTINGUISHABLE. That
|
|
100
|
+
* is: the palette had one action color and that color read as text. What
|
|
101
|
+
* looks like text does not get clicked.
|
|
102
|
+
*
|
|
103
|
+
* The fix is not to add decorative colors: it is to give each ROLE its own
|
|
104
|
+
* color, and to measure it. Five roles and not one more:
|
|
105
|
+
*
|
|
106
|
+
* primary — what the screen wants you to press (background or text)
|
|
107
|
+
* secondary— the action with weight but without urgency (box + `primary`)
|
|
108
|
+
* danger — what destroys something and cannot be undone
|
|
109
|
+
* info — what informs without being a state of the data
|
|
110
|
+
* good/warn/bad — the three states, which already existed
|
|
111
|
+
*
|
|
112
|
+
* `data` is NOT touched and is NOT an action: it remains the color of the
|
|
113
|
+
* DATA — a bar, a series, an axis. That the data and the button shared a
|
|
114
|
+
* color is what kept either of them from meaning anything.
|
|
115
|
+
* ---------------------------------------------------------------- */
|
|
116
|
+
|
|
117
|
+
/* The action. It serves both purposes at once — as TEXT on the ground and as a
|
|
118
|
+
BACKGROUND with white on top. A color that only works as a background forces
|
|
119
|
+
you to keep two blues; this is a single one.
|
|
120
|
+
|
|
121
|
+
It used to be a saturated royal blue (#1a4fa0, 216°) "so it looks blue at a
|
|
122
|
+
glance". Next to the chrome navy (224°) that made TWO unrelated blues on every
|
|
123
|
+
screen — the bar one family, every link and selected day another — and the
|
|
124
|
+
product read as assembled, not designed. It is now the chrome's own navy,
|
|
125
|
+
less saturated: still clearly blue, one family with the bar, and it reads
|
|
126
|
+
BETTER — 8.26:1 as text (was 7.48:1), white on top 8.69:1 (was 7.87:1). */
|
|
127
|
+
--color-primary: #2a4a85; /* 8.26:1 · white on top 8.69:1 */
|
|
128
|
+
--color-primary-hover: #1c3765; /* 11.19:1 · white on top 11.77:1 */
|
|
129
|
+
|
|
130
|
+
/* Destructive. It is the SAME red as the `bad` state, on purpose: the red
|
|
131
|
+
that says «this went wrong» and the one that says «this is going to delete
|
|
132
|
+
something» are the same warning, and two different reds are two things to
|
|
133
|
+
learn. */
|
|
134
|
+
--color-danger: #9b3b30; /* 6.52:1 · white on top 6.86:1 */
|
|
135
|
+
--color-danger-hover: #7c2d24; /* 8.84:1 · white on top 9.30:1 */
|
|
136
|
+
|
|
137
|
+
/* Informative. Blue-green, chosen because it is the only gap left between the
|
|
138
|
+
action blue and the state green: it is not confused with «press me» nor
|
|
139
|
+
with «this is fine». It is carried by a note, a counter, a label that
|
|
140
|
+
explains — never by a button. */
|
|
141
|
+
--color-info: #0f5c6e; /* 7.19:1 · white on top 7.56:1 */
|
|
142
|
+
|
|
143
|
+
/* The second stroke, for when there are two series. 4.43:1: it is still
|
|
144
|
+
legible text if it needs a label, and well above the 3:1 that a graphical
|
|
145
|
+
element requires. */
|
|
146
|
+
--color-data-soft: #5c74a6; /* 4.43:1 */
|
|
147
|
+
|
|
148
|
+
/* States. Muted on purpose —this is a tool, not an alarm panel— but all three
|
|
149
|
+
pass AA as TEXT on the ground, because all three are used as a word and not
|
|
150
|
+
as decoration. */
|
|
151
|
+
--color-good: #2f6b3c; /* 6.07:1 */
|
|
152
|
+
--color-warn: #7e5e12; /* 5.71:1 */
|
|
153
|
+
--color-bad: #9b3b30; /* 6.52:1 */
|
|
154
|
+
|
|
155
|
+
/* ---------------------------------------------------------------- *
|
|
156
|
+
* THE CHROME — and why it exists.
|
|
157
|
+
*
|
|
158
|
+
* One number settles this entire decision:
|
|
159
|
+
*
|
|
160
|
+
* gold #C89B3C on the canvas #FAF9F7 → 2.43:1 unusable
|
|
161
|
+
* gold #C89B3C on the chrome #141B2E → 6.70:1 AA
|
|
162
|
+
*
|
|
163
|
+
* The gold is NOT a color for a light application: it is a color for a dark
|
|
164
|
+
* surface. As long as the only background was #FAF9F7, the brand could only
|
|
165
|
+
* appear as the fill of a button, and there it competed with the action blue
|
|
166
|
+
* — which is exactly the symptom of «it is in only two colors» and of the
|
|
167
|
+
* three contradictory primaries (`+ Crear` blue, `Registrar pago` gold).
|
|
168
|
+
*
|
|
169
|
+
* So the interface has TWO surfaces and each one has its own set:
|
|
170
|
+
*
|
|
171
|
+
* CHROME header, menu rail, tab bar, activity bar.
|
|
172
|
+
* It is the brand. Here the gold IS text.
|
|
173
|
+
* CANVAS the workspace. This is where the data lives, and the gold
|
|
174
|
+
* appears only as the background of the CTA with `ink` on top.
|
|
175
|
+
*
|
|
176
|
+
* And along the way it solves something that was not about color: today the
|
|
177
|
+
* near-white header and the near-white work area are the SAME surface, so
|
|
178
|
+
* nothing says where navigation ends and content begins.
|
|
179
|
+
*
|
|
180
|
+
* The states are repeated lightened because the canvas ones cannot be read
|
|
181
|
+
* on #141B2E: `good` #2F6B3C gives 1.4:1 there. They are not new colors —
|
|
182
|
+
* they are the same meaning measured against the other surface.
|
|
183
|
+
* ---------------------------------------------------------------- */
|
|
184
|
+
/* DEEP NAVY. Chosen among six candidates that all passed AA, so the
|
|
185
|
+
measurement did not decide it: the eye looking at the real bar decided it.
|
|
186
|
+
It is the other end of the brand's blue→gold gradient, and that has a
|
|
187
|
+
practical consequence — the `brand-wash` is BORN out of the background
|
|
188
|
+
itself instead of being painted on top, which is why here the wash reads as
|
|
189
|
+
light and on black it read as a stain. */
|
|
190
|
+
--color-chrome: #141b2e; /* the base */
|
|
191
|
+
--color-chrome-raised: #1e2740; /* active tab, open menu */
|
|
192
|
+
/* The hairline under the application bar. On light the chrome already
|
|
193
|
+
separates from the canvas by luminance, so it IS the chrome (invisible); in the
|
|
194
|
+
dark theme the two are both near-black and the hairline is what tells them apart. */
|
|
195
|
+
--color-chrome-edge: #141b2e; /* 1.00:1 on the chrome — invisible on light, a hairline on dark */
|
|
196
|
+
--color-chrome-ink: #e8e4de; /* 13.53:1 — text */
|
|
197
|
+
--color-chrome-muted: #a39b90; /* 6.24:1 — faint text */
|
|
198
|
+
--color-chrome-accent: #c89b3c; /* 6.70:1 — the gold, here it IS text */
|
|
199
|
+
|
|
200
|
+
/* ── The link had to change, and the reason is NOT contrast ──────────
|
|
201
|
+
The blue #6FA0EA gave 6.44:1 on this background: too dark, it sank into the
|
|
202
|
+
navy. Then came a cyan (#7fd1e8, 193°) to separate by hue as well — and it
|
|
203
|
+
became the one color of the interface that belonged to neither family, the
|
|
204
|
+
navy or the gold: "Descartar" on the rail read as a different product.
|
|
205
|
+
The answer is luminance, not hue: a pale periwinkle of the navy's own family
|
|
206
|
+
(220°) at 9.46:1 separates from the bar as clearly as the cyan did (9.94:1)
|
|
207
|
+
and stays in the palette. WCAG measures luminance and people see hue — here
|
|
208
|
+
both agree: light on dark, one family. */
|
|
209
|
+
--color-chrome-link: #a9c1f2; /* 9.46:1 · same family as the navy, separated by luminance */
|
|
210
|
+
|
|
211
|
+
--color-chrome-good: #6fcf8b; /* 8.95:1 */
|
|
212
|
+
--color-chrome-warn: #e3b341; /* 8.80:1 */
|
|
213
|
+
--color-chrome-bad: #ff8a7a; /* 7.48:1 */
|
|
214
|
+
|
|
215
|
+
/* The veil behind a dialog or the command palette. It is a token because the
|
|
216
|
+
obvious `bg-ink/30` is a LIGHT veil in the dark theme (ink flips), which
|
|
217
|
+
would lighten what it is meant to dim. */
|
|
218
|
+
--color-scrim: #1a1714; /* 16.96:1 on the ground — a veil, never text */
|
|
219
|
+
|
|
220
|
+
/* ---------------------------------------------------------------- *
|
|
221
|
+
* THE TWO FAMILIES — and the rule that decides which one goes.
|
|
222
|
+
*
|
|
223
|
+
* This was already written here and it was NOT being followed: 242 uses of
|
|
224
|
+
* `font-mono` and ZERO of `font-sans` were counted across the whole product.
|
|
225
|
+
* Which means the monospaced face was not the identity of anything — it was
|
|
226
|
+
* the only font, and that is why `ds_01a0999f…` and «buscar por nombre…»
|
|
227
|
+
* looked exactly alike.
|
|
228
|
+
*
|
|
229
|
+
* The monospaced face is useful when it says something the proportional one
|
|
230
|
+
* cannot: that this was written by the machine, or that this column aligns
|
|
231
|
+
* with the one below. Used for everything, it says nothing and on top of
|
|
232
|
+
* that it reads worse — it is wider, it has less contrast between shapes and
|
|
233
|
+
* it tires the eye sooner.
|
|
234
|
+
*
|
|
235
|
+
* THE RULE, and there is no third case:
|
|
236
|
+
*
|
|
237
|
+
* mono — what the MACHINE wrote or what has to be ALIGNED:
|
|
238
|
+
* identifiers, file and column names, fingerprints, figures,
|
|
239
|
+
* units, keys, code fragments.
|
|
240
|
+
* sans — what a PERSON wrote to be read: sentences, help text, empty
|
|
241
|
+
* states, button labels, titles, menu entries, placeholder text.
|
|
242
|
+
*
|
|
243
|
+
* When in doubt, the question is: if this goes out of alignment with the
|
|
244
|
+
* line below, is anything lost? If the answer is no, it goes in the
|
|
245
|
+
* proportional face.
|
|
246
|
+
* ---------------------------------------------------------------- */
|
|
247
|
+
/* One pair, drawn together. A system sans next to Menlo is two products:
|
|
248
|
+
the mono is wider, the accents fall out, and a filename looks like it was
|
|
249
|
+
pasted in from a terminal. Plex Sans and Plex Mono share the same skeleton,
|
|
250
|
+
so an id and the sentence around it are the same voice at two widths. */
|
|
251
|
+
--font-sans: "IBM Plex Sans", ui-sans-serif, system-ui, sans-serif;
|
|
252
|
+
--font-mono: "IBM Plex Mono", ui-monospace, monospace;
|
|
253
|
+
|
|
254
|
+
/* ---------------------------------------------------------------- *
|
|
255
|
+
* THE TYPE SCALE
|
|
256
|
+
*
|
|
257
|
+
* Six steps, 14px base, ratio ~1.15. A size is never written at the point
|
|
258
|
+
* of use: `text-[0.68rem]` is a size picked by eye, and a handful of those
|
|
259
|
+
* end with two things that mean the same looking different and two that
|
|
260
|
+
* mean different things looking the same.
|
|
261
|
+
*
|
|
262
|
+
* micro — the label of a pill, the index of a note
|
|
263
|
+
* meta — the subtitle of a row, the unit, the secondary datum
|
|
264
|
+
* body — the tool's default reading size
|
|
265
|
+
* lead — the name of a row, the label that heads it
|
|
266
|
+
* title — the title of a panel
|
|
267
|
+
* display — the title of the workspace, and nothing else
|
|
268
|
+
*
|
|
269
|
+
* Each step carries its own line height, which is half the job: a micro at
|
|
270
|
+
* 1.5 floats, and a display at 1.5 detaches from its own block.
|
|
271
|
+
*
|
|
272
|
+
* The scale is deliberately generous. Density worth having is earned with
|
|
273
|
+
* air and hierarchy —fewer things shouting at once—, not by shrinking the
|
|
274
|
+
* type until it fits: small type plus lines everywhere is the vocabulary of
|
|
275
|
+
* a control panel, and this is not one.
|
|
276
|
+
* ------------------------------------------------------------------- */
|
|
277
|
+
--text-micro: 0.75rem;
|
|
278
|
+
--text-micro--line-height: 1.4;
|
|
279
|
+
--text-meta: 0.8125rem;
|
|
280
|
+
--text-meta--line-height: 1.45;
|
|
281
|
+
--text-body: 0.875rem;
|
|
282
|
+
--text-body--line-height: 1.6;
|
|
283
|
+
--text-lead: 0.9375rem;
|
|
284
|
+
--text-lead--line-height: 1.45;
|
|
285
|
+
--text-title: 1.1875rem;
|
|
286
|
+
--text-title--line-height: 1.35;
|
|
287
|
+
--text-display: 1.75rem;
|
|
288
|
+
--text-display--line-height: 1.2;
|
|
289
|
+
|
|
290
|
+
/* ---------------------------------------------------------------- *
|
|
291
|
+
* THE RADIUS SCALE — «it is very square», and that was true.
|
|
292
|
+
*
|
|
293
|
+
* The whole product used a literal `rounded-[2px]`, everywhere and for
|
|
294
|
+
* everything: a button, a box, a notice band and a chip all had the same
|
|
295
|
+
* corner. A single radius is not a density decision — it is the absence of a
|
|
296
|
+
* scale, and what it produces is exactly the feeling that everything is the
|
|
297
|
+
* same box repeated.
|
|
298
|
+
*
|
|
299
|
+
* The radius GROWS WITH THE SURFACE, which is the only rule that makes a
|
|
300
|
+
* radius scale read as intentional:
|
|
301
|
+
* mark — a glyph-sized box. At 8px a 16px checkbox is already a circle
|
|
302
|
+
* control — what you touch and what measures one line of text
|
|
303
|
+
* box — a content box, a panel, a band
|
|
304
|
+
* panel — a floating surface: popup menu, notice, window
|
|
305
|
+
* pill — what is round because it is a capsule, not a box
|
|
306
|
+
*
|
|
307
|
+
* **The measured step.** 6/10/14 still read as the dense console. The median
|
|
308
|
+
* control across measured production systems is 8px, and the button shares
|
|
309
|
+
* it with the field: surveyed systems match those three (button, input,
|
|
310
|
+
* select) instead of rounding the button alone. The surface still grows.
|
|
311
|
+
* 12 is the next published peak and the modal step; 16 is what floats.
|
|
312
|
+
* The pill stays a capsule — a tag, a switch, an icon-only target — and
|
|
313
|
+
* is not a labeled button.
|
|
314
|
+
* ------------------------------------------------------------------- */
|
|
315
|
+
--radius-mark: 4px;
|
|
316
|
+
--radius-control: 8px;
|
|
317
|
+
--radius-box: 12px;
|
|
318
|
+
--radius-panel: 16px;
|
|
319
|
+
--radius-pill: 9999px;
|
|
320
|
+
|
|
321
|
+
/* ---------------------------------------------------------------- *
|
|
322
|
+
* ELEVATION
|
|
323
|
+
*
|
|
324
|
+
* On light a black shadow looks like dirt, so these are shadows the color of
|
|
325
|
+
* the TEXT (warm) and very wide open. Three steps:
|
|
326
|
+
* raised — something that barely lifts off the ground (an active row)
|
|
327
|
+
* float — a menu, a popover: there is something underneath and it has to
|
|
328
|
+
* be believable
|
|
329
|
+
* window — a floating window of the panel manager
|
|
330
|
+
*
|
|
331
|
+
* The rule that survives from the previous theme: the PRIMARY elevation is
|
|
332
|
+
* still a hairline border. The shadow accompanies, it does not replace — a
|
|
333
|
+
* box with no border and a shadow on #FAF9F7 has no edge and looks blurry. */
|
|
334
|
+
--shadow-raised: 0 1px 2px -1px rgba(26, 23, 20, 0.1);
|
|
335
|
+
--shadow-float: 0 8px 24px -12px rgba(26, 23, 20, 0.35);
|
|
336
|
+
--shadow-window:
|
|
337
|
+
0 2px 6px -2px rgba(26, 23, 20, 0.18), 0 18px 40px -20px rgba(26, 23, 20, 0.42);
|
|
338
|
+
|
|
339
|
+
/* The air between the blocks of a view. Before, each view picked its `gap-3`
|
|
340
|
+
or its `gap-4` by eye; this is the value, and separating SIBLING blocks
|
|
341
|
+
uses twice as much. It went up with the «Aire» decision: what separates one
|
|
342
|
+
row from the next became the space, so the space has to be enough. */
|
|
343
|
+
--spacing-block: 1rem;
|
|
344
|
+
|
|
345
|
+
/* One step of NESTING: a group inside a group, a report line under its
|
|
346
|
+
section. Every presentation that draws depth multiplies by this one value,
|
|
347
|
+
so a grouped list and a grouped table step in at the same rhythm — nesting
|
|
348
|
+
read at two different widths is what makes two views of the same set look
|
|
349
|
+
like two different products.
|
|
350
|
+
|
|
351
|
+
It is wider than it looks necessary on purpose: the depth has to survive a
|
|
352
|
+
truncated label, and a step you have to measure to see is not a step. */
|
|
353
|
+
--spacing-indent: 1.25rem;
|
|
354
|
+
|
|
355
|
+
/* ---------------------------------------------------------------- *
|
|
356
|
+
* ICONOGRAPHY — three sizes, and the stroke weight is part of the size.
|
|
357
|
+
*
|
|
358
|
+
* `lucide` was chosen because it draws with a STROKE and not with a fill,
|
|
359
|
+
* and a stroke can accompany 13px text without shouting. But a stroke has a
|
|
360
|
+
* trap that a fill does not: **the weight does not scale with the size**. A
|
|
361
|
+
* 12px icon with the default 2px stroke clogs up and reads as a smudge; that
|
|
362
|
+
* same stroke on an 18px one looks thin.
|
|
363
|
+
*
|
|
364
|
+
* That is why the three steps come with their weight and are not mixed:
|
|
365
|
+
*
|
|
366
|
+
* sm 12px / 2.00 — inside a line of text: a chevron, a breadcrumb
|
|
367
|
+
* md 16px / 1.75 — the default size: a button, a row
|
|
368
|
+
* lg 18px / 1.50 — the identity icon of a record
|
|
369
|
+
*
|
|
370
|
+
* The size GROWS and the weight GOES DOWN. It is counterintuitive written
|
|
371
|
+
* down and obvious once looked at: what stays constant is the optical
|
|
372
|
+
* weight, not the number.
|
|
373
|
+
* ---------------------------------------------------------------- */
|
|
374
|
+
/* ---------------------------------------------------------------- *
|
|
375
|
+
* CONTROL HEIGHT — the target you touch.
|
|
376
|
+
*
|
|
377
|
+
* The product had 24px controls and the diagnosis was literal: «things that
|
|
378
|
+
* are very flat and could be buttons». It was not a lack of color — it was
|
|
379
|
+
* that at 24px a control stops reading as a target and starts getting missed
|
|
380
|
+
* when you aim at it.
|
|
381
|
+
*
|
|
382
|
+
* Three steps, and each one is a PLACE, not a taste:
|
|
383
|
+
*
|
|
384
|
+
* sm 28px inside a row or the header of a block
|
|
385
|
+
* md 34px the working size: form fields, menus, most buttons
|
|
386
|
+
* lg 40px the action that has to be found without looking
|
|
387
|
+
*
|
|
388
|
+
* ── Why the whole scale went up ─────────────────────────────────
|
|
389
|
+
*
|
|
390
|
+
* «the action buttons are kind of small and do not call to action, it is
|
|
391
|
+
* hard to find them». It was true and the diagnosis is measurable: an action
|
|
392
|
+
* bar at 26px has the same visual weight as a row of labels, so the eye does
|
|
393
|
+
* not tell it apart from the content and you have to READ to find what to
|
|
394
|
+
* press. The consoles that work —DigitalOcean, Odoo— put their primary
|
|
395
|
+
* action near 40px, and it is not generosity: it is that at that size it can
|
|
396
|
+
* be seen from far away, which is a primary button's entire job.
|
|
397
|
+
*
|
|
398
|
+
* `md` is the working size because a form field and a bar button have to
|
|
399
|
+
* measure the SAME: put in the same row —and they always end up in the same
|
|
400
|
+
* row— two different heights read as an alignment mistake, not as a
|
|
401
|
+
* hierarchy.
|
|
402
|
+
* ---------------------------------------------------------------- */
|
|
403
|
+
/* ---------------------------------------------------------------- *
|
|
404
|
+
* THE MEASURE — how many characters fit in a line.
|
|
405
|
+
*
|
|
406
|
+
* It is a typographic decision and that is why it is a token, not a loose
|
|
407
|
+
* number in each component. The legible range is between 45 and 75
|
|
408
|
+
* characters: below it the eye jumps lines too often, above it, it loses
|
|
409
|
+
* track of where the next line started.
|
|
410
|
+
*
|
|
411
|
+
* It matters MORE here than on the web: the panels of this product reach
|
|
412
|
+
* 1,800px, and a help paragraph stretched to 1,800px is illegible even if it
|
|
413
|
+
* is perfectly contrasted.
|
|
414
|
+
*
|
|
415
|
+
* short 48ch an empty state, a notice — read in one go
|
|
416
|
+
* prose 68ch a paragraph of documentation or of help
|
|
417
|
+
* ---------------------------------------------------------------- */
|
|
418
|
+
--container-short: 48ch;
|
|
419
|
+
--container-prose: 68ch;
|
|
420
|
+
|
|
421
|
+
/* The width of a list's search field. It has a cap and it does not stretch: a
|
|
422
|
+
700px field to type «ventas» is an empty rectangle in the middle of the
|
|
423
|
+
screen, and what the eye reads first in a list has to be the rows. See
|
|
424
|
+
`ListingBar`. */
|
|
425
|
+
--container-search: 30rem;
|
|
426
|
+
|
|
427
|
+
/* Lo que PIDE una miga antes de recortarse, y lo que pide el rastro entero.
|
|
428
|
+
Los dos son pedidos y no anchos: una línea más angosta se los baja, y una
|
|
429
|
+
más ancha se los deja crecer.
|
|
430
|
+
|
|
431
|
+
Hacen falta los dos porque son dos problemas distintos. Un nombre de este
|
|
432
|
+
producto llega a cincuenta caracteres, así que UNA miga puede comerse la
|
|
433
|
+
línea entera aunque el rastro tenga tres: `--container-crumb` es lo que
|
|
434
|
+
cada una pide antes de terminar en «…», y ahí la que manda es la última
|
|
435
|
+
—dónde estoy— que nunca se recorta del todo.
|
|
436
|
+
|
|
437
|
+
`--container-trail` es lo que pide el rastro COMPLETO en la línea de la
|
|
438
|
+
banda. Sin él, «de dónde vengo» le saca el lugar a lo que cuelga del
|
|
439
|
+
registro y el centro deja de estar en el centro. */
|
|
440
|
+
--container-crumb: 12rem;
|
|
441
|
+
--container-trail: 20rem;
|
|
442
|
+
|
|
443
|
+
/* The grid of the app switcher: three columns of icon + word. */
|
|
444
|
+
--container-apps: 21rem;
|
|
445
|
+
|
|
446
|
+
/* The height of the application bar. It belongs to the system because
|
|
447
|
+
EVERYTHING that hangs up there has to measure the same. */
|
|
448
|
+
--size-appbar: 2.75rem;
|
|
449
|
+
|
|
450
|
+
/* The width of a toast. Capped and not full-screen: a toast as wide as the
|
|
451
|
+
window reads as a system band, and what it is has to be ignorable. */
|
|
452
|
+
--container-toast: 24rem;
|
|
453
|
+
|
|
454
|
+
/* The label column of an inline field. It belongs to the system and not to
|
|
455
|
+
the form: if every sheet picks its own, two sheets of the same product have
|
|
456
|
+
their values starting on different verticals. 10 rem: 11 left a hole next
|
|
457
|
+
to short values (a size, a date) and the sheet read as empty; 8 cut the
|
|
458
|
+
labels an ERP actually has — «Referencia del cliente», «Plazo de pago» —
|
|
459
|
+
to «Referencia del cli…», and a truncated label is a question. */
|
|
460
|
+
--container-label: 10rem;
|
|
461
|
+
|
|
462
|
+
/* The tag column of a list row. It is what the column ASKS FOR, and it
|
|
463
|
+
belongs to the system: with a width of its own every tag starts where its
|
|
464
|
+
own ends and twelve rows give twelve indents. Asking for the same 8 rem,
|
|
465
|
+
they all fall on the same vertical.
|
|
466
|
+
|
|
467
|
+
It is asked for and not imposed —a base and not a fixed width— because a
|
|
468
|
+
row also lands in a 480 px drawer, and there a quarter of the box reserved
|
|
469
|
+
for the word «vencida» is a quarter taken away from the name. When there
|
|
470
|
+
is not enough for everyone the column gives back what it is not using, and
|
|
471
|
+
it stops at the width of its own tags: from there down what shrinks is a
|
|
472
|
+
tag, not the column. */
|
|
473
|
+
--container-tags: 8rem;
|
|
474
|
+
|
|
475
|
+
/* What a name ASKS FOR in a list row before the rest of the line gives up
|
|
476
|
+
its place.
|
|
477
|
+
|
|
478
|
+
It is the only measure the row needs to lay itself out, and it is not a
|
|
479
|
+
breakpoint: there is no width at which a row «becomes narrow». There is a
|
|
480
|
+
name that asks for 12 rem, a metadata that asks for what it says, a tag
|
|
481
|
+
column that asks for `--container-tags`, and a box. When the three do not
|
|
482
|
+
fit, the metadata drops to the line below and the name keeps the first one
|
|
483
|
+
whole — it is what identifies the record, and cut down to «ve…» it
|
|
484
|
+
identifies nothing.
|
|
485
|
+
|
|
486
|
+
That it is an ask and not a minimum is what makes the row work in a box
|
|
487
|
+
narrower than this: with the metadata already below, the name uses whatever
|
|
488
|
+
there is instead of overflowing. And it is what makes a checkbox, an icon
|
|
489
|
+
or a row of actions shift the moment it wraps without anyone declaring it
|
|
490
|
+
— they take their width from the same line, and the ask stops being met
|
|
491
|
+
earlier. */
|
|
492
|
+
--container-name: 12rem;
|
|
493
|
+
|
|
494
|
+
/* The minimum width of a metric. It belongs to the system and not to the
|
|
495
|
+
component: three metrics of different widths read as three different
|
|
496
|
+
things, and what they have to let you compare is precisely the number. */
|
|
497
|
+
--container-metric: 7rem;
|
|
498
|
+
|
|
499
|
+
/* The width of the peek drawer. It is a system decision and not a component
|
|
500
|
+
one: 30rem is what fits of a record without leaving the list behind it
|
|
501
|
+
—which has to stay clickable— reduced to a useless strip. */
|
|
502
|
+
--container-peek: 30rem;
|
|
503
|
+
|
|
504
|
+
/* The width of the chatter, beside a record. Narrower than the peek because it
|
|
505
|
+
is not a record: it is a column of one-line entries —«se evaluó el
|
|
506
|
+
resultado», «el proyecto pasó a generado»— and 22rem holds one of those
|
|
507
|
+
without wrapping while leaving the record the width it needs. A system
|
|
508
|
+
decision for the same reason the peek is: two records whose history column
|
|
509
|
+
is a different width read as two different screens. */
|
|
510
|
+
--container-chatter: 22rem;
|
|
511
|
+
|
|
512
|
+
/* Below this a surface is held in one hand: a phone, or a panel just as narrow. 40rem is
|
|
513
|
+
the width the list already stops offering a table at (`ListView`), and it is the same
|
|
514
|
+
line for the record's thumb bar and for a dialog or a peek that turn into a bottom
|
|
515
|
+
sheet — three rules about «narrow» drawn at three different widths would make a
|
|
516
|
+
screen that is a phone for one piece and a desk for the next. As a container it is
|
|
517
|
+
`@max-handheld/<name>:`; on the window it is Tailwind's `max-sm:`, the same 40rem. */
|
|
518
|
+
--container-handheld: 40rem;
|
|
519
|
+
|
|
520
|
+
/* How tall a bottom sheet may grow: 90 % of the visible height. The strip it leaves on
|
|
521
|
+
top is functional — it shows the screen the sheet came from, and tapping it closes a
|
|
522
|
+
dismissable one. `dvh` and not `vh`: on a phone `vh` counts the browser bar that is
|
|
523
|
+
covering the bottom of the screen, which is exactly where the sheet's actions are. */
|
|
524
|
+
--size-sheet: 90dvh;
|
|
525
|
+
|
|
526
|
+
/* The width of a module's sidebar. 15rem holds an icon, a screen name of two
|
|
527
|
+
words —«Notas de crédito», «Órdenes de compra»— a count and one step of
|
|
528
|
+
nesting without truncating, and still leaves a 1280-wide window more than
|
|
529
|
+
1,000 px of list. Narrower and the names it exists to show get cut; wider
|
|
530
|
+
and it starts taking columns from the table beside it. A system decision:
|
|
531
|
+
two modules whose sidebars differ in width read as two products. */
|
|
532
|
+
--container-sidebar: 15rem;
|
|
533
|
+
|
|
534
|
+
/* The record sheet. The rail above it spans the whole window; the sheet does not: a form
|
|
535
|
+
stretched to 1,400 px puts the label and its value half a screen apart and turns every
|
|
536
|
+
input into a bar nobody fills. 72rem holds two columns of fields at a readable width —
|
|
537
|
+
the same measure an ERP sheet (Odoo's, for one) has always had — and centers on wider
|
|
538
|
+
screens, so the sheet reads as a document on a desk. */
|
|
539
|
+
--container-sheet: 72rem;
|
|
540
|
+
|
|
541
|
+
/* Three control heights. Everything that shares a row shares a height: the
|
|
542
|
+
hierarchy is made by color and weight, not by size — a primary taller than
|
|
543
|
+
its neighbor reads as a measuring mistake, and it was one.
|
|
544
|
+
|
|
545
|
+
They went up 2px with «Aire» to accompany the type: a 34px control with
|
|
546
|
+
14px text is cramped against its own border. */
|
|
547
|
+
--size-control-sm: 30px;
|
|
548
|
+
--size-control-md: 36px;
|
|
549
|
+
--size-control-lg: 42px;
|
|
550
|
+
|
|
551
|
+
--size-icon-sm: 12px;
|
|
552
|
+
--size-icon-md: 16px;
|
|
553
|
+
--size-icon-lg: 18px;
|
|
554
|
+
|
|
555
|
+
/* The thickness of a progress track. A hairline, not a bar: what informs is
|
|
556
|
+
the PROGRESS, and a thick bar competes with the content that is waiting. It
|
|
557
|
+
belongs to the system and not to the component — two progress bars of
|
|
558
|
+
different thickness on the same screen read as two different things. */
|
|
559
|
+
--size-track: 6px;
|
|
560
|
+
|
|
561
|
+
/* ---------------------------------------------------------------- *
|
|
562
|
+
* MOTION — three durations, and none of them decorates.
|
|
563
|
+
*
|
|
564
|
+
* There is a single rule: **an animation exists to explain where something
|
|
565
|
+
* came from, never to decorate.** A panel that enters from the right says it
|
|
566
|
+
* came from there and that it goes back there. A color that changes in 90ms
|
|
567
|
+
* says the control responded. Everything else —a counter that ticks up, a
|
|
568
|
+
* card that bounces— is time the user waits without receiving information.
|
|
569
|
+
*
|
|
570
|
+
* instant 90ms the control acknowledged: hover, focus, color
|
|
571
|
+
* quick 160ms something appeared where it was not: menu, notice, tag
|
|
572
|
+
* panel 240ms a whole surface moved: the peek drawer
|
|
573
|
+
*
|
|
574
|
+
* Above ~250ms a working interface starts to feel slow, and this is used
|
|
575
|
+
* eight hours straight. The ceiling is deliberate.
|
|
576
|
+
*
|
|
577
|
+
* There are two curves and they are split by direction: what ENTERS
|
|
578
|
+
* decelerates (it arrives and it stays), what moves between two states is
|
|
579
|
+
* symmetrical.
|
|
580
|
+
* ---------------------------------------------------------------- */
|
|
581
|
+
--duration-instant: 90ms;
|
|
582
|
+
--duration-quick: 160ms;
|
|
583
|
+
--duration-panel: 240ms;
|
|
584
|
+
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
|
|
585
|
+
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/* ==================================================================== *
|
|
589
|
+
* DARK THEME
|
|
590
|
+
*
|
|
591
|
+
* The same tokens, redefined — components keep using the same utilities and
|
|
592
|
+
* do not know which theme they are in. Two entry points, one palette:
|
|
593
|
+
*
|
|
594
|
+
* · `data-theme="dark"` on any ancestor forces it;
|
|
595
|
+
* · with no `data-theme`, `prefers-color-scheme: dark` selects it, and
|
|
596
|
+
* `data-theme="light"` (on the root or on any subtree) is the way to opt out.
|
|
597
|
+
*
|
|
598
|
+
* The palette is written twice because CSS has no way to share a block between
|
|
599
|
+
* a media query and an attribute selector; `tests/tokens.test.ts` fails if the
|
|
600
|
+
* two copies drift apart, and measures every number below against the DARK
|
|
601
|
+
* ground, surface and chrome — same thresholds as the light theme.
|
|
602
|
+
*
|
|
603
|
+
* What changes beyond colour: elevation. On dark a lighter surface is what
|
|
604
|
+
* lifts, and a shadow is a deeper black rather than a warm tint. The chrome
|
|
605
|
+
* gets a hairline (`chrome-edge`) because it and the canvas are both near
|
|
606
|
+
* black. The gold keeps being the primary action; `on-accent` keeps the text
|
|
607
|
+
* on it dark.
|
|
608
|
+
* ==================================================================== */
|
|
609
|
+
:root {
|
|
610
|
+
color-scheme: light dark;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
@media (prefers-color-scheme: dark) {
|
|
614
|
+
:root:not([data-theme="light"]) {
|
|
615
|
+
color-scheme: dark;
|
|
616
|
+
--color-ground: #181614; /* the canvas: warm near-black, not #000 */
|
|
617
|
+
--color-surface: #221f1c; /* 1.10:1 above the ground — in the dark, raised is LIGHTER */
|
|
618
|
+
--color-line: #302c28; /* 1.30:1 — common separator */
|
|
619
|
+
--color-line-strong: #443f38; /* 1.73:1 — border of a box, table header */
|
|
620
|
+
--color-ink: #ece8e1; /* 14.78:1 */
|
|
621
|
+
--color-muted: #a39b90; /* 6.57:1 */
|
|
622
|
+
--color-accent: #c89b3c; /* 7.05:1 — on dark the gold CAN be read, but it stays the primary action */
|
|
623
|
+
--color-accent-hover: #dbb055; /* on dark the hover LIGHTENS: on-accent on top gives 8.80:1 */
|
|
624
|
+
--color-on-accent: #1a1714; /* 6.98:1 on the gold */
|
|
625
|
+
--color-data: #8ea6d9; /* 7.40:1 */
|
|
626
|
+
--color-primary: #8fa9e8; /* 7.74:1 · surface on top 7.03:1 */
|
|
627
|
+
--color-primary-hover: #b0c3f1; /* 10.25:1 · surface on top 9.31:1 */
|
|
628
|
+
--color-danger: #f08d7f; /* 7.58:1 · surface on top 6.89:1 */
|
|
629
|
+
--color-danger-hover: #f6ab9f; /* 9.66:1 · surface on top 8.78:1 */
|
|
630
|
+
--color-info: #5cc4d8; /* 8.89:1 */
|
|
631
|
+
--color-data-soft: #6f88bd; /* 5.10:1 */
|
|
632
|
+
--color-good: #6fcf8b; /* 9.43:1 */
|
|
633
|
+
--color-warn: #e3b341; /* 9.27:1 */
|
|
634
|
+
--color-bad: #f08d7f; /* 7.58:1 */
|
|
635
|
+
--color-chrome: #0b101e; /* the base: deeper than the canvas */
|
|
636
|
+
--color-chrome-raised: #18223a; /* active tab, open menu */
|
|
637
|
+
--color-chrome-edge: #2b3656; /* 1.59:1 on the chrome — the hairline that separates the bar from the canvas */
|
|
638
|
+
--color-chrome-ink: #e8e4de; /* 14.98:1 — text */
|
|
639
|
+
--color-chrome-muted: #a39b90; /* 6.91:1 — faint text */
|
|
640
|
+
--color-chrome-accent: #c89b3c; /* 7.42:1 — the gold, here it IS text */
|
|
641
|
+
--color-chrome-link: #a9c1f2; /* 10.48:1 */
|
|
642
|
+
--color-chrome-good: #6fcf8b; /* 9.91:1 */
|
|
643
|
+
--color-chrome-warn: #e3b341; /* 9.75:1 */
|
|
644
|
+
--color-chrome-bad: #ff8a7a; /* 8.28:1 */
|
|
645
|
+
--color-scrim: #000000; /* 1.16:1 on the ground — a veil, never text */
|
|
646
|
+
--shadow-raised: 0 1px 2px -1px rgba(0, 0, 0, 0.5);
|
|
647
|
+
--shadow-float: 0 8px 24px -12px rgba(0, 0, 0, 0.75);
|
|
648
|
+
--shadow-window:
|
|
649
|
+
0 2px 6px -2px rgba(0, 0, 0, 0.55), 0 18px 40px -20px rgba(0, 0, 0, 0.85);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
[data-theme="dark"] {
|
|
654
|
+
color-scheme: dark;
|
|
655
|
+
--color-ground: #181614; /* the canvas: warm near-black, not #000 */
|
|
656
|
+
--color-surface: #221f1c; /* 1.10:1 above the ground — in the dark, raised is LIGHTER */
|
|
657
|
+
--color-line: #302c28; /* 1.30:1 — common separator */
|
|
658
|
+
--color-line-strong: #443f38; /* 1.73:1 — border of a box, table header */
|
|
659
|
+
--color-ink: #ece8e1; /* 14.78:1 */
|
|
660
|
+
--color-muted: #a39b90; /* 6.57:1 */
|
|
661
|
+
--color-accent: #c89b3c; /* 7.05:1 — on dark the gold CAN be read, but it stays the primary action */
|
|
662
|
+
--color-accent-hover: #dbb055; /* on dark the hover LIGHTENS: on-accent on top gives 8.80:1 */
|
|
663
|
+
--color-on-accent: #1a1714; /* 6.98:1 on the gold */
|
|
664
|
+
--color-data: #8ea6d9; /* 7.40:1 */
|
|
665
|
+
--color-primary: #8fa9e8; /* 7.74:1 · surface on top 7.03:1 */
|
|
666
|
+
--color-primary-hover: #b0c3f1; /* 10.25:1 · surface on top 9.31:1 */
|
|
667
|
+
--color-danger: #f08d7f; /* 7.58:1 · surface on top 6.89:1 */
|
|
668
|
+
--color-danger-hover: #f6ab9f; /* 9.66:1 · surface on top 8.78:1 */
|
|
669
|
+
--color-info: #5cc4d8; /* 8.89:1 */
|
|
670
|
+
--color-data-soft: #6f88bd; /* 5.10:1 */
|
|
671
|
+
--color-good: #6fcf8b; /* 9.43:1 */
|
|
672
|
+
--color-warn: #e3b341; /* 9.27:1 */
|
|
673
|
+
--color-bad: #f08d7f; /* 7.58:1 */
|
|
674
|
+
--color-chrome: #0b101e; /* the base: deeper than the canvas */
|
|
675
|
+
--color-chrome-raised: #18223a; /* active tab, open menu */
|
|
676
|
+
--color-chrome-edge: #2b3656; /* 1.59:1 on the chrome — the hairline that separates the bar from the canvas */
|
|
677
|
+
--color-chrome-ink: #e8e4de; /* 14.98:1 — text */
|
|
678
|
+
--color-chrome-muted: #a39b90; /* 6.91:1 — faint text */
|
|
679
|
+
--color-chrome-accent: #c89b3c; /* 7.42:1 — the gold, here it IS text */
|
|
680
|
+
--color-chrome-link: #a9c1f2; /* 10.48:1 */
|
|
681
|
+
--color-chrome-good: #6fcf8b; /* 9.91:1 */
|
|
682
|
+
--color-chrome-warn: #e3b341; /* 9.75:1 */
|
|
683
|
+
--color-chrome-bad: #ff8a7a; /* 8.28:1 */
|
|
684
|
+
--color-scrim: #000000; /* 1.16:1 on the ground — a veil, never text */
|
|
685
|
+
--shadow-raised: 0 1px 2px -1px rgba(0, 0, 0, 0.5);
|
|
686
|
+
--shadow-float: 0 8px 24px -12px rgba(0, 0, 0, 0.75);
|
|
687
|
+
--shadow-window: 0 2px 6px -2px rgba(0, 0, 0, 0.55), 0 18px 40px -20px rgba(0, 0, 0, 0.85);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/* The light palette again, for a SUBTREE forced to light inside a dark page — a printed
|
|
691
|
+
preview of an invoice, an email template. On the root it restates what `@theme` already
|
|
692
|
+
says; its reason to exist is nesting, which CSS cannot do from the `:root` copy alone.
|
|
693
|
+
`tests/tokens.test.ts` keeps it equal to `@theme`. */
|
|
694
|
+
[data-theme="light"] {
|
|
695
|
+
color-scheme: light;
|
|
696
|
+
--color-ground: #faf9f7;
|
|
697
|
+
--color-surface: #ffffff;
|
|
698
|
+
--color-line: #e4dfd7; /* 1.26:1 — common separator */
|
|
699
|
+
--color-line-strong: #d5cec2; /* 1.49:1 — border of a box, table header */
|
|
700
|
+
--color-ink: #1a1714;
|
|
701
|
+
--color-muted: #6b6459;
|
|
702
|
+
--color-accent: #c89b3c; /* 2.43:1 on the ground → NEVER text */
|
|
703
|
+
--color-accent-hover: #b88a2e; /* on light the hover DARKENS: ink on top gives 5.71:1 */
|
|
704
|
+
--color-on-accent: #1a1714; /* 6.98:1 on the gold */
|
|
705
|
+
--color-data: #1b2a4a; /* 13.52:1 */
|
|
706
|
+
--color-primary: #2a4a85; /* 8.26:1 · white on top 8.69:1 */
|
|
707
|
+
--color-primary-hover: #1c3765; /* 11.19:1 · white on top 11.77:1 */
|
|
708
|
+
--color-danger: #9b3b30; /* 6.52:1 · white on top 6.86:1 */
|
|
709
|
+
--color-danger-hover: #7c2d24; /* 8.84:1 · white on top 9.30:1 */
|
|
710
|
+
--color-info: #0f5c6e; /* 7.19:1 · white on top 7.56:1 */
|
|
711
|
+
--color-data-soft: #5c74a6; /* 4.43:1 */
|
|
712
|
+
--color-good: #2f6b3c; /* 6.07:1 */
|
|
713
|
+
--color-warn: #7e5e12; /* 5.71:1 */
|
|
714
|
+
--color-bad: #9b3b30; /* 6.52:1 */
|
|
715
|
+
--color-chrome: #141b2e; /* the base */
|
|
716
|
+
--color-chrome-raised: #1e2740; /* active tab, open menu */
|
|
717
|
+
--color-chrome-edge: #141b2e; /* 1.00:1 on the chrome — invisible on light, a hairline on dark */
|
|
718
|
+
--color-chrome-ink: #e8e4de; /* 13.53:1 — text */
|
|
719
|
+
--color-chrome-muted: #a39b90; /* 6.24:1 — faint text */
|
|
720
|
+
--color-chrome-accent: #c89b3c; /* 6.70:1 — the gold, here it IS text */
|
|
721
|
+
--color-chrome-link: #a9c1f2; /* 9.46:1 · same family as the navy, separated by luminance */
|
|
722
|
+
--color-chrome-good: #6fcf8b; /* 8.95:1 */
|
|
723
|
+
--color-chrome-warn: #e3b341; /* 8.80:1 */
|
|
724
|
+
--color-chrome-bad: #ff8a7a; /* 7.48:1 */
|
|
725
|
+
--color-scrim: #1a1714; /* 16.96:1 on the ground — a veil, never text */
|
|
726
|
+
--shadow-raised: 0 1px 2px -1px rgba(26, 23, 20, 0.1);
|
|
727
|
+
--shadow-float: 0 8px 24px -12px rgba(26, 23, 20, 0.35);
|
|
728
|
+
--shadow-window:
|
|
729
|
+
0 2px 6px -2px rgba(26, 23, 20, 0.18), 0 18px 40px -20px rgba(26, 23, 20, 0.42);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
@layer base {
|
|
733
|
+
/* No forced height anywhere: the document is exactly as tall as its content.
|
|
734
|
+
* The color still has to reach the bottom of the viewport on a short page —
|
|
735
|
+
* `html` carries it because `html` always covers the viewport, with or
|
|
736
|
+
* without a height rule; `body` would need `min-height: 100%` to do the
|
|
737
|
+
* same, and that's the rule that kept re-appearing as a blank gap below a
|
|
738
|
+
* short view. One color, on the element that doesn't need a height rule to
|
|
739
|
+
* cover the screen. */
|
|
740
|
+
html {
|
|
741
|
+
background-color: var(--color-ground);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
body {
|
|
745
|
+
background-color: var(--color-ground);
|
|
746
|
+
color: var(--color-ink);
|
|
747
|
+
font-family: var(--font-sans);
|
|
748
|
+
-webkit-font-smoothing: antialiased;
|
|
749
|
+
/* Sólo horizontal. Sacar el `overflow: hidden` entero destapó algo que se
|
|
750
|
+
* desborda a los costados —un panel oculto fuera de pantalla, medido para
|
|
751
|
+
* un ancho que ya no es el suyo— y sin este freno se podía scrollear hacia
|
|
752
|
+
* la derecha para verlo. El alto sigue libre: es lo que se vino a arreglar. */
|
|
753
|
+
overflow-x: hidden;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
/* Tabular figures on everything monospaced: a column of rows has to line up
|
|
757
|
+
across rows. */
|
|
758
|
+
.font-mono {
|
|
759
|
+
font-variant-numeric: tabular-nums;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
/* Thin and warm scrollbars. With six panels open, six grey system bars are
|
|
763
|
+
six stripes that say nothing. */
|
|
764
|
+
* {
|
|
765
|
+
scrollbar-width: thin;
|
|
766
|
+
scrollbar-color: var(--color-line-strong) transparent;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/* ------------------------------------------------------------------ *
|
|
771
|
+
* THE BRAND GRADIENT — deep blue towards gold.
|
|
772
|
+
*
|
|
773
|
+
* It keeps appearing in FOUR places and nowhere else:
|
|
774
|
+
* 1. `brand-wash` — the wash behind the workspace title bar
|
|
775
|
+
* 2. `rule-brand` — the 1px rule under that bar
|
|
776
|
+
* 3. `btn-accent` — the primary action
|
|
777
|
+
* 4. `bar-brand` — the bar of an import in progress
|
|
778
|
+
*
|
|
779
|
+
* On light it goes MUCH fainter than on black. A gradient that on #0A0806 read
|
|
780
|
+
* as light, on #FAF9F7 reads as a stain: the alphas were halved and the gold
|
|
781
|
+
* almost disappears.
|
|
782
|
+
*
|
|
783
|
+
* If a fifth one is added, one has to be removed.
|
|
784
|
+
* ------------------------------------------------------------------ */
|
|
785
|
+
|
|
786
|
+
/* 1. The wash. Effective alpha of ~6% (blue) to ~4% (gold). */
|
|
787
|
+
@utility brand-wash {
|
|
788
|
+
position: relative;
|
|
789
|
+
isolation: isolate;
|
|
790
|
+
|
|
791
|
+
&::before {
|
|
792
|
+
content: "";
|
|
793
|
+
position: absolute;
|
|
794
|
+
inset: 0;
|
|
795
|
+
z-index: -1;
|
|
796
|
+
pointer-events: none;
|
|
797
|
+
background-image: linear-gradient(
|
|
798
|
+
104deg,
|
|
799
|
+
color-mix(in srgb, var(--color-data) 6%, transparent) 0%,
|
|
800
|
+
color-mix(in srgb, var(--color-data) 4%, transparent) 38%,
|
|
801
|
+
color-mix(in srgb, var(--color-accent) 4%, transparent) 74%,
|
|
802
|
+
transparent 100%
|
|
803
|
+
);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
/* 1b. The same wash, on the chrome.
|
|
808
|
+
*
|
|
809
|
+
* Context: the one above is calibrated against #FAF9F7 — the blue at 6% on the
|
|
810
|
+
* navy cannot be seen, because it is blue on blue. Here both ends are the
|
|
811
|
+
* LIGHTENED ones (the chrome's cyan and gold), and that is why the wash reads
|
|
812
|
+
* as light born out of the background itself instead of as a stain on top. */
|
|
813
|
+
@utility brand-wash-chrome {
|
|
814
|
+
position: relative;
|
|
815
|
+
isolation: isolate;
|
|
816
|
+
|
|
817
|
+
&::before {
|
|
818
|
+
content: "";
|
|
819
|
+
position: absolute;
|
|
820
|
+
inset: 0;
|
|
821
|
+
z-index: -1;
|
|
822
|
+
pointer-events: none;
|
|
823
|
+
background-image: linear-gradient(
|
|
824
|
+
104deg,
|
|
825
|
+
color-mix(in srgb, var(--color-chrome-link) 11%, transparent) 0%,
|
|
826
|
+
color-mix(in srgb, var(--color-chrome-link) 5%, transparent) 38%,
|
|
827
|
+
color-mix(in srgb, var(--color-chrome-accent) 7%, transparent) 74%,
|
|
828
|
+
transparent 100%
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
/* 2. The rule. It is the detail that sells it: 1px, blue → gold, transparent at
|
|
834
|
+
both ends so that it does not look like the border of a box. Decorative:
|
|
835
|
+
there is nothing to read in it, which is what allows it to carry the
|
|
836
|
+
gold. */
|
|
837
|
+
@utility rule-brand {
|
|
838
|
+
height: 1px;
|
|
839
|
+
border: 0;
|
|
840
|
+
background-image: linear-gradient(
|
|
841
|
+
90deg,
|
|
842
|
+
transparent 0%,
|
|
843
|
+
color-mix(in srgb, var(--color-data) 64%, transparent) 22%,
|
|
844
|
+
color-mix(in srgb, var(--color-accent) 80%, transparent) 62%,
|
|
845
|
+
transparent 100%
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
/* 3. The primary action. Gold background, `ink` text on top: 6.98:1. The
|
|
850
|
+
gradient is barely a metallic sheen — on light, a strong gradient turns a
|
|
851
|
+
tool button into a landing page button. */
|
|
852
|
+
@utility btn-accent {
|
|
853
|
+
color: var(--color-on-accent);
|
|
854
|
+
background-image: linear-gradient(
|
|
855
|
+
160deg,
|
|
856
|
+
color-mix(in oklab, var(--color-accent) 88%, white) 0%,
|
|
857
|
+
var(--color-accent) 58%,
|
|
858
|
+
var(--color-accent-hover) 100%
|
|
859
|
+
);
|
|
860
|
+
|
|
861
|
+
&:hover {
|
|
862
|
+
background-image: linear-gradient(
|
|
863
|
+
160deg,
|
|
864
|
+
var(--color-accent) 0%,
|
|
865
|
+
var(--color-accent-hover) 62%,
|
|
866
|
+
color-mix(in oklab, var(--color-accent-hover) 88%, black) 100%
|
|
867
|
+
);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
/* 4. The bar of an import: the only place where the brand and motion cross paths.
|
|
872
|
+
Blue at the start, gold at the tip. The stops are explicit on purpose:
|
|
873
|
+
letting the browser interpolate blue against gold gives a muddy grey
|
|
874
|
+
midpoint. */
|
|
875
|
+
@utility bar-brand {
|
|
876
|
+
background-image: linear-gradient(
|
|
877
|
+
90deg,
|
|
878
|
+
var(--color-data) 0%,
|
|
879
|
+
var(--color-data) 46%,
|
|
880
|
+
var(--color-accent) 100%
|
|
881
|
+
);
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
/* ------------------------------------------------------------------ *
|
|
885
|
+
* Horizontal scroll hint.
|
|
886
|
+
*
|
|
887
|
+
* It is NOT a fifth use of the gradient: it carries neither blue nor gold. It
|
|
888
|
+
* is the ground itself fading out over the edge of a box that scrolls, so that
|
|
889
|
+
* it can be seen that there is something beyond. With 300px-wide panels, a
|
|
890
|
+
* twelve-column table hides nine — and a hidden column with no warning is
|
|
891
|
+
* worse than one cut off in plain sight.
|
|
892
|
+
*
|
|
893
|
+
* Two `local` layers that travel with the content cover two `scroll` layers
|
|
894
|
+
* that stay fixed: the shadow on one side is only visible when something was
|
|
895
|
+
* left on that side. It is the only way to ask «is there overflow?» from CSS.
|
|
896
|
+
*
|
|
897
|
+
* On light the shadow is DARK again — on the black theme it had to be inverted
|
|
898
|
+
* and use an edge the color of the text.
|
|
899
|
+
* ------------------------------------------------------------------ */
|
|
900
|
+
@utility scroll-hint {
|
|
901
|
+
background-image:
|
|
902
|
+
linear-gradient(to right, var(--color-surface) 40%, transparent),
|
|
903
|
+
linear-gradient(to left, var(--color-surface) 40%, transparent),
|
|
904
|
+
linear-gradient(
|
|
905
|
+
to right,
|
|
906
|
+
color-mix(in srgb, var(--color-scrim) 14%, transparent),
|
|
907
|
+
transparent
|
|
908
|
+
),
|
|
909
|
+
linear-gradient(
|
|
910
|
+
to left,
|
|
911
|
+
color-mix(in srgb, var(--color-scrim) 14%, transparent),
|
|
912
|
+
transparent
|
|
913
|
+
);
|
|
914
|
+
background-position:
|
|
915
|
+
0 0,
|
|
916
|
+
100% 0,
|
|
917
|
+
0 0,
|
|
918
|
+
100% 0;
|
|
919
|
+
background-repeat: no-repeat;
|
|
920
|
+
background-size:
|
|
921
|
+
36px 100%,
|
|
922
|
+
36px 100%,
|
|
923
|
+
10px 100%,
|
|
924
|
+
10px 100%;
|
|
925
|
+
background-attachment: local, local, scroll, scroll;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
/* ------------------------------------------------------------------ *
|
|
929
|
+
* WHAT DOES NOT LIVE HERE: THE WINDOW MANAGER THEME.
|
|
930
|
+
*
|
|
931
|
+
* `dockview` and its `.dockview-theme-sincpro` class stayed in the
|
|
932
|
+
* application, and it is the same rule that separates this project from
|
|
933
|
+
* `frontend/`: a theme for a concrete panel library is not a design token —
|
|
934
|
+
* it is the infrastructure of ONE application. The day the window manager is
|
|
935
|
+
* swapped, this file does not find out.
|
|
936
|
+
*
|
|
937
|
+
* What does come out of here are the variables that theme consumes
|
|
938
|
+
* (`--color-surface`, `--color-line`, the radius scale…), which is exactly the
|
|
939
|
+
* right direction: the app reads the system, the system does not know the app.
|
|
940
|
+
* ------------------------------------------------------------------ */
|
|
941
|
+
|
|
942
|
+
/* ------------------------------------------------------------------ *
|
|
943
|
+
* REDUCED MOTION
|
|
944
|
+
*
|
|
945
|
+
* It goes here and not in each component because an accessibility rule spread
|
|
946
|
+
* across forty files is honored in thirty. If the system declares durations,
|
|
947
|
+
* the system declares how they are turned off.
|
|
948
|
+
*
|
|
949
|
+
* It is not turned off to zero: 1ms keeps `transitionend` alive, which is what
|
|
950
|
+
* the components that unmount something when the animation finishes hang
|
|
951
|
+
* from. At zero that event does not fire in some browsers and the menu never
|
|
952
|
+
* closes.
|
|
953
|
+
* ------------------------------------------------------------------ */
|
|
954
|
+
@media (prefers-reduced-motion: reduce) {
|
|
955
|
+
*,
|
|
956
|
+
*::before,
|
|
957
|
+
*::after {
|
|
958
|
+
animation-duration: 1ms !important;
|
|
959
|
+
animation-iteration-count: 1 !important;
|
|
960
|
+
transition-duration: 1ms !important;
|
|
961
|
+
scroll-behavior: auto !important;
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
/* ------------------------------------------------------------------ *
|
|
966
|
+
* THE WIDTH OF THE PEEK DRAWER
|
|
967
|
+
*
|
|
968
|
+
* It is not a fixed width: it is 30rem, unless the window is so narrow that it
|
|
969
|
+
* would cover everything. The clamp always leaves 2rem of what is behind in
|
|
970
|
+
* view, and that margin is functional, not aesthetic — **a peek that covers
|
|
971
|
+
* 100% is a dialog without a scrim**, and then it is not understood that what
|
|
972
|
+
* is behind can still be used.
|
|
973
|
+
* ------------------------------------------------------------------ */
|
|
974
|
+
@utility peek-width {
|
|
975
|
+
width: min(var(--container-peek), calc(100% - 2rem));
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
/* ------------------------------------------------------------------ *
|
|
979
|
+
* THE BOTTOM EDGE OF A PHONE
|
|
980
|
+
*
|
|
981
|
+
* What sits on the bottom edge of the screen —a sheet's footer, the record's thumb
|
|
982
|
+
* bar— keeps its usual 0.75rem below the buttons, unless the phone has a home
|
|
983
|
+
* indicator there, and then it keeps that. A button under the indicator is a button
|
|
984
|
+
* whose tap the system takes for a swipe home. It needs `viewport-fit=cover` in the
|
|
985
|
+
* page to measure anything; without it the inset is 0 and this is plain `pb-3`.
|
|
986
|
+
* ------------------------------------------------------------------ */
|
|
987
|
+
@utility safe-bottom {
|
|
988
|
+
padding-bottom: max(0.75rem, env(safe-area-inset-bottom));
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
/* A field with its label beside it: the label column is `--container-label`. When the
|
|
992
|
+
container it sits in is too narrow to give the control at least as much room as the
|
|
993
|
+
label, the label goes on top — a 10rem label next to a zero-width input is what a
|
|
994
|
+
two-column form looked like on a phone. */
|
|
995
|
+
@utility grid-label {
|
|
996
|
+
grid-template-columns: var(--container-label) minmax(0, 1fr);
|
|
997
|
+
& > [data-under-control] {
|
|
998
|
+
grid-column-start: 2;
|
|
999
|
+
}
|
|
1000
|
+
@container (width < 21rem) {
|
|
1001
|
+
grid-template-columns: minmax(0, 1fr);
|
|
1002
|
+
& > [data-under-control] {
|
|
1003
|
+
grid-column-start: auto;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
/* ==================================================================== *
|
|
1009
|
+
* TOUCH — the same controls, sized for a finger.
|
|
1010
|
+
*
|
|
1011
|
+
* The three control heights above are for a mouse: 30/36/42 px keep an ERP
|
|
1012
|
+
* dense, forty rows on a screen. A finger needs more — 44 pt is Apple's
|
|
1013
|
+
* minimum, 48 dp Material's — and a 30 px target on a phone is a tap that
|
|
1014
|
+
* lands on the row below. Only the POINTER changes the size, not the width:
|
|
1015
|
+
* a desktop browser narrowed to 400 px keeps the mouse sizes, a tablet in
|
|
1016
|
+
* landscape gets the finger ones. Every component reads these tokens, so
|
|
1017
|
+
* nothing else has to know about touch.
|
|
1018
|
+
* ==================================================================== */
|
|
1019
|
+
@media (pointer: coarse) {
|
|
1020
|
+
:root {
|
|
1021
|
+
--size-control-sm: 44px;
|
|
1022
|
+
--size-control-md: 44px;
|
|
1023
|
+
--size-control-lg: 48px;
|
|
1024
|
+
}
|
|
1025
|
+
}
|