@kurtbruns/kestrel 1.5.0 → 1.6.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.
@@ -1169,6 +1169,7 @@ button:active {
1169
1169
  button.primary,
1170
1170
  button.secondary,
1171
1171
  button.ghost,
1172
+ a.ghost.btn,
1172
1173
  button.danger,
1173
1174
  button.danger-subtle {
1174
1175
  min-height: 35px;
@@ -1212,7 +1213,8 @@ button.secondary:hover {
1212
1213
  /* Ghost — the quiet neutral role: back, cancel, a menu trigger, a pager step, a copy
1213
1214
  control. The default for anything not asking for attention; absorbs the old
1214
1215
  .ghost-btn / .set-btn-ghost idioms. */
1215
- button.ghost {
1216
+ button.ghost,
1217
+ a.ghost.btn {
1216
1218
  display: inline-flex;
1217
1219
  align-items: center;
1218
1220
  gap: 6px;
@@ -1223,10 +1225,19 @@ button.ghost {
1223
1225
  padding: 6px 12px;
1224
1226
  border-radius: 6px;
1225
1227
  }
1226
- button.ghost:hover {
1228
+ button.ghost:hover,
1229
+ a.ghost.btn:hover {
1227
1230
  color: var(--fg);
1228
1231
  border-color: var(--muted);
1229
1232
  }
1233
+ /* A link that wears Ghost (`a.ghost.btn`: Web version ↗ goes to a page rather than
1234
+ acting on one), with a link's own underline and transition taken off so it reads as
1235
+ the buttons beside it. The plain `a.ghost` is the sidebar's text-link Sign out. */
1236
+ a.ghost.btn {
1237
+ text-decoration: none;
1238
+ line-height: normal;
1239
+ transition: border-color 0.12s;
1240
+ }
1230
1241
  /* A copy control flashes green (--ok) once the clipboard write lands. */
1231
1242
  button.ghost.copied {
1232
1243
  color: var(--ok-fg);
@@ -1699,6 +1710,83 @@ th.num .th-sort {
1699
1710
  align-items: center;
1700
1711
  gap: 10px;
1701
1712
  }
1713
+ /* A post's web version (#/web/post/…): the post page as it will look, filling the window
1714
+ with no app chrome, since it opens in a tab of its own. The window scrolls the page. */
1715
+ body.web-mode .sidebar,
1716
+ body.web-mode .nav-toggle {
1717
+ display: none;
1718
+ }
1719
+ body.web-mode .content {
1720
+ max-width: none;
1721
+ margin: 0;
1722
+ padding: 0;
1723
+ }
1724
+ /* The page sits under one strip that says where the post stands (DESIGN §7). The strip is
1725
+ the editor's, above the frame, and the frame is as tall as the post, so the window scrolls
1726
+ and the strip goes up with the page like the test pages' strip. It wears the brand accent
1727
+ like the reader surface's dev pill, its tag as the pill's inverted chip, and its content
1728
+ sits in the default template's column so it lines up with the post. The test pages carry
1729
+ the same strip in the reader shell (READER_BANNER_STYLE in src/lib/page.ts); keep the two
1730
+ in sync. */
1731
+ /* At least the window's height, so a post shorter than the window still has its own
1732
+ ground to the bottom: the frame grows into what's left, and the framed page's background
1733
+ fills it. */
1734
+ .web-page {
1735
+ display: flex;
1736
+ flex-direction: column;
1737
+ min-height: 100dvh;
1738
+ }
1739
+ .web-bar {
1740
+ flex: none;
1741
+ background: var(--accent);
1742
+ color: var(--accent-on);
1743
+ font-size: 13.5px;
1744
+ font-weight: 500;
1745
+ line-height: 1.4;
1746
+ }
1747
+ /* The tag runs in, inline with the sentence, so on a phone the sentence wraps beneath it at
1748
+ the strip's full width rather than in a narrow column beside it. */
1749
+ .web-bar-in {
1750
+ /* The default template's 664px column inside its 12px gutter, with the column's 32px
1751
+ padding, so the strip's text starts on the post's left edge at any width over a phone's. */
1752
+ max-width: 688px;
1753
+ margin: 0 auto;
1754
+ padding: 10px 44px;
1755
+ text-align: left;
1756
+ }
1757
+ .web-bar-tag {
1758
+ display: inline-block;
1759
+ margin-right: 10px;
1760
+ font-size: 10px;
1761
+ letter-spacing: 0.09em;
1762
+ text-transform: uppercase;
1763
+ font-weight: 700;
1764
+ padding: 1px 7px;
1765
+ border-radius: 999px;
1766
+ background: var(--accent-on);
1767
+ color: var(--accent);
1768
+ }
1769
+ .web-bar-text {
1770
+ overflow-wrap: anywhere;
1771
+ }
1772
+ @media (max-width: 560px) {
1773
+ .web-bar-in {
1774
+ padding: 10px 16px;
1775
+ }
1776
+ }
1777
+ .web-frame {
1778
+ display: block;
1779
+ flex: 1 0 auto;
1780
+ width: 100%;
1781
+ /* The post's height once measured; until then a window's, so the page doesn't flash short. */
1782
+ height: 100vh;
1783
+ border: 0;
1784
+ }
1785
+ .web-error {
1786
+ max-width: 640px;
1787
+ margin: 48px auto;
1788
+ padding: 0 16px;
1789
+ }
1702
1790
  /* Sent record view (#148): a read-only delivery record. The outcome tiles reuse the
1703
1791
  semantic feedback scales (--ok / --warn / --danger, DESIGN §3); a status badge, not
1704
1792
  a feedback color, still labels the send's lifecycle. */
@@ -2332,6 +2420,13 @@ th.num .th-sort {
2332
2420
  .composer-foot:hover {
2333
2421
  color: var(--fg);
2334
2422
  }
2423
+ /* On the Preview tab the image foot dims with the toolbar (`.toolbar.off`): a click there
2424
+ would insert into the hidden Edit text. A drop on the composer still works; it switches
2425
+ to Edit first, so the result is seen. */
2426
+ .composer-foot.off {
2427
+ opacity: 0.4;
2428
+ pointer-events: none;
2429
+ }
2335
2430
  .composer-foot svg {
2336
2431
  width: 16px;
2337
2432
  height: 16px;
@@ -2437,29 +2532,52 @@ th.num .th-sort {
2437
2532
  }
2438
2533
 
2439
2534
  /* toasts (live region) */
2535
+ /* Spans the viewport and centers its toasts, rather than left: 50% plus a transform,
2536
+ which caps a toast's width at half the screen and wraps a sentence on a phone. */
2440
2537
  #toasts {
2441
2538
  position: fixed;
2442
2539
  bottom: 20px;
2443
- left: 50%;
2444
- transform: translateX(-50%);
2540
+ left: 16px;
2541
+ right: 16px;
2445
2542
  display: flex;
2446
2543
  flex-direction: column;
2447
- gap: 8px;
2448
2544
  align-items: center;
2449
2545
  z-index: var(--z-toast);
2450
2546
  pointer-events: none;
2451
2547
  }
2548
+ /* Spaced by margin, not the column's gap, so a leaving toast can collapse its share. */
2452
2549
  .toast {
2550
+ box-sizing: border-box;
2551
+ max-width: 480px;
2552
+ margin-top: 8px;
2553
+ overflow: hidden;
2453
2554
  background: var(--fg);
2454
2555
  color: var(--bg);
2455
2556
  padding: 10px 16px;
2456
2557
  border-radius: 8px;
2457
2558
  font-size: 14px;
2458
2559
  opacity: 0;
2459
- transition: opacity 0.2s;
2560
+ transform: translateY(6px);
2561
+ transition:
2562
+ opacity 0.2s,
2563
+ transform 0.2s;
2460
2564
  }
2461
2565
  .toast.show {
2462
2566
  opacity: 1;
2567
+ transform: none;
2568
+ }
2569
+ /* Leaving: fade while the height (set inline by leaveToast, since a transition can't run
2570
+ from auto), padding, and spacing collapse, so the stack settles smoothly. 0.3s is
2571
+ TOAST_LEAVE_MS in client/ui/widgets.ts. */
2572
+ .toast.leaving {
2573
+ opacity: 0;
2574
+ margin-top: 0;
2575
+ padding-block: 0;
2576
+ transition:
2577
+ opacity 0.2s,
2578
+ height 0.3s,
2579
+ margin-top 0.3s,
2580
+ padding-block 0.3s;
2463
2581
  }
2464
2582
 
2465
2583
  /* modal */
@@ -2784,7 +2902,7 @@ textarea.is-invalid {
2784
2902
  .info.tip-below:focus::after {
2785
2903
  transform: translateY(0);
2786
2904
  }
2787
- /* "Auto-generate from subject" toggle, grouped just under the slug field. */
2905
+ /* The "Auto-generate from subject" toggle, grouped just under the slug field. */
2788
2906
  .slug-auto-toggle {
2789
2907
  display: inline-flex;
2790
2908
  align-items: center;
@@ -2802,7 +2920,7 @@ textarea.is-invalid {
2802
2920
  accent-color: var(--ring);
2803
2921
  cursor: pointer;
2804
2922
  }
2805
- /* Slug is muted while it auto-tracks the subject; normal once hand-set. */
2923
+ /* Slug is muted while it follows the subject; normal once set by hand. */
2806
2924
  #f-slug.slug-auto {
2807
2925
  color: var(--muted);
2808
2926
  }
@@ -3411,44 +3529,6 @@ button.api-filter-clear svg {
3411
3529
  margin: 6px 0 0;
3412
3530
  font-size: 14px;
3413
3531
  }
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
3532
  /* A query parameter the route can't be called without. */
3453
3533
  .api-req {
3454
3534
  margin-left: 4px;
@@ -3458,22 +3538,7 @@ button.api-copy.copied svg {
3458
3538
  border-radius: 4px;
3459
3539
  padding: 0 4px;
3460
3540
  }
3461
- .api-ex-label {
3462
- display: block;
3463
- font-size: 12px;
3464
- font-weight: 600;
3465
- color: var(--muted);
3466
- }
3467
3541
 
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
3542
  .api-empty {
3478
3543
  margin: 20px 0;
3479
3544
  }
@@ -3852,9 +3917,6 @@ button.api-copy.copied svg {
3852
3917
  border-radius: 6px;
3853
3918
  padding: 6px 9px;
3854
3919
  }
3855
- .pub-row button.ghost {
3856
- flex: 0 0 auto;
3857
- }
3858
3920
  .pub-foot {
3859
3921
  margin-top: 4px;
3860
3922
  }
@@ -3928,6 +3990,16 @@ button.api-copy.copied svg {
3928
3990
  .setup-steps li button {
3929
3991
  flex: 0 0 auto;
3930
3992
  }
3993
+ /* The setup step's Copy keeps the step's button look, with the shared Copy's glyph. */
3994
+ .setup-steps li button[data-copy] {
3995
+ display: inline-flex;
3996
+ align-items: center;
3997
+ gap: 5px;
3998
+ }
3999
+ .setup-steps li button[data-copy] svg {
4000
+ width: 14px;
4001
+ height: 14px;
4002
+ }
3931
4003
  .setup-url {
3932
4004
  font: 12.5px var(--mono);
3933
4005
  color: var(--muted);
@@ -3968,6 +4040,75 @@ button.api-copy.copied svg {
3968
4040
  .set-sec {
3969
4041
  margin-bottom: 34px;
3970
4042
  }
4043
+
4044
+ /* --- copying text out of the app (DESIGN §1, ui/copy.ts) ---
4045
+ A labelled block (a code block, or the API reference's query and content-type tables):
4046
+ its label line, then the block. The Copy is a control outside the five roles: the word
4047
+ and its glyph, no fill or border, quiet on the line it shares; the glyph turns to a
4048
+ check in the ok hue for a moment once a copy has landed, and the word stays. A Copy
4049
+ beside a single value (an address) is the same control. --- */
4050
+ .block {
4051
+ margin-top: 10px;
4052
+ }
4053
+ .block-head {
4054
+ display: flex;
4055
+ align-items: center;
4056
+ justify-content: space-between;
4057
+ margin: 8px 0 4px;
4058
+ }
4059
+ button.copy-btn {
4060
+ display: inline-flex;
4061
+ align-items: center;
4062
+ gap: 5px;
4063
+ border: 0;
4064
+ background: transparent;
4065
+ color: var(--muted);
4066
+ font-size: 13px;
4067
+ padding: 4px 8px;
4068
+ border-radius: 5px;
4069
+ }
4070
+ button.copy-btn:hover {
4071
+ color: var(--fg);
4072
+ background: var(--hover);
4073
+ }
4074
+ /* No pressed nudge: on a quiet word the button-wide half-pixel drop reads as a jump. */
4075
+ button.copy-btn:active {
4076
+ transform: none;
4077
+ }
4078
+ button.copy-btn svg {
4079
+ width: 14px;
4080
+ height: 14px;
4081
+ }
4082
+ /* Any Copy that has just landed, whatever button it rides (ui/copy.ts). */
4083
+ button.copied svg {
4084
+ color: var(--ok-fg);
4085
+ }
4086
+ .block-label {
4087
+ display: block;
4088
+ font-size: 12px;
4089
+ font-weight: 600;
4090
+ color: var(--muted);
4091
+ }
4092
+ .block pre {
4093
+ margin: 0;
4094
+ overflow-x: auto;
4095
+ background: var(--bg);
4096
+ border: 1px solid var(--line);
4097
+ border-radius: 6px;
4098
+ padding: 10px 12px;
4099
+ font: 12.5px / 1.5 var(--mono);
4100
+ }
4101
+ /* A Copy beside a value keeps its size however long the value runs. */
4102
+ .pub-row button.copy-btn {
4103
+ flex: 0 0 auto;
4104
+ }
4105
+
4106
+ /* --- groups: the sections kept together by whom they serve (DESIGN §5). A group has no
4107
+ heading, since each section's title already says what it is; a wider gap than the one
4108
+ between sections is all that separates one group from the next. --- */
4109
+ .set-group + .set-group {
4110
+ margin-top: 22px;
4111
+ }
3971
4112
  .set-sec-head {
3972
4113
  display: flex;
3973
4114
  align-items: center;
@@ -4065,7 +4206,7 @@ button.api-copy.copied svg {
4065
4206
  padding: 18px;
4066
4207
  }
4067
4208
 
4068
- /* Kestrel-accent links inside the settings cards (operator-guide links, Open ↗). */
4209
+ /* Kestrel-accent links inside the settings cards (operator-guide links). */
4069
4210
  .set-link {
4070
4211
  color: var(--accent);
4071
4212
  font-weight: 550;
@@ -4087,6 +4228,11 @@ button.api-copy.copied svg {
4087
4228
  flex-direction: column;
4088
4229
  gap: 8px;
4089
4230
  }
4231
+ /* The hint and the not-square warning stack in one column, under the tile on a wide
4232
+ screen and beside it where the slot becomes a row. */
4233
+ .set-logo-notes {
4234
+ min-width: 0;
4235
+ }
4090
4236
  .set-logo-tile {
4091
4237
  width: 100%;
4092
4238
  aspect-ratio: 1;
@@ -4481,16 +4627,6 @@ button.set-field-remove {
4481
4627
  .set-ce-edit {
4482
4628
  padding: 18px;
4483
4629
  }
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
4630
  .set-ce-editfoot {
4495
4631
  display: flex;
4496
4632
  align-items: center;
@@ -4589,89 +4725,194 @@ button.set-field-remove {
4589
4725
  flex: 1;
4590
4726
  }
4591
4727
 
4592
- /* --- ways to subscribe: URL row reuses .pub-row; embed adds a rendered preview --- */
4593
- .set-embed-head {
4728
+ /* --- ways to subscribe (DESIGN §5): one card, the two ways as bands in reading order,
4729
+ the page then the form, split by a full-width rule like the other cards' bands. Each
4730
+ band has the same head (a glyph, a title, one line) so they read as two options. The
4731
+ card folds by the section's own width, since the sidebar decides how wide Settings is. --- */
4732
+ .set-sec-subscribe {
4733
+ container-type: inline-size;
4734
+ }
4735
+ .set-way + .set-way {
4736
+ border-top: 1px solid var(--line);
4737
+ }
4738
+ /* A control that switches what the band shows (the embed's Styled | Plain) sits at
4739
+ the head's right end, as the reader pages' width toggle does. */
4740
+ .set-way-head {
4594
4741
  display: flex;
4595
- align-items: center;
4742
+ align-items: flex-start;
4596
4743
  justify-content: space-between;
4597
- gap: 12px;
4598
- flex-wrap: wrap;
4599
- margin: 2px 0 8px;
4744
+ gap: 10px 16px;
4745
+ padding: 18px 18px 0;
4746
+ }
4747
+ /* The name takes the room the control leaves, so the control keeps its size. */
4748
+ .set-way-name {
4749
+ flex: 1 1 0;
4750
+ min-width: 0;
4600
4751
  }
4601
- .set-embed pre {
4752
+ /* Set like a field label, its glyph like a note's, so the band ranks with the other cards. */
4753
+ .set-way-title {
4754
+ display: flex;
4755
+ align-items: center;
4756
+ gap: 7px;
4602
4757
  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);
4758
+ font-size: 13px;
4759
+ font-weight: 600;
4760
+ color: var(--fg);
4609
4761
  }
4610
- .set-embed pre code {
4611
- white-space: pre;
4762
+ .set-way-title svg {
4763
+ width: 15px;
4764
+ height: 15px;
4765
+ flex: 0 0 auto;
4766
+ fill: currentColor;
4767
+ color: var(--muted);
4768
+ }
4769
+ .set-way-line {
4770
+ margin: 3px 0 0;
4771
+ font-size: 12.5px;
4772
+ color: var(--muted);
4773
+ line-height: 1.5;
4774
+ }
4775
+ .set-way-body {
4776
+ padding: 10px 18px 18px;
4777
+ }
4778
+ /* The address is a value to copy, not a field: plain monospace, its Copy and Open beside it. */
4779
+ .set-way-addr {
4780
+ display: flex;
4781
+ align-items: center;
4782
+ flex-wrap: wrap;
4783
+ gap: 4px 6px;
4784
+ min-width: 0;
4785
+ }
4786
+ .set-addr {
4787
+ font: 13.5px var(--mono);
4788
+ color: var(--fg);
4789
+ overflow-wrap: anywhere;
4790
+ margin-right: 4px;
4791
+ }
4792
+ /* The form as it will look beside the code that makes it, both always shown, edge to
4793
+ edge in the card like the reader pages' outline beside its frame. The preview grows to
4794
+ 480px, near the styled form's own 460px cap, so it shows the form about as wide as a
4795
+ site will; the code keeps 280px, so on a narrower card the preview gives way first. */
4796
+ .set-embed {
4797
+ display: grid;
4798
+ grid-template-columns: minmax(240px, 480px) minmax(280px, 1fr);
4799
+ margin-top: 16px;
4800
+ border-top: 1px solid var(--line);
4612
4801
  }
4613
4802
  .set-embed-preview {
4614
- margin: 14px 0 0;
4615
- border: 1px dashed var(--line-soft);
4616
- border-radius: 8px;
4617
- padding: 18px 16px;
4803
+ display: flex;
4804
+ align-items: center;
4805
+ padding: 18px;
4618
4806
  background: var(--sunken);
4807
+ border-right: 1px solid var(--line);
4808
+ min-width: 0;
4619
4809
  }
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;
4810
+ .set-embed-preview > form {
4811
+ width: 100%;
4627
4812
  }
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);
4813
+ /* The code wraps rather than scrolling sideways, so the Styled snippet's long lines read
4814
+ whole in the column; the copied text is unchanged. */
4815
+ .set-embed-code {
4816
+ margin: 0;
4817
+ min-width: 0;
4818
+ padding: 12px 14px;
4819
+ max-height: 220px;
4820
+ overflow: auto;
4821
+ font: 12px / 1.55 var(--mono);
4635
4822
  }
4636
- .set-pf-plain input {
4637
- max-width: 260px;
4638
- margin-bottom: 8px;
4823
+ .set-embed-code code {
4824
+ white-space: pre-wrap;
4825
+ overflow-wrap: anywhere;
4639
4826
  }
4640
- .set-pf-plain button {
4641
- display: block;
4827
+ /* The band ends in its one act: read top to bottom, choose, see the form and its code,
4828
+ then copy. A strip across the card's foot, as Notifications ends in its test row. */
4829
+ .set-embed-foot {
4830
+ display: flex;
4831
+ justify-content: flex-end;
4832
+ padding: 12px 18px;
4833
+ border-top: 1px solid var(--line);
4834
+ }
4835
+ /* Copy HTML is Secondary with the shared Copy's word and glyph, as the setup step's is. */
4836
+ button.set-embed-copy {
4837
+ display: inline-flex;
4838
+ align-items: center;
4839
+ gap: 6px;
4840
+ }
4841
+ button.set-embed-copy svg {
4842
+ width: 14px;
4843
+ height: 14px;
4844
+ }
4845
+ @container (max-width: 620px) {
4846
+ /* No room beside the title: the toggle drops under the band's line, at its left. */
4847
+ .set-way-head {
4848
+ flex-direction: column;
4849
+ }
4850
+ .set-way-name {
4851
+ flex-basis: auto;
4852
+ }
4853
+ .set-embed {
4854
+ grid-template-columns: minmax(0, 1fr);
4855
+ }
4856
+ .set-embed-preview {
4857
+ border-right: 0;
4858
+ border-bottom: 1px solid var(--line);
4859
+ }
4642
4860
  }
4861
+ /* Rendered form previews (inert: they never submit here). The plain form is bare
4862
+ markup, so it previews with the browser's own controls and inline flow, as a page
4863
+ with no styles of its own would show it, never the admin's inputs. */
4864
+ .set-pf-plain,
4865
+ .set-pf-plain :is(label, input, button) {
4866
+ all: revert;
4867
+ }
4868
+ .set-pf-plain input {
4869
+ margin: 4px 4px 4px 0;
4870
+ }
4871
+ /* The styled form wears the hosted subscribe page's look (SPEC §5), at the snippet's own
4872
+ sizes, so the preview is the form a publisher copies. */
4643
4873
  .set-pf-styled {
4644
- max-width: 440px;
4874
+ max-width: 460px;
4875
+ font-size: 16px;
4645
4876
  }
4646
4877
  .set-pf-styled .l {
4647
4878
  display: block;
4648
- font-size: 13.5px;
4649
- font-weight: 600;
4650
- margin-bottom: 6px;
4879
+ font:
4880
+ 600 22px / 1.2 Georgia,
4881
+ "Times New Roman",
4882
+ serif;
4883
+ letter-spacing: -0.01em;
4884
+ margin-bottom: 12px;
4651
4885
  color: var(--fg);
4652
4886
  }
4653
4887
  .set-pf-styled .rowf {
4654
4888
  display: flex;
4655
- gap: 8px;
4889
+ flex-wrap: wrap;
4890
+ gap: 10px;
4656
4891
  }
4657
4892
  .set-pf-styled input {
4658
- flex: 1;
4893
+ flex: 1 1 220px;
4659
4894
  min-width: 0;
4895
+ padding: 11px 13px;
4896
+ border-radius: 9px;
4897
+ font-size: 16px;
4660
4898
  }
4661
4899
  /* 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
4900
+ so it isn't one of the five button roles, but it rides the neutral --ink token
4663
4901
  (was a hardcoded near-black that vanished in dark mode) so it themes correctly. */
4664
4902
  .set-pf-styled .sub-btn {
4665
4903
  flex: 0 0 auto;
4904
+ padding: 11px 20px;
4905
+ border-radius: 9px;
4666
4906
  background: var(--ink);
4667
4907
  color: var(--ink-on);
4668
4908
  border-color: var(--ink);
4909
+ font-size: 15px;
4669
4910
  font-weight: 600;
4670
4911
  white-space: nowrap;
4671
4912
  }
4672
4913
  .set-pf-fine {
4673
- margin: 8px 0 0;
4674
- font-size: 12px;
4914
+ margin: 12px 0 0;
4915
+ font-size: 12.5px;
4675
4916
  color: var(--muted);
4676
4917
  }
4677
4918
 
@@ -5028,10 +5269,6 @@ button.set-field-remove {
5028
5269
  background: var(--accent-soft);
5029
5270
  border-color: color-mix(in srgb, var(--accent) 30%, var(--line-soft));
5030
5271
  }
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
5272
  /* Required-token pills (issue #123 revisit): live green/red for the two tokens whose
5036
5273
  absence blocks a save, shown upper-right of the editor toolbar. */
5037
5274
  .set-tpl-required {
@@ -5184,6 +5421,243 @@ button.set-field-remove {
5184
5421
  color: var(--muted);
5185
5422
  }
5186
5423
 
5424
+ /* --- Reader pages: an outline of every page a reader can land on, beside one framed page ---
5425
+ The journeys are the outline's headings, set like section titles and never controls;
5426
+ the pages are its rows. A journey's steps are numbered on a thread, peers carry a dot,
5427
+ and an off-path page carries a dashed branch mark. The section folds by its own width,
5428
+ not the window's (the sidebar decides how wide Settings is): too narrow for the outline,
5429
+ it becomes one grouped select, and the width toggle goes, since the frame is already
5430
+ no wider than a phone. The page brings its own ground, so the frame has none. */
5431
+ .set-rp-sec {
5432
+ container-type: inline-size;
5433
+ }
5434
+ .set-rp {
5435
+ display: grid;
5436
+ grid-template-columns: 210px minmax(0, 1fr);
5437
+ overflow: hidden;
5438
+ }
5439
+ .set-rp-nav {
5440
+ border-right: 1px solid var(--line);
5441
+ background: var(--sunken);
5442
+ padding: 14px 10px 18px;
5443
+ }
5444
+ .set-rp-journey + .set-rp-journey {
5445
+ margin-top: 16px;
5446
+ }
5447
+ .set-rp-jhead {
5448
+ display: flex;
5449
+ align-items: center;
5450
+ gap: 7px;
5451
+ margin: 0 6px 6px;
5452
+ font-size: 11px;
5453
+ font-weight: 650;
5454
+ letter-spacing: 0.07em;
5455
+ text-transform: uppercase;
5456
+ color: var(--muted);
5457
+ }
5458
+ .set-rp-jhead svg {
5459
+ width: 13px;
5460
+ height: 13px;
5461
+ }
5462
+ .set-rp-journey.current .set-rp-jhead {
5463
+ color: var(--fg);
5464
+ }
5465
+ .set-rp-list {
5466
+ list-style: none;
5467
+ margin: 0;
5468
+ padding: 0;
5469
+ position: relative;
5470
+ }
5471
+ /* The thread behind a journey's numbered steps, from the first mark to the last row. */
5472
+ .set-rp-list.steps::before {
5473
+ content: "";
5474
+ position: absolute;
5475
+ left: 17px;
5476
+ top: 14px;
5477
+ bottom: 14px;
5478
+ width: 1px;
5479
+ background: var(--line-soft);
5480
+ }
5481
+ .set-rp-item {
5482
+ width: 100%;
5483
+ display: flex;
5484
+ align-items: center;
5485
+ gap: 9px;
5486
+ text-align: left;
5487
+ border: 0;
5488
+ background: transparent;
5489
+ border-radius: 7px;
5490
+ padding: 5px 8px;
5491
+ font-size: 13px;
5492
+ font-weight: 400;
5493
+ color: var(--fg);
5494
+ cursor: pointer;
5495
+ }
5496
+ .set-rp-item:hover {
5497
+ background: var(--hover);
5498
+ }
5499
+ .set-rp-item[aria-current="true"] {
5500
+ background: var(--accent-soft);
5501
+ color: var(--accent);
5502
+ font-weight: 600;
5503
+ }
5504
+ .set-rp-item.off {
5505
+ color: var(--muted);
5506
+ }
5507
+ .set-rp-item.off[aria-current="true"] {
5508
+ color: var(--accent);
5509
+ }
5510
+ .set-rp-mark {
5511
+ position: relative;
5512
+ z-index: 1;
5513
+ flex: 0 0 auto;
5514
+ display: grid;
5515
+ place-items: center;
5516
+ width: 19px;
5517
+ height: 19px;
5518
+ border-radius: 50%;
5519
+ border: 1px solid var(--line-soft);
5520
+ background: var(--card);
5521
+ font-size: 10.5px;
5522
+ font-weight: 650;
5523
+ color: var(--muted);
5524
+ }
5525
+ .set-rp-item[aria-current="true"] .set-rp-mark {
5526
+ border-color: var(--accent);
5527
+ color: var(--accent);
5528
+ }
5529
+ .set-rp-item.off .set-rp-mark {
5530
+ border-style: dashed;
5531
+ background: transparent;
5532
+ }
5533
+ .set-rp-dot {
5534
+ width: 3px;
5535
+ height: 3px;
5536
+ border-radius: 50%;
5537
+ background: currentColor;
5538
+ }
5539
+ .set-rp-mark svg {
5540
+ width: 11px;
5541
+ height: 11px;
5542
+ }
5543
+ .set-rp-pick {
5544
+ display: none;
5545
+ }
5546
+ .set-rp-pick select {
5547
+ width: 100%;
5548
+ }
5549
+ .set-rp-main {
5550
+ min-width: 0;
5551
+ display: flex;
5552
+ flex-direction: column;
5553
+ }
5554
+ .set-rp-top {
5555
+ display: flex;
5556
+ align-items: flex-start;
5557
+ justify-content: space-between;
5558
+ gap: 12px;
5559
+ padding: 14px 16px 12px;
5560
+ border-bottom: 1px solid var(--line);
5561
+ }
5562
+ .set-rp-crumb {
5563
+ display: flex;
5564
+ flex-wrap: wrap;
5565
+ align-items: center;
5566
+ gap: 6px;
5567
+ margin: 0 0 3px;
5568
+ font-size: 14px;
5569
+ font-weight: 650;
5570
+ }
5571
+ .set-rp-crumb-j {
5572
+ color: var(--muted);
5573
+ font-weight: 500;
5574
+ }
5575
+ .set-rp-crumb-sep {
5576
+ color: var(--line-soft);
5577
+ font-weight: 400;
5578
+ }
5579
+ .set-rp-note {
5580
+ margin: 0;
5581
+ font-size: 12.5px;
5582
+ line-height: 1.5;
5583
+ color: var(--muted);
5584
+ max-width: 60ch;
5585
+ }
5586
+ .set-rp .set-wtog {
5587
+ flex: 0 0 auto;
5588
+ }
5589
+ .set-rp .wtog-btn {
5590
+ display: inline-flex;
5591
+ align-items: center;
5592
+ gap: 5px;
5593
+ }
5594
+ .set-rp .wtog-btn svg {
5595
+ width: 13px;
5596
+ height: 13px;
5597
+ }
5598
+ /* Where a reader meets the page, and that this one is a sample: the cue a strip inside
5599
+ the page would otherwise carry, kept in the app's chrome around it. */
5600
+ .set-rp-at {
5601
+ display: flex;
5602
+ flex-wrap: wrap;
5603
+ justify-content: space-between;
5604
+ gap: 4px 10px;
5605
+ padding: 7px 16px;
5606
+ border-bottom: 1px solid var(--line);
5607
+ font-size: 11.5px;
5608
+ color: var(--muted);
5609
+ }
5610
+ .set-rp-at .mono {
5611
+ overflow-wrap: anywhere;
5612
+ }
5613
+ .set-rp-stage.phone {
5614
+ padding: 18px 12px;
5615
+ background: var(--sunken);
5616
+ }
5617
+ .set-rp-frame {
5618
+ display: block;
5619
+ width: 100%;
5620
+ min-height: 320px;
5621
+ border: 0;
5622
+ }
5623
+ .set-rp-stage.phone .set-rp-frame {
5624
+ width: 375px;
5625
+ max-width: 100%;
5626
+ margin: 0 auto;
5627
+ border: 1px solid var(--line);
5628
+ border-radius: 10px;
5629
+ }
5630
+ .set-rp-error {
5631
+ margin: 0;
5632
+ padding: 12px 16px;
5633
+ font-size: 13px;
5634
+ color: var(--danger-fg);
5635
+ }
5636
+ @container (max-width: 640px) {
5637
+ .set-rp {
5638
+ grid-template-columns: minmax(0, 1fr);
5639
+ }
5640
+ .set-rp-nav,
5641
+ .set-rp .set-wtog {
5642
+ display: none;
5643
+ }
5644
+ .set-rp-pick {
5645
+ display: block;
5646
+ padding: 12px 14px;
5647
+ border-bottom: 1px solid var(--line);
5648
+ background: var(--sunken);
5649
+ }
5650
+ .set-rp-stage.phone {
5651
+ padding: 0;
5652
+ background: none;
5653
+ }
5654
+ .set-rp-stage.phone .set-rp-frame {
5655
+ width: 100%;
5656
+ border: 0;
5657
+ border-radius: 0;
5658
+ }
5659
+ }
5660
+
5187
5661
  /* --- shared unsaved-changes bar -------------------------------------------------
5188
5662
  One full-width banner fixed to the bottom of the viewport, used by every surface
5189
5663
  with an explicit save + a revertible baseline (Settings and the Template page).