@apliteni/apliteni-ui 0.12.0 → 0.23.1

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.
@@ -13,18 +13,18 @@
13
13
  color: var(--dim);
14
14
  }
15
15
  .ui-callout__icon { width: 18px; height: 18px; flex: none; margin-top: 1px; color: var(--muted); }
16
- .ui-callout__icon svg { width: 100%; height: 100%; stroke: currentColor; fill: none; stroke-width: 1.8; }
16
+ .ui-callout__icon svg { width: 100%; height: 100%; stroke: currentColor; fill: none; stroke-width: 2.1; } /* 2.1 of a 24 box drawn at 18px = 1.58 CSS px, which is what buys the 3:1 graphic bar rather than the 4.5:1 text one. docs/specification.md#icons-and-glyphs */
17
17
  .ui-callout b { color: var(--text); font-weight: var(--weight-semibold); }
18
18
  .ui-callout code { font-family: var(--font-mono); font-size: 0.92em; color: var(--text); }
19
19
 
20
20
  .ui-callout--info { background: var(--glow-cyan); }
21
- .ui-callout--info .ui-callout__icon { color: var(--cyan); }
21
+ .ui-callout--info .ui-callout__icon { color: var(--chip-info-ink); } /* the chip inks, not the raw signals: in light the raw signal cleared the graphic bar by a tenth on its own wash and read as a smudge, and a stroke is read the way text is. The two are the same value in dark by construction, so only light moves. #206 */
22
22
  .ui-callout--success { background: var(--glow-green); }
23
- .ui-callout--success .ui-callout__icon { color: var(--green); }
23
+ .ui-callout--success .ui-callout__icon { color: var(--chip-success-ink); }
24
24
  .ui-callout--warn { background: color-mix(in srgb, var(--amber) 13%, transparent); }
25
- .ui-callout--warn .ui-callout__icon { color: var(--amber); }
25
+ .ui-callout--warn .ui-callout__icon { color: var(--chip-warn-ink); }
26
26
  .ui-callout--danger { background: var(--glow-pink); }
27
- .ui-callout--danger .ui-callout__icon { color: var(--pink); }
27
+ .ui-callout--danger .ui-callout__icon { color: var(--chip-danger-ink); }
28
28
 
29
29
  /* ----------------------------------------------------------------------------
30
30
  * Toast — a floating, dismissible notification.
@@ -42,9 +42,9 @@
42
42
  color: var(--text);
43
43
  font-size: var(--text-base);
44
44
  width: 100%;
45
- max-width: 400px;
45
+ max-width: var(--panel-md);
46
46
  overflow: hidden; /* clips the timer bar + left marker to the rounded corners */
47
- animation: ui-toast-in 0.28s cubic-bezier(0.22, 1, 0.36, 1) both;
47
+ animation: ui-toast-in var(--dur-med) var(--ease-out) both;
48
48
  }
49
49
 
50
50
  /* status → the paint tokens every style below consumes.
@@ -57,15 +57,15 @@
57
57
  fill is: the accent is a graphic colour, sized for a 3px rule and a 22px
58
58
  circle, and in the light theme it is not a colour text can be set in. The
59
59
  trailing action is the one part of a toast that is BOTH the status colour and
60
- a piece of text, so it takes the chip inks, which are the text-grade version
61
- of the same five statuses. In the dark theme the two are the same value by
62
- construction --chip-success-ink IS --green there so this splits the light
63
- theme apart and leaves the dark one exactly as it was. */
60
+ a piece of text, so it takes the chip inks the text-grade version of the
61
+ same five statuses, and the same value as the accent in dark by construction.
62
+ neutral's --toast-on is --signal-solid-ink because the neutral circle IS
63
+ --signal-solid-neutral: one fill, so one ink, chosen once. #206 */
64
64
  .ui-toast--success { --toast-accent: var(--green); --toast-action-ink: var(--chip-success-ink); --toast-glow: var(--glow-green); --toast-on: var(--signal-contrast); --toast-solid: var(--signal-solid-success); }
65
65
  .ui-toast--danger { --toast-accent: var(--pink); --toast-action-ink: var(--chip-danger-ink); --toast-glow: var(--glow-pink); --toast-on: var(--danger-contrast); --toast-solid: var(--signal-solid-danger); }
66
66
  .ui-toast--warn { --toast-accent: var(--amber); --toast-action-ink: var(--chip-warn-ink); --toast-glow: color-mix(in srgb, var(--amber) 14%, transparent); --toast-on: var(--signal-contrast); --toast-solid: var(--signal-solid-warn); }
67
67
  .ui-toast--info { --toast-accent: var(--cyan); --toast-action-ink: var(--chip-info-ink); --toast-glow: var(--glow-cyan); --toast-on: var(--signal-contrast); --toast-solid: var(--signal-solid-info); }
68
- .ui-toast--neutral { --toast-accent: var(--muted); --toast-action-ink: var(--muted); --toast-glow: var(--surface-2); --toast-on: var(--strong); --toast-solid: var(--signal-solid-neutral); }
68
+ .ui-toast--neutral { --toast-accent: var(--muted); --toast-action-ink: var(--muted); --toast-glow: var(--surface-2); --toast-on: var(--signal-solid-ink); --toast-solid: var(--signal-solid-neutral); }
69
69
 
70
70
  /* left status marker (soft + outline; solid is already a full fill) */
71
71
  .ui-toast--soft::before,
@@ -75,7 +75,7 @@
75
75
  }
