/* ══════════════════════════════════════════════════════════════
   Casetta Cards — design system (§11c)
   Light  <- Bootswatch Sandstone   Dark <- Bootswatch Darkly
   Both share ONE token contract. No component holds a raw hex.
   ══════════════════════════════════════════════════════════════ */

:root {
  --ground:#F8F5F0; --surface:#FFFFFF; --raised:#EFEBE3; --sunken:#EDE8DF;
  --line:#DFD7C9; --line-soft:#EAE4D9;
  --ink:#3E3F3A; --ink-dim:#6E6F68; --ink-faint:#9B998F;
  --primary:#325D88; --primary-hi:#3D6E9E; --primary-wash:#E4EDF5; --on-primary:#FFF;
  --positive:#6E9B36; --positive-wash:#EBF2E0;
  --negative:#C9433F; --negative-wash:#F8E6E5;
  --warning:#B8860B; --warning-wash:#FBF2DA;
  --felt:#2F5D4C; --felt-line:#3E7361; --on-felt:#CFE0D8;
  --felt-wash:#E4EEE9; --on-felt-solid:#FFFFFF;
  --shadow-1:0 1px 2px rgba(62,63,58,.08);
  --shadow-2:0 4px 14px rgba(62,63,58,.10);
  --shadow-3:0 14px 38px rgba(62,63,58,.16);

  --t-xs:11px; --t-sm:12.5px; --t-base:14px; --t-md:16px;
  --t-lg:19px; --t-xl:23px; --t-2xl:28px; --t-3xl:34px;
  --s-1:4px; --s-2:8px; --s-3:12px; --s-4:16px;
  --s-5:24px; --s-6:32px; --s-7:48px; --s-8:72px;
  --r-sm:4px; --r-md:7px; --r-lg:11px; --r-full:999px;

  --sans:system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  --mono:ui-monospace,"SF Mono",SFMono-Regular,Menlo,Consolas,monospace;
  /* Used sparingly — wordmark, page titles, round numbers. Old-style serifs
     present on every platform, so no webfont and no silent fallback. */
  --display:"Iowan Old Style","Palatino Linotype",Palatino,"Book Antiqua",
            Georgia,"Times New Roman",serif;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --ground:#222; --surface:#303030; --raised:#3A3A3A; --sunken:#1B1B1B;
    --line:#464646; --line-soft:#383838;
    --ink:#EBEBEB; --ink-dim:#A2A2A2; --ink-faint:#767676;
    --primary:#5B8DC0; --primary-hi:#74A2D1; --primary-wash:#2A3947; --on-primary:#10161C;
    --positive:#00BC8C; --positive-wash:#16342C;
    --negative:#E74C3C; --negative-wash:#3A211E;
    --warning:#F39C12; --warning-wash:#3A2E14;
    --felt:#52977D; --felt-line:#3E7361; --on-felt:#C6DED4;
    --felt-wash:#1F332B; --on-felt-solid:#0C1512;
    --shadow-1:0 1px 2px rgba(0,0,0,.4);
    --shadow-2:0 4px 14px rgba(0,0,0,.45);
    --shadow-3:0 14px 38px rgba(0,0,0,.6);
  }
}
:root[data-theme="dark"] {
  --ground:#222; --surface:#303030; --raised:#3A3A3A; --sunken:#1B1B1B;
  --line:#464646; --line-soft:#383838;
  --ink:#EBEBEB; --ink-dim:#A2A2A2; --ink-faint:#767676;
  --primary:#5B8DC0; --primary-hi:#74A2D1; --primary-wash:#2A3947; --on-primary:#10161C;
  --positive:#00BC8C; --positive-wash:#16342C;
  --negative:#E74C3C; --negative-wash:#3A211E;
  --warning:#F39C12; --warning-wash:#3A2E14;
  --felt:#52977D; --felt-line:#3E7361; --on-felt:#C6DED4;
  --felt-wash:#1F332B; --on-felt-solid:#0C1512;
  --shadow-1:0 1px 2px rgba(0,0,0,.4);
  --shadow-2:0 4px 14px rgba(0,0,0,.45);
  --shadow-3:0 14px 38px rgba(0,0,0,.6);
}

/* ── reset ─────────────────────────────────────────────────── */
*,*::before,*::after{box-sizing:border-box;}
html{-webkit-text-size-adjust:100%;}
body{
  margin:0; background:var(--ground); color:var(--ink);
  font-family:var(--sans); font-size:var(--t-md); line-height:1.55;
  -webkit-font-smoothing:antialiased;
  min-height:100vh; display:flex; flex-direction:column;
}
img{max-width:100%;}
a{color:var(--primary);}
:focus-visible{outline:2px solid var(--primary); outline-offset:2px;}
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{transition:none!important;animation:none!important;}
}

/* ── layout ────────────────────────────────────────────────── */
.wrap{
  width:100%;max-width:760px;margin:0 auto;
  /* viewport-fit=cover lets content run under rounded corners and the notch,
     so the gutter has to be at least the safe-area inset. Without this the
     app bar sits flush to the glass on a Pixel/iPhone. */
  padding-left:max(var(--s-4), env(safe-area-inset-left));
  padding-right:max(var(--s-4), env(safe-area-inset-right));
}
.wrap--wide{max-width:1100px;}
main{flex:1;padding:var(--s-4) 0 var(--s-8);}
.stack{display:flex;flex-direction:column;gap:var(--s-3);}
.stack--tight{gap:var(--s-2);}
.row-between{display:flex;justify-content:space-between;align-items:center;gap:var(--s-3);}
.scroll-x{overflow-x:auto;}
.muted{color:var(--ink-dim);}
.faint{color:var(--ink-faint);}
.small{font-size:var(--t-sm);}
.center{text-align:center;}
.mt-4{margin-top:var(--s-4);} .mt-5{margin-top:var(--s-5);}
.mb-3{margin-bottom:var(--s-3);} .mb-4{margin-bottom:var(--s-4);}

