:root {
  /* Dark "void" theme -- originally scoped to the home portal hub only
     (see body.page-hub below), promoted to the site-wide default so
     every page shares one consistent look instead of the hub being an
     island inside an otherwise light site. */
  --bg: #0a0714;
  --surface: rgba(255, 255, 255, 0.055);
  --ink: rgba(255, 255, 255, 0.92);
  --ink-soft: rgba(255, 255, 255, 0.6);
  --accent: #ef7b53;
  --accent-soft: rgba(239, 123, 83, 0.16);
  --good: #34d97a;
  --line: rgba(255, 255, 255, 0.14);
  --radius: 10px;

  --navy: #081a36;
  --navy-2: #102b55;
  --paper: #f8f2e5;
  --paper-2: #fffaf0;
  --gold: #f5c542;
  --red: #e11d48;
  --green: #22c55e;
  --blue: #38bdf8;
  --muted: rgba(255, 255, 255, 0.55);
  --dash-line: rgba(255, 255, 255, 0.16);
  --shadow: 0 18px 50px rgba(0, 0, 0, 0.45);
  --radius-xl: 28px;
  --radius-lg: 20px;
  --radius-md: 14px;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  background:
    radial-gradient(ellipse 1400px 700px at 50% -60px, #1c1240 0%, transparent 65%),
    linear-gradient(180deg, #130b28 0%, #0a0714 35%, #060309 75%, #030205 100%);
  background-attachment: fixed;
  color: var(--ink);
  line-height: 1.5;
  /* The nebula/star texture layers used on the home hub deliberately
     bleed to 100vw so their scenes fill the viewport edge to edge
     instead of stopping at the 1180px content column -- clip that
     overflow here, at the page level, not on a narrower ancestor. */
  overflow-x: hidden;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

.container {
  max-width: 1180px;
  margin: 0 auto;
  padding: 0 20px 60px;
}

nav.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 20px;
  background: transparent;
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
  color: #fff;
  margin-bottom: 32px;
  flex-wrap: wrap;
  gap: 12px;
}

nav.topbar .brand { font-weight: 800; font-size: 18px; color: #fff; }
nav.topbar .links { display: flex; gap: 18px; align-items: center; font-size: 14px; flex-wrap: wrap; }
nav.topbar .links a { color: rgba(255,255,255,.86); }
nav.topbar .links a:hover { color: var(--gold); }
nav.topbar .muted { color: rgba(255,255,255,.6); }
.lang-switch { display: inline-flex; gap: 4px; align-items: center; font-size: 12.5px; font-weight: 800; }
.lang-switch a { color: rgba(255,255,255,.55); }
.lang-switch a.active { color: var(--gold); }
.lang-switch span { color: rgba(255,255,255,.35); }

h1, h2, h3 { line-height: 1.2; }

.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 18px 20px;
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}

.grid { display: grid; gap: 14px; }
.grid-2 { grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); }

.flash { padding: 10px 14px; border-radius: var(--radius); margin-bottom: 16px; font-size: 14px; }
.flash.success { background: rgba(52, 217, 122, 0.14); color: var(--good); }
.flash.error { background: var(--accent-soft); color: var(--accent); }

form.stacked { display: flex; flex-direction: column; gap: 12px; max-width: 380px; }
form.stacked label { font-size: 13px; color: var(--ink-soft); }
input, textarea, select {
  padding: 9px 11px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.05);
  color: var(--ink);
  font-size: 15px;
  font-family: inherit;
  width: 100%;
}
input::placeholder, textarea::placeholder { color: rgba(255, 255, 255, 0.35); }
select option { background: #130b28; color: var(--ink); }

button, .btn {
  display: inline-block;
  padding: 8px 14px;
  border-radius: 6px;
  border: 1px solid var(--gold);
  background: var(--gold);
  color: #241a03;
  font-weight: 700;
  font-size: 14px;
  cursor: pointer;
  font-family: inherit;
}
button.secondary, .btn.secondary { background: rgba(255, 255, 255, 0.06); color: var(--ink); border-color: var(--line); font-weight: 400; }
button.small { padding: 5px 10px; font-size: 12.5px; }

table { width: 100%; border-collapse: collapse; }
th, td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--line); font-size: 14px; }
th { font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--ink-soft); }

