:root {
  --bg: #14161a;
  --panel: #1d2026;
  --panel2: #242833;
  --ink: #e8eaed;
  --muted: #9aa1ad;
  --accent: #6ea8fe;
  --good: #5ad19a;
  --bad: #ff7b7b;
  --warn: #f0c674;
  --border: #2c313c;
  --price-hot: #ff4d4d;   /* the $500 end of the price ramp */
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 15px/1.5 system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
}

main { max-width: 1300px; margin: 0 auto; padding: 1.2rem; }
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
.muted { color: var(--muted); }
.warn { color: var(--warn); }
.bad { color: var(--bad); }
.back { color: var(--muted); }

h1 { font-size: 1.6rem; }
h2 { font-size: 1.1rem; margin: 0 0 .5rem; }
h3 { font-size: .95rem; margin: 1rem 0 .4rem; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; }

button, .btn, .button {
  font: inherit;
  background: var(--accent);
  color: #08121f;
  border: 0;
  border-radius: 7px;
  padding: .4rem .8rem;
  cursor: pointer;
  font-weight: 600;
}
button:hover, .btn:hover, .button:hover { filter: brightness(1.08); text-decoration: none; }
.btn.ghost { background: transparent; color: var(--ink); border: 1px solid var(--border); }
.inline { display: inline; }

/* --- home --- */
.hero { max-width: 640px; margin: 8vh auto; text-align: center; }
.hero h1 { font-size: 2.6rem; margin-bottom: .3rem; }
.tagline { color: var(--muted); margin-bottom: 1.6rem; }
.search { display: flex; gap: .5rem; }
.search input { flex: 1; }
input[type=text] {
  background: var(--panel);
  border: 1px solid var(--border);
  color: var(--ink);
  border-radius: 7px;
  padding: .5rem .7rem;
  font: inherit;
}
input[type=text]:focus { outline: 2px solid var(--accent); }

/* --- deck picker --- */
table.decks { width: 100%; border-collapse: collapse; margin-top: .8rem; }
table.decks th, table.decks td { text-align: left; padding: .5rem .7rem; border-bottom: 1px solid var(--border); }
table.decks th { color: var(--muted); font-weight: 600; font-size: .85rem; }
.badge { background: var(--panel2); border: 1px solid var(--border); border-radius: 5px; padding: .05rem .45rem; }
.sortbar { margin-top: .8rem; display: flex; gap: .5rem; align-items: center; }
.sortbar label { color: var(--muted); display: flex; gap: .4rem; align-items: center; }
select { background: var(--panel); color: var(--ink); border: 1px solid var(--border); border-radius: 7px; padding: .35rem .5rem; font: inherit; }
select:focus { outline: 2px solid var(--accent); }

/* --- builder --- */
.topbar { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 1rem; padding-bottom: .8rem; border-bottom: 1px solid var(--border); }
.topbar .cmd { display: flex; align-items: center; gap: .6rem; font-size: 1.05rem; }
.topbar .actions { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; }
.topbar .back {
  display: inline-flex; place-items: center; justify-content: center;
  min-width: 44px; min-height: 44px;
}
.topbar .btn, .topbar button {
  min-height: 44px; padding-top: .6rem; padding-bottom: .6rem;
  display: inline-flex; align-items: center; justify-content: center;
}

.flash { color: var(--warn); min-height: 1.2rem; padding: .3rem 0; }
.flash:empty { display: none; }

.layout { display: grid; grid-template-columns: 1fr 380px; gap: 1.2rem; align-items: start; }
.right { position: sticky; top: 1rem; max-height: calc(100vh - 2rem); overflow-y: auto; }

.cat { margin-bottom: 1.2rem; }
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: .7rem; }

/* Mainboard sort bar. Wraps because four buttons plus the label must not push
   the document past a 390px viewport. */
.sortbar.cardsort { flex-wrap: wrap; margin: 0 0 .9rem; }
.sortbar.cardsort .sortbtn { min-height: 44px; padding: .5rem .8rem; }
.sortbar.cardsort .sortbtn.on { border-color: var(--accent); color: var(--ink); }
.sortbar.cardsort .dir { margin-left: .35rem; color: var(--muted); }

