/* ==========================================================================
   AppMates design system

   Two decisions shape everything below.

   Scores are drawn as rings, not bars. Six signals stacked as horizontal bars
   read as a progress list — as though the user were waiting for something to
   finish. A ring reads as an instrument: a value on a dial, comparable at a
   glance across a table without alignment.

   Density is deliberate. This tool answers questions by comparison — which
   storefront, which competitor, which term — and comparison needs rows close
   enough to scan in one movement of the eye.
   ========================================================================== */

:root {
  color-scheme: light dark;

  --bg: #f7f7fa;
  --surface: #ffffff;
  --surface-2: #f2f2f6;
  --surface-3: #eaeaf0;
  --border: #e4e4ec;
  --border-strong: #d3d3de;

  --text: #17171c;
  --text-2: #55555f;
  --muted: #6e6e79;

  /* Indigo rather than the orange of the tools this borrows its shape from —
     the layout is a good idea worth learning from, the identity is not. */
  --accent: #5457d6;
  --accent-soft: #ececfd;
  --accent-strong: #4245c4;
  --on-accent: #ffffff;

  --ok: #15803d;
  --ok-soft: #e8f7ec;
  --warn: #a35a07;
  --warn-soft: #fdf3e2;
  --bad: #be123c;
  --bad-soft: #fdecef;

  /* Apple's own system blue — distinct from `--accent`'s indigo, used only
     for the iOS platform badge so "iOS" reads as a store colour, not the
     site's brand colour. */
  --ios: #007aff;
  --ios-soft: #e8f2ff;

  --r-sm: 7px;
  --r-md: 10px;
  --r-lg: 14px;
  --r-pill: 100px;

  --sidebar-w: 246px;
  --shadow-sm: 0 1px 2px rgb(20 20 30 / 6%);
  --shadow-md: 0 4px 16px rgb(20 20 30 / 8%);
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0b0b0f;
    --surface: #151519;
    --surface-2: #1c1c22;
    --surface-3: #24242c;
    --border: #26262f;
    --border-strong: #34343f;

    --text: #f0f0f4;
    --text-2: #b4b4c0;
    --muted: #83838f;

    --accent: #8b8ef5;
    --accent-soft: #21213a;
    --accent-strong: #a0a3ff;
    --on-accent: #14142a;

    --ok: #4ade80;
    --ok-soft: #10241a;
    --warn: #fbbf24;
    --warn-soft: #2a2010;
    --bad: #fb7185;
    --bad-soft: #2b1219;

    --ios: #0a84ff;
    --ios-soft: #10233d;

    --shadow-sm: 0 1px 2px rgb(0 0 0 / 40%);
    --shadow-md: 0 4px 18px rgb(0 0 0 / 45%);
  }
}

* { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 15px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, system-ui, sans-serif;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

code, .mono, .num {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
  font-variant-numeric: tabular-nums;
}

.muted { color: var(--muted); }
.hidden { display: none !important; }

/* ============================ shell ============================ */

.app {
  display: grid;
  grid-template-columns: var(--sidebar-w) minmax(0, 1fr);
  min-height: 100vh;
}

.sidebar {
  background: var(--surface);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  padding: .85rem;
  gap: .3rem;
  position: sticky;
  top: var(--tape-h);
  height: calc(100vh - (var(--tape-h) * 2));
  overflow-y: auto;
}

.brand {
  display: flex;
  align-items: center;
  gap: .65rem;
  padding: .6rem .65rem;
  border-radius: var(--r-md);
  background: var(--surface-2);
  border: 1px solid var(--border);
  margin-bottom: .5rem;
}
.brand-mark {
  width: 34px; height: 34px;
  border-radius: 9px;
  background: linear-gradient(140deg, var(--accent), var(--accent-strong));
  color: var(--on-accent);
  display: grid; place-items: center;
  font-size: 1rem;
  flex: 0 0 auto;
  box-shadow: var(--shadow-sm);
}
.brand-name { font-weight: 650; font-size: .92rem; letter-spacing: -0.01em; line-height: 1.2; }
.brand-sub { color: var(--muted); font-size: .74rem; }

.nav-section {
  color: var(--muted);
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  padding: .8rem .65rem .35rem;
}

.nav-item {
  display: flex;
  align-items: center;
  gap: .6rem;
  padding: .48rem .65rem;
  border-radius: var(--r-sm);
  color: var(--text-2);
  font-size: .88rem;
  font-weight: 500;
  cursor: pointer;
  transition: background .12s, color .12s;
}
.nav-item:hover { background: var(--surface-2); color: var(--text); text-decoration: none; }
.nav-item.active {
  background: var(--accent);
  color: var(--on-accent);
  font-weight: 600;
}
.nav-item.active .nav-icon { opacity: 1; }
.nav-icon { width: 17px; display: inline-flex; align-items: center; justify-content: center; opacity: .75; flex: 0 0 auto; }
.nav-icon svg { width: 16px; height: 16px; }

.sidebar-foot { margin-top: auto; margin-bottom: .5rem; padding-top: .5rem; display: grid; gap: .4rem; }

/* Same box as the app card up top (.brand) — same background, border and
   hover state — just a single line with an arrow instead of an icon +
   two-line name/subtitle, and no bottom margin since the footer's own
   grid gap already spaces it from the legal links below. */
.brand.sidebar-back { margin-bottom: 0; font-size: .84rem; font-weight: 600; }
.sidebar-back-arrow { font-size: 1rem; line-height: 1; }

/* No width set here on purpose: `.sidebar-back` right above it has none
   either, and stretches to the grid column's full width because that's
   `.sidebar-foot`'s (display: grid) default `justify-items: stretch` —
   matching that width means matching the same absence of a rule, not
   guessing a pixel value that happens to look similar today. */
/* On top of .sidebar-foot's own .4rem grid gap — a little more breathing
   room specifically above the badge, not between every item in the
   footer. */
.sidebar-badge { display: block; margin-top: .3rem; }
.sidebar-badge img { display: block; width: 100%; height: auto; }

.sidebar-legal-links {
  display: flex;
  gap: .7rem;
  padding: 0 .1rem;
  font-size: .72rem;
}
.sidebar-legal-links a { color: var(--muted); }
.sidebar-legal-links a:hover { color: var(--text); }

/* ============================ main ============================ */

/* `padding-top` here, not just `.topbar`'s own `position:sticky; top:
   var(--tape-h)` — sticky reserves flow space at its element's *static*
   position (as if it were `position:static`), then only visually shifts it
   at paint time; later siblings lay out from that unshifted static spot,
   not from where sticky actually paints it. With the fixed tape occupying
   no flow space of its own, that static position was y:0, so `.content`
   started 42px too high and rendered underneath the topbar's own painted
   (shifted) box — exactly `var(--tape-h)` of every view's first line lost
   under it. Padding `.main` by that same amount makes the topbar's static
   position already equal to its stuck one, so sticky has nothing left to
   shift — the two positions agree and every later sibling lines up
   correctly. Zeroed back out on the mobile stack below, where `.app`'s own
   padding-top already clears the tape once for the whole column. */
.main { min-width: 0; display: flex; flex-direction: column; padding-top: var(--tape-h); }

.topbar {
  display: flex;
  align-items: center;
  gap: .8rem;
  padding: .85rem 1.5rem;
  border-bottom: 1px solid var(--border);
  background: color-mix(in srgb, var(--bg) 85%, transparent);
  backdrop-filter: saturate(180%) blur(12px);
  position: sticky; top: var(--tape-h); z-index: 20;
  min-height: 60px;
}
.topbar h1 { font-size: 1.02rem; font-weight: 650; margin: 0; letter-spacing: -0.015em; }
.topbar .sub { color: var(--muted); font-size: .8rem; }
.topbar-actions { margin-left: auto; display: flex; gap: .5rem; align-items: center; }

/* ============================ sponsor tape ============================
   Compact horizontal sponsor strip — shared look between the dashboard's
   `.dash-tape` (styles.css, this file) and the landing page's
   `.landing-tape` (landing.css); each page decides its own breakpoint and
   position since one is a fixed-height app shell and the other a normal
   scrolling page.

   Two identical `.tape-half`s scrolled by `translate(-50%)` is the
   standard seamless-marquee trick: because the halves are identical, the
   instant the first has scrolled fully out of view the second sits
   exactly where the first started, so the loop point is invisible. */
.tape { display: none; height: 42px; overflow: hidden; background: var(--surface); }
.tape:hover .tape-track, .tape:focus-within .tape-track { animation-play-state: paused; }

.tape-track { display: flex; width: max-content; animation: tape-slide 27s linear infinite; }
.tape-track--static { animation: none; }
.tape-half { display: flex; }

.tape-item {
  display: flex; align-items: center; gap: .5rem;
  height: 42px; padding: 0 1.1rem;
  font-size: .8rem; font-weight: 600; color: var(--text-2);
  white-space: nowrap;
}
.tape-item:hover { color: var(--text); text-decoration: none; background: var(--surface-2); }
.tape-item--open { color: var(--accent); font-weight: 500; font-style: italic; }

.tape-icon { width: 18px; height: 18px; border-radius: 5px; display: block; flex: 0 0 auto; object-fit: cover; }
.tape-icon--blank { background: var(--border); }

@keyframes tape-slide { from { transform: translateX(0); } to { transform: translateX(-50%); } }

@media (prefers-reduced-motion: reduce) {
  .tape-track { animation: none; }
  .tape { overflow-x: auto; }
}

/* Dashboard tape: always on, full viewport width, independent of the
   sidebar and of viewport size — `position: fixed` with `left: 0` rather
   than `left: var(--sidebar-w)`, so it runs edge to edge over the sidebar
   too instead of stopping at the content column. `--tape-h` is the single
   source every other rule that has to make room for it reads from: the
   sidebar's own top offset and height, the topbar's sticky offset, the
   content's bottom padding, and the inbox view's fixed-height calc further
   down. Changing the tape's height only ever means changing this one
   value. */
:root { --tape-h: 42px; }

.dash-tape {
  display: block;
  position: fixed; left: 0; right: 0; z-index: 25;
}
.dash-tape.tape-top { top: 0; border-bottom: 1px solid var(--border); }
.dash-tape.tape-bottom { bottom: 0; border-top: 1px solid var(--border); }


/* ============================ favorites tray ============================ */

.fav-tray { position: relative; }
.fav-tray-toggle {
  display: flex; align-items: center;
  border: 1px solid var(--border); background: var(--surface);
  border-radius: var(--r-pill); padding: .28rem .5rem;
  cursor: pointer;
}
.fav-tray-toggle:hover { border-color: var(--border-strong); background: var(--surface-2); }
.fav-tray-toggle .icon-stack img { width: 22px; height: 22px; }

.fav-dropdown {
  position: absolute; top: calc(100% + .5rem); right: 0;
  width: 260px; max-height: 340px; overflow-y: auto;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-md); box-shadow: var(--shadow-md);
  z-index: 30; padding: .35rem;
}
.fav-dropdown-head {
  font-size: .72rem; font-weight: 650; color: var(--muted);
  text-transform: uppercase; letter-spacing: .04em;
  padding: .4rem .55rem .3rem;
}
.fav-item {
  display: flex; align-items: center; gap: .55rem;
  padding: .4rem .5rem; border-radius: var(--r-sm);
  color: var(--text);
}
.fav-item:hover { background: var(--surface-2); text-decoration: none; }
.fav-item-body { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.fav-item-body strong {
  font-size: .85rem; font-weight: 550;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.fav-item-body .muted { font-size: .72rem; }
.fav-remove {
  border: none; background: none; color: var(--muted); cursor: pointer;
  font-size: 1rem; line-height: 1; padding: .15rem .35rem; border-radius: var(--r-sm);
  flex: 0 0 auto;
}
.fav-remove:hover { color: var(--bad); background: var(--bad-soft); }

/* Favorite-app shortcuts on Rank: one click fills the id field instead of
   typing it. Grey outline at rest, same as any hoverable control; only the
   currently-selected one turns purple, matching the primary "Check" button. */
.fav-pick-row { display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; }
.fav-pick {
  padding: 2px; border-radius: 9px;
  border: 1.5px solid var(--border-strong);
  background: none; cursor: pointer; line-height: 0;
  transition: border-color .12s, background .12s;
}
.fav-pick:hover { border-color: var(--muted); background: var(--surface-2); }
.fav-pick.active { border-color: var(--accent); background: var(--accent-soft); }
.fav-pick.active:hover { border-color: var(--accent); }
.fav-pick .app-icon { border: none; display: block; }

.content { padding: 1.5rem; padding-bottom: calc(1.5rem + var(--tape-h)); max-width: 1400px; width: 100%; }
.view { display: none; }
.view.active { display: block; animation: fade .18s ease-out; }
@keyframes fade { from { opacity: 0; transform: translateY(3px); } to { opacity: 1; transform: none; } }

.lead { font-size: .89rem; color: var(--text-2); max-width: 74ch; margin: 0 0 1rem; }
.lead strong { color: var(--text); font-weight: 600; }

h2 { font-size: .95rem; font-weight: 650; margin: 1.75rem 0 .7rem; letter-spacing: -0.01em; }
h3 { font-size: .86rem; font-weight: 650; margin: 1.5rem 0 .55rem; color: var(--text-2); }

/* ============================ controls ============================ */

.toolbar {
  display: flex; flex-wrap: wrap; gap: .6rem; align-items: flex-end;
  padding: .9rem 1rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  margin-bottom: 1.25rem;
}
.field { display: flex; flex-direction: column; gap: .28rem; min-width: 0; }
.field > label { color: var(--muted); font-size: .72rem; font-weight: 600; letter-spacing: .02em; }

.platform-toggle { display: flex; gap: 1.1rem; padding-top: .15rem; }
.platform-toggle label {
  display: flex; align-items: center; gap: .4rem;
  font-size: .87rem; color: var(--text); cursor: pointer;
}
.platform-toggle input[type=checkbox] { width: 1rem; height: 1rem; accent-color: var(--accent); cursor: pointer; }

input[type=text], input[type=email], input[type=number], input[type=color], textarea, select {
  font: inherit;
  font-size: .87rem;
  padding: .45rem .65rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  background: var(--bg);
  color: var(--text);
  transition: border-color .12s, box-shadow .12s;
}
input:hover, textarea:hover, select:hover { border-color: var(--muted); }
input:focus, textarea:focus, select:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);
}
input::placeholder, textarea::placeholder { color: var(--muted); }
textarea { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .82rem; resize: vertical; }
input[type=color] { padding: 2px; width: 2.5rem; height: 2.05rem; cursor: pointer; }

