/* home-search -- one board over the Chicago listing alerts.
 *
 * Dark-first, carried over from the dv design system (dv.css): shadcn neutral
 * base, hairline borders, Geist + Geist Mono, 10px radius / 12px on cards.
 *
 * Rewritten when the three views collapsed into one (ADR 0006), and again when
 * the four bands that replaced them became a filter (ADR 0007). The triage
 * grid, the shortlist table, the comparison slots, the tier badges and the band
 * headers have all been through here; what is left is one grid and one rail.
 *
 * Green means keep, red means drop. Those two are the only saturated colors on
 * a card, on purpose: a board of cards should show its verdicts from across the
 * room without being read.
 *
 * EVERY @media block lives at the BOTTOM of this file. A media block placed
 * above the base rule it overrides is silently dead, and that has bitten this
 * codebase before.
 */

:root {
  color-scheme: dark;

  --bg:       #0a0a0a;
  --fg:       #fafafa;
  --dim:      #a1a1a1;
  --faint:    #999999;
  /* Placeholder text -- "no size", "no neighborhood", the recency stamp. It was
     #52525b, which is 2.4:1 on a card and not a colour to write words in.
     #7b7b86 replaced it and was described as clearing 4.5:1; measured on the
     rendered page it lands at 4.29 on the photo placeholder and on the dark
     pill, i.e. just under AA rather than just over. This is 5.5:1 on both, and
     still reads as absent rather than stated. */
  --missing:  #8b8b96;
  --line:     rgba(255, 255, 255, 0.10);
  --line-hi:  rgba(255, 255, 255, 0.22);
  --card:     rgba(255, 255, 255, 0.03);
  --card-hi:  rgba(255, 255, 255, 0.06);

  --em:       #34d399;   /* save */
  --em-ink:   #04120c;
  --red:      #ff6568;   /* pass */
  --red-ink:  #1a0406;
  --warn:     #e8a64a;
  --warn-ink: #1a1004;
  --pin:      #f5c84c;   /* pinned, an absolute favorite */
  --pin-ink:  #1a1503;
  --info:     #5fa9c9;
  --pos:      #66c46e;
  /* Focus is blue on purpose: a ring in green or red on a Save or Pass button
     reads as a cast verdict rather than as "your keyboard is here". */
  --focus:    #8ab4ff;

  --r-sm: 6px;
  --r:    10px;
  --r-lg: 14px;

  /* The top bar's height, less any top inset. 44px on a phone (ADR 0019). */
  --topbar-h: 56px;
  /* The phone's dock of tabs (ADR 0028), less the home indicator's inset. */
  --dock-h: 56px;

  --sans: "Geist", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  --mono: "Geist Mono", ui-monospace, "SF Mono", Menlo, monospace;
}

* { box-sizing: border-box; }

html, body {
  margin: 0; height: 100%;
  background: var(--bg); color: var(--fg);
  font-family: var(--sans); font-size: 16px; line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--info); text-decoration: none; }
button { font-family: inherit; cursor: pointer; color: inherit; }
img { display: block; }

.lbl {
  font-family: var(--mono); font-size: 10px; font-weight: 500;
  letter-spacing: .10em; text-transform: uppercase; color: var(--faint);
}
.muted { color: var(--dim); }
.none { color: var(--missing); }
.spacer { flex-grow: 1; }
.hint { font-size: 11px; color: var(--faint); line-height: 1.45; }
/* Said to a screen reader, drawn for nobody. */
.sr-only {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}
:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

/* dvh after vh, so a browser without dvh keeps the old value. On a phone 100vh
   is the tallest the viewport ever gets -- the toolbars retract into it -- so a
   vh-only floor leaves a strip of empty page under the board that scrolls. */
#app { display: flex; flex-direction: column; min-height: 100vh; min-height: 100dvh; }

/* ── Top bar ─────────────────────────────────────────────────────────── */
/* The brand, the tabs (ADR 0028) and Sign out. The tally that sat here
   ("0 to review · 54 saved · 869 held back") is gone: the number worth reading
   on sight rides on the Hopper tab. On a phone the tabs leave the bar for a
   dock on the bottom edge, below. */

/* On a phone this bar is also the color of the status bar above it (ADR 0019).
   The installed app has no apple-mobile-web-app-status-bar-style, so iOS gives
   it an opaque status bar and starts the page below it. WebKit then paints
   that strip in the background-color of an opaque, full-width fixed or sticky
   element at the top of the page (its "fixed color extension") and hides the
   iOS 26+ Liquid Glass blur while it does. This bar is that element, and it
   only works while it stays one: pinned at top 0, the full width, background
   exactly #0a0a0a (the same hex as theme-color, the manifest and <html>), no
   background-image across its top edge, no top border, and no backdrop-filter
   on it, on a pseudo-element of it or on anything above it. The bottom
   hairline is fine.

   The insets are base rules, not mobile ones. env(safe-area-inset-top) is 0
   in the installed app now that it no longer runs under the status bar, and 0
   anywhere without a notch; the side pads matter in landscape, where the notch
   eats a margin. */
.topbar {
  height: calc(var(--topbar-h) + env(safe-area-inset-top));
  padding-top: env(safe-area-inset-top);
  flex: none; display: flex; align-items: center; gap: 20px;
  padding-left: max(20px, env(safe-area-inset-left));
  padding-right: max(20px, env(safe-area-inset-right));
  border-bottom: 1px solid var(--line);
  position: sticky; top: 0; z-index: 30; background: var(--bg);
}
.brand { display: flex; align-items: center; gap: 9px; font-size: 14px; font-weight: 600; letter-spacing: -0.01em; }

/* The tabs (ADR 0028). Drawn twice, like the On market switch: .in-top in the
   bar, .in-dock along the bottom edge on a phone; the width blocks at the end
   of the file show one. */
.tabs { display: flex; gap: 2px; min-width: 0; }
.tabs.in-dock { display: none; }
.tab {
  position: relative;   /* holds the badge's .sr-only text, as .pick does */
  display: inline-flex; align-items: center; gap: 7px; flex: none;
  height: 34px; padding: 0 11px; border: 0; border-radius: 8px;
  background: transparent; color: var(--dim); font-size: 13px; font-weight: 500; white-space: nowrap;
}
.tabs.in-top .tab svg { width: 15px; height: 15px; }
.tab:hover { background: var(--card-hi); color: var(--fg); }
.tab.on { background: rgba(52,211,153,0.12); color: var(--fg); font-weight: 600; }
.tab-n {
  min-width: 18px; padding: 1px 6px; border-radius: 999px; box-sizing: border-box; text-align: center;
  font-family: var(--mono); font-size: 10.5px; font-weight: 600; line-height: 16px;
  background: var(--em); color: var(--em-ink);
}
/* The bar over the loading and sign-in screens, which is there for a phone's
   status bar (see bareTopbar in app.js). Shown in the phone block below. */
.topbar.bare { display: none; }

.linkbtn { padding: 0; border: 0; background: none; color: var(--dim); font-size: 12px; text-decoration: underline; text-underline-offset: 2px; }
.linkbtn:hover { color: var(--fg); }
.linkbtn[disabled] { opacity: .55; cursor: default; }
.btn-ghost {
  padding: 7px 13px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.16);
  background: transparent; color: var(--fg); font-size: 12px; font-weight: 500;
}
.btn-ghost:hover { border-color: var(--line-hi); }

/* ── Layout ──────────────────────────────────────────────────────────── */

.board { flex-grow: 1; display: flex; min-height: 0; }

.rail {
  width: 252px; flex: none; margin: 0; padding: 16px 16px 18px; border-right: 1px solid var(--line);
  display: flex; flex-direction: column; overflow-y: auto;
}
.rail-body { display: flex; flex-direction: column; gap: 18px; min-height: 0; }
/* Shown only where the rail folds, under 1000px. The On market switch rides on
   the same line there (ADR 0021); its desktop copy sits beside Sort. */
.rail-toggle { display: none; }
.rail-top { display: none; }
.grp { display: flex; flex-direction: column; gap: 8px; min-height: 0; }
.grp.hoods { flex-shrink: 1; min-height: 0; }
.grp .lbl { display: flex; align-items: baseline; gap: 8px; }
.grp .lbl .clear { text-transform: none; letter-spacing: 0; font-family: var(--sans); font-size: 11px; }

/* The state list. One at a time, in the order they get worked, as rows rather
   than chips: a bag of bubbles reads as "pick any", and this is a "pick one".
   The count sits right-aligned so the column of numbers can be read on its own. */
.states { display: flex; flex-direction: column; gap: 1px; }
.state-row {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  width: 100%; min-height: 34px; padding: 6px 10px;
  /* Only so the lead row's rule below can hang off its bottom edge. */
  position: relative;
  border: 0; border-radius: 8px; background: transparent;
  color: var(--dim); font-size: 13px; text-align: left;
}
.state-row:hover { background: var(--card-hi); color: var(--fg); }
.state-row.on { background: rgba(52,211,153,0.12); color: var(--fg); font-weight: 600; }
.state-row.on .state-n { color: var(--em); }
.state-n { font-family: var(--mono); font-size: 11px; color: var(--faint); font-variant-numeric: tabular-nums; }
.state-row .state-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }


