:root {
    /* Sized for the actual audience. Grandparents are the primary readers,
       so the base is 19px rather than the usual 16, line length is capped in
       characters rather than pixels, and every control clears 44px. */
    --ink: #1c1917;
    --ink-soft: #57534e;
    --paper: #fdfcfb;
    --rule: #e0dcd7;
    --accent: #2f5d50;
    --accent-ink: #ffffff;
    --radius: 10px;
    --measure: 34rem;
    font-size: 19px;
}

/* In a media query `rem` is the browser's initial 16px, not the 19px set
   above -- so this is the usual 1024px, not 1216px.

   Modest on purpose. The extra width has to be spent on the letter itself,
   because a photo the missionary embedded is where it is for a reason -- the
   paragraph beside it is what it is a picture of -- and anything that moves it
   out of the text to use up a wide screen throws that away. So this is as wide
   as a line of prose can be read comfortably and no wider.

   One measure, every page. The archive had a container of its own for a while,
   on the argument that a page of instructions does not improve for being wider.
   True in itself, and still the wrong answer: it meant walking from the FAQ
   into an archive resized the site under you. */
@media (min-width: 64rem) {
    :root {
        --measure: 42rem;
    }
}

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

/* The browser's own `[hidden] { display: none }` lives in its default
   stylesheet, and any author rule beats it no matter how specific -- so the
   moment something toggled with `hidden` is also given a `display`, `hidden`
   quietly stops working. That is not hypothetical: it put an empty subject
   box under every letter on the live site. Restated here at author level, and
   with `!important`, because this has to outrank the rule that broke it. */
[hidden] {
    display: none !important;
}

html {
    /* Never suppress the reader's own zoom. */
    -webkit-text-size-adjust: 100%;
}

body {
    margin: 0;
    background: var(--paper);
    color: var(--ink);
    font-family: Georgia, 'Iowan Old Style', 'Times New Roman', serif;
    line-height: 1.6;
}

.wrap {
    max-width: var(--measure);
    margin: 0 auto;
    padding: 1.5rem 1.25rem 4rem;
}

/* --- masthead --- */

.masthead {
    border-bottom: 1px solid var(--rule);
    padding: 1.25rem 1.25rem 1rem;
}

.masthead__inner {
    max-width: var(--measure);
    margin: 0 auto;
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.5rem 0.75rem;
    /* The menu panel hangs from here rather than from its own trigger. On a
       phone the row wraps and the trigger lands in the *left* corner, so a
       panel anchored to its right edge hung off the side of the page. */
    position: relative;
}

.wordmark {
    font-size: 1.15rem;
    font-weight: 700;
    letter-spacing: 0.01em;
    color: var(--ink);
    text-decoration: none;
}

/* The mark beside the name.

   Left as an ordinary inline image rather than making the wordmark a flex
   box, because the masthead aligns its children on the baseline and an
   inline-flex wordmark would offer the flex box's own baseline instead --
   which moves the name relative to the `beta` pill next to it. A nudge below
   the baseline is what sits it level with the capitals.

   `alt` is empty on purpose: the link already says the words, and a screen
   reader announcing "Pday Letters logo, link, Pday Letters" is worse than
   silence. Width and height are the file's own, so the row does not jump
   while it loads. */
.wordmark__mark {
    width: 1.5em;
    height: 1.5em;
    vertical-align: -0.4em;
    margin-right: 0.4rem;
}

.beta {
    font-family: system-ui, sans-serif;
    font-size: 0.62rem;
    font-weight: 600;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--ink-soft);
    border: 1px solid var(--rule);
    border-radius: 999px;
    padding: 0.1rem 0.45rem;
    vertical-align: 0.15em;
}

.masthead__spacer {
    flex: 1 1 auto;
}

/* The mark for the identity provider, inside the menu trigger. */
.account__icon {
    font-size: 0.85rem;
    flex: none;
}

/* Available to a screen reader, absent from the page. */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    border: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* --- links and controls --- */

a {
    color: var(--accent);
}

.button {
    display: inline-block;
    min-height: 44px;
    padding: 0.6rem 1.1rem;
    border-radius: var(--radius);
    background: var(--accent);
    color: var(--accent-ink);
    font-family: system-ui, sans-serif;
    font-size: 0.95rem;
    text-decoration: none;
    border: 0;
    cursor: pointer;
}

.button--quiet {
    background: transparent;
    color: var(--accent);
    border: 1px solid var(--rule);
}

.button--buy {
    background: #2f7d32;
    color: #fff;
}

/* Destructive, and only ever reached after typing the archive's name.

   Red, but not the loudest thing on the page it lives on: the danger section
   is already closed by default and already below a rule, so the color is
   confirming what somebody has worked to reach rather than warning them off
   something they might hit by accident. */
.button--danger {
    background: #9b2226;
    color: #fff;
}

.button--danger:disabled {
    background: #d9c2c3;
    color: #6b4f50;
    cursor: not-allowed;
}

/* The 44px rule above is for controls a reader has to find and hit. These sit
   in the masthead beside an email address, and at full size they wrapped onto
   their own line on a phone and pushed the letters below the fold. They are
   deliberately smaller; the provider buttons on the sign-in page, which are the
   only thing on that page and have to be hit with certainty, are not. */
.button--compact {
    display: inline-flex;
    align-items: center;
    min-height: 34px;
    padding: 0.25rem 0.6rem;
    font-size: 0.75rem;
    line-height: 1.2;
}

/* A button whose label is one glyph or two. Fixed width because it swaps
   between them on every press, and a control that changes size under the
   finger moves the one next to it out from under the next press. */
.button--glyph {
    justify-content: center;
    min-width: 2.75rem;
    font-size: 1rem;
    letter-spacing: 0.08em;
}

/* --- the masthead menu --- */

/* One control for everything that is not a letter. A `details` rather than a
   hand-built menu: the browser already owns the open-and-close behavior, the
   keyboard handling and the expanded state a screen reader announces, and none
   of that is worth reimplementing.

   Deliberately not positioned: see `.masthead__inner`. */
.menu {
    min-width: 0;
}

/* Subordinate to the wordmark on purpose: it answers a question -- which
   account is this page for -- as much as it invites an action. */
.menu__trigger {
    gap: 0.35rem;
    font-family: system-ui, sans-serif;
    font-size: 0.7rem;
    color: var(--ink-soft);
    /* Chrome and Safari draw a disclosure triangle that would put a marker
       inside something styled as a button. */
    list-style: none;
}

.menu__trigger::-webkit-details-marker {
    display: none;
}

.menu__trigger::after {
    content: '';
    flex: none;
    width: 0.4em;
    height: 0.4em;
    border-right: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    transform: translateY(-0.15em) rotate(45deg);
}

.menu[open] .menu__trigger::after {
    transform: translateY(0.1em) rotate(-135deg);
}

/* Long addresses must not shove the trigger off the masthead. */
.menu__name {
    min-width: 0;
    max-width: 11rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The landing page is a page of prose, so its container is the measure rather
   than the wider archive width. A couple of rems is all it takes to wrap, and
   the address is the item that gives way: it is truncated at any width, so a
   little more costs less than a wrapped masthead would. */
.masthead--landing .menu__name {
    max-width: 8rem;
}

/* `top` is left alone so the panel keeps the place it would have had in flow,
   directly under the trigger, while `right` comes from the masthead. */
.menu__panel {
    position: absolute;
    right: 0;
    z-index: 20;
    margin-top: 0.35rem;
    min-width: 13rem;
    max-width: 17rem;
    background: var(--paper);
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    box-shadow: 0 6px 20px rgb(0 0 0 / 12%);
    font-family: system-ui, sans-serif;
}

/* The address in full, which the trigger can only ever show truncated. */
.menu__who {
    margin: 0;
    padding: 0.55rem 0.85rem;
    font-size: 0.72rem;
    color: var(--ink-soft);
    overflow-wrap: anywhere;
}

.menu__address {
    color: var(--ink);
}

.menu__group,
.menu__panel > .menu__list {
    border-top: 1px solid var(--rule);
}

.menu__label {
    margin: 0;
    padding: 0.5rem 0.85rem 0.15rem;
    font-size: 0.62rem;
    font-weight: 600;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--ink-soft);
}

