/* Application chrome. Card faces are styled entirely by card.css. */

:root {
  --bg: #14161c;
  --panel: #1c1f28;
  --panel-2: #232733;
  --line: #313746;
  --text: #e6e8ee;
  --muted: #9aa3b6;
  --accent: #4c8dff;
  --ok: #37c98a;
  --warn: #f0b429;
  --err: #ff5f57;
  --radius: 10px;

  /* Problem-Solving Card Text highlighting, used only by the inspector's effect
     box. Each part of an effect gets one colour, chosen to stay distinguishable
     on the dark panel and from the effect text itself:

       condition  amber    — the activation condition, before the colon
       cost       pink     — what is paid, before the semicolon
       target     violet   — targeting, also before the semicolon but not a cost
       effect     the normal text colour, so the effect reads as the default
       conjunctive green   — "and if you do", "then", "also"

     The four bullet colours rotate, so adjacent bullets in a list never share
     one and a long list still reads as a list. */
  --psct-condition: #f0b429;
  --psct-cost: #ff8fb0;
  --psct-target: #b79bff;
  --psct-conjunctive: #57d4a0;
  --psct-effect: var(--text);
  --psct-b1: #6cb6ff;
  --psct-b2: #ffb86b;
  --psct-b3: #b8e986;
  --psct-b4: #ff9ecb;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  background: var(--bg);
  color: var(--text);
  font: 14px/1.45 system-ui, "Segoe UI", Roboto, sans-serif;
}

/* ------------------------------------------------------------- topbar -- */

.topbar {
  display: flex;
  align-items: center;
  gap: 20px;
  padding: 10px 16px;
  background: var(--panel);
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  z-index: 30;
  flex-wrap: wrap;
}

.topbar h1 {
  margin: 0;
  font-size: 16px;
  font-weight: 600;
  letter-spacing: 0.2px;
  white-space: nowrap;
}

/* The running commit. Deliberately quiet: it is there to confirm an update landed
   after a self-updating deployment pulls a new image, not to be read on every
   visit. */
