@wtfalch/design 0.1.0 → 0.3.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 +84 -37
- package/dist/components/Brand.d.ts +35 -6
- package/dist/components/Brand.js +26 -201
- package/dist/components/Icon.d.ts +1 -1
- package/dist/components/Icon.js +5 -228
- package/dist/components/Illustration.js +1 -2
- package/dist/components/Tour.d.ts +3 -1
- package/dist/components/Tour.js +3 -3
- package/dist/components/brandMarks.d.ts +71 -0
- package/dist/components/brandMarks.js +26 -0
- package/dist/components/icons.d.ts +37 -0
- package/dist/components/icons.js +253 -0
- package/dist/components/tourMarker.d.ts +14 -3
- package/dist/components/tourMarker.js +11 -7
- package/dist/index.d.ts +13 -1
- package/dist/index.js +11 -1
- package/dist/products/index.d.ts +69 -0
- package/dist/products/index.js +54 -0
- package/dist/products/tf.d.ts +32 -0
- package/dist/products/tf.js +104 -0
- package/dist/products/valet.d.ts +23 -0
- package/dist/products/valet.js +103 -0
- package/dist/styles/index.css +23 -57
- package/dist/tf.css +3362 -0
- package/dist/tf.d.ts +18 -0
- package/dist/tf.js +17 -0
- package/dist/themes/css.d.ts +45 -0
- package/dist/themes/css.js +78 -0
- package/dist/{themes.d.ts → themes/index.d.ts} +11 -13
- package/dist/{themes.js → themes/index.js} +12 -89
- package/dist/tokens.css +17 -0
- package/dist/valet.css +3366 -0
- package/dist/valet.d.ts +18 -0
- package/dist/valet.js +17 -0
- package/package.json +15 -5
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tf's themes: the three the package shipped with, and the ones the gallery
|
|
3
|
+
* was photographed in first.
|
|
4
|
+
*
|
|
5
|
+
* `system` is a theme, not a mode. A `prefers-color-scheme` block that
|
|
6
|
+
* overrides `:root` unconditionally means choosing a dark theme on a
|
|
7
|
+
* light-mode laptop gets silently repainted; that media query is scoped to
|
|
8
|
+
* this theme in `base.css`, so following the OS is a choice among the others
|
|
9
|
+
* rather than a rule above them.
|
|
10
|
+
*/
|
|
11
|
+
export const system = {
|
|
12
|
+
name: 'System',
|
|
13
|
+
note: 'Follows your OS between light and dark',
|
|
14
|
+
scheme: 'dark',
|
|
15
|
+
// Empty on purpose: this is the one theme that must *not* state a palette,
|
|
16
|
+
// because the `prefers-color-scheme` block is scoped to it and needs the
|
|
17
|
+
// base values to fall through. Its swatch is a special case.
|
|
18
|
+
tokens: {},
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* The dark palette, written out rather than inherited.
|
|
22
|
+
*
|
|
23
|
+
* `night` was `tokens: {}` once, "whatever the base is", and that was wrong
|
|
24
|
+
* twice over. It made the theme depend on a file it does not own, and it made
|
|
25
|
+
* its swatch in the picker preview *the theme currently applied*, because an
|
|
26
|
+
* empty map falls back to the live values.
|
|
27
|
+
*/
|
|
28
|
+
const NIGHT = {
|
|
29
|
+
'--bg': '#0f1115',
|
|
30
|
+
'--panel': '#161a21',
|
|
31
|
+
'--panel-2': '#1c222b',
|
|
32
|
+
'--border': '#262d38',
|
|
33
|
+
'--text': '#e6e9ef',
|
|
34
|
+
'--muted': '#8b94a4',
|
|
35
|
+
'--accent': '#5b9dff',
|
|
36
|
+
'--accent-dim': '#2a4877',
|
|
37
|
+
/* No `--on-accent` here on purpose, so it inherits the base near-black.
|
|
38
|
+
Night used to set `#ffffff`, which is 2.72:1 on this accent -- the exact
|
|
39
|
+
pair `tokens.css` records as the reason the token exists at all. The base
|
|
40
|
+
was fixed and the theme carrying the old palette was never revisited, so
|
|
41
|
+
the primary button in the app's fixed dark theme failed the body-text
|
|
42
|
+
minimum by a wide margin for as long as the token had been "fixed".
|
|
43
|
+
`#06181a` on `#5b9dff` is 6.69:1. Found by the contrast scan, which is the
|
|
44
|
+
argument for shipping the measurement rather than the rule. */
|
|
45
|
+
'--app-bg': '#0f1115',
|
|
46
|
+
};
|
|
47
|
+
export const night = {
|
|
48
|
+
name: 'Night',
|
|
49
|
+
note: 'The dark palette, fixed — ignores the OS',
|
|
50
|
+
scheme: 'dark',
|
|
51
|
+
tokens: NIGHT,
|
|
52
|
+
};
|
|
53
|
+
export const paper = {
|
|
54
|
+
name: 'Paper',
|
|
55
|
+
note: 'Light, with shadows that suit it',
|
|
56
|
+
scheme: 'light',
|
|
57
|
+
tokens: {
|
|
58
|
+
'--bg': '#f6f7f9',
|
|
59
|
+
'--panel': '#ffffff',
|
|
60
|
+
'--panel-2': '#f0f2f5',
|
|
61
|
+
'--border': '#e4e8ec',
|
|
62
|
+
'--border-strong': '#8792a1',
|
|
63
|
+
'--text': '#191d23',
|
|
64
|
+
'--muted': '#5d6773',
|
|
65
|
+
'--accent': '#0e7872',
|
|
66
|
+
'--accent-dim': '#7fbdb8',
|
|
67
|
+
'--on-accent': '#ffffff',
|
|
68
|
+
// The base set is tuned for a dark panel. Unoverridden, `--good` was
|
|
69
|
+
// 1.74:1 on white -- a `running` pill nobody could read.
|
|
70
|
+
'--good': '#1c7a4a',
|
|
71
|
+
'--warn': '#8a6216',
|
|
72
|
+
'--bad': '#b3312c',
|
|
73
|
+
// `#2f7fe6` was 3.96:1 on this theme's white panel -- the "information is
|
|
74
|
+
// blue" colour was measured on the dark base when it was added and never
|
|
75
|
+
// on a light one. Found by contrast.test.ts on its first run. This is the
|
|
76
|
+
// lightest step on the same hue that clears 4.5:1 on the panel *and* the
|
|
77
|
+
// page, with room: 5.24 on white, 4.88 on the page.
|
|
78
|
+
'--info': '#216bc9',
|
|
79
|
+
// Black shadows are right on a dark UI and muddy on a light one. This is
|
|
80
|
+
// the whole reason elevation had to become a token.
|
|
81
|
+
'--shadow-1': '0 4px 14px rgba(16, 24, 40, 0.08)',
|
|
82
|
+
'--shadow-2': '0 8px 24px rgba(16, 24, 40, 0.10)',
|
|
83
|
+
'--shadow-3': '0 12px 32px rgba(16, 24, 40, 0.12)',
|
|
84
|
+
'--scrim': 'rgba(16, 24, 40, 0.32)',
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
/** Keyed by the name `applyTheme` takes, which is `name` lowercased. */
|
|
88
|
+
export const TF_THEMES = { system, night, paper };
|
|
89
|
+
/**
|
|
90
|
+
* tf, the product.
|
|
91
|
+
*
|
|
92
|
+
* Its identity is empty on purpose: `tokens.css` carries tf's font, shape and
|
|
93
|
+
* density as the base values, so there is nothing to lay over them. `system`
|
|
94
|
+
* is the default because following the OS is the choice a tool open all day
|
|
95
|
+
* should make for you -- and because it is a `prefers-color-scheme` rule
|
|
96
|
+
* rather than a palette, tf's stylesheet writes no default on `:root`; tf sets
|
|
97
|
+
* `data-theme` before the bundle loads, as it always has.
|
|
98
|
+
*/
|
|
99
|
+
export const tf = {
|
|
100
|
+
name: 'tf',
|
|
101
|
+
identity: {},
|
|
102
|
+
themes: TF_THEMES,
|
|
103
|
+
defaultTheme: 'system',
|
|
104
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Theme, ThemeTokens } from '../themes';
|
|
2
|
+
import type { Product } from './index';
|
|
3
|
+
/**
|
|
4
|
+
* valet's identity: what it is under every theme.
|
|
5
|
+
*
|
|
6
|
+
* The fonts are named, not shipped. `--font` reads a `--font-sans` variable
|
|
7
|
+
* the app defines with whatever loads its fonts, and falls back to the family
|
|
8
|
+
* by name; the gallery loads both families from `gallery/public/fonts` so the
|
|
9
|
+
* specimens are photographed in them. Sharper corners than the package
|
|
10
|
+
* default, because a console is read in rows and columns and a large radius
|
|
11
|
+
* rounds the grid away.
|
|
12
|
+
*/
|
|
13
|
+
export declare const VALET_IDENTITY: Partial<ThemeTokens>;
|
|
14
|
+
export declare const light: Theme;
|
|
15
|
+
export declare const night: Theme;
|
|
16
|
+
/** Keyed by the name `applyTheme` takes: `name` lowercased, spaces to hyphens. */
|
|
17
|
+
export declare const VALET_THEMES: {
|
|
18
|
+
readonly valet: Theme;
|
|
19
|
+
readonly 'valet-night': Theme;
|
|
20
|
+
};
|
|
21
|
+
/** valet, the product: its badge in `brandMarks.ts`, the identity above, the
|
|
22
|
+
* two palettes, and light until somebody picks. */
|
|
23
|
+
export declare const valet: Product;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* valet's two palettes.
|
|
3
|
+
*
|
|
4
|
+
* One hue that belongs to nothing else. The accent is an indigo, chosen so it
|
|
5
|
+
* sits apart from all four status tones: good is green, warn is amber, bad is
|
|
6
|
+
* red, and info is a cyan rather than a blue so a link and an information pill
|
|
7
|
+
* cannot be confused at a glance. The accent is what a person acts on; the
|
|
8
|
+
* tones are what the system says. They never share a colour.
|
|
9
|
+
*
|
|
10
|
+
* Every colour is measured, not judged: `contrast.test.ts` holds both palettes
|
|
11
|
+
* to the same pairs as tf's.
|
|
12
|
+
*
|
|
13
|
+
* The palettes name colours and nothing else. What makes valet valet under
|
|
14
|
+
* either of them -- the type and the corners -- is the identity below, on
|
|
15
|
+
* `:root` in valet's stylesheet and under every palette `applyTheme` lays on.
|
|
16
|
+
* Both palettes used to restate it, which is the shape that breaks the moment
|
|
17
|
+
* a theme is shared between products: silent on the font, it would have fallen
|
|
18
|
+
* back to tf's.
|
|
19
|
+
*/
|
|
20
|
+
const FONT = 'var(--font-sans, "IBM Plex Sans"), -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif';
|
|
21
|
+
const FONT_MONO = 'var(--font-mono, "JetBrains Mono"), ui-monospace, SFMono-Regular, Menlo, monospace';
|
|
22
|
+
/**
|
|
23
|
+
* valet's identity: what it is under every theme.
|
|
24
|
+
*
|
|
25
|
+
* The fonts are named, not shipped. `--font` reads a `--font-sans` variable
|
|
26
|
+
* the app defines with whatever loads its fonts, and falls back to the family
|
|
27
|
+
* by name; the gallery loads both families from `gallery/public/fonts` so the
|
|
28
|
+
* specimens are photographed in them. Sharper corners than the package
|
|
29
|
+
* default, because a console is read in rows and columns and a large radius
|
|
30
|
+
* rounds the grid away.
|
|
31
|
+
*/
|
|
32
|
+
export const VALET_IDENTITY = {
|
|
33
|
+
'--font': FONT,
|
|
34
|
+
'--font-mono': FONT_MONO,
|
|
35
|
+
'--radius-sm': '2px',
|
|
36
|
+
'--radius': '4px',
|
|
37
|
+
'--radius-md': '6px',
|
|
38
|
+
'--radius-lg': '10px',
|
|
39
|
+
};
|
|
40
|
+
export const light = {
|
|
41
|
+
name: 'valet',
|
|
42
|
+
note: 'Light. Ink on paper, indigo where you act.',
|
|
43
|
+
scheme: 'light',
|
|
44
|
+
tokens: {
|
|
45
|
+
'--bg': '#f4f5f8',
|
|
46
|
+
'--panel': '#ffffff',
|
|
47
|
+
'--panel-2': '#eceef3',
|
|
48
|
+
'--border': '#dcdfe7',
|
|
49
|
+
'--border-strong': '#7b8597',
|
|
50
|
+
'--text': '#171a21',
|
|
51
|
+
'--muted': '#5b6474',
|
|
52
|
+
'--accent': '#4f46e5',
|
|
53
|
+
'--accent-dim': '#a9a4f0',
|
|
54
|
+
'--on-accent': '#ffffff',
|
|
55
|
+
'--good': '#1b7f4b',
|
|
56
|
+
'--warn': '#8a5f0a',
|
|
57
|
+
'--bad': '#bf3a31',
|
|
58
|
+
'--info': '#0e6f8e',
|
|
59
|
+
'--app-bg': '#f4f5f8',
|
|
60
|
+
// Black shadows muddy a light surface; these carry the page's own ink.
|
|
61
|
+
'--shadow-1': '0 4px 14px rgba(23, 26, 33, 0.08)',
|
|
62
|
+
'--shadow-2': '0 8px 24px rgba(23, 26, 33, 0.10)',
|
|
63
|
+
'--shadow-3': '0 12px 32px rgba(23, 26, 33, 0.12)',
|
|
64
|
+
'--scrim': 'rgba(23, 26, 33, 0.32)',
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
export const night = {
|
|
68
|
+
name: 'valet night',
|
|
69
|
+
note: 'Dark. The same ink, lit from behind.',
|
|
70
|
+
scheme: 'dark',
|
|
71
|
+
tokens: {
|
|
72
|
+
'--bg': '#0c0f14',
|
|
73
|
+
'--panel': '#141820',
|
|
74
|
+
'--panel-2': '#1b2029',
|
|
75
|
+
'--border': '#262c37',
|
|
76
|
+
'--border-strong': '#616b7d',
|
|
77
|
+
'--text': '#e8eaf0',
|
|
78
|
+
'--muted': '#9ba4b5',
|
|
79
|
+
'--accent': '#8f88ff',
|
|
80
|
+
'--accent-dim': '#3f3a8f',
|
|
81
|
+
// Near-black on the pale indigo, 6.46:1. White would be 2.6:1.
|
|
82
|
+
'--on-accent': '#0d0b2e',
|
|
83
|
+
'--good': '#5fcb8f',
|
|
84
|
+
'--warn': '#e2ae58',
|
|
85
|
+
'--bad': '#f28b84',
|
|
86
|
+
'--info': '#57c4e8',
|
|
87
|
+
'--app-bg': '#0c0f14',
|
|
88
|
+
'--shadow-1': '0 6px 20px rgba(0, 0, 0, 0.28)',
|
|
89
|
+
'--shadow-2': '0 8px 28px rgba(0, 0, 0, 0.34)',
|
|
90
|
+
'--shadow-3': '0 10px 34px rgba(0, 0, 0, 0.38)',
|
|
91
|
+
'--scrim': 'rgba(0, 0, 0, 0.5)',
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
/** Keyed by the name `applyTheme` takes: `name` lowercased, spaces to hyphens. */
|
|
95
|
+
export const VALET_THEMES = { valet: light, 'valet-night': night };
|
|
96
|
+
/** valet, the product: its badge in `brandMarks.ts`, the identity above, the
|
|
97
|
+
* two palettes, and light until somebody picks. */
|
|
98
|
+
export const valet = {
|
|
99
|
+
name: 'valet',
|
|
100
|
+
identity: VALET_IDENTITY,
|
|
101
|
+
themes: VALET_THEMES,
|
|
102
|
+
defaultTheme: 'valet',
|
|
103
|
+
};
|
package/dist/styles/index.css
CHANGED
|
@@ -329,10 +329,10 @@ button[aria-busy='true']::after {
|
|
|
329
329
|
width: 40%;
|
|
330
330
|
background: currentColor;
|
|
331
331
|
opacity: 0.55;
|
|
332
|
-
animation:
|
|
332
|
+
animation: ds-busy 1.1s var(--ease) infinite;
|
|
333
333
|
}
|
|
334
334
|
|
|
335
|
-
@keyframes
|
|
335
|
+
@keyframes ds-busy {
|
|
336
336
|
0% { transform: translateX(-100%); }
|
|
337
337
|
100% { transform: translateX(350%); }
|
|
338
338
|
}
|
|
@@ -423,13 +423,12 @@ button.ghost:hover:not(:disabled) {
|
|
|
423
423
|
height: calc(var(--text-md) * 1.5);
|
|
424
424
|
width: auto;
|
|
425
425
|
color: var(--good);
|
|
426
|
-
/*
|
|
426
|
+
/* A product that animates its mark turns it about its own centre, which is
|
|
427
|
+
the ink box's; tf's cog does. */
|
|
427
428
|
transform-origin: center;
|
|
428
429
|
}
|
|
429
430
|
|
|
430
|
-
@keyframes
|
|
431
|
-
|
|
432
|
-
@keyframes tf-callout-clock {
|
|
431
|
+
@keyframes ds-callout-clock {
|
|
433
432
|
from { transform: scaleX(1); }
|
|
434
433
|
to { transform: scaleX(0); }
|
|
435
434
|
}
|
|
@@ -515,16 +514,16 @@ button.ghost:hover:not(:disabled) {
|
|
|
515
514
|
|
|
516
515
|
.icon-btn:hover { opacity: 1; color: var(--text); }
|
|
517
516
|
|
|
518
|
-
@keyframes
|
|
517
|
+
@keyframes ds-bar {
|
|
519
518
|
0% { transform: translateX(-110%); }
|
|
520
519
|
100% { transform: translateX(320%); }
|
|
521
520
|
}
|
|
522
521
|
|
|
523
|
-
@keyframes
|
|
522
|
+
@keyframes ds-toast-in {
|
|
524
523
|
from { opacity: 0; transform: translateY(8px); }
|
|
525
524
|
}
|
|
526
525
|
|
|
527
|
-
@keyframes
|
|
526
|
+
@keyframes ds-toast-out {
|
|
528
527
|
to { opacity: 0; transform: translateY(4px); }
|
|
529
528
|
}
|
|
530
529
|
|
|
@@ -545,7 +544,7 @@ button.ghost:hover:not(:disabled) {
|
|
|
545
544
|
.icon-btn:hover:not(:disabled) { color: var(--text); }
|
|
546
545
|
|
|
547
546
|
/* skeleton pulse ------------------------------------------------------ */
|
|
548
|
-
@keyframes
|
|
547
|
+
@keyframes ds-pulse {
|
|
549
548
|
0%, 100% { opacity: 0.25; }
|
|
550
549
|
50% { opacity: 0.65; }
|
|
551
550
|
}
|
|
@@ -684,7 +683,7 @@ input:active, textarea:active, select:active { transform: none; }
|
|
|
684
683
|
line-height: 1.5;
|
|
685
684
|
}
|
|
686
685
|
|
|
687
|
-
@keyframes
|
|
686
|
+
@keyframes ds-throb {
|
|
688
687
|
0%, 100% { opacity: 0.55; }
|
|
689
688
|
50% { opacity: 1; }
|
|
690
689
|
}
|
|
@@ -720,7 +719,7 @@ input:active, textarea:active, select:active { transform: none; }
|
|
|
720
719
|
/* A round is in flight. On the button that asks for one, because that is where
|
|
721
720
|
you are looking when you have just pressed it -- and not in the foot, which
|
|
722
721
|
would flicker every thirty seconds on a tile that is working perfectly. */
|
|
723
|
-
@keyframes
|
|
722
|
+
@keyframes ds-spin { to { transform: rotate(360deg); } }
|
|
724
723
|
|
|
725
724
|
@keyframes dock-working {
|
|
726
725
|
0%, 100% { opacity: .15; transform: scale(1); }
|
|
@@ -737,10 +736,10 @@ input:active, textarea:active, select:active { transform: none; }
|
|
|
737
736
|
The animation is not made more robust; the shape is made indifferent to it. */
|
|
738
737
|
.spin {
|
|
739
738
|
display: inline-block;
|
|
740
|
-
animation:
|
|
739
|
+
animation: ds-spin 900ms linear infinite;
|
|
741
740
|
}
|
|
742
741
|
|
|
743
|
-
@keyframes
|
|
742
|
+
@keyframes ds-spin { to { transform: rotate(360deg); } }
|
|
744
743
|
|
|
745
744
|
@media (prefers-reduced-motion: reduce) {
|
|
746
745
|
/* Still says "working" -- it just says it without moving. */
|
|
@@ -781,7 +780,7 @@ input:active, textarea:active, select:active { transform: none; }
|
|
|
781
780
|
border: 0;
|
|
782
781
|
}
|
|
783
782
|
|
|
784
|
-
@keyframes
|
|
783
|
+
@keyframes ds-skel {
|
|
785
784
|
from { opacity: 0.55; }
|
|
786
785
|
to { opacity: 1; }
|
|
787
786
|
}
|
|
@@ -1139,7 +1138,7 @@ button.danger:active:not(:disabled) {
|
|
|
1139
1138
|
transform-origin: left;
|
|
1140
1139
|
background: currentColor;
|
|
1141
1140
|
opacity: 0.5;
|
|
1142
|
-
animation:
|
|
1141
|
+
animation: ds-callout-clock linear forwards;
|
|
1143
1142
|
}
|
|
1144
1143
|
|
|
1145
1144
|
/* Held while the pointer is on it. A message that expires while you are reading
|
|
@@ -1561,10 +1560,10 @@ input[type='radio']:active:not(:disabled) {
|
|
|
1561
1560
|
width: 220%; height: 220%;
|
|
1562
1561
|
translate: -50% -50%;
|
|
1563
1562
|
background: conic-gradient(from 0deg, transparent 0deg, transparent 60deg, color-mix(in oklab, var(--accent) 70%, transparent) 180deg, transparent 300deg, transparent 360deg);
|
|
1564
|
-
animation:
|
|
1563
|
+
animation: ds-spin 1s linear infinite;
|
|
1565
1564
|
}
|
|
1566
1565
|
|
|
1567
|
-
@keyframes
|
|
1566
|
+
@keyframes ds-spin { to { rotate: 1turn; } }
|
|
1568
1567
|
|
|
1569
1568
|
@media (prefers-reduced-motion: reduce) {
|
|
1570
1569
|
.toggle-sweep::before { animation: none; background: color-mix(in oklab, var(--accent) 45%, transparent); }
|
|
@@ -1578,7 +1577,7 @@ input[type='radio']:active:not(:disabled) {
|
|
|
1578
1577
|
height: 8px;
|
|
1579
1578
|
border-radius: var(--radius-sm);
|
|
1580
1579
|
background: var(--muted);
|
|
1581
|
-
animation:
|
|
1580
|
+
animation: ds-pulse 1.4s ease-in-out infinite;
|
|
1582
1581
|
vertical-align: middle;
|
|
1583
1582
|
}
|
|
1584
1583
|
|
|
@@ -1619,7 +1618,7 @@ input[type='radio']:active:not(:disabled) {
|
|
|
1619
1618
|
|
|
1620
1619
|
/* Breathing rather than sliding. A sweep travelling across a skeleton implies
|
|
1621
1620
|
progress from left to right, and nothing here knows how far along it is. */
|
|
1622
|
-
.skel-pulse { animation:
|
|
1621
|
+
.skel-pulse { animation: ds-skel calc(var(--dur-slow) * 3) var(--ease) infinite alternate; }
|
|
1623
1622
|
|
|
1624
1623
|
@media (prefers-reduced-motion: reduce) {
|
|
1625
1624
|
/* Still distinguishable from a real block of colour, and still still. */
|
|
@@ -1761,7 +1760,7 @@ input[type='radio']:active:not(:disabled) {
|
|
|
1761
1760
|
something happening now. */
|
|
1762
1761
|
.rows-pick > .rows-row.is-loading {
|
|
1763
1762
|
border-color: var(--accent);
|
|
1764
|
-
animation:
|
|
1763
|
+
animation: ds-pulse 1.4s ease-in-out infinite;
|
|
1765
1764
|
}
|
|
1766
1765
|
|
|
1767
1766
|
/* And nothing else can be chosen while it happens: picking a second model
|
|
@@ -1973,7 +1972,7 @@ input[type='radio']:active:not(:disabled) {
|
|
|
1973
1972
|
happening" without claiming to know how much of it is left. */
|
|
1974
1973
|
.bar-indeterminate > i {
|
|
1975
1974
|
width: 35%;
|
|
1976
|
-
animation:
|
|
1975
|
+
animation: ds-bar 1.2s var(--ease) infinite;
|
|
1977
1976
|
}
|
|
1978
1977
|
|
|
1979
1978
|
@media (prefers-reduced-motion: reduce) {
|
|
@@ -2103,7 +2102,7 @@ input[type='radio']:active:not(:disabled) {
|
|
|
2103
2102
|
background: var(--panel);
|
|
2104
2103
|
box-shadow: var(--shadow-2);
|
|
2105
2104
|
font-size: var(--text-sm);
|
|
2106
|
-
animation:
|
|
2105
|
+
animation: ds-toast-in var(--dur-md) var(--ease);
|
|
2107
2106
|
}
|
|
2108
2107
|
|
|
2109
2108
|
/* The tone is the edge and the mark, in the tone's own colour. It used to be the
|
|
@@ -2133,7 +2132,7 @@ input[type='radio']:active:not(:disabled) {
|
|
|
2133
2132
|
/* `data-exiting` is React Aria's: it keeps a toast in the DOM for the length
|
|
2134
2133
|
of this animation after it is closed, then removes it, which is what the
|
|
2135
2134
|
`.leaving` class and a second timer used to do by hand. */
|
|
2136
|
-
.toast[data-exiting] { animation:
|
|
2135
|
+
.toast[data-exiting] { animation: ds-toast-out var(--dur-fast) var(--ease) forwards; }
|
|
2137
2136
|
|
|
2138
2137
|
@media (prefers-reduced-motion: reduce) {
|
|
2139
2138
|
.toast, /* `data-exiting` is React Aria's: it keeps a toast in the DOM for the length
|
|
@@ -2779,39 +2778,6 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
|
|
|
2779
2778
|
|
|
2780
2779
|
.table thead th[style*='right'] { text-align: right; }
|
|
2781
2780
|
|
|
2782
|
-
/* ---- ./brand.css ---- */
|
|
2783
|
-
/* Once the morph has finished (`Brand.tsx` adds the class at t = 1), the cog
|
|
2784
|
-
turns -- a revolution in 5 s, a tooth every 0.8 s: the pace of a
|
|
2785
|
-
mechanism, not a spinner. It was 2.4 s, which was too quick to William's
|
|
2786
|
-
eye once the teeth were six and fat. Off under reduced motion, where the
|
|
2787
|
-
mark never becomes a cog in the first place.
|
|
2788
|
-
|
|
2789
|
-
Not applied since 2026-09-03 -- William asked to try it without, and
|
|
2790
|
-
`Brand.tsx` says so where the class would go. The rule stays so that putting
|
|
2791
|
-
the class back is the whole change; it stays HERE, rather than in the app
|
|
2792
|
-
that first held it, because the component that would add the class is this
|
|
2793
|
-
package's. The keyframes are in `base.css` beside `.brand`. */
|
|
2794
|
-
.brand-turning { animation: brand-turn 5s linear infinite; }
|
|
2795
|
-
|
|
2796
|
-
@media (prefers-reduced-motion: reduce) {
|
|
2797
|
-
.brand-turning { animation: none; }
|
|
2798
|
-
}
|
|
2799
|
-
|
|
2800
|
-
.tf-dots { display: inline-flex; gap: var(--space-1); }
|
|
2801
|
-
|
|
2802
|
-
.tf-dots i {
|
|
2803
|
-
width: 5px; height: 5px; border-radius: 50%; background: currentColor;
|
|
2804
|
-
animation: tf-bounce 1.1s ease-in-out infinite;
|
|
2805
|
-
}
|
|
2806
|
-
|
|
2807
|
-
.tf-dots i:nth-child(2) { animation-delay: 0.15s; }
|
|
2808
|
-
|
|
2809
|
-
.tf-dots i:nth-child(3) { animation-delay: 0.3s; }
|
|
2810
|
-
|
|
2811
|
-
@media (prefers-reduced-motion: reduce) {
|
|
2812
|
-
.tf-dots i { animation: none; opacity: 0.6; }
|
|
2813
|
-
}
|
|
2814
|
-
|
|
2815
2781
|
/* ---- ./sizegrid.css ---- */
|
|
2816
2782
|
/* Tile size, picked as a rectangle rather than read as a notation. */
|
|
2817
2783
|
.size-grid-wrap { display: flex; align-items: center; gap: var(--space-2); }
|