/* The site's one stylesheet. Its tokens are the tool's own, so a page here and a page in the tool
   are recognisably the same product; there is far less of it because a marketing page has far
   fewer parts than a data table. Two themes, light unless the reader asks for dark. */
:root {
    --accent-rgb: 91 75 214;
    --ink-rgb: 28 32 36;
    --ink: rgb(var(--ink-rgb));
    --ink-soft: #5b6570;
    --accent: rgb(var(--accent-rgb));
    --accent-strong: #4c3fb4;
    --on-fill: #fff;
    --danger: #b32121;
    --ground: #fff;
    --surface: #f7f8fa;
    --rule: #e3e6ea;
    --field-rule: #c4cad2;
    /* What lifts a tile or a card off the ground, the tool's own: a hair of shadow under it, so a
       sheet sits on the page rather than being outlined on it. Made of the ink's channels here,
       and of black in the dark theme, where the ink turns light and a shadow must not. */
    --shadow-rgb: 28 32 36;
    --shadow-card: 0 1px 2px -1px rgb(var(--shadow-rgb) / 0.12), 0 2px 4px -2px rgb(var(--shadow-rgb) / 0.1);
    color-scheme: light;
}

/* The dark theme, as token values and nothing else: every rule below draws through the tokens, so
   none is repeated here. Two states, not the tool's three. The tool follows the operating system
   until told otherwise; this site is light until told otherwise, so the reader's choice is the
   one thing that turns it dark, and the operating system's preference is not consulted. The
   choice is stamped on the root before the first paint (ThemeStamp) and kept by site.js.

   --on-fill is inverted rather than darkened, as in the tool: a filled control is a light violet
   here, and white text on it would be unreadable where dark text is not. color-scheme with it,
   so the browser draws its own controls and the scrollbar in the theme the page is in. */
:root[data-theme="dark"] {
    --accent-rgb: 155 140 255;
    --ink-rgb: 228 231 235;
    --ink-soft: #98a1ac;
    --accent-strong: #b3a6ff;
    --on-fill: #14171c;
    --danger: #ef6b6b;
    --ground: #14171c;
    --surface: #1a1e24;
    --rule: #282e36;
    --field-rule: #3a424d;
    /* Stronger than in the light, as in the tool: a black shadow on a dark ground has less to
       show against. */
    --shadow-rgb: 0 0 0;
    --shadow-card: 0 1px 2px -1px rgb(var(--shadow-rgb) / 0.5), 0 2px 4px -2px rgb(var(--shadow-rgb) / 0.4);
    color-scheme: dark;
}

*, *::before, *::after {
    box-sizing: border-box;
}

html {
    font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    font-size: 16px;
    line-height: 1.5;
    color: var(--ink);
    background: var(--ground);
    -webkit-text-size-adjust: 100%;
}

body {
    margin: 0;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
}

a {
    color: var(--accent);
    text-decoration: none;
}

a:hover, a:focus-visible {
    text-decoration: underline;
}

:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

code, pre {
    font-family: ui-monospace, "Cascadia Mono", "SF Mono", Menlo, Consolas, monospace;
    font-size: 0.9375em;
}

pre {
    padding: 1rem;
    overflow-x: auto;
    background: var(--surface);
    border: 1px solid var(--rule);
    border-radius: 6px;
}

/* A tile holding something to take away, a command or a password (Components/Snippet.razor): the
   box above, and the copy button in its top corner, over the box's padding rather than in a strip
   of its own, so the tile is the box and nothing more. The box keeps a space clear under the
   button so the first line does not run beneath it; a longer line scrolls, as any pre does. */
.snippet {
    position: relative;
}

.snippet pre {
    padding-right: 5rem;
}

.snippet .copy {
    position: absolute;
    top: 0.5rem;
    right: 0.5rem;
    padding: 0.2rem 0.6rem;
    border: 1px solid var(--rule);
    border-radius: 6px;
    background: var(--ground);
    color: var(--accent);
    font: inherit;
    font-size: 0.8125rem;
    font-weight: 600;
    cursor: pointer;
}

.snippet .copy:hover {
    border-color: var(--accent);
    color: var(--accent-strong);
}

/* Said outright: the button is rendered hidden until site.js shows it, and position above would
   otherwise outweigh the browser's own rule for the attribute. */
.snippet .copy[hidden] {
    display: none;
}

/* The ring, the tool's own, drawn from a border with one side left out: three quarters of a circle
   turning is read as waiting. Sized in em and inked in currentColor so it belongs to the word it
   stands in front of. */
.spinner {
    animation: spinner-turn 0.8s linear infinite;
    border: 2px solid currentColor;
    border-radius: 50%;
    border-top-color: transparent;
    display: inline-block;
    flex: none;
    height: 1em;
    width: 1em;
}

@keyframes spinner-turn {
    to {
        transform: rotate(1turn);
    }
}

/* Slowed rather than stopped for a reader who has asked for less movement: the turning is the only
   thing saying the wait is still going somewhere. */
@media (prefers-reduced-motion: reduce) {
    .spinner {
        animation-duration: 3s;
    }
}