.tag { display: inline-block; font-size: 11px; padding: 2px 8px; border-radius: 100px; background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.tag.mutual { background: rgba(52, 217, 122, 0.16); color: var(--good); }

.actions-inline { display: flex; gap: 6px; align-items: center; }
.muted { color: var(--ink-soft); font-size: 13.5px; }

.search-bar { margin-bottom: 18px; display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }
.search-bar input[type="text"] { max-width: 320px; }
.sort-toggle { display: flex; align-items: center; gap: 8px; font-size: 13.5px; }
.sort-toggle a { padding: 5px 10px; border-radius: 6px; border: 1px solid var(--line); color: var(--ink-soft); }
.sort-toggle a.active { background: var(--gold); color: #241a03; border-color: var(--gold); }

/* ---------------- Per-collection ambient page background ---------------- */

/* Extends a collection's own scene art (the same pixel-art asset used in
   .dash-hero and the home portal rift) into a soft, blurred backdrop that
   bleeds outward from the hero in every direction and fades back into the
   shared void background beneath it -- rather than the art stopping dead
   at the hero's own box. A mask-image radial fade does the "continues in
   all directions" part; the mid-tone color glow underneath ties the page
   to the collection's theme color even where the blurred art itself has
   faded out. Fixed + negative z-index so it sits behind real content but
   above the plain body background, and doesn't scroll with the page. */
.page-scene-bg {
  position: fixed;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
}
.page-scene-bg::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: var(--page-scene, none);
  background-repeat: no-repeat;
  background-position: top center;
  background-size: 85% auto;
  filter: blur(60px) saturate(1.4) brightness(.6);
  opacity: .8;
  -webkit-mask-image: radial-gradient(ellipse 65% 55% at 50% 10%, black 0%, transparent 75%);
  mask-image: radial-gradient(ellipse 65% 55% at 50% 10%, black 0%, transparent 75%);
}
.page-scene-bg::after {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(ellipse 1100px 750px at 50% -80px, color-mix(in srgb, var(--page-mid, #8a5fd1) 32%, transparent) 0%, transparent 68%);
  mix-blend-mode: screen;
}

/* ---------------- Collection dashboard (World Cup style) ---------------- */

/* The collection-specific scene art sits as its own background layer
   between two things that were already here: a darkening gradient on top
   (so the title/description text, which has no card behind it like the
   KPI tiles do, stays readable over busy art) and the original navy/accent
   gradient underneath as a fallback for any collection that doesn't have
   its own scene (`--hero-scene` then resolves to `none`, and that layer is
   simply absent, leaving the exact pre-existing look). */
.dash-hero {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-xl);
  padding: 30px;
  color: white;
  box-shadow: var(--shadow);
  border: 1px solid rgba(255,255,255,.18);
  background:
    linear-gradient(100deg, rgba(6,10,24,.78) 0%, rgba(6,10,24,.48) 38%, rgba(6,10,24,.16) 62%, rgba(6,10,24,.04) 85%),
    var(--hero-scene, none),
    linear-gradient(110deg, rgba(8,26,54,.96), rgba(13,47,92,.88) 58%, color-mix(in srgb, var(--hero-accent, var(--red)) 78%, rgba(13,47,92,.88))),
    repeating-linear-gradient(135deg, rgba(255,255,255,.08), rgba(255,255,255,.08) 9px, transparent 9px, transparent 18px);
  background-size: cover, cover, cover, auto;
  background-position: center, center, center, center;
  margin-bottom: 18px;
}

.dash-hero h1 {
  margin: 0 0 8px;
  font-size: clamp(26px, 4vw, 44px);
  line-height: 1;
  letter-spacing: -0.03em;
  text-transform: uppercase;
}

.dash-hero p { margin: 0; max-width: 720px; color: rgba(255,255,255,.84); font-size: 14.5px; }

.kpi-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 12px;
  margin-top: 20px;
}

.kpi {
  position: relative;
  overflow: hidden;
  padding: 14px 16px;
  border-radius: var(--radius-lg);
  background: rgba(255,255,255,.07);
  border: 1px solid rgba(255,255,255,.12);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  color: var(--ink);
  box-shadow: 0 10px 24px rgba(0,0,0,.25);
}

.kpi::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 4px;
  background: linear-gradient(90deg, var(--gold), var(--red), var(--blue));
}

.kpi-label { display: block; color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; font-weight: 800; }
.kpi-value { display: block; margin-top: 6px; font-size: clamp(22px, 2.4vw, 30px); font-weight: 900; letter-spacing: -.03em; }

.dash-body { display: grid; grid-template-columns: 280px 1fr; gap: 16px; margin-top: 16px; }
@media (max-width: 900px) { .dash-body { grid-template-columns: 1fr; } }

.side-card {
  padding: 16px;
  border-radius: var(--radius-lg);
  background: var(--surface);
  border: 1px solid var(--line);
  box-shadow: 0 10px 22px rgba(8,26,54,.06);
  margin-bottom: 14px;
}
.side-card h2 { margin: 0 0 12px; font-size: 15px; letter-spacing: -.01em; }

.ranking { display: grid; gap: 9px; margin: 0; padding: 0; list-style: none; }
.ranking li { display: grid; grid-template-columns: 1fr auto; gap: 10px; align-items: center; font-weight: 700; font-size: 13.5px; }
.rbar { grid-column: 1 / -1; height: 7px; border-radius: 999px; background: rgba(8,26,54,.09); overflow: hidden; }
.rbar span { display: block; height: 100%; border-radius: inherit; background: linear-gradient(90deg, var(--red), var(--gold)); }
.rbar span.dup { background: linear-gradient(90deg, var(--gold), var(--green)); }

.team-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(290px, 1fr)); gap: 14px; }

.team-card {
  position: relative;
  overflow: hidden;
  border-radius: 20px;
  background: linear-gradient(180deg, rgba(255,255,255,.06), rgba(255,255,255,.02));
  border: 1px solid var(--dash-line);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  box-shadow: 0 12px 26px rgba(0,0,0,.25);
}
.team-card::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 6px;
  background: var(--team-color, var(--accent));
}