76
76
 
77
77
  .ui-toast__icon { width: 22px; height: 22px; flex: none; margin-top: 1px; display: grid; place-items: center; border-radius: 50%; background: var(--toast-accent); color: var(--toast-on); }
78
- .ui-toast__icon svg { width: 13px; height: 13px; stroke: currentColor; fill: none; stroke-width: 2; }
78
+ .ui-toast__icon svg { width: 13px; height: 13px; stroke: currentColor; fill: none; stroke-width: 2.8; } /* 2.8 of a 24 box drawn at 13px = 1.52 CSS px. The circle has no room for a bigger glyph, so the weight comes out of the stroke. docs/specification.md#icons-and-glyphs */
79
79
  .ui-toast__body { flex: 1; min-width: 0; }
80
80
  .ui-toast__title { font-weight: var(--weight-medium); color: var(--strong); }
81
81
  .ui-toast__text { color: var(--dim); }
@@ -91,13 +91,32 @@
91
91
  .ui-toast__action { flex: none; align-self: center; background: none; border: 0; padding: 4px 9px; border-radius: var(--radius-xs); font: inherit; font-weight: var(--weight-medium); color: var(--toast-action-ink); cursor: pointer; }
92
92
  .ui-toast__action:hover { background: color-mix(in srgb, var(--bg) 55%, transparent); }
93
93
 
94
- .ui-toast__close { flex: none; align-self: flex-start; background: none; border: 0; color: var(--muted); cursor: pointer; padding: 2px; display: grid; place-items: center; }
95
- .ui-toast__close svg { width: 15px; height: 15px; stroke: currentColor; fill: none; stroke-width: 1.8; }
94
+ .ui-toast__close { flex: none; align-self: flex-start; background: none; border: 0; color: var(--muted); cursor: pointer; padding: 2px; display: grid; place-items: center; position: relative; }
95
+ .ui-toast__close svg { width: 15px; height: 15px; stroke: currentColor; fill: none; stroke-width: 2.5; }
96
+ /* The target, not the ink. WCAG 2.5.8 measures what a pointer can land on, and
97
+ the drawn button is 19x19 — a 15px glyph in 2px of padding. Growing the box
98
+ to 24 would push the glyph 2.5px off the corner it is aligned to, so the
99
+ TARGET grows instead and the glyph does not move at all.
100
+ Centred, so the overhang is 2.5px on every side. It has 12px of flex gap to
101
+ .ui-toast__action beside it and 12px between stacked toasts, so it reaches no
102
+ neighbouring target; .ui-toast's overflow: hidden clips at 15px of padding,
103
+ well outside it. Measured, not asserted:
104
+ stories/guidelines/accessibility-floor.test.js reads this rule out of the
105
+ sheet and reports the target it makes. #219 */
106
+ .ui-toast__close::before {
107
+ content: '';
108
+ position: absolute;
109
+ left: 50%;
110
+ top: 50%;
111
+ width: 24px;
112
+ height: 24px;
113
+ transform: translate(-50%, -50%);
114
+ }
96
115
  .ui-toast__close:hover { color: var(--text); }
97
116
 
98
117
  /* auto-dismiss progress bar — .is-running is added by wireToastStack() */
99
118
  .ui-toast__timer { position: absolute; left: 0; bottom: 0; height: 3px; width: 100%; background: var(--toast-accent); opacity: 0.85; transform-origin: left; }
100
- .ui-toast__timer.is-running { animation: ui-toast-timer var(--toast-dur, 5s) linear forwards; }
119
+ .ui-toast__timer.is-running { animation: ui-toast-timer var(--toast-dur, 5s) linear forwards; /* motion: ambient — the bar spends the toast's dismiss timer, which the caller sets */ }
101
120
 
102
121
  /* style: soft — tinted surface */
103
122
  .ui-toast--soft { background: var(--toast-glow); box-shadow: var(--shadow-sm); }
@@ -142,7 +161,7 @@
142
161
  @keyframes ui-toast-in { from { opacity: 0; transform: translateX(18px); } to { opacity: 1; transform: none; } }
143
162
  @keyframes ui-toast-out { to { opacity: 0; transform: translateX(24px); } }
144
163
  @keyframes ui-toast-timer { from { transform: scaleX(1); } to { transform: scaleX(0); } }
145
- .ui-toast.is-leaving { animation: ui-toast-out 0.2s ease forwards; }
164
+ .ui-toast.is-leaving { animation: ui-toast-out var(--dur-fast) var(--ease-sharp) forwards; }
146
165
 
147
166
  @media (prefers-reduced-motion: reduce) {
148
167
  .ui-toast, .ui-toast.is-leaving { animation: none; }
@@ -150,7 +169,7 @@
150
169
  }
151
170
 
152
171
  /* stack container for queued toasts */
153
- .ui-toast-stack { display: flex; flex-direction: column; gap: 12px; width: 400px; max-width: 100%; }
172
+ .ui-toast-stack { display: flex; flex-direction: column; gap: 12px; width: var(--panel-md); max-width: 100%; }
154
173
 
155
174
  /* Success panel — centered confirmation, used after form submit */
156
175
  .ui-success {
@@ -89,4 +89,4 @@
89
89
  color: var(--accent);
90
90
  flex: none;
91
91
  }
92
- .ui-card__icon svg { width: 19px; height: 19px; stroke: currentColor; fill: none; stroke-width: 1.7; }
92
+ .ui-card__icon svg { width: 19px; height: 19px; stroke: currentColor; fill: none; stroke-width: 2; }
@@ -42,10 +42,16 @@
42
42
  align-items: center;