/* The bar: the mark and the name at one end, the account at the other, and the site's places
   centred between them, each behind a mark of its own. The two ends are given the same share of
   the width so the menu sits in the middle of the window rather than of what is left of it. On a
   narrow window both menus fold behind one button beside the brand and open under it as a list;
   without a script to drive the button they stay in view, wrapped. */
header {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem 1.5rem;
    padding: 0.75rem 1rem;
    border-bottom: 1px solid var(--rule);
}

/* Given focus so a jump back to the top of the page lands on it, not because anyone tabbed to it:
   the ring would be drawn around the whole bar, which the reader did not choose. See BackToTop. */
header:focus {
    outline: none;
}

header .brand {
    display: inline-flex;
    flex: 1 1 0;
    align-items: center;
    font-size: 1.125rem;
}

/* The full logo, sized by its height as the mark alone was, so the lens is drawn as large as
   before and the name beside it follows the drawing's own proportions. */
header .mark {
    height: 3em;
    width: auto;
}

/* One drawing per theme; the other is out of the page altogether, so a screen reader hears the
   name once. */
header .brand .for-dark,
:root[data-theme="dark"] header .brand .for-light {
    display: none;
}

:root[data-theme="dark"] header .brand .for-dark {
    display: block;
}

/* The wrapper is there for the button to point at; it takes no part in the wide layout. */
header .menus {
    display: contents;
}

header .menu-toggle {
    display: none;
    padding: 0.25rem;
    border: 1px solid var(--rule);
    border-radius: 6px;
    background: none;
    color: var(--ink);
    cursor: pointer;
}

header .menu-toggle svg {
    display: block;
    width: 1.5rem;
    height: 1.5rem;
}

header .menu-toggle .cross,
header .menu-toggle[aria-expanded="true"] .bars {
    display: none;
}

/* The theme button, drawn as the menu button is so the two read as a pair on a narrow window,
   and quiet on a wide one: the bar's other marks are soft until they matter, and so is this.
   Which mark and which words it shows is the theme's to say, through the stamp on the root, so
   the button is right in either theme with no script to redraw it. */
header .theme-toggle {
    display: block;
    padding: 0.25rem;
    border: 1px solid var(--rule);
    border-radius: 6px;
    background: none;
    color: var(--ink-soft);
    cursor: pointer;
}

header .theme-toggle:hover {
    color: var(--ink);
}

header .theme-toggle .glyph {
    display: block;
    width: 1.5rem;
    height: 1.5rem;
}

header .theme-toggle .for-dark,
:root[data-theme="dark"] header .theme-toggle .for-light {
    display: none;
}

:root[data-theme="dark"] header .theme-toggle .glyph.for-dark {
    display: block;
}

:root[data-theme="dark"] header .theme-toggle .visually-hidden.for-dark {
    display: inline;
}

header .menu-toggle[aria-expanded="true"] .cross {
    display: block;
}

header nav {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 1rem;
}

header nav a {
    color: var(--ink-soft);
}

header nav a.active {
    color: var(--ink);
    font-weight: 600;
}

header nav.site {
    justify-content: center;
}

/* Every item on the bar, the account's as much as the site's, is a mark and then a word, on
   whichever element carries it: a link, the heading of the account's menu, the sign-out button. */
header nav a,
header nav summary,
header nav button.link {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
}

header nav .glyph {
    flex: none;
    width: 1rem;
    height: 1rem;
}

header nav a.active .glyph,
header nav summary.active .glyph {
    color: var(--accent);
}

header nav.account {
    flex: 1 1 0;
    justify-content: flex-end;
}

/* The account signed in: a menu under the name, drawn the way the tool draws its operator's. The
   panel hangs from the right edge, being the last thing on the bar, so that a sheet opening from
   it stays on the page. */
header nav.account .menu {
    position: relative;
}

header nav.account summary {
    color: var(--ink-soft);
    cursor: pointer;
    list-style: none;
}

/* Safari draws its own marker through list-style. */
header nav.account summary::-webkit-details-marker {
    display: none;
}

header nav.account summary:hover {
    text-decoration: underline;
}

header nav.account summary.active {
    color: var(--ink);
    font-weight: 600;
}

/* A disclosure caret, turned over when the menu is open: the affordance that this is a menu
   rather than a link. */
header nav.account summary::after {
    content: "";
    width: 0;
    height: 0;
    border-left: 0.3rem solid transparent;
    border-right: 0.3rem solid transparent;
    border-top: 0.3rem solid currentColor;
}

header nav.account .menu[open] summary::after {
    transform: rotate(180deg);
}

header nav.account .submenu {
    position: absolute;
    right: 0;
    top: calc(100% + 0.5rem);
    z-index: 10;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    min-width: 9rem;
    padding: 0.6rem 0.75rem;
    background: var(--surface);
    border: 1px solid var(--rule);
    border-radius: 6px;
}

/* The one button among links, drawn as the links are: it is a way out beside a place to go, and
   the accent it wears elsewhere would make it read as the point of the menu. */