.team-head { position: relative; display: grid; grid-template-columns: auto 1fr; gap: 10px; padding: 16px 14px 10px; align-items: center; }
.team-emblem {
  width: 42px; height: 42px; border-radius: 50%;
  display: grid; place-items: center; color: white;
  background-color: var(--team-color, var(--accent));
  background-image: var(--team-flag, none);
  background-size: cover;
  background-position: center;
  border: 2px solid #fff;
  box-shadow: 0 6px 14px rgba(8,26,54,.16);
  font-weight: 900; font-size: 12px; letter-spacing: -.02em;
  text-shadow: 0 1px 3px rgba(0,0,0,.65);
}
.team-head h3 { margin: 0; font-size: 17px; letter-spacing: -.02em; }

.team-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; padding: 0 14px 10px; }
.stat-pill { border-radius: 12px; padding: 7px; text-align: center; background: rgba(255,255,255,.06); }
.stat-pill b { display: block; font-size: 15px; }
.stat-pill span { display: block; color: rgba(255,255,255,.5); font-size: 10px; text-transform: uppercase; letter-spacing: .05em; font-weight: 800; }

.mini-progress { margin: 0 14px 12px; height: 8px; border-radius: 999px; background: rgba(255,255,255,.12); overflow: hidden; }
.mini-progress span { display: block; height: 100%; border-radius: inherit; background: linear-gradient(90deg, var(--team-color, var(--accent)), var(--green)); }

.sticker-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; padding: 0 14px 14px; }

.slot {
  position: relative;
  min-height: 92px;
  border-radius: 10px;
  padding: 8px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 6px;
  border: 1px dashed var(--dash-line);
  background: rgba(255,255,255,.03);
  overflow: hidden;
}
.slot-top { display: flex; align-items: center; justify-content: space-between; gap: 4px; }
.slot-no { font-size: 13px; font-weight: 900; }