button {
  font: inherit; font-size: .86rem; font-weight: 600;
  padding: .48rem .85rem;
  border-radius: var(--r-sm);
  border: 1px solid var(--border-strong);
  background: var(--surface);
  color: var(--text);
  cursor: pointer;
  transition: background .12s, border-color .12s, transform .06s, filter .12s, box-shadow .12s;
  white-space: nowrap;
}
button:hover:not(:disabled) { background: var(--surface-2); border-color: var(--muted); }
button:active:not(:disabled) { transform: translateY(1px); }
button.primary {
  background: linear-gradient(140deg, var(--accent), var(--accent-strong));
  color: var(--on-accent);
  border-color: transparent;
  box-shadow: var(--shadow-sm);
  /* A gradient can't cross-fade into the flat hover colour below — trying to
     ease that swap is exactly what caused the flicker. Dropping `background`
     from the transitioned properties here makes the swap instant instead of
     attempted-and-broken, which reads as clean rather than as a glitch. */
  transition: border-color .12s, transform .06s;
}
button.primary:hover:not(:disabled) { background: var(--accent); }
button:disabled { opacity: .5; cursor: not-allowed; }
button.ghost { border-color: transparent; background: none; color: var(--accent); padding: .3rem .45rem; }
button.ghost:hover:not(:disabled) { background: var(--accent-soft); }
button.ghost.danger { color: var(--bad); }
button.ghost.danger:hover:not(:disabled) { background: color-mix(in srgb, var(--bad) 12%, transparent); }

.form-grid {
  display: grid; grid-template-columns: 11.5rem minmax(0, 1fr);
  gap: .65rem 1rem; align-items: center;
  padding: 1.1rem 1.25rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  max-width: 58rem;
  margin-bottom: 1.25rem;
}
.form-grid > label { color: var(--text-2); font-size: .84rem; font-weight: 500; }
.form-grid input, .form-grid textarea { width: 100%; }
@media (max-width: 720px) {
  .form-grid { grid-template-columns: 1fr; gap: .3rem; }
  .form-grid > label { margin-top: .6rem; }
}

/* Prepare's "Connect your real App Store Connect data" — the four-step
   callout and the paste-box/import callout side by side and matched in
   style (both .callout), so the two read as one paired step rather than
   two differently-boxed things stacked on top of each other. */
.asc-connect {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.5rem;
  align-items: stretch;
  margin: 1.25rem 0;
}
/* stretch (not start): the right column is shorter than the four-step list
   on the left, so without this its box falls short of the left one's
   height instead of matching it. */
.asc-connect .callout { max-width: none; margin: 0; display: flex; flex-direction: column; }
.asc-connect .callout > .toolbar { margin-top: auto; padding-top: 1rem; }
@media (max-width: 860px) {
  .asc-connect { grid-template-columns: 1fr; }
}

