/*
 * NeverForget — family-facing UI.
 *
 * Vanilla CSS, no build step (ROADMAP B4). Mobile-first: every rule here is
 * written for a phone held in a kitchen, and the few wider layouts are added
 * back at a breakpoint rather than reduced down to it.
 *
 * The frame is the brand. The device is a dark round panel with one amber
 * highlight, and this borrows that vocabulary without copying it literally —
 * a photo-forward, quiet page whose only saturated colour is the same amber
 * the warning ring uses. Light by default, because the primary client is a
 * phone in daylight; dark honoured when the phone asks for it.
 */

:root {
  color-scheme: light dark;

  /* Surfaces, warm rather than neutral — a photo sits better on warm grey. */
  --bg: #f7f5f2;
  --surface: #ffffff;
  --surface-sunken: #efece7;
  --border: #e2ddd5;

  --text: #1c1a17;
  --text-soft: #6b655c;
  /*
   * B6 phase 7. This was #97918a and it failed WCAG AA everywhere it was used:
   * 2.65:1 on --surface-sunken, 2.87:1 on --bg, and 3.12:1 even on plain white
   * — against the 4.5:1 that 0.85 rem text needs. It is not decoration either;
   * `.muted`, `.device__facts dt`, `.member__refusal` and `.invitation__by` are
   * all real sentences somebody has to read.
   *
   * #706a63 is the lightest tone on this hue that clears 4.5:1 against every
   * surface it meets: 4.53 sunken, 4.91 bg, 5.34 white.
   *
   * Which leaves it five units from --text-soft (4.89 sunken), and that is the
   * honest finding rather than an oversight: on a warm light surface there is
   * no room for a third text tone that is both quieter than the second and
   * still readable. So the hierarchy is carried by size and weight — .muted is
   * 0.85 rem, .prose is 1 rem — and the colour difference is what is left over
   * rather than what does the work. Do not "restore the contrast" between them
   * by lightening this one; that is the bug.
   */
  --text-faint: #706a63;

  /* The device's highlight. The fill stays as it is on the panel; the text
   * variant is darkened, because #ffb000 on white is unreadable. */
  --accent: #ffb000;
  --accent-ink: #8a5c00;
  --accent-wash: #fff4dc;

  --danger: #a33a26;
  --danger-wash: #fdeeea;

  --radius: 14px;
  --radius-lg: 20px;

  /* One spacing scale, used everywhere. Generous by default. */
  --s1: 0.25rem;
  --s2: 0.5rem;
  --s3: 0.75rem;
  --s4: 1rem;
  --s5: 1.5rem;
  --s6: 2rem;
  --s7: 3rem;

  --shadow: 0 1px 2px rgba(28, 26, 23, 0.04), 0 6px 20px rgba(28, 26, 23, 0.06);
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #121114;
    --surface: #1b191d;
    --surface-sunken: #232025;
    --border: #322e35;

    --text: #f2efea;
    --text-soft: #a8a29b;
    /* The same repair, the other way up: #7d7770 gave 3.64:1 on the sunken
     * surface. #8d8780 clears 4.53 there, 4.91 on --surface and 5.30 on --bg.
     * Dark has the room light does not — --text-soft sits at 6.37 — so the
     * three tones stay visibly three. */
    --text-faint: #8d8780;

    --accent: #ffb000;
    --accent-ink: #ffc44d;
    --accent-wash: #2b2213;

    --danger: #e8907c;
    --danger-wash: #2e1a16;

    --shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 6px 20px rgba(0, 0, 0, 0.35);
  }
}

* {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 1rem/1.55 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  /* Safe areas: a phone in a case, held one-handed, with a home indicator. */
  padding: 0 env(safe-area-inset-right) env(safe-area-inset-bottom)
    env(safe-area-inset-left);
}

h1,
h2,
h3 {
  margin: 0;
  font-weight: 620;
  letter-spacing: -0.01em;
  line-height: 1.25;
}

h1 {
  font-size: 1.5rem;
}

h2 {
  font-size: 1.1rem;
}

a {
  color: var(--accent-ink);
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}

/* --- shell ---------------------------------------------------------------- */

