/* ============================================================
   Cartodex — Design System
   Tokens, self-hosted fonts, base typography.
   Energy-type palette + holo signature live in the layer at the
   bottom of this file. Legacy variable names are kept as aliases
   so existing rules pick up the new palette automatically.
   ============================================================ */

/* — Fonts (self-hosted in app/assets/fonts, digested by Propshaft) — */
@font-face {
  font-family: "Archivo"; font-style: normal; font-weight: 600 800; font-display: swap;
  src: url("/assets/archivo-latin-7a918f31.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Archivo"; font-style: normal; font-weight: 600 800; font-display: swap;
  src: url("/assets/archivo-latinext-4e7e2dd1.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "IBM Plex Sans"; font-style: normal; font-weight: 400 700; font-display: swap;
  src: url("/assets/plexsans-latin-578a0041.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "IBM Plex Sans"; font-style: normal; font-weight: 400 700; font-display: swap;
  src: url("/assets/plexsans-latinext-dfe25727.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "IBM Plex Mono"; font-style: normal; font-weight: 400; font-display: swap;
  src: url("/assets/plexmono-400-latin-d13bb5b4.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "IBM Plex Mono"; font-style: normal; font-weight: 400; font-display: swap;
  src: url("/assets/plexmono-400-latinext-182606ec.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "IBM Plex Mono"; font-style: normal; font-weight: 600; font-display: swap;
  src: url("/assets/plexmono-600-latin-0fdd541f.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "IBM Plex Mono"; font-style: normal; font-weight: 600; font-display: swap;
  src: url("/assets/plexmono-600-latinext-71edd651.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  /* — Neutrals (cool slate, not pure grey) — */
  --ink-900: #0E1320;  --ink-800: #1A2233;  --ink-700: #28324A;
  --ink-500: #5A6678;  --ink-300: #93A0B4;  --ink-200: #B6C0CD;
  --line: #E5E9F0;     --paper: #F7F8FA;    --surface: #FFFFFF;

  /* — Brand flare (refined poké-red), reserved for primary actions — */
  --flare: #DD2C16;    --flare-ink: #B0210F;
  --water-strong: #1D6AA5;

  /* — Energy-type palette (the signature colour language) — */
  --grass: #3FA34D;    --grass-bg: #E6F4E8;
  --fire: #E4451E;     --fire-bg: #FCE9E2;
  --water: #2B8FD8;    --water-bg: #E2F0FB;
  --bolt: #E0A800;     --bolt-bg: #FBF1CF;
  --psychic: #9B5BC4;  --psychic-bg: #F1E8F8;
  --fighting: #C2632B; --fighting-bg: #F6E8DC;
  --darkness: #3B4252; --darkness-bg: #E4E7EC;
  --metal: #7E8CA0;    --metal-bg: #EAEDF1;
  --dragon: #B8902A;   --dragon-bg: #F5ECD2;
  --fairy: #DE6FA8;    --fairy-bg: #FBE7F1;

  /* — Semantic result colours (separate from the accent) — */
  --win: #2E9E5B;      --win-bg: #E5F4EC;
  --loss: #DD3B3B;     --loss-bg: #FBE7E7;
  --draw: #6B7686;     --draw-bg: #ECEFF3;
  --timeout: #E08A1E;  --timeout-bg: #FBEFD9;

  /* — Elevation — */
  --e1: 0 1px 2px rgba(14, 19, 32, 0.06), 0 1px 3px rgba(14, 19, 32, 0.08);
  --e2: 0 4px 12px rgba(14, 19, 32, 0.08), 0 2px 4px rgba(14, 19, 32, 0.06);
  --e3: 0 12px 32px rgba(14, 19, 32, 0.14);

  /* — Type roles — */
  --font-display: "Archivo", "Helvetica Neue", system-ui, sans-serif;
  --font-body: "IBM Plex Sans", system-ui, -apple-system, sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, "SFMono-Regular", Menlo, monospace;

  /* — Legacy aliases (existing rules reference these) — */
  --primary-color: var(--flare);
  --secondary-color: var(--water);
  --background-color: var(--paper);
  --text-color: #1D2433;
  --border-color: var(--line);
}

/* The UA rule for [hidden] is a plain `display: none` on the element, so any component that sets
   its own display — .btn, flex containers — silently outranks it and stays visible. */
[hidden] {
  display: none !important;
}

body {
  margin: 0;
  padding: 0;
  font-family: var(--font-body);
  background-color: var(--background-color);
  color: var(--text-color);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* Navbar */
.navbar {
  background: var(--surface);
  box-shadow: var(--e1);
  position: sticky;
  top: 0;
  z-index: 100;
}

.navbar-inner {
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 1rem;
  display: flex;
  align-items: center;
  height: 56px;
  /* 1.5rem, not 2. `.navbar-menu` is `display: contents`, so this gap falls between four items in
     the visitor's navbar, and 2rem of it spent 96px on whitespace in a row that had none to spare:
     measured, adding the brand mark took that navbar — the one this change leaves flat — from 0 to
     6px of document overflow at 769px. The grouped navbars are dense enough that the row reads as
     well at 1.5. */
  gap: 1.5rem;
}

.navbar-brand {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 1.25rem;
  font-weight: bold;
  color: var(--primary-color);
  text-decoration: none;
}

/* flex: none because the brand is a flex item that the row may compress: without it the mark is
   the first thing squeezed when the row runs out of space, which is exactly when it matters. */
.navbar-logo {
  display: block;
  flex: none;
}

/* Just above the breakpoint the member's row has no slack left, and the wordmark is the cheapest
   91px in it — which is what a mark is for. Measured on the member navbar: the row needs 806px,
   `.navbar-inner` gives `viewport − 32`, so the word fits from 838px up; the band ends at 880 to
   leave the next entry somewhere to go rather than reopening this question. Below 768px it comes
   back: the drawer's bar holds nothing but the brand, the search trigger and the hamburger. */
@media (min-width: 769px) and (max-width: 880px) {
  .navbar-brand-word {
    display: none;
  }
}

.navbar-links {
  display: flex;
  gap: 0.25rem;
}

/* Stretched to the bar's full height, with its own children re-centred: a group's panel is
   `top: 100%` of the group, and a group centred in the row is a 35px box in a 56px bar — the panel
   then opened 7px *above* the bar's bottom edge and overlapped it. Stretching moves 100% to where
   the bar actually ends.

   `:has(.navbar-group)` and not the bare class, because `align-items: center` is the half of this
   that must NOT reach `Ui::PublicNavbar`. Centring sizes each link to its own content, and a row
   where one label wraps then holds a 35px box beside a 54px one and centres both independently:
   measured on the visitor's navbar at 769px, "Cards / Archetypes / Tournaments" sat 10px below the
   first line of "Shared decks" — in the one navbar this change leaves flat, at the one desktop
   width CI renders every run. Without these two declarations the children stretch to the tallest,
   which is what master did and what keeps the first lines on one baseline; with them, a grouped
   navbar keeps its 35px pills, and nothing in a grouped navbar wraps (0px overflow at 769 and
   1280, member and admin, asserted in navbar_layout_test).

   A browser without `:has()` loses the anchoring and gets the 7px overlap back — a degradation,
   not a break, and the right way round. */
.navbar-links:has(.navbar-group) {
  align-self: stretch;
  align-items: center;
}

/* A navbar entry that expands rather than navigating. Above the breakpoint the trigger is the
   entry and the panel hangs off it; below it (see the mobile block) the trigger disappears, the
   heading takes its place and the panel becomes a plain section of the drawer — the whole
   responsive switch is these rules and no JavaScript, which is what keeps the trigger's
   aria-expanded from ever describing a panel the drawer is already showing. */
.navbar-group {
  position: relative;
  display: flex;
  align-self: stretch;
  align-items: center;
}

.navbar-group-trigger {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.5rem 0.75rem;
  border: none;
  border-radius: 4px;
  background: none;
  color: var(--text-color);
  font: inherit;
  font-size: 0.9rem;
  cursor: pointer;
}

.navbar-group-trigger:hover {
  background-color: var(--background-color);
}

.navbar-group-trigger.active {
  color: var(--primary-color);
  font-weight: 600;
}

/* Drawn rather than shipped, like the search lens and the hamburger beside it. */
.navbar-group-caret {
  width: 0;
  height: 0;
  border-top: 5px solid currentcolor;
  border-right: 4px solid transparent;
  border-left: 4px solid transparent;
}

.navbar-group-heading {
  display: none;
}

.navbar-group-panel {
  display: none;
  position: absolute;
  top: 100%;
  left: 0;
  min-width: 190px;
  margin-top: 0.25rem;
  padding: 0.35rem;
  border-radius: 8px;
  /* --ink-800, not --surface: .dropdown-menu is a white card, which is right hanging off a page
     and wrong hanging off a black bar. */
  background: var(--ink-800);
  box-shadow: var(--e2);
  z-index: 50;
}

/* The account menu is the last item in the row, so a panel anchored to its left edge would leave
   the viewport rather than open into it. */
.navbar-group-panel--right {
  right: 0;
  left: auto;
}

.navbar-group-panel--open {
  display: block;
}

/* nowrap here and deliberately NOT on .navbar-link at large: the panel is a column that sizes
   itself to its content, so there is nothing for a wide label to overflow. In the row there is —
   measured, `white-space: nowrap` on every .navbar-link costs Ui::PublicNavbar 48px of document
   overflow at 769px, where it currently has none, because that navbar's fit depends on "Shared
   decks" wrapping to two lines. */
.navbar-group-panel .navbar-link {
  display: block;
  white-space: nowrap;
}

.navbar-group-panel .navbar-user {
  display: block;
  padding: 0.4rem 0.75rem;
}

.navbar-group-initial {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--ink-700);
  color: #fff;
  font-size: 0.8rem;
  font-weight: 600;
}

.navbar-link {
  color: var(--text-color);
  text-decoration: none;
  padding: 0.5rem 0.75rem;
  border-radius: 4px;
  font-size: 0.9rem;
  transition: background-color 0.2s;
}

.navbar-link:hover {
  background-color: var(--background-color);
}

.navbar-link.active {
  color: var(--primary-color);
  font-weight: 600;
}

/* The search trigger sits before .navbar-menu in the DOM so the hamburger can never hide it (see
   Ui::NavbarShell). `order` is what puts it on the right of the links here; the mobile block below
   hands it back to DOM order, where the menu is collapsed anyway and it belongs beside the
   hamburger. .navbar-right has to be ordered past it, or it would come first whatever its margin. */
.navbar-search-trigger {
  order: 1;
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem;
  background: none;
  border: none;
  color: var(--text-color);
  cursor: pointer;
}

/* The lens is drawn, not shipped: the hamburger beside it is a CSS shape too, and the app has no
   icon set to take a magnifier from. */
.navbar-search-trigger-icon {
  display: block;
  position: relative;
  width: 13px;
  height: 13px;
  border: 2px solid currentcolor;
  border-radius: 50%;
}

.navbar-search-trigger-icon::after {
  content: "";
  position: absolute;
  top: 11px;
  left: 10px;
  width: 2px;
  height: 7px;
  background: currentcolor;
  border-radius: 1px;
  transform: rotate(-45deg);
  transform-origin: top left;
}

.navbar-search-trigger-hint {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  color: var(--ink-500);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 0.05rem 0.3rem;
}

.navbar-right {
  order: 2;
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 1rem;
}

/* The account menu is what replaced .navbar-right in the two signed-in navbars: the email plus
   Settings/Admin/Sign out used to sit in the row, and the email — one unbreakable word — was the
   single widest thing in it that no amount of flex shrinking could narrow. .navbar-right stays
   because Ui::PublicNavbar still has one (Sign in / Sign up), so both rules are live. */
.navbar-account {
  order: 2;
  margin-left: auto;
  display: flex;
  align-self: stretch;
  align-items: center;
}

/* Two flex items with `margin-left: auto` split the free space between them rather than one of
   them taking it, which parked the trigger mid-navbar — the same construct, and the same fix, as
   the mobile block below applies to the hamburger. Only the first item needs the margin; scoping
   it to a navbar that has a trigger leaves the admin one, which has none, pushing its own
   account menu right. */
.navbar-search-trigger ~ .navbar-menu .navbar-right,
.navbar-search-trigger ~ .navbar-menu .navbar-account {
  margin-left: 0;
}

.navbar-user {
  font-size: 0.85rem;
  color: var(--ink-500);
}

/* Authentication pages */
.welcome-container {
  max-width: 600px;
  margin: 100px auto;
  padding: 2rem;
  text-align: center;
  background: var(--surface);
  border-radius: 8px;
  box-shadow: var(--e1);
}

.auth-buttons {
  margin-top: 2rem;
  display: flex;
  gap: 1rem;
  justify-content: center;
}

/* Dashboard */
.dashboard-container {
  max-width: 1200px;
  margin: 2rem auto;
  padding: 0 1rem;
}

.dashboard-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 2rem;
  margin-top: 2rem;
}

.dashboard-card {
  background: var(--surface);
  padding: 1.5rem;
  border-radius: 8px;
  box-shadow: var(--e1);
}

.dashboard-card h2 {
  margin-top: 0;
  color: var(--primary-color);
}

.card-count, .deck-count {
  font-size: 2rem;
  font-weight: bold;
  margin: 1rem 0;
}

.card-actions, .deck-actions {
  display: flex;
  gap: 1rem;
  margin-top: 1rem;
}

.dashboard-showcase {
  margin-top: 2.5rem;
}

.dashboard-showcase-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 1rem;
  margin: 1rem 0 1.5rem;
}

.dashboard-showcase-deck {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  background: var(--surface);
  padding: 1rem;
  border-radius: 8px;
  box-shadow: var(--e1);
  color: inherit;
  text-decoration: none;
}

.dashboard-showcase-deck:hover {
  box-shadow: var(--e2);
}

.dashboard-showcase-deck-name {
  font-weight: 600;
}

/* Scanner Modal */
.modal {
  display: none;
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.8);
  z-index: 1000;
}

.modal-content {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background: var(--surface);
  padding: 2rem;
  border-radius: 8px;
  max-width: 90%;
  width: 500px;
  text-align: center;
}

/* Importing list */
.importing-section {
  margin-top: 1.5rem;
  padding-top: 1rem;
  border-top: 1px solid var(--border-color);
}

.importing-section h3 {
  margin: 0 0 0.5rem;
  font-size: 0.95rem;
  color: var(--text-color);
}

.importing-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.importing-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.4rem 0;
  font-size: 0.9rem;
  color: var(--ink-500);
}

.importing-spinner {
  width: 14px;
  height: 14px;
  border: 2px solid var(--border-color);
  border-top-color: var(--primary-color);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}

@keyframes spin {
  to { transform: rotate(360deg); }
}

textarea.form-input {
  resize: vertical;
  min-height: 120px;
}

.import-actions {
  display: flex;
  gap: 1rem;
}

.import-actions .btn {
  flex: 1;
}

#scanner-video {
  width: 100%;
  max-width: 400px;
  margin: 1rem auto;
  border-radius: 4px;
}

/* Buttons */
.btn {
  display: inline-block;
  padding: 0.75rem 1.5rem;
  border-radius: 4px;
  border: none;
  font-size: 1rem;
  font-weight: 500;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.2s, transform 0.1s;
  box-sizing: border-box;
}

.btn:active {
  transform: translateY(1px);
}

.btn-primary {
  background-color: var(--primary-color);
  color: white;
}

.btn-primary:hover {
  background-color: var(--flare-ink);
}

.btn-secondary {
  background-color: var(--secondary-color);
  color: white;
}

.btn-secondary:hover {
  background-color: var(--water-strong);
}

/* Auth container */
.auth-container {
  max-width: 480px;
  margin: 60px auto;
  padding: 2rem;
  background: var(--surface);
  border-radius: 8px;
  box-shadow: var(--e1);
}

.auth-container h1 {
  text-align: center;
  color: var(--primary-color);
  margin-top: 0;
  margin-bottom: 1.5rem;
}

/* Form elements */
.form-group {
  margin-bottom: 1.25rem;
}

.form-label {
  display: block;
  margin-bottom: 0.5rem;
  font-weight: 500;
  color: var(--text-color);
}

.form-input {
  width: 100%;
  padding: 0.75rem;
  border: 1px solid var(--border-color);
  border-radius: 4px;
  font-size: 1rem;
  font-family: inherit;
  box-sizing: border-box;
  transition: border-color 0.2s;
}

.form-input:focus {
  outline: none;
  border-color: var(--secondary-color);
  box-shadow: 0 0 0 2px rgba(48, 167, 215, 0.2);
}

.form-hint {
  display: block;
  font-size: 0.85rem;
  font-style: italic;
  color: var(--ink-300);
  margin-top: 0.25rem;
}

.form-error {
  display: block;
  font-size: 0.85rem;
  color: var(--primary-color);
  margin-top: 0.25rem;
}

.form-check {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 1.25rem;
}

.form-check input[type="checkbox"] {
  accent-color: var(--primary-color);
}

.form-actions {
  margin-top: 1.5rem;
}

.form-actions .btn {
  width: 100%;
}

/* Auth links */
.auth-links {
  text-align: center;
  margin-top: 1.5rem;
  padding-top: 1rem;
  border-top: 1px solid var(--border-color);
}

.auth-links a {
  display: block;
  color: var(--secondary-color);
  text-decoration: none;
  margin-bottom: 0.5rem;
  font-size: 0.9rem;
}

.auth-links a:hover {
  text-decoration: underline;
}

/* Flash messages */
.flash {
  max-width: 480px;
  margin: 1rem auto;
  padding: 0.75rem 1rem;
  border-radius: 4px;
  text-align: center;
}

.flash-notice {
  background-color: var(--win-bg);
  color: #1E7544;
  border: 1px solid var(--win-bg);
}

.flash-alert {
  background-color: var(--loss-bg);
  color: #B62A2A;
  border: 1px solid var(--loss-bg);
}

/* An announcement rather than an outcome: persistent, so it carries its own close button, and the
   text sits left of it rather than centred over it. */
.flash-info {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  text-align: left;
  background-color: var(--water-bg);
  color: var(--water-strong);
  border: 1px solid var(--water-bg);
}

.flash-info a {
  color: inherit;
  font-weight: 600;
}

.flash-close {
  margin-left: auto;
  flex-shrink: 0;
  padding: 0 0.25rem;
  border: 0;
  background: none;
  color: inherit;
  font-size: 1.25rem;
  line-height: 1;
  cursor: pointer;
}

/* Form errors block */
.form-errors {
  background-color: var(--loss-bg);
  color: #B62A2A;
  border: 1px solid var(--loss-bg);
  border-radius: 4px;
  padding: 1rem;
  margin-bottom: 1.25rem;
}

.form-errors h3 {
  margin-top: 0;
  font-size: 0.95rem;
}

.form-errors ul {
  margin: 0;
  padding-left: 1.25rem;
  font-size: 0.9rem;
}

/* Danger action */
.btn-danger {
  color: var(--primary-color);
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  font-size: 0.9rem;
  text-decoration: underline;
}

.btn-danger:hover {
  color: var(--flare-ink);
}

.badge {
  display: inline-block;
  padding: 0.2em 0.6em;
  border-radius: 4px;
  font-size: 0.85em;
  font-weight: 500;
}

.badge-warning {
  background: var(--bolt-bg);
  color: #8A6800;
}

.badge-success {
  background: var(--win-bg);
  color: #1E7544;
}

.badge-danger {
  background: var(--loss-bg);
  color: #B62A2A;
}

.badge-format {
  background: #e2e3f3;
  color: #3b3f6b;
}

.badge-neutral {
  background: var(--line);
  color: var(--ink-700);
}

.badge-archetype {
  background: var(--water-bg);
  color: var(--water-strong);
}

/* Archetype picker (deck form) */
.deck-archetype-field {
  position: relative;
}

.archetype-field-search {
  display: flex;
  gap: 0.5rem;
  align-items: flex-start;
}

.archetype-field-search .form-input {
  flex: 1;
}

/* Stats / matchups */
.deck-stats-archetype {
  margin: 0 0 1rem;
  color: var(--ink-500);
}

/* Deck versions: the versions page, the per-version summary on the stats page, and the diff
   page's one-line caption above the compare table (whose container carries its own centring,
   so the caption repeats it rather than sitting against the page edge). */
.deck-versions-status {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem 1rem;
  margin-bottom: 1.5rem;
  color: var(--ink-500);
}

.deck-versions-status p {
  margin: 0;
}

.deck-versions-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

/* button_to wraps its button in a form, which is a block and would stack the row's two actions. */
.deck-versions-delete {
  display: inline;
  margin: 0;
}

.version-summary {
  margin-bottom: 2rem;
}

.version-summary-scope {
  margin: 0.75rem 0 0;
  color: var(--ink-500);
}

.deck-version-summary {
  max-width: 1000px;
  margin: 2rem auto 0;
  padding: 0 1rem;
  color: var(--ink-500);
}

/* The modal's version question takes the place of its Save row, so it keeps that row's spacing. */
.result-version-prompt {
  margin-top: 1rem;
  padding-top: 1rem;
  border-top: 1px solid var(--line);
}

.result-version-prompt-text {
  margin: 0;
  font-weight: 600;
}

/* The version question's three answers do not fit one row: side by side each squeezed to its
   longest word and wrapped — three lines at 344px, two even at 1400px inside the dialog's width.
   One full-width row each, at every width, and `flex: none` because the row's `flex: 1` would
   otherwise size them against a column's height. */
.result-version-prompt .result-modal-actions {
  flex-direction: column;
}

.result-version-prompt .result-modal-actions .btn {
  flex: none;
  width: 100%;
}

.matchup-archetype-section {
  margin-bottom: 2.5rem;
}

.matchup-deck-count {
  color: var(--ink-300);
  margin: 0 0 0.75rem;
}

/* Deck classification */
.deck-badges {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin: 0.5rem 0;
}

.deck-badges:empty {
  display: none;
}

.form-fieldset {
  border: 1px solid var(--border-color);
  border-radius: 4px;
  padding: 0.75rem 1rem;
  margin-bottom: 1.25rem;
}

.form-fieldset legend {
  padding: 0 0.35rem;
  font-weight: 500;
}

.form-fieldset .form-check {
  margin-bottom: 0.5rem;
}

.form-fieldset .form-check:last-child {
  margin-bottom: 0;
}

.deck-filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 1.5rem;
}

.deck-filter-select {
  width: auto;
  min-width: 9rem;
  padding: 0.4rem 0.6rem;
}

.deck-filter-search {
  flex: 1 1 14rem;
  min-width: 10rem;
}

/* Result modal */
.result-modal {
  /* The UA stylesheet only centers a modal dialog on the block axis (inset-block: 0 + margin:
     auto); the inline axis is left at its DOM static position, so a wide ancestor (.deck-show-
     container is centered with its own auto margins) can leave the dialog hugging that offset
     instead of the viewport center. Setting inset-inline: 0 too makes the auto-margin centering
     trick apply on both axes, independent of where the dialog sits in the DOM. */
  position: fixed;
  inset: 0;
  margin: auto;
  border: none;
  border-radius: 12px;
  padding: 2rem;
  max-width: 480px;
  width: 90%;
  box-shadow: var(--e3);
}

.result-modal::backdrop {
  background: rgba(0, 0, 0, 0.5);
}

.result-modal-content h2 {
  margin: 0 0 1.5rem;
}

.result-type-buttons {
  display: flex;
  gap: 0.5rem;
  margin-bottom: 1.5rem;
}

.result-type-btn {
  flex: 1;
  padding: 0.6rem;
  border: 2px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
  cursor: pointer;
  font-weight: 500;
  transition: all 0.15s;
}

.result-type-btn.active {
  color: white;
}

.result-win.active { background: var(--win); border-color: var(--win); }
.result-loss.active { background: var(--loss); border-color: var(--loss); }
.result-draw.active { background: var(--draw); border-color: var(--draw); }
.result-timeout.active { background: var(--timeout); border-color: var(--timeout); }

.result-type-btn:disabled {
  cursor: not-allowed;
  opacity: 0.5;
}

.result-type-btn.locked.active {
  opacity: 1;
}

.match-format-btn.active {
  background: var(--water-strong);
  border-color: var(--water-strong);
  color: white;
}

/* Per-game BO3 score selector */
.match-score-section {
  margin-bottom: 1.5rem;
}

.match-score-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.5rem;
}

.match-score-game-label {
  width: 4rem;
  font-size: 0.9em;
  color: var(--ink-500);
}

.match-game-btn {
  padding: 0.35rem 0.5rem;
}

.match-game-btn.game-w.active { background: var(--win); border-color: var(--win); color: white; }
.match-game-btn.game-l.active { background: var(--loss); border-color: var(--loss); color: white; }
.match-game-btn.game-t.active { background: var(--timeout); border-color: var(--timeout); color: white; }
.match-game-btn.game-d.active { background: var(--draw); border-color: var(--draw); color: white; }

.match-score-clear {
  margin-top: 0.25rem;
}

.archetype-search-results {
  position: relative;
}

.archetype-search-item {
  padding: 0.5rem;
  cursor: pointer;
  border-bottom: 1px solid var(--line);
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.archetype-search-item:hover {
  background: var(--paper);
}

.archetype-search-pokemon {
  color: var(--ink-300);
  font-size: 0.85em;
}

.result-modal-actions {
  display: flex;
  gap: 0.5rem;
  margin-top: 1rem;
}

.result-modal-actions .btn {
  flex: 1;
  text-align: center;
}

.archetype-create-item {
  color: var(--water-strong);
  border-top: 2px solid var(--line);
}

.create-archetype-section {
  background: var(--paper);
  border-radius: 8px;
  padding: 1rem;
  margin-bottom: 1rem;
}

/* Decks list */
.decks-container {
  max-width: 1200px;
  margin: 2rem auto;
  padding: 0 1rem;
}

.decks-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 2rem;
}

.decks-header h1 {
  margin: 0;
}

/* Every header action row wraps. Unwrapped, its min-content width is the sum of its buttons', so
   below the breakpoint — where the header stacks and the row sizes to its content — it ran off the
   screen while each button shrank to its longest word and grew three or four lines tall.
   Wrapped lines are pushed right so the last button still meets the content's right edge on a
   desktop, where the row sits at the end of the header; the stacked phone header resets that. */
.decks-header-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 0.5rem;
}