.prompt-copy-row {
  display: flex; align-items: center; gap: .5rem;
  margin-top: .4rem; padding: .5rem .6rem;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
}
.prompt-copy-row code {
  flex: 1; min-width: 0;
  font-size: .74rem; color: var(--muted); line-height: 1.4;
  display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.copy-btn {
  flex: 0 0 auto;
  width: 2rem; height: 2rem;
  display: flex; align-items: center; justify-content: center;
  font-size: 1rem; line-height: 1;
  color: var(--text-2);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  cursor: pointer;
  transition: color .12s, border-color .12s;
}
.copy-btn:hover { color: var(--text); border-color: var(--border-strong); }
.copy-btn.copied { color: var(--ok); border-color: var(--ok); }

/* ============================ rings ============================ */

/* inline-block (not block): lets `text-align` on an ancestor table cell
   position the ring — a flex parent (panel-head) still blockifies it the
   same as `block` would, so that layout is unaffected. */
.ring { display: inline-block; flex: 0 0 auto; }
.ring .track { stroke: var(--surface-3); }
.ring .fill { transition: stroke-dashoffset .5s cubic-bezier(.4, 0, .2, 1); }
.ring .label { font-size: 11px; font-weight: 700; fill: var(--text); font-family: inherit; }
.ring.ok .fill { stroke: var(--ok); }
.ring.warn .fill { stroke: var(--warn); }
.ring.bad .fill { stroke: var(--bad); }
.ring-cell { display: flex; align-items: center; gap: .5rem; }

/* ============================ pills ============================ */

.pill {
  display: inline-flex; align-items: center; gap: .3rem;
  padding: .16rem .55rem;
  border-radius: var(--r-pill);
  font-size: .73rem;
  font-weight: 650;
  letter-spacing: .01em;
  white-space: nowrap;
  border: 1px solid transparent;
}
.pill.ok   { background: var(--ok-soft);   color: var(--ok);   border-color: color-mix(in srgb, var(--ok) 22%, transparent); }
.pill.warn { background: var(--warn-soft); color: var(--warn); border-color: color-mix(in srgb, var(--warn) 25%, transparent); }
.pill.bad  { background: var(--bad-soft);  color: var(--bad);  border-color: color-mix(in srgb, var(--bad) 22%, transparent); }
.pill.info { background: var(--accent-soft); color: var(--accent); border-color: color-mix(in srgb, var(--accent) 22%, transparent); }
.pill.neutral { background: var(--surface-2); color: var(--muted); border-color: var(--border); }

.delta { font-size: .75rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.delta.up { color: var(--ok); }
.delta.down { color: var(--bad); }
.delta.flat { color: var(--muted); }

/* ============================ tables ============================ */

.panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  overflow: hidden;
  margin-bottom: 1.1rem;
}
.panel-head {
  display: flex; align-items: center; gap: .7rem;
  padding: .75rem 1rem;
  border-bottom: 1px solid var(--border);
  font-size: .85rem; font-weight: 650;
}
.panel-head .sub { color: var(--muted); font-weight: 400; font-size: .8rem; }

.table-wrap { overflow-x: auto; }
table { border-collapse: collapse; width: 100%; font-size: .85rem; }
thead th {
  text-align: left;
  font-size: .69rem; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase;
  color: var(--muted);
  padding: .6rem .85rem;
  border-bottom: 1px solid var(--border);
  background: var(--surface-2);
  white-space: nowrap;
  position: sticky; top: 0;
}
tbody td {
  padding: .55rem .85rem;
  border-bottom: 1px solid var(--border);
  vertical-align: middle;
}
tbody tr:last-child td { border-bottom: none; }
tbody tr { transition: background .1s; }
tbody tr:hover { background: var(--surface-2); }
th.num, td.num { text-align: right; font-variant-numeric: tabular-nums; }
th.center, td.center { text-align: center; }
td.tight { padding-top: .35rem; padding-bottom: .35rem; }

/* ============================ app rows ============================ */

.app-cell { display: flex; align-items: center; gap: .6rem; min-width: 0; }
.app-icon {
  width: 30px; height: 30px; border-radius: 7px;
  border: 1px solid var(--border);
  background: var(--surface-2);
  flex: 0 0 auto;
}
.letter-avatar {
  display: flex; align-items: center; justify-content: center;
  font-size: .85rem; font-weight: 700;
  color: var(--accent);
  background: var(--accent-soft);
  border-color: color-mix(in srgb, var(--accent) 30%, transparent);
}
.app-name { font-weight: 550; line-height: 1.25; }
.app-seller { color: var(--muted); font-size: .76rem; }

.icon-stack { display: flex; }
.icon-stack img {
  width: 24px; height: 24px; border-radius: 6px;
  border: 1.5px solid var(--surface);
  background: var(--surface-2);
  margin-left: -6px;
}
.icon-stack img:first-child { margin-left: 0; }

.gallery { display: flex; gap: .5rem; overflow-x: auto; padding: .1rem .1rem .5rem; }
.gallery img {
  height: 180px; width: auto; border-radius: 9px;
  border: 1px solid var(--border);
  background: var(--surface-2);
  flex: 0 0 auto;
  transition: transform .15s, box-shadow .15s;
}
.gallery img:hover { transform: translateY(-3px); box-shadow: var(--shadow-md); }
.gallery-group { margin: .9rem 0 1.3rem; }
.gallery-title { font-size: .8rem; color: var(--text-2); margin-bottom: .4rem; font-weight: 550; }

/* Overview's own screenshot set: large and filling the row, unlike the small
   horizontal-scroll strip `.gallery` uses to compare many competitors at
   once. A fixed column count — one per screenshot, set inline as
   `--shot-count` — means a full set of up to 10 always shares the row rather
   than wrapping once there happen to be enough of them to outrun a fixed
   minimum size. Below `--wall-breakpoint`, that stops being viable (10 equal
   columns on a phone screen would be slivers), so the media query switches
   to a normal wrapping grid instead. */
.screenshot-wall {
  display: grid;
  grid-template-columns: repeat(var(--shot-count, 5), 1fr);
  gap: .7rem;
  padding: .1rem;
  margin-bottom: 1.0rem;
}
.screenshot-wall img {
  width: 100%; height: auto; display: block;
  border-radius: 12px;
  border: 1px solid var(--border);
  background: var(--surface-2);
  box-shadow: var(--shadow-sm);
  transition: transform .15s, box-shadow .15s;
}
.screenshot-wall img:hover { transform: translateY(-3px); box-shadow: var(--shadow-md); }

@media (max-width: 760px) {
  .screenshot-wall { grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); }
}

/* ============================ findings ============================ */

.findings { border-top: 1px solid var(--border); }
.finding {
  display: flex; gap: .7rem; align-items: flex-start;
  padding: .6rem .95rem;
  border-bottom: 1px solid var(--border);
}
.finding:last-child { border-bottom: none; }
.finding-mark {
  width: 18px; height: 18px; border-radius: 50%;
  display: grid; place-items: center;
  font-size: .68rem; font-weight: 800;
  flex: 0 0 auto; margin-top: .1rem;
}
.finding.error .finding-mark { background: var(--bad-soft); color: var(--bad); }
.finding.warning .finding-mark { background: var(--warn-soft); color: var(--warn); }
.finding.info .finding-mark { background: var(--accent-soft); color: var(--accent); }
.finding-body { min-width: 0; flex: 1; }
.finding-code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .7rem; font-weight: 700; letter-spacing: .02em;
  margin-right: .4rem;
}
.finding.error .finding-code { color: var(--bad); }
.finding.warning .finding-code { color: var(--warn); }
.finding.info .finding-code { color: var(--accent); }
.finding-msg { font-size: .85rem; }
.finding-hint { color: var(--muted); font-size: .8rem; margin-top: .15rem; }
.finding-cost {
  margin-left: auto; flex: 0 0 auto;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .72rem; color: var(--muted);
}

/* ============================ summary + callouts ============================ */

.summary {
  display: flex; align-items: center; gap: .7rem; flex-wrap: wrap;
  padding: .8rem 1.1rem;
  border-radius: var(--r-lg);
  border: 1px solid var(--border);
  border-left: 3px solid var(--muted);
  background: var(--surface);
  font-size: .87rem;
  margin-bottom: 1.1rem;
}
.summary.pass { border-left-color: var(--ok); }
.summary.warn { border-left-color: var(--warn); }
.summary.fail { border-left-color: var(--bad); }
.summary .verdict { font-weight: 700; }
.summary.pass .verdict { color: var(--ok); }
.summary.warn .verdict { color: var(--warn); }
.summary.fail .verdict { color: var(--bad); }