.card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 9px;
  padding: .4rem;
  display: flex;
  flex-direction: column;
  gap: .35rem;
  transition: box-shadow .15s ease;  /* combo outlines fade in when readouts land */
}
.card.unresolved { border-color: var(--warn); }
/* Scryfall `normal` images are a fixed 488x680 (ratio 61/85, ~0.7176) — hardcode
   it, not a rounded decimal, so the box is reserved before the image loads and
   never reflows the grid on an htmx swap. */
/* `height: auto` is load-bearing, do not drop it. The width/height ATTRIBUTES on
   the tag become presentational hints (height: 680px); with `width: 100%` also set,
   both dimensions are then specified and `aspect-ratio` is ignored outright —
   which rendered 155x680 cards, ~1.1 per phone screen. `height: auto` hands the
   height back to aspect-ratio while the attributes still supply the pre-load
   ratio. */
.card img { width: 100%; height: auto; aspect-ratio: 488 / 680; object-fit: cover; border-radius: 6px; display: block; }
/* Must match `.card img` exactly, or a card resizes the instant enrichment
   resolves and swaps the placeholder for the real image. */
.card .noimg { aspect-ratio: 488 / 680; display: grid; place-items: center; text-align: center; padding: .4rem; background: var(--panel2); border-radius: 6px; font-size: .8rem; }
.cardname { font-size: .82rem; line-height: 1.25; }
/* margin-top:auto pins the controls to the card's bottom so they line up across a
   row whether or not a card shows the (conditional) popularity bar. */