header nav.account .submenu button.link {
    color: var(--ink-soft);
    text-align: left;
}

@media (max-width: 48rem) {
    header .menu-toggle {
        display: block;
    }

    header .menus {
        display: flex;
        flex-direction: column;
        flex-basis: 100%;
        gap: 0.75rem;
        padding-top: 0.5rem;
        border-top: 1px solid var(--rule);
    }

    header[data-menus="closed"] .menus {
        display: none;
    }

    /* The menus are the last thing in the bar's markup but the first to fold under it, so they
       are sent after the theme button, which stays on the top row beside the menu button. */
    header .menus {
        order: 1;
    }

    header nav.site,
    header nav.account {
        flex-direction: column;
        align-items: stretch;
        gap: 0;
    }

    header nav a,
    header nav summary,
    header nav.account form,
    header nav.account button.link {
        display: flex;
        width: 100%;
        padding: 0.5rem 0.25rem;
        text-align: left;
    }

    /* Once the bar is a column there is no bar for a sheet to hang from: the account's pages
       open in the list itself, set in under the name, like the rest of the fold. */
    header nav.account .submenu {
        position: static;
        gap: 0;
        min-width: 0;
        padding: 0 0 0 1.25rem;
        background: none;
        border: 0;
    }

    /* The caret rides the far edge of its row, as on a list of rows. */
    header nav.account summary::after {
        margin-left: auto;
    }
}

header nav.account form {
    display: inline;
}

button.link {
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    color: var(--accent);
    cursor: pointer;
}

button.link:hover {
    text-decoration: underline;
}

main {
    flex: 1;
    width: 100%;
    max-width: 60rem;
    margin: 0 auto;
    padding: 2rem 1rem 3rem;
}

h1 {
    font-size: 2rem;
    line-height: 1.2;
    margin: 0 0 1rem;
}

/* Given focus so a navigation lands on it, rather than because anyone tabbed to it: the ring would
   be pointing at something the reader did not choose. The tool does the same. */
h1:focus {
    outline: none;
}

h2 {
    font-size: 1.25rem;
    margin: 2rem 0 0.5rem;
}

.lead p {
    font-size: 1.125rem;
    color: var(--ink-soft);
    max-width: 44rem;
}

.actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    margin-top: 1.5rem;
}

.button, .btn {
    display: inline-block;
    padding: 0.5rem 1rem;
    border: 1px solid var(--accent);
    border-radius: 6px;
    background: var(--accent);
    color: var(--on-fill);
    font: inherit;
    font-weight: 600;
    cursor: pointer;
}

.button:hover, .btn:hover {
    background: var(--accent-strong);
    border-color: var(--accent-strong);
    text-decoration: none;
}

.button.quiet {
    background: transparent;
    color: var(--accent);
}

.button.quiet:hover {
    background: var(--surface);
    color: var(--accent-strong);
}

/* A button that has been pressed and is waiting on the answer, put into this state by site.js.
   After .button, whose inline-block it has to outweigh. Inline-flex so the ring and the word are
   centred against each other; the size is left alone, because the button is already on the page
   and must not resize under the pointer that pressed it. */
.busy {
    align-items: center;
    display: inline-flex;
    gap: 0.5rem;
}

.busy:disabled {
    cursor: default;
    opacity: 0.75;
}

.points {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
    gap: 1rem;
    margin-top: 3rem;
}

.points article {
    padding: 1.25rem;
    background: var(--surface);
    border: 1px solid var(--rule);
    border-radius: 8px;
    box-shadow: var(--shadow-card);
}

.points h2 {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0 0 0.5rem;
    font-size: 1.0625rem;
}

.points .glyph {
    flex: none;
    width: 1.125rem;
    height: 1.125rem;
    color: var(--accent);
}

.points p {
    margin: 0;
    color: var(--ink-soft);
}

table {
    width: 100%;
    border-collapse: collapse;
    margin: 1rem 0;
}

th, td {
    padding: 0.5rem 0.75rem;
    text-align: left;
    vertical-align: top;
    border-bottom: 1px solid var(--rule);
}

th {
    font-weight: 600;
}

/* The way back to the top, the last thing in the page's column: held to the far end, as the
   tool's is under its tables, and as wide as its own words so the click target is the link and
   not the empty line beside it. Held off the page's last word by the gap a heading takes, and
   quiet, since it acts on where the page is read from rather than on what is in it. */
.back-to-top {
    align-items: center;
    color: var(--ink-soft);
    display: flex;
    gap: 0.35rem;
    margin: 2rem 0 0 auto;
    width: fit-content;
}

.back-to-top:hover {
    color: var(--accent);
    text-decoration: none;
}

/* Sized in ems so the mark follows the words beside it, at the size the menu's marks are. */
.back-to-top .glyph {
    flex: none;
    width: 1.1em;
    height: 1.1em;
}

/* One row: the copyright and the places after it at one end, the mark at the other. The same
   height as the tool's footer: its padding and its small text size, so the two read as one
   product. */
footer {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    gap: 1rem;
    padding: 1rem;
    border-top: 1px solid var(--rule);
    color: var(--ink-soft);
    font-size: 0.8125rem;
}