.callout {
  padding: .75rem 1rem;
  border-radius: var(--r-md);
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  font-size: .84rem;
  color: var(--text-2);
  margin: 1rem 0;
  max-width: 78ch;
}
.callout strong { color: var(--text); }
.note {
  font-size: .8rem; color: var(--muted);
  margin: .6rem 0; max-width: 78ch;
  padding-left: .75rem; border-left: 2px solid var(--border);
}
/* A link inline in a text block needs a non-color cue too — axe's
   link-in-text-block rule (WCAG 1.4.1): color alone isn't enough to tell
   it apart from the surrounding prose. */
.note a, .hero-sub a { text-decoration: underline; text-underline-offset: .15em; }

/* ============================ dropzone ============================ */

#drop {
  border: 1.5px dashed var(--border-strong);
  border-radius: var(--r-lg);
  background: var(--surface);
  padding: 2.4rem 1.5rem;
  text-align: center;
  cursor: pointer;
  transition: border-color .15s, background .15s;
}
#drop:hover, #drop:focus-visible { border-color: var(--accent); background: var(--surface-2); }
#drop.dragging { border-color: var(--accent); background: var(--accent-soft); }
#drop .drop-icon { font-size: 1.6rem; opacity: .5; display: block; margin-bottom: .5rem; }
#drop strong { display: block; font-size: .97rem; margin-bottom: .25rem; }
#drop span.hint { color: var(--muted); font-size: .82rem; }
input[type=file] { display: none; }

/* ============================ shot wall ============================
   The draggable thumbnail grid Screenshots shows once files are dropped —
   reordering uses native HTML5 drag-and-drop plus a small FLIP animation
   (see views/screenshots.js) so a moved thumbnail's neighbours slide into
   place instead of just jumping. */
.shot-wall {
  display: grid;
  grid-template-columns: repeat(var(--shot-count, 5), 1fr);
  gap: .5rem;
  margin: 1.1rem 0;
}
@media (max-width: 760px) {
  .shot-wall { grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); }
}
.shot-thumb {
  position: relative;
  border-radius: var(--r-md);
  overflow: hidden;
  border: 1px solid var(--border);
  background: var(--surface-2);
  cursor: grab;
  user-select: none;
  transition: transform .15s, box-shadow .15s, border-color .15s, opacity .15s;
}
.shot-thumb:active { cursor: grabbing; }
.shot-thumb:hover { transform: translateY(-3px); border-color: var(--border-strong); box-shadow: var(--shadow-md); }
.shot-thumb.dragging { opacity: .45; transform: scale(1.04); box-shadow: var(--shadow-md); z-index: 2; }
.shot-thumb img {
  display: block; width: 100%; height: auto; object-fit: contain;
  background: var(--surface-2);
  pointer-events: none; /* clicks/drags land on the thumbnail, not the image */
}
.shot-thumb .shot-index {
  position: absolute; top: .35rem; left: .35rem;
  width: 1.3rem; height: 1.3rem; padding: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: .7rem; font-weight: 700; color: #fff;
  background: rgba(0,0,0,.55); border-radius: 999px;
  pointer-events: none;
}
.shot-thumb .shot-remove {
  appearance: none; -webkit-appearance: none;
  position: absolute; top: .35rem; right: .35rem;
  width: 1.3rem; height: 1.3rem; padding: 0; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  color: #fff;
  background: rgba(120,120,128,.65); border: none; cursor: pointer;
  transition: background .12s, transform .12s, box-shadow .12s;
}
.shot-thumb .shot-remove:hover { background: var(--bad); transform: scale(1.1); box-shadow: var(--shadow-sm); }
.shot-thumb .shot-remove svg { width: .6rem; height: .6rem; display: block; }
/* status dot lives in the name row (not floated over the image) so it lines
   up with the filename it describes instead of guessing an offset. */
.shot-thumb .shot-name {
  display: flex; align-items: center; gap: .35rem;
  padding: .3rem .5rem;
  font-size: .68rem; color: var(--muted);
  background: var(--surface);
  border-top: 1px solid var(--border);
}
.shot-thumb .shot-name-text {
  flex: 1; min-width: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.shot-thumb .shot-status {
  flex: 0 0 auto;
  width: .5rem; height: .5rem; border-radius: 50%;
}
.shot-thumb .shot-status.ok { background: var(--ok); }
.shot-thumb .shot-status.warn { background: var(--warn); }
.shot-thumb .shot-status.fail { background: var(--bad); }

/* ============================ states ============================ */

.empty {
  text-align: center;
  padding: 3rem 1.5rem;
  color: var(--muted);
  background: var(--surface);
  border: 1px dashed var(--border-strong);
  border-radius: var(--r-lg);
}
.empty .empty-icon { font-size: 1.7rem; opacity: .45; display: block; margin-bottom: .55rem; }
.empty strong { display: block; color: var(--text-2); font-size: .92rem; margin-bottom: .25rem; font-weight: 600; }
.empty span { font-size: .83rem; }

.status { font-size: .84rem; color: var(--muted); margin: .8rem 0; display: flex; align-items: center; gap: .5rem; }
.status.error { color: var(--bad); }
.status.ok { color: var(--ok); }
.spinner {
  width: 13px; height: 13px; border-radius: 50%;
  border: 2px solid var(--border-strong);
  border-top-color: var(--accent);
  animation: spin .7s linear infinite;
  flex: 0 0 auto;
}
@keyframes spin { to { transform: rotate(360deg); } }

.skeleton-row {
  height: 40px;
  border-radius: var(--r-sm);
  background: linear-gradient(90deg, var(--surface-2) 25%, var(--surface-3) 37%, var(--surface-2) 63%);
  background-size: 400% 100%;
  animation: shimmer 1.4s ease-in-out infinite;
  margin-bottom: .4rem;
}
@keyframes shimmer { 0% { background-position: 100% 0; } 100% { background-position: 0 0; } }

/* ============================ budget meter ============================ */

.budget {
  display: flex; align-items: center; gap: .85rem; flex-wrap: wrap;
  padding: .8rem 1.1rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  margin-bottom: 1rem;
  font-size: .86rem;
}
.bar { flex: 1; min-width: 140px; height: 7px; border-radius: 4px; background: var(--surface-3); overflow: hidden; }
.bar > span { display: block; height: 100%; border-radius: 4px; transition: width .35s ease; }
.bar.ok > span { background: var(--ok); }
.bar.warn > span { background: var(--warn); }
.bar.over > span { background: var(--bad); }

.suggestion {
  padding: .85rem 1.1rem;
  border-radius: var(--r-lg);
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  margin-top: 1.1rem;
}
.suggestion code {
  display: block; margin: .4rem 0 .35rem;
  font-size: .84rem; word-break: break-all;
  padding: .5rem .65rem; border-radius: var(--r-sm);
  background: var(--surface-2);
}

/* ============================ responsive ============================ */

@media (max-width: 860px) {
  .app { grid-template-columns: 1fr; padding-top: var(--tape-h); }
  /* Already cleared once by `.app`'s own padding-top above — the stacked
     mobile layout has no separate sidebar column needing its own
     clearance, so a second `padding-top` here would just add a blank
     42px gap between the sidebar row and the topbar. */
  .main { padding-top: 0; }
  .sidebar {
    position: static; height: auto;
    flex-direction: row; align-items: center;
    overflow-x: auto; gap: .2rem; padding: .6rem;
  }
  .brand { margin-bottom: 0; flex: 0 0 auto; }
  .nav-section, .sidebar-foot { display: none; }
  .nav-item { flex: 0 0 auto; }
  .content { padding: 1rem; padding-bottom: calc(1rem + var(--tape-h)); }
  .topbar { padding: .7rem 1rem; }
}

/* ============================ app card + overview ============================ */

/* The brand slot is the app slot. Making it a link with a hover state says the
   identity at the top is something you act on, not a logo. */
a.brand { text-decoration: none; color: inherit; transition: background .12s, border-color .12s; }
a.brand:hover { background: var(--surface-3); border-color: var(--border-strong); text-decoration: none; }
.brand-text { min-width: 0; }
.brand-name {
  display: block; font-weight: 650; font-size: .9rem; letter-spacing: -0.01em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.brand-sub {
  display: block; color: var(--muted); font-size: .73rem;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.brand-mark { overflow: hidden; padding: 0; }
.brand-mark img { width: 100%; height: 100%; object-fit: cover; display: block; }

.app-header {
  display: flex; align-items: center; gap: 1.1rem; flex-wrap: wrap;
  padding: 1.1rem 1.25rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  margin-bottom: 1rem;
}
.app-hero {
  width: 68px; height: 68px; border-radius: 15px;
  border: 1px solid var(--border); background: var(--surface-2);
  flex: 0 0 auto; box-shadow: var(--shadow-sm);
}
.app-header-body { flex: 1; min-width: 12rem; }
.star-btn {
  border: none; background: none; cursor: pointer;
  font-size: 1.2rem; line-height: 1; padding: 0 .15rem;
  color: var(--muted); vertical-align: middle;
  transition: color .12s, transform .12s;
}
.star-btn:hover { color: var(--warn); transform: scale(1.12); }
.star-btn.active { color: var(--warn); }
.app-header-meta {
  display: flex; flex-wrap: wrap; gap: .4rem; align-items: center;
  margin-top: .5rem; font-size: .8rem;
}
.app-score { flex: 0 0 auto; text-align: center; }

.check-mark {
  display: grid; place-items: center;
  width: 20px; height: 20px; border-radius: 50%;
  font-size: .72rem; font-weight: 800;
}
.check-mark.ok { background: var(--ok-soft); color: var(--ok); }
.check-mark.bad { background: var(--bad-soft); color: var(--bad); }
.check-mark.unknown { background: var(--surface-3); color: var(--muted); }

.next-grid {
  display: grid; gap: .6rem;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  margin-bottom: 1.25rem;
}
.next-card {
  display: flex; flex-direction: column; gap: .2rem;
  padding: .8rem .9rem;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface);
  color: inherit; text-decoration: none;
  transition: border-color .12s, transform .1s, box-shadow .12s;
}
.next-card:hover {
  border-color: var(--accent); text-decoration: none;
  transform: translateY(-2px); box-shadow: var(--shadow-md);
}
.next-card strong { font-size: .87rem; }
.next-card span { font-size: .79rem; line-height: 1.4; }

/* ============================ community ============================ */

/* The "request to test" dialog — a short pitch reviewed by the listing's
   owner before a slot is theirs, the same shape as a marketplace's
   contact-the-owner modal: name/email only when nobody's signed in yet,
   one message field, one primary action. */
.modal-overlay {
  position: fixed; inset: 0; z-index: 100;
  display: flex; align-items: center; justify-content: center;
  background: rgb(10 10 16 / 55%);
  padding: 1.2rem;
  animation: fade .15s ease-out;
}
.modal {
  width: 100%; max-width: 26rem;
  max-height: calc(100vh - 3rem);
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-md);
  padding: 1.3rem 1.4rem 1.5rem;
}
.modal-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; }
.modal-head h3 { margin: 0; font-size: 1rem; color: var(--text); }
.modal-close {
  background: none; border: none; color: var(--muted);
  font-size: 1.05rem; line-height: 1; padding: .25rem .4rem; cursor: pointer;
  border-radius: var(--r-sm);
}
.modal-close:hover { color: var(--text); background: var(--surface-2); }
.modal-sub { color: var(--muted); font-size: .82rem; margin: .3rem 0 1.1rem; }
.modal-fields { display: flex; flex-direction: column; gap: .8rem; }
.modal-fields textarea { min-height: 5.5rem; }
.modal .status { margin: .7rem 0 0; }

