/* ---------------------------------------------------------------------------
   One block kit, ten biomes.

   Every colour comes from the biome the trip picked for itself — the palette is
   injected as custom properties at load, so nothing here hardcodes a hue. The
   geometry never changes: 3px outline, 16px corner, 4px hard shadow. Adding a
   holiday type costs a palette, not a redesign.
   --------------------------------------------------------------------------- */
:root {
  /* Fallback biome (city) — replaced by the trip's own palette on load.

     **These were the old dark purple**, left behind by the OKLCH rewrite that
     moved every biome to a light ground (L 0.96) because the app is used outdoors
     in bright sun. The JS injects the real palette on load, so the only symptom
     was a dark flash before it arrived and a page that came out inverted if the
     trip failed to fetch — which is exactly when somebody is least able to read
     it. Kept in step with `city` in `src/biomes.ts`. */
  --ground: #e6f3fd;
  --block: #f7fbfe;
  --line: #264358;
  --ink: #0e212f;
  --dim: #576c7c;
  --accent: #a85419;
  --accent2: #39579f;
  --hero-a: #18587f;
  --hero-b: #3278a6;

  --yes: var(--accent2);
  --maybe: #ffd166;
  --no: var(--dim);
  /* The block kit as two numbers rather than thirty literals, so its *weight*
     can change without its shape changing. See the wide-screen block below. */
  --bd: 3px;
  --shw: 4px;
  --radius: 16px;
  --shadow: var(--shw) var(--shw) 0 var(--line);
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body { margin: 0; padding: 0; }
body {
  background: var(--ground); color: var(--ink);
  font: 16px/1.45 "Rubik", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  overscroll-behavior-y: none;
  transition: background-color .5s ease, color .5s ease;
}
#app { max-width: 560px; margin: 0 auto; padding: 0 16px calc(92px + env(safe-area-inset-bottom)); }

h1, h2, h3 { margin: 0; font-family: "Baloo 2", "Rubik", sans-serif; font-weight: 800; letter-spacing: -0.01em; }
h1 { font-size: 30px; }
h2 { font-size: 20px; }
p { margin: 0 0 12px; }
.muted { color: var(--dim); }
.small { font-size: 13px; }
.center { text-align: center; }
.row { display: flex; align-items: center; gap: 10px; }
.between { justify-content: space-between; }
.wrap { flex-wrap: wrap; }
.grow { flex: 1; min-width: 0; }
.stack > * + * { margin-top: 10px; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fat { font-family: "Baloo 2", "Rubik", sans-serif; font-weight: 800; }

header.top {
  position: sticky; top: 0; z-index: 5; background: var(--ground);
  padding: 16px 0 12px; margin-bottom: 14px;
}
header.top .code { font-size: 13px; color: var(--dim); }

/* ---------- the block ---------- */
.card {
  background: var(--block); border: var(--bd) solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow);
  padding: 14px; margin-bottom: 12px;
}
.card.tight { padding: 12px; }
.card.win { border-color: var(--accent2); }

button, .btn {
  font: inherit; font-family: "Baloo 2", "Rubik", sans-serif; font-weight: 700;
  border: var(--bd) solid var(--line); background: var(--block); color: var(--ink);
  border-radius: 12px; padding: 10px 14px; cursor: pointer;
  box-shadow: calc(var(--shw) - 1px) calc(var(--shw) - 1px) 0 var(--line);
  transition: transform .06s ease, box-shadow .06s ease;
}
button:active { transform: translate(3px, 3px); box-shadow: 0 0 0 var(--line); }
/* **A disabled block must not press.** The fade is `button[disabled]` further down — one
   opinion on the opacity, not two — but the press was never excluded, so a step you have not
   earned yet still moved into its own shadow and then did nothing, which reads as the app
   being broken rather than as the button waiting. `[disabled]` to match the rule that already
   exists rather than introducing a second spelling of the same state. */
button[disabled]:active { transform: none; cursor: default;
  box-shadow: calc(var(--shw) - 1px) calc(var(--shw) - 1px) 0 var(--line); }
.btn-primary { background: var(--accent); border-color: var(--line); color: var(--line); }
.btn-full { width: 100%; }
.btn-sm { padding: 8px 12px; font-size: 14px; border-radius: 10px; box-shadow: calc(var(--shw) - 2px) calc(var(--shw) - 2px) 0 var(--line); }
.btn-ghost { background: transparent; border-color: transparent; box-shadow: none; color: var(--dim); }
.btn-ghost:active { transform: none; }
.btn-on { background: var(--accent2); color: var(--line); }

input, select, textarea {
  font: inherit; width: 100%; background: var(--ground); color: var(--ink);
  border: var(--bd) solid var(--line); border-radius: 12px; padding: 11px 12px;
}
input:focus, select:focus, textarea:focus { outline: 3px solid var(--accent); outline-offset: -1px; }
label.field { display: block; margin-bottom: 10px; }
label.field span { display: block; font-size: 12px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--dim); margin-bottom: 5px; font-weight: 500; }