footer p {
    margin: 0;
}

/* The copyright's line, as the tool's: the holder, then a dot and a place, wrapping on a narrow
   window as one line of words would. */
footer .copyright {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
}

footer .copyright a {
    color: var(--ink-soft);
}

footer .copyright a:hover {
    color: var(--ink);
}

/* The dot between one thing and the next, faint because it separates rather than says anything. */
footer .separator {
    opacity: 0.6;
}

/* Where the owner is: the mark, at the far end. */
footer .reach {
    display: inline-flex;
    align-items: center;
}

footer .mark {
    display: inline-flex;
    color: var(--ink-soft);
}

footer .mark:hover {
    color: var(--ink);
}

footer .mark svg {
    width: 1.1em;
    height: 1.1em;
}

/* The documentation, laid out as a reference is: the body is the whole width of the window, the
   sidebar down the left on its own surface and the page beside it, measured for reading; on a
   narrow window the sidebar is a row of the pages over the page. The shapes a rendered page has
   that a marketing page has not, a callout, a contents list, a screenshot, a heading three deep,
   are drawn as the builder's are: tiles on the surface, captions in small capitals, marks beside
   words. The class names are the ones the pages were written with, kramdown's, kept so the
   source needs no edit. */
main:has(.docs) {
    max-width: none;
    padding: 0;
}

.docs {
    display: grid;
    grid-template-columns: 17rem minmax(0, 1fr);
    align-items: stretch;
}

.docs .glyph {
    flex: none;
    width: 1em;
    height: 1em;
}

/* The sidebar: headed in small capitals, the pages under it, the open one marked at its edge in
   the accent. It stays in view as the page scrolls, and scrolls itself if it is the taller. */
.docs-nav {
    padding: 1.5rem 1rem 1.5rem 1.5rem;
    border-right: 1px solid var(--rule);
    background: var(--surface);
}

.docs-nav ul {
    position: sticky;
    top: 1.5rem;
    max-height: calc(100vh - 3rem);
    overflow-y: auto;
    list-style: none;
    margin: 0;
    padding: 0;
}

.docs-nav-title {
    position: sticky;
    top: 0;
    margin: 0 0 0.75rem 0.875rem;
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-soft);
}

.docs-nav a {
    display: block;
    padding: 0.4375rem 0.75rem;
    border-left: 2px solid transparent;
    border-radius: 0 6px 6px 0;
    color: var(--ink-soft);
    font-size: 0.9375rem;
}

.docs-nav a:hover {
    background: rgb(var(--accent-rgb) / 0.06);
    color: var(--ink);
    text-decoration: none;
}

.docs-nav a.active {
    border-left-color: var(--accent);
    background: rgb(var(--accent-rgb) / 0.1);
    color: var(--ink);
    font-weight: 600;
}

/* The page: measured for reading, set in from the sidebar, the turns at its foot. */
.docs-page {
    max-width: 66rem;
    padding: 2rem 3rem 3rem;
}

.doc > .crumb {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    margin: 0 0 1rem;
    font-size: 0.8125rem;
    color: var(--ink-soft);
}

.doc > .crumb a {
    color: var(--ink-soft);
}

.doc > .crumb a:hover {
    color: var(--accent);
}

.doc > .crumb > span:last-child {
    color: var(--ink);
    font-weight: 600;
}

.doc h1 {
    margin: 0 0 1.5rem;
    padding-bottom: 1rem;
    border-bottom: 1px solid var(--rule);
    font-size: 2rem;
}

/* A section's heading stands under a rule, as the builder's steps do, so the page scans as its
   sections; the handle of a folded section is the same, and the contents' small heading is not. */
.doc h2 {
    margin: 2.5rem 0 0.75rem;
    padding-top: 1.5rem;
    border-top: 1px solid var(--rule);
    font-size: 1.375rem;
}

.doc h3 {
    margin: 1.75rem 0 0.5rem;
    font-size: 1.0625rem;
}

.doc h4 {
    margin: 1.5rem 0 0.5rem;
    font-size: 0.9375rem;
}

.doc .text-delta,
.doc h2.text-delta {
    margin: 1.5rem 0 0.5rem;
    padding: 0;
    border: 0;
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-soft);
}

/* The contents at the head of the page: a tile, two columns wide when the window allows. */
.doc .toc {
    margin: 0 0 1.5rem;
    padding: 0.75rem 1rem;
    border: 1px solid var(--rule);
    border-radius: 8px;
    background: var(--surface);
}

.doc .toc ol {
    margin: 0;
    padding-left: 1.25rem;
}

.doc .toc > ol {
    columns: 2;
    column-gap: 2rem;
}

.doc .toc > ol > li {
    margin: 0.25rem 0;
    break-inside: avoid;
}

.doc img {
    max-width: 100%;
    height: auto;
    border: 1px solid var(--rule);
    border-radius: 6px;
}

/* A screenshot is drawn small, a link to itself: the prose is the page, and the picture is
   there to be opened. The cursor says so. */