h1{font-size:var(--t-2xl);margin:0 0 var(--s-3);font-weight:700;letter-spacing:-.02em;text-wrap:balance;}
h2{font-size:var(--t-lg);margin:0 0 var(--s-2);font-weight:700;}
h3{font-size:var(--t-md);margin:0 0 var(--s-2);font-weight:700;}
p{margin:0 0 var(--s-3);}

/* ── app bar ───────────────────────────────────────────────── */
.appbar{
  background:var(--surface);border-bottom:1px solid var(--line);
  position:sticky;top:0;z-index:20;
}
.appbar__inner{
  display:flex;align-items:center;justify-content:space-between;gap:var(--s-3);
  padding:var(--s-3) 0;
}
.wordmark{font-size:var(--t-md);font-weight:700;letter-spacing:-.02em;
  text-decoration:none;color:var(--ink);}
.wordmark span{color:var(--primary);}
.navlinks{display:flex;gap:var(--s-4);align-items:center;font-size:var(--t-sm);}
.navlinks a{color:var(--ink-dim);text-decoration:none;}
.navlinks a:hover{color:var(--primary);}

/* ── buttons ───────────────────────────────────────────────── */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:var(--s-2);
  font-family:var(--sans);font-size:var(--t-base);font-weight:600;
  border-radius:var(--r-md);border:1px solid transparent;
  padding:11px var(--s-4);min-height:44px;   /* touch target at a table */
  background:var(--primary);color:var(--on-primary);
  cursor:pointer;text-decoration:none;line-height:1.2;
}
.btn:hover{background:var(--primary-hi);}
.btn[disabled]{opacity:.5;cursor:not-allowed;}
.btn--ghost{background:transparent;color:var(--ink);border-color:var(--line);}
.btn--ghost:hover{background:var(--raised);}
.btn--quiet{background:transparent;color:var(--ink-dim);border-color:transparent;}
.btn--danger{background:var(--negative);color:#fff;}
.btn--block{display:flex;width:100%;}
.btn--lg{font-size:var(--t-md);padding:14px var(--s-5);min-height:52px;}
.btn--sm{font-size:var(--t-sm);padding:6px var(--s-3);min-height:34px;}

/* ── forms ─────────────────────────────────────────────────── */
.field{display:flex;flex-direction:column;gap:5px;margin-bottom:var(--s-3);}
.field label{font-size:var(--t-sm);font-weight:600;color:var(--ink-dim);}
.field .hint{font-size:var(--t-xs);color:var(--ink-faint);}
.input,select,textarea{
  font-family:var(--sans);font-size:var(--t-md);
  padding:11px var(--s-3);min-height:44px;
  border:1px solid var(--line);border-radius:var(--r-md);
  /* --surface, not --ground: a field on --ground is the same colour as the
     page behind it, so it sank into the background while the cards beside it
     stood up. Both themes carry a surface a step off their body colour. */
  background:var(--surface);color:var(--ink);width:100%;
}
.input:focus,select:focus,textarea:focus{
  border-color:var(--primary);outline:none;
  box-shadow:0 0 0 3px var(--primary-wash);
}
textarea{min-height:84px;}

/* ── cards & surfaces ──────────────────────────────────────── */
.card{
  background:var(--surface);border:1px solid var(--line);
  border-radius:var(--r-md);padding:var(--s-4);
}
.card--live{border-color:var(--primary);border-width:1.5px;}
.card__label{
  font-size:var(--t-xs);letter-spacing:.11em;text-transform:uppercase;
  color:var(--primary);font-weight:700;margin-bottom:var(--s-1);
}
.card__title{font-weight:700;font-size:var(--t-md);}
.card__sub{font-size:var(--t-sm);color:var(--ink-dim);margin-top:2px;}

/* ── badges ────────────────────────────────────────────────── */
.badge{
  display:inline-block;font-size:var(--t-xs);font-weight:700;letter-spacing:.04em;
  padding:3px 8px;border-radius:var(--r-full);
  background:var(--raised);color:var(--ink-dim);
}
.badge--primary{background:var(--primary-wash);color:var(--primary);}
.badge--positive{background:var(--positive-wash);color:var(--positive);}
.badge--negative{background:var(--negative-wash);color:var(--negative);}
.badge--warning{background:var(--warning-wash);color:var(--warning);}

/* ── avatars ───────────────────────────────────────────────── */
.av{
  width:34px;height:34px;border-radius:var(--r-full);
  background:var(--raised);border:1px solid var(--line);
  display:grid;place-items:center;flex-shrink:0;
  font-size:var(--t-base);font-weight:700;color:var(--ink-dim);
  overflow:hidden;
}
.av img{width:100%;height:100%;object-fit:cover;}
.av--turn{background:var(--primary);color:var(--on-primary);border-color:var(--primary);}
.av--lg{width:46px;height:46px;font-size:var(--t-lg);}
.av--sm{width:26px;height:26px;font-size:var(--t-xs);}
.av--empty{border-style:dashed;background:transparent;color:var(--ink-faint);}

/* ── numbers ───────────────────────────────────────────────── */
.num{font-family:var(--mono);font-variant-numeric:tabular-nums;font-weight:600;}
.num--neg{color:var(--negative);}
.num--lead{color:var(--primary);}
.num--lg{font-size:var(--t-xl);}

/* ── stat tiles ────────────────────────────────────────────── */
.stats{display:flex;gap:var(--s-2);}
.stat{
  flex:1;background:var(--surface);border:1px solid var(--line);
  border-radius:var(--r-md);padding:var(--s-3) var(--s-2);text-align:center;
}
.stat__n{font-family:var(--mono);font-size:var(--t-xl);font-weight:700;
  font-variant-numeric:tabular-nums;line-height:1.1;}
.stat__l{font-size:10px;letter-spacing:.07em;text-transform:uppercase;
  color:var(--ink-faint);margin-top:3px;}

/* ── seat rows ─────────────────────────────────────────────── */
.seatrow{
  display:flex;align-items:center;gap:var(--s-3);
  background:var(--surface);border:1px solid var(--line);
  border-radius:var(--r-md);padding:var(--s-2) var(--s-3);min-height:56px;
}
.seatrow--active{border-color:var(--primary);border-width:1.5px;background:var(--primary-wash);}
.seatrow--done{opacity:.72;}
.seatrow__who{flex:1;min-width:0;}
.seatrow__name{font-size:var(--t-base);font-weight:600;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.seatrow__sub{font-size:var(--t-xs);color:var(--ink-faint);}
.seatrow__val{font-family:var(--mono);font-size:var(--t-lg);font-weight:700;
  font-variant-numeric:tabular-nums;min-width:26px;text-align:center;}
.seatrow__val--pending{color:var(--ink-faint);}
.seatrow__tot{font-family:var(--mono);font-size:var(--t-sm);color:var(--ink-dim);
  font-variant-numeric:tabular-nums;min-width:38px;text-align:right;}
.seatrow__drag{cursor:grab;color:var(--ink-faint);padding:0 var(--s-1);
  font-size:var(--t-lg);line-height:1;touch-action:none;}
.sortable-ghost{opacity:.4;}

/* ── round context strip ───────────────────────────────────── */
.ctx{
  background:var(--surface);border:1px solid var(--line);
  border-radius:var(--r-md);padding:var(--s-2) var(--s-3);margin-bottom:var(--s-3);
}
.ctx__top{display:flex;justify-content:space-between;align-items:baseline;gap:var(--s-2);}
.ctx__round{font-weight:700;font-size:var(--t-md);}
.ctx__cards{font-family:var(--mono);font-size:var(--t-xs);color:var(--ink-faint);}
.ctx__line{display:flex;gap:6px;align-items:center;flex-wrap:wrap;
  font-size:var(--t-xs);color:var(--ink-dim);margin-top:3px;}
.ctx__line .sep{color:var(--ink-faint);}

/* ── number pad ────────────────────────────────────────────── */
/* Size to the number of keys, capped at 5 per row — a 2-card round rendered
   three enormous blocks when the grid was fixed at 5 columns. */
.keys{
  display:grid;
  /* Cap the column width so a 1-card round doesn't render two enormous slabs
     on a desktop, while still filling a phone. */
  grid-template-columns:repeat(min(var(--keycount, 5), 5), minmax(0, 132px));
  gap:var(--s-2);margin-top:var(--s-3);margin-bottom:var(--s-2);
}
.key{
  min-height:52px;max-height:64px;
  border:1px solid var(--line);border-radius:var(--r-md);
  background:var(--surface);color:var(--ink);
  display:grid;place-items:center;cursor:pointer;
  font-family:var(--mono);font-size:var(--t-lg);font-weight:700;
}
.key:hover{border-color:var(--primary);}
.key--sel{background:var(--primary);border-color:var(--primary);color:var(--on-primary);}

/* ── score grid ────────────────────────────────────────────── */
.grid{width:100%;border-collapse:collapse;font-family:var(--mono);
  font-variant-numeric:tabular-nums;}
.grid th{
  font-family:var(--sans);font-size:var(--t-xs);font-weight:700;
  color:var(--ink-dim);text-align:center;padding:var(--s-1) 3px;
  border-bottom:2px solid var(--line);letter-spacing:.03em;
  position:sticky;top:0;background:var(--ground);
}
.grid th:first-child,.grid td:first-child{text-align:left;padding-left:4px;}
.grid td{text-align:center;padding:5px 3px;border-bottom:1px solid var(--line-soft);
  font-size:var(--t-sm);}
.grid td:first-child{color:var(--ink-faint);font-size:var(--t-xs);}
/* --ink-dim, not --ink-faint: at 10px the faint tone is ~2.5:1 against the
   page, well under WCAG AA, and this is the densest text in the product. */
.grid .g-bid{display:block;font-size:10.5px;color:var(--ink-dim);line-height:1.3;}
.g-rank{
  display:inline-block;font-size:9px;font-weight:700;
  color:var(--primary);vertical-align:super;margin-left:2px;
}
.grid .g-tot{font-weight:700;}
.grid tr.is-now td{background:var(--primary-wash);border-bottom-color:var(--primary);}
.grid tfoot td{border-top:2px solid var(--line);border-bottom:none;
  padding-top:var(--s-2);font-weight:700;font-size:var(--t-md);}

/* ── felt table ────────────────────────────────────────────── */
.felt{
  background:var(--felt);border:2px solid var(--felt-line);
  border-radius:50%/34%;aspect-ratio:1/.86;position:relative;
  margin:var(--s-5) 0 var(--s-4);max-width:420px;margin-inline:auto;
}
.felt__seat{position:absolute;width:78px;text-align:center;transform:translate(-50%,-50%);}
.felt__seat .av{margin:0 auto 4px;}
.felt__name{font-size:var(--t-xs);font-weight:700;color:var(--on-felt);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.felt__tag{font-size:10px;color:var(--felt-line);}
.felt__mid{position:absolute;inset:0;display:grid;place-content:center;text-align:center;}
.felt__hint{font-size:var(--t-xs);color:var(--on-felt);font-family:var(--mono);}

/* ── alerts ────────────────────────────────────────────────── */
.alert{
  border-radius:var(--r-md);padding:var(--s-3);margin-bottom:var(--s-3);
  font-size:var(--t-sm);border:1px solid transparent;
}
.alert--error{background:var(--negative-wash);color:var(--negative);border-color:var(--negative);}
.alert--ok{background:var(--positive-wash);color:var(--positive);border-color:var(--positive);}
.alert--info{background:var(--primary-wash);color:var(--primary);border-color:var(--primary);}
.alert--warn{background:var(--warning-wash);color:var(--warning);border-color:var(--warning);}

/* ── empty states ──────────────────────────────────────────── */
.empty{text-align:center;padding:var(--s-7) var(--s-4);color:var(--ink-dim);}
.empty__icon{font-size:44px;margin-bottom:var(--s-3);opacity:.55;}
.empty__title{font-size:var(--t-lg);font-weight:700;color:var(--ink);margin-bottom:var(--s-1);}

/* ── landing (§11c) ────────────────────────────────────────── */
.land{position:relative;overflow:hidden;min-height:calc(100vh - 60px);
  display:grid;place-items:center;}
.land__tiles{
  /* 🔴 fixed, not absolute. The landing renders inside .wrap, which caps at
     760px — so an absolutely positioned layer was only ever ~870px wide and
     the pattern stopped well short of the screen edges. Fixed escapes the
     wrap and covers the viewport at any resolution. */
  position:fixed;inset:-10%;
  display:grid;
  /* A FIXED column width, not minmax(...,1fr). With 1fr the cards stretched to
     fill whatever space was left, so their size drifted with the viewport and
     with the gap — exactly what "don't stretch the cards" rules out. Fixed
     tracks mean the gap alone controls density. */
  grid-template-columns:repeat(auto-fill,88px);
  justify-content:center;
  /* align-content defaults to `stretch`, which pours spare height into
     auto-sized rows; the cards have a fixed aspect-ratio so each sat at the
     top of an inflated row, leaving bands of empty space. */
  align-content:start;
  grid-auto-rows:min-content;
  gap:58px;
  animation:drift 90s ease-in-out infinite;
  pointer-events:none;z-index:0;
}
@keyframes drift{
  /* Translation only. Rotating meant the grid sat at whatever angle the
     animation happened to be at when you looked — reads as a rendering fault
     rather than as motion. */
  0%{transform:translate(0,0);}
  50%{transform:translate(-26px,-32px);}
  100%{transform:translate(0,0);}
}
.tile{aspect-ratio:.72;border-radius:10px;display:grid;place-items:center;
  font-size:24px;color:rgba(255,255,255,.92);box-shadow:var(--shadow-1);}
.tile--1{background:var(--primary);} .tile--2{background:var(--felt);}
.tile--3{background:var(--negative);} .tile--4{background:var(--warning);}
.tile--5{background:var(--positive);}
.land__card{
  position:relative;z-index:2;background:var(--surface);
  border:1px solid var(--line);border-radius:var(--r-lg);
  box-shadow:var(--shadow-3);padding:var(--s-6) var(--s-5);
  width:min(340px,88vw);text-align:center;
}
.land__logo{font-size:34px;line-height:1;margin-bottom:var(--s-2);}
.land__name{font-size:var(--t-xl);font-weight:700;letter-spacing:-.025em;line-height:1.1;}
.land__tag{font-size:var(--t-xs);letter-spacing:.1em;text-transform:uppercase;
  color:var(--ink-faint);margin-bottom:var(--s-4);}
.land__links{display:flex;flex-direction:column;gap:var(--s-1);
  margin-top:var(--s-4);font-size:var(--t-sm);}
.land__links a{color:var(--ink-dim);text-decoration:none;}
.land__ver{font-size:10px;color:var(--ink-faint);margin-top:var(--s-3);font-family:var(--mono);}

/* ── setup options ─────────────────────────────────────────── */
.opts{display:flex;gap:var(--s-2);}
.opt{flex:1;border:1px solid var(--line);border-radius:var(--r-md);
  background:var(--surface);padding:var(--s-3) var(--s-2);text-align:center;
  cursor:pointer;min-height:56px;}
.opt--on{border-color:var(--primary);border-width:1.5px;background:var(--primary-wash);}
.opt__t{font-size:var(--t-sm);font-weight:700;}
.opt__s{font-size:var(--t-xs);color:var(--ink-faint);font-family:var(--mono);}
.summary{border:1px dashed var(--line);border-radius:var(--r-md);
  padding:var(--s-3);text-align:center;margin:var(--s-4) 0;}
.summary__t{font-size:var(--t-sm);font-weight:700;}
.summary__s{font-size:var(--t-xs);color:var(--ink-dim);font-family:var(--mono);margin-top:2px;}

/* ── misc ──────────────────────────────────────────────────── */
.divider{height:1px;background:var(--line-soft);margin:var(--s-4) 0;}
.chip-toggle{font-size:var(--t-xs);font-weight:700;letter-spacing:.05em;
  padding:5px 9px;border-radius:var(--r-sm);border:1px dashed var(--line);
  color:var(--ink-faint);background:transparent;cursor:pointer;min-height:34px;}
.chip-toggle--on{background:var(--primary);color:var(--on-primary);
  border-style:solid;border-color:var(--primary);}
footer{border-top:1px solid var(--line);padding:var(--s-4) 0;
  font-size:var(--t-xs);color:var(--ink-faint);}
/* Feedback for something the user did — NOT for background polling. The round
   panel polls itself, so a blanket rule dimmed the whole panel every 2s, which
   is exactly the flicker people notice on a dark phone at a table. */
.htmx-request:not(.polls){opacity:.6;}
.polls, .polls.htmx-request{opacity:1;}

/* Tapping an entered value reopens its keypad — the affordance has to look
   like one, or a mistyped round reads as unfixable (learned the hard way). */
.seatrow__val--edit{
  background:transparent;border:1px dashed var(--line);border-radius:var(--r-sm);
  color:var(--ink);cursor:pointer;padding:4px 8px;min-height:34px;
  font-family:var(--mono);font-size:var(--t-lg);font-weight:700;
  font-variant-numeric:tabular-nums;
}
.seatrow__val--edit:hover{border-color:var(--primary);color:var(--primary);border-style:solid;}

/* A made bid is positive, a miss negative — semantic colour, never the accent
   (§11c). Blue means "interactive" in this system and shouldn't score a hand. */
.num--good{color:var(--positive);}

/* Game-specific readout under the round header — Wizard's running bid total.
   Tone is styling only; the platform never interprets the number (§2). */
.tally{
  display:flex;align-items:baseline;gap:var(--s-2);
  margin-top:var(--s-2);padding-top:var(--s-2);
  border-top:1px solid var(--line-soft);
}
.tally__value{
  font-family:var(--mono);font-variant-numeric:tabular-nums;
  font-size:var(--t-lg);font-weight:700;color:var(--ink);
}
.tally__label{font-size:var(--t-xs);color:var(--ink-dim);}
.tally--good .tally__value{color:var(--positive);}
.tally--warn .tally__value{color:var(--warning);}
.tally--bad  .tally__value{color:var(--negative);}
.tally--good .tally__label{color:var(--positive);}
.tally--warn .tally__label{color:var(--warning);}
.tally--bad  .tally__label{color:var(--negative);}

/* Game code — meant to be read aloud across a table, so it wants to be big,
   monospaced and widely tracked. */
.joincode{
  font-family:var(--mono);font-size:var(--t-xl);font-weight:700;
  letter-spacing:.22em;text-transform:uppercase;text-align:center;
}
.joincode-display{
  font-family:var(--mono);font-size:38px;font-weight:700;
  letter-spacing:.16em;color:var(--primary);
  margin:var(--s-2) 0 var(--s-1);
}

/* Footer toggle — quiet, but present enough to find if the behaviour surprises
   someone. Hidden entirely where the Wake Lock API isn't available. */
.linkbtn{
  background:none;border:none;padding:0;cursor:pointer;
  font-family:var(--sans);font-size:var(--t-xs);
  color:var(--ink-faint);text-decoration:underline;
}
.linkbtn:hover{color:var(--primary);}
.ok{color:var(--positive);font-weight:600;}

/* Seat rows carry up to four controls plus a drag handle. On a narrow phone
   that squeezes the name, which is the one thing that must stay readable. */
@media (max-width: 480px){
  .seatrow{gap:var(--s-2);padding:var(--s-2);}
  .seatrow .btn--sm{padding:6px 8px;min-width:34px;}
  .seatrow__name{font-size:var(--t-sm);}
  .seatrow__sub{font-size:10px;line-height:1.35;}
}

/* ── "it's you" ────────────────────────────────────────────────
   The keypad appearing is too quiet a signal for someone holding a phone
   while watching the cards. This is deliberately loud, and only ever shows
   on the device actually being waited on. */
.yourturn{
  display:flex;align-items:center;gap:var(--s-3);
  background:var(--primary);color:var(--on-primary);
  border-radius:var(--r-md);padding:var(--s-3) var(--s-4);
  margin-bottom:var(--s-3);font-size:var(--t-base);
  position:sticky;top:56px;z-index:10;box-shadow:var(--shadow-2);
}
.yourturn strong{font-weight:700;}
.yourturn__dot{
  width:10px;height:10px;border-radius:var(--r-full);
  background:var(--on-primary);flex-shrink:0;
  animation:turnpulse 1.6s ease-in-out infinite;
}
@keyframes turnpulse{
  0%,100%{opacity:1;transform:scale(1);}
  50%{opacity:.35;transform:scale(.75);}
}
/* The active seat should read as "yours", not merely "current". */
#roundpanel[data-your-turn="1"] .seatrow--active{
  border-color:var(--primary);border-width:2px;
  box-shadow:0 0 0 3px var(--primary-wash);
}

/* Waiting on someone else — informational, deliberately unlike .yourturn.
   No accent fill, no pulse: the host sees this constantly and it must not read
   as a summons. */
.waitingfor{
  display:flex;align-items:center;gap:var(--s-2);
  background:var(--surface);border:1px solid var(--line);
  border-left:3px solid var(--ink-faint);
  border-radius:var(--r-md);padding:var(--s-2) var(--s-3);
  margin-bottom:var(--s-3);
  font-size:var(--t-sm);color:var(--ink-dim);
}
.waitingfor strong{color:var(--ink);font-weight:600;}


/* ── phase tone ────────────────────────────────────────────────
   Bidding and trick entry looked identical apart from wording, which makes
   it easy to type a trick count while you still think you're bidding. The
   panel now retones: slate for predicting, felt green for recording what
   actually happened.

   Deliberately NOT the semantic green — --positive is brighter and means
   "made your bid". These never share a screen (made/missed only appears on
   the summary), and the felt tone is darker and more muted. */
#roundpanel{
  --phase:var(--primary);
  --phase-wash:var(--primary-wash);
  --on-phase:var(--on-primary);
}
#roundpanel[data-phase="tricks"]{
  --phase:var(--felt);
  --phase-wash:var(--felt-wash);
  --on-phase:var(--on-felt-solid);
}