nav.tabs {
  position: fixed; left: 50%; transform: translateX(-50%); bottom: 0; z-index: 10;
  width: 100%; max-width: 560px;
  display: flex; background: var(--block); border-top: var(--bd) solid var(--line);
  padding-bottom: env(safe-area-inset-bottom);
}
nav.tabs button {
  flex: 1; border: 0; background: none; border-radius: 0; padding: 11px 2px 14px;
  font-size: 10px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase;
  color: var(--dim); display: grid; gap: 4px; justify-items: center; box-shadow: none;
}
nav.tabs button:active { transform: none; }
nav.tabs button .ic { display: block; width: 22px; height: 22px; }
nav.tabs button .ic svg { width: 22px; height: 22px; display: block; }
nav.tabs button[aria-current="true"] { color: var(--accent); }

.avatar {
  width: 30px; height: 30px; border-radius: 9px; display: grid; place-items: center;
  font-size: 12px; font-weight: 800; color: var(--line); flex: none;
  border: 2px solid var(--line); font-family: "Baloo 2", sans-serif;
}
.pill {
  font-size: 12px; font-weight: 700; padding: 4px 10px; border-radius: 999px;
  border: 2px solid var(--line); background: var(--ground);
}
.pill.yes { background: var(--accent2); color: var(--line); }
.pill.maybe { background: var(--maybe); color: var(--line); }
.pill.no { color: var(--dim); }

.bar { height: 8px; border-radius: 3px; background: var(--line); overflow: hidden; margin: 8px 0 6px; }
.bar > i { display: block; height: 100%; background: var(--accent2); }

.stack-avatars { display: flex; }
.stack-avatars .avatar { width: 26px; height: 26px; font-size: 10px; margin-left: -7px; }
.stack-avatars .avatar:first-child { margin-left: 0; }

.day-head { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .12em;
  color: var(--dim); margin: 20px 0 8px; }
/* A culture drawing never gets a row of its own — `CultureSheet.swift` carries the argument and
   what was rejected. It shares a row with a block of text: the first card of the section it
   belongs to, with the rest of the section running full width underneath. Standing it beside the
   *heading* instead made a 104px row that was 90px of empty ground, three times down one screen.

   `row-reverse`, so the drawing is at the trailing edge and the words start at the leading one —
   and so that when it wraps the drawing takes the first line and the card the second, which is
   the order the phone falls back to at the accessibility text sizes.

   Bottom-aligned, so the object's feet stand on the card's bottom edge, the way the destination
   card stands its subject on the card's own. Decorative and `aria-hidden`. */
.culture-pair { display: flex; flex-direction: row-reverse; flex-wrap: wrap;
  gap: 12px; align-items: flex-end; }
/* `15rem` rather than a pixel width, because `rem` follows the reader's own text size: somebody
   browsing at 200% gets the stacked layout for the same reason the phone does above the
   accessibility sizes — a 240px column of 20px type is three words a line. */
