@kurtbruns/kestrel 1.11.0 → 1.12.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/CHANGELOG.md +52 -1
- package/README.md +6 -7
- package/cli/reset.mjs +2 -0
- package/cli/seed.mjs +2 -0
- package/demo/README.md +4 -2
- package/demo/posts/0-test-email/index.md +13 -0
- package/demo/posts/5-a-quick-note/index.md +2 -0
- package/dist/public/dashboard/app.js +302 -293
- package/dist/public/dashboard/app.js.map +4 -4
- package/dist/public/dashboard/index.html +2 -2
- package/dist/public/dashboard/styles.css +434 -54
- package/dist/worker/index.js +1413 -307
- package/docs/get-started/04-ses.md +22 -2
- package/docs/get-started/05-verify.md +18 -2
- package/docs/get-started/06-go-live.md +1 -1
- package/docs/guides/01-connect-claude.md +3 -1
- package/docs/publishing/04-write-a-post.md +18 -4
- package/docs/publishing/06-troubleshooting.md +37 -8
- package/docs/publishing/07-working-with-claude.md +3 -2
- package/docs/publishing/11-email-template.md +1 -1
- package/docs/publishing/README.md +1 -1
- package/docs/reference/01-configuration.md +1 -1
- package/migrations/0007_sends_progress_at.sql +12 -0
- package/migrations/0008_notifications_sandbox.sql +35 -0
- package/migrations/0009_post_email_only.sql +12 -0
- package/migrations/0010_unlisted_links.sql +43 -0
- package/migrations/0011_sends_render_warnings.sql +13 -0
- package/package.json +1 -1
- package/template/claude/CLAUDE.md +5 -2
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
6
|
<title>Kestrel</title>
|
|
7
7
|
<link rel="icon" href="/favicon.svg">
|
|
8
|
-
<link rel="stylesheet" href="./styles.css?v=
|
|
8
|
+
<link rel="stylesheet" href="./styles.css?v=1396c31b">
|
|
9
9
|
<script>
|
|
10
10
|
// Sidebar mode guard — set the width-based data-nav on <html> before first paint so the
|
|
11
11
|
// sidebar renders in its final width with no flash. The collapse toggle's override is
|
|
@@ -113,6 +113,6 @@
|
|
|
113
113
|
</section>
|
|
114
114
|
<div id="toasts" role="status" aria-live="polite"></div>
|
|
115
115
|
|
|
116
|
-
<script src="./app.js?v=
|
|
116
|
+
<script src="./app.js?v=fc72c770"></script>
|
|
117
117
|
</body>
|
|
118
118
|
</html>
|
|
@@ -1558,6 +1558,42 @@ td a:hover {
|
|
|
1558
1558
|
color: var(--status-sending-fg);
|
|
1559
1559
|
border-color: var(--status-sending-line);
|
|
1560
1560
|
}
|
|
1561
|
+
/* A list's title with its Email only mark (sends/progress.ts): the title wraps within its room
|
|
1562
|
+
and the mark, an envelope and muted words, stays whole on the title's line, so it never drops
|
|
1563
|
+
onto a line of its own. */
|
|
1564
|
+
.title-with-label {
|
|
1565
|
+
display: flex;
|
|
1566
|
+
/* Centered, not on a baseline: the mark's text is smaller and led by an icon, and only
|
|
1567
|
+
their centers line up with the title's. */
|
|
1568
|
+
align-items: center;
|
|
1569
|
+
gap: 8px;
|
|
1570
|
+
min-width: 0;
|
|
1571
|
+
}
|
|
1572
|
+
.title-with-label .title-text {
|
|
1573
|
+
min-width: 0;
|
|
1574
|
+
}
|
|
1575
|
+
/* Where a table's columns are fixed, a long title ends in an ellipsis, so the row stays one
|
|
1576
|
+
line tall; the whole title is a click away in the post. */
|
|
1577
|
+
.list-table .title-with-label .title-text,
|
|
1578
|
+
.dash-drafts .title-with-label .title-text {
|
|
1579
|
+
overflow: hidden;
|
|
1580
|
+
text-overflow: ellipsis;
|
|
1581
|
+
white-space: nowrap;
|
|
1582
|
+
}
|
|
1583
|
+
.email-only-mark {
|
|
1584
|
+
display: inline-flex;
|
|
1585
|
+
align-items: center;
|
|
1586
|
+
gap: 4px;
|
|
1587
|
+
flex: none;
|
|
1588
|
+
font-size: 12.5px;
|
|
1589
|
+
font-weight: 400;
|
|
1590
|
+
color: var(--muted);
|
|
1591
|
+
white-space: nowrap;
|
|
1592
|
+
}
|
|
1593
|
+
.email-only-mark svg {
|
|
1594
|
+
width: 13px;
|
|
1595
|
+
height: 13px;
|
|
1596
|
+
}
|
|
1561
1597
|
/* Subscriber consent states borrow a hue that matches the state's meaning: confirmed →
|
|
1562
1598
|
the sent green, unsubscribed → the draft grey, suppressed → danger red (below). Pending
|
|
1563
1599
|
awaits double-opt-in — a waiting/attention state — so it takes the amber `warn` scale,
|
|
@@ -1810,6 +1846,24 @@ td a:hover {
|
|
|
1810
1846
|
.list-table .c-delivered {
|
|
1811
1847
|
width: 176px;
|
|
1812
1848
|
}
|
|
1849
|
+
/* Every Sent row, on the Sent page and the dashboard, as tall as one with a delivery note under its count, so a row with none (a
|
|
1850
|
+
one-recipient test) sits in the same rhythm as the rest. A row's height is its floor; once
|
|
1851
|
+
the table stacks, each row is a block that takes its own height. */
|
|
1852
|
+
/* The dashboard's Drafts card is half the page wide: its date stays on one line, and the title
|
|
1853
|
+
takes what room is left. */
|
|
1854
|
+
.dash-drafts {
|
|
1855
|
+
table-layout: fixed;
|
|
1856
|
+
}
|
|
1857
|
+
.dash-drafts td.edited {
|
|
1858
|
+
width: 11.5em;
|
|
1859
|
+
white-space: nowrap;
|
|
1860
|
+
}
|
|
1861
|
+
@media (min-width: 721px) {
|
|
1862
|
+
.sent-table tbody tr,
|
|
1863
|
+
.dash-sent tbody tr {
|
|
1864
|
+
height: 56px;
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1813
1867
|
.list-table .c-act {
|
|
1814
1868
|
width: 56px;
|
|
1815
1869
|
}
|
|
@@ -1965,14 +2019,16 @@ body.web-mode .content {
|
|
|
1965
2019
|
padding: 10px 44px;
|
|
1966
2020
|
text-align: left;
|
|
1967
2021
|
}
|
|
2022
|
+
/* The reader pages' Dev pill exactly (`.r-dev-tag` in src/lib/page.ts), so the two read as one. */
|
|
1968
2023
|
.web-bar-tag {
|
|
1969
2024
|
display: inline-block;
|
|
1970
2025
|
margin-right: 10px;
|
|
1971
|
-
font-size:
|
|
2026
|
+
font-size: 9.5px;
|
|
2027
|
+
line-height: 1.6;
|
|
1972
2028
|
letter-spacing: 0.09em;
|
|
1973
2029
|
text-transform: uppercase;
|
|
1974
2030
|
font-weight: 700;
|
|
1975
|
-
padding:
|
|
2031
|
+
padding: 2px 7px;
|
|
1976
2032
|
border-radius: 999px;
|
|
1977
2033
|
background: var(--accent-on);
|
|
1978
2034
|
color: var(--accent);
|
|
@@ -2415,6 +2471,12 @@ body.web-mode .content {
|
|
|
2415
2471
|
border-color: var(--ring);
|
|
2416
2472
|
box-shadow: 0 0 0 1px var(--ring);
|
|
2417
2473
|
}
|
|
2474
|
+
/* A scheduled post's body is locked, so focus rings it in grey, as the locked subject's is,
|
|
2475
|
+
rather than in the blue that says "type here" (DESIGN §3). */
|
|
2476
|
+
.composer.composer-locked:focus-within {
|
|
2477
|
+
border-color: var(--muted);
|
|
2478
|
+
box-shadow: 0 0 0 1px var(--muted);
|
|
2479
|
+
}
|
|
2418
2480
|
.composer-head {
|
|
2419
2481
|
display: flex;
|
|
2420
2482
|
align-items: flex-end;
|
|
@@ -2621,14 +2683,25 @@ body.web-mode .content {
|
|
|
2621
2683
|
}
|
|
2622
2684
|
.composer-body.locked textarea.editor {
|
|
2623
2685
|
caret-color: transparent;
|
|
2686
|
+
cursor: default;
|
|
2687
|
+
}
|
|
2688
|
+
/* Its text is muted, as the subject's is, so the body reads as locked at a glance. */
|
|
2689
|
+
.composer-body.locked .md-hl code {
|
|
2690
|
+
opacity: 0.72;
|
|
2624
2691
|
}
|
|
2625
|
-
/* The subject
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2692
|
+
/* The subject of a scheduled post is read-only too, and readonly (unlike disabled) carries no
|
|
2693
|
+
native greying, so it is styled to look locked while keeping what disabled would take away:
|
|
2694
|
+
it can still be focused, selected, and copied, and a keystroke still pulses the foot line.
|
|
2695
|
+
The recessed surface, muted text, an arrow cursor, and a grey focus ring in place of the
|
|
2696
|
+
blue one that says "type here" (DESIGN §3). */
|
|
2697
|
+
#f-subject[readonly] {
|
|
2630
2698
|
background: var(--sunken);
|
|
2631
2699
|
border-color: var(--line-soft);
|
|
2700
|
+
color: var(--muted);
|
|
2701
|
+
cursor: default;
|
|
2702
|
+
}
|
|
2703
|
+
#f-subject[readonly]:focus-visible {
|
|
2704
|
+
outline-color: var(--muted);
|
|
2632
2705
|
}
|
|
2633
2706
|
.composer-body.dragover::after {
|
|
2634
2707
|
content: "Drop images to upload";
|
|
@@ -2696,6 +2769,11 @@ body.web-mode .content {
|
|
|
2696
2769
|
}
|
|
2697
2770
|
}
|
|
2698
2771
|
|
|
2772
|
+
/* The editor's subject, body, and actions sit on the page itself, with no card around them:
|
|
2773
|
+
the fields and the composer carry their own borders, aligned with the head and the banner. */
|
|
2774
|
+
.editor-form > label:first-child {
|
|
2775
|
+
margin-top: 6px;
|
|
2776
|
+
}
|
|
2699
2777
|
.actions-bar {
|
|
2700
2778
|
display: flex;
|
|
2701
2779
|
align-items: center;
|
|
@@ -2910,13 +2988,10 @@ body.web-mode .content {
|
|
|
2910
2988
|
flex: none;
|
|
2911
2989
|
white-space: nowrap;
|
|
2912
2990
|
}
|
|
2913
|
-
/* The
|
|
2914
|
-
rather than demotes below, the Schedule view. */
|
|
2915
|
-
.modal .
|
|
2916
|
-
margin:
|
|
2917
|
-
padding-top: 0;
|
|
2918
|
-
border-top: 0;
|
|
2919
|
-
justify-content: flex-start;
|
|
2991
|
+
/* The Send-now view's way back to the Schedule view, at the start of its button row: it
|
|
2992
|
+
returns from, rather than demotes below, the Schedule view, so it takes no row of its own. */
|
|
2993
|
+
.modal .actions .actions-back {
|
|
2994
|
+
margin-right: auto;
|
|
2920
2995
|
}
|
|
2921
2996
|
/* Link — a text-link-styled action (DESIGN §7), not one of the button roles. Reserved
|
|
2922
2997
|
for a deliberately subordinate choice, like the in-modal Send now that sits below
|
|
@@ -3122,16 +3197,6 @@ textarea.is-invalid {
|
|
|
3122
3197
|
.modal .dialog-error {
|
|
3123
3198
|
margin-top: 12px;
|
|
3124
3199
|
}
|
|
3125
|
-
/* Label + info icon grouped at the top of the slug field. */
|
|
3126
|
-
.label-row {
|
|
3127
|
-
display: flex;
|
|
3128
|
-
align-items: center;
|
|
3129
|
-
gap: 6px;
|
|
3130
|
-
margin: 14px 0 8px;
|
|
3131
|
-
}
|
|
3132
|
-
.label-row label {
|
|
3133
|
-
margin: 0;
|
|
3134
|
-
}
|
|
3135
3200
|
/* Info icon: the field explanation lives in its hover/focus tooltip. The padding
|
|
3136
3201
|
+ equal negative margin enlarge the hover/click target to ~28px without moving
|
|
3137
3202
|
the glyph or growing the label row. */
|
|
@@ -3196,28 +3261,6 @@ textarea.is-invalid {
|
|
|
3196
3261
|
visibility: visible;
|
|
3197
3262
|
transform: translateY(0);
|
|
3198
3263
|
}
|
|
3199
|
-
/* The "Auto-generate from subject" toggle, grouped just under the slug field. */
|
|
3200
|
-
.slug-auto-toggle {
|
|
3201
|
-
display: inline-flex;
|
|
3202
|
-
align-items: center;
|
|
3203
|
-
gap: 6px;
|
|
3204
|
-
margin-top: 8px;
|
|
3205
|
-
font-size: 14px;
|
|
3206
|
-
color: var(--muted);
|
|
3207
|
-
cursor: pointer;
|
|
3208
|
-
user-select: none;
|
|
3209
|
-
}
|
|
3210
|
-
.slug-auto-toggle input {
|
|
3211
|
-
width: 14px;
|
|
3212
|
-
height: 14px;
|
|
3213
|
-
margin: 0;
|
|
3214
|
-
accent-color: var(--ring);
|
|
3215
|
-
cursor: pointer;
|
|
3216
|
-
}
|
|
3217
|
-
/* Slug is muted while it follows the subject; normal once set by hand. */
|
|
3218
|
-
#f-slug.slug-auto {
|
|
3219
|
-
color: var(--muted);
|
|
3220
|
-
}
|
|
3221
3264
|
/* --- Editor-state banner (DESIGN §5): a standing, in-flow line at the top of the
|
|
3222
3265
|
editor, in two variants. `scheduled` marks a frozen, soft-locked post (SPEC §6);
|
|
3223
3266
|
`conflict` warns the draft changed elsewhere (another tab, or Claude), and its two
|
|
@@ -3285,16 +3328,327 @@ textarea.is-invalid {
|
|
|
3285
3328
|
font-size: 12.5px;
|
|
3286
3329
|
color: var(--status-scheduled-fg);
|
|
3287
3330
|
}
|
|
3288
|
-
|
|
3289
|
-
.
|
|
3290
|
-
display:
|
|
3291
|
-
|
|
3331
|
+
/* The Email only box under a dialog's time: the label, then its helper beneath. */
|
|
3332
|
+
.listing-check {
|
|
3333
|
+
display: flex;
|
|
3334
|
+
align-items: flex-start;
|
|
3292
3335
|
gap: 10px;
|
|
3336
|
+
margin: 14px 0 0;
|
|
3337
|
+
font-size: 14px;
|
|
3338
|
+
color: var(--fg);
|
|
3339
|
+
cursor: pointer;
|
|
3293
3340
|
}
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3341
|
+
.listing-check input {
|
|
3342
|
+
width: auto;
|
|
3343
|
+
margin: 3px 0 0;
|
|
3344
|
+
flex: 0 0 auto;
|
|
3345
|
+
accent-color: var(--ring);
|
|
3346
|
+
cursor: pointer;
|
|
3347
|
+
}
|
|
3348
|
+
.listing-check strong {
|
|
3349
|
+
font-weight: 600;
|
|
3350
|
+
}
|
|
3351
|
+
.listing-help {
|
|
3352
|
+
display: block;
|
|
3353
|
+
margin-top: 2px;
|
|
3354
|
+
font-size: 13px;
|
|
3355
|
+
line-height: 1.45;
|
|
3356
|
+
color: var(--muted);
|
|
3357
|
+
}
|
|
3358
|
+
/* A scheduled post's banner in two rows (DESIGN §4): the time, its notes, and the controls that
|
|
3359
|
+
act on it; then, under a hairline, where the post is going, on a slightly deeper ground. */
|
|
3360
|
+
.banner-split {
|
|
3361
|
+
display: block;
|
|
3362
|
+
padding: 0;
|
|
3363
|
+
overflow: hidden;
|
|
3364
|
+
}
|
|
3365
|
+
.banner-top {
|
|
3366
|
+
display: flex;
|
|
3367
|
+
align-items: center;
|
|
3368
|
+
justify-content: space-between;
|
|
3369
|
+
gap: 12px;
|
|
3370
|
+
flex-wrap: wrap;
|
|
3371
|
+
padding: 12px 16px;
|
|
3372
|
+
font-size: 14px;
|
|
3373
|
+
}
|
|
3374
|
+
.banner-bottom {
|
|
3375
|
+
display: flex;
|
|
3376
|
+
align-items: center;
|
|
3377
|
+
gap: 8px;
|
|
3378
|
+
padding: 9px 16px;
|
|
3379
|
+
border-top: 1px solid var(--status-scheduled-line);
|
|
3380
|
+
/* A shade deeper than the banner, so the row reads as its footer rather than a strip of its own. */
|
|
3381
|
+
background: color-mix(in srgb, var(--status-scheduled-fg) 6%, var(--status-scheduled-bg));
|
|
3382
|
+
font-size: 13px;
|
|
3383
|
+
}
|
|
3384
|
+
.banner-bottom svg {
|
|
3385
|
+
flex: none;
|
|
3386
|
+
width: 14px;
|
|
3387
|
+
height: 14px;
|
|
3388
|
+
}
|
|
3389
|
+
.sched-dest-icon {
|
|
3390
|
+
display: flex;
|
|
3391
|
+
flex: none;
|
|
3392
|
+
}
|
|
3393
|
+
/* A filled icon takes the row's violet; a stroked one (fill="none") already draws in it. */
|
|
3394
|
+
.banner-bottom svg:not([fill="none"]),
|
|
3395
|
+
#settingsBtn svg {
|
|
3396
|
+
fill: currentColor;
|
|
3397
|
+
}
|
|
3398
|
+
#schedDest {
|
|
3399
|
+
min-width: 0;
|
|
3400
|
+
overflow-wrap: anywhere;
|
|
3401
|
+
}
|
|
3402
|
+
.sched-dest code {
|
|
3403
|
+
font-family: var(--mono);
|
|
3404
|
+
font-size: 12.5px;
|
|
3405
|
+
}
|
|
3406
|
+
.pub-taken code {
|
|
3407
|
+
font-family: var(--mono);
|
|
3408
|
+
font-size: 12.5px;
|
|
3409
|
+
color: var(--fg);
|
|
3410
|
+
}
|
|
3411
|
+
/* The settings form in a dialog under the time, set off from it by a rule. */
|
|
3412
|
+
.dialog-settings {
|
|
3413
|
+
margin-top: 14px;
|
|
3414
|
+
padding-top: 2px;
|
|
3415
|
+
}
|
|
3416
|
+
.dialog-settings .listing-check {
|
|
3417
|
+
margin-top: 0;
|
|
3418
|
+
}
|
|
3419
|
+
/* The archive address as one URL field: the base greyed and fixed, then the slug, the one part
|
|
3420
|
+
to type. The whole field is the label, so a click on the base lands in the slug too. */
|
|
3421
|
+
.pub-address {
|
|
3422
|
+
margin-top: 14px;
|
|
3423
|
+
}
|
|
3424
|
+
.pub-label {
|
|
3425
|
+
display: block;
|
|
3426
|
+
margin: 0 0 6px;
|
|
3427
|
+
font-size: 13px;
|
|
3428
|
+
color: var(--muted);
|
|
3429
|
+
}
|
|
3430
|
+
.url-field {
|
|
3431
|
+
display: flex;
|
|
3432
|
+
align-items: stretch;
|
|
3433
|
+
min-width: 0;
|
|
3434
|
+
margin: 0;
|
|
3435
|
+
overflow: hidden;
|
|
3436
|
+
border: 1px solid var(--line);
|
|
3437
|
+
border-radius: 6px;
|
|
3438
|
+
background: var(--card);
|
|
3439
|
+
font-family: var(--mono);
|
|
3440
|
+
font-size: 13px;
|
|
3441
|
+
cursor: text;
|
|
3442
|
+
}
|
|
3443
|
+
.url-field:focus-within {
|
|
3444
|
+
border-color: var(--ring);
|
|
3445
|
+
box-shadow: 0 0 0 1px var(--ring);
|
|
3446
|
+
}
|
|
3447
|
+
.url-field.is-invalid {
|
|
3448
|
+
border-color: var(--danger-fg);
|
|
3449
|
+
}
|
|
3450
|
+
.url-base {
|
|
3451
|
+
display: flex;
|
|
3452
|
+
align-items: center;
|
|
3453
|
+
padding: 8px 0 8px 10px;
|
|
3454
|
+
white-space: nowrap;
|
|
3455
|
+
color: var(--muted);
|
|
3456
|
+
}
|
|
3457
|
+
.url-field input {
|
|
3458
|
+
flex: 1;
|
|
3459
|
+
min-width: 6em;
|
|
3460
|
+
border: 0;
|
|
3461
|
+
border-radius: 0;
|
|
3462
|
+
padding: 8px 10px 8px 0;
|
|
3463
|
+
font-family: var(--mono);
|
|
3464
|
+
font-size: 13px;
|
|
3465
|
+
font-weight: 600;
|
|
3466
|
+
}
|
|
3467
|
+
.url-field input:focus-visible {
|
|
3468
|
+
outline: none;
|
|
3469
|
+
}
|
|
3470
|
+
.url-field.readonly {
|
|
3471
|
+
cursor: default;
|
|
3472
|
+
background: var(--sunken);
|
|
3473
|
+
}
|
|
3474
|
+
.url-field.readonly input {
|
|
3475
|
+
color: var(--muted);
|
|
3476
|
+
font-weight: 500;
|
|
3477
|
+
background: transparent;
|
|
3478
|
+
}
|
|
3479
|
+
@media (max-width: 480px) {
|
|
3480
|
+
.url-field {
|
|
3481
|
+
flex-direction: column;
|
|
3482
|
+
}
|
|
3483
|
+
.url-base {
|
|
3484
|
+
padding-bottom: 0;
|
|
3297
3485
|
}
|
|
3486
|
+
.url-field input {
|
|
3487
|
+
padding-left: 10px;
|
|
3488
|
+
}
|
|
3489
|
+
}
|
|
3490
|
+
/* A refused address, in the server's words, under its field. */
|
|
3491
|
+
.pub-error {
|
|
3492
|
+
margin: 8px 0 0;
|
|
3493
|
+
font-size: 13px;
|
|
3494
|
+
color: var(--danger-fg);
|
|
3495
|
+
}
|
|
3496
|
+
/* The Post settings button carries a gear before its words. */
|
|
3497
|
+
#settingsBtn {
|
|
3498
|
+
display: inline-flex;
|
|
3499
|
+
align-items: center;
|
|
3500
|
+
gap: 6px;
|
|
3501
|
+
}
|
|
3502
|
+
#settingsBtn svg {
|
|
3503
|
+
width: 16px;
|
|
3504
|
+
height: 16px;
|
|
3505
|
+
}
|
|
3506
|
+
.pub-taken {
|
|
3507
|
+
display: flex;
|
|
3508
|
+
align-items: flex-start;
|
|
3509
|
+
gap: 6px;
|
|
3510
|
+
margin: 8px 0 0;
|
|
3511
|
+
font-size: 13px;
|
|
3512
|
+
line-height: 1.5;
|
|
3513
|
+
color: var(--muted);
|
|
3514
|
+
}
|
|
3515
|
+
.pub-taken svg {
|
|
3516
|
+
width: 15px;
|
|
3517
|
+
height: 15px;
|
|
3518
|
+
flex: none;
|
|
3519
|
+
margin-top: 2px;
|
|
3520
|
+
}
|
|
3521
|
+
.pub-note {
|
|
3522
|
+
margin: 8px 0 0;
|
|
3523
|
+
font-size: 13px;
|
|
3524
|
+
color: var(--muted);
|
|
3525
|
+
}
|
|
3526
|
+
/* A sent post's page in two parts (DESIGN §3): the post, then its delivery. */
|
|
3527
|
+
.rec-head-page {
|
|
3528
|
+
margin-bottom: 14px;
|
|
3529
|
+
}
|
|
3530
|
+
.rec-part-title {
|
|
3531
|
+
margin: 0 0 10px;
|
|
3532
|
+
font-size: 11px;
|
|
3533
|
+
font-weight: 600;
|
|
3534
|
+
letter-spacing: 0.08em;
|
|
3535
|
+
text-transform: uppercase;
|
|
3536
|
+
color: var(--muted);
|
|
3537
|
+
}
|
|
3538
|
+
.rec-web {
|
|
3539
|
+
display: grid;
|
|
3540
|
+
}
|
|
3541
|
+
.rec-link-label {
|
|
3542
|
+
margin: 0 0 6px;
|
|
3543
|
+
font-size: 12.5px;
|
|
3544
|
+
color: var(--muted);
|
|
3545
|
+
}
|
|
3546
|
+
/* The link as one field: the address, then Copy and Open attached to it. */
|
|
3547
|
+
.rec-link {
|
|
3548
|
+
display: flex;
|
|
3549
|
+
align-items: stretch;
|
|
3550
|
+
min-width: 0;
|
|
3551
|
+
overflow: hidden;
|
|
3552
|
+
border: 1px solid var(--line);
|
|
3553
|
+
border-radius: 7px;
|
|
3554
|
+
background: var(--sunken);
|
|
3555
|
+
}
|
|
3556
|
+
/* An address is one line, shortened at its end, since Copy carries it whole. */
|
|
3557
|
+
.rec-link code {
|
|
3558
|
+
flex: 1 1 auto;
|
|
3559
|
+
align-self: center;
|
|
3560
|
+
min-width: 0;
|
|
3561
|
+
padding: 8px 10px;
|
|
3562
|
+
font-family: var(--mono);
|
|
3563
|
+
font-size: 12.5px;
|
|
3564
|
+
white-space: nowrap;
|
|
3565
|
+
overflow: hidden;
|
|
3566
|
+
text-overflow: ellipsis;
|
|
3567
|
+
}
|
|
3568
|
+
.rec-link button {
|
|
3569
|
+
flex: none;
|
|
3570
|
+
display: inline-flex;
|
|
3571
|
+
align-items: center;
|
|
3572
|
+
gap: 5px;
|
|
3573
|
+
min-height: 35px;
|
|
3574
|
+
padding: 0 11px;
|
|
3575
|
+
border: 0;
|
|
3576
|
+
border-left: 1px solid var(--line);
|
|
3577
|
+
border-radius: 0;
|
|
3578
|
+
background: var(--card);
|
|
3579
|
+
color: var(--muted);
|
|
3580
|
+
font-size: 13px;
|
|
3581
|
+
white-space: nowrap;
|
|
3582
|
+
}
|
|
3583
|
+
.rec-link button:hover {
|
|
3584
|
+
color: var(--fg);
|
|
3585
|
+
background: var(--hover);
|
|
3586
|
+
}
|
|
3587
|
+
.rec-link button.rec-open {
|
|
3588
|
+
color: var(--accent);
|
|
3589
|
+
font-weight: 500;
|
|
3590
|
+
}
|
|
3591
|
+
.rec-link-note {
|
|
3592
|
+
margin: 6px 0 0;
|
|
3593
|
+
font-size: 12.5px;
|
|
3594
|
+
}
|
|
3595
|
+
/* Where the post stands, in words beside the one change it allows (which asks first). */
|
|
3596
|
+
.rec-arch {
|
|
3597
|
+
display: flex;
|
|
3598
|
+
align-items: center;
|
|
3599
|
+
justify-content: space-between;
|
|
3600
|
+
flex-wrap: wrap;
|
|
3601
|
+
gap: 10px 16px;
|
|
3602
|
+
margin: 14px -16px 0;
|
|
3603
|
+
padding: 14px 16px 0;
|
|
3604
|
+
border-top: 1px solid var(--line);
|
|
3605
|
+
}
|
|
3606
|
+
/* A post with no web link yet has only its standing to show, with no rule above it. */
|
|
3607
|
+
.rec-web > .rec-arch:first-child {
|
|
3608
|
+
margin-top: 0;
|
|
3609
|
+
padding-top: 0;
|
|
3610
|
+
border-top: 0;
|
|
3611
|
+
}
|
|
3612
|
+
.rec-arch-what {
|
|
3613
|
+
flex: 1 1 220px;
|
|
3614
|
+
min-width: 0;
|
|
3615
|
+
font-size: 14px;
|
|
3616
|
+
}
|
|
3617
|
+
.rec-arch-what strong {
|
|
3618
|
+
display: flex;
|
|
3619
|
+
align-items: center;
|
|
3620
|
+
gap: 6px;
|
|
3621
|
+
font-weight: 600;
|
|
3622
|
+
}
|
|
3623
|
+
.rec-arch-what svg {
|
|
3624
|
+
width: 16px;
|
|
3625
|
+
height: 16px;
|
|
3626
|
+
flex: none;
|
|
3627
|
+
color: var(--muted);
|
|
3628
|
+
}
|
|
3629
|
+
.rec-arch-what svg:not([fill="none"]) {
|
|
3630
|
+
fill: currentColor;
|
|
3631
|
+
}
|
|
3632
|
+
.rec-arch-note {
|
|
3633
|
+
display: block;
|
|
3634
|
+
margin-top: 2px;
|
|
3635
|
+
font-size: 13px;
|
|
3636
|
+
line-height: 1.45;
|
|
3637
|
+
color: var(--muted);
|
|
3638
|
+
}
|
|
3639
|
+
/* The address Remove from archive takes down, struck through in its dialog. */
|
|
3640
|
+
.url-gone {
|
|
3641
|
+
padding: 8px 10px;
|
|
3642
|
+
background: var(--sunken);
|
|
3643
|
+
border: 1px solid var(--line);
|
|
3644
|
+
border-radius: 6px;
|
|
3645
|
+
font-family: var(--mono);
|
|
3646
|
+
font-size: 13px;
|
|
3647
|
+
color: var(--muted);
|
|
3648
|
+
overflow-wrap: anywhere;
|
|
3649
|
+
}
|
|
3650
|
+
|
|
3651
|
+
@media (max-width: 620px) {
|
|
3298
3652
|
.tb {
|
|
3299
3653
|
width: 34px;
|
|
3300
3654
|
height: 34px;
|
|
@@ -4209,7 +4563,8 @@ button.api-filter-clear svg {
|
|
|
4209
4563
|
gap: 10px;
|
|
4210
4564
|
}
|
|
4211
4565
|
|
|
4212
|
-
/* Setup
|
|
4566
|
+
/* Setup steps: the first-run checklist, and the regular dashboard's Finish setting up card
|
|
4567
|
+
(one component, two layouts). The card is the compact one. */
|
|
4213
4568
|
.setup-title {
|
|
4214
4569
|
margin-top: 0;
|
|
4215
4570
|
}
|
|
@@ -4282,6 +4637,20 @@ button.api-filter-clear svg {
|
|
|
4282
4637
|
color: var(--muted);
|
|
4283
4638
|
word-break: break-all;
|
|
4284
4639
|
}
|
|
4640
|
+
.setup-card {
|
|
4641
|
+
margin-bottom: 20px;
|
|
4642
|
+
}
|
|
4643
|
+
.setup-card .setup-title {
|
|
4644
|
+
font-size: 16px;
|
|
4645
|
+
}
|
|
4646
|
+
.setup-card .setup-steps li {
|
|
4647
|
+
padding: 8px 2px;
|
|
4648
|
+
}
|
|
4649
|
+
.setup-card .setup-steps li::before {
|
|
4650
|
+
width: 22px;
|
|
4651
|
+
height: 22px;
|
|
4652
|
+
font-size: 12px;
|
|
4653
|
+
}
|
|
4285
4654
|
|
|
4286
4655
|
/* ============================================================
|
|
4287
4656
|
Settings page. Grouped by intent, made visible: each section
|
|
@@ -4378,6 +4747,17 @@ button.copied svg {
|
|
|
4378
4747
|
.pub-row button.copy-btn {
|
|
4379
4748
|
flex: 0 0 auto;
|
|
4380
4749
|
}
|
|
4750
|
+
/* In a sent post's link field, Copy is a segment of the field like Open: square, full height,
|
|
4751
|
+
and divided from the address, so its hover fills the segment rather than a chip inside it. */
|
|
4752
|
+
.rec-link button.copy-btn {
|
|
4753
|
+
padding: 0 11px;
|
|
4754
|
+
border-left: 1px solid var(--line);
|
|
4755
|
+
border-radius: 0;
|
|
4756
|
+
background: var(--card);
|
|
4757
|
+
}
|
|
4758
|
+
.rec-link button.copy-btn:hover {
|
|
4759
|
+
background: var(--hover);
|
|
4760
|
+
}
|
|
4381
4761
|
|
|
4382
4762
|
/* --- groups: the sections kept together by whom they serve (DESIGN §3). A group has no
|
|
4383
4763
|
heading, since each section's title already says what it is; a wider gap than the one
|