/* MHFU game font (falls back to sans-serif for glyphs it lacks, e.g. ★ — © ) */
@font-face{
  font-family:'MHFU';
  src:url('fonts/mhfu_font.ttf') format('truetype');
  font-display:swap;
}
/* Theme variables are set at runtime from the chosen color (see app.js). These :root
   values are only the pre-theme fallback. Note the Quest Randomizer's stylesheet refers
   to a --text-hint that its applyTheme never defines; this app uses --text-dim instead. */
:root{
  --bg:#3e3e3e; --bg1:#000000; --bg2:#333; --accent:#7a7a7a; --accent-hover:#9a9a9a;
  --win:#19e0d2; --hint:#dfe6ef;
  --text:#f3f3f3; --text-dim:#ffffff; --line:#ffffff1f; --card:#ffffff0d;
}
*{box-sizing:border-box;scrollbar-color:var(--accent) var(--bg2);scrollbar-width:thin}
::-webkit-scrollbar{width:6px;height:6px}
::-webkit-scrollbar-track{background:var(--bg2)}
::-webkit-scrollbar-thumb{background:var(--accent);border-radius:3px}
::-webkit-scrollbar-thumb:hover{background:var(--accent-hover)}
input,select,button,textarea{font-family:inherit}
html,body{margin:0;height:100%;overflow:hidden}
body{
  font-family:'MHFU',"Segoe UI",system-ui,sans-serif;
  background:var(--bg); color:var(--text); font-size:14px;
  user-select:none; -webkit-user-select:none;
}
#app{display:flex;flex-direction:column;height:100vh}
.hidden{display:none !important}

