:root{
  --felt:#0B3D2B; --felt-2:#082E20; --felt-3:#051E15;
  --leather:#1E120C; --leather-2:#2B1A11; --stitch:#6E5A3E;
  --ivory:#F5EFE2; --ivory-2:#EAE2D0;
  --card-ink:#16130F; --card-ink-2:#5E574B;
  --gold:#C9A55C; --gold-2:#8E7340; --gold-soft:rgba(201,165,92,.35);
  --ruby:#8E1B2C; --sapphire:#1C3F6E; --amethyst:#4E2E6B;
  --emerald:#1B7049; --topaz:#8A5A12; --teal:#135B63;
  --on-felt:#E9E1CF; --on-felt-2:#A9B8AE;

  /* Tints, for depth and hairlines rather than new colours: the felt's own
     highlight (the pot's lit face), gold and shade laid thin over felt, and
     card ink laid thin over ivory. The lattice is the fine white cross-hatch
     on a card back. */
  --felt-sheen:#2A5A45;
  --gold-faint:rgba(201,165,92,.14);
  --felt-shade:rgba(0,0,0,.18); --felt-well:rgba(0,0,0,.2);
  --card-line:rgba(22,19,15,.13); --card-line-2:rgba(22,19,15,.32); --card-wash:rgba(22,19,15,.04);
  --lattice:rgba(255,255,255,.16);

  --shadow-card:0 1px 1px rgba(0,0,0,.25), 0 10px 22px -6px rgba(0,0,0,.55);
  --shadow-panel:0 1px 0 rgba(201,165,92,.12), 0 14px 30px -14px rgba(0,0,0,.7);
  --shadow-rail:0 8px 18px -4px rgba(0,0,0,.45);
  /* The pot's two engraved rings and its drop; a card back's ivory edge; the
     gold ring round a picked price, parted from its ink by a sliver of ivory. */
  --shadow-pot:0 0 0 1px var(--gold), 0 0 0 7px var(--felt-2), 0 0 0 8px var(--gold-2), 0 18px 34px rgba(0,0,0,.45);
  --shadow-back:0 0 0 1.5px var(--ivory), 0 3px 6px rgba(0,0,0,.4);
  --shadow-ring:0 0 0 2px var(--ivory), 0 0 0 3.5px var(--gold);
  /* The lit edge along the top of a jewel: a message's bubble. */
  --shadow-gleam:inset 0 1px 0 var(--lattice);

  --p1:var(--sapphire); --p2:var(--ruby); --p3:var(--amethyst);
  --p4:var(--emerald); --p5:var(--topaz); --p6:var(--teal);
  --on-member:var(--ivory);
}
*{box-sizing:border-box}
/* The felt: lit from above the column, falling to the dark edge of the
   table, on the root so it is the canvas itself, with the cloth's grain as a
   second layer of that same background. ONE PAINTED SURFACE, NO BLEND MODE:
   the grain used to be a fixed full-viewport layer with mix-blend-mode over a
   fixed gradient, which forces a repaint on every scroll frame (and iOS
   ignores fixed backgrounds anyway). The grain scrolls with the page, which
   nobody can tell of noise, and its alpha is baked low enough (pale sage at .04,
   about the lift the overlay blend gave on felt) that it reads as cloth and
   never lies on a card. */
html{
  color-scheme:dark;
  background:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 .2  0 0 0 0 1  0 0 0 0 .7  0 0 0 .04 0'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>") 0 0 / 160px 160px repeat scroll,
    radial-gradient(120% 100% at 50% 40%, var(--felt) 0%, var(--felt-2) 60%, var(--felt-3) 100%) fixed var(--felt-3);
}
body{
  margin:0; min-height:100vh; color:var(--on-felt);
  font-family:Figtree,ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
  font-size:15px; line-height:1.5; -webkit-font-smoothing:antialiased;
}
.wrap{max-width:780px; margin:0 auto; padding-inline:16px; padding-block:0 56px}
/* Every number is Bodoni, in lining figures that line up down a column. */
.num{
  font-family:"Bodoni Moda",Didot,"Bodoni 72",Georgia,serif;
  font-variant-numeric:tabular-nums lining-nums; font-feature-settings:"lnum","tnum";
}
/* The + or - of odds, in Figtree bold: Bodoni's hairline signs vanish, and
   the sign is what tells a favourite from an underdog. */
.sign{
  font-family:Figtree,ui-sans-serif,system-ui,sans-serif; font-weight:700;
  font-size:.72em; vertical-align:.12em; margin-right:.04em;
}

/* The masthead: a name set large in Bodoni, and a line under it. Every page
   has one; the pool page centres its own and hangs the suits under it
   (./pool.ts). */
header.mast{padding-block:28px 18px}
.mark{
  font-family:"Bodoni Moda",Didot,"Bodoni 72",Georgia,serif; font-weight:900; letter-spacing:-.01em;
  font-size:clamp(40px,12vw,60px); line-height:.95; margin:0; color:var(--ivory);
  font-variant-numeric:lining-nums; text-wrap:balance;
}
.mast .sub{
  display:flex; flex-wrap:wrap; gap:6px 14px; align-items:baseline;
  margin:12px 0 0; color:var(--on-felt-2); font-size:13.5px;
}
.mast .sub b{font-weight:600; color:var(--on-felt)}

/* ---- components: the page shell, the board and the slip. Colour here is token references only. ---- */

/* THE SITE HEADER (src/ui/site-header.ts) IS THE TABLE'S LEATHER RAIL, on
   every page, so nobody needs the back button. It spans the whole window
   (page() renders it outside the column) and lines its contents up with the
   780px column by padding, so the leather meets both edges of the screen
   without anything overflowing sideways. Sticky; every target 44px tall. */
.site-bar,.pool-tabs{
  padding-inline:max(16px, calc((100% - 748px) / 2));
  background:linear-gradient(var(--leather-2), var(--leather));
  border-bottom:1px solid var(--gold-soft); box-shadow:var(--shadow-rail);
}
.site-bar{
  position:sticky; top:0; z-index:5;
  display:flex; align-items:center; justify-content:space-between; gap:12px;
  min-height:60px;
}
/* The rail's lower edge: a stitched seam just inside it and a gold hairline
   under it. When the commissioner's tabs hang below the bar they are the
   lower edge, so the seam moves to them rather than splitting the rail.
   The bar keeps its shadow even then: once the tabs scroll away under it,
   the shadow is what still separates the sticky rail from the felt. */
.site-bar::after,.pool-tabs::after{
  content:""; position:absolute; left:10px; right:10px; bottom:5px;
  border-bottom:1px dashed var(--stitch); opacity:.7; pointer-events:none;
}
.site-bar:has(+ .pool-tabs){border-bottom:0}
.site-bar:has(+ .pool-tabs)::after{content:none}
.site-nav{display:flex; align-items:baseline; gap:10px; min-width:0}
.brand{
  font-family:"Bodoni Moda",Didot,"Bodoni 72",Georgia,serif; font-weight:700; font-size:23px; letter-spacing:.01em;
  color:var(--ivory); text-decoration:none; display:inline-flex; align-items:center; min-height:44px; flex:none;
}
.brand-pip{color:var(--gold); font-size:13px; margin:0 1px 0 3px; position:relative; top:-4px}
/* A block, not a flex box: text-overflow only ellipsizes a block's own
   text, and a long pool name must end in "…" rather than a clean cut. */