.slot.owned { border-style: solid; border-color: rgba(34,197,94,.5); background: linear-gradient(135deg, rgba(34,197,94,.24), rgba(255,255,255,.02)); }
.slot.missing { border-style: solid; border-color: #fb7185; background: linear-gradient(135deg, rgba(244,63,94,.24), rgba(255,255,255,.02)); }
.slot.duplicate { border-style: solid; border-color: #f5c542; background: linear-gradient(135deg, rgba(245,197,66,.24), rgba(255,255,255,.02)); }
.slot.unset { background: rgba(255,255,255,.02); opacity: .7; }

.slot-photo { width: 100%; height: 26px; border-radius: 5px; object-fit: cover; }

.slot-form { display: flex; flex-direction: column; gap: 4px; }
.slot-form form { display: flex; }
.slot-form button,
.slot-form .btn-photo {
  flex: 1;
  padding: 5px 0;
  font-size: 11px;
  font-weight: 800;
  min-width: 0;
  border-radius: 6px;
  border: none;
  text-align: center;
  line-height: 1.3;
  cursor: pointer;
  white-space: nowrap;
}

.qty-row { display: flex; align-items: center; justify-content: center; gap: 6px; }
.qty-row form { flex: none; display: block; }
.qty-btn {
  width: 22px; height: 22px; padding: 0; border-radius: 6px; border: none;
  background: rgba(255,255,255,.14); color: var(--ink); font-weight: 900; cursor: pointer;
  line-height: 1;
}
.qty-count { font-weight: 900; font-size: 12.5px; min-width: 22px; text-align: center; }

.btn-missing { background: var(--red); color: #fff; }
.btn-duplicate { background: var(--gold); color: #713f12; }
.btn-owned { background: var(--green); color: #063d13; }
.btn-photo { background: var(--navy); color: #fff; display: block; }

/* ---------------- Vertical sets list (Pokemon-style) ---------------- */

.set-list { display: flex; flex-direction: column; gap: 14px; }

.set-row {
  display: flex;
  gap: 18px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 16px;
  box-shadow: 0 10px 22px rgba(8,26,54,.06);
}

.set-row-image {
  flex: none;
  width: 120px;
  height: 120px;
  border-radius: 12px;
  overflow: hidden;
  display: grid;
  place-items: center;
  background: var(--bg);
  border: 1px solid var(--line);
}
.set-row-image img { max-width: 100%; max-height: 100%; object-fit: contain; padding: 8px; }
.set-row-image-fallback {
  width: 100%; height: 100%; display: grid; place-items: center;
  color: #fff; font-weight: 900; font-size: 22px; letter-spacing: -.02em;
}

.set-row-body { flex: 1; min-width: 0; }
.set-row-header { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.set-row-header h3 { margin: 0; font-size: 19px; }
.set-row-header h3 a { color: var(--ink); }
.set-row-header h3 a:hover { color: var(--accent); }
.set-row-meta { margin: 4px 0 0; font-size: 13px; }

@media (max-width: 640px) {
  .set-row { flex-direction: column; }
  .set-row-image { width: 100%; height: 160px; }
}

/* ---------------- Card checklist rows (set detail page) ---------------- */

.card-row-list { display: flex; flex-direction: column; }
.card-row {
  display: grid;
  grid-template-columns: 70px 1fr auto;
  gap: 12px;
  align-items: center;
  padding: 10px 8px;
  border-bottom: 1px solid var(--line);
}
.card-row.missing { background: linear-gradient(90deg, rgba(244,63,94,.16), transparent 60%); }
.card-row.duplicate { background: linear-gradient(90deg, rgba(245,197,66,.16), transparent 60%); }
.card-row.owned { background: linear-gradient(90deg, rgba(34,197,94,.14), transparent 60%); }
.card-row-number { font-weight: 800; color: var(--ink-soft); font-size: 13px; font-variant-numeric: tabular-nums; }
.card-row-name { font-weight: 700; min-width: 0; overflow: visible; }
.card-row-actions { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; justify-content: flex-end; }
.card-row-actions form { display: inline-flex; }
.card-row-actions button, .card-row-actions .btn-photo { padding: 5px 10px; font-size: 11.5px; }
.card-row-photo { width: 34px; height: 34px; border-radius: 6px; object-fit: cover; }
.card-row-price { margin-left: 8px; font-size: 11.5px; font-weight: 800; color: var(--good); background: rgba(52,217,122,.14); padding: 2px 7px; border-radius: 999px; }
.condition-badge { margin-left: 6px; font-size: 10.5px; font-weight: 900; color: rgba(255,255,255,.85); background: rgba(255,255,255,.12); padding: 2px 6px; border-radius: 5px; letter-spacing: .03em; }
.condition-select { font-size: 11px; padding: 4px 6px; border-radius: 6px; width: auto; max-width: 130px; }

/* ---------------- Home page feature highlights ---------------- */

/* Positioned + z-index above .hub-ground's stacking context (.hub-inner
   is z-index:1) -- .hub-ground's tall floor-grid graphic geometrically
   extends down through this row so its fade-to-black shows through the
   gaps, but without this promotion it would also paint OVER this static
   content (positioned descendants of a z-index:1 ancestor still layer
   above later static siblings regardless of DOM order), dimming the
   actual card text along with the decorative grid behind it. */
.feature-row { position: relative; z-index: 2; display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 16px; margin-top: 20px; }
.feature-block { padding: 20px; border-radius: 14px; background: rgba(255,255,255,.05); border: 1px solid rgba(255,255,255,.12); }
.feature-icon { font-size: 26px; display: block; margin-bottom: 10px; }
.feature-block h3 { margin: 0 0 8px; font-size: 17px; color: #fff; }
.feature-block p { margin: 0; font-size: 13.5px; color: rgba(255,255,255,.6); }

/* ---------------- Dashboard KPI tiles ---------------- */

.kpi-tile-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 12px; }
.kpi-tile {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 20px;
  border-radius: 12px;
  color: #fff;
  text-decoration: none;
}
.kpi-tile:hover { text-decoration: none; filter: brightness(1.06); }
.kpi-tile-value { font-size: 26px; font-weight: 900; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.kpi-tile-label { font-size: 12.5px; font-weight: 700; opacity: .92; }
.tile-navy { background: var(--navy); }
.tile-navy-2 { background: var(--navy-2); }
.tile-gold { background: linear-gradient(135deg, var(--gold), #c98a1f); color: #3a2600; }
.tile-red { background: linear-gradient(135deg, var(--red), #a8391f); }
.tile-green { background: linear-gradient(135deg, #2f6b3f, #1f4d2c); }

.reputation-badge { display: inline-flex; align-items: center; gap: 3px; font-size: 11px; font-weight: 800; padding: 2px 8px; border-radius: 999px; white-space: nowrap; }
.reputation-badge.tier-outstanding { background: rgba(34,197,94,.16); color: #4ade80; }
.reputation-badge.tier-very-good { background: rgba(56,189,248,.16); color: #7fe9ff; }
.reputation-badge.tier-good { background: rgba(245,197,66,.18); color: var(--gold); }
.reputation-badge.tier-mixed { background: rgba(225,29,72,.18); color: #fb7185; }
.reputation-badge.tier-new { background: rgba(255,255,255,.08); color: var(--ink-soft); }

.plan-badge { display: inline-flex; align-items: center; gap: 3px; font-size: 11px; font-weight: 800; padding: 2px 8px; border-radius: 999px; white-space: nowrap; text-decoration: none; }
.plan-badge.plan-basic { background: rgba(255,255,255,.08); color: var(--ink-soft); }
.plan-badge.plan-premium { background: linear-gradient(135deg, var(--gold), #c98a1f); color: #3a2600; }

.card-hover-preview { position: relative; cursor: help; border-bottom: 1px dotted var(--ink-soft); }
.card-hover-preview img {
  display: none;
  position: absolute;
  left: 0;
  top: calc(100% + 8px);
  width: 220px;
  border-radius: 10px;
  box-shadow: 0 16px 36px rgba(0,0,0,.4);
  border: 1px solid var(--line);
  z-index: 50;
}
.card-hover-preview:hover img,
.card-hover-preview:focus-within img { display: block; }

.chip-list { display: flex; flex-wrap: wrap; gap: 6px; max-height: 220px; overflow: auto; }
.chip { display: inline-flex; align-items: center; gap: 4px; border-radius: 999px; padding: 5px 8px; font-size: 11px; font-weight: 800; border: 1px solid transparent; }
.chip.missing { color: #fb7185; background: rgba(225,29,72,.16); border-color: rgba(225,29,72,.35); }
.chip.duplicate { color: var(--gold); background: rgba(245,197,66,.16); border-color: rgba(245,197,66,.35); }

/* ---------------- Portal hub (home page) ---------------- */

/* The void theme (background/color/overflow-x) now lives on the bare
   `body` selector up top, shared by every page. `.page-hub` only keeps
   the bits that are genuinely specific to the home page: the giant
   title already acts as the brand, so the nav's wordmark is redundant
   here (and only here). */
body.page-hub nav.topbar {
  justify-content: flex-end;
  margin-bottom: 20px;
}
body.page-hub nav.topbar .brand { display: none; }

.hub-scene {
  position: relative;
  padding: 32px 24px 38px;
  margin-bottom: 18px;
}
.hub-scene::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: 100vw;
  max-width: 2400px;
  transform: translateX(-50%);
  background:
    radial-gradient(circle at 20% 8%, rgba(138,95,209,.20), transparent 40%),
    radial-gradient(circle at 82% 14%, rgba(138,95,209,.14), transparent 42%);
  pointer-events: none;
}
.hub-scene::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: 100vw;
  max-width: 2400px;
  transform: translateX(-50%);
  background-image:
    radial-gradient(rgba(255,255,255,.6) 1px, transparent 1px),
    radial-gradient(rgba(255,255,255,.4) 1px, transparent 1px),
    radial-gradient(rgba(255,255,255,.5) 1px, transparent 1px);
  background-size: 180px 180px, 240px 240px, 130px 130px;
  background-position: 20px 30px, 120px 90px, 60px 140px;
  opacity: .4;
  pointer-events: none;
}

/* Ground plane — a glowing horizon line with a perspective grid floor
   below it, full-bleed behind the whole scene. `top: 100%` anchors it to
   .hub-stage's own (auto) height, so the horizon always starts exactly
   where the rifts end — whether they wrap to one row or two — instead of
   a guessed fixed offset that would drift with row count or viewport
   width. Above the horizon is left empty on purpose — just stars.

   Height is deliberately tall (well past the stats/CTA that sit right
   below the rifts) so the floor keeps going through the "more
   collections" strip and the ad slot, then fades out through the feature
   row below that — see .hub-ground-fade. There's no reliable CSS-only way
   to measure exactly where the ad slot lands (it depends on how much text
   wraps above it), so this is a generous fixed height with a gradual fade
   built to look intentional even when actual content is shorter. */
.hub-ground {
  position: absolute;
  top: 100%;
  left: 50%;
  width: 100vw;
  max-width: 2400px;
  height: 1000px;
  transform: translateX(-50%);
  overflow: hidden;
  pointer-events: none;
  z-index: 0;
}
.hub-horizon-glow {
  position: absolute;
  top: -70px;
  left: 0;
  right: 0;
  height: 140px;
  background: radial-gradient(ellipse at center, rgba(127,233,255,.18), transparent 72%);
}
.hub-horizon-line {
  position: absolute;
  top: 0;
  left: 6%;
  right: 6%;
  height: 2px;
  background: linear-gradient(90deg, transparent, #7fe9ff 15%, #eafcff 50%, #7fe9ff 85%, transparent);
  box-shadow: 0 0 16px 3px rgba(127,233,255,.6), 0 0 42px 12px rgba(127,233,255,.22);
}
.hub-ground-grid {
  position: absolute;
  top: 0;
  left: 50%;
  right: 0;
  bottom: 0;
  width: 160%;
  transform: translateX(-50%) perspective(320px) rotateX(72deg);
  transform-origin: top center;
  background-image:
    repeating-linear-gradient(90deg, rgba(127,233,255,.16) 0px, rgba(127,233,255,.16) 1px, transparent 1px, transparent 44px),
    repeating-linear-gradient(0deg, rgba(127,233,255,.14) 0px, rgba(127,233,255,.14) 1px, transparent 1px, transparent 30px);
}
.hub-ground-fade {
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom, transparent 0%, transparent 34%, #0a0714 62%, #030205 85%);
}

.hub-inner { position: relative; z-index: 1; text-align: center; max-width: 1100px; margin: 0 auto; }

.hub-eyebrow {
  margin: 0 0 6px;
  color: #c9b3ff;
  font-size: 12px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: .12em;
}
.hub-title {
  margin: 0 0 10px;
  color: #fff;
  font-size: clamp(26px, 4.2vw, 42px);
  line-height: 1.1;
  letter-spacing: -.02em;
}
.hub-sub {
  margin: 0 auto 30px;
  max-width: 560px;
  color: rgba(255,255,255,.72);
  font-size: 15px;
}

/* `.actions-inline` is shared with non-hub pages (e.g. the trade thread
   view) where left-alignment next to other content is correct, so the fix
   is scoped to the hub instead of changing the shared rule. `display:
   flex` makes it a block-level box that fills `.hub-inner`'s width by
   default -- `.hub-inner`'s own `text-align: center` doesn't reach inside
   a flex container, so with no `justify-content` the buttons packed to
   the left edge, visibly off-center under everything else in the hub
   (which centers via that same `text-align` or its own `margin: auto`). */
body.page-hub .actions-inline { justify-content: center; }

.hub-stage {
  position: relative;
  margin: 10px 0 8px;
}

/* Portals split into short themed rows (TCGs, football) rather than one
   long line -- a single row of six was crowded enough that hovering the
   *edge* portal could push its ring past the row's own right/left padding
   and force a real horizontal scrollbar into existence, on top of just
   being a lot to scan in one strip. Each row still doesn't wrap -- on
   narrow viewports it scrolls horizontally instead, same fallback as
   before, just over fewer items per row so it kicks in less often. `safe
   center` keeps a row centered when it fits and falls back to a
   scrollable start-alignment when it doesn't (plain `center` would make
   overflowing portals unreachable by scroll in that case).

   Generous vertical padding here is load-bearing, not decorative: setting
   `overflow-x: auto` on a flex container makes the browser compute
   `overflow-y` as `auto` too (a visible axis paired with a non-visible one
   always gets forced to auto, per the CSS Overflow spec) — so the rifts'
   glow, which extends well past their own box via box-shadow, was getting
   clipped top and bottom by the scrollport. Padding is part of the
   scrollport and isn't clipped, so it's the actual fix; `overflow-y:
   visible` alone does not work here (the spec forces it back to auto).

   All three of the values below have to clear the ring at its LARGEST
   transient size, which is NOT the 1.22x hover scale -- it's the 1.4x
   peak of the click-transition (`.portal-activate`'s `riftActivate`
   keyframes hit scale(1.4) at 100%, briefly, on every click, on top of
   `.portal-rift` scaling around its own center same as hover). An
   earlier pass sized these off hover alone and looked fine under a mouse
   hover test; it still overlapped and clipped for a few frames on every
   actual click, which is what was actually reported. Re-measured against
   scale(1.4) specifically (with each CSS transition disabled and a forced
   reflow, so the geometry read is the settled value, not a mid-transition
   one) rather than trusting a screenshot.

   Top padding: the ring's bleed above the rift scales around the rift's
   own center, same math as the sideways cases below just on the vertical
   axis. At the 1.4x peak it pokes ~23px past the old 75px allowance;
   100px covers it with a 10px margin on top.

   `gap` and the left/right padding need the same 1.4x-not-1.22x
   correction sideways, for two different reasons. `gap`: the activating
   card's ring can bleed up to ~53px past its own card box at scale(1.4)
   (worst case: the narrowest wood sign, ~104px), and a resting neighbor
   at its own narrowest still bleeds ~23px past *its* box -- 90px covers
   that combined worst case (~76px) with a 10px margin. Left/right
   padding: the *first or last* portal has no neighbor to measure against,
   just its own ~53px bleed at scale(1.4); 65px covers that bleed with a
   10px margin -- this is also what was forcing a scrollbar open on click
   in production, the same mechanism as the hover-only version of this bug
   fixed earlier, just at the larger scale. */
.portal-grid {
  position: relative;
  z-index: 1;
  display: flex;
  flex-wrap: nowrap;
  justify-content: center;
  justify-content: safe center;
  overflow-x: auto;
  gap: 90px;
  padding: 100px 65px 40px;
}

.portal-row-label {
  position: relative;
  z-index: 1;
  margin: 0 0 8px;
  color: rgba(255,255,255,.85);
  font-size: clamp(17px, 2.2vw, 20px);
  font-weight: 800;
  letter-spacing: -.01em;
}
.portal-grid + .portal-row-label { margin-top: 10px; }

.portal-card {
  position: relative;
  flex: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  text-decoration: none;
  color: inherit;
  padding-top: 6px;
}

/* Diablo-style rift: a dark void core (or, for a couple of collections, a
   glimpse of a hand-built pixel-art scene) with an organic swirling energy
   ring at the edge — an SVG turbulence filter displacing plain stroked
   ellipses, not a geometric conic-gradient, so the edge reads as licking
   flame/smoke rather than a static outline. Hover is the only motion on
   top of that — a zoom plus a brighter/wider glow around just that
   portal. */
.portal-rift {
  position: relative;
  width: 104px;
  aspect-ratio: 0.56;
  display: grid;
  place-items: center;
  animation: riftShimmer 3s ease-in-out infinite;
  transition: transform .25s ease;
}
/* The scene/void and its soft edge-fade live on this INNER element, kept
   separate from .portal-rift itself. `mask-image` composites an element's
   full render as one layer -- including any children, regardless of their
   own position/overflow -- so putting the mask directly on .portal-rift
   was quietly also masking .portal-ring (a child, positioned outside
   .portal-rift's own box) down to nothing past that boundary. */
.portal-core {
  /* Sized to match .portal-ring's REAL footprint, not .portal-rift's own
     box -- the ring lives in a sibling with `inset: -22%` (144% of
     portal-rift's box) and its ellipse (rx=88/ry=160 of a 184x328 viewBox,
     ~96%/98% of that box's own half-dimensions) ends up at ~138%/141% of
     portal-rift's half-width/half-height, not the ~100% it might look
     like from the numbers alone. At the old 100% size, the art/void faded
     out well short of where the ring actually sits, leaving a visible dark
     gap between "the art" and "the swirling edge" -- this closes that gap
     so the art extends out to meet the ring instead of floating inside it.
     `.portal-rift`'s `place-items: center` centers this automatically even
     though it now overflows its own grid track. */
  width: 137.7%;
  height: 140.5%;
  border-radius: 50%;
  background:
    var(--rift-scene, none),
    radial-gradient(ellipse at center, #050208 0%, #050208 56%, color-mix(in srgb, var(--rift-mid, #8a5fd1) 75%, transparent) 82%, transparent 100%);
  /* The scene layer is cropped in tight on that collection's actual icon
     (--rift-zoom/--rift-pos, hand-tuned per collection in portal_theme.py
     -- each scene SVG is a small diorama with the icon sitting somewhere
     inside a much larger dark sky/floor, so a plain `cover` leaves most of
     the oval showing that empty diorama space instead of "the art"). The
     void gradient underneath always stays plain `cover`/`center`. */
  background-size: var(--rift-zoom, cover), cover;
  background-position: var(--rift-pos, center), center;
  /* Soft-fade the scene/void's own edge inward from the flat oval boundary
     -- the ring around it is an organic, turbulence-displaced shape, not a
     perfect ellipse, so a hard-edged oval underneath it will occasionally
     poke past the ring at whichever points the turbulence happens to pull
     inward. Fading the edge means any such mismatch blends into shadow
     instead of showing a visible straight edge. */
  -webkit-mask-image: radial-gradient(ellipse 46% 46% at center, #000 62%, transparent 92%);
  mask-image: radial-gradient(ellipse 46% 46% at center, #000 62%, transparent 92%);
  box-shadow:
    0 0 20px 5px color-mix(in srgb, var(--rift-mid, #8a5fd1) 65%, transparent),
    0 0 40px 12px color-mix(in srgb, var(--rift-mid, #8a5fd1) 35%, transparent),
    inset 0 0 22px 8px rgba(0,0,0,.6);
  transition: box-shadow .25s ease;
}
/* The ring itself doesn't spin — the SMIL <animate> on each filter's
   feTurbulence baseFrequency (started from portal.js) morphs the noise
   instead, so the jagged edge boils and flows in place like churning
   energy, rather than the whole ring rotating like a pinwheel.

   `inset` is percentage-based (not a flat px offset) so this box keeps
   the exact same aspect ratio as .portal-rift itself -- a flat px inset
   changes width and height by the same absolute amount, which distorts
   the aspect ratio for a tall oval and was quietly making the ring render
   smaller (and non-uniformly scaled) than the SVG viewBox math assumed. */
.portal-ring {
  position: absolute;
  inset: -22%;
  overflow: visible;
  pointer-events: none;
}
@keyframes riftShimmer {
  0%, 100% { filter: brightness(1); }
  50% { filter: brightness(1.15); }
}

.portal-card:hover .portal-rift { transform: scale(1.22); }
.portal-card:hover .portal-core {
  box-shadow:
    0 0 34px 10px color-mix(in srgb, var(--rift-mid, #8a5fd1) 85%, transparent),
    0 0 70px 22px color-mix(in srgb, var(--rift-mid, #8a5fd1) 55%, transparent),
    inset 0 0 22px 8px rgba(0,0,0,.6);
}

.wood-sign {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 8px 12px 9px;
  min-width: 100px;
  max-width: 150px;
  border-radius: 5px;
  background: linear-gradient(180deg, #8a5a34, #6b4423);
  border: 1px solid #4a2e17;
  box-shadow: 0 6px 14px rgba(0,0,0,.35);
  font-family: Georgia, 'Times New Roman', serif;
  text-align: center;
}
.wood-sign::before, .wood-sign::after {
  content: "";
  position: absolute;
  top: 5px;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 35%, #e8d7a8, #a88a4a);
}
.wood-sign::before { left: 6px; }
.wood-sign::after { right: 6px; }
.wood-sign strong {
  color: #f8ecd0;
  font-size: 13px;
  line-height: 1.25;
  text-shadow: 0 1px 1px rgba(0,0,0,.4);
}
.wood-sign-sub {
  color: #d8bd8e;
  font-size: 10.5px;
  font-family: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  text-transform: uppercase;
  letter-spacing: .04em;
}

.portal-card:hover .wood-sign { box-shadow: 0 8px 18px rgba(0,0,0,.45); }

/* Stat readout — a slim glass HUD panel instead of the light `.kpi` cards
   used elsewhere on the site. Scoped under `.hub-stats` only (doesn't
   touch the shared `.kpi`/`.kpi-grid` rules the dashboard/catalog pages
   still rely on). */
.hub-stats {
  display: flex;
  max-width: 520px;
  margin: 34px auto 0;
  border: 1px solid rgba(127,233,255,.25);
  border-radius: 14px;
  background: rgba(127,233,255,.05);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  overflow: hidden;
}
.hub-stats .kpi {
  flex: 1;
  padding: 16px 10px;
  text-align: center;
  background: none;
  box-shadow: none;
  border-radius: 0;
  border: none;
  border-right: 1px solid rgba(127,233,255,.18);
}
.hub-stats .kpi:last-child { border-right: none; }
.hub-stats .kpi::before { display: none; }
.hub-stats .kpi-label { color: rgba(255,255,255,.55); }
.hub-stats .kpi-value { color: #fff; text-shadow: 0 0 14px rgba(127,233,255,.4); }

/* Entrance flourish — opt-in via JS only for a visitor's first-ever paint
   of the hub (localStorage-gated); everyone else sees the final state
   immediately, so nothing here can slow down or block a normal load. */
.hub-enter .portal-card {
  opacity: 0;
  transform: translateY(16px) scale(.94);
}
.hub-enter-play .portal-card {
  transition: opacity .45s ease, transform .45s ease;
  transition-delay: calc(var(--i, 0) * 70ms);
  opacity: 1;
  transform: none;
}
.hub-enter .hub-eyebrow, .hub-enter .hub-title, .hub-enter .hub-sub {
  opacity: 0;
  transform: translateY(-8px);
}
.hub-enter-play .hub-eyebrow, .hub-enter-play .hub-title, .hub-enter-play .hub-sub {
  transition: opacity .4s ease, transform .4s ease;
  opacity: 1;
  transform: none;
}

/* Click transition — the "step through" moment, capped short so it reads
   as a flourish, not a loading wait. */
@keyframes riftActivate {
  0% { transform: scale(1); filter: brightness(1); }
  60% { transform: scale(1.22); filter: brightness(1.6); }
  100% { transform: scale(1.4); filter: brightness(2.2); opacity: 0; }
}
.portal-activate .portal-rift { animation: riftActivate .32s ease forwards; }
.portal-activate .wood-sign { transition: opacity .2s ease .1s; opacity: 0; }

/* Cursor avatar — a small companion spark that stands in for the pointer
   inside the hub. JS-only (adds .hub-cursor-hidden and creates the element)
   so devices without a fine pointer (touch) keep their normal cursor. */
.hub-scene.hub-cursor-hidden, .hub-scene.hub-cursor-hidden * { cursor: none; }
/* Split in two: JS positions the outer element via `transform: translate()`
   every frame, while the inner element runs the CSS pulse `animation` on
   its OWN `transform`. Animations outrank inline styles in the cascade, so
   putting both on one element would let the pulse silently clobber the
   JS-driven position every frame. */
.hub-avatar {
  position: absolute;
  top: 0;
  left: 0;
  width: 26px;
  height: 26px;
  margin: -13px 0 0 -13px;
  pointer-events: none;
  z-index: 20;
  opacity: 0;
  transition: opacity .2s ease;
}
.hub-avatar.is-visible { opacity: 1; }
.hub-avatar-core {
  width: 100%;
  height: 100%;
  border-radius: 50%;
  background: radial-gradient(circle at 40% 35%, #fff, #d9c2ff 45%, #8a5fd1 75%, transparent 100%);
  box-shadow: 0 0 18px 6px rgba(180,140,255,.55), 0 0 4px 1px rgba(255,255,255,.8);
  animation: avatarPulse 2.2s ease-in-out infinite;
}
@keyframes avatarPulse {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.15); }
}

@media (prefers-reduced-motion: reduce) {
  .hub-enter .portal-card { opacity: 1; transform: none; }
  .hub-enter .hub-eyebrow, .hub-enter .hub-title, .hub-enter .hub-sub { opacity: 1; transform: none; }
  .portal-activate .portal-rift { animation: none; }
  .portal-rift { animation: none; }
  .hub-avatar-core { animation: none; }
  .hub-avatar { display: none; }
}

/* ---------------- More-collections strip + ad slot ---------------- */

.more-collections {
  position: relative;
  z-index: 2;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin: 16px 0 22px;
  font-size: 13px;
}
.more-collections .muted, .hub-stage .muted { color: rgba(255,255,255,.5); }
.chip-link {
  display: inline-block;
  padding: 5px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,.06);
  border: 1px solid rgba(255,255,255,.14);
  color: rgba(255,255,255,.65);
  font-size: 12.5px;
}
.chip-link:hover { border-color: #7fe9ff; color: #eafcff; text-decoration: none; }

.ad-slot {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 90px;
  max-width: 728px;
  margin: 0 auto 30px;
  border: 1px dashed rgba(255,255,255,.18);
  border-radius: var(--radius);
  background: rgba(255,255,255,.04);
}
.ad-slot-label {
  color: rgba(255,255,255,.5);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: .08em;
}