43
43
  gap: 6px;
44
44
  padding: 2px 4px;
45
+ /* WCAG 2.5.8's 24 CSS px target floor, taken as a real box rather than as an
46
+ overlay: 2 + a 12px label at line-height 1.62 + 2 came to 23.44, so the
47
+ drawn button grows by 0.56px and the bar it sits in grows with it. Nothing
48
+ smaller than a rounding error moves, which is why this one is not a hit
49
+ area. Measured in stories/guidelines/accessibility-floor.test.js. #219 */
50
+ min-height: 24px;
45
51
  transition: color var(--dur-fast) var(--ease);
46
52
  }
47
53
  .ui-snippet__copy:hover { color: var(--accent); }
48
- .ui-snippet__copy svg { width: 13px; height: 13px; stroke: currentColor; fill: none; stroke-width: 1.8; }
54
+ .ui-snippet__copy svg { width: 13px; height: 13px; stroke: currentColor; fill: none; stroke-width: 2.8; }
49
55
 
50
56
  /* Shell token highlighting (matches viz/account.mjs classes) */
51
57
  .ui-snippet pre .k { color: var(--purple-mid); font-weight: var(--weight-medium); }
@@ -19,7 +19,7 @@
19
19
  --confirm-surface: var(--surface-2);
20
20
  --confirm-shadow: var(--shadow-lg);
21
21
  --confirm-scrim: var(--scrim);
22
- --confirm-w: 420px;
22
+ --confirm-w: var(--panel-md);
23
23
 
24
24
  position: fixed;
25
25
  inset: 0;
@@ -14,7 +14,7 @@
14
14
  --drawer-surface: var(--surface-2);
15
15
  --drawer-shadow: var(--shadow-lg);
16
16
  --drawer-scrim: var(--scrim);
17
- --drawer-w: 420px; /* left|right panels: width */
17
+ --drawer-w: var(--panel-md); /* left|right panels: width */
18
18
  --drawer-h: 45vh; /* top|bottom panels: height */
19
19
 
20
20
  position: fixed;
@@ -102,9 +102,9 @@
102
102
 
103
103
  /* -- Sizes ----------------------------------------------------------------- */
104
104
  /* left|right → width; top|bottom → height. Same names, axis chosen by side. */
105
- .ui-drawer--right.ui-drawer--sm, .ui-drawer--left.ui-drawer--sm { --drawer-w: 320px; }
106
- .ui-drawer--right.ui-drawer--md, .ui-drawer--left.ui-drawer--md { --drawer-w: 420px; }
107
- .ui-drawer--right.ui-drawer--lg, .ui-drawer--left.ui-drawer--lg { --drawer-w: 560px; }
105
+ .ui-drawer--right.ui-drawer--sm, .ui-drawer--left.ui-drawer--sm { --drawer-w: var(--panel-sm); }
106
+ .ui-drawer--right.ui-drawer--md, .ui-drawer--left.ui-drawer--md { --drawer-w: var(--panel-md); }
107
+ .ui-drawer--right.ui-drawer--lg, .ui-drawer--left.ui-drawer--lg { --drawer-w: var(--panel-lg); }
108
108
  .ui-drawer--top.ui-drawer--sm, .ui-drawer--bottom.ui-drawer--sm { --drawer-h: 30vh; }
109
109
  .ui-drawer--top.ui-drawer--md, .ui-drawer--bottom.ui-drawer--md { --drawer-h: 45vh; }
110
110
  .ui-drawer--top.ui-drawer--lg, .ui-drawer--bottom.ui-drawer--lg { --drawer-h: 72vh; }
@@ -138,11 +138,14 @@
138
138
  background: transparent;
139
139
  border: 1px solid transparent;
140
140
  border-radius: var(--radius-sm);
141
- transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease;
141
+ transition:
142
+ color var(--dur-fast) var(--ease),
143
+ background var(--dur-fast) var(--ease),
144
+ border-color var(--dur-fast) var(--ease);
142
145
  }
143
146
  .ui-drawer__close:hover { color: var(--strong); background: var(--surface); }
144
147
  .ui-drawer__close:focus-visible { outline: none; box-shadow: var(--ring); border-color: var(--accent); }
145
- .ui-drawer__close svg { width: 18px; height: 18px; stroke: currentColor; fill: none; stroke-width: 1.8; }
148
+ .ui-drawer__close svg { width: 18px; height: 18px; stroke: currentColor; fill: none; stroke-width: 2.1; }
146
149
 
147
150
  .ui-drawer__body {
148
151
  flex: 1 1 auto;
@@ -21,7 +21,7 @@
21
21
  border: 1px solid var(--border);
22
22
  border-radius: var(--radius-pill);
23
23
  padding: 7px 11px 7px 13px;
24
- transition: border-color 0.2s ease, background 0.2s ease;
24
+ transition: border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
25
25
  }
26
26
  .ui-dropdown__trigger:hover { border-color: var(--accent); }
27
27
  .ui-dropdown__trigger:focus-visible { outline: none; box-shadow: var(--ring); border-color: var(--accent); }
@@ -34,7 +34,7 @@
34
34
  border-right: 1.6px solid var(--muted);
35
35
  border-bottom: 1.6px solid var(--muted);
36
36
  transform: rotate(45deg) translateY(-1px);