.topbar h1 .build-label {
  margin-left: 6px;
  font-size: 11px;
  font-weight: 400;
  letter-spacing: 0;
  color: var(--muted, #8a8a8a);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.topbar h1 .build-label:empty {
  display: none;
}

.controls {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.controls label {
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--muted);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.4px;
}

/* The banlist and cardpool controls sit in the top bar and have no width of
   their own, so they shrank to their placeholder. A min-width keeps the shortest
   names legible ("EDISON", ~42px; "GOAT", ~30px). Measured against the longest
   name actually present rather than the count, which is no longer shown. */
#pool-select-wrap { min-width: 108px; }
#banlist-select-wrap { min-width: 108px; }

/* ------------------------------------------------------------ controls -- */

.btn {
  background: var(--panel-2);
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 6px 12px;
  font-size: 13px;
  cursor: pointer;
  transition: background 0.12s, border-color 0.12s;
}
.btn:hover { background: #2b3241; border-color: #414a5e; }
.btn:active { transform: translateY(1px); }
.btn.primary { background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 600; }
.btn.primary:hover { background: #3d7ae8; }
/* For actions that destroy something. Filled, so it reads as the deliberate
   choice rather than one of two equal buttons. */
.btn.danger { background: var(--err); border-color: var(--err); color: #fff; font-weight: 600; }
.btn.danger:hover { background: #d9534f; border-color: #d9534f; }
.btn.ghost { background: transparent; }
.btn.small { padding: 3px 8px; font-size: 12px; }
.btn.wide { width: 100%; margin-top: 10px; }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }

/* Infinite-scroll sentinel under the search grid.
 *
 * Occupies real height so it can be scrolled into view, which is what triggers
 * the next page. Kept visually quiet: it is a status line, not a control.
 */
.search-sentinel {
  padding: 18px 0;
  text-align: center;
  font-size: 12px;
  color: var(--muted);
}
.search-sentinel.hidden { display: none; }

/* A malformed search token, shown in the grid's empty state. Styled apart from
   the neutral "no matches" message because the fix is to edit the query, not to
   loosen it. */
.card-grid > p.search-error {
  color: #ff8080;
  font-size: 12px;
  line-height: 1.5;
  word-break: break-word;
}

select, input[type="search"], input[type="number"], input[type="text"], textarea {
  background: var(--panel-2);
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 5px 8px;
  font: inherit;
  font-size: 13px;
}
select:focus, input:focus, textarea:focus { outline: 2px solid var(--accent); outline-offset: -1px; }

/* The stat-bound inputs sit beside the custom dropdowns, so they adopt the same
   type size, padding and corner radius. Otherwise a 13px input next to an 11px
   button reads as a different control.
 *
 * The native spinner buttons are drawn by the OS and ignore the dark theme, so
 * they are removed. The fields still accept typed digits and arrow keys. */
.range-row input[type="number"] {
  font-size: 11px;
  padding: 6px 8px;
  border-radius: 6px;
  min-width: 0;
  /* Matches `.icon-select-button`, so a stat bound and a dropdown look like the
     same kind of control rather than two different widgets. */
  background: var(--panel);
  -moz-appearance: textfield;
  appearance: textfield;
}
.range-row input[type="number"]::-webkit-outer-spin-button,
.range-row input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.range-row input[type="number"]::placeholder { color: var(--muted); }

/* -------------------------------------------------------------- banner -- */

.banner {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 10px 16px;
  background: #3a2f12;
  border-bottom: 1px solid #5a4a1c;
  color: #f5e3b0;
  flex-wrap: wrap;
}
.banner.hidden { display: none; }

.progress {
  flex: 1;
  min-width: 120px;
  height: 6px;
  background: #2a2412;
  border-radius: 3px;
  overflow: hidden;
}
.progress.hidden { display: none; }
.progress-bar { height: 100%; width: 0; background: var(--accent); transition: width 0.2s; }

/* -------------------------------------------------------------- layout -- */

.layout {
  display: grid;
  grid-template-columns: minmax(320px, 1fr) minmax(420px, 1.35fr) minmax(240px, 300px);
  /* One row that fills the viewport exactly. `minmax(0, 1fr)` allows the row to
     be shorter than its content, which is what lets a pane scroll internally
     instead of the row growing and being clipped. */
  grid-template-rows: minmax(0, 1fr);
  gap: 1px;
  background: var(--line);
  height: calc(100vh - 53px);
  overflow: hidden;
}

.pane {
  background: var(--bg);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  min-height: 0;
}

.pane-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--line);
  background: var(--panel);
  flex: 0 0 auto;
}
.pane-head h2 { margin: 0; font-size: 13px; text-transform: uppercase; letter-spacing: 0.6px; color: var(--muted); }

.muted { color: var(--muted); font-size: 12px; }

/* -------------------------------------------------------- search pane -- */

/* The search bar: the effect-mode toggle, the box, and the credit link.
   The box keeps its own margins vertically but not horizontally, so the row
   owns the spacing and the three line up together. */
.search-row {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 12px 14px 8px;
}
.search-row .search-input {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
}
.search-input {
  margin: 12px 14px 8px;
  padding: 8px 10px;
  font-size: 14px;
}

/* Effect search toggle. `aria-pressed` carries the state, because the visual
   difference alone would be invisible to assistive technology. */
.effect-toggle { flex: 0 0 auto; white-space: nowrap; }
.effect-toggle[aria-pressed="true"] {
  background: var(--accent, #4a7fd4);
  border-color: var(--accent, #4a7fd4);
  color: #fff;
}
.effect-toggle-icon { font-size: 11px; }

/* Credit for searchygo, shown only while effect mode is on. Deliberately
   understated: it is attribution, not a call to action. */
.effect-credit {
  flex: 0 0 auto;
  font-size: 11px;
  color: var(--muted);
  text-decoration: none;
  border-bottom: 1px dotted currentColor;
  white-space: nowrap;
}
.effect-credit:hover,
.effect-credit:focus-visible { color: inherit; }

/* How effect mode read the search. Sits directly above the grid so the reading
   and its results are read together. */
.search-reading {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 5px;
  margin: 0 14px 8px;
}
.reading-chip {
  font-size: 11px;
  line-height: 1.5;
  padding: 1px 7px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--panel);
  color: inherit;
  white-space: nowrap;
}
.reading-note {
  font-size: 11px;
  color: var(--muted);
  font-style: italic;
}

/* `hidden` is applied per element rather than globally in this stylesheet, so
   each of the new search-bar pieces needs its own rule or it would stay on
   screen: `.search-reading` is a flex container, where a bare `hidden`
   attribute has no effect. */
.effect-toggle.hidden { display: none; }
.effect-credit.hidden { display: none; }
.search-reading.hidden { display: none; }

.filters {
  margin: 0 14px 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel);
}
.filters summary {
  cursor: pointer;
  padding: 7px 10px;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--muted);
}
.filter-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  padding: 4px 10px 10px;
}
.filter-grid label {
  display: flex;
  flex-direction: column;
  gap: 3px;
  font-size: 11px;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.4px;
}
/* Archetype names are long, so let that one span the full row. */
.filter-grid label.wide-field { grid-column: 1 / -1; }