.phasechip{
  display:inline-block;font-size:var(--t-xs);font-weight:700;
  letter-spacing:.08em;text-transform:uppercase;
  padding:3px 9px;border-radius:var(--r-full);
  background:var(--phase);color:var(--on-phase);
}

/* The context strip carries the tone as a rule down its edge. */
#roundpanel .ctx{border-left:4px solid var(--phase);}

/* Everything that signals "act here" follows the phase. */
#roundpanel .yourturn{background:var(--phase);color:var(--on-phase);}
#roundpanel .yourturn__dot{background:var(--on-phase);}
#roundpanel .seatrow--active{
  border-color:var(--phase);background:var(--phase-wash);
}
#roundpanel[data-your-turn="1"] .seatrow--active{
  box-shadow:0 0 0 3px var(--phase-wash);
}
#roundpanel .key--sel{
  background:var(--phase);border-color:var(--phase);color:var(--on-phase);
}
#roundpanel .key:hover{border-color:var(--phase);}
/* Every entered value carries the phase colour and a box — green while
   recording tricks, slate while bidding — because that describes the value,
   not who may edit it. Solid border = fixed; dashed = you can tap it. */
#roundpanel .seatrow__val--set{
  border:1px solid var(--phase);
  border-radius:var(--r-sm);
  color:var(--phase);
  padding:4px 8px;min-height:34px;
  display:inline-flex;align-items:center;justify-content:center;
  font-family:var(--mono);font-size:var(--t-lg);font-weight:700;
  font-variant-numeric:tabular-nums;
  background:transparent;
}
#roundpanel .seatrow__val--edit{
  border-style:dashed;
  cursor:pointer;
}
#roundpanel .seatrow__val--edit:hover,
#roundpanel .seatrow__val--edit:focus-visible{
  background:var(--phase-wash);
  border-style:solid;
}