37
- transition: transform 0.2s ease;
37
+ transition: transform var(--dur-fast) var(--ease);
38
38
  flex: none;
39
39
  }
40
40
  .ui-dropdown.open .ui-dropdown__chevron { transform: rotate(-135deg) translateY(2px); }
@@ -53,7 +53,12 @@
53
53
  opacity: 0;
54
54
  visibility: hidden;
55
55
  transform: translateY(-6px);
56
- transition: opacity 0.18s ease, transform 0.18s ease, visibility 0.18s ease;
56
+ transition:
57
+ opacity var(--dur-med) var(--ease),
58
+ transform var(--dur-med) var(--ease),
59
+ /* `linear`, not var(--ease): visibility is discrete, so easing it buys nothing and a
60
+ curve leaving [0, 1] flips it mid-fade. Same rule the drawer states at length. */
61
+ visibility var(--dur-med) linear;
57
62
  z-index: var(--z-dropdown);
58
63
  }
59
64
  .ui-dropdown__panel.is-end { left: auto; right: 0; }
@@ -70,7 +75,7 @@
70
75
  cursor: pointer;
71
76
  text-decoration: none;
72
77
  color: var(--text);
73
- transition: background 0.15s ease;
78
+ transition: background var(--dur-fast) var(--ease);
74
79
  }
75
80
  .ui-dropdown__item:hover { background: var(--surface); }
76
81
  .ui-dropdown__item:focus-visible { outline: none; background: var(--surface); box-shadow: var(--ring); }
@@ -78,11 +83,11 @@
78
83
  .ui-dropdown__label { font-family: var(--font-sans); font-size: 12.5px; color: var(--strong); font-weight: 600; letter-spacing: 0.01em; }
79
84
  .ui-dropdown__desc { font-size: 11px; color: var(--muted); line-height: 1.35; }
80
85
  .ui-dropdown__ic { display: inline-flex; flex: none; color: var(--muted); }
81
- .ui-dropdown__ic svg { width: 18px; height: 18px; stroke: currentColor; fill: none; stroke-width: 1.8; }
86
+ .ui-dropdown__ic svg { width: 18px; height: 18px; stroke: currentColor; fill: none; stroke-width: 2.1; }
82
87
 
83
88
  /* Trailing selected tick (listbox variant) */
84
89
  .ui-dropdown__tick { margin-left: auto; flex: none; display: none; color: var(--accent); }
85
- .ui-dropdown__tick svg { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 2.2; }
90
+ .ui-dropdown__tick svg { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 2.4; }
86
91
  .ui-dropdown__item.is-selected .ui-dropdown__tick { display: inline-flex; }
87
92
  .ui-dropdown__item.is-selected .ui-dropdown__label { color: var(--accent); }
88
93
 
@@ -106,7 +111,9 @@
106
111
  .ui-dropdown__badge.is-accent { color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, transparent); }
107
112
 
108
113
  /* Disabled + danger rows */
109
- .ui-dropdown__item.is-disabled { opacity: 0.45; cursor: not-allowed; pointer-events: none; }
114
+ .ui-dropdown__item.is-disabled { color: var(--disabled-ink); cursor: not-allowed; pointer-events: none; }
115
+ .ui-dropdown__item.is-disabled .ui-dropdown__label,
116
+ .ui-dropdown__item.is-disabled .ui-dropdown__desc { color: inherit; }
110
117
  .ui-dropdown__item.is-danger .ui-dropdown__label { color: var(--muted); }
111
118
  .ui-dropdown__item.is-danger:hover .ui-dropdown__label { color: var(--pink); }
112
119
 
@@ -56,7 +56,7 @@
56
56
  }
57
57
  .ui-empty__sub {
58
58
  font-size: var(--text-sm);
59
- max-width: 340px;
59
+ max-width: var(--prose-caption);
60
60
  margin: 0;
61
61
  line-height: 1.55;
62
62
  }
@@ -27,7 +27,10 @@
27
27
  box-shadow: 0 10px 26px var(--ui-fb-pill-glow),
28
28
  0 2px 6px color-mix(in srgb, var(--shadow-ink) 28%, transparent),
29
29
  inset 0 1px 0 color-mix(in srgb, var(--sheen) 28%, transparent);
30
- transition: transform .2s var(--easing-spring, cubic-bezier(.34,1.56,.64,1)), opacity .16s ease, box-shadow .2s ease;
30
+ transition:
31
+ transform var(--dur-fast) var(--ease-spring),
32
+ opacity var(--dur-fast) var(--ease),
33
+ box-shadow var(--dur-fast) var(--ease);
31
34
  }
32
35
  .ui-fbpill.show { opacity: 1; pointer-events: auto; transform: translate(-50%, -100%) translateY(-9px) scale(1); }
33
36
  .ui-fbpill:hover {
@@ -37,19 +40,19 @@
37
40
  }
38
41
  .ui-fbpill.show:hover { transform: translate(-50%, -100%) translateY(-12px) scale(1.04); }
39
42
  .ui-fbpill:active { transform: translate(-50%, -100%) translateY(-8px) scale(.98); }
40
- .ui-fbpill svg { width: 16px; height: 16px; color: currentColor; filter: drop-shadow(0 1px 1px color-mix(in srgb, var(--shadow-ink) 25%, transparent)); }
43
+ .ui-fbpill svg { width: 16px; height: 16px; stroke-width: 2.4; color: currentColor; filter: drop-shadow(0 1px 1px color-mix(in srgb, var(--shadow-ink) 25%, transparent)); }
41
44
  .ui-fbpill::before {
42
45
  content: ""; position: absolute; top: 0; bottom: 0; left: -60%; width: 45%;
43
46
  background: linear-gradient(100deg, transparent, color-mix(in srgb, var(--sheen) 55%, transparent), transparent); transform: skewX(-18deg);
44
47
  }