.decks-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 1.5rem;
}

.deck-item {
  background: var(--surface);
  padding: 1.5rem;
  border-radius: 8px;
  box-shadow: var(--e1);
  position: relative;
  transition: box-shadow 0.2s;
}

.deck-item:hover {
  box-shadow: var(--e2);
}

.deck-item-link {
  text-decoration: none;
  color: inherit;
  display: block;
}

.deck-item h2 {
  margin-top: 0;
  color: var(--primary-color);
  font-size: 1.2rem;
  padding-right: 5rem;
  padding-left: 1.75rem;
}

.deck-compare-checkbox {
  position: absolute;
  top: 1rem;
  left: 1rem;
  width: 1.1rem;
  height: 1.1rem;
  cursor: pointer;
  z-index: 1;
}

.deck-compare-checkbox:disabled {
  cursor: not-allowed;
  opacity: 0.4;
}

/* Floating compare action bar on the deck listings and the shared deck page. It names the
   selected decks, which may have been picked on another page, so it wraps rather than pills. */
.deck-compare-bar {
  position: fixed;
  left: 50%;
  bottom: 1.5rem;
  transform: translate(-50%, 150%);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.5rem 1rem;
  width: max-content;
  max-width: calc(100vw - 2rem);
  box-sizing: border-box;
  padding: 0.75rem 1.25rem;
  background: var(--surface);
  border-radius: 1rem;
  box-shadow: var(--e3);
  transition: transform 0.2s ease;
  z-index: 50;
}