/* During trick entry the bid is the number you're checking against — it was
   the faintest thing on the row. */
.bidref{font-size:var(--t-sm);color:var(--ink-dim);}
.bidref strong{
  font-family:var(--mono);font-size:var(--t-base);font-weight:700;
  color:var(--ink);font-variant-numeric:tabular-nums;
}
/* Host override for a seat whose owner hasn't entered yet — available, but
   not competing with the seats you're actually responsible for. */
.enterfor{
  display:block;margin:0 0 var(--s-2) auto;
  color:var(--ink-faint);font-size:var(--t-xs);
}
.enterfor:hover{color:var(--phase);}

/* ── row markers ───────────────────────────────────────────────
   Finding your own line at a glance matters more than anything else on this
   screen. Icons are inline SVG, not glyphs — a font-supplied pencil rendered
   with emoji presentation once already, and these sit next to text. */
.seatrow__name{display:flex;align-items:center;gap:6px;flex-wrap:wrap;}
.is-you{font-weight:800;}

.rowchip{
  display:inline-flex;align-items:center;gap:3px;
  font-size:10px;font-weight:700;letter-spacing:.05em;
  text-transform:uppercase;padding:2px 7px;border-radius:var(--r-full);
  white-space:nowrap;line-height:1.4;
}
.rowchip svg{width:11px;height:11px;fill:currentColor;flex-shrink:0;}