/* The archive list is a second round trip and not a fast one. This is inside
   the panel rather than in the masthead, so it is read only by somebody who
   opened the menu looking for that list -- and so nothing in the row moves
   when the answer lands. */
.menu__wait {
    margin: 0;
    padding: 0.55rem 0.85rem;
    font-size: 0.85rem;
    color: var(--ink-soft);
}

.menu__list {
    margin: 0;
    padding: 0.15rem 0;
    list-style: none;
}

.menu__list a {
    display: block;
    /* A tap target, not a line of prose -- these are read on a phone by people
       who are not steady with one. */
    padding: 0.55rem 0.85rem;
    font-size: 0.85rem;
    text-decoration: none;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.menu__list a:hover,
.menu__list a:focus-visible {
    background: rgb(0 0 0 / 4%);
}

/* An entry in the list that opens something instead of going somewhere, so it
   has to be a button and then has to be talked back into looking like the
   links either side of it. */
.menu__act {
    display: block;
    width: 100%;
    padding: 0.55rem 0.85rem;
    font: inherit;
    font-size: 0.85rem;
    text-align: left;
    color: var(--accent);
    background: none;
    border: 0;
    cursor: pointer;
}

.menu__act:hover,
.menu__act:focus-visible {
    background: rgb(0 0 0 / 4%);
}

/* --- putting the archive on a home screen --- */

/* The same box as the question dialog, because it is the same kind of
   interruption; it is only wider by the width of a numbered list. */
.steps {
    width: min(26rem, 92vw);
    margin: auto;
    padding: 1.25rem;
    color: var(--ink);
    background: var(--paper);
    border: 0;
    border-radius: var(--radius);
    font-family: system-ui, sans-serif;
}

.steps::backdrop {
    background: rgba(28, 25, 23, 0.6);
}

.steps__title {
    margin: 0 0 0.75rem;
    font-size: 1.05rem;
}

.steps__list {
    margin: 0;
    padding-left: 1.35rem;
    font-size: 0.9rem;
    line-height: 1.5;
}

.steps__list li + li {
    margin-top: 0.5rem;
}

/* The one thing here that is about this site rather than about the phone, and
   the question somebody is most likely to have got wrong. */
.steps__note {
    margin: 1rem 0 0;
    font-size: 0.8rem;
    color: var(--ink-soft);
}

.steps__close {
    display: flex;
    justify-content: flex-end;
    margin-top: 1.25rem;
}

/* A short handoff before sending a reader to the printer's checkout. */
.purchase {
    position: relative;
    width: min(28rem, 92vw);
    margin: auto;
    padding: 1.5rem;
    color: var(--ink);
    background: var(--paper);
    border: 0;
    border-radius: var(--radius);
    font-family: system-ui, sans-serif;
}

.purchase::backdrop {
    background: rgba(28, 25, 23, 0.6);
}

.purchase__title {
    margin: 0 2rem 0.75rem 0;
    font-size: 1.2rem;
}

.purchase__close {
    position: absolute;
    top: 0.65rem;
    right: 0.75rem;
    padding: 0.2rem 0.45rem;
    color: var(--ink-soft);
    background: transparent;
    border: 0;
    font-size: 1.5rem;
    line-height: 1;
    cursor: pointer;
}

.purchase__actions {
    display: flex;
    justify-content: flex-end;
    margin: 1.25rem 0 0;
}

/* Visible focus everywhere. Removing it is not an option for this audience. */
a:focus-visible,
button:focus-visible {
    outline: 3px solid var(--accent);
    outline-offset: 2px;
}

/* --- landing --- */

.lede {
    font-size: 1.25rem;
    line-height: 1.5;
}

/* --- the dictionary entry that explains the landing page's name --- */

/* Borrowed wholesale from a printed dictionary, because the form itself does
   half the explaining: a reader who has never heard the word recognizes the
   shape on sight and knows to read it as a definition rather than as a slogan.
   Tinted and ruled rather than plain, so it reads as an inset quotation from
   somewhere else and not as the site talking about itself. */
.entry {
    background: #f8f6f3;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    padding: 1.5rem 1.5rem 1.25rem;
    margin: 0 0 2rem;
}

/* Set above the body copy but well below a display size -- it is a headword
   introducing a definition, not a banner. */
.entry__word {
    font-size: 2rem;
    line-height: 1.1;
    font-weight: 700;
    letter-spacing: -0.01em;
    margin: 0;
}

/* Pronunciation, part of speech and spellings on one line, wrapping to two on
   a phone rather than being three stacked fragments. */
.entry__head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.15rem 0.7rem;
    margin: 0.5rem 0 0;
    color: var(--ink-soft);
}

.entry__say {
    font-size: 1.05rem;
}

/* The stress marks and the IPA vowels are outside Georgia's repertoire, so a
   stack that actually carries them keeps the browser from falling back one
   glyph at a time and setting the line in two faces. */
.entry__say,
.entry__ipa {
    font-family: 'Charis SIL', 'Gentium Plus', 'Segoe UI', 'Lucida Sans Unicode', system-ui, sans-serif;
    white-space: nowrap;
}

.entry__ipa {
    font-size: 0.9rem;
}

.entry__also {
    font-family: system-ui, sans-serif;
    font-size: 0.8rem;
}

/* The functional label. It sits on the sense rather than on the headword
   because the two senses are different parts of speech, which a print
   dictionary would answer by splitting the word into two numbered entries --
   more apparatus than one word needs when the whole point is to explain it. */
.entry__part {
    font-style: italic;
    color: var(--ink-soft);
}

.entry__part::after {
    content: ' \2014 ';
}

/* Own counter, so the numeral can take the accent and sit in the margin the
   way a dictionary sets it. */
.entry__senses {
    list-style: none;
    counter-reset: sense;
    margin: 1.25rem 0 0;
    padding: 0;
}

.entry__senses > li {
    counter-increment: sense;
    position: relative;
    padding-left: 1.6rem;
}

.entry__senses > li + li {
    margin-top: 1rem;
}

.entry__senses > li::before {
    content: counter(sense);
    position: absolute;
    left: 0;
    font-weight: 700;
    color: var(--accent);
}

/* Attributed to nobody, because these are the sentences a family actually
   says rather than a quotation from anywhere. */
.entry__example {
    font-style: italic;
    color: var(--ink-soft);
    margin: 0.35rem 0 0;
}

.landing-steps {
    padding-left: 1.4rem;
}

.landing-steps > li {
    margin-bottom: 0.75rem;
}

@media (max-width: 30rem) {
    .entry {
        padding: 1.25rem 1.1rem 1rem;
    }

    .entry__word {
        font-size: 1.7rem;
    }
}

/* An address, always inside a sentence -- there is not one standalone use of
   this on the site. So it has to sit *in* the line rather than push it open:
   `display: inline` means the vertical padding overflows the line box instead
   of growing it, which is the whole trick. The tint and the border still mark
   it out, and the paragraph keeps its rhythm.

   The size is stepped down slightly because monospace reads larger than the
   body face at the same nominal size, and `em` rather than `rem` so it tracks
   whatever it is quoted inside -- the lede, a heading, an ordinary paragraph.

   `anywhere` rather than `break-all`: an address should break mid-word only
   when a narrow screen leaves no choice, not every time it nears the edge. */
.address {
    font-family: ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace;
    font-size: 0.92em;
    background: #f4f1ee;
    border: 1px solid var(--rule);
    border-radius: 4px;
    padding: 0.1em 0.3em;
    overflow-wrap: anywhere;
    /* Both halves of a broken address keep the tint and the border. */
    -webkit-box-decoration-break: clone;
    box-decoration-break: clone;
}

/* --- post list --- */

.posts {
    list-style: none;
    margin: 1rem 0 0;
    padding: 0;
}

/* --- months --- */

/* The contents and the letters are one list at two zoom levels. A month
   heading folds the letters under it away, so two years shuts down to a couple
   of dozen rows and opens back up in place. There is no second copy of the
   subject lines anywhere, which is the whole reason for doing it this way. */
.month {
    border-top: 1px solid var(--rule);
}

