/* Binder Planner — shared styles.
   Per-set accent colours are injected as CSS vars by binder.js (see set JSON "theme").

   Two halves, and the split matters: everything above "binder" is the page shell
   (rail, toolbar, run strip, account) and everything below it draws the binder
   itself. binder.js emits the binder class names, so the shell must not reuse
   them: .spread .page .gutter .ring .grid .slot .pocket .filler .empty-slot
   .tag .run .check .caption .price .pagenum .n .nm .q .t .why .owned .blank
   .left .right .paged .pagerbar .pagernav .mini .where .pagercount */

:root{
  --ink:#06070A;
  --ink2:#0C0F15;
  --page:#141922;
  --page-edge:#1E2530;
  --pocket:rgba(255,255,255,.035);
  --line:rgba(255,255,255,.10);
  --text:#E7EAF1;
  --muted:#8792A5;      /* was #7C8697, which missed 4.5:1 on the panel surfaces */

  /* Page shell */
  --shell:#0E1116;
  --panel:#151A21;
  --panel2:#1B212A;
  --rule:#252D38;
  --edge:#333C49;
  --dim:#C6CEDB;

  /* Theme slots — overridden per set */
  --accent:#8FB8FF;
  --rh:#A98BFA;
  --secret:#E4A244;
  --chase:#F06E8E;
  --print:#67D97B;
  --glow:#131A26;
}