.culture-pair > .card { flex: 1 1 15rem; min-width: 0; }
/* The opener stands at the top of the screen rather than beside a fact, so it starts where the
   block starts. Bottom-aligning it against a card three times its height would strand it beside
   the last line of something it is not about. */
.culture-pair.opens { align-items: flex-start; }
.culture-art { width: 104px; height: 104px; object-fit: contain; flex: none; }
/* 84 in the opening slot on both clients — `CultureScreen.artSize(.opening)` is the same number
   for the same reason. The block it stands beside is the phrases card, which is the one card in
   the app somebody reads out loud in a restaurant, so it gets the 20px back. */
.culture-pair.opens .culture-art { width: 84px; height: 84px; }

/* 44px, the same reason the iOS row carries it: taking the explaining line off a checklist
   row left one line of text, and a one-line row is about 27px of target. `.check` above has
   had this since it was written — the list is the surface it was missing on. */
.item { display: flex; gap: 10px; align-items: center; min-height: 44px; }
.item .kind { font-size: 18px; width: 24px; text-align: center; flex: none; }

/* Mayzee's list. A row is a button, so it needs the card's own alignment back — the
   default centres and centre-aligns everything on it, which turns a two-line row into a
   pair of centred sentences. */
.chk { display: flex; width: 100%; text-align: left; align-items: flex-start; gap: 10px; }
.chk strong { display: block; }
/* The urgency is a mark rather than a colour on the words: a tinted label measured 3.1:1
   here once, which is the mistake this app has already fixed on the RSVP wash. */
.chk .kind { color: var(--dim); font-weight: 700; line-height: 1.4; }
.chk .kind.due { color: var(--accent); }
.time { font-variant-numeric: tabular-nums; font-weight: 700; font-size: 13px; color: var(--accent); }

.tl { position: relative; padding-left: 24px; }
.tl::before { content: ""; position: absolute; left: 7px; top: 8px; bottom: 8px; width: 3px;
  background: var(--line); border-radius: 2px; }
.tl .dot { position: absolute; left: 0; width: 16px; height: 16px; border-radius: 5px;
  border: var(--bd) solid var(--line); }

.money { font-variant-numeric: tabular-nums; font-weight: 800; font-family: "Baloo 2", sans-serif; }
.pos { color: var(--accent2); } .neg { color: var(--accent); }

dialog {
  border: 0; border-top: var(--bd) solid var(--line); border-radius: 20px 20px 0 0;
  padding: 18px 16px calc(18px + env(safe-area-inset-bottom));
  width: 100%; max-width: 560px; background: var(--block); color: var(--ink);
  margin: auto auto 0; position: fixed; inset: auto 0 0 0;
}
dialog::backdrop { background: color-mix(in srgb, var(--line) 70%, transparent); }
.sheet-title { font-family: "Baloo 2", sans-serif; font-weight: 800; font-size: 20px; margin-bottom: 12px; }

/* A screen rather than a sheet: a title, one Done, and a body that scrolls. A sheet is a form
   answered in one go; the scrapbook and the settings are places you go, look at, act in several
   times and leave — which on the phone is a `NavigationStack` with a Done in the toolbar.

   **`display` is on `[open]` and nowhere else.** Author styles beat the UA sheet's
   `dialog:not([open]) { display: none }` whatever their specificity, so a `display: flex` on the
   bare class would paint the screen for the frame between appending it and `showModal()`. */
dialog.screen {
  inset: 0; margin: 0 auto; height: 100dvh; max-height: 100dvh;
  border-radius: 0; border-top: 0; padding: 0; background: var(--ground);
}
dialog.screen[open] { display: flex; flex-direction: column; }
.screen-head { flex: none; gap: 10px; padding: 13px 16px; background: var(--block);
  border-bottom: var(--bd) solid var(--line); }
.screen-head h2 { font-size: 18px; }
.screen-body { flex: 1; overflow-y: auto;
  padding: 16px 16px calc(28px + env(safe-area-inset-bottom)); }