/* Quieter than a letter's summary line in every way it can be -- smaller, sans
   serif, a shorter row. It is scaffolding, and it has to lose to the thing it
   is holding up. */
.month__toggle {
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: baseline;
    gap: 0 0.75rem;
    position: relative;
    width: 100%;
    min-height: 44px;
    padding: 0.4rem 1.6rem 0.4rem 0;
    margin: 0;
    background: none;
    border: 0;
    border-radius: var(--radius);
    font-family: system-ui, sans-serif;
    color: inherit;
    text-align: left;
    cursor: pointer;
}

.month__toggle:hover .month__name {
    text-decoration: underline;
    text-underline-offset: 2px;
}

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

.month__name {
    font-size: 0.85rem;
    font-weight: 600;
    letter-spacing: 0.04em;
}

.month__count {
    font-size: 0.75rem;
    color: var(--ink-soft);
}

/* The same arrow a letter has, turned the same way for the same reason. */
.month__toggle::after {
    content: '';
    position: absolute;
    top: 50%;
    right: 0.3rem;
    width: 0.5rem;
    height: 0.5rem;
    margin-top: -0.35rem;
    border-right: 2px solid var(--ink-soft);
    border-bottom: 2px solid var(--ink-soft);
    transform: rotate(-135deg);
    transition: transform 0.15s ease;
}

.month--folded .month__toggle::after {
    margin-top: -0.5rem;
    transform: rotate(45deg);
}

.month__posts {
    list-style: none;
    margin: 0;
    padding: 0;
}

/* The month's own rule already separates it from what is above it, so the
   first letter underneath does not need a second one a few pixels below. */
.month__posts .post:first-child {
    border-top: 0;
}

.post {
    border-top: 1px solid var(--rule);
}

/* The whole summary line is the control. A letter closed by mistake has to be
   trivially reopenable by a grandparent on a phone, so the target is the full
   width of the column rather than a chevron in a corner. */
.post__toggle {
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: baseline;
    gap: 0 0.75rem;
    width: 100%;
    min-height: 48px;
    padding: 0.5rem 1.6rem 0.5rem 0;
    margin: 0;
    background: none;
    border: 0;
    border-radius: var(--radius);
    font: inherit;
    color: inherit;
    text-align: left;
    cursor: pointer;
}

.post__toggle:hover .post__title {
    text-decoration: underline;
    text-underline-offset: 2px;
}

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

/* The arrow is the only part of the summary that says "this opens", so it is
   drawn rather than left to a font the offline archive does not ship. */
.post__toggle::after {
    content: '';
    position: absolute;
    right: 0.3rem;
    width: 0.55rem;
    height: 0.55rem;
    margin-top: 0.55rem;
    border-right: 2px solid var(--ink-soft);
    border-bottom: 2px solid var(--ink-soft);
    transform: rotate(45deg);
    transition: transform 0.15s ease;
}

.post--open .post__toggle::after {
    transform: rotate(-135deg);
}

/* One clear step above the letter's own prose, and no more. The row is a
   contents entry far more often than it is a heading, and at 1.4rem it dwarfed
   the month it sits under and made the contents scroll for no gain. */
.post__subject {
    position: relative;
    font-size: 1.15rem;
    line-height: 1.3;
    margin: 0;
}

/* The archive's own title borrows the letter heading's type but not its
   layout -- it is a plain heading, with nothing to open. */
#site-title {
    margin-bottom: 0.1rem;
}

/* The name and the mission are one block, so the gap belongs below the pair.
   That way the mission sits tight under the name, and an archive without one
   keeps the spacing it always had. */
.archive-name {
    margin-bottom: 0.9rem;
}

/* Wide and flat, so it reads as a rule under the name rather than as a second
   heading. The mission is where the name was, not a thing of its own, and
   tracking this loose at this size gives a line that is mostly horizontal --
   which is the whole idea. Uppercase for the same reason: it has no ascenders
   or descenders to break the band. */
.archive-mission {
    margin: 0;
    font-family: system-ui, sans-serif;
    font-size: 0.7rem;
    font-weight: 600;
    letter-spacing: 0.2em;
    line-height: 1.5;
    text-transform: uppercase;
    color: var(--ink-soft);
}

/* The title and the count-up clock share a line.

   Baseline alignment rather than centering, because the thing that has to line
   up is the clock's first line with the title's -- "at the same height" means
   the two pieces of text sit on one rule, not that a two-line block is
   centered against a one-line one.

   Wrapping is left on and only the clock is allowed to shrink. A long name is
   the common case and it should keep the full measure; a clock squeezed into
   a column of single characters is not a smaller clock, it is a broken one. */
.archive-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 0 1rem;
}

/* The label and the reading are one phrase -- "Home in 84 days, 06:11:02" --
   so they are set at one size and told apart by color alone. They sit on one
   line and stack only if the screen is too narrow to hold them. Stacked
   unconditionally, the clock spent three lines of a phone screen -- its own
   two plus the one it pushed the title onto -- on a detail nobody opened the
   archive to read. */
.elapsed {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: flex-end;
    gap: 0 0.35rem;
    /* Right-justified against the measure, which on a narrow screen means
       right-justified under the title rather than beside it. */
    margin-left: auto;
    margin-bottom: 0.9rem;
    text-align: right;
    font-family: system-ui, sans-serif;
    font-size: 0.78rem;
    color: var(--ink-soft);
    line-height: 1.3;
}

/* Tabular figures so the seconds do not shove the rest of the line sideways
   every time they change. Without them the whole block twitches once a second,
   which is exactly the sort of movement that makes a page hard to read. */
.elapsed__value {
    font-variant-numeric: tabular-nums;
    color: var(--ink);
}

/* Inside the summary these are spans in a grid, and the date has to sit above
   the subject rather than beside it. */
.post__date {
    grid-column: 1;
    font-family: system-ui, sans-serif;
    font-size: 0.8rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ink-soft);
    margin: 0;
}

.post__title {
    grid-column: 1;
    grid-row: 2;
    display: block;
}

.post__meta {
    grid-column: 2;
    grid-row: 1 / span 2;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 0.2rem;
    font-family: system-ui, sans-serif;
    font-size: 0.72rem;
    letter-spacing: 0.04em;
    color: var(--ink-soft);
    white-space: nowrap;
}

.post__hits {
    color: var(--accent);
    font-weight: 600;
}

.post__panel {
    padding: 0 0 1.75rem;
}

.toolbar {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-top: 1.25rem;
}

/* The folding controls are built last, and they sit over on the right above
   the Expand buttons they work on. Everything before them opens a window over
   the archive rather than rearranging it, so those group at the other end. */
.toolbar > :last-child {
    margin-left: auto;
}

.toolbar__group {
    display: flex;
    gap: 0.5rem;
}

.post__body {
    overflow-wrap: break-word;
}

/* Floated photos are taken out of the flow, so without this the letter's own
   border-top would ride up beside the last picture in the letter above it. */
.post__body::after {
    content: '';
    display: block;
    clear: both;
}

/* Letters arrive with their own markup. These rules keep it from breaking the
   page: nothing is allowed to overflow, and a photo that was not put through
   the frame below still lands at a sane size. */
.post__body img {
    max-width: 100%;
    height: auto;
    border-radius: var(--radius);
    display: block;
    margin: 1rem 0;
}

.post__body table {
    max-width: 100%;
    display: block;
    overflow-x: auto;
}

.post__body a {
    word-break: break-word;
}

/* --- photos inside a letter --- */

/* A photo pasted into a letter used to render at the full width of the
   column: it broke the reading line, pushed the paragraphs apart, and gave no
   sign that a bigger version existed. Floated small with the text wrapping
   around it, a letter reads as a letter again.

   Floated rather than laid out with grid or flex, because the photo sits in
   the middle of markup we did not write -- it can be inside a paragraph, a
   div, a table cell, or nothing at all -- and float is the only mechanism
   that makes surrounding text wrap without knowing anything about the
   container it is in. */