/* Monster-only fields are hidden (not just disabled) when a Spell or Trap is
   selected, so the panel only offers choices that can apply. */
.filter-grid label.hidden { display: none; }

/* A stat range reads [min] <= LABEL <= [max], spanning the full row. */
.filter-grid label.range-field { grid-column: 1 / -1; }

/* A single on/off filter (Staples). Spans the row like a range field so it lines
   up with the fields above rather than sitting half-width in a two-column grid,
   and keeps its checkbox and text on one baseline. */
.filter-grid label.check-field {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 7px;
  cursor: pointer;
}
.filter-grid label.check-field input[type="checkbox"] {
  flex: 0 0 auto;
  margin: 0;
  cursor: pointer;
}
.range-row {
  display: flex;
  align-items: center;
  gap: 6px;
}
/* The two bound inputs share the width evenly and the operators/label take only
   what they need, so the row stays aligned with the fields above. */
.range-row input {
  flex: 1 1 0;
  min-width: 0;
}
.range-row .range-op {
  flex: 0 0 auto;
  color: var(--muted);
  font-size: 12px;
}
.range-row .range-label {
  flex: 0 0 auto;
  color: var(--muted);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  white-space: nowrap;
}

/* A disabled dropdown must read as unavailable, not merely inert. */
.filter-grid select:disabled { opacity: 0.45; cursor: not-allowed; }

/* Options carrying a UI icon (currently the monster Attribute list).
 *
 * A native <option> cannot contain an <img>, so fillSelect sets the icon as the
 * option's background image. The sizes here must stay in step with the source
 * PNGs, which are 16x16. The left padding keeps the text clear of the icon.
 * Firefox does not render option backgrounds; the text label is always kept, so
 * the menu stays readable there. */
.filter-grid select option[data-icon] {
  background-repeat: no-repeat;
  background-position: 4px center;
  background-size: 16px 16px;
  padding-left: 24px;
}

/* Attribute select: a custom listbox that shows an icon per row.
 *
 * A native <select> cannot do this. Its <option> elements have no layout box, so
 * an icon set as a CSS background is ignored by the browser's own popup — the
 * icons only showed up once a value was chosen. Real elements sidestep that.
 * The hidden <select> underneath keeps the existing value/disable wiring. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.icon-select { position: relative; }

.icon-select-button {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 6px 8px;
  background: var(--panel);
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: 6px;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.icon-select-button:disabled { opacity: 0.45; cursor: not-allowed; }
.icon-select-button .icon-select-label { flex: 1 1 auto; }
.icon-select-button .icon-select-caret { color: var(--muted); font-size: 10px; }

/* The chosen entry's icon on the closed control.
 *
 * Hidden with `visibility` rather than `display: none` so the space is always
 * reserved: otherwise a control showing "Any" would sit its label 24px further
 * left than its neighbours, which reads as a misalignment. */
.icon-select-icon {
  width: 16px;
  height: 16px;
  flex: 0 0 16px;
  object-fit: contain;
}
.icon-select-icon.hidden { visibility: hidden; }

/* A control whose values never carry an icon (the banlist, the cardpool) marks
   its wrapper `no-icons`. Reserving icon space there would indent the label with
   nothing to align to, so the element is dropped and the label starts at the
   button's padding edge. */
.icon-select.no-icons .icon-select-icon { display: none; }
.icon-select.no-icons .icon-select-button { gap: 6px; }

/* Rows in the open list only get an <img> when that value has an icon (a spacer
   span is used otherwise), so `hidden` never applies there and the tighter
   `display` rule above is not needed. */

/* The open list. Absolutely positioned so it overlays the panel rather than
   pushing the fields below it out of place. */
.icon-select-list {
  position: absolute;
  z-index: 20;
  left: 0;
  right: 0;
  top: calc(100% + 2px);
  margin: 0;
  padding: 4px;
  list-style: none;
  max-height: 260px;
  overflow-y: auto;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 6px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.55);
}
.icon-select-list.hidden { display: none; }

.icon-select-option {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 6px;
  border-radius: 4px;
  cursor: pointer;
}
/* The blue highlight a native <select> shows over the hovered row. Without it
   the list gave no feedback about which entry the pointer was on. */