/* A card that is a way in. The whole row is the target, and a button needs the card's own
   alignment back — the default centres everything on it. */
.rowlink { display: flex; width: 100%; text-align: left; align-items: center; }

/* The segmented control — `DESIGN.md`. One border and one corner around the whole thing, so it
   reads as one control rather than as buttons in a row, and the selected segment goes **heavy as
   well as light**: on a bright biome the fill alone is not enough of a difference to read at a
   glance. */
.segs { display: flex; margin-bottom: 10px; overflow: hidden;
  border: var(--bd) solid var(--line); border-radius: 12px;
  box-shadow: calc(var(--shw) - 1px) calc(var(--shw) - 1px) 0 var(--line); }
.segs button { flex: 1; min-width: 0; padding: 10px 8px; font-size: 14px; font-weight: 600;
  border: 0; border-left: var(--bd) solid var(--line); border-radius: 0; box-shadow: none; }
.segs button:first-child { border-left: 0; }
.segs button:active { transform: none; }
.segs button[aria-pressed="true"] { background: var(--accent); color: var(--line); font-weight: 800; }

/* The same label as `label.field span`, for the times the thing being labelled is not a field. */
.lab { margin: 0 0 6px 2px; font-size: 12px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--dim); font-weight: 500; }

/* A tick you can hit. 44px minimum, and the whole row is the target rather than the box. */
.check { display: flex; align-items: center; gap: 10px; min-height: 44px; cursor: pointer; }
.check input { width: 22px; height: 22px; flex: none; padding: 0; accent-color: var(--accent); }

/* The quiet button's colour lives in its text — `DESIGN.md`. `dim` where the act takes nothing
   off anybody, `accent` where it takes an answer off the group. Never `ink`, which is body-text
   colour and makes a button an exact copy of the card stating the fact above it. */
.tint-dim { color: var(--dim); }
.tint-accent { color: var(--accent); }

.rule { border: 0; border-top: var(--bd) solid var(--line); opacity: .25; margin: 24px 0 14px; }
.chips { display: flex; flex-wrap: wrap; gap: 8px; }
.empty { text-align: center; color: var(--dim); padding: 26px 10px; }
.empty .big { font-size: 32px; display: block; margin-bottom: 6px; }
.loading::after { content: "…"; display: block; text-align: center; padding: 60px; color: var(--dim); }
.fab-row { padding-top: 14px; }

/* ---------- the trip tab ---------- */
.ask { padding: 14px 2px 12px; }
.ask h3 { font-size: 28px; margin: 0 0 4px; }

.deadline {
  display: block; width: 100%; text-align: left;
  background: var(--accent); border-color: var(--line); color: var(--line);
}
.deadline.due { background: var(--block); color: var(--dim); }

.hero {
  position: relative; overflow: hidden; text-align: center;
  padding: 26px 16px 88px; margin-bottom: 12px;
  background: linear-gradient(180deg, var(--hero-a), var(--hero-b));
  border: var(--bd) solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow);
}
.hero-where { font-family: "Baloo 2", sans-serif; font-weight: 800; font-size: 46px;
  letter-spacing: -0.02em; line-height: 1; }
.hero-when {
  display: inline-flex; align-items: center; gap: 7px; margin-top: 12px;
  background: var(--line); border-radius: 999px; padding: 7px 14px;
  font-size: 13px; font-weight: 700; color: var(--accent2);
}
/* **The badge sits beside the countdown, never inside it.** `.hero-when` is already a pill, and
   a second pill inset inside it is two containers with a hairline between them — the same
   mistake `BlockButton` exists to prevent on iOS, one surface out. */
.hero-row { display: flex; align-items: center; justify-content: center; gap: 8px;
            flex-wrap: wrap; }
/* An idea, and only an idea — "happening" is the absence of a badge. Its own solid ground
   rather than a tint on the hero art, because this card has lost a contrast fight before:
   the RSVP wash tinted its own label to 3.1–4.0:1 over the gradient. */