.rowchip--you{background:var(--phase, var(--primary));color:var(--on-phase, var(--on-primary));}
.rowchip--dealer{background:var(--raised);color:var(--ink-dim);border:1px solid var(--line);}
.rowchip--leads{background:transparent;color:var(--ink-faint);border:1px dashed var(--line);}

@media (max-width: 480px){
  .rowchip{font-size:9px;padding:2px 5px;}
  .rowchip--leads{display:none;}   /* dealer is the one that matters on a phone */
}


/* App bar on a narrow phone: the nav had no room to shrink, so it overflowed
   the gutter rather than compacting. */
.appbar__inner{min-width:0;}
.wordmark{flex-shrink:0;}
.navlinks{min-width:0;flex-wrap:wrap;justify-content:flex-end;row-gap:2px;}

@media (max-width: 520px){
  .navlinks{gap:var(--s-3);font-size:var(--t-xs);}
  .appbar__inner{padding:var(--s-2) 0;}
  .wordmark{font-size:var(--t-sm);}
}

/* Give the bottom of the page clearance on gesture-nav phones. */
body{padding-bottom:env(safe-area-inset-bottom);}


/* ══════════════════════════════════════════════════════════════
   Rider Back flourishes
   Ornament only — nothing here changes a control or a layout. Everything
   inherits currentColor so light and dark need one implementation.
   ══════════════════════════════════════════════════════════════ */

