@scattered-light/loomiere 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/README.md +60 -2
- package/bin/loomiere.mjs +112 -0
- package/build/build-tokens.mjs +485 -0
- package/exporters/README.md +45 -0
- package/exporters/ag-grid-settings.mjs +177 -0
- package/exporters/ag-grid.mjs +112 -0
- package/exporters/chakra.mjs +630 -0
- package/exporters/component-styles.mjs +207 -0
- package/exporters/dtcg.mjs +31 -0
- package/exporters/figma.mjs +100 -0
- package/exporters/index.mjs +38 -0
- package/exporters/mui.mjs +666 -0
- package/exporters/tailwind.mjs +35 -0
- package/exporters/theme.mjs +93 -0
- package/generator/brand.mjs +328 -0
- package/generator/brand.schema.json +657 -0
- package/generator/color.mjs +130 -0
- package/generator/data-colors.mjs +104 -0
- package/generator/format-brand.mjs +60 -0
- package/generator/generate.mjs +43 -0
- package/generator/pairs.mjs +236 -0
- package/generator/roles.mjs +258 -0
- package/generator/vision.mjs +167 -0
- package/lib/config.mjs +71 -0
- package/lib/contrast.mjs +134 -0
- package/lib/lab.mjs +108 -0
- package/lib/studio.mjs +127 -0
- package/lib/tool-server.mjs +96 -0
- package/package.json +51 -4
- package/templates/brand.json +104 -0
- package/tokens/README.md +219 -0
- package/tokens/component/avatar.json +10 -0
- package/tokens/component/button.json +12 -0
- package/tokens/component/drawer.json +26 -0
- package/tokens/component/navigation-menu.json +13 -0
- package/tokens/component/popover.json +13 -0
- package/tokens/component/preview-card.json +13 -0
- package/tokens/component/slider.json +13 -0
- package/tokens/component/switch.json +41 -0
- package/tokens/component/toast.json +21 -0
- package/tokens/component/toggle.json +9 -0
- package/tokens/component/toolbar.json +9 -0
- package/tokens/component/tooltip.json +9 -0
- package/tokens/foundation/layering.json +57 -0
- package/tokens/foundation/layout.json +75 -0
- package/tokens/foundation/motion.json +41 -0
- package/tokens/foundation/type.json +92 -0
- package/tokens/semantic/accessibility.json +39 -0
- package/tokens/semantic/bar.json +45 -0
- package/tokens/semantic/check.json +57 -0
- package/tokens/semantic/color.json +270 -0
- package/tokens/semantic/content.json +124 -0
- package/tokens/semantic/disclosure.json +49 -0
- package/tokens/semantic/elevation.json +72 -0
- package/tokens/semantic/field.json +77 -0
- package/tokens/semantic/focus.json +36 -0
- package/tokens/semantic/gauge.json +45 -0
- package/tokens/semantic/icon-button.json +33 -0
- package/tokens/semantic/icon.json +30 -0
- package/tokens/semantic/item.json +65 -0
- package/tokens/semantic/modal.json +86 -0
- package/tokens/semantic/motion.json +20 -0
- package/tokens/semantic/notice.json +37 -0
- package/tokens/semantic/popup.json +89 -0
- package/tokens/semantic/scheme.json +11 -0
- package/tokens/semantic/shape.json +54 -0
- package/tokens/semantic/spacing.json +110 -0
- package/tokens/semantic/table.json +73 -0
- package/tokens/semantic/typography.json +312 -0
- package/tools/brand-builder/builder.js +1119 -0
- package/tools/brand-builder/index.html +329 -0
- package/tools/pair-lab/index.html +119 -0
- package/tools/pair-lab/lab.js +249 -0
package/tokens/README.md
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# Tokens
|
|
2
|
+
|
|
3
|
+
The source of truth for the theme. `loomiere build` turns these files and a project's brand file into CSS custom properties, JS and JSON in the project's theme folder (`theme/` unless `out` in `loomiere.config.json` says otherwise).
|
|
4
|
+
|
|
5
|
+
| Layer | Folder | What it holds | Edited by |
|
|
6
|
+
|---|---|---|---|
|
|
7
|
+
| Foundation | `foundation/` | Universal rules: z-index order, breakpoints, containers, line lengths, font weights, letter spacing, easing | Rarely, by hand |
|
|
8
|
+
| Brand | the project's `brand/` | The inputs that define a theme (`<brand>.json`) plus optional exact pins (`<brand>.overrides.json`). Not in this folder: each project keeps its own | Per brand |
|
|
9
|
+
| Primitive | `<theme>/generated/tokens/primitive/` | Ladders generated from the brand: color, space, type, radius, stroke, shadow, duration | **Generated. Don't edit.** |
|
|
10
|
+
| Semantic | `semantic/` | Meaning: surfaces, text, borders, fills, status, control sizes, type tiers. Light and dark modes live here. `pair.json` holds the brand pairs; `scope.json` and `emphasis.json` hold the [roles](#roles-chrome-accent-and-the-emphasis-ladder) | By hand, shared across brands (`pair.json`, `scope.json` and `emphasis.json` are **generated** from the brand into `<theme>/generated/tokens/semantic/`) |
|
|
11
|
+
| Component | `component/` | Per component: `button.solid.bg`, `input.height` | By hand, shared across brands |
|
|
12
|
+
|
|
13
|
+
Rules:
|
|
14
|
+
|
|
15
|
+
- Each layer references only the layer below it, plus Foundation.
|
|
16
|
+
- Only Foundation and Brand contain raw values.
|
|
17
|
+
- Every semantic and component token has a `$description` that says **why** it exists and when to use it.
|
|
18
|
+
- Component colors point at semantic tokens, not palette steps. That is what lets dark mode and [brand pairs](#brand-pairs) reach them. A few still point at a palette step (an avatar's fallback fill, focus glows); they keep their color inside a pair.
|
|
19
|
+
|
|
20
|
+
## Modes
|
|
21
|
+
|
|
22
|
+
Light, dark and high-contrast colors are decided in one place: the slot table in `generator/roles.mjs`, which the generator writes into `tokens/semantic/scope.json` with each mode in `$extensions["sl.modes"]`. Semantic color tokens point at a slot (`text.primary` → `scope.chrome.fg`), and content tokens point at semantic tokens, so neither carries modes of its own and they follow the slot. The build turns each mode into its own block:
|
|
23
|
+
|
|
24
|
+
| Mode | When it applies |
|
|
25
|
+
|---|---|
|
|
26
|
+
| `dark` | `prefers-color-scheme: dark` (unless `data-theme="light"`), or `data-theme="dark"` |
|
|
27
|
+
| `contrast-more` | `prefers-contrast: more` |
|
|
28
|
+
| `dark-contrast-more` | Both of the above |
|
|
29
|
+
| `reduced-motion` | `prefers-reduced-motion: reduce` (shadows flatten to outlines) |
|
|
30
|
+
|
|
31
|
+
Palettes never change between modes. Dark mode picks different palette steps, so `--sl-color-neutral-100` is the same color everywhere and `--sl-scope-neutral-overlay` (and through it `--sl-surface-overlay`) is what flips. Use semantic tokens in app code. Set `data-theme` on the root element: the variables that point at slots resolve there.
|
|
32
|
+
|
|
33
|
+
### Color vision themes
|
|
34
|
+
|
|
35
|
+
There's no media query for color blindness, so these are opt-in, like a theme switch: set `data-vision` on the root element to `deuteranopia` (green-blind, the most common), `protanopia` (red-blind), `tritanopia` (blue-blind) or `achromatopsia` (no color vision, which also suits grayscale screens). Each re-declares the spot palettes (`--sl-color-primary-*` through `quaternary` and `success`, `warning`, `error`, `info`, with their on-colors) and the data colors (`--sl-color-data-*`) with versions generated for that kind of color vision; everything that reads them follows, in light and dark. They work with any mode. Set the attribute on the root element only: variables declared at `:root` resolve there.
|
|
36
|
+
|
|
37
|
+
The generator (`generator/vision.mjs`) designs each theme from the brand's own alert and data colors every build, so any brand file gets its own four themes. Nothing is a fixed, shared palette:
|
|
38
|
+
|
|
39
|
+
- **Palettes.** Every spot palette (brand colors and alerts; the neutral stays) moves until colors that look different with typical vision don't blur together with that vision. Colors that look alike to everyone, like an ember brand color and an error red, aren't pushed apart. For the three color blindnesses palettes turn hue (alerts at most 40°, so a green success never turns yellow; brand colors cost four times as much to move, since they carry identity). For achromatopsia, where hue means nothing, they darken instead (never lighten, so none loses contrast). Each ramp is rebuilt on the same contrast table, so every step keeps its contrast, and the theme re-declares every step and on-color.
|
|
40
|
+
- **Data colors** are nudged, in series order, until each is at least ΔE 15 from the two before and after it as seen with that vision. None loses contrast on paper.
|
|
41
|
+
- **Pins.** `color.vision.deuteranopia.keys.primary = "#3a5bd9"` fixes the color that stands for a palette (its most colorful key; the whole ramp moves with it), and `color.vision.deuteranopia.data.coral = "#d55e00"` fixes one data color. The others move to make room. The brand builder's **Color vision** section shows the brand and each theme as cards; open a theme to see every color as designed and as seen, and pin any of them.
|
|
42
|
+
|
|
43
|
+
For low vision, the `contrast-more` modes above already raise contrast when the system asks for it.
|
|
44
|
+
|
|
45
|
+
## Roles: chrome, accent and the emphasis ladder
|
|
46
|
+
|
|
47
|
+
Semantic and component tokens never name a brand color. They say `color.chrome.*` (surfaces, text, borders) or `color.accent.*` (links, focus, the loudest actions), and the brand file's `roles` decides which palettes those are:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
"roles": {
|
|
51
|
+
"chrome": "neutral",
|
|
52
|
+
"accent": "primary",
|
|
53
|
+
"emphasis": {
|
|
54
|
+
"bold": { "style": "solid", "color": "accent" },
|
|
55
|
+
"standard": { "style": "outline", "color": "accent" },
|
|
56
|
+
"subtle": { "style": "soft", "color": "chrome" },
|
|
57
|
+
"quiet": { "style": "outline", "color": "chrome" }
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The accent can be any brand color; a red brand might keep red for errors and pick a calmer accent. Leave `roles` out to get the values above. The brand builder's **Roles** section edits them with a light and dark preview.
|
|
63
|
+
|
|
64
|
+
**Scopes.** Every palette gets the same slots at fixed steps (`tokens/semantic/scope.json`, generated): `solid`, `solid-hover`, `solid-active`, `on-solid`, `soft` (and hover, active, strong), `on-soft`, `border`, `border-subtle`, `fg`, `focus`. Spot palettes add `ink` (tinted text) and `signal` (status icons and bars). Chrome palettes add the page concepts: `canvas`, `raised`, `overlay`, `inverse`, `fg-secondary`, `fg-tertiary`, `divider`, `border-strong`, `border-hover`; some of these also change under high contrast.
|
|
65
|
+
|
|
66
|
+
**Concept picks.** `roles.concepts` in the brand file replaces a role's default step for one concept, in light, dark or both: `"chrome.fg": { "light": "primary.1000" }` sets body text to the brand's text color without touching any palette. A pick is `palette.step`, `white` (paper), `black` (ink) or a hex. The Concepts section of `loomiere studio` shows every concept in light and dark with its contrast and what uses it, and Reset drops a pick. Spot palettes share a contrast table, so `solid` has the same contrast in every hue; that is why any palette can stand in for another. `--sl-scope-<slot>` is the scope in effect: the accent by default, any palette under `data-color="secondary"` (or `error`, `chrome`…).
|
|
67
|
+
|
|
68
|
+
**Emphasis.** Styles are `solid`, `soft`, `outline` and `ghost`. `data-emphasis="subtle"` sets `--sl-e-bg`, `-bg-hover`, `-bg-active`, `-fg`, `-border` and `-border-hover` from the level's style. Accent levels follow the scope around them, so a `data-color` on a section re-colors them; chrome and named-palette levels stay put unless the element sets its own `data-color`. In React: `<Button emphasis="bold">`, `<Button emphasis="subtle" palette="error">`. Any component can read `--sl-e-*` the same way.
|
|
69
|
+
|
|
70
|
+
## Exports for other frameworks
|
|
71
|
+
|
|
72
|
+
The universal outputs are the CSS variables (`tokens.css`, works with any framework or none) and **DTCG JSON** (`<theme>/exports/tokens.dtcg.json`, the W3C design tokens format that Figma importers, Tokens Studio, Style Dictionary and Terrazzo read). Frameworks that want their own theme object get an adapter from the catalog in [`exporters/`](../exporters/README.md), built on every `loomiere build` into `<theme>/exports/`:
|
|
73
|
+
|
|
74
|
+
| Export | File | Use |
|
|
75
|
+
|---|---|---|
|
|
76
|
+
| DTCG | `tokens.dtcg.json` | Any design-token tool |
|
|
77
|
+
| Tailwind CSS v4 | `tailwind.css` | `@import` after `tokens.css`; points at the `--sl-` variables, so modes keep working |
|
|
78
|
+
| MUI | `mui.js` | `createTheme(options)`, light and dark color schemes |
|
|
79
|
+
| Chakra UI v3 | `chakra.js` | `createSystem(defaultConfig, config)`; every palette, plus `accent` and `chrome`, is a `colorPalette` |
|
|
80
|
+
|
|
81
|
+
## How the color math works
|
|
82
|
+
|
|
83
|
+
- **Paper and ink** (`color.paper`, `color.ink`) are the lightest and darkest colors. They become `color.white` and `color.black` and the ends of every ramp. Change paper for an off-white brand; contrast is always measured against it. A palette can end somewhere other than ink with `darkest` (Mojave's primary ends at the brand's plum black).
|
|
84
|
+
- **Contrast tables** (`color.contrast`) set each step's contrast against paper. Every palette on the same table has the same contrast at the same step, so 500 is 4.5:1 (AA) everywhere. `locked` steps keep their target no matter what.
|
|
85
|
+
- **Keys** are brand colors the ramp passes through, as in Leonardo. With `exact: true`, each key also appears as-is on the step nearest its contrast.
|
|
86
|
+
- **Tone** tints a ramp like a transparent color over a grayscale. `intensity: [0.1, 0.6]` means 10% in the lights rising to 60% in the darks on a log curve. Neutrals use a tone plus exact keys, so the gray bends through the brand's own light and dark neutrals.
|
|
87
|
+
- **On colors.** Every step gets an `on` color (`color.primary.on.500`): text that passes AA on it, from its own palette when possible, otherwise paper or ink. `<theme>/pairs.json` lists every palette color against every other with its WCAG level, and `loomiere check` checks the semantic text and surface pairs in each mode.
|
|
88
|
+
|
|
89
|
+
## Text styles and utilities
|
|
90
|
+
|
|
91
|
+
Each text style is one `typography` token in `semantic/typography.json`, named by tier and use: `base`, `base-label`, `condense-subtitle-label`, `maximize-2xl`. A style can be another style (`"base-link": "{font.base}"`) when only color sets it apart. The build expands every style into the CSS variables components use, `--sl-font-<style>-font` (shorthand), `-font-family`, `-font-size`, `-font-weight`, `-line-height`, `-letter-spacing` and, for uppercase styles, `-text-transform`.
|
|
92
|
+
|
|
93
|
+
`loomiere build` also writes the utility classes into `<theme>/utilities.css`, so they always match the tokens: `.text-style-<style>` for every text style, `.icon-<tier>` for every icon size, `.focus-ring` and `.focus-ring-inset`, and `.hide-on-mobile|tablet|desktop` from the breakpoints.
|
|
94
|
+
|
|
95
|
+
Icons have one size per text tier in `semantic/icon.json` (`icon-size.minimize` 12px through `icon-size.maximize` 24px). An icon takes the tier of the text beside it. Buttons are the exception: their icon fills the button line, one tier up from the label.
|
|
96
|
+
|
|
97
|
+
Keyboard focus is one ring, `focus.outline`, drawn outside (`focus.outline-offset`) or, for controls packed into a container, inset (`focus.outline-offset-inset`). Outside rings get a soft glow beyond them (`focus.halo-spread`, `focus.halo-color`): the ring carries the 3:1 contrast, so the glow can stay pale. Use the `focus-ring` pattern or the `.focus-ring` class.
|
|
98
|
+
|
|
99
|
+
Reduced motion is handled once, in `reset-accessibility.css`: every transition and animation finishes at once, so components only add rules that remove movement (a transform), never their own `transition: none`.
|
|
100
|
+
|
|
101
|
+
## Shared component concepts and CSS patterns
|
|
102
|
+
|
|
103
|
+
Some looks belong to a family of components, not to one. Those are concepts in the semantic layer, so the family stays consistent without one component borrowing another's tokens:
|
|
104
|
+
|
|
105
|
+
- `field.*` (`semantic/field.json`): anything you type into or pick a value from. Input, Field, Select, Combobox, Autocomplete and Number field point here. Sizes stay in `control.*`.
|
|
106
|
+
- `popup.*` (`semantic/popup.json`): the floating layer of menus, listboxes, popovers and preview cards. `popup.radius-field` is for lists that drop from a field. Arrows take `popup.arrow-*`, so an arrow always matches its popup in both themes. `popup.min-width` is the narrowest a menu or popover gets. Context menu, Menu and Menubar have no component tokens; they are built from `popup.*` and `item.*`.
|
|
107
|
+
- `modal.*` (`semantic/modal.json`): dialogs, alert dialogs and drawers. One surface (`surface.modal`, a Concepts row: paper in light, a step above the page in dark), scrim, heading, spacing and motion. Dialog and Alert dialog have no component tokens; Drawer keeps only its size and grab bar.
|
|
108
|
+
- `bar.*` (`semantic/bar.json`): the strip behind a row of controls (toolbars, toggle groups), on `surface.tray` (a Concepts row). Toggle group has no component tokens; Toolbar keeps only its field width.
|
|
109
|
+
- `disclosure.*` (`semantic/disclosure.json`): controls that show and hide a panel (Accordion, Collapsible, Tabs): hover and press fills, chevron, divider, selected tab line and motion. None of the three has component tokens.
|
|
110
|
+
- Tooltip, Preview card and Navigation menu are built from existing concepts. Tooltip uses `surface.inverse` with `text.on-inverse-surface`, which has its own Concepts row. Preview card uses `popup.*`. Navigation menu uses `bar.*` for the strip, `popup.*` for the panel, and item hover for links. Each keeps only size tokens.
|
|
111
|
+
- `notice.*` (`semantic/notice.json`): toasts, on the popup surface. A toast whose type is success, warning, error or info takes that palette's soft fill, edge and text (the build points the scope at it); `data-color` picks any other palette. Toast keeps only the stack's width and corner margins.
|
|
112
|
+
- `gauge.*` (`semantic/gauge.json`): Meter and Progress share one bar. The track is `fill.track` (a Concepts row, near the page in dark mode so every palette's fill reads 3:1 on it); the fill is the scope's solid color, so `data-color` on the root sets a zone or status. Neither has component tokens. Slider is a gauge you can drag: its track, fill and label row are `gauge.*` (thin size), the knob is field-colored and ringed in the scope's solid color, and it keeps only its knob size and vertical length.
|
|
113
|
+
- Avatar, Separator and Scroll area are built from existing tokens. Avatar initials use the scope's solid fill and on-solid text (the bold emphasis colors), its ring is the page color, and it keeps only its five sizes. Separator is `border.default`; Scroll area is `content.scrollbar-*`. Neither has component tokens. Badge, Card and Dropdown token files were removed: no style read them (Base UI has no Badge or Card; menus and selects use `popup.*` and `item.*`).
|
|
114
|
+
- `item.*` (`semantic/item.json`): rows inside menus and listboxes, and the check or dot that marks a chosen row. `item.padding-block-dense` is for menus.
|
|
115
|
+
- `icon-button.*` (`semantic/icon-button.json`): small icon-only buttons, such as the X that dismisses a popover, dialog or toast, and a field's clear and open buttons.
|
|
116
|
+
- `check.*` (`semantic/check.json`): the box or circle you tick. Checkbox, Radio and the Switch track share it, and AG Grid's checkboxes point at it.
|
|
117
|
+
|
|
118
|
+
A component only borrows another component's tokens when it really contains that component.
|
|
119
|
+
|
|
120
|
+
CSS that repeats across components is a pattern in `loomiere-base-ui/styles/patterns/shared.css`, written once as `@sl-define name { … }` and used as `@sl-pattern name;` inside a rule. The build pastes it in (Vite's own PostCSS step for the app and Storybook, `build/build-styles.mjs` for the package), so no preprocessor is needed and the published CSS is plain. Patterns hold declarations only and read concept tokens only; states stay in the component. Current patterns: `field`, `popup-surface`, `popup-motion`, `item-row`, `icon-box`, `focus-ring`, `icon-button`, `item-indicator`, `popup-group-label`, `popup-separator`, `popup-empty`, `check-box`, `check-row`, plus `text-<style>` for every text style (generated from the text style tokens by `textStylePatterns()` in `build/build-tokens.mjs`), so component CSS uses a whole text style in one line.
|
|
121
|
+
|
|
122
|
+
A component keeps tokens only for decisions that are really its own. Input and Field have none (they are the `field` concept, `control.*` sizes and text styles). Button keeps two: its pill radius and its disabled fade. Popover keeps its min and max width. Fieldset, Form, Number field, Select, Combobox and Autocomplete have none: their inputs share Input's rules, their labels and messages share Field's, and their lists are popups of item rows with the shared group label, separator and empty message. Checkbox and Radio have none: the box is the icon size one tier above the label's text, and the mark one tier below. Toggle keeps its disabled fade and otherwise sizes like a button. Switch keeps its track sizes, thumb inset and ON/OFF text size; the thumb, its travel and the text position are worked out from them. Its colors are the brand's emphasis levels; `data-variant` is an alias (solid = bold and the default, outline = standard, ghost = quiet), so the brand file drives every button and `data-emphasis` wins when both are set.
|
|
123
|
+
|
|
124
|
+
## How the type math works
|
|
125
|
+
|
|
126
|
+
- **Sizes** are a modular scale: each step is the one below times the ratio, which can differ on small and large screens (`type.ratio`), easing between `type.viewport` widths. Below the base step, sizes are fixed px (`type.small`), from the ratio with a floor.
|
|
127
|
+
- **Step down** (`type.stepDown: true`). On small screens each size above the base starts where the step below ends on large screens, so a 2xl heading on a phone is the size of xl on a desktop. Headings drop one step instead of shrinking by the base's ratio.
|
|
128
|
+
- **Line height** is the font's own height plus leading. The own height is ascent + descent from `type.metrics` (Funnel Display is 1.25, Barlow Semi Condensed 1.2), so a taller font gets more room without a pin. Leading eases on a log scale from `lineHeight.text` (em, at `textStep` and below) to `lineHeight.heading` (at `headingStep` and up), because small text needs air between lines and big headings sit tight. From `solidFrom` up, display sizes are set solid (line height 1). Steps from `type.displayFrom` up use the display font's measurements. The brand builder's **Measure fonts** reads them from the font files.
|
|
129
|
+
|
|
130
|
+
## Brand pairs
|
|
131
|
+
|
|
132
|
+
A brand pair is a named background and text color, plus an optional accent, that the brand has decided go together: `ember-dune` is dune text on ember. Pairs aren't tied to a component. Any container can wear one:
|
|
133
|
+
|
|
134
|
+
```html
|
|
135
|
+
<div class="sl-pair-ember-dune">…</div>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
```tsx
|
|
139
|
+
<Toast.Root className="sl-pair-ember-dune">…</Toast.Root>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### The workflow
|
|
143
|
+
|
|
144
|
+
1. **Fill in the brand file**, giving each palette a `label` (`ember`, `dune`). Labels name the pairs.
|
|
145
|
+
2. **`loomiere build`** builds the palettes.
|
|
146
|
+
3. **`loomiere lab`** and open the address it prints (`http://localhost:4321`). The lab shows every combination that passes as a small mockup, lowest contrast first. Filter by background, text palette and WCAG level. Star the pairs you'd use and hide the rest. Starred pairs are named from the labels (`ember-dune`, or `ember-deep`/`ember-soft` within one palette); rename any of them. For each pair you can pick an **accent** (only colors that pass on that background are offered) and a **dark mode** pair from your other starred pairs.
|
|
147
|
+
4. **Save** writes them into the brand file as `color.pairs` and runs `loomiere build` again. If the build refuses a pair, the brand file is put back and the lab says why.
|
|
148
|
+
|
|
149
|
+
You can also edit `color.pairs` by hand and run `loomiere build`. The lab reads the brand file when it opens, so the two stay in step. `loomiere lab --brand brand/<name>.json --port 5000` picks another brand or port.
|
|
150
|
+
|
|
151
|
+
```json
|
|
152
|
+
"pairs": {
|
|
153
|
+
"ember-dune": { "bg": "secondary.600", "fg": "tertiary.50" },
|
|
154
|
+
"stone-soft": {
|
|
155
|
+
"bg": "neutral.100", "fg": "neutral.1200", "accent": "primary.600",
|
|
156
|
+
"dark": { "bg": "neutral.1100", "fg": "neutral.100", "accent": "primary.300" },
|
|
157
|
+
"description": "A quiet inset panel."
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Colors are `role.step`, or `white` and `black` for paper and ink. `dark` names only what changes; the rest carries over. Without `dark`, a pair looks the same in both modes.
|
|
163
|
+
|
|
164
|
+
### What the build makes
|
|
165
|
+
|
|
166
|
+
For each pair, `tokens/semantic/pair.json` gets these tokens (all `--sl-pair-NAME-*`):
|
|
167
|
+
|
|
168
|
+
| Part | What it is |
|
|
169
|
+
|---|---|
|
|
170
|
+
| `bg`, `fg`, `accent` | The colors you chose. `accent` is `fg` when the pair names none. |
|
|
171
|
+
| `hover`, `active` | The accent moved 15% and 30% toward `fg` (toward `bg` without an accent), for buttons. The button label is `bg`, so each falls back to the accent itself if moving it would lose AA. |
|
|
172
|
+
| `subtle`, `emphasis` | The accent 12% and 22% into `bg`, for highlighted and selected items. Each falls back to a lighter part if text on it would lose AA. |
|
|
173
|
+
| `muted` | `fg` 6% into `bg`, for code. Falls back to `bg` the same way. |
|
|
174
|
+
|
|
175
|
+
It also makes the class `.sl-pair-NAME`, which re-points the semantic color tokens at those parts: surfaces become `bg`, text and strong borders `fg`, links, buttons and focus rings `accent`, and the quieter borders and fills become `fg` mixed into `bg`. The full table is `PAIR_ROLES` in `generator/pairs.mjs`. Status colors and the overlay keep their global values.
|
|
176
|
+
|
|
177
|
+
Custom properties resolve where they're declared, so a component token set at `:root` to `var(--sl-surface-canvas)` would keep the global surface inside the class. The class therefore re-declares every token that leads to a re-pointed one (about 350 for Mojave), and they resolve again inside it. Those declarations hold in every mode, because the pair carries its own dark colors. Put the class on a container, not on `<html>`.
|
|
178
|
+
|
|
179
|
+
### What guards them
|
|
180
|
+
|
|
181
|
+
The build stops if a pair's text or accent is under 4.5:1 (AA) on its background, in either mode, or if a name isn't `a-z`, `0-9` and dashes. Derived parts never drop below AA because they fall back instead. `loomiere check` lists every pair's text, accent, button label and tinted parts alongside the semantic pairs. The **Base Styles/Brand Pairs** story shows each pair with real components in it.
|
|
182
|
+
|
|
183
|
+
## Brand studio
|
|
184
|
+
|
|
185
|
+
`loomiere studio` opens a page (`http://localhost:4322`) for writing the brand file with live previews. It runs the same generator the build does, so what you see is what `loomiere build` makes.
|
|
186
|
+
|
|
187
|
+
- **Color:** paper and ink, the contrast step tables, brand colors and alerts (keys, label, which table, tone, exact keys, own darkest color), and data colors. Each palette shows its ramp with contrast on paper, on-color text and its chroma curve.
|
|
188
|
+
- **Data colors** are checked against paper (pick a goal: none, 1.5:1, or 3:1 as WCAG asks for graphics) and against their neighbor with typical, red-blind, green-blind and blue-blind vision. **Suggest fixes** finds the nearest color that clears every check; use one or all.
|
|
189
|
+
- **Vision menu** (top bar) shows every preview as seen with a kind of color blindness or with no color at all. The **Color vision** section shows the brand and the four generated themes as cards; open one to tune any of its colors.
|
|
190
|
+
- **Type:** families and their measurements, base, ratio, step down, line height leading and step names, with ratio presets you can pick separately for small and large screens, and the full scale (with line heights in px) at any screen width.
|
|
191
|
+
- **Space, radius, lines, shadows, motion:** the unit and steps, drawn as bars, tiles, strokes, cards and a playable timeline.
|
|
192
|
+
- **Brand file:** the JSON as it will be saved, unsaved changes and the override pins in effect. **Pins** shows the ramps with `<brand>.overrides.json` applied; turn it off to see the pure math.
|
|
193
|
+
|
|
194
|
+
**Save** writes `brand/<file name>.json`. Saving the brand the project builds also rebuilds the theme, and if the build refuses the file it is put back the way it was. Saving under a new file name makes a new brand. `loomiere studio --brand brand/<name>.json --port 5000` picks another brand or port, and `loomiere studio --export out.html` (needs esbuild) writes a single page with no server (Copy JSON instead of Save) for sharing.
|
|
195
|
+
|
|
196
|
+
## Make a new brand
|
|
197
|
+
|
|
198
|
+
1. Run `loomiere init <name>`. It writes `brand/<name>.json` (a copy of Mojave's brand to start from) and `loomiere.config.json`. `generator/brand.schema.json` documents each field, and editors use it for autocomplete.
|
|
199
|
+
2. Run `loomiere studio` and work through the page, then Save.
|
|
200
|
+
3. Run `loomiere build`.
|
|
201
|
+
4. Pick pairs with `loomiere lab` (see [Brand pairs](#brand-pairs)).
|
|
202
|
+
5. Optional: add `brand/<name>.overrides.json` to pin exact values (`{ "color.primary.600": "#883ac3" }`).
|
|
203
|
+
|
|
204
|
+
## Commands
|
|
205
|
+
|
|
206
|
+
- `loomiere build`: generate primitives and write the theme (`tokens.css`, `utilities.css`, `tokens.js`, `tokens.d.ts`, `tokens.json`, `pairs.json`, `exports/`), then check contrast. `--brand`, `--out` and `--prefix` override `loomiere.config.json`.
|
|
207
|
+
- `loomiere studio`: edit the brand file with live previews. Save writes the brand file and rebuilds. `--port <port>`, or `--export <file>` for a standalone page.
|
|
208
|
+
- `loomiere lab`: choose brand pairs. Save writes `color.pairs` into the brand file and rebuilds.
|
|
209
|
+
- `loomiere check`: check semantic color pairs in light, dark and high-contrast modes, and every brand pair in light and dark. `loomiere build` runs it too, so a change that breaks contrast fails the build.
|
|
210
|
+
|
|
211
|
+
In this repo, `npm run tokens:pins` lists every Mojave override pin with how far the math is from it (`--report <file.md>` writes it as a table), and `npm run check:tokens` fails if any CSS uses a `var(--…)` that nothing defines.
|
|
212
|
+
|
|
213
|
+
## Outputs
|
|
214
|
+
|
|
215
|
+
- `tokens.css`: every token as `--sl-*` inside `@layer sl.tokens`, so a host app's own styles win.
|
|
216
|
+
- `pairs.json`: every palette color against every other, grouped by WCAG level (AAA, AA, AA-large).
|
|
217
|
+
- `tokens.json`: a flat list of `{ name, path, layer, type, value, description }` for agents and docs.
|
|
218
|
+
- `tokens.js` / `tokens.d.ts`: nested resolved values, e.g. `tokens.color.primary['600']`.
|
|
219
|
+
- `../exports/`: DTCG JSON and framework themes ([Exports](#exports-for-other-frameworks)), with `catalog.json` listing them.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"avatar": {
|
|
3
|
+
"$description": "Avatar tokens: only the five sizes. The ring is the page color so overlapping avatars cut out of each other; initials are the scope's solid fill and its text (bold emphasis), so data-color re-colors them.",
|
|
4
|
+
"size-xs": { "$value": "{space.8}", "$description": "32px, inline with text and in dense lists." },
|
|
5
|
+
"size-sm": { "$value": "{space.10}", "$description": "40px, in list rows and comments." },
|
|
6
|
+
"size-md": { "$value": "{space.12}", "$description": "48px, the default." },
|
|
7
|
+
"size-lg": { "$value": "{space.16}", "$description": "64px, in cards and headers." },
|
|
8
|
+
"size-xl": { "$value": "{space.24}", "$description": "96px, on a profile page." }
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"button": {
|
|
3
|
+
"radius": {
|
|
4
|
+
"$value": "{radius.pill}",
|
|
5
|
+
"$description": "Buttons are pills, so they read as actions and never as fields or cards."
|
|
6
|
+
},
|
|
7
|
+
"opacity-disabled": {
|
|
8
|
+
"$value": "0.5",
|
|
9
|
+
"$description": "A disabled button fades as a whole, keeping its shape so the layout doesn't jump."
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"drawer": {
|
|
3
|
+
"$description": "Drawer tokens: only what differs from a dialog. Surface, scrim, heading and motion come from modal.*; layout mechanics (fixed position, swipe transforms) live in Drawer.css.",
|
|
4
|
+
"popup-size": {
|
|
5
|
+
"$value": "min(420px, 90vw)",
|
|
6
|
+
"$description": "Width of a side drawer. A bottom or top drawer sizes to its content up to max-size."
|
|
7
|
+
},
|
|
8
|
+
"popup-max-size": {
|
|
9
|
+
"$value": "85vh",
|
|
10
|
+
"$description": "Tallest a bottom or top drawer gets, leaving the page visible above it."
|
|
11
|
+
},
|
|
12
|
+
"handle-color": {
|
|
13
|
+
"$value": "{border.strong}",
|
|
14
|
+
"$type": "color",
|
|
15
|
+
"$description": "The grab bar on bottom drawers. Strong border so it reads as something to drag."
|
|
16
|
+
},
|
|
17
|
+
"handle-width": {
|
|
18
|
+
"$value": "{space.10}",
|
|
19
|
+
"$description": "Grab bar length."
|
|
20
|
+
},
|
|
21
|
+
"handle-height": {
|
|
22
|
+
"$value": "{space.1}",
|
|
23
|
+
"$description": "Grab bar thickness."
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"navigation-menu": {
|
|
3
|
+
"$description": "Navigation menu tokens: only its panel sizes. The strip is the bar concept, the panel is the popup concept and the links are item rows.",
|
|
4
|
+
"popup-min-width": {
|
|
5
|
+
"$value": "400px",
|
|
6
|
+
"$description": "Narrowest a navigation panel gets on wider screens, and the widest a single column of links grows."
|
|
7
|
+
},
|
|
8
|
+
"column-width": {
|
|
9
|
+
"$value": "12rem",
|
|
10
|
+
"$description": "Width of each column in a two-column link list."
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"popover": {
|
|
3
|
+
"$description": "Popover's own sizes. Everything else is the popup and close concepts, text styles and CSS.",
|
|
4
|
+
"popup-max-width": {
|
|
5
|
+
"$value": "400px",
|
|
6
|
+
"$description": "Keeps longer text to a readable line length."
|
|
7
|
+
},
|
|
8
|
+
"popup-min-width": {
|
|
9
|
+
"$value": "{popup.min-width}",
|
|
10
|
+
"$description": "Narrow enough for a short note, wide enough that a title doesn't wrap word by word."
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"preview-card": {
|
|
3
|
+
"$description": "Preview card tokens: only its size. The surface, arrow and motion are the popup concept (tokens/semantic/popup.json).",
|
|
4
|
+
"popup-min-width": {
|
|
5
|
+
"$value": "300px",
|
|
6
|
+
"$description": "Narrowest a preview card gets, so a short title still has room for its image."
|
|
7
|
+
},
|
|
8
|
+
"popup-max-width": {
|
|
9
|
+
"$value": "450px",
|
|
10
|
+
"$description": "Widest a preview card gets, so its description keeps a comfortable line length."
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"slider": {
|
|
3
|
+
"$description": "Slider tokens: only sizes. The track and fill are the gauge's (gauge.*), so a slider reads as a meter you can drag; the thumb is a field-colored knob ringed in the scope's solid color.",
|
|
4
|
+
"thumb-size": {
|
|
5
|
+
"$value": "{space.5}",
|
|
6
|
+
"$description": "Knob diameter, big enough to grab with a finger on a 4px track."
|
|
7
|
+
},
|
|
8
|
+
"vertical-height": {
|
|
9
|
+
"$value": "200px",
|
|
10
|
+
"$description": "Default length of a vertical slider."
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"switch": {
|
|
3
|
+
"$description": "Switch's own sizes: the track at each size, the thumb's inset, and the ON/OFF text size. Colors come from the check concept; the thumb, its travel and the text position are worked out from the track in Switch.css. Labels run one step smaller than checkbox and radio labels (base-label, minimize, base), a deliberate exception: the track already carries text.",
|
|
4
|
+
"track-width": {
|
|
5
|
+
"$value": "48px",
|
|
6
|
+
"$description": "Wide enough for the thumb plus the ON/OFF text."
|
|
7
|
+
},
|
|
8
|
+
"track-width-sm": {
|
|
9
|
+
"$value": "44px"
|
|
10
|
+
},
|
|
11
|
+
"track-width-lg": {
|
|
12
|
+
"$value": "68px"
|
|
13
|
+
},
|
|
14
|
+
"track-height": {
|
|
15
|
+
"$value": "24px",
|
|
16
|
+
"$description": "Track height; the thumb fills it inside the padding."
|
|
17
|
+
},
|
|
18
|
+
"track-height-sm": {
|
|
19
|
+
"$value": "20px"
|
|
20
|
+
},
|
|
21
|
+
"track-height-lg": {
|
|
22
|
+
"$value": "32px"
|
|
23
|
+
},
|
|
24
|
+
"track-padding": {
|
|
25
|
+
"$value": "2px",
|
|
26
|
+
"$description": "Gap between the track edge and the thumb."
|
|
27
|
+
},
|
|
28
|
+
"track-text-font-size": {
|
|
29
|
+
"$value": "{font-size.xs}",
|
|
30
|
+
"$description": "Sized to the track, not a text style: the ON/OFF label has to fit inside the switch at each size. The rest of the label uses condense-subtitle-label."
|
|
31
|
+
},
|
|
32
|
+
"track-text-font-size-sm": {
|
|
33
|
+
"$value": "{font-size.xxs}",
|
|
34
|
+
"$description": "Sized to the track, not a text style: the ON/OFF label has to fit inside the switch at each size. The rest of the label uses condense-subtitle-label."
|
|
35
|
+
},
|
|
36
|
+
"track-text-font-size-lg": {
|
|
37
|
+
"$value": "{font-size.sm}",
|
|
38
|
+
"$description": "Sized to the track, not a text style: the ON/OFF label has to fit inside the switch at each size. The rest of the label uses condense-subtitle-label."
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"toast": {
|
|
3
|
+
"$description": "Toast tokens: only where the stack sits and how wide it is. The look is notice.* on the popup surface.",
|
|
4
|
+
"viewport-width": {
|
|
5
|
+
"$value": "250px",
|
|
6
|
+
"$description": "Stack width on small screens."
|
|
7
|
+
},
|
|
8
|
+
"viewport-width-md": {
|
|
9
|
+
"$value": "300px",
|
|
10
|
+
"$description": "Stack width from 500px up."
|
|
11
|
+
},
|
|
12
|
+
"viewport-margin": {
|
|
13
|
+
"$value": "{space.4}",
|
|
14
|
+
"$description": "Distance from the screen corner on small screens."
|
|
15
|
+
},
|
|
16
|
+
"viewport-margin-md": {
|
|
17
|
+
"$value": "{space.8}",
|
|
18
|
+
"$description": "Distance from the screen corner from 500px up."
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"toggle": {
|
|
3
|
+
"$description": "Toggle's own decision. Sizes and text follow Button; colors are semantic concepts in Toggle.css.",
|
|
4
|
+
"opacity-disabled": {
|
|
5
|
+
"$value": "0.5",
|
|
6
|
+
"$description": "A disabled toggle fades as a whole, like a disabled button."
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"toolbar": {
|
|
3
|
+
"$description": "Toolbar tokens: only what a toolbar adds to the bar concept (tokens/semantic/bar.json).",
|
|
4
|
+
"input-max-width": {
|
|
5
|
+
"$value": "200px",
|
|
6
|
+
"$description": "A text field in a toolbar stays short so the other controls keep their place."
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tooltip": {
|
|
3
|
+
"$description": "Tooltip tokens: only the width limit. The look is the inverse surface (surface.inverse, text.on-inverse-surface) and the motion is the popup's.",
|
|
4
|
+
"max-width": {
|
|
5
|
+
"$value": "300px",
|
|
6
|
+
"$description": "A tooltip wraps after a short line, so it labels and never explains at length."
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"z-index": {
|
|
3
|
+
"$type": "number",
|
|
4
|
+
"hide": {
|
|
5
|
+
"$value": -1,
|
|
6
|
+
"$description": "Push behind the normal flow."
|
|
7
|
+
},
|
|
8
|
+
"auto": {
|
|
9
|
+
"$value": "auto",
|
|
10
|
+
"$description": "Let the browser decide; no stacking context."
|
|
11
|
+
},
|
|
12
|
+
"base": {
|
|
13
|
+
"$value": 0,
|
|
14
|
+
"$description": "Normal flow."
|
|
15
|
+
},
|
|
16
|
+
"docked": {
|
|
17
|
+
"$value": 10,
|
|
18
|
+
"$description": "Sticky parts inside a component (table headers, docked panels)."
|
|
19
|
+
},
|
|
20
|
+
"dropdown": {
|
|
21
|
+
"$value": 1000,
|
|
22
|
+
"$description": "Menus and listboxes anchored to a trigger."
|
|
23
|
+
},
|
|
24
|
+
"sticky": {
|
|
25
|
+
"$value": 1100,
|
|
26
|
+
"$description": "Page-level sticky bars."
|
|
27
|
+
},
|
|
28
|
+
"banner": {
|
|
29
|
+
"$value": 1200,
|
|
30
|
+
"$description": "Site-wide announcement banners."
|
|
31
|
+
},
|
|
32
|
+
"scrim": {
|
|
33
|
+
"$value": 1300,
|
|
34
|
+
"$description": "Backdrop behind modal content."
|
|
35
|
+
},
|
|
36
|
+
"modal": {
|
|
37
|
+
"$value": 1400,
|
|
38
|
+
"$description": "Dialogs and drawers."
|
|
39
|
+
},
|
|
40
|
+
"popover": {
|
|
41
|
+
"$value": 1500,
|
|
42
|
+
"$description": "Popovers that can open from inside a modal."
|
|
43
|
+
},
|
|
44
|
+
"skip-link": {
|
|
45
|
+
"$value": 1600,
|
|
46
|
+
"$description": "Skip-to-content link must sit above everything it skips."
|
|
47
|
+
},
|
|
48
|
+
"toast": {
|
|
49
|
+
"$value": 1700,
|
|
50
|
+
"$description": "Notifications stay visible over modals."
|
|
51
|
+
},
|
|
52
|
+
"tooltip": {
|
|
53
|
+
"$value": 1800,
|
|
54
|
+
"$description": "Tooltips label whatever is beneath them, so they go on top."
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"breakpoint": {
|
|
3
|
+
"$type": "dimension",
|
|
4
|
+
"$description": "Viewport widths where layouts may change. Use in media queries via the JS/JSON output; CSS custom properties can't be used inside @media.",
|
|
5
|
+
"sm": {
|
|
6
|
+
"$value": "30em",
|
|
7
|
+
"$description": "480px. Large phones."
|
|
8
|
+
},
|
|
9
|
+
"md": {
|
|
10
|
+
"$value": "48em",
|
|
11
|
+
"$description": "768px. Tablets, portrait."
|
|
12
|
+
},
|
|
13
|
+
"lg": {
|
|
14
|
+
"$value": "62em",
|
|
15
|
+
"$description": "992px. Tablets landscape, small laptops."
|
|
16
|
+
},
|
|
17
|
+
"xl": {
|
|
18
|
+
"$value": "80em",
|
|
19
|
+
"$description": "1280px. Desktop."
|
|
20
|
+
},
|
|
21
|
+
"2xl": {
|
|
22
|
+
"$value": "96em",
|
|
23
|
+
"$description": "1536px. Wide desktop."
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"container": {
|
|
27
|
+
"$type": "dimension",
|
|
28
|
+
"$description": "Max widths for page containers, matched to the breakpoints.",
|
|
29
|
+
"sm": {
|
|
30
|
+
"$value": "30rem"
|
|
31
|
+
},
|
|
32
|
+
"md": {
|
|
33
|
+
"$value": "48rem"
|
|
34
|
+
},
|
|
35
|
+
"lg": {
|
|
36
|
+
"$value": "62rem"
|
|
37
|
+
},
|
|
38
|
+
"xl": {
|
|
39
|
+
"$value": "80rem"
|
|
40
|
+
},
|
|
41
|
+
"2xl": {
|
|
42
|
+
"$value": "96rem"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"measure": {
|
|
46
|
+
"$type": "dimension",
|
|
47
|
+
"$description": "Comfortable line lengths for running text.",
|
|
48
|
+
"narrow": {
|
|
49
|
+
"$value": "45ch",
|
|
50
|
+
"$description": "Captions, sidebars."
|
|
51
|
+
},
|
|
52
|
+
"base": {
|
|
53
|
+
"$value": "60ch",
|
|
54
|
+
"$description": "Default body copy."
|
|
55
|
+
},
|
|
56
|
+
"wide": {
|
|
57
|
+
"$value": "75ch",
|
|
58
|
+
"$description": "Upper limit before reading gets tiring."
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"space": {
|
|
62
|
+
"px": {
|
|
63
|
+
"$type": "dimension",
|
|
64
|
+
"$value": "1px",
|
|
65
|
+
"$description": "A single device-independent pixel, for hairline offsets. Not part of the brand unit scale."
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"radius": {
|
|
69
|
+
"full": {
|
|
70
|
+
"$type": "dimension",
|
|
71
|
+
"$value": "9999px",
|
|
72
|
+
"$description": "Fully rounded ends (pills, circles), whatever the brand radius."
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"easing": {
|
|
3
|
+
"$type": "cubicBezier",
|
|
4
|
+
"default": {
|
|
5
|
+
"$value": [
|
|
6
|
+
0.4,
|
|
7
|
+
0,
|
|
8
|
+
0.2,
|
|
9
|
+
1
|
|
10
|
+
],
|
|
11
|
+
"$description": "Most UI transitions: quick start, gentle settle."
|
|
12
|
+
},
|
|
13
|
+
"in": {
|
|
14
|
+
"$value": [
|
|
15
|
+
0.4,
|
|
16
|
+
0,
|
|
17
|
+
1,
|
|
18
|
+
1
|
|
19
|
+
],
|
|
20
|
+
"$description": "Things leaving the screen."
|
|
21
|
+
},
|
|
22
|
+
"out": {
|
|
23
|
+
"$value": [
|
|
24
|
+
0,
|
|
25
|
+
0,
|
|
26
|
+
0.2,
|
|
27
|
+
1
|
|
28
|
+
],
|
|
29
|
+
"$description": "Things entering the screen."
|
|
30
|
+
},
|
|
31
|
+
"in-out": {
|
|
32
|
+
"$value": [
|
|
33
|
+
0.4,
|
|
34
|
+
0,
|
|
35
|
+
0.2,
|
|
36
|
+
1
|
|
37
|
+
],
|
|
38
|
+
"$description": "Things moving from one place to another on screen."
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|