@kurtbruns/kestrel 1.20.0 → 1.22.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.
@@ -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=00bc34db">
8
+ <link rel="stylesheet" href="./styles.css?v=102ce128">
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
@@ -116,6 +116,6 @@
116
116
  </section>
117
117
  <div id="toasts" role="status" aria-live="polite"></div>
118
118
 
119
- <script src="./app.js?v=0af2b352"></script>
119
+ <script src="./app.js?v=867db6b1"></script>
120
120
  </body>
121
121
  </html>
@@ -2,7 +2,13 @@
2
2
  prefers-color-scheme (see the dark block below). This is the admin surface's own
3
3
  palette — independent of, and never loaded alongside, the reader/email-card
4
4
  `TOKENS` in src/lib/page.ts. The two reuse names like --muted and --bg with
5
- different values on purpose; don't assume they share meaning. */
5
+ different values on purpose; don't assume they share meaning.
6
+
7
+ Specificity, not source order, decides this file's cascade, so rules stay grouped by
8
+ component. That is why Biome's noDescendingSpecificity is off in biome.json, which takes no
9
+ comments: it compares selectors by their last element alone, so `#settingsBtn svg` outranks
10
+ every later `svg` rule, components that never nest included, and satisfying it would mean
11
+ moving rules out of their sections, changing the cascade to fix nothing. */
6
12
  :root {
7
13
  color-scheme: light dark;
8
14
  /* Layering — one named z-index ladder (DESIGN §9). Every fixed or floating surface
@@ -1197,7 +1203,6 @@ body.tool-mode .content {
1197
1203
  }
1198
1204
  }
1199
1205
 
1200
- /* biome-ignore lint/style/noDescendingSpecificity: specificity (not source order) decides the cascade; base rule grouped here intentionally */
1201
1206
  button {
1202
1207
  font-family: inherit;
1203
1208
  font-size: 14px;
@@ -1524,7 +1529,6 @@ td a {
1524
1529
  text-decoration: none;
1525
1530
  font-weight: 500;
1526
1531
  }
1527
- /* biome-ignore lint/style/noDescendingSpecificity: specificity (not source order) decides the cascade; hover rule grouped with its base */
1528
1532
  td a:hover {
1529
1533
  text-decoration: underline;
1530
1534
  }
@@ -1942,6 +1946,24 @@ th.num .th-sort {
1942
1946
  cursor: default;
1943
1947
  }
1944
1948
 
1949
+ /* A labelled block (a code block, or the API reference's query and content-type tables):
1950
+ its label line, then the block. Ahead of the sections below, which refine it. */
1951
+ .block {
1952
+ margin-top: 10px;
1953
+ }
1954
+ .block-head {
1955
+ display: flex;
1956
+ align-items: center;
1957
+ justify-content: space-between;
1958
+ margin: 8px 0 4px;
1959
+ }
1960
+ .block-label {
1961
+ display: block;
1962
+ font-size: 12px;
1963
+ font-weight: 600;
1964
+ color: var(--muted);
1965
+ }
1966
+
1945
1967
  /* A route's or an object's sections (query, body, returns, refusals, fields) as Stripe's and
1946
1968
  Mux's references set theirs: a heading over a rule, then unboxed rows, each with the name,
1947
1969
  its type, and Required on one line and what it means under it, so a long name never
@@ -4148,6 +4170,11 @@ button.api-filter-clear svg {
4148
4170
  max-width: 1440px;
4149
4171
  line-height: 1.5;
4150
4172
  }
4173
+ /* A resource: its heading over a rule, scrolling with its routes. */
4174
+ .api-res {
4175
+ margin-top: 16px;
4176
+ scroll-margin-top: var(--bar-h);
4177
+ }
4151
4178
  /* One reading measure for the page, a doc page's (720px): the intro, the topics, and each
4152
4179
  route's and object's prose and tables keep it, and only a route's examples, beside its
4153
4180
  prose on a wide screen, reach past it. */
@@ -4192,11 +4219,6 @@ button.api-filter-clear svg {
4192
4219
  margin: 0 0 10px;
4193
4220
  font-size: 14px;
4194
4221
  }
4195
- /* A resource: its heading over a rule, scrolling with its routes. */
4196
- .api-res {
4197
- margin-top: 16px;
4198
- scroll-margin-top: var(--bar-h);
4199
- }
4200
4222
  /* A resource's heading, set like the page's other headings, flush with the text: it only
4201
4223
  pins under the bar, its vertical padding the band the rows scroll beneath. */
4202
4224
  .api-res-head {
@@ -4314,24 +4336,33 @@ button.api-filter-clear svg {
4314
4336
  outline: none;
4315
4337
  }
4316
4338
  /* A heading is a link to its own address, in the heading's own ink, with a link icon in the
4317
- margin to its left while it is hovered or focused, as GitHub's headings show theirs. */
4318
- a.api-self {
4339
+ margin to its left while it is hovered or focused, as GitHub's headings show theirs. The
4340
+ API reference's headings and the Settings sections' titles wear the same. The icon's padding
4341
+ runs its box up to the text, so moving between the two never leaves the link: no gap for the
4342
+ cursor to change in or the icon to vanish in. */
4343
+ a.api-self,
4344
+ a.set-sec-link {
4319
4345
  position: relative;
4320
4346
  color: inherit;
4321
4347
  text-decoration: none;
4322
4348
  }
4323
- a.api-self svg {
4349
+ a.api-self svg,
4350
+ a.set-sec-link svg {
4324
4351
  position: absolute;
4325
4352
  left: -20px;
4326
4353
  top: 50%;
4327
- width: 14px;
4354
+ box-sizing: border-box;
4355
+ width: 20px;
4328
4356
  height: 14px;
4357
+ padding-right: 6px;
4329
4358
  transform: translateY(-50%);
4330
- color: var(--muted);
4359
+ fill: var(--muted);
4331
4360
  opacity: 0;
4332
4361
  }
4333
4362
  a.api-self:hover svg,
4334
- a.api-self:focus-visible svg {
4363
+ a.api-self:focus-visible svg,
4364
+ a.set-sec-link:hover svg,
4365
+ a.set-sec-link:focus-visible svg {
4335
4366
  opacity: 1;
4336
4367
  }
4337
4368
  .api-op-call {
@@ -4992,20 +5023,10 @@ button.copy-btn.api-copy-path {
4992
5023
  }
4993
5024
 
4994
5025
  /* --- copying text out of the app (DESIGN §7, ui/copy.ts) ---
4995
- A labelled block (a code block, or the API reference's query and content-type tables):
4996
- its label line, then the block. The Copy is a control outside the five roles: the word
4997
- and its glyph, no fill or border, quiet on the line it shares; the glyph turns to a
4998
- check in the ok hue for a moment once a copy has landed, and the word stays. A Copy
4999
- beside a single value (an address) is the same control. --- */
5000
- .block {
5001
- margin-top: 10px;
5002
- }
5003
- .block-head {
5004
- display: flex;
5005
- align-items: center;
5006
- justify-content: space-between;
5007
- margin: 8px 0 4px;
5008
- }
5026
+ The Copy is a control outside the five roles: the word and its glyph, no fill or border,
5027
+ quiet on the line it shares; the glyph turns to a check in the ok hue for a moment once a
5028
+ copy has landed, and the word stays. A Copy beside a single value (an address) is the same
5029
+ control. --- */
5009
5030
  button.copy-btn {
5010
5031
  display: inline-flex;
5011
5032
  align-items: center;
@@ -5037,12 +5058,6 @@ button.copy-btn svg {
5037
5058
  button.copied svg {
5038
5059
  color: var(--ok-fg);
5039
5060
  }
5040
- .block-label {
5041
- display: block;
5042
- font-size: 12px;
5043
- font-weight: 600;
5044
- color: var(--muted);
5045
- }
5046
5061
  /* A code block (an example, a curl command, a template): a raised card with its label and Copy
5047
5062
  in a header inside it, so the code stands apart from the page in both themes. */
5048
5063
  .code-block {
@@ -5122,6 +5137,20 @@ button.copied svg {
5122
5137
  align-items: center;
5123
5138
  gap: 12px;
5124
5139
  margin: 0 0 14px;
5140
+ scroll-margin-top: 20px; /* a link to the section leaves its title room at the top */
5141
+ }
5142
+ @media (max-width: 539px) {
5143
+ /* The phone's floating menu button, 52px tall from the top, would cover the title. */
5144
+ .set-sec-head {
5145
+ scroll-margin-top: 60px;
5146
+ }
5147
+ /* The page's gutter here is narrower than the link icon's place in the margin. */
5148
+ a.set-sec-link svg {
5149
+ left: -15px;
5150
+ width: 15px;
5151
+ height: 12px;
5152
+ padding-right: 3px;
5153
+ }
5125
5154
  }
5126
5155
  .set-sec-title {
5127
5156
  margin: 0;
@@ -5234,6 +5263,7 @@ button.copied svg {
5234
5263
  min-width: 0;
5235
5264
  }
5236
5265
  .set-logo-tile {
5266
+ position: relative;
5237
5267
  width: 100%;
5238
5268
  aspect-ratio: 1;
5239
5269
  border-radius: 10px;
@@ -5253,6 +5283,22 @@ button.copied svg {
5253
5283
  .set-logo-tile:hover {
5254
5284
  border-color: var(--accent);
5255
5285
  }
5286
+ .set-logo-tile.dragover::after {
5287
+ content: "Drop to upload";
5288
+ position: absolute;
5289
+ inset: 0;
5290
+ display: flex;
5291
+ align-items: center;
5292
+ justify-content: center;
5293
+ padding: 8px;
5294
+ border: 2px dashed var(--ring);
5295
+ border-radius: inherit;
5296
+ background: color-mix(in srgb, var(--ring) 14%, var(--card));
5297
+ color: var(--fg);
5298
+ font-size: 11px;
5299
+ font-weight: 600;
5300
+ text-align: center;
5301
+ }
5256
5302
  .set-logo-ph {
5257
5303
  display: flex;
5258
5304
  flex-direction: column;
@@ -5526,29 +5572,38 @@ button.set-field-remove {
5526
5572
  margin-top: 5px;
5527
5573
  }
5528
5574
 
5529
- /* --- confirmation email: the rendered card + the words-only edit body ---
5530
- The card colors are LITERALS matching src/emails/system.ts, which now ships light+dark
5531
- (its `prefers-color-scheme` block). The light literals are here; a dark @media block
5532
- below overrides them with the email's dark palette, so the preview tracks the dashboard
5533
- theme and stays byte-honest about what a light- or dark-mode inbox receives. */
5575
+ /* --- confirmation email: the rendered preview + the words-only edit body ---
5576
+ The stage and column copy the email's frame in src/emails/system.ts, and their colors are
5577
+ LITERALS matching its light and dark palettes (its `prefers-color-scheme` block). The
5578
+ light literals are here; a dark @media block below overrides them, so the preview tracks
5579
+ the dashboard theme and shows what a light- or dark-mode inbox receives. */
5580
+ /* The top border is the app's, not the email's: it keeps the inbox row above from reading as
5581
+ part of an email whose page is the same white. */
5534
5582
  .set-ce-stage {
5535
- background: #f4f4f5;
5536
- padding: 22px 18px;
5583
+ border-top: 1px solid var(--line);
5584
+ background: #ffffff;
5585
+ padding: 8px 12px 48px;
5537
5586
  }
5538
- .set-ce-card {
5539
- max-width: 440px;
5587
+ .set-ce-col {
5588
+ max-width: 664px;
5540
5589
  margin: 0 auto;
5541
- background: #ffffff;
5542
- border-radius: 8px;
5543
- padding: 28px 26px;
5544
- font-size: 15px;
5590
+ padding: 24px 32px 32px;
5591
+ font-size: 16px;
5545
5592
  line-height: 1.6;
5546
5593
  color: #18181b;
5594
+ word-break: break-word;
5595
+ }
5596
+ /* The email's narrow rule, at its width, so a phone sees what its inbox will show. */
5597
+ @media (max-width: 560px) {
5598
+ .set-ce-col {
5599
+ padding-left: 12px;
5600
+ padding-right: 12px;
5601
+ }
5547
5602
  }
5548
5603
  .set-ce-msg {
5549
5604
  margin: 0 0 20px;
5550
5605
  }
5551
- /* Publication masthead inside the preview card, matching masthead() in
5606
+ /* Publication masthead inside the preview, matching masthead() in
5552
5607
  src/emails/system.ts (logo beside name + tagline, over a hairline rule). Light
5553
5608
  literals here; the dark @media block below carries the dark overrides. */
5554
5609
  .set-ce-mast {
@@ -5638,8 +5693,6 @@ button.set-field-remove {
5638
5693
  color: #ffffff;
5639
5694
  border-radius: 6px;
5640
5695
  text-decoration: none;
5641
- font-size: 15px;
5642
- font-weight: 500;
5643
5696
  }
5644
5697
  .set-ce-foot {
5645
5698
  color: #71717a;
@@ -5660,17 +5713,15 @@ button.set-field-remove {
5660
5713
  .set-ce-editfoot .grow {
5661
5714
  flex: 1;
5662
5715
  }
5663
- /* Dark rendering of the preview card — the email's dark palette (the reader card pages'
5664
- tokens: page #18181b, card #232327, ink #ededed, muted #a1a1aa, rule #2e2e33, accent
5665
- button #e5e7eb/#18181b), matching the `prefers-color-scheme` block in
5666
- src/emails/system.ts. Overrides only the card literals; the edit body + chrome around
5667
- it already theme via the --* tokens. */
5716
+ /* Dark rendering of the preview: the email's dark palette (page #18181b, ink #ededed,
5717
+ muted #a1a1aa, rule #2e2e33, accent button #e5e7eb/#18181b), matching the
5718
+ `prefers-color-scheme` block in src/emails/system.ts. Overrides only the preview's
5719
+ literals; the edit body + chrome around it already theme via the --* tokens. */
5668
5720
  @media (prefers-color-scheme: dark) {
5669
5721
  .set-ce-stage {
5670
5722
  background: #18181b;
5671
5723
  }
5672
- .set-ce-card {
5673
- background: #232327;
5724
+ .set-ce-col {
5674
5725
  color: #ededed;
5675
5726
  }
5676
5727
  .set-ce-mast {
@@ -6218,9 +6269,8 @@ button.set-embed-copy svg {
6218
6269
  here — only safe container defaults. */
6219
6270
  /* The sample email renders in an isolated <iframe> — so the template's own <style>
6220
6271
  block applies exactly as it would in a mail client and can't leak into the
6221
- dashboard, and the reading measure is constrained to the email column
6222
- (~640px, matching the view-in-browser render). Its height is sized to content
6223
- from script. */
6272
+ dashboard, and the document inside draws the email's page and column. Its height
6273
+ is sized to content from script. */
6224
6274
  .set-email-frame {
6225
6275
  display: block;
6226
6276
  width: 100%;
@@ -6230,19 +6280,42 @@ button.set-embed-copy svg {
6230
6280
  }
6231
6281
 
6232
6282
  /* --- Template page: sample-email header actions, dropdown, variables --- */
6283
+ /* The bar stays one row at every width: the titles wrap beside the controls, and the toggle
6284
+ gives way in the two steps below, so narrowing the pane changes the layout only there. */
6285
+ .set-tpl-sample {
6286
+ container-type: inline-size;
6287
+ }
6288
+ /* A tablet's pane: the toggle keeps its use, since Phone still looks different, but only its
6289
+ icons, so the bar stays one row. */
6290
+ @container (max-width: 640px) {
6291
+ .set-tpl-sample .wtog-lbl {
6292
+ display: none;
6293
+ }
6294
+ }
6295
+ /* A pane this narrow is close to a phone's width already, so Desktop and Phone would look
6296
+ alike, and the toggle goes. */
6297
+ @container (max-width: 480px) {
6298
+ .set-tpl-sample .set-wtog {
6299
+ display: none;
6300
+ }
6301
+ .set-email-stage .set-email-frame.phone {
6302
+ max-width: 100%;
6303
+ }
6304
+ }
6233
6305
  .set-preview-titles {
6234
6306
  display: flex;
6307
+ flex: 1 1 0;
6235
6308
  flex-direction: column;
6236
6309
  gap: 1px;
6237
6310
  min-width: 0;
6238
6311
  }
6239
6312
  .set-preview-actions {
6240
6313
  display: flex;
6314
+ flex: 0 0 auto;
6241
6315
  align-items: center;
6242
6316
  gap: 8px;
6243
- flex-wrap: wrap;
6244
6317
  }
6245
- /* Preview width toggle (640 / 375) — proof both inbox measures; 640 is the default. */
6318
+ /* Preview width toggle — proof both inbox measures; Desktop is the default. */
6246
6319
  .set-wtog {
6247
6320
  display: inline-flex;
6248
6321
  gap: 2px;
@@ -6344,25 +6417,27 @@ button.set-embed-copy svg {
6344
6417
  .set-req-pill.bad .dot {
6345
6418
  background: var(--danger-fg);
6346
6419
  }
6347
- /* The sample email sits on a sunken stage so the (width-capped) email card reads as a
6348
- message on a surface; the width toggle sets its max-width. */
6420
+ /* The sample email fills its pane, as a post's Preview does, since an email arrives as a page
6421
+ and not as a card on a surface. So the stage shows only beside the width toggle's Phone, where
6422
+ its color says the frame ends there. --chip, since --sunken is all but the email's own page
6423
+ in dark mode. */
6349
6424
  .set-email-stage {
6350
6425
  display: flex;
6351
6426
  justify-content: center;
6352
- padding: 16px;
6353
- background: var(--sunken);
6427
+ background: var(--chip);
6354
6428
  }
6355
6429
  .set-email-stage .set-email-frame {
6356
6430
  width: 100%;
6357
- max-width: 640px;
6431
+ max-width: 100%;
6358
6432
  /* Floor the auto-sized frame at the default template's height, so emptying or
6359
6433
  shortening the template doesn't collapse the preview and yank the page scroll
6360
- (the frame can still grow taller for a longer template or the 375 measure). */
6361
- min-height: 392px;
6362
- border-radius: 6px;
6363
- box-shadow: 0 1px 4px rgba(15, 18, 25, 0.1);
6434
+ (the frame can still grow taller for a longer template or the Phone measure). */
6435
+ min-height: 452px;
6364
6436
  transition: max-width 0.16s ease;
6365
6437
  }
6438
+ .set-email-stage .set-email-frame.phone {
6439
+ max-width: 375px;
6440
+ }
6366
6441
  /* "Start from example": the trigger of a shared menu (openMenu). */
6367
6442
  .set-menu-btn {
6368
6443
  display: inline-flex;
@@ -6574,12 +6649,14 @@ button.set-embed-copy svg {
6574
6649
  .set-rp .set-wtog {
6575
6650
  flex: 0 0 auto;
6576
6651
  }
6577
- .set-rp .wtog-btn {
6652
+ .set-rp .wtog-btn,
6653
+ .set-tpl-sample .wtog-btn {
6578
6654
  display: inline-flex;
6579
6655
  align-items: center;
6580
6656
  gap: 5px;
6581
6657
  }
6582
- .set-rp .wtog-btn svg {
6658
+ .set-rp .wtog-btn svg,
6659
+ .set-tpl-sample .wtog-btn svg {
6583
6660
  width: 13px;
6584
6661
  height: 13px;
6585
6662
  }