.chips { display: flex; flex-wrap: wrap; gap: 6px; }
.chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 11px; border-radius: 999px; border: 1px solid rgba(255,255,255,0.12);
  background: transparent; color: var(--dim); font-size: 12px; min-height: 30px;
}
.chip:hover { border-color: var(--line-hi); color: var(--fg); }
.chip.on { background: var(--em); border-color: transparent; color: var(--em-ink); font-weight: 600; }
.chip .n { font-family: var(--mono); font-size: 10px; opacity: .7; }

.hood-q {
  height: 32px; padding: 0 10px; border-radius: 8px; font-size: 12.5px;
  border: 1px solid rgba(255,255,255,0.14); background: var(--card); color: var(--fg);
  font-family: inherit;
}
.hood-q:focus { outline: none; border-color: var(--em); }
.hood-list { overflow-y: auto; max-height: 46vh; align-content: start; padding-right: 2px; }
.infer-msg { color: var(--warn); }

.results { flex-grow: 1; min-width: 0; display: flex; flex-direction: column; }
.results-head {
  flex: none; display: flex; align-items: center; gap: 8px; padding: 11px 20px;
  border-bottom: 1px solid var(--line); flex-wrap: wrap;
}

/* Search. It sits over the board rather than in the rail, because it searches
   the homes and not the filters. */
.search {
  position: relative; display: flex; align-items: center; gap: 8px;
  min-width: 260px; flex-grow: 1; max-width: 420px;
  height: 34px; padding: 0 10px; border-radius: 9px;
  border: 1px solid rgba(255,255,255,0.14); background: var(--card); color: var(--faint);
}
.search:focus-within { border-color: var(--em); color: var(--dim); }
.search input {
  flex-grow: 1; min-width: 0; height: 100%; border: 0; outline: none; padding: 0;
  background: transparent; color: var(--fg); font-family: inherit; font-size: 13px;
}
.search input::placeholder { color: var(--missing); }
/* Safari draws its own clear affordance on a search input; this file has one. */
.search input::-webkit-search-cancel-button { display: none; }
.search-x {
  flex: none; width: 22px; height: 22px; border: 0; border-radius: 999px;
  background: transparent; color: var(--faint); font-size: 16px; line-height: 1;
}
.search-x:hover { background: var(--card-hi); color: var(--fg); }
/* The On market switch (ADR 0021). A chip's box with a track and a knob in
   it, so it reads as on/off rather than as one more sort. */
.switch {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 5px 11px 5px 7px; border-radius: 999px; border: 1px solid rgba(255,255,255,0.12);
  background: transparent; color: var(--dim); font-size: 12px; min-height: 30px;
}
.switch:hover { border-color: var(--line-hi); color: var(--fg); }
.switch-track {
  position: relative; flex: none; width: 26px; height: 15px; border-radius: 999px;
  background: rgba(255,255,255,0.16); transition: background .15s ease;
}
.switch-knob {
  position: absolute; top: 2px; left: 2px; width: 11px; height: 11px; border-radius: 999px;
  background: var(--fg); transition: transform .15s ease;
}
.switch.on { color: var(--fg); border-color: rgba(52,211,153,0.55); font-weight: 600; }
.switch.on .switch-track { background: var(--em); }
.switch.on .switch-knob { transform: translateX(11px); background: var(--em-ink); }
.switch[disabled] { opacity: .45; cursor: default; }
.switch[disabled]:hover { border-color: rgba(255,255,255,0.12); color: var(--dim); }
.results-count .linkbtn { margin-left: 8px; }
.results-count { font-size: 12px; }
.board-scroll { flex-grow: 1; padding: 6px 20px 26px; }

/* The harvest needs a hand (ADR 0027). Above the cards, not in the top bar:
   the bar is the iOS status strip's color source and stays plain (ADR 0019). */
.harvest-note {
  display: flex; align-items: flex-start; gap: 10px;
  margin: 4px 0 14px; padding: 10px 12px;
  border: 1px solid color-mix(in srgb, var(--warn) 45%, transparent);
  border-radius: var(--r);
  background: color-mix(in srgb, var(--warn) 9%, transparent);
  color: var(--fg); font-size: 13px; line-height: 1.45;
}
.harvest-note svg { flex: none; margin-top: 2px; color: var(--warn); }
.harvest-fix { color: var(--dim); }
.harvest-note code {
  font-family: var(--mono); font-size: 12px; color: var(--fg);
  overflow-wrap: anywhere;
}

/* ── The grid ────────────────────────────────────────────────────────── */
/* One grid, filtered. It was four stacked bands with their own headers and
   counts, which read well and still made you scroll past a shortlist to reach
   the queue -- and every count a band header carried is a count a filter chip
   carries, so the bands were doing a filter's job with more furniture. */

.more { margin: 16px 0 4px; }

/* Saved, live first (ADR 0028): a heading over each group you can buy from,
   and a full-width line for each folded one, saying how many and why. */
.stand-h {
  margin: 14px 0 10px; display: flex; align-items: baseline; gap: 8px;
  font-family: var(--mono); font-size: 11px; font-weight: 500;
  letter-spacing: .10em; text-transform: uppercase; color: var(--faint);
}
.stand-h:first-child { margin-top: 8px; }
.stand-h .n, .stand-fold-t .n { font-family: var(--mono); font-size: 11px; color: var(--faint); letter-spacing: 0; }
.stand-fold {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  width: 100%; margin: 22px 0 10px; padding: 12px 14px; min-height: 60px;
  border-radius: 12px; border: 1px solid var(--line); background: var(--card);
  color: var(--fg); text-align: left;
}
.stand-fold:first-child { margin-top: 8px; }
.stand-fold:hover { border-color: var(--line-hi); }
.stand-fold-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.stand-fold-t { font-size: 14px; font-weight: 600; }
.stand-fold-why { font-size: 12.5px; color: var(--dim); }
.stand-fold svg { flex: none; color: var(--dim); transition: transform .15s ease; }
.stand-fold[aria-expanded="true"] svg { transform: rotate(180deg); }

/* ── The Hopper page (ADR 0028, 0029) ─────────────────────────────────── */
/* The page the app opens on: the queue, the picks still for sale, the news
   on saved homes and the Saved summary. Two columns where there is room for
   them, one on a phone. It reuses the card's type and colors: the same price,
   the same pin gold, the same keep-green and drop-red. */
.hop { flex: 1 1 auto; min-height: 0; padding: 26px 28px 40px; }
/* Focus lands on the screen itself after a move between the Hopper page, the
   review and the board (landFocus in app.js), so the next Tab starts inside
   it. A ring around the whole screen would say nothing. */