.rep-badge { font-family: inherit; }

/* A request awaiting the owner's review — accept/decline sit where a
   completed session's "mark complete" button used to, so the review step
   reads as one more stage of the same list rather than a bolted-on extra. */
.request-card {
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: .7rem .8rem;
  margin-bottom: .5rem;
  background: var(--accent-soft);
}
.request-card .request-head { display: flex; justify-content: space-between; align-items: baseline; gap: .6rem; }
.request-card .request-who { font-weight: 600; font-size: .87rem; }
.request-card .request-message { font-size: .85rem; color: var(--text-2); margin: .35rem 0 .55rem; }
.request-card .request-actions { display: flex; gap: .5rem; }

.eval-fields { display: flex; gap: 1rem; flex-wrap: wrap; margin: .5rem 0; font-size: .83rem; }
.eval-fields label { display: flex; align-items: center; gap: .35rem; color: var(--text-2); }
.eval-summary { display: flex; gap: .4rem; flex-wrap: wrap; margin-top: .3rem; }

/* A test session's message thread — the tester and the listing owner
   coordinating once a request is `accepted`, past the single opening
   pitch. `.mine` (the signed-in viewer's own messages) leans on the
   surface tokens rather than a hardcoded colour, so it reads correctly for
   *either* party: whoever is looking at the thread sees their own
   messages picked out the same way. */
.comm-thread { margin-top: .5rem; padding: .6rem .7rem; border: 1px solid var(--border); border-radius: var(--r-md); background: var(--surface-2); }
.thread-list { display: flex; flex-direction: column; gap: .5rem; max-height: 14rem; overflow-y: auto; }
.thread-message { align-self: flex-start; max-width: 85%; padding: .4rem .55rem; border-radius: var(--r-sm); background: var(--surface); font-size: .84rem; }
.thread-message.mine { align-self: flex-end; background: var(--accent-soft); }

/* ============================ inbox (dedicated view) ============================ */

/* A three-pane layout reads as an app section, not a document that scrolls
 * as one column like every other view — each pane (list/thread/details)
 * scrolls independently within a fixed-height shell instead.
 *
 * That took two attempts. The first used a negative margin on
 * `#view-inbox.active` to bleed out of `.content`'s padding while giving it
 * an explicit height — `.content` itself was never height-constrained
 * (every other view relies on it auto-sizing to its content so the *page*
 * scrolls), so a negative margin's contribution to that auto-height is
 * fragile: it measured out ~200px taller than the viewport in testing,
 * making `<body>` itself scrollable. Scrolling inside a thread with room
 * left to scroll doesn't reach that 200px until the inner pane's own
 * scroll is exhausted, but a short thread — nothing to scroll internally —
 * forwards the very first scroll gesture straight to that phantom 200px on
 * `<body>`, which reads as "the whole page slides."
 *
 * `:has()` fixes it at the source instead of patching the symptom: only
 * while `#view-inbox.active` exists does anything here override the
 * default auto-height chain, so every other view's normal whole-page
 * scroll is completely untouched.
 */
/* Both html and body, not just body: which of the two is the actual root
 * scroller is a browser-specific overflow-propagation call, not something
 * safe to assume — locking only one left a ~19px sliver still scrollable
 * in testing. */