.doc a.screenshot {
    display: block;
    max-width: 30rem;
    margin: 1rem 0;
    cursor: zoom-in;
}

.doc a.screenshot img {
    display: block;
    width: 100%;
    box-shadow: var(--shadow-card);
}

/* The picture at full size, over the page, opened by site.js from the thumbnail's link. The
   dialog is the whole viewport and closes on any click, since there is nothing in it but the
   picture; Escape closes it as any dialog. */
dialog.lightbox {
    width: 100vw;
    height: 100vh;
    max-width: none;
    max-height: none;
    margin: 0;
    padding: 2rem;
    border: 0;
    background: transparent;
    display: none;
    place-items: center;
    cursor: zoom-out;
}

dialog.lightbox[open] {
    display: grid;
}

dialog.lightbox::backdrop {
    background: rgba(0, 0, 0, 0.75);
}

dialog.lightbox img {
    max-width: 100%;
    max-height: calc(100vh - 4rem);
    border-radius: 6px;
    box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.5);
}

/* A folded section: its heading is the handle, marked with the arrow the questions carry. */
.doc details.section > summary {
    display: flex;
    align-items: center;
    gap: 1rem;
    cursor: pointer;
    list-style: none;
}

.doc details.section > summary::-webkit-details-marker {
    display: none;
}

.doc details.section > summary h2 {
    flex: 1;
}

.doc details.section > summary:hover h2 {
    color: var(--accent);
}

.doc details.section > summary::after {
    content: "";
    flex: none;
    width: 0;
    height: 0;
    margin-top: 1.5rem;
    border-left: 0.3rem solid transparent;
    border-right: 0.3rem solid transparent;
    border-top: 0.3rem solid currentColor;
    transition: transform 0.15s ease;
}

.doc details.section[open] > summary::after {
    transform: rotate(180deg);
}

/* A table is a tile: its head on the surface in small capitals, and a wide one scrolls within
   the page rather than widening it. */
.doc table {
    display: block;
    overflow-x: auto;
    margin: 1rem 0;
    border: 1px solid var(--rule);
    border-radius: 8px;
}

.doc th {
    background: var(--surface);
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ink-soft);
}

.doc pre code {
    font-size: 0.875em;
}

/* The files a README's directory holds: a tile each, with the mark for a download. */
.files {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
    gap: 0.5rem;
    list-style: none;
    margin: 0;
    padding: 0;
}

.files a {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0.75rem;
    border: 1px solid var(--rule);
    border-radius: 6px;
    background: var(--surface);
    color: var(--ink);
    font-family: ui-monospace, "Cascadia Mono", "SF Mono", Menlo, Consolas, monospace;
    font-size: 0.875rem;
}

.files a:hover {
    border-color: var(--accent);
    color: var(--accent);
    text-decoration: none;
}

.files .glyph {
    color: var(--accent);
}

/* The turns at the foot: the page before on the left, the page after on the right, each a tile
   naming the page it opens. */
.turns {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1rem;
    margin-top: 2.5rem;
    padding-top: 1.5rem;
    border-top: 1px solid var(--rule);
}

.turn {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.875rem 1rem;
    border: 1px solid var(--rule);
    border-radius: 8px;
    background: var(--surface);
    color: var(--ink);
}

.turn:hover {
    border-color: var(--accent);
    text-decoration: none;
}

.turn:hover .turn-title {
    color: var(--accent);
}

.turn.next {
    grid-column: 2;
    justify-content: flex-end;
    text-align: right;
}

.turn .caption {
    display: block;
    font-size: 0.75rem;
    letter-spacing: 0.02em;
    color: var(--ink-soft);
}

.turn-title {
    font-weight: 600;
}

.turn .glyph {
    width: 1.25em;
    height: 1.25em;
    color: var(--accent);
}

@media (max-width: 64rem) {
    .docs {
        grid-template-columns: 14rem minmax(0, 1fr);
    }

    .docs-page {
        padding: 1.5rem 1.5rem 2.5rem;
    }
}

@media (max-width: 48rem) {
    .docs {
        grid-template-columns: minmax(0, 1fr);
    }

    .docs-nav {
        padding: 0.5rem 1rem 0;
        border-right: 0;
        border-bottom: 1px solid var(--rule);
    }

    .docs-nav-title {
        display: none;
    }

    .docs-nav ul {
        position: static;
        display: flex;
        gap: 0.25rem;
        max-height: none;
        overflow-x: auto;
    }

    .docs-nav a {
        padding: 0.5rem 0.75rem;
        border-left: 0;
        border-bottom: 2px solid transparent;
        border-radius: 6px 6px 0 0;
        white-space: nowrap;
    }

    .docs-nav a.active {
        border-bottom-color: var(--accent);
        background: transparent;
    }

    .docs-page {
        padding: 1.25rem 1rem 2rem;
    }

    .doc .toc > ol {
        columns: 1;
    }

    .turns {
        grid-template-columns: minmax(0, 1fr);
    }

    .turn.next {
        grid-column: auto;
    }
}