.cardctl { display: flex; gap: .3rem; margin-top: auto; }
.rm { background: var(--panel2); color: var(--muted); padding: .25rem .5rem; }
.rm:hover { background: var(--bad); color: #1a0000; }
/* The buy link carries the TCGplayer price, so it sizes to its text and takes the
   slack in the control row; `.unpriced` is the bare "$" fallback. */
.buy { background: var(--panel2); color: var(--good); border: 1px solid var(--border); border-radius: 7px; padding: .25rem .5rem; font-weight: 700; display: grid; place-items: center; flex: 1; font-size: .78rem; font-variant-numeric: tabular-nums; white-space: nowrap; }
.buy:hover { background: var(--good); color: #042417; text-decoration: none; }
.buy.unpriced { color: var(--muted); flex: 0 0 auto; }
.deckprice { color: var(--good); font-variant-numeric: tabular-nums; }

/* --- collection / owned --- */
.owned-summary { color: var(--muted); margin: 0 0 .8rem; }
/* Body-copy link, not chrome — clears the 24px AA floor, not the 44px topbar one. */
.owned-summary a { display: inline-block; padding: .2rem 0; }
.owned-tag { color: var(--good); font-size: .72rem; white-space: nowrap; }
.pop { display: flex; align-items: center; gap: .35rem; font-size: .7rem; color: var(--muted); }
.pop .popbar { flex: 1; height: 4px; background: var(--panel2); border-radius: 2px; overflow: hidden; }
.pop .popbar > span { display: block; height: 100%; background: var(--accent); }
.pop .poplabel { white-space: nowrap; min-width: 2.2em; text-align: right; }
.collection { max-width: 640px; margin: 2rem auto; }
.import { display: flex; gap: .5rem; align-items: center; margin: 1rem 0; }
.flash.ok { color: var(--good); }
.flash.bad { color: var(--bad); }

/* --- buy missing --- */
.buy-missing { max-width: 720px; margin: 1.5rem auto; }
.missing-list { list-style: none; padding: 0; margin: 1rem 0; }
.missing-list li { display: flex; justify-content: space-between; align-items: center; gap: .6rem; padding: .35rem .2rem; border-bottom: 1px solid var(--border); }
.paste { margin-top: 1rem; }
.paste textarea { width: 100%; background: var(--panel); color: var(--ink); border: 1px solid var(--border); border-radius: 7px; padding: .5rem; font-family: ui-monospace, monospace; font-size: .85rem; }
.big-ok { font-size: 1.4rem; color: var(--good); }

/* --- readouts --- */
.panel { background: var(--panel); border: 1px solid var(--border); border-radius: 10px; padding: .9rem 1rem; margin-bottom: 1rem; }
.panel p { margin: .35rem 0; }
.bracket-num { font-size: 3rem; font-weight: 700; line-height: 1; }
.bracket-num span { font-size: 1rem; color: var(--muted); font-weight: 400; }
.reasons { margin: .5rem 0 0; padding-left: 1.1rem; color: var(--muted); font-size: .85rem; }
.combos, .addone { margin: .3rem 0; padding-left: 0; list-style: none; }
.combos li, .addone li { padding: .2rem 0; border-bottom: 1px solid var(--border); font-size: .88rem; }
/* Colour swatch tying a combo line to the outline ui.js draws on its cards. */
.combo-dot { display: inline-block; width: .7em; height: .7em; border-radius: 50%; margin-right: .45em; vertical-align: middle; background: var(--muted); flex: none; }
.add { background: var(--good); color: #042417; padding: .15rem .5rem; font-size: .82rem; }
.addlist { list-style: none; margin: .3rem 0 0; padding: 0; max-height: none; }
.addlist li { display: flex; justify-content: space-between; align-items: center; gap: .5rem; padding: .25rem 0; border-bottom: 1px solid var(--border); }
.addlist .add { flex: 1; text-align: left; }

/* --- composition vs commander average --- */
.composition h3 { margin: .9rem 0 .3rem; }
.comp { width: 100%; border-collapse: collapse; font-size: .8rem; }
.comp th { color: var(--muted); font-weight: 600; text-align: left; padding: .15rem .3rem; }
.comp td { padding: .15rem .3rem; border-top: 1px solid var(--border); }
.comp .num { text-align: right; font-variant-numeric: tabular-nums; }
/* A row far from average is the whole point of the panel — make it findable at a
   glance, without implying it's an error (the line is fuzzy by nature). */
.comp tr.off td, .comp td.off { color: var(--accent); font-weight: 700; }
.comp .delta { color: var(--muted); }
.comp tr.off .delta { color: var(--accent); }
/* The curve table is one column per mana value; let it scroll rather than
   squeeze the sidebar or wrap mid-row. */
.composition .curve { display: block; overflow-x: auto; white-space: nowrap; }
.comp-flag { font-size: .8rem; margin: .4rem 0 .6rem; color: var(--accent); }
.comp-flag.ok { color: var(--good); }
/* by-type / by-role switch. Inactive link reads muted like the rest of the
   panel's chrome; the active one steps up to --ink + weight so which view is
   showing is legible without reading the table underneath it. */
.comp-switch { display: flex; gap: .9rem; }
.comp-switch a { color: var(--muted); }
.comp-switch a.active { color: var(--ink); font-weight: 600; }
.comp-switch a.active:hover { text-decoration: none; }
/* Price column on palette / swap rows, and the delta beside it on the swap
   surfaces. Fixed min-width + tabular figures so the numbers line up down the
   list instead of ragging with each row's name length — the delta needs that
   same alignment, sitting immediately left of a heated, fixed-width price. The
   delta carries its own meaning in its sign rather than in colour (colour
   means absolute price everywhere now, see .heat), so it shares this rule for
   alignment only, not `.heat` itself. */
.listprice, .pricedelta { color: var(--muted); font-size: .74rem; font-variant-numeric: tabular-nums; white-space: nowrap; min-width: 3.8em; text-align: right; }
.swaphead .listprice { min-width: 0; font-size: .8rem; }
/* Divider between the delta and the price it sits beside, on swap rows only.
   A sibling selector - ".listprice that immediately follows a .pricedelta" -
   rather than a class, so it reaches exactly the swap-suggestion rows where
   both figures render side by side and leaves every lone `.listprice` (the
   palette, the swap-out header, a swap-in row with no delta) untouched. Thin
   and quiet: a separator, not a box, just enough padding that the border
   isn't crowding the digits. */
.pricedelta + .listprice { border-left: 1px solid var(--border); padding-left: .4em; }
/* Price heat: colour carries how much a card costs, from unremarkable at $50
   to alarming at $500 (see price_heat.py for the ramp and why it is log).
   `--price-base` is whatever colour the site would otherwise use, so at heat 0
   the mix is 100% base and a cheap price renders exactly as it did before. */
.heat {
  color: color-mix(in oklab, var(--price-base, var(--ink)), var(--price-hot) calc(var(--heat, 0) * 100%));
}
.buy { --price-base: var(--good); }
.listprice { --price-base: var(--muted); }
.setcard-meta .price { --price-base: var(--ink); }
.type-tag {
  font-size: .62rem; font-weight: 700; letter-spacing: .04em;
  color: var(--muted); background: var(--panel2); border: 1px solid var(--border);
  border-radius: 4px; padding: .05rem .3rem; white-space: nowrap; flex: none;
}

/* --- hover card preview (palette) --- */
.cardpreview { position: absolute; z-index: 55; pointer-events: none; }
.cardpreview[hidden] { display: none; }
/* height:auto is load-bearing — see `.card img`. The width/height attributes on the
   tag pin height to 680px, so without this the preview renders 300x680 (stretched)
   instead of 300x418. */
.cardpreview img { width: 300px; height: auto; max-width: 44vw; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,.6); display: block; }

/* --- topbar card count + over-100 hint (B1/B2) --- */
.count .over { color: var(--warn); margin-left: .3rem; white-space: nowrap; }

/* --- commander tile (E3) --- */
.card.commander { border-color: var(--accent); }
.cmd-tag { flex: 1; color: var(--accent); font-size: .78rem; font-weight: 600; display: grid; place-items: center; }

/* --- full-size card modal (E1) --- */
#cardlist .card img { cursor: zoom-in; }
.cardmodal {
  position: fixed; inset: 0; z-index: 50;
  background: rgba(0, 0, 0, .8);
  display: grid; place-items: center; padding: 2rem;
  cursor: zoom-out;
}
.cardmodal[hidden] { display: none; }
/* height:auto is load-bearing — see `.card img`. Without it the height attribute
   pins the image to 680px while max-width squeezes the width to 90vw, so on a phone
   the full-size view rendered ~351x680 (squashed) instead of ~351x489. With
   height:auto the ratio is preserved and max-height still caps tall viewports. */
.cardmodal img { height: auto; max-height: 90vh; max-width: 90vw; border-radius: 14px; box-shadow: 0 12px 48px rgba(0,0,0,.6); }

/* --- swap modal (category-matched suggestions) --- */
.swapmodal {
  position: fixed; inset: 0; z-index: 50;
  background: rgba(0, 0, 0, .8);
  display: grid; place-items: center; padding: 2rem;
}
.swapmodal[hidden] { display: none; }
.swapmodal-inner {
  position: relative;
  width: 100%; max-width: 520px; max-height: 85vh; overflow-y: auto;
  background: var(--panel); border: 1px solid var(--border); border-radius: 12px;
  padding: 1.1rem 1.2rem; box-shadow: 0 12px 48px rgba(0,0,0,.6);
}
.swapmodal-close { position: absolute; top: .7rem; right: .7rem; background: var(--panel2); color: var(--muted); padding: .2rem .5rem; }
.swapmodal-close:hover { background: var(--bad); color: #1a0000; }
.swaphead h2 { margin: 0 1.6rem .2rem 0; }
.swap-search { margin: .6rem 0 .8rem; }
.swap-search input { width: 100%; }
.swaplist li form { flex: 1; display: flex; }
.swaplist .add { flex: 1; text-align: left; }
.swap-empty { margin: .6rem 0 0; }
.swap-open { background: var(--panel2); color: var(--muted); padding: .25rem .5rem; flex: 1; }
.swap-open:hover { background: var(--accent); color: #08121f; }

/* --- autocomplete menu (E5/E6) --- */
.ac-menu {
  position: absolute; z-index: 60; margin: 2px 0 0; padding: .2rem;
  list-style: none; max-height: 260px; overflow-y: auto;
  background: var(--panel2); border: 1px solid var(--border); border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0,0,0,.5); font-size: .85rem;
}
.ac-menu[hidden] { display: none; }
.ac-menu li { padding: .3rem .5rem; border-radius: 5px; cursor: pointer; white-space: nowrap; }
.ac-menu li.on, .ac-menu li:hover { background: var(--accent); color: #08121f; }

/* --- commander browse (E2) --- */
.cmdsearch { max-width: 640px; margin: 0 0 1.2rem; }
.cmdlist { list-style: none; padding: 0; margin: 1rem 0; }
.cmdlist li { display: flex; justify-content: space-between; align-items: center; gap: .6rem; padding: .5rem .2rem; border-bottom: 1px solid var(--border); }
.cmdpick { font-weight: 600; }
.pager { display: flex; gap: .8rem; align-items: center; margin: 1.2rem 0; }
.btn.disabled { opacity: .4; pointer-events: none; }

/* --- lazy readouts loading state --- */
.readouts-loading { color: var(--muted); display: flex; align-items: center; gap: .5rem; }
.readouts-loading .spin {
  width: 14px; height: 14px; border-radius: 50%;
  border: 2px solid var(--border); border-top-color: var(--accent);
  animation: spin 0.7s linear infinite; flex: none;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* --- spinner --- */
#global-spinner {
  position: fixed; top: .6rem; right: .8rem;
  background: var(--accent); color: #08121f;
  padding: .3rem .7rem; border-radius: 7px; font-weight: 600;
  opacity: 0; transition: opacity .15s; pointer-events: none; z-index: 10;
}
#global-spinner.htmx-request { opacity: 1; }

@media (max-width: 900px) {
  .layout { grid-template-columns: 1fr; }
  .right { position: static; max-height: none; overflow: visible; }
}

/* --- phone: topbar actions drop to their own full-width row --- */
@media (max-width: 560px) {
  .topbar .actions { width: 100%; }
  .topbar .actions .btn, .topbar .actions button { flex: 1 1 auto; }
}

/* Precon rows in the deck picker — pinned above the EDHREC deck table. */
.precons { margin: 1rem 0 1.5rem; }
.precon-list { list-style: none; margin: 0; padding: 0; }
.precon {
  display: flex; align-items: center; gap: 0.75rem;
  padding: 0.5rem 0; border-bottom: 1px solid var(--border, #2c313c);
}
.precon-art { border-radius: 4px; flex: none; }
.precon-meta { display: flex; flex-direction: column; gap: 0.15rem; flex: 1 1 auto; }
.precon-badge { letter-spacing: 0.06em; }

/* --- swap-IN chooser: what to cut (build/swap-in) --- */
/* A cut button is the mirror of `.add`: same shape, danger colouring instead of
   the "good" green, so a list of cuts never reads like a list of adds. Bare
   `button` is accent-blue with dark text, which `.cut` must override outright.
   A combo piece is listed (never hidden) but dimmed and marked, so the cost of
   the cut is visible where the choice is. */
.cut { background: var(--panel2, #242833); color: var(--bad, #ff7b7b); padding: .15rem .5rem; font-size: .82rem; }
.cut:hover { background: var(--bad, #ff7b7b); color: #1a0000; }
.cutlist li form { flex: 1; display: flex; }
.cutlist .cut { flex: 1; text-align: left; }
/* A multi-copy entry loses one copy, not the entry — the count rides along quietly. */
.cut-qty { color: var(--muted, #9aa1ad); font-weight: 400; font-size: .74rem; }
.cut:hover .cut-qty { color: inherit; }
.cutlist li.cut-combo { opacity: 0.72; }
/* The combo a cut would break, named in the row. Not nowrap: the label carries
   the combo's full description, and clipping the cost of the choice defeats it.
   Capped so one long combo cannot crowd out the card name beside it. */
.combo-warn {
  font-size: 0.75rem;
  line-height: 1.25;
  padding: 0.05rem 0.35rem;
  border-radius: 3px;
  border: 1px solid var(--border, #2c313c);
  color: var(--muted, #9aa1ad);
  max-width: 45%;
  text-align: right;
}
/* What the incoming card completes, under the modal's title. */
.completes { margin: 0 0 0.35rem; font-size: 0.85rem; color: var(--good, #5ad19a); }

/* --- set browser: /sets index list -------------------------------------- */
.setlist { list-style: none; padding: 0; }
.setlist li { display: flex; gap: .75rem; align-items: baseline;
  padding: .4rem 0; border-bottom: 1px solid var(--border); }
.setlist .progress { margin-left: auto; font-variant-numeric: tabular-nums; }

/* --- set browser: /sets/{code} grid -------------------------------------- */
.gridcontrols { display: flex; gap: 1rem; flex-wrap: wrap; align-items: end;
  margin: 1rem 0; }
.gridcontrols .buylist-link { margin-left: auto; }
.setprogress { font-variant-numeric: tabular-nums; }
/* What the set is made of - role and count pairs, beside the progress
   readout. Same quiet muted tone, not a competing headline. */
.setmakeup { color: var(--muted); font-size: .85rem; font-variant-numeric: tabular-nums; }
.setmakeup .role-count { white-space: nowrap; }

.setcards { list-style: none; padding: 0; display: grid; gap: 1rem;
  grid-template-columns: repeat(auto-fill, minmax(146px, 1fr)); }
.setcard { display: flex; flex-direction: column; gap: .25rem; }
.setcard img { width: 100%; height: auto; border-radius: 4.75% / 3.5%; }
/* Owned cards dim rather than disappear — the point of the grid is seeing what
   you have alongside what you don't, not hiding half of it by default. */
.setcard[data-owned="yes"] img { opacity: .45; }
.setcard-name { font-size: .85em; line-height: 1.3; }
/* A quiet marker, not a badge (see the template comment) — small and muted like
   the tile's own meta line, not a competing highlight. */
.staple-mark { font-size: .8em; color: var(--muted); margin-left: .15em; }
.setcard-meta { display: flex; justify-content: space-between; font-size: .85em; }
.setcard-actions { display: flex; gap: .5rem; align-items: center; }
.owned-badge { font-size: .8em; color: var(--good); }
.setcard button.in-list { background: var(--panel2); }

/* --- collection browser: /collection grid -------------------------------- */
/* Lives inside the swapped fragment (see _collection_grid.html) so its href
   always carries the filters currently shown, not the ones at page load. */
.gridprint { display: flex; justify-content: flex-end; padding: 0 0.75rem; }
/* Same tile geometry as the set grid, minus the actions row: you already own
   these, so there is nothing to add to a buy list. */
.owncards { list-style: none; margin: 0; padding: 0.75rem;
            display: grid; gap: 0.75rem;
            grid-template-columns: repeat(auto-fill, minmax(146px, 1fr)); }
.owncard { display: flex; flex-direction: column; gap: 0.25rem; }
.owncard img { width: 100%; height: auto; border-radius: 4px; }
.owncard-name { font-weight: 600; line-height: 1.2; }
.owncard-name .qty { font-weight: 400; opacity: 0.7; margin-left: 0.25rem; }
.owncard-meta { display: flex; justify-content: space-between; gap: 0.5rem;
                align-items: baseline; }

/* --- buy list: /buylist -------------------------------------------------- */
.buylist-table { width: 100%; border-collapse: collapse; margin: 1rem 0; }
.buylist-table th, .buylist-table td {
  text-align: left; padding: .5rem .6rem; border-bottom: 1px solid var(--border);
}
.buylist-table tfoot td { border-bottom: 0; font-weight: 600; }
.buylist-table .qty { display: flex; gap: .4rem; align-items: center; }
.buylist-table .qty input[type=number] { width: 4rem; }

/* Cards Mass Entry can't resolve - flagged so none go silently unordered. */
.held-back {
  margin: 1rem 0; padding: .7rem .9rem; border: 1px solid var(--warn);
  border-radius: 7px; background: var(--panel2);
}
.held-back strong { color: var(--warn); }
.held-back ul { margin: .5rem 0 0; padding-left: 1.2rem; }
.held-back li { margin: .25rem 0; }

/* --- collection browser: /collection/print -------------------------------- */
/* Print list. Dense on screen, denser on paper: two columns, no chrome. */
.printlist { padding: 0.75rem 1rem; }
.printgroup { break-inside: avoid; margin-bottom: 0.75rem; }
.printgroup h2 { font-size: 1rem; margin: 0.5rem 0 0.25rem; }
.printgroup ul { list-style: none; margin: 0; padding: 0;
                 columns: 2; column-gap: 1.5rem; }
.printgroup li { break-inside: avoid; display: flex; gap: 0.4rem;
                 align-items: baseline; line-height: 1.45; }
.printgroup .pname { flex: 1; }
.printonly { display: none; }

@media print {
  .noprint, .topbar, nav, footer { display: none !important; }
  .printonly { display: block; }
  .printlist { padding: 0; }
  .printgroup ul { columns: 3; }
  body { background: #fff; color: #000; font-size: 10pt; }
}

/* --- fill-in preview: /build/fill-in -------------------------------------- */
/* One row per swap, old and new on one line so the exchange reads left to
   right. */
.fillin { padding: 0.75rem 1rem; }
.filllist { list-style: none; margin: 0.5rem 0; padding: 0; }
.fillrow label { display: flex; gap: 0.5rem; align-items: baseline;
                 padding: 0.4rem 0; min-height: 44px; cursor: pointer; }
.fillrow .out { text-decoration: line-through; opacity: 0.75; }
.fillrow .in { font-weight: 600; }
.fillrow .saved { margin-left: auto; }
.fillactions { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.remaining { columns: 3; column-gap: 1.5rem; list-style: none; padding: 0; }