.reality { display: inline-flex; align-items: center; margin-top: 12px;
           background: #fbd970; color: #22190a; border: 2px solid rgba(0,0,0,.4);
           border-radius: 999px; padding: 6px 12px; font-size: 13px; font-weight: 800; }
.reality-why { margin: -4px 0 12px; padding: 0 4px; font-size: 13px; color: var(--dim);
               text-align: center; }
.reality-why b { color: var(--ink); font-weight: 600; }

/* The pronunciation is the useful half, so it takes the accent rather than the English —
   you already know what you are trying to say. */
.sounds { color: var(--accent2); font-weight: 600; margin-right: 6px; }

/* `COUNTRIES.md`: **never a number without a date on it.** The date is on the screen rather
   than only in the table, because the whole claim of the culture screen is that it is checked
   rather than remembered — and a claim nobody can see is a claim nobody can weigh. Deliberately
   the quietest thing in the card: it is provenance, not content. */
.checked { margin-top: 6px; font-size: 11px; letter-spacing: .02em; opacity: .75; }

/* the scrapbook. **A page is a photo with words under it** — that is what a scrapbook page is,
   and it is why `capture` collapsed from three shapes to one. */
.page { padding: 10px; }
/* Reporting a page and taking one out are **quiet, and at the bottom**. Neither is an action
   anybody comes to this screen to take, and the block kit's own weight on either would make a
   delete control the loudest thing on a screen of photographs. Body type rather than the
   heading face, which is what makes it read as a link rather than as a button. */
.page .btn-ghost { font-family: "Rubik", sans-serif; font-size: 13px; font-weight: 600;
                   padding: 8px 4px; }
.pagephoto { display: block; width: 100%; border-radius: 10px;
             border: var(--bd) solid var(--line); background: var(--ground); }

/* She photobombed it — `PHOTOBOMB.md`, and `PhotobombPage` is the phone's half of this.

   **The frame clips her, it never letterboxes her.** `front` hangs 30% off a side edge and 44%
   below the bottom one on purpose: somebody nearer the lens than the frame's own plane is cut by
   it, and a whole figure standing politely inside the picture reads as luggage left on the sand.
   So `.shot` owns the border and the corner and hides its overflow, and the photograph inside it
   gives both up — otherwise she would be sawn off by a rounded edge that is not the picture's.

   **`.bomb` is her box, and every number on it is a percentage of the photograph.** It is square
   in points and measured against the *height*, which is what `aspect-ratio` buys: `height` is
   `size`, the width follows, and neither depends on how wide the photograph happens to be. `top`
   is `y` — the **bottom** of her box, hence the -100% — and `left` is her middle, hence the -50%.
   The clip is `cut`, taken off the bottom so what shows is her from the handle down.

   Her colours are `mayzee()`'s and are fixed. `--tan` is hers and only hers, so nothing here
   tints her with the biome. */
.shot { position: relative; overflow: hidden; border-radius: 10px;
        border: var(--bd) solid var(--line); background: var(--ground); }
.shot .pagephoto { border: 0; border-radius: 0; }
.bomb { position: absolute; aspect-ratio: 1; transform: translate(-50%, -100%);
        pointer-events: none; }
/* Mirrored rather than redrawn: there is one drawing of her, and turning her round so she looks
   into the picture is a transform on it and never a second set of curves. `--flip` is `flip` off
   the wire, written on the box inline — a class would put the word "flip" in `COPY.md`, which
   reads every quoted string in this file looking for words somebody says. */
.bomb .mayzee { display: block; width: 100%; height: 100%; transform: scaleX(var(--flip, 1)); }
/* A caption kept before 21 Aug 2026 could be a quote, and a quote is set as one. */
.said { font-size: 18px; font-style: italic; line-height: 1.4; }

/* The roll: twenty-seven frames, shot and unshot, on one sheet. **The empty ones are the
   feature** — a grid of the photos you have taken is a gallery and every app has one; drawing
   the frames you have not used is what makes it a roll, and it needs no counter or warning
   because you can see it. 3:2 because that is 35mm's own shape and not the square every photo
   grid uses.

   **Five across and it has to fit**: the whole of this shelf is seeing all twenty-seven at once,
   and a roll you scroll is a gallery again. */
