@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.
@@ -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: 50%;
2444
- transform: translateX(-50%);
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
- transition: opacity 0.2s;
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 auto-tracks the subject; normal once hand-set. */
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, Open ↗). */
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: URL row reuses .pub-row; embed adds a rendered preview --- */
4593
- .set-embed-head {
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: center;
4782
+ align-items: flex-start;
4596
4783
  justify-content: space-between;
4597
- gap: 12px;
4598
- flex-wrap: wrap;
4599
- margin: 2px 0 8px;
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
- .set-embed pre {
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
- overflow-x: auto;
4604
- background: var(--bg);
4605
- border: 1px solid var(--line);
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-embed pre code {
4611
- white-space: pre;
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
- margin: 14px 0 0;
4615
- border: 1px dashed var(--line-soft);
4616
- border-radius: 8px;
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-cap {
4621
- font-size: 10.5px;
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
- /* Rendered form previews (inert — they never submit here). */
4629
- .set-pf-plain label {
4630
- display: block;
4631
- font-size: 13px;
4632
- margin: 0 0 4px;
4633
- font-weight: 600;
4634
- color: var(--fg);
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-pf-plain input {
4637
- max-width: 260px;
4638
- margin-bottom: 8px;
4863
+ .set-embed-code code {
4864
+ white-space: pre-wrap;
4865
+ overflow-wrap: anywhere;
4639
4866
  }
4640
- .set-pf-plain button {
4641
- display: block;
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: 440px;
4914
+ max-width: 460px;
4915
+ font-size: 16px;
4645
4916
  }
4646
4917
  .set-pf-styled .l {
4647
4918
  display: block;
4648
- font-size: 13.5px;
4649
- font-weight: 600;
4650
- margin-bottom: 6px;
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
- gap: 8px;
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 — but it rides the neutral --ink token
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: 8px 0 0;
4674
- font-size: 12px;
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).