.photo {
    float: left;
    /* Two floats will happily sit side by side, and floats placed from
       different paragraphs still see each other, so a picture, one line of
       caption, and another picture puts them shoulder to shoulder with a
       sliver of text down the edge. `clear` says a photo always starts below
       the one before it. reader.js also promotes the tight cases to full
       width, which handles most of this -- but that reads the letter's text
       to decide, and this needs no judgment to be right. */
    clear: left;
    display: block;
    width: 45%;
    max-width: 240px;
    margin: 0.4rem 1.1rem 0.6rem 0;
    padding: 0;
    background: none;
    border: 0;
    border-radius: var(--radius);
    cursor: zoom-in;
}

/* Below this the column is too narrow to wrap anything around a picture: at
   45% of it the text beside the photo comes out about thirty characters wide,
   which is a stack of single words rather than a paragraph. So on a phone the
   photo takes the full width and the letter simply continues underneath it,
   which still leaves it exactly where the missionary put it. */
@media (max-width: 37rem) {
    .photo {
        float: none;
        width: 100%;
        max-width: none;
        margin: 1rem 0;
    }
}

/* The wider column can carry a slightly larger picture without the text
   beside it thinning to a column of single words. */
@media (min-width: 64rem) {
    .photo {
        max-width: 280px;
    }
}

.photo img {
    display: block;
    width: 100%;
    height: auto;
    margin: 0;
    border-radius: var(--radius);
}

/* Set by reader.js on a photo with too little letter after it to wrap around:
   the last picture before another one, or the one at the end above "Love,
   Declan". It stands on its own instead, centered, still exactly where it was
   written.

   Last on purpose. These have the same specificity as the rules they undo, so
   source order is what decides, and that includes the two media queries above
   -- a photo that cannot be wrapped cannot be wrapped at any screen width.

   `fit-content` rather than a full-width block, and a capped height, because
   a portrait photo blown up to the width of the column is over a thousand
   pixels tall and turns one picture into a page of scrolling. */
.photo--block {
    float: none;
    width: fit-content;
    max-width: 100%;
    margin: 1.5rem auto;
}

.photo--block img {
    width: auto;
    max-width: 100%;
    max-height: 70vh;
}

/* The affordance is the cursor and nothing else. A caption printed over every
   picture in the archive read as clutter long before it read as an
   invitation, and a pointer device shows the zoom cursor on hover while a
   touch device gets people tapping pictures out of habit. The invitation that
   does survive is the one nobody can see: reader.js puts "View larger" on
   aria-label, because the frame is a button wrapping an image with empty alt
   text and would otherwise have no accessible name at all. */
.photo:focus-visible {
    outline: 3px solid var(--accent);
    outline-offset: 3px;
}

/* --- a run of photos, and the album under the letter --- */

/* One row of identical squares that scrolls sideways when it runs out of
   room, rather than a grid that reflowed into rows of whatever fit. Mixed
   portraits and landscapes at their own proportions read as a pile; a
   straight line of equal tiles reads as a set.

   Shared by both places pictures come in quantity -- the album of attached
   photos under a letter, and a burst the missionary pasted into one with no
   text between -- because they are the same thing to a reader looking at
   them, whatever the mail client did on the way in.

   `proximity`, not `mandatory`: snapping is a nicety here, and mandatory
   snapping fights anyone who wants to nudge the row a little, especially with
   a trackpad. */
.album,
.photo-row {
    display: flex;
    gap: 0.6rem;
    padding: 0 0 0.5rem;
    overflow-x: auto;
    scroll-snap-type: x proximity;
    /* Stops a swipe that runs off the end of the row from turning into the
       browser's back gesture or scrolling the page behind it. */
    overscroll-behavior-x: contain;
    scrollbar-width: thin;
    scrollbar-color: var(--rule) transparent;
}

.album {
    margin: 1.25rem 0 0;
    list-style: none;
}

.photo-row {
    /* A run can follow a floated photo from earlier in the letter, and a
       scrolling row overlapping one would be unusable. */
    clear: left;
    margin: 1.5rem 0;
}

.album li,
.photo-row .photo {
    /* No shrinking. A flex item's default is to give up width under pressure,
       which is exactly the reflow this layout exists to avoid -- eight photos
       would quietly squeeze to eight slivers instead of scrolling. */
    flex: 0 0 auto;
    scroll-snap-align: start;
}

/* Undoing the float and the block treatment, both of which are written with a
   single class and so lose to this on specificity rather than on order. The
   album selector is for a run reader.js has moved in from the end of a letter,
   which arrives as a frame rather than a link. */
.photo-row .photo,
.album .photo {
    float: none;
    width: auto;
    max-width: none;
    margin: 0;
}

.album a {
    display: block;
    border-radius: var(--radius);
    cursor: zoom-in;
}

/* The owner's way of taking back a picture they added. Positioned against the
   tile, which is why the list item needs to be a containing block -- and only
   the item, not `.album li` in general, would be the same rule written twice.
   Sits over the corner of the photo rather than beside it: a row of tiles with
   a control under some of them and not others reflows as soon as one is
   removed, and nothing that only owners see is worth that. */
.album li {
    position: relative;
}

.album__remove {
    position: absolute;
    top: 0.25rem;
    right: 0.25rem;
    width: 1.6rem;
    height: 1.6rem;
    padding: 0;
    border: 0;
    border-radius: 50%;
    /* Dark rather than tinted. It has to read against whatever photograph is
       underneath it, and a white sky is as likely as a night street. */
    background: rgba(0, 0, 0, 0.65);
    color: #fff;
    font-size: 1.1rem;
    line-height: 1;
    cursor: pointer;
}

.album__remove:hover {
    background: rgba(0, 0, 0, 0.85);
}

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

.album__remove[disabled] {
    cursor: progress;
    opacity: 0.6;
}

.album a:focus-visible {
    outline: 3px solid var(--accent);
    outline-offset: 3px;
}

.album img,
.photo-row img {
    display: block;
    width: 8.5rem;
    height: 8.5rem;
    max-height: none;
    object-fit: cover;
    border-radius: var(--radius);
    background: #eee;
}

@media (max-width: 37rem) {
    /* Smaller, so more than two tiles are visible at once -- a row that is cut
       off mid-photo is the only thing telling a reader there is more of it. */
    .album img,
    .photo-row img {
        width: 6.5rem;
        height: 6.5rem;
    }
}

/* --- the lightbox --- */

.lightbox {
    max-width: 100vw;
    max-height: 100vh;
    /* fit-content, not auto. The browser's own dialog rule uses fit-content
       here, and `auto` is not the same thing: a fixed element pinned to all
       four edges with an automatic height stretches to fill instead of
       shrinking to its photo, the automatic margins then resolve to nothing,
       and the picture ends up jammed against the top of the screen. */
    width: fit-content;
    height: fit-content;
    margin: auto;
    padding: 0;
    background: none;
    border: 0;
    overflow: visible;
}

.lightbox::backdrop {
    background: rgba(28, 25, 23, 0.88);
}

/* Leaves room for the close button underneath. A photo sized to the full
   height pushes the button off the bottom of the screen, which on a phone is
   the only way out that is visible. */
.lightbox__image {
    display: block;
    max-width: 96vw;
    max-height: 78vh;
    width: auto;
    height: auto;
    border-radius: var(--radius);
    background: #1c1917;
}

.lightbox__loading {
    display: grid;
    place-items: center;
    gap: 0.75rem;
    min-width: 12rem;
    min-height: 8rem;
    color: #fff;
    font-family: system-ui, sans-serif;
    font-size: 0.9rem;
}

.lightbox__loading::before {
    width: 2rem;
    height: 2rem;
    content: '';
    border: 3px solid rgba(255, 255, 255, 0.3);
    border-top-color: #fff;
    border-radius: 50%;
    animation: lightbox-spin 0.8s linear infinite;
}

.lightbox__loading--failed::before {
    display: none;
}

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

.lightbox__close {
    display: block;
    margin: 0.75rem auto 0;
    min-height: 44px;
    padding: 0.5rem 1.4rem;
    font-family: system-ui, sans-serif;
    font-size: 0.9rem;
    color: var(--ink);
    background: var(--paper);
    border: 0;
    border-radius: 999px;
    cursor: pointer;
}

.lightbox__close:focus-visible {
    outline: 3px solid var(--paper);
    outline-offset: 3px;
}

/* --- the word cloud --- */

