
/* Milligram has no card, list or alert primitives, so those are
    built here. Everything else (type, grid, inputs, buttons) is Milligram's.
    Both themes live in one token block via light-dark(). */
:root {
    color-scheme: light dark;

    --surface:    light-dark(#ffffff, #1f242b);
    --ink:        light-dark(#33383f, #e6e9ee);
    --line:       light-dark(#e3e7ec, #333a44);

    --primary:    light-dark(#4b6cc9, #86a2ee);
    --secondary:  light-dark(#606c76, #a4b0bd);
    --success:    light-dark(#2f9e5f, #4cbd7d);
    --danger:     light-dark(#cf4652, #ef8b93);
    --warning:    light-dark(#9a7418, #e3c268);

    /* A list's two gradient stops. The stops are the token rather than a
       finished gradient, because the direction depends on the box: a page is
       tall enough for a diagonal, a home row is short and wide and needs it
       left to right. Composed at each use rather than nested inside another
       custom property, so an inline override on <body> or on a row is certain
       to be picked up. Pages for a specific list set these inline; this
       neutral slate is the default for home and account, and is the one
       gradient that is themed: it sits behind --surface cards and inputs, so
       in dark mode it stays below them and they read as lifted rather than
       as dark holes. The dark range is bounded by that: the lighter stop
       cannot pass --surface's ~14.5% lightness or the gradient would cross
       it partway down the page and cards would blend into the band where
       they match. A list's own colours stay as chosen in both
       schemes, which is why --chrome stays unthemed white. */
    --bg-from: light-dark(hsl(216 10% 50%), hsl(216 14% 12%));
    --bg-to:   light-dark(hsl(232  9% 36%), hsl(232 16%  3%));

    /* Header chrome sits on the gradient, which is not themed, so it
       stays white in both colour schemes. Every run of text that sits
       straight on the gradient takes the same shadow, to lift it off the
       busy middle of the ramp. */
    --chrome:        rgba(255, 255, 255, .96);
    --chrome-dim:    rgba(255, 255, 255, .72);
    --chrome-shadow: 0 .1rem .3rem rgba(0, 0, 0, .2);

    /* Strength of every wash: .tone-* fills and button hovers. */
    --wash: 12%;

    /* Row metrics. The add input derives its indent from these,
        so its text stays on the same left edge as the titles. */
    --check-size:   1.7rem;
    --control-size: 3.4rem;
    /* The list icon shares the checkbox's box, so its column reads straight
       down and the gap beside it resolves to a plain --row-gap. Ionicons draw
       their art inset in the viewBox, so the glyph will not quite fill this;
       raising it trades that back for a tighter gap. .crumb subtracts any
       difference from its gap, so the name holds the title column either way.
       Keep it under --check-size + --row-gap, or that gap goes negative. */
    --crumb-icon:    var(--check-size);
    --row-gap:      .8rem;
    --row-pad:      1rem;

    /* The column every text run starts on: item titles, the add-item
       placeholder, the crumb label, the home row names and their group
       headings. Safe to compose from other tokens because all three inputs
       are set only here — unlike --bg-from/--bg-to, which pages override. */
    --title-col: calc(var(--row-pad) + var(--check-size) + var(--row-gap));

    /* Space between stacked controls inside a card. --row-gap is tuned for
       items sitting side by side in a row; a stack needs more air. */
    --form-gap: 1rem;

    /* The container's side gutter. The sticky row bleeds into it so the
       card shadows passing underneath stay covered. */
    --gutter:       1.5rem;
}

html {
    overflow-y:scroll;
}

body {
    min-height: 100vh;
    /* Breathing room above the header at rest. It needs no scroll animation:
       the header is sticky, so this simply scrolls away and the header pins
       flush to the top. And because the header repaints the same
       fixed-attachment gradient as the page, this space is visually
       indistinguishable from extra padding inside the header itself. */
    padding: var(--gutter) 0 0;
    color: var(--ink);
    background: linear-gradient(to bottom right, var(--bg-from), var(--bg-to)) fixed;
}

blockquote, dl, figure, form, ol, p, pre, table, ul {
    margin-bottom: var(--gutter);
}

.container {
    max-width: 60rem;
    padding: 0 var(--gutter);
}

/* ---- Header ---------------------------------------------------------
   The wordmark carries the identity and doubles as home; either side of it
   sits a quiet line for this list and this user, set like button text rather
   than a title, so the only loud thing here is the brand.

   One row, three columns: where you are, the brand, who you are. 1fr/auto/1fr
   keeps the wordmark centred on the page however wide the sides get, because
   the side columns stay equal to each other.

   It sticks to the top on scroll, repainting the page gradient so the cards
   passing underneath are hidden; because that gradient is fixed-attachment
   the repaint matches the page exactly and no seam shows. The negative inline
   margin is cancelled by equal padding: the background bleeds out to the
   container's edges, covering the card shadows, while the content stays on
   its columns. */
header {
    position: sticky;
    top: 0;
    z-index: 5;
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: 1rem;
    margin-bottom: 1.6rem;
    margin-inline: calc(-1 * var(--gutter));
    padding: .6rem var(--gutter);
    color: var(--chrome);
    text-shadow: var(--chrome-shadow);
    background: linear-gradient(to bottom right, var(--bg-from), var(--bg-to)) fixed;
}

/* Every link in the header is chrome on the gradient, so the colour is stated
   once here and .crumb and .account need none of their own. The states are
   spelled out because Milligram paints a:focus/a:hover at (0,1,1): a bare
   `header a` is only (0,0,2) and would lose to it, where `header a:hover` is
   (0,1,2) and wins. */
header a,
header a:focus,
header a:hover {
    color: inherit;
    text-decoration: none;
}

/* Lobster ships a single 400 weight, so 600 asks the browser to synthesise a
   heavier cut — deliberate here, it thickens the strokes a little. There is no
   italic cut either, which is why the lean is an explicit skew rather than
   font-style: italic, and that is what lets us set the angle.

   Colour and shadow are stated on the wordmark itself rather than inherited:
   the logged-out pages use it outside any header, where it would otherwise
   fall through to Milligram's purple anchor colour. */
.wordmark {
    display: block;
    width: fit-content;
    margin: 0;
    font-family: 'Lobster', 'Segoe Script', cursive;
    font-size: 4rem;
    font-weight: 600;
    line-height: 1.25;
    color: var(--chrome);
    text-shadow: var(--chrome-shadow);
    text-decoration: none;
    transform: skewX(-16deg);
    /* Optical nudge, the same mechanism the row text uses — relative offset
       rather than a translate in the transform, which would have to sit
       before the skew or it would shift horizontally too. In em, not rem, so
       it holds its proportion as the header size below scales: at the 4rem
       base it resolves to the -0.25rem this was tuned at. */
    position: relative;
    top: -0.0625em;
}

/* In the header the wordmark shares a row with the list name and the account
   chip, so it has to give ground on a narrow screen; standing alone on the
   logged-out pages it does not, which is why this is scoped rather than set
   on .wordmark itself.

   6.67vw is 4rem/60rem — the mark holds at a fifteenth of the viewport, so it
   stops growing at 600px, exactly where .container stops widening. Below that
   the sides get the room instead: at 4rem the three columns need 320px of a
   375px phone before any truncation, at 2.6rem they need 250px. The floor is
   set so the mark stays roughly twice the 1.2rem text beside it and its skew
   still leans clear inside the 1rem grid gap. */
header .wordmark {
    font-size: clamp(2.6rem, 6.67vw, 4rem);
}

/* Milligram paints a:focus/a:hover #606c76 at (0,1,1), which outranks the
   .wordmark colour rule at (0,1,0) — that is the grey. Restating the colour at
   matching specificity takes it back. :focus is covered as well as
   :focus-visible, because Milligram targets the former. */
.wordmark:hover,
.wordmark:focus,
.wordmark:focus-visible {
    color: var(--chrome);
}

.wordmark:hover,
.wordmark:focus-visible {
    opacity: .66;
}

/* A strip of the page background below the header, masked to fade out, so
   content scrolling up dissolves into the gradient instead of meeting a hard
   edge. The gradient is fixed-attachment, so this repaint lines up with the page
   exactly; left/right 0 carries the gutter bleed down with it, fading the card
   shadows too. pointer-events stays off so it never swallows a click. */
header::after {
    content: "";
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    height: 0.25rem;
    background: linear-gradient(to bottom right, var(--bg-from), var(--bg-to)) fixed;
    -webkit-mask-image: linear-gradient(to bottom, #000, transparent);
    mask-image: linear-gradient(to bottom, #000, transparent);
    pointer-events: none;
    opacity: 0;
}

/* It fades in only once the page has scrolled — at rest it would wash out the
   top of the first card. A scroll timeline does this with no script; wrapped
   in @supports because without it the animation would fall back to a
   time-based one and show at once. Unsupported, the strip stays hidden. */
@supports (animation-timeline: scroll()) {
    @keyframes header-fade {
        to { opacity: 1; }
    }

    header::after {
        animation: header-fade linear both;
        animation-timeline: scroll();
        animation-range: 0 3rem;
    }
}

header h1 {
    /* Stretches its column so a long list name has something to truncate
       against; the .crumb inside shrinks to its content, so the hover box
       still hugs the text rather than spanning the column. */
    display: flex;
    align-items: center;
    justify-self: stretch;
    min-width: 0;
    margin: 0;
}

/* Set like Milligram's button text, so it reads as a control rather than a
   heading — but at a size that gives a real hit area. Padding takes it to
   roughly --control-size tall, and an equal negative margin keeps the name
   on the same left edge as the cards below. */
.crumb {
    display: flex;
    align-items: center;
    justify-self: start;
    /* padding + icon + gap resolves to --title-col for any --crumb-icon. */
    gap: calc(var(--title-col) - var(--row-pad) - var(--crumb-icon));
    min-width: 0;
    max-width: 100%;
    /* The text alone falls 0.4px short of the avatar opposite it; pinning the
       height to the same token the avatar uses squares the two hit areas. */
    min-height: var(--control-size);
    padding: .8rem var(--row-pad);
    padding-right: 1.2rem;
    border-radius: .4rem;
    font-size: 1.1rem;
    font-weight: 700;
    letter-spacing: .1rem;
    text-transform: uppercase;
    transition: background-color .12s ease;
}

/* The leading icon of a crumb and of a home row: one rule, because they are
   deliberately the same size, so the crumb icon, the home row icons and the
   checkboxes all share one column. ion-icon takes its box from font-size and
   its SVG fills that box, so font-size is the only way to make the glyph
   bigger; the gap on each container absorbs the extra width, so growing this
   never moves the text beside it. */
.crumb > ion-icon,
.row-link > ion-icon {
    flex: none;
    font-size: var(--crumb-icon);
    margin-top: -1px;
}

/* Truncation moves to the name itself, now that the link is a flex row. */
.crumb span {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Colour is already chrome in every state via `header a`, so the hover
   only has to add the wash. */
.crumb:hover,
.crumb:focus-visible {
    background: color-mix(in srgb, var(--chrome) var(--wash), transparent);
}

/* The account control is a plain link to the account page — sign-out lives
   there. Inset by a row's padding, so the avatar sits on the same vertical
   line as the icon buttons down the right of the list rows. */
.account {
    display: flex;
    align-items: center;
    justify-self: end;
    flex: none;
    gap: var(--row-gap);
    margin-right: var(--row-pad);
}

/* Timestamp size, but at full strength — --chrome-dim reads too faint
   against the gradient at 1.2rem. */
.account .who {
    /* Capped so the name can never crowd the wordmark. */
    max-width: 9rem;
    overflow: hidden;
    text-overflow: ellipsis;
    font-size: 1.2rem;
    color: var(--chrome);
    white-space: nowrap;
}

/* Sized to --control-size, so it keeps the rhythm of every icon button;
   round so it still reads as a person rather than an action. */
.avatar {
    display: grid;
    place-items: center;
    flex: none;
    width: var(--control-size);
    height: var(--control-size);
    border-radius: 50%;
    background: color-mix(in srgb, var(--chrome) 20%, transparent);
    font-size: 1.3rem;
    font-weight: 700;
    letter-spacing: .05rem;
    transition: background-color .12s ease;
}

.account:hover .avatar,
.account:focus-visible .avatar {
    background: color-mix(in srgb, var(--chrome) 32%, transparent);
}

/* ---- Cards and rows -------------------------------------------------
   Every <ul> in the app is a card and every <li> is a row, so these are
   element selectors rather than classes: the shape is the markup, not
   something each list has to opt into. Both rules also undo a Milligram
   default at equal weight — ul's `list-style: circle inside` and li's
   1rem bottom margin — which works only because style.css is loaded after
   milligram.min.css. The modifiers below stay classes, since they mark a
   row that differs from the rest. */
ul {
    padding: 0;
    list-style: none;
    background: var(--surface);
    border-radius: .6rem;
    box-shadow: 0 .2rem 1.2rem rgba(16, 24, 40, .16);
    overflow: hidden;
}

li {
    display: flex;
    align-items: center;
    gap: var(--row-gap);
    margin: 0;
    padding: var(--row-pad);
    border-bottom: .1rem solid var(--line);
}

li:last-child {
    border-bottom: 0;
}

li > button {
    margin: 0;
}

li.flush {
    padding: 0;
}

/* A flush row's button is the row: full width, square corners so
    the hover wash meets the row edges cleanly. */
li.flush > .button {
    flex: 1;
    border-radius: 0;
    height: 4.5rem; line-height: 4.5rem;
    margin-bottom: 0 !important;
}

/* Row contents. .title takes the slack, pushing the rest right. */
.title {
    flex: 1;
    margin: 0;
    font-weight: 700;
    overflow-wrap: anywhere;
}

.title.purchased {
    color: var(--secondary);
    text-decoration: line-through;
}

.timestamp {
    display: flex;
    align-items: center;
    gap: .3rem;
    font-size: 1.2rem;
    color: var(--secondary);
    white-space: nowrap;
}

/* Optical nudge, shared so every text run beside a box sits on one line.
    Centring aligns the line box, but the cap height sits high within it, so
    text reads a pixel above the checkboxes, the icon glyphs, the list icon
    and the avatar. */
.title,
.timestamp,
.crumb span,
.account .who {
    position: relative;
    top: 1px;
}

input[type="checkbox"] {
    width: var(--check-size);
    height: var(--check-size);
    margin: 0;
    accent-color: var(--success);
}

/* Borderless input filling the add row */
.add-input[type="text"] {
    flex: 1;
    height: var(--control-size);
    margin: 0;
    padding-left: calc(var(--title-col) - var(--row-pad));
    border: 0;
    background: none;
    color: var(--ink);
    font-weight: 700;
}

.add-input::placeholder {
    color: var(--secondary);
    font-weight: 400;
}

.add-input[type="text"]:focus {
    border: 0;
    box-shadow: none;
}

/* ---- Colour helpers ------------------------------------------
    .name       tints one element, and buttons through --tint
    .tone-name  makes a whole row read that way: wash plus matching text
    Both mix from the same token, so a colour and its wash cannot drift
    apart and dark mode follows for free. A tone is a single class, so a
    surface can never be applied without the text that belongs with it. */
.primary   { --tint: var(--primary);   color: var(--tint); }
.secondary { --tint: var(--secondary); color: var(--tint); }
.success   { --tint: var(--success);   color: var(--tint); }
.danger    { --tint: var(--danger);    color: var(--tint); }
.warning   { --tint: var(--warning);   color: var(--tint); }

.tone-primary   { background: color-mix(in srgb, var(--primary)   var(--wash), var(--surface)); color: var(--primary); }
.tone-secondary { background: color-mix(in srgb, var(--secondary) var(--wash), var(--surface)); color: var(--secondary); }
.tone-success   { background: color-mix(in srgb, var(--success)   var(--wash), var(--surface)); color: var(--success); }
.tone-danger    { background: color-mix(in srgb, var(--danger)    var(--wash), var(--surface)); color: var(--danger); }
.tone-warning   { background: color-mix(in srgb, var(--warning)   var(--wash), var(--surface)); color: var(--warning); }

.button.button-clear {
    color: var(--tint, var(--secondary));
}

/* Milligram greys buttons on hover, which reads as disabled. Keep the
    tint and show state with a wash (clear) or a fill (outline) instead. */
.button.button-clear:focus,
.button.button-clear:hover {
    color: var(--tint, var(--secondary));
    background: color-mix(in srgb, var(--tint, var(--secondary)) var(--wash), transparent);
}

.button.button-outline {
    color: var(--tint);
    border-color: var(--tint);
}

.button.button-outline:focus,
.button.button-outline:hover {
    color: var(--surface);
    background: var(--tint);
    border-color: var(--tint);
}

/* A disabled button takes no hover treatment and keeps its own colour.
   Milligram repaints [disabled]:hover in its purple at (0,4,0), which
   outranks the rules above whatever the source order — and that leaked
   into the spinner, which draws its border from currentColor. Matching
   that specificity is what takes it back. Milligram's opacity: .5 still
   carries the disabled look. */
.button.button-clear[disabled],
.button.button-clear[disabled]:focus,
.button.button-clear[disabled]:hover {
    color: var(--tint, var(--secondary));
    background: transparent;
}

.button.button-outline[disabled],
.button.button-outline[disabled]:focus,
.button.button-outline[disabled]:hover {
    color: var(--tint);
    background: transparent;
    border-color: var(--tint);
}

/* Square icon buttons on top of Milligram's .button-clear */
.icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: var(--control-size);
    height: var(--control-size);
    margin: 0;
    padding: 0;
    font-size: 1.9rem;
    line-height: 1;
}

/* Inline confirmation: the whole row carries a tone's wash, and the buttons
   stay transparent so they sit on it until hovered, when the existing
   .button-outline rule fills them. The text is a question to read rather than
   a status to skim, so it stays ink rather than taking the tone's colour.
   It centres like the informational rows, but sets text-align itself: the
   column direction puts .center's justify-content on the vertical axis, where
   it would do nothing. */
.confirm {
    flex-direction: column;
    align-items: stretch;
    color: var(--ink);
    text-align: center;
}

/* No inset, so the text starts on the Cancel button's left edge. */
.confirm span {
    margin-bottom: 3px;
}

/* A row of equal-width buttons: the confirmation rows, the card footers and
   the logged-out pages all want exactly this, so it is one class. Milligram
   gives every .button a 1rem bottom margin and 3rem of side padding; neither
   suits a button that is sharing a row. */
.actions {
    display: flex;
    gap: var(--form-gap);
}

.actions .button {
    flex: 1;
    margin: 0;
    padding: 0;
}

/* Informational rows centre; content and controls stay left/right. */
.center {
    justify-content: center;
    text-align: center;
}


.spinner {
    width: 1.4rem;
    height: 1.4rem;
    border: .2rem solid currentColor;
    border-right-color: transparent;
    border-radius: 50%;
    animation: spin .7s linear infinite;
}

@keyframes spin {
    to { transform: rotate(360deg); }
}

/* ---- Home: one row per list ----------------------------------------
   Google-Sheets shaped, but each row carries its list's own gradient, set
   inline from the list record. No `fixed` here: a row paints its gradient
   inside its own box, where a page paints it against the viewport.

   The link is the row rather than a stretched pseudo-element over it: .title
   takes position:relative from the optical-nudge rule, which would make the
   anchor the containing block for its own ::after and shrink the hit area
   back to the text. Wrapping the row's contents avoids the problem instead
   of working around it, and the hover darkens the whole row for free. */
.group-label {
    margin: 2.4rem 0 .8rem;
    /* On the same column as the list names below it. */
    padding-left: var(--title-col);
    font-size: 1.1rem;
    font-weight: 700;
    letter-spacing: .1rem;
    text-transform: uppercase;
    color: var(--chrome-dim);
    text-shadow: var(--chrome-shadow);
}

/* The row keeps the standard row padding; the hover is an inset shadow over
   the whole <li>, so it reaches the edges without the link having to. */
.list-row {
    background: linear-gradient(to bottom right, var(--bg-from), var(--bg-to));
    color: var(--chrome);
    text-shadow: 0 .1rem .3rem rgba(0, 0, 0, .25);
    /* --line is a themed grey for white cards; on a gradient the divider has
       to come from the chrome side, as it does everywhere else on the bg. */
    border-bottom-color: color-mix(in srgb, var(--chrome) 22%, transparent);
    transition: box-shadow .12s ease;
}

.row-link {
    display: flex;
    align-items: center;
    flex: 1;
    min-width: 0;
    /* Resolves to --title-col for any --crumb-icon, the same compensation
       .crumb makes, so the two stay on one line as the icon is tuned. */
    gap: calc(var(--title-col) - var(--row-pad) - var(--crumb-icon));
    text-decoration: none;
}

/* Milligram paints a/a:hover at (0,0,1) and (0,1,1); both are restated here
   at matching weight so the row keeps its chrome. */
.row-link,
.row-link:focus,
.row-link:hover {
    color: var(--chrome);
}

/* Dims the whole row, not just the link, so the edit button beside it is
   covered too. An inset shadow rather than a second background layer: layered
   gradients do not interpolate, so a background swap would snap instead of
   fading, and this needs no restatement of the row's own gradient. */
.list-row:focus-within,
.list-row:hover {
    box-shadow: inset 0 0 0 100vmax rgba(0, 0, 0, .14);
}

/* The row's edit button sits on the gradient, so it takes chrome rather than
   the card-side grey that .button-clear falls back to. */
.list-row .button {
    --tint: var(--chrome);
}

.list-row .timestamp {
    color: var(--chrome-dim);
}


/* Available to screen readers, absent from layout — including grid layout,
   since an absolutely positioned child claims no cell. */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* ---- Edit pages -----------------------------------------------------
   The whole form is one card. That is what makes the themed tokens usable
   again: on a --surface the semantic colours behave normally, where on the
   gradient every one of them needed an unthemed twin. */
.form {
    flex-direction: column;
    align-items: stretch;
    gap: var(--form-gap);
}

.form input {
    margin: 0;
    border-color: var(--line);
    color: var(--ink);
}

.form input:focus {
    border-color: var(--primary);
}

/* The header's avatar is a chrome wash, invisible on a card; on a --surface
   it takes the secondary tint instead. At (0,1,1) this still outranks
   .avatar-sm below, which only resizes. */
ul .avatar {
    background: color-mix(in srgb, var(--secondary) 16%, transparent);
    color: var(--secondary);
}

/* Shrunk to a checkbox, so person rows start their text on --title-col like
   every other row in the app. Two initials in 17px need the base avatar's
   letter-spacing removed to stay inside the circle. Declared after .avatar so
   it wins the tie at equal specificity. */
.avatar-sm {
    width: var(--check-size);
    height: var(--check-size);
    font-size: .9rem;
    letter-spacing: 0;
}

/* Controls laid out side by side inside a card: .form-row is an input paired
   with a button that sizes to its own text, .icon-picker a wrapping bank of
   icon buttons. Both wrap rather than crushing their contents when the card
   gets narrow; only .form-row needs the child rules below, because .icon
   already sizes its own buttons. */
.form-row,
.icon-picker {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--form-gap);
}

.form-row input {
    flex: 1;
    min-width: 14rem;
}

.form-row .button {
    flex: none;
    margin: 0;
    /* Milligram pads buttons 3rem a side; too wide to sit beside a field. */
    padding: 0 1.2rem;
}

.form-note {
    margin: 0;
    font-size: 1.3rem;
    color: var(--secondary);
    text-align: center;
}

/* Solid fill for the one action a card is really about. Milligram's base
   .button hardcodes its own purple, so the tint is reapplied here the same
   way .button-clear and .button-outline are. */
.button.filled {
    color: var(--surface);
    background: var(--tint);
    border-color: var(--tint);
}

.button.filled:focus,
.button.filled:hover {
    color: var(--surface);
    background: color-mix(in srgb, var(--tint) 85%, black);
    border-color: color-mix(in srgb, var(--tint) 85%, black);
}

/* The <form> only wraps a card; the <ul> inside carries the spacing, so the
   element's own margin would double the gap to the next card. */
form {
    margin-bottom: 0;
}

/* ---- Logged out -----------------------------------------------------
   No header row, and no cards: a filled button and an input each carry their
   own --surface, so their text contrast is self-contained and they can sit
   straight on the gradient. Only outlined controls needed a card, because
   their colour has to contrast with whatever is behind them. */
.centered {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--gutter);
    /* body carries a gutter of top padding, for the sticky header on the
       other pages; subtracting it keeps this exactly one viewport tall. */
    min-height: calc(100vh - var(--gutter));
    text-align: center;
}

.centered > form,
.centered > .actions {
    width: 100%;
}

.centered > form {
    display: flex;
    flex-direction: column;
    gap: var(--form-gap);
}

.centered input {
    margin: 0;
    /* Left, against the centred column: an email is read from its start. */
    text-align: left;
    background: var(--surface);
    border-color: transparent;
    color: var(--ink);
    box-shadow: 0 .2rem 1.2rem rgba(16, 24, 40, .14);
}

.centered input:focus {
    border-color: var(--primary);
}

/* Lifts the filled controls off a background of a similar tone. */
.centered .button {
    box-shadow: 0 .2rem 1.2rem rgba(16, 24, 40, .18);
}

/* Scales with the viewport, so the mark reads as the page rather than a
   banner on it, and still fits "FreeLists" on a narrow phone. */
.wordmark-lg {
    font-size: clamp(4.5rem, 17vw, 9rem);
}

.lede {
    margin: 0;
    font-size: 1.6rem;
    color: var(--chrome);
    text-shadow: var(--chrome-shadow);
}

.foot-link {
    margin: 0;
    font-size: 1.3rem;
    color: var(--chrome-dim);
    text-shadow: var(--chrome-shadow);
}

.foot-link a,
.foot-link a:focus,
.foot-link a:hover {
    color: var(--chrome);
}

/* One-time code: spaced out so digits are easy to check against the email.
   Typed as input.code-input to outrank .centered input's left alignment. */
input.code-input {
    text-align: center;
    font-size: 2.2rem;
    letter-spacing: .5rem;
}