45
- .ui-fbpill.show::before { animation: uiFbSheen 1.15s ease .12s 1; }
46
- .ui-fbpill:hover::before { animation: uiFbSheen .7s ease 1; }
48
+ .ui-fbpill.show::before { animation: uiFbSheen 1.15s ease .12s 1; } /* motion: ambient — a decorative sheen crossing the pill; its length is the width it travels, not a response */
49
+ .ui-fbpill:hover::before { animation: uiFbSheen .7s ease 1; } /* motion: ambient — the same sweep, quicker on hover so it finishes inside the gesture */
47
50
  @keyframes uiFbSheen { 0% { left: -60%; } 100% { left: 130%; } }
48
51
 
49
52
  /* Scrim behind the open composer */
50
53
  .ui-fbscrim {
51
54
  position: fixed; inset: 0; z-index: 70; background: var(--scrim);
52
- opacity: 0; pointer-events: none; transition: opacity .2s ease;
55
+ opacity: 0; pointer-events: none; transition: opacity var(--dur-med) var(--ease);
53
56
  }
54
57
  .ui-fbscrim.show { opacity: 1; pointer-events: auto; }
55
58
 
@@ -58,47 +61,53 @@
58
61
  position: fixed; z-index: 71; left: 50%; bottom: 26px; transform: translateX(-50%) translateY(14px);
59
62
  width: min(520px, calc(100vw - 32px)); background: var(--bg-elevated); border-radius: 18px;
60
63
  box-shadow: var(--shadow-lg); opacity: 0; pointer-events: none;
61
- transition: opacity .2s ease, transform .2s ease; overflow: hidden;
64
+ transition: opacity var(--dur-med) var(--ease), transform var(--dur-med) var(--ease); overflow: hidden;
62
65
  }
63
66
  .ui-fbcomposer.show { opacity: 1; pointer-events: auto; transform: translateX(-50%) translateY(0); }
64
67
 
65
68
  .ui-fbc__head { display: flex; align-items: center; gap: 12px; padding: 17px 20px 0; }
66
69
  .ui-fbc__chip { display: inline-flex; align-items: center; gap: 8px; color: var(--accent); font-size: 13px; font-weight: var(--weight-semibold); letter-spacing: .01em; white-space: nowrap; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
67
- .ui-fbc__chip svg { width: 14px; height: 14px; flex: none; opacity: .85; }
70
+ .ui-fbc__chip svg { width: 14px; height: 14px; flex: none; opacity: .85; stroke-width: 2.7; }
68
71
  .ui-fbc__x { margin-left: auto; background: var(--surface-2); color: var(--dim); border: 0; width: 28px; height: 28px; border-radius: 8px; cursor: pointer; display: grid; place-items: center; flex: none; }
69
72
  .ui-fbc__x:hover { color: var(--strong); }
70
- .ui-fbc__x svg { width: 14px; height: 14px; }
73
+ .ui-fbc__x svg { width: 14px; height: 14px; stroke-width: 2.7; }
71
74
  .ui-fbc__quote { margin: 14px 20px 0; background: var(--surface-2); border-radius: 12px; padding: 12px 15px; display: flex; gap: 11px; }
72
75
  .ui-fbc__qm { color: var(--accent); font-size: 22px; font-weight: var(--weight-bold); line-height: .9; flex: none; opacity: .55; }
73
76
  .ui-fbc__quote q { quotes: none; color: var(--dim); font-style: italic; font-size: 13.5px; line-height: 1.55; }
74
77
  .ui-fbc__body { padding: 14px 20px 0; }
75
78
  .ui-fbcomposer textarea {
76
79
  width: 100%; min-height: 96px; resize: vertical; font-family: var(--font-sans); font-size: 14.5px; line-height: 1.55;
77
- color: var(--strong); background: var(--surface-2); border: 0; border-radius: 12px; padding: 13px 15px; transition: box-shadow .18s ease;
80
+ color: var(--strong); background: var(--surface-2); border: 0; border-radius: 12px; padding: 13px 15px; transition: box-shadow var(--dur-fast) var(--ease);
78
81
  }
79
82
  .ui-fbcomposer textarea::placeholder { color: var(--muted); }
80
83
  .ui-fbcomposer textarea:focus { outline: none; box-shadow: 0 0 0 2px var(--accent); }
81
84
  .ui-fbc__foot { display: flex; align-items: center; justify-content: flex-end; gap: 12px; padding: 16px 20px 18px; }
82
85
 
83
- .ui-fbbtn { font-family: var(--font-sans); font-size: 13.5px; font-weight: var(--weight-semibold); border-radius: 10px; padding: 9px 17px; cursor: pointer; border: 0; transition: filter .15s, background .15s, color .15s; }
86
+ .ui-fbbtn { font-family: var(--font-sans); font-size: 13.5px; font-weight: var(--weight-semibold); border-radius: 10px; padding: 9px 17px; cursor: pointer; border: 0; transition: filter var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease); }
84
87
  .ui-fbbtn.ghost { background: transparent; color: var(--dim); }
85
88
  .ui-fbbtn.ghost:hover { color: var(--strong); background: var(--surface-2); }