.orn{display:inline-block;vertical-align:middle;color:inherit;}
.orn--flourish{opacity:.5;}
.orn--corner{position:absolute;opacity:.28;pointer-events:none;}

/* Wordmark: the one place worth a display face and an ornament. */
.wordmark{
  font-family:var(--display);
  font-size:var(--t-lg);font-weight:600;letter-spacing:.01em;
  display:inline-flex;align-items:center;gap:6px;
}
.wordmark .orn--spade{color:var(--primary);opacity:.9;}

/* Page titles borrow the same face — restrained, not everywhere. */
h1{font-family:var(--display);font-weight:600;letter-spacing:-.01em;}
.land__name{font-family:var(--display);}
.ctx__round{font-family:var(--display);font-size:var(--t-md);}

/* An ornamental rule instead of a plain one, where a section actually breaks. */
.rule{
  display:flex;align-items:center;justify-content:center;
  gap:var(--s-3);margin:var(--s-5) 0;color:var(--ink-faint);
}
.rule::before,.rule::after{
  content:"";height:1px;flex:1;max-width:120px;
  background:linear-gradient(to right,transparent,var(--line),transparent);
}

/* ── Rider Back backdrop ──────────────────────────────────────
   A large ornamental medallion pinned bottom-right, plus a fine hatch across
   everything. Monochrome by construction: both inherit a single ink token, so
   dark mode needs no second artwork. Loud enough to notice, quiet enough that
   the numbers still win. */