html:has(#view-inbox.active) {
  height: 100vh;
  overflow: hidden;
}
body:has(#view-inbox.active) {
  height: 100vh;
  overflow: hidden;
}
body:has(#view-inbox.active) .app { height: 100vh; min-height: 0; }
body:has(#view-inbox.active) .main { height: 100vh; min-height: 0; }
body:has(#view-inbox.active) .content {
  /* topbar's own height, minus the top tape's flow space when the
     900–1300px band has it showing, minus the bottom tape's fixed
     overlay over the same span — see `--tape-h` above. */
  height: calc(100vh - 61px - (var(--tape-h) * 2));
  min-height: 0;
  padding: 0;
  overflow: hidden;
}

#view-inbox.active { display: flex; height: 100%; width: 100%; }
.inbox-shell { display: flex; width: 100%; min-height: 0; }

.inbox-list-pane {
  width: 320px; flex: 0 0 320px;
  border-right: 1px solid var(--border);
  display: flex; flex-direction: column;
  min-height: 0;
}
.inbox-list-head { padding: 1rem 1rem .5rem; display: flex; align-items: center; justify-content: space-between; gap: .6rem; }
.inbox-list-head h2 { font-size: 1rem; margin: 0; }
.inbox-filter-select {
  font-size: .78rem;
  padding: .3rem .5rem;
  border-radius: var(--r-sm);
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text);
}
.inbox-fav-star { color: var(--warn); flex-shrink: 0; }
.inbox-muted-icon { font-size: .78rem; flex-shrink: 0; }
.inbox-list { flex: 1; min-height: 0; overflow-y: auto; padding: 0 .7rem .7rem; }

.inbox-banner {
  display: flex; flex-direction: column; gap: .5rem;
  margin: 0 .7rem .6rem;
  padding: .7rem .8rem;
  font-size: .78rem;
}
.inbox-banner button { align-self: flex-start; }

.inbox-row {
  display: flex; align-items: flex-start; gap: .6rem;
  width: 100%; text-align: left;
  padding: .65rem .7rem;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--r-lg);
  margin-bottom: .3rem;
  cursor: pointer;
  font: inherit; color: inherit;
}
.inbox-row:hover { background: var(--surface-2); }
.inbox-row.selected { background: var(--accent-soft); border-color: color-mix(in srgb, var(--accent) 35%, transparent); }
.inbox-row.unread .inbox-row-head strong { font-weight: 750; }
.inbox-row-body { flex: 1; min-width: 0; }
.inbox-row-head { display: flex; align-items: baseline; gap: .35rem; font-size: .85rem; }
.inbox-row-head strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.inbox-row-time { margin-left: auto; font-size: .72rem; white-space: nowrap; flex-shrink: 0; }
.inbox-row-sub { font-size: .74rem; margin-top: .1rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.inbox-row-preview { font-size: .8rem; margin-top: .15rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.inbox-dot { width: .5rem; height: .5rem; border-radius: 50%; background: var(--accent); flex-shrink: 0; margin-top: .3rem; }

.inbox-thread-pane {
  flex: 1; min-width: 0;
  display: flex; flex-direction: column;
  min-height: 0;
}
.inbox-thread-head {
  display: flex; align-items: center; gap: .7rem;
  padding: .85rem 1.25rem;
  border-bottom: 1px solid var(--border);
}
.inbox-thread-head-info { flex: 1; min-width: 0; }
.inbox-thread-head strong { display: block; font-size: .92rem; }
.inbox-thread-head .muted { font-size: .78rem; }

.inbox-thread-menu-wrap { position: relative; flex-shrink: 0; }
.inbox-thread-menu-btn {
  padding: .3rem .55rem;
  font-size: 1rem;
  line-height: 1;
  border-radius: var(--r-sm);
}
.inbox-thread-menu {
  position: absolute; top: calc(100% + .4rem); right: 0;
  width: 13rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-md);
  z-index: 30;
  padding: .35rem;
  display: flex; flex-direction: column;
}
.inbox-menu-item {
  text-align: left;
  padding: .5rem .6rem;
  border-radius: var(--r-sm);
  font-size: .84rem;
  background: none;
  border: none;
  color: var(--text);
  cursor: pointer;
}
.inbox-menu-item:hover { background: var(--surface-2); }

.report-cause-list { display: flex; flex-direction: column; gap: .4rem; }
.report-cause-option {
  display: flex; align-items: center; gap: .5rem;
  font-size: .85rem; font-weight: 400; color: var(--text);
  text-transform: none; letter-spacing: normal;
  cursor: pointer;
}
.report-cause-option input { flex-shrink: 0; }
.inbox-thread-messages {
  flex: 1; min-height: 0; overflow-y: auto;
  padding: 1.25rem;
  display: flex; flex-direction: column; gap: .6rem;
}
.inbox-thread-messages .thread-message { max-width: 32rem; }
.thread-safety-note {
  margin: 0; padding: .5rem 1.25rem 0;
  font-size: .72rem; color: var(--muted);
}
.inbox-thread-composer {
  display: flex; gap: .5rem;
  padding: .6rem 1.25rem 1rem;
  border-top: 1px solid var(--border);
}
.inbox-thread-composer input { flex: 1; }
#inboxComposerStatus:not(:empty) { padding: 0 1.25rem .75rem; }

.inbox-details-pane {
  width: 300px; flex: 0 0 300px;
  border-left: 1px solid var(--border);
  overflow-y: auto;
  padding: 1.25rem 1rem;
  display: flex; flex-direction: column; gap: .9rem;
}
.inbox-detail-card {
  padding: .9rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  font-size: .84rem;
}
.inbox-detail-card > .app-icon { margin-bottom: .5rem; }
.inbox-detail-card strong { display: block; margin-bottom: .35rem; }
.inbox-detail-label {
  font-size: .68rem; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: var(--muted); margin-bottom: .35rem;
}
.inbox-detail-pills { display: flex; gap: .35rem; flex-wrap: wrap; margin-bottom: .4rem; }
.inbox-detail-desc { font-size: .8rem; line-height: 1.5; margin: .4rem 0; }
.inbox-detail-links { display: flex; flex-direction: column; gap: .3rem; margin-top: .5rem; }
.inbox-detail-links a { font-size: .82rem; }
.inbox-detail-facts { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; font-size: .8rem; }

@media (max-width: 1000px) {
  .inbox-details-pane { display: none; }
}
@media (max-width: 760px) {
  .inbox-list-pane { width: 240px; flex-basis: 240px; }
}

.toast {
  position: fixed;
  right: 1.2rem;
  bottom: 1.2rem;
  z-index: 200;
  max-width: 20rem;
  display: flex;
  flex-direction: column;
  gap: .2rem;
  text-align: left;
  padding: .8rem 1rem;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-lg);
  box-shadow: 0 8px 28px rgba(0,0,0,.28);
  cursor: pointer;
  font: inherit; color: inherit;
  animation: toast-in .18s ease-out;
}
.toast strong { font-size: .86rem; }
.toast span { font-size: .8rem; color: var(--muted); }
@keyframes toast-in {
  from { transform: translateY(.5rem); opacity: 0; }
  to { transform: translateY(0); opacity: 1; }
}

/* ============================ get-started checklist ============================
   Floating bottom-left, tinted with the app's own accent (purple) rather
   than a status colour — this is guidance, not a warning or a success
   state. Semi-transparent (color-mix against transparent, not `--surface`)
   so it reads as an overlay sitting on top of the dashboard, not another
   opaque panel competing with it. See views/onboard.js for the two
   real-tracked steps and their done logic. */
.onboard-card {
  position: fixed;
  right: .8rem;
  /* Clears the always-on bottom sponsor tape (--tape-h) plus its own
     breathing room, instead of sitting flush underneath it. */
  bottom: calc(var(--tape-h) + .8rem);
  z-index: 200;
  width: 19rem;
  max-width: calc(100vw - 2.4rem);
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  backdrop-filter: blur(14px) saturate(160%);
  border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
  border-radius: var(--r-lg);
  box-shadow: 0 8px 28px rgba(0,0,0,.28);
  overflow: hidden;
  animation: toast-in .18s ease-out;
}

.onboard-head {
  display: flex;
  align-items: center;
  gap: .6rem;
  padding: .8rem .9rem;
  cursor: pointer;
  user-select: none;
}
.onboard-head strong { font-size: .85rem; flex: 1; min-width: 0; }
.onboard-progress-label { font-size: .72rem; color: var(--text-2); white-space: nowrap; }
.onboard-chevron { color: var(--text-2); transition: transform .15s; flex: 0 0 auto; }
.onboard-card.collapsed .onboard-chevron { transform: rotate(-90deg); }
.onboard-card.collapsed .onboard-bar,
.onboard-card.collapsed .onboard-steps { display: none; }

.onboard-bar {
  height: 3px;
  background: color-mix(in srgb, var(--accent) 25%, transparent);
}
.onboard-bar-fill { height: 100%; background: var(--accent); transition: width .2s ease-out; }

.onboard-steps { padding: .3rem .55rem .65rem; display: flex; flex-direction: column; gap: .1rem; }
.onboard-step {
  display: flex;
  align-items: flex-start;
  gap: .65rem;
  padding: .5rem .5rem;
  border-radius: var(--r-md);
  color: inherit;
  transition: background .12s;
}
.onboard-step:hover { background: color-mix(in srgb, var(--accent) 18%, transparent); text-decoration: none; }
.onboard-step-icon {
  width: 25px; height: 25px;
  border-radius: 7px;
  flex: 0 0 auto;
  display: grid; place-items: center;
  font-size: .8rem;
  background: color-mix(in srgb, var(--accent) 28%, transparent);
  color: var(--accent-strong);
}
.onboard-step-icon svg { width: 14px; height: 14px; }
.onboard-step.done .onboard-step-icon { background: var(--accent); color: var(--on-accent); }
.onboard-step-body { min-width: 0; }
.onboard-step-title { display: block; font-size: .82rem; font-weight: 650; line-height: 1.3; }
.onboard-step.done .onboard-step-title {
  color: var(--text-2);
  text-decoration: line-through;
  text-decoration-color: color-mix(in srgb, var(--text-2) 55%, transparent);
}
.onboard-step-desc { display: block; font-size: .74rem; color: var(--text-2); line-height: 1.4; margin-top: .1rem; }

@media (max-width: 700px) {
  .onboard-card { right: .7rem; bottom: calc(var(--tape-h) + .7rem); width: calc(100vw - 1.4rem); }
}

.leaderboard-row {
  display: grid; grid-template-columns: 2rem 1fr auto; gap: .6rem; align-items: center;
  padding: .5rem .2rem; border-top: 1px solid var(--border);
}
.leaderboard-row:first-child { border-top: none; }
.leaderboard-rank { font-family: var(--mono, ui-monospace); color: var(--muted); font-size: .82rem; font-variant-numeric: tabular-nums; }
.leaderboard-row:nth-child(1) .leaderboard-rank,
.leaderboard-row:nth-child(2) .leaderboard-rank,
.leaderboard-row:nth-child(3) .leaderboard-rank { color: var(--warn); font-weight: 700; }
.leaderboard-name { font-weight: 550; font-size: .88rem; }
.leaderboard-count { color: var(--muted); font-size: .8rem; font-variant-numeric: tabular-nums; }

/* The community view is the one page here that holds three jobs at once —
   a marketplace, a ranking and your own dashboard. Tabs keep each of them
   a full page rather than a third of a scroll, without spending three
   sidebar slots on a single feature. */