86
89
  .ui-fbbtn.primary { background: var(--accent-strong); color: var(--accent-contrast); display: inline-flex; align-items: center; gap: 9px; }
87
90
  .ui-fbbtn.primary:hover { filter: brightness(1.08); }
88
- .ui-fbbtn.primary:disabled { opacity: .6; cursor: default; filter: none; }
91
+ /* Same flat pair as .ui-btn:disabled, for the same reason (#220). */
92
+ .ui-fbbtn.primary:disabled {
93
+ background: var(--disabled-surface);
94
+ color: var(--disabled-ink);
95
+ cursor: default;
96
+ filter: none;
97
+ }
89
98
  .ui-fbc__send { display: inline-flex; align-items: center; justify-content: center; gap: 8px; }
90
- .ui-fbspin { flex: none; width: 14px; height: 14px; box-sizing: border-box; border-radius: 50%; border: 2px solid color-mix(in srgb, currentColor 40%, transparent); border-top-color: currentColor; animation: uiFbSpin .7s linear infinite; }
99
+ .ui-fbspin { flex: none; width: 14px; height: 14px; box-sizing: border-box; border-radius: 50%; border: 2px solid color-mix(in srgb, currentColor 40%, transparent); border-top-color: currentColor; animation: uiFbSpin .7s linear infinite; } /* motion: ambient — a spinner turns until the request answers */
91
100
  @keyframes uiFbSpin { to { transform: rotate(360deg); } }
92
101
 
93
102
  /* Success state */
94
103
  .ui-fbc__done { padding: 30px 26px 24px; text-align: center; background: radial-gradient(112% 60% at 50% 0%, var(--glow-green), transparent 62%); }
95
104
  .ui-fbc__ck { width: 88px; height: 88px; margin: 0 auto 13px; display: grid; place-items: center; }
96
- .ui-fbck { width: 88px; height: 88px; overflow: visible; display: block; transform-origin: center; animation: uiFbCkSpring 1s cubic-bezier(.22,1,.3,1) both; }
105
+ .ui-fbck { width: 88px; height: 88px; overflow: visible; display: block; transform-origin: center; animation: uiFbCkSpring 1s cubic-bezier(.22,1,.3,1) both; } /* motion: choreographed — the frame the mark and the comet are timed against */
97
106
  .ui-fbck-t { fill: none; stroke: var(--surface-2); stroke-width: 9; stroke-linecap: round; stroke-linejoin: round; }
98
- .ui-fbck-m { fill: none; stroke: var(--green); stroke-width: 9; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 118; stroke-dashoffset: 118; animation: uiFbCkDraw .6s cubic-bezier(.5,0,.3,1) .1s forwards; }
99
- .ui-fbck-s { fill: none; stroke: var(--sheen); stroke-width: 9; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 15 400; stroke-dashoffset: 133; filter: drop-shadow(0 0 8px var(--green)) drop-shadow(0 0 15px var(--green)); opacity: 0; animation: uiFbCkComet .6s cubic-bezier(.5,0,.3,1) .1s forwards; }
107
+ .ui-fbck-m { fill: none; stroke: var(--green); stroke-width: 9; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 118; stroke-dashoffset: 118; animation: uiFbCkDraw .6s cubic-bezier(.5,0,.3,1) .1s forwards; /* motion: choreographed — the mark draws while the spring is still settling */ }
108
+ .ui-fbck-s { fill: none; stroke: var(--sheen); stroke-width: 9; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 15 400; stroke-dashoffset: 133; filter: drop-shadow(0 0 8px var(--green)) drop-shadow(0 0 15px var(--green)); opacity: 0; animation: uiFbCkComet .6s cubic-bezier(.5,0,.3,1) .1s forwards; /* motion: choreographed — the comet rides the mark, so it shares its duration and delay exactly */ }
100
109
  .ui-fbc__done h4 { color: var(--strong); font-size: 19px; font-weight: var(--weight-semibold); letter-spacing: -.01em; margin-bottom: 7px; }
101
- .ui-fbc__done p { color: var(--dim); font-size: 13.5px; line-height: 1.55; max-width: 38ch; margin: 0 auto; }
110
+ .ui-fbc__done p { color: var(--dim); font-size: 13.5px; line-height: 1.55; max-width: var(--prose-caption); margin: 0 auto; }
102
111
  .ui-fbc__done p b { color: var(--text); font-weight: var(--weight-semibold); }
103
112
  .ui-fbc__done .row { margin-top: 18px; display: flex; gap: 10px; justify-content: center; }
104
113
  @keyframes uiFbCkDraw { to { stroke-dashoffset: 0; } }
@@ -21,7 +21,7 @@
21
21
  }
22
22
  .ui-footer__in {
23
23
  width: 100%;
24
- max-width: 1180px;
24
+ max-width: var(--container);
25
25
  margin: 0 auto;
26
26
  padding: 40px clamp(14px, 2.4vw, 26px) 30px;
27
27
  }
@@ -34,6 +34,10 @@
34
34
  flex-wrap: wrap;
35
35
  padding-bottom: 30px;
36
36
  }
37
+ /* Under --panel-sm, and deliberately: this is a flex TRACK in a wrapping
38
+ row, not a panel and not a paragraph — its width is what decides when the
39
+ footer breaks into columns, so it answers to the row rather than to a
40
+ scale. The one width below the floor the measure gate draws. See #208. */
37
41
  .ui-footer__brand { max-width: 300px; }