.backdrop{
  position:fixed;
  right:-14%;bottom:-18%;
  width:min(96vw, 820px);height:min(96vw, 820px);
  z-index:-1;pointer-events:none;
  color:var(--ink);
  opacity:.085;
}
:root[data-theme="dark"] .backdrop{color:var(--primary);opacity:.17;}
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]) .backdrop{color:var(--primary);opacity:.17;}
}
@media (max-width: 640px){
  /* Big and low. Safe to be this prominent because every content block now
     sits on its own surface — the medallion only ever shows through the gaps
     between cards, which is where ornament belongs. */
  .backdrop{right:-20%;bottom:-10%;width:128vw;height:128vw;opacity:.075;}
  :root[data-theme="dark"] .backdrop{opacity:.15;}
}

/* Fine hatch, the way the card back fills its field. */
body::before{
  content:"";position:fixed;inset:0;z-index:-2;pointer-events:none;
  background-image:
    repeating-linear-gradient(45deg,  var(--ink) 0 .5px, transparent .5px 7px),
    repeating-linear-gradient(-45deg, var(--ink) 0 .5px, transparent .5px 7px);
  opacity:.05;
}
:root[data-theme="dark"] body::before{opacity:.07;}
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]) body::before{opacity:.07;}
}

/* Results reads as a printed sheet, corners and all. */
.sheet{position:relative;padding:var(--s-5) var(--s-4) var(--s-4);}
.sheet .orn--corner:nth-of-type(1){top:0;left:0;}
.sheet .orn--corner:nth-of-type(2){top:0;right:0;transform:scaleX(-1);}
.sheet h1{text-align:center;}
.sheet .rule{margin-top:var(--s-2);}
.empty__icon{color:var(--primary);opacity:.32;font-size:inherit;line-height:0;}


/* ── arched wordmark ──────────────────────────────────────────
   The Bicycle mark's signature move: name on a curve with a flourish beneath.
   SVG text, so the arc works without a webfont and stays crisp at any size. */
.wordmark{
  display:inline-flex;align-items:center;
  padding:0;line-height:0;color:var(--ink);
}
.wm{height:38px;width:auto;overflow:visible;}
.wm__text{
  font-family:var(--display);
  font-size:21px;font-weight:600;
  letter-spacing:.06em;
  fill:currentColor;
}
.wordmark:hover{color:var(--primary);}

/* The bar gets a double rule, the way a card border does. */
.appbar{
  border-bottom:1px solid var(--line);
  box-shadow:0 2px 0 -1px var(--line-soft);
}
@media (max-width: 520px){
  .wm{height:30px;}
  .wm__text{font-size:23px;}
}

/* ── the scoreboard is a sheet, not a transparency ─────────────
   The backdrop was showing straight through the score grid and crossing the
   numbers — the one thing ornament must never do. Everything else on the page
   already sits on --surface; the grid was the only thing that didn't. Giving
   it one fixes the legibility AND reads more like a printed score sheet. */
.sheetbox{
  background:var(--surface);
  border:1px solid var(--line);
  border-radius:var(--r-md);
  padding:var(--s-3);
}
.grid th{background:var(--surface);}

/* A hidden bid still occupies its box and still wears the phase colour — the
   table can see the seat has bid, just not what. Slightly dimmed so a screen
   of masks doesn't read as louder than a screen of real numbers. */
#roundpanel .seatrow__val--masked{
  letter-spacing:.12em;
  opacity:.75;
  cursor:default;
}

/* Explanation for the selected item in a choice field. Sits under the control
   so it can wrap — inside an <option> it was truncated when closed and made
   the open dropdown as wide as the longest sentence. */
.choicehelp{font-size:var(--t-xs);color:var(--ink-dim);line-height:1.45;}

/* Which partnership a seat belongs to, shown while entering rather than only
   on the results page — in a pairs game the score belongs to the pair. */
.rowchip--team{
  background:var(--primary-wash);
  color:var(--primary);
  border-color:transparent;
  font-variant-numeric:tabular-nums;
}

/* Free numeric entry, for games whose values are too many to tap: Hearts
   deals 26 points a hand, a Rummy hand can be worth several hundred. */
.numentry{display:flex;gap:var(--s-2);align-items:center;margin-top:var(--s-2);}
.numentry__field{
  font-family:var(--mono);font-size:var(--t-lg);font-weight:700;
  font-variant-numeric:tabular-nums;text-align:center;max-width:130px;
}

/* Visually hidden but still focusable and still announced. `hidden` would
   remove these from the tab order and the accessibility tree, which is how
   the scoring-mode tiles ended up pointer-only. */
.opt__input{
  position:absolute;
  width:1px;height:1px;
  margin:0;padding:0;border:0;
  opacity:0;
  pointer-events:none;
}
.opt:focus-within{
  outline:2px solid var(--primary);
  outline-offset:2px;
}

/* A scoresheet cell you can correct. Underlined on hover only — a grid of
   permanently decorated numbers reads as noise, and the affordance is needed
   at the moment you spot a wrong one, not before. */