.crumb{
  font-family:"Bodoni Moda",Didot,"Bodoni 72",Georgia,serif; font-style:italic; font-weight:500; font-size:16px;
  color:var(--on-felt-2); text-decoration:none;
  display:block; line-height:44px; min-width:0;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.brand:hover,.crumb:hover{color:var(--gold)}
.site-bar a:focus-visible,.account-toggle:focus-visible,.pool-tab:focus-visible{
  outline:2px solid var(--gold); outline-offset:2px; border-radius:6px;
}

.account{position:relative; flex:none}
.account-toggle{
  list-style:none; cursor:pointer; display:grid; place-items:center;
  min-width:44px; min-height:44px;
}
.account-toggle::-webkit-details-marker{display:none}
/* A sapphire chip ringed in gold: the one piece of jewel on the rail. */
.account-initial{
  display:grid; place-items:center; width:34px; height:34px; border-radius:999px;
  background:var(--sapphire); color:var(--ivory); border:1px solid var(--gold);
  font-family:"Bodoni Moda",Didot,"Bodoni 72",Georgia,serif; font-weight:700; font-size:15px;
}
.account[open] .account-initial{outline:1px solid var(--gold); outline-offset:2px}
.account-menu{
  position:absolute; right:0; top:calc(100% + 8px); z-index:6; width:min(280px, calc(100vw - 32px));
  background:var(--felt-2); color:var(--on-felt); border:1px solid var(--gold-soft); border-radius:12px;
  box-shadow:var(--shadow-panel); padding:14px;
}
.account-email{margin:0 0 10px; color:var(--on-felt-2); font-size:13.5px; overflow-wrap:anywhere}
.account-signin{
  color:var(--gold); font-weight:600; text-decoration:none;
  display:inline-flex; align-items:center; min-height:44px;
}

/* A commissioner's two pages of their pool, the lower band of the rail:
   uppercase and letterspaced, the current one lit ivory over a gold
   underline, so it is marked by a line as well as a colour. */
.pool-tabs{position:relative; display:flex; gap:22px; padding-block:0 12px}
.pool-tab{
  display:inline-flex; align-items:center; min-height:44px; padding:0 2px;
  border-bottom:2px solid transparent; color:var(--on-felt-2);
  font-weight:600; font-size:13px; letter-spacing:.14em; text-transform:uppercase; text-decoration:none;
}
.pool-tab:hover{color:var(--ivory)}
.pool-tab[aria-current="page"]{color:var(--ivory); border-bottom-color:var(--gold)}


/* ---- the pool page's frame: masthead, section heads, standing, notices ---- */
.pool{padding-block:0 8px}
.pool > .mast{padding-block:32px 0; text-align:center}
/* A sentence with its numbers in it, not a row of chips: the base masthead's
   flex line would space every word apart. */
.pool > .mast .sub{display:block; margin-top:12px; line-height:1.6}
.pool > .mast .sub b.num{font-size:1.12em; font-weight:700; color:var(--ivory)}
/* The four suits on a gold rule that fades out at both ends. */
.ornament{
  display:flex; align-items:center; justify-content:center; gap:12px;
  margin:16px 0 0; color:var(--gold); font-size:11.5px; letter-spacing:.3em;
}
.ornament::before,.ornament::after{content:""; width:64px; height:1px; background:linear-gradient(90deg, transparent, var(--gold-soft))}
.ornament::after{transform:scaleX(-1)}

.section-head{display:flex; justify-content:space-between; align-items:baseline; gap:12px; margin:44px 0 16px}
.section-head h2{
  margin:0; color:var(--ivory); font-size:27px; font-weight:700; line-height:1.1;
  font-family:"Bodoni Moda",Didot,"Bodoni 72",Georgia,serif;
}
.section-count{flex:none; color:var(--on-felt-2); font-size:12px; font-weight:600; letter-spacing:.14em; text-transform:uppercase}
.hand-hint{margin:4px 0 0; color:var(--on-felt-2); font-size:12.5px; text-align:center}

/* THE STANDING IS A PANEL ON THE FELT, led by its word (src/ui/standing-banner.ts):
   "Final." in gold, "Provisional." in plain on-felt, because provisional is
   the ordinary state of a pool in play and gold is kept for what is settled.
   The word is the status; the colour is its second reading. */
.standing{
  margin:26px 0 0; padding:12px 14px; border:1px solid var(--gold-soft); border-radius:12px;
  background:var(--felt-shade); color:var(--on-felt-2); font-size:13.5px;
}
.standing b{font-weight:700; letter-spacing:.02em; color:var(--on-felt)}
.standing[data-standing="final"] b{color:var(--gold)}

/* Whether the page is live, said small under the pot; gold once it is. */
.connection{
  margin:14px 0 0; color:var(--on-felt-2); text-align:center;
  font-size:12px; font-weight:700; letter-spacing:.16em; text-transform:uppercase;
}
.connection[data-connection="open"]{color:var(--gold)}
/* A refused pick, said in words on a gold-edged panel: attention, never red. */
.notice{
  margin:14px 0 0; padding:10px 14px; border:1px solid var(--gold); border-radius:12px;
  background:var(--felt-shade); color:var(--on-felt); font-size:13.5px;
}
.live-note{margin:8px 2px 0; color:var(--gold); font-size:12.5px}

/* Focus is gold on the felt and never removed. The cards, which are ivory,
   ring it in the darker gold (./board.ts). */
.pool :is(a,button,textarea,summary):focus-visible{outline:2px solid var(--gold); outline-offset:2px}

/* ---- the pot: a round engraved medallion in the middle of the table ---- */
/* It sizes its number to its own width (container units), so the whole disc
   can shrink under 200% zoom rather than push the page sideways. */
.pot{
  position:relative; display:grid; place-items:center; container-type:inline-size;
  width:min(212px, 100%); aspect-ratio:1; margin:28px auto 0; border-radius:50%; text-align:center;
  background:radial-gradient(circle at 35% 30%, var(--felt-sheen), var(--felt) 55%, var(--felt-3));
  box-shadow:var(--shadow-pot);
}
.pot::before{content:""; position:absolute; inset:12px; border-radius:50%; border:1px solid var(--gold-soft); pointer-events:none}
.pot-face{position:relative; padding:0 12%}
/* The label sits under the lit part of the disc, where gold on the felt sheen
   measures 3.4:1; the ivory reads at better than 6:1 everywhere on the felt. */
.pot-label{margin:0; color:var(--on-felt); font-size:11.5px; font-weight:700; letter-spacing:.24em; text-transform:uppercase}
/* THE NUMBER FITS ITS DISC AT ANY LENGTH. --chars is its length, set inline by
   the server and by the live client alike (public/pot-words.js); the face
   leaves 76cqi, and a Bodoni bold figure runs about .62em wide (the sign and
   the commas are counted at that too, which is generous). The size is that
   width shared out over the characters, capped at 44px, so a short pot is
   large and a twenty-character one is small but never leaves the ring. */
.pot-number{
  margin:4px 0 2px; color:var(--ivory); font-weight:700; line-height:1.05; white-space:nowrap;
  font-size:min(44px, calc(76cqi / (var(--chars, 6) * .62)));
}
.pot-meta{margin:0; color:var(--on-felt-2); font-size:12px; font-weight:600; line-height:1.35}
.pot-status{margin:6px 0 0; color:var(--on-felt); font-size:11.5px; font-weight:700; letter-spacing:.2em; text-transform:uppercase}
/* HIT IS GOLD; BUSTED IS STRUCK THROUGH AND MUTED. Both are said in words
   first, under the number; the colour and the line are second readings. */
.pot[data-state="hit"] .pot-status{color:var(--gold)}
.pot[data-state="busted"] .pot-status{color:var(--on-felt-2)}
.pot[data-state="busted"] .pot-number{color:var(--on-felt-2); text-decoration:line-through; text-decoration-thickness:2px}
.pot[data-state="empty"] .pot-meta{
  margin-top:6px; color:var(--on-felt); font-size:16px; font-style:italic; font-weight:500;
  font-family:"Bodoni Moda",Didot,"Bodoni 72",Georgia,serif;
}

/* ---- your hand: each dealt game is a card ---- */
.boards{display:grid; grid-template-columns:minmax(0,1fr); gap:24px}
.board-slot[hidden]{display:none}
/* THE SUIT SETS THE INK OF THE CARD'S FACE, as on a real deck: hearts and
   diamonds in ruby, spades and clubs in black. It is decoration, and says
   nothing a reader needs (every index is aria-hidden). Each suit also lies at
   its own slight angle, so a hand reads as cards dealt, not panels stacked. */
.board{
  position:relative; color:var(--card-ink); --suit:var(--card-ink);
  background:linear-gradient(180deg, var(--ivory), var(--ivory-2));
  border-radius:14px; box-shadow:var(--shadow-card); padding:18px 18px 56px;
}
.board::before{
  content:""; position:absolute; inset:6px; border:1px solid var(--gold); border-radius:9px;
  opacity:.7; pointer-events:none;
}
.board[data-suit="hearts"],.board[data-suit="diamonds"]{--suit:var(--ruby)}
.board[data-suit="hearts"]{rotate:-.6deg}
.board[data-suit="spades"]{rotate:.5deg}
.board[data-suit="diamonds"]{rotate:-.3deg}
.board[data-suit="clubs"]{rotate:.7deg}

.board-head{position:relative; display:flex; align-items:flex-start; gap:12px}
.index{
  display:flex; flex-direction:column; align-items:center; flex:none; min-width:42px;
  font-family:"Bodoni Moda",Didot,"Bodoni 72",Georgia,serif; font-weight:900; line-height:.9;
}
.index-team{font-size:22px; letter-spacing:.01em; color:var(--card-ink)}
.index-pip{margin-top:5px; font-size:15px; color:var(--suit)}
.index-foot{position:absolute; right:18px; bottom:14px; rotate:180deg}
.board-when{display:flex; flex-direction:column; gap:2px; min-width:0; padding-top:2px}
.board-head .matchup{color:var(--card-ink); font-size:11.5px; font-weight:700; letter-spacing:.14em; text-transform:uppercase}
.board-head time{color:var(--card-ink-2); font-size:12.5px}

/* THE FACE IS THE PICK: the player large in Bodoni, the price in the suit's
   ink, and the market and book in small capitals under it. */
.picked-summary,.face-empty{margin:20px 0 8px; text-align:center}
.picked-summary{display:flex; flex-direction:column; align-items:center; gap:3px}
.picked-summary strong{
  color:var(--card-ink); font-size:27px; font-weight:700; line-height:1.1; text-wrap:balance;
  font-family:"Bodoni Moda",Didot,"Bodoni 72",Georgia,serif;
}
.picked-summary .num{color:var(--suit); font-size:34px; font-weight:700; line-height:1.1}
.face-line{color:var(--card-ink-2); font-size:11.5px; font-weight:700; letter-spacing:.16em; text-transform:uppercase}
.face-empty{
  color:var(--card-ink-2); font-size:21px; font-style:italic; font-weight:500;
  font-family:"Bodoni Moda",Didot,"Bodoni 72",Georgia,serif;
}

.market-tabs{display:flex; justify-content:center; gap:20px; margin-top:12px; border-bottom:1px solid var(--card-line)}
.tab{
  display:inline-flex; align-items:center; min-height:44px; margin-bottom:-1px; padding:0 2px;
  border-bottom:2px solid transparent; color:var(--card-ink-2); text-decoration:none;
  font-size:12px; font-weight:700; letter-spacing:.12em; text-transform:uppercase;
}
.tab:hover{color:var(--card-ink)}
.tab.selected{color:var(--card-ink); border-bottom-color:var(--gold-2)}

/* ONE DECLARATION SIZES BOTH THE HEADER CELL AND THE PRICE CELL BENEATH IT, on
   a fixed table layout, so the two books stay the same width as each other and
   the header cannot drift off the column it labels. Comparing one book against
   the other down a column is the activity; a column that resizes per row, or a
   label a few pixels out of place, defeats it. */
.rows{width:100%; table-layout:fixed; border-collapse:collapse; margin-top:4px}
.rows th,.rows td{padding:7px 6px; border-top:1px solid var(--card-line); text-align:left}
.rows thead th{
  border-top:0; padding-block:12px 6px; color:var(--card-ink-2);
  font-size:11.5px; font-weight:700; letter-spacing:.12em; text-transform:uppercase;
}
.rows th.book-col,.rows td.price{width:94px; text-align:right}
.rows tr.row:hover{background:var(--card-wash)}
td.player{
  font-family:"Bodoni Moda",Didot,"Bodoni 72",Georgia,serif; font-size:15.5px; font-weight:700;
  line-height:1.2; overflow-wrap:anywhere;
}
.team{font-family:Figtree,ui-sans-serif,system-ui,sans-serif; color:var(--card-ink-2); font-size:11.5px; font-weight:700; letter-spacing:.1em}
.price{font-size:15px; vertical-align:middle}
.price.not-offered{color:var(--card-ink-2); font-size:12px; font-style:italic; line-height:1.25; overflow-wrap:anywhere}
.pick{
  width:100%; min-height:44px; padding:6px 10px; border:1px solid var(--card-line-2); border-radius:8px;
  background:var(--ivory); color:var(--card-ink); font:inherit; font-weight:700; text-align:right; cursor:pointer;
}
.pick:hover{border-color:var(--gold-2)}
/* YOUR PICK INVERTS: ink stock, an ivory numeral, a gold ring. The ring and
   the inversion both say it, and so does the card's face above. */
.pick.current{background:var(--card-ink); border-color:var(--card-ink); color:var(--ivory); box-shadow:var(--shadow-ring)}
/* A leg that locks while the page is open keeps its buttons -- the client
   disables them rather than rebuilding markup it does not own (see
   renderLocks in public/pool.js) -- so a disabled pick has to stop
   looking like something to press. */
/* aria-disabled is a press in flight (public/pool.js): the button keeps focus,
   which the disabled attribute would take away, and looks the same. */
.pick:disabled,.pick[aria-disabled="true"]{background:transparent; border-color:var(--card-line); color:var(--card-ink-2); cursor:default}
.pick:disabled:hover,.pick[aria-disabled="true"]:hover{border-color:var(--card-line)}
/* Losing the affordance must not lose the answer to "which one was mine?" --
   a leg locks with its pick still on it, and that is the cell to keep lit. */
.pick.current:disabled,.pick.current[aria-disabled="true"]{background:var(--card-ink); border-color:var(--card-ink); color:var(--ivory)}
/* THE SIGN OF EVERY PRICE IS SET IN FIGTREE BOLD, as the spec asks: Bodoni's
   plus and minus are hairlines, and the sign is what tells a favourite from
   an underdog. The prices are one text run each (the client rewrites some of
   them, and a hyphenated price in the text is what the tests read), so the
   sign is reached as the number's first letter rather than wrapped in a
   span; the pot, which builds its own markup, uses .sign. */
.pick .num{display:inline-block}
.pick .num::first-letter,td.price.num::first-letter,.picked-summary .num::first-letter{
  font-family:Figtree,ui-sans-serif,system-ui,sans-serif; font-weight:700; font-size:.72em; vertical-align:.12em;
}
/* Gold would vanish on ivory; the cards ring focus in the darker gold. */
.board :is(a,button):focus-visible{outline:2px solid var(--gold-2); outline-offset:2px}

/* A CARD SHUTS ONCE ITS VIEWER HAS PICKED OR ITS GAME HAS LOCKED (src/ui/board.ts,
   public/pool-cards.js): the header, the face and the corner rank stay, the
   market tabs and prices go behind a toggle. Three cards then fit a phone's
   first scroll, and the hand reads as a hand rather than a stack of forms.
   The body is hidden with the attribute, so it leaves the tab order as well
   as the page; the author rules below only make sure nothing overrides it. */
.board-body[hidden],.board-toggle[hidden]{display:none}
.board[data-collapsed="true"]{padding-block:14px 8px}
.board[data-collapsed="true"] :is(.picked-summary,.face-empty){margin-block:6px 2px}
/* The toggle keeps clear of the upside-down rank in the far corner, so the
   two share the card's foot instead of stacking. */
.board-toggle{
  display:flex; align-items:center; gap:9px; width:fit-content; min-width:44px; min-height:44px; margin:4px 58px 0 0;
  padding:0 14px; border:1px solid var(--card-line-2); border-radius:8px; background:transparent; color:var(--card-ink);
  font:inherit; font-size:12.5px; font-weight:700; letter-spacing:.08em; text-transform:uppercase; cursor:pointer;
}
.board-toggle:hover{border-color:var(--gold-2)}
.board-toggle::after{
  content:""; flex:none; width:10px; height:6px; background:currentColor; clip-path:polygon(0 0,100% 0,50% 100%);
}
.board-toggle[aria-expanded="true"]::after{rotate:180deg}

/* LOCKED IS SAID THREE WAYS AT ONCE -- the word, stamped in the card's
   corner, a dashed gilt border, and muted prices -- so a reader who cannot
   see one still reads another. The stamp's word is the header's data-stamp:
   "Locked", or Won, Lost or Voided once the viewer's leg is graded. */
.board-slot[data-locked="true"] .board::before{border-style:dashed}
.board-slot[data-locked="true"] .board-head::after{
  content:attr(data-stamp); margin-left:auto; padding:3px 9px; rotate:4deg;
  border:1.5px solid var(--card-ink-2); border-radius:4px; color:var(--card-ink-2);
  font-size:11.5px; font-weight:700; letter-spacing:.18em; text-transform:uppercase;
}
.board-slot[data-locked="true"] .price{color:var(--card-ink-2)}

/* ---- card backs: identity as a jewel and a lattice ---- */
/* --accent is set inline on .backs (or on the slip that holds them) to one of
   --p1..--p6. */
.backs{display:flex}
.back{
  width:24px; height:34px; margin-right:-10px; border-radius:4px; box-shadow:var(--shadow-back);
  background-color:var(--accent);
  background-image:
    repeating-linear-gradient(45deg, var(--lattice) 0 1px, transparent 1px 5px),
    repeating-linear-gradient(-45deg, var(--lattice) 0 1px, transparent 1px 5px);
}
.back:last-child{margin-right:0}

/* ---- the table: one seat per member ---- */
/* --accent is set inline per member to one of --p1..--p6, on the slip; the
   two card backs in its head wear it. Identity is those backs and the name,
   never a coloured edge (the No Side Stripes Rule). The viewer's own seat is
   the one that glows, faintly, in gold. */
.slips{display:grid; gap:14px}
.slip{border:1px solid var(--gold-faint); border-radius:14px; overflow:hidden; background:var(--felt-shade)}
article.member[data-mine="true"] .slip{
  border-color:var(--gold-soft);
  background:linear-gradient(90deg, var(--gold-faint), transparent 70%), var(--felt-shade);
}

.slip-head{display:grid; grid-template-columns:auto 1fr auto; align-items:center; gap:2px 14px; padding:14px}
/* A seat still holding a card shows its backs face down and dim; the words
   under the name say why. */
.slip:has(.price-line.pending) .back{opacity:.4}
.seat-name{min-width:0; overflow-wrap:anywhere}
.slip-head .member{
  color:var(--ivory); font-size:19px; font-weight:700; line-height:1.15;
  font-family:"Bodoni Moda",Didot,"Bodoni 72",Georgia,serif;
}
.slip-head .you{margin-left:6px; color:var(--gold); font-size:11.5px; font-weight:700; letter-spacing:.16em; text-transform:uppercase; vertical-align:2px}
/* THE PRICE SITS AT THE SEAT'S RIGHT: the number large, the book under it.
   A seat without one says why in words under the name instead. */
.price-line{margin:0; color:var(--on-felt-2); font-size:11.5px; font-weight:600; text-align:right}
.price-line .num{display:block; color:var(--ivory); font-size:25px; font-weight:700; line-height:1.05}
.price-line.pending,.price-line.none{
  grid-column:2 / -1; color:var(--gold); text-align:left;
  font-family:"Bodoni Moda",Didot,"Bodoni 72",Georgia,serif; font-size:14px; font-style:italic; font-weight:500;
}

/* The legs, on a darker well of felt under the head, between gold hairlines. */
.legs{list-style:none; margin:0; padding:0 14px; border-top:1px solid var(--gold-faint); background:var(--felt-well)}
.leg{
  display:flex; flex-wrap:wrap; align-items:baseline; gap:2px 8px;
  padding:10px 0; border-top:1px solid var(--gold-faint); color:var(--on-felt); font-size:13.5px;
}
.leg:first-child{border-top:0}
.leg strong{color:var(--ivory); font-family:"Bodoni Moda",Didot,"Bodoni 72",Georgia,serif; font-size:15.5px; font-weight:700}
.leg .num{color:var(--ivory); font-weight:700}
/* The price's sign in Figtree bold, reached as its first letter: see ./board.ts. */
.leg .num{display:inline-block}
.leg .num::first-letter,.price-line .num::first-letter{
  font-family:Figtree,ui-sans-serif,system-ui,sans-serif; font-weight:700; font-size:.72em; vertical-align:.12em;
}
.leg .matchup{min-width:76px; color:var(--on-felt-2); font-size:12px; font-weight:700; letter-spacing:.1em; text-transform:uppercase}
.leg .book{color:var(--on-felt-2); font-size:12px}
/* Unpicked is said in words ("not picked"), muted and italic. */
.leg.unpicked .status{color:var(--on-felt-2); font-size:12.5px; font-style:italic}

/* THE GRADED VIEW REPORTS DOUBT RATHER THAN HIDING IT. Won is gold; lost is
   muted; a leg settleLeg could not resolve (src/rules/settle.ts) stays in
   full ivory and italics, so the eye stops on the leg that needs a person
   rather than sliding past it as an ordinary loss. Every one is a word first. */
.leg-status{margin-left:auto; font-size:12px; font-weight:700}
.leg-status[data-status="won"]{color:var(--gold)}
.leg-status[data-status="lost"]{color:var(--on-felt-2)}
.leg-status[data-status="unresolved"]{color:var(--ivory); font-style:italic}
/* A voided leg is off the slip: its pick is struck through, not hidden, so
   a member can still see what was there and the line below says who took
   it off. */
.leg-status[data-status="voided"]{color:var(--on-felt-2)}
/* An ambiguous leg in a settled pool that no answer could change (a slip
   already lost, or never a bet): muted like a voided leg, because nobody is
   going to answer it. */
.leg-status[data-status="moot"]{color:var(--on-felt-2)}
.leg.voided strong,.leg.voided .num,.leg.voided .book{text-decoration:line-through; color:var(--on-felt-2)}
.leg-resolution{flex-basis:100%; color:var(--on-felt-2); font-size:12px}

.slip-verdict{
  margin:0; padding:11px 14px; border-top:1px solid var(--gold-faint);
  font-size:13.5px; font-weight:700; letter-spacing:.02em;
}
.slip-verdict[data-verdict="won"]{color:var(--gold)}
.slip-verdict[data-verdict="lost"]{color:var(--on-felt-2)}
.slip-verdict[data-verdict="unresolved"]{color:var(--ivory); font-style:italic}
.slip-verdict[data-verdict="none"]{color:var(--on-felt-2); font-weight:500}

/* ---- buttons and links ---- */
.btn{
  display:block; width:100%; min-height:44px; padding:10px 16px;
  border:1px solid var(--gold); border-radius:10px;
  background:var(--ivory); color:var(--card-ink);
  font:inherit; font-weight:700; letter-spacing:.02em; text-align:center; cursor:pointer;
}
.btn:hover{background:var(--ivory-2)}
.btn:active{translate:0 1px}
.btn:disabled{opacity:.55; cursor:progress}
.btn:focus-visible{outline:2px solid var(--gold); outline-offset:3px}
.btn + .btn{margin-top:10px}
/* The secondary: nothing to fill, a gold outline. It is what a second or a
   turn-it-off control looks like, so the ivory one is always the way on. */
.btn-quiet{background:transparent; color:var(--gold)}
.btn-quiet:hover{background:var(--gold-faint)}

.link{color:var(--gold); font-weight:600; text-underline-offset:3px}
.link:hover{color:var(--ivory)}

/* ---- auth pages: sign in, link failures, signed out, join. Narrower than
   the pool page -- one form or one sentence, never a board or a slip. ---- */
.auth{max-width:420px; margin:0 auto}
/* The wordmark large above the card, and one italic line saying which door
   this is. */
.auth > .mast{padding-block:44px 24px; text-align:center}
/* The floor is low so the wordmark shrinks with the window rather than
   holding at 56px (204px of Bodoni, wider than a phone at 200% zoom); it is
   20vw from about 180px up, so no ordinary phone sees it. */
.auth .mark{font-size:clamp(36px,20vw,88px); overflow-wrap:anywhere}
.auth .mast .sub{
  display:block; margin-top:14px; color:var(--on-felt-2); line-height:1.35;
  font-family:"Bodoni Moda",Didot,"Bodoni 72",Georgia,serif; font-size:20px; font-style:italic; font-weight:500;
}
.auth .mast .sub b{color:var(--ivory); font-style:normal; font-weight:700}

/* A card on the table: ivory stock and the gilt inner border of the boards
   (./board.ts). On ivory the ink is card ink and the primary button
   inverts, because an ivory button would vanish into an ivory card. */
.auth-card{
  position:relative; padding:26px 24px; color:var(--card-ink);
  background:linear-gradient(180deg, var(--ivory), var(--ivory-2));
  border-radius:14px; box-shadow:var(--shadow-card);
}
.auth-card::before{
  content:""; position:absolute; inset:6px; border:1px solid var(--gold); border-radius:9px;
  opacity:.7; pointer-events:none;
}
.auth-card p{color:var(--card-ink-2); margin:0}
.auth-sent{color:var(--card-ink-2); margin:0}
.auth-card .link{color:var(--card-ink); text-decoration-color:var(--gold-2)}
.auth-card .link:hover{color:var(--card-ink-2)}
.auth-card .btn{background:var(--card-ink); color:var(--ivory)}
.auth-card .btn:hover{background:var(--card-ink-2)}
.auth-card :is(a,button,input):focus-visible{outline-color:var(--gold-2)}
/* A refusal said in words in card ink, on a wash of it: never red. */
.auth-card .outcome{margin-top:14px; background:var(--card-wash); border-color:var(--card-line-2); color:var(--card-ink)}

/* Fields are ivory-2 with a card-ink-2 edge, on a card or on a felt panel. A
   select sits alongside the input because the new-pool form offers the slate
   as a list of the dates D1 actually has games for, never a free date field
   -- see renderNewPoolForm in src/ui/new-pool-form.ts. 16px so a phone does
   not zoom the page on focus. */
.auth-field{display:flex; flex-direction:column; gap:6px; margin-bottom:14px}
.auth-field label{color:var(--on-felt-2); font-size:11.5px; font-weight:700; letter-spacing:.14em; text-transform:uppercase}
.auth-card .auth-field label{color:var(--card-ink-2)}
.auth-field :is(input,select){
  min-height:44px; padding:9px 12px; border:1px solid var(--card-ink-2); border-radius:8px;
  background:var(--ivory-2); color:var(--card-ink); font:inherit; font-size:16px;
}
.auth-field :is(input,select):focus-visible{outline:2px solid var(--gold); outline-offset:2px}
/* Declared after the gold rule above, and one class more specific: on ivory
   gold measures 2.03:1, under the 3:1 a focus indicator needs, and gold-2 is
   about 3.9:1. The felt panels keep gold, which reads there. */
.auth-card .auth-field :is(input,select):focus-visible{outline-color:var(--gold-2)}

/* ---- the commissioner console and the landing page: felt panels of one
   subject each, a roster that says accepted or pending in words, and the
   controls that each spend something real. ---- */
.home,.console{padding-block:0 8px}
/* The console's masthead is the pool page's: the name centred, one line under
   it. Block, not the base masthead's flex line, which would space "slate",
   the date and the status apart and strand the link on a line of its own. */
.console > .mast{padding-block:32px 0; text-align:center}
.console > .mast .sub{display:block; margin-top:12px; line-height:1.7}
/* A panel is a darker tint of the felt under a gold hairline, never a second
   card colour: ivory is kept for cards and forms laid on the table. */
.panel{
  background:var(--felt-shade); border:1px solid var(--gold-soft); border-radius:14px;
  box-shadow:var(--shadow-panel); padding:18px 18px 20px; margin-top:18px;
}
.panel-title{
  margin:0 0 12px; color:var(--ivory); font-size:22px; font-weight:700; line-height:1.15;
  font-family:"Bodoni Moda",Didot,"Bodoni 72",Georgia,serif;
}
.people{list-style:none; margin:0 0 14px; padding:0}
.people:last-child{margin-bottom:0}
.person{
  display:flex; flex-wrap:wrap; align-items:baseline; gap:2px 8px;
  padding:9px 0; border-top:1px solid var(--gold-faint); font-size:14px;
  overflow-wrap:anywhere;
}
.person b{color:var(--ivory)}
.meta{color:var(--on-felt-2); font-size:12.5px}
/* The colour is a SECOND reading of a fact the markup already states in
   words ("Accepted", "Pending" -- src/ui/commissioner-roster.ts), never the
   only one: a commissioner opens this page to find out who has not joined
   yet. Pending is ivory italic, the console's mark for "look at this". */
.meta[data-state="accepted"]{color:var(--gold); font-weight:600}
.meta[data-state="pending"]{color:var(--ivory); font-weight:600; font-style:italic}
/* Nobody seated, no invites sent, no pools yet, no slate stored. Every one of
   these is an ordinary state of an invite-only product, so none of them is
   styled as a warning. */
.empty{margin:0; color:var(--on-felt-2); font-size:14px}
.empty + .btn{margin-top:14px}
/* The fresh join link and its Share button, under the button that made them. */
[data-join-link-out]{margin-top:16px}

/* THE ONE CONTROL AT THE FOOT OF A PANEL THAT CANNOT BE TAKEN BACK (deal, turn
   the link off, spend odds credits, send mail): on a row of its own, under a
   hairline and clear of anything above it, so a thumb sliding down the page
   never lands on it by accident. The clearance is the field's or the note's
   own margin plus a little, not a band of its own; and directly under a
   heading there is nothing to be clear of, so the hairline goes with it
   instead of drawing a rule under a title with an empty strip between. */
.panel-foot{margin-top:6px; padding-top:18px; border-top:1px solid var(--gold-faint)}
.panel-title + .panel-foot{margin-top:0; padding-top:0; border-top:0}
.outcome + .panel-foot{margin-top:16px}

/* A leg the rules could not grade: the question on its own line under the
   member's pick, then one full-width button per answer the route accepts. */
.note{flex-basis:100%; margin:4px 0 0}
.choices{flex-basis:100%; display:grid; gap:8px; margin:8px 0 4px}
/* What became of the click, right under the buttons that were pressed. */
.person > .outcome{flex-basis:100%; margin:4px 0}

/* Address and name for one invitee, side by side while there is room. */
.invite-row{display:flex; flex-wrap:wrap; gap:0 12px}
/* min(): a 200px basis is wider than the panel at 200% zoom; it may shrink to
   the row and wrap onto a line of its own instead of pushing the page sideways. */
.invite-row .auth-field{flex:1 1 min(200px,100%); min-width:0}

/* The new-pool form's sport beside its slate date, one under the other on a phone. */
.slate-row{display:flex; flex-wrap:wrap; gap:0 12px}
.slate-row .auth-field{flex:1 1 140px}

/* THE ONLY TRACE A PARTIAL BATCH LEAVES. POST /p/:slug/invite answers a
   mid-batch refusal with both invited and refused lists, and some of that mail
   has already gone out; this is where the console prints both lists. Plain
   gold text under the control that was pressed, not a box: it sits inside a
   panel already, and a bordered card in a card is what a reader stops seeing.
   Gold is attention, never an error tone. */
.outcome{margin:14px 0 0; color:var(--gold); font-size:14px; font-weight:600; line-height:1.45; overflow-wrap:anywhere}
.outcome[hidden]{display:none}

/* ---- the landing page: your tables ---- */
.home > .mast{padding-block:36px 0; text-align:center}
.home > .mast .sub{display:block; margin-top:12px}

.pool-list{list-style:none; margin:0; padding:0}
/* The whole row is the way in: the name's link stretches over it, and the
   console link sits above that stretch. */
.pool-entry{
  position:relative; display:flex; flex-wrap:wrap; align-items:center; gap:0 18px;
  padding:14px 0; border-top:1px solid var(--gold-faint);
}
.pool-entry:first-child{border-top:0; padding-top:0}
.pool-entry:last-child{padding-bottom:0}
.pool-entry .backs{flex:none; padding-left:2px}
/* At 200% zoom the console link wraps under the name rather than pushing the row wider than the screen. */
.pool-info{flex:1 1 9rem; min-width:0}
.pool-title{display:block}
.pool-title .link{
  color:var(--ivory); font-size:22px; font-weight:700; line-height:1.15; text-decoration:none; overflow-wrap:anywhere;
  font-family:"Bodoni Moda",Didot,"Bodoni 72",Georgia,serif;
}
.pool-title .link:hover{color:var(--gold)}
.pool-title .link::after{content:""; position:absolute; inset:0}
.pool-title .link:focus-visible{outline:none}
.pool-entry:has(.pool-title .link:focus-visible){outline:2px solid var(--gold); outline-offset:4px; border-radius:6px}
.pool-title .sport-tag{margin-right:8px; vertical-align:.18em}
.pool-line{margin:4px 0 0}
.console-link{
  position:relative; z-index:1; display:inline-flex; align-items:center; min-height:44px; padding:0 4px;
  font-size:12px; letter-spacing:.14em; text-transform:uppercase;
}
/* Which league a pool plays: a person can be in pools of both. */
.sport-tag{
  padding:1px 7px; border:1px solid var(--gold); border-radius:4px;
  color:var(--gold); font-size:11.5px; font-weight:700; letter-spacing:.16em;
}

/* A PHONE IS THE COMMON CASE, NOT THE EDGE. Six people check a slate on a
   Sunday from the sofa. The book columns narrow rather than the table
   overflowing: table-layout:fixed means the player name is what gives way,
   and it wraps, so both prices stay on screen and stay aligned. */
@media (max-width:420px){
  .wrap{padding-inline:12px}
  .board{padding:16px 13px 52px}
  .rows th,.rows td{padding:6px 3px}
  /* 88px, not 80: "DraftKings" set at the 11.5px floor is 79px of ink even with its tracking eased, and the header must not spill into the player's column. */
  .rows th.book-col,.rows td.price{width:88px}
  .rows thead th{letter-spacing:.06em}
  .price{font-size:14px}
  .price.not-offered{font-size:12px}
  .pick{padding:6px 7px}
  .leg .matchup{min-width:0}
  .section-head{margin-top:36px}
  .panel{padding:13px 14px}
}

/* AT 200% ZOOM ON A PHONE (about 188px) TWO PRICE COLUMNS AND A NAME CANNOT
   SHARE A ROW: the books' headers alone are 60px each, which leaves the player
   a few letters. So a row becomes a small card of its own: the name across the
   top, then the two prices side by side, each button naming its book above the
   price (the header row, which no longer sits over the columns, is hidden from
   sight but kept for a screen reader). The name wraps at its full width, every
   button stays 44px tall, and the two prices stay level with each other. */
@media (max-width:240px){
  .wrap{padding-inline:8px}
  .board{padding:16px 10px 52px}
  /* No room for the toggle beside the corner rank: it takes the row, and the card keeps the rank's own room below. */
  .board[data-collapsed="true"]{padding-bottom:52px}
  .board-toggle{width:100%; margin-right:0; justify-content:center}
  /* The stamp of a locked card would leave the matchup a few pixels: it takes a line of its own. */
  .board-head{flex-wrap:wrap; row-gap:6px}
  .rows,.rows tbody{display:block}
  .rows thead{display:block; position:absolute; width:1px; height:1px; overflow:hidden; clip-path:inset(50%); white-space:nowrap}
  .rows tr.row{display:grid; grid-template-columns:1fr 1fr; gap:4px 6px; padding:8px 0; border-top:1px solid var(--card-line)}
  .rows tr.row td{padding:0; border-top:0}
  .rows td.player{grid-column:1 / -1}
  .rows td.price{width:auto}
  .pick{display:flex; flex-direction:column; align-items:flex-end; justify-content:center; padding:4px 6px}
  /* The label is BOOK_LABELS' (src/ui/format.ts); it is set here because the
     header that carried it is gone, and only the books' data-book says which. */
  .pick::before{
    font-size:11.5px; font-weight:700; letter-spacing:.04em; text-transform:uppercase; opacity:.75; line-height:1.2;
  }
  .pick[data-book="draftkings"]::before{content:"DraftKings"}
  .pick[data-book="fanduel"]::before{content:"FanDuel"}
  .price.not-offered{font-size:12px}
  /* A tab's label may wrap here, and a short one ("2+ TD") still needs a 44px-wide target. */
  .tab{min-width:44px; justify-content:center; text-align:center}
}
/* ---- the seat's foot and the reaction chips under every seat and leg ---- */
/* THE SEAT'S FOOT, INSIDE THE SEAT: its reactions and "Talk about this"
   close it under a gold hairline, the same one the legs sit between. */
.slip-foot{
  display:flex; flex-wrap:wrap; align-items:flex-start; gap:6px 8px;
  padding:10px 14px 12px; border-top:1px solid var(--gold-faint);
}
.slip-foot .reactions{flex:1 1 auto; min-width:0; margin-top:0}
.talk-about{
  min-height:44px; padding:0 16px; border:1px solid var(--gold-soft); border-radius:999px;
  background:transparent; color:var(--on-felt); font:inherit; font-size:13px; font-weight:600; cursor:pointer;
}
.talk-about:hover{border-color:var(--gold); color:var(--ivory)}

/* Chips are pills on the felt. Your own is lit gold, and says so in its label too. */
.reactions{display:flex; flex-wrap:wrap; align-items:center; gap:6px; margin-top:8px}
.reaction,.reaction-add{
  display:inline-flex; align-items:center; justify-content:center; gap:5px; min-width:44px; min-height:44px; padding:0 12px;
  border:1px solid var(--gold-soft); border-radius:999px; background:var(--felt-shade); color:var(--on-felt);
  font:inherit; font-size:15px; line-height:1; cursor:pointer;
}
.reaction .num{color:var(--on-felt-2); font-size:13px}
.reaction[aria-pressed="true"]{background:var(--gold-faint); border-color:var(--gold)}
.reaction[aria-pressed="true"] .num{color:var(--gold); font-weight:700}
.reaction:hover,.reaction-add:hover{border-color:var(--gold)}
/* A long-press lists who reacted; the phone's own callout and text
   selection would fire on the same press. */
.reaction{-webkit-touch-callout:none; -webkit-user-select:none; user-select:none}
.reaction-who{flex-basis:100%; margin:0; color:var(--on-felt-2); font-size:12.5px}
.reaction-add{color:var(--on-felt-2)}
.reaction-picker{display:flex; flex-wrap:wrap; gap:4px; flex-basis:100%}
.reaction-picker[hidden]{display:none}
.reaction-choice{
  min-width:44px; min-height:44px; border:1px solid var(--gold-faint); border-radius:8px;
  background:var(--felt-2); font:inherit; font-size:20px; line-height:1; cursor:pointer;
}
.reaction-choice[aria-pressed="true"]{background:var(--gold-faint); border-color:var(--gold)}
/* A leg's row, right under its leg and before the next leg's hairline, shown
   once someone has reacted (hidden until then, src/ui/reactions.ts; so no
   display here, which would beat [hidden]). Smaller chips, still 44px to a
   thumb. */
.leg-reactions{padding-bottom:6px}
.leg-reactions .reactions{margin-top:0; gap:4px}
.leg-reactions .reaction,.leg-reactions .reaction-add{padding:0 10px; font-size:13px}
/* A leg nobody has reacted to carries the client's "＋" at its end, which
   reveals its row: the same visible pill as every "＋" (a bare one read as a
   stray mark, Ruling 8b), at the right, after the leg's status. The row
   centres on the pill rather than on text baselines, which left a gap. */
.leg:has(.reaction-add){align-items:center}
.leg .reaction-add{margin-left:auto; padding:0 10px; font-size:13px}
/* A graded leg's status already takes the free space to sit at the right
   edge; the "＋" follows it there rather than splitting the space with it,
   which pulled "Won: 3 goals" into the middle of the row. */
.leg-status + .reaction-add{margin-left:0}

/* ---- talk: the messages ---- */
.talk-list{list-style:none; margin:0; padding:0}
.talk-msg{position:relative; display:flex; align-items:flex-start; gap:8px; margin-top:4px}
.talk-msg:first-child{margin-top:0}
/* A run's head parts from what came before; its next messages sit close. */
.talk-msg.first{margin-top:16px}
.talk-msg:first-child.first{margin-top:0}
.talk-msg.mine{justify-content:flex-end}

/* A member's initial in their jewel, ringed in ivory so every jewel parts
   from the felt it sits on. In the thread it stands beside the run's first
   bubble; a bubble that carries the run on keeps its place under it,
   unseen. */
.badge{
  display:inline-grid; place-items:center; flex:none; width:24px; height:24px; border-radius:999px;
  background:var(--accent); color:var(--on-member); box-shadow:var(--shadow-back);
  font-family:"Bodoni Moda",Didot,"Bodoni 72",Georgia,serif; font-weight:700; font-size:12px; line-height:1;
}
.talk-msg > .badge{margin-top:24px}
.talk-msg.next > .badge{visibility:hidden}

.talk-main{flex:1 1 auto; min-width:0; display:flex; flex-direction:column; align-items:flex-start}
.talk-msg.mine .talk-main{align-items:flex-end}
.talk-meta{display:flex; align-items:baseline; gap:8px; min-height:22px; margin-bottom:2px}
.talk-msg.mine .talk-meta{justify-content:flex-end}
.talk-author{color:var(--ivory); font-family:"Bodoni Moda",Didot,"Bodoni 72",Georgia,serif; font-weight:700; font-size:15px}
.talk-meta time{color:var(--on-felt-2); font-size:12px; font-variant-numeric:tabular-nums}
/* A message that carries a run on keeps its name and time for a screen
   reader and shows neither: read aloud, never seen, as .talk-status does. */
.talk-msg.next .talk-meta{
  position:absolute; width:1px; height:1px; margin:-1px; padding:0; border:0;
  overflow:hidden; clip-path:inset(50%); white-space:nowrap;
}

/* The row is the bubble and its Delete. Your own reads right to left, so its
   Delete stands to the left of the bubble, in the space the bubble leaves. */
.talk-row{display:flex; align-items:center; gap:2px; width:100%}
.talk-msg.mine .talk-row{flex-direction:row-reverse; justify-content:flex-start}
.talk-bubble{
  flex:0 1 auto; min-width:0; max-width:calc(100% - 48px); overflow:hidden;
  padding:9px 12px; border:1px solid var(--gold-soft); border-radius:16px;
  background:var(--accent, var(--leather-2)); color:var(--on-member); box-shadow:var(--shadow-gleam);
}
.talk-msg.theirs .talk-bubble{border-top-left-radius:4px}
.talk-msg.mine .talk-bubble{border-top-right-radius:4px}
.talk-body{margin:0; white-space:pre-wrap; overflow-wrap:anywhere; font-size:15px; line-height:1.4}

/* "re: Sam's slip" is a strip at the head of its bubble: a darker inset of
   the jewel under a hairline of gold, set in italics. The button is 44px
   tall for a thumb and the strip is the top 32px of it, the rest being the
   space before the body. */
.talk-re{
  position:relative; isolation:isolate; display:block; width:calc(100% + 24px); height:44px; min-height:44px;
  margin:-9px -12px 0; padding:0 12px 12px; border:0; background:none; color:var(--on-member);
  font:inherit; font-size:12px; font-style:italic; line-height:32px; text-align:left; cursor:pointer;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.talk-re::before{
  content:""; position:absolute; inset:0 0 12px; z-index:-1;
  background:var(--felt-shade); border-bottom:1px solid var(--gold-soft);
}
.talk-re:hover::before{border-bottom-color:var(--gold)}
.talk-re:focus-visible{outline-offset:-3px}

/* Quiet, and 44px to press: a bubble is about 41px tall, so the button
   overlaps its neighbours' gaps by a pixel or two, not their buttons. */
.talk-delete{
  flex:none; min-width:44px; min-height:44px; margin-block:-2px; padding:0 6px; border:0; border-radius:8px; background:none;
  color:var(--on-felt-2); font:inherit; font-size:12px; font-weight:600; cursor:pointer;
}
.talk-delete:hover{color:var(--ivory); text-decoration:underline; text-underline-offset:3px}

/* Said in words ("Message removed · Jo"), in muted italics, never in a
   jewel: nobody is speaking. */
.talk-msg.removed{display:block; margin-top:14px; padding:2px 0; color:var(--on-felt-2); font-size:13px; font-style:italic; text-align:center}
.talk-msg.removed:first-child{margin-top:0}

/* At 200% zoom (about 188px) a bubble beside its Delete is a sliver: the
   Delete drops under the bubble when they do not both fit. */
@media (max-width:240px){
  .talk-row{flex-wrap:wrap}
  .talk-bubble{max-width:100%}
}
/* ---- talk: the drawer, the thread, and the wide-screen panel. Leather
   for what you hold (the bar, the sheet's handle), felt for the thread,
   ivory for what people said and for the box you say it in. ---- */

/* THE BAR COVERS THE BOTTOM OF A PHONE, so the page gains room under the last
   seat, or the bar would sit on it for good. */
body:has(.talk){padding-bottom:76px}

.talk-bar{
  position:fixed; inset-inline:0; bottom:0; z-index:10;
  display:flex; align-items:center; gap:12px; width:100%; min-height:56px;
  padding:8px 16px calc(8px + env(safe-area-inset-bottom));
  border:0; border-top:1px solid var(--gold-soft); background:linear-gradient(var(--leather-2), var(--leather));
  color:var(--on-felt); font:inherit; text-align:left; cursor:pointer;
}
.talk-bar:hover .talk-title{color:var(--gold)}
.talk-title{
  flex:none; margin:0; color:var(--ivory); font-size:19px; font-weight:700;
  font-family:"Bodoni Moda",Didot,"Bodoni 72",Georgia,serif;
}
/* The newest message's head (badge, name, time), then its first line on a
   line of its own, cut short rather than wrapped: it is a preview, and the
   thread has the rest. The head stays one line and gives up its time before
   its name. */
.talk-preview{display:flex; flex-wrap:wrap; align-items:center; gap:0 6px; flex:1; min-width:0; font-size:13px; color:var(--on-felt-2)}
.talk-preview .badge{width:20px; height:20px; font-size:11.5px}
.talk-preview .talk-author{min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:14px}
.talk-preview time{flex:none; color:var(--on-felt-2); font-size:12px; font-variant-numeric:tabular-nums}
.talk-line{flex-basis:100%; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
/* How many arrived while it was shut: a small gold pill, said in words. */
.talk-new{
  flex:none; padding:3px 10px; border:1px solid var(--gold); border-radius:999px; background:var(--gold-faint);
  color:var(--gold); font-size:12px; font-weight:700; line-height:1.2; white-space:nowrap;
}
/* Read aloud, never seen: the client writes each new message from someone
   else here ("Sam: …"). Not display:none, which would silence it. */
.talk-status{
  position:absolute; width:1px; height:1px; margin:-1px; padding:0; border:0;
  overflow:hidden; clip-path:inset(50%); white-space:nowrap;
}

/* The open thread: a sheet over the seats with the box pinned to its foot. It
   scrolls inside itself so the page behind it never moves. CLOSED BY CSS, NOT
   BY [hidden]: the server renders it shown (src/ui/talk.ts says why), phones
   keep it out of sight and out of the accessibility tree until the client
   sets data-open, and the wide panel simply shows it. */
.talk-thread{
  position:fixed; inset-inline:0; bottom:0; z-index:20; height:80vh; height:80dvh;
  display:none; flex-direction:column; overflow:hidden;
  background:var(--felt-2); border:1px solid var(--gold-soft); border-bottom:0; border-radius:14px 14px 0 0;
  box-shadow:var(--shadow-panel);
}
.talk-thread[data-open]{display:flex}
.talk-head{
  display:flex; align-items:center; justify-content:space-between; gap:12px; min-height:52px; padding:4px 6px 4px 16px;
  background:linear-gradient(var(--leather-2), var(--leather)); border-bottom:1px solid var(--gold-soft);
}
.talk-handle{
  min-height:44px; padding:0 12px; border:0; background:none; color:var(--on-felt-2);
  font:inherit; font-size:13px; font-weight:600; cursor:pointer;
}
.talk-handle:hover{color:var(--gold)}
.talk-scroll{flex:1; min-height:0; overflow-y:auto; overscroll-behavior:contain; padding:6px 16px}
.talk-older{
  display:block; margin:6px auto; min-height:44px; padding:0 16px;
  border:1px solid var(--gold-soft); border-radius:999px; background:var(--felt-shade); color:var(--on-felt);
  font:inherit; font-size:13px; font-weight:600; cursor:pointer;
}
.talk-older[hidden]{display:none}
.talk-older:hover{border-color:var(--gold)}
.talk-empty{margin:28px 0; color:var(--on-felt-2); font-size:13.5px; text-align:center}

/* One row: the box and Send, both 44px at their least. The box grows with
   what is typed, up to a few lines (browsers without field-sizing keep one
   line and scroll), and Send stays level with its foot. The count and the
   error read beneath, out of the row. */
.talk-compose{
  display:grid; grid-template-columns:minmax(0,1fr) auto; align-items:end; gap:4px 8px;
  padding:10px 16px calc(10px + env(safe-area-inset-bottom)); border-top:1px solid var(--gold-soft); background:var(--felt-3);
}
.talk-compose textarea{
  grid-column:1; grid-row:2; width:100%; min-width:0; min-height:44px; max-height:30vh; padding:10px 16px; resize:none;
  border:1px solid var(--gold-2); border-radius:22px; background:var(--ivory); color:var(--card-ink);
  font:inherit; line-height:1.4; field-sizing:content;
}
.talk-compose textarea::placeholder{color:var(--card-ink-2)}
/* Send is the one gold fill in the thread: a round-ended button, and the
   thing to press. */
.talk-send{
  grid-column:2; grid-row:2; align-self:end; width:auto; min-width:44px; min-height:44px; padding:0 20px;
  border-color:var(--gold); border-radius:999px; background:var(--gold); color:var(--card-ink); font-weight:700;
}
.talk-send:hover{background:var(--gold); border-color:var(--ivory); color:var(--card-ink)}
.talk-count{grid-column:1 / -1; grid-row:3; justify-self:end; padding-right:12px; color:var(--on-felt-2); font-size:12px}
/* Over 500 the count says so in gold and Send is disabled; the number
   itself ("512/500") is the words. */
.talk-count[data-over]{color:var(--gold); font-weight:700}
/* "re: Sam's slip" above the box, in the language of the strip on a message:
   a darker inset under a hairline of gold, in italics, with its own remove
   button. The strip is 32px; the button is 44px and overhangs it. */
.talk-re-tag{
  grid-column:1 / -1; grid-row:1; display:flex; align-items:center; justify-content:space-between; gap:4px;
  min-height:32px; margin:0; padding:0 0 0 16px; border-bottom:1px solid var(--gold-soft); border-radius:12px 12px 0 0;
  background:var(--felt-shade); color:var(--on-felt); font-size:12px; font-style:italic;
}
.talk-re-tag[hidden]{display:none}
.talk-re-text{min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.talk-re-remove{
  flex:none; min-width:44px; min-height:44px; margin-block:-6px; border:0; border-radius:999px; background:none; color:var(--on-felt-2);
  font:inherit; font-size:16px; font-style:normal; cursor:pointer;
}
.talk-re-remove:hover{color:var(--gold)}
.talk-error{grid-column:1 / -1; grid-row:4; margin:0; padding-inline:12px; color:var(--gold); font-size:12.5px; font-weight:700}

/* At 200% zoom (about 188px) nothing fits beside anything: Send takes a row
   of its own under the box, with the count and error after it, and the bar
   puts its preview on a second row, without the time, so the head keeps its
   one line and nothing is cut off the side. */
@media (max-width:240px){
  body:has(.talk){padding-bottom:104px}
  .talk-send{grid-column:1; grid-row:3}
  .talk-count{grid-row:4}
  .talk-error{grid-row:5}
  .talk-bar{flex-wrap:wrap; gap:4px 12px}
  .talk-new{margin-left:auto}
  .talk-preview{order:3; flex-basis:100%}
  .talk-preview time{display:none}
}

/* WIDE SCREENS: THE THREAD IS A PANEL BESIDE THE COLUMN, never a drawer, and
   a labelled region rather than a dialog. It starts at 1140px (Ruling 5),
   the first width where the column keeps its full 780px beside a 344px
   panel: the wrap grows by the panel's width and gives it that room on the
   right. Below 1140px the phone drawer is used. The panel sits at the wrap's
   right edge, not the window's. */
@media (min-width:1140px){
  body:has(.talk){padding-bottom:0}
  .wrap:has(.talk){max-width:1140px; padding-right:376px}
  /* The rail lines its contents up with the column, which the wider wrap has
     moved left of centre; centred on 748px as elsewhere (./base.ts) it would
     float right of it. 1108px is the wrap's 1140 less its two 16px gutters,
     and the column is its first 748px, so the right padding adds the 360px
     the panel and its gap take. The account menu stays over the column,
     clear of the panel, which is fixed above the rail. */
  body:has(.talk) .site-bar,body:has(.talk) .pool-tabs{
    padding-left:max(16px, calc((100% - 1108px) / 2));
    padding-right:max(16px, calc((100% - 1108px) / 2 + 360px));
  }
  .talk-bar{display:none}
  .talk-thread{
    display:flex; top:16px; bottom:16px; left:auto; right:max(16px, calc((100vw - 1140px) / 2 + 16px));
    width:344px; height:auto; border-bottom:1px solid var(--gold-soft); border-radius:14px;
  }
  .talk-handle{display:none}
  .talk-compose{padding-bottom:10px}
}