/* A page of letters is a list of dates. This is the only view of the same
   archive that is about what is in them, so it opens over the top rather than
   replacing anything -- it is somewhere to look, not somewhere to go. */
.cloud {
    width: min(44rem, 94vw);
    margin: auto;
    padding: 1rem 1.25rem 1.5rem;
    color: var(--ink);
    background: var(--paper);
    border: 0;
    border-radius: var(--radius);
    overflow: hidden;
}

.cloud::backdrop {
    background: rgba(28, 25, 23, 0.6);
}

.cloud__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
}

.cloud__title {
    margin: 0;
    font-size: 1.1rem;
}

.cloud__close {
    flex: none;
    width: 36px;
    height: 36px;
    font-size: 1.4rem;
    line-height: 1;
    color: var(--ink-soft);
    background: none;
    border: 0;
    border-radius: var(--radius);
    cursor: pointer;
}

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

.cloud__close:focus-visible {
    outline: 3px solid var(--accent);
    outline-offset: 1px;
}

.cloud__note {
    margin: 0.2rem 0 1.25rem;
    font-family: system-ui, sans-serif;
    font-size: 0.8rem;
    color: var(--ink-soft);
}

/* The words are packed into this box by wordcloud2, which measures it and then
   positions every span inside it absolutely. So it needs a size of its own --
   a box that grows to fit its contents has nothing to pack into. */
.cloud__words {
    position: relative;
    width: 100%;
    height: min(58vh, 30rem);
}

/* Position, size and rotation are set on each span as it is placed. What is
   left here is everything about how a word looks rather than where it is. */
.cloud__word {
    cursor: pointer;
    border-radius: 4px;
}

/* One tone per word, picked off the word itself in reader.js. They are pastels
   pulled toward the ink rather than true pastels: at the small end a word is
   11px, and anything pale enough to look like sugar at that size cannot be
   read. Every one of these clears 5:1 against the paper. The first is the
   site's own accent, so the cloud belongs to the same page as the buttons. */
.cloud__word--0 { color: #2f5d50; } /* sea green, the accent */
.cloud__word--1 { color: #3a5c72; } /* slate blue */
.cloud__word--2 { color: #5a4a72; } /* muted plum */
.cloud__word--3 { color: #7b4a5c; } /* dusty rose */
.cloud__word--4 { color: #8a5a3c; } /* clay */
.cloud__word--5 { color: #6b6234; } /* olive */

/* Darkening to the ink rather than moving to some seventh color, because the
   word under the pointer has to look picked whichever of the six it started
   as, and there is no single color that reads as a change from all of them. */
.cloud__word:hover {
    color: var(--ink);
}

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

/* --- asking first --- */

/* Narrow on purpose. The question is one line and the detail is two, and a
   dialog wide enough for the archive would set them across a measure nobody
   reads a warning at. */
.ask {
    width: min(30rem, 92vw);
    margin: auto;
    padding: 1.25rem;
    color: var(--ink);
    background: var(--paper);
    border: 0;
    border-radius: var(--radius);
}

.ask::backdrop {
    background: rgba(28, 25, 23, 0.6);
}

.ask__question {
    margin: 0 0 0.5rem;
    font-size: 1.05rem;
    line-height: 1.35;
}

.ask__detail {
    margin: 0;
    font-size: 0.9rem;
    line-height: 1.5;
    color: var(--ink-soft);
    /* A run's report is a list of letters, one to a line, and the text is set
       with textContent -- so the newlines have to survive collapsing. */
    white-space: pre-line;
}

.ask__buttons {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 0.5rem;
    margin-top: 1.25rem;
}

.ask__button {
    min-height: 44px;
    padding: 0.5rem 1.1rem;
    font: inherit;
    font-size: 0.9rem;
    color: var(--ink);
    background: #fff;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    cursor: pointer;
}

.ask__button:hover {
    border-color: var(--accent);
}

/* Red, because most questions asked through this dialog are about something
   that will not come back. The same red as the archive's own delete. */
.ask__button--go {
    color: #fff;
    background: #9b2226;
    border-color: #9b2226;
}

/* Not all of them are, though. A question that only explains what is about to
   happen is a step towards something the owner asked for, and a red button on
   it would read as a warning about their own choice. */
.ask__button--calm {
    background: var(--accent);
    border-color: var(--accent);
}

/* At the far end of the row, away from the buttons: it is a preference rather
   than an answer, and it must not be catchable by a thumb reaching for Cancel. */
.ask__again {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    margin-right: auto;
    font-size: 0.85rem;
    color: var(--ink-soft);
    cursor: pointer;
}

.ask__box {
    width: 1rem;
    height: 1rem;
    accent-color: var(--accent);
}

/* --- book-only pages --- */

.book-part {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.8rem 0;
    border-bottom: 1px solid var(--rule);
}

.book-part h3,
.book-part p {
    margin: 0;
}

.book-part .note--fine,
.book-part-dialog .note--fine {
    font-size: 0.7rem;
}

.book-part__actions,
.book-part-dialog__actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 0.5rem;
}

.book-part-dialog {
    width: min(44rem, 92vw);
    margin: auto;
    padding: 1.25rem;
    color: var(--ink);
    background: var(--paper);
    border: 0;
    border-radius: var(--radius);
}

.book-part-dialog::backdrop {
    background: rgba(28, 25, 23, 0.6);
}

.book-part-editor {
    min-height: 14rem;
    max-height: 60vh;
    margin: 1rem 0;
    padding: 0.8rem;
    overflow: auto;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    background: #fff;
}

.book-part-editor[contenteditable="true"] {
    outline-color: var(--accent);
}

/* A letter that is out of view, seen by an owner.

   Dimmed rather than badged. The badge was a pill on its own line above the
   heading, which is a lot of page for a state that only says "nobody else can
   see this one" -- and on an archive with several held letters it turned the
   list into a column of warnings. Dimming says the same thing without taking
   any room, and it says it the way every other interface says it, so there is
   nothing to read in order to understand it.

   Opacity rather than a paler ink, so the pictures dim with the words. A held
   letter with full-strength photos beside gray text reads as broken. */
.post--hidden {
    opacity: 0.55;
}

/* Except once it is open, where the owner is reading it in order to decide
   about it and needs it legible. */
.post--hidden.post--open {
    opacity: 1;
}

/* The reason lives in the summary line with the photo count. Dimming can say
   that a letter is out of view and cannot say why, and why is the whole of
   what the owner needs in order to do anything about it. */
.post__held {
    color: #8a5a12;
    font-weight: 600;
}

.note {
    color: var(--ink-soft);
    font-size: 0.95rem;
}

/* For a note that explains a field rather than making a point of its own. */
.note--small {
    font-size: 0.8rem;
}

/* --- the mark beside a label --- */

/* One question and its answer, where the answer is folded away until it is
   asked for. The settings page carried four explanations standing open at
   once, which is longer than the form they explain and pushed the last
   question off a phone screen.

   A `details` again, for the reasons the masthead menu is one, and they share
   a `name` so opening the second closes the first. */
.field {
    /* The panel hangs from here rather than from its own mark. The mark sits
       at the end of the label, and on a phone that is most of the way across
       the screen -- so a panel anchored to it hung off the right edge. */
    position: relative;
    margin: 1rem 0;
}

.hint {
    display: inline-block;
    margin-left: 0.35rem;
}

.hint__mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.2rem;
    height: 1.2rem;
    border: 1px solid var(--rule);
    border-radius: 999px;
    font-family: system-ui, sans-serif;
    font-size: 0.72rem;
    font-weight: 700;
    line-height: 1;
    color: var(--ink-soft);
    cursor: pointer;
    /* Chrome and Safari would draw a disclosure triangle inside the circle. */
    list-style: none;
}

.hint__mark::-webkit-details-marker {
    display: none;
}

.hint__mark:hover,
.hint[open] .hint__mark {
    border-color: var(--accent);
    color: var(--accent);
}

/* Absolute, so opening one does not shove the field it belongs to down the
   page and move the input out from under the pointer. `top` is left alone for
   the same reason the menu panel leaves it: the flow position is already
   directly under the mark. */
.hint__panel {
    position: absolute;
    left: 0;
    z-index: 20;
    margin-top: 0.35rem;
    width: max-content;
    max-width: min(24rem, 100%);
    padding: 0.65rem 0.85rem;
    background: var(--paper);
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    box-shadow: 0 6px 20px rgb(0 0 0 / 12%);
    font-family: system-ui, sans-serif;
    font-size: 0.85rem;
    line-height: 1.5;
    color: var(--ink-soft);
}

/* An operator reading an archive they do not belong to.

   Loud on purpose, and at the top of the content rather than in the masthead:
   the failure it guards against is somebody forgetting whose letters these are
   while they are looking straight at them, so it has to sit where the reading
   happens. It is the one warning on this site that is meant to be slightly
   uncomfortable. */
.operator-banner {
    font-family: system-ui, sans-serif;
    font-size: 0.85rem;
    line-height: 1.5;
    margin: 0 0 1.25rem;
    padding: 0.7rem 0.9rem;
    border: 1px solid #d9a441;
    border-left-width: 5px;
    border-radius: var(--radius);
    background: #fdf0d5;
    color: #6b4f14;
}

/* An archive that has been deleted and not yet erased.

   Red where the operator banner is amber, and the difference is the point.
   That one is true on every visit to somebody else's archive, so it has to be
   readable without being alarming; this one is true almost never, and when it
   is, something is on a timer. Sharing its styling would have made it the
   second yellow box on the page and therefore the one nobody reads. */
.deleted-banner {
    font-family: system-ui, sans-serif;
    font-size: 0.85rem;
    line-height: 1.5;
    margin: 0 0 1.25rem;
    padding: 0.7rem 0.9rem;
    border: 1px solid #b42318;
    border-left-width: 5px;
    border-radius: var(--radius);
    background: #fef3f2;
    color: #7a271a;
}

.deleted-banner a {
    color: inherit;
}

/* Deleting an archive.

   Last on the settings page, behind a rule, and closed until somebody opens
   it. Somebody correcting a typo in a display name should not have to read
   past a delete button to do it, and the summary is the whole warning for
   anybody who is not looking for this. */
.rule {
    border: 0;
    border-top: 1px solid var(--rule);
    margin: 2.5rem 0 1.5rem;
}

.danger > summary {
    font-family: system-ui, sans-serif;
    font-size: 0.95rem;
    color: #9b2226;
    cursor: pointer;
}

.danger[open] {
    padding: 0.25rem 0.9rem 0.6rem;
    border: 1px solid #e3c4c5;
    border-radius: var(--radius);
    background: #fdf5f5;
}

.danger[open] > summary {
    margin: 0.5rem 0 0;
}

.danger code {
    font-size: 0.95em;
    padding: 0.05rem 0.3rem;
    border-radius: 3px;
    background: #efdcdc;
}

/* --- the operator's list of deleted archives --- */

/* A table, which nothing else on this site uses, because this is the one page
   written for an operator rather than for a family. What it shows is six
   facts about each of several archives, and every other shape would be a
   worse way to read them. It is ordinarily empty. */
.deletions {
    width: 100%;
    border-collapse: collapse;
    font-family: system-ui, sans-serif;
    font-size: 0.92rem;
    margin: 1.5rem 0;
}

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

.deletions th {
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--muted);
    border-bottom-width: 2px;
}

/* A reason is free text somebody typed into a form and can be a sentence. It
   is the only column allowed to be long, and it wraps rather than pushing the
   dates off the side of a phone. */
.deletions td:nth-child(4) {
    max-width: 18rem;
}

/* The last-received view is the one that grows without bound: every archive the
   service has ever had is a row in it, for as long as it exists. Smaller and
   tighter than the others so the answer somebody came for stays on one
   screen. */
.deletions--dense {
    font-size: 0.8rem;
}

.deletions--dense th,
.deletions--dense td {
    padding: 0.3rem 0.6rem;
}

.deletions--dense th {
    font-size: 0.68rem;
}

/* Numbers, right-aligned so the eye can compare them down the column, and on
   figures of the same width so they do not shuffle as they are filled in. */
.deletions th.count,
.deletions td.count {
    text-align: right;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* --- how big the service is --- */

/* A row of boxes rather than a table: there is one value per label and nothing
   to compare across rows, which is the only thing a table would buy. */
.tally {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    margin: 1.5rem 0;
    padding: 0;
    font-family: system-ui, sans-serif;
}

.tally__box {
    flex: 1 1 8rem;
    padding: 0.6rem 0.8rem;
    border: 1px solid var(--rule);
    border-radius: 6px;
}

.tally__box dt {
    font-size: 0.68rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--muted);
}

.tally__number {
    margin: 0.1rem 0 0;
    font-size: 1.6rem;
    line-height: 1.1;
    font-variant-numeric: tabular-nums;
}

.tally__aside {
    margin: 0.1rem 0 0;
    font-size: 0.68rem;
    color: var(--ink-soft);
}

/* The refusals table is the one with a subject line in it, which is somebody
   else's words and the longest thing on the page. It wraps; the dates beside
   it do not. */
#refused td:nth-child(3) {
    max-width: 18rem;
}

/* Three buttons in one cell, and on a phone they stack rather than squeezing
   the columns beside them. The gap is what stops "Start it anyway" from
   sitting close enough to "Retry" to be hit by mistake. */
.actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
}

/* --- jump links --- */

/* The box at the top of a page that offers to send you further down it. Shared
   by the questions page and the getting-started page, which is the reason it
   lives up here rather than with either of them: both open by asking the
   reader to say which of several things they are, and the answer to that
   question should not look like a different control depending on which page
   asked it.

   Links rather than a fold-out, because every heading below one of these has
   an id and is linked to from elsewhere in the service -- a refusal on the
   People page can point at the one answer that explains it -- and because
   find-in-page only reaches text a browser has already laid out. */
.contents {
    background: #faf8f4;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    padding: 0.25rem 1.25rem 1rem;
    margin: 2rem 0;
}

.contents h2 {
    font-size: 1.05rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--ink-soft);
}

