@kurtbruns/kestrel 1.5.0 → 1.7.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 +5 -17
- package/CHANGELOG.md +78 -1
- package/README.md +1 -1
- package/cli/context.mjs +20 -11
- package/cli/dev-api.mjs +73 -9
- package/cli/dev-server.mjs +72 -0
- package/cli/dev.mjs +39 -11
- package/cli/init.mjs +8 -2
- package/cli/reset.mjs +2 -3
- package/cli/seed.mjs +7 -5
- package/cli/sync-migrations.mjs +5 -1
- package/dist/public/dashboard/app.js +366 -325
- package/dist/public/dashboard/app.js.map +4 -4
- package/dist/public/dashboard/index.html +2 -2
- package/dist/public/dashboard/styles.css +640 -126
- package/dist/worker/index.js +1198 -748
- package/docs/README.md +3 -3
- package/docs/get-started/02-deploy.md +29 -13
- package/docs/get-started/03-access.md +3 -3
- package/docs/get-started/04-resend.md +8 -6
- package/docs/get-started/05-verify.md +3 -3
- package/docs/get-started/06-go-live.md +2 -2
- package/docs/guides/02-ses.md +2 -2
- package/docs/guides/08-try-locally.md +108 -0
- package/docs/reference/01-configuration.md +18 -7
- package/docs/reference/02-sending-domain-dns.md +1 -1
- package/migrations/0003_post_slug_auto.sql +14 -0
- package/package.json +5 -6
- package/template/claude/CLAUDE.md +2 -2
- package/template/package.json +5 -0
- package/template/wrangler.jsonc +7 -6
- package/cli/dev-port.mjs +0 -57
- package/docs/get-started/01-overview.md +0 -47
|
@@ -899,6 +899,34 @@ body.nav-resizing .sidebar {
|
|
|
899
899
|
.auth-wall button {
|
|
900
900
|
margin-top: 12px;
|
|
901
901
|
}
|
|
902
|
+
/* 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
|
+
.config-wall {
|
|
905
|
+
max-width: 560px;
|
|
906
|
+
text-align: left;
|
|
907
|
+
}
|
|
908
|
+
/* Text for a screen reader alone, such as a link's "(opens in a new tab)" beside a ↗ that is
|
|
909
|
+
hidden from it. */
|
|
910
|
+
.visually-hidden {
|
|
911
|
+
position: absolute;
|
|
912
|
+
width: 1px;
|
|
913
|
+
height: 1px;
|
|
914
|
+
margin: -1px;
|
|
915
|
+
padding: 0;
|
|
916
|
+
overflow: hidden;
|
|
917
|
+
clip-path: inset(50%);
|
|
918
|
+
white-space: nowrap;
|
|
919
|
+
border: 0;
|
|
920
|
+
}
|
|
921
|
+
.config-wall h2:focus {
|
|
922
|
+
outline: none;
|
|
923
|
+
}
|
|
924
|
+
.config-wall-problem {
|
|
925
|
+
padding: 10px 12px;
|
|
926
|
+
border-radius: 8px;
|
|
927
|
+
background: var(--bg);
|
|
928
|
+
overflow-wrap: anywhere;
|
|
929
|
+
}
|
|
902
930
|
|
|
903
931
|
/* The routed view. Capped for readability and centered in the space beside the
|
|
904
932
|
sidebar; the editor gets a slightly narrower canvas to match its old width. */
|
|
@@ -1169,6 +1197,7 @@ button:active {
|
|
|
1169
1197
|
button.primary,
|
|
1170
1198
|
button.secondary,
|
|
1171
1199
|
button.ghost,
|
|
1200
|
+
a.ghost.btn,
|
|
1172
1201
|
button.danger,
|
|
1173
1202
|
button.danger-subtle {
|
|
1174
1203
|
min-height: 35px;
|
|
@@ -1212,7 +1241,8 @@ button.secondary:hover {
|
|
|
1212
1241
|
/* Ghost — the quiet neutral role: back, cancel, a menu trigger, a pager step, a copy
|
|
1213
1242
|
control. The default for anything not asking for attention; absorbs the old
|
|
1214
1243
|
.ghost-btn / .set-btn-ghost idioms. */
|
|
1215
|
-
button.ghost
|
|
1244
|
+
button.ghost,
|
|
1245
|
+
a.ghost.btn {
|
|
1216
1246
|
display: inline-flex;
|
|
1217
1247
|
align-items: center;
|
|
1218
1248
|
gap: 6px;
|
|
@@ -1223,10 +1253,19 @@ button.ghost {
|
|
|
1223
1253
|
padding: 6px 12px;
|
|
1224
1254
|
border-radius: 6px;
|
|
1225
1255
|
}
|
|
1226
|
-
button.ghost:hover
|
|
1256
|
+
button.ghost:hover,
|
|
1257
|
+
a.ghost.btn:hover {
|
|
1227
1258
|
color: var(--fg);
|
|
1228
1259
|
border-color: var(--muted);
|
|
1229
1260
|
}
|
|
1261
|
+
/* A link that wears Ghost (`a.ghost.btn`: Web version ↗ goes to a page rather than
|
|
1262
|
+
acting on one), with a link's own underline and transition taken off so it reads as
|
|
1263
|
+
the buttons beside it. The plain `a.ghost` is the sidebar's text-link Sign out. */
|
|
1264
|
+
a.ghost.btn {
|
|
1265
|
+
text-decoration: none;
|
|
1266
|
+
line-height: normal;
|
|
1267
|
+
transition: border-color 0.12s;
|
|
1268
|
+
}
|
|
1230
1269
|
/* A copy control flashes green (--ok) once the clipboard write lands. */
|
|
1231
1270
|
button.ghost.copied {
|
|
1232
1271
|
color: var(--ok-fg);
|
|
@@ -1699,6 +1738,83 @@ th.num .th-sort {
|
|
|
1699
1738
|
align-items: center;
|
|
1700
1739
|
gap: 10px;
|
|
1701
1740
|
}
|
|
1741
|
+
/* A post's web version (#/web/post/…): the post page as it will look, filling the window
|
|
1742
|
+
with no app chrome, since it opens in a tab of its own. The window scrolls the page. */
|
|
1743
|
+
body.web-mode .sidebar,
|
|
1744
|
+
body.web-mode .nav-toggle {
|
|
1745
|
+
display: none;
|
|
1746
|
+
}
|
|
1747
|
+
body.web-mode .content {
|
|
1748
|
+
max-width: none;
|
|
1749
|
+
margin: 0;
|
|
1750
|
+
padding: 0;
|
|
1751
|
+
}
|
|
1752
|
+
/* The page sits under one strip that says where the post stands (DESIGN §7). The strip is
|
|
1753
|
+
the editor's, above the frame, and the frame is as tall as the post, so the window scrolls
|
|
1754
|
+
and the strip goes up with the page like the test pages' strip. It wears the brand accent
|
|
1755
|
+
like the reader surface's dev pill, its tag as the pill's inverted chip, and its content
|
|
1756
|
+
sits in the default template's column so it lines up with the post. The test pages carry
|
|
1757
|
+
the same strip in the reader shell (READER_BANNER_STYLE in src/lib/page.ts); keep the two
|
|
1758
|
+
in sync. */
|
|
1759
|
+
/* At least the window's height, so a post shorter than the window still has its own
|
|
1760
|
+
ground to the bottom: the frame grows into what's left, and the framed page's background
|
|
1761
|
+
fills it. */
|
|
1762
|
+
.web-page {
|
|
1763
|
+
display: flex;
|
|
1764
|
+
flex-direction: column;
|
|
1765
|
+
min-height: 100dvh;
|
|
1766
|
+
}
|
|
1767
|
+
.web-bar {
|
|
1768
|
+
flex: none;
|
|
1769
|
+
background: var(--accent);
|
|
1770
|
+
color: var(--accent-on);
|
|
1771
|
+
font-size: 13.5px;
|
|
1772
|
+
font-weight: 500;
|
|
1773
|
+
line-height: 1.4;
|
|
1774
|
+
}
|
|
1775
|
+
/* The tag runs in, inline with the sentence, so on a phone the sentence wraps beneath it at
|
|
1776
|
+
the strip's full width rather than in a narrow column beside it. */
|
|
1777
|
+
.web-bar-in {
|
|
1778
|
+
/* The default template's 664px column inside its 12px gutter, with the column's 32px
|
|
1779
|
+
padding, so the strip's text starts on the post's left edge at any width over a phone's. */
|
|
1780
|
+
max-width: 688px;
|
|
1781
|
+
margin: 0 auto;
|
|
1782
|
+
padding: 10px 44px;
|
|
1783
|
+
text-align: left;
|
|
1784
|
+
}
|
|
1785
|
+
.web-bar-tag {
|
|
1786
|
+
display: inline-block;
|
|
1787
|
+
margin-right: 10px;
|
|
1788
|
+
font-size: 10px;
|
|
1789
|
+
letter-spacing: 0.09em;
|
|
1790
|
+
text-transform: uppercase;
|
|
1791
|
+
font-weight: 700;
|
|
1792
|
+
padding: 1px 7px;
|
|
1793
|
+
border-radius: 999px;
|
|
1794
|
+
background: var(--accent-on);
|
|
1795
|
+
color: var(--accent);
|
|
1796
|
+
}
|
|
1797
|
+
.web-bar-text {
|
|
1798
|
+
overflow-wrap: anywhere;
|
|
1799
|
+
}
|
|
1800
|
+
@media (max-width: 560px) {
|
|
1801
|
+
.web-bar-in {
|
|
1802
|
+
padding: 10px 16px;
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
.web-frame {
|
|
1806
|
+
display: block;
|
|
1807
|
+
flex: 1 0 auto;
|
|
1808
|
+
width: 100%;
|
|
1809
|
+
/* The post's height once measured; until then a window's, so the page doesn't flash short. */
|
|
1810
|
+
height: 100vh;
|
|
1811
|
+
border: 0;
|
|
1812
|
+
}
|
|
1813
|
+
.web-error {
|
|
1814
|
+
max-width: 640px;
|
|
1815
|
+
margin: 48px auto;
|
|
1816
|
+
padding: 0 16px;
|
|
1817
|
+
}
|
|
1702
1818
|
/* Sent record view (#148): a read-only delivery record. The outcome tiles reuse the
|
|
1703
1819
|
semantic feedback scales (--ok / --warn / --danger, DESIGN §3); a status badge, not
|
|
1704
1820
|
a feedback color, still labels the send's lifecycle. */
|
|
@@ -2332,6 +2448,13 @@ th.num .th-sort {
|
|
|
2332
2448
|
.composer-foot:hover {
|
|
2333
2449
|
color: var(--fg);
|
|
2334
2450
|
}
|
|
2451
|
+
/* On the Preview tab the image foot dims with the toolbar (`.toolbar.off`): a click there
|
|
2452
|
+
would insert into the hidden Edit text. A drop on the composer still works; it switches
|
|
2453
|
+
to Edit first, so the result is seen. */
|
|
2454
|
+
.composer-foot.off {
|
|
2455
|
+
opacity: 0.4;
|
|
2456
|
+
pointer-events: none;
|
|
2457
|
+
}
|
|
2335
2458
|
.composer-foot svg {
|
|
2336
2459
|
width: 16px;
|
|
2337
2460
|
height: 16px;
|
|
@@ -2437,29 +2560,52 @@ th.num .th-sort {
|
|
|
2437
2560
|
}
|
|
2438
2561
|
|
|
2439
2562
|
/* toasts (live region) */
|
|
2563
|
+
/* Spans the viewport and centers its toasts, rather than left: 50% plus a transform,
|
|
2564
|
+
which caps a toast's width at half the screen and wraps a sentence on a phone. */
|
|
2440
2565
|
#toasts {
|
|
2441
2566
|
position: fixed;
|
|
2442
2567
|
bottom: 20px;
|
|
2443
|
-
left:
|
|
2444
|
-
|
|
2568
|
+
left: 16px;
|
|
2569
|
+
right: 16px;
|
|
2445
2570
|
display: flex;
|
|
2446
2571
|
flex-direction: column;
|
|
2447
|
-
gap: 8px;
|
|
2448
2572
|
align-items: center;
|
|
2449
2573
|
z-index: var(--z-toast);
|
|
2450
2574
|
pointer-events: none;
|
|
2451
2575
|
}
|
|
2576
|
+
/* Spaced by margin, not the column's gap, so a leaving toast can collapse its share. */
|
|
2452
2577
|
.toast {
|
|
2578
|
+
box-sizing: border-box;
|
|
2579
|
+
max-width: 480px;
|
|
2580
|
+
margin-top: 8px;
|
|
2581
|
+
overflow: hidden;
|
|
2453
2582
|
background: var(--fg);
|
|
2454
2583
|
color: var(--bg);
|
|
2455
2584
|
padding: 10px 16px;
|
|
2456
2585
|
border-radius: 8px;
|
|
2457
2586
|
font-size: 14px;
|
|
2458
2587
|
opacity: 0;
|
|
2459
|
-
|
|
2588
|
+
transform: translateY(6px);
|
|
2589
|
+
transition:
|
|
2590
|
+
opacity 0.2s,
|
|
2591
|
+
transform 0.2s;
|
|
2460
2592
|
}
|
|
2461
2593
|
.toast.show {
|
|
2462
2594
|
opacity: 1;
|
|
2595
|
+
transform: none;
|
|
2596
|
+
}
|
|
2597
|
+
/* Leaving: fade while the height (set inline by leaveToast, since a transition can't run
|
|
2598
|
+
from auto), padding, and spacing collapse, so the stack settles smoothly. 0.3s is
|
|
2599
|
+
TOAST_LEAVE_MS in client/ui/widgets.ts. */
|
|
2600
|
+
.toast.leaving {
|
|
2601
|
+
opacity: 0;
|
|
2602
|
+
margin-top: 0;
|
|
2603
|
+
padding-block: 0;
|
|
2604
|
+
transition:
|
|
2605
|
+
opacity 0.2s,
|
|
2606
|
+
height 0.3s,
|
|
2607
|
+
margin-top 0.3s,
|
|
2608
|
+
padding-block 0.3s;
|
|
2463
2609
|
}
|
|
2464
2610
|
|
|
2465
2611
|
/* modal */
|
|
@@ -2491,6 +2637,18 @@ th.num .th-sort {
|
|
|
2491
2637
|
margin: 0 0 14px;
|
|
2492
2638
|
line-height: 1.5;
|
|
2493
2639
|
}
|
|
2640
|
+
/* A dialog's not-connected line (DESIGN §2) takes the health block's amber warning, so it
|
|
2641
|
+
reads as the reason the dialog's action is off rather than as one more hint. */
|
|
2642
|
+
.modal .hint.not-connected {
|
|
2643
|
+
color: var(--warn-fg);
|
|
2644
|
+
background: var(--warn-bg);
|
|
2645
|
+
border: 1px solid var(--warn-line);
|
|
2646
|
+
border-radius: 8px;
|
|
2647
|
+
padding: 10px 12px;
|
|
2648
|
+
}
|
|
2649
|
+
.modal .hint.not-connected a {
|
|
2650
|
+
color: inherit;
|
|
2651
|
+
}
|
|
2494
2652
|
.modal .actions {
|
|
2495
2653
|
display: flex;
|
|
2496
2654
|
justify-content: flex-end;
|
|
@@ -2784,7 +2942,7 @@ textarea.is-invalid {
|
|
|
2784
2942
|
.info.tip-below:focus::after {
|
|
2785
2943
|
transform: translateY(0);
|
|
2786
2944
|
}
|
|
2787
|
-
/* "Auto-generate from subject" toggle, grouped just under the slug field. */
|
|
2945
|
+
/* The "Auto-generate from subject" toggle, grouped just under the slug field. */
|
|
2788
2946
|
.slug-auto-toggle {
|
|
2789
2947
|
display: inline-flex;
|
|
2790
2948
|
align-items: center;
|
|
@@ -2802,7 +2960,7 @@ textarea.is-invalid {
|
|
|
2802
2960
|
accent-color: var(--ring);
|
|
2803
2961
|
cursor: pointer;
|
|
2804
2962
|
}
|
|
2805
|
-
/* Slug is muted while it
|
|
2963
|
+
/* Slug is muted while it follows the subject; normal once set by hand. */
|
|
2806
2964
|
#f-slug.slug-auto {
|
|
2807
2965
|
color: var(--muted);
|
|
2808
2966
|
}
|
|
@@ -3411,44 +3569,6 @@ button.api-filter-clear svg {
|
|
|
3411
3569
|
margin: 6px 0 0;
|
|
3412
3570
|
font-size: 14px;
|
|
3413
3571
|
}
|
|
3414
|
-
.api-ex {
|
|
3415
|
-
margin-top: 10px;
|
|
3416
|
-
}
|
|
3417
|
-
.api-ex-head {
|
|
3418
|
-
display: flex;
|
|
3419
|
-
align-items: center;
|
|
3420
|
-
justify-content: space-between;
|
|
3421
|
-
margin: 8px 0 4px;
|
|
3422
|
-
}
|
|
3423
|
-
/* A code block's Copy (DESIGN §1, a control outside the five roles): the word and its
|
|
3424
|
-
glyph, no fill or border, quiet on the label line it shares. The glyph turns to a check
|
|
3425
|
-
in the ok hue for a moment once the copy has landed; the word stays. */
|
|
3426
|
-
button.api-copy {
|
|
3427
|
-
display: inline-flex;
|
|
3428
|
-
align-items: center;
|
|
3429
|
-
gap: 5px;
|
|
3430
|
-
border: 0;
|
|
3431
|
-
background: transparent;
|
|
3432
|
-
color: var(--muted);
|
|
3433
|
-
font-size: 12px;
|
|
3434
|
-
padding: 2px 6px;
|
|
3435
|
-
border-radius: 5px;
|
|
3436
|
-
}
|
|
3437
|
-
button.api-copy:hover {
|
|
3438
|
-
color: var(--fg);
|
|
3439
|
-
background: var(--hover);
|
|
3440
|
-
}
|
|
3441
|
-
/* No pressed nudge: on a quiet word the button-wide half-pixel drop reads as a jump. */
|
|
3442
|
-
button.api-copy:active {
|
|
3443
|
-
transform: none;
|
|
3444
|
-
}
|
|
3445
|
-
button.api-copy svg {
|
|
3446
|
-
width: 13px;
|
|
3447
|
-
height: 13px;
|
|
3448
|
-
}
|
|
3449
|
-
button.api-copy.copied svg {
|
|
3450
|
-
color: var(--ok-fg);
|
|
3451
|
-
}
|
|
3452
3572
|
/* A query parameter the route can't be called without. */
|
|
3453
3573
|
.api-req {
|
|
3454
3574
|
margin-left: 4px;
|
|
@@ -3458,22 +3578,7 @@ button.api-copy.copied svg {
|
|
|
3458
3578
|
border-radius: 4px;
|
|
3459
3579
|
padding: 0 4px;
|
|
3460
3580
|
}
|
|
3461
|
-
.api-ex-label {
|
|
3462
|
-
display: block;
|
|
3463
|
-
font-size: 12px;
|
|
3464
|
-
font-weight: 600;
|
|
3465
|
-
color: var(--muted);
|
|
3466
|
-
}
|
|
3467
3581
|
|
|
3468
|
-
.api-ex pre {
|
|
3469
|
-
margin: 0;
|
|
3470
|
-
overflow-x: auto;
|
|
3471
|
-
background: var(--bg);
|
|
3472
|
-
border: 1px solid var(--line);
|
|
3473
|
-
border-radius: 6px;
|
|
3474
|
-
padding: 10px 12px;
|
|
3475
|
-
font: 12.5px / 1.5 var(--mono);
|
|
3476
|
-
}
|
|
3477
3582
|
.api-empty {
|
|
3478
3583
|
margin: 20px 0;
|
|
3479
3584
|
}
|
|
@@ -3852,9 +3957,6 @@ button.api-copy.copied svg {
|
|
|
3852
3957
|
border-radius: 6px;
|
|
3853
3958
|
padding: 6px 9px;
|
|
3854
3959
|
}
|
|
3855
|
-
.pub-row button.ghost {
|
|
3856
|
-
flex: 0 0 auto;
|
|
3857
|
-
}
|
|
3858
3960
|
.pub-foot {
|
|
3859
3961
|
margin-top: 4px;
|
|
3860
3962
|
}
|
|
@@ -3928,6 +4030,16 @@ button.api-copy.copied svg {
|
|
|
3928
4030
|
.setup-steps li button {
|
|
3929
4031
|
flex: 0 0 auto;
|
|
3930
4032
|
}
|
|
4033
|
+
/* The setup step's Copy keeps the step's button look, with the shared Copy's glyph. */
|
|
4034
|
+
.setup-steps li button[data-copy] {
|
|
4035
|
+
display: inline-flex;
|
|
4036
|
+
align-items: center;
|
|
4037
|
+
gap: 5px;
|
|
4038
|
+
}
|
|
4039
|
+
.setup-steps li button[data-copy] svg {
|
|
4040
|
+
width: 14px;
|
|
4041
|
+
height: 14px;
|
|
4042
|
+
}
|
|
3931
4043
|
.setup-url {
|
|
3932
4044
|
font: 12.5px var(--mono);
|
|
3933
4045
|
color: var(--muted);
|
|
@@ -3968,6 +4080,75 @@ button.api-copy.copied svg {
|
|
|
3968
4080
|
.set-sec {
|
|
3969
4081
|
margin-bottom: 34px;
|
|
3970
4082
|
}
|
|
4083
|
+
|
|
4084
|
+
/* --- copying text out of the app (DESIGN §1, ui/copy.ts) ---
|
|
4085
|
+
A labelled block (a code block, or the API reference's query and content-type tables):
|
|
4086
|
+
its label line, then the block. The Copy is a control outside the five roles: the word
|
|
4087
|
+
and its glyph, no fill or border, quiet on the line it shares; the glyph turns to a
|
|
4088
|
+
check in the ok hue for a moment once a copy has landed, and the word stays. A Copy
|
|
4089
|
+
beside a single value (an address) is the same control. --- */
|
|
4090
|
+
.block {
|
|
4091
|
+
margin-top: 10px;
|
|
4092
|
+
}
|
|
4093
|
+
.block-head {
|
|
4094
|
+
display: flex;
|
|
4095
|
+
align-items: center;
|
|
4096
|
+
justify-content: space-between;
|
|
4097
|
+
margin: 8px 0 4px;
|
|
4098
|
+
}
|
|
4099
|
+
button.copy-btn {
|
|
4100
|
+
display: inline-flex;
|
|
4101
|
+
align-items: center;
|
|
4102
|
+
gap: 5px;
|
|
4103
|
+
border: 0;
|
|
4104
|
+
background: transparent;
|
|
4105
|
+
color: var(--muted);
|
|
4106
|
+
font-size: 13px;
|
|
4107
|
+
padding: 4px 8px;
|
|
4108
|
+
border-radius: 5px;
|
|
4109
|
+
}
|
|
4110
|
+
button.copy-btn:hover {
|
|
4111
|
+
color: var(--fg);
|
|
4112
|
+
background: var(--hover);
|
|
4113
|
+
}
|
|
4114
|
+
/* No pressed nudge: on a quiet word the button-wide half-pixel drop reads as a jump. */
|
|
4115
|
+
button.copy-btn:active {
|
|
4116
|
+
transform: none;
|
|
4117
|
+
}
|
|
4118
|
+
button.copy-btn svg {
|
|
4119
|
+
width: 14px;
|
|
4120
|
+
height: 14px;
|
|
4121
|
+
}
|
|
4122
|
+
/* Any Copy that has just landed, whatever button it rides (ui/copy.ts). */
|
|
4123
|
+
button.copied svg {
|
|
4124
|
+
color: var(--ok-fg);
|
|
4125
|
+
}
|
|
4126
|
+
.block-label {
|
|
4127
|
+
display: block;
|
|
4128
|
+
font-size: 12px;
|
|
4129
|
+
font-weight: 600;
|
|
4130
|
+
color: var(--muted);
|
|
4131
|
+
}
|
|
4132
|
+
.block pre {
|
|
4133
|
+
margin: 0;
|
|
4134
|
+
overflow-x: auto;
|
|
4135
|
+
background: var(--bg);
|
|
4136
|
+
border: 1px solid var(--line);
|
|
4137
|
+
border-radius: 6px;
|
|
4138
|
+
padding: 10px 12px;
|
|
4139
|
+
font: 12.5px / 1.5 var(--mono);
|
|
4140
|
+
}
|
|
4141
|
+
/* A Copy beside a value keeps its size however long the value runs. */
|
|
4142
|
+
.pub-row button.copy-btn {
|
|
4143
|
+
flex: 0 0 auto;
|
|
4144
|
+
}
|
|
4145
|
+
|
|
4146
|
+
/* --- groups: the sections kept together by whom they serve (DESIGN §5). A group has no
|
|
4147
|
+
heading, since each section's title already says what it is; a wider gap than the one
|
|
4148
|
+
between sections is all that separates one group from the next. --- */
|
|
4149
|
+
.set-group + .set-group {
|
|
4150
|
+
margin-top: 22px;
|
|
4151
|
+
}
|
|
3971
4152
|
.set-sec-head {
|
|
3972
4153
|
display: flex;
|
|
3973
4154
|
align-items: center;
|
|
@@ -4065,7 +4246,7 @@ button.api-copy.copied svg {
|
|
|
4065
4246
|
padding: 18px;
|
|
4066
4247
|
}
|
|
4067
4248
|
|
|
4068
|
-
/* Kestrel-accent links inside the settings cards (operator-guide links
|
|
4249
|
+
/* Kestrel-accent links inside the settings cards (operator-guide links). */
|
|
4069
4250
|
.set-link {
|
|
4070
4251
|
color: var(--accent);
|
|
4071
4252
|
font-weight: 550;
|
|
@@ -4087,6 +4268,11 @@ button.api-copy.copied svg {
|
|
|
4087
4268
|
flex-direction: column;
|
|
4088
4269
|
gap: 8px;
|
|
4089
4270
|
}
|
|
4271
|
+
/* The hint and the not-square warning stack in one column, under the tile on a wide
|
|
4272
|
+
screen and beside it where the slot becomes a row. */
|
|
4273
|
+
.set-logo-notes {
|
|
4274
|
+
min-width: 0;
|
|
4275
|
+
}
|
|
4090
4276
|
.set-logo-tile {
|
|
4091
4277
|
width: 100%;
|
|
4092
4278
|
aspect-ratio: 1;
|
|
@@ -4481,16 +4667,6 @@ button.set-field-remove {
|
|
|
4481
4667
|
.set-ce-edit {
|
|
4482
4668
|
padding: 18px;
|
|
4483
4669
|
}
|
|
4484
|
-
.set-ce-lock {
|
|
4485
|
-
display: flex;
|
|
4486
|
-
align-items: center;
|
|
4487
|
-
gap: 6px;
|
|
4488
|
-
}
|
|
4489
|
-
.set-ce-lock svg {
|
|
4490
|
-
width: 13px;
|
|
4491
|
-
height: 13px;
|
|
4492
|
-
flex: 0 0 auto;
|
|
4493
|
-
}
|
|
4494
4670
|
.set-ce-editfoot {
|
|
4495
4671
|
display: flex;
|
|
4496
4672
|
align-items: center;
|
|
@@ -4589,89 +4765,194 @@ button.set-field-remove {
|
|
|
4589
4765
|
flex: 1;
|
|
4590
4766
|
}
|
|
4591
4767
|
|
|
4592
|
-
/* --- ways to subscribe:
|
|
4593
|
-
|
|
4768
|
+
/* --- ways to subscribe (DESIGN §5): one card, the two ways as bands in reading order,
|
|
4769
|
+
the page then the form, split by a full-width rule like the other cards' bands. Each
|
|
4770
|
+
band has the same head (a glyph, a title, one line) so they read as two options. The
|
|
4771
|
+
card folds by the section's own width, since the sidebar decides how wide Settings is. --- */
|
|
4772
|
+
.set-sec-subscribe {
|
|
4773
|
+
container-type: inline-size;
|
|
4774
|
+
}
|
|
4775
|
+
.set-way + .set-way {
|
|
4776
|
+
border-top: 1px solid var(--line);
|
|
4777
|
+
}
|
|
4778
|
+
/* A control that switches what the band shows (the embed's Styled | Plain) sits at
|
|
4779
|
+
the head's right end, as the reader pages' width toggle does. */
|
|
4780
|
+
.set-way-head {
|
|
4594
4781
|
display: flex;
|
|
4595
|
-
align-items:
|
|
4782
|
+
align-items: flex-start;
|
|
4596
4783
|
justify-content: space-between;
|
|
4597
|
-
gap:
|
|
4598
|
-
|
|
4599
|
-
|
|
4784
|
+
gap: 10px 16px;
|
|
4785
|
+
padding: 18px 18px 0;
|
|
4786
|
+
}
|
|
4787
|
+
/* The name takes the room the control leaves, so the control keeps its size. */
|
|
4788
|
+
.set-way-name {
|
|
4789
|
+
flex: 1 1 0;
|
|
4790
|
+
min-width: 0;
|
|
4600
4791
|
}
|
|
4601
|
-
.
|
|
4792
|
+
/* Set like a field label, its glyph like a note's, so the band ranks with the other cards. */
|
|
4793
|
+
.set-way-title {
|
|
4794
|
+
display: flex;
|
|
4795
|
+
align-items: center;
|
|
4796
|
+
gap: 7px;
|
|
4602
4797
|
margin: 0;
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
border-radius: 8px;
|
|
4607
|
-
padding: 12px 14px;
|
|
4608
|
-
font: 12.5px / 1.6 var(--mono);
|
|
4798
|
+
font-size: 13px;
|
|
4799
|
+
font-weight: 600;
|
|
4800
|
+
color: var(--fg);
|
|
4609
4801
|
}
|
|
4610
|
-
.set-
|
|
4611
|
-
|
|
4802
|
+
.set-way-title svg {
|
|
4803
|
+
width: 15px;
|
|
4804
|
+
height: 15px;
|
|
4805
|
+
flex: 0 0 auto;
|
|
4806
|
+
fill: currentColor;
|
|
4807
|
+
color: var(--muted);
|
|
4808
|
+
}
|
|
4809
|
+
.set-way-line {
|
|
4810
|
+
margin: 3px 0 0;
|
|
4811
|
+
font-size: 12.5px;
|
|
4812
|
+
color: var(--muted);
|
|
4813
|
+
line-height: 1.5;
|
|
4814
|
+
}
|
|
4815
|
+
.set-way-body {
|
|
4816
|
+
padding: 10px 18px 18px;
|
|
4817
|
+
}
|
|
4818
|
+
/* The address is a value to copy, not a field: plain monospace, its Copy and Open beside it. */
|
|
4819
|
+
.set-way-addr {
|
|
4820
|
+
display: flex;
|
|
4821
|
+
align-items: center;
|
|
4822
|
+
flex-wrap: wrap;
|
|
4823
|
+
gap: 4px 6px;
|
|
4824
|
+
min-width: 0;
|
|
4825
|
+
}
|
|
4826
|
+
.set-addr {
|
|
4827
|
+
font: 13.5px var(--mono);
|
|
4828
|
+
color: var(--fg);
|
|
4829
|
+
overflow-wrap: anywhere;
|
|
4830
|
+
margin-right: 4px;
|
|
4831
|
+
}
|
|
4832
|
+
/* The form as it will look beside the code that makes it, both always shown, edge to
|
|
4833
|
+
edge in the card like the reader pages' outline beside its frame. The preview grows to
|
|
4834
|
+
480px, near the styled form's own 460px cap, so it shows the form about as wide as a
|
|
4835
|
+
site will; the code keeps 280px, so on a narrower card the preview gives way first. */
|
|
4836
|
+
.set-embed {
|
|
4837
|
+
display: grid;
|
|
4838
|
+
grid-template-columns: minmax(240px, 480px) minmax(280px, 1fr);
|
|
4839
|
+
margin-top: 16px;
|
|
4840
|
+
border-top: 1px solid var(--line);
|
|
4612
4841
|
}
|
|
4613
4842
|
.set-embed-preview {
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
padding: 18px 16px;
|
|
4843
|
+
display: flex;
|
|
4844
|
+
align-items: center;
|
|
4845
|
+
padding: 18px;
|
|
4618
4846
|
background: var(--sunken);
|
|
4847
|
+
border-right: 1px solid var(--line);
|
|
4848
|
+
min-width: 0;
|
|
4619
4849
|
}
|
|
4620
|
-
.set-embed-
|
|
4621
|
-
|
|
4622
|
-
font-weight: 650;
|
|
4623
|
-
letter-spacing: 0.07em;
|
|
4624
|
-
text-transform: uppercase;
|
|
4625
|
-
color: var(--muted);
|
|
4626
|
-
margin-bottom: 12px;
|
|
4850
|
+
.set-embed-preview > form {
|
|
4851
|
+
width: 100%;
|
|
4627
4852
|
}
|
|
4628
|
-
/*
|
|
4629
|
-
.
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4853
|
+
/* The code wraps rather than scrolling sideways, so the Styled snippet's long lines read
|
|
4854
|
+
whole in the column; the copied text is unchanged. */
|
|
4855
|
+
.set-embed-code {
|
|
4856
|
+
margin: 0;
|
|
4857
|
+
min-width: 0;
|
|
4858
|
+
padding: 12px 14px;
|
|
4859
|
+
max-height: 220px;
|
|
4860
|
+
overflow: auto;
|
|
4861
|
+
font: 12px / 1.55 var(--mono);
|
|
4635
4862
|
}
|
|
4636
|
-
.set-
|
|
4637
|
-
|
|
4638
|
-
|
|
4863
|
+
.set-embed-code code {
|
|
4864
|
+
white-space: pre-wrap;
|
|
4865
|
+
overflow-wrap: anywhere;
|
|
4639
4866
|
}
|
|
4640
|
-
|
|
4641
|
-
|
|
4867
|
+
/* The band ends in its one act: read top to bottom, choose, see the form and its code,
|
|
4868
|
+
then copy. A strip across the card's foot, as Notifications ends in its test row. */
|
|
4869
|
+
.set-embed-foot {
|
|
4870
|
+
display: flex;
|
|
4871
|
+
justify-content: flex-end;
|
|
4872
|
+
padding: 12px 18px;
|
|
4873
|
+
border-top: 1px solid var(--line);
|
|
4642
4874
|
}
|
|
4875
|
+
/* Copy HTML is Secondary with the shared Copy's word and glyph, as the setup step's is. */
|
|
4876
|
+
button.set-embed-copy {
|
|
4877
|
+
display: inline-flex;
|
|
4878
|
+
align-items: center;
|
|
4879
|
+
gap: 6px;
|
|
4880
|
+
}
|
|
4881
|
+
button.set-embed-copy svg {
|
|
4882
|
+
width: 14px;
|
|
4883
|
+
height: 14px;
|
|
4884
|
+
}
|
|
4885
|
+
@container (max-width: 620px) {
|
|
4886
|
+
/* No room beside the title: the toggle drops under the band's line, at its left. */
|
|
4887
|
+
.set-way-head {
|
|
4888
|
+
flex-direction: column;
|
|
4889
|
+
}
|
|
4890
|
+
.set-way-name {
|
|
4891
|
+
flex-basis: auto;
|
|
4892
|
+
}
|
|
4893
|
+
.set-embed {
|
|
4894
|
+
grid-template-columns: minmax(0, 1fr);
|
|
4895
|
+
}
|
|
4896
|
+
.set-embed-preview {
|
|
4897
|
+
border-right: 0;
|
|
4898
|
+
border-bottom: 1px solid var(--line);
|
|
4899
|
+
}
|
|
4900
|
+
}
|
|
4901
|
+
/* Rendered form previews (inert: they never submit here). The plain form is bare
|
|
4902
|
+
markup, so it previews with the browser's own controls and inline flow, as a page
|
|
4903
|
+
with no styles of its own would show it, never the admin's inputs. */
|
|
4904
|
+
.set-pf-plain,
|
|
4905
|
+
.set-pf-plain :is(label, input, button) {
|
|
4906
|
+
all: revert;
|
|
4907
|
+
}
|
|
4908
|
+
.set-pf-plain input {
|
|
4909
|
+
margin: 4px 4px 4px 0;
|
|
4910
|
+
}
|
|
4911
|
+
/* The styled form wears the hosted subscribe page's look (SPEC §5), at the snippet's own
|
|
4912
|
+
sizes, so the preview is the form a publisher copies. */
|
|
4643
4913
|
.set-pf-styled {
|
|
4644
|
-
max-width:
|
|
4914
|
+
max-width: 460px;
|
|
4915
|
+
font-size: 16px;
|
|
4645
4916
|
}
|
|
4646
4917
|
.set-pf-styled .l {
|
|
4647
4918
|
display: block;
|
|
4648
|
-
font
|
|
4649
|
-
|
|
4650
|
-
|
|
4919
|
+
font:
|
|
4920
|
+
600 22px / 1.2 Georgia,
|
|
4921
|
+
"Times New Roman",
|
|
4922
|
+
serif;
|
|
4923
|
+
letter-spacing: -0.01em;
|
|
4924
|
+
margin-bottom: 12px;
|
|
4651
4925
|
color: var(--fg);
|
|
4652
4926
|
}
|
|
4653
4927
|
.set-pf-styled .rowf {
|
|
4654
4928
|
display: flex;
|
|
4655
|
-
|
|
4929
|
+
flex-wrap: wrap;
|
|
4930
|
+
gap: 10px;
|
|
4656
4931
|
}
|
|
4657
4932
|
.set-pf-styled input {
|
|
4658
|
-
flex: 1;
|
|
4933
|
+
flex: 1 1 220px;
|
|
4659
4934
|
min-width: 0;
|
|
4935
|
+
padding: 11px 13px;
|
|
4936
|
+
border-radius: 9px;
|
|
4937
|
+
font-size: 16px;
|
|
4660
4938
|
}
|
|
4661
4939
|
/* The styled subscribe-form preview shows reader-facing chrome, not an admin control,
|
|
4662
|
-
so it isn't one of the five button roles
|
|
4940
|
+
so it isn't one of the five button roles, but it rides the neutral --ink token
|
|
4663
4941
|
(was a hardcoded near-black that vanished in dark mode) so it themes correctly. */
|
|
4664
4942
|
.set-pf-styled .sub-btn {
|
|
4665
4943
|
flex: 0 0 auto;
|
|
4944
|
+
padding: 11px 20px;
|
|
4945
|
+
border-radius: 9px;
|
|
4666
4946
|
background: var(--ink);
|
|
4667
4947
|
color: var(--ink-on);
|
|
4668
4948
|
border-color: var(--ink);
|
|
4949
|
+
font-size: 15px;
|
|
4669
4950
|
font-weight: 600;
|
|
4670
4951
|
white-space: nowrap;
|
|
4671
4952
|
}
|
|
4672
4953
|
.set-pf-fine {
|
|
4673
|
-
margin:
|
|
4674
|
-
font-size:
|
|
4954
|
+
margin: 12px 0 0;
|
|
4955
|
+
font-size: 12.5px;
|
|
4675
4956
|
color: var(--muted);
|
|
4676
4957
|
}
|
|
4677
4958
|
|
|
@@ -5028,10 +5309,6 @@ button.set-field-remove {
|
|
|
5028
5309
|
background: var(--accent-soft);
|
|
5029
5310
|
border-color: color-mix(in srgb, var(--accent) 30%, var(--line-soft));
|
|
5030
5311
|
}
|
|
5031
|
-
.set-icon-btn.copied {
|
|
5032
|
-
color: var(--ok-fg);
|
|
5033
|
-
border-color: color-mix(in srgb, var(--ok-fg) 40%, var(--line-soft));
|
|
5034
|
-
}
|
|
5035
5312
|
/* Required-token pills (issue #123 revisit): live green/red for the two tokens whose
|
|
5036
5313
|
absence blocks a save, shown upper-right of the editor toolbar. */
|
|
5037
5314
|
.set-tpl-required {
|
|
@@ -5184,6 +5461,243 @@ button.set-field-remove {
|
|
|
5184
5461
|
color: var(--muted);
|
|
5185
5462
|
}
|
|
5186
5463
|
|
|
5464
|
+
/* --- Reader pages: an outline of every page a reader can land on, beside one framed page ---
|
|
5465
|
+
The journeys are the outline's headings, set like section titles and never controls;
|
|
5466
|
+
the pages are its rows. A journey's steps are numbered on a thread, peers carry a dot,
|
|
5467
|
+
and an off-path page carries a dashed branch mark. The section folds by its own width,
|
|
5468
|
+
not the window's (the sidebar decides how wide Settings is): too narrow for the outline,
|
|
5469
|
+
it becomes one grouped select, and the width toggle goes, since the frame is already
|
|
5470
|
+
no wider than a phone. The page brings its own ground, so the frame has none. */
|
|
5471
|
+
.set-rp-sec {
|
|
5472
|
+
container-type: inline-size;
|
|
5473
|
+
}
|
|
5474
|
+
.set-rp {
|
|
5475
|
+
display: grid;
|
|
5476
|
+
grid-template-columns: 210px minmax(0, 1fr);
|
|
5477
|
+
overflow: hidden;
|
|
5478
|
+
}
|
|
5479
|
+
.set-rp-nav {
|
|
5480
|
+
border-right: 1px solid var(--line);
|
|
5481
|
+
background: var(--sunken);
|
|
5482
|
+
padding: 14px 10px 18px;
|
|
5483
|
+
}
|
|
5484
|
+
.set-rp-journey + .set-rp-journey {
|
|
5485
|
+
margin-top: 16px;
|
|
5486
|
+
}
|
|
5487
|
+
.set-rp-jhead {
|
|
5488
|
+
display: flex;
|
|
5489
|
+
align-items: center;
|
|
5490
|
+
gap: 7px;
|
|
5491
|
+
margin: 0 6px 6px;
|
|
5492
|
+
font-size: 11px;
|
|
5493
|
+
font-weight: 650;
|
|
5494
|
+
letter-spacing: 0.07em;
|
|
5495
|
+
text-transform: uppercase;
|
|
5496
|
+
color: var(--muted);
|
|
5497
|
+
}
|
|
5498
|
+
.set-rp-jhead svg {
|
|
5499
|
+
width: 13px;
|
|
5500
|
+
height: 13px;
|
|
5501
|
+
}
|
|
5502
|
+
.set-rp-journey.current .set-rp-jhead {
|
|
5503
|
+
color: var(--fg);
|
|
5504
|
+
}
|
|
5505
|
+
.set-rp-list {
|
|
5506
|
+
list-style: none;
|
|
5507
|
+
margin: 0;
|
|
5508
|
+
padding: 0;
|
|
5509
|
+
position: relative;
|
|
5510
|
+
}
|
|
5511
|
+
/* The thread behind a journey's numbered steps, from the first mark to the last row. */
|
|
5512
|
+
.set-rp-list.steps::before {
|
|
5513
|
+
content: "";
|
|
5514
|
+
position: absolute;
|
|
5515
|
+
left: 17px;
|
|
5516
|
+
top: 14px;
|
|
5517
|
+
bottom: 14px;
|
|
5518
|
+
width: 1px;
|
|
5519
|
+
background: var(--line-soft);
|
|
5520
|
+
}
|
|
5521
|
+
.set-rp-item {
|
|
5522
|
+
width: 100%;
|
|
5523
|
+
display: flex;
|
|
5524
|
+
align-items: center;
|
|
5525
|
+
gap: 9px;
|
|
5526
|
+
text-align: left;
|
|
5527
|
+
border: 0;
|
|
5528
|
+
background: transparent;
|
|
5529
|
+
border-radius: 7px;
|
|
5530
|
+
padding: 5px 8px;
|
|
5531
|
+
font-size: 13px;
|
|
5532
|
+
font-weight: 400;
|
|
5533
|
+
color: var(--fg);
|
|
5534
|
+
cursor: pointer;
|
|
5535
|
+
}
|
|
5536
|
+
.set-rp-item:hover {
|
|
5537
|
+
background: var(--hover);
|
|
5538
|
+
}
|
|
5539
|
+
.set-rp-item[aria-current="true"] {
|
|
5540
|
+
background: var(--accent-soft);
|
|
5541
|
+
color: var(--accent);
|
|
5542
|
+
font-weight: 600;
|
|
5543
|
+
}
|
|
5544
|
+
.set-rp-item.off {
|
|
5545
|
+
color: var(--muted);
|
|
5546
|
+
}
|
|
5547
|
+
.set-rp-item.off[aria-current="true"] {
|
|
5548
|
+
color: var(--accent);
|
|
5549
|
+
}
|
|
5550
|
+
.set-rp-mark {
|
|
5551
|
+
position: relative;
|
|
5552
|
+
z-index: 1;
|
|
5553
|
+
flex: 0 0 auto;
|
|
5554
|
+
display: grid;
|
|
5555
|
+
place-items: center;
|
|
5556
|
+
width: 19px;
|
|
5557
|
+
height: 19px;
|
|
5558
|
+
border-radius: 50%;
|
|
5559
|
+
border: 1px solid var(--line-soft);
|
|
5560
|
+
background: var(--card);
|
|
5561
|
+
font-size: 10.5px;
|
|
5562
|
+
font-weight: 650;
|
|
5563
|
+
color: var(--muted);
|
|
5564
|
+
}
|
|
5565
|
+
.set-rp-item[aria-current="true"] .set-rp-mark {
|
|
5566
|
+
border-color: var(--accent);
|
|
5567
|
+
color: var(--accent);
|
|
5568
|
+
}
|
|
5569
|
+
.set-rp-item.off .set-rp-mark {
|
|
5570
|
+
border-style: dashed;
|
|
5571
|
+
background: transparent;
|
|
5572
|
+
}
|
|
5573
|
+
.set-rp-dot {
|
|
5574
|
+
width: 3px;
|
|
5575
|
+
height: 3px;
|
|
5576
|
+
border-radius: 50%;
|
|
5577
|
+
background: currentColor;
|
|
5578
|
+
}
|
|
5579
|
+
.set-rp-mark svg {
|
|
5580
|
+
width: 11px;
|
|
5581
|
+
height: 11px;
|
|
5582
|
+
}
|
|
5583
|
+
.set-rp-pick {
|
|
5584
|
+
display: none;
|
|
5585
|
+
}
|
|
5586
|
+
.set-rp-pick select {
|
|
5587
|
+
width: 100%;
|
|
5588
|
+
}
|
|
5589
|
+
.set-rp-main {
|
|
5590
|
+
min-width: 0;
|
|
5591
|
+
display: flex;
|
|
5592
|
+
flex-direction: column;
|
|
5593
|
+
}
|
|
5594
|
+
.set-rp-top {
|
|
5595
|
+
display: flex;
|
|
5596
|
+
align-items: flex-start;
|
|
5597
|
+
justify-content: space-between;
|
|
5598
|
+
gap: 12px;
|
|
5599
|
+
padding: 14px 16px 12px;
|
|
5600
|
+
border-bottom: 1px solid var(--line);
|
|
5601
|
+
}
|
|
5602
|
+
.set-rp-crumb {
|
|
5603
|
+
display: flex;
|
|
5604
|
+
flex-wrap: wrap;
|
|
5605
|
+
align-items: center;
|
|
5606
|
+
gap: 6px;
|
|
5607
|
+
margin: 0 0 3px;
|
|
5608
|
+
font-size: 14px;
|
|
5609
|
+
font-weight: 650;
|
|
5610
|
+
}
|
|
5611
|
+
.set-rp-crumb-j {
|
|
5612
|
+
color: var(--muted);
|
|
5613
|
+
font-weight: 500;
|
|
5614
|
+
}
|
|
5615
|
+
.set-rp-crumb-sep {
|
|
5616
|
+
color: var(--line-soft);
|
|
5617
|
+
font-weight: 400;
|
|
5618
|
+
}
|
|
5619
|
+
.set-rp-note {
|
|
5620
|
+
margin: 0;
|
|
5621
|
+
font-size: 12.5px;
|
|
5622
|
+
line-height: 1.5;
|
|
5623
|
+
color: var(--muted);
|
|
5624
|
+
max-width: 60ch;
|
|
5625
|
+
}
|
|
5626
|
+
.set-rp .set-wtog {
|
|
5627
|
+
flex: 0 0 auto;
|
|
5628
|
+
}
|
|
5629
|
+
.set-rp .wtog-btn {
|
|
5630
|
+
display: inline-flex;
|
|
5631
|
+
align-items: center;
|
|
5632
|
+
gap: 5px;
|
|
5633
|
+
}
|
|
5634
|
+
.set-rp .wtog-btn svg {
|
|
5635
|
+
width: 13px;
|
|
5636
|
+
height: 13px;
|
|
5637
|
+
}
|
|
5638
|
+
/* Where a reader meets the page, and that this one is a sample: the cue a strip inside
|
|
5639
|
+
the page would otherwise carry, kept in the app's chrome around it. */
|
|
5640
|
+
.set-rp-at {
|
|
5641
|
+
display: flex;
|
|
5642
|
+
flex-wrap: wrap;
|
|
5643
|
+
justify-content: space-between;
|
|
5644
|
+
gap: 4px 10px;
|
|
5645
|
+
padding: 7px 16px;
|
|
5646
|
+
border-bottom: 1px solid var(--line);
|
|
5647
|
+
font-size: 11.5px;
|
|
5648
|
+
color: var(--muted);
|
|
5649
|
+
}
|
|
5650
|
+
.set-rp-at .mono {
|
|
5651
|
+
overflow-wrap: anywhere;
|
|
5652
|
+
}
|
|
5653
|
+
.set-rp-stage.phone {
|
|
5654
|
+
padding: 18px 12px;
|
|
5655
|
+
background: var(--sunken);
|
|
5656
|
+
}
|
|
5657
|
+
.set-rp-frame {
|
|
5658
|
+
display: block;
|
|
5659
|
+
width: 100%;
|
|
5660
|
+
min-height: 320px;
|
|
5661
|
+
border: 0;
|
|
5662
|
+
}
|
|
5663
|
+
.set-rp-stage.phone .set-rp-frame {
|
|
5664
|
+
width: 375px;
|
|
5665
|
+
max-width: 100%;
|
|
5666
|
+
margin: 0 auto;
|
|
5667
|
+
border: 1px solid var(--line);
|
|
5668
|
+
border-radius: 10px;
|
|
5669
|
+
}
|
|
5670
|
+
.set-rp-error {
|
|
5671
|
+
margin: 0;
|
|
5672
|
+
padding: 12px 16px;
|
|
5673
|
+
font-size: 13px;
|
|
5674
|
+
color: var(--danger-fg);
|
|
5675
|
+
}
|
|
5676
|
+
@container (max-width: 640px) {
|
|
5677
|
+
.set-rp {
|
|
5678
|
+
grid-template-columns: minmax(0, 1fr);
|
|
5679
|
+
}
|
|
5680
|
+
.set-rp-nav,
|
|
5681
|
+
.set-rp .set-wtog {
|
|
5682
|
+
display: none;
|
|
5683
|
+
}
|
|
5684
|
+
.set-rp-pick {
|
|
5685
|
+
display: block;
|
|
5686
|
+
padding: 12px 14px;
|
|
5687
|
+
border-bottom: 1px solid var(--line);
|
|
5688
|
+
background: var(--sunken);
|
|
5689
|
+
}
|
|
5690
|
+
.set-rp-stage.phone {
|
|
5691
|
+
padding: 0;
|
|
5692
|
+
background: none;
|
|
5693
|
+
}
|
|
5694
|
+
.set-rp-stage.phone .set-rp-frame {
|
|
5695
|
+
width: 100%;
|
|
5696
|
+
border: 0;
|
|
5697
|
+
border-radius: 0;
|
|
5698
|
+
}
|
|
5699
|
+
}
|
|
5700
|
+
|
|
5187
5701
|
/* --- shared unsaved-changes bar -------------------------------------------------
|
|
5188
5702
|
One full-width banner fixed to the bottom of the viewport, used by every surface
|
|
5189
5703
|
with an explicit save + a revertible baseline (Settings and the Template page).
|