.g-bid--fix{color:inherit;text-decoration:none;cursor:pointer;}
.g-bid--fix:hover,.g-bid--fix:focus-visible{
  text-decoration:underline dotted;
  text-underline-offset:2px;
}

/* A seat that has gone home. Still listed, still scored, no longer dealt to. */
.rowchip--out{
  background:var(--sunken);
  color:var(--ink-faint);
  border-color:transparent;
}

/* Elapsed-time clocks: how long the game has run, and how long the table has
   been waiting on whoever is up. Quiet by default — a clock that shouts turns
   a friendly game into a chess match. */
.clock{color:var(--ink-faint);font-variant-numeric:tabular-nums;}
.clock--turn{color:var(--ink-dim);font-weight:600;}
.clock--slow{color:var(--warning);}

/* The per-turn clock, worn as a chip beside You / Deals / Leads. Quiet until
   it has been a while, and never alarming — thinking is playing. */
.rowchip--clock{
  background:transparent;
  border-color:var(--line);
  color:var(--ink-faint);
  font-variant-numeric:tabular-nums;
  min-width:2.6em;
  text-align:center;
}
.rowchip--clock.clock--slow{color:var(--warning);border-color:var(--warning);}

/* Modals. A plain <dialog>, styled to match the cards it sits over. */
.modal{
  border:1px solid var(--line);
  border-radius:var(--r-lg);
  background:var(--ground);
  color:var(--ink);
  padding:var(--s-4);
  max-width:min(38rem, 92vw);
  width:100%;
  box-shadow:var(--shadow-3);
}
.modal::backdrop{background:rgba(0,0,0,.45);}
.modal__head{
  display:flex;align-items:center;justify-content:space-between;
  gap:var(--s-3);margin-bottom:var(--s-4);
}
.ctx__rules{margin-left:auto;}

/* ── analytics: charts, drawn as SVG on the server ─────────── */
/* No charting library: no build step and no CDN, so a few dozen lines of
   Jinja emit the shapes. They inherit these tokens, so they theme for free. */
.stat__note{margin-top:2px;}
.chart{margin:0 0 var(--s-4);}
.chart__cap{font-size:10px;letter-spacing:.07em;text-transform:uppercase;
  color:var(--ink-faint);margin-bottom:var(--s-2);display:flex;
  justify-content:space-between;align-items:baseline;gap:var(--s-2);}
.chart__cap .small{text-transform:none;letter-spacing:0;}
.chart__svg{display:block;width:100%;}
.chart__axis{display:flex;justify-content:space-between;margin-top:4px;}

/* Ranked list — reads better than a pie and survives a narrow screen. */
.rank{display:grid;grid-template-columns:minmax(5.5rem,32%) 1fr auto;
  align-items:center;gap:var(--s-2);padding:3px 0;}
.rank__key{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  font-size:var(--t-sm);}
.rank__bar{background:var(--sunken);border-radius:999px;height:9px;
  overflow:hidden;}
.rank__bar>span{display:block;height:100%;background:var(--primary);
  border-radius:999px;min-width:2px;}
.rank__n{font-size:var(--t-sm);text-align:right;white-space:nowrap;}

/* Hour-of-day grid. Opacity rather than hue, so it stays readable in both
   themes and for anyone who cannot separate red from green. */
.heat{display:grid;grid-template-columns:auto repeat(24,1fr);gap:2px;
  align-items:center;}
.heat__day{text-align:right;padding-right:4px;}
.heat__cell{aspect-ratio:1;background:var(--primary);border-radius:2px;
  min-height:9px;}
.heat__scale{display:flex;justify-content:space-between;margin-top:4px;
  padding-left:2.4rem;}

.map{display:block;width:100%;}
.map__sea{fill:var(--sunken);}
/* Land against sea needs real contrast — --raised on --sunken is a two-shade
   difference that disappears at map size. */
.map__land{fill:var(--surface);stroke:var(--line);stroke-width:.35;
  stroke-linejoin:round;}
.map__grid{stroke:var(--line-soft);stroke-width:.4;}
.map__equator{stroke:var(--line);stroke-width:.6;}
/* Drawn over the land, so it needs a rim to stay visible against it. */
.map__dot{fill:var(--primary);fill-opacity:.65;stroke:var(--surface);
  stroke-width:.6;}

/* Wide tables scroll inside their own box rather than the page. */
.tablewrap{overflow-x:auto;-webkit-overflow-scrolling:touch;}

/* Side-by-side panels that become a single column on a phone.
   NOT .opts — that is a non-wrapping flex row built for two or three radio
   tiles, and four cards of content in it run straight off the screen. */
.panels{display:grid;gap:var(--s-2);
  grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));align-items:start;}
/* 🔴 Grid and flex children default to min-width:auto, which refuses to
   shrink below their content — so an overflow-x box inside one pushes the
   whole page sideways instead of scrolling itself. */
.panels>*{min-width:0;}
.tablewrap{min-width:0;}

/* ── log levels ────────────────────────────────────────────── */
/* Colour carries the level, but never alone — the word is in the badge, so
   this reads the same to anyone who cannot separate red from amber. */
.lvl{font-family:var(--mono);font-size:10px;letter-spacing:.04em;}
.lvl--critical{background:var(--negative);color:#fff;border-color:var(--negative);}
.lvl--error{background:var(--negative-wash);color:var(--negative);
  border-color:var(--negative);}
.lvl--warning{background:var(--sunken);color:var(--ink-dim);}
.lvl--info,.lvl--debug{background:transparent;color:var(--ink-faint);}