/*
 * The skip link (B6 phase 7). Off-screen until it takes focus, which is the
 * one place `top` may be animated away rather than `display: none` — a hidden
 * element is not focusable, and a skip link that cannot be tabbed to is
 * decoration.
 *
 * It is deliberately *not* .visually-hidden: that class clips, and a clipped
 * element stays clipped when focused.
 */
.skiplink {
  position: absolute;
  top: -100%;
  left: var(--s4);
  z-index: 10;
  padding: 0.7rem 1rem;
  border-radius: 0 0 var(--radius) var(--radius);
  background: var(--accent);
  color: #241800;
  font-weight: 640;
  text-decoration: none;
}

.skiplink:focus {
  top: 0;
}

/*
 * <main> takes focus from the skip link and nowhere else, so it needs no ring
 * of its own: the person pressed the link a moment ago and knows where they
 * are. Removing an outline is usually the wrong instinct — here the element is
 * not in the tab order at all (tabindex="-1") and can only be reached
 * deliberately.
 */
main:focus {
  outline: none;
}

/*
 * The region is rendered on every page so the live region exists before a
 * message is put into it, which means the spacing has to be conditional
 * instead. A modifier rather than `:empty` — the template leaves whitespace
 * inside the element, and `:empty` counts a whitespace text node as content,
 * so `:empty` would never match and every page would gain 1.5 rem of nothing.
 */
.messages--filled {
  margin-bottom: var(--s5);
}

.shell {
  max-width: 34rem;
  margin: 0 auto;
  padding: var(--s4) var(--s4) var(--s7);
}

.masthead {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  padding: var(--s4) 0 var(--s5);
}

.wordmark {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  font-weight: 640;
  font-size: 1.05rem;
  letter-spacing: -0.015em;
  color: var(--text);
  text-decoration: none;
}

/* The frame, in miniature: a ring with one lit segment. */
.wordmark::before {
  content: "";
  width: 1.1rem;
  height: 1.1rem;
  border-radius: 50%;
  border: 2.5px solid var(--border);
  border-top-color: var(--accent);
  flex: none;
}

.masthead nav {
  display: flex;
  align-items: center;
  gap: var(--s4);
  font-size: 0.9rem;
}

.masthead nav a,
.linkbutton {
  color: var(--text-soft);
  text-decoration: none;
}

.masthead nav a:hover,
.linkbutton:hover {
  color: var(--text);
  text-decoration: underline;
}

/* A logout has to be a POST in Django 5, so it is a form that reads as a link. */
.linkbutton {
  appearance: none;
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  cursor: pointer;
}

.inline-form {
  display: inline;
}

/* --- cards and panels ----------------------------------------------------- */

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
  padding: var(--s5);
}

.stack > * + * {
  margin-top: var(--s4);
}

.prose {
  color: var(--text-soft);
  margin: 0;
}

.muted {
  color: var(--text-faint);
  font-size: 0.85rem;
}

/* --- forms ---------------------------------------------------------------- */

.field {
  display: block;
}

.field + .field {
  margin-top: var(--s4);
}

.field label {
  display: block;
  margin-bottom: var(--s2);
  font-size: 0.9rem;
  font-weight: 560;
  color: var(--text-soft);
}

input[type="text"],
input[type="password"],
input[type="number"],
input[type="email"],
select,
textarea {
  width: 100%;
  padding: 0.7rem 0.8rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: inherit;
  /* 16px exactly: anything smaller makes iOS zoom on focus. */
  font-size: 16px;
  font-family: inherit;
}

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

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  padding: 0.8rem 1rem;
  border: 0;
  border-radius: var(--radius);
  background: var(--accent);
  color: #241800;
  font: inherit;
  font-weight: 640;
  cursor: pointer;
  text-decoration: none;
}

.button:hover {
  filter: brightness(0.96);
}

.button--quiet {
  background: var(--surface-sunken);
  color: var(--text);
  border: 1px solid var(--border);
}

.helptext,
.errorlist {
  margin: var(--s2) 0 0;
  padding: 0;
  font-size: 0.85rem;
}