.deck-compare-bar.is-visible {
  transform: translate(-50%, 0);
}

/* The bar floats over the end of the page; leave room to scroll the last row out from under it.
   It wraps to three lines below the breakpoint, hence a figure sized for that. */
[data-controller~="deck-compare"]:has(> .deck-compare-bar.is-visible) {
  padding-bottom: 10rem;
}

.deck-compare-bar-label {
  font-weight: 600;
  color: var(--secondary-color);
}

.deck-compare-bar-list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.deck-compare-bar-list:empty {
  display: none;
}

.deck-compare-bar-item {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  max-width: 14rem;
  padding: 0.15rem 0.25rem 0.15rem 0.6rem;
  border-radius: 999px;
  background: var(--background-color);
  font-size: 0.85rem;
}

.deck-compare-bar-item span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.deck-compare-bar-remove {
  border: none;
  background: none;
  padding: 0 0.3rem;
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  color: var(--secondary-color);
}

.deck-compare-bar-actions {
  display: flex;
  gap: 0.5rem;
}

/* Deck compare page */
.deck-compare-container {
  max-width: 1000px;
  margin: 2rem auto;
  padding: 0 1rem;
}

.deck-compare-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  /* Three items now, and the title alone is already 40% of the row at 500px: let them stack
     rather than squeeze the toggle's label onto four lines. */
  flex-wrap: wrap;
  margin-bottom: 1.5rem;
}

/* The box and the one sentence explaining the parenthesised figure are one item of the header
   flex row, stacked: three siblings already wrap at 500px, and a fourth would wrap between the
   hint and the box it describes. */
.deck-compare-toggle-group {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  max-width: 22rem;
}

.deck-compare-toggle-hint {
  font-size: 0.8rem;
  font-weight: 400;
  color: var(--ink-300);
}

.deck-compare-toggle {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  cursor: pointer;
  font-weight: 600;
  color: var(--ink-500);
  white-space: nowrap;
}

.deck-compare-toggle input {
  width: 1.1rem;
  height: 1.1rem;
  cursor: pointer;
}

.deck-compare-header h1 {
  margin: 0;
}

.deck-compare-content {
  display: flex;
  gap: 2rem;
  align-items: flex-start;
}

.deck-compare-table-wrap {
  flex: 1;
  min-width: 0;
  overflow-x: auto;
}

.deck-compare-preview {
  width: 300px;
  flex-shrink: 0;
  position: sticky;
  top: 2rem;
}

.deck-compare-card-row {
  cursor: pointer;
}

.deck-compare-table .deck-compare-card-row:hover td {
  background-color: var(--water-bg);
}

.deck-compare-table {
  width: 100%;
  border-collapse: collapse;
  background: var(--surface);
  box-shadow: var(--e1);
  border-radius: 8px;
  overflow: hidden;
}

.deck-compare-table th,
.deck-compare-table td {
  padding: 0.5rem 0.75rem;
  text-align: center;
  border-bottom: 1px solid var(--line);
}

.deck-compare-table thead th {
  background: var(--primary-color);
  color: white;
}

.deck-compare-table thead th a {
  color: white;
  text-decoration: none;
}

.deck-compare-table thead th a:hover {
  text-decoration: underline;
}

.deck-compare-card-col {
  text-align: left;
}

.deck-compare-card-link {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  text-decoration: none;
  color: inherit;
}

.deck-compare-card-link:hover .deck-compare-card-name {
  text-decoration: underline;
}

.deck-compare-card-name {
  flex: 1;
}

.deck-compare-card-set {
  font-family: var(--font-mono);
  font-size: 0.8rem;
  color: var(--ink-300);
}

.deck-compare-group-header th {
  background: var(--paper);
  text-align: left;
  font-size: 0.85rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ink-500);
}

.deck-compare-table tr.is-diff td {
  background: var(--bolt-bg);
}

/* The whole filter: one class on the container, so the server renders the filtered state as
   readily as the checkbox toggles it. A group the decks agree on goes entirely — its header
   and its subtotal name nothing the reader asked to see. */
.deck-compare-container.is-diff-only .deck-compare-card-row:not(.is-diff),
.deck-compare-container.is-diff-only tbody.is-uniform {
  display: none;
}

/* Rendered only when nothing differs at all, and then only worth showing while filtered —
   unfiltered, the table above it is the answer. */
.deck-compare-no-diff {
  display: none;
}

.deck-compare-container.is-diff-only .deck-compare-no-diff {
  display: table-row-group;
}

.deck-compare-no-diff td {
  padding: 1.5rem;
  color: var(--ink-500);
}

/* Second figure in a subtotal or total cell, and not a share of the first — it is dimmed so
   the deck's own count stays the one the eye lands on. */
.deck-compare-diff-count {
  font-weight: 400;
  font-size: 0.85rem;
  color: var(--ink-300);
}

.deck-compare-table .is-absent {
  color: var(--ink-300);
}

.deck-compare-subtotal td,
.deck-compare-total td {
  font-weight: 600;
  border-top: 2px solid var(--line);
}

.deck-compare-total td {
  border-top: 2px solid var(--primary-color);
}

.deck-description {
  color: var(--ink-500);
  font-size: 0.9rem;
}

.deck-card-count {
  font-weight: 600;
  color: var(--secondary-color);
}

/* Where a list was played, on an archetype's deck list (Archetypes::ShowView). */
.deck-caption {
  color: var(--ink-500);
  font-size: 0.85rem;
}

.deck-item-actions {
  position: absolute;
  top: 0.75rem;
  right: 0.75rem;
}

/* Deck show */
.deck-show-container {
  max-width: 800px;
  margin: 2rem auto;
  padding: 0 1rem;
}

.deck-show-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 1rem;
}

.deck-show-header h1 {
  margin: 0;
}

.deck-show-description {
  color: var(--ink-500);
  margin-top: 0.25rem;
}

.deck-actions-bar {
  display: flex;
  gap: 0.5rem;
  margin-bottom: 1.5rem;
  flex-wrap: wrap;
}

.deck-show-stats {
  display: flex;
  gap: 2rem;
  margin-bottom: 2rem;
  padding: 1rem 1.5rem;
  background: var(--surface);
  border-radius: 8px;
  box-shadow: var(--e1);
}

.stat {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.stat-value {
  font-size: 1.5rem;
  font-weight: bold;
  color: var(--primary-color);
}

.stat-label {
  font-size: 0.85rem;
  color: var(--ink-500);
}

.deck-section {
  margin-bottom: 1.5rem;
}

.deck-section h2 {
  font-size: 1.1rem;
  color: var(--secondary-color);
  border-bottom: 2px solid var(--secondary-color);
  padding-bottom: 0.5rem;
}

.deck-section-group {
  margin-bottom: 1.5rem;
}

.deck-section-group h2 {
  font-size: 1.1rem;
  color: var(--secondary-color);
  border-bottom: 2px solid var(--secondary-color);
  padding-bottom: 0.5rem;
}

.deck-subsection {
  margin-left: 1rem;
  margin-bottom: 1rem;
}

.deck-subsection h3 {
  font-size: 0.95rem;
  color: var(--secondary-color);
  border-bottom: 1px solid var(--line);
  padding-bottom: 0.35rem;
}

.deck-card-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.deck-card-item {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--line);
}

.deck-card-qty {
  font-weight: 600;
  min-width: 2rem;
  color: var(--primary-color);
}

.deck-card-name {
  flex: 1;
}

.deck-card-set {
  font-size: 0.85rem;
  color: var(--ink-300);
}

/* Deck show layout */
.deck-show-content {
  display: flex;
  gap: 2rem;
  align-items: flex-start;
}

.deck-show-main {
  flex: 1;
  min-width: 0;
}

.deck-show-preview {
  width: 300px;
  flex-shrink: 0;
  position: sticky;
  top: 2rem;
}

.card-preview-image {
  width: 100%;
  border-radius: 12px;
  box-shadow: var(--e2);
}

.deck-card-item {
  cursor: pointer;
}

.deck-card-item:hover {
  background-color: var(--water-bg);
}

.deck-card-qty-controls {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  min-width: 4.5rem;
}

.qty-btn {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 1px solid var(--border-color);
  background: var(--surface);
  font-size: 0.85rem;
  font-weight: bold;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-color);
  padding: 0;
  transition: background-color 0.2s, border-color 0.2s;
}

.qty-btn:hover {
  background: var(--primary-color);
  border-color: var(--primary-color);
  color: white;
}

.card-preview-link {
  display: block;
  text-align: center;
  margin-top: 0.75rem;
  color: var(--secondary-color);
  font-size: 0.9rem;
}

/* Card show */
.card-show-container {
  max-width: 1000px;
  margin: 2rem auto;
  padding: 0 1rem;
}

.card-show-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 2rem;
}

.card-show-header h1 {
  margin: 0;
}

.card-show-header-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 0.5rem;
  align-items: center;
}

.card-collection-counter {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  background: var(--surface);
  padding: 0.35rem 0.6rem;
  border-radius: 6px;
  box-shadow: var(--e1);
}

.card-collection-counter-label {
  font-size: 0.85rem;
  color: var(--ink-500);
  margin-right: 0.25rem;
}

.card-collection-counter-qty {
  font-weight: 600;
  min-width: 1.5rem;
  text-align: center;
}

.card-show-content {
  display: flex;
  gap: 2rem;
  align-items: flex-start;
}

.card-show-image {
  width: 350px;
  flex-shrink: 0;
}

.card-full-image {
  width: 100%;
  border-radius: 12px;
  box-shadow: var(--e2);
}

.card-show-details {
  flex: 1;
  min-width: 0;
}

.card-info-section {
  background: var(--surface);
  padding: 1.5rem;
  border-radius: 8px;
  box-shadow: var(--e1);
  margin-bottom: 1.5rem;
}

.card-info-section h2 {
  margin-top: 0;
  color: var(--primary-color);
  font-size: 1.1rem;
  border-bottom: 2px solid var(--primary-color);
  padding-bottom: 0.5rem;
  margin-bottom: 1rem;
}

.card-info-list {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.5rem 1rem;
  margin: 0;
}

.card-info-list dt {
  font-weight: 600;
  color: var(--ink-500);
}

.card-info-list dd {
  margin: 0;
}

.card-move {
  padding: 0.75rem 0;
  border-bottom: 1px solid var(--line);
}

.card-move:last-child {
  border-bottom: none;
}

.card-move-header {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.card-move-cost {
  font-size: 0.85rem;
  color: var(--ink-500);
}

.card-move-damage {
  font-weight: bold;
  color: var(--primary-color);
  margin-left: auto;
}

.alt-printings {
  display: flex;
  gap: 1rem;
  flex-wrap: wrap;
}

.alt-printing-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-decoration: none;
  width: 120px;
  transition: transform 0.2s;
}

.alt-printing-item:hover {
  transform: scale(1.05);
}

.alt-printing-image {
  width: 120px;
  border-radius: 8px;
  box-shadow: var(--e1);
}

.alt-printing-set {
  margin-top: 0.4rem;
  font-size: 0.75rem;
  color: var(--ink-500);
  text-align: center;
}

/* Card search */
.deck-card-search {
  position: relative;
  margin-bottom: 1.5rem;
}

.card-search-input {
  max-width: 400px;
}

.card-search-results {
  position: absolute;
  top: 100%;
  left: 0;
  width: 400px;
  max-height: 400px;
  overflow-y: auto;
  background: var(--surface);
  border-radius: 0 0 8px 8px;
  box-shadow: var(--e2);
  z-index: 50;
}

.card-search-result-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid var(--line);
}

.card-search-result-item:hover {
  background-color: var(--water-bg);
}

.card-search-result-info {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-width: 0;
}

.card-search-result-name {
  font-weight: 500;
}

.card-search-result-set {
  font-size: 0.8rem;
  color: var(--ink-300);
}

.card-search-result-type {
  font-size: 0.75rem;
  color: white;
  background: var(--secondary-color);
  padding: 0.1rem 0.4rem;
  border-radius: 3px;
  white-space: nowrap;
}

.card-search-add-btn {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: none;
  background: var(--primary-color);
  color: white;
  font-size: 1.1rem;
  font-weight: bold;
  cursor: pointer;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background-color 0.2s;
}

.card-search-add-btn:hover {
  background: var(--flare-ink);
}

.card-search-empty {
  padding: 0.75rem;
  color: var(--ink-500);
  font-size: 0.9rem;
}

/* Cards index */
.cards-container {
  max-width: 1200px;
  margin: 2rem auto;
  padding: 0 1rem;
}

.cards-container > h1 {
  margin-top: 0;
  margin-bottom: 1.5rem;
}

.cards-search {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-bottom: 1.5rem;
  align-items: center;
}

.cards-search-input {
  flex: 1 1 280px;
  min-width: 200px;
}

/* The register of the archetype report's own overlap and provenance notes: a sentence written to
   qualify what is below it rather than to announce anything. */
.cards-search-note {
  margin: -0.25rem 0 1rem;
  font-size: 0.85rem;
  color: var(--ink-500);
}