.hop:focus, .results:focus { outline: none; }
.hop-in { max-width: 1160px; margin: 0 auto; display: flex; flex-direction: column; gap: 22px; }
.hop-head { display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap; }
.hop-head h1 { margin: 0; font-size: 26px; font-weight: 600; letter-spacing: -0.02em; }
.hop-date, .hop-aside { font-size: 12px; color: var(--dim); }
.hop-date { font-family: var(--mono); font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--faint); }
.hop-cols { display: flex; flex-wrap: wrap; gap: 28px; align-items: flex-start; }
.hop-main { flex: 999 1 520px; min-width: 0; display: flex; flex-direction: column; gap: 28px; }
.hop-side { flex: 1 1 320px; min-width: 0; display: flex; flex-direction: column; gap: 14px; }
.hop-sec { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
.hop-sec-h { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.hop-sec-h h2 { margin: 0; }

.thumb { flex: none; width: 46px; height: 46px; border-radius: 8px; overflow: hidden; background: #171717; }
.thumb img, .thumb .ph, .pick-photo img, .pick-photo .ph { display: block; width: 100%; height: 100%; object-fit: cover; }

/* The queue: a door into the Hopper when it has homes in it, a quiet line
   saying so when it does not. */
.q-card {
  display: flex; align-items: center; gap: 14px; width: 100%; min-height: 84px; padding: 14px;
  border-radius: var(--r-lg); border: 1px solid rgba(52,211,153,0.40); background: rgba(52,211,153,0.07);
  color: var(--fg); text-align: left;
}
.q-card:hover { background: rgba(52,211,153,0.12); }
.q-card.done { border-color: var(--line); background: var(--card); }
.q-card.done svg { flex: none; color: var(--em); }
.q-thumbs { flex: none; display: flex; }
.q-thumbs .thumb { width: 56px; height: 56px; border-radius: 10px; border: 2px solid var(--bg); box-sizing: border-box; }
.q-thumbs .thumb + .thumb { margin-left: -26px; }
.q-text { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.q-title { font-size: 16px; font-weight: 600; letter-spacing: -0.01em; }
.q-sub { font-size: 12.5px; color: var(--dim); }
.q-go {
  flex: none; display: inline-flex; align-items: center; height: 36px; padding: 0 16px;
  border-radius: 999px; background: var(--em); color: var(--em-ink); font-size: 13px; font-weight: 600;
}

/* What changed on the saved homes. Green for news in your favor, amber for a
   contract, red for a sale: the board's own keep and drop colors. */
.news { list-style: none; margin: 0; padding: 0; border: 1px solid var(--line); border-radius: 12px; overflow: hidden; background: var(--card); }
.news li + li { border-top: 1px solid var(--line); }
.news-row {
  display: flex; align-items: center; gap: 12px; width: 100%; min-height: 64px; padding: 9px 12px;
  border: 0; background: transparent; color: var(--fg); text-align: left;
}
.news-row:hover { background: var(--card-hi); }
.news-text { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.news-addr { font-size: 14px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.news-what { display: flex; align-items: center; gap: 6px; font-size: 12.5px; }
.news-what::before { content: ""; flex: none; width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
.news-what.good { color: var(--em); }
.news-what.held { color: var(--warn); }
.news-what.gone { color: var(--red); }
.news-when { flex: none; white-space: nowrap; font-family: var(--mono); font-size: 11px; color: var(--faint); }
.news-empty { margin: 0; padding: 14px 16px; border: 1px solid var(--line); border-radius: 12px; font-size: 13px; color: var(--dim); }

/* Top picks still for sale. Each opens that home on the board. */
.picks { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 14px; }
/* position: relative is load-bearing. A pick carries an .sr-only "Pinned."
   label, which is absolutely positioned; with no positioned box around it, it
   escaped the sideways scroller on a phone, sat at the fourth pick's x, and
   widened the page to 819px, so the phone opened zoomed out. */
.pick {
  position: relative;
  display: flex; flex-direction: column; padding: 0; overflow: hidden;
  border-radius: 12px; border: 1px solid rgba(52,211,153,0.55); background: var(--card);
  color: var(--fg); text-align: left;
}
.pick.pinned { border-color: rgba(245,200,76,0.75); }
.pick:hover { border-color: var(--line-hi); }
.pick-shot { position: relative; display: block; height: 150px; background: #171717; }
.pick-photo { display: block; width: 100%; height: 100%; }
.pick-body { display: flex; flex-direction: column; gap: 2px; padding: 10px 12px 12px; }
.pick-addr { font-size: 12px; color: var(--dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* The Saved row in one line: the same three groups it is drawn in. */
.standing {
  display: flex; flex-direction: column; gap: 10px; width: 100%; padding: 14px 16px;
  border-radius: 12px; border: 1px solid var(--line); background: var(--card);
  color: var(--fg); text-align: left;
}
.standing:hover { border-color: var(--line-hi); }
.standing-top { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.standing-t { font-size: 14px; font-weight: 600; }
.standing-bar { display: flex; gap: 3px; height: 8px; }
.standing-bar span { min-width: 6px; border-radius: 4px; }
/* Told apart by lightness as well as hue: bright green, dark grey, mid red. */
.sb-forsale { background: var(--em); }
.sb-quiet { background: #4b4b55; }
.sb-gone { background: rgba(255,101,104,0.55); }
.standing-key { font-size: 12.5px; color: var(--dim); }

/* The waiting homes listed under the Start card: the news list's rows with a
   price and a place in them. */
.q-price { font-family: var(--mono); font-weight: 600; letter-spacing: -0.02em; }
.q-specs { font-weight: 400; color: var(--dim); }
.q-row-sub { font-size: 12.5px; color: var(--dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hop-link { align-self: flex-start; }

/* ── Review, one home at a time (ADR 0029) ─────────────────────────────── */
/* One home, big, and the three verdicts on a bar along the bottom edge,
   where a thumb is. After a pass the bar gives way to the reason chips. */
/* A column that fills the screen, so the verdict bar sits on the bottom edge
   even when the home above it is short. */
.rv { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.rv:focus { outline: none; }
.rv-in { flex: 1 0 auto; width: 100%; box-sizing: border-box; max-width: 760px; margin: 0 auto; padding: 6px 20px 0; display: flex; flex-direction: column; }
.rv-head { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); align-items: center; min-height: 48px; }
.rv-back {
  justify-self: start; display: inline-flex; align-items: center; gap: 4px;
  min-height: 44px; padding: 0 8px 0 0; border: 0; background: none; color: var(--dim); font-size: 14px;
}
.rv-back:hover { color: var(--fg); }
.rv-pos { justify-self: center; font-family: var(--mono); font-size: 12px; color: var(--dim); white-space: nowrap; }
.rv-side { justify-self: end; display: flex; gap: 2px; }
.rv-link { min-height: 44px; padding: 0 10px; border: 0; background: none; color: var(--dim); font-size: 14px; }
.rv-link:hover { color: var(--fg); }
.rv-progress { height: 3px; margin-bottom: 14px; border-radius: 2px; overflow: hidden; background: rgba(255,255,255,0.12); }
.rv-progress span { display: block; height: 100%; background: var(--em); }
.rv-home { display: flex; flex-direction: column; overflow: hidden; margin-bottom: 14px; border: 1px solid var(--line); border-radius: 14px; background: var(--card); }
.rv-home .card-shot { height: clamp(220px, 44vh, 420px); }
.rv-info { padding: 14px 16px 10px; display: flex; flex-direction: column; gap: 3px; }
.rv-price { font-family: var(--mono); font-size: 26px; font-weight: 600; letter-spacing: -0.02em; }
.rv-specs { font-size: 15px; color: #e4e4e4; }
.rv-acts {
  position: sticky; bottom: 0; z-index: 5; margin: auto -20px 0;
  padding-bottom: env(safe-area-inset-bottom); background: var(--bg);
}
.rv-acts .act { height: 60px; font-size: 14px; }
/* The nudge and a toast float above the review's bar, not on it: centered at
   the bottom, they sat on Not yet and on Next home. */
body.in-review .update-pill { bottom: calc(76px + env(safe-area-inset-bottom)); }
body.in-review .toast { bottom: calc(76px + env(safe-area-inset-bottom)); }
body.in-review.has-nudge .toast { bottom: calc(132px + env(safe-area-inset-bottom)); }
.rv-why {
  position: sticky; bottom: 0; z-index: 5; margin: auto -20px 0;
  padding: 16px 20px calc(18px + env(safe-area-inset-bottom));
  display: flex; flex-direction: column; gap: 12px;
  border-top: 1px solid var(--line-hi); border-radius: 18px 18px 0 0; background: #141414;
}
.rv-why-top { display: flex; align-items: center; gap: 12px; }
.rv-why-mark {
  flex: none; width: 32px; height: 32px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: rgba(255,101,104,0.14); color: var(--red); font-size: 18px; font-weight: 700;
}
.rv-why-what { min-width: 0; display: flex; flex-direction: column; font-size: 12.5px; color: var(--dim); }
.rv-why-what b { font-size: 15px; color: var(--fg); }
.rv-why-what span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rv-why-h { margin: 2px 0 0; font-size: 19px; font-weight: 600; letter-spacing: -0.01em; }
.rv-why-sub { margin: -8px 0 0; font-size: 13px; color: var(--dim); }
.rv-next {
  height: 50px; border: 0; border-radius: 12px; background: var(--fg); color: var(--bg);
  display: inline-flex; align-items: center; justify-content: center; gap: 8px; font-size: 14px; font-weight: 600;
}
.rv-next:hover { background: #ffffff; }
.rv-done { margin: 10vh auto 40px; max-width: 420px; display: flex; flex-direction: column; align-items: center; gap: 12px; text-align: center; }
.rv-done svg { width: 40px; height: 40px; color: var(--em); }
.rv-done h1 { margin: 0; font-size: 24px; font-weight: 600; letter-spacing: -0.01em; }
.rv-done p { margin: 0; color: var(--dim); }
.rv-again { border: 0; height: 44px; }

/* Why a home was passed (ADR 0029): one-tap chips, in the review after a pass
   and on a passed home's sheet. Pressed ones take the pass's red. */
.reasons { display: flex; flex-wrap: wrap; gap: 8px; }
.reason {
  min-height: 40px; padding: 0 15px; border-radius: 999px;
  border: 1px solid rgba(255,255,255,0.14); background: transparent;
  color: #d4d4d4; font-size: 13px; font-weight: 500;
}
.reason:hover { border-color: var(--line-hi); color: var(--fg); }
.reason.on { border-color: rgba(255,101,104,0.60); background: rgba(255,101,104,0.16); color: var(--fg); }
.card-why { margin: 0 12px 10px; font-size: 12px; color: var(--red); }

/* ── The detail sheet (ADR 0029) ───────────────────────────────────────── */
/* A drawer beside the board on a desktop, the whole screen on a phone (below).
   z-index 60: over the dock (40) and the bar (30), under the note (80) and the
   photo viewer, both of which open from it. */
body.sheet-open { overflow: hidden; }
.sheet-scrim { position: fixed; inset: 0; z-index: 60; display: flex; justify-content: flex-end; background: rgba(0,0,0,0.55); }
.sheet { width: min(480px, 100%); height: 100%; display: flex; flex-direction: column; border-left: 1px solid var(--line-hi); background: #0d0d0d; }
.sheet-head {
  flex: none; display: flex; align-items: center; gap: 8px;
  /* At least 10px above the close button: focus lands on it as the sheet
     opens, and its ring must stay out of the top 6px, which on a phone is the
     status bar's color source (ADR 0019; statusbar_probe "sheet"). */
  min-height: 56px; padding: max(10px, env(safe-area-inset-top)) 14px 6px 6px;
  border-bottom: 1px solid var(--line); background: var(--bg);
}
.sheet-x {
  flex: none; width: 44px; height: 44px; border: 0; border-radius: 999px;
  background: transparent; color: var(--dim); font-size: 24px; line-height: 1;
}
.sheet-x:hover { background: var(--card-hi); color: var(--fg); }
.sheet-title { margin: 0; min-width: 0; font-size: 15px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sheet-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding-bottom: calc(28px + env(safe-area-inset-bottom)); }
.sheet-hero { position: relative; display: block; width: 100%; height: 260px; padding: 0; border: 0; background: #171717; cursor: zoom-in; }
.sheet-hero img, .sheet-hero .ph { display: block; width: 100%; height: 100%; object-fit: cover; }
.sheet-hero.dead { cursor: default; }
.sheet-ms { position: absolute; top: 10px; left: 10px; }
.sheet-n { position: absolute; right: 10px; bottom: 10px; font-family: var(--mono); font-size: 10.5px; }
.sheet-strip { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 6px; padding: 6px 16px 0; }
.sheet-thumb { position: relative; height: 56px; padding: 0; border: 0; border-radius: 6px; overflow: hidden; background: #171717; }
.sheet-thumb img, .sheet-thumb .ph { display: block; width: 100%; height: 100%; object-fit: cover; }
.sheet-more {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  background: rgba(0,0,0,0.6); color: var(--fg); font-family: var(--mono); font-size: 13px; font-weight: 600;
}
.sheet-sec { padding: 18px 16px 0; display: flex; flex-direction: column; gap: 8px; }
.sheet-sec h3 { margin: 0; }
.sheet-price { gap: 3px; }
.sheet-cut {
  align-self: flex-start; margin: 2px 0 4px; padding: 3px 8px; border-radius: 6px;
  background: rgba(52,211,153,0.12); color: var(--em); font-size: 12px; font-weight: 600;
}
.sheet-out { align-self: flex-start; margin-top: 6px; font-size: 13px; }
.sheet-acts, .sheet-tools { border: 1px solid var(--line); border-radius: 12px; overflow: hidden; }
.sheet-acts { margin: 16px 16px 0; }
.sheet-note { border: 1px solid var(--line); border-radius: 10px; }
.facts {
  margin: 0; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1px;
  overflow: hidden; border: 1px solid var(--line); border-radius: 12px; background: var(--line);
}
.fact { min-width: 0; padding: 10px 12px; background: #101010; }
.fact dt { font-size: 11.5px; color: var(--faint); }
.fact dd { margin: 0; font-size: 14px; overflow-wrap: anywhere; }
.fact-sub { display: block; font-size: 11.5px; color: var(--warn); }
.hist { margin: 0; padding: 0; list-style: none; }
.hist li { display: flex; gap: 12px; padding: 8px 0; border-top: 1px solid rgba(255,255,255,0.08); font-size: 13.5px; }
.hist li:first-child { border-top: 0; }
.hist-when { flex: none; width: 54px; font-family: var(--mono); font-size: 12px; color: var(--faint); }
.sheet-agent { margin: 0; font-size: 13.5px; line-height: 1.6; }

/* ── Cards ───────────────────────────────────────────────────────────── */

.cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(272px, 1fr)); gap: 14px; align-content: start; }

.card {
  display: flex; flex-direction: column; border-radius: 12px; overflow: hidden;
  border: 1px solid var(--line); background: var(--card);
  transition: border-color .15s ease, opacity .16s ease;
}
.card:hover { border-color: var(--line-hi); }
.card.saved { border-color: rgba(52,211,153,0.55); box-shadow: 0 0 0 1px rgba(52,211,153,0.22); }
.card.pinned { border-color: rgba(245,200,76,0.75); box-shadow: 0 0 0 1px rgba(245,200,76,0.30); }
/* Dimmed, but only where there is a pointer that can undim it. On a touch
   screen there is no hover, so a card at .5 opacity is simply unreadable and
   stays that way -- and the chip it was filtered in under already says it is
   passed or gone.
   Keyboard focus lifts it too, for the same reason. */
@media (hover: hover) {
  .card.passed { opacity: .55; }
  .card.inactive { opacity: .68; }
  .card.passed:hover, .card.inactive:hover,
  .card.passed:focus-within, .card.inactive:focus-within { opacity: 1; }
}

/* The photo is the link. There is no separate "view listing" button: the
   picture of the house is the most obvious way out to the house. The badges
   are siblings of the anchor rather than children of it, because the anchor is
   aria-hidden and "Sold" is not a thing to hide from a screen reader. */
.card-shot {
  position: relative; height: 164px; flex: none; background: #171717; overflow: hidden;
  /* A sideways swipe flips the photo (ADR 0023); a vertical one is the page. */
  touch-action: pan-y pinch-zoom;
}
/* The photo is a button that opens the viewer (ADR 0023), no longer a link
   out: the address under it is the way to the listing. */
.card-photo {
  display: block; width: 100%; height: 100%; padding: 0; border: 0; background: none;
  cursor: zoom-in;
}
.card-photo img { width: 100%; height: 100%; object-fit: cover; transition: transform .2s ease; }
.card-shot:hover img { transform: scale(1.02); }
/* Nothing to open: no zoom, no cursor promise. */
.card-photo.dead { cursor: default; }
.card-shot:has(.card-photo.dead):hover img { transform: none; }
.card-photo .ph {
  width: 100%; height: 100%; display: flex; align-items: center; justify-content: center;
  font-size: 11px; color: var(--missing); background: rgba(255,255,255,0.04);
}

.card-ms { position: absolute; top: 8px; left: 8px; }
/* The neighborhood, on the picture, because it is the second thing you want
   after the price and it was a grey clause at the end of a line. No marker for
   an inferred one: the title says where it came from, and a caveat badge on
   most of the board is noise rather than information (ADR 0008). */
.card-hood {
  position: absolute; left: 8px; bottom: 8px; max-width: calc(100% - 16px);
  font-size: 12px; font-weight: 600; letter-spacing: -0.01em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.card-hood.none { color: var(--missing); font-weight: 500; }
.card-mark {
  position: absolute; top: 8px; right: 8px; width: 22px; height: 22px; border-radius: 7px;
  display: flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 700; color: #0a0a0a;
}
.card-mark.saved { background: var(--em); }
.card-mark.passed { background: rgba(255,101,104,0.9); color: var(--red-ink); }
.card-mark.waiting { background: var(--warn); color: var(--warn-ink); }
.card-mark.pinned { background: var(--pin); color: var(--pin-ink); }
.card-mark.pinned svg { width: 13px; height: 13px; }
.card-mark.waiting svg { width: 13px; height: 13px; }

/* Flip through a home's photos on the card (ADR 0023): arrows at the edges,
   shown on hover where there is a pointer to hover with, and a count in the
   corner the "Open listing" hint used to have. */
.shot-nav {
  position: absolute; top: 50%; z-index: 1; transform: translateY(-50%);
  width: 32px; height: 32px; border: 0; border-radius: 999px;
  display: flex; align-items: center; justify-content: center;
  color: #fff; background: rgba(0,0,0,0.55);
  opacity: 0; transition: opacity .14s ease;
}
.shot-nav.prev { left: 6px; }
.shot-nav.next { right: 6px; }
.card-shot:hover .shot-nav, .shot-nav:focus-visible { opacity: 1; }
.shot-nav:hover { background: rgba(0,0,0,0.8); }
.shot-n {
  position: absolute; right: 8px; bottom: 8px;
  font-family: var(--mono); font-size: 10.5px; font-variant-numeric: tabular-nums;
}
.card-shot:has(.shot-n) .card-hood { max-width: calc(100% - 84px); }

/* ── The photo viewer (ADR 0023) ─────────────────────────────────────────
   Opaque #0a0a0a from its top edge and no backdrop-filter anywhere: on a
   phone it covers the status bar strip, and WebKit paints that strip only
   from what is there (ADR 0019). */
.viewer {
  position: fixed; inset: 0; z-index: 90;
  display: flex; flex-direction: column;
  background: #0a0a0a;
  padding-top: env(safe-area-inset-top); padding-bottom: env(safe-area-inset-bottom);
  animation: note-in .14s ease-out;
}
body.viewer-open { overflow: hidden; }
.viewer-top {
  flex: none; display: flex; align-items: center; gap: 12px;
  padding: 10px max(12px, env(safe-area-inset-right)) 10px max(16px, env(safe-area-inset-left));
}
.viewer-title { min-width: 0; flex: 1; display: flex; align-items: baseline; gap: 10px; }
.viewer-addr { min-width: 0; font-size: 14px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.viewer-price { flex: none; font-family: var(--mono); font-size: 13px; color: var(--dim); }
.viewer-n { flex: none; font-family: var(--mono); font-size: 12px; color: var(--dim); font-variant-numeric: tabular-nums; }
.viewer-out {
  flex: none; display: inline-flex; align-items: center; gap: 4px;
  font-size: 12px; color: var(--dim); text-decoration: none;
}
.viewer-out:hover { color: var(--fg); }
/* A phone in portrait: the address needs the room more than the price does. */
@media (max-width: 480px) { .viewer-price { display: none; } }
.viewer-x {
  flex: none; width: 36px; height: 36px; border: 0; border-radius: 999px;
  color: var(--fg); background: rgba(255,255,255,0.08); font-size: 20px; line-height: 1;
}
.viewer-x:hover { background: rgba(255,255,255,0.16); }
.viewer-stage {
  position: relative; flex: 1; min-height: 0;
  display: flex; align-items: center; justify-content: center;
  touch-action: pan-y pinch-zoom;
}
.viewer-img { max-width: 100%; max-height: 100%; object-fit: contain; user-select: none; -webkit-user-drag: none; }
.viewer-nav {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 44px; height: 44px; border: 0; border-radius: 999px;
  display: flex; align-items: center; justify-content: center;
  color: #fff; background: rgba(0,0,0,0.55);
}
.viewer-nav.prev { left: max(12px, env(safe-area-inset-left)); }
.viewer-nav.next { right: max(12px, env(safe-area-inset-right)); }
.viewer-nav:hover { background: rgba(0,0,0,0.8); }
.viewer-strip {
  flex: none; display: flex; gap: 6px; overflow-x: auto;
  padding: 10px max(12px, env(safe-area-inset-right)) 12px max(12px, env(safe-area-inset-left));
  scrollbar-width: thin;
}
.viewer-thumb {
  flex: none; width: 72px; height: 52px; padding: 0; overflow: hidden;
  border: 2px solid transparent; border-radius: 6px; background: #171717; opacity: .6;
}
.viewer-thumb img { display: block; width: 100%; height: 100%; object-fit: cover; }
.viewer-thumb:hover { opacity: .9; }
.viewer-thumb.on { border-color: var(--em); opacity: 1; }

.card-body { padding: 10px 12px 12px; display: flex; flex-direction: column; gap: 2px; flex-grow: 1; }
/* The card's facts are a button into its detail sheet (ADR 0029). Drawn as
   the text it was; the chevron says it opens. position: relative holds its
   .sr-only label (see .pick). */
.card-open {
  position: relative; display: flex; flex-direction: column; gap: 2px; width: 100%;
  padding: 0; border: 0; background: none; color: inherit; font: inherit; text-align: left;
}
.card-more { margin-left: auto; align-self: center; display: inline-flex; color: var(--faint); }
.card-open:hover .card-more { color: var(--fg); }
.card-top { display: flex; align-items: baseline; gap: 8px; }
.card-price { font-family: var(--mono); font-size: 18px; font-weight: 600; letter-spacing: -0.02em; }
.card-was { font-family: var(--mono); font-size: 12px; color: var(--faint); text-decoration: line-through; }
.card-specs { font-size: 13px; color: #e4e4e4; }
.card-second { font-size: 12px; color: var(--dim); }
.card-place { font-size: 12px; color: var(--faint); }
.card-addr { font-size: 12px; color: var(--dim); margin-top: 4px; overflow-wrap: anywhere; }
.card-seen { color: var(--missing); }
/* The keyboard's way out to the listing, and the only one a screen reader is
   offered: the photo above points at the same place but is hidden from both,
   because two tab stops per card announcing one destination is worse than one. */
.addr-link { color: var(--fg); font-weight: 500; text-decoration: underline; text-decoration-color: rgba(255,255,255,0.22); text-underline-offset: 3px; }
.addr-link:hover { color: #fff; text-decoration-color: var(--em); }
.addr-link svg { display: inline-block; vertical-align: -1px; opacity: .55; }

/* A verdict cast on a card that is still being filtered as whatever it was when
   it was clicked. It stays put on purpose: with the state as a filter, a card
   that re-files itself the instant you click it does not move, it vanishes. */
.card-moved {
  display: flex; align-items: center; gap: 8px; width: 100%; text-align: left;
  padding: 7px 12px; border: 0; border-top: 1px solid var(--line);
  font-size: 11px; color: var(--faint); background: rgba(255,255,255,0.02);
}
.card-moved:hover { color: var(--fg); background: var(--card-hi); }
.card-moved .tidy-hint { color: var(--em); text-decoration: underline; text-underline-offset: 2px; }

/* What a "not yet" is waiting on (ADR 0021), or that it came. Amber while it
   waits, the save color once the photos are in: the second is the reason a
   home you passed is in the Hopper again, and should read as good news. */
.card-wait {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  padding: 7px 12px; border-top: 1px solid var(--line);
  font-size: 11.5px; font-weight: 600; color: var(--warn); background: rgba(232,166,74,0.06);
}
.card-wait svg { width: 12px; height: 12px; flex: none; }
.card-wait-n { font-weight: 400; color: var(--dim); }
.card-wait.back { color: var(--em); background: rgba(52,211,153,0.07); }

/* What was said about the house. One line, clipped: the card answers "was
   anything said", the popout answers "what". */
.card-note {
  display: block; width: 100%; text-align: left;
  padding: 8px 12px; border: 0; border-top: 1px solid var(--line);
  background: rgba(52,211,153,0.05); cursor: pointer;
  font-size: 12px; color: #d4d4d4; line-height: 1.35;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.card-note:hover { background: rgba(52,211,153,0.10); }

/* ── Card actions ────────────────────────────────────────────────────── */
/* Two rows. The verdict is the loud one, in the two colours that mean keep and
   drop; note and share sit under it, labelled, because an icon on its own read
   as decoration and both were being missed. */

.card-acts { display: flex; border-top: 1px solid var(--line); }
.act {
  flex-grow: 1; flex-basis: 0; min-width: 0;
  height: 44px; border: 0; background: transparent;
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  font-size: 12.5px; font-weight: 600; color: var(--dim);
}
.act + .act { border-left: 1px solid var(--line); }
.act svg { flex: none; }

.act.pass { color: var(--red); background: rgba(255,101,104,0.07); }
.act.pass:hover { background: rgba(255,101,104,0.16); }
.act.pass.on { background: var(--red); color: var(--red-ink); }
.act.pass.on:hover { background: #ff7d80; }

.act.save { color: var(--em); background: rgba(52,211,153,0.07); }
.act.save:hover { background: rgba(52,211,153,0.16); }
.act.save.on { background: var(--em); color: var(--em-ink); }
.act.save.on:hover { background: #4ce0a8; }

/* Not yet (ADR 0021) sits between Save and Pass, in the one color that is
   neither keep nor drop. */
.act.wait { color: var(--warn); background: rgba(232,166,74,0.07); }
.act.wait:hover { background: rgba(232,166,74,0.16); }
.act.wait.on { background: var(--warn); color: var(--warn-ink); }
.act.wait.on:hover { background: #f0b866; }

/* Pin (ADR 0022) takes the middle slot on a saved card. Gold, the color of a
   star, and never on the same card as Not yet's amber. */
.act.pin { color: var(--pin); background: rgba(245,200,76,0.07); }
.act.pin:hover { background: rgba(245,200,76,0.16); }
.act.pin.on { background: var(--pin); color: var(--pin-ink); }
.act.pin.on:hover { background: #f8d673; }

.card-acts.second .act { height: 38px; font-size: 11.5px; font-weight: 500; color: var(--dim); background: transparent; gap: 5px; padding: 0 4px; }
/* An <a> in a row of <button>s: same box, same weight, no underline. */
.card-acts .commute { text-decoration: none; color: var(--dim); }
.card-acts .commute:hover { color: var(--fg); }
.card-acts.second .act:hover { background: var(--card-hi); color: var(--fg); }
.card-acts.second .act.note.on { color: var(--em); }
.card-acts.second .act.note.on:hover { background: rgba(52,211,153,0.10); }
.card-acts.second .act.share.done { color: var(--em); }
.act[disabled] { color: var(--missing); cursor: default; background: transparent; }
.act[disabled]:hover { background: transparent; }

/* ── Small labels ────────────────────────────────────────────────────── */

.pill-dark {
  padding: 4px 9px; border-radius: var(--r-sm); font-size: 11px; font-weight: 600;
  color: var(--fg); background: rgba(0,0,0,0.62);
}
.pill-dark.gone { color: #0a0a0a; background: var(--red); }
.pill-dark.private { color: var(--info); }

/* Which feed alerted on this home. Provenance, not a filter axis -- cards
   render identically whatever found them (CONTEXT.md "Source chip"). */
.chip-src {
  font-size: 9px; font-weight: 600; letter-spacing: .02em;
  padding: 1px 5px; border-radius: 4px; vertical-align: 1px;
  border: 1px solid currentColor; background: rgba(255,255,255,0.04);
}
.chip-src[data-s="redfin"] { color: var(--red); }
.chip-src[data-s="zenlist"] { color: var(--info); }
.chip-src[data-s="manual"] { color: var(--dim); }

/* Inferred neighborhood. A qualifier, not a badge -- it should read as a
   caveat on the name it follows, never compete with it. */
.guess {
  font-family: var(--mono); font-size: 9px; font-weight: 500;
  letter-spacing: .08em; text-transform: uppercase;
  padding: 1px 5px; border-radius: 4px; vertical-align: 1px;
  color: var(--warn); border: 1px solid rgba(232,166,74,0.30); background: rgba(232,166,74,0.10);
}

/* ── Shared states ───────────────────────────────────────────────────── */

.empty { padding: 40px 20px; text-align: center; }
.empty h3 { margin: 0; font-size: 15px; font-weight: 500; }
.empty p { margin: 5px 0 0; font-size: 13px; color: var(--faint); }
.loading { padding: 70px 20px; text-align: center; color: var(--dim); font-size: 14px; }

.login { min-height: 100vh; min-height: 100dvh; display: flex; align-items: center; justify-content: center; padding: 24px; }
.login form { width: 100%; max-width: 320px; display: flex; flex-direction: column; gap: 12px; }
.login h1 { margin: 0 0 4px; font-size: 19px; font-weight: 600; }
/* 16px, not 15: this is the first field anyone touches on a phone, and at 15px
   iOS zooms the page the moment it takes focus -- so the sign-in screen greets
   you scrolled sideways. Unconditional rather than in the touch block, because
   16px costs a desktop nothing. */
.login input {
  height: 46px; padding: 0 13px; border-radius: var(--r); font-size: 16px;
  border: 1px solid rgba(255,255,255,0.16); background: var(--card); color: var(--fg);
  font-family: inherit;
}
.login input:focus { outline: none; border-color: var(--em); }
.login button { height: 46px; border-radius: var(--r); border: 0; background: var(--em); color: var(--em-ink); font-size: 15px; font-weight: 700; }
.login button[disabled] { opacity: .6; }
.login .err { font-size: 13px; color: var(--red); min-height: 18px; }

/* z-index 90 keeps a failed-save toast above the note popout (80), which is
   the only thing on this site that ever covers the board. */
.toast {
  position: fixed; left: 50%; bottom: 22px; transform: translateX(-50%);
  padding: 10px 16px; border-radius: var(--r); font-size: 13px; z-index: 90;
  border: 1px solid var(--line); background: #171717; color: var(--fg);
  box-shadow: 0 8px 28px rgba(0,0,0,0.5);
}
.toast.err { border-color: rgba(255,101,104,0.4); color: var(--red); }

/* ── "The board moved" nudge ──────────────────────────────────────────── */
/* Raised by the revision poll (app.js, "Staying current") when the ingest has
   landed listings, or the other person has ruled on one, since this page
   loaded. Drawn inside the header so it survives a render, but fixed, so the
   header does not grow a row when it appears.

   It floats at the BOTTOM, not under the top bar, and that is the second place
   it was put. Centered below the header it sat squarely on the search box and
   the sort buttons, which is a poor trade: an offer to reload should not cover
   the control you were reaching for. Down here it covers cards, which scroll,
   and on a phone it is where the thumb already is.

   z-index sits between the topbar (30) and the note popout (80): above the
   board it is about, below the words someone is still typing. The failed-save
   toast (90) stays above both, and moves up out of the way -- see below. */
.update-pill {
  position: fixed;
  bottom: calc(22px + env(safe-area-inset-bottom));
  left: 50%; transform: translateX(-50%);
  z-index: 70;
  display: inline-flex; align-items: center; gap: 8px;
  /* 44px tall, like every other thing on this board a thumb has to hit. */
  min-height: 44px; padding: 0 18px;
  border-radius: 999px;
  border: 1px solid rgba(52, 211, 153, 0.42);
  background: #14231d;
  color: var(--em);
  font-size: 13.5px; font-weight: 600; letter-spacing: -0.005em;
  /* Without this it wraps on a phone, and not for the reason it looks like: a
     fixed box with `left: 50%` and no `right` is offered only the half of the
     viewport to its right, so 390px screens sized it to 195 and broke the line
     before the transform pulled it back to center. nowrap makes max-content the
     width instead. */
  white-space: nowrap;
  box-shadow: 0 10px 30px rgba(0,0,0,0.55);
  animation: update-in .3s cubic-bezier(0.16, 1, 0.3, 1);
  transition: background .14s ease-out, transform .14s ease-out;
}
.update-pill:hover { background: #193026; transform: translateX(-50%) translateY(-1px); }
.update-pill:active { transform: translateX(-50%) translateY(0); }
.update-pill:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.update-pill-dot {
  width: 7px; height: 7px; border-radius: 50%; background: var(--em);
  animation: update-beat 1.9s ease-out infinite;
}
/* The two share a bottom edge, so the toast steps over the nudge rather than
   onto it. `has-nudge` is set on <body> by renderNow(). */
body.has-nudge .toast { bottom: calc(80px + env(safe-area-inset-bottom)); }
@keyframes update-in {
  from { opacity: 0; transform: translateX(-50%) translateY(8px); }
  to   { opacity: 1; transform: translateX(-50%) translateY(0); }
}
@keyframes update-beat {
  0%   { box-shadow: 0 0 0 0 rgba(52, 211, 153, 0.45); }
  70%  { box-shadow: 0 0 0 7px rgba(52, 211, 153, 0); }
  100% { box-shadow: 0 0 0 0 rgba(52, 211, 153, 0); }
}

/* ── Written feedback ────────────────────────────────────────────────── */
/* The popout that edits a note. Rendered markdown uses the same class names as
   the notes in dylan-os (note-p, note-ul, note-task ...) so the two are
   recognizably the same thing; the colors and radii are this repo's. */

.note-scrim {
  position: fixed; inset: 0; z-index: 80;
  display: flex; align-items: center; justify-content: center; padding: 24px;
  background: rgba(0,0,0,0.62); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  animation: note-in .14s ease-out;
}
@keyframes note-in { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .note-scrim { animation: none; }
  .card-photo img { transition: none; }
  .update-pill, .update-pill-dot { animation: none; }
}
body.note-open { overflow: hidden; }

.note-sheet {
  width: 100%; max-width: 560px; max-height: min(78vh, 620px);
  display: flex; flex-direction: column;
  border-radius: 18px; overflow: hidden;
  border: 1px solid var(--line-hi); background: #131313;
  box-shadow: 0 24px 64px rgba(0,0,0,0.62);
}

.note-head {
  flex: none; display: flex; align-items: center; gap: 12px;
  padding: 13px 14px 13px 18px; border-bottom: 1px solid var(--line);
}
.note-title { min-width: 0; flex-grow: 1; display: flex; flex-direction: column; gap: 1px; }
.note-title b { font-size: 14px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.note-title span { font-family: var(--mono); font-size: 11px; color: var(--faint); }

/* Read / Write is an explicit toggle, exactly as it is in the dylan-os
   Notepad, and for the same reason: in a pane you might want to scroll,
   tap-to-edit means tapping to scroll drops you into a textarea. Clicking a
   rendered LINE still opens the editor there -- that is a deliberate gesture,
   scrolling is not. */
.note-mode {
  flex: none; display: flex; gap: 2px; padding: 2px;
  border-radius: 999px; background: rgba(255,255,255,0.06);
}
.note-mode button {
  padding: 5px 12px; border: 0; border-radius: 999px; background: transparent;
  color: var(--dim); font-size: 12px; font-weight: 500;
}
.note-mode button.on { background: rgba(255,255,255,0.12); color: var(--fg); font-weight: 600; }

.note-x {
  flex: none; width: 32px; height: 32px; border: 0; border-radius: 999px;
  display: flex; align-items: center; justify-content: center;
  background: transparent; color: var(--dim);
}
.note-x:hover { background: var(--card-hi); color: var(--fg); }

/* Roomy enough to write in, and never taller than the screen. The floor stops
   Write mode opening as a five-line slot on a home nobody has said anything
   about yet -- but it has to be a floor the viewport can actually afford. A
   flex child's min-height is a hard minimum that beats the sheet's max-height,
   so a flat 300px overflowed the whole footer off a landscape phone. Tying it
   to the viewport keeps the roomy default and shrinks out of the way. */
.note-body { flex-grow: 1; min-height: min(300px, 40vh); display: flex; overflow: hidden; }

.note-textarea {
  flex-grow: 1; width: 100%; padding: 16px 18px; border: 0; outline: none; resize: none;
  background: transparent; color: var(--fg);
  font-family: var(--sans); font-size: 15px; line-height: 1.55;
}
.note-textarea::placeholder { color: var(--missing); }

.note-render { flex-grow: 1; padding: 14px 18px 18px; overflow-y: auto; font-size: 15px; line-height: 1.55; }
.note-empty {
  flex-grow: 1; border: 0; background: transparent; text-align: left;
  padding: 16px 18px; color: var(--missing); font-size: 15px;
}
.note-empty:hover { color: var(--dim); }

.note-realtor {
  flex: none; display: flex; flex-direction: column; gap: 4px;
  padding: 10px 18px 11px; border-top: 1px solid var(--line);
}
.note-realtor label { font-size: 11px; font-weight: 600; color: var(--dim); }
.note-realtor input {
  width: 100%; padding: 7px 10px; border-radius: 8px;
  border: 1px solid var(--line); background: rgba(255,255,255,0.04);
  color: var(--fg); font-size: 13px;
}
.note-realtor input:focus { outline: none; border-color: var(--line-hi); }
.note-realtor span { font-size: 11px; color: var(--missing); }

.note-foot {
  flex: none; display: flex; align-items: center; gap: 10px;
  padding: 11px 14px 11px 18px; border-top: 1px solid var(--line);
  background: rgba(255,255,255,0.014);
}
.note-hint { font-family: var(--mono); font-size: 10.5px; color: var(--missing); }
.note-status { font-size: 11px; color: var(--faint); min-width: 52px; text-align: right; }

/* Rendered markdown. Deliberately tight: a note is a few lines about a house,
   not a document, so headings step down gently and blocks sit close. */
.note-render > :first-child { margin-top: 0; }
.note-p { margin: 0 0 9px; }
.note-h { margin: 15px 0 6px; font-weight: 600; letter-spacing: -0.01em; }
.note-h1 { font-size: 17px; }
.note-h2 { font-size: 15px; }
.note-h3 { font-size: 14px; color: var(--dim); }
.note-ul, .note-ol { margin: 0 0 9px; padding-left: 20px; }
.note-ul { list-style: disc; }
.note-ol { list-style: decimal; }
.note-ul li, .note-ol li { margin: 2px 0; }
.note-ul li::marker, .note-ol li::marker { color: var(--em); }
.note-render code.note-code {
  font-family: var(--mono); font-size: 12.5px; padding: 1px 5px; border-radius: 5px;
  background: rgba(255,255,255,0.07);
}
.note-render del { color: var(--missing); }

/* A checkbox item drops the bullet: the tick is the marker. */
.note-ul li.note-task { list-style: none; margin-left: -20px; display: flex; align-items: flex-start; gap: 9px; }
.note-task.done .note-task-text { color: var(--missing); text-decoration: line-through; }
.note-check {
  flex: none; margin-top: 2px; width: 19px; height: 19px; border-radius: 6px;
  display: flex; align-items: center; justify-content: center;
  border: 1.5px solid var(--line-hi); background: transparent; color: var(--em-ink);
  transition: background .12s ease, border-color .12s ease;
}
.note-check:hover { border-color: var(--em); }
.note-check.on { background: var(--em); border-color: var(--em); }
.note-task-text { min-width: 0; }

/* ── Responsive. All media queries live here, at the end, on purpose. ── */

/* Desktop and tablet: the app is one window tall and never scrolls as a page;
   each region scrolls inside itself instead. */
@media (min-width: 1001px) {
  #app { height: 100vh; height: 100dvh; overflow: hidden; }
  /* The scroller, and only here. Below this width the page itself scrolls, and
     an inner overflow container with no height cap never scrolls at all, so the
     page would scroll while the container pretended to. */
  .board-scroll { overflow-y: auto; overscroll-behavior: contain; }
  .hop, .rv { overflow-y: auto; overscroll-behavior: contain; }
}

/* Under a thousand pixels the rail stops being a rail: it becomes a block of
   filters above the board, with the neighborhood list kept short enough that
   the homes are still on screen under it. */
@media (max-width: 1000px) {
  .board { flex-direction: column; }
  .rail {
    width: auto; border-right: 0; border-bottom: 1px solid var(--line);
    padding: 10px 20px;
  }
  /* Folded by default: two filter groups above the board is most of a phone
     screen spent before the first home. The count says whether anything is on
     while it is shut. */
  .rail-toggle {
    display: inline-flex; align-items: center; gap: 8px; align-self: flex-start;
    min-height: 40px; padding: 0 4px; border: 0; background: none;
    color: var(--dim); font-size: 13px; font-weight: 500;
  }
  .rail-toggle .n {
    font-family: var(--mono); font-size: 11px; font-weight: 600;
    padding: 1px 7px; border-radius: 999px; background: var(--em); color: var(--em-ink);
  }
  .rail-top { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
  .switch.in-head { display: none; }
  .rail-body { display: none; padding: 6px 0 10px; }
  .rail.open .rail-body { display: flex; }
  .hood-list { max-height: 132px; }
}

/* Narrow: layout only. Everything about the size of a tap target moved to the
   pointer:coarse block below -- see the note there for why width was the wrong
   question to ask. */
@media (max-width: 720px) {
  /* 44px, not 56 (ADR 0019). The status bar strip above it is now the same
     color and reads as the top of the bar, so the bar itself no longer needs
     the extra height to look like a bar. A floor, not a height: at 320px, or
     with a three-digit count at 375, the tally wraps to two lines, and the bar
     grows around it instead of letting the second line spill out. */
  :root { --topbar-h: 44px; }
  /* max() and not a flat 14px: a flat value here would win over the base rule
     and throw away the safe-area inset, putting the brand under the notch in
     landscape. */
  .topbar {
    height: auto;
    min-height: calc(var(--topbar-h) + env(safe-area-inset-top));
    padding-top: max(4px, env(safe-area-inset-top));
    padding-bottom: 4px;
    gap: 12px;
    padding-left: max(14px, env(safe-area-inset-left));
    padding-right: max(14px, env(safe-area-inset-right));
  }
  .topbar.bare { display: flex; }
  .topbar.bare:empty { border-bottom-color: transparent; }
  .topbar.bare + .login { min-height: 0; flex-grow: 1; }

  /* The dock (ADR 0028): the tabs on the bottom edge, where a thumb is. The
     bar keeps the brand and Sign out, which is why the brand's name is back
     on a phone: the tally that crowded it out has gone. Opaque, and the home
     indicator's inset is inside it, so nothing scrolls visibly under it. */
  .tabs.in-top { display: none; }
  .tabs.in-dock {
    display: flex; gap: 0; position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
    padding: 0 env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
    border-top: 1px solid var(--line); background: var(--bg);
  }
  .tabs.in-dock .tab {
    position: relative; flex: 1 1 0; min-width: 0; flex-direction: column; justify-content: center; gap: 3px;
    height: var(--dock-h); padding: 0; border-radius: 0; background: transparent;
    font-size: 11px; font-weight: 500;
  }
  .tabs.in-dock .tab.on { background: transparent; color: var(--em); font-weight: 600; }
  .tabs.in-dock .tab-n { position: absolute; top: 5px; left: calc(50% + 6px); }
  body.has-dock #app { padding-bottom: calc(var(--dock-h) + env(safe-area-inset-bottom)); }
  body.has-dock .update-pill { bottom: calc(var(--dock-h) + 12px + env(safe-area-inset-bottom)); }
  body.has-dock .toast { bottom: calc(var(--dock-h) + 12px + env(safe-area-inset-bottom)); }
  body.has-dock.has-nudge .toast { bottom: calc(var(--dock-h) + 70px + env(safe-area-inset-bottom)); }

  .hop { padding: 16px 14px 28px; }
  .hop-in { gap: 18px; }
  .hop-cols, .hop-main { gap: 24px; }
  /* The picks scroll sideways on a phone, so the second one shows its edge
     and says there is more, instead of stacking two tall cards. */
  .picks {
    display: flex; overflow-x: auto; scroll-snap-type: x mandatory;
    margin: 0 -14px; padding: 0 14px 4px; scroll-padding: 0 14px;
  }
  .pick { flex: none; width: 250px; scroll-snap-align: start; }

  /* The review fills the width, and its bars run edge to edge. */
  .rv-in { padding: 2px 14px 0; }
  .rv-acts, .rv-why { margin-left: -14px; margin-right: -14px; }
  .rv-why { padding-left: 14px; padding-right: 14px; }
  .rv-home { border-radius: 12px; }
  .rv-done { margin-top: 6vh; }

  /* The detail sheet takes the whole screen, opaque from its top edge, so the
     status bar strip above it is the sheet's own #0a0a0a (ADR 0019). */
  .sheet-scrim { background: var(--bg); }
  .sheet { width: 100%; border-left: 0; background: var(--bg); }
  /* The one control in the bar. 13px, like the rail's rows: 12px was the
     desktop link size, small for the only thing a thumb aims at up here. */
  .topbar .linkbtn { font-size: 13px; flex: none; white-space: nowrap; }
  .rail { padding: 12px 14px; }
  .results-head { padding: 10px 14px; }
  .board-scroll { padding: 4px 14px 22px; }
  .cards { grid-template-columns: 1fr; }
  .search { width: 100%; max-width: none; }

  /* The popout becomes a sheet off the bottom edge, iOS-style: full width,
     rounded only at the top, and clear of the home indicator.

     The scrim covers the top bar, and the status bar strip does not follow it:
     WebKit keeps the strip's existing color under a viewport-sized dimming
     layer (ADR 0019). A dimmed, blurred bar under a #0a0a0a strip is a seam.
     So on a phone the scrim is #0a0a0a over the bar's height (--bar-h, the
     bar as measured by openNote), the strip and that band read as one
     surface, and it dims the board below without a blur, because a
     backdrop-filter at the top edge is what WebKit refuses to color. */
  .note-scrim {
    align-items: flex-end; padding: 0;
    background-image: linear-gradient(var(--bg) 0 var(--bar-h, calc(var(--topbar-h) + env(safe-area-inset-top))), transparent 0);
    -webkit-backdrop-filter: none; backdrop-filter: none;
  }
  .note-sheet {
    max-width: none; max-height: 88vh;
    border-radius: 18px 18px 0 0; border-bottom: 0;
    padding-bottom: env(safe-area-inset-bottom);
  }
  .note-head { padding: 12px 10px 12px 16px; }
  .note-hint { display: none; }
}

/* ── Touch. Asks about the pointer, not the width. ────────────────────── */
/*
 * All of this used to live in the max-width:720px block, which quietly meant
 * "phone in portrait". An iPhone 13 in landscape is 844px wide and a tablet is
 * 768px, so turning the phone sideways handed back 30px chips, 34px rows, 38px
 * buttons and a 13px search box that makes iOS zoom the page on focus. The
 * question was never how wide the screen is; it is whether the thing pointing
 * at it is a finger. pointer:coarse asks that directly.
 *
 * Last block in the file on purpose: it has to win over both width blocks.
 *
 * 44px is Apple's minimum and Android's is 48; everything a thumb lands on
 * clears 44 here.
 */
@media (pointer: coarse) {
  .act { height: 48px; }
  .card-acts.second .act { height: 44px; }
  .chip { min-height: 44px; }
  .switch { min-height: 44px; }
  .state-row { min-height: 44px; }
  .card-moved { min-height: 44px; }
  .card-note { min-height: 44px; padding: 12px; }
  .rail-toggle { min-height: 44px; }
  .btn-ghost { min-height: 44px; }
  .search { height: 44px; }
  .hood-q { height: 44px; }
  /* The clear affordance was a 22px circle -- a target half the size of the
     finger aiming at it, sitting right beside the text it would erase. */
  .search-x { width: 44px; height: 44px; font-size: 18px; }
  /* The tabs across the bar, on a tablet or a phone held sideways (the dock's
     are taller already). */
  .tabs.in-top .tab { height: 44px; }
  .reason { min-height: 44px; }
  /* The Hopper page's text links get a real 44px box, not the ::after hit
     area the other links use: the picks row is drawn after "All N for sale"
     and took the taps meant for the overflow above it. */
  .hop-sec-h { align-items: center; }
  .hop-sec-h .linkbtn, .hop-link { min-height: 44px; }
  .hop-sec-h .linkbtn::after, .hop-link::after { content: none; }

  /* 16px or iOS zooms the whole page the moment the field takes focus, and
     leaves the reader scrolled sideways in a layout that no longer fits. */
  .search input, .hood-q, .note-textarea { font-size: 16px; }

  /* The note popout. Sized here rather than with the sheet layout below 720px,
     because in landscape it stays a centred dialog and was handing back a 26px
     Read/Write toggle and a 32px close button -- the two controls you reach for
     with a thumb while holding the phone sideways. */
  .note-x { width: 44px; height: 44px; }
  .note-mode button { min-height: 44px; padding: 0 14px; }

  /* No hover on a touch screen, so the photo arrows have to be visible
     without one, and as big as a thumb (ADR 0023). */
  .shot-nav { opacity: .9; width: 44px; height: 44px; }
  .viewer-x { width: 44px; height: 44px; }
  .viewer-thumb { width: 64px; height: 48px; }

  /* Two inline text links -- the address and the small "Show all"/"Tidy up"
     actions. Growing the box would push the lines they sit in apart and change
     a card's rhythm for a layout problem it does not have, so the hit area is
     grown instead of the ink: a transparent ::after centred on the link, 44px
     tall, full width. The text stays where the design put it. */
  .addr-link, .linkbtn { position: relative; }
  .addr-link::after, .linkbtn::after {
    content: ""; position: absolute; left: 0; right: 0;
    top: 50%; height: 44px; transform: translateY(-50%);
  }
  /* The rail's "clear N" sits inline in a label next to other text; a full
     -width overlay there would swallow taps meant for the group beside it. */
  .grp .lbl .linkbtn::after { left: -6px; right: -6px; }

  /* Press feedback. The file had 24 :hover rules and no :active, so on a phone
     a tap produced nothing at all until the data came back -- and Save and Pass
     are exactly the two presses that need to feel answered. The tap highlight
     is only suppressed because these replace it; without them iOS's grey box
     was the sole confirmation a press had registered. */
  button, a, [role="button"] { -webkit-tap-highlight-color: transparent; }
  .act:active { background: rgba(255,255,255,0.10); }
  .act.save:active { background: rgba(52,211,153,0.26); }
  .act.pass:active { background: rgba(255,101,104,0.26); }
  .act.save.on:active { background: #4ce0a8; }
  .act.pass.on:active { background: #ff7d80; }
  .act.wait:active { background: rgba(232,166,74,0.26); }
  .act.wait.on:active { background: #f0b866; }
  .act.pin:active { background: rgba(245,200,76,0.26); }
  .act.pin.on:active { background: #f8d673; }
  .switch:active { background: var(--card-hi); }
  .chip:active, .state-row:active, .card-note:active, .card-moved:active,
  .btn-ghost:active, .rail-toggle:active, .search-x:active { background: var(--card-hi); }
  .chip.on:active { background: #4ce0a8; }
  .linkbtn:active, .addr-link:active { color: var(--em); }
  .tab:active, .news-row:active, .stand-fold:active, .standing:active, .pick:active,
  .reason:active, .sheet-x:active, .rv-link:active, .rv-back:active { background: var(--card-hi); }
  .reason.on:active { background: rgba(255,101,104,0.26); }
  .card-open:active .card-more, .card-open:active .card-price { color: var(--em); }
  .q-card:active { background: rgba(52,211,153,0.18); }

  /* iOS keeps :hover applied after a tap until you tap somewhere else, so a
     hover style on a touch screen is a stuck style. These are the ones that
     actually show: the photo's zoom, the verdict buttons, the card border.
     Re-asserting the resting value costs nothing on a device that never
     hovers, and the rest of the file's hover rules are too subtle to notice. */
  .card-shot:hover img { transform: none; }
  .card:hover { border-color: var(--line); }
  .card.saved:hover { border-color: rgba(52,211,153,0.55); }
  .act.save:hover { background: rgba(52,211,153,0.07); }
  .act.pass:hover { background: rgba(255,101,104,0.07); }
  .act.save.on:hover { background: var(--em); }
  .act.pass.on:hover { background: var(--red); }
  .act.wait:hover { background: rgba(232,166,74,0.07); }
  .act.wait.on:hover { background: var(--warn); }
  .act.pin:hover { background: rgba(245,200,76,0.07); }
  .act.pin.on:hover { background: var(--pin); }
  .card.pinned:hover { border-color: rgba(245,200,76,0.75); }
  .switch:hover { border-color: rgba(255,255,255,0.12); color: var(--dim); }
  .switch.on:hover { border-color: rgba(52,211,153,0.55); color: var(--fg); }
  .chip:hover { border-color: rgba(255,255,255,0.12); color: var(--dim); }
  .chip.on:hover { border-color: transparent; color: var(--em-ink); }
  .tab:hover { background: transparent; color: var(--dim); }
  .tabs.in-top .tab.on:hover { background: rgba(52,211,153,0.12); color: var(--fg); }
  .tabs.in-dock .tab.on:hover { color: var(--em); }
  .news-row:hover { background: transparent; }
  .q-card:hover { background: rgba(52,211,153,0.07); }
  .q-card.done:hover { background: var(--card); }
  .pick:hover { border-color: rgba(52,211,153,0.55); }
  .pick.pinned:hover { border-color: rgba(245,200,76,0.75); }
  .stand-fold:hover, .standing:hover { border-color: var(--line); }
  .reason:hover { border-color: rgba(255,255,255,0.14); color: #d4d4d4; }
  .reason.on:hover { border-color: rgba(255,101,104,0.60); color: var(--fg); }
  .card-open:hover .card-more { color: var(--faint); }
  .sheet-x:hover { background: transparent; color: var(--dim); }
  .rv-link:hover, .rv-back:hover { color: var(--dim); }
  .rv-next:hover { background: var(--fg); }
}

/* ── A phone held sideways: ~390px of height to spend. ────────────────── */
/*
 * The bar, the filter row and the search line were taking half of it before
 * the first card, so landscape showed a sliver of one home. This is spacing
 * only -- every tap target above keeps its 44px, because the thumb does not
 * get smaller when the phone turns.
 */
@media (max-height: 520px) and (pointer: coarse) {
  :root { --topbar-h: 46px; }
  /* Sideways there are ~390px of height, and the nudge is not the thing worth
     spending 44 of them on. It still clears the 38px the chips get here. */
  .update-pill { bottom: calc(10px + env(safe-area-inset-bottom)); min-height: 38px; }
  body.has-nudge .toast { bottom: calc(56px + env(safe-area-inset-bottom)); }
  .rail { padding-top: 2px; padding-bottom: 2px; }
  .results-head { padding-top: 6px; padding-bottom: 6px; }
  .board-scroll { padding-top: 2px; }
}