/* ── Title bar ─────────────────────────────────────────────────────────────── */
.titlebar{display:flex;align-items:center;gap:10px;padding:8px 14px}
.titlebar-header{
  background-image:
    linear-gradient(color-mix(in srgb,var(--accent) 90%,transparent),color-mix(in srgb,var(--accent) 90%,transparent)),
    url('assets/titlebar-background.png');
  background-size:auto 100%;
  background-position:left center;
  background-repeat:repeat;
}
.title-icon{width:32px;height:32px;object-fit:contain}
.titlebar h1{font-size:15px;font-weight:600;margin:0;white-space:nowrap}
.icon-btn{
  background:transparent;border:none;color:var(--text);font-size:18px;
  cursor:pointer;padding:6px 9px;border-radius:6px;line-height:1;
}
.icon-btn:hover{background:#ffffff1a}
/* The Quest Randomizer's icon-btn is always a <button>; ours is sometimes an <a>. */
a.icon-btn{display:inline-flex;align-items:center;text-decoration:none}

/* Seed bar lives in the title bar and takes the leftover width. */
.seedbar{display:flex;align-items:center;gap:6px;flex:1;min-width:0;margin-left:6px}
.seedbar input{
  flex:1;min-width:0;background:var(--bg2);color:var(--text);
  border:1px solid var(--line);border-radius:7px;padding:5px 9px;font-size:12.5px;
  font-family:ui-monospace,Consolas,monospace;
  /* body sets user-select:none globally — without this the seed can't be copied. */
  user-select:text;-webkit-user-select:text;
}
.seedbar input::placeholder{color:var(--text-dim);opacity:.6}
.seed-modified{color:var(--text-dim);font-size:11px;opacity:.9;flex-shrink:0}

/* ── Layout ────────────────────────────────────────────────────────────────── */
.layout{display:flex;flex:1;min-height:0}
.sidebar{
  /* 380 rather than 300 to fit the draw block. Measured at 1440x900: the talisman NAME
     already fitted (195px of 195), but a log row's text had 101px for content needing 252,
     so every two-skill draw was ellipsised down to the talisman name.
     Costs nothing where it matters: at that size the card is HEIGHT-bound (wrap 1112x799),
     so taking 80px of width leaves it unchanged at 778. It only shrinks the card on windows
     narrow enough for the card to be width-bound, which is also where the layout stacks. */
  width:380px;flex-shrink:0;padding:12px;overflow-y:scroll;scrollbar-gutter:stable;
  display:flex;flex-direction:column;gap:8px;
  background-image:
    linear-gradient(color-mix(in srgb,var(--accent) 90%,transparent),color-mix(in srgb,var(--accent) 90%,transparent)),
    url('assets/sidebar-background-texture1.png');
  background-size:auto 100%;
  background-position:left center;
  background-repeat:repeat;
}

/* ── Buttons ───────────────────────────────────────────────────────────────── */
.btn{
  background:#ffffff14;color:var(--text);border:1px solid var(--line);
  border-radius:7px;padding:8px 14px;cursor:pointer;font-size:14px;
}
.btn:hover{background:#ffffff24}
.btn:disabled{opacity:.45;cursor:not-allowed}
.btn:disabled:hover{background:#ffffff14}
.btn.block{width:100%}
.btn.tiny{padding:3px 10px;font-size:12px}
.btn.block,.btn.tiny{
  background-image:
    linear-gradient(color-mix(in srgb,var(--bg2) 80%,transparent),color-mix(in srgb,var(--bg2) 80%,transparent)),
    url('assets/banner-background.png');
  background-position:right;
}
/* The one call to action. --cta is the theme's own hue at the brightest lightness that
   still clears 4.5:1 against white text (see ctaColor), so it stays legible on all 27
   themes. background-image:none drops .btn.block's texture, which washed the colour out. */
.btn.accent{
  background:var(--cta);background-image:none;
  border-color:rgba(0,0,0,.45);color:#fff;font-weight:600;
  text-shadow:-1px 0 rgba(0,0,0,.55),1px 0 rgba(0,0,0,.55),0 -1px rgba(0,0,0,.55),0 1px rgba(0,0,0,.55);
}
.btn.accent:hover{background:color-mix(in srgb,var(--cta) 84%,#ffffff)}
.btn.accent:disabled:hover{background:var(--cta)}

/* ── Collapsible panels ────────────────────────────────────────────────────── */
.panel{border:1px solid var(--line);border-radius:8px;overflow:clip;background:var(--bg2)}
.panel-head{
  width:100%;text-align:left;background:transparent;border:none;color:var(--text);
  padding:10px 26px;font-size:30px;font-weight:600;cursor:pointer;
  display:flex;justify-content:space-between;align-items:center;
  background-image:
    linear-gradient(color-mix(in srgb,var(--bg2) 80%,transparent),color-mix(in srgb,var(--bg2) 80%,transparent)),
    url('assets/banner-background.png');
  background-size:auto 100%;
  background-position:left center;
  background-repeat:repeat;
}
.panel-head:hover{background-color:#ffffff0d}
.chev{transition:transform .15s;font-size:20px;font-weight:700;color:transparent}
.panel[data-open="true"] .chev{transform:rotate(180deg)}
.panel-body{
  display:none;padding:6px 12px 12px;flex-direction:column;gap:4px;
  background-image:
    linear-gradient(color-mix(in srgb,var(--bg1) 60%,transparent),color-mix(in srgb,var(--bg1) 60%,transparent)),
    url('assets/panel-body-bg.png');
  background-size:contain 100%;
  background-position:left center;
  background-repeat:repeat;
}
.panel[data-open="true"] .panel-body{display:flex}

/* ── Form bits ─────────────────────────────────────────────────────────────── */
.chk{display:flex;align-items:center;gap:8px;padding:3px 0;cursor:pointer;line-height:1.3}
.chk input{accent-color:var(--accent);width:16px;height:16px;cursor:pointer;flex-shrink:0}
.chk:has(input:disabled){opacity:.45;cursor:default}
.chk:has(input:disabled) input{cursor:default}
.chk .wicon{width:18px;height:18px;object-fit:contain}
.chk:hover{color:var(--accent-hover)}
.chk:hover input[type="checkbox"]{accent-color:var(--accent-hover,#9a9a9a)}
.chk input:indeterminate{opacity:.7}

.hint{color:var(--text-dim);opacity:.86;font-size:12px;margin:4px 0}
.row{display:flex;align-items:center}
.row.gap{gap:8px}
.row.end{justify-content:flex-end}

.field{display:flex;flex-direction:column;gap:4px;font-size:12px;color:var(--text-dim)}
.field select,.field input{
  background:var(--bg2);color:var(--text);border:1px solid var(--line);
  border-radius:7px;padding:6px 9px;font-size:13px;line-height:1.4;
}
.field select option:disabled{opacity:.4}
.num{
  background:var(--bg2);color:var(--text);border:1px solid var(--line);
  border-radius:6px;padding:3px 5px;font-size:12px;width:44px;text-align:center;
}

/* Monster tree (species → monsters) */
.tree{max-height:260px;overflow-y:auto;padding-right:4px}
.tree .species{margin-top:4px}
.tree .species>.chk{font-weight:600}
.tree .children{margin-left:22px;display:none;flex-direction:column}
.tree .species.open .children{display:flex}
.tree .twist{cursor:pointer;width:22px;display:inline-block;text-align:center;color:var(--accent);font-size:18px;font-weight:700;user-select:none}
.tree .twist:hover{filter:brightness(1.4)}

/* ── Category rows (pool on/off + weight) ──────────────────────────────────── */
.cat-row{display:flex;align-items:center;gap:8px;padding:3px 0}
.cat-row .chk{flex:1;min-width:0}
.cat-row .num{flex-shrink:0}
.cat-count{font-size:11px;opacity:.6;flex-shrink:0;min-width:34px;text-align:right}

/* ── Custom pool rows (mirrors the randomizer's Challenges panel) ──────────── */
.cp-add{display:flex;gap:6px;align-items:center;flex-wrap:wrap;margin-bottom:6px}
.cp-add input[type="text"]{
  flex:1;min-width:0;background:var(--bg2);color:var(--text);
  border:1px solid var(--line);border-radius:7px;padding:5px 8px;font-size:13px;
}
.cp-list{display:flex;flex-direction:column;gap:4px}
.cp-tag{display:flex;align-items:center;gap:6px;background:#ffffff0f;border:1px solid var(--line);border-radius:6px;padding:4px 8px;font-size:13px}
.cp-tag input[type="checkbox"]{accent-color:var(--accent);width:15px;height:15px;cursor:pointer;flex-shrink:0}
.cp-edit-text{
  flex:1;min-width:0;background:transparent;color:var(--text);
  border:1px solid transparent;border-radius:5px;padding:2px 5px;font-size:12px;
  transition:background .15s,border-color .15s;
}
.cp-edit-text:hover,.cp-edit-text:focus{background:var(--bg2);border-color:var(--line);outline:none}
.cp-edit-text.off{opacity:.45}
.cp-tag .num{width:34px}
.cp-del{background:transparent;border:none;color:var(--text-dim);cursor:pointer;font-size:16px;padding:0 2px;line-height:1;flex-shrink:0}
.cp-del:hover{color:red}

/* ── Card stage ────────────────────────────────────────────────────────────── */
.stage{
  flex:1;min-width:0;padding:14px;background-color:var(--bg1);
  display:flex;flex-direction:column;gap:8px;
}
.banner{
  flex-shrink:0;border:1px solid var(--line);border-radius:8px;padding:8px 12px;
  background:#ffffff0f;font-size:13px;line-height:1.4;
  display:flex;align-items:center;gap:10px;flex-wrap:wrap;
}
.banner .btn{flex-shrink:0}
/* Deliberately darker than the squares sitting on it (--well is derived in applyTheme),
   otherwise the grid dissolves into the background. */
.card-wrap{
  flex:1;min-height:0;container-type:size;
  display:grid;place-items:center;overflow:auto;
  background:var(--well,#101216);
  border:1px solid var(--line);border-radius:10px;padding:10px;
}
.status{flex-shrink:0;margin:0;text-align:center;font-size:14px;min-height:1.2em;color:var(--text)}
/* White too, and bold rather than coloured: --win is a button fill searched against WHITE,
   so as text on the page ground it had the same problem the draw block's colours did. */
.status.win{color:var(--text);font-weight:700}

/* The card sizes itself to the smaller of the stage's width and height, so it always
   fits without the page ever needing to scroll (body is overflow:hidden). Font sizes
   are in cqi so every size looks the same at any window dimension. */
.bingo-card{
  width:min(100cqi,100cqh);height:min(100cqi,100cqh);
  container-type:inline-size;
  display:grid;gap:calc(4.5cqi / var(--n));
  grid-template-columns:repeat(var(--n),1fr);
  grid-template-rows:repeat(var(--n),1fr);
}
.cell{
  /* Not clickable: the draws do the marking, so a square is a readout, not a control.
     The reroll button on top is the only interactive thing in the grid. */
  position:relative;overflow:hidden;cursor:default;
  border:2px solid transparent;border-radius:8px;
  background:#ffffff12;color:var(--text);font-family:inherit;text-align:center;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:calc(2cqi / var(--n));padding:calc(4.5cqi / var(--n));min-width:0;min-height:0;
  transition:background .12s,border-color .12s;
}
/* Fallback only — markedBorder() in app.js sets the real colour inline, a lightened form
   of the cell's own pool tint. Not solid white: that shouted over the --win fill a
   completed line gets, which is the state that should be loud. */
.cell.marked{border-color:rgba(255,255,255,.55);background:color-mix(in srgb,var(--accent) 55%,transparent)}

/* Crossed off. A recoloured border and a dimmed icon were too subtle to pick a marked
   square out of a full grid at a glance — a cross is unmistakable and, unlike colour,
   survives being squinted at from across a room. Drawn over the text the way you'd score
   out a line on paper, kept translucent so the goal stays readable underneath.
   The overhang is clipped by .cell's overflow:hidden, so 150% width lands corner to
   corner on a square cell (its diagonal is ~141%). --x is overridden for print, where the
   cell is white and a white cross would vanish. */
.cell{--x:rgba(255,255,255,.5)}
.cell.marked::before,
.cell.marked::after{
  content:"";position:absolute;left:-25%;right:-25%;top:calc(50% - .3cqi);
  height:.6cqi;background:var(--x);pointer-events:none;z-index:2;
}
.cell.marked::before{transform:rotate(45deg)}
.cell.marked::after{transform:rotate(-45deg)}
.cell.marked .cell-icon{opacity:.55}
/* The soft highlight: a drawn talisman rings every square it would satisfy. It is a nudge,
   never a mark -- the player clicks, and .marked takes over with the full fill and cross. So
   this is what a square looks like BEFORE you claim it, not a second kind of marked.

   The ring is an INSET box-shadow, chosen after two failures worth recording:
     - border-color loses outright. Every square carries its pool tint as an inline style,
       which beats any stylesheet rule, so the declaration silently never applied.
     - outline with a negative offset paints inside a box that is overflow:hidden, and
       whether that survives is engine-dependent.
   An inset shadow is clipped by nothing, ignores the inline border, and follows the radius.

   Three cues rather than one, because no single colour survives every theme crossed with
   every pool tint: the ring, a lift, and a pulse. --hint is contrast-searched per theme
   (see hintColor in app.js), so it goes light on dark themes and dark on pale ones.
   z-index keeps the outer glow above the neighbouring squares instead of under them.

   Named `hinted`, not `hint`: `.hint` is already the utility class for the small grey
   paragraphs, and a square matching it would inherit opacity:.72 and font-size:12px --
   dimming and shrinking the very tile it is trying to draw the eye to. */
.cell.hinted{
  z-index:2;
  transform:scale(1.045);
  box-shadow:
    inset 0 0 0 3px var(--hint),
    inset 0 0 0 9999px color-mix(in srgb,var(--hint) 12%,transparent),
    0 0 18px 4px color-mix(in srgb,var(--hint) 55%,transparent);
  animation:hintPulse 1.5s ease-in-out infinite;
}
@keyframes hintPulse{
  0%,100%{box-shadow:
    inset 0 0 0 3px var(--hint),
    inset 0 0 0 9999px color-mix(in srgb,var(--hint) 10%,transparent),
    0 0 14px 3px color-mix(in srgb,var(--hint) 45%,transparent)}
  50%    {box-shadow:
    inset 0 0 0 4px color-mix(in srgb,var(--hint) 80%,var(--text)),
    inset 0 0 0 9999px color-mix(in srgb,var(--hint) 20%,transparent),
    0 0 26px 7px color-mix(in srgb,var(--hint) 75%,transparent)}
}
/* Marking wins: the claimed state must visibly supersede the nudge. */
/* Marking wins: the claimed state must visibly supersede the nudge. .marked has no
   box-shadow of its own so clearing it is right; .line does, and clearing it there would
   out-specify .cell.line and strip the win ring -- so that one only stops the motion and
   lets .cell.line take the shadow on source order below. */
.cell.hinted.marked{animation:none;transform:none;box-shadow:none}
.cell.hinted.line{animation:none;transform:none}
@media (prefers-reduced-motion:reduce){
  .cell.hinted{animation:none;transform:none}
}
/* A completed line fills its squares with --win (a hue rotated 180° off the theme, see
   winColor) instead of only outlining them — a filled run of squares is unmistakable at a
   glance, where a brighter border was not. Must stay after .cell.marked to override its
   background. The glow adds a second, non-colour cue: nothing else on the page glows. */
/* Not yet drawn for. Deliberately no dimming or badge: the point is to stop a misclick,
   not to advertise which squares are still outstanding — that would hand the player the
   inverse of the hint for free, even with the highlight switched off. */
.cell.locked{cursor:default}
.cell[role="button"]{cursor:pointer}
.cell[role="button"]:hover{background:#ffffff14}
.cell[role="button"]:focus-visible{outline:2px solid var(--accent);outline-offset:2px}

.cell.line{
  background:var(--win);
  /* Inset white ring + outer glow. The ring is deliberately not colour-based: --win is a
     full 180° off the theme's hue, but hue rotation says nothing about luminance, and on
     the gold themes a win square and an unmarked one land within 1.02:1 of each other.
     Anyone reading by brightness alone still gets an unambiguous double-ring. */
  box-shadow:inset 0 0 0 2px rgba(255,255,255,.9),
             0 0 10px 2px color-mix(in srgb,var(--win) 60%,transparent);
}
.cell.line .cell-text{font-weight:700}
.cell.line:hover{background:var(--win);filter:brightness(1.08)}
/* .cell.marked dims the icon to let the fill read; on a win square the fill is already
   solid, so put the icon back to full strength. */
.cell.line .cell-icon{opacity:1}
.cell.free,.cell.empty{cursor:default}
.cell.empty{opacity:.3}
.cell.empty:hover{background:#ffffff12}
.cell-icon{width:34%;max-height:38%;object-fit:contain;flex-shrink:0}
/* A 40-character entry (the MAX_CELL_TEXT cap) needs three lines at 5x5 and four on the
   bigger grids, where the fixed 2px border eats proportionally more of a smaller cell. The
   clamp is a safety net rather than the fitting mechanism, so it sits at four everywhere —
   there is vertical room for roughly ten lines at any size. overflow-wrap catches a single
   unbroken 40-character word, which would otherwise run past the square's edge. */
.cell-text{line-height:1.15;overflow:hidden;display:-webkit-box;-webkit-line-clamp:4;-webkit-box-orient:vertical;overflow-wrap:anywhere}
/* Text scales inversely with the column count, so a square holds about the same number of
   characters at any grid size. The constants reproduce the sizes these rules used to hard-
   code for 3/4/5 (3.4 / 2.6 / 2.1 cqi and 2.2 / 1.7 / 1.4) and carry on up to 10 without
   another rule per size. Still not shrink-to-fit — MAX_CELL_TEXT is what guarantees the
   text fits; this just picks the size for the grid. */
.bingo-card .cell-text{font-size:calc(10.4cqi / var(--n))}

.cell-reroll{
  position:absolute;top:2%;right:2%;opacity:0;transition:opacity .12s;
  background:#00000066;border:none;color:#fff;border-radius:5px;cursor:pointer;
  font-size:2.2cqi;line-height:1;padding:.5cqi .8cqi;font-family:inherit;
}
.cell:hover .cell-reroll,.cell:focus-within .cell-reroll{opacity:1}
.cell-reroll:hover{background:#000000aa}

/* Colour legend in the Help modal. Built from the same tables the cards use, so it can't
   drift from what's actually on screen. */
.legend{display:grid;grid-template-columns:1fr 1fr;gap:4px 12px;margin:0 0 10px}
.legend div{display:flex;align-items:center;gap:7px;font-size:12.5px;line-height:1.5}
.legend-strip{display:flex;flex-wrap:wrap;gap:5px;margin:0 0 4px}
.sw{width:14px;height:14px;border-radius:4px;border:2px solid;background:#ffffff12;flex-shrink:0}

/* ── Enlarged tile preview ─────────────────────────────────────────────────── */
/* A hover zoom of the square under the cursor. It reuses a clone of the cell itself, so
   it stays in step with cell styling automatically — but everything the card sizes in cqi
   has to be restated in px here, because the clone lives on <body>, outside the card's
   container, where cqi and --n don't resolve. Sits below .modal's z-index, and is
   pointer-events:none so it can never eat a click. */
/* Opaque backdrop matching the card's own well: a marked square's fill is translucent, so
   without this the grid shows straight through the zoom. */
.tile-preview{position:fixed;z-index:9;pointer-events:none;display:none;
  border-radius:10px;background:var(--well,#101216)}
.tile-preview.on{display:block}
.tile-preview .cell{
  width:210px;height:210px;border-width:3px;border-radius:10px;
  gap:6px;padding:12px;box-shadow:0 10px 30px rgba(0,0,0,.6);cursor:default;
}
.tile-preview .cell-text{font-size:17px;-webkit-line-clamp:5}
.tile-preview .cell-reroll{display:none}
.tile-preview .cell.marked::before,
.tile-preview .cell.marked::after{height:3px;top:calc(50% - 1.5px)}

/* No hover on touch, so the preview would only ever be a stuck panel. */
@media (hover:none){ .tile-preview{display:none !important} }

/* ── Modals ────────────────────────────────────────────────────────────────── */
.modal{position:fixed;inset:0;background:#000000a8;display:flex;align-items:center;justify-content:center;z-index:10}
.modal-card{background:var(--bg2);border:1px solid var(--line);border-radius:10px;
  padding:20px;max-width:560px;width:100%;max-height:82vh;overflow-y:auto}
.modal-card h2{margin:0 0 16px;font-size:18px}
.modal-card h3{margin:16px 0 6px;font-size:14px}
.modal-card p{margin:0 0 10px;line-height:1.45}
.modal-card a{color:color-mix(in srgb, hsl(from var(--accent) calc(h + 180) 75% 60%) 50%, var(--text))}
.modal-card .muted{color:var(--text-dim);font-size:13px;opacity:.75}
.modal-card ul{margin:0 0 10px;padding-left:18px}
.modal-card li{margin-bottom:5px;line-height:1.4}
.modal-card code{background:var(--bg);border:1px solid var(--line);border-radius:5px;padding:1px 5px;font-family:ui-monospace,Consolas,monospace;font-size:12px;user-select:text;-webkit-user-select:text}
.modal-divider{border:none;border-top:1px solid var(--line);margin:14px 0 10px}
.modal-card textarea{
  width:100%;height:180px;background:var(--bg);color:var(--text);
  border:1px solid var(--line);border-radius:7px;padding:8px;font-size:12px;
  font-family:ui-monospace,Consolas,monospace;resize:vertical;
  user-select:text;-webkit-user-select:text;
}
.swatches{display:grid;grid-template-columns:repeat(4,1fr);gap:9px;margin-bottom:15px}
.swatch{aspect-ratio:1;border-radius:8px;border:2px solid transparent;cursor:pointer;position:relative;overflow:hidden;padding:0}
.swatch.sel{border-color:#fff}
.swatch-icon{position:absolute;top:7%;left:50%;transform:translateX(-50%);width:60%;pointer-events:none;filter:drop-shadow(0 1px 2px rgba(0,0,0,.45))}
.swatch span{position:absolute;bottom:0;left:0;right:0;text-align:center;font-size:10px;line-height:1.5;color:#fff;background:rgba(0,0,0,.45)}
.share-row{display:flex;align-items:center;gap:6px;margin-bottom:8px}
.share-row code{flex:1;min-width:0;word-break:break-all}
/* Small caption above each command row saying which bot the form suits. */
.cmd-desc{font-size:11.5px;color:var(--text-dim);opacity:.7;margin:8px 0 3px;line-height:1.4}

/* Other MHGU apps modal */
.app-links{list-style:none;margin:0 0 14px;padding:0;display:flex;flex-direction:column;gap:10px}
.app-links li{border:1px solid var(--line);border-radius:8px;padding:9px 12px;background:#ffffff08}
.app-links li.current{opacity:.72}
.app-links a,.app-name{font-weight:600;font-size:14px}
.app-here{font-weight:400;font-size:11px;opacity:.7;border:1px solid var(--line);border-radius:6px;padding:1px 7px;margin-left:7px}
.app-desc{display:block;margin-top:3px;font-size:12px;opacity:.8;line-height:1.4}

/* ── Print ─────────────────────────────────────────────────────────────────── */
@media print{
  html,body{overflow:visible !important;height:auto !important;background:#fff !important;color:#000 !important}
  .titlebar,.sidebar,.banner,.status,.modal,.cell-reroll{display:none !important}
  .layout{display:block}
  .stage{padding:0;background:none !important}
  .card-wrap{container-type:normal;overflow:visible;display:block}
  .bingo-card{width:7in;height:7in;gap:2px;page-break-inside:avoid}
  .cell{border-color:#000 !important;background:#fff !important;color:#000 !important}
  .cell.marked{background:#d8d8d8 !important;--x:#000}
  .cell.line{--x:#fff}
  /* Three distinguishable levels on paper, where --win doesn't survive: unmarked white,
     marked light grey, completed line dark with reversed text. */
  .cell.line{background:#4a4a4a !important;color:#fff !important;box-shadow:none !important}
  .cell.line .cell-text{font-weight:700}
  .cell-text{font-size:9pt !important}
  *{-webkit-print-color-adjust:exact;print-color-adjust:exact}
}

/* ── Mobile ────────────────────────────────────────────────────────────────── */
@media (max-width:760px){
  .layout{flex-direction:column}
  .sidebar{width:auto;max-height:42vh;order:2}
  .stage{order:1;padding:10px}
  .card-wrap{container-type:inline-size}
  .bingo-card{width:100%;height:auto;aspect-ratio:1}
  .cell-reroll{opacity:1}
  .titlebar{flex-wrap:wrap}
  .seedbar{order:3;flex-basis:100%}
}

/* ── Pool rows in the sidebar ──────────────────────────────────────────────── */
/* Each pool wears its own colour on a left edge, so the sidebar and the squares agree
   about which pool is which without a separate legend. */
.cat-box{border-left:3px solid var(--line);padding:2px 0 2px 8px;margin-bottom:6px}
.cat-hint{margin:0 0 2px}
.legend-sw{width:14px;height:14px;border-radius:4px;flex-shrink:0}

/* ── Draw panel ────────────────────────────────────────────────────────────── */
/* The draw block. A plain sidebar block rather than a .panel, because the panel handler is
   an accordion -- it closes every panel before toggling the clicked one, so a .panel here
   could never stay open. This is what "always visible" means in practice. */
.drawblock{
  display:flex;flex-direction:column;gap:6px;
  border:1px solid var(--line);border-radius:8px;padding:10px;
  /* The same texture .panel-body uses, so the draw block reads as part of the sidebar rather
     than a flat well dropped on top of it. Kept in step with that rule if it ever changes. */
  background-color:var(--bg2);
  background-image:
    linear-gradient(color-mix(in srgb,var(--bg1) 60%,transparent),color-mix(in srgb,var(--bg1) 60%,transparent)),
    url('assets/panel-body-bg.png');
  background-size:contain 100%;
  background-position:left center;
  background-repeat:repeat;
}
/* Gutters. Every part of this block changes height as you play -- the charm goes from one
   line of prompt to an icon with two skills, the log fills from empty to scrolling -- and
   without reserved space the block would grow and shove the panels below it down the
   sidebar on the first draw, then again on the second. Each part gets the room its filled
   state needs, so the block is the same size before the first draw and after the last. */
/* No floor here any more. The panel is a role line plus one button -- nothing in it varies --
   and the only thing that did vary, #peNote's message, reserves its own two lines. A floor on
   top of that was 33px of dead space under the Draw button for every caller. */
.drawblock .last-charm{min-height:94px}
.drawblock .draw-log{height:200px;min-height:200px;max-height:200px;flex:none}
/* A rule closing the current draw and another closing the log. The block stacks three
   unrelated things -- controls, the talisman just called, the history -- and without the
   dividers they read as one long list of text in the same colour. */
.drawblock .last-charm{border-bottom:1px solid var(--line);padding-bottom:8px}
.drawblock .draw-log{border-bottom:1px solid var(--line);padding-bottom:6px}
#logCheckBtn{margin-top:2px}
.log-head{
  margin:0;font-size:14px;font-weight:600;color:var(--text);opacity:1;
  border-bottom:1px solid var(--line);padding-bottom:5px;
}
.btn.on{border-color:#fff;color:#fff;background:#ffffff2e;font-weight:600}
.draw-controls{display:flex;flex-direction:column;gap:6px;min-width:0}
.draw-controls .hint{margin:0}

/* The drawn charm, laid out the way the game shows one: rarity icon, name, slot pips,
   then a line per skill. */
.last-charm{
  display:flex;flex-direction:column;justify-content:center;gap:6px;min-width:0;
  border-top:1px solid var(--line);padding:8px 0 0;margin-top:0;
}
.charm{--n-w:30px;--fld:calc(var(--n-w) + 6px);
  display:flex;align-items:center;gap:10px;min-width:0}
.charm-icon{width:40px;height:40px;object-fit:contain;flex-shrink:0}
.charm-body{min-width:0;flex:1}
.charm-name{font-size:14px;font-weight:600;display:flex;align-items:baseline;gap:6px}
.charm-n{font-weight:400;opacity:.75;flex-shrink:0;min-width:var(--n-w)}
/* Monospaced for the same reason the log's are: the glyphs read as a slot diagram rather
   than three characters. */
.charm-slots{margin-left:var(--fld);font-size:12px;opacity:.9;
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;letter-spacing:.08em}
.charm-skill{margin-left:var(--fld);font-size:12.5px;opacity:1;line-height:1.35}
/* A negative second skill is the joke half of a charm — it should be visible at a glance,
   not something you have to read the minus sign to notice. */
.charm-skill.neg{color:#ff8f8f}
/* The placeholder second skill. Dimmed so it reads as an absent field rather than a value. */
.charm-skill.none,.log-skill.none{opacity:.55}
.charm-note{font-size:12px;opacity:.86}
/* A draw that marked something gets the win colour, so a hit is legible out of the corner
   of your eye during an auto run — which is the only way anyone watches 700 draws. */
/* Plain white. Coloured text on this block cannot clear AA on six of the themes -- the only
   colours that reach it there are white and black -- so a hit is marked out by the bold weight
   on its match count instead. */
.charm.hit,
.charm-note.hit{color:var(--text)}
/* The win colour beats the negative-skill red on a matching draw. A log row that hits colours
   every line, and the current draw is the same talisman -- it colouring only its name was the
   two displays disagreeing about what a hit looks like. */
.charm.hit .charm-skill.neg{color:var(--text)}
.charm.god{outline:2px solid var(--win);outline-offset:4px;border-radius:8px}

/* Which seat you are at the table. Two tabs rather than a toggle because they are not two
   states of one control -- the Gamemaster rolls talismans, the player transcribes them, and
   the panel underneath changes completely. */

/* Live-session controls. Deliberately quiet: going live is a thing you do once at the
   start of a stream, not a control you reach for during play. */
/* Chat commands are meant to be copied verbatim, so they get to wrap rather than scroll. */
.cmd{
  background:var(--bg2);border:1px solid var(--line);border-radius:6px;padding:8px 10px;
  font:12px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  white-space:pre-wrap;overflow-wrap:anywhere;margin:4px 0 10px;user-select:all;
}
/* A live session should be obvious at a glance, from the header, without opening anything. */
.live-dot{
  display:inline-block;width:8px;height:8px;border-radius:50%;background:#ff4d4d;
  box-shadow:0 0 6px 1px rgba(255,77,77,.8);
}
.icon-btn .live-dot{position:absolute;top:4px;right:4px}
/* A dropped session, which used to be reported by a line in the draw panel. Amber rather than
   red, and it stops pulsing: the session is not dead, it just is not reaching the server. */
.live-dot.lost{background:#ffb14d;box-shadow:0 0 6px 1px rgba(255,177,77,.8);animation:none}
#twitchBtn{position:relative}
.live-banner{
  display:flex;align-items:center;gap:7px;margin:6px 0 4px;
  font-size:13px;letter-spacing:.06em;color:var(--text);
}
@media (prefers-reduced-motion:no-preference){
  .live-dot{animation:livePulse 2s ease-in-out infinite}
}
@keyframes livePulse{50%{opacity:.45}}

.seed-share{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11.5px}
/* A lost connection has to look different from ordinary guidance, or a viewer will keep
   waiting for draws that are never coming. */
.hint.bad{color:#ff8f8f;opacity:1}

/* The sync result reserves two lines whether or not there is a message. Otherwise the panel
   grew the moment one appeared, and the whole block below it shifted -- and the floor would
   need re-measuring every time the wording changed. Keep these messages within two lines. */
#peNote{min-height:20px;margin:0}
/* Which seat you are at. Shown, not chosen. */
.role-line{margin:0 0 4px;font-size:14px;color:var(--text-dim);opacity:.86}
/* The role itself is the point of the line, so it carries the size and weight while the
   "You are the" stays quiet. */
.role-line strong{font-size:18px;font-weight:700;letter-spacing:.01em;color:var(--text)}

.draw-log-wrap{display:flex;flex-direction:column;gap:4px;min-width:0}
.draw-log-wrap .hint{margin:0}
.draw-log{
  flex:1;min-height:64px;max-height:190px;overflow-y:auto;
  display:flex;flex-direction:column;gap:2px;font-size:13px;line-height:1.35;
  /* The log scrolls, so its overlay scrollbar sat on top of the right-hand end of each row —
     which is exactly where "N to mark" lives. scrollbar-gutter reserves the track even when
     the log is short enough not to scroll, so rows don't shift as entries accumulate; the
     padding covers engines that ignore it. */
  scrollbar-gutter:stable;
  padding-right:10px;
}
/* One field per line, not one row per entry. On a single line the talisman competed with the
   draw number, the slot glyph and the match count for the same narrow column, so every
   two-skill draw was ellipsised down to roughly its name -- the half worth reading. Stacked,
   nothing truncates and the skills line up into a column down the log. Costs vertical space
   in a box that scrolls anyway. */
.log-row{
  --n-w:30px;--fld:calc(var(--n-w) + 6px);
  display:flex;flex-direction:column;gap:1px;
  padding:3px 0;border-bottom:1px solid #ffffff0f;
}
.log-row:last-child{border-bottom:0}
.log-row.hit{color:var(--text)}
.log-line{display:flex;gap:6px;align-items:baseline;min-width:0}
.log-n{flex-shrink:0;opacity:.75;min-width:var(--n-w)}
.log-name{flex:1;min-width:0;font-weight:700;overflow-wrap:anywhere}
.log-skill{margin-left:var(--fld);opacity:1;overflow-wrap:anywhere;line-height:1.3}
.log-hit{margin-left:var(--fld);font-weight:700}
.log-row:not(.hit) .log-hit{opacity:.86;font-weight:400}
/* Monospaced so the glyphs line up into a column down the log, which is what makes them
   scannable at a glance rather than three more characters to read. */
.log-slots{margin-left:var(--fld);
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  letter-spacing:.08em;opacity:.9}

@media print{
  .drawblock{display:none !important}
}