/* Groups within the box. The getting-started page has too few links to need
   them; the questions page would be a wall of forty without. */
.contents h3 {
    font-size: 1rem;
    margin-bottom: 0.3rem;
}

.contents ul {
    margin: 0 0 1rem;
    padding-left: 1.2rem;
}

.contents li {
    margin-bottom: 0.35rem;
}

/* --- the questions page --- */

/* The topic headings between groups of questions. Quieter than the questions
   themselves, which are what somebody is actually scanning for. */
.topic {
    margin-top: 3rem;
    padding-bottom: 0.4rem;
    border-bottom: 2px solid var(--rule);
    font-size: 1.05rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--ink-soft);
}

/* Room above a heading jumped to from the contents, so it does not land
   flush against the top of the window with its answer cut off. */
[id] {
    scroll-margin-top: 1.5rem;
}

/* Says which question was jumped to. Landing halfway down a page of similar
   headings, the one you asked for is not otherwise distinguishable from its
   neighbours. */
h3:target {
    background: #fdf6e3;
    box-shadow: -0.6rem 0 0 #fdf6e3, 0.6rem 0 0 #fdf6e3;
}

/* The numbered instructions on the getting-started page. Each step is a bold
   sentence with its explanation underneath, so the list reads as a sequence at
   a glance and as prose when someone stops to actually follow it. */
.steps {
    padding-left: 1.4rem;
}

.steps>li {
    margin-bottom: 1.5rem;
}

.steps>li>.note:last-child {
    margin-bottom: 0;
}

/* --- who made this --- */

.about-copy {
    margin-bottom: 2.5rem;
}

/* --- who can read an archive --- */

.people {
    list-style: none;
    margin: 0 0 2rem;
    padding: 0;
}

/* Deliberately denser than the rest of the site. This is a list to scan rather
   than prose to read, and at thirty relatives a full-size row puts the person
   being looked for below the fold on a phone. */
.people__row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem;
    padding: 0.3rem 0;
    font-size: 0.8rem;
    border-bottom: 1px solid var(--rule);
}

/* The controls come down with the row. Below the 34px the rest of the site
   uses, which is the trade for the density -- acceptable only because Remove
   asks before it does anything. */