/* The shop: the term toggle, the editions as tiles side by side with the list price struck through
   under the offer price and the way to buy at the foot, the questions; and the account's purchases
   beside Identity's pages. */
.term-toggle {
    display: flex;
    gap: 0.5rem;
    margin: 0 0 1rem;
}

.term-option {
    padding: 0.375rem 0.875rem;
    border: 1px solid var(--field-rule);
    border-radius: 6px;
    background: var(--ground);
    color: var(--ink);
    font: inherit;
    cursor: pointer;
}

.term-option[aria-pressed="true"] {
    border-color: var(--accent);
    background: var(--accent);
    color: var(--on-fill);
}

/* Four across, two by two, then one under another: never three and a straggler. */
.editions {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 1rem;
    margin: 0 0 1.5rem;
}

@media (min-width: 56rem) {
    .editions {
        grid-template-columns: repeat(4, minmax(0, 1fr));
    }
}

@media (max-width: 32rem) {
    .editions {
        grid-template-columns: minmax(0, 1fr);
    }
}

.edition {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    padding: 1.25rem;
    background: var(--surface);
    border: 1px solid var(--rule);
    border-radius: 8px;
    box-shadow: var(--shadow-card);
}

.edition h3 {
    margin: 0;
    font-size: 1.0625rem;
}

.edition .price {
    margin: 0;
}

.edition .price-launch {
    display: block;
    font-size: 1.75rem;
    line-height: 1.1;
}

/* What the edition covers fills the tile, so the buttons line up along the bottom row. */
.edition ul {
    flex: 1;
    margin: 0;
    padding: 0;
    list-style: none;
    color: var(--ink-soft);
}

.edition li + li {
    margin-top: 0.375rem;
}

.edition .button, .edition .buy {
    text-align: center;
}

.price-launch {
    font-weight: 600;
}

.price-list {
    color: var(--ink-soft);
}

.price-term {
    display: block;
    font-size: 0.75em;
    color: var(--ink-soft);
}

.buy {
    display: inline-block;
    padding: 0.5rem 1rem;
    border: 1px solid var(--accent);
    border-radius: 6px;
    background: var(--accent);
    color: var(--on-fill);
    font: inherit;
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
}

.buy:hover {
    background: var(--accent-strong);
    border-color: var(--accent-strong);
}

.note {
    padding: 1rem 1.25rem;
    border-left: 4px solid var(--accent);
    border-radius: 6px;
    background: var(--surface);
}

/* The FAQ: one folded row per question, divided by rules, so the section scans as the questions
   alone. The whole question line is the handle, with the header menu's caret at its far edge as the
   affordance that it opens. */
.faq .question {
    border-top: 1px solid var(--rule);
}

.faq .question:last-child {
    border-bottom: 1px solid var(--rule);
}

.faq summary {
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: 0.85rem 0;
    font-weight: 600;
    cursor: pointer;
    list-style: none;
}

/* Safari draws its own marker through list-style. */
.faq summary::-webkit-details-marker {
    display: none;
}

.faq summary:hover {
    color: var(--accent);
}

.faq summary::after {
    content: "";
    flex: none;
    margin-left: auto;
    width: 0;
    height: 0;
    border-left: 0.3rem solid transparent;
    border-right: 0.3rem solid transparent;
    border-top: 0.3rem solid currentColor;
    transition: transform 0.15s ease;
}

.faq .question[open] summary::after {
    transform: rotate(180deg);
}

.faq .question p {
    margin: 0 0 1rem;
    color: var(--ink-soft);
}

.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
}

.purchases td, .purchases th {
    white-space: nowrap;
}

/* The manifest builder: the form beside the manifest it writes, which stays in view as the form
   scrolls; on a narrow window, one under the other. */
main:has(.builder) {
    max-width: 90rem;
}

.builder {
    display: grid;
    grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
    gap: 2rem;
    align-items: start;
}

@media (max-width: 64rem) {
    .builder {
        grid-template-columns: minmax(0, 1fr);
    }
}

/* The marks are the header's: the same grid, the same stroke, sized to the words they ride
   with. One beside a word is decoration; one alone is a button that names itself. */
.builder .glyph {
    flex: none;
    width: 1em;
    height: 1em;
}

/* One service is one card: its number and name across the top, with the example it started
   from, and the three steps under them, parted by rules rather than boxed each in its own. */
.builder .service {
    margin-bottom: 1.5rem;
    border: 1px solid var(--rule);
    border-radius: 10px;
    background: var(--ground);
    box-shadow: var(--shadow-card);
}

.builder .service-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.75rem 1.25rem;
    padding: 1rem 1.25rem;
    border-bottom: 1px solid var(--rule);
    border-radius: 10px 10px 0 0;
    background: var(--surface);
}

.builder .service-title {
    display: flex;
    flex: 1 1 12rem;
    align-items: center;
    gap: 0.625rem;
    min-width: 0;
    margin: 0;
    font-size: 1.125rem;
    overflow-wrap: anywhere;
}

