@kurtbruns/kestrel 1.7.0 → 1.8.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/.dev.vars.example +13 -8
- package/CHANGELOG.md +51 -2
- package/README.md +1 -1
- package/cli/dev-api.mjs +7 -1
- package/cli/dev.mjs +46 -1
- package/cli/init.mjs +6 -1
- package/dist/public/dashboard/app.js +217 -222
- package/dist/public/dashboard/app.js.map +4 -4
- package/dist/public/dashboard/index.html +6 -6
- package/dist/public/dashboard/styles.css +151 -181
- package/dist/worker/index.js +1959 -1980
- package/docs/README.md +1 -2
- package/docs/get-started/02-deploy.md +23 -8
- package/docs/get-started/03-access.md +2 -2
- package/docs/{guides/02-ses.md → get-started/04-ses.md} +75 -52
- package/docs/get-started/05-verify.md +8 -10
- package/docs/get-started/06-go-live.md +19 -6
- package/docs/guides/07-upgrade.md +2 -0
- package/docs/reference/01-configuration.md +15 -15
- package/docs/reference/02-sending-domain-dns.md +19 -19
- package/migrations/0004_keyed_pending_to_resolve.sql +27 -0
- package/package.json +1 -1
- package/template/claude/CLAUDE.md +3 -1
- package/template/package.json +1 -0
- package/template/wrangler.jsonc +6 -3
- package/docs/get-started/04-resend.md +0 -136
|
@@ -5,26 +5,28 @@
|
|
|
5
5
|
different values on purpose; don't assume they share meaning. */
|
|
6
6
|
:root {
|
|
7
7
|
color-scheme: light dark;
|
|
8
|
-
/* Layering — one named z-index ladder (DESIGN §
|
|
8
|
+
/* Layering — one named z-index ladder (DESIGN §9). Every fixed or floating surface
|
|
9
9
|
references a rung by token, never a bare literal; the order encodes the intent.
|
|
10
10
|
z-index is theme-independent, so it lives only here (not the dark block). */
|
|
11
11
|
--z-content: 1;
|
|
12
12
|
--z-header: 10;
|
|
13
13
|
--z-menu: 20;
|
|
14
14
|
--z-bar: 30;
|
|
15
|
-
--z-
|
|
16
|
-
|
|
15
|
+
--z-modal: 40;
|
|
16
|
+
/* Toasts above modals: a toast raised while a dialog is open (an autosave that failed,
|
|
17
|
+
a send that started) must not sit dimmed beneath its backdrop. */
|
|
18
|
+
--z-toast: 50;
|
|
17
19
|
--fg: #18181b;
|
|
18
20
|
--muted: #52525b;
|
|
19
21
|
--line: #e4e4e7;
|
|
20
22
|
--bg: #fbfbfa;
|
|
21
23
|
--card: #fff;
|
|
22
24
|
--hover: #efefef;
|
|
23
|
-
/* Neutral ink — the strong near-black/near-white neutral (DESIGN §
|
|
25
|
+
/* Neutral ink — the strong near-black/near-white neutral (DESIGN §8). A text token
|
|
24
26
|
for headings and strong labels; never a button fill (Primary is --accent). */
|
|
25
27
|
--ink: #18181b;
|
|
26
28
|
--ink-on: #ffffff;
|
|
27
|
-
/* Semantic feedback — one {fg, bg, line} scale each for danger / warn / ok (DESIGN §
|
|
29
|
+
/* Semantic feedback — one {fg, bg, line} scale each for danger / warn / ok (DESIGN §8).
|
|
28
30
|
fg is the saturated hue (text on the soft bg, or a solid fill paired with an on-color);
|
|
29
31
|
bg is the soft tint behind a notice; line is its border. There is one red, not two —
|
|
30
32
|
the old --danger vs --b-bad-* split is collapsed here. Kept distinct from the
|
|
@@ -40,13 +42,11 @@
|
|
|
40
42
|
--ok-bg: #dcfce7;
|
|
41
43
|
--ok-line: #bbf7d0;
|
|
42
44
|
--ring: #2563eb;
|
|
43
|
-
--banner-bg: #eff6ff;
|
|
44
|
-
--banner-line: #bfdbfe;
|
|
45
45
|
--status-draft-bg: #f4f4f5;
|
|
46
46
|
--status-draft-fg: #52525b;
|
|
47
47
|
--status-draft-line: #d4d4d8;
|
|
48
48
|
/* Scheduled is a violet, deliberately off the slate-blue action accent (--accent)
|
|
49
|
-
so the "scheduled" status never reads as the primary action (DESIGN §
|
|
49
|
+
so the "scheduled" status never reads as the primary action (DESIGN §8); the fill
|
|
50
50
|
backs the scheduled pill, banner, and cards, and the line borders them. */
|
|
51
51
|
--status-scheduled-bg: #ece6f9;
|
|
52
52
|
--status-scheduled-fg: #6743ac;
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
--accent: #3355cc;
|
|
66
66
|
--accent-on: #ffffff;
|
|
67
67
|
--accent-soft: rgba(51, 85, 204, 0.12);
|
|
68
|
-
/* Syntax-highlight palette, shared by the template
|
|
68
|
+
/* Syntax-highlight palette, shared by the template and post
|
|
69
69
|
editors — the HTML/CSS .cx-* tokens and the Markdown .cx-md-* tokens map onto it. */
|
|
70
70
|
--syntax-bg: #fbfbfc;
|
|
71
71
|
--syntax-fg: #3f3f46;
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
--syntax-punctuation: #a1a1aa;
|
|
80
80
|
--syntax-variable: #2d47b8;
|
|
81
81
|
--syntax-variable-bg: rgba(51, 85, 204, 0.11);
|
|
82
|
-
/* Post editor's minimal scheme
|
|
82
|
+
/* Post editor's minimal scheme: marks get the color, content keeps --syntax-fg.
|
|
83
83
|
Heading # is crimson; the emphasis asterisks are a magenta-pink kept clear of the heading
|
|
84
84
|
crimson; block markers (>, list bullets, fence language) are a saturated, bright orange. */
|
|
85
85
|
--syntax-heading: #be123c;
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
/* Code surface — inline `code` chip and the fenced-block band. A hair off the editor well
|
|
89
89
|
(--syntax-bg), lighter than the neutral --chip, so code reads as code without a heavy box. */
|
|
90
90
|
--syntax-code-bg: #f2f2f4;
|
|
91
|
-
/* HTTP method badges in the API reference (DESIGN §
|
|
91
|
+
/* HTTP method badges in the API reference (DESIGN §8). A self-contained palette like
|
|
92
92
|
--syntax-*: verb colors are a naming convention, not feedback or lifecycle, so they
|
|
93
93
|
borrow no semantic scale. The badge tints each over a wash of itself (.api-method). */
|
|
94
94
|
--method-get: #166534;
|
|
@@ -122,8 +122,6 @@
|
|
|
122
122
|
--ok-bg: #14532d;
|
|
123
123
|
--ok-line: #15803d;
|
|
124
124
|
--ring: #60a5fa;
|
|
125
|
-
--banner-bg: #1e293b;
|
|
126
|
-
--banner-line: #334155;
|
|
127
125
|
--status-draft-bg: #2e2e33;
|
|
128
126
|
--status-draft-fg: #a1a1aa;
|
|
129
127
|
--status-draft-line: #52525b;
|
|
@@ -157,7 +155,7 @@
|
|
|
157
155
|
--syntax-punctuation: #6a6a72;
|
|
158
156
|
--syntax-variable: #a9bbf2;
|
|
159
157
|
--syntax-variable-bg: rgba(120, 150, 240, 0.18);
|
|
160
|
-
/* Post editor minimal scheme
|
|
158
|
+
/* Post editor minimal scheme — near-black/saturated hues lift to legible
|
|
161
159
|
tints on the dark well; the heading crimson becomes a rose, emphasis a lighter pink. */
|
|
162
160
|
--syntax-heading: #fb7185;
|
|
163
161
|
--syntax-emphasis: #f472b6;
|
|
@@ -593,7 +591,7 @@ body.nav-resizing .sidebar {
|
|
|
593
591
|
margin: 10px 2px 0;
|
|
594
592
|
line-height: 1.5;
|
|
595
593
|
}
|
|
596
|
-
/* The connected state: an inline status line reusing the --ok feedback scale (DESIGN §
|
|
594
|
+
/* The connected state: an inline status line reusing the --ok feedback scale (DESIGN §8),
|
|
597
595
|
not a notification — a quiet confirmation the agent has edited here. */
|
|
598
596
|
.conn-status {
|
|
599
597
|
display: flex;
|
|
@@ -900,8 +898,10 @@ body.nav-resizing .sidebar {
|
|
|
900
898
|
margin-top: 12px;
|
|
901
899
|
}
|
|
902
900
|
/* The refused-configuration wall says more than the sign-in wall, so it reads as text: left
|
|
903
|
-
aligned, a little wider, with the variable and the server's words set apart.
|
|
904
|
-
.
|
|
901
|
+
aligned, a little wider, with the variable and the server's words set apart. The locked
|
|
902
|
+
editor, before Access is set up, reads the same way. */
|
|
903
|
+
.config-wall,
|
|
904
|
+
.access-wall {
|
|
905
905
|
max-width: 560px;
|
|
906
906
|
text-align: left;
|
|
907
907
|
}
|
|
@@ -918,7 +918,8 @@ body.nav-resizing .sidebar {
|
|
|
918
918
|
white-space: nowrap;
|
|
919
919
|
border: 0;
|
|
920
920
|
}
|
|
921
|
-
.config-wall h2:focus
|
|
921
|
+
.config-wall h2:focus,
|
|
922
|
+
.access-wall h2:focus {
|
|
922
923
|
outline: none;
|
|
923
924
|
}
|
|
924
925
|
.config-wall-problem {
|
|
@@ -949,8 +950,8 @@ body.tool-mode .content {
|
|
|
949
950
|
}
|
|
950
951
|
|
|
951
952
|
/* --- Collapsed icon rail. Driven by data-nav="rail" on <html> (set from the viewport
|
|
952
|
-
width and the
|
|
953
|
-
index.html), so the collapse toggle can pin it at any width in range. The rail is the
|
|
953
|
+
width and the collapse toggle's override for this load — see client/shell.ts and the
|
|
954
|
+
<head> guard in index.html), so the collapse toggle can pin it at any width in range. The rail is the
|
|
954
955
|
default across the tablet range and small desktops; the nav keeps its one-click
|
|
955
956
|
reach, labels move to hover/focus tooltips, and the secondary chrome (brand text,
|
|
956
957
|
account, tool footer) folds down to its marks. The whole block is gated at ≥540 so a
|
|
@@ -1180,7 +1181,7 @@ button:hover {
|
|
|
1180
1181
|
button:active {
|
|
1181
1182
|
transform: translateY(0.5px);
|
|
1182
1183
|
}
|
|
1183
|
-
/* --- Buttons: five roles (DESIGN §
|
|
1184
|
+
/* --- Buttons: five roles (DESIGN §7), chosen by importance and consequence, one
|
|
1184
1185
|
class each. The bare `button` above is the neutral default (Ghost's card-filled
|
|
1185
1186
|
end); the classes below step up to Primary / Secondary or down to the quiet Ghost,
|
|
1186
1187
|
plus Danger and the square Icon. Exactly one solid brand-blue action — Primary — is
|
|
@@ -1189,7 +1190,7 @@ button:active {
|
|
|
1189
1190
|
|
|
1190
1191
|
/* One box height across the label roles, so a row that mixes them (the editor's
|
|
1191
1192
|
Save draft / Send test email / Schedule, a modal's Cancel + confirm) aligns:
|
|
1192
|
-
roles differ by treatment — fill, weight, font-size — never by height (DESIGN §
|
|
1193
|
+
roles differ by treatment — fill, weight, font-size — never by height (DESIGN §7).
|
|
1193
1194
|
A smaller-font role (Secondary, Ghost) grows to this floor with its label centered
|
|
1194
1195
|
rather than sitting a few pixels short of the Primary beside it. The bare `button`
|
|
1195
1196
|
default already meets it; the square Icon role and the segmented toggle Control keep
|
|
@@ -1518,16 +1519,10 @@ td a:hover {
|
|
|
1518
1519
|
color: var(--status-sending-fg);
|
|
1519
1520
|
border-color: var(--status-sending-line);
|
|
1520
1521
|
}
|
|
1521
|
-
.badge.canceled,
|
|
1522
|
-
.badge.failed {
|
|
1523
|
-
background: var(--danger-bg);
|
|
1524
|
-
color: var(--danger-fg);
|
|
1525
|
-
border-color: var(--danger-line);
|
|
1526
|
-
}
|
|
1527
1522
|
/* Subscriber consent states borrow a hue that matches the state's meaning: confirmed →
|
|
1528
1523
|
the sent green, unsubscribed → the draft grey, suppressed → danger red (below). Pending
|
|
1529
1524
|
awaits double-opt-in — a waiting/attention state — so it takes the amber `warn` scale,
|
|
1530
|
-
NOT `--status-sending`: the send "sending" badge is blue (DESIGN §
|
|
1525
|
+
NOT `--status-sending`: the send "sending" badge is blue (DESIGN §8), and a pending subscriber
|
|
1531
1526
|
must never read as a send in flight. (The two coincided only while `--status-sending` was
|
|
1532
1527
|
amber; when it went blue for the dispatch treatment, this pill must stay amber.) */
|
|
1533
1528
|
.badge.confirmed {
|
|
@@ -1749,7 +1744,7 @@ body.web-mode .content {
|
|
|
1749
1744
|
margin: 0;
|
|
1750
1745
|
padding: 0;
|
|
1751
1746
|
}
|
|
1752
|
-
/* The page sits under one strip that says where the post stands (DESIGN §
|
|
1747
|
+
/* The page sits under one strip that says where the post stands (DESIGN §3). The strip is
|
|
1753
1748
|
the editor's, above the frame, and the frame is as tall as the post, so the window scrolls
|
|
1754
1749
|
and the strip goes up with the page like the test pages' strip. It wears the brand accent
|
|
1755
1750
|
like the reader surface's dev pill, its tag as the pill's inverted chip, and its content
|
|
@@ -1815,8 +1810,8 @@ body.web-mode .content {
|
|
|
1815
1810
|
margin: 48px auto;
|
|
1816
1811
|
padding: 0 16px;
|
|
1817
1812
|
}
|
|
1818
|
-
/* Sent record view
|
|
1819
|
-
semantic feedback scales (--ok / --warn / --danger, DESIGN §
|
|
1813
|
+
/* Sent record view: a read-only delivery record. The outcome tiles reuse the
|
|
1814
|
+
semantic feedback scales (--ok / --warn / --danger, DESIGN §8); a status badge, not
|
|
1820
1815
|
a feedback color, still labels the send's lifecycle. */
|
|
1821
1816
|
.rec-card {
|
|
1822
1817
|
display: flex;
|
|
@@ -1916,7 +1911,7 @@ body.web-mode .content {
|
|
|
1916
1911
|
}
|
|
1917
1912
|
}
|
|
1918
1913
|
|
|
1919
|
-
/* ---- per-recipient record
|
|
1914
|
+
/* ---- per-recipient record: the rows behind the outcome tiles ---- */
|
|
1920
1915
|
.rec-people {
|
|
1921
1916
|
margin-top: 26px;
|
|
1922
1917
|
}
|
|
@@ -1931,7 +1926,7 @@ body.web-mode .content {
|
|
|
1931
1926
|
margin: 0;
|
|
1932
1927
|
font-size: 15px;
|
|
1933
1928
|
}
|
|
1934
|
-
/* The view toggle (Failures / Delivered / All) is a Control, not a button (DESIGN §
|
|
1929
|
+
/* The view toggle (Failures / Delivered / All) is a Control, not a button (DESIGN §7):
|
|
1935
1930
|
the same segmented, aria-pressed pill group as the Template preview-width toggle, its
|
|
1936
1931
|
active segment in the Secondary palette. It switches what you're looking at; it never
|
|
1937
1932
|
commits an action. */
|
|
@@ -1983,8 +1978,8 @@ body.web-mode .content {
|
|
|
1983
1978
|
font-size: 13px;
|
|
1984
1979
|
}
|
|
1985
1980
|
|
|
1986
|
-
/* ---- live in-flight watch
|
|
1987
|
-
/* The sending treatment is blue (--status-sending-*, DESIGN §
|
|
1981
|
+
/* ---- live in-flight watch: the `sending` state of the record view ---- */
|
|
1982
|
+
/* The sending treatment is blue (--status-sending-*, DESIGN §8) — distinct from the
|
|
1988
1983
|
action slate-blue — carried by the phase pill, the dispatch bar, and the swatch. */
|
|
1989
1984
|
.rec-sw.sw-sending {
|
|
1990
1985
|
background: var(--status-sending-fg);
|
|
@@ -2150,7 +2145,7 @@ body.web-mode .content {
|
|
|
2150
2145
|
}
|
|
2151
2146
|
}
|
|
2152
2147
|
|
|
2153
|
-
/* ---- active-send row (Sent page "In progress") + dashboard widget
|
|
2148
|
+
/* ---- active-send row (Sent page "In progress") + dashboard widget ---- */
|
|
2154
2149
|
/* An in-flight send as a card with a live mini dispatch bar; blue, so it reads as
|
|
2155
2150
|
"sending" — distinct from the scheduled violet cards and the neutral sent rows. */
|
|
2156
2151
|
.card.active-card {
|
|
@@ -2194,6 +2189,27 @@ body.web-mode .content {
|
|
|
2194
2189
|
font-size: 12.5px;
|
|
2195
2190
|
color: var(--warn-fg);
|
|
2196
2191
|
}
|
|
2192
|
+
/* A scheduled send while no provider is connected (DESIGN §4): the countdown stays, and a
|
|
2193
|
+
line beneath it in the same amber says the send will be held when it fires. */
|
|
2194
|
+
.sched-held {
|
|
2195
|
+
margin-top: 4px;
|
|
2196
|
+
font-size: 12.5px;
|
|
2197
|
+
font-weight: 500;
|
|
2198
|
+
color: var(--warn-fg);
|
|
2199
|
+
}
|
|
2200
|
+
/* The editor's scheduled banner stacks its line and the held line on the left. */
|
|
2201
|
+
.banner-scheduled .sched-main {
|
|
2202
|
+
display: flex;
|
|
2203
|
+
flex-direction: column;
|
|
2204
|
+
/* Takes the room and wraps its own lines (the re-made line is a sentence), so Reschedule
|
|
2205
|
+
and Cancel stay at the right rather than dropping below; on a phone the row still wraps. */
|
|
2206
|
+
flex: 1 1 18rem;
|
|
2207
|
+
min-width: 0;
|
|
2208
|
+
}
|
|
2209
|
+
/* A draft's not-connected line sits above its actions bar, which keeps its own top gap. */
|
|
2210
|
+
.health.editor-not-connected {
|
|
2211
|
+
margin: 16px 0 0;
|
|
2212
|
+
}
|
|
2197
2213
|
|
|
2198
2214
|
/* Composer: GitHub-style Write/Preview tabs + a formatting toolbar */
|
|
2199
2215
|
.composer {
|
|
@@ -2327,7 +2343,7 @@ body.web-mode .content {
|
|
|
2327
2343
|
.composer-body {
|
|
2328
2344
|
position: relative;
|
|
2329
2345
|
}
|
|
2330
|
-
/* Post editor syntax-highlight overlay
|
|
2346
|
+
/* Post editor syntax-highlight overlay: a transparent <textarea> layered over
|
|
2331
2347
|
a highlighted <pre> that shows the colored Markdown beneath the caret. The two share
|
|
2332
2348
|
identical box + font metrics so the caret sits exactly on the colored text; the textarea
|
|
2333
2349
|
is the only scroller and the <pre> is synced to it in script. Unlike the template editor
|
|
@@ -2402,7 +2418,7 @@ body.web-mode .content {
|
|
|
2402
2418
|
background: #fff;
|
|
2403
2419
|
display: block;
|
|
2404
2420
|
}
|
|
2405
|
-
/* A scheduled post's Edit tab (DESIGN §
|
|
2421
|
+
/* A scheduled post's Edit tab (DESIGN §3): the text stays fully legible, selectable, and
|
|
2406
2422
|
copyable (the fields are readonly, not disabled, since a disabled textarea cannot even be
|
|
2407
2423
|
selected), on the recessed surface Settings uses for a read-only card (`.set-card.sunken`);
|
|
2408
2424
|
the caret is hidden because the field is not for typing. The way out is the composer's
|
|
@@ -2462,7 +2478,7 @@ body.web-mode .content {
|
|
|
2462
2478
|
}
|
|
2463
2479
|
/* The locked composer's foot: where a draft's foot offers images, a scheduled post's says
|
|
2464
2480
|
the way out, "Cancel the schedule to edit", with the read-only lock Settings uses. It is
|
|
2465
|
-
a hint, not a control (Cancel stays on the banner, DESIGN §
|
|
2481
|
+
a hint, not a control (Cancel stays on the banner, DESIGN §3), so no pointer and no hover
|
|
2466
2482
|
lift. `.nudge` pulses it once, in the read-only note's tint, when an edit is attempted,
|
|
2467
2483
|
so the refusal is seen where the keystroke landed; reduced-motion holds the tint instead. */
|
|
2468
2484
|
.composer-foot-lock {
|
|
@@ -2501,7 +2517,14 @@ body.web-mode .content {
|
|
|
2501
2517
|
.actions-bar > .row:only-child {
|
|
2502
2518
|
margin-left: auto;
|
|
2503
2519
|
}
|
|
2504
|
-
/* The
|
|
2520
|
+
/* The save side takes the room left over and lets a long cue (why a save failed) wrap
|
|
2521
|
+
inside it, so the test and schedule buttons keep their place at the right. */
|
|
2522
|
+
.actions-bar > .row:first-child:not(:only-child) {
|
|
2523
|
+
flex: 1 1 16rem;
|
|
2524
|
+
min-width: 0;
|
|
2525
|
+
flex-wrap: nowrap;
|
|
2526
|
+
}
|
|
2527
|
+
/* The autosave cue (DESIGN §5, §6), beside Save draft. The editor's save lifecycle:
|
|
2505
2528
|
Unsaved (amber --warn dot) · Saving… (pulsing --accent dot) · Saved (green --ok
|
|
2506
2529
|
dot). The dot is a reinforcement, never the whole signal — the text label carries
|
|
2507
2530
|
each state too, so the cue survives a reader who can't tell the dot's color. The
|
|
@@ -2510,6 +2533,7 @@ body.web-mode .content {
|
|
|
2510
2533
|
.save-status {
|
|
2511
2534
|
display: inline-flex;
|
|
2512
2535
|
align-items: center;
|
|
2536
|
+
min-width: 0;
|
|
2513
2537
|
gap: 7px;
|
|
2514
2538
|
font-size: 13px;
|
|
2515
2539
|
color: var(--muted);
|
|
@@ -2520,9 +2544,13 @@ body.web-mode .content {
|
|
|
2520
2544
|
.save-status.is-dirty {
|
|
2521
2545
|
color: var(--fg);
|
|
2522
2546
|
}
|
|
2547
|
+
.save-status.is-error {
|
|
2548
|
+
color: var(--danger-fg);
|
|
2549
|
+
}
|
|
2523
2550
|
.save-status.is-dirty::before,
|
|
2524
2551
|
.save-status.is-saving::before,
|
|
2525
|
-
.save-status.is-saved::before
|
|
2552
|
+
.save-status.is-saved::before,
|
|
2553
|
+
.save-status.is-error::before {
|
|
2526
2554
|
content: "";
|
|
2527
2555
|
width: 8px;
|
|
2528
2556
|
height: 8px;
|
|
@@ -2539,6 +2567,9 @@ body.web-mode .content {
|
|
|
2539
2567
|
.save-status.is-saved::before {
|
|
2540
2568
|
background: var(--ok-fg);
|
|
2541
2569
|
}
|
|
2570
|
+
.save-status.is-error::before {
|
|
2571
|
+
background: var(--danger-fg);
|
|
2572
|
+
}
|
|
2542
2573
|
@keyframes save-pulse {
|
|
2543
2574
|
0%,
|
|
2544
2575
|
100% {
|
|
@@ -2637,7 +2668,7 @@ body.web-mode .content {
|
|
|
2637
2668
|
margin: 0 0 14px;
|
|
2638
2669
|
line-height: 1.5;
|
|
2639
2670
|
}
|
|
2640
|
-
/* A dialog's not-connected line (DESIGN §
|
|
2671
|
+
/* A dialog's not-connected line (DESIGN §4) takes the health block's amber warning, so it
|
|
2641
2672
|
reads as the reason the dialog's action is off rather than as one more hint. */
|
|
2642
2673
|
.modal .hint.not-connected {
|
|
2643
2674
|
color: var(--warn-fg);
|
|
@@ -2666,10 +2697,16 @@ body.web-mode .content {
|
|
|
2666
2697
|
padding-top: 14px;
|
|
2667
2698
|
border-top: 1px solid var(--line);
|
|
2668
2699
|
}
|
|
2669
|
-
/* The alternate-path row can carry a plain note beside its link
|
|
2700
|
+
/* The alternate-path row can carry a plain note beside its link; the note wraps and the
|
|
2701
|
+
link stays whole on its line. */
|
|
2670
2702
|
.modal .altrow .altrow-note {
|
|
2671
2703
|
font-size: 12.5px;
|
|
2672
2704
|
color: var(--muted);
|
|
2705
|
+
min-width: 0;
|
|
2706
|
+
}
|
|
2707
|
+
.modal .altrow .linkbtn {
|
|
2708
|
+
flex: none;
|
|
2709
|
+
white-space: nowrap;
|
|
2673
2710
|
}
|
|
2674
2711
|
/* The back link at the top of the Send-now view needs no divider — it returns from,
|
|
2675
2712
|
rather than demotes below, the Schedule view. */
|
|
@@ -2679,7 +2716,7 @@ body.web-mode .content {
|
|
|
2679
2716
|
border-top: 0;
|
|
2680
2717
|
justify-content: flex-start;
|
|
2681
2718
|
}
|
|
2682
|
-
/* Link — a text-link-styled action (DESIGN §
|
|
2719
|
+
/* Link — a text-link-styled action (DESIGN §7), not one of the button roles. Reserved
|
|
2683
2720
|
for a deliberately subordinate choice, like the in-modal Send now that sits below
|
|
2684
2721
|
the Schedule Primary: it must read as clearly demoted, so it is a link, not a Ghost
|
|
2685
2722
|
button that would compete with Cancel. Still a real <button>, so it's keyboard- and
|
|
@@ -2700,12 +2737,12 @@ button.linkbtn:hover {
|
|
|
2700
2737
|
|
|
2701
2738
|
.countdown {
|
|
2702
2739
|
font-variant-numeric: tabular-nums;
|
|
2703
|
-
/* The scheduled violet (DESIGN §
|
|
2740
|
+
/* The scheduled violet (DESIGN §8), so the fire-time countdown on the scheduled
|
|
2704
2741
|
cards (Sent page + dashboard) carries the same "scheduled" cue as the pill. */
|
|
2705
2742
|
color: var(--status-scheduled-fg);
|
|
2706
2743
|
}
|
|
2707
2744
|
/* Past the server's missed tolerance the countdown says how late the send is, in the
|
|
2708
|
-
danger tone: a missed fire time needs a look (DESIGN §
|
|
2745
|
+
danger tone: a missed fire time needs a look (DESIGN §4). */
|
|
2709
2746
|
.countdown.countdown-missed {
|
|
2710
2747
|
color: var(--danger-fg);
|
|
2711
2748
|
}
|
|
@@ -2829,6 +2866,23 @@ td.act {
|
|
|
2829
2866
|
.menu-item.danger-item {
|
|
2830
2867
|
color: var(--danger-fg);
|
|
2831
2868
|
}
|
|
2869
|
+
/* A menu whose items say what each pick does: a name over a muted line. */
|
|
2870
|
+
.menu-described {
|
|
2871
|
+
width: 280px;
|
|
2872
|
+
max-width: min(280px, 78vw);
|
|
2873
|
+
}
|
|
2874
|
+
.menu-item-name {
|
|
2875
|
+
display: block;
|
|
2876
|
+
font-size: 13px;
|
|
2877
|
+
font-weight: 600;
|
|
2878
|
+
}
|
|
2879
|
+
.menu-item-desc {
|
|
2880
|
+
display: block;
|
|
2881
|
+
margin-top: 1px;
|
|
2882
|
+
font-size: 11.5px;
|
|
2883
|
+
color: var(--muted);
|
|
2884
|
+
line-height: 1.35;
|
|
2885
|
+
}
|
|
2832
2886
|
|
|
2833
2887
|
/* editor field hints + scheduled banner */
|
|
2834
2888
|
.field-hint {
|
|
@@ -2836,7 +2890,7 @@ td.act {
|
|
|
2836
2890
|
color: var(--muted);
|
|
2837
2891
|
margin-top: 4px;
|
|
2838
2892
|
}
|
|
2839
|
-
/* --- Field validation (DESIGN §
|
|
2893
|
+
/* --- Field validation (DESIGN §5): an error about one field lives on that
|
|
2840
2894
|
field — the input goes to an invalid state and the reason sits directly beneath it,
|
|
2841
2895
|
tied by aria-describedby. This replaces the old disable-the-button + `title` tooltip,
|
|
2842
2896
|
which never reached keyboard, touch, or screen-reader users. --- */
|
|
@@ -2856,6 +2910,11 @@ textarea.is-invalid {
|
|
|
2856
2910
|
.field-error .field-error-ico {
|
|
2857
2911
|
font-weight: 700;
|
|
2858
2912
|
}
|
|
2913
|
+
/* A dialog's own error, when no one field is at fault: just above its actions, where the
|
|
2914
|
+
reader is looking rather than in a toast at the foot of the screen (DESIGN §5). */
|
|
2915
|
+
.modal .dialog-error {
|
|
2916
|
+
margin-top: 12px;
|
|
2917
|
+
}
|
|
2859
2918
|
/* Label + info icon grouped at the top of the slug field. */
|
|
2860
2919
|
.label-row {
|
|
2861
2920
|
display: flex;
|
|
@@ -2964,11 +3023,10 @@ textarea.is-invalid {
|
|
|
2964
3023
|
#f-slug.slug-auto {
|
|
2965
3024
|
color: var(--muted);
|
|
2966
3025
|
}
|
|
2967
|
-
/* --- Editor-state banner (DESIGN §
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
a neutral notice. It replaces the old `.sched-banner` + `.fresh-banner` pair. --- */
|
|
3026
|
+
/* --- Editor-state banner (DESIGN §5): a standing, in-flow line at the top of the
|
|
3027
|
+
editor, in two variants. `scheduled` marks a frozen, soft-locked post (SPEC §6);
|
|
3028
|
+
`conflict` warns the draft changed elsewhere (another tab, or Claude), and its two
|
|
3029
|
+
actions are equal-weight Ghost, since both choices lose something. --- */
|
|
2972
3030
|
.banner {
|
|
2973
3031
|
display: flex;
|
|
2974
3032
|
align-items: center;
|
|
@@ -2997,59 +3055,21 @@ textarea.is-invalid {
|
|
|
2997
3055
|
color: var(--warn-fg);
|
|
2998
3056
|
border-color: var(--warn-line);
|
|
2999
3057
|
}
|
|
3000
|
-
.banner-info {
|
|
3001
|
-
background: var(--banner-bg);
|
|
3002
|
-
color: var(--fg);
|
|
3003
|
-
border-color: var(--banner-line);
|
|
3004
|
-
}
|
|
3005
3058
|
|
|
3006
|
-
/*
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
its layout is prose plus one control, not a status line with an actions row, so it never
|
|
3010
|
-
wraps the dismiss beneath the text the way the banner wraps its buttons. Padding is a
|
|
3011
|
-
full 1rem because it is a sentence or two, not a status line. Removed on dismiss with
|
|
3012
|
-
no animation. Both themes come through the existing tokens. --- */
|
|
3013
|
-
.notice {
|
|
3059
|
+
/* Look changed since your last test (SPEC §8): the server's condition, a line in the scheduled
|
|
3060
|
+
banner that says to send a new test, and a mark on the dashboard's scheduled card. */
|
|
3061
|
+
.sched-remade {
|
|
3014
3062
|
display: flex;
|
|
3015
|
-
|
|
3016
|
-
gap: 12px;
|
|
3017
|
-
border: 1px solid var(--banner-line);
|
|
3018
|
-
border-radius: 8px;
|
|
3019
|
-
background: var(--banner-bg);
|
|
3020
|
-
color: var(--fg);
|
|
3021
|
-
padding: 1rem;
|
|
3022
|
-
margin-bottom: 14px;
|
|
3023
|
-
font-size: 14px;
|
|
3024
|
-
line-height: 1.45;
|
|
3025
|
-
}
|
|
3026
|
-
.notice-text {
|
|
3027
|
-
flex: 1 1 auto;
|
|
3028
|
-
min-width: 0;
|
|
3029
|
-
}
|
|
3030
|
-
/* Links are allowed (buttons are not); underlined so they stay findable on the tint. */
|
|
3031
|
-
.notice a {
|
|
3032
|
-
color: var(--accent);
|
|
3033
|
-
font-weight: 550;
|
|
3034
|
-
text-decoration: underline;
|
|
3035
|
-
text-underline-offset: 2px;
|
|
3036
|
-
}
|
|
3037
|
-
/* The dismiss is the Icon role (button.icon) carrying the app's `x` glyph: a square hit
|
|
3038
|
-
target pinned to the right, pulled 4px into the padding on three sides so it centers on
|
|
3039
|
-
the first line of text without making a one-line notice taller than its text. */
|
|
3040
|
-
button.notice-dismiss {
|
|
3041
|
-
flex: 0 0 auto;
|
|
3042
|
-
display: inline-flex;
|
|
3063
|
+
flex-wrap: wrap;
|
|
3043
3064
|
align-items: center;
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
padding: 0;
|
|
3048
|
-
margin: -4px -4px -4px 0;
|
|
3065
|
+
gap: 4px 10px;
|
|
3066
|
+
margin-top: 6px;
|
|
3067
|
+
font-size: 13px;
|
|
3049
3068
|
}
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3069
|
+
.sched-remade-mark {
|
|
3070
|
+
margin-top: 4px;
|
|
3071
|
+
font-size: 12.5px;
|
|
3072
|
+
color: var(--status-scheduled-fg);
|
|
3053
3073
|
}
|
|
3054
3074
|
|
|
3055
3075
|
.grid2 {
|
|
@@ -3931,7 +3951,7 @@ button.api-filter-clear svg {
|
|
|
3931
3951
|
text-decoration: underline;
|
|
3932
3952
|
}
|
|
3933
3953
|
|
|
3934
|
-
/* Publication card: the
|
|
3954
|
+
/* Publication card: the home page's address with Copy, and the way out to it. */
|
|
3935
3955
|
.pub-card {
|
|
3936
3956
|
display: flex;
|
|
3937
3957
|
flex-direction: column;
|
|
@@ -3942,10 +3962,6 @@ button.api-filter-clear svg {
|
|
|
3942
3962
|
align-items: center;
|
|
3943
3963
|
gap: 12px;
|
|
3944
3964
|
}
|
|
3945
|
-
.pub-key {
|
|
3946
|
-
flex: 0 0 84px;
|
|
3947
|
-
font-size: 12px;
|
|
3948
|
-
}
|
|
3949
3965
|
.pub-val {
|
|
3950
3966
|
flex: 1 1 auto;
|
|
3951
3967
|
min-width: 0;
|
|
@@ -4052,10 +4068,9 @@ button.api-filter-clear svg {
|
|
|
4052
4068
|
deploy / Copy-out), editable surfaces sit on cards and read-only
|
|
4053
4069
|
facts on a recessed "sunken" card. Kestrel's own accents use the
|
|
4054
4070
|
fixed --accent (a slate-blue), independent of the reader surface.
|
|
4055
|
-
A sticky save bar collects
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
the OS via the tokens at the top of this file.
|
|
4071
|
+
A sticky save bar collects every editable section's unsaved
|
|
4072
|
+
changes; the logo saves on its own and doesn't feed it. Theme
|
|
4073
|
+
follows the OS via the tokens at the top of this file.
|
|
4059
4074
|
============================================================ */
|
|
4060
4075
|
.settings {
|
|
4061
4076
|
max-width: 940px;
|
|
@@ -4081,7 +4096,7 @@ button.api-filter-clear svg {
|
|
|
4081
4096
|
margin-bottom: 34px;
|
|
4082
4097
|
}
|
|
4083
4098
|
|
|
4084
|
-
/* --- copying text out of the app (DESIGN §
|
|
4099
|
+
/* --- copying text out of the app (DESIGN §7, ui/copy.ts) ---
|
|
4085
4100
|
A labelled block (a code block, or the API reference's query and content-type tables):
|
|
4086
4101
|
its label line, then the block. The Copy is a control outside the five roles: the word
|
|
4087
4102
|
and its glyph, no fill or border, quiet on the line it shares; the glyph turns to a
|
|
@@ -4143,7 +4158,7 @@ button.copied svg {
|
|
|
4143
4158
|
flex: 0 0 auto;
|
|
4144
4159
|
}
|
|
4145
4160
|
|
|
4146
|
-
/* --- groups: the sections kept together by whom they serve (DESIGN §
|
|
4161
|
+
/* --- groups: the sections kept together by whom they serve (DESIGN §3). A group has no
|
|
4147
4162
|
heading, since each section's title already says what it is; a wider gap than the one
|
|
4148
4163
|
between sections is all that separates one group from the next. --- */
|
|
4149
4164
|
.set-group + .set-group {
|
|
@@ -4194,7 +4209,7 @@ button.copied svg {
|
|
|
4194
4209
|
background: var(--accent-soft);
|
|
4195
4210
|
}
|
|
4196
4211
|
/* In use by scheduled posts (SPEC §9): the scheduled violet, a lifecycle fact and not a
|
|
4197
|
-
warning, the same reading as the scheduled badge (DESIGN §
|
|
4212
|
+
warning, the same reading as the scheduled badge (DESIGN §8). Adds no token. */
|
|
4198
4213
|
.set-chip.inuse {
|
|
4199
4214
|
color: var(--status-scheduled-fg);
|
|
4200
4215
|
border-color: var(--status-scheduled-line);
|
|
@@ -4207,22 +4222,12 @@ button.copied svg {
|
|
|
4207
4222
|
background: currentColor;
|
|
4208
4223
|
opacity: 0.85;
|
|
4209
4224
|
}
|
|
4225
|
+
/* Set at deploy: a fact about where the value lives, not a warning, so it stays neutral
|
|
4226
|
+
rather than borrowing the amber `warn` scale (DESIGN §8). */
|
|
4210
4227
|
.set-chip.readonly {
|
|
4211
|
-
color: var(--warn-fg);
|
|
4212
|
-
border-color: color-mix(in srgb, var(--warn-fg) 30%, var(--line-soft));
|
|
4213
|
-
background: color-mix(in srgb, var(--warn-fg) 9%, transparent);
|
|
4214
|
-
}
|
|
4215
|
-
/* A dashed "Mock" tag beside the Email-template chip — says the section is a
|
|
4216
|
-
preview of a feature not wired to the backend yet. */
|
|
4217
|
-
.set-tag-mock {
|
|
4218
|
-
font-size: 10px;
|
|
4219
|
-
font-weight: 650;
|
|
4220
|
-
letter-spacing: 0.06em;
|
|
4221
|
-
text-transform: uppercase;
|
|
4222
|
-
padding: 3px 7px;
|
|
4223
|
-
border-radius: 6px;
|
|
4224
|
-
border: 1px dashed var(--line-soft);
|
|
4225
4228
|
color: var(--muted);
|
|
4229
|
+
border-color: var(--line);
|
|
4230
|
+
background: var(--chip);
|
|
4226
4231
|
}
|
|
4227
4232
|
|
|
4228
4233
|
/* --- cards: editable surfaces raised, read-only facts recessed --- */
|
|
@@ -4411,7 +4416,7 @@ button.set-field-remove {
|
|
|
4411
4416
|
align-items: flex-start;
|
|
4412
4417
|
padding: 11px 16px;
|
|
4413
4418
|
border-bottom: 1px solid var(--line-soft);
|
|
4414
|
-
background:
|
|
4419
|
+
background: var(--chip);
|
|
4415
4420
|
font-size: 12.5px;
|
|
4416
4421
|
color: var(--muted);
|
|
4417
4422
|
line-height: 1.5;
|
|
@@ -4421,7 +4426,7 @@ button.set-field-remove {
|
|
|
4421
4426
|
height: 15px;
|
|
4422
4427
|
flex: 0 0 auto;
|
|
4423
4428
|
margin-top: 1px;
|
|
4424
|
-
color: var(--
|
|
4429
|
+
color: var(--muted);
|
|
4425
4430
|
}
|
|
4426
4431
|
.set-kv {
|
|
4427
4432
|
display: grid;
|
|
@@ -4758,14 +4763,21 @@ button.set-field-remove {
|
|
|
4758
4763
|
}
|
|
4759
4764
|
.set-recip-add {
|
|
4760
4765
|
display: flex;
|
|
4766
|
+
flex-wrap: wrap;
|
|
4761
4767
|
gap: 8px;
|
|
4762
4768
|
max-width: 420px;
|
|
4763
4769
|
}
|
|
4764
4770
|
.set-recip-add input {
|
|
4765
4771
|
flex: 1;
|
|
4766
4772
|
}
|
|
4773
|
+
/* A refused address says why on its own line under the field and its button (DESIGN §5). */
|
|
4774
|
+
.set-recip-add .field-error {
|
|
4775
|
+
flex-basis: 100%;
|
|
4776
|
+
order: 1;
|
|
4777
|
+
margin-top: 0;
|
|
4778
|
+
}
|
|
4767
4779
|
|
|
4768
|
-
/* --- ways to subscribe (DESIGN §
|
|
4780
|
+
/* --- ways to subscribe (DESIGN §3): one card, the two ways as bands in reading order,
|
|
4769
4781
|
the page then the form, split by a full-width rule like the other cards' bands. Each
|
|
4770
4782
|
band has the same head (a glyph, a title, one line) so they read as two options. The
|
|
4771
4783
|
card folds by the section's own width, since the sidebar decides how wide Settings is. --- */
|
|
@@ -4956,7 +4968,7 @@ button.set-embed-copy svg {
|
|
|
4956
4968
|
color: var(--muted);
|
|
4957
4969
|
}
|
|
4958
4970
|
|
|
4959
|
-
/* --- email template
|
|
4971
|
+
/* --- email template: a standalone sample-email frame, then a card holding
|
|
4960
4972
|
the HTML template editor and a collapsible variable reference. The example
|
|
4961
4973
|
templates are authored with a <style> block; the preview renders them in an
|
|
4962
4974
|
isolated iframe, so a template's own styles apply as a mail client would show
|
|
@@ -4991,7 +5003,7 @@ button.set-embed-copy svg {
|
|
|
4991
5003
|
font-size: 12px;
|
|
4992
5004
|
color: var(--muted);
|
|
4993
5005
|
}
|
|
4994
|
-
/* Syntax-highlight overlay editor
|
|
5006
|
+
/* Syntax-highlight overlay editor: a transparent <textarea> over a
|
|
4995
5007
|
highlighted <pre>, plus a line-number gutter. All three share identical font
|
|
4996
5008
|
metrics so the caret sits exactly over the colored text; the textarea is the only
|
|
4997
5009
|
scroller and the gutter + overlay are synced to it in script. */
|
|
@@ -5067,7 +5079,7 @@ button.set-embed-copy svg {
|
|
|
5067
5079
|
.set-tpl-editor-wrap.show-lines .set-tpl-editor {
|
|
5068
5080
|
left: 47px;
|
|
5069
5081
|
}
|
|
5070
|
-
/* Syntax colors
|
|
5082
|
+
/* Syntax colors. */
|
|
5071
5083
|
.cx-tag {
|
|
5072
5084
|
color: var(--syntax-tag);
|
|
5073
5085
|
}
|
|
@@ -5097,7 +5109,7 @@ button.set-embed-copy svg {
|
|
|
5097
5109
|
padding: 0 2px;
|
|
5098
5110
|
font-weight: 600;
|
|
5099
5111
|
}
|
|
5100
|
-
/* Markdown token classes
|
|
5112
|
+
/* Markdown token classes — the minimal scheme: the MARK carries the color, the
|
|
5101
5113
|
CONTENT keeps --syntax-fg and carries only weight/slant. So a bold word reads as weight, not
|
|
5102
5114
|
as a hue that competes with a heading. Heading text is bold with a crimson # mark; emphasis
|
|
5103
5115
|
asterisks are pink around plain weighted/slanted text; block markers (>, bullets, the fence
|
|
@@ -5309,7 +5321,7 @@ button.set-embed-copy svg {
|
|
|
5309
5321
|
background: var(--accent-soft);
|
|
5310
5322
|
border-color: color-mix(in srgb, var(--accent) 30%, var(--line-soft));
|
|
5311
5323
|
}
|
|
5312
|
-
/* Required-token pills
|
|
5324
|
+
/* Required-token pills: live green/red for the two tokens whose
|
|
5313
5325
|
absence blocks a save, shown upper-right of the editor toolbar. */
|
|
5314
5326
|
.set-tpl-required {
|
|
5315
5327
|
display: flex;
|
|
@@ -5373,10 +5385,7 @@ button.set-embed-copy svg {
|
|
|
5373
5385
|
box-shadow: 0 1px 4px rgba(15, 18, 25, 0.1);
|
|
5374
5386
|
transition: max-width 0.16s ease;
|
|
5375
5387
|
}
|
|
5376
|
-
/* "Start from example"
|
|
5377
|
-
.set-menu {
|
|
5378
|
-
position: relative;
|
|
5379
|
-
}
|
|
5388
|
+
/* "Start from example": the trigger of a shared menu (openMenu). */
|
|
5380
5389
|
.set-menu-btn {
|
|
5381
5390
|
display: inline-flex;
|
|
5382
5391
|
align-items: center;
|
|
@@ -5390,45 +5399,6 @@ button.set-embed-copy svg {
|
|
|
5390
5399
|
transform: translateY(-1px) rotate(45deg);
|
|
5391
5400
|
opacity: 0.6;
|
|
5392
5401
|
}
|
|
5393
|
-
.set-menu-list {
|
|
5394
|
-
position: absolute;
|
|
5395
|
-
left: 0;
|
|
5396
|
-
top: calc(100% + 6px);
|
|
5397
|
-
z-index: var(--z-menu);
|
|
5398
|
-
width: 280px;
|
|
5399
|
-
max-width: min(280px, 78vw);
|
|
5400
|
-
padding: 5px;
|
|
5401
|
-
background: var(--card);
|
|
5402
|
-
border: 1px solid var(--line);
|
|
5403
|
-
border-radius: 10px;
|
|
5404
|
-
box-shadow: 0 10px 28px rgba(15, 18, 25, 0.16);
|
|
5405
|
-
}
|
|
5406
|
-
.set-menu-list button {
|
|
5407
|
-
display: block;
|
|
5408
|
-
width: 100%;
|
|
5409
|
-
text-align: left;
|
|
5410
|
-
border: 0;
|
|
5411
|
-
background: transparent;
|
|
5412
|
-
border-radius: 7px;
|
|
5413
|
-
padding: 8px 10px;
|
|
5414
|
-
cursor: pointer;
|
|
5415
|
-
}
|
|
5416
|
-
.set-menu-list button:hover {
|
|
5417
|
-
background: var(--sunken);
|
|
5418
|
-
}
|
|
5419
|
-
.set-menu-name {
|
|
5420
|
-
display: block;
|
|
5421
|
-
font-size: 13px;
|
|
5422
|
-
font-weight: 600;
|
|
5423
|
-
color: var(--fg);
|
|
5424
|
-
}
|
|
5425
|
-
.set-menu-desc {
|
|
5426
|
-
display: block;
|
|
5427
|
-
margin-top: 1px;
|
|
5428
|
-
font-size: 11.5px;
|
|
5429
|
-
color: var(--muted);
|
|
5430
|
-
line-height: 1.35;
|
|
5431
|
-
}
|
|
5432
5402
|
/* Variables card heading. */
|
|
5433
5403
|
.set-tpl-vartitle {
|
|
5434
5404
|
margin: 0 0 2px;
|
|
@@ -5442,7 +5412,7 @@ button.set-embed-copy svg {
|
|
|
5442
5412
|
.set-tpl-varcard .set-tpl-vars-body {
|
|
5443
5413
|
padding: 0;
|
|
5444
5414
|
}
|
|
5445
|
-
/* The
|
|
5415
|
+
/* The note at the foot of a card: what the section is for, or where it is set. */
|
|
5446
5416
|
.set-note {
|
|
5447
5417
|
display: flex;
|
|
5448
5418
|
align-items: flex-start;
|
|
@@ -5707,7 +5677,7 @@ button.set-embed-copy svg {
|
|
|
5707
5677
|
icon rail — and collapses to edge-to-edge once the sidebar becomes the off-canvas
|
|
5708
5678
|
mobile drawer. The post editor keeps its own autosave + conflict model, so it does
|
|
5709
5679
|
NOT use this bar. Its Save is a Primary button — now brand-blue --accent app-wide
|
|
5710
|
-
(DESIGN §
|
|
5680
|
+
(DESIGN §7), so the bar needs no primary override of its own. --- */
|
|
5711
5681
|
.savebar {
|
|
5712
5682
|
position: fixed;
|
|
5713
5683
|
/* Clears the full 240px sidebar; narrowed to the rail / drawer in the rules below. */
|