.cards-search-select {
  flex: 0 0 auto;
  width: auto;
  cursor: pointer;
}

.cards-search-count {
  margin: 0 0 1rem;
  font-size: 0.85rem;
  color: var(--ink-500);
}

.cards-pagination {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-top: 1.5rem;
  padding-top: 1rem;
  border-top: 1px solid var(--line);
}

.cards-pagination-link {
  padding: 0.4rem 0.8rem;
  border: 1px solid var(--line);
  border-radius: 4px;
  text-decoration: none;
  color: inherit;
}

.cards-pagination-link:hover {
  background: var(--paper);
}

.cards-pagination-info {
  font-size: 0.9rem;
  color: var(--ink-500);
}

.cards-layout {
  display: flex;
  gap: 2rem;
  align-items: flex-start;
}

.sets-sidebar {
  width: 250px;
  flex-shrink: 0;
  background: var(--surface);
  border-radius: 8px;
  box-shadow: var(--e1);
  padding: 1rem;
  position: sticky;
  top: 72px;
  max-height: calc(100vh - 90px);
  overflow-y: auto;
  box-sizing: border-box;
}

.sets-block {
  margin-bottom: 1rem;
}

.sets-block:last-child {
  margin-bottom: 0;
}

.sets-block-name {
  margin: 0 0 0.4rem;
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--primary-color);
  padding-bottom: 0.3rem;
  border-bottom: 1px solid var(--border-color);
}

.sets-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.sets-list li a {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.4rem 0.5rem;
  text-decoration: none;
  color: var(--text-color);
  border-radius: 4px;
  font-size: 0.85rem;
  transition: background-color 0.2s;
}

.sets-list li a:hover {
  background-color: var(--background-color);
}

.sets-list li.active a {
  background-color: var(--primary-color);
  color: white;
}

.set-full-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.set-code {
  font-size: 0.75rem;
  opacity: 0.7;
  margin-left: 0.5rem;
  white-space: nowrap;
}

.cards-grid-area {
  flex: 1;
  min-width: 0;
}

.cards-grid-area h2 {
  margin-top: 0;
}

.cards-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 1rem;
}

.card-grid-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-decoration: none;
  color: var(--text-color);
  transition: transform 0.2s;
}

.card-grid-item:hover {
  transform: scale(1.05);
}

.card-grid-image {
  width: 100%;
  border-radius: 8px;
  box-shadow: var(--e1);
}

.card-grid-name {
  margin-top: 0.4rem;
  font-size: 0.8rem;
  font-weight: 500;
  text-align: center;
}

.card-grid-number {
  font-size: 0.7rem;
  color: var(--ink-300);
}

/* .empty-state is the generic name; .cards-empty predates it on the card index. */
.cards-empty,
.empty-state {
  color: var(--ink-500);
  font-size: 1.1rem;
}

/* Deck form */
.deck-form-container {
  max-width: 600px;
  margin: 2rem auto;
  padding: 0 1rem;
}

.deck-form {
  background: var(--surface);
  padding: 1.5rem;
  border-radius: 8px;
  box-shadow: var(--e1);
  margin-top: 1.5rem;
}

.deck-form-actions {
  display: flex;
  gap: 1rem;
}

.deck-form-actions .btn {
  flex: 1;
  text-align: center;
}

/* Admin */
.admin-navbar {
  background: var(--ink-900);
}

.admin-navbar .navbar-brand {
  color: #EAF0F8;
}

.admin-navbar .navbar-link {
  color: var(--ink-200);
}

.admin-navbar .navbar-link:hover {
  background: var(--ink-700);
}

.admin-navbar .navbar-link.active {
  color: white;
  font-weight: 600;
}

.admin-navbar .navbar-user {
  color: var(--ink-300);
}

.admin-navbar .navbar-toggle-icon,
.admin-navbar .navbar-toggle-icon::before,
.admin-navbar .navbar-toggle-icon::after {
  background: #EAF0F8;
}

.admin-container {
  max-width: 1200px;
  margin: 2rem auto;
  padding: 0 1rem;
}

.admin-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: 1.5rem;
}

.admin-header-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 0.5rem;
}

.admin-stats-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 1.5rem;
  margin-bottom: 2rem;
}

.admin-stat-card {
  background: var(--surface);
  padding: 1.5rem;
  border-radius: 8px;
  box-shadow: var(--e1);
  text-align: center;
}

.admin-stat-value {
  font-size: 2rem;
  font-weight: bold;
  color: var(--primary-color);
}

.admin-stat-label {
  font-size: 0.85rem;
  color: var(--ink-500);
}

.admin-grid-2col {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2rem;
}

/* Div-based table: flex on desktop, cards on mobile */
.data-table {
  width: 100%;
  background: var(--surface);
  border-radius: 8px;
  box-shadow: var(--e1);
}

.data-table-header {
  display: flex;
  border-bottom: 2px solid var(--border-color);
}

/* `min-width: 0` is what keeps a header cell over its own column, and it is here because the row
   cell below has it: `flex: 1` alone floors a flex item at min-content, so a header of long words
   ("ENERGY ACCELERATION") divides the row differently from the cells it labels, and the two drift
   apart once the header's min-content sum passes the container.

   Measured on /admin/card_roles, the app's widest table, at the moment two roles took it from ten
   cells to twelve. Without this line, 9 roles at a 1100px window overflow the header by 40px and
   put three checkboxes under the wrong role name — and above 768px the per-checkbox label is
   `display: none`, so the header is the only visible thing naming a box. The same table with 7
   roles fitted 1100px exactly (overflow -16px, nothing misplaced) and was already misaligned at
   1000px, so this closes a pre-existing defect as well as the regression that surfaced it. The
   390px geometry test cannot see any of it: below the breakpoint `.data-table-header` is
   `display: none`. */
.data-table-header .data-table-cell {
  flex: 1;
  min-width: 0;
  padding: 0.75rem 1rem;
  font-size: 0.85rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ink-500);
}

.data-table-row {
  display: flex;
  border-bottom: 1px solid var(--line);
}

.data-table-row:last-child {
  border-bottom: none;
}

.data-table-row:hover {
  background-color: var(--paper);
}

.data-table-row .data-table-cell {
  flex: 1;
  padding: 0.75rem 1rem;
  min-width: 0;
}

/* The card-roles curation screen. Its rows are the app's widest table — the card, its type and
   one checkbox per role — and every rule here is single-class, in the layer the file's own
   comment prescribes, so nothing below silently undoes it.

   A suggestion is pre-ticked and must not look like a decision: the difference between "the rules
   proposed this" and "a human said yes" is the only thing this screen records, so it is carried
   by a dashed outline and a muted label rather than by the checked state alone. */
/* Each row is a wrapper around its <form> and the hidden form its Clear button submits, so that a
   Turbo Stream replaces the pair as one element. That makes every .data-table-row a :last-child of
   its own wrapper, which would take the table's separators with it — hence this rule, which is the
   separator put back where the wrapper moved it. */
.card-role-row:not(:last-child) .data-table-row {
  border-bottom: 1px solid var(--line);
}