.tabs {
  display: flex; gap: .1rem; flex-wrap: wrap;
  border-bottom: 1px solid var(--border);
  margin: 0 0 1.25rem;
}
.tab {
  font: inherit; font-size: .86rem; font-weight: 600;
  padding: .5rem .8rem;
  background: none;
  border: none; border-bottom: 2px solid transparent;
  border-radius: 0;
  margin-bottom: -1px;
  color: var(--muted);
  cursor: pointer;
  transition: color .12s, border-color .12s;
}
.tab:hover:not(:disabled) { background: none; color: var(--text); border-color: var(--border-strong); }
.tab.active { color: var(--accent); border-bottom-color: var(--accent); }
.tab .tab-count {
  display: inline-block; margin-left: .35rem;
  font-size: .72rem; font-variant-numeric: tabular-nums;
  color: var(--muted); font-weight: 600;
}
.tab.active .tab-count { color: var(--accent); }

/* A listing card carries the same shape of headline metrics a marketplace
   puts on a for-sale card — except every number here is recomputed from the
   public catalogue when you look at it, never typed in by the person who
   posted it. `pending` is the state before that recomputation lands. */
.listing-card { padding: .95rem 1.05rem; margin-bottom: .6rem; }
.listing-head { display: flex; gap: .7rem; align-items: flex-start; }

/* ============================ contribution boost tiers ============================
   A listing card's owner lifetime High Fives received, tinted bronze
   through Légendaire — `ownerBoostTier` in
   community/src/routes/listings.js, reading `owner_contribution_count`.
   Cosmetic only: it never touches `ORDER BY`, so a 0-High-Five listing
   still ranks #1 under "newest" — see that file's `browse()` docstring
   for why that stays true on purpose. Applies to both card shapes
   (.listing-card, the wide Browse-tab row; .marketplace-card, the
   curated-grid tile), since a listing can appear in either.

   A soft background tint plus a tinted border, no box-shadow ring — the
   same escalating-`.featured`-look landing.css's `.listing-tile.boost-*`
   uses (Silver/boost-2 is the exact accent-tint formula `.featured` gets
   there), just without the shine sweep, since no other card variant on
   this surface has that motion either. Same ramp as landing.css, so the
   marketplace and the dashboard read as one system. */
/* Bronze mixes warn with --bad (red) before diluting into --border — a
   mahogany/acajou brown with a hue of its own, not just a duller gold
   (see landing.css's copy of this rule). */
.listing-card.boost-1, .marketplace-card.boost-1 {
  background: color-mix(in srgb, color-mix(in srgb, var(--warn) 60%, var(--bad) 40%) 10%, var(--surface));
  border-color: color-mix(in srgb, color-mix(in srgb, var(--warn) 60%, var(--bad) 40%) 55%, var(--border));
}
.listing-card.boost-2, .marketplace-card.boost-2 {
  background: color-mix(in srgb, var(--accent) 9%, var(--surface));
  border-color: color-mix(in srgb, var(--accent) 28%, var(--border));
}
/* Gold — deliberately modest, so it stays a clear step below Légendaire's
   fully gold card. */
.listing-card.boost-3, .marketplace-card.boost-3 {
  background: color-mix(in srgb, var(--warn) 8%, var(--surface));
  border-color: color-mix(in srgb, var(--warn) 45%, var(--border));
}
/* Platine — blue, same hex as the sponsor rail's `blue` swatch
   (RAIL_COLORS in landing.js). */