.roll { padding: 10px 8px; }
/* The film edge, once across the top and once across the bottom rather than a perforation per
   frame — at this size a hole per frame is grit. The strip clips, so it fills whatever width it
   is given without anybody counting. */
.sprockets { display: flex; gap: 10px; height: 7px; overflow: hidden; }
.sprockets i { flex: none; width: 9px; height: 7px; border-radius: 2px;
               border: 1.5px solid var(--line); background: var(--ground); }
.rollgrid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 6px; margin: 12px 0; }
/* The unshot frame differs only in being empty. Greyed out or dashed would read as *disabled*,
   and these are not disabled — they are available. */
.rollgrid .frame i { display: block; aspect-ratio: 3 / 2;
                     background: var(--ground) center/cover no-repeat;
                     border: 2px solid var(--line); border-radius: 3px; }
/* **The number goes under the frame**, shot or not, because that is what makes an empty one read
   as *frame 17* rather than as a gap. Over the picture it is a caption on the ones with a photo
   and invisible on the ones without. Never wrapped: a two-digit number broken over two lines
   reads as two frames. */
.rollgrid .frame b { display: block; text-align: center; white-space: nowrap;
                     font: 700 10px/1.5 "Rubik", sans-serif;
                     font-variant-numeric: tabular-nums; color: var(--dim); }
.rollgrid .frame.shot b { color: var(--line); }

/* The baked map fills the card rather than sitting in a strip along the bottom —
   it is a picture of the place, so it is the ground the words sit on. `cover`
   because the textures are square-ish and the card is not. */
.hero-art { position: absolute; inset: 0; width: 100%; height: 100%;
            object-fit: cover; opacity: .9; }

.chase { display: flex; width: 100%; text-align: left; align-items: center; }
.chase strong { color: var(--accent); }

.load { border-color: var(--accent); }
.load strong { display: block; margin-bottom: 3px; }

.seg {
  display: flex; gap: 4px; padding: 4px; margin-bottom: 14px;
  background: var(--block); border: var(--bd) solid var(--line); border-radius: 14px;
}
.seg button {
  flex: 1; border: 0; background: none; border-radius: 9px; padding: 9px 6px;
  font-size: 14px; color: var(--dim); box-shadow: none;
}
.seg button:active { transform: none; }
.seg button[aria-current="true"] { background: var(--accent); color: var(--line); }
.seg.seg-sm { margin: 0; padding: 3px; }
.seg.seg-sm button { padding: 5px 10px; font-size: 12px; flex: none; }

.seats { margin-top: 10px; padding-top: 10px; border-top: 3px dashed var(--line); }
.who {
  display: inline-flex; align-items: center; gap: 4px;
  background: var(--ground); border: 2px solid var(--line); border-radius: 999px; padding: 2px 9px 2px 2px;
}
.who .avatar { width: 22px; height: 22px; font-size: 10px; }
button[disabled] { opacity: .45; }

.commit { display: block; width: 100%; text-align: left; }
.commit.ask-commit { color: var(--dim); }
.commit.short { border-color: var(--accent); }
.commit.met { border-color: var(--accent2); }
.commit.off { border-color: var(--dim); }
.commit.off strong { display: block; margin-bottom: 3px; }
.commit .small { color: var(--dim); }

.card.objected { border-color: var(--maybe); }
.objection {
  margin-top: 8px; padding: 8px 10px; border-radius: 10px;
  background: var(--ground); border: 2px solid var(--line);
  font-size: 13px; color: var(--maybe);
}

.pledge { border-color: var(--accent2); }
.notfussed { background: var(--ground); }
.notfussed select { max-width: 100%; }

/* the hero art sits behind the content, never under a face */
.hero > *:not(.hero-art):not(.mayzee-stage) { position: relative; z-index: 1; }