.people__row .button--compact {
    min-height: 26px;
    padding: 0.15rem 0.45rem;
    font-size: 0.68rem;
}

/* Square, so the glyph sits in the middle of it rather than on a baseline. */
.button--icon {
    justify-content: center;
    width: 26px;
    padding: 0;
    font-size: 1rem;
    line-height: 1;
}

/* Takes the slack so the controls sit right, and wraps rather than truncating:
   the whole point of the row is the address, and an address elided in the
   middle is one somebody cannot check before pressing Remove. */
.people__who {
    position: relative;
    flex: 1 1 12rem;
    overflow-wrap: anywhere;
}

/* The role, sitting against the address it describes.

   A pill rather than a word, and on the left rather than out by the buttons.
   Between the address and the controls it had no fixed position: how far in it
   sat depended on how many buttons the row happened to have, so the one column
   every row shares was the only ragged thing on the page. */
.role {
    display: inline-block;
    margin-left: 0.4rem;
    padding: 0.05rem 0.5rem;
    border: 1px solid var(--rule);
    border-radius: 999px;
    font-family: system-ui, sans-serif;
    font-size: 0.6rem;
    font-weight: 600;
    letter-spacing: 0.03em;
    color: var(--ink-soft);
    white-space: nowrap;
}

/* Filled rather than colored. Owners can remove people, so the distinction has
   to survive being read by somebody who cannot tell the two greens apart. */
.role--owner {
    border-color: transparent;
    background: rgb(0 0 0 / 7%);
    color: var(--ink);
}

/* Nobody has accepted this one yet, and a solid outline says they have. */
.role--pending {
    border-style: dashed;
}

/* Said in place of the buttons the row has not got, so it must not be the
   loudest thing in a row it is only explaining. */
.people__aside {
    color: var(--ink-soft);
    font-size: 0.72rem;
}

.people__controls {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

/* Why a row's button did nothing. Full-width so it drops onto its own line
   under the row it belongs to -- squeezed in beside the buttons it would
   push them off the edge on a phone, which is where somebody is most likely
   to be pressing Resend in the first place. */
.people__trouble {
    flex: 1 0 100%;
    margin: 0;
    color: var(--ink-soft);
}

/* An address our mail is not reaching. Dimmed the same way and by the same
   amount as a hidden letter, because it means the same thing to the eye: still
   here, still real, not currently working. The row keeps all its controls at
   full contrast -- Resend is very often the thing to press next, and dimming
   the remedy along with the problem would be the wrong half. */
.people__row--undelivered .people__address,
.people__row--undelivered .people__aside {
    opacity: 0.55;
}

/* Warm rather than red: nobody has done anything wrong, and an alarming color
   beside a grandmother's address reads as an accusation. Two words in the row
   instead of a sentence under it -- three bad addresses in a list of thirty
   cost three extra lines to say one thing. The sentence is on the title. */
.people__undelivered {
    font-size: 0.72rem;
    color: #8a5a12;
    white-space: nowrap;
    cursor: help;
}

/* Exempt from the dimming above: it is the one part of the row that is the point. */
.people__row--undelivered .people__undelivered {
    opacity: 1;
}

/* How many people this is, so a long list can be checked against a number. */
.people__count {
    margin: 0 0 0.5rem;
}

/* The address box on the people page. Wide and tall enough that a pasted
   list of a dozen relatives is visible as a list, which is what makes a typo
   findable before it is sent. */
.addresses {
    width: 100%;
    max-width: 32rem;
    box-sizing: border-box;
    font: inherit;
    padding: 0.5rem;
    border: 1px solid var(--rule);
    border-radius: 4px;
    resize: vertical;
}

.state {
    padding: 3rem 0;
    text-align: center;
    color: var(--ink-soft);
}

/* The wait for the first letters is a cold start and runs to several seconds,
   which is long enough that an unmoving line reads as a page that has stopped
   rather than one still working.

   The dots hang off the end of the text rather than sitting in it, because the
   line is centred and a run of dots that grows and shrinks in the flow would
   rock the whole sentence from side to side. Every code path that has
   something to say replaces the message wholesale, which takes this span with
   it -- so there is nothing to switch off when the waiting ends. */
.waiting {
    position: relative;
}

.waiting::after {
    position: absolute;
    left: 100%;
    /* Doubles as the fallback. A browser that will not animate `content` keeps
       this and the line reads exactly as it did before. */
    content: '\2026';
    animation: waiting 1.2s steps(1) infinite;
}

@keyframes waiting {
    0% {
        content: '.';
    }

    33% {
        content: '..';
    }

    66% {
        content: '...';
    }
}

@media (prefers-reduced-motion: reduce) {
    .waiting::after {
        animation: none;
    }
}

/* Shown instead of the letters when the archive refuses. Deliberately not
   styled as an error -- the overwhelmingly likely cause is signing in with the
   wrong one of several accounts, which is a wrong turn rather than a fault. */
.denied {
    margin: 2.5rem 0;
    padding: 1.5rem;
    border: 1px solid var(--rule);
    border-radius: 6px;
    background: #faf8f4;
}

.denied__title {
    margin-top: 0;
    font-size: 1.25rem;
}

/* The address is the whole point of the panel, so let it wrap rather than
   spill out of the box on a phone. */
.denied__who {
    overflow-wrap: anywhere;
}

.denied__actions {
    margin: 1.5rem 0;
}

/* --- search --- */

/* Sticky, because the reason to search a long archive is to move around
   inside it, and a next/previous control that scrolls off the top the moment
   you use it is a control you can use exactly once. The masthead does not
   scroll with the page, so top: 0 is the top of the viewport. */
.search {
    position: sticky;
    top: 0;
    z-index: 5;
    margin: 1.2rem 0 0;
    padding: 0.4rem 0;
    background: var(--paper);
    /* The letters underneath have to stop somewhere visible rather than
       sliding out from behind the box mid-word. */
    box-shadow: 0 0.6rem 0.6rem -0.6rem rgba(28, 25, 23, 0.18);
}

/* One row, always open. It is a single short line, so it earns its place at
   the top of the page instead of asking to be summoned. */
.search__fields {
    position: relative;
}

.search__label {
    font-family: system-ui, sans-serif;
    font-size: 0.9rem;
    color: var(--ink-soft);
}

/* Inside the box rather than beside it, so the icon costs width and not
   height. Nothing is clickable about it -- the box behind it already is. */
.search__icon {
    position: absolute;
    top: 50%;
    left: 0.6rem;
    transform: translateY(-50%);
    color: var(--ink-soft);
    pointer-events: none;
}

.search__input {
    width: 100%;
    min-height: 36px;
    /* The sides clear the icon under the text and the button on top of it. */
    padding: 0.3rem 2.2rem 0.3rem 2.2rem;
    font-family: inherit;
    font-size: 0.95rem;
    color: var(--ink);
    background: #fff;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
}

.search__input:focus-visible {
    outline: 3px solid var(--accent);
    outline-offset: 1px;
}

.search__input::placeholder {
    color: #a8a29e;
}

/* Several browsers only paint their own cancel button while the pointer is
   over the field, which is a control a phone can never reach. */
.search__input::-webkit-search-cancel-button {
    display: none;
}

.search__clear {
    position: absolute;
    top: 50%;
    right: 0.2rem;
    width: 30px;
    height: 30px;
    transform: translateY(-50%);
    font-size: 1.15rem;
    line-height: 1;
    color: var(--ink-soft);
    background: none;
    border: 0;
    border-radius: var(--radius);
    cursor: pointer;
}

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

.search__clear:focus-visible {
    outline: 3px solid var(--accent);
    outline-offset: 1px;
}

.search__nav {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 0.3rem;
    margin-top: 0.3rem;
}

.search__position {
    font-family: system-ui, sans-serif;
    font-size: 0.78rem;
    color: var(--ink-soft);
    margin: 0 auto 0 0;
}

/* Smaller than the 48px the letters use. These are a pair of arrows a reader
   taps repeatedly with a thumb already resting on them, not a target to be
   found cold, and 34px still clears the 24px minimum. */
.search__step {
    min-width: 34px;
    min-height: 34px;
    font-size: 0.9rem;
    line-height: 1;
    color: var(--ink);
    background: #fff;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    cursor: pointer;
}

.search__step:hover {
    border-color: var(--accent);
}

.search__step:focus-visible {
    outline: 3px solid var(--accent);
    outline-offset: 1px;
}

/* Every match, marked. The plain one is a highlighter; the current one is
   where the reader is standing, and has to be findable at a glance after the
   page scrolls to it. */
.hit {
    background: #fde68a;
    color: inherit;
    border-radius: 3px;
    padding: 0 0.08em;
}

.hit--current {
    background: var(--accent);
    color: var(--accent-ink);
}

/* --- owner controls --- */

/* Provisional. The reader's layout is due a rework (see the UI backlog in
   docs/plan.md); this is the smallest treatment that makes the moderation
   endpoints usable without pretending to be the final design. */

.admin__area {
    margin-top: 1rem;
    padding-top: 0.75rem;
    border-top: 1px dashed var(--rule);
}

.admin {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
}

.admin__button {
    min-height: 44px;
    padding: 0.4rem 0.9rem;
    font: inherit;
    font-size: 0.85rem;
    color: var(--ink);
    background: #fff;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    cursor: pointer;
}

.admin__button:hover {
    border-color: var(--accent);
}

.admin__button:focus-visible {
    outline: 3px solid var(--accent);
    outline-offset: 1px;
}

/* A glyph where the words were, so the padding that set a label has to go or
   the button keeps the width of a word it no longer shows. Square, because
   nothing in it wants to be wider than it is tall. */
.admin__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    padding: 0;
    font-size: 0.95rem;
}