*{box-sizing:border-box}
html,body{height:100%;margin:0}
body{
  background:var(--shell);
  color:var(--text);
  font-family:'Inter Tight',system-ui,-apple-system,sans-serif;
  -webkit-font-smoothing:antialiased;
  display:grid;grid-template-columns:268px 1fr;overflow:hidden;
}
a{color:var(--accent)}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}
::-webkit-scrollbar{width:10px}
::-webkit-scrollbar-thumb{background:#2A3340;border-radius:6px;border:2px solid var(--shell)}
::-webkit-scrollbar-thumb:hover{background:#3A4553}

/* ---------- rail ----------
   The set picker was a 70-item <select>; it is now a permanent searchable list
   showing the number you actually navigate by, which is how close each set is. */
.rail{background:var(--panel);border-right:1px solid var(--rule);display:flex;flex-direction:column;min-height:0}
.brand{padding:15px 16px 13px;border-bottom:1px solid var(--rule);display:flex;align-items:baseline;gap:8px}
.brand b{font-family:'Bricolage Grotesque',sans-serif;font-size:16px;font-weight:800;letter-spacing:-.02em}
.brand span{font-family:'JetBrains Mono',ui-monospace,monospace;font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--muted)}

.search{padding:12px 12px 8px;position:relative}
.search input{
  width:100%;background:var(--shell);border:1px solid var(--rule);border-radius:6px;color:var(--text);
  padding:8px 32px;font:13px 'Inter Tight',sans-serif;
}
.search input::placeholder{color:var(--muted)}
.search input:focus{outline:none;border-color:var(--accent)}
.search .mag{position:absolute;left:23px;top:50%;transform:translateY(-40%);color:var(--muted);font-size:13px;pointer-events:none}

.setlist{overflow-y:auto;flex:1;padding:0 8px 12px;min-height:0}
.series{
  position:sticky;top:0;background:var(--panel);z-index:1;
  font-family:'JetBrains Mono',ui-monospace,monospace;font-size:10px;line-height:1;
  letter-spacing:.15em;text-transform:uppercase;color:var(--muted);padding:15px 8px 8px;
}
.setrow{width:100%;display:grid;grid-template-columns:1fr auto;gap:3px 8px;padding:7px 8px;border-radius:6px;text-align:left;align-items:center}
.setrow:hover{background:var(--panel2)}
.setrow:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
.setrow[aria-current="true"]{background:color-mix(in srgb,var(--accent) 18%,transparent)}
.setrow-name{font-size:13px;color:var(--dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.setrow[aria-current="true"] .setrow-name{color:var(--text);font-weight:500}
.setrow-code{font-family:'JetBrains Mono',ui-monospace,monospace;font-size:10px;color:var(--muted)}
.setrow-bar{grid-column:1/-1;height:3px;border-radius:2px;background:#1D242D;overflow:hidden}
.setrow-bar i{display:block;height:100%;background:var(--accent);border-radius:2px;transition:width .25s ease}
.no-match{padding:24px 10px;color:var(--muted);font-size:13px;text-align:center}

/* ---------- main column ---------- */
.main{display:flex;flex-direction:column;min-width:0;min-height:0;position:relative}

/* One 52px row while the controls fit beside the set name, two rows when they
   don't — 51 + 44 + the 1px rule = 96px. `barTwoRows()` in index.html decides,
   from the controls' measured widths rather than a pixel breakpoint.

   The bar was a single nowrap row until 2026-08-04. Its only flexible item was
   the set name, which had `min-width:0` and no floor, so flexbox took the name
   to 0px and kept every control: measured absent — not clipped, absent — at
   every viewport from 380 to 1030, whole only at 1260. It also overflowed with
   no way to reach the tail from 770 to 1020, worst case 259px over at 800.

   `flex-wrap` alone does not fix that: a wrapping flex line breaks *before* it
   shrinks anything, so the set code would push the owned counter onto a line of
   its own. Each row is its own nowrap container, and the bar's children are all
   `flex-basis:100%`, so the bar can break in exactly one place — between them. */
/* The right end is spoken for by the pinned account, so the bar reserves it —
   without this the owned count sits under an opaque panel, which is half of
   what was wrong before. --acct is measured and published by paintAccount(),
   because the signed-out button and the signed-in avatar are different widths;
   the fallback is the signed-out measurement. */
.toolbar{
  flex:0 0 auto;display:flex;flex-wrap:wrap;align-content:center;row-gap:0;
  padding:0 var(--acct,107px) 0 18px;
  border-bottom:1px solid var(--rule);background:var(--panel);
}
.barrow,.barctl{flex:1 0 100%;display:flex;align-items:center;gap:14px;min-width:0}
/* 51, not 52: the height comes from the row now, and the bar's 1px bottom rule
   is added on top of it. 51 + 1 is the 52px the rest of the shell is built on. */
.barrow{height:51px}

/* Row two, when there is one. It sits below the pinned account, so it takes
   that column's width back. A phone fits neither three control groups on a line
   nor a fourth row, so this one scrolls sideways — the answer the whole bar used
   to use, now applied to a row that holds nothing but controls, where scrolling
   cannot bury the set name. */
.barctl{
  height:44px;border-top:1px solid var(--rule);
  margin-right:calc(-1 * var(--acct,107px));padding-right:18px;
  overflow-x:auto;scrollbar-width:none;
}
.barctl::-webkit-scrollbar{display:none}

/* The set name and its code are one item, so nothing can come between them and
   the gap is a constant 14px at every width and for every set.

   Two rules hold that. The pair must not grow, or it absorbs the row's slack
   and the empty box it leaves pushes the code away from the name — measured
   291px apart at 1050, moving on every window drag. And the name's floor lives
   on the *pair*: `min-width` on the `h1` puts a name shorter than the floor in a
   box wider than its text, which drifts the code by another 13px. On the pair,
   that slack lands after the code instead. `9ch` is read in the h1's own font,
   which is why the pair carries the h1's font metrics; `+ 14px` is the pair's
   own gap, so the name still gets a whole 9ch after the gap is paid for. */
.setid{
  display:flex;align-items:baseline;gap:14px;flex:0 1 auto;
  font-family:'Bricolage Grotesque',sans-serif;font-size:17px;
  min-width:calc(9ch + 14px);
}
.toolbar h1{
  font-family:'Bricolage Grotesque',sans-serif;font-size:17px;font-weight:700;letter-spacing:-.02em;
  margin:0;white-space:nowrap;min-width:0;overflow:hidden;text-overflow:ellipsis;
}
/* The code yields all of it before the name yields any. Both shrink — a long
   name has to be able to ellipse — but the weighting makes the order between
   them explicit rather than proportional to their widths. */
.setmeta{
  font-family:'JetBrains Mono',ui-monospace,monospace;font-size:10.5px;color:var(--muted);
  white-space:nowrap;flex:0 1000 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;
}

.seg{display:flex;background:var(--shell);border:1px solid var(--rule);border-radius:6px;padding:2px;gap:2px}
.seg button{padding:5px 10px;border-radius:4px;font-size:12px;color:var(--muted);white-space:nowrap}
.seg button:hover{color:var(--text)}
.seg button[aria-pressed="true"]{background:var(--panel2);color:var(--text);box-shadow:0 1px 0 rgba(255,255,255,.05)}
.seg button:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}

/* The meter spends the row's leftover width rather than leaving it blank: the
   track was 120px beside 720px of empty bar on a 1920 desktop. It grows to
   --track-max and shrinks to 44px, so it is the row's shock absorber in both
   directions and no control has to be hidden to make room.

   `width:120px` and `flex-basis:120px` are both required. A flex basis with
   `width:auto` does not contribute the basis to the parent's intrinsic size in
   Chromium, so `.meter` sizes itself to min-content and holds the track at its
   44px floor at *every* width. Measured: the two together give 120px, the basis
   alone gives 44px. Below 44px there is no bar left to read, which is why the
   floor exists rather than letting it shrink to an invisible sliver.

   The cap is judgement, not a finding: uncapped the track reaches 764px at a
   1920 window and 1404px at a 2560, at which point the bar is mostly meter. */
.toolbar{--track-max:320px}
.meter{display:flex;align-items:center;gap:10px;white-space:nowrap;flex:1 1 auto;min-width:0;justify-content:flex-end}
.meter .track{
  flex:1 1 120px;width:120px;min-width:44px;max-width:var(--track-max);
  height:6px;border-radius:3px;background:#232B35;overflow:hidden;
}
.meter .track i{display:block;height:100%;background:linear-gradient(90deg,var(--accent),#5FD3A0);border-radius:3px;transition:width .25s ease}
.meter .fig{font-family:'JetBrains Mono',ui-monospace,monospace;font-size:12px;color:var(--muted)}
.meter .fig b{color:var(--text);font-weight:600}

/* ---------- account ----------
   Top-right, and pinned to .main rather than flowed as the toolbar's last item.
   As a flex child it was simply the last thing in a nowrap row and so the first
   thing the row dropped — measured unreachable at 900px wide and below. */
.account{
  position:absolute;top:0;right:0;height:52px;z-index:55;
  display:flex;align-items:center;gap:10px;padding-right:18px;background:var(--panel);
}
.account .rule{width:1px;height:22px;background:var(--rule)}
.account .trigger{display:flex;align-items:center;gap:6px;padding:4px;border-radius:99px}
.account .trigger:hover{background:var(--panel2)}
.account .trigger:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.account .avatar{
  width:28px;height:28px;border-radius:50%;display:grid;place-items:center;flex:0 0 auto;
  font-family:'JetBrains Mono',ui-monospace,monospace;font-size:10.5px;font-weight:600;
  color:var(--ink);background:linear-gradient(135deg,var(--accent),#5FD3A0);
}
.account .trigger[aria-haspopup]::after{content:"⌄";color:var(--muted);font-size:13px;margin-right:2px}
/* Signed out: no invented identity, just the way in. */
.account .signin{
  padding:6px 13px;border:1px solid var(--rule);border-radius:6px;font-size:12.5px;color:var(--dim);
  background:var(--shell);white-space:nowrap;
}
.account .signin:hover{border-color:var(--edge);color:var(--text)}
.account .signin:focus-visible{outline:2px solid var(--accent);outline-offset:2px}

.menu{
  position:absolute;top:calc(100% + 9px);right:0;z-index:70;min-width:214px;
  background:var(--panel2);border:1px solid var(--edge);border-radius:8px;padding:5px;
  box-shadow:0 24px 50px -20px #000;
}
.menu[hidden]{display:none}
.menu button{display:block;width:100%;text-align:left;padding:8px 10px;border-radius:5px;font-size:13px;color:var(--dim)}
.menu button:hover{background:#242C36;color:var(--text)}
.menu hr{border:0;border-top:1px solid var(--rule);margin:5px 2px}
.menu .who{
  font-family:'JetBrains Mono',ui-monospace,monospace;font-size:10px;letter-spacing:.12em;
  text-transform:uppercase;color:var(--muted);padding:8px 10px 4px;
}

/* ---------- layout picker ----------
   Presets sit on top of the free grid rather than in place of it: 4/9/12 are the
   pocket counts real binders ship in, and the 8×8 hover grid and the numeric
   fields underneath still reach anything up to 8×8. */
.layout-ctl{position:relative}
#layout-btn{
  background:var(--shell);border:1px solid var(--rule);border-radius:6px;color:var(--text);
  padding:6px 10px;font-family:'JetBrains Mono',ui-monospace,monospace;font-size:12px;
  letter-spacing:.03em;white-space:nowrap;
}
#layout-btn::after{content:"▾";margin-left:7px;color:var(--muted)}
#layout-btn:hover{border-color:var(--edge)}
#layout-btn:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
#layout-btn[aria-expanded="true"]{border-color:var(--accent)}

.layout-pop{
  position:absolute;top:calc(100% + 9px);left:0;z-index:60;width:max-content;
  background:var(--panel2);border:1px solid var(--edge);border-radius:9px;padding:14px;
  box-shadow:0 26px 55px -20px #000;
}
.layout-pop[hidden]{display:none}
/* On a narrow screen the picker sits on the scrolling control row, and two
   things break it there: `left:0` anchors a 265px panel to a trigger near the
   right edge — measured 31px off a 390px phone — and a scrolling row clips its
   absolutely positioned descendants. A fixed sheet answers both, being laid out
   against the viewport rather than the trigger or the row. Row two starts at
   52px and is 44px tall, hence 104. */
@media (max-width:560px){
  .barctl .layout-pop{
    position:fixed;top:104px;left:12px;right:12px;width:auto;max-width:340px;
    max-height:calc(100vh - 120px);overflow-y:auto;
  }
}
.presets{display:flex;gap:5px;margin-bottom:13px}
.presets button{
  border:1px solid var(--rule);border-radius:5px;padding:5px 9px;background:var(--shell);
  font-family:'JetBrains Mono',ui-monospace,monospace;font-size:11px;color:var(--muted);
}
.presets button:hover{color:var(--text);border-color:#3A4553}
.presets button[aria-pressed="true"]{background:color-mix(in srgb,var(--accent) 22%,transparent);border-color:var(--accent);color:#DCE6FF}
.lp-grid{display:grid;grid-template-columns:repeat(8,25px);grid-auto-rows:25px;gap:5px}
.lp-cell{background:var(--pocket);border:1px solid rgba(255,255,255,.09);border-radius:3px;padding:0}
.lp-cell:hover{border-color:rgba(255,255,255,.32)}
.lp-cell:focus-visible{outline:2px solid var(--accent);outline-offset:-1px}
.lp-cell.on{background:color-mix(in srgb,var(--accent) 40%,transparent);border-color:var(--accent)}
.lp-caption{
  margin-top:11px;font-family:'JetBrains Mono',ui-monospace,monospace;
  font-size:10.5px;letter-spacing:.05em;color:var(--muted);
}
.lp-fields{display:flex;align-items:flex-end;gap:9px;margin-top:13px;padding-top:13px;border-top:1px solid var(--rule)}
.lp-fields label{
  display:flex;flex-direction:column;gap:5px;
  font-family:'JetBrains Mono',ui-monospace,monospace;font-size:9.5px;letter-spacing:.14em;
  text-transform:uppercase;color:var(--muted);
}
.lp-fields input{
  width:62px;background:var(--shell);color:var(--text);border:1px solid var(--rule);border-radius:5px;
  padding:7px 6px;font-family:'JetBrains Mono',ui-monospace,monospace;font-size:12px;text-align:center;
}
.lp-fields input:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
.lp-fields .x{color:var(--muted);font-family:'JetBrains Mono',ui-monospace,monospace;font-size:11px;padding-bottom:9px}

/* ---------- run strip ----------
   Replaces the tally and the colour key, which were two separate rows that
   between them reached 11 stat blocks and 9 swatches on Ascended Heroes and
   pushed the toolbar off screen. One card per run, each stating its own
   progress, scrolling sideways instead of wrapping. */
.runs{display:flex;gap:8px;padding:10px 18px;border-bottom:1px solid var(--rule);overflow-x:auto;flex:0 0 auto}
.runcard{flex:0 0 auto;min-width:130px;border:1px solid var(--rule);border-radius:6px;padding:8px 10px;background:var(--panel)}
.runcard .label{display:flex;align-items:center;gap:6px;font-size:11.5px;color:var(--muted);margin-bottom:4px;white-space:nowrap}
.runcard .dot{width:7px;height:7px;border-radius:50%;flex:0 0 auto}
.runcard .fig{font-family:'JetBrains Mono',ui-monospace,monospace;font-size:13px;font-weight:600}
.runcard .fig span{color:var(--muted);font-weight:400;font-size:11px}
.runcard .bar{height:3px;border-radius:2px;background:#232B35;margin-top:6px;overflow:hidden}
.runcard .bar i{display:block;height:100%;border-radius:2px}

/* The two money cards. Set apart from the run cards by a rule rather than a
   colour: they are a different kind of fact, not another run, and giving them
   an accent would put them in competition with the progress they sit beside. */
.runcard.money{border-color:#2E3843;background:linear-gradient(180deg,var(--panel),#12161C)}
.runcard.money .fig{font-size:14px;letter-spacing:-.02em}
.runcard.money .fig.dim{color:var(--muted);font-weight:400}

/* Says out loud that the number is incomplete, and why. A total that silently
   omits the expensive missing card is worse than no total at all. */
.runcard .partial{
  display:block;margin-top:5px;font-family:'JetBrains Mono',ui-monospace,monospace;
  font-size:9px;letter-spacing:.04em;color:#9A7B4A;white-space:nowrap;
}
.runcard .partial.ok{color:#5F6875}

/* ---------- first-run tip ----------
   In the flow directly under the controls it describes, and it retires itself
   the moment you mark a pocket — teaching copy shouldn't be permanent chrome. */
.tip{
  display:flex;align-items:center;gap:10px;flex:0 0 auto;
  padding:8px 18px;border-bottom:1px solid var(--rule);
  font-size:12.5px;color:var(--muted);background:color-mix(in srgb,var(--accent) 7%,transparent);
}
.tip[hidden]{display:none}
.tip b{color:var(--dim);font-weight:500}
.tip .close{margin-left:auto;color:var(--muted);font-size:15px;line-height:1;padding:2px 6px;border-radius:4px}
.tip .close:hover{background:var(--panel2);color:var(--text)}

/* Same strip, different job: teaching copy is tinted with the set accent, a
   stale-data notice with amber, so the two can't be mistaken for each other at
   a glance. Its own hue rather than --secret, which means "secret rare" here. */
.tip.stale{background:color-mix(in srgb,#E4A244 8%,transparent)}
.tip.stale b{color:#E8B563}
.tip .linkish{
  margin-left:auto;color:var(--muted);font-size:12px;padding:2px 8px;border-radius:4px;
  border:1px solid var(--rule);
}
.tip .linkish:hover{background:var(--panel2);color:var(--text)}

.scroll{
  flex:1;overflow-y:auto;min-height:0;padding:26px 18px 90px;
  background:radial-gradient(1200px 700px at 50% -10%, var(--glow) 0%, transparent 60%);
}
/* The single-column breakpoint has to key off the width the binder actually got,
   not the viewport's — with a 268px rail those differ by exactly 268px. */
.sheet{max-width:1060px;margin:0 auto;container-type:inline-size}

/* ---------- binder ---------- */
/* Gutter, page padding and pocket gaps all come off the ramp declared on
   .sheet — see "the spacing ramp" below. At an 820px sheet and wider they are
   34/14/9, which is what they always were. */
.spread{display:grid;grid-template-columns:1fr var(--gutter) 1fr;align-items:start;margin:0 0 40px}

.gutter{
  align-self:stretch;display:flex;flex-direction:column;justify-content:space-evenly;
  align-items:center;padding:calc(var(--pad) * 1.85) 0;
  background:linear-gradient(90deg,rgba(0,0,0,.55),rgba(255,255,255,.03),rgba(0,0,0,.55));
}
.ring{
  width:var(--ring);height:var(--ring);border-radius:50%;background:#05060A;
  border:1px solid rgba(255,255,255,.18);
  box-shadow:inset 0 1px 2px rgba(0,0,0,.9),0 1px 0 rgba(255,255,255,.06);
}

.page{
  background:linear-gradient(180deg,var(--page) 0%,#10141B 100%);
  border:1px solid var(--page-edge);padding:var(--pad);position:relative;
  box-shadow:0 24px 50px -30px #000;
}
.page.left{border-radius:5px 0 0 5px;box-shadow:inset -22px 0 34px -30px #000,0 24px 50px -30px #000}
.page.right{border-radius:0 5px 5px 0;box-shadow:inset 22px 0 34px -30px #000,0 24px 50px -30px #000}
.page.blank{
  background:repeating-linear-gradient(-45deg,transparent,transparent 9px,rgba(255,255,255,.017) 9px,rgba(255,255,255,.017) 18px);
  border-style:dashed;border-color:rgba(255,255,255,.07);
}

.pagenum{
  position:absolute;bottom:-24px;font-family:'JetBrains Mono',ui-monospace,monospace;
  font-size:10px;letter-spacing:.18em;color:#5A6273;text-transform:uppercase;
}
.page.left .pagenum{left:2px}
.page.right .pagenum{right:2px}

.grid{display:grid;gap:var(--gap)}

/* One grid cell = one pocket plus its price. The wrapper exists because the
   price is a buy link and an <a> cannot nest inside the <button> that toggles
   ownership; see slotEl() in binder.js. It adds no size of its own — the pocket
   still carries the aspect ratio, so every layout measurement is unchanged.

   The hover lift moved up here from .pocket so the pair rises together. Without
   that, hovering a price left it hanging 3px below the card it belongs to. */
.slot{position:relative;display:flex;flex-direction:column;min-width:0;transition:transform .16s ease}
.slot:hover{transform:translateY(-3px)}
.slot > .pocket{width:100%}

.pocket{
  position:relative;aspect-ratio:5/7;border-radius:6px;
  background:var(--pocket);border:1px solid rgba(255,255,255,.055);
  overflow:hidden;cursor:pointer;padding:0;
  transition:box-shadow .16s ease,border-color .16s ease;
}
.slot:hover > .pocket{border-color:rgba(255,255,255,.2);box-shadow:0 10px 22px -12px #000}
.pocket:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.pocket.filler{cursor:default;opacity:.25}
.pocket img{width:100%;height:100%;object-fit:cover;display:block;opacity:.94}
.pocket.owned img{opacity:1}
.pocket:not(.owned) img{filter:saturate(.28) brightness(.6)}

.empty-slot{width:100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:5px;padding:8px;text-align:center}
.empty-slot .q{font-family:'Bricolage Grotesque',sans-serif;font-size:22px;color:#39414F}
.empty-slot .t{font-family:'JetBrains Mono',ui-monospace,monospace;font-size:8.5px;letter-spacing:.1em;color:#4E5766;line-height:1.4}
/* Why this pocket has no artwork. Brighter than the name above it, not dimmer:
   the whole point is that an empty pocket stops reading as a rendering bug, so
   the one line that explains it is the line that has to survive a glance. */
.empty-slot .why{
  margin-top:1px;font-family:'JetBrains Mono',ui-monospace,monospace;
  font-size:7.5px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--muted);border:1px solid var(--rule);border-radius:2px;padding:1px 4px;
}

.tag{
  position:absolute;top:5px;left:5px;z-index:2;
  font-family:'JetBrains Mono',ui-monospace,monospace;font-size:8.5px;font-weight:600;
  letter-spacing:.06em;padding:2px 5px;border-radius:2px;
  background:rgba(6,7,10,.82);border:1px solid rgba(255,255,255,.14);color:var(--text);
}
.tag.rh{color:var(--rh);border-color:color-mix(in srgb,var(--rh) 45%,transparent)}
/* A print run (Shadowless, 1st Edition) reads against the same artwork as the
   default printing, so its tag is the only thing telling the two apart. Hues
   are kept clear of --rh, --secret and --chase for that reason. */
.tag.print{color:var(--print);border-color:color-mix(in srgb,var(--print) 45%,transparent)}
/* A patterned run (Poké Ball, Master Ball, …) carries its own colour inline. */
.tag.run{color:var(--run);border-color:color-mix(in srgb,var(--run) 45%,transparent)}
.tag.sec{color:var(--secret);border-color:color-mix(in srgb,var(--secret) 45%,transparent)}
.tag.chase{color:var(--chase);border-color:color-mix(in srgb,var(--chase) 45%,transparent)}

.check{position:absolute;top:5px;right:5px;z-index:2;width:15px;height:15px;border-radius:50%;border:1px solid rgba(255,255,255,.22);background:rgba(6,7,10,.7)}
.pocket.owned .check{background:var(--accent);border-color:var(--accent)}
.pocket.owned .check::after{content:"";position:absolute;left:4.5px;top:2px;width:4px;height:8px;border:solid #06070A;border-width:0 2px 2px 0;transform:rotate(43deg)}

.caption{
  position:absolute;left:0;right:0;bottom:0;z-index:2;padding:14px 6px 5px;
  background:linear-gradient(180deg,transparent,rgba(4,5,8,.93) 55%);font-size:10px;line-height:1.25;
}
.caption .n{font-family:'JetBrains Mono',ui-monospace,monospace;font-size:8.5px;color:#8C95A6}
.caption .nm{display:block;font-weight:500;letter-spacing:-.01em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

/* ---------- price ----------
   TCGplayer Market, per printing, as a link to that printing. Secondary by
   construction: dimmer than the card name, no colour of its own so it cannot
   argue with a set theme, and never heavier than the ownership check.

   The caption's own metrics are restated here as custom properties because
   draft A parks the price on the caption's first line and the price is not
   inside the caption — it is a sibling of the button. Change .caption's padding
   or the name's line box and these move with it. */
.slot{--cap-b:5px;--cap-x:6px;--cap-name:12.5px}

/* Link blue, fixed rather than themed. `var(--accent)` would have been the
   obvious choice and is wrong twice over: it is the colour of the owned check,
   and it changes per set, so the price would be gold on Base Set and blue on
   Pitch Black — a link affordance that moves is not an affordance.

   Measured against every colour that can share a pocket (24 of them, across
   accents, --rh, secret, chase and every reverse-run tag): this sits 7° of hue
   from the default accent #8FB8FF and 6° from the reverse-run blue #4FA3E0.
   That is deliberately not a hue separation — nothing in the 200-225° band is
   more than 6° from something. It holds because the *forms* differ absolutely:
   the check is a filled 15px circle at top-right, the run tag is a bordered
   chip at top-left, and this is a currency string at bottom-right. No two of
   them can be mistaken for each other the way two 9px tag chips could. */
.price{
  position:absolute;z-index:3;
  right:var(--cap-x);bottom:calc(var(--cap-b) + var(--cap-name));
  font-family:'JetBrains Mono',ui-monospace,monospace;
  font-size:9px;font-weight:600;line-height:1.15;letter-spacing:-.02em;
  color:#5AA9FF;text-decoration:none;white-space:nowrap;
}
.price:hover{color:#9ECBFF;text-decoration:underline;text-underline-offset:2px}
.price:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:2px}

/* Fetched, and TCGdex lists no price for this printing. Never "$0.00". */
.price.none{color:#5F6875;font-weight:400}

/* Not an exact product link — a keyword search that may land on the wrong
   printing. The dotted rule is the whole disclosure, so it is always on. */
.price.search{text-decoration:underline dotted;text-decoration-color:#4E5766;text-underline-offset:2px}
.price.search:hover{text-decoration-color:currentColor}

/* Not fetched yet. Invisible rather than absent, so nothing reflows when the
   number lands. */
.price.pending{opacity:0;pointer-events:none}

/* ---------- zoom trigger ----------
   Third affordance on a pocket, after the ownership check and the price. It
   sits immediately left of the check, which is the only free corner: the tag
   holds top-left and the caption owns the whole bottom edge including the
   number the price shares a line with.

   Adjacency to the check is the risk this has to manage, so the two are told
   apart by every means except position — the check is a filled 15px circle and
   this is a hollow outlined square, the check is always on and this fades in,
   and, decisively, they are separate elements in separate event paths, so the
   worst a mis-aimed click can do is open a card the reader closes again.

   The gap between them is deliberate and was 3px in the first draft. A miss to
   the right of this button is not a no-op — it lands on the pocket and marks
   the card owned — so it is sized for the consequence, not the look. It
   measures 5px wide / 4px narrow rather than the 6px the offsets imply: this
   button is positioned against .slot and the check against .pocket, which is
   inset by its own 1px border. Swept 420-1600px on Base Set (longest tags):
   worst tag gap 20.2px, worst check gap 4px, no overlap at any width. */
.zoom{
  position:absolute;top:3px;right:26px;z-index:4;
  width:20px;height:20px;padding:0;border-radius:3px;
  display:flex;align-items:center;justify-content:center;
  background:rgba(6,7,10,.78);border:1px solid rgba(255,255,255,.16);
  color:#B6BECC;cursor:pointer;
  opacity:0;transition:opacity .14s ease,color .14s ease,border-color .14s ease;
}
.slot:hover .zoom,
.zoom:focus-visible{opacity:1}
.zoom:hover{color:#fff;border-color:rgba(255,255,255,.4);background:rgba(6,7,10,.92)}
.zoom:focus-visible{outline:2px solid var(--accent);outline-offset:1px}

/* The lens, drawn rather than drawn from a file — the same trick the owned
   check uses, and the reason this repo still ships no icon asset. */
.zoom i{display:block;position:relative;width:8px;height:8px;border:1.5px solid currentColor;border-radius:50%}
.zoom i::after{content:"";position:absolute;right:-3.5px;bottom:-3px;width:1.5px;height:5px;background:currentColor;transform:rotate(-45deg);border-radius:1px}

/* Hover cannot reveal anything on a touch screen, so there the trigger is
   simply always on, and gets a hit area half again its size without growing
   the icon into the artwork. */
@media (hover:none){
  .zoom{opacity:1}
  .zoom::after{content:"";position:absolute;inset:-6px}
}

/* Matches the check and tag shrinking at the same breakpoint (see below): the
   check is 12px at right:3px there, so this lands 3px to its left. */
@container (max-width:620px){
  .zoom{top:2px;right:20px;width:16px;height:16px}
  .zoom i{width:6px;height:6px;border-width:1.5px}
  .zoom i::after{right:-3px;bottom:-2.5px;height:4px}
}

/* ---------- zoom dialog ----------
   Full-viewport, transparent, with the card centred in it. Sizing the dialog to
   the card instead would have been tidier and wrong twice: the arrows would
   have nowhere to sit but on top of the artwork, and backdrop-click-to-close
   would only work on the strip of screen the card did not cover. */
.zoomdlg{
  border:0;padding:0;background:transparent;overflow:hidden;
  width:100vw;max-width:100vw;height:100dvh;max-height:100dvh;
}
/* Guarded, because `dialog:not([open])` is display:none in the UA sheet and an
   unconditional display here would leave this on screen permanently. */
.zoomdlg[open]{
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:14px;
  animation:zoomin .17s ease-out;
}
.zoomdlg::backdrop{background:rgba(4,6,10,.86)}
/* A modal dialog blocks clicks on what is behind it but not the wheel, and the
   binder scrolls in .scroll rather than on the document, so this is the element
   that has to be pinned — otherwise closing the dialog returns you to a
   different row than the card you opened. */
body.zoomed .scroll{overflow:hidden}
@keyframes zoomin{from{opacity:0;transform:scale(.96)}to{opacity:1;transform:none}}

/* Height-led, so a wide short window gets a card that fits rather than one
   cropped by the viewport, and the width follows the ratio. Both scans are 5:7,
   so the low-to-high swap cannot move anything. */
.zoom-stage{
  width:min(calc(100vw - 88px), calc(82dvh * 5 / 7));
  aspect-ratio:5/7;border-radius:10px;overflow:hidden;
  box-shadow:0 30px 80px rgba(0,0,0,.7);
}
.zoom-stage img{
  width:100%;height:100%;display:block;object-fit:contain;background:var(--pocket);
  transition:filter .2s ease;
}
/* While the grid image is standing in for the large scan. The blur is doing a
   job the sharp upscale would not: it makes the swap read as the picture coming
   into focus, rather than as a picture that was broken and got fixed. */
.zoomdlg.soft .zoom-stage img{filter:blur(1.5px)}

/* Note there is deliberately no `filter` above. The grid desaturates cards you
   do not own so the owned ones are findable at a glance; enlarging one is
   asking to see that card, and a single card has nothing to be picked out from.
   So the dialog shows every card in full colour and says owned in words below. */
.zoom-meta{
  display:flex;align-items:baseline;gap:10px;margin:0;
  max-width:min(calc(100vw - 88px), calc(82dvh * 5 / 7));
}
.zoom-num{font-family:'JetBrains Mono',ui-monospace,monospace;font-size:11px;color:var(--muted);flex:0 0 auto}
.zoom-name{font-size:14px;font-weight:500;letter-spacing:-.01em;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.zoom-price{
  flex:0 0 auto;font-family:'JetBrains Mono',ui-monospace,monospace;
  font-size:12px;font-weight:600;letter-spacing:-.02em;color:#5AA9FF;text-decoration:none;
}
.zoom-price:hover{color:#9ECBFF;text-decoration:underline;text-underline-offset:2px}
.zoom-price.search{text-decoration:underline dotted;text-decoration-color:#4E5766;text-underline-offset:2px}
/* Owned is stated in words here. The grid's filled check is a 15px target you
   can hit; repeating it on a surface where it cannot be clicked would read as
   a control that stopped working. */
.zoomdlg.owned .zoom-num::before{content:"✓ ";color:var(--accent)}

.zoom-nav,.zoom-x{
  position:fixed;z-index:1;padding:0;
  display:flex;align-items:center;justify-content:center;
  border:1px solid var(--rule);border-radius:50%;
  background:rgba(14,17,22,.9);color:var(--dim);cursor:pointer;
  transition:color .14s ease,border-color .14s ease,opacity .14s ease;
}
.zoom-nav{top:50%;transform:translateY(-50%);width:42px;height:42px;font-size:24px;line-height:1}
.zoom-nav.prev{left:14px}
.zoom-nav.next{right:14px}
.zoom-x{top:14px;right:14px;width:36px;height:36px;font-size:20px;line-height:1}
.zoom-nav:hover:not(:disabled),.zoom-x:hover{color:var(--text);border-color:var(--edge)}
.zoom-nav:disabled{opacity:.22;cursor:default}
.zoom-nav:focus-visible,.zoom-x:focus-visible{outline:2px solid var(--accent);outline-offset:2px}

/* A phone has no 88px of margin to park arrows in, so they drop to the bottom
   corners and flank the caption instead of covering the card. They are not
   removed: with the binder behind it hidden by the backdrop, these arrows are
   the only way to reach the next card without closing and re-aiming. */
@media (max-width:560px){
  .zoom-stage,.zoom-meta{width:min(calc(100vw - 24px), calc(72dvh * 5 / 7));max-width:none}
  .zoom-nav{top:auto;bottom:16px;transform:none;width:38px;height:38px;font-size:21px}
  .zoom-meta{justify-content:center}
  .zoom-name{font-size:13px}
}

.notes{max-width:78ch;margin:36px auto 0;color:#7B8494;font-size:12.5px;line-height:1.7}
.notes b{color:var(--muted);font-weight:600}

.error{max-width:70ch;margin:60px auto;padding:24px;border:1px dashed var(--rule);border-radius:6px;color:var(--muted);font-size:14px;line-height:1.7}
.error code{color:var(--text);font-family:'JetBrains Mono',ui-monospace,monospace;font-size:12.5px}

/* ---------- account dialog and toasts ----------
   The corner itself is unchanged — .account, .signin, .trigger, .avatar and
   .menu are all as they were. Everything here is the surface behind it, which
   did not exist before there was an account to sign in to. */

.sheetdlg{
  border:1px solid var(--rule);border-radius:10px;padding:0;
  background:var(--panel);color:var(--text);
  width:min(420px,calc(100vw - 32px));max-height:calc(100vh - 64px);
  box-shadow:0 24px 70px rgba(0,0,0,.6);
}
.sheetdlg::backdrop{background:rgba(4,6,10,.62)}
.dlg-body{padding:26px 26px 22px;overflow-y:auto;max-height:calc(100vh - 64px)}
.dlg-x{position:absolute;top:0;right:0;margin:0}
.dlg-x button{
  width:34px;height:34px;color:var(--muted);font-size:18px;line-height:1;border-radius:6px;
}
.dlg-x button:hover{background:var(--panel2);color:var(--text)}

.sheetdlg h2{
  font-family:'Bricolage Grotesque',sans-serif;font-size:20px;font-weight:800;
  letter-spacing:-.02em;margin:0 0 6px;
}
.dlg-sub{color:var(--muted);font-size:13px;line-height:1.6;margin:0 0 18px}

.dlg-form{display:flex;flex-direction:column;gap:13px;margin:0 0 4px}
.dlg-form label{display:flex;flex-direction:column;gap:5px;font-size:12.5px;color:var(--muted)}
.dlg-form label i{font-style:normal;color:#6C7686;font-size:11.5px}
.dlg-form input{
  background:var(--shell);border:1px solid var(--rule);border-radius:6px;color:var(--text);
  padding:9px 11px;font:14px 'Inter Tight',sans-serif;width:100%;
}
.dlg-form input:focus{outline:none;border-color:var(--accent)}
.dlg-form input:disabled{color:var(--muted);background:#11151B}
.dlg-form small{color:#6C7686;font-size:11.5px;line-height:1.5}

.sheetdlg .primary{
  background:var(--accent);color:#0A0E14;border-radius:6px;padding:10px 14px;
  font-size:13.5px;font-weight:600;width:100%;margin-top:4px;
}
.sheetdlg .primary:hover{filter:brightness(1.08)}
.sheetdlg .primary:disabled{opacity:.55;cursor:default}
.dlg-alt{
  width:100%;border:1px solid var(--rule);border-radius:6px;padding:9px 14px;
  font-size:13px;color:var(--dim);margin-top:9px;
}
.dlg-alt:hover{background:var(--panel2);border-color:var(--edge);color:var(--text)}

.dlg-or{display:flex;align-items:center;gap:12px;margin:18px 0 4px;color:#6C7686;font-size:11.5px}
.dlg-or::before,.dlg-or::after{content:"";flex:1;height:1px;background:var(--rule)}

.dlg-err{
  color:#F0908C;font-size:12.5px;line-height:1.5;margin:0;
  background:color-mix(in srgb,#F0908C 9%,transparent);
  border-left:3px solid #F0908C;padding:8px 11px;border-radius:0 5px 5px 0;
}
.dlg-err[hidden]{display:none}

/* Says the surface is not wired to anything yet. Amber, like .tip.stale and the
   legal pages' dormant-clause callout, because it means the same thing: what
   you are reading does not describe a live system. */
.dlg-note{
  border-left:3px solid #E4A244;background:color-mix(in srgb,#E4A244 8%,transparent);
  padding:9px 12px;border-radius:0 5px 5px 0;margin:0 0 16px;
  font-size:12.5px;line-height:1.55;color:var(--muted);
}
.dlg-note b{color:#E8B563}

.dlg-foot{color:var(--muted);font-size:12px;line-height:1.6;margin:16px 0 0}
.linklike{color:var(--accent);font-size:inherit;padding:0;text-decoration:underline}

.dlg-stats{margin:0 0 18px;display:flex;flex-direction:column;gap:1px}
.dlg-stats div{display:flex;justify-content:space-between;gap:16px;padding:9px 0;border-bottom:1px solid var(--rule)}
.dlg-stats dt{color:var(--muted);font-size:12.5px;margin:0}
.dlg-stats dd{margin:0;font-size:12.5px;color:var(--text)}

/* Bottom-centre, above everything, never blocking a click. */
.toasts{
  position:fixed;left:50%;bottom:26px;transform:translateX(-50%);z-index:200;
  display:flex;flex-direction:column;gap:8px;align-items:center;pointer-events:none;
}
.toast{
  background:var(--panel2);border:1px solid var(--edge);border-radius:7px;
  padding:10px 16px;font-size:13px;color:var(--text);
  box-shadow:0 14px 40px rgba(0,0,0,.5);
  animation:toastin .22s ease;max-width:min(460px,calc(100vw - 32px));
}
.toast.out{opacity:0;transition:opacity .35s ease}
@keyframes toastin{from{opacity:0;transform:translateY(9px)}to{opacity:1;transform:none}}

/* ---------- site footer and the legal pages ----------
   The footer is rendered by src/site.js into #sitefoot on all three pages, so
   it gets one set of rules here whether it is sitting under the binder or under
   a policy. docs/legal-findings.md is why it exists at all: an on-page
   non-affiliation notice is the cheapest part of the whole legal position. */

.sitefoot{
  max-width:78ch;margin:44px auto 0;padding-top:18px;border-top:1px solid var(--rule);
  color:#7B8494;font-size:12px;line-height:1.7;
}
.sitefoot .footlinks{display:flex;flex-wrap:wrap;gap:16px;margin-bottom:10px}
.sitefoot .footlinks a{color:var(--muted);text-decoration:none}
.sitefoot .footlinks a:hover{color:var(--text);text-decoration:underline}
.sitefoot .disclaimer{margin:0;max-width:70ch}

/* A policy page is a document, not an app: the shell's full-height grid and
   overflow:hidden would clip it to the viewport and eat the scroll. */
body.docpage{
  display:block;height:auto;overflow:visible;
  background:radial-gradient(1100px 620px at 50% -8%, var(--glow) 0%, transparent 60%) var(--shell);
}
body.docpage .sitefoot{margin-bottom:70px;padding-left:22px;padding-right:22px}

.doc{max-width:74ch;margin:0 auto;padding:38px 22px 0;line-height:1.75;font-size:14.5px;color:var(--dim)}
.doc .crumb{margin:0 0 30px;font-size:12.5px}
.doc .crumb a{color:var(--muted);text-decoration:none}
.doc .crumb a:hover{color:var(--text)}
.doc h1{
  font-family:'Bricolage Grotesque',var(--ui,'Inter Tight'),system-ui,sans-serif;
  font-size:30px;font-weight:800;letter-spacing:-.02em;margin:0 0 14px;color:var(--text);
}
.doc h2{font-size:16px;font-weight:600;margin:38px 0 10px;color:var(--text)}
.doc .lede{font-size:16px;color:var(--muted);margin:0 0 6px}
.doc .stamp{font-size:12px;color:#7B8494;margin:0 0 4px}
.doc .stamp b{color:var(--muted);font-weight:500}
.doc p{margin:0 0 14px}
.doc b{color:var(--text);font-weight:600}
.doc code{
  font-family:'JetBrains Mono',ui-monospace,monospace;font-size:12.5px;
  background:var(--panel2);padding:1px 5px;border-radius:4px;color:var(--muted);
}
.doc ul.plain{margin:0 0 14px;padding-left:20px}
.doc ul.plain li{margin-bottom:7px}

/* Not a warning — a statement that a clause is dormant. Amber for the same
   reason .tip.stale is: it means "this does not describe today". */
.doc .callout{
  border-left:3px solid #E4A244;background:color-mix(in srgb,#E4A244 7%,transparent);
  padding:11px 15px;border-radius:0 5px 5px 0;margin:0 0 16px;font-size:13.5px;
}
.doc .callout b{color:#E8B563}

.doc .grid{
  width:100%;border-collapse:collapse;margin:0 0 16px;font-size:13px;
  display:block;overflow-x:auto;
}
.doc .grid th,.doc .grid td{
  text-align:left;vertical-align:top;padding:9px 14px 9px 0;border-bottom:1px solid var(--rule);
}
.doc .grid th{color:var(--muted);font-weight:600;font-size:11px;letter-spacing:.08em;text-transform:uppercase}
.doc .grid .host{
  display:block;font-family:'JetBrains Mono',ui-monospace,monospace;
  font-size:11px;color:#7B8494;margin-top:3px;
}

@media (max-width:620px){
  .doc{padding:26px 18px 0;font-size:14px}
  .doc h1{font-size:25px}
}

/* ---------- responsive ----------
   The arrangement is no longer chosen here. `chooseView()` in binder.js picks a
   tier from the width the sheet actually got and puts `tight` or `paged` on the
   binder root; this file only dresses the tier it is told about.

   That split matters. When the choice lived in a media query, CSS could remove
   the spread's second column but could not remove the blank inside cover from
   the page list, so a spread-only artifact survived into a layout with no
   spreads. Only the code that builds pages can decline to build one. */

/* The spacing ramp. --t is progress along it expressed as a length, 0px at a
   502px sheet and 318px at an 820px one, which lets the three spacings be
   interpolated with plain calc(). At or above 820 every value pins to what it
   has always been, so the wide spread is untouched; below it the gutter, the
   page padding and the pocket gaps give way so the spread itself can survive.

   These endpoints mirror RAMP/WIDE/TIGHT in binder.js, which uses them to
   predict the pocket size and pick the tier. Change one, change both.

   The 100cqw works even though it is written on .sheet, which is itself the
   container: an unregistered custom property holds an unresolved token stream,
   so the unit is resolved where the property is *used* — in .spread, .page and
   .grid, whose nearest ancestor container is .sheet. Declaring these on a
   descendant would work too; declaring them here reads better. Verified by
   measurement, not assumed — the rendered pocket matches binder.js's prediction
   to 0.1px from a 460px sheet to 1060px. */
.sheet{
  --t:clamp(0px, calc(100cqw - 502px), 318px);
  --gutter:calc(14px + var(--t) * 20 / 318);
  --pad:calc(8px + var(--t) * 6 / 318);
  --gap:calc(5px + var(--t) * 4 / 318);
  --ring:calc(7px + var(--t) * 4 / 318);
}

/* Below roughly an 85px pocket the card name is three ellipsed characters, and
   the artwork already prints it. The number stays. */
@container (max-width:620px){
  .caption{padding:10px 4px 3px}
  .caption .nm{display:none}
  .tag{font-size:7.5px;padding:1px 3px;top:3px;left:3px}
  .check{width:12px;height:12px;top:3px;right:3px}
  .pocket.owned .check::after{left:3.5px;top:1.5px;width:3px;height:6px;border-width:0 2px 2px 0}
  .pagenum{bottom:-20px;font-size:9px}

  /* The name row is gone, so the number line *is* the bottom line and the
     price sits on it. Everything below follows the caption's new padding. */
  .slot{--cap-b:3px;--cap-x:4px;--cap-name:0px}
  .price{font-size:8px;letter-spacing:-.03em}
}

/* Paged tier — one page, and the spread said rather than drawn. */
.paged .page{margin-top:16px}
.paged .pagenum{position:static;display:block;margin-top:11px;text-align:center}

.pagerbar{
  position:sticky;top:0;z-index:20;display:flex;align-items:center;gap:12px;
  padding:9px 10px;background:rgba(14,17,22,.94);border:1px solid var(--rule);
  border-radius:9px;backdrop-filter:blur(8px);
}
.pagernav{
  flex:0 0 auto;width:32px;height:32px;border:1px solid var(--rule);border-radius:7px;
  background:var(--shell);color:var(--dim);font-size:17px;line-height:1;
}
.pagernav:hover:not(:disabled){border-color:var(--edge);color:var(--text)}
.pagernav:disabled{opacity:.32;cursor:default}
.pagernav:focus-visible{outline:2px solid var(--accent);outline-offset:2px}

/* The spread, 26px wide. The lit half is the page you are on; a half with no
   page is dashed — which is how "page 1 faces the inside cover" gets said
   without building a blank page to say it. */
.pagerbar .mini{flex:0 0 auto;display:flex;gap:2px}
.pagerbar .mini i{width:12px;height:17px;border:1px solid #39414F;border-radius:2px;background:#161B22}
.pagerbar .mini i:first-child{border-radius:3px 1px 1px 3px}
.pagerbar .mini i:last-child{border-radius:1px 3px 3px 1px}
.pagerbar .mini i.on{background:var(--accent);border-color:var(--accent)}
.pagerbar .mini i.none{border-style:dashed;background:none}

.pagerbar .where{flex:1;min-width:0;line-height:1.3}
.pagerbar .where b{display:block;font-size:13px;font-weight:600;letter-spacing:-.01em}
.pagerbar .where span{
  display:block;font-family:'JetBrains Mono',ui-monospace,monospace;
  font-size:9.5px;letter-spacing:.09em;text-transform:uppercase;color:var(--muted);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.pagercount{
  flex:0 0 auto;font-family:'JetBrains Mono',ui-monospace,monospace;
  font-size:10.5px;color:var(--muted);
}
/* Below this the count is the least useful thing in the bar and the facing-page
   line is the most, so drop the count and let the line wrap rather than ellipse
   away the words that carry the spread. */
@container (max-width:430px){
  .pagercount{display:none}
  .pagerbar .where span{white-space:normal;overflow:visible}
}

/* ---------- collapsible rail ----------
   48-64px is the documented band for a collapsed rail, and 56 sits in it. It is
   not an icon rail though: Material's rail assumes 3-7 destinations and this one
   holds 70 sets behind a search box, so the strip carries affordances — reopen,
   jump to search, current set's completion — rather than pretending to be a
   shrunken list. */
/* The rail's width is a body grid track, not a width on .rail, so the animation
   has to go on the grid. */
body{transition:grid-template-columns .2s ease}
.railtoggle{
  margin-left:auto;align-self:center;width:24px;height:24px;border-radius:5px;color:var(--muted);
  font-size:15px;line-height:1;display:grid;place-items:center;flex:0 0 auto;
}
.railtoggle:hover{background:var(--panel2);color:var(--text)}
.railtoggle:focus-visible{outline:2px solid var(--accent);outline-offset:-1px}

/* Only ever shown at phone widths — see the toolbar markup. */
.railopen{
  display:none;flex:0 0 auto;width:30px;height:30px;border-radius:7px;
  border:1px solid var(--rule);background:var(--shell);color:var(--dim);
  font-size:14px;line-height:1;place-items:center;
}
.railopen:hover{border-color:var(--edge);color:var(--text)}
.railopen:focus-visible{outline:2px solid var(--accent);outline-offset:2px}

.railstrip{display:none;flex-direction:column;align-items:center;gap:12px;padding:12px 0}
.railbtn{
  width:32px;height:32px;border-radius:7px;border:1px solid var(--rule);background:var(--shell);
  color:var(--muted);font-size:15px;line-height:1;display:grid;place-items:center;
}
.railbtn:hover{border-color:var(--edge);color:var(--text)}
.railbtn:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.railpct{display:flex;flex-direction:column;align-items:center;gap:6px;margin-top:2px}
.railpct .track{width:4px;height:56px;border-radius:2px;background:#232B35;overflow:hidden;display:flex;align-items:flex-end}
.railpct .track i{display:block;width:100%;background:var(--accent);border-radius:2px;transition:height .25s ease}
.railpct b{font-family:'JetBrains Mono',ui-monospace,monospace;font-size:9px;color:var(--muted);font-weight:500}

body.rail-collapsed{grid-template-columns:56px 1fr}
body.rail-collapsed .brand{justify-content:center;padding-left:0;padding-right:0}
body.rail-collapsed .brand b,
body.rail-collapsed .brand span,
body.rail-collapsed .search,
body.rail-collapsed .setlist{display:none}
body.rail-collapsed .railtoggle{margin-left:0}
body.rail-collapsed .railstrip{display:flex}

/* Below this the rail cannot be a column at all, so the same toggle drives an
   overlay instead. Without this the toggle would be a no-op on exactly the
   devices the paged tier exists for. */
.scrim{
  position:fixed;inset:0;z-index:75;background:rgba(4,5,8,.6);
  opacity:0;pointer-events:none;transition:opacity .2s ease;
}
@media (max-width:760px){
  body,body.rail-collapsed{grid-template-columns:1fr}
  /* Only the control row scrolls here, so the set name and the owned count stay
     put — see .barctl. The hamburger sits on the row that does not move, so it
     does not need to be sticky. */
  .railopen{display:grid}
  .rail{
    position:fixed;left:0;top:0;bottom:0;width:268px;z-index:80;
    transform:translateX(-100%);transition:transform .2s ease;
  }
  body:not(.rail-collapsed) .rail{transform:none;box-shadow:0 0 60px rgba(0,0,0,.7)}
  body:not(.rail-collapsed) .scrim{opacity:1;pointer-events:auto}
  /* The strip has nowhere to live on a phone; the toolbar button reopens. */
  body.rail-collapsed .railstrip{display:none}
  body.rail-collapsed .brand b,
  body.rail-collapsed .brand span,
  body.rail-collapsed .search,
  body.rail-collapsed .setlist{display:revert}
}
/* The zoom dialog is the first thing here to use a keyframe rather than a
   transition, so the blanket rule below no longer covers everything on its own. */
@media (prefers-reduced-motion:reduce){*{transition:none!important;animation:none!important}}
@media print{
  body{display:block;height:auto;overflow:visible;background:#fff;color:#000}
  .rail,.toolbar,.runs,.tip,.account,.notes{display:none}
  .main,.scroll{display:block;overflow:visible;padding:0;background:none}
  .page{break-inside:avoid}
}