.helptext {
  color: var(--text-faint);
}

.errorlist {
  list-style: none;
  color: var(--danger);
}

/* --- messages and banners -------------------------------------------------- */

.banner {
  border-radius: var(--radius);
  padding: var(--s3) var(--s4);
  font-size: 0.92rem;
  border: 1px solid var(--border);
  background: var(--surface-sunken);
}

.banner--error {
  background: var(--danger-wash);
  border-color: var(--danger);
  color: var(--danger);
}

.banner--success {
  background: var(--accent-wash);
  border-color: var(--accent);
  color: var(--accent-ink);
}

.banner + .banner {
  margin-top: var(--s2);
}

/* --- event cards ----------------------------------------------------------- */

/*
 * One column on a phone, a comfortable grid once there is room. The cards are
 * not links yet — editing is phase 2 — so nothing here has a hover state that
 * promises a click.
 */
/*
 * ALWAYS ONE COLUMN, at every width. Do not add a multi-column breakpoint.
 *
 * The list is a timeline, not a gallery — the page's own subtitle says "in
 * derselben Reihenfolge", and the order is the content. Two columns break
 * chronological reading: the eye goes left-to-right, so the second-soonest
 * event lands beside the soonest rather than under it, and "what comes next"
 * stops being answerable by looking down the page.
 *
 * A single-column grid rather than plain blocks, so `gap` keeps the spacing in
 * one place.
 */
/*
 * `role="list"` on the markup, and it is not redundant with the <ul> (B6 phase
 * 7). Safari drops list semantics from any list whose `list-style` is `none`,
 * on the reasoning that an unbulleted list was probably not meant as one — so
 * VoiceOver stops announcing "Liste, 5 Einträge" on a page whose entire content
 * is a list of five upcoming events, and the count is the thing that page is
 * for. The audience is iPhones, so this is the default browser here rather
 * than an edge case. Every list in this stylesheet that hides its markers
 * carries the role: .cards, .members, .invitations, .codes.
 */
.cards {
  list-style: none;
  margin: var(--s5) 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s4);
}

/*
 * Horizontal at every width: photograph left, text right, thumbnail centred
 * against the card's left edge.
 *
 * Including on a narrow phone. At ~380px the text column still gets ~260px,
 * which holds "in 166 Tagen · 29.01. · wird 30" comfortably — and stacking
 * would roughly double the card's height, halving how many events fit on one
 * screen. For a list whose whole job is "what is coming", seeing more of it at
 * once beats giving each entry a bigger picture.
 */
.card--event {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  gap: var(--s4);
  padding: var(--s4);
}

/*
 * Circular, like the panel. The frame is a round display and a square crop of
 * the same photograph would read as a different product.
 */
.thumb {
  width: 4.5rem;
  height: 4.5rem;
  flex: none;
  border-radius: 50%;
  overflow: hidden;
  background: var(--surface-sunken);
}

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

.event__name {
  font-size: 1.05rem;
  overflow-wrap: anywhere;
}

.event__when {
  margin: var(--s1) 0 0;
  font-size: 0.95rem;
  color: var(--text-soft);
}

.event__days {
  font-weight: 620;
  color: var(--text);
  /* Lining figures, so a column of countdowns does not jitter. */
  font-variant-numeric: tabular-nums;
}

.event__sep {
  margin: 0 0.4em;
  color: var(--text-faint);
}

.event__meta {
  margin: var(--s3) 0 0;
  display: flex;
  align-items: center;
  gap: var(--s3);
  font-size: 0.8rem;
  color: var(--text-faint);
}

.badge {
  display: inline-block;
  padding: 0.15rem 0.55rem;
  border-radius: 999px;
  background: var(--surface-sunken);
  border: 1px solid var(--border);
  color: var(--text-soft);
  font-size: 0.78rem;
  white-space: nowrap;
}

/* The preview pin, on a card. Filled rather than outlined, because unlike the
 * other badges it says something is true *right now* and will stop being true
 * on its own. */
.badge--preview {
  background: var(--accent);
  border-color: transparent;
  color: #241800;
  font-weight: 600;
}

.preview {
  margin-top: var(--s6);
}