.listing-card.boost-4, .marketplace-card.boost-4 {
  background: color-mix(in srgb, #2f5fa8 15%, var(--surface));
  border-color: color-mix(in srgb, #2f5fa8 45%, var(--border));
}
/* Diamant — violet, rail's `violet` swatch. */
.listing-card.boost-5, .marketplace-card.boost-5 {
  background: color-mix(in srgb, #6d3aa8 17%, var(--surface));
  border-color: color-mix(in srgb, #6d3aa8 50%, var(--border));
}
/* Rubis — red, rail's `red` swatch. Strongest tint before Légendaire. */
.listing-card.boost-6, .marketplace-card.boost-6 {
  background: color-mix(in srgb, #a8342f 19%, var(--surface));
  border-color: color-mix(in srgb, #a8342f 55%, var(--border));
}
/* Légendaire — the top of the ladder, a fully gold card. */
.listing-card.boost-7, .marketplace-card.boost-7 {
  border-color: var(--warn);
  background: linear-gradient(
    135deg,
    color-mix(in srgb, var(--warn) 32%, var(--surface)),
    color-mix(in srgb, var(--warn) 16%, var(--surface))
  );
}
.listing-title { display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; }
.listing-title strong { font-size: .95rem; letter-spacing: -0.01em; }
.listing-desc { color: var(--text-2); font-size: .85rem; margin-top: .35rem; max-width: 70ch; }

.card-metrics {
  display: flex; flex-wrap: wrap; gap: 1.5rem;
  margin-top: .8rem; padding-top: .7rem;
  border-top: 1px solid var(--border);
}
.card-metric { display: flex; flex-direction: column; gap: .15rem; min-width: 0; }
.card-metric .metric-label {
  font-size: .63rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .07em;
  color: var(--muted);
}
.card-metric .metric-value {
  font-size: .95rem; font-weight: 650;
  font-variant-numeric: tabular-nums;
  line-height: 1.2;
}
.card-metric .metric-value.pending { color: var(--muted); font-weight: 500; }
.card-metric .metric-value.ok { color: var(--ok); }
.card-metric .metric-value.warn { color: var(--warn); }
.card-metric .metric-value.bad { color: var(--bad); }
.card-metric .metric-sub { font-size: .72rem; color: var(--muted); }

.verified-note {
  display: flex; align-items: center; gap: .4rem;
  font-size: .74rem; color: var(--muted);
  margin: -.4rem 0 1rem;
}

/* Ranked board. Rank, who, a proportion bar against the leader, and the
   headline number — the bar is what turns a list of numbers into a shape
   you can read without comparing digits. */
.board { padding: .35rem 1.05rem; }
.board-row {
  display: grid;
  grid-template-columns: 2.2rem minmax(0, 1fr) minmax(5rem, 9rem) auto;
  gap: .75rem; align-items: center;
  padding: .6rem 0;
  border-top: 1px solid var(--border);
}
.board-row:first-child { border-top: none; }
.board-rank {
  font-size: .82rem; font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
  text-align: center;
}
.board-row.top-3 .board-rank { font-size: 1rem; }
.board-who { min-width: 0; }
.board-name {
  font-weight: 600; font-size: .88rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.board-sub { color: var(--muted); font-size: .75rem; }
.board-metric {
  font-size: .88rem; font-weight: 650;
  font-variant-numeric: tabular-nums;
  text-align: right; white-space: nowrap;
}
.board-metric .unit { color: var(--muted); font-weight: 500; font-size: .78rem; }

/* A contributor and the apps they have open, nested under their standing —
   the reward for giving back, made literal: contribute, and what you are
   building is what gets seen. */
.contributor-group { padding: 0; margin-bottom: .7rem; overflow: hidden; }
.contributor-head {
  display: flex; align-items: center; gap: .7rem;
  padding: .8rem 1.05rem;
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
}
.contributor-head .board-rank { flex: 0 0 auto; }
.contributor-listings { padding: .2rem 1.05rem .6rem; }
.contributor-listing {
  display: flex; align-items: center; gap: .65rem;
  padding: .55rem 0;
  border-top: 1px solid var(--border);
}
.contributor-listing:first-child { border-top: none; }
.contributor-listing .app-icon { width: 30px; height: 30px; }
.contributor-listing-body { flex: 1; min-width: 0; }
.contributor-listing-name { font-weight: 550; font-size: .86rem; }

@media (max-width: 700px) {
  .board-row { grid-template-columns: 1.8rem minmax(0, 1fr) auto; }
  .board-row .bar { display: none; }
  .card-metrics { gap: 1.1rem; }
}

/* ============================ marketplace home ============================ */

/* Curated grids at the top of the Marketplace tab — the same rhythm as a
   marketplace's own front page (a couple of curated groups, then the full
   directory), rendered as a responsive grid rather than a scrolling
   carousel so it behaves like everything else in this app. */
.marketplace-section { margin-bottom: 1.6rem; }
.marketplace-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: .75rem;
}
.marketplace-card {
  padding: .95rem 1rem;
  display: flex; flex-direction: column; gap: .7rem;
}
.marketplace-card-head { display: flex; gap: .6rem; align-items: flex-start; }
.marketplace-card-name {
  display: block; font-size: .9rem; letter-spacing: -0.01em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.marketplace-card-badges { display: flex; flex-wrap: wrap; gap: .3rem; margin-top: .3rem; }
.marketplace-card-metrics {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: .5rem;
  padding-top: .6rem; border-top: 1px solid var(--border);
}
.mini-metric { display: flex; flex-direction: column; gap: .1rem; min-width: 0; }
.mini-metric .metric-label { font-size: .6rem; }
.mini-metric .metric-value {
  font-size: .82rem; font-weight: 650;
  font-variant-numeric: tabular-nums;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ghost-link { font-size: .85rem; font-weight: 600; text-align: center; }

/* The compact table a marketplace homepage teases its full leaderboard
   with — plain rows, no proportion bar, unlike `.board` on the Leaderboard
   tab which has room to spare. */
.teaser-head { display: flex; align-items: baseline; justify-content: space-between; gap: .8rem; }
.teaser-head h3 { margin-bottom: 0; }
.leaderboard-teaser { padding: .2rem 1.05rem; }
.teaser-row {
  display: grid; grid-template-columns: 1.6rem minmax(0, 1fr) auto;
  gap: .6rem; align-items: center;
  padding: .5rem 0;
  border-top: 1px solid var(--border);
  font-size: .85rem;
}
.teaser-row:first-child { border-top: none; }
.teaser-rank { font-weight: 700; color: var(--muted); text-align: center; }
.teaser-name { font-weight: 550; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.teaser-metric { font-weight: 650; font-variant-numeric: tabular-nums; white-space: nowrap; }
.teaser-metric .unit { color: var(--muted); font-weight: 500; font-size: .78rem; }

/* ============================ your-testing dashboard ============================ */

.dashboard-stats {
  display: flex; gap: 1.6rem; flex-wrap: wrap;
  padding: .9rem 1.1rem;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg);
  margin-bottom: 1.25rem;
}
.dashboard-stat { display: flex; flex-direction: column; gap: .2rem; }
.dashboard-stat-value { font-size: 1.4rem; font-weight: 700; font-variant-numeric: tabular-nums; letter-spacing: -0.02em; }

/* The pont between this dashboard and the ASO tools: the exact same health
   ring Overview draws, so a builder never wonders whether this is a second,
   different opinion about their listing. */
.aso-bridge { padding: 1rem 1.1rem; margin-bottom: 1.5rem; border-left: 3px solid var(--accent); }
.aso-bridge-head { display: flex; gap: .8rem; align-items: center; }
.aso-bridge-issues { margin: .7rem 0 .6rem; padding-left: 1.1rem; font-size: .85rem; color: var(--text-2); }
.aso-bridge-issues li { margin-bottom: .25rem; }

@media (max-width: 700px) {
  .marketplace-card-metrics { grid-template-columns: 1fr 1fr; }
  .teaser-row { grid-template-columns: 1.4rem minmax(0, 1fr) auto; }
}

/* ============================ tests (Track) ============================ */

.test-card {
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--surface);
  margin-bottom: .7rem;
  overflow: hidden;
}
.test-card.expanded { border-color: var(--border-strong); }
.test-card-head {
  width: 100%;
  display: flex; align-items: center; gap: .7rem;
  padding: .7rem .9rem;
  background: none; border: none; cursor: pointer;
  font: inherit; text-align: left; color: var(--text);
}
.test-card-head:hover { background: var(--surface-2); }
.test-icon-badge { position: relative; flex: 0 0 auto; line-height: 0; }
.store-badge {
  position: absolute; bottom: -3px; right: -3px;
  width: 16px; height: 16px; border-radius: 4.5px;
  display: flex; align-items: center; justify-content: center;
  background: #fff;
  border: 1.5px solid var(--surface);
  overflow: hidden;
}
.store-badge img { width: 100%; height: 100%; object-fit: cover; display: block; }
.store-badge.google img { width: 70%; height: 70%; }
/* Bottom-left placement for listing/marketplace cards — same badge, mirrored
   corner (the leaderboard's own placement stays bottom-right, `.store-badge`
   above, unmodified). See landing.css for the mirrored `.both` diagonal. */
.store-badge.bl { left: -3px; right: auto; }
/* Positioning anchor for a platform badge on a listing/marketplace card's
   app icon — unlike `.lb-app-icon-wrap` (landing.css), always visible, not
   a hover reveal: a card isn't a dense table row, so there's no attention
   cost to showing it plainly. */
.card-icon-wrap { position: relative; display: inline-block; line-height: 0; flex: 0 0 auto; }

/* ============================ platform chips ============================
   Verdict-chip styling, borrowed for a fact this app actually has: which
   store(s) a listing lives on. Shared between the landing page's browse
   table (web/browse-view.js) and the dashboard's Marketplace tab
   (web/views/be-tester.js) — both load this file. */
.vchip-group { display: inline-flex; gap: .4rem; flex-wrap: wrap; }
.vchip {
  font: inherit;
  font-size: .74rem;
  font-weight: 700;
  letter-spacing: .02em;
  padding: .4rem .75rem;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--muted);
  cursor: pointer;
  transition: color .12s, border-color .12s, background-color .12s, transform .1s;
}
.vchip:hover { color: var(--text); border-color: var(--border-strong); transform: translateY(-1px); }
.vchip:active { transform: scale(.94); }
.vchip.all.active { background: var(--text); border-color: var(--text); color: var(--bg); }
.vchip.ios.active { background: var(--ios); border-color: var(--ios); color: #fff; }
.vchip.android.active { background: var(--ok); border-color: var(--ok); color: #062b16; }
.vchip.both.active { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.test-card-body { display: flex; flex-direction: column; gap: .1rem; min-width: 0; flex: 1; }
.test-card-body strong { font-size: .9rem; }
.test-card-body .muted { font-size: .74rem; }
.test-card-chevron { color: var(--muted); font-size: .8rem; flex: 0 0 auto; }
.test-detail { padding: 0 .9rem .9rem; border-top: 1px solid var(--border); }
.test-detail > .callout { margin: .9rem 0 0; }

.invite-row { display: flex; gap: .5rem; margin-top: .75rem; }
.invite-row input { flex: 1; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .78rem; }

.store-toggle { display: flex; gap: .5rem; }
.store-toggle-btn {
  display: flex; align-items: center; gap: .4rem;
  padding: .45rem .8rem;
  border: 1.5px solid var(--border-strong); border-radius: var(--r-md);
  background: var(--surface); color: var(--text); cursor: pointer;
  font: inherit; font-size: .85rem;
}
.store-toggle-btn:hover { background: var(--surface-2); }
.store-toggle-btn.active { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
.store-toggle-btn img { flex: 0 0 auto; }

.test-app-selected {
  display: flex; align-items: center; gap: .5rem;
  padding: .4rem .5rem;
  border: 1px solid var(--border); border-radius: var(--r-md);
  background: var(--surface-2);
  width: fit-content;
}
.test-app-selected button { padding: .15rem .4rem; font-size: .8rem; }

.test-timeline { margin-top: .85rem; }
.test-timeline summary { cursor: pointer; font-size: .78rem; color: var(--muted); }
.test-timeline summary:hover { color: var(--text-2); }
.test-timeline textarea { width: 100%; max-width: 40rem; margin-top: .5rem; }

/* ============================ daily check-ins ============================ */

.checkin-strip-row { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }
.checkin-strip { display: flex; gap: .2rem; }
.checkin-day {
  width: 14px; height: 14px; border-radius: 3px;
  background: var(--surface-3);
  border: 1px solid var(--border);
}
.checkin-day.done { background: var(--ok); border-color: var(--ok); }
.checkin-day.today:not(.done) { border-color: var(--accent); border-style: dashed; }

.checkin-upload-label {
  display: inline-flex; align-items: center; gap: .35rem;
  margin-top: .5rem;
  padding: .4rem .75rem;
  border: 1.5px solid var(--border-strong); border-radius: var(--r-md);
  background: var(--surface); color: var(--text);
  font-size: .82rem; cursor: pointer;
}
.checkin-upload-label:hover { background: var(--surface-2); }
.checkin-upload-label.disabled { color: var(--ok); border-color: var(--ok); cursor: default; background: none; }
.checkin-status { margin-top: .3rem; }

.checkin-proof-grid {
  display: flex; gap: .6rem; flex-wrap: wrap;
  margin-top: .5rem; padding-top: .5rem;
  border-top: 1px solid var(--border);
}
.checkin-proof {
  display: flex; flex-direction: column; gap: .25rem; align-items: center;
  text-decoration: none; width: 4.5rem;
}
.checkin-proof img {
  width: 4.5rem; height: 4.5rem; object-fit: cover;
  border-radius: var(--r-md); border: 1px solid var(--border);
}
.checkin-proof span { font-size: .68rem; }
.checkin-proof-placeholder {
  width: 4.5rem; height: 4.5rem;
  display: flex; align-items: center; justify-content: center;
  border-radius: var(--r-md); border: 1px dashed var(--border-strong);
  color: var(--muted); font-size: 1.1rem;
}