/* The service's number and the steps' are the same badge, so a reader sees one order throughout. */
.builder .service-number,
.builder .step-number {
    display: inline-flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: 1.625rem;
    height: 1.625rem;
    border-radius: 50%;
    background: var(--accent);
    color: var(--on-fill);
    font-size: 0.8125rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

.builder .start {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0;
    white-space: nowrap;
}

.builder .start select {
    width: auto;
    margin: 0;
    font-weight: 600;
}

.builder .service-head .icon {
    margin-left: auto;
}

/* A step: its numbered title, a line saying what it is for, and its fields, under a rule that
   parts it from the step before. */
.builder .step {
    padding: 0.25rem 1.25rem 1.25rem;
}

.builder .step + .step {
    border-top: 1px solid var(--rule);
}

.builder .step-title {
    display: flex;
    align-items: center;
    gap: 0.625rem;
    margin: 1rem 0 0;
    font-size: 1.0625rem;
    font-weight: 600;
}

.builder .hint {
    margin: 0;
    font-size: 0.8125rem;
    font-weight: 400;
    color: var(--ink-soft);
}

.builder .step-hint {
    margin: 0.25rem 0 0.5rem;
}

/* A group within a step is headed in small capitals, with its hint beside, under a dashed rule. */
.builder .group {
    margin-top: 1.25rem;
    padding-top: 1rem;
    border-top: 1px dashed var(--rule);
}

.builder .group-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.25rem 0.75rem;
    margin-bottom: 0.25rem;
}

.builder .group-head h4 {
    margin: 0;
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-soft);
}

/* A part of the domain, and a snapshot source: each a tile on the surface, named, holding its
   rows and the button that adds one. */
.builder .part,
.builder .snapshot {
    margin: 0.75rem 0 0;
    padding: 0.75rem 1rem 1rem;
    border: 1px solid var(--rule);
    border-radius: 8px;
    background: var(--surface);
}

.builder .part-head,
.builder .snapshot-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.25rem 0.75rem;
}

.builder .snapshot-head {
    justify-content: space-between;
}

.builder .part-head h5,
.builder .snapshot-head h5 {
    margin: 0;
    font-size: 0.9375rem;
    font-weight: 600;
}

.builder .empty {
    margin: 0.5rem 0 0;
    font-size: 0.875rem;
    font-style: italic;
    color: var(--ink-soft);
}

.builder label {
    display: block;
    margin: 0.5rem 0;
    font-size: 0.875rem;
    color: var(--ink-soft);
}

.builder label.check {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    color: var(--ink);
}

.builder input:not([type="checkbox"]), .builder select, .builder textarea {
    display: block;
    width: 100%;
    margin-top: 0.25rem;
    padding: 0.375rem 0.5rem;
    border: 1px solid var(--field-rule);
    border-radius: 6px;
    background: var(--ground);
    color: var(--ink);
    font: inherit;
    font-size: 0.9375rem;
}

.builder input:focus-visible, .builder select:focus-visible, .builder textarea:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}

.builder .columns {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
    gap: 0 1rem;
}

.builder .columns .wide {
    grid-column: 1 / -1;
}

/* The word over a field in a row, where there is no room for a label beside it. */
.builder .caption {
    display: block;
    margin-bottom: 0.25rem;
    font-size: 0.75rem;
    letter-spacing: 0.02em;
    color: var(--ink-soft);
}

.builder .columns-caption {
    margin: 0.75rem 0 0;
    font-weight: 600;
}

/* A selector is a grid: what it picks by and the name on one row, how a model is addressed on
   the next, and the mark at the end of the first. A naming row is two fields wide and has no
   mark. On a phone, one field to a row. */
.builder .selector,
.builder .naming {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr) auto;
    gap: 0.5rem 0.75rem;
    align-items: end;
    margin: 0.5rem 0 0;
}

.builder .naming {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
}

.builder .selector > label,
.builder .naming > label {
    min-width: 0;
    margin: 0;
}

.builder .selector > .icon {
    grid-column: 3;
    grid-row: 1;
}

@media (max-width: 32rem) {
    .builder .selector {
        grid-template-columns: minmax(0, 1fr) auto;
    }

    .builder .naming {
        grid-template-columns: minmax(0, 1fr);
    }

    .builder .selector > .icon {
        grid-column: 2;
    }
}

.builder .selector input, .builder .selector select,
.builder .naming input, .builder .naming select {
    margin-top: 0;
}

/* The two kinds of button. One that adds says so in a word with a plus before it, drawn in
   outline so it reads as an action and not as a field. One that takes away is a mark alone,
   quiet until the pointer reaches it, when it turns the colour of what it would do. */
.builder .add {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
    margin-top: 0.75rem;
    padding: 0.375rem 0.75rem;
    border: 1px dashed var(--accent);
    border-radius: 6px;
    background: transparent;
    color: var(--accent);
    font: inherit;
    font-size: 0.875rem;
    font-weight: 600;
    cursor: pointer;
}

.builder .add:hover,
.builder .add:focus-visible {
    border-style: solid;
    background: rgb(var(--accent-rgb) / 0.08);
}