.preview__lead {
  margin: 0;
}

.preview__left {
  margin: var(--s2) 0 var(--s3);
  font-size: 1.4rem;
  font-weight: 640;
  color: var(--accent-ink);
  font-variant-numeric: tabular-nums;
}

.preview__active {
  border-color: var(--accent);
  box-shadow: var(--shadow), inset 0 0 0 1px var(--accent);
}

.preview .muted {
  margin: var(--s2) 0 var(--s3);
}

.badge--group {
  background: var(--accent-wash);
  border-color: transparent;
  color: var(--accent-ink);
}

/*
 * The three states, borrowed from the panel rather than invented here.
 *
 * "calm" is the plain card and is what the year mostly looks like. "soon" is
 * the three days in which the device draws its amber warning ring, so the card
 * grows the same amber at its edge. "today" is the celebration state, and it
 * is the only place on this page where a colour fills rather than outlines.
 */
.card--soon {
  border-color: var(--accent);
  box-shadow: var(--shadow), inset 0 0 0 1px var(--accent);
}

.card--today {
  border-color: transparent;
  background: linear-gradient(180deg, var(--accent-wash), var(--surface) 70%);
  box-shadow: var(--shadow), inset 0 0 0 2px var(--accent);
}

.card--today .event__days {
  color: var(--accent-ink);
}

/* The photo gets the ring too, the way the panel puts it around the display. */
.card--today .thumb,
.card--soon .thumb {
  box-shadow: 0 0 0 2px var(--accent);
}

@media (min-width: 40rem) {
  /* No column change here. See .cards — the single column is deliberate at
   * every width, and this is the breakpoint where somebody would reach for a
   * grid. Only the thumbnail grows. */
  .thumb {
    width: 5rem;
    height: 5rem;
  }
}

/* --- event form and detail -------------------------------------------------- */

.pagehead {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s4);
  flex-wrap: wrap;
}

.button--compact {
  width: auto;
  padding: 0.55rem 0.9rem;
  font-size: 0.92rem;
  flex: none;
}

.button--danger {
  background: var(--danger-wash);
  color: var(--danger);
  border: 1px solid var(--danger);
}

.backlink {
  margin: 0 0 var(--s4);
  font-size: 0.92rem;
}

.fields > .field:first-child {
  margin-top: 0;
}

.field__label {
  display: block;
  margin-bottom: var(--s2);
  font-size: 0.9rem;
  font-weight: 560;
  color: var(--text-soft);
}

/*
 * Day and month as two native selects. Native because a phone then gives its
 * own wheel — the least fiddly target a thumb has — and because this model
 * stores a recurring month/day whose year is optional and separate, which is
 * not what <input type="date"> is for.
 */
.daterow {
  display: grid;
  grid-template-columns: 5.5rem 1fr;
  gap: var(--s3);
}

.daterow select {
  min-height: 2.9rem;
}

.caption-preview {
  margin: var(--s2) 0 0;
  font-size: 0.9rem;
  color: var(--accent-ink);
}

/* A HEIC from a phone is several megabytes and its render is a real decode, so
 * the pause after choosing a file is visible. Saying so beats a page that
 * looks like it ignored the tap. */
.photo-upload__busy {
  margin: var(--s2) 0 0;
  font-size: 0.9rem;
  color: var(--accent-ink);
}

.photo-slot {
  display: flex;
  align-items: center;
  gap: var(--s4);
  padding: var(--s3);
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  background: var(--surface-sunken);
}

.photo-slot .thumb__img {
  width: 3rem;
  height: 3rem;
  border-radius: 50%;
  flex: none;
}

.danger-zone {
  margin-top: var(--s6);
  padding-top: var(--s5);
  border-top: 1px solid var(--border);
}

.danger-zone .muted {
  margin: var(--s2) 0 0;
}

/*
 * A card that is a link. The anchor is stretched over the whole card rather
 * than wrapped around its contents, which keeps the markup semantic (a heading
 * stays a heading) while giving a thumb the entire card to aim at.
 */
.card--link {
  position: relative;
}