38
42
  .ui-footer .ui-footer__brand .brand { font-size: 15px; }
39
43
  .ui-footer__tagline {
@@ -52,7 +56,10 @@
52
56
  border: 1px solid var(--footer-border);
53
57
  color: var(--footer-link);
54
58
  background: var(--surface);
55
- transition: color 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
59
+ transition:
60
+ color var(--dur-fast) var(--ease),
61
+ border-color var(--dur-fast) var(--ease),
62
+ transform var(--dur-fast) var(--ease);
56
63
  }
57
64
  .ui-footer__social:hover { color: var(--accent); border-color: var(--accent); transform: translateY(-1px); }
58
65
  .ui-footer__social svg { width: 17px; height: 17px; display: block; }
@@ -76,7 +83,7 @@
76
83
  font-size: 13.5px;
77
84
  color: var(--footer-link);
78
85
  text-decoration: none;
79
- transition: color 0.15s ease;
86
+ transition: color var(--dur-fast) var(--ease);
80
87
  }
81
88
  .ui-footer__links a:hover { color: var(--footer-link-hover); }
82
89
 
@@ -94,7 +101,7 @@
94
101
  }
95
102
  .ui-footer__legal { color: var(--footer-link); }
96
103
  .ui-footer__legal-links { display: flex; gap: 22px; flex-wrap: wrap; }
97
- .ui-footer__legal-links a { color: var(--footer-link); text-decoration: none; transition: color 0.15s ease; }
104
+ .ui-footer__legal-links a { color: var(--footer-link); text-decoration: none; transition: color var(--dur-fast) var(--ease); }
98
105
  .ui-footer__legal-links a:hover { color: var(--footer-link-hover); }
99
106
  .ui-footer__switcher { display: inline-flex; align-items: center; gap: 12px; margin-left: auto; }
100
107
 
@@ -110,13 +117,15 @@
110
117
  .ui-footer--app .ui-footer__in { max-width: none; padding: 12px clamp(14px, 2.4vw, 22px); }
111
118
  .ui-footer--app .ui-footer__bar { padding-top: 0; border-top: 0; font-size: 12.5px; }
112
119
 
113
- /* Responsive — columns collapse to a stacked layout on narrow screens. */
120
+ /* Responsive — columns collapse to a stacked layout on narrow screens. Two of
121
+ the kit's three steps, and the second one is the phone step rather than a
122
+ fourth value of the footer's own: docs/specification.md#breakpoints. */
114
123
  @media (max-width: 720px) {
115
124
  .ui-footer__top { flex-direction: column; gap: 30px; }
116
125
  .ui-footer__nav { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 26px 30px; }
117
126
  .ui-footer__brand { max-width: none; }
118
127
  }
119
- @media (max-width: 460px) {
128
+ @media (max-width: 560px) {
120
129
  .ui-footer__nav { grid-template-columns: 1fr; }
121
130
  .ui-footer__bar { flex-direction: column; align-items: flex-start; }
122
131
  .ui-footer__switcher { margin-left: 0; }
@@ -15,6 +15,7 @@
15
15
  .ui-field__req { color: var(--pink); margin-left: 4px; }
16
16
  .ui-field__hint { color: var(--muted); font-size: var(--text-sm); margin-top: 7px; line-height: 1.5; }
17
17
  .ui-field__error { color: var(--pink); font-size: var(--text-sm); margin-top: 7px; display: flex; align-items: center; gap: 6px; }
18
+ .ui-field__error svg { width: 16px; height: 16px; flex: none; stroke-width: 2.4; }
18
19
 
19
20
  .ui-input,
20
21
  .ui-textarea,
@@ -46,8 +47,15 @@
46
47
  .ui-input::placeholder,
47
48
  .ui-textarea::placeholder { color: var(--muted); opacity: 0.9; }
48
49
 
50
+ /* The value a reader typed is still the value; it goes quiet, it does not fade
51
+ into the field under it the way an opacity would take both down together. #220 */
49
52
  .ui-input:disabled,
50
- .ui-textarea:disabled { opacity: 0.5; cursor: not-allowed; }
53
+ .ui-textarea:disabled {
54
+ background: var(--disabled-surface);
55
+ border-color: var(--disabled-border);
56
+ color: var(--disabled-ink);
57
+ cursor: not-allowed;
58
+ }
51
59
 
52
60
  .ui-input.is-invalid,
53
61
  .ui-textarea.is-invalid { border-color: var(--pink); }
@@ -64,7 +72,7 @@
64
72
  color: var(--muted);
65
73
  pointer-events: none;
66
74
  }
67
- .ui-input-group__icon svg { width: 100%; height: 100%; stroke: currentColor; fill: none; stroke-width: 1.8; }
75
+ .ui-input-group__icon svg { width: 100%; height: 100%; stroke: currentColor; fill: none; stroke-width: 2.2; }
68
76
 
69
77
  .ui-select {
70
78
  appearance: none;
@@ -98,6 +106,24 @@
98
106
  transition: border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
99
107
  position: relative;
100
108
  }
109
+ /* The target, not the ink. The box above is drawn at 19x19 because that is what
110
+ sits beside a 15px label without shouting; WCAG 2.5.8 wants 24 CSS px of
111
+ pointer target, which is a different question. This overlay answers it and
112
+ the checkbox keeps its size. ::after is the tick and the dot, so the target
113
+ is ::before — it paints first, under both, and is transparent anyway.
114
+ Centred, so the overhang is 2.5px on every side: 11px of gap to the label
115
+ inside the same <label> (which toggles the same input, so an overlap there
116
+ would cost nothing) and 14px between stacked rows in the specimen. Measured
117
+ in stories/guidelines/accessibility-floor.test.js. #219 */
118
+ .ui-check input::before {
119
+ content: '';
120
+ position: absolute;
121
+ left: 50%;
122
+ top: 50%;
123
+ width: 24px;
124
+ height: 24px;
125
+ transform: translate(-50%, -50%);
126
+ }
101
127
  .ui-check input[type="radio"] { border-radius: 50%; }
102
128
  .ui-check input:hover { border-color: var(--accent); }
103
129
  .ui-check input:checked { background: var(--accent); border-color: var(--accent); }
@@ -114,12 +114,14 @@
114
114
  The width is what keeps the auto margins honest: an auto inline margin takes
115
115
  a grid item out of stretch, and Chrome then lays it out at its max-content
116
116
  width, which is how the payout ledger pushed the page 529px sideways at 375.
117
- The 860px is the floor for hand-written markup shell.js validates what it
118
- writes into --ui-app-main and falls back to this same number. */
117
+ --measure is the ONE source for this width now. shell.js used to carry its
118
+ own copy of the number and a test held the two in step; it now omits the
119
+ property rather than writing a default, so an absent or unparseable maxWidth
120
+ lands here, on the token, instead of on a second literal. See issue #198. */
119
121
  .ui-app__main {
120
122
  min-width: 0;
121
123
  width: 100%;
122
- max-width: var(--ui-app-main, 860px);
124
+ max-width: var(--ui-app-main, var(--measure));
123
125
  margin-inline: auto;
124
126
  padding: 40px clamp(24px, 4vw, 56px) 72px;
125
127
  }
@@ -134,7 +136,7 @@
134
136
  letter-spacing: var(--tracking-tight);
135
137
  margin-bottom: 8px;
136
138
  }