.builder .add-service {
    justify-content: center;
    width: 100%;
    margin: 0;
    padding: 0.75rem;
    font-size: 1rem;
}

.builder .icon {
    display: inline-flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: 2.125rem;
    height: 2.125rem;
    padding: 0;
    border: 1px solid var(--field-rule);
    border-radius: 6px;
    background: var(--ground);
    color: var(--ink-soft);
    font-size: 1rem;
    cursor: pointer;
}

.builder .icon:hover,
.builder .icon:focus-visible {
    border-color: var(--danger);
    color: var(--danger);
}

.builder details {
    margin: 0.75rem 0 0;
    padding: 0.5rem 0.75rem;
    border: 1px solid var(--rule);
    border-radius: 8px;
}

.builder summary {
    font-size: 0.875rem;
    color: var(--ink-soft);
    cursor: pointer;
}

.builder details[open] summary {
    margin-bottom: 0.25rem;
    color: var(--ink);
}

/* The manifest: named as the file it is, the two ways to take it beside the name, and the text. */
.builder-output {
    position: sticky;
    top: 1rem;
}

.builder-output .output-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    margin-bottom: 0.75rem;
}

.builder-output .file-name {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0;
    font-family: ui-monospace, "Cascadia Mono", "SF Mono", Menlo, Consolas, monospace;
    font-size: 1.0625rem;
    font-weight: 600;
}

.builder-output .file-name .glyph {
    width: 1.25em;
    height: 1.25em;
    color: var(--accent);
}

.builder-output .actions {
    margin: 0;
}

.builder-output .actions .button {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
}

.builder-output pre {
    max-height: 75vh;
    margin: 0 0 1rem;
    font-size: 0.8125rem;
}

.builder-output .note {
    margin: 0 0 1rem;
    padding: 0.625rem 0.875rem;
    border-left: 3px solid var(--danger);
    border-radius: 4px;
    background: var(--surface);
}

@media (max-width: 48rem) {
    .builder .service-head {
        padding: 0.875rem 1rem;
    }

    .builder .step {
        padding-right: 1rem;
        padding-left: 1rem;
    }

    .builder .start {
        flex: 1 1 100%;
        white-space: normal;
    }

    .builder .start select {
        flex: 1;
    }
}

/* Identity's pages are written against Bootstrap's class names. These are the few of them the
   pages use, given the site's own shapes, so the pages are usable without the framework they were
   drawn for. */
.account-page .row {
    display: flex;
    flex-wrap: wrap;
    gap: 2rem;
}

.account-page .col-md-4, .account-page .col-md-6 {
    flex: 1 1 18rem;
    max-width: 28rem;
}

.account-page .col-md-6.col-md-offset-2 {
    flex: 1 1 18rem;
}

.account-page hr {
    border: 0;
    border-top: 1px solid var(--rule);
    margin: 1rem 0;
}

.form-floating {
    display: flex;
    flex-direction: column-reverse;
    margin-bottom: 1rem;
}

.form-floating label {
    font-size: 0.875rem;
    color: var(--ink-soft);
    margin-bottom: 0.25rem;
}

.form-control, .form-select {
    width: 100%;
    padding: 0.5rem 0.75rem;
    border: 1px solid var(--field-rule);
    border-radius: 6px;
    background: var(--ground);
    color: var(--ink);
    font: inherit;
}

.form-control:focus {
    border-color: var(--accent);
    outline: 2px solid var(--accent);
    outline-offset: 0;
}

/* The forms' message: room to write, and the reader's to make taller. */
textarea.form-control {
    min-height: 12rem;
    resize: vertical;
}

/* The contact form's trap: off the page rather than display: none, which a bot may check for,
   and out of the flow, so the form is laid out as if it were not there. */
.trap {
    position: absolute;
    left: -100vw;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

/* What a form says is missing, under the field it is missing from. */
.validation-message {
    color: var(--danger);
    margin: -0.5rem 0 1rem;
}

.form-check {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 1rem;
}

.btn-primary, .btn-lg {
    width: auto;
}

.w-100 {
    width: 100%;
}

.text-danger, .validation-summary-errors {
    color: var(--danger);
}

.text-danger:empty {
    display: none;
}

.mb-3 {
    margin-bottom: 1rem;
}

/* The account's menu (Account/Manage/_Layout.cshtml), in the package's class names: a row of
   places across the top of the page, wrapping on a narrow window, over the rule the page's own
   heading starts from. */
.account-menu .nav {
    list-style: none;
    padding: 0 0 0.75rem;
    margin: 0 0 1.5rem;
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 0.5rem;
    border-bottom: 1px solid var(--rule);
}

.nav-pills .nav-link {
    display: block;
    padding: 0.375rem 0.75rem;
    border-radius: 6px;
    color: var(--ink-soft);
}

.nav-pills .nav-link.active {
    background: var(--accent);
    color: var(--on-fill);
}

.alert {
    padding: 0.75rem 1rem;
    border: 1px solid var(--rule);
    border-radius: 6px;
    background: var(--surface);
    box-shadow: var(--shadow-card);
    margin-bottom: 1rem;
}