/*
 * The whole card is the link, and the anchor is taken out of flow to do it.
 *
 * That is not cosmetic. `.card--event` is a two-column grid, and an anchor
 * left in normal flow becomes a *third* grid item: it takes the first cell,
 * the thumbnail takes the second, and the text wraps to a second row — which
 * renders as image-on-top-with-text-below and looks like a deliberate layout
 * rather than a stray element. It shipped that way from phase 2 until
 * 2026-08-16. Absolute positioning removes it from the grid entirely, so the
 * card lays out with exactly the two children it was designed around.
 */
.card__hit {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  z-index: 1;
}

/*
 * The focus ring on a card is ink, not amber, and that is the one thing the
 * B6 phase 7 pass changed about `.card__hit`.
 *
 * Amber is already spoken for on this element: `.card--soon` puts an amber
 * border and an amber inset ring on the card for the three days before an
 * event, and `.card--today` puts two of them there. The generic
 * `a:focus-visible` rule painted the focus ring in the same amber — so on
 * exactly the cards a family looks at most, "this has keyboard focus" and
 * "this is in three days" were the same colour at the same place, and tabbing
 * down the list appeared to do nothing.
 *
 * Both rules are needed: `:focus-within` draws the ring on the card, and the
 * second stops the global anchor rule painting a second amber one on the
 * stretched hit area underneath it.
 */
.card--link:focus-within {
  outline: 2px solid var(--text);
  outline-offset: 2px;
}

.card__hit:focus-visible {
  outline-color: var(--text);
}

@media (hover: hover) {
  .card--link:hover {
    border-color: var(--text-faint);
  }
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* --- the crop ---------------------------------------------------------------- */

.crop__stage {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s3);
}

/*
 * Round, because the panel is round. A square crop tool over a circular
 * display would let someone centre a face perfectly and still find it clipped
 * on the wall.
 */
.crop__viewport {
  position: relative;
  width: min(18rem, 78vw);
  aspect-ratio: 1;
  border-radius: 50%;
  overflow: hidden;
  background: var(--surface-sunken);
  box-shadow: inset 0 0 0 2px var(--border);
  touch-action: none; /* the gestures are ours: no scroll, no browser pinch */
  cursor: grab;
  user-select: none;
}

.crop__viewport.is-dragging {
  cursor: grabbing;
}

.crop__preview {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  -webkit-user-drag: none;
}

/* A quiet ring while a render is in flight. Deliberately not a spinner that
 * blocks the image: the previous preview stays visible and readable. */
.crop__busy {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  box-shadow: inset 0 0 0 3px var(--accent);
  opacity: 0.7;
  pointer-events: none;
}

.crop__hint {
  margin: 0;
  text-align: center;
}

/*
 * The numeric offsets, folded away. They are what the drag writes into and
 * what makes the form work without JavaScript, but they are not something a
 * family should have to look at to move a photograph.
 */
.crop__fine > summary {
  cursor: pointer;
  font-size: 0.9rem;
  color: var(--text-soft);
  padding: var(--s2) 0;
}

.crop__fine > summary:hover {
  color: var(--text);
}

.crop__fine[open] > summary {
  margin-bottom: var(--s2);
}

.crop__offsets {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s4);
}

.crop input[type="range"] {
  width: 100%;
  accent-color: var(--accent);
}

/* --- devices and pairing codes ---------------------------------------------- */

/*
 * The code is read across a room, off a phone held in one hand, by somebody
 * whose other hand has a picture frame in it. So it is the largest thing on
 * the page by a wide margin, with the digits spaced apart — six digits run
 * together are misread as five.
 */
.code {
  margin-top: var(--s5);
  text-align: center;
  border-color: var(--accent);
  box-shadow: var(--shadow), inset 0 0 0 1px var(--accent);
}

.code__for {
  margin: 0;
  font-size: 0.9rem;
  color: var(--text-soft);
}

.code__digits {
  margin: var(--s2) 0;
  font-size: clamp(2.75rem, 14vw, 4rem);
  font-weight: 680;
  letter-spacing: 0.12em;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}

.code__left {
  margin: 0;
  font-size: 0.95rem;
  color: var(--accent-ink);
  font-variant-numeric: tabular-nums;
}