137
- .ui-app__sub { color: var(--dim); font-size: 15px; line-height: 1.6; max-width: 60ch; margin-bottom: 32px; }
139
+ .ui-app__sub { color: var(--dim); font-size: 15px; line-height: 1.6; max-width: var(--prose-body); margin-bottom: 32px; }
138
140
  .ui-app__body { display: flex; flex-direction: column; gap: var(--space-5); }
139
141
  /* A flex child's min-width is its content by default, so one wide block — a
140
142
  ledger, a code sample — pushes the whole page sideways instead of scrolling
@@ -219,7 +221,7 @@
219
221
  position: relative;
220
222
  z-index: 1;
221
223
  width: 100%;
222
- max-width: 420px;
224
+ max-width: var(--panel-md);
223
225
  background: var(--surface);
224
226
  border-radius: 22px;
225
227
  padding: 38px 36px 32px;
@@ -245,7 +247,7 @@
245
247
  background: var(--glow-purple);
246
248
  color: var(--accent);
247
249
  }
248
- .ui-scope__icon svg { width: 21px; height: 21px; stroke: currentColor; fill: none; stroke-width: 1.7; }
250
+ .ui-scope__icon svg { width: 21px; height: 21px; stroke: currentColor; fill: none; stroke-width: 1.8; }
249
251
  .ui-scope__t { color: var(--strong); font-size: 14px; font-weight: 500; }
250
252
  .ui-scope__d { color: var(--dim); font-size: 13px; margin-top: 2px; line-height: 1.5; }
251
253
 
@@ -275,7 +277,7 @@
275
277
  letter-spacing: -0.025em;
276
278
  font-weight: 700;
277
279
  color: var(--strong);
278
- max-width: 14ch;
280
+ max-width: var(--prose-display);
279
281
  margin: 0 auto 22px;
280
282
  }
281
283
  .ui-hero__title .grad {
@@ -287,7 +289,7 @@
287
289
  .ui-hero__sub {
288
290
  font-size: clamp(16px, 2.1vw, 20px);
289
291
  color: var(--dim);
290
- max-width: 52ch;
292
+ max-width: var(--prose-lede);
291
293
  margin: 0 auto 34px;
292
294
  line-height: 1.6;
293
295
  }
@@ -298,7 +300,9 @@
298
300
  display: grid;
299
301
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
300
302
  gap: 20px;
301
- max-width: 1080px;
303
+ /* Was 1080px — a third page-scale number with nothing arguing for it. The
304
+ feature row is a top-level band and lines up with the page it sits on. */
305
+ max-width: var(--container);
302
306
  margin: 0 auto;
303
307
  padding: 0 26px;
304
308
  }
@@ -314,6 +318,6 @@
314
318
  .ui-feature h3 { color: var(--strong); font-size: 17px; font-weight: 600; margin-bottom: 7px; }
315
319
  .ui-feature p { color: var(--dim); font-size: 14.5px; line-height: 1.6; }
316
320
 
317
- .ui-section-head { text-align: center; max-width: 620px; margin: 0 auto clamp(40px, 6vh, 64px); }
321
+ .ui-section-head { text-align: center; max-width: var(--panel-lg); margin: 0 auto clamp(40px, 6vh, 64px); }
318
322
  .ui-section-head h2 { color: var(--strong); font-size: clamp(28px, 4vw, 40px); font-weight: 700; letter-spacing: -0.02em; margin-bottom: 14px; }
319
323
  .ui-section-head p { color: var(--dim); font-size: 17px; line-height: 1.6; }