.card-role-card {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.card-role-name {
  font-weight: 600;
}

.card-role-note {
  display: block;
  font-size: 0.8rem;
  color: var(--ink-500);
}

.card-role-choice {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.85rem;
  cursor: pointer;
}

.card-role-choice--suggested {
  color: var(--ink-500);
  border-bottom: 1px dashed var(--ink-500);
}

/* A decision, yes or no. Without it a refusal renders exactly like a box nobody has looked at,
   and the screen cannot show its own progress — which is the one thing it exists to record. */
.card-role-choice--decided {
  border-bottom: 1px solid var(--ink-900, currentColor);
}

/* Submitted only through the row's own Clear button, which points at it with the HTML5 `form`
   attribute: forms cannot nest, and this one is a DELETE. */
.card-role-clear-form {
  display: none;
}

/* The role name repeats the column heading above the breakpoint, where the header row already
   names it; below it the header is hidden and the cell's own ::before label takes over, so this
   would be the third copy of the word on one line. The checkbox keeps an aria-label either way —
   `display: none` takes this span out of the accessibility tree, not merely out of the layout. */
.card-role-choice-name {
  display: none;
}

.card-role-filter {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  font-size: 0.85rem;
  color: var(--ink-500);
}

.card-role-filter--check {
  flex-direction: row;
  align-items: center;
  gap: 0.4rem;
}

.data-table a:not(.btn) {
  color: var(--secondary-color);
  text-decoration: none;
}

.data-table a:not(.btn):hover {
  text-decoration: underline;
}

/* The Error cell of the admin imports table. A bulk standings run's message is a list of per-row
   failures, so it is disclosed rather than parked in a title= tooltip — below 768px the row is a
   data-label card and there is no hover to reveal one. min-width: 0 is what lets it shrink there:
   the cell is a flex container at that width, and a flex item defaults to min-content, which a
   long unbroken error string would push past the card's edge. */
.import-error {
  min-width: 0;
}

.import-error > summary {
  cursor: pointer;
  color: var(--ink-500);
}

.import-error-full {
  margin: 0.4rem 0 0;
  font-family: var(--font-mono);
  font-size: 0.78rem;
  line-height: 1.55;
  color: var(--ink-700);
  /* The message is written with newlines between failures, and a URL or a card name in one of
     them has no break opportunity of its own. */
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* A bulk card add discloses its receipt inside the Label cell, the same shape .import-error uses
   one cell further along — and deliberately not the same class, since the pre-existing test
   selects `details.import-error > summary` with `.first` and a second carrier of that class,
   rendered earlier in the row, would silently answer for it.

   It also deliberately does *not* copy .import-error's `min-width: 0` and `overflow-wrap:
   anywhere`, which look like they belong here and are measurably dead. Measured at 344px with a
   60-character card name: with both rules and with neither, the line's scrollWidth, its
   clientWidth and the document's are identical (206 / 206 / 344) — the cell already constrains the
   box, and the longest single word in any of the 4916 card names is 14 characters, so there is no
   unbreakable run for `overflow-wrap` to break. .import-error needs them because its content is a
   bulk-import failure list that really can carry a URL. */
.import-receipt > summary {
  cursor: pointer;
}

.import-receipt-list {
  margin: 0.4rem 0 0;
  padding-left: 1.1rem;
  font-size: 0.78rem;
  line-height: 1.55;
  color: var(--ink-700);
}

/* Detail table (key/value pairs in admin show views) */
.detail-table {
  width: 100%;
  background: var(--surface);
  border-radius: 8px;
  box-shadow: var(--e1);
  border-collapse: collapse;
}

.detail-table td {
  padding: 0.75rem 1rem;
  border-bottom: 1px solid var(--line);
}

.detail-table tr:last-child td {
  border-bottom: none;
}

.admin-search {
  display: flex;
  gap: 0.5rem;
  margin-bottom: 1.5rem;
}

.admin-search-input {
  max-width: 300px;
}

.btn-sm {
  padding: 0.4rem 0.75rem;
  font-size: 0.85rem;
}

.text-muted {
  color: var(--ink-300);
  font-size: 0.85rem;
}

/* Settings — MCP token panel */
.settings-section {
  background: var(--surface);
  border-radius: 8px;
  box-shadow: var(--e1);
  padding: 1.5rem;
}

.settings-section-lead {
  color: var(--ink-500);
  max-width: 64ch;
  margin: 0.25rem 0 1.5rem;
}

.settings-empty {
  color: var(--ink-500);
  font-style: italic;
  margin: 0 0 1.5rem;
}

.settings-meta {
  margin: 0 0 1.5rem;
}

.settings-meta-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 1.5rem;
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--line);
}

.settings-meta-row:last-child {
  border-bottom: none;
}

.settings-meta-row dt {
  flex: 0 0 140px;
  font-weight: 600;
  font-size: 0.85rem;
  color: var(--ink-500);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.settings-meta-row dd {
  flex: 1 1 200px;
  min-width: 0;
  margin: 0;
}

.settings-form {
  max-width: 420px;
  margin-top: 1.5rem;
}

.settings-form + .settings-form {
  margin-top: 0.75rem;
}

.settings-section + .settings-section {
  margin-top: 1.5rem;
}

/* Settings — connected applications list. One row per OAuth application. */
.settings-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.settings-list-item {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.25rem 1rem;
  padding: 0.75rem 0;
  border-bottom: 1px solid var(--line);
}

.settings-list-item:last-child {
  border-bottom: none;
}

.settings-list-meta {
  flex: 1 1 auto;
  min-width: 0;
  color: var(--ink-500);
  font-size: 0.85rem;
}

.settings-list-revoke {
  margin-left: auto;
}

.settings-steps {
  max-width: 64ch;
  margin: 0.5rem 0 1.5rem;
  padding-left: 1.25rem;
  line-height: 1.6;
}

.settings-steps code,
.settings-section-lead kbd {
  font-family: var(--font-mono);
  font-size: 0.85em;
  overflow-wrap: anywhere;
}

/* The announcement links straight here, and the navbar is sticky: without the margin the anchor
   lands with the section's heading under the bar. */
#search-engine {
  scroll-margin-top: 6rem;
}

#search-engine h3 {
  margin: 1.25rem 0 0.5rem;
  font-size: 1rem;
}

/* The reveal block is the only moment the raw token is ever visible again —
   weighted as a callout (elevated, amber-ringed) rather than a neutral box. */
.settings-reveal {
  background: var(--surface);
  border: 2px solid var(--bolt);
  border-radius: 8px;
  box-shadow: var(--e2);
  padding: 1.25rem;
  margin: 0 0 1.5rem;
}

.settings-reveal h3 {
  margin: 1.25rem 0 0.5rem;
  font-size: 1rem;
}

.settings-reveal-warning {
  background: var(--bolt-bg);
  border-radius: 4px;
  padding: 0.6rem 0.85rem;
  margin: 0 0 1rem;
}

.settings-reveal-value {
  display: block;
  font-family: var(--font-mono);
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 0.75rem;
  margin: 0 0 0.75rem;
  overflow-wrap: anywhere;
  word-break: break-all;
}

.settings-reveal-snippet {
  font-family: var(--font-mono);
  font-size: 0.85rem;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 0.75rem;
  margin: 0.5rem 0 0;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  word-break: break-all;
}

/* Dropdown */
.dropdown {
  position: relative;
  display: inline-block;
}

.dropdown-menu {
  display: none;
  position: absolute;
  top: 100%;
  left: 0;
  margin-top: 0.25rem;
  min-width: 180px;
  background: var(--surface);
  border-radius: 8px;
  box-shadow: var(--e2);
  z-index: 50;
  padding: 0.25rem 0;
}

.dropdown-menu--open {
  display: block;
}

/* A menu listing the reader's own decks has no natural length. */
.dropdown-menu--scroll {
  max-height: 60vh;
  overflow-y: auto;
}

.dropdown-item {
  display: block;
  width: 100%;
  box-sizing: border-box;
  padding: 0.5rem 1rem;
  border: none;
  background: none;
  text-align: left;
  text-decoration: none;
  font-size: 0.9rem;
  cursor: pointer;
  color: var(--text-color);
}

.dropdown-item:hover {
  background: var(--background-color);
}

.dropdown-item-form {
  margin: 0;
  padding: 0;
}

.dropdown-item-danger {
  color: var(--flare-ink);
}

.deck-header-form {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  width: 100%;
  max-width: 500px;
}

.deck-header-name-input {
  font-size: 2rem;
  font-weight: 700;
  padding: 0.25rem 0.5rem;
}

.deck-header-description-input {
  font-size: 0.95rem;
  padding: 0.5rem;
  min-height: auto;
}

.deck-header-form-actions {
  display: flex;
  gap: 0.5rem;
  margin-top: 0.25rem;
}

/* Card preview modal (mobile) */
.card-preview-modal {
  border: none;
  border-radius: 12px;
  padding: 1.5rem;
  max-width: 300px;
  width: 85%;
  box-shadow: var(--e3);
}

.card-preview-modal::backdrop {
  background: rgba(0, 0, 0, 0.6);
}

.card-preview-modal-content {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
}

.card-preview-modal-image {
  width: 100%;
  border-radius: 12px;
}

/* Navbar toggle (hidden on desktop) */
.navbar-toggle {
  display: none;
  background: none;
  border: none;
  cursor: pointer;
  padding: 0.5rem;
  margin-left: auto;
}

.navbar-toggle-icon {
  display: block;
  width: 22px;
  height: 2px;
  background: var(--text-color);
  position: relative;
}

.navbar-toggle-icon::before,
.navbar-toggle-icon::after {
  content: "";
  display: block;
  width: 22px;
  height: 2px;
  background: var(--text-color);
  position: absolute;
  left: 0;
}

.navbar-toggle-icon::before { top: -6px; }
.navbar-toggle-icon::after { top: 6px; }

.navbar-menu {
  display: contents;
}

/* Responsive adjustments */
@media (max-width: 768px) {
  /* Navbar: hamburger menu */
  .navbar-inner {
    flex-wrap: wrap;
    height: auto;
    min-height: 56px;
    gap: 0;
  }

  .navbar-toggle {
    display: block;
  }

  .navbar-search-trigger {
    order: 0;
  }

  /* Two flex items with `margin-left: auto` split the free space between them, which parked the
     trigger mid-navbar. Only the first one needs it; the sibling selector leaves the admin
     navbar — which has no trigger — pushing its own hamburger right. */
  .navbar-search-trigger + .navbar-toggle {
    margin-left: 0;
  }

  /* A phone has no ⌘K to press. */
  .navbar-search-trigger-hint {
    display: none;
  }

  .navbar-menu {
    display: none;
    width: 100%;
    padding-bottom: 0.75rem;
  }

  .navbar-menu.navbar-menu--open {
    display: block;
  }

  .navbar-links {
    flex-direction: column;
    gap: 0;
    width: 100%;
  }

  .navbar-link {
    display: block;
    padding: 0.6rem 0.75rem;
  }

  /* The whole responsive half of Ui::NavGroup, and it is entirely here: the trigger goes away, the
     heading it shares a label with takes its place, and the panel stops floating and becomes a
     plain section of the drawer that is always shown. No second tap to reach a link on a phone,
     and — because the button is gone rather than restyled — no aria-expanded="false" describing a
     panel the drawer is displaying. */
  .navbar-group {
    flex-direction: column;
    align-items: stretch;
    width: 100%;
  }

  .navbar-group-trigger,
  .navbar-group-initial {
    display: none;
  }

  .navbar-group-heading {
    display: block;
    padding: 0.7rem 0.75rem 0.25rem;
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
  }

  .navbar-group-panel {
    display: block;
    position: static;
    min-width: 0;
    margin-top: 0;
    padding: 0;
    border-radius: 0;
    background: none;
    box-shadow: none;
  }

  .navbar-group-panel .navbar-link {
    padding: 0.6rem 0.75rem;
  }

  .navbar-group-panel .navbar-user {
    padding: 0.4rem 0.75rem;
  }

  .navbar-right,
  .navbar-account {
    flex-direction: column;
    align-items: flex-start;
    gap: 0;
    margin-left: 0;
    width: 100%;
    border-top: 1px solid var(--border-color);
    padding-top: 0.5rem;
    margin-top: 0.25rem;
  }

  .navbar-user {
    padding: 0.4rem 0.75rem;
  }

  .navbar-right .navbar-link {
    padding: 0.6rem 0.75rem;
  }

  /* Dashboard */
  .dashboard-grid {
    grid-template-columns: 1fr;
  }

  .card-actions, .deck-actions {
    flex-direction: column;
  }

  .card-actions .btn, .deck-actions .btn {
    width: 100%;
  }

  /* Cards index: stack sidebar above grid */
  .cards-layout {
    flex-direction: column;
  }

  .sets-sidebar {
    width: 100%;
    position: static;
    max-height: 250px;
  }

  .cards-grid {
    grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
  }

  /* Card show: stack image above details */
  .card-show-content {
    flex-direction: column;
  }

  .card-show-image {
    width: 100%;
    max-width: 300px;
    margin: 0 auto;
  }

  .card-show-header {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.5rem;
  }

  /* Decks index */
  .decks-header {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.75rem;
  }

  .decks-grid {
    grid-template-columns: 1fr;
  }

  /* Deck show */
  .deck-show-header {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.5rem;
  }

  .deck-actions-bar {
    flex-direction: column;
  }

  .deck-actions-bar .btn {
    width: 100%;
    text-align: center;
  }

  .deck-show-stats {
    gap: 0.75rem;
    padding: 0.75rem;
    flex-wrap: wrap;
    justify-content: center;
  }

  .stat-value {
    font-size: 1.2rem;
  }

  .deck-show-content {
    flex-direction: column;
  }

  .deck-show-preview,
  .deck-compare-preview {
    display: none;
  }

  .deck-compare-content {
    flex-direction: column;
  }

  /* Card search dropdown */
  .card-search-input {
    max-width: 100%;
  }

  .card-search-results {
    width: 100%;
  }

  .spotlight-option {
    flex-direction: column;
    gap: 0.125rem;
  }

  .spotlight-option-meta {
    white-space: normal;
  }

  /* Deck form */
  .deck-form-actions {
    flex-direction: column;
  }

  .deck-form-actions .btn {
    width: 100%;
  }

  /* Result modal */
  .result-modal {
    max-width: 95%;
    padding: 1.25rem;
  }

  .result-type-buttons {
    flex-wrap: wrap;
  }

  .result-type-btn {
    flex: 1 1 40%;
  }

  /* Auth */
  .auth-container {
    margin: 2rem 1rem;
  }

  .welcome-container {
    margin: 2rem 1rem;
  }

  /* Admin grid */
  .admin-grid-2col {
    grid-template-columns: 1fr;
  }

  .admin-grid-2col img {
    max-width: 100%;
    height: auto;
  }

  /* Admin navbar mobile adjustments */
  .admin-navbar .navbar-right {
    border-top-color: var(--ink-700);
  }

  /* Admin header (used in stats/results) */
  .admin-header {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.75rem;
  }

  /* Page-header buttons take .btn-sm's size on a phone: an event page can carry six of them, and at
     full size they wrapped into three rows of 48px buttons above the content they lead to. */
  .admin-header .btn, .decks-header .btn, .card-show-header .btn {
    padding: 0.4rem 0.75rem;
    font-size: 0.85rem;
  }

  .decks-header-actions, .admin-header-actions, .card-show-header-actions {
    justify-content: flex-start;
  }

  /* Table → card layout */
  .data-table-header {
    display: none;
  }

  .data-table-row {
    flex-direction: column;
    padding: 1rem;
    margin-bottom: 0.75rem;
    border-bottom: none;
    border-radius: 8px;
    box-shadow: var(--e1);
    background: var(--surface);
    gap: 0.4rem;
  }

  .data-table-body {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
  }

  .data-table-row .data-table-cell {
    padding: 0.15rem 0;
    display: flex;
    justify-content: space-between;
    align-items: center;
  }

  /* The catalog's name cell carries the link and the participations line as one value, so it
     aligns right like every other value in the stacked card. */
  .tournament-name {
    text-align: right;
  }

  .data-table-row .data-table-cell::before {
    content: attr(data-label);
    font-weight: 600;
    font-size: 0.8rem;
    color: var(--ink-500);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    margin-right: 1rem;
    flex-shrink: 0;
  }

  /* Settings: stack the label/value pair instead of relying on wrap */
  .settings-meta-row {
    flex-direction: column;
    gap: 0.15rem;
  }

  .settings-meta-row dt {
    flex-basis: auto;
  }

  .settings-meta-row dd {
    flex-basis: auto;
  }
}

/* Collection */
.collection-container {
  max-width: 1200px;
  margin: 2rem auto;
  padding: 0 1rem;
}

.collection-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 1.5rem;
  gap: 1rem;
  flex-wrap: wrap;
}

.collection-header h1 {
  margin: 0;
}

.collection-stats {
  display: flex;
  gap: 1.5rem;
}

.collection-filters {
  display: flex;
  gap: 1rem;
  align-items: flex-end;
  flex-wrap: wrap;
  background: var(--surface);
  padding: 1rem;
  border-radius: 8px;
  box-shadow: var(--e1);
  margin-bottom: 1.5rem;
}

.collection-filters .form-group {
  margin: 0;
  display: flex;
  flex-direction: column;
}

.collection-filter-search {
  flex: 1;
  min-width: 200px;
}

.collection-filter-actions {
  display: flex;
  gap: 0.5rem;
}

.collection-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 1rem;
}

.collection-tile {
  background: var(--surface);
  border-radius: 8px;
  box-shadow: var(--e1);
  padding: 0.75rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.collection-tile-link {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-decoration: none;
  color: var(--text-color);
  transition: transform 0.2s;
}

.collection-tile-link:hover {
  transform: scale(1.03);
}

.collection-tile-image {
  width: 100%;
  border-radius: 6px;
  box-shadow: var(--e1);
}

.collection-tile-name {
  margin-top: 0.5rem;
  font-size: 0.85rem;
  font-weight: 500;
  text-align: center;
}

.collection-tile-meta {
  font-size: 0.7rem;
  color: var(--ink-300);
}

.collection-tile-controls {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.25rem;
  margin-top: auto;
}

.collection-tile-qty {
  font-weight: 600;
  min-width: 1.5rem;
  text-align: center;
}

.collection-empty {
  color: var(--ink-500);
  font-size: 1.1rem;
  text-align: center;
  padding: 3rem 1rem;
}


/* Limitless standings import — the plan preview */
.standings-import-plan {
  margin-top: 2rem;
}

.standings-import-totals {
  color: var(--ink-500);
  margin-bottom: 1.5rem;
}

.standings-import-event {
  background: var(--surface);
  border-radius: 8px;
  box-shadow: var(--e1);
  padding: 1.25rem;
  margin-bottom: 1.25rem;
}

/* A left rail rather than a wash: the rows underneath keep their own status badges, and two
   competing backgrounds made a blocked event's table harder to read than an importable one's. */
.standings-import-event--blocked {
  border-left: 4px solid var(--loss-bg);
}

.standings-import-event-meta,
.standings-import-event-catalog {
  color: var(--ink-500);
  font-size: 0.9rem;
  margin: 0.25rem 0 0.75rem;
}

/* The flash treatment is centred and capped at 480px, which is right for a banner at the top of
   a page and wrong for a warning that belongs to the event block it sits in. */
.standings-import-refusal,
.standings-import-duplicate {
  max-width: none;
  margin: 0 0 1rem;
  text-align: left;
}

.standings-import-confirm {
  margin-top: 1.5rem;
}

/* One line per Limitless deck, above the plan those lines decide. */
.standings-import-mappings {
  margin-top: 2rem;
}

/* The deck's own reference — 284, 284/3 — printed beside its name because two decks of one base
   id read alike and the reference is what the answer is stored under. */
.standings-import-reference {
  color: var(--ink-500);
  font-size: 0.85rem;
}

/* A deck whose representative list could not be read says so with the page and the rank in the
   message, and a URL is one unbreakable token. `.badge` is inline-block, so its min-content width
   is that token's width: measured at 390px, nine of the fifteen lines the real event produces
   pushed the document to 487px — the only elements on the page to overflow it at all, and clean at
   768 and 1400. Scoped here rather than put on `.badge`, because everywhere else a badge is two
   words and breaking inside one would be the wrong answer. */
.standings-import-mappings .badge {
  overflow-wrap: anywhere;
  text-align: left;
}

.standings-import-confirm-actions {
  margin-top: 1.5rem;
}

/* The lines that need a human, carried by the row rather than by the badge alone: measured on
   event 578, 3 of 28 lines needed one and they read exactly like the 25 that did not. A rail *and*
   a wash here, unlike a blocked event's rail alone, because these rows carry one badge and no
   status column for a wash to compete with. `border-left` rather than an inset shadow, so the
   stacked-card layout below the breakpoint keeps its own `--e1` shadow. Two classes, so that
   `.data-table-row:hover` and the breakpoint's `background: var(--surface)` cannot outrank it. */
.data-table-row.standings-import-mapping--decide {
  border-left: 4px solid var(--flare);
  background: var(--loss-bg);
}

.data-table-row.standings-import-mapping--check {
  border-left: 4px solid var(--bolt);
  background: var(--bolt-bg);
}

/* Answered in the browser since the preview rendered: nothing left to do on it. */
.data-table-row.standings-import-mapping--answered {
  border-left-color: var(--win);
  background: var(--surface);
}

.standings-import-mapping-summary {
  margin: 0 0 1rem;
}

/* One value in the cell, stacked, rather than three flex items spread across it — below the
   breakpoint a cell is `display: flex; justify-content: space-between`. */
.standings-import-mapping-choice {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0.5rem;
  min-width: 0;
  width: 100%;
}

/* The select and its "+ New archetype" on one line where the column allows, wrapping below it
   where it does not. */
.standings-import-mapping-pick {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.25rem 0.75rem;
}

.standings-import-mapping-pick select {
  flex: 1 1 12rem;
  min-width: 0;
}

.standings-import-mapping-new {
  background: none;
  border: none;
  padding: 0;
  color: var(--water-strong);
  font: inherit;
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
}

.standings-import-mapping-new:hover {
  text-decoration: underline;
}

/* The badge's own soft fill is the row's wash, so on these two rows it would vanish into it:
   solid instead. */
.standings-import-mapping--decide .badge-danger {
  background: var(--flare);
  color: #fff;
}

.standings-import-mapping--check .badge-warning {
  background: var(--bolt);
  color: var(--ink-900);
}

.standings-import-mapping-create {
  position: relative;
  padding: 0.75rem;
  border: 1px dashed var(--line);
  border-radius: 6px;
  background: var(--surface);
}


/* ============================================================
   Design System layer — overrides applied last (source order
   wins on equal specificity). Typography, dark navbar rails,
   semantic accents, focus & reduced-motion floor.
   ============================================================ */

/* — Typography roles — */
h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 800;
  letter-spacing: -0.015em;
  line-height: 1.15;
  text-wrap: balance;
  color: var(--ink-900);
}
.navbar-brand { font-family: var(--font-display); font-weight: 800; letter-spacing: -0.01em; }

.stat-value, .admin-stat-value, .card-count, .deck-count {
  font-family: var(--font-display);
  color: var(--ink-900);
}
.stat-value, .admin-stat-value, .card-count, .deck-count, .deck-card-count,
.card-collection-counter-qty, .collection-tile-qty, .deck-card-qty {
  font-variant-numeric: tabular-nums;
}
.set-code, .card-grid-number, .deck-card-set, .card-search-result-set,
.collection-tile-meta, .alt-printing-set, .cards-search-count, .cards-pagination-info {
  font-family: var(--font-mono);
}
.deck-card-qty { color: var(--ink-700); }

/* — Section heading accents: ink instead of cartoon red — */
.dashboard-card h2, .auth-container h1, .deck-item h2, .card-info-section h2,
.deck-section h2, .deck-section-group h2, .deck-subsection h3 { color: var(--ink-900); }
.card-info-section h2 { border-bottom-color: var(--line); }
.deck-section h2, .deck-section-group h2, .deck-subsection h3 { border-bottom-color: var(--water); }
.sets-block-name { color: var(--ink-500); border-bottom-color: var(--line); }

/* — Buttons — */
.btn { font-family: var(--font-body); font-weight: 600; border-radius: 5px; }
.btn-primary { box-shadow: var(--e1); }

/* — Dark navbar rails — */
.navbar { background: var(--ink-900); box-shadow: var(--e2); }
.navbar-brand { color: #fff; }
.navbar-link { color: var(--ink-200); }
.navbar-link:hover { background: rgba(255, 255, 255, 0.08); }
.navbar-link.active { color: #fff; font-weight: 600; }
.navbar-group-trigger { color: var(--ink-200); }
.navbar-group-trigger:hover { background: rgba(255, 255, 255, 0.08); }
.navbar-group-trigger.active { color: #fff; font-weight: 600; }
.navbar-group-heading { color: var(--ink-300); }
/* The brand is already the loudest thing in the bar, so it cannot get louder to say it is the
   active entry — Dashboard's marker is a rule in brand red under the word, which is the same
   colour .navbar-link.active carries away from the dark rail. */
.navbar-brand.active .navbar-brand-word { box-shadow: inset 0 -2px 0 var(--flare); }
.navbar-user { color: var(--ink-300); font-family: var(--font-mono); font-size: 0.78rem; }
.navbar-toggle-icon,
.navbar-toggle-icon::before,
.navbar-toggle-icon::after { background: #fff; }

/* Scoped to .navbar, unlike its neighbours here, because Ui::SearchTrigger is also rendered
   outside one — the styleguide documents the shipped button on a paper background, where these
   colours would make it invisible. Specificity, not source order, is what makes this win. */
.navbar .navbar-search-trigger { color: var(--ink-200); border-radius: 4px; }
.navbar .navbar-search-trigger:hover { background: rgba(255, 255, 255, 0.08); color: #fff; }
.navbar .navbar-search-trigger-hint {
  color: var(--ink-300);
  border-color: rgba(255, 255, 255, 0.25);
}

/* — Dark table headers instead of red — */
.deck-compare-table thead th { background: var(--ink-900); }
.deck-compare-total td { border-top-color: var(--flare); }

/* — Focus visibility floor — */
a:focus-visible, button:focus-visible, input:focus-visible,
select:focus-visible, textarea:focus-visible, [tabindex]:focus-visible {
  outline: 2px solid var(--water);
  outline-offset: 2px;
}

@media (max-width: 768px) {
  .navbar-right,
  .navbar-account { border-top-color: rgba(255, 255, 255, 0.12); }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

/* ============================================================
   Signature layer — energy-typed badges + holo treatment.
   Archetype badges speak the game's own type colours; the holo
   foil is reserved for proven ("hot") decks.
   ============================================================ */

/* — Energy-typed archetype badges — */
.badge-energy {
  display: inline-flex;
  align-items: center;
  gap: 0.35em;
}
.badge-energy .badge-pip {
  width: 7px;
  height: 7px;
  border-radius: 999px;
  flex: none;
}
.badge-grass     { background: var(--grass-bg);     color: #1F6E2B; }
.badge-grass     .badge-pip { background: var(--grass); }
.badge-fire      { background: var(--fire-bg);      color: #B32D10; }
.badge-fire      .badge-pip { background: var(--fire); }
.badge-water     { background: var(--water-bg);     color: var(--water-strong); }
.badge-water     .badge-pip { background: var(--water); }
.badge-lightning { background: var(--bolt-bg);      color: #8A6800; }
.badge-lightning .badge-pip { background: var(--bolt); }
.badge-fighting  { background: var(--fighting-bg);  color: #8C451C; }
.badge-fighting  .badge-pip { background: var(--fighting); }
.badge-psychic   { background: var(--psychic-bg);   color: #6F3A92; }
.badge-psychic   .badge-pip { background: var(--psychic); }
.badge-darkness  { background: var(--darkness-bg);  color: #2C333F; }
.badge-darkness  .badge-pip { background: var(--darkness); }
.badge-metal     { background: var(--metal-bg);     color: #525C6A; }
.badge-metal     .badge-pip { background: var(--metal); }
.badge-dragon    { background: var(--dragon-bg);    color: #8A6A18; }
.badge-dragon    .badge-pip { background: var(--dragon); }
.badge-fairy     { background: var(--fairy-bg);     color: #B04680; }
.badge-fairy     .badge-pip { background: var(--fairy); }
.badge-colorless { background: var(--metal-bg);     color: #525C6A; }
.badge-colorless .badge-pip { background: var(--metal); }

/* — Deck card: type identity stripe (rounds its own top corners so the
   card needs no overflow:hidden, which would clip the actions dropdown) — */
.deck-stripe {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 4px;
  border-radius: 8px 8px 0 0;
  pointer-events: none;
}

/* — Holo treatment, reserved for "hot" decks — */
.deck-item.is-foil {
  box-shadow: var(--e2), inset 0 0 0 1px rgba(184, 144, 42, 0.35);
}
.deck-foil-sheen {
  position: absolute;
  inset: 0;
  pointer-events: none;
  border-radius: 8px;
  mix-blend-mode: soft-light;
  opacity: 0;
  transition: opacity 0.3s;
  background: linear-gradient(115deg, transparent 30%, #36D1C4 42%, #8A7BFF 50%, #FFCF5C 58%, transparent 70%);
  background-size: 300% 300%;
}
.deck-item.is-foil:hover .deck-foil-sheen {
  opacity: 0.85;
  animation: deck-foil 2.4s linear infinite;
}
@keyframes deck-foil {
  0%   { background-position: 0% 50%; }
  100% { background-position: 200% 50%; }
}
.deck-hot-flag {
  position: absolute;
  bottom: 1rem;
  right: 1rem;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  gap: 0.3em;
  font-family: var(--font-mono);
  font-size: 0.66rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  color: #8A6A18;
  background: var(--dragon-bg);
  padding: 0.2em 0.55em;
  border-radius: 999px;
  box-shadow: inset 0 0 0 1px rgba(184, 144, 42, 0.4);
}

/* ============================================================
   Styleguide — living documentation page (/styleguide, non-prod).
   ============================================================ */
.sg { max-width: 1100px; margin: 2rem auto 4rem; padding: 0 1rem; }
.sg-hero h1 { font-size: 2.5rem; margin: 0 0 0.5rem; }
.sg-lead { color: var(--ink-500); max-width: 64ch; font-size: 1.05rem; margin: 0; }
.sg-tag {
  display: inline-block; margin-bottom: 1rem;
  font-family: var(--font-mono); font-size: 0.7rem; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--ink-300);
  border: 1px solid var(--line); border-radius: 999px; padding: 0.2em 0.7em;
}
.sg-section { padding: 1.75rem 0; border-top: 1px solid var(--line); }
.sg-section > h2 { font-size: 1.4rem; margin: 0 0 0.25rem; }
.sg-section > p { color: var(--ink-500); margin: 0; max-width: 64ch; }
.sg-eyebrow {
  font-family: var(--font-mono); font-size: 0.7rem; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--ink-300); margin: 0 0 0.5rem;
}
.sg-grid {
  display: grid; gap: 1rem; margin-top: 1rem;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}
.sg-swatch { border: 1px solid var(--line); border-radius: 9px; overflow: hidden; background: var(--surface); }
.sg-swatch .chip { height: 56px; }
.sg-swatch .meta { padding: 0.55rem 0.7rem; }
.sg-swatch .name { font-size: 0.85rem; font-weight: 600; }
.sg-swatch .val { font-family: var(--font-mono); font-size: 0.72rem; color: var(--ink-500); }
.sg-row { display: flex; flex-wrap: wrap; gap: 0.75rem; align-items: center; margin-top: 1rem; }
.sg-stack { display: flex; flex-direction: column; gap: 0.75rem; margin-top: 1rem; max-width: 420px; }
.sg-type-row { display: flex; align-items: baseline; gap: 1.5rem; padding: 0.6rem 0; border-bottom: 1px solid var(--line); }
.sg-type-row .tag { font-family: var(--font-mono); font-size: 0.72rem; color: var(--ink-300); width: 160px; flex: none; }
.sg-ts-3xl { font-family: var(--font-display); font-weight: 800; font-size: 2.5rem; letter-spacing: -0.03em; }
.sg-ts-2xl { font-family: var(--font-display); font-weight: 700; font-size: 1.75rem; letter-spacing: -0.02em; }
.sg-ts-xl  { font-family: var(--font-display); font-weight: 700; font-size: 1.375rem; }
.sg-ts-base { font-size: 1rem; }
.sg-ts-sm { font-size: 0.875rem; color: var(--ink-500); }
.sg-ts-mono { font-family: var(--font-mono); font-size: 0.85rem; }
.sg-deckdemo {
  display: grid; gap: 1.5rem; margin-top: 1rem; max-width: 600px;
  grid-template-columns: repeat(2, minmax(220px, 1fr));
}
.sg-tokens {
  display: grid; grid-template-columns: auto auto 1fr; gap: 0.75rem 1.25rem;
  align-items: center; margin-top: 1rem; font-size: 0.88rem;
}
.sg-tokens code {
  font-family: var(--font-mono); font-size: 0.78rem; background: var(--paper);
  border: 1px solid var(--line); padding: 0.1em 0.4em; border-radius: 4px; justify-self: start;
}
.sg-tokens .box { width: 36px; height: 24px; border-radius: 6px; border: 1px solid var(--line); }

@media (max-width: 768px) {
  .sg-deckdemo { grid-template-columns: 1fr; }
  .sg-type-row { flex-direction: column; gap: 0.1rem; }
  .sg-type-row .tag { width: auto; }
}

.deck-card-alloc { display: inline-flex; align-items: center; gap: 0.375rem; margin-left: 0.5rem; font-family: var(--font-mono); font-size: 0.75rem; color: var(--ink-500); }
.deck-card-alloc-label { min-width: 8ch; text-align: center; }
.deck-card-warning { margin-left: 0.25rem; }

/* — Printing picker: the set/number line of a deck row, where another printing exists — */
.deck-card-printing { position: relative; display: inline-flex; }
.deck-card-set-swap { padding: 0; border: 0; background: none; font: inherit; color: var(--ink-300); cursor: pointer; }
.deck-card-set-swap:hover, .deck-card-set-swap[aria-expanded="true"] { color: var(--ink-700); text-decoration: underline; }

.printing-picker-menu {
  position: absolute; top: 100%; right: 0; z-index: 50;
  min-width: 15rem; margin: 0.25rem 0 0; padding: 0.25rem; list-style: none;
  max-height: 18rem; overflow-y: auto;
  background: var(--surface); border-radius: 8px; box-shadow: var(--e2);
  cursor: default;
}
.printing-option {
  display: flex; flex-direction: column; gap: 0.1rem;
  width: 100%; box-sizing: border-box; padding: 0.4rem 0.6rem;
  border: 0; border-radius: 6px; background: none; text-align: left; font: inherit; cursor: pointer;
}
.printing-option:hover:not(:disabled) { background: var(--water-bg); }
.printing-option-current { cursor: default; opacity: 0.65; }
.printing-option-set { font-family: var(--font-mono); font-size: 0.8rem; color: var(--ink-900); }
.printing-option-counts { font-size: 0.7rem; color: var(--ink-500); }
.printing-option-warning { font-size: 0.7rem; color: #8A6800; }

.collection-tile-alloc { display: block; font-family: var(--font-mono); font-size: 0.7rem; color: var(--ink-500); margin: 0.25rem 0; }

.over-allocation-row { display: flex; flex-wrap: wrap; gap: 0.75rem; align-items: baseline; padding: 0.5rem 0; border-bottom: 1px solid var(--line); }
.over-allocation-decks { display: inline-flex; gap: 0.5rem; }
.over-allocation-banner { display: block; padding: 0.5rem 0.75rem; border-radius: 6px; margin-bottom: 1rem; font-size: 0.875rem; }

.tournaments-search {
  margin-bottom: 1rem;
}

.tournaments-search .form-input {
  max-width: 22rem;
}

/* The reader's participations under an event's name in the catalog: its own line, so the badge
   never glues onto the link and wraps as a unit at phone width. */
.tournament-participations {
  display: block;
  margin-top: 0.25rem;
  font-size: 0.875rem;
}

.tournament-participations-players {
  color: var(--ink-500);
}

/* The standings sheet sits under the event's own facts. One rule and nothing more: the table,
   the badges and the buttons inside it are the app's existing components. */
.tournament-standings { margin-top: 1.5rem; }
.tournament-standings h3 { margin: 1rem 0 0.5rem; }

/* The W-L-T triplet, three narrow fields on one line. Single-class specificity, like every
   other rule in this layer. */
.form-row { display: flex; gap: 0.75rem; }
.form-row .form-group { flex: 1; }

.sg-spotlight-demo {
  position: relative;
  min-height: 4rem;
}

/* In the app the panel floats out of the field; here it is forced open and there is nothing to
   float over, so it lays out in flow instead. .sg-printing-demo reserves the room with a
   min-height, which cannot work for this one — the panel is a child of the demo box and anchored
   to its *bottom* edge, so every rem added pushed it further down the page and further over the
   section below it. Same classes either way, so the reference still can't drift on looks. */
.sg-spotlight-demo .spotlight-panel {
  position: static;
}

/* The trigger takes the navbar's free space with margin-left: auto; in a demo row that would
   park it against the right edge, far from the paragraph describing it. */
.sg-search-trigger-demo .navbar-search-trigger {
  margin-left: 0;
}

/* Ui::Logo and Ui::NavGroup only ever exist on the dark bar, and the group's own colours come from
   the unscoped dark-navbar layer, so a paper ground would document them wrong. min-height leaves
   room for the panel, which is absolutely positioned and would otherwise open over the next
   section. */
.sg-nav-demo {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  min-height: 5rem;
  padding: 0 1rem;
  border-radius: 8px;
  background: var(--ink-900);
}

/* The picker's menu is absolutely positioned, so the demo has to reserve the room it floats in —
   enough for the narrowest screen, where each option's counts and warning wrap onto more lines. */
.sg-printing-demo {
  position: relative;
  min-height: 13rem;
  margin-top: 1rem;
}

/* Dashboard spotlight search */
.spotlight {
  position: relative;
  margin-top: 1.5rem;
}

.spotlight-input {
  width: 100%;
}

.spotlight-panel {
  display: none;
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  max-height: 60vh;
  overflow-y: auto;
  background: var(--surface);
  border-radius: 0 0 8px 8px;
  box-shadow: var(--e2);
  z-index: 50;
}

.spotlight-panel-open {
  display: block;
}

.spotlight-group + .spotlight-group {
  border-top: 1px solid var(--line);
}

.spotlight-group-header {
  padding: 0.5rem 0.75rem 0.25rem;
  font-family: var(--font-display);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  color: var(--ink-500);
}

.spotlight-option {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.5rem 0.75rem;
  color: var(--ink-900);
  text-decoration: none;
}

.spotlight-option:hover,
.spotlight-option.is-active {
  background: var(--flare);
  color: var(--paper);
}

.spotlight-option-meta {
  color: var(--ink-500);
  font-size: 0.875rem;
  white-space: nowrap;
}

.spotlight-option:hover .spotlight-option-meta,
.spotlight-option.is-active .spotlight-option-meta {
  color: var(--paper);
}

.spotlight-see-all {
  display: block;
  padding: 0.5rem 0.75rem;
  font-size: 0.875rem;
}

/* The see-all link is a listbox option too, so it has to show the keyboard highlight. */
.spotlight-see-all:hover,
.spotlight-see-all.is-active {
  background: var(--flare);
  color: var(--paper);
}

.spotlight-empty {
  padding: 0.75rem;
  color: var(--ink-500);
}

/* /search opened as a page (a browser search engine lands here): the spotlight's own list, laid out
   in flow on the surface the floating panel would otherwise provide. */
.search-page-hint {
  color: var(--ink-500);
  margin: 0 0 1rem;
}

.search-page-results {
  background: var(--surface);
  border-radius: 8px;
  box-shadow: var(--e1);
  overflow: hidden;
}

/* The same spotlight, in a dialog, for every page that has no field of its own. Anchored near the
   top rather than centred: the panel drops out of the field, and a vertically centred field would
   push its own results off the bottom. `inset: 0` for the same reason as .result-modal — the UA
   stylesheet only centres a modal dialog on the block axis. */
.search-overlay {
  position: fixed;
  inset: 0;
  margin: 12vh auto auto;
  width: min(90vw, 34rem);
  max-height: none;
  padding: 0;
  border: none;
  border-radius: 12px;
  background: var(--surface);
  box-shadow: var(--e2);
  overflow: visible;
}

.search-overlay::backdrop {
  background: rgb(0 0 0 / 45%);
}

/* The dialog's padding lives here, not on the dialog: a click reported against the dialog element
   is how clickBackdrop recognises a click outside the panel, so padding on it would dismiss the
   overlay from inside its own visible edge. */
.search-overlay-content {
  padding: 1rem;
}

/* Inside the dialog the field is the whole content, so it carries none of the dashboard's
   spacing, and the panel meets its bottom edge instead of floating clear of it. */
.search-overlay .spotlight {
  margin-top: 0;
}

.search-overlay .spotlight-panel {
  margin-top: 0.25rem;
  border-radius: 8px;
  border: 1px solid var(--line);
}

/* — The deck row on a phone —
   Every selector here is scoped to the deck list, and that is load-bearing twice over. The rules
   these override live in the layer above at single-class specificity, and a media query adds none:
   an unscoped copy of this block would win only by sitting later in the file, which the next
   trailing layer — this file has grown three of them — would silently undo. And the picker's
   re-anchoring below is written in terms of the deck row, so it must not reach the styleguide,
   which renders the same component standalone inside a box sized to hold its menu. */
@media (max-width: 768px) {
  /* A deck row holds a quantity stepper, a name, a set/number and — on a physical deck — an
     allocation stepper, on one flex line at every width. On a phone that is more than fits: the set
     label and the real/proxy label each wrap mid-word and the last button falls off the edge. The
     allocation stepper takes a line of its own instead.

     Deterministically, not "when the browser runs out of room": how much room that is depends on
     how large the browser draws text, which is why this showed up on a phone and not in a headless
     Chrome at the same width. */
  .deck-card-list .deck-card-item {
    flex-wrap: wrap;
    position: relative; /* the printing picker's menu anchors to the row here — see below */
  }

  .deck-card-list .deck-card-name {
    min-width: 0; /* a long card name has to be allowed to shrink, or it pushes the row wider */
  }

  .deck-card-list .deck-card-alloc {
    flex-basis: 100%;
    margin-left: 0;
    justify-content: flex-end;
  }

  /* Anchored to the set/number label — a narrow element sitting mid-row — a menu wide enough to
     read hangs off the left of a phone screen. Dropping that anchor lets it resolve against the
     row instead, where it fits by construction. */
  .deck-card-list .deck-card-printing {
    position: static;
  }

  .deck-card-list .printing-picker-menu {
    left: 0;
    right: 0;
    min-width: 0;
  }
}

/* — The archetype metagame page —
   Nothing here beats anything. This block adds the two shapes the design system did not already
   hold — a proportion bar, and a card row that carries sub-rows — and every one of its selectors
   matches markup only this page emits, so it never meets a rule from the layers above.

   A handful of them are two-part (.archetype-printing-row .archetype-card-name,
   .archetypes-search .form-input) or reach an element (.archetype-category-header h3,
   .archetype-breakdown h3, .archetype-method > summary, .archetype-method p). That is descent
   into this page's own markup, not weight bought to outrank an earlier layer: each is scoped to
   a container this block itself defines, and the .form-input one is the single case that touches
   a shared class at all — it declares max-width, which .form-input itself never sets, on a field
   inside .archetypes-search and nowhere else. It is character for character the shape
   .tournaments-search .form-input already has, which is the listing this page was built from.

   The rule these layers exist to enforce is that no override here may depend on sitting later in
   the file, since the next appended layer would silently undo it. One selector here **is** an
   override and says so in its own weight: .archetype-range-note + .archetype-overlap-note, which
   exists to beat .archetype-overlap-note's own margin when the two notes stack in role mode. It
   is an adjacent sibling and not descent, so it is the one entry in this block that buys weight
   rather than scope — and it buys it in the selector, which is the whole point: an unconditional
   negative margin-top on .archetype-overlap-note would pull that note under the summary in the
   one-list case, where the range note above it is withheld. Every other selector below is scope,
   not weight.

   Deliberately absent: any energy-type colour. The one thing on this page that is typed is the
   archetype badge in the header, and Ui::ArchetypeBadge already colours itself from
   Card::TYPE_TOKENS. A second, page-local tint would be a literal hex by another name and would
   drift from the palette the badge reads.

   Also absent: a fixed table layout for the card report. The report is a list of rows whose
   sub-rows nest, which a .data-table cannot express — its mobile mode turns every row into a card
   and a nested one would render as a card inside a card. The rows here wrap instead, on both
   sides of the 768px breakpoint, so nothing needs a media query and nothing can overflow the
   body. The three performance breakdowns *are* .data-table, because they are genuinely two
   columns and get their data-label mobile mode for free. */

.archetype-identity {
  margin-bottom: 1.5rem;
}

.archetype-art {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
}

.archetype-art-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.35rem;
  width: 140px;
  text-decoration: none;
  color: inherit;
}

.archetype-art-image {
  width: 100%;
  height: auto;
  border-radius: 8px;
  box-shadow: var(--e1);
}

.archetype-art-label {
  font-size: 0.75rem;
  text-align: center;
  color: var(--ink-500);
}

.archetype-lineage {
  display: flex;
  flex-wrap: wrap;
  gap: 1.25rem;
  margin: 0.75rem 0 0;
  font-size: 0.9rem;
  color: var(--ink-500);
}

.archetype-sample {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-bottom: 1.5rem;
}

/* The label wraps its select (Ui::FilterSelect emits no id to point at), so it is the flex line
   the two of them share. */
.archetype-sample-label {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-weight: 500;
}

.archetype-sample-note {
  margin: 0;
  font-size: 0.85rem;
  color: var(--ink-500);
}

/* The catalog's first cell stacks the badge's link and the note under it.

   What fixes the mobile layout is the wrapper *existing*, not this declaration: below the
   breakpoint .data-table turns each row into a card whose cells are
   `display: flex; align-items: center`, so a link and a note placed directly in the cell become
   two flex items on one line — measured at 390px, the note took 109px of a 326px cell,
   right-aligned, and grew the row from 29px to 64px. Wrapped, they are one flex item and stack
   inside it however this rule says. Verified by sabotage: removing the wrapper turns the mobile
   half of the system sweep red, while swapping this column for `display: block` does not.

   Kept as a column with a gap anyway, for the reason .archetype-member-cards is one — it is the
   same problem in the same table and the next reader should meet one answer, not two. */
.archetype-row-cell {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  min-width: 0;
}

/* --ink-500 and not .text-muted's --ink-300: this sentence exists to stop a misreading of the
   numbers beside it, so it has to be readable. --ink-300 measures 2.65:1 on the surface and 2.49:1
   on a hovered row, both under WCAG AA's 4.5:1 for normal text; --ink-500 is 5.82:1 and is what
   .archetype-sample-note, the detail page's equivalent sentence, already uses. */
.archetype-row-note {
  margin: 0;
  font-size: 0.85rem;
  color: var(--ink-500);
}

/* Loud enough to be read before the percentages it qualifies, which is the whole point: the
   default view of a freshly imported archetype is very often a three-list sample. */
.archetype-notice {
  margin: 0;
  padding: 0.6rem 0.9rem;
  border-left: 3px solid var(--timeout);
  border-radius: 4px;
  background: var(--timeout-bg);
  color: var(--ink-800);
  font-size: 0.9rem;
}

.archetype-panel {
  margin-top: 2rem;
}

/* The card report's heading and its grouping control on one line, wrapping to two when they stop
   fitting rather than overlapping or pushing the panel wider than the page. Same shape as
   .archetype-category-header below, and the same reason it is a class of its own rather than
   .admin-header: that one carries a page header's margin.

   No media query, deliberately — the whole block is written so that nothing here has to beat
   anything, and flex-wrap answers 390px and 1400px with one rule. The h2 loses its own margin
   because the flex line is what spaces it now; that is descent into markup this block defines,
   not weight bought to outrank a layer above. */
.archetype-report-header {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.5rem 1rem;
  margin-bottom: 0.75rem;
}

.archetype-report-header h2 {
  margin: 0;
}

.archetype-report-modes {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem;
  font-size: 0.85rem;
}

.archetype-report-modes-label {
  color: var(--ink-500);
}

/* A tab, not a button: the current one stays clickable (a tab that cannot be clicked reads as
   disabled) and is told apart by weight and an underline as well as by colour, since colour alone
   is not a distinction every reader has. */
.archetype-report-mode {
  padding: 0.15rem 0.5rem;
  border-radius: 999px;
  color: var(--ink-500);
  text-decoration: none;
}

.archetype-report-mode--current {
  background: var(--paper);
  color: var(--ink-800);
  font-weight: 600;
  text-decoration: underline;
}

/* The register of .archetype-printing-note below — the other sentence on this page that stops a
   reader taking a set of numbers for parts of a whole. --ink-500 for the reason
   .archetype-row-note carries it: a sentence written to prevent a misreading has to be readable. */
.archetype-overlap-note {
  margin: 0 0 1.25rem;
  font-size: 0.85rem;
  color: var(--ink-500);
}

/* Same register as the note above it, deliberately: both qualify the sections rather than
   announce anything, and at default <p> weight this one was louder than the report it describes. */
.archetype-provenance-note {
  margin: -0.75rem 0 1.25rem;
  font-size: 0.85rem;
  color: var(--ink-500);
}

.archetype-facts {
  margin: 0 0 1.25rem;
}

.archetype-fact {
  margin: 0 0 0.35rem;
}

.archetype-fact-muted {
  color: var(--ink-500);
  font-size: 0.9rem;
}

/* auto-fit rather than a fixed column count: three small tables side by side above the
   breakpoint, stacked below it, without a media query that the next appended layer could
   outrank. */
.archetype-breakdowns {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 1.5rem;
}

.archetype-breakdown h3 {
  margin: 0 0 0.5rem;
  font-size: 1rem;
}

.archetype-summary {
  margin: 0 0 1.25rem;
  color: var(--ink-700);
}

.archetype-category {
  margin-bottom: 1.75rem;
}

/* Its own class rather than .admin-header, which the tournament page borrows for the same
   shape: that one carries a page header's 1.5rem margin, which reads as a gap between the
   heading and the list it belongs to when it is repeated eight times down one page. */
.archetype-category-header {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.5rem;
  margin-bottom: 0.6rem;
}

.archetype-category-header h3 {
  margin: 0;
  font-size: 1.05rem;
}

/* The two figures of a heading, wrapped so `space-between` on the header above puts *them* at
   the far end together rather than spreading the three children across the row — see
   Archetypes::CategorySection. Its own `flex-wrap` so the pair stacks under the heading at a
   width that cannot hold both, instead of overflowing. */
.archetype-category-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.15rem 0.6rem;
}

.archetype-category-count,
.archetype-category-copies {
  color: var(--ink-500);
  font-size: 0.85rem;
}

/* Only the card count refuses to break. The copies figure carries a mode list of no fixed length
   — a four-way tie in a small sample renders "0-1 copies · most often 0 / 1 / 2 / 3 — tied" — and
   it is the last thing on the line, so letting it wrap costs nothing while `nowrap` would make it
   the one unbreakable string on the page. The meta wrapper's own flex-wrap only moves the two
   spans relative to each other; it cannot break either of them. */
.archetype-category-count {
  white-space: nowrap;
}

/* Same register as .archetype-overlap-note and .archetype-printing-note: the third sentence on
   this page written to stop a reader adding up a set of numbers that are not parts of a whole.
   It renders in both modes, so in role mode the overlap note follows it and the pair is drawn
   together — the spacing .archetype-provenance-note already takes from the note above it. */
.archetype-range-note {
  margin: 0 0 1.25rem;
  font-size: 0.85rem;
  color: var(--ink-500);
}

.archetype-range-note + .archetype-overlap-note {
  margin-top: -0.75rem;
}

/* Same register as the three notes above it. Scope, not weight: it follows whichever of them
   rendered, and takes no negative margin of its own — the pair-tightening trick above exists for
   two notes that always stack together, while this one may follow any of three or none. */
.archetype-reprint-note {
  margin: 0 0 1.25rem;
  font-size: 0.85rem;
  color: var(--ink-500);
}

.archetype-card-list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.archetype-card-row {
  padding: 0.7rem 1rem;
  border-radius: 8px;
  background: var(--surface);
  box-shadow: var(--e1);
}

.archetype-card-main {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1rem;
}

/* Each of the three cells may take the whole line when the row runs out of room, which is what
   makes the row legible at 390px without a breakpoint of its own. min-width: 0 is what lets a
   long card name shrink instead of pushing the row past the body.
   flex-wrap is what lets a type label (flex-basis: 100% below) claim a line of its own rather
   than sharing this one with the name and the fixed flag — harmless to the printing sub-rows
   that reuse this class with no label to wrap, since wrapping only happens when a child actually
   demands it. */
.archetype-card-name {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
  flex: 1 1 12rem;
  min-width: 0;
  font-weight: 600;
}

/* The card names are links to the printing they name. Two of .deck-compare-card-link's five
   declarations, plus a hover underline: that rule's display:flex/align-items/gap are *inert* on
   an anchor holding a single run of text — measured, identical boxes with and without them at
   1400px and at 344px — so copying them would assert a layout this element does not have.
   `inherit` is what keeps the two weights this page sets: 600 on a name line, and
   400/--ink-700 on a printing sub-row through .archetype-printing-row .archetype-card-name. So
   linking the text changes nothing about how the report reads, and the hover underline is what
   says it is a link — the same trade /decks/compare already makes, which is also the one thing
   here no test in this repository can see: nothing in test/ reads a computed style. */
.archetype-card-link {
  color: inherit;
  text-decoration: none;
}

.archetype-card-link:hover {
  text-decoration: underline;
}

/* A fixed basis, and the copies cell absorbs the slack instead — not the other way round. A bar
   whose length depends on how long the text beside it happens to be cannot be compared with the
   bar on the next row, which is the only thing a bar is for: two cards both at 100% drew visibly
   different bars because "1 copy" and "2-3 copies · most often 2 / 3 — tied" left them different
   room. `1` on the shrink factor keeps it collapsible below the breakpoint, where the cells wrap
   onto their own lines anyway. */
.archetype-card-share {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex: 0 1 14rem;
  min-width: 0;
}

.archetype-bar {
  flex: 1 1 auto;
  min-width: 2.5rem;
  height: 0.45rem;
  border-radius: 999px;
  background: var(--line);
  overflow: hidden;
}

.archetype-bar-fill {
  height: 100%;
  border-radius: 999px;
  background: var(--secondary-color);
}

.archetype-card-pct {
  font-size: 0.8rem;
  color: var(--ink-500);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* Grows so the row still reaches the right edge, and stays right-aligned so the copy counts line
   up with each other down the column rather than starting wherever the bar above them ended. */
.archetype-card-copies {
  flex: 1 1 10rem;
  text-align: right;
  font-size: 0.85rem;
  color: var(--ink-700);
  font-variant-numeric: tabular-nums;
}

/* The rule is the only thing saying these belong to the row above; they are indented, never
   styled as rows of their own, because they are not a decomposition of it — see NameGroupRow. */
.archetype-printing-list {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  margin: 0.5rem 0 0;
  padding: 0 0 0 0.9rem;
  border-left: 2px solid var(--line);
  list-style: none;
}

.archetype-printing-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem 1rem;
  font-size: 0.9rem;
}

.archetype-printing-row .archetype-card-name {
  font-weight: 400;
  color: var(--ink-700);
}

.archetype-printing-note {
  margin: 0.5rem 0 0 0.9rem;
  font-size: 0.78rem;
  font-style: italic;
  color: var(--ink-500);
}

.archetype-fixed-flag {
  flex-shrink: 0;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: 0.7rem;
}

/* .archetype-card-label-line is the flex item that forces the line break; .archetype-card-label,
   nested inside it, is the small bordered pill the reader actually sees. They have to be two
   elements: a flex item's max-width clamps its *hypothetical main size* before the flex-wrap
   line-breaking decision is made, so giving the visible badge both `flex-basis: 100%` (to force
   a deterministic wrap, the same call .deck-card-alloc makes on a phone, made here
   unconditionally because .archetype-card-name never gets a media query — see the section
   comment above .archetype-identity) and a smaller max-width (to keep it from rendering as a
   full-width bar) does not work on one element — measured: the browser uses the *clamped* size to
   decide whether the line even needs to break, so a small max-width silently cancels the very
   wrap it was meant to leave alone, and the badge sits back on the name's own line. Splitting the
   two answers each question separately: the wrapper is full width and invisible, so it is what
   the wrap decision sees; the badge inside it is an ordinary inline-block box sized to its own
   content, so it is what the reader sees. */
.archetype-card-label-line {
  flex-basis: 100%;
  flex-shrink: 0;
}

/* Same shape as .archetype-fixed-flag — a different token, so a type label does not read as a
   second "fixed". Neutral rather than a per-energy-type token: this page's own comment above
   already rules those out for anything but the header badge, and a card's *label* has nothing to
   do with its energy type. No flex properties of its own: it is a plain inline-block box (from
   .badge) laid out in normal flow inside .archetype-card-label-line above, not a flex item. */
.archetype-card-label {
  background: var(--paper);
  color: var(--ink-700);
  border: 1px solid var(--line);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: 0.7rem;
}

.archetype-method {
  margin-top: 2.5rem;
  color: var(--ink-500);
  font-size: 0.85rem;
}

.archetype-method > summary {
  cursor: pointer;
}

.archetype-method p {
  max-width: 70ch;
}

/* The archetype catalog's own two rules, the pair /tournaments needed. Everything else on that
   page is .admin-container, .data-table and .cards-pagination — a listing that needs more than
   this is a listing that has stopped matching the other three. */
.archetypes-search {
  margin-bottom: 1rem;
}

.archetypes-search .form-input {
  max-width: 22rem;
}

/* Both printing labels stack in one cell rather than sitting side by side: below the breakpoint
   .data-table turns each row into a card, and two labels on one line is what overflows it. */
.archetype-member-cards {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  min-width: 0;
}

/* ============================================================
   DECK BUILD ODDS — /decks/:id/odds
   ============================================================ */

.odds-panel {
  margin-block: 2rem;
}

.odds-scenario {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem;
  padding: 1rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
  box-shadow: var(--e1);
}

.odds-stepper {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.odds-stepper-label {
  color: var(--ink-500);
  font-size: 0.875rem;
}

/* Wide enough for two digits plus the number spinner, and it must not stretch: the row wraps to one
   column below the breakpoint, and a flex-grown input would push its own buttons off it. */
.odds-stepper-input {
  width: 5rem;
  flex: none;
  text-align: center;
}

.odds-scenario-summary {
  flex-basis: 100%;
  margin: 0;
  color: var(--ink-500);
}

/* Tabular figures: every one of these is rewritten in place as a stepper moves, and proportional
   digits make the whole column jitter on each notch. */
.odds-value {
  font-variant-numeric: tabular-nums;
}

.odds-refusal,
.odds-notice {
  padding: 1rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
}

.odds-note,
.odds-coverage-note,
.odds-overlap-note {
  color: var(--ink-500);
  font-size: 0.875rem;
}

.odds-combo-group {
  margin-block: 0.75rem;
  padding: 0.75rem;
  border: 1px solid var(--line);
  border-radius: 8px;
}

.odds-combo-group-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

.odds-combo-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-block: 0.5rem;
}

.odds-combo-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.125rem 0.5rem;
  border-radius: 8px;
  background: var(--paper);
}

.odds-combo-chip-drop {
  border: 0;
  background: none;
  color: var(--ink-500);
  cursor: pointer;
}

.odds-combo-picker {
  margin-top: 1rem;
  padding: 0.75rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
  box-shadow: var(--e2);
}

/* The whole deck ships here — about 25 entries — so the list scrolls rather than pushing the answer
   below the fold, at either viewport. */
.odds-combo-options {
  max-height: 16rem;
  overflow-y: auto;
  margin: 0.5rem 0 0;
  padding: 0;
  list-style: none;
}

.odds-combo-option {
  width: 100%;
  padding: 0.375rem 0.5rem;
  border: 0;
  background: none;
  text-align: left;
  cursor: pointer;
}

.odds-combo-option[disabled] {
  opacity: 0.4;
  cursor: not-allowed;
}

.odds-combo-answer .odds-value {
  font-family: var(--font-mono);
  font-size: 1.25rem;
}

.odds-combo-error {
  color: var(--loss);
}