.code--expired {
  opacity: 0.5;
}

.steps {
  margin: 0;
  padding-left: 1.2rem;
  display: grid;
  gap: var(--s3);
}

.steps li {
  padding-left: 0.2rem;
}

.device {
  display: block;
}

.device--revoked {
  opacity: 0.7;
}

.device__head {
  display: flex;
  align-items: center;
  gap: var(--s3);
  flex-wrap: wrap;
}

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

.badge--revoked {
  background: var(--danger-wash);
  border-color: var(--danger);
  color: var(--danger);
}

.device__facts {
  margin: var(--s3) 0 0;
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.3rem var(--s4);
  font-size: 0.9rem;
}

.device__facts dt {
  color: var(--text-faint);
}

.device__facts dd {
  margin: 0;
  text-align: right;
}

.device__actions {
  margin-top: var(--s4);
  display: grid;
  gap: var(--s2);
}

/* --- circles ---------------------------------------------------------------- */

/*
 * A circle card is a heading and a list of people, which is all the page has
 * to say in this phase. It borrows the device card's head/badge arrangement
 * rather than inventing a second one — the two pages are read one after the
 * other and a family should not have to learn two card shapes.
 */
.circle {
  display: block;
}

.circle__head {
  display: flex;
  align-items: center;
  gap: var(--s3);
  flex-wrap: wrap;
}

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

.circle__count {
  margin: var(--s1) 0 0;
  font-size: 0.85rem;
}

.members {
  margin: var(--s3) 0 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--s2);
}

/*
 * A member row carries a name, maybe a badge, and up to two actions. On a
 * phone the actions wrap under the name rather than squeezing it, which is
 * why this wraps instead of being a three-column grid: the name is the part
 * that must stay readable, and it is also the part of unpredictable length.
 */
.member {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s2) var(--s3);
  padding-top: var(--s2);
  border-top: 1px solid var(--border);
  font-size: 0.95rem;
}

.member__name {
  flex: 1 1 8rem;
}

.member__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
}

.member__actions:empty {
  display: none;
}

/* Why a button is missing, in the place it would have been. Quiet, because it
 * is an explanation rather than an error: nothing went wrong, the rule simply
 * says no. */
.member__refusal {
  flex: 1 1 100%;
  color: var(--text-faint);
  font-size: 0.82rem;
}

/*
 * Small enough to sit beside a name, still large enough for a thumb. Sized
 * with padding rather than a fixed box so the label can be as long as German
 * makes it.
 *
 * This comment claimed 44 px until B6 phase 7 measured it. It is 36 px:
 * 0.82 rem at the body's 1.55 line-height is 20.3 px of text, plus 2 × 0.5 rem
 * of padding — 38 px on the `--quiet` and `--danger` variants, which add a
 * border. That clears WCAG 2.2's 24 px minimum (SC 2.5.8) comfortably and does
 * not reach the 44 px of the AAA criterion, which was never what these were
 * built to. Left at 36 rather than grown: a member row already carries up to
 * four of these beside a name, and 44 px each would wrap every row on a phone
 * — trading a real layout for a figure this control was not measured against.
 */
.button--mini {
  width: auto;
  flex: none;
  padding: 0.5rem 0.75rem;
  font-size: 0.82rem;
  font-weight: 600;
  border-radius: 10px;
}

/*
 * The single-admin warning. Amber-washed rather than red: nothing is broken,
 * and a circle with one admin is the ordinary state of a circle somebody just
 * created. It is a thing to fix before it matters, which is what this colour
 * says everywhere else in the app.
 */
.circle__warning {
  margin: var(--s3) 0 0;
  padding: var(--s3);
  border-radius: var(--radius);
  background: var(--accent-wash);
  color: var(--accent-ink);
  font-size: 0.85rem;
}

/* --- two-factor -------------------------------------------------------------- */

/*
 * The enrolment QR: inline SVG, black modules on a white plate this rule
 * paints. The one element in the app that deliberately does *not* follow dark
 * mode — a camera reads dark-on-light and nothing else, and a QR that inverts
 * politely at night is a QR that stops scanning at night.
 */
