@kurtbruns/kestrel 1.11.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 +154 -1
- package/README.md +6 -7
- package/cli/dev-api.mjs +1 -1
- package/cli/reset.mjs +2 -0
- package/cli/seed.mjs +3 -1
- package/demo/README.md +4 -2
- package/demo/posts/0-test-email/index.md +13 -0
- package/demo/posts/5-a-quick-note/index.md +2 -0
- package/dist/public/dashboard/app.js +383 -324
- package/dist/public/dashboard/app.js.map +4 -4
- package/dist/public/dashboard/index.html +2 -2
- package/dist/public/dashboard/styles.css +850 -191
- package/dist/worker/index.js +11327 -6891
- 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 +24 -4
- package/docs/get-started/05-verify.md +21 -5
- package/docs/get-started/06-go-live.md +2 -2
- package/docs/guides/01-connect-claude.md +8 -3
- 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 +20 -6
- package/docs/publishing/06-troubleshooting.md +46 -12
- package/docs/publishing/07-working-with-claude.md +4 -4
- 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 +3 -1
- package/docs/publishing/README.md +1 -1
- package/docs/reference/01-configuration.md +3 -3
- package/migrations/0007_sends_progress_at.sql +12 -0
- package/migrations/0008_notifications_sandbox.sql +35 -0
- package/migrations/0009_post_email_only.sql +12 -0
- package/migrations/0010_unlisted_links.sql +43 -0
- package/migrations/0011_sends_render_warnings.sql +13 -0
- package/migrations/0012_unsubscribe_cause.sql +139 -0
- package/package.json +1 -1
- package/template/claude/CLAUDE.md +13 -9
- package/template/claude/skills/migrate-list/SKILL.md +5 -4
|
@@ -1558,6 +1558,42 @@ td a:hover {
|
|
|
1558
1558
|
color: var(--status-sending-fg);
|
|
1559
1559
|
border-color: var(--status-sending-line);
|
|
1560
1560
|
}
|
|
1561
|
+
/* A list's title with its Email only mark (sends/progress.ts): the title wraps within its room
|
|
1562
|
+
and the mark, an envelope and muted words, stays whole on the title's line, so it never drops
|
|
1563
|
+
onto a line of its own. */
|
|
1564
|
+
.title-with-label {
|
|
1565
|
+
display: flex;
|
|
1566
|
+
/* Centered, not on a baseline: the mark's text is smaller and led by an icon, and only
|
|
1567
|
+
their centers line up with the title's. */
|
|
1568
|
+
align-items: center;
|
|
1569
|
+
gap: 8px;
|
|
1570
|
+
min-width: 0;
|
|
1571
|
+
}
|
|
1572
|
+
.title-with-label .title-text {
|
|
1573
|
+
min-width: 0;
|
|
1574
|
+
}
|
|
1575
|
+
/* Where a table's columns are fixed, a long title ends in an ellipsis, so the row stays one
|
|
1576
|
+
line tall; the whole title is a click away in the post. */
|
|
1577
|
+
.list-table .title-with-label .title-text,
|
|
1578
|
+
.dash-drafts .title-with-label .title-text {
|
|
1579
|
+
overflow: hidden;
|
|
1580
|
+
text-overflow: ellipsis;
|
|
1581
|
+
white-space: nowrap;
|
|
1582
|
+
}
|
|
1583
|
+
.email-only-mark {
|
|
1584
|
+
display: inline-flex;
|
|
1585
|
+
align-items: center;
|
|
1586
|
+
gap: 4px;
|
|
1587
|
+
flex: none;
|
|
1588
|
+
font-size: 12.5px;
|
|
1589
|
+
font-weight: 400;
|
|
1590
|
+
color: var(--muted);
|
|
1591
|
+
white-space: nowrap;
|
|
1592
|
+
}
|
|
1593
|
+
.email-only-mark svg {
|
|
1594
|
+
width: 13px;
|
|
1595
|
+
height: 13px;
|
|
1596
|
+
}
|
|
1561
1597
|
/* Subscriber consent states borrow a hue that matches the state's meaning: confirmed →
|
|
1562
1598
|
the sent green, unsubscribed → the draft grey, suppressed → danger red (below). Pending
|
|
1563
1599
|
awaits double-opt-in — a waiting/attention state — so it takes the amber `warn` scale,
|
|
@@ -1810,16 +1846,38 @@ td a:hover {
|
|
|
1810
1846
|
.list-table .c-delivered {
|
|
1811
1847
|
width: 176px;
|
|
1812
1848
|
}
|
|
1849
|
+
/* Every Sent row, on the Sent page and the dashboard, as tall as one with a delivery note under its count, so a row with none (a
|
|
1850
|
+
one-recipient test) sits in the same rhythm as the rest. A row's height is its floor; once
|
|
1851
|
+
the table stacks, each row is a block that takes its own height. */
|
|
1852
|
+
/* The dashboard's Drafts card is half the page wide: its date stays on one line, and the title
|
|
1853
|
+
takes what room is left. */
|
|
1854
|
+
.dash-drafts {
|
|
1855
|
+
table-layout: fixed;
|
|
1856
|
+
}
|
|
1857
|
+
/* 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. */
|
|
1859
|
+
.dash-drafts td.edited {
|
|
1860
|
+
width: 15em;
|
|
1861
|
+
white-space: nowrap;
|
|
1862
|
+
}
|
|
1863
|
+
@media (min-width: 721px) {
|
|
1864
|
+
.sent-table tbody tr,
|
|
1865
|
+
.dash-sent tbody tr {
|
|
1866
|
+
height: 56px;
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1813
1869
|
.list-table .c-act {
|
|
1814
1870
|
width: 56px;
|
|
1815
1871
|
}
|
|
1816
|
-
/* Subscribers:
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
width: 152px;
|
|
1820
|
-
}
|
|
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. */
|
|
1821
1875
|
.list-table.subs-table {
|
|
1822
|
-
min-width:
|
|
1876
|
+
min-width: 610px;
|
|
1877
|
+
}
|
|
1878
|
+
.list-table.subs-table .c-status,
|
|
1879
|
+
.list-table.subs-table .c-date {
|
|
1880
|
+
width: 24%;
|
|
1823
1881
|
}
|
|
1824
1882
|
|
|
1825
1883
|
.dash-section table {
|
|
@@ -1880,20 +1938,143 @@ th.num .th-sort {
|
|
|
1880
1938
|
cursor: default;
|
|
1881
1939
|
}
|
|
1882
1940
|
|
|
1883
|
-
/*
|
|
1884
|
-
|
|
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 {
|
|
1885
1950
|
margin: 0;
|
|
1951
|
+
padding-bottom: 8px;
|
|
1952
|
+
border-bottom: 1px solid var(--line-soft);
|
|
1886
1953
|
}
|
|
1887
|
-
.api-
|
|
1888
|
-
|
|
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 {
|
|
1889
1981
|
font-size: 13px;
|
|
1890
|
-
|
|
1982
|
+
font-weight: 600;
|
|
1983
|
+
overflow-wrap: break-word;
|
|
1891
1984
|
}
|
|
1892
|
-
.api-
|
|
1893
|
-
|
|
1894
|
-
|
|
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);
|
|
1895
2077
|
}
|
|
1896
|
-
|
|
1897
2078
|
/* editor header */
|
|
1898
2079
|
.editor-head {
|
|
1899
2080
|
display: flex;
|
|
@@ -1965,14 +2146,16 @@ body.web-mode .content {
|
|
|
1965
2146
|
padding: 10px 44px;
|
|
1966
2147
|
text-align: left;
|
|
1967
2148
|
}
|
|
2149
|
+
/* The reader pages' Dev pill exactly (`.r-dev-tag` in src/lib/page.ts), so the two read as one. */
|
|
1968
2150
|
.web-bar-tag {
|
|
1969
2151
|
display: inline-block;
|
|
1970
2152
|
margin-right: 10px;
|
|
1971
|
-
font-size:
|
|
2153
|
+
font-size: 9.5px;
|
|
2154
|
+
line-height: 1.6;
|
|
1972
2155
|
letter-spacing: 0.09em;
|
|
1973
2156
|
text-transform: uppercase;
|
|
1974
2157
|
font-weight: 700;
|
|
1975
|
-
padding:
|
|
2158
|
+
padding: 2px 7px;
|
|
1976
2159
|
border-radius: 999px;
|
|
1977
2160
|
background: var(--accent-on);
|
|
1978
2161
|
color: var(--accent);
|
|
@@ -2186,7 +2369,7 @@ body.web-mode .content {
|
|
|
2186
2369
|
font-size: 22px;
|
|
2187
2370
|
letter-spacing: -0.01em;
|
|
2188
2371
|
}
|
|
2189
|
-
/* The derived-phase pill (progressing / retrying /
|
|
2372
|
+
/* The derived-phase pill (progressing / retrying / backing_off / needs_attention /
|
|
2190
2373
|
settling / …). Tones reuse the status + semantic palettes so a phase reads the same
|
|
2191
2374
|
hue as its badge elsewhere. A plain label, so the default cursor (DESIGN §11). */
|
|
2192
2375
|
.phase-pill {
|
|
@@ -2415,6 +2598,12 @@ body.web-mode .content {
|
|
|
2415
2598
|
border-color: var(--ring);
|
|
2416
2599
|
box-shadow: 0 0 0 1px var(--ring);
|
|
2417
2600
|
}
|
|
2601
|
+
/* A scheduled post's body is locked, so focus rings it in grey, as the locked subject's is,
|
|
2602
|
+
rather than in the blue that says "type here" (DESIGN §3). */
|
|
2603
|
+
.composer.composer-locked:focus-within {
|
|
2604
|
+
border-color: var(--muted);
|
|
2605
|
+
box-shadow: 0 0 0 1px var(--muted);
|
|
2606
|
+
}
|
|
2418
2607
|
.composer-head {
|
|
2419
2608
|
display: flex;
|
|
2420
2609
|
align-items: flex-end;
|
|
@@ -2621,14 +2810,25 @@ body.web-mode .content {
|
|
|
2621
2810
|
}
|
|
2622
2811
|
.composer-body.locked textarea.editor {
|
|
2623
2812
|
caret-color: transparent;
|
|
2813
|
+
cursor: default;
|
|
2814
|
+
}
|
|
2815
|
+
/* Its text is muted, as the subject's is, so the body reads as locked at a glance. */
|
|
2816
|
+
.composer-body.locked .md-hl code {
|
|
2817
|
+
opacity: 0.72;
|
|
2624
2818
|
}
|
|
2625
|
-
/* The subject
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2819
|
+
/* The subject of a scheduled post is read-only too, and readonly (unlike disabled) carries no
|
|
2820
|
+
native greying, so it is styled to look locked while keeping what disabled would take away:
|
|
2821
|
+
it can still be focused, selected, and copied, and a keystroke still pulses the foot line.
|
|
2822
|
+
The recessed surface, muted text, an arrow cursor, and a grey focus ring in place of the
|
|
2823
|
+
blue one that says "type here" (DESIGN §3). */
|
|
2824
|
+
#f-subject[readonly] {
|
|
2630
2825
|
background: var(--sunken);
|
|
2631
2826
|
border-color: var(--line-soft);
|
|
2827
|
+
color: var(--muted);
|
|
2828
|
+
cursor: default;
|
|
2829
|
+
}
|
|
2830
|
+
#f-subject[readonly]:focus-visible {
|
|
2831
|
+
outline-color: var(--muted);
|
|
2632
2832
|
}
|
|
2633
2833
|
.composer-body.dragover::after {
|
|
2634
2834
|
content: "Drop images to upload";
|
|
@@ -2696,6 +2896,11 @@ body.web-mode .content {
|
|
|
2696
2896
|
}
|
|
2697
2897
|
}
|
|
2698
2898
|
|
|
2899
|
+
/* The editor's subject, body, and actions sit on the page itself, with no card around them:
|
|
2900
|
+
the fields and the composer carry their own borders, aligned with the head and the banner. */
|
|
2901
|
+
.editor-form > label:first-child {
|
|
2902
|
+
margin-top: 6px;
|
|
2903
|
+
}
|
|
2699
2904
|
.actions-bar {
|
|
2700
2905
|
display: flex;
|
|
2701
2906
|
align-items: center;
|
|
@@ -2910,13 +3115,10 @@ body.web-mode .content {
|
|
|
2910
3115
|
flex: none;
|
|
2911
3116
|
white-space: nowrap;
|
|
2912
3117
|
}
|
|
2913
|
-
/* The
|
|
2914
|
-
rather than demotes below, the Schedule view. */
|
|
2915
|
-
.modal .
|
|
2916
|
-
margin:
|
|
2917
|
-
padding-top: 0;
|
|
2918
|
-
border-top: 0;
|
|
2919
|
-
justify-content: flex-start;
|
|
3118
|
+
/* The Send-now view's way back to the Schedule view, at the start of its button row: it
|
|
3119
|
+
returns from, rather than demotes below, the Schedule view, so it takes no row of its own. */
|
|
3120
|
+
.modal .actions .actions-back {
|
|
3121
|
+
margin-right: auto;
|
|
2920
3122
|
}
|
|
2921
3123
|
/* Link — a text-link-styled action (DESIGN §7), not one of the button roles. Reserved
|
|
2922
3124
|
for a deliberately subordinate choice, like the in-modal Send now that sits below
|
|
@@ -3122,16 +3324,6 @@ textarea.is-invalid {
|
|
|
3122
3324
|
.modal .dialog-error {
|
|
3123
3325
|
margin-top: 12px;
|
|
3124
3326
|
}
|
|
3125
|
-
/* Label + info icon grouped at the top of the slug field. */
|
|
3126
|
-
.label-row {
|
|
3127
|
-
display: flex;
|
|
3128
|
-
align-items: center;
|
|
3129
|
-
gap: 6px;
|
|
3130
|
-
margin: 14px 0 8px;
|
|
3131
|
-
}
|
|
3132
|
-
.label-row label {
|
|
3133
|
-
margin: 0;
|
|
3134
|
-
}
|
|
3135
3327
|
/* Info icon: the field explanation lives in its hover/focus tooltip. The padding
|
|
3136
3328
|
+ equal negative margin enlarge the hover/click target to ~28px without moving
|
|
3137
3329
|
the glyph or growing the label row. */
|
|
@@ -3196,28 +3388,6 @@ textarea.is-invalid {
|
|
|
3196
3388
|
visibility: visible;
|
|
3197
3389
|
transform: translateY(0);
|
|
3198
3390
|
}
|
|
3199
|
-
/* The "Auto-generate from subject" toggle, grouped just under the slug field. */
|
|
3200
|
-
.slug-auto-toggle {
|
|
3201
|
-
display: inline-flex;
|
|
3202
|
-
align-items: center;
|
|
3203
|
-
gap: 6px;
|
|
3204
|
-
margin-top: 8px;
|
|
3205
|
-
font-size: 14px;
|
|
3206
|
-
color: var(--muted);
|
|
3207
|
-
cursor: pointer;
|
|
3208
|
-
user-select: none;
|
|
3209
|
-
}
|
|
3210
|
-
.slug-auto-toggle input {
|
|
3211
|
-
width: 14px;
|
|
3212
|
-
height: 14px;
|
|
3213
|
-
margin: 0;
|
|
3214
|
-
accent-color: var(--ring);
|
|
3215
|
-
cursor: pointer;
|
|
3216
|
-
}
|
|
3217
|
-
/* Slug is muted while it follows the subject; normal once set by hand. */
|
|
3218
|
-
#f-slug.slug-auto {
|
|
3219
|
-
color: var(--muted);
|
|
3220
|
-
}
|
|
3221
3391
|
/* --- Editor-state banner (DESIGN §5): a standing, in-flow line at the top of the
|
|
3222
3392
|
editor, in two variants. `scheduled` marks a frozen, soft-locked post (SPEC §6);
|
|
3223
3393
|
`conflict` warns the draft changed elsewhere (another tab, or Claude), and its two
|
|
@@ -3255,46 +3425,357 @@ textarea.is-invalid {
|
|
|
3255
3425
|
banner that says to send a new test, and a mark on the dashboard's scheduled card. */
|
|
3256
3426
|
.sched-remade {
|
|
3257
3427
|
display: flex;
|
|
3258
|
-
flex-wrap: wrap;
|
|
3428
|
+
flex-wrap: wrap;
|
|
3429
|
+
align-items: center;
|
|
3430
|
+
gap: 4px 10px;
|
|
3431
|
+
margin-top: 6px;
|
|
3432
|
+
font-size: 13px;
|
|
3433
|
+
}
|
|
3434
|
+
/* The scheduled banner's notes under the countdown (DESIGN §4): a send that will be held
|
|
3435
|
+
with no provider connected, and one too large for D1's daily writes on Workers Free's
|
|
3436
|
+
limits. Each keeps the banner's own violet, since amber words on violet fall short of
|
|
3437
|
+
contrast and mix two of the color meanings, and an amber ⚠️ leads it to say it's worth a
|
|
3438
|
+
look. Held is the stronger of the two (the send won't go out), so it keeps the weight. */
|
|
3439
|
+
.sched-note {
|
|
3440
|
+
display: flex;
|
|
3441
|
+
gap: 6px;
|
|
3442
|
+
align-items: baseline;
|
|
3443
|
+
margin-top: 6px;
|
|
3444
|
+
font-size: 12.5px;
|
|
3445
|
+
color: var(--status-scheduled-fg);
|
|
3446
|
+
}
|
|
3447
|
+
.sched-note-mark {
|
|
3448
|
+
flex: none;
|
|
3449
|
+
}
|
|
3450
|
+
.sched-note-held {
|
|
3451
|
+
font-weight: 500;
|
|
3452
|
+
}
|
|
3453
|
+
.sched-remade-mark {
|
|
3454
|
+
margin-top: 4px;
|
|
3455
|
+
font-size: 12.5px;
|
|
3456
|
+
color: var(--status-scheduled-fg);
|
|
3457
|
+
}
|
|
3458
|
+
/* The Email only box under a dialog's time: the label, then its helper beneath. */
|
|
3459
|
+
.listing-check {
|
|
3460
|
+
display: flex;
|
|
3461
|
+
align-items: flex-start;
|
|
3462
|
+
gap: 10px;
|
|
3463
|
+
margin: 14px 0 0;
|
|
3464
|
+
font-size: 14px;
|
|
3465
|
+
color: var(--fg);
|
|
3466
|
+
cursor: pointer;
|
|
3467
|
+
}
|
|
3468
|
+
.listing-check input {
|
|
3469
|
+
width: auto;
|
|
3470
|
+
margin: 3px 0 0;
|
|
3471
|
+
flex: 0 0 auto;
|
|
3472
|
+
accent-color: var(--ring);
|
|
3473
|
+
cursor: pointer;
|
|
3474
|
+
}
|
|
3475
|
+
.listing-check strong {
|
|
3476
|
+
font-weight: 600;
|
|
3477
|
+
}
|
|
3478
|
+
.listing-help {
|
|
3479
|
+
display: block;
|
|
3480
|
+
margin-top: 2px;
|
|
3481
|
+
font-size: 13px;
|
|
3482
|
+
line-height: 1.45;
|
|
3483
|
+
color: var(--muted);
|
|
3484
|
+
}
|
|
3485
|
+
/* A scheduled post's banner in two rows (DESIGN §4): the time, its notes, and the controls that
|
|
3486
|
+
act on it; then, under a hairline, where the post is going, on a slightly deeper ground. */
|
|
3487
|
+
.banner-split {
|
|
3488
|
+
display: block;
|
|
3489
|
+
padding: 0;
|
|
3490
|
+
overflow: hidden;
|
|
3491
|
+
}
|
|
3492
|
+
.banner-top {
|
|
3493
|
+
display: flex;
|
|
3494
|
+
align-items: center;
|
|
3495
|
+
justify-content: space-between;
|
|
3496
|
+
gap: 12px;
|
|
3497
|
+
flex-wrap: wrap;
|
|
3498
|
+
padding: 12px 16px;
|
|
3499
|
+
font-size: 14px;
|
|
3500
|
+
}
|
|
3501
|
+
.banner-bottom {
|
|
3502
|
+
display: flex;
|
|
3503
|
+
align-items: center;
|
|
3504
|
+
gap: 8px;
|
|
3505
|
+
padding: 9px 16px;
|
|
3506
|
+
border-top: 1px solid var(--status-scheduled-line);
|
|
3507
|
+
/* A shade deeper than the banner, so the row reads as its footer rather than a strip of its own. */
|
|
3508
|
+
background: color-mix(in srgb, var(--status-scheduled-fg) 6%, var(--status-scheduled-bg));
|
|
3509
|
+
font-size: 13px;
|
|
3510
|
+
}
|
|
3511
|
+
.banner-bottom svg {
|
|
3512
|
+
flex: none;
|
|
3513
|
+
width: 14px;
|
|
3514
|
+
height: 14px;
|
|
3515
|
+
}
|
|
3516
|
+
.sched-dest-icon {
|
|
3517
|
+
display: flex;
|
|
3518
|
+
flex: none;
|
|
3519
|
+
}
|
|
3520
|
+
/* A filled icon takes the row's violet; a stroked one (fill="none") already draws in it. */
|
|
3521
|
+
.banner-bottom svg:not([fill="none"]),
|
|
3522
|
+
#settingsBtn svg {
|
|
3523
|
+
fill: currentColor;
|
|
3524
|
+
}
|
|
3525
|
+
#schedDest {
|
|
3526
|
+
min-width: 0;
|
|
3527
|
+
overflow-wrap: anywhere;
|
|
3528
|
+
}
|
|
3529
|
+
.sched-dest code {
|
|
3530
|
+
font-family: var(--mono);
|
|
3531
|
+
font-size: 12.5px;
|
|
3532
|
+
}
|
|
3533
|
+
.pub-taken code {
|
|
3534
|
+
font-family: var(--mono);
|
|
3535
|
+
font-size: 12.5px;
|
|
3536
|
+
color: var(--fg);
|
|
3537
|
+
}
|
|
3538
|
+
/* The settings form in a dialog under the time, set off from it by a rule. */
|
|
3539
|
+
.dialog-settings {
|
|
3540
|
+
margin-top: 14px;
|
|
3541
|
+
padding-top: 2px;
|
|
3542
|
+
}
|
|
3543
|
+
.dialog-settings .listing-check {
|
|
3544
|
+
margin-top: 0;
|
|
3545
|
+
}
|
|
3546
|
+
/* The archive address as one URL field: the base greyed and fixed, then the slug, the one part
|
|
3547
|
+
to type. The whole field is the label, so a click on the base lands in the slug too. */
|
|
3548
|
+
.pub-address {
|
|
3549
|
+
margin-top: 14px;
|
|
3550
|
+
}
|
|
3551
|
+
.pub-label {
|
|
3552
|
+
display: block;
|
|
3553
|
+
margin: 0 0 6px;
|
|
3554
|
+
font-size: 13px;
|
|
3555
|
+
color: var(--muted);
|
|
3556
|
+
}
|
|
3557
|
+
.url-field {
|
|
3558
|
+
display: flex;
|
|
3559
|
+
align-items: stretch;
|
|
3560
|
+
min-width: 0;
|
|
3561
|
+
margin: 0;
|
|
3562
|
+
overflow: hidden;
|
|
3563
|
+
border: 1px solid var(--line);
|
|
3564
|
+
border-radius: 6px;
|
|
3565
|
+
background: var(--card);
|
|
3566
|
+
font-family: var(--mono);
|
|
3567
|
+
font-size: 13px;
|
|
3568
|
+
cursor: text;
|
|
3569
|
+
}
|
|
3570
|
+
.url-field:focus-within {
|
|
3571
|
+
border-color: var(--ring);
|
|
3572
|
+
box-shadow: 0 0 0 1px var(--ring);
|
|
3573
|
+
}
|
|
3574
|
+
.url-field.is-invalid {
|
|
3575
|
+
border-color: var(--danger-fg);
|
|
3576
|
+
}
|
|
3577
|
+
.url-base {
|
|
3578
|
+
display: flex;
|
|
3579
|
+
align-items: center;
|
|
3580
|
+
padding: 8px 0 8px 10px;
|
|
3581
|
+
white-space: nowrap;
|
|
3582
|
+
color: var(--muted);
|
|
3583
|
+
}
|
|
3584
|
+
.url-field input {
|
|
3585
|
+
flex: 1;
|
|
3586
|
+
min-width: 6em;
|
|
3587
|
+
border: 0;
|
|
3588
|
+
border-radius: 0;
|
|
3589
|
+
padding: 8px 10px 8px 0;
|
|
3590
|
+
font-family: var(--mono);
|
|
3591
|
+
font-size: 13px;
|
|
3592
|
+
font-weight: 600;
|
|
3593
|
+
}
|
|
3594
|
+
.url-field input:focus-visible {
|
|
3595
|
+
outline: none;
|
|
3596
|
+
}
|
|
3597
|
+
.url-field.readonly {
|
|
3598
|
+
cursor: default;
|
|
3599
|
+
background: var(--sunken);
|
|
3600
|
+
}
|
|
3601
|
+
.url-field.readonly input {
|
|
3602
|
+
color: var(--muted);
|
|
3603
|
+
font-weight: 500;
|
|
3604
|
+
background: transparent;
|
|
3605
|
+
}
|
|
3606
|
+
@media (max-width: 480px) {
|
|
3607
|
+
.url-field {
|
|
3608
|
+
flex-direction: column;
|
|
3609
|
+
}
|
|
3610
|
+
.url-base {
|
|
3611
|
+
padding-bottom: 0;
|
|
3612
|
+
}
|
|
3613
|
+
.url-field input {
|
|
3614
|
+
padding-left: 10px;
|
|
3615
|
+
}
|
|
3616
|
+
}
|
|
3617
|
+
/* A refused address, in the server's words, under its field. */
|
|
3618
|
+
.pub-error {
|
|
3619
|
+
margin: 8px 0 0;
|
|
3620
|
+
font-size: 13px;
|
|
3621
|
+
color: var(--danger-fg);
|
|
3622
|
+
}
|
|
3623
|
+
/* The Post settings button carries a gear before its words. */
|
|
3624
|
+
#settingsBtn {
|
|
3625
|
+
display: inline-flex;
|
|
3626
|
+
align-items: center;
|
|
3627
|
+
gap: 6px;
|
|
3628
|
+
}
|
|
3629
|
+
#settingsBtn svg {
|
|
3630
|
+
width: 16px;
|
|
3631
|
+
height: 16px;
|
|
3632
|
+
}
|
|
3633
|
+
.pub-taken {
|
|
3634
|
+
display: flex;
|
|
3635
|
+
align-items: flex-start;
|
|
3636
|
+
gap: 6px;
|
|
3637
|
+
margin: 8px 0 0;
|
|
3638
|
+
font-size: 13px;
|
|
3639
|
+
line-height: 1.5;
|
|
3640
|
+
color: var(--muted);
|
|
3641
|
+
}
|
|
3642
|
+
.pub-taken svg {
|
|
3643
|
+
width: 15px;
|
|
3644
|
+
height: 15px;
|
|
3645
|
+
flex: none;
|
|
3646
|
+
margin-top: 2px;
|
|
3647
|
+
}
|
|
3648
|
+
.pub-note {
|
|
3649
|
+
margin: 8px 0 0;
|
|
3650
|
+
font-size: 13px;
|
|
3651
|
+
color: var(--muted);
|
|
3652
|
+
}
|
|
3653
|
+
/* A sent post's page in two parts (DESIGN §3): the post, then its delivery. */
|
|
3654
|
+
.rec-head-page {
|
|
3655
|
+
margin-bottom: 14px;
|
|
3656
|
+
}
|
|
3657
|
+
.rec-part-title {
|
|
3658
|
+
margin: 0 0 10px;
|
|
3659
|
+
font-size: 11px;
|
|
3660
|
+
font-weight: 600;
|
|
3661
|
+
letter-spacing: 0.08em;
|
|
3662
|
+
text-transform: uppercase;
|
|
3663
|
+
color: var(--muted);
|
|
3664
|
+
}
|
|
3665
|
+
.rec-web {
|
|
3666
|
+
display: grid;
|
|
3667
|
+
}
|
|
3668
|
+
.rec-link-label {
|
|
3669
|
+
margin: 0 0 6px;
|
|
3670
|
+
font-size: 12.5px;
|
|
3671
|
+
color: var(--muted);
|
|
3672
|
+
}
|
|
3673
|
+
/* The link as one field: the address, then Copy and Open attached to it. */
|
|
3674
|
+
.rec-link {
|
|
3675
|
+
display: flex;
|
|
3676
|
+
align-items: stretch;
|
|
3677
|
+
min-width: 0;
|
|
3678
|
+
overflow: hidden;
|
|
3679
|
+
border: 1px solid var(--line);
|
|
3680
|
+
border-radius: 7px;
|
|
3681
|
+
background: var(--sunken);
|
|
3682
|
+
}
|
|
3683
|
+
/* An address is one line, shortened at its end, since Copy carries it whole. */
|
|
3684
|
+
.rec-link code {
|
|
3685
|
+
flex: 1 1 auto;
|
|
3686
|
+
align-self: center;
|
|
3687
|
+
min-width: 0;
|
|
3688
|
+
padding: 8px 10px;
|
|
3689
|
+
font-family: var(--mono);
|
|
3690
|
+
font-size: 12.5px;
|
|
3691
|
+
white-space: nowrap;
|
|
3692
|
+
overflow: hidden;
|
|
3693
|
+
text-overflow: ellipsis;
|
|
3694
|
+
}
|
|
3695
|
+
.rec-link button {
|
|
3696
|
+
flex: none;
|
|
3697
|
+
display: inline-flex;
|
|
3698
|
+
align-items: center;
|
|
3699
|
+
gap: 5px;
|
|
3700
|
+
min-height: 35px;
|
|
3701
|
+
padding: 0 11px;
|
|
3702
|
+
border: 0;
|
|
3703
|
+
border-left: 1px solid var(--line);
|
|
3704
|
+
border-radius: 0;
|
|
3705
|
+
background: var(--card);
|
|
3706
|
+
color: var(--muted);
|
|
3707
|
+
font-size: 13px;
|
|
3708
|
+
white-space: nowrap;
|
|
3709
|
+
}
|
|
3710
|
+
.rec-link button:hover {
|
|
3711
|
+
color: var(--fg);
|
|
3712
|
+
background: var(--hover);
|
|
3713
|
+
}
|
|
3714
|
+
.rec-link button.rec-open {
|
|
3715
|
+
color: var(--accent);
|
|
3716
|
+
font-weight: 500;
|
|
3717
|
+
}
|
|
3718
|
+
.rec-link-note {
|
|
3719
|
+
margin: 6px 0 0;
|
|
3720
|
+
font-size: 12.5px;
|
|
3721
|
+
}
|
|
3722
|
+
/* Where the post stands, in words beside the one change it allows (which asks first). */
|
|
3723
|
+
.rec-arch {
|
|
3724
|
+
display: flex;
|
|
3259
3725
|
align-items: center;
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3726
|
+
justify-content: space-between;
|
|
3727
|
+
flex-wrap: wrap;
|
|
3728
|
+
gap: 10px 16px;
|
|
3729
|
+
margin: 14px -16px 0;
|
|
3730
|
+
padding: 14px 16px 0;
|
|
3731
|
+
border-top: 1px solid var(--line);
|
|
3263
3732
|
}
|
|
3264
|
-
/*
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3733
|
+
/* A post with no web link yet has only its standing to show, with no rule above it. */
|
|
3734
|
+
.rec-web > .rec-arch:first-child {
|
|
3735
|
+
margin-top: 0;
|
|
3736
|
+
padding-top: 0;
|
|
3737
|
+
border-top: 0;
|
|
3738
|
+
}
|
|
3739
|
+
.rec-arch-what {
|
|
3740
|
+
flex: 1 1 220px;
|
|
3741
|
+
min-width: 0;
|
|
3742
|
+
font-size: 14px;
|
|
3743
|
+
}
|
|
3744
|
+
.rec-arch-what strong {
|
|
3270
3745
|
display: flex;
|
|
3746
|
+
align-items: center;
|
|
3271
3747
|
gap: 6px;
|
|
3272
|
-
|
|
3273
|
-
margin-top: 6px;
|
|
3274
|
-
font-size: 12.5px;
|
|
3275
|
-
color: var(--status-scheduled-fg);
|
|
3748
|
+
font-weight: 600;
|
|
3276
3749
|
}
|
|
3277
|
-
.
|
|
3750
|
+
.rec-arch-what svg {
|
|
3751
|
+
width: 16px;
|
|
3752
|
+
height: 16px;
|
|
3278
3753
|
flex: none;
|
|
3754
|
+
color: var(--muted);
|
|
3279
3755
|
}
|
|
3280
|
-
.
|
|
3281
|
-
|
|
3756
|
+
.rec-arch-what svg:not([fill="none"]) {
|
|
3757
|
+
fill: currentColor;
|
|
3282
3758
|
}
|
|
3283
|
-
.
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3759
|
+
.rec-arch-note {
|
|
3760
|
+
display: block;
|
|
3761
|
+
margin-top: 2px;
|
|
3762
|
+
font-size: 13px;
|
|
3763
|
+
line-height: 1.45;
|
|
3764
|
+
color: var(--muted);
|
|
3287
3765
|
}
|
|
3288
|
-
|
|
3289
|
-
.
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3766
|
+
/* The address Remove from archive takes down, struck through in its dialog. */
|
|
3767
|
+
.url-gone {
|
|
3768
|
+
padding: 8px 10px;
|
|
3769
|
+
background: var(--sunken);
|
|
3770
|
+
border: 1px solid var(--line);
|
|
3771
|
+
border-radius: 6px;
|
|
3772
|
+
font-family: var(--mono);
|
|
3773
|
+
font-size: 13px;
|
|
3774
|
+
color: var(--muted);
|
|
3775
|
+
overflow-wrap: anywhere;
|
|
3293
3776
|
}
|
|
3777
|
+
|
|
3294
3778
|
@media (max-width: 620px) {
|
|
3295
|
-
.grid2 {
|
|
3296
|
-
grid-template-columns: 1fr;
|
|
3297
|
-
}
|
|
3298
3779
|
.tb {
|
|
3299
3780
|
width: 34px;
|
|
3300
3781
|
height: 34px;
|
|
@@ -3662,16 +4143,27 @@ button.api-filter-clear svg {
|
|
|
3662
4143
|
color: var(--fg);
|
|
3663
4144
|
background: var(--hover);
|
|
3664
4145
|
}
|
|
4146
|
+
/* The reference reads as a document: a doc page's line height, not the browser's default. */
|
|
3665
4147
|
.api-content {
|
|
3666
4148
|
min-width: 0;
|
|
3667
|
-
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;
|
|
3668
4160
|
}
|
|
3669
4161
|
.api-head {
|
|
3670
4162
|
margin: 0 0 12px;
|
|
3671
4163
|
}
|
|
3672
4164
|
.api-head h1 {
|
|
3673
4165
|
margin: 0 0 4px;
|
|
3674
|
-
font-size:
|
|
4166
|
+
font-size: 28px;
|
|
3675
4167
|
}
|
|
3676
4168
|
.api-section {
|
|
3677
4169
|
scroll-margin-top: calc(var(--bar-h) + 2px);
|
|
@@ -3682,35 +4174,53 @@ button.api-filter-clear svg {
|
|
|
3682
4174
|
.api-section.api-last {
|
|
3683
4175
|
min-height: calc(100vh - var(--bar-h));
|
|
3684
4176
|
}
|
|
4177
|
+
/* A tier is a page heading here, not the app's small-caps section label that h2 is elsewhere. */
|
|
3685
4178
|
.api-section h2 {
|
|
3686
|
-
font-size:
|
|
3687
|
-
|
|
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;
|
|
3688
4191
|
}
|
|
3689
4192
|
.api-blurb {
|
|
3690
|
-
margin: 0;
|
|
4193
|
+
margin: 0 0 10px;
|
|
3691
4194
|
font-size: 14px;
|
|
3692
|
-
max-width: 62ch;
|
|
3693
4195
|
}
|
|
3694
|
-
/* A resource: its heading
|
|
4196
|
+
/* A resource: its heading over a rule, scrolling with its routes. */
|
|
3695
4197
|
.api-res {
|
|
3696
4198
|
margin-top: 16px;
|
|
3697
4199
|
scroll-margin-top: var(--bar-h);
|
|
3698
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. */
|
|
3699
4203
|
.api-res-head {
|
|
3700
|
-
position: sticky;
|
|
3701
|
-
top: var(--bar-h);
|
|
3702
|
-
z-index: var(--z-content);
|
|
3703
4204
|
display: flex;
|
|
3704
4205
|
align-items: center;
|
|
3705
4206
|
gap: 8px;
|
|
3706
|
-
|
|
4207
|
+
margin: 0;
|
|
4208
|
+
padding: 6px 0;
|
|
4209
|
+
border-bottom: 1px solid var(--line-soft);
|
|
3707
4210
|
background: var(--bg);
|
|
3708
|
-
border-radius: 6px;
|
|
3709
4211
|
}
|
|
3710
4212
|
|
|
3711
4213
|
.api-res-head h3 {
|
|
3712
4214
|
margin: 0;
|
|
3713
|
-
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;
|
|
3714
4224
|
}
|
|
3715
4225
|
.api-rows {
|
|
3716
4226
|
list-style: none;
|
|
@@ -3719,46 +4229,8 @@ button.api-filter-clear svg {
|
|
|
3719
4229
|
}
|
|
3720
4230
|
.api-rows > li {
|
|
3721
4231
|
border-bottom: 1px solid var(--line);
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
place to the full documentation. */
|
|
3725
|
-
.api-route-sum {
|
|
3726
|
-
display: grid;
|
|
3727
|
-
grid-template-columns: 62px minmax(0, max-content) minmax(0, 1fr) 20px;
|
|
3728
|
-
gap: 14px;
|
|
3729
|
-
align-items: center;
|
|
3730
|
-
padding: 9px 6px;
|
|
3731
|
-
border-radius: 6px;
|
|
3732
|
-
list-style: none;
|
|
3733
|
-
cursor: pointer;
|
|
3734
|
-
}
|
|
3735
|
-
.api-route-sum::-webkit-details-marker {
|
|
3736
|
-
display: none;
|
|
3737
|
-
}
|
|
3738
|
-
.api-route-sum:hover {
|
|
3739
|
-
background: var(--hover);
|
|
3740
|
-
}
|
|
3741
|
-
.api-route-line {
|
|
3742
|
-
color: var(--muted);
|
|
3743
|
-
font-size: 13.5px;
|
|
3744
|
-
white-space: nowrap;
|
|
3745
|
-
overflow: hidden;
|
|
3746
|
-
text-overflow: ellipsis;
|
|
3747
|
-
}
|
|
3748
|
-
.api-route[open] .api-route-line {
|
|
3749
|
-
visibility: hidden;
|
|
3750
|
-
}
|
|
3751
|
-
.api-chev {
|
|
3752
|
-
width: 6px;
|
|
3753
|
-
height: 6px;
|
|
3754
|
-
border-right: 1.6px solid var(--muted);
|
|
3755
|
-
border-bottom: 1.6px solid var(--muted);
|
|
3756
|
-
justify-self: center;
|
|
3757
|
-
transform: rotate(-45deg);
|
|
3758
|
-
transition: transform 0.15s;
|
|
3759
|
-
}
|
|
3760
|
-
.api-route[open] .api-chev {
|
|
3761
|
-
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);
|
|
3762
4234
|
}
|
|
3763
4235
|
/* The verb as a tinted label: its own --method-* color as text over a wash of itself. */
|
|
3764
4236
|
.api-method {
|
|
@@ -3782,6 +4254,13 @@ button.api-filter-clear svg {
|
|
|
3782
4254
|
.api-method.m-DELETE {
|
|
3783
4255
|
--m: var(--method-delete);
|
|
3784
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
|
+
}
|
|
3785
4264
|
.api-path {
|
|
3786
4265
|
font: 13.5px var(--mono);
|
|
3787
4266
|
overflow-wrap: anywhere;
|
|
@@ -3792,30 +4271,128 @@ button.api-filter-clear svg {
|
|
|
3792
4271
|
border-radius: 3px;
|
|
3793
4272
|
padding: 0 2px;
|
|
3794
4273
|
}
|
|
3795
|
-
.api-route-detail {
|
|
3796
|
-
padding: 2px 6px 18px 82px;
|
|
3797
|
-
}
|
|
3798
4274
|
.api-summary {
|
|
3799
|
-
margin:
|
|
3800
|
-
font-size:
|
|
4275
|
+
margin: 0 0 10px;
|
|
4276
|
+
font-size: 15px;
|
|
3801
4277
|
}
|
|
3802
4278
|
.api-desc {
|
|
3803
|
-
margin:
|
|
4279
|
+
margin: 0 0 10px;
|
|
3804
4280
|
font-size: 14px;
|
|
3805
4281
|
}
|
|
3806
|
-
/* A
|
|
4282
|
+
/* A field or parameter the request can't do without. */
|
|
3807
4283
|
.api-req {
|
|
3808
|
-
|
|
3809
|
-
font-
|
|
3810
|
-
color: var(--
|
|
3811
|
-
border: 1px solid var(--line);
|
|
3812
|
-
border-radius: 4px;
|
|
3813
|
-
padding: 0 4px;
|
|
4284
|
+
font-size: 11.5px;
|
|
4285
|
+
font-weight: 600;
|
|
4286
|
+
color: var(--warn-fg);
|
|
3814
4287
|
}
|
|
3815
4288
|
|
|
3816
4289
|
.api-empty {
|
|
3817
4290
|
margin: 20px 0;
|
|
3818
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
|
+
}
|
|
3819
4396
|
/* Reference room on mobile. The bar stays ONE row at --bar-h, the same as on desktop, so
|
|
3820
4397
|
the in-page anchors (scroll-margin-top) and the scroll-spy line keep clearing it: the back
|
|
3821
4398
|
link is a --bar-h square holding its arrow, the wordmark and the two tabs follow, and the
|
|
@@ -3930,11 +4507,13 @@ button.api-filter-clear svg {
|
|
|
3930
4507
|
font-size: 13px;
|
|
3931
4508
|
}
|
|
3932
4509
|
/* The API rail is the tier chips alone on a phone: its resources need the column the
|
|
3933
|
-
rail no longer has.
|
|
4510
|
+
rail no longer has. */
|
|
3934
4511
|
.api-filter {
|
|
3935
4512
|
max-width: none;
|
|
3936
4513
|
}
|
|
3937
|
-
.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 {
|
|
3938
4517
|
display: none;
|
|
3939
4518
|
}
|
|
3940
4519
|
.api-nav a.api-nav-tier {
|
|
@@ -3943,25 +4522,6 @@ button.api-filter-clear svg {
|
|
|
3943
4522
|
.api-nav a.api-nav-tier ~ a.api-nav-tier {
|
|
3944
4523
|
margin-top: 0;
|
|
3945
4524
|
}
|
|
3946
|
-
.api-route-sum {
|
|
3947
|
-
grid-template-columns: 62px minmax(0, 1fr) 20px;
|
|
3948
|
-
gap: 4px 10px;
|
|
3949
|
-
}
|
|
3950
|
-
.api-route-line {
|
|
3951
|
-
grid-column: 2;
|
|
3952
|
-
grid-row: 2;
|
|
3953
|
-
white-space: normal;
|
|
3954
|
-
}
|
|
3955
|
-
.api-chev {
|
|
3956
|
-
grid-column: 3;
|
|
3957
|
-
grid-row: 1;
|
|
3958
|
-
}
|
|
3959
|
-
.api-route[open] .api-route-line {
|
|
3960
|
-
display: none;
|
|
3961
|
-
}
|
|
3962
|
-
.api-route-detail {
|
|
3963
|
-
padding: 0 4px 14px;
|
|
3964
|
-
}
|
|
3965
4525
|
/* inline-flex centres the chip's text in its 32px; the gap stands in for the space
|
|
3966
4526
|
between the label and its ↗ that flex drops. */
|
|
3967
4527
|
.rail-link {
|
|
@@ -4209,7 +4769,8 @@ button.api-filter-clear svg {
|
|
|
4209
4769
|
gap: 10px;
|
|
4210
4770
|
}
|
|
4211
4771
|
|
|
4212
|
-
/* Setup
|
|
4772
|
+
/* Setup steps: the first-run checklist, and the regular dashboard's Finish setting up card
|
|
4773
|
+
(one component, two layouts). The card is the compact one. */
|
|
4213
4774
|
.setup-title {
|
|
4214
4775
|
margin-top: 0;
|
|
4215
4776
|
}
|
|
@@ -4282,6 +4843,20 @@ button.api-filter-clear svg {
|
|
|
4282
4843
|
color: var(--muted);
|
|
4283
4844
|
word-break: break-all;
|
|
4284
4845
|
}
|
|
4846
|
+
.setup-card {
|
|
4847
|
+
margin-bottom: 20px;
|
|
4848
|
+
}
|
|
4849
|
+
.setup-card .setup-title {
|
|
4850
|
+
font-size: 16px;
|
|
4851
|
+
}
|
|
4852
|
+
.setup-card .setup-steps li {
|
|
4853
|
+
padding: 8px 2px;
|
|
4854
|
+
}
|
|
4855
|
+
.setup-card .setup-steps li::before {
|
|
4856
|
+
width: 22px;
|
|
4857
|
+
height: 22px;
|
|
4858
|
+
font-size: 12px;
|
|
4859
|
+
}
|
|
4285
4860
|
|
|
4286
4861
|
/* ============================================================
|
|
4287
4862
|
Settings page. Grouped by intent, made visible: each section
|
|
@@ -4365,12 +4940,22 @@ button.copied svg {
|
|
|
4365
4940
|
font-weight: 600;
|
|
4366
4941
|
color: var(--muted);
|
|
4367
4942
|
}
|
|
4368
|
-
|
|
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 {
|
|
4369
4957
|
margin: 0;
|
|
4370
4958
|
overflow-x: auto;
|
|
4371
|
-
background: var(--bg);
|
|
4372
|
-
border: 1px solid var(--line);
|
|
4373
|
-
border-radius: 6px;
|
|
4374
4959
|
padding: 10px 12px;
|
|
4375
4960
|
font: 12.5px / 1.5 var(--mono);
|
|
4376
4961
|
}
|
|
@@ -4378,6 +4963,17 @@ button.copied svg {
|
|
|
4378
4963
|
.pub-row button.copy-btn {
|
|
4379
4964
|
flex: 0 0 auto;
|
|
4380
4965
|
}
|
|
4966
|
+
/* In a sent post's link field, Copy is a segment of the field like Open: square, full height,
|
|
4967
|
+
and divided from the address, so its hover fills the segment rather than a chip inside it. */
|
|
4968
|
+
.rec-link button.copy-btn {
|
|
4969
|
+
padding: 0 11px;
|
|
4970
|
+
border-left: 1px solid var(--line);
|
|
4971
|
+
border-radius: 0;
|
|
4972
|
+
background: var(--card);
|
|
4973
|
+
}
|
|
4974
|
+
.rec-link button.copy-btn:hover {
|
|
4975
|
+
background: var(--hover);
|
|
4976
|
+
}
|
|
4381
4977
|
|
|
4382
4978
|
/* --- groups: the sections kept together by whom they serve (DESIGN §3). A group has no
|
|
4383
4979
|
heading, since each section's title already says what it is; a wider gap than the one
|
|
@@ -6135,7 +6731,7 @@ body.has-savebar .content {
|
|
|
6135
6731
|
.subs-wrap {
|
|
6136
6732
|
container-type: inline-size;
|
|
6137
6733
|
}
|
|
6138
|
-
@container (max-width:
|
|
6734
|
+
@container (max-width: 609px) {
|
|
6139
6735
|
.list-table.subs-table.stacks,
|
|
6140
6736
|
.list-table.subs-table.stacks tbody,
|
|
6141
6737
|
.list-table.subs-table.stacks tr {
|
|
@@ -6176,10 +6772,7 @@ body.has-savebar .content {
|
|
|
6176
6772
|
.list-table.subs-table.stacks td.empty {
|
|
6177
6773
|
display: none;
|
|
6178
6774
|
}
|
|
6179
|
-
/* Subscribers: status pill ·
|
|
6180
|
-
.subs-table td.how::before {
|
|
6181
|
-
content: " · ";
|
|
6182
|
-
}
|
|
6775
|
+
/* Subscribers: status pill · joined <date>. */
|
|
6183
6776
|
.subs-table td.joined::before {
|
|
6184
6777
|
content: " · joined ";
|
|
6185
6778
|
}
|
|
@@ -6307,8 +6900,10 @@ body.has-savebar .content {
|
|
|
6307
6900
|
font-weight: 600;
|
|
6308
6901
|
margin: 14px 0 4px;
|
|
6309
6902
|
}
|
|
6310
|
-
/*
|
|
6311
|
-
|
|
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 {
|
|
6312
6907
|
color: var(--warn-fg);
|
|
6313
6908
|
background: var(--warn-bg);
|
|
6314
6909
|
border: 1px solid var(--warn-line);
|
|
@@ -6349,9 +6944,73 @@ body.has-savebar .content {
|
|
|
6349
6944
|
gap: 14px;
|
|
6350
6945
|
margin-top: 18px;
|
|
6351
6946
|
}
|
|
6352
|
-
/*
|
|
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. */
|
|
6353
6949
|
.sub-title {
|
|
6354
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;
|
|
6355
7014
|
}
|
|
6356
7015
|
.imp-dl,
|
|
6357
7016
|
.sub-dl {
|