.icon-select-option:hover,
.icon-select-option.active {
  background: var(--accent);
  color: #fff;
}
.icon-select-option:hover .icon-select-spacer,
.icon-select-option.active .icon-select-spacer { background: none; }
.icon-select-option[aria-selected="true"] { color: var(--accent); }
.icon-select-option[aria-selected="true"]:hover { color: #fff; }

/* Every icon is drawn at a uniform 16x16 regardless of its source size: the
   attribute PNGs are 16x16 and the monster type ones are 32x32, so without this
   the rows would differ in height between the two lists. */
.icon-select-option .icon-select-icon {
  flex: 0 0 16px;
  width: 16px;
  height: 16px;
  object-fit: contain;
}
/* A row whose race has no icon keeps the label indented so the column lines up
   with the rows that do. */
.icon-select-spacer { flex: 0 0 16px; }

.filter-note {
  margin: 0 10px 8px;
  font-size: 11px;
  color: var(--muted);
}
.filters .btn { margin: 0 10px 10px; }

/* ---------------------------------------------------------- card grids -- */

.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(112px, 1fr));
  gap: 10px;
  padding: 12px 14px;
  overflow-y: auto;
  align-content: start;
  flex: 1 1 auto;
  min-height: 60px;
  /* Paging appends cards below the view. Browser scroll anchoring then tries to
     keep a previously-visible element in place by adjusting scrollTop, which
     fights the explicit restore in runSearch and drags the list around. The
     offset is managed by that code instead, so anchoring is switched off here. */
  overflow-anchor: none;
}

.card-cell {
  position: relative;
  border-radius: 6px;
  cursor: pointer;
  transition: transform 0.1s;
  outline: none;
}
.card-cell:hover, .card-cell:focus-visible { transform: translateY(-2px); }
.card-cell .ygocard-slot { border-radius: 5px; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.55); }
.card-cell.selected { box-shadow: 0 0 0 2px var(--accent); border-radius: 6px; }
.card-cell .qty {
  position: absolute;
  right: -3px;
  top: -3px;
  min-width: 19px;
  height: 19px;
  padding: 0 5px;
  border-radius: 10px;
  background: var(--accent);
  color: #fff;
  font: 600 11px/19px system-ui, sans-serif;
  text-align: center;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
}
/* Corner badges, all one family.
 *
 * The legality badge (SEMI / LIMIT / BAN) is the model: a compact, squared tab
 * flush against the card edge, solid colour, small bold caps. The Genesys cost
 * and the TCG/OCG exclusivity marker below deliberately reuse this shape — the
 * same `_corner-badge` rules — so every annotation on a card reads as one kind of
 * thing rather than three different widgets.
 *
 * The outer corner radius matches the card frame's 5px, so the tab follows the
 * frame's curve instead of sitting on top of it as a floating pill.
 */
.card-cell .badge,
.card-cell .genesys,
.card-cell .format-exclusive {
  position: absolute;
  padding: 1px 5px;
  font: 700 10px/1.4 system-ui, sans-serif;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  white-space: nowrap;
  box-shadow: none;
}