.qr {
  display: flex;
  justify-content: center;
  padding: var(--s4);
  background: #ffffff;
  color: #000000;
  border-radius: var(--radius);
}

/*
 * This scales only because the SVG carries a viewBox — see `_qr_svg`. Without
 * one the symbol would be clipped to the box rather than resized into it,
 * which is a QR that looks right and decodes to nothing.
 */
.qr svg {
  width: min(100%, 15rem);
  height: auto;
}

.codes {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
  gap: var(--s2);
}

.codes__item code {
  display: block;
  padding: var(--s2) var(--s3);
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface-sunken);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.95rem;
  letter-spacing: 0.04em;
  text-align: center;
  user-select: all;
}

/* --- invitations ------------------------------------------------------------- */

.invitations {
  margin: var(--s4) 0 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--s2);
}

.invitation {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s2) var(--s3);
  padding: var(--s2) var(--s3);
  border-radius: var(--radius);
  background: var(--surface-sunken);
  font-size: 0.88rem;
}

.invitation__who {
  display: flex;
  flex-direction: column;
  flex: 1 1 9rem;
}

.invitation__name {
  font-weight: 600;
}

.invitation__by {
  font-size: 0.8rem;
}

.circle__invite {
  margin: var(--s4) 0 0;
}

/*
 * The one-time link. Monospace and breaking anywhere, because it is a long
 * opaque string somebody may have to read across to a second device — and
 * selectable, which is what makes the copy button an enhancement rather than
 * the only way through.
 */
.linkbox {
  padding: var(--s3);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-sunken);
}

.linkbox__url {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.85rem;
  line-height: 1.5;
  overflow-wrap: anywhere;
  user-select: all;
}

/* --- the removal confirmation ------------------------------------------------ */

.consequence__lead {
  margin: 0;
  font-weight: 600;
}

.consequence__list {
  margin: var(--s2) 0 var(--s3);
  padding-left: 1.1rem;
  display: grid;
  gap: var(--s1);
}

.consequence__list li {
  padding-left: 0.2rem;
}

/* A checkbox is a control, not a field with a label above it — so the label
 * wraps it and the two share a row a thumb can hit anywhere on. */
.field--check label {
  display: flex;
  align-items: flex-start;
  gap: var(--s3);
  font-weight: 600;
  cursor: pointer;
}

.field--check input[type="checkbox"] {
  width: 1.15rem;
  height: 1.15rem;
  margin-top: 0.15rem;
  flex: none;
  accent-color: var(--accent);
}

/* The reader's own row, marked by weight rather than by colour: the amber is
 * spoken for elsewhere and "which one am I" is not an alert. */
.member--me {
  font-weight: 600;
}

.member__you {
  color: var(--text-faint);
  font-weight: 400;
}

/* --- the sign-in screen ---------------------------------------------------- */

/*
 * Its own centred layout rather than the shell: nothing to navigate to yet,
 * and a masthead offering links a signed-out visitor cannot use is noise.
 */
.signin {
  min-height: 100svh;
  display: grid;
  place-items: center;
  padding: var(--s5) var(--s4);
}

.signin__inner {
  width: 100%;
  max-width: 22rem;
}

.signin__brand {
  text-align: center;
  margin-bottom: var(--s5);
}

.signin__brand .wordmark {
  font-size: 1.25rem;
}

.signin__tagline {
  margin: var(--s2) 0 0;
  color: var(--text-faint);
  font-size: 0.9rem;
}

@media (min-width: 40rem) {
  .shell {
    /* A reading measure, not a canvas. This was 52rem while the card list was
     * a two-column grid and existed only to fit the second column; with the
     * list back to a single column that width would stretch each card into a
     * banner — a thumbnail on the far left, a countdown stranded on the far
     * right, and a lot of nothing between them.
     *
     * 42rem keeps the same measure for every page in the app, which is the
     * point: one content width, so a card, a form and a detail page all line
     * up on a desktop instead of each finding its own edge. */
    max-width: 42rem;
    padding-inline: var(--s5);
  }

  h1 {
    font-size: 1.75rem;
  }
}