/* Whose buttons these are. Worth the words because the controls themselves no
   longer carry any until they are hovered. */
.admin__label {
    font-size: 0.85rem;
    color: var(--ink-soft);
}

.admin__status {
    font-size: 0.85rem;
    color: var(--ink-soft);
}

/* Above the controls and hard left. It is a sentence to be read rather than a
   control to be used, and sharing the row put it wherever the buttons happened
   to leave room -- which on a phone was underneath them. */
.admin__note {
    display: block;
    margin-bottom: 0.5rem;
    font-size: 0.8rem;
    font-style: italic;
    color: var(--ink-soft);
}

.admin__button--primary {
    color: #fff;
    background: var(--accent);
    border-color: var(--accent);
}

/* Where a picture comes from. Positioned, unlike the account menu, because the
   owner bar is a wrapping row and the panel has to hang under this button
   rather than under whichever end of the row it happens to be nearest. */
.admin__menu {
    position: relative;
}

.admin__trigger {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    /* Chrome and Safari draw a disclosure triangle that would sit inside
       something styled as a button. */
    list-style: none;
}

.admin__trigger::-webkit-details-marker {
    display: none;
}

.admin__trigger::after {
    content: '';
    flex: none;
    width: 0.4em;
    height: 0.4em;
    border-right: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    transform: translateY(-0.15em) rotate(45deg);
}

.admin__menu[open] .admin__trigger::after {
    transform: translateY(0.1em) rotate(-135deg);
}

.admin__panel {
    position: absolute;
    left: 0;
    z-index: 20;
    margin: 0.35rem 0 0;
    padding: 0.15rem 0;
    min-width: 11rem;
    list-style: none;
    background: var(--paper);
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    box-shadow: 0 6px 20px rgb(0 0 0 / 12%);
}

.admin__item {
    display: block;
    width: 100%;
    /* A tap target, not a line of prose. */
    padding: 0.55rem 0.85rem;
    font: inherit;
    font-size: 0.85rem;
    text-align: left;
    color: var(--ink);
    background: none;
    border: 0;
    cursor: pointer;
}

.admin__item:hover,
.admin__item:focus-visible {
    background: rgb(0 0 0 / 4%);
}

/* The subject stands in for the heading while editing, so it is set in the
   same face and size rather than shrinking to a form control. */
.admin__subject {
    display: block;
    width: 100%;
    margin: 0 0 0.75rem;
    padding: 0.3rem 0.5rem;
    font: inherit;
    font-size: 1.15rem;
    font-weight: 600;
    color: var(--ink);
    background: #fff;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
}

/* The letter is edited where it sits. The rule down the side is the only
   thing that says so, which is the point -- the text must not move or reflow
   when editing starts, or the owner loses their place. Padding is added to
   the side that gains the border so the total width is unchanged. */
.post__body--editing {
    padding-left: 0.75rem;
    border-left: 3px solid var(--accent);
    outline: none;
}

.post__body--editing:focus-visible {
    outline: none;
}

/* A picture is a thing you can select and delete, so it should look like one. */
.post__body--editing img {
    cursor: pointer;
}

.admin__subject:focus-visible {
    outline: 3px solid var(--accent);
    outline-offset: 1px;
}

/* --- the book's cover --- */

.cover {
    display: flex;
    flex-wrap: wrap;
    gap: 1.5rem;
    margin: 1rem 0 0;
    /* Without this the board stretches to whatever height the choices beside
       it happen to be, which overrides its aspect-ratio and makes picking a
       picture -- the thing that opens the tallest panel -- distort the
       preview most. */
    align-items: flex-start;
}

/* US Letter, which is what the book is. The proportions matter more than the
   size here: the whole panel exists to answer what the thing will look like,
   and a preview in the wrong shape answers it wrongly. */
.cover__board {
    position: relative;
    flex: 0 0 auto;
    width: 11rem;
    aspect-ratio: 8.5 / 11;
    border-radius: 3px;
    /* Boards are printed on card and photographed under a light. Neither is
       reproducible in a browser, but a shadow at least stops it reading as a
       colored rectangle in the middle of a form. */
    box-shadow: 0 2px 8px rgba(28, 25, 23, 0.25);
    overflow: hidden;
    background: #ffffff;
}

.cover__plate {
    display: block;
    width: 100%;
    /* The share of the board the picture takes, and it has to match
       PLATE_HEIGHT in book.js -- this preview is only worth having while the
       two agree. */
    height: 52%;
    object-fit: cover;
}

/* Both of these are positioned from the script, because where the name sits
   depends on whether there is a picture above it -- which is exactly what the
   book does. */
.cover__name {
    position: absolute;
    left: 8%;
    right: 8%;
    font-family: Georgia, 'Times New Roman', serif;
    font-size: 0.85rem;
    line-height: 1.2;
    text-align: center;
}

.cover__mission,
.cover__dates {
    position: absolute;
    left: 8%;
    right: 8%;
    font-family: Georgia, 'Times New Roman', serif;
    font-style: italic;
    font-size: 0.6rem;
    line-height: 1.2;
    text-align: center;
}

.cover__dates {
    font-style: normal;
    font-size: 0.56rem;
}

.cover__choices {
    flex: 1 1 16rem;
}

.cover__legend {
    font-family: system-ui, sans-serif;
    font-size: 0.9rem;
    margin: 0 0 0.5rem;
}

.cover__cloths {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-bottom: 1.5rem;
}

/* Big enough to be a target on a phone, which is the only reason a swatch is
   this size -- 44px is the floor everything on this site clears. */
.cover__cloth {
    width: 44px;
    height: 44px;
    padding: 0;
    border: 2px solid var(--rule);
    border-radius: 50%;
    cursor: pointer;
}

.cover__cloth[aria-pressed='true'] {
    border-color: var(--ink);
    /* A ring rather than a tick: a tick has to be drawn in an ink that reads
       against eight different colors, and this reads against all of them. */
    box-shadow: 0 0 0 2px var(--paper) inset;
}

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

.cover__actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}

.cover__grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(5rem, 1fr));
    gap: 0.4rem;
    max-height: 18rem;
    overflow-y: auto;
    margin-top: 0.75rem;
    padding: 0.4rem;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
}

.cover__thumb {
    display: block;
    width: 100%;
    aspect-ratio: 1;
    padding: 0;
    border: 2px solid transparent;
    border-radius: 4px;
    background: none;
    cursor: pointer;
    overflow: hidden;
}

.cover__thumb[aria-pressed='true'] {
    border-color: var(--accent);
}

.cover__thumb:focus-visible {
    outline: 3px solid var(--accent);
    outline-offset: 1px;
}

.cover__thumb img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}