/* Top-left, text colour chosen per status for contrast. */
.card-cell .badge {
  left: 0;
  top: 0;
  border-radius: 5px 0 5px 0;
}
.card-cell .badge.forbidden { background: #c0392b; color: #fff; }
.card-cell .badge.limited { background: #d68910; color: #1a1a1a; }
.card-cell .badge.semi { background: #b7950b; color: #1a1a1a; }
.card-cell .out-of-pool { opacity: 0.45; }

/* Remaining copies in a custom (CSV) cardpool, bottom-left of the card. */
.card-cell .stock {
  position: absolute;
  left: -3px;
  bottom: -3px;
  min-width: 19px;
  height: 19px;
  padding: 0 5px;
  border-radius: 10px;
  background: var(--ok);
  color: #08130d;
  font: 700 11px/19px system-ui, sans-serif;
  text-align: center;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
}
.card-cell .stock.low { background: var(--warn); color: #1a1a1a; }
.card-cell .stock.empty { background: var(--err); color: #fff; }

/* When the region badge is present it takes the bottom-left corner, and the
 * stock count steps to its right so the two do not overlap. */
.card-cell.has-format-badge .stock { left: 41px; }

/* Genesys cost, bottom-right, in the legality badge's tab style.
 *
 * The tab is positioned against the CARD SLOT (`.ygocard-slot`), not the cell.
 * A grid column can be wider than the card face it holds — at 1280px the cell is
 * ~123px against a 112px face — and `position: absolute` on the cell would place
 * the tab in that empty strip, several pixels outside the card. The slot wraps
 * the face exactly, so anchoring there puts the tab on the card at any width.
 *
 * Only rendered when the cost is above 0 — see updateGenesysBadge. */
.card-cell .ygocard-slot { position: relative; }

.card-cell .genesys {
  right: 0;
  bottom: 0;
  border-radius: 5px 0 5px 0;
  background: #6b4ea8;
  color: #fff;
}

/* TCG-only / OCG-only marker, bottom-left, same tab treatment.
 *
 * Usually absent: around 95% of cards are legal in both regions. Colour marks
 * the region at a glance, and the text says the same thing, so colour is never
 * the only cue. */
.card-cell .format-exclusive {
  left: 0;
  bottom: 0;
  border-radius: 0 5px 0 5px;
  color: #fff;
}
.card-cell .format-exclusive.tcg { background: #2a78d6; }
.card-cell .format-exclusive.ocg { background: #c43e3e; }

/* Genesys toggle in the Card search heading.
 *
 * A "G" with a bar struck through it while off, and without the bar while on.
 * The bar is a pseudo-element so the two states share one sized button and the
 * label never shifts. */
.genesys-toggle {
  position: relative;
  min-width: 24px;
  padding: 0 6px;
  font-weight: 700;
  font-size: 12px;
  letter-spacing: 0.5px;
}
.genesys-toggle::after {
  content: '';
  position: absolute;
  left: 3px;
  right: 3px;
  top: 50%;
  height: 2px;
  border-radius: 1px;
  background: currentColor;
  transform: rotate(-18deg);
  transition: opacity 0.15s;
}
/* aria-pressed="true" means on, so the bar is removed. */
.genesys-toggle[aria-pressed='true']::after { opacity: 0; }
.genesys-toggle[aria-pressed='true'] {
  background: rgba(139, 108, 214, 0.22);
  border-color: #6b4ea8;
  color: #b9a6e8;
}

/* The Genesys filter row only exists while the overlay is on. */
.genesys-only.hidden { display: none; }

/* A checkbox row in the filters panel, matching the other controls' spacing. */
.toggle-row {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 10px 0 6px;
  font-size: 12px;
  color: var(--muted);
  cursor: pointer;
  user-select: none;
}
.toggle-row input { accent-color: var(--accent); cursor: pointer; }
.toggle-row:hover { color: var(--text); }

/* ---------------------------------------------------------- deck pane -- */

/* The deck bar holds the name, the legality verdict and Sort. It aligns to the
   top because a long violation list grows downward from the badge rather than
   re-centring the name and the button. */
.deck-bar { align-items: flex-start; }

.deck-name {
  background: transparent;
  border: 1px solid transparent;
  /* Matches the Card search heading, so both pane titles read the same. */
  color: var(--muted);
  font-size: 15px;
  font-weight: 600;
  padding: 3px 6px;
  flex: 1 1 auto;
  min-width: 120px;
}
.deck-name:hover, .deck-name:focus { border-color: var(--line); background: var(--panel-2); }

/* Legality sits on the deck bar. It takes the space between the name and Sort
   and can shrink, so a long message wraps instead of pushing Sort off the bar. */
.deck-legality {
  position: relative;
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
}

/* The Genesys total and the legality verdict as one row, so they line up as a
   pair of status pills rather than stacking. */
.deck-status {
  display: flex;
  align-items: center;
  gap: 6px;
}

/* Genesys total in the deck bar. Same pill as the legality badge beside it (it
 * carries `.status-badge` too) and the same colour as the per-card cost tab, so
 * the deck's total and the numbers on the cards read as one value.
 *
 * `display: none` rather than `visibility` when hidden: the toggle removes the
 * number entirely, and leaving a gap beside the legality badge would suggest
 * something is missing. */
.status-badge.genesys {
  background: #6b4ea8;
  color: #fff;
  cursor: default;
  white-space: nowrap;
}
.status-badge.genesys.hidden { display: none; }

/* The detail list floats over the deck rather than sitting in the bar: showing
   it inline grew the bar and pushed the deck sections down every time a rule
   tripped. Anchored to the badge, which is what was clicked. */
.deck-legality .violations {
  position: absolute;
  top: calc(100% + 4px);
  right: 0;
  z-index: 25;
  margin: 0;
  width: min(360px, 60vw);
  max-height: 260px;
  overflow-y: auto;
  padding: 8px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 6px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.55);
}
.deck-legality .violations.hidden { display: none; }
.deck-legality .violations li {
  margin-bottom: 4px;
  padding: 5px 8px;
  text-align: left;
}
.deck-legality .violations li:last-child { margin-bottom: 0; }

.sections { overflow-y: auto; flex: 1 1 auto; min-height: 0; }

.deck-section { border-bottom: 1px solid var(--line); }
.deck-section h3 {
  margin: 0;
  padding: 8px 14px;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--muted);
  background: var(--panel);
  display: flex;
  align-items: center;
  gap: 6px;
  position: sticky;
  top: 0;
  z-index: 5;
}
.deck-section h3 .count { color: var(--text); font-weight: 700; font-size: 13px; }
.deck-section h3 .limit { color: var(--muted); font-weight: 400; }
.deck-section.over h3 .count { color: var(--err); }

.dropzone {
  min-height: 84px;
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  padding: 10px 14px;
  overflow: visible;
}
.dropzone.dragover { background: rgba(76, 141, 255, 0.08); outline: 2px dashed var(--accent); outline-offset: -6px; }
.dropzone.empty::after {
  content: "Drop or click cards to add";
  color: var(--muted);
  font-size: 12px;
  grid-column: 1 / -1;
  padding: 18px 0;
  text-align: center;
}

/* ------------------------------------------------------- inspect pane -- */

.inspect-pane {
  padding: 12px 14px;
  gap: 12px;
  /* Scrolls as a single column. Three things are needed for this to work, and
     all three were missing:
       1. `overflow-y: auto` on the pane itself;
       2. `min-height: 0` so the flex item may shrink below its content height
          (the default is `auto`, which makes the pane grow instead);
       3. `height: 100%` so the pane takes the grid ROW height rather than its
          own content height. Without it the grid row is content-sized, the pane
          never actually overflows, and the parent `.layout { overflow: hidden }`
          silently clips the bottom. */
  overflow-y: auto;
  overflow-x: hidden;
  min-height: 0;
  height: 100%;
}

.preview {
  display: flex;
  justify-content: center;
  align-items: flex-start;
  min-height: 120px;
  /* The preview sizes itself to the card it holds. A fixed height let the card
     render over whatever followed it, which put the name under the artwork. */
  flex: 0 0 auto;
}
.preview .ygocard-slot { border-radius: 6px; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.6); }

/* Each block keeps its natural height so overflowing content scrolls the pane
   rather than being squeezed into an unreadable strip. */
.card-info { font-size: 12px; flex: 0 0 auto; }
.card-info h4 { margin: 0 0 6px; font-size: 14px; }
.card-info dl { display: grid; grid-template-columns: auto 1fr; gap: 2px 10px; margin: 8px 0 0; }
.card-info dt { color: var(--muted); }
.card-info dd { margin: 0; }

/* A Pendulum card shows two effects, so each box gets a small heading. */
.card-info .desc-label {
  margin: 10px 0 0;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--muted);
  font-weight: 600;
}

.card-info .desc {
  margin-top: 8px;
  padding: 8px;
  background: var(--panel);
  border-radius: 6px;
  max-height: 190px;
  overflow-y: auto;
  white-space: pre-wrap;
  font-size: 12px;
  color: #cfd5e3;
}
/* When a heading sits directly above, the box starts right under it. */
.card-info .desc-label + .desc { margin-top: 4px; }

/* Problem-Solving Card Text highlighting, in the inspector's effect box only.
 *
 * Deliberately colour ONLY, with no weight or size changes: the text has to keep
 * the card's own shape, or a wrapped line shifts whenever a colour changes and
 * the box gets harder to read rather than easier.
 *
 * `.psct-effect` is intentionally left unstyled so it inherits the box's own
 * colour: the effect is the bulk of the text and should read as ordinary text,
 * with the structure picked out around it. */
.desc .psct-condition { color: var(--psct-condition); }
.desc .psct-cost { color: var(--psct-cost); }
.desc .psct-target { color: var(--psct-target); }
.desc .psct-conjunctive {
  color: var(--psct-conjunctive);
  font-style: italic;
}
/* Bulleted effects: the marker is bold, and the marker plus its line share one
 * colour that cycles, so neighbouring bullets never look alike. */
.desc .psct-bullet { font-weight: 700; }
.desc .psct-b1 { color: var(--psct-b1); }
.desc .psct-b2 { color: var(--psct-b2); }
.desc .psct-b3 { color: var(--psct-b3); }
.desc .psct-b4 { color: var(--psct-b4); }

/* Sets the card was printed in, listed after the effect text.
 *
 * Scrolls rather than pushing the pane taller: one card is printed in 61 sets,
 * and the inspector sits below the card image with limited room left. Wrapping
 * chips keep even that long list compact.
 */
.set-codes { margin-top: 10px; }
.set-code-list {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 4px;
  max-height: 132px;
  overflow-y: auto;
  font-size: 12px;
}
.set-code-list.muted { color: var(--muted); }

/* Each chip is one set prefix; hovering shows the full product name(s). */
.set-chip {
  padding: 2px 7px;
  border-radius: 4px;
  background: var(--panel);
  border: 1px solid var(--line);
  color: #cfd5e3;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11px;
  cursor: help;
  white-space: nowrap;
}
.set-chip:hover { border-color: var(--accent); color: var(--text); }

/* Legality now lives on the deck bar (see `.deck-legality`), so the inspector
   pane holds only the preview and the card details. */

.status-badge {
  display: inline-block;
  padding: 4px 10px;
  border-radius: 20px;
  font-size: 12px;
  font-weight: 600;
  /* It is a <button>, so the browser's chrome has to be cleared or it will not
     match the colouring below. */
  border: 0;
  font-family: inherit;
  cursor: pointer;
}
/* Nothing to reveal means nothing to click. */
.status-badge:disabled { cursor: default; }
.status-badge:not(:disabled):hover { filter: brightness(1.35); }
.status-badge.ok { background: rgba(55, 201, 138, 0.15); color: var(--ok); }
.status-badge.warn { background: rgba(240, 180, 41, 0.15); color: var(--warn); }
.status-badge.err { background: rgba(255, 95, 87, 0.15); color: var(--err); }

.violations { list-style: none; margin: 10px 0 0; padding: 0; }
.violations li {
  padding: 6px 8px;
  margin-bottom: 5px;
  border-radius: 6px;
  font-size: 12px;
  border-left: 3px solid var(--line);
  background: var(--panel);
}
.violations li.error { border-left-color: var(--err); }
.violations li.warning { border-left-color: var(--warn); }

/* -------------------------------------------------------------- modal -- */

.modal {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;
  padding: 20px;
}
.modal.hidden { display: none; }

.modal-box {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  width: min(720px, 100%);
  max-height: 85vh;
  display: flex;
  flex-direction: column;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
}
.modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 16px;
  border-bottom: 1px solid var(--line);
}
.modal-head h2 { margin: 0; font-size: 15px; }
.modal-body { padding: 16px; overflow-y: auto; }
.modal-body h3 { margin: 16px 0 6px; font-size: 13px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; }
.modal-body h3:first-child { margin-top: 0; }
.modal-body textarea { width: 100%; min-height: 220px; font-family: ui-monospace, Consolas, monospace; font-size: 12px; }
.modal-body .row { display: flex; gap: 8px; align-items: center; margin-bottom: 10px; flex-wrap: wrap; }

/* A confirmation puts Cancel and the destructive action at the far end, right
   to left, so Cancel sits nearest the pointer's entry point. */
.modal-body .confirm-row {
  justify-content: flex-end;
  margin-top: 16px;
  margin-bottom: 0;
}
.modal-body ul { margin: 6px 0; padding-left: 18px; font-size: 13px; }
.modal-body li { margin-bottom: 4px; }

/* Names that failed to import. Scrolls rather than growing without bound, so a
   long list cannot push the Close button out of reach. */.import-failures {
  list-style: none;
  padding: 8px 10px;
  margin: 6px 0 12px;
  max-height: 300px;
  overflow-y: auto;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 6px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
}
.import-failures li {
  margin-bottom: 3px;
  color: var(--err);
  word-break: break-word;
}
/* The "and N more" tail is a note, not another failure. */
.import-failures li.more { color: var(--muted); margin-top: 6px; }

/* Cardpool management list in Settings. */
.pool-list { list-style: none; padding-left: 0; }
.pool-list li {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 8px;
  background: var(--panel-2);
  border-radius: 6px;
  margin-bottom: 4px;
}
.pool-list .pool-name { flex: 1 1 auto; }
.pool-list .pool-tag {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: var(--muted);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 1px 6px;
}
.pool-list .pool-tag.active { color: var(--accent); border-color: var(--accent); }

/* --- cardpool editor ---------------------------------------------------- */

.pool-add-row {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin: 10px 0 4px;
}
/* The wrapper anchors the suggestion list to the input. */
.pool-name-input { position: relative; flex: 1 1 auto; min-width: 0; }
.pool-name-input input { width: 100%; }
.pool-qty { width: 68px; flex: 0 0 auto; }

/* Suggestions drop over the list below rather than pushing it down. */
.pool-suggest {
  position: absolute;
  top: calc(100% + 2px);
  left: 0;
  right: 0;
  z-index: 30;
  margin: 0;
  padding: 4px;
  list-style: none;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 6px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.55);
}
.pool-suggest.hidden { display: none; }
.pool-suggest-item {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  padding: 5px 8px;
  border-radius: 4px;
  cursor: pointer;
  font-size: 13px;
}
.pool-suggest-item:hover,
.pool-suggest-item.active { background: var(--accent); color: #fff; }
.pool-suggest-item:hover .muted,
.pool-suggest-item.active .muted { color: #fff; }

.pool-editor-status { min-height: 17px; font-size: 12px; margin: 4px 0 0; }
.pool-editor-status.error { color: var(--err); }
.pool-editor-status.ok { color: var(--ok); }

.pool-edit-list {
  list-style: none;
  padding-left: 0;
  max-height: 280px;
  overflow-y: auto;
}
.pool-edit-list li {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 8px;
  border-bottom: 1px solid var(--line);
  font-size: 13px;
}
.pool-edit-list li > span:first-child { flex: 1 1 auto; }

/* The per-card copy count. Narrow, and matched to the other inputs in the
   editor so a row reads as one control rather than a form. */
.pool-row-qty {
  width: 56px;
  flex: 0 0 auto;
  padding: 3px 6px;
  font-size: 12px;
  text-align: right;
  background: var(--panel);
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: 6px;
  -moz-appearance: textfield;
  appearance: textfield;
}
.pool-row-qty:focus { border-color: var(--accent); outline: none; }
.pool-row-owned { flex: 0 0 auto; font-size: 11px; }

/* -------------------------------------------------------------- toast -- */

.toast {
  position: fixed;
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%);
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-left: 3px solid var(--accent);
  padding: 10px 18px;
  border-radius: 8px;
  z-index: 200;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
  max-width: 70vw;
}
.toast.hidden { display: none; }
.toast.error { border-left-color: var(--err); }
.toast.ok { border-left-color: var(--ok); }

/* --------------------------------------------------------------- help -- */

/* The help buttons are circular so they read as "information" rather than as
   another action button.
 *
 * Centring is done with flex rather than padding: a text glyph is laid out on
 * its baseline, so equal padding leaves it visibly off-centre. Flex centres the
 * line box itself, which is what "centred in the circle" actually means. */
.help-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  min-width: 24px;
  padding: 0;
  border-radius: 50%;
  font-weight: 700;
  /* The glyph carries the line box; `line-height: 1` stops the font's default
     leading from pushing it below centre. */
  line-height: 1;
  flex: 0 0 auto;
}
/* Sits between the heading and the result count, pushed left of the count. */
.pane-head .help-btn { margin-left: 8px; margin-right: auto; }

.syntax-table {
  width: 100%;
  border-collapse: collapse;
  margin: 4px 0 12px;
  font-size: 12px;
}
.syntax-table th,
.syntax-table td {
  text-align: left;
  vertical-align: top;
  padding: 5px 8px;
  border-bottom: 1px solid var(--line);
}
.syntax-table th { color: var(--muted); font-weight: 600; }
.syntax-table td:nth-child(3) { color: var(--muted); }

/* A code chip. Examples are clickable, so they get a pointer and a hover tint
   to look like the shortcut they are. */
.syntax-table code,
.syntax-example {
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 1px 5px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11px;
  white-space: nowrap;
}
.syntax-example {
  cursor: pointer;
  color: var(--accent);
}
.syntax-example:hover { border-color: var(--accent); }

.syntax-examples,
.docs-list {
  margin: 4px 0 12px;
  padding-left: 18px;
  color: var(--muted);
  font-size: 12px;
  line-height: 1.7;
}
.syntax-examples { list-style: none; padding-left: 0; }

/* ------------------------------------------------------------ loading -- */

.spinner {
  width: 26px;
  height: 26px;
  border: 3px solid var(--line);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
  margin: 26px auto;
}
@keyframes spin { to { transform: rotate(360deg); } }

@media (max-width: 1200px) {
  .layout { grid-template-columns: 1fr 1fr; height: auto; }
  .inspect-pane { display: none; }
}
