@kurtbruns/kestrel 1.12.0 → 1.13.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 +103 -1
- package/cli/dev-api.mjs +1 -1
- package/cli/seed.mjs +1 -1
- package/dist/public/dashboard/app.js +341 -291
- package/dist/public/dashboard/app.js.map +4 -4
- package/dist/public/dashboard/index.html +2 -2
- package/dist/public/dashboard/styles.css +395 -116
- package/dist/worker/index.js +8722 -5392
- package/docs/get-started/02-deploy.md +1 -1
- package/docs/get-started/03-access.md +3 -3
- package/docs/get-started/04-ses.md +2 -2
- package/docs/get-started/05-verify.md +4 -4
- package/docs/get-started/06-go-live.md +1 -1
- package/docs/guides/01-connect-claude.md +7 -4
- package/docs/guides/09-as-your-list-grows.md +6 -2
- package/docs/publishing/01-your-list.md +35 -30
- package/docs/publishing/02-sending-rules.md +3 -3
- package/docs/publishing/04-write-a-post.md +2 -2
- package/docs/publishing/06-troubleshooting.md +10 -5
- package/docs/publishing/07-working-with-claude.md +1 -2
- package/docs/publishing/09-share-your-subscribe-link.md +2 -2
- package/docs/publishing/10-check-your-setup.md +2 -2
- package/docs/publishing/11-email-template.md +2 -0
- package/docs/reference/01-configuration.md +2 -2
- package/migrations/0012_unsubscribe_cause.sql +139 -0
- package/package.json +1 -1
- package/template/claude/CLAUDE.md +11 -10
- package/template/claude/skills/migrate-list/SKILL.md +5 -4
|
@@ -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=7f1a9a46">
|
|
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=1aa2dbe5"></script>
|
|
117
117
|
</body>
|
|
118
118
|
</html>
|
|
@@ -1854,8 +1854,10 @@ td a:hover {
|
|
|
1854
1854
|
.dash-drafts {
|
|
1855
1855
|
table-layout: fixed;
|
|
1856
1856
|
}
|
|
1857
|
+
/* Wide enough for the longest "edited <date>", a two-digit day and hour included, with the
|
|
1858
|
+
cell's padding: the fixed layout clips anything wider rather than growing the column. */
|
|
1857
1859
|
.dash-drafts td.edited {
|
|
1858
|
-
width:
|
|
1860
|
+
width: 15em;
|
|
1859
1861
|
white-space: nowrap;
|
|
1860
1862
|
}
|
|
1861
1863
|
@media (min-width: 721px) {
|
|
@@ -1867,13 +1869,15 @@ td a:hover {
|
|
|
1867
1869
|
.list-table .c-act {
|
|
1868
1870
|
width: 56px;
|
|
1869
1871
|
}
|
|
1870
|
-
/* Subscribers:
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
width: 152px;
|
|
1874
|
-
}
|
|
1872
|
+
/* Subscribers: a floor that keeps the email column about 250px beside its two others. With
|
|
1873
|
+
only Status and Joined beside an address, which rarely needs more than half the row, fixed
|
|
1874
|
+
widths left one wide gap before them; a share of the row each spreads the space evenly. */
|
|
1875
1875
|
.list-table.subs-table {
|
|
1876
|
-
min-width:
|
|
1876
|
+
min-width: 610px;
|
|
1877
|
+
}
|
|
1878
|
+
.list-table.subs-table .c-status,
|
|
1879
|
+
.list-table.subs-table .c-date {
|
|
1880
|
+
width: 24%;
|
|
1877
1881
|
}
|
|
1878
1882
|
|
|
1879
1883
|
.dash-section table {
|
|
@@ -1934,20 +1938,143 @@ th.num .th-sort {
|
|
|
1934
1938
|
cursor: default;
|
|
1935
1939
|
}
|
|
1936
1940
|
|
|
1937
|
-
/*
|
|
1938
|
-
|
|
1941
|
+
/* A route's or an object's sections (query, body, returns, refusals, fields) as Stripe's and
|
|
1942
|
+
Mux's references set theirs: a heading over a rule, then unboxed rows, each with the name,
|
|
1943
|
+
its type, and Required on one line and what it means under it, so a long name never
|
|
1944
|
+
squeezes its description. A name breaks only where its words join (the <wbr> the page puts
|
|
1945
|
+
after each _ . /). */
|
|
1946
|
+
.block.api-sec {
|
|
1947
|
+
margin-top: 22px;
|
|
1948
|
+
}
|
|
1949
|
+
.api-sec > .block-head {
|
|
1939
1950
|
margin: 0;
|
|
1951
|
+
padding-bottom: 8px;
|
|
1952
|
+
border-bottom: 1px solid var(--line-soft);
|
|
1940
1953
|
}
|
|
1941
|
-
.api-
|
|
1942
|
-
|
|
1954
|
+
.api-sec > .block-head .block-label {
|
|
1955
|
+
font-size: 15px;
|
|
1956
|
+
font-weight: 600;
|
|
1957
|
+
color: var(--fg);
|
|
1958
|
+
}
|
|
1959
|
+
.api-sec-meta {
|
|
1960
|
+
margin-left: 2px;
|
|
1961
|
+
font: 400 12px var(--mono);
|
|
1962
|
+
color: var(--muted);
|
|
1963
|
+
}
|
|
1964
|
+
/* Rules divide rows; the last row in a section has none under it. */
|
|
1965
|
+
.api-row,
|
|
1966
|
+
.api-lrow {
|
|
1967
|
+
padding: 11px 0;
|
|
1968
|
+
border-bottom: 1px solid var(--line);
|
|
1969
|
+
}
|
|
1970
|
+
.api-row:last-child,
|
|
1971
|
+
.api-lrow:last-child {
|
|
1972
|
+
border-bottom: 0;
|
|
1973
|
+
}
|
|
1974
|
+
.api-row-head {
|
|
1975
|
+
display: flex;
|
|
1976
|
+
align-items: baseline;
|
|
1977
|
+
flex-wrap: wrap;
|
|
1978
|
+
gap: 8px;
|
|
1979
|
+
}
|
|
1980
|
+
code.api-row-name {
|
|
1943
1981
|
font-size: 13px;
|
|
1944
|
-
|
|
1982
|
+
font-weight: 600;
|
|
1983
|
+
overflow-wrap: break-word;
|
|
1945
1984
|
}
|
|
1946
|
-
.api-
|
|
1947
|
-
|
|
1948
|
-
|
|
1985
|
+
.api-row-type {
|
|
1986
|
+
font: 12px var(--mono);
|
|
1987
|
+
color: var(--muted);
|
|
1988
|
+
}
|
|
1989
|
+
/* A type that names an object links to it, and reads as a link. */
|
|
1990
|
+
.api-row-type a {
|
|
1991
|
+
color: var(--fg);
|
|
1992
|
+
text-decoration: underline;
|
|
1993
|
+
text-underline-offset: 2px;
|
|
1994
|
+
}
|
|
1995
|
+
.api-row-desc {
|
|
1996
|
+
margin-top: 3px;
|
|
1997
|
+
font-size: 13.5px;
|
|
1998
|
+
}
|
|
1999
|
+
/* The refusals and the statuses are a ledger: the status in a column of its own, tinted by
|
|
2000
|
+
its class, so a reader scans by status. */
|
|
2001
|
+
.api-lrow {
|
|
2002
|
+
display: grid;
|
|
2003
|
+
grid-template-columns: 44px minmax(0, 1fr);
|
|
2004
|
+
gap: 6px;
|
|
2005
|
+
}
|
|
2006
|
+
.api-st {
|
|
2007
|
+
font: 600 12.5px / 20px var(--mono);
|
|
2008
|
+
color: var(--muted);
|
|
2009
|
+
}
|
|
2010
|
+
.api-st-4xx {
|
|
2011
|
+
color: var(--warn-fg);
|
|
2012
|
+
}
|
|
2013
|
+
.api-st-5xx {
|
|
2014
|
+
color: var(--danger-fg);
|
|
2015
|
+
}
|
|
2016
|
+
.api-st-2xx {
|
|
2017
|
+
color: var(--ok-fg);
|
|
2018
|
+
}
|
|
2019
|
+
.api-with {
|
|
2020
|
+
margin-top: 2px;
|
|
2021
|
+
font-size: 12px;
|
|
2022
|
+
color: var(--muted);
|
|
2023
|
+
}
|
|
2024
|
+
/* A status's name is words, not a code. */
|
|
2025
|
+
.api-statuses code.api-row-name {
|
|
2026
|
+
font-family: inherit;
|
|
2027
|
+
}
|
|
2028
|
+
/* A value's short cells beside its name (a condition kind's severity and fields), each led by
|
|
2029
|
+
its column's name, and the label of a later long line ("Remedy:"). */
|
|
2030
|
+
.api-row-meta {
|
|
2031
|
+
font-size: 12.5px;
|
|
2032
|
+
color: var(--muted);
|
|
2033
|
+
}
|
|
2034
|
+
.api-row-key {
|
|
2035
|
+
font-size: 12px;
|
|
2036
|
+
font-weight: 600;
|
|
2037
|
+
color: var(--muted);
|
|
2038
|
+
}
|
|
2039
|
+
/* What every route shares, described once ahead of the routes: concept prose, an object's
|
|
2040
|
+
description, an enum's values under a field's line, and the refusal table's next moves. */
|
|
2041
|
+
/* The reference's paragraphs, each with space below it, as a doc page's are. */
|
|
2042
|
+
.api-prose {
|
|
2043
|
+
margin: 0 0 10px;
|
|
2044
|
+
font-size: 14px;
|
|
2045
|
+
}
|
|
2046
|
+
/* A list in the reference's prose (the status ranges under Requests), set like its paragraphs. */
|
|
2047
|
+
.prose-list {
|
|
2048
|
+
padding-left: 20px;
|
|
2049
|
+
}
|
|
2050
|
+
.prose-list li {
|
|
2051
|
+
margin: 6px 0;
|
|
2052
|
+
}
|
|
2053
|
+
.api-values {
|
|
2054
|
+
margin: 4px 0 0;
|
|
2055
|
+
padding-left: 16px;
|
|
2056
|
+
}
|
|
2057
|
+
.api-values li {
|
|
2058
|
+
margin: 2px 0;
|
|
2059
|
+
}
|
|
2060
|
+
/* The name of a group an enum's values fall into, over its bullets. */
|
|
2061
|
+
.api-values-group {
|
|
2062
|
+
margin: 8px 0 0;
|
|
2063
|
+
font-size: 12.5px;
|
|
2064
|
+
font-weight: 600;
|
|
2065
|
+
color: var(--muted);
|
|
2066
|
+
}
|
|
2067
|
+
.api-returns {
|
|
2068
|
+
margin: 4px 0 6px;
|
|
2069
|
+
font-size: 13.5px;
|
|
2070
|
+
}
|
|
2071
|
+
/* A refusal's next move, under what it means: quieter, the step a client takes from it. */
|
|
2072
|
+
.api-move-next {
|
|
2073
|
+
display: block;
|
|
2074
|
+
margin-top: 2px;
|
|
2075
|
+
font-size: 12.5px;
|
|
2076
|
+
color: var(--muted);
|
|
1949
2077
|
}
|
|
1950
|
-
|
|
1951
2078
|
/* editor header */
|
|
1952
2079
|
.editor-head {
|
|
1953
2080
|
display: flex;
|
|
@@ -2242,7 +2369,7 @@ body.web-mode .content {
|
|
|
2242
2369
|
font-size: 22px;
|
|
2243
2370
|
letter-spacing: -0.01em;
|
|
2244
2371
|
}
|
|
2245
|
-
/* The derived-phase pill (progressing / retrying /
|
|
2372
|
+
/* The derived-phase pill (progressing / retrying / backing_off / needs_attention /
|
|
2246
2373
|
settling / …). Tones reuse the status + semantic palettes so a phase reads the same
|
|
2247
2374
|
hue as its badge elsewhere. A plain label, so the default cursor (DESIGN §11). */
|
|
2248
2375
|
.phase-pill {
|
|
@@ -4016,16 +4143,27 @@ button.api-filter-clear svg {
|
|
|
4016
4143
|
color: var(--fg);
|
|
4017
4144
|
background: var(--hover);
|
|
4018
4145
|
}
|
|
4146
|
+
/* The reference reads as a document: a doc page's line height, not the browser's default. */
|
|
4019
4147
|
.api-content {
|
|
4020
4148
|
min-width: 0;
|
|
4021
|
-
max-width:
|
|
4149
|
+
max-width: 1440px;
|
|
4150
|
+
line-height: 1.5;
|
|
4151
|
+
}
|
|
4152
|
+
/* One reading measure for the page, a doc page's (720px): the intro, the topics, and each
|
|
4153
|
+
route's and object's prose and tables keep it, and only a route's examples, beside its
|
|
4154
|
+
prose on a wide screen, reach past it. */
|
|
4155
|
+
.api-head,
|
|
4156
|
+
.api-blurb,
|
|
4157
|
+
.api-section[data-tier="overview"] .api-res,
|
|
4158
|
+
.api-op-body {
|
|
4159
|
+
max-width: 720px;
|
|
4022
4160
|
}
|
|
4023
4161
|
.api-head {
|
|
4024
4162
|
margin: 0 0 12px;
|
|
4025
4163
|
}
|
|
4026
4164
|
.api-head h1 {
|
|
4027
4165
|
margin: 0 0 4px;
|
|
4028
|
-
font-size:
|
|
4166
|
+
font-size: 28px;
|
|
4029
4167
|
}
|
|
4030
4168
|
.api-section {
|
|
4031
4169
|
scroll-margin-top: calc(var(--bar-h) + 2px);
|
|
@@ -4036,35 +4174,53 @@ button.api-filter-clear svg {
|
|
|
4036
4174
|
.api-section.api-last {
|
|
4037
4175
|
min-height: calc(100vh - var(--bar-h));
|
|
4038
4176
|
}
|
|
4177
|
+
/* A tier is a page heading here, not the app's small-caps section label that h2 is elsewhere. */
|
|
4039
4178
|
.api-section h2 {
|
|
4040
|
-
font-size:
|
|
4041
|
-
|
|
4179
|
+
font-size: 24px;
|
|
4180
|
+
font-weight: 650;
|
|
4181
|
+
color: var(--fg);
|
|
4182
|
+
text-transform: none;
|
|
4183
|
+
letter-spacing: normal;
|
|
4184
|
+
margin: 28px 0 4px;
|
|
4185
|
+
}
|
|
4186
|
+
/* Where a jump from the rail or an address moves focus: a place to read from, not a control,
|
|
4187
|
+
so it takes no ring. */
|
|
4188
|
+
.api-section h2:focus,
|
|
4189
|
+
.api-res-head h3:focus {
|
|
4190
|
+
outline: none;
|
|
4042
4191
|
}
|
|
4043
4192
|
.api-blurb {
|
|
4044
|
-
margin: 0;
|
|
4193
|
+
margin: 0 0 10px;
|
|
4045
4194
|
font-size: 14px;
|
|
4046
|
-
max-width: 62ch;
|
|
4047
4195
|
}
|
|
4048
|
-
/* A resource: its heading
|
|
4196
|
+
/* A resource: its heading over a rule, scrolling with its routes. */
|
|
4049
4197
|
.api-res {
|
|
4050
4198
|
margin-top: 16px;
|
|
4051
4199
|
scroll-margin-top: var(--bar-h);
|
|
4052
4200
|
}
|
|
4201
|
+
/* A resource's heading, set like the page's other headings, flush with the text: it only
|
|
4202
|
+
pins under the bar, its vertical padding the band the rows scroll beneath. */
|
|
4053
4203
|
.api-res-head {
|
|
4054
|
-
position: sticky;
|
|
4055
|
-
top: var(--bar-h);
|
|
4056
|
-
z-index: var(--z-content);
|
|
4057
4204
|
display: flex;
|
|
4058
4205
|
align-items: center;
|
|
4059
4206
|
gap: 8px;
|
|
4060
|
-
|
|
4207
|
+
margin: 0;
|
|
4208
|
+
padding: 6px 0;
|
|
4209
|
+
border-bottom: 1px solid var(--line-soft);
|
|
4061
4210
|
background: var(--bg);
|
|
4062
|
-
border-radius: 6px;
|
|
4063
4211
|
}
|
|
4064
4212
|
|
|
4065
4213
|
.api-res-head h3 {
|
|
4066
4214
|
margin: 0;
|
|
4067
|
-
font-size:
|
|
4215
|
+
font-size: 20px;
|
|
4216
|
+
font-weight: 650;
|
|
4217
|
+
}
|
|
4218
|
+
/* A Getting started topic is a heading over prose, not a list's head: no rule, a step smaller. */
|
|
4219
|
+
.api-section[data-tier="overview"] .api-res-head {
|
|
4220
|
+
border-bottom: none;
|
|
4221
|
+
}
|
|
4222
|
+
.api-section[data-tier="overview"] .api-res-head h3 {
|
|
4223
|
+
font-size: 18px;
|
|
4068
4224
|
}
|
|
4069
4225
|
.api-rows {
|
|
4070
4226
|
list-style: none;
|
|
@@ -4073,46 +4229,8 @@ button.api-filter-clear svg {
|
|
|
4073
4229
|
}
|
|
4074
4230
|
.api-rows > li {
|
|
4075
4231
|
border-bottom: 1px solid var(--line);
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
place to the full documentation. */
|
|
4079
|
-
.api-route-sum {
|
|
4080
|
-
display: grid;
|
|
4081
|
-
grid-template-columns: 62px minmax(0, max-content) minmax(0, 1fr) 20px;
|
|
4082
|
-
gap: 14px;
|
|
4083
|
-
align-items: center;
|
|
4084
|
-
padding: 9px 6px;
|
|
4085
|
-
border-radius: 6px;
|
|
4086
|
-
list-style: none;
|
|
4087
|
-
cursor: pointer;
|
|
4088
|
-
}
|
|
4089
|
-
.api-route-sum::-webkit-details-marker {
|
|
4090
|
-
display: none;
|
|
4091
|
-
}
|
|
4092
|
-
.api-route-sum:hover {
|
|
4093
|
-
background: var(--hover);
|
|
4094
|
-
}
|
|
4095
|
-
.api-route-line {
|
|
4096
|
-
color: var(--muted);
|
|
4097
|
-
font-size: 13.5px;
|
|
4098
|
-
white-space: nowrap;
|
|
4099
|
-
overflow: hidden;
|
|
4100
|
-
text-overflow: ellipsis;
|
|
4101
|
-
}
|
|
4102
|
-
.api-route[open] .api-route-line {
|
|
4103
|
-
visibility: hidden;
|
|
4104
|
-
}
|
|
4105
|
-
.api-chev {
|
|
4106
|
-
width: 6px;
|
|
4107
|
-
height: 6px;
|
|
4108
|
-
border-right: 1.6px solid var(--muted);
|
|
4109
|
-
border-bottom: 1.6px solid var(--muted);
|
|
4110
|
-
justify-self: center;
|
|
4111
|
-
transform: rotate(-45deg);
|
|
4112
|
-
transition: transform 0.15s;
|
|
4113
|
-
}
|
|
4114
|
-
.api-route[open] .api-chev {
|
|
4115
|
-
transform: rotate(45deg);
|
|
4232
|
+
/* A route reached from its address lands just under the bar, the rule above it hidden. */
|
|
4233
|
+
scroll-margin-top: calc(var(--bar-h) - 1px);
|
|
4116
4234
|
}
|
|
4117
4235
|
/* The verb as a tinted label: its own --method-* color as text over a wash of itself. */
|
|
4118
4236
|
.api-method {
|
|
@@ -4136,6 +4254,13 @@ button.api-filter-clear svg {
|
|
|
4136
4254
|
.api-method.m-DELETE {
|
|
4137
4255
|
--m: var(--method-delete);
|
|
4138
4256
|
}
|
|
4257
|
+
/* An object heads its resource as a row of its own, marked in the verb's column. */
|
|
4258
|
+
.api-method.m-OBJECT {
|
|
4259
|
+
text-transform: uppercase;
|
|
4260
|
+
color: var(--muted);
|
|
4261
|
+
background: var(--card);
|
|
4262
|
+
box-shadow: inset 0 0 0 1px var(--line-soft);
|
|
4263
|
+
}
|
|
4139
4264
|
.api-path {
|
|
4140
4265
|
font: 13.5px var(--mono);
|
|
4141
4266
|
overflow-wrap: anywhere;
|
|
@@ -4146,30 +4271,128 @@ button.api-filter-clear svg {
|
|
|
4146
4271
|
border-radius: 3px;
|
|
4147
4272
|
padding: 0 2px;
|
|
4148
4273
|
}
|
|
4149
|
-
.api-route-detail {
|
|
4150
|
-
padding: 2px 6px 18px 82px;
|
|
4151
|
-
}
|
|
4152
4274
|
.api-summary {
|
|
4153
|
-
margin:
|
|
4154
|
-
font-size:
|
|
4275
|
+
margin: 0 0 10px;
|
|
4276
|
+
font-size: 15px;
|
|
4155
4277
|
}
|
|
4156
4278
|
.api-desc {
|
|
4157
|
-
margin:
|
|
4279
|
+
margin: 0 0 10px;
|
|
4158
4280
|
font-size: 14px;
|
|
4159
4281
|
}
|
|
4160
|
-
/* A
|
|
4282
|
+
/* A field or parameter the request can't do without. */
|
|
4161
4283
|
.api-req {
|
|
4162
|
-
|
|
4163
|
-
font-
|
|
4164
|
-
color: var(--
|
|
4165
|
-
border: 1px solid var(--line);
|
|
4166
|
-
border-radius: 4px;
|
|
4167
|
-
padding: 0 4px;
|
|
4284
|
+
font-size: 11.5px;
|
|
4285
|
+
font-weight: 600;
|
|
4286
|
+
color: var(--warn-fg);
|
|
4168
4287
|
}
|
|
4169
4288
|
|
|
4170
4289
|
.api-empty {
|
|
4171
4290
|
margin: 20px 0;
|
|
4172
4291
|
}
|
|
4292
|
+
/* An object, first under the resource whose routes return it: a row like a route's, that
|
|
4293
|
+
opens to its fields and example. */
|
|
4294
|
+
/* A route or an object, laid out in full as Stripe and Mux lay theirs: its name and tools,
|
|
4295
|
+
its method and path, then what it does beside its examples on a wide screen, under them
|
|
4296
|
+
on a narrow one. The examples scroll with the page: pinned, one taller than the window
|
|
4297
|
+
(the send view's) would never show its end. */
|
|
4298
|
+
/* Each route and object its own block, with room around it so the page reads as one at a time. */
|
|
4299
|
+
.api-op {
|
|
4300
|
+
padding: 36px 0 40px;
|
|
4301
|
+
}
|
|
4302
|
+
.api-op-head {
|
|
4303
|
+
display: flex;
|
|
4304
|
+
flex-wrap: wrap;
|
|
4305
|
+
align-items: baseline;
|
|
4306
|
+
justify-content: space-between;
|
|
4307
|
+
gap: 8px 16px;
|
|
4308
|
+
}
|
|
4309
|
+
.api-op-head h4 {
|
|
4310
|
+
margin: 0;
|
|
4311
|
+
font-size: 18px;
|
|
4312
|
+
font-weight: 650;
|
|
4313
|
+
}
|
|
4314
|
+
.api-op-head h4:focus {
|
|
4315
|
+
outline: none;
|
|
4316
|
+
}
|
|
4317
|
+
/* A heading is a link to its own address, in the heading's own ink, with a link icon in the
|
|
4318
|
+
margin to its left while it is hovered or focused, as GitHub's headings show theirs. */
|
|
4319
|
+
a.api-self {
|
|
4320
|
+
position: relative;
|
|
4321
|
+
color: inherit;
|
|
4322
|
+
text-decoration: none;
|
|
4323
|
+
}
|
|
4324
|
+
a.api-self svg {
|
|
4325
|
+
position: absolute;
|
|
4326
|
+
left: -20px;
|
|
4327
|
+
top: 50%;
|
|
4328
|
+
width: 14px;
|
|
4329
|
+
height: 14px;
|
|
4330
|
+
transform: translateY(-50%);
|
|
4331
|
+
color: var(--muted);
|
|
4332
|
+
opacity: 0;
|
|
4333
|
+
}
|
|
4334
|
+
a.api-self:hover svg,
|
|
4335
|
+
a.api-self:focus-visible svg {
|
|
4336
|
+
opacity: 1;
|
|
4337
|
+
}
|
|
4338
|
+
.api-op-call {
|
|
4339
|
+
display: flex;
|
|
4340
|
+
align-items: center;
|
|
4341
|
+
gap: 8px;
|
|
4342
|
+
margin: 6px 0 10px;
|
|
4343
|
+
}
|
|
4344
|
+
.api-op-call .api-method {
|
|
4345
|
+
padding: 0 6px;
|
|
4346
|
+
}
|
|
4347
|
+
/* Copy sits right after the path it copies, an icon alone, so the two read as one. */
|
|
4348
|
+
button.copy-btn.api-copy-path {
|
|
4349
|
+
padding: 3px 5px;
|
|
4350
|
+
}
|
|
4351
|
+
button.copy-btn.api-copy-path span:empty {
|
|
4352
|
+
display: none;
|
|
4353
|
+
}
|
|
4354
|
+
.api-op-body {
|
|
4355
|
+
display: grid;
|
|
4356
|
+
gap: 4px 28px;
|
|
4357
|
+
}
|
|
4358
|
+
.api-op-text,
|
|
4359
|
+
.api-op-code {
|
|
4360
|
+
min-width: 0;
|
|
4361
|
+
}
|
|
4362
|
+
@media (min-width: 1280px) {
|
|
4363
|
+
.api-op-body {
|
|
4364
|
+
max-width: none;
|
|
4365
|
+
/* The examples take what the window has beyond the prose's measure, never under 360px. */
|
|
4366
|
+
grid-template-columns: minmax(0, 720px) minmax(360px, 1fr);
|
|
4367
|
+
}
|
|
4368
|
+
.api-op-code {
|
|
4369
|
+
align-self: start;
|
|
4370
|
+
}
|
|
4371
|
+
.api-op-code > .block:first-child {
|
|
4372
|
+
margin-top: 0;
|
|
4373
|
+
}
|
|
4374
|
+
}
|
|
4375
|
+
/* The rail lists every route by name under its resource, so it reads as the page's contents. */
|
|
4376
|
+
.api-nav-ops {
|
|
4377
|
+
display: none;
|
|
4378
|
+
}
|
|
4379
|
+
.api-nav-ops.open,
|
|
4380
|
+
.api-nav.filtering .api-nav-ops {
|
|
4381
|
+
display: flex;
|
|
4382
|
+
flex-direction: column;
|
|
4383
|
+
}
|
|
4384
|
+
.api-nav a.api-nav-op {
|
|
4385
|
+
padding-left: 30px;
|
|
4386
|
+
font-size: 13px;
|
|
4387
|
+
}
|
|
4388
|
+
.api-nav-tier + .api-nav-ops a.api-nav-op {
|
|
4389
|
+
padding-left: 20px;
|
|
4390
|
+
}
|
|
4391
|
+
.api-object {
|
|
4392
|
+
border-bottom: 1px solid var(--line);
|
|
4393
|
+
/* Reached from a route's Returns, it lands just under the bar. */
|
|
4394
|
+
scroll-margin-top: calc(var(--bar-h) - 1px);
|
|
4395
|
+
}
|
|
4173
4396
|
/* Reference room on mobile. The bar stays ONE row at --bar-h, the same as on desktop, so
|
|
4174
4397
|
the in-page anchors (scroll-margin-top) and the scroll-spy line keep clearing it: the back
|
|
4175
4398
|
link is a --bar-h square holding its arrow, the wordmark and the two tabs follow, and the
|
|
@@ -4284,11 +4507,13 @@ button.api-filter-clear svg {
|
|
|
4284
4507
|
font-size: 13px;
|
|
4285
4508
|
}
|
|
4286
4509
|
/* The API rail is the tier chips alone on a phone: its resources need the column the
|
|
4287
|
-
rail no longer has.
|
|
4510
|
+
rail no longer has. */
|
|
4288
4511
|
.api-filter {
|
|
4289
4512
|
max-width: none;
|
|
4290
4513
|
}
|
|
4291
|
-
.api-nav a.api-nav-res
|
|
4514
|
+
.api-nav a.api-nav-res,
|
|
4515
|
+
.api-nav .api-nav-ops,
|
|
4516
|
+
.api-nav.filtering .api-nav-ops {
|
|
4292
4517
|
display: none;
|
|
4293
4518
|
}
|
|
4294
4519
|
.api-nav a.api-nav-tier {
|
|
@@ -4297,25 +4522,6 @@ button.api-filter-clear svg {
|
|
|
4297
4522
|
.api-nav a.api-nav-tier ~ a.api-nav-tier {
|
|
4298
4523
|
margin-top: 0;
|
|
4299
4524
|
}
|
|
4300
|
-
.api-route-sum {
|
|
4301
|
-
grid-template-columns: 62px minmax(0, 1fr) 20px;
|
|
4302
|
-
gap: 4px 10px;
|
|
4303
|
-
}
|
|
4304
|
-
.api-route-line {
|
|
4305
|
-
grid-column: 2;
|
|
4306
|
-
grid-row: 2;
|
|
4307
|
-
white-space: normal;
|
|
4308
|
-
}
|
|
4309
|
-
.api-chev {
|
|
4310
|
-
grid-column: 3;
|
|
4311
|
-
grid-row: 1;
|
|
4312
|
-
}
|
|
4313
|
-
.api-route[open] .api-route-line {
|
|
4314
|
-
display: none;
|
|
4315
|
-
}
|
|
4316
|
-
.api-route-detail {
|
|
4317
|
-
padding: 0 4px 14px;
|
|
4318
|
-
}
|
|
4319
4525
|
/* inline-flex centres the chip's text in its 32px; the gap stands in for the space
|
|
4320
4526
|
between the label and its ↗ that flex drops. */
|
|
4321
4527
|
.rail-link {
|
|
@@ -4734,12 +4940,22 @@ button.copied svg {
|
|
|
4734
4940
|
font-weight: 600;
|
|
4735
4941
|
color: var(--muted);
|
|
4736
4942
|
}
|
|
4737
|
-
|
|
4943
|
+
/* A code block (an example, a curl command, a template): a raised card with its label and Copy
|
|
4944
|
+
in a header inside it, so the code stands apart from the page in both themes. */
|
|
4945
|
+
.code-block {
|
|
4946
|
+
background: var(--card);
|
|
4947
|
+
border: 1px solid var(--line);
|
|
4948
|
+
border-radius: 8px;
|
|
4949
|
+
overflow: hidden;
|
|
4950
|
+
}
|
|
4951
|
+
.code-block > .block-head {
|
|
4952
|
+
margin: 0;
|
|
4953
|
+
padding: 4px 6px 4px 12px;
|
|
4954
|
+
border-bottom: 1px solid var(--line);
|
|
4955
|
+
}
|
|
4956
|
+
.code-block pre {
|
|
4738
4957
|
margin: 0;
|
|
4739
4958
|
overflow-x: auto;
|
|
4740
|
-
background: var(--bg);
|
|
4741
|
-
border: 1px solid var(--line);
|
|
4742
|
-
border-radius: 6px;
|
|
4743
4959
|
padding: 10px 12px;
|
|
4744
4960
|
font: 12.5px / 1.5 var(--mono);
|
|
4745
4961
|
}
|
|
@@ -6515,7 +6731,7 @@ body.has-savebar .content {
|
|
|
6515
6731
|
.subs-wrap {
|
|
6516
6732
|
container-type: inline-size;
|
|
6517
6733
|
}
|
|
6518
|
-
@container (max-width:
|
|
6734
|
+
@container (max-width: 609px) {
|
|
6519
6735
|
.list-table.subs-table.stacks,
|
|
6520
6736
|
.list-table.subs-table.stacks tbody,
|
|
6521
6737
|
.list-table.subs-table.stacks tr {
|
|
@@ -6556,10 +6772,7 @@ body.has-savebar .content {
|
|
|
6556
6772
|
.list-table.subs-table.stacks td.empty {
|
|
6557
6773
|
display: none;
|
|
6558
6774
|
}
|
|
6559
|
-
/* Subscribers: status pill ·
|
|
6560
|
-
.subs-table td.how::before {
|
|
6561
|
-
content: " · ";
|
|
6562
|
-
}
|
|
6775
|
+
/* Subscribers: status pill · joined <date>. */
|
|
6563
6776
|
.subs-table td.joined::before {
|
|
6564
6777
|
content: " · joined ";
|
|
6565
6778
|
}
|
|
@@ -6687,8 +6900,10 @@ body.has-savebar .content {
|
|
|
6687
6900
|
font-weight: 600;
|
|
6688
6901
|
margin: 14px 0 4px;
|
|
6689
6902
|
}
|
|
6690
|
-
/*
|
|
6691
|
-
|
|
6903
|
+
/* On Workers Free's limits, the advice to move to Workers Paid on a list past what Free suits,
|
|
6904
|
+
on the Dashboard, the Subscribers screen, and an import's plan (DESIGN §4): the same amber
|
|
6905
|
+
box the Schedule dialog gives D1's daily-writes warning. */
|
|
6906
|
+
.workers-plan {
|
|
6692
6907
|
color: var(--warn-fg);
|
|
6693
6908
|
background: var(--warn-bg);
|
|
6694
6909
|
border: 1px solid var(--warn-line);
|
|
@@ -6729,9 +6944,73 @@ body.has-savebar .content {
|
|
|
6729
6944
|
gap: 14px;
|
|
6730
6945
|
margin-top: 18px;
|
|
6731
6946
|
}
|
|
6732
|
-
/*
|
|
6947
|
+
/* A subscriber's page is titled with the address, so a long one breaks rather than widens
|
|
6948
|
+
the head; the status pill and the flag sit beside it, as the roster draws them. */
|
|
6733
6949
|
.sub-title {
|
|
6734
6950
|
overflow-wrap: anywhere;
|
|
6951
|
+
margin: 0;
|
|
6952
|
+
}
|
|
6953
|
+
.sub-page-title {
|
|
6954
|
+
display: flex;
|
|
6955
|
+
align-items: center;
|
|
6956
|
+
gap: 12px;
|
|
6957
|
+
flex-wrap: wrap;
|
|
6958
|
+
min-width: 0;
|
|
6959
|
+
}
|
|
6960
|
+
.sub-page-title .sub-pills {
|
|
6961
|
+
white-space: nowrap;
|
|
6962
|
+
}
|
|
6963
|
+
/* Whether they receive posts: the page's first answer, under the head, in the record's
|
|
6964
|
+
meta-line place; muted, with a green dot only when they do, as a confirmed pill is. */
|
|
6965
|
+
.sub-receives {
|
|
6966
|
+
margin: -6px 0 14px;
|
|
6967
|
+
font-size: 13.5px;
|
|
6968
|
+
color: var(--muted);
|
|
6969
|
+
}
|
|
6970
|
+
.sub-receives.yes::before {
|
|
6971
|
+
content: "";
|
|
6972
|
+
display: inline-block;
|
|
6973
|
+
width: 8px;
|
|
6974
|
+
height: 8px;
|
|
6975
|
+
margin-right: 7px;
|
|
6976
|
+
border-radius: 50%;
|
|
6977
|
+
background: var(--ok-fg);
|
|
6978
|
+
}
|
|
6979
|
+
/* The import note: the card's own small print, outside a dialog's hint styles. */
|
|
6980
|
+
.sub-note {
|
|
6981
|
+
margin: 10px 0 0;
|
|
6982
|
+
font-size: 12.5px;
|
|
6983
|
+
color: var(--muted);
|
|
6984
|
+
}
|
|
6985
|
+
.sub-sec .sub-meaning {
|
|
6986
|
+
margin: 8px 0;
|
|
6987
|
+
font-size: 13.5px;
|
|
6988
|
+
}
|
|
6989
|
+
.sub-sec p:last-child {
|
|
6990
|
+
margin-bottom: 0;
|
|
6991
|
+
}
|
|
6992
|
+
.sub-sup-line,
|
|
6993
|
+
.sub-left-line {
|
|
6994
|
+
margin: 0;
|
|
6995
|
+
}
|
|
6996
|
+
/* Sends is a table, and a table is already a card, so its section is no card of its own:
|
|
6997
|
+
the label sits on the page above it, as the dashboard's sections do. */
|
|
6998
|
+
.sub-sends {
|
|
6999
|
+
margin-top: 22px;
|
|
7000
|
+
}
|
|
7001
|
+
.sub-sends-table .c-out {
|
|
7002
|
+
width: 160px;
|
|
7003
|
+
}
|
|
7004
|
+
.sub-sends-table .sub-send a {
|
|
7005
|
+
font-weight: 500;
|
|
7006
|
+
}
|
|
7007
|
+
.sub-more {
|
|
7008
|
+
margin-top: 12px;
|
|
7009
|
+
}
|
|
7010
|
+
/* The roster's address is its row's link, as a Sent row's subject is, in the text's color
|
|
7011
|
+
since the whole row is the click target. */
|
|
7012
|
+
.subs-table a.sub-email {
|
|
7013
|
+
color: inherit;
|
|
6735
7014
|
}
|
|
6736
7015
|
.imp-dl,
|
|
6737
7016
|
.sub-dl {
|