/* the biome badge — says why the app looks like this, so it never seems random */
.biome-tag {
  display: inline-flex; align-items: center; gap: 6px; font-size: 11px; font-weight: 600;
  letter-spacing: .06em; text-transform: uppercase; color: var(--dim);
  background: var(--block); border: 2px solid var(--line); border-radius: 999px;
  padding: 5px 11px; box-shadow: none;
}

[hidden] { display: none !important; }

/* ---------- Mayzee ---------- */
.mayzee { display: block; }
.mayzee-stage {
  position: absolute; right: 12px; bottom: 6px; z-index: 2;
  animation: bob 3.2s ease-in-out infinite;
}
@keyframes bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-5px); } }
@media (prefers-reduced-motion: reduce) { .mayzee-stage { animation: none; } }

/* ══ the wide screen ═══════════════════════════════════════════════
   **One column, and a wider one.** This was briefly two masonry columns with a
   navigation rail down the side, and it was worse in every way that matters: a
   `columns` layout has no reading order — the cards jump from the bottom of one
   column to the top of the next — so the eye has nowhere to start, and two
   columns of 3px-outlined, hard-shadowed cards is a wall of boxes rather than a
   page.

   The single column was never the problem. A phone-width column stranded in the
   middle of a monitor was, and the fix for that is room to breathe, not a second
   information architecture. */
@media (min-width: 620px) {
  #app { max-width: 640px; padding-bottom: calc(104px + env(safe-area-inset-bottom)); }
  nav.tabs { max-width: 640px; }
  .hero { padding: 34px 20px 96px; }

  /* **Lighter, not different.** Same geometry, less of it: a 3px outline and a
     4px hard shadow are sized for a phone at arm's length, and on a monitor there
     is three times as much of both on screen at once — which is what reads as
     harsh rather than characterful. Shapes, corners and every colour untouched,
     and a phone browser still gets exactly what iOS gets. */
  :root { --bd: 2px; --shw: 3px; --radius: 14px; }
}

/* What to pack — a list you tick, on the Plan tab.
 *
 * The whole row is the target, not a box at the end of it: this is a screen used one-handed
 * over an open suitcase, and a 24pt checkbox beside 300px of inert text is the wrong shape.
 * Same reasoning as `PackingSheet`'s `Row` on the phone.
 *
 * It is a `button`, so it resets the app's own button skin rather than inheriting a block —
 * a 3pt-outlined capsule per packing item would be nine boxes inside one box, which is the
 * pill-in-a-column mistake `BlockButton` exists to prevent, one surface out. */
.packrow {
  display: flex; align-items: flex-start; gap: 10px; width: 100%;
  border: 0; box-shadow: none; background: transparent; border-radius: 10px;
  padding: 8px 6px; text-align: left; color: var(--ink);
}
.packrow:hover { background: color-mix(in srgb, var(--accent) 8%, transparent); }
.packrow .packtick {
  flex: 0 0 22px; height: 22px; margin-top: 2px;
  border: var(--bd) solid var(--line); border-radius: 999px;
  display: grid; place-items: center; font-size: 13px; line-height: 1;
  color: var(--block); background: var(--block);
}
.packrow.packed .packtick { background: var(--accent); color: var(--block); }
.packrow .grow { display: flex; flex-direction: column; gap: 1px; }
.packrow .packwhat { font-weight: 700; }
/* The strikethrough is the half that reads at a glance — a packed bag should be legible as a
 * shape without reading a word of it. */
.packrow.packed .packwhat { text-decoration: line-through; color: var(--dim); }

/* A packing row and its remove control share a line. The × is quiet and small on purpose:
 * nine items with nine loud delete buttons is nine invitations to delete, on a card whose job
 * is ticking things off. */
.packline { display: flex; align-items: flex-start; gap: 2px; }
.packline .packrow { flex: 1; }
.packline .packdrop { flex: 0 0 auto; margin-top: 6px; opacity: .45; }
.packline:hover .packdrop, .packline .packdrop:focus-visible { opacity: 1; }
