@apliteni/apliteni-ui 0.23.2 → 0.23.4
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 +6 -0
- package/package.json +1 -1
- package/src/components/overlay.js +28 -44
- package/src/components/shell.js +48 -76
- package/src/styles/base.css +1 -1
- package/src/styles/table.css +10 -20
- package/src/tokens/accents.css +24 -19
- package/src/tokens/tokens.css +76 -138
package/README.md
CHANGED
|
@@ -132,6 +132,12 @@ Each accent re-points only the accent family (`--accent`, `--purple*`, `--glow-p
|
|
|
132
132
|
stay put — so **every accent works in both themes** and every component follows with no
|
|
133
133
|
component-level change.
|
|
134
134
|
|
|
135
|
+
Both attributes are overrides, not requirements: with neither present the kit paints dark
|
|
136
|
+
Nebula, and `data-accent` alone paints that accent on the dark theme. An absent `data-theme`
|
|
137
|
+
is *not* "follow the system" — the kit ships no `prefers-color-scheme` rule, so a host that
|
|
138
|
+
wants the OS preference resolves it in JS and stamps the attribute. See
|
|
139
|
+
[`docs/library.md`](./docs/library.md#an-absent-attribute-means-dark).
|
|
140
|
+
|
|
135
141
|
Shipped accents: **Nebula** (purple, default), **Phoenix** (ember), **Ocean** (azure),
|
|
136
142
|
**Emerald** (jade). Runtime helpers: `applyTheme('light')` / `applyAccent('phoenix')`
|
|
137
143
|
(both persist to `localStorage`); or the `accentPicker()` component wired by `wireTopbar()`.
|
package/package.json
CHANGED
|
@@ -1,19 +1,12 @@
|
|
|
1
|
-
// Overlay primitives — the page state the kit's modal surfaces share.
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// the
|
|
5
|
-
//
|
|
6
|
-
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// Internal — not re-exported from src/index.js.
|
|
11
|
-
|
|
12
|
-
// What each overlay paints on, as its stylesheet resolves it today: a drawer at
|
|
13
|
-
// `--z-overlay` (src/styles/drawer.css) and a confirm one above it
|
|
14
|
-
// (src/styles/confirm.css). Absolute values, not ranks, so a sheet that moves and
|
|
15
|
-
// a table that did not is a failed test rather than a keyboard on the wrong layer
|
|
16
|
-
// — stories/overlay-css.test.js holds these two numbers to those two rules.
|
|
1
|
+
// Overlay primitives — the page state the kit's modal surfaces share. Drawer and Confirm
|
|
2
|
+
// are the same problem twice: content over a scrim that owns the keyboard until it is
|
|
3
|
+
// answered. What is inert, which overlay Escape talks to and where Tab may go are
|
|
4
|
+
// properties of the *page*, so they are answered here from one stack per document rather
|
|
5
|
+
// than from either component's own storage. Internal — not re-exported from src/index.js.
|
|
6
|
+
|
|
7
|
+
// What each overlay paints on today: a drawer at `--z-overlay` (styles/drawer.css) and a
|
|
8
|
+
// confirm above it (styles/confirm.css). Absolute values, not ranks, so a sheet that moves
|
|
9
|
+
// and a table that did not is a failed test — stories/overlay-css.test.js holds both.
|
|
17
10
|
export const OVERLAY_LAYER = { drawer: 100, confirm: 101 };
|
|
18
11
|
|
|
19
12
|
const FOCUSABLE = [
|
|
@@ -33,10 +26,9 @@ function pageOf(doc) {
|
|
|
33
26
|
return page;
|
|
34
27
|
}
|
|
35
28
|
|
|
36
|
-
// Focusable to the *browser*, not merely matching the selector. A control in a
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
// and Tab walks straight out of the modal.
|
|
29
|
+
// Focusable to the *browser*, not merely matching the selector. A control in a closed
|
|
30
|
+
// overlay, an inert subtree or a hidden ancestor is skipped by the real tab order, so a
|
|
31
|
+
// trap that counts it wraps at an element focus never reaches.
|
|
40
32
|
function reachable(el) {
|
|
41
33
|
for (let n = el; n && n.nodeType === 1; n = n.parentElement) {
|
|
42
34
|
if (n.inert || n.hasAttribute('inert') || n.hasAttribute('hidden')) return false;
|
|
@@ -53,10 +45,9 @@ export function focusablesIn(panel) {
|
|
|
53
45
|
return Array.from(panel.querySelectorAll(FOCUSABLE)).filter(reachable);
|
|
54
46
|
}
|
|
55
47
|
|
|
56
|
-
// Hide everything *outside* `root` from AT + the tab order: walk root→body and
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
// not — a drawer under an aria-modal alertdialog must not be tabbable.
|
|
48
|
+
// Hide everything *outside* `root` from AT + the tab order: walk root→body and mark each
|
|
49
|
+
// ancestor's other children. Scrim and panel live inside the root so they stay
|
|
50
|
+
// interactive; a drawer under an aria-modal alertdialog is outside it and must not be.
|
|
60
51
|
function mark(page, doc, root) {
|
|
61
52
|
let node = root;
|
|
62
53
|
while (node && node.parentElement && node !== doc.body) {
|
|
@@ -81,10 +72,9 @@ function unmark(page) {
|
|
|
81
72
|
page.marked = [];
|
|
82
73
|
}
|
|
83
74
|
|
|
84
|
-
// Recompute the page from the stack — never replay a snapshot taken when an
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
// what to give back outlives the node that hid it.
|
|
75
|
+
// Recompute the page from the stack — never replay a snapshot taken when an overlay
|
|
76
|
+
// opened, because by the time it closes that snapshot describes a page that has moved on.
|
|
77
|
+
// Roots torn down while open drop out here.
|
|
88
78
|
function sync(doc) {
|
|
89
79
|
const page = pageOf(doc);
|
|
90
80
|
for (let i = page.stack.length - 1; i >= 0; i--) {
|
|
@@ -128,12 +118,11 @@ function place(root, panel, dismiss, layer, where) {
|
|
|
128
118
|
sync(doc);
|
|
129
119
|
}
|
|
130
120
|
|
|
131
|
-
// The layer this root actually paints on. In a browser the live z-index is the
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
// stands. `auto` must not read as 0; anything unparseable falls through.
|
|
121
|
+
// The layer this root actually paints on. In a browser the live z-index is the truth, so
|
|
122
|
+
// a consumer who moves an overlay in their own stylesheet gets the keyboard on the layer
|
|
123
|
+
// they can see. JSDOM hands back declared text rather than a number, so under test the
|
|
124
|
+
// passed constant stands — `auto` must not read as 0, and anything unparseable falls
|
|
125
|
+
// through.
|
|
137
126
|
function paintedLayer(root, layer) {
|
|
138
127
|
const painted = root.ownerDocument.defaultView?.getComputedStyle(root)?.zIndex;
|
|
139
128
|
const live = painted ? Number(painted) : NaN;
|
|
@@ -152,16 +141,11 @@ export function pushOverlay(root, panel, dismiss, layer) {
|
|
|
152
141
|
}
|
|
153
142
|
|
|
154
143
|
/**
|
|
155
|
-
* Take on a root that arrived already open — markup rendered with `open: true`,
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
* position only separates two overlays on the same layer, and there it is the
|
|
161
|
-
* right answer rather than a fallback: at equal stack levels the later root
|
|
162
|
-
* paints on top (CSS 2.2 Appendix E, steps 8 and 9).
|
|
163
|
-
*
|
|
164
|
-
* A root that renders closed is left alone — wiring is not an opening.
|
|
144
|
+
* Take on a root that arrived already open — markup rendered with `open: true`, which
|
|
145
|
+
* nobody called open…() for. Without this its aria-modal is a claim the page contradicts.
|
|
146
|
+
* It goes in by PAINT ORDER, because wiring has no history to order by; document position
|
|
147
|
+
* separates two overlays on the same layer, where the later root paints on top (CSS 2.2
|
|
148
|
+
* Appendix E, steps 8 and 9). A root that renders closed is left alone.
|
|
165
149
|
*/
|
|
166
150
|
export function adoptOverlay(root, panel, dismiss, layer) {
|
|
167
151
|
if (!root.classList.contains('is-open')) return;
|
package/src/components/shell.js
CHANGED
|
@@ -10,9 +10,8 @@ import { sidebarNav, breadcrumbs } from './nav.js';
|
|
|
10
10
|
import { prism } from '../assets/brand.js';
|
|
11
11
|
import { ACCOUNT_NAV, toMenuTuple, initials } from './account-nav.js';
|
|
12
12
|
|
|
13
|
-
// The one account navigation definition lives in account-nav.js
|
|
14
|
-
//
|
|
15
|
-
// here so the published name docs/library.md documents keeps working.
|
|
13
|
+
// The one account navigation definition lives in account-nav.js because topbar.js needs
|
|
14
|
+
// it too; re-exported here so the name docs/library.md publishes keeps working.
|
|
16
15
|
export { ACCOUNT_NAV };
|
|
17
16
|
|
|
18
17
|
const str = (v) => (v == null ? '' : String(v));
|
|
@@ -20,32 +19,24 @@ const isRecord = (v) => typeof v === 'object' && v !== null;
|
|
|
20
19
|
|
|
21
20
|
// ---- the options bag, settled once --------------------------------------
|
|
22
21
|
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
// accountShell() has always taken its nav as [id, icon, label, href?, target?].
|
|
33
|
-
// Accept that shape and nav.js's object shape side by side, so a consumer's
|
|
34
|
-
// existing tuples and the exported ACCOUNT_NAV both work. A nav that is not a
|
|
35
|
-
// list at all falls back to the default; an entry inside one that is neither
|
|
36
|
-
// shape is dropped, because sideItem() reads `.items` off whatever it is given.
|
|
37
|
-
// An empty list is an answer, not a mistake — it stays empty.
|
|
22
|
+
// A default parameter covers `undefined` and nothing else, so `nav: null` from an
|
|
23
|
+
// /auth/me and a `crumbs` string from somebody reading the migration note both arrived
|
|
24
|
+
// as they were. A shell that throws mid-render takes the page with it, so each option is
|
|
25
|
+
// settled here, before the first sink sees it. Adding one to appShell() means adding it
|
|
26
|
+
// to SHAPES or deciding in the open that it needs nothing.
|
|
27
|
+
|
|
28
|
+
// accountShell()'s tuple nav — [id, icon, label, href?, target?] — and nav.js's object
|
|
29
|
+
// shape are accepted side by side. A nav that is not a list falls back to the default;
|
|
30
|
+
// an entry that is neither shape is dropped. An empty list is an answer and stays.
|
|
38
31
|
const toItems = (nav) => (Array.isArray(nav) ? nav : ACCOUNT_NAV)
|
|
39
32
|
.filter(isRecord)
|
|
40
33
|
.map((n) => (Array.isArray(n)
|
|
41
34
|
? { id: n[0], icon: n[1], label: n[2], href: n[3], target: n[4] }
|
|
42
35
|
: n));
|
|
43
36
|
|
|
44
|
-
// The trail is the caller's, and `crumbs`
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
// read as a one-crumb trail — that would draw a plausible page and hide the
|
|
48
|
-
// migration mistake — so it is no trail at all, which is the visible answer. A
|
|
37
|
+
// The trail is the caller's, and `crumbs` changed shape this release: `crumb: 'Payouts'`
|
|
38
|
+
// became `crumbs: [{ label }]`, one letter apart. A non-list is NOT read as a one-crumb
|
|
39
|
+
// trail — that hides the migration mistake — so it is no trail at all, which shows. A
|
|
49
40
|
// crumb with no label would draw an empty cell, so it goes too.
|
|
50
41
|
const toCrumbs = (crumbs) => (Array.isArray(crumbs) ? crumbs : [])
|
|
51
42
|
.filter((c) => isRecord(c) && !Array.isArray(c) && str(c.label) !== '');
|
|
@@ -56,23 +47,16 @@ const toReader = (a) => (isRecord(a) ? { name: str(a.name), email: str(a.email)
|
|
|
56
47
|
|
|
57
48
|
// ---- the topbar, which interpolates where the rail escapes ----------------
|
|
58
49
|
//
|
|
59
|
-
// brand() writes `word` straight into its markup and accountMenu() does the
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
// `initials` travels with them because it is derived, and a derived value comes
|
|
71
|
-
// from what the caller passed, not from the entities made of it: `<Ada>` and
|
|
72
|
-
// `<Ada>` do not start with the same character. The entries land in the
|
|
73
|
-
// same sink and toMenuTuple() is what escapes them; it reads item objects, so
|
|
74
|
-
// tuples go through toItems() first. A nav nobody passed stays unpassed, and
|
|
75
|
-
// accountMenu() falls back to its own derived default rather than to nothing.
|
|
50
|
+
// brand() writes `word` straight into its markup and accountMenu() does the same with
|
|
51
|
+
// the reader's name, address and menu entries, so the escaping is here on the one path
|
|
52
|
+
// into productTopbar() rather than in each caller. The caller passes text either way.
|
|
53
|
+
|
|
54
|
+
// The reader the menu draws. Both fields are always written, empty when the caller gave
|
|
55
|
+
// none: accountMenu()'s defaults are a demo identity, so a key dropped here is a key its
|
|
56
|
+
// fixture fills in. `initials` is derived from what the caller passed rather than from
|
|
57
|
+
// the entities made of it — `<Ada>` and `<Ada>` do not start with the same
|
|
58
|
+
// character. toMenuTuple() escapes the entries and reads item objects, so tuples go
|
|
59
|
+
// through toItems() first; a nav nobody passed stays unpassed.
|
|
76
60
|
const toMenuReader = (a) => {
|
|
77
61
|
const { name, email } = toReader(a);
|
|
78
62
|
const rest = isRecord(a) && !Array.isArray(a) ? a : {};
|
|
@@ -89,12 +73,11 @@ const toTopbar = (t) => {
|
|
|
89
73
|
return out;
|
|
90
74
|
};
|
|
91
75
|
|
|
92
|
-
// `maxWidth` lands inside a style attribute, so a length is all this accepts —
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
// drops the column to `none`, the full track.
|
|
76
|
+
// `maxWidth` lands inside a style attribute, so a length is all this accepts — a number
|
|
77
|
+
// and a unit, or `none`. Anything else yields '' and the caller writes no style
|
|
78
|
+
// attribute, falling through to --measure. It must REMOVE the property rather than pass
|
|
79
|
+
// a bad value on: a custom property accepts any token stream, so garbage is a valid
|
|
80
|
+
// declaration that drops the column to `none`, the full track.
|
|
98
81
|
// why: docs/specification.md#widths
|
|
99
82
|
const LENGTH = /^(?:\d+|\d*\.\d+)(?:px|rem|em|ch|%|vw)$/;
|
|
100
83
|
const mainMax = (v) => {
|
|
@@ -108,11 +91,9 @@ const SHAPES = {
|
|
|
108
91
|
nav: toItems, crumbs: toCrumbs, account: toReader, maxWidth: mainMax, topbar: toTopbar,
|
|
109
92
|
};
|
|
110
93
|
|
|
111
|
-
// The text options settle
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
// left the brand link with no accessible name at all. Dropping the key is what
|
|
115
|
-
// lets the declared default apply.
|
|
94
|
+
// The text options settle by the same argument. `body: null` from a record with no
|
|
95
|
+
// description drew the word "null" on the page and `word: null` left the brand link with
|
|
96
|
+
// no accessible name; dropping the key is what lets the declared default apply.
|
|
116
97
|
const TEXT = ['word', 'brandHref', 'navLabel', 'title', 'sub', 'body', 'signOutHref', 'active'];
|
|
117
98
|
|
|
118
99
|
function settle(options) {
|
|
@@ -122,23 +103,18 @@ function settle(options) {
|
|
|
122
103
|
return out;
|
|
123
104
|
}
|
|
124
105
|
|
|
125
|
-
// Signing out is a navigation action, so it belongs in the rail nav's footer
|
|
126
|
-
//
|
|
127
|
-
// no session behind it.
|
|
106
|
+
// Signing out is a navigation action, so it belongs in the rail nav's footer slot.
|
|
107
|
+
// Opt-in: rendering it unasked puts a dead link on a page with no session behind it.
|
|
128
108
|
const signOut = (href) =>
|
|
129
109
|
`<a class="ui-nav__item is-danger" href="${esc(href)}" aria-label="Sign out">` +
|
|
130
110
|
`<span class="ui-nav__ic">${icon('logout')}</span>` +
|
|
131
111
|
`<span class="ui-nav__label">Sign out</span></a>`;
|
|
132
112
|
|
|
133
|
-
// Who is signed in. A sibling of the <nav>, not its footer: a
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
// The initials carry the name, and the spelled-out half is aria-hidden. The
|
|
139
|
-
// narrow rail folds `.ui-app__who` out of view, so a name that lived only there
|
|
140
|
-
// left the initials on screen with nothing at all in the accessibility tree.
|
|
141
|
-
// Naming the mark instead makes the two agree at every width, and says it once.
|
|
113
|
+
// Who is signed in. A sibling of the <nav>, not its footer: a name and address are not
|
|
114
|
+
// navigation, and inside the landmark a screen reader announces the address as an entry.
|
|
115
|
+
// Empty when nobody is. The initials carry the name and the spelled-out half is
|
|
116
|
+
// aria-hidden, because the narrow rail folds `.ui-app__who` out of view and a name that
|
|
117
|
+
// lived only there left nothing in the accessibility tree.
|
|
142
118
|
function railUser({ name, email }) {
|
|
143
119
|
if (!name && !email) return '';
|
|
144
120
|
const who = [name, email].filter(Boolean).join(', ');
|
|
@@ -179,16 +155,14 @@ export function appShell(options = {}) {
|
|
|
179
155
|
ariaLabel: navLabel,
|
|
180
156
|
footer: signOutHref ? signOut(signOutHref) : '',
|
|
181
157
|
});
|
|
182
|
-
// The topbar already says the product word
|
|
183
|
-
//
|
|
184
|
-
//
|
|
185
|
-
// so the name is written out — the mark itself is aria-hidden.
|
|
158
|
+
// The topbar already says the product word, so the rail head steps aside when there is
|
|
159
|
+
// one. The word is the link's only text and the narrow rail folds it out of view, so
|
|
160
|
+
// the name is written out — the mark itself is aria-hidden.
|
|
186
161
|
const brand = topbar ? '' : `<a class="ui-app__brand" href="${esc(brandHref)}" aria-label="${esc(word)}">`
|
|
187
162
|
+ `${prism(`appb-${++_shellUid}`, 24)}<span>${esc(word)}</span></a>`;
|
|
188
|
-
// A <div>, not an <aside>: <aside> is the `complementary` landmark
|
|
189
|
-
//
|
|
190
|
-
//
|
|
191
|
-
// already the landmark that names the menu.
|
|
163
|
+
// A <div>, not an <aside>: <aside> is the `complementary` landmark, and this holds the
|
|
164
|
+
// page's primary navigation and the signed-in reader. The <nav> inside it is already
|
|
165
|
+
// the landmark that names the menu.
|
|
192
166
|
const grid = `<div class="ui-app">
|
|
193
167
|
<div class="ui-app__rail">
|
|
194
168
|
${brand}
|
|
@@ -225,11 +199,9 @@ export function accountShell({
|
|
|
225
199
|
// topbar menu are handed one list rather than two readings of `nav`.
|
|
226
200
|
const items = toItems(nav);
|
|
227
201
|
const trail = [{ label: cap }, { label: crumb || title }];
|
|
228
|
-
// The preset hands the topbar the caller's text,
|
|
229
|
-
//
|
|
230
|
-
//
|
|
231
|
-
// as entities, and escaping nowhere is what the public `topbar` option used
|
|
232
|
-
// to do.
|
|
202
|
+
// The preset hands the topbar the caller's text, as it does the rail. toTopbar()
|
|
203
|
+
// escapes for the menu's raw sinks and runs once inside appShell(); escaping here as
|
|
204
|
+
// well would reach the menu as entities.
|
|
233
205
|
return appShell({
|
|
234
206
|
word,
|
|
235
207
|
nav: items,
|
package/src/styles/base.css
CHANGED
package/src/styles/table.css
CHANGED
|
@@ -34,14 +34,10 @@
|
|
|
34
34
|
.ui-table tbody tr { transition: background var(--dur-fast) var(--ease); }
|
|
35
35
|
|
|
36
36
|
/* Row hover: an inset, rounded fill so the highlight never sits flush against a
|
|
37
|
-
* container's border (
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* Zebra tables keep the original full-bleed tint (finance ledger), so they're
|
|
42
|
-
* excluded here — their hover rule lives further down.
|
|
43
|
-
* The inset was 6px, which is a tie between --space-1 and --space-2; it rounds
|
|
44
|
-
* up, because what it buys is clearance and clearance rounds away from the edge. */
|
|
37
|
+
* container's border (#71). The separate border model with zero spacing keeps the row
|
|
38
|
+
* separators continuous while letting the row ends round, and table padding — which
|
|
39
|
+
* only applies in that model — insets the rows. Zebra keeps the original full-bleed
|
|
40
|
+
* tint (finance ledger) and is excluded here; its hover rule is below. */
|
|
45
41
|
.ui-table--hover:not(.ui-table--zebra) {
|
|
46
42
|
border-collapse: separate;
|
|
47
43
|
border-spacing: 0;
|
|
@@ -54,9 +50,7 @@
|
|
|
54
50
|
border-top-right-radius: var(--radius-sm); border-bottom-right-radius: var(--radius-sm); }
|
|
55
51
|
|
|
56
52
|
/* Data-table modifiers, composable: .ui-table--dense --zebra --hover.
|
|
57
|
-
* --dense --space-3 across, --space-2 down — one step in from the base
|
|
58
|
-
* table, and both round DOWN, because the modifier exists to fit
|
|
59
|
-
* more rows and rounding up spends the distinction it is for.
|
|
53
|
+
* --dense --space-3 across, --space-2 down — one step in from the base table
|
|
60
54
|
* --zebra row striping; 2px header rule; the hover tint out-ranks the stripe
|
|
61
55
|
* Cell helpers: .ui-table__num (right, tabular), .ui-table__code.
|
|
62
56
|
* why: docs/specification.md#spacing-and-rhythm
|
|
@@ -71,15 +65,11 @@
|
|
|
71
65
|
.ui-table--zebra th { border-bottom-width: 2px; }
|
|
72
66
|
.ui-table--zebra td { border-bottom: 0; }
|
|
73
67
|
.ui-table--zebra tbody tr:nth-child(even) td { background: var(--surface-2); }
|
|
74
|
-
/* The stripe
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* x=0 of the stripe. `DataTable` is that composition — it writes `ui-table
|
|
80
|
-
* ui-table--hover ui-table--zebra` and no `--dense` — so its select-all and per-row
|
|
81
|
-
* checkboxes sat flush against the tint with a measured 0px gap.
|
|
82
|
-
* Header and body are inset together, or the columns stop lining up. */
|
|
68
|
+
/* The stripe stays full-bleed (the #71 inset above is hover only), but it only reads right
|
|
69
|
+
* if the end cells are inset from the tint's own edge, and `.ui-table td` sets
|
|
70
|
+
* `padding-left: 0`. Only `--dense` supplied that inset, so `--zebra` without `--dense` —
|
|
71
|
+
* `DataTable`'s composition — sat its checkboxes at x=0 of the stripe. Head and body are
|
|
72
|
+
* inset together, or the columns stop lining up. */
|
|
83
73
|
.ui-table--zebra th:first-child,
|
|
84
74
|
.ui-table--zebra td:first-child { padding-left: var(--space-3); }
|
|
85
75
|
.ui-table--zebra th:last-child,
|
package/src/tokens/accents.css
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/* Accent sub-themes — an orthogonal `data-accent` dimension on top of
|
|
2
|
-
* `data-theme`.
|
|
2
|
+
* `data-theme`. On <html>, with or without a theme:
|
|
3
3
|
*
|
|
4
4
|
* <html data-theme="dark" data-accent="phoenix">
|
|
5
|
+
* <html data-accent="phoenix"> <!-- default theme, same accent -->
|
|
5
6
|
*
|
|
6
7
|
* Each sub-theme only re-points the accent family. Surfaces, text and signal
|
|
7
8
|
* colours (green = live, pink = danger) stay put, so every accent works in
|
|
@@ -10,12 +11,24 @@
|
|
|
10
11
|
* --ring is NOT here: it is declared once in tokens.css as var(--accent), so
|
|
11
12
|
* re-pointing --accent re-points the focus ring too.
|
|
12
13
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
14
|
+
* Every dark cell is a two-line selector list — a bare `:root[data-accent]`
|
|
15
|
+
* above the stamped form — which is the shape tokens.css gives the dark theme
|
|
16
|
+
* one file earlier, and for the same reason: the default theme needs no
|
|
17
|
+
* attribute, so neither may the accent. Written stamped-only, `data-accent`
|
|
18
|
+
* paints nothing at all in the state hosts use to mean "follow the system",
|
|
19
|
+
* where data-theme is simply absent. See issue #250.
|
|
20
|
+
*
|
|
21
|
+
* That bare line ties on specificity with :root[data-theme="light"] and wins on
|
|
22
|
+
* import order, so LIGHT is what the cascade has to be careful about: each light
|
|
23
|
+
* cell carries three attributes to out-specify it, and declares the same
|
|
24
|
+
* properties as its dark twin so none of the dark ramp leaks past it. Adding a
|
|
25
|
+
* property to one half of a pair and not the other is what breaks that, and
|
|
26
|
+
* stories/accent-without-theme.test.js is the gate that says so.
|
|
15
27
|
*
|
|
16
28
|
* why: docs/specification.md#the-focus-ring */
|
|
17
29
|
|
|
18
30
|
/* ---- Phoenix — ember / rising fire (the strategy's namesake) ------------- */
|
|
31
|
+
:root[data-accent="phoenix"],
|
|
19
32
|
:root[data-theme="dark"][data-accent="phoenix"] {
|
|
20
33
|
--purple: #e0531f;
|
|
21
34
|
--purple-light: #ff8a5c;
|
|
@@ -45,6 +58,7 @@
|
|
|
45
58
|
}
|
|
46
59
|
|
|
47
60
|
/* ---- Ocean — azure ------------------------------------------------------- */
|
|
61
|
+
:root[data-accent="ocean"],
|
|
48
62
|
:root[data-theme="dark"][data-accent="ocean"] {
|
|
49
63
|
--purple: #1f7fe0;
|
|
50
64
|
--purple-light: #5ab0ff;
|
|
@@ -62,31 +76,22 @@
|
|
|
62
76
|
--purple: #1156b8;
|
|
63
77
|
--purple-light: #1568d6;
|
|
64
78
|
--purple-mid: #1560c8;
|
|
65
|
-
/* Deepened for WCAG AA on the raised grey panel and its wash
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
which is what this cell ships — an earlier version of this comment quoted
|
|
69
|
-
them at the 0.05 the wash briefly carried, which flattered both inks.
|
|
70
|
-
Light Ocean is the one light accent #96 never deepened, because it happened
|
|
71
|
-
to clear plain white. Same kind of move, smaller: hue and chroma unchanged
|
|
72
|
-
in OKLCH (H 258, C 0.185) and four points of lightness down, where #96's
|
|
73
|
-
two took about ten points and gave up 15.9% of the chroma on Phoenix and
|
|
74
|
-
17.6% on Emerald. See issues #96 and #157. */
|
|
79
|
+
/* Deepened for WCAG AA on the raised grey panel and its wash — hue and chroma held
|
|
80
|
+
in OKLCH (H 258, C 0.185), four points of lightness down. The washed figures are
|
|
81
|
+
read at the 0.10 alpha below, which is what this cell ships. See issues #96, #157. */
|
|
75
82
|
--accent: #005bc8;
|
|
76
83
|
--accent-strong: var(--accent);
|
|
77
84
|
--accent-contrast: #ffffff;
|
|
78
|
-
/* Back to 0.10
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
wash does not have to be the thing that gives. The rgb stays --purple-light
|
|
82
|
-
rather than the accent — in light a glow tints --purple-light, which
|
|
83
|
-
stories/signal-contrast.test.js holds. See issue #157. */
|
|
85
|
+
/* Back to the 0.10 this cell carried before #157: deepening the ink closed the pair,
|
|
86
|
+
so the wash does not have to give. The rgb stays --purple-light rather than the
|
|
87
|
+
accent — in light a glow tints --purple-light, which signal-contrast.test.js holds. */
|
|
84
88
|
--glow-purple: rgba(21, 104, 214, 0.10);
|
|
85
89
|
--grad-from: #1568d6;
|
|
86
90
|
--grad-to: #17a2c0;
|
|
87
91
|
}
|
|
88
92
|
|
|
89
93
|
/* ---- Emerald — jade ------------------------------------------------------ */
|
|
94
|
+
:root[data-accent="emerald"],
|
|
90
95
|
:root[data-theme="dark"][data-accent="emerald"] {
|
|
91
96
|
--purple: #0fa876;
|
|
92
97
|
--purple-light: #3ad9a0;
|
package/src/tokens/tokens.css
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
/* ============================================================================
|
|
2
|
-
* apliteni-ui — design tokens
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* purple accent, near-black surfaces, green = live, cyan = link/flag.
|
|
7
|
-
* Two themes: dark (default) and light. Toggle with `data-theme` on <html>.
|
|
8
|
-
*
|
|
9
|
-
* Everything downstream references the SEMANTIC tokens (--bg, --surface,
|
|
10
|
-
* --accent, --text…), never the raw ramp. Swap a ramp value once, and every
|
|
11
|
-
* component follows.
|
|
2
|
+
* apliteni-ui — design tokens. Single source of truth for the Apliteni deck theme,
|
|
3
|
+
* with viz/'s colour semantics: purple accent, near-black surfaces, green = live,
|
|
4
|
+
* cyan = link/flag. Two themes, dark (default) and light, toggled with `data-theme`
|
|
5
|
+
* on <html>. Everything downstream references the SEMANTIC tokens, never the ramp.
|
|
12
6
|
* ========================================================================== */
|
|
13
7
|
|
|
14
8
|
:root {
|
|
@@ -44,7 +38,6 @@
|
|
|
44
38
|
/* -- Measure ------------------------------------------------------------
|
|
45
39
|
* --container is the page, gutter to gutter; --measure is the reading column
|
|
46
40
|
* INSIDE a track, beside a sidebar. Different axes, never interchangeable.
|
|
47
|
-
* Below --measure are --panel-* and --prose-*, at the end of this file (#208).
|
|
48
41
|
* why: docs/specification.md#widths */
|
|
49
42
|
--container: 1120px;
|
|
50
43
|
--measure: 860px;
|
|
@@ -79,13 +72,12 @@
|
|
|
79
72
|
--tracking-caps: 0.14em;
|
|
80
73
|
|
|
81
74
|
/* -- Motion -------------------------------------------------------------
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* concern). The reusable effect layer lives in styles/motion.css. */
|
|
75
|
+
* The kit's legacy --dur-* and --ease names alias onto the brand's
|
|
76
|
+
* --duration-* and --easing-* primitives, so every component consumes one
|
|
77
|
+
* vocabulary; the fallbacks keep tokens.css self-sufficient when imported
|
|
78
|
+
* without brand.generated.css. Delays are the kit's own, and the reusable
|
|
79
|
+
* effect layer lives in styles/motion.css.
|
|
80
|
+
* why: CONTRIBUTING.md#brand-tokens-synced-from-design-system */
|
|
89
81
|
--ease: var(--easing-ease-in-out, cubic-bezier(0.4, 0, 0.2, 1));
|
|
90
82
|
--ease-out: var(--easing-ease-out, cubic-bezier(0, 0, 0.2, 1));
|
|
91
83
|
--ease-in: var(--easing-ease-in, cubic-bezier(0.4, 0, 1, 1));
|
|
@@ -119,22 +111,16 @@
|
|
|
119
111
|
color-scheme: dark;
|
|
120
112
|
|
|
121
113
|
--purple: #6a2dcc;
|
|
122
|
-
/* The ramp moves up one step so --accent is --purple-light again,
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
the two tokens would resolve to one colour and the ramp would carry a
|
|
126
|
-
duplicate step. #bd8cff is the value docs/specification.md#colour-and-contrast reasons about and
|
|
127
|
-
rejects as the accent; it becomes the ramp's top step instead. Light
|
|
128
|
-
Nebula's ramp does not move. See issue #157. */
|
|
114
|
+
/* The ramp moves up one step so --accent is --purple-light again, the shape light
|
|
115
|
+
Nebula and light Ocean already ship; left alone the two tokens would resolve to
|
|
116
|
+
one colour. #bd8cff becomes the ramp's top step instead. See issue #157. */
|
|
129
117
|
--purple-light: #b479ff;
|
|
130
118
|
--purple-mid: #bd8cff;
|
|
131
119
|
--cyan: #20dcf5;
|
|
132
120
|
--green: #98ff8f;
|
|
133
|
-
/* Lighter than it looks like it should be
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
under AA — so danger moved AWAY from the canvas, not toward it. Light moves
|
|
137
|
-
the opposite way for the same reason. See issue #131. */
|
|
121
|
+
/* Lighter than it looks like it should be: --pink is read mostly ON its own 16%
|
|
122
|
+
glow, and that wash lifts the ground faster than the hue lifts the ink, so danger
|
|
123
|
+
moved AWAY from the canvas. Light moves the opposite way. See issue #131. */
|
|
138
124
|
--pink: #e97ca5;
|
|
139
125
|
--amber: #ffcf6a;
|
|
140
126
|
|
|
@@ -144,9 +130,8 @@
|
|
|
144
130
|
--surface-2: #1b1927;
|
|
145
131
|
--surface-3: #2a2739;
|
|
146
132
|
--seg-active-bg: #34314a; /* raised pill — clearly above the inset track in dark */
|
|
147
|
-
/* The raised handle on a control track (switch knob).
|
|
148
|
-
|
|
149
|
-
the accent fill the track takes when the control is on. */
|
|
133
|
+
/* The raised handle on a control track (switch knob). White in both themes and under
|
|
134
|
+
every accent: it reads on the plain track AND on the accent fill of an on control. */
|
|
150
135
|
--control-knob: #ffffff;
|
|
151
136
|
--border: #332f45;
|
|
152
137
|
--border-strong: #453f5c;
|
|
@@ -156,53 +141,39 @@
|
|
|
156
141
|
--dim: #c6c2d6;
|
|
157
142
|
--muted: #948fa8;
|
|
158
143
|
|
|
159
|
-
/* The paint of a control that is off. NOT an opacity: `opacity` is a group
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
faded accent. These three composite predictably instead: the ink is read on
|
|
164
|
-
the surface beside it, and both are opaque. They are aliases, not new
|
|
165
|
-
colours — the ramp gains nothing to keep in sync. A disabled control drops
|
|
166
|
-
its accent, which is most of what makes it read as inert; the ink going
|
|
167
|
-
quiet is the rest. Held by stories/guidelines/accessibility-floor.test.js.
|
|
144
|
+
/* The paint of a control that is off. NOT an opacity: `opacity` is a group property,
|
|
145
|
+
so it fades label and box toward the ground TOGETHER — a disabled primary button
|
|
146
|
+
measured 1.48:1 that way. These three are aliases that composite predictably, the
|
|
147
|
+
ink read on the surface beside it. Held by guidelines/accessibility-floor.test.js.
|
|
168
148
|
why: docs/specification.md#colour-and-contrast */
|
|
169
149
|
--disabled-ink: var(--muted);
|
|
170
150
|
--disabled-surface: var(--surface-2);
|
|
171
151
|
--disabled-border: var(--border);
|
|
172
152
|
|
|
173
|
-
/* Lifted from #9b5dff, which cleared the flat surfaces and failed on its own
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
--purple-light, which is how every other Nebula and Ocean cell is written.
|
|
177
|
-
--glow-purple follows it down the file; --ring is written as var(--accent)
|
|
178
|
-
and so follows it by reference. Held by
|
|
179
|
-
stories/accent-contrast.test.js. See issue #157.
|
|
153
|
+
/* Lifted from #9b5dff, which cleared the flat surfaces and failed on its own wash over
|
|
154
|
+
a card; the value was already in the ramp. --ring is var(--accent) and follows by
|
|
155
|
+
reference. Held by stories/accent-contrast.test.js. #157.
|
|
180
156
|
why: docs/specification.md#colour-and-contrast */
|
|
181
157
|
--accent: #b479ff;
|
|
182
158
|
--accent-strong: #7c3aed; /* button bg — white text clears WCAG AA at 5.70:1 */
|
|
183
159
|
--accent-contrast: #ffffff;
|
|
184
|
-
--ink: #e9e7f0;
|
|
185
160
|
|
|
186
|
-
/* Ink that reads on a SIGNAL colour once that colour becomes a fill — the
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
because LIGHT still needs one.
|
|
161
|
+
/* Ink that reads on a SIGNAL colour once that colour becomes a fill — the glyph in a
|
|
162
|
+
success circle, or on a toast's status circle. One near-black ink clears all five in
|
|
163
|
+
dark; danger keeps its own token because LIGHT still needs one.
|
|
190
164
|
why: docs/specification.md#colour-and-contrast */
|
|
191
165
|
--signal-contrast: #0c0c0c;
|
|
192
166
|
--danger-contrast: var(--signal-contrast);
|
|
193
167
|
|
|
194
168
|
--glow-green: rgba(152, 255, 143, 0.16);
|
|
195
|
-
/* Dropped from 0.18: the wash, not the surface beneath it, is what put --accent
|
|
196
|
-
|
|
197
|
-
it. Re-tinted from the new --accent, which signal-contrast.test.js requires.
|
|
198
|
-
See issue #157. */
|
|
169
|
+
/* Dropped from 0.18: the wash, not the surface beneath it, is what put --accent under
|
|
170
|
+
AA. Re-tinted from the new --accent, which signal-contrast.test.js requires. #157. */
|
|
199
171
|
--glow-purple: rgba(180, 121, 255, 0.12);
|
|
200
172
|
--glow-cyan: rgba(32, 220, 245, 0.14);
|
|
201
173
|
--glow-pink: rgba(233, 124, 165, 0.16);
|
|
202
174
|
|
|
203
|
-
/* Status-chip paint — the ink + fill of a badge / pill. Dark tints with the
|
|
204
|
-
|
|
205
|
-
on white), which is why these are a token pair and not just --green etc. */
|
|
175
|
+
/* Status-chip paint — the ink + fill of a badge / pill. Dark tints with the signal's
|
|
176
|
+
own glow; light needs a solid, deepened pair, which is why these are a token pair. */
|
|
206
177
|
--chip-success-ink: var(--green);
|
|
207
178
|
--chip-success-fill: var(--glow-green);
|
|
208
179
|
--chip-warn-ink: var(--amber);
|
|
@@ -212,14 +183,10 @@
|
|
|
212
183
|
--chip-info-ink: var(--cyan);
|
|
213
184
|
--chip-info-fill: var(--glow-cyan);
|
|
214
185
|
|
|
215
|
-
/* Solid signal surfaces — a status that has stopped being ink and become the
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
the bright signals, so the ink is near-black — it measures 15.87 on --green,
|
|
220
|
-
7.35 on --pink, 13.40 on --amber, 11.75 on --cyan and 6.29 on --muted.
|
|
221
|
-
Neutral needs no chip pair to take part: --muted is already its
|
|
222
|
-
fill-strength form. See issues #131 and #149. */
|
|
186
|
+
/* Solid signal surfaces — a status that has stopped being ink and become the FILL (a
|
|
187
|
+
solid toast). Fill and ink are ONE pair, never two independent picks: the fill is the
|
|
188
|
+
status at this theme's extreme and the ink the pole opposite it, so a single ink
|
|
189
|
+
clears all five. --muted is already its own fill-strength form. See #131, #149. */
|
|
223
190
|
--signal-solid-ink: var(--signal-contrast);
|
|
224
191
|
--signal-solid-success: var(--green);
|
|
225
192
|
--signal-solid-danger: var(--pink);
|
|
@@ -232,19 +199,18 @@
|
|
|
232
199
|
--shadow-lg: 0 18px 50px rgba(0, 0, 0, 0.5);
|
|
233
200
|
/* Tight elevation for the active segmented pill — stays contained in its track. */
|
|
234
201
|
--shadow-seg: 0 1px 2px rgba(0, 0, 0, 0.45), 0 2px 4px rgba(0, 0, 0, 0.3);
|
|
235
|
-
/* Resting elevation for a card. Dark cards are borderless — the surface
|
|
236
|
-
|
|
202
|
+
/* Resting elevation for a card. Dark cards are borderless — the surface groups on
|
|
203
|
+
its own — so they cast nothing. */
|
|
237
204
|
--shadow-card: none;
|
|
238
|
-
/* THE ONLY --ring in the kit — light and every data-accent sub-theme declare
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
stories/guidelines/accessibility-floor.test.js. See issue #218.
|
|
205
|
+
/* THE ONLY --ring in the kit — light and every data-accent sub-theme declare an
|
|
206
|
+
--accent and inherit this line. Opaque because a translucent ring is a glow, and the
|
|
207
|
+
eight rgba() literals it replaced all missed WCAG 1.4.11's 3:1 (#218). Held by
|
|
208
|
+
stories/guidelines/accessibility-floor.test.js.
|
|
243
209
|
why: docs/specification.md#the-focus-ring */
|
|
244
210
|
--ring: 0 0 0 3px var(--accent);
|
|
245
211
|
|
|
246
|
-
/* The ink a cast shadow is made of, and the specular white that reads as
|
|
247
|
-
|
|
212
|
+
/* The ink a cast shadow is made of, and the specular white that reads as gloss on a
|
|
213
|
+
raised surface. Effects mix these instead of writing rgba(). */
|
|
248
214
|
--shadow-ink: #000000;
|
|
249
215
|
--sheen: #ffffff;
|
|
250
216
|
|
|
@@ -252,10 +218,8 @@
|
|
|
252
218
|
--scrim: rgba(0, 0, 0, 0.58);
|
|
253
219
|
|
|
254
220
|
/* Accent gradient stops (hero headline, marketing) — overridden per sub-theme.
|
|
255
|
-
--grad-from is pinned to
|
|
256
|
-
|
|
257
|
-
first stop, a visible change nobody asked for. Every --grad-from in
|
|
258
|
-
src/tokens/accents.css is already written as a literal for the same reason. */
|
|
221
|
+
--grad-from is pinned to a literal rather than tracking --purple-light, which #157
|
|
222
|
+
moved up a step; every --grad-from in accents.css is a literal for the same reason. */
|
|
259
223
|
--grad-from: #9b5dff;
|
|
260
224
|
--grad-to: var(--cyan);
|
|
261
225
|
}
|
|
@@ -271,15 +235,13 @@
|
|
|
271
235
|
--purple-mid: #7a3be0;
|
|
272
236
|
--cyan: #0c8fa8;
|
|
273
237
|
--green: #1c8a2c;
|
|
274
|
-
/* Deeper than the dark pink for the same reason dark's is lighter: the wash
|
|
275
|
-
|
|
276
|
-
3.87:1 on --glow-pink over white and 4.42:1 on plain white — the required
|
|
277
|
-
marker and the form error missed AA on nothing at all. See issue #131. */
|
|
238
|
+
/* Deeper than the dark pink for the same reason dark's is lighter: the wash under it
|
|
239
|
+
is white, so the ink has to come down to meet it. See issue #131. */
|
|
278
240
|
--pink: #b63361;
|
|
279
241
|
--amber: #b5730a;
|
|
280
242
|
|
|
281
|
-
/* A WHITE app:
|
|
282
|
-
|
|
243
|
+
/* A WHITE app: page and cards are both white, and cards are delineated by a hairline
|
|
244
|
+
border + soft shadow (see card.css), not a grey canvas. */
|
|
283
245
|
--bg: #ffffff;
|
|
284
246
|
--bg-elevated: #ffffff;
|
|
285
247
|
--surface: #ffffff;
|
|
@@ -295,9 +257,8 @@
|
|
|
295
257
|
--dim: #3a404b;
|
|
296
258
|
--muted: #5c6270;
|
|
297
259
|
|
|
298
|
-
/* Same three aliases as dark
|
|
299
|
-
|
|
300
|
-
card: a control that is off should sink, not rise. */
|
|
260
|
+
/* Same three aliases as dark. Light moves the disabled surface AWAY from white for the
|
|
261
|
+
same reason dark moves it away from the card: a control that is off should sink. */
|
|
301
262
|
--disabled-ink: var(--muted);
|
|
302
263
|
--disabled-surface: var(--surface-2);
|
|
303
264
|
--disabled-border: var(--border);
|
|
@@ -305,50 +266,37 @@
|
|
|
305
266
|
--accent: #6a2dcc;
|
|
306
267
|
--accent-strong: #6a2dcc; /* already dark enough for white text */
|
|
307
268
|
--accent-contrast: #ffffff;
|
|
308
|
-
--ink: #1e232b;
|
|
309
269
|
|
|
310
270
|
--signal-contrast: #0c0c0c;
|
|
311
271
|
--danger-contrast: #ffffff;
|
|
312
272
|
|
|
313
|
-
/* Re-tinted from --green
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
0.86 of a deltaE — under the 1.0 that is normally called imperceptible. No
|
|
318
|
-
verdict moves with it: body copy on the success wash goes 9.29 -> 9.15
|
|
319
|
-
against the 4.5 AA bar, and the --green icon on it 3.97 -> 3.91 against the
|
|
320
|
-
3.0 bar WCAG 1.4.11 sets for a graphic. The success chips are untouched —
|
|
321
|
-
light's --chip-success-fill is the solid #dff3e4, not this. Gated for all
|
|
322
|
-
four glows in stories/signal-contrast.test.js. See issue #131. */
|
|
273
|
+
/* Re-tinted from --green; it was rgba(30, 150, 50, .1), a colour that exists nowhere
|
|
274
|
+
else in the kit while the other three light glows are exact tints of their own token.
|
|
275
|
+
The wash over white moves at most two levels on a channel and no verdict moves with
|
|
276
|
+
it. Gated for all four glows in stories/signal-contrast.test.js. */
|
|
323
277
|
--glow-green: rgba(28, 138, 44, 0.1);
|
|
324
278
|
--glow-purple: rgba(106, 45, 204, 0.09);
|
|
325
279
|
--glow-cyan: rgba(12, 143, 168, 0.1);
|
|
326
280
|
--glow-pink: rgba(182, 51, 97, 0.1);
|
|
327
281
|
|
|
328
|
-
/* Solid chip pairs for the white app. Each ink is deepened past its signal
|
|
329
|
-
|
|
330
|
-
e.g. --pink (#b63361) on #fbe0ea is 4.66:1, #b7295f is 4.85:1. */
|
|
282
|
+
/* Solid chip pairs for the white app. Each ink is deepened past its signal hue so 10px
|
|
283
|
+
uppercase text clears WCAG AA on its own pale fill. */
|
|
331
284
|
--chip-success-ink: #1f7a38;
|
|
332
285
|
--chip-success-fill: #dff3e4;
|
|
333
286
|
--chip-warn-ink: #8a5e00;
|
|
334
287
|
--chip-warn-fill: #fbedd2;
|
|
335
288
|
--chip-danger-ink: #b7295f;
|
|
336
289
|
--chip-danger-fill: #fbe0ea;
|
|
337
|
-
/* Info was the one signal family without a pair, so the info badge
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
three pairs already sit in. See issue #131. */
|
|
290
|
+
/* Info was the one signal family without a pair, so the info badge painted --cyan on
|
|
291
|
+
--glow-cyan over white and missed AA. This pair sits in the band the other three
|
|
292
|
+
already occupy. See issue #131. */
|
|
341
293
|
--chip-info-ink: #0a7286;
|
|
342
294
|
--chip-info-fill: #ddeff3;
|
|
343
295
|
|
|
344
|
-
/* Light runs the same rule the other way. The signals are mid-tone here
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
clears every one: #ffffff measures 5.39 on --chip-success-ink, 6.01 on
|
|
349
|
-
--chip-danger-ink, 5.70 on --chip-warn-ink, 5.58 on --chip-info-ink and 6.11
|
|
350
|
-
on --muted. A future status supplies one --signal-solid-* value dark enough
|
|
351
|
-
for white and inherits the ink. See issues #131 and #149. */
|
|
296
|
+
/* Light runs the same rule the other way. The signals are mid-tone here, so NO ink
|
|
297
|
+
rescues them and the fill is what has to move: it takes the deepened form the chip
|
|
298
|
+
pair already carries, and white clears every one. A future status supplies one
|
|
299
|
+
--signal-solid-* value dark enough for white and inherits the ink. See #131, #149. */
|
|
352
300
|
--signal-solid-ink: #ffffff;
|
|
353
301
|
--signal-solid-success: var(--chip-success-ink);
|
|
354
302
|
--signal-solid-danger: var(--chip-danger-ink);
|
|
@@ -361,8 +309,8 @@
|
|
|
361
309
|
--shadow-lg: 0 18px 50px rgba(30, 30, 50, 0.16);
|
|
362
310
|
/* Tight elevation for the active segmented pill — stays contained in its track. */
|
|
363
311
|
--shadow-seg: 0 1px 2px rgba(30, 30, 50, 0.12), 0 2px 4px rgba(30, 30, 50, 0.1);
|
|
364
|
-
/* A white card on a white page needs its own
|
|
365
|
-
|
|
312
|
+
/* A white card on a white page needs its own flatter-than-sm elevation: a hairline
|
|
313
|
+
contact shadow plus a wide soft one (see card.css). */
|
|
366
314
|
--shadow-card: 0 1px 2px rgba(20, 25, 45, 0.04), 0 4px 14px rgba(20, 25, 45, 0.05);
|
|
367
315
|
/* No --ring here. :root's is var(--accent), and light re-points --accent. */
|
|
368
316
|
|
|
@@ -376,42 +324,32 @@
|
|
|
376
324
|
}
|
|
377
325
|
|
|
378
326
|
/* ============================================================================
|
|
379
|
-
* WIDTHS BELOW THE PAGE — the box, and the line
|
|
380
|
-
*
|
|
381
|
-
* --container is the page and --measure the reading column inside it (both
|
|
382
|
-
* above). Underneath them the kit has two scales and one rule for choosing
|
|
383
|
-
* between them: THE UNIT SAYS WHICH SCALE YOU ARE ON.
|
|
327
|
+
* WIDTHS BELOW THE PAGE — the box, and the line. Two scales, and one rule for
|
|
328
|
+
* choosing between them: THE UNIT SAYS WHICH SCALE YOU ARE ON.
|
|
384
329
|
*
|
|
385
330
|
* a box that holds a COMPONENT -> --panel-* (px)
|
|
386
331
|
* a box that holds a LINE -> --prose-* (ch)
|
|
387
332
|
*
|
|
388
|
-
* Both are theme-invariant,
|
|
333
|
+
* Both are theme-invariant, so they sit outside the palette blocks.
|
|
389
334
|
* why: docs/specification.md#boxes-below-the-page
|
|
390
335
|
* ========================================================================== */
|
|
391
336
|
:root {
|
|
392
337
|
/* -- Panel widths -------------------------------------------------------
|
|
393
|
-
* Not a new scale: the drawer has shipped sm/md/lg at
|
|
394
|
-
*
|
|
395
|
-
* wrote one of them out again on their own. --panel-md answers the question
|
|
396
|
-
* #208 asked — the two 420s were three (--confirm-w, --drawer-w md and
|
|
397
|
-
* .ui-auth__card), and they are one measure now. */
|
|
338
|
+
* Not a new scale: the drawer has shipped sm/md/lg at these three values, and
|
|
339
|
+
* confirm, the auth card and the toast each wrote one out again (#208). */
|
|
398
340
|
--panel-sm: 320px;
|
|
399
341
|
--panel-md: 420px;
|
|
400
342
|
--panel-lg: 560px;
|
|
401
343
|
|
|
402
344
|
/* -- Prose measures -----------------------------------------------------
|
|
403
|
-
* ch resolves against the font-size of the element it is DECLARED on, so a
|
|
404
|
-
*
|
|
405
|
-
*
|
|
406
|
-
*
|
|
407
|
-
* The four steps are the reading band (44–72ch), named for what is being
|
|
408
|
-
* read rather than sized s/m/l, so the choice is made by the writer:
|
|
345
|
+
* ch resolves against the font-size of the element it is DECLARED on, so a step
|
|
346
|
+
* goes on the paragraph itself, never on a wrapper holding two type sizes. The
|
|
347
|
+
* four steps are the reading band (44–72ch), named for what is being read:
|
|
409
348
|
* caption one or two sentences under a glyph, centred
|
|
410
349
|
* lede the line under a title
|
|
411
350
|
* body a left-aligned column of copy
|
|
412
351
|
* dense reference prose set below 13px, where the band runs wider
|
|
413
|
-
* --prose-display is NOT a measure
|
|
414
|
-
* it is far under the band on purpose. */
|
|
352
|
+
* --prose-display is NOT a measure — it is where a display headline rags. */
|
|
415
353
|
--prose-display: 14ch;
|
|
416
354
|
--prose-caption: 44ch;
|
|
417
355
|
--prose-lede: 54ch;
|