  /* Neutrals are the SITE tokens (styles.css / index.css / builder.css), not a
     second palette. rewards.html doesn't load styles.css, so it declares its
     own copy — and that copy had drifted onto a Tailwind-ish gray ramp
     (#F8F9FB / #111827 / #9CA3AF / #EBEBEB) which read as a subtly different
     product from every other page.
     The one that actually mattered: --text-muted #9CA3AF on #F8F9FB is a 2.41:1
     contrast ratio — WCAG AA needs 4.5:1 for body text, and the canonical
     #6E6E78 on #FAFAFA gives 4.83:1. Every muted label on this page (tab
     labels, XP notes, sidebar headings, dates) was failing. Keep these four in
     lockstep with styles.css; see the design-tokens note. */
  :root {
    --bg: #FAFAFA; --card-bg: #FFFFFF; --border: #ECECEC;
    --text: #1A1A1A; --text-muted: #6E6E78; --text-soft: #3D3D3D;
    --green: #16A34A; --green-light: #DCFCE7; --green-border: #BBF7D0;
    /* Text sitting ON a solid green fill (not the page background). It reads
       white, which forces the fill under it to be --green-cta (#15803D) and
       NOT --green (#16A34A): white on #16A34A is only 3.30:1 and fails AA at
       this size, which is why this was a near-black green before. #15803D
       carries white at 5.02:1. Change the two together or not at all. */
    --on-green: #FFFFFF;
    --green-cta: #15803D;
    --radius: 16px; --radius-sm: 12px;
    --page-title-size: 34px; --page-title-weight: 800;
    --page-title-line-height: 1.1; --page-title-letter-spacing: -0.02em;
    --page-header-margin-top: 6px; --page-header-margin-bottom: 12px;

    /* Star / Blueprint / Housing-Style pill colors — see the same block in
       styles.css for why this needs an explicit light+dark split. rewards.html
       doesn't load styles.css, so it needs its own copy. */
    --pp-star-bg:rgba(254,243,199,.82);--pp-star-border:rgba(245,158,11,.85);--pp-star-icon:#92400E;
    --pp-star-shadow:0 0 0 2px rgba(0,0,0,.04),0 2px 8px rgba(0,0,0,.10);
    --pp-recipe-bg:rgba(219,234,254,.82);--pp-recipe-border:rgba(59,130,246,.85);--pp-recipe-icon:#1D4ED8;
    --pp-recipe-shadow:0 0 0 2px rgba(0,0,0,.04),0 2px 8px rgba(0,0,0,.10);
    --pp-style-bg:rgba(250,232,255,.82);--pp-style-border:rgba(217,70,239,.85);--pp-style-icon:#A21CAF;
    --pp-style-shadow:0 0 0 2px rgba(0,0,0,.04),0 2px 8px rgba(0,0,0,.10);
    /* Unselected ("off") state — a light neutral chip, NOT a dark one. */
    --pp-off-bg:rgba(255,255,255,.82);--pp-off-border:rgba(0,0,0,.18);--pp-off-icon:#8A8A94;
    --pp-off-shadow:0 0 0 2px rgba(0,0,0,.04),0 2px 8px rgba(0,0,0,.10);
  }
  html.dark {
    /* Same lockstep as :root above. Dark was much closer already — both muted
       values cleared AA (5.59 vs 5.64) — but they are the site tokens now
       rather than near-misses of them. */
    --bg: #0E0E10; --card-bg: #16161A; --border: #26262B;
    --text: #F2F2F0; --text-muted: #8A8A94; --text-soft: #B0B0B8;
    --green-light: #082F19; --green-border: #14532D;
    --on-green: #FFFFFF;
    --green-cta: #15803D;
    --pp-star-bg:rgba(77,59,5,.82);--pp-star-border:rgba(245,179,1,.85);--pp-star-icon:#F5B301;
    --pp-star-shadow:0 0 0 2px rgba(15,15,17,.62),0 4px 12px rgba(0,0,0,.5);
    --pp-recipe-bg:rgba(16,42,79,.82);--pp-recipe-border:rgba(59,130,246,.85);--pp-recipe-icon:#60A5FA;
    --pp-recipe-shadow:0 0 0 2px rgba(15,15,17,.62),0 4px 12px rgba(0,0,0,.5);
    --pp-style-bg:rgba(59,23,79,.82);--pp-style-border:rgba(217,70,239,.85);--pp-style-icon:#F0ABFC;
    --pp-style-shadow:0 0 0 2px rgba(15,15,17,.62),0 4px 12px rgba(0,0,0,.5);
    --pp-off-bg:rgba(20,20,22,.82);--pp-off-border:rgba(255,255,255,.28);--pp-off-icon:#9A9AA4;
    --pp-off-shadow:0 0 0 2px rgba(15,15,17,.62),0 4px 12px rgba(0,0,0,.5);
  }
  * { box-sizing: border-box; margin: 0; padding: 0; }
  body { font-family: 'Nunito','Inter',sans-serif; background: var(--bg); color: var(--text); min-height: 100vh; }
  body::before { display: none; }

  .page-wrap { margin-left: var(--sidebar-w, 320px); flex: 1 0 auto; min-height: 0; transition: margin-left .15s; }
  @media (max-width: 999.98px) { .page-wrap { margin-left: 0 !important; } }
  .rw-page { padding: 24px 20px 24px; }
  @media (max-width: 600px) { .rw-page { padding: 20px 16px 24px; } }

  /* ── Header — title on the left, status pill on the right (same layout as
     votes.html's page header) ── */
  .rw-hdr {
    width: 85%; max-width: 1400px; margin: var(--page-header-margin-top) auto var(--page-header-margin-bottom); box-sizing: border-box;
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
  }
  .rw-hdr h1 { font-family: 'Inter','Geist',sans-serif; font-size: var(--page-title-size); font-weight: var(--page-title-weight); color: var(--text); letter-spacing: var(--page-title-letter-spacing); margin: 0; line-height: var(--page-title-line-height); }
  /* No horizontal padding here — .rw-page already carries it (20px desktop,
     16px at <=600px below). Adding another 16px on top of that doubled the
     side margins at <=1024px, squeezing the banner and everything else in
     .rw-layout ~32px narrower than the rest of the page instead of the
     ~16px every other page uses. */
  @media (max-width: 1024px) { .rw-hdr { width: 100%; } }

  /* ── Layout shell: full-width single column ── */
  .rw-layout, .rw-shell {
    width: 85%; max-width: 1400px; margin: 0 auto;
    display: flex; flex-direction: column; gap: 16px;
  }
  @media (max-width: 1024px) { .rw-layout, .rw-shell { width: 100%; } }

  .rw-main, .rw-center {
    width: 100%; display: flex; flex-direction: column; gap: 16px; min-width: 0;
  }

  .rw-side-left, .rw-side-right { display: none; }

  /* ── Panel switcher bar (tabs + that panel's action buttons) ── */
  .pnl-bar {
    display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap;
    margin: 4px 0;
  }
  /* One tab, one screen — at every width, not just on phones (where this
     started). The path banner is the biggest thing on the page, so leaving it
     above the wheel meant Wheelspin opened with the PATH at the top: the two
     tabs read as one page with an extra panel bolted on rather than as two
     places. The tier track and the path picker were already swapped out by
     _rwCloseAllPanelsExcept() — the banner was the last piece of the path view
     that stayed behind.
     !important because goToMain()/closePathPicker() set `display:flex` inline
     on the banner when they hand the page back to the path. */
  body.rw-ws-active #seasonDetail { display: none !important; }

  /* Above phone widths the switcher leads the page instead of sitting under
     the banner. It selects which of the two screens you are on, so it belongs
     ABOVE the thing it swaps, not buried between the banner and the track —
     and with the banner gone on Wheelspin it was the only way back to the path
     that was still on screen. Done with `order` rather than by moving it in
     the DOM: on a phone the tabs live in the fixed bottom bar and only this
     row's action buttons remain, and those still belong under the banner.
     (.rw-main is a flex column; every other child keeps the default order 0.) */
  @media (min-width: 641px) {
    .rw-main > .pnl-bar { order: -1; margin-top: 0; }
  }

  /* ── Panel switcher (tabs + that panel's actions) ── */
  .pnl-tabs {
    display: inline-flex; position: relative;
    background: var(--card-bg); border: 1.5px solid var(--border);
    border-radius: 50px; padding: 4px; gap: 2px;
  }
  /* Position and width are set from JS against the active tab's real geometry —
     the two labels are different lengths (and more so once translated), so a
     fixed 50% slider would never line up with either of them.

     The fill is the same solid green the sidebar's vertical tabs (.pnl-tabs-v
     .pnl-tab.active) already used. It was --card-bg on a --bg strip: on dark
     that is two greys a few percent apart, so the only thing telling you which
     of the two panels you were looking at was invisible at a glance. */
  .pnl-tab-indicator {
    position: absolute; top: 4px; bottom: 4px; left: 0; width: 0;
    background: var(--green-cta);
    border-radius: 50px;
    box-shadow: 0 6px 16px rgba(22,163,74,.30);
    transition: left .22s cubic-bezier(.4,0,.2,1), width .22s cubic-bezier(.4,0,.2,1);
    z-index: 0;
  }
  .pnl-tab {
    position: relative; z-index: 1;
    display: inline-flex; align-items: center; justify-content: center; gap: 7px;
    background: none; border: none; cursor: pointer;
    padding: 10px 20px; border-radius: 50px;
    font-family: inherit; font-size: 14px; font-weight: 800;
    color: var(--text-soft); white-space: nowrap;
    transition: color .18s;
  }
  /* White on the green fill in both themes — the fill is the same colour in
     light and dark, so the label must not follow the theme's text token. The
     fill is --green-cta (#15803D) and not --green (#16A34A) for the reason
     spelled out on --on-green above: white on the lighter green is 3.30:1 and
     fails AA at this size. */
  .pnl-tab.active { color: var(--on-green); }
  .pnl-tab:hover:not(.active) { color: var(--text); }

  .pnl-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
  /* Both buttons were built for a 240px sidebar column, where full-width and a
     margin-top:auto push-down made sense. In the bar — and now up in the
     banner's pill row — they sit inline instead. */
  .pnl-actions .btn-change-path,
  .pnl-actions .btn-edit-paths,
  #sbPillsRow .btn-change-path,
  #sbPillsRow .btn-edit-paths { width: auto; margin-top: 0; }

  /* Controls, not chips. In the banner these two sat at exactly the chip
     recipe's size — 28px tall, 12px/700 — next to PERMANENT PATH and the days
     counter, so the only two things on the banner you can actually press were
     the same visual weight as the two things you can't. Two separate problems
     came out of that: nothing read as pressable, and 28px is well under the
     44px minimum touch target, which is why they were fiddly to hit.

     Squaring the corners is the cheap half of the fix: status chips stay full
     pills, actions become 12px rounded rects, so shape alone separates "this
     is a label" from "this is a button" before colour or size is considered.
     The rest is footprint — 38px tall with real horizontal padding, a 13px/800
     label and a 14px icon. */
  #sbPillsRow .btn-change-path,
  #sbPillsRow .btn-edit-paths {
    height: 40px; min-height: 40px; padding: 0 20px;
    border-radius: 50px;
    font-size: 13px; font-weight: 800; letter-spacing: .2px;
    gap: 8px; flex-shrink: 0;
  }
  /* The markup sets width/height="12" as attributes; CSS beats those, so the
     icons scale with the bigger label instead of floating in it. */
  #sbPillsRow .btn-change-path svg,
  #sbPillsRow .btn-edit-paths svg { width: 14px; height: 14px; flex-shrink: 0; }

  /* Vertical variants — the left sidebar stacks the tabs top-to-bottom instead
     of side-by-side, so the sliding pill indicator (measured in horizontal
     offsetLeft/width by JS) doesn't apply here. Active state is a solid
     rounded pill — green, not the purple it started as. */
  .pnl-tabs-v {
    display: flex; flex-direction: column; gap: 6px;
    background: none; border: none; padding: 0;
  }
  .pnl-tabs-v .pnl-tab-indicator { display: none; }
  .pnl-tabs-v .pnl-tab {
    justify-content: flex-start; width: 100%;
    padding: 11px 16px; border-radius: 50px;
    background: var(--card-bg); border: 1.5px solid var(--border);
    color: var(--text-soft);
  }
  .pnl-tabs-v .pnl-tab.active {
    background: linear-gradient(135deg, #16A34A, #15803D);
    border-color: transparent; color: #fff;
    box-shadow: 0 6px 16px rgba(22,163,74,.28);
  }
  .pnl-tabs-v .pnl-tab:hover:not(.active) { color: var(--text); border-color: var(--text-muted); }

  .pnl-actions-v { flex-direction: column; align-items: stretch; }
  .pnl-actions-v .btn-change-path,
  .pnl-actions-v .btn-edit-paths,
  .pnl-actions-v .claim-ready-pill,
  .pnl-actions-v .ws-tier-badge { width: 100%; justify-content: center; }

  @media (max-width: 1024px) {
    .rw-side-left .pnl-tabs-v { flex-direction: row; flex: 1 0 auto; }
    .rw-side-left .pnl-tabs-v .pnl-tab { flex: 1; justify-content: center; }
    .rw-side-left .pnl-actions-v { flex-direction: row; flex-wrap: wrap; }
    .rw-side-left .pnl-actions-v .btn-change-path,
    .rw-side-left .pnl-actions-v .btn-edit-paths,
    .rw-side-left .pnl-actions-v .claim-ready-pill,
    .rw-side-left .pnl-actions-v .ws-tier-badge { width: auto; }
  }

  @media (max-width: 640px) {
    .pnl-tabs { display: flex; width: 100%; box-sizing: border-box; }
    .pnl-tab { flex: 1 1 0; min-width: 0; padding: 9px 10px; gap: 5px; font-size: 13px; }
  }

  /* ── Unclaimed XP (banner top-right) ── */
  /* Lives in the banner's pill row and is pushed to the far right of the left
     content column — clear of the absolutely-positioned Next Reward panel. The
     resting state is one small pill; the claim controls only appear on click,
     which is what keeps the banner from feeling cluttered. */
  .banner-xp-wrap { position: relative; }
  .banner-xp-pill {
    display: inline-flex; align-items: center; gap: 6px;
    background: rgba(245,158,11,.12); color: #f59e0b; border: 1.5px solid rgba(245,158,11,.3);
    border-radius: 50px; padding: 0 12px; height: 28px; box-sizing: border-box; cursor: pointer;
    font-family: inherit; font-size: 11px; font-weight: 800;
    letter-spacing: .3px; transition: all .15s;
  }
  .banner-xp-pill:hover { background: rgba(245,158,11,.2); }
  /* Count and label are one balance, not a number with a tiny metadata caption.
     Keep them on the same type size/baseline; weight and colour give the count
     just enough priority without making “Unclaimed XP” look unrelated. */
  .banner-xp-pill-amt {
    font-size: 17px; font-weight: 900;
    color: #FFF6DC; letter-spacing: -.01em;
  }
  .banner-xp-pill-lbl {
    font-size: 17px; font-weight: 800;
    text-transform: none; letter-spacing: -.005em;
    color: rgba(255, 246, 220, 0.86);
  }
  .banner-xp-pill-chev { transition: transform .18s; }
  .banner-xp-wrap.open .banner-xp-pill-chev { transform: rotate(180deg); }
  html.dark .banner-xp-pill { background: rgba(245,158,11,.16); color: #fbbf24; border-color: rgba(245,158,11,.32); }
  html.dark .banner-xp-pill:hover { background: rgba(245,158,11,.26); }

  .banner-xp-pop {
    display: none;
    position: absolute; top: calc(100% + 8px); right: 0; z-index: 20;
    min-width: 260px;
    background: var(--card-bg); border: 1.5px solid var(--border);
    border-radius: 14px; padding: 12px 14px;
    box-shadow: 0 12px 32px rgba(0,0,0,.16);
  }
  .banner-xp-wrap.open .banner-xp-pop { display: block; }
  .banner-xp-pop-title {
    font-size: 10px; font-weight: 800; letter-spacing: .6px; text-transform: uppercase;
    color: var(--text-muted); margin-bottom: 8px;
  }
  /* Input + Use Max share the top row, Claim gets its own beneath — all three
     inline squeezed the input down to ~60px and clipped its placeholder. */
  .xp-strip-controls { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
  .xp-strip-controls .xpb-claim-btn { flex: 1 0 100%; width: auto; margin-top: 2px; padding: 9px 16px; }
  .xp-strip-controls input {
    flex: 1; min-width: 0; box-sizing: border-box;
    background: var(--bg); border: 1.5px solid var(--border); border-radius: 50px;
    padding: 8px 14px; font-family: inherit; font-size: 13px; font-weight: 600;
    color: var(--text);
  }
  .xp-strip-controls input:focus { outline: none; border-color: var(--green); }
  .xp-strip-note { font-size: 12px; color: var(--text-muted); font-weight: 500; line-height: 1.4; }
  .xp-strip-controls + .xp-strip-note { margin-top: 8px; }

  /* .season-banner sets overflow:hidden to round off the banner photo, which
     also clips this popover — harmless on desktop (it fits inside the 250px
     banner) but not once the banner stacks and shortens on mobile. Lift the
     clip only while the popover is open, and raise the banner above the panel
     bar that follows it in DOM order so the popover isn't painted under it.
     .banner-scene-bg keeps its own radius so the photo still rounds. */
  .season-banner:has(.banner-xp-wrap.open) { overflow: visible; z-index: 30; }
  .season-banner:has(.banner-xp-wrap.open) .banner-scene { overflow: visible; }
  .banner-scene-bg { border-radius: 18px; }

  /* The pill wraps onto its own line here, so drop the auto margin — but keep
     the wrapper positioned, or the popover's top:100% would anchor to
     .banner-content instead and land far below the banner. */
  @media (max-width: 640px) {
    .banner-xp-pop { left: 0; right: auto; min-width: 0; width: 260px; max-width: calc(100vw - 64px); }
  }

  /* ── Sidebar tab switcher ── */
  .sb-tabs {
    display: flex;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 3px;
    margin-bottom: 10px;
    gap: 2px;
    width: 100%;
    position: relative;
    z-index: 1;
  }
  .sb-tab-indicator {
    position: absolute;
    top: 3px;
    bottom: 3px;
    left: 3px;
    width: calc(50% - 4px);
    background: var(--card-bg);
    border-radius: 9px;
    box-shadow: 0 1px 6px rgba(0,0,0,.09);
    transition: transform 0.22s cubic-bezier(0.4, 0, 0.2, 1);
    z-index: 0;
  }
  .sb-tabs.ws-selected .sb-tab-indicator {
    transform: translateX(calc(100% + 2px));
  }
  .sb-tab {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 5px;
    padding: 9px 6px;
    border-radius: 9px;
    border: none;
    background: transparent;
    font-size: 12px;
    font-weight: 700;
    color: var(--text-muted);
    cursor: pointer;
    transition: color .2s ease;
    font-family: inherit;
    white-space: nowrap;
    position: relative;
    z-index: 2;
  }
  .sb-tab:hover { color: var(--text); }
  .sb-tab.active {
    color: var(--green);
  }

  /* ── Slide animations between views ── */
  @keyframes slideInRight {
    from { transform: translateX(20px); opacity: 0; }
    to { transform: translateX(0); opacity: 1; }
  }
  @keyframes slideInLeft {
    from { transform: translateX(-20px); opacity: 0; }
    to { transform: translateX(0); opacity: 1; }
  }
  .slide-in-right {
    animation: slideInRight 0.35s cubic-bezier(0.16, 1, 0.3, 1) forwards;
  }
  .slide-in-left {
    animation: slideInLeft 0.35s cubic-bezier(0.16, 1, 0.3, 1) forwards;
  }
  .sb-tab-content { display: none; flex-direction: column; align-items: center; width: 100%; }
  .sb-tab-content.visible { display: flex; }

  .sidebar-card {
    background: var(--card-bg);
    border: 1.5px solid var(--border);
    border-radius: 16px;
    padding: 20px 18px 10px;
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 0;
    position: relative;
    transition: border-color .15s, box-shadow .15s;
  }
  .sidebar-card.path-active, .sidebar-card.ws-active { }

  .sidebar-your-path-lbl { font-size: 11px; font-weight: 700; color: var(--text-muted); text-transform: uppercase; letter-spacing: .6px; margin-bottom: 14px; }

  .sidebar-path-icon-wrap {
    width: 80px; height: 80px;
    border-radius: 50%;
    border: 3px solid var(--green-border);
    background: var(--green-light);
    display: flex; align-items: center; justify-content: center;
    font-size: 32px;
    margin-bottom: 12px;
    position: relative;
  }
  .sidebar-path-icon-wrap::after {
    content: '';
    position: absolute;
    inset: -6px;
    border-radius: 50%;
    border: 2px dashed var(--green-border);
    animation: spinSlow 20s linear infinite;
  }
  @keyframes spinSlow { to { transform: rotate(360deg); } }

  #sidebarWsCard.ws-empty .sidebar-path-icon-wrap,
  #sidebarCard.path-empty .sidebar-path-icon-wrap {
    background: var(--border);
    border-color: var(--border);
  }
  #sidebarWsCard.ws-empty .sidebar-path-icon-wrap::after,
  #sidebarCard.path-empty .sidebar-path-icon-wrap::after {
    border-color: var(--text-muted);
  }

  .sidebar-path-name { font-size: 15px; font-weight: 800; color: var(--text); margin-bottom: 8px; }
  .sidebar-active-badge {
    display: inline-flex; align-items: center; gap: 5px;
    background: var(--green-light); color: var(--green);
    font-size: 10px; font-weight: 800; text-transform: uppercase; letter-spacing: .5px;
    border-radius: 50px; padding: 4px 10px; margin-bottom: 12px;
  }
  .sidebar-active-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--green); }

  .sidebar-profile-wrap {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    margin-bottom: 16px;
    margin-top: 4px;
  }
  .sidebar-avatar-circle {
    width: 80px;
    height: 80px;
    border-radius: 50%;
    background: var(--border);
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 24px;
    font-weight: 800;
    color: var(--text-soft);
    border: 2.5px solid var(--border);
    transition: border-color 0.15s ease-in-out, transform 0.15s ease-in-out;
  }
  .sidebar-avatar-circle.skel {
    background: linear-gradient(90deg, var(--border) 25%, color-mix(in srgb, var(--border) 60%, transparent) 50%, var(--border) 75%);
    background-size: 200% 100%;
    animation: rw-shimmer 1.4s infinite;
  }
  .sidebar-avatar-circle:hover {
    border-color: var(--green);
    transform: scale(1.02);
  }
  .sidebar-avatar-circle img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: 50%;
  }

  .btn-dashboard-path {
    display: inline-flex; align-items: center; justify-content: center; gap: 7px;
    background: transparent; border: 1.5px solid var(--border);
    color: var(--text-soft);
    font-size: 13px; font-weight: 700; border-radius: 12px; padding: 9px 14px;
    cursor: pointer; text-decoration: none; transition: all .15s;
    width: 100%; margin-bottom: 10px;
  }
  .btn-dashboard-path:hover { border-color: var(--green); color: var(--green); transform: translateY(-1px); }

  /* Same pill shape as votes.html's Change Character / Refresh Votes buttons
     (.change-char-btn / .refresh-char-btn) — border + flat fill, bold 12px
     label, rounded fully. */
  .btn-change-path {
    display: inline-flex; align-items: center; gap: 7px;
    /* --bg, not --card-bg: these sit ON card surfaces (the banner, the panel
       bar, the sidebar), and a button filled with its own container's colour
       is just a floating 1.5px outline. */
    background: var(--card-bg); border: 1.5px solid color-mix(in srgb, var(--text-muted) 34%, var(--border));
    color: var(--text-soft); font-size: 13px; font-weight: 800;
    border-radius: 50px; padding: 0 16px; min-height: 36px; cursor: pointer;
    box-shadow: 0 3px 10px rgba(0, 0, 0, .08), inset 0 1px 0 rgba(255, 255, 255, .55);
    transition: background .18s ease, border-color .18s ease, color .18s ease, box-shadow .18s ease, transform .18s ease;
    width: 100%; justify-content: center;
    margin-top: auto;
  }
  /* Hover moved the border and label to green but left the fill flat, so on a
     card the only feedback was a 1.5px line changing colour. Tinting the fill
     and lifting it a pixel is the same feedback every other button on the page
     gives, and it makes "this is live" readable without hunting for the edge. */
  .btn-change-path:hover {
    border-color: var(--green); color: var(--green);
    background: var(--green-light); transform: translateY(-1px);
    box-shadow: 0 6px 16px rgba(22, 163, 74, .14), inset 0 1px 0 rgba(255, 255, 255, .65);
  }
  .btn-change-path:active { transform: translateY(0); }
  .btn-change-path.loading {
    background: linear-gradient(90deg, var(--border) 25%, color-mix(in srgb, var(--border) 60%, transparent) 50%, var(--border) 75%);
    background-size: 200% 100%;
    animation: rw-shimmer 1.4s infinite;
    border-color: transparent;
    cursor: default;
    pointer-events: none;
  }
  .btn-change-path.loading svg, .btn-change-path.loading span { visibility: hidden; }

  /* A bordered pill like .btn-change-path/.picker-choose-btn, not plain text —
     as a link it read as a caption under the Active badge rather than an
     actionable button. Neutral at rest so it doesn't read as alarming next to
     the Active badge; red only shows up on hover to confirm the action. This
     same markup is reused (unstyled) inside .upcoming-card's flat, non-photo
     layout (see rewards.css's #bannerNextReward comment), so the rest state
     has to work on a plain card background too — not just the photo banner. */
  .btn-cancel-path {
    display: inline-flex; align-items: center; gap: 7px;
    background: var(--chip, rgba(0,0,0,.06)); border: 1.5px solid var(--border);
    color: var(--text-soft, var(--text)); font-size: 13px; font-weight: 700;
    border-radius: 50px; padding: 9px 18px; cursor: pointer;
    transition: all .15s; justify-content: center;
    margin: 10px auto 0;
  }
  .btn-cancel-path:hover { background: rgba(239,68,68,.12); border-color: rgba(239,68,68,.45); color: #dc2626; transform: translateY(-1px); }
  html.dark .btn-cancel-path { background: rgba(255,255,255,.08); border-color: rgba(255,255,255,.22); color: rgba(255,255,255,.85); }
  html.dark .btn-cancel-path:hover { background: rgba(248,113,113,.16); border-color: rgba(248,113,113,.5); color: #fca5a5; }
  /* Over the path's photo banner: same glass-pill recipe as .sb-path-pill so
     it stays legible on a busy scene instead of blending into the photo. */
  .season-banner.has-photo .btn-cancel-path {
    background: rgba(26, 18, 8, 0.5); border-color: rgba(255,255,255,.3);
    color: #fff; backdrop-filter: blur(16px) saturate(150%); -webkit-backdrop-filter: blur(16px) saturate(150%);
    text-shadow: 0 1px 3px rgba(0,0,0,.5);
  }
  .season-banner.has-photo .btn-cancel-path:hover { background: rgba(239,68,68,.28); border-color: rgba(239,68,68,.6); color: #fecaca; }

  .sidebar-divider { width: 100%; height: 1px; background: var(--border); margin: 4px 0 14px; }

  .sidebar-xp-lbl { font-size: 11px; font-weight: 700; color: var(--text-muted); text-transform: uppercase; letter-spacing: .5px; margin-bottom: 6px; align-self: flex-start; width: 100%; text-align: left; }
  .sidebar-xp-amount { font-size: 22px; font-weight: 800; color: var(--text); letter-spacing: -.02em; margin-bottom: 6px; align-self: flex-start; }
  .sidebar-xp-bar { width: 100%; height: 6px; background: var(--border); border-radius: 50px; overflow: hidden; margin-bottom: 6px; }
  .sidebar-xp-fill { height: 100%; background: linear-gradient(90deg, #16a34a, #4ade80); border-radius: 50px; transition: width .6s cubic-bezier(.4,0,.2,1); }
  .sidebar-xp-to-next { font-size: 11px; color: var(--text-muted); font-weight: 500; align-self: flex-start; margin-bottom: 14px; }

  .sidebar-date-row { display: flex; align-items: center; gap: 5px; font-size: 11px; color: var(--text-muted); font-weight: 500; align-self: flex-start; margin-bottom: 4px; }
  .sidebar-days-left { font-size: 11px; color: var(--text-muted); font-weight: 500; align-self: flex-start; padding-left: 16px; }
  .sidebar-days-left::before { content: '• '; }

  /* Sidebar checkin */
  .sidebar-checkin { margin-top: 10px; display: flex; flex-direction: column; gap: 0; }
  .sidebar-checkin .checkin-reset { margin-left: 6px; }

  /* Daily pet panel */
  .pet-panel { display: flex; flex-direction: column; align-items: center; gap: 8px; }
  .pet-btn {
    position: relative;
    background: var(--card-bg); border: 1.5px solid var(--border); border-radius: 12px;
    padding: 10px 10px 14px; cursor: pointer; width: 100%;
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
    transition: transform .15s, border-color .15s, box-shadow .15s;
  }
  .pet-btn:hover:not(:disabled) { border-color: var(--green); box-shadow: 0 0 0 3px rgba(22,163,74,.12); transform: translateY(-1px); }
  .pet-btn:active:not(:disabled) { transform: scale(.97); }
  .pet-btn:disabled { cursor: default; }
  .pet-img { width: 140px; height: 140px; object-fit: contain; transition: transform .25s cubic-bezier(.34,1.56,.64,1); }
  .pet-btn:hover:not(:disabled) .pet-img { transform: scale(1.06) rotate(-2deg); }
  .pet-img.pet-pop { animation: petPop .5s cubic-bezier(.34,1.56,.64,1); }
  @keyframes petPop { 0% { transform: scale(1); } 40% { transform: scale(1.25) rotate(6deg); } 100% { transform: scale(1) rotate(0); } }
  .pet-msg {
    max-width: calc(100% - 28px);
    background: rgba(255, 255, 255, 0.92);
    border: 1px solid rgba(0, 0, 0, 0.06);
    border-radius: 50px;
    padding: 5px 14px;
    font-size: 12px; font-weight: 700; color: var(--text-soft); text-align: center;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    pointer-events: none;
  }
  html.dark .pet-msg { background: rgba(0, 0, 0, 0.45); border-color: rgba(255, 255, 255, 0.08); }
  .pet-count {
    font-size: 10px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
    color: var(--text-muted); pointer-events: none;
  }

  /* XP Jar — Unclaimed XP holder; claim some or all of it into the active path */
  .xp-jar {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 10px;
    width: 100%;
    box-sizing: border-box;
    background: var(--card-bg);
    border: 1.5px solid var(--border);
    border-radius: 14px;
    padding: 16px;
    cursor: pointer;
    transition: all .15s ease-in-out;
    font-family: inherit;
  }
  .xp-jar-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
  }
  .xp-jar-icon {
    width: 26px;
    height: 26px;
    object-fit: contain;
  }
  .xp-jar:hover {
    border-color: var(--green);
    box-shadow: 0 4px 12px rgba(22,163,74,.08);
  }
  .xp-jar:active {
    box-shadow: inset 0 1px 4px rgba(0,0,0,.08);
  }
  .xp-jar.xp-jar-inactive {
    opacity: .65;
  }
  .xp-jar.xp-jar-inactive:hover {
    border-color: var(--border);
    box-shadow: none;
    transform: none;
  }
  .xp-jar-lbl {
    font-size: 12px;
    font-weight: 700;
    color: var(--text-soft);
    text-transform: uppercase;
    letter-spacing: .5px;
  }
  .xp-jar-amount {
    font-size: 13px;
    font-weight: 800;
    color: var(--green);
    background: var(--green-light);
    padding: 4px 10px;
    border-radius: 50px;
    transition: all 0.15s ease-in-out;
  }
  .xp-jar.xp-jar-loading .xp-jar-amount {
    background: transparent;
    padding: 0;
  }
  .xp-jar.xp-jar-inactive .xp-jar-amount {
    color: var(--text-muted);
    background: var(--border);
  }

  /* Claim widget — same input+button shape as the Reward Votes widget on
     votes.html, recoloured to this page's green instead of that page's red. */
  .xpb-widget { width: 100%; margin-top: 2px; }
  .xpb-input-row { display: flex; gap: 8px; }
  .xpb-input-row input {
    flex: 1; min-width: 0; box-sizing: border-box;
    border: 1.5px solid var(--border); border-radius: 10px;
    background: var(--bg); color: var(--text);
    font-family: inherit; font-size: 13px; font-weight: 700;
    padding: 8px 10px; outline: none; transition: border-color .15s;
  }
  .xpb-input-row input:focus { border-color: var(--green); }
  .xpb-max-btn {
    flex-shrink: 0; background: none; border: none; color: var(--green);
    font-family: inherit; font-size: 12px; font-weight: 800; cursor: pointer;
    padding: 0 4px;
  }
  .xpb-max-btn:hover { color: var(--green-deep, var(--green)); }
  .xpb-claim-btn {
    width: 100%; margin-top: 8px;
    background: var(--green); color: #fff; border: none; border-radius: 12px;
    font-family: inherit; font-size: 13px; font-weight: 800; cursor: pointer;
    padding: 9px 0; transition: background .15s, opacity .15s;
  }
  .xpb-claim-btn:hover { background: var(--green-deep, var(--green)); }
  .xpb-claim-btn:disabled { opacity: .5; cursor: not-allowed; }
  .xpb-note {
    font-size: 11px; font-weight: 600; color: var(--text-muted);
    line-height: 1.4; margin-top: 2px;
  }

  /* Help "?" icon + tooltip — same style as the ticket-cap help on votes.html */
  .capacity-help-btn {
    width: 16px;
    height: 16px;
    border-radius: 50%;
    border: 1.5px solid var(--border);
    background: var(--card-bg);
    font-family: inherit;
    font-size: 9px;
    font-weight: 800;
    color: var(--text-muted);
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: all 0.15s;
    padding: 0;
    flex-shrink: 0;
  }
  .capacity-help-btn:hover {
    border-color: #16A34A;
    color: #16A34A;
  }
  .help-wrap { position: relative; display: inline-flex; align-items: center; }
  /* position:fixed, placed via JS (see toggleHelpTooltip) rather than
     position:absolute anchored to .help-wrap — the frag pill's help button
     sits inside .ws-odo-counter, which has overflow:hidden for the odometer
     digit-roll animation, so an absolutely-positioned tooltip popping out of
     it was silently clipped to invisible. Fixed positioning escapes that
     clipping regardless of which ancestor a future help button sits inside. */
  .token-tooltip-box {
    display: none;
    position: fixed;
    background: #1c1c1e;
    color: #FFFFFF;
    border-radius: 8px;
    padding: 12px 16px;
    font-family: inherit;
    font-size: 12px;
    font-weight: 500;
    line-height: 1.6;
    width: 220px;
    box-shadow: 0 4px 12px rgba(0,0,0,.15), 0 16px 40px rgba(0,0,0,.15);
    z-index: 9999;
    text-align: left;
    white-space: normal;
    letter-spacing: -0.005em;
    --tip-arrow-x: 50%;
  }
  .token-tooltip-box.open { display: block; }
  .token-tooltip-box::before {
    content:'';position:absolute;left:var(--tip-arrow-x);top:-5px;
    width:10px;height:10px;background:#1c1c1e;
    transform:translateX(-50%) rotate(45deg);border-radius:2px;
  }
  .token-tooltip-box[data-placement="above"]::before { top:auto;bottom:-5px; }

  /* Skeleton inside sidebar */
  .sidebar-skel { width: 100%; height: 14px; background: var(--border); border-radius: 6px; margin-bottom: 8px; animation: skelPulse 1.5s ease-in-out infinite; }
  @keyframes skelPulse { 0%,100%{opacity:1} 50%{opacity:.5} }

  /* ── Shimmer skeleton system (matches dashboard.html) ── */
  @keyframes rw-shimmer { 0%{background-position:-200% 0} 100%{background-position:200% 0} }
  .skel {
    display: inline-block;
    border-radius: 6px;
    background: linear-gradient(90deg, var(--border) 25%, color-mix(in srgb, var(--border) 60%, transparent) 50%, var(--border) 75%);
    background-size: 200% 100%;
    animation: rw-shimmer 1.4s infinite;
  }
  .skel-block  { display: block; }
  .skel-circle { border-radius: 50%; }
  .skel-h10  { height: 10px; }
  .skel-h12  { height: 12px; }
  .skel-h14  { height: 14px; }
  .skel-h18  { height: 18px; }
  .skel-h28  { height: 28px; }
  .skel-w40  { width: 40%; }
  .skel-w55  { width: 55%; }
  .skel-w70  { width: 70%; }
  .skel-w80  { width: 80%; }
  .skel-w100 { width: 100%; }

  /* ── Season Banner ── */
  .season-banner {
    background: var(--card-bg);
    border: 1.5px solid var(--border);
    border-radius: 20px;
    overflow: hidden;
    position: relative;
    /* Matches votes.html's ticket-hero-card height exactly, so the two pages'
       banners read as the same component. */
    min-height: 248px;
    display: flex;
    flex-direction: column;
    isolation: isolate;
  }
  .banner-scene {
    position: relative;
    display: flex;
    align-items: stretch;
    min-height: 246px;
    border-radius: 18px;
    overflow: hidden;
    -webkit-mask-image: -webkit-radial-gradient(white, black);
  }

  .banner-scene-bg {
    position: absolute;
    inset: 0;
    background: var(--card-bg);
    background-size: cover;
    background-position: center;
    z-index: 1;
    border-radius: 18px;
    overflow: hidden;
  }
  .banner-scene-bg.has-image { filter: saturate(1.1) contrast(1.02); }

  /* Entry reveal, matched to votes.html's banner (.vote-banner-wrap/.vote-banner-img):
     the photo fades up over .45s while easing out of a 1.15 scale over .55s.
     Added by renderBanner() once the image has actually decoded, so it never
     animates a blank box. Replaces the old hover zoom, which was the only
     motion the banner had and didn't match the votes page. */
  .banner-scene-bg.has-image {
    opacity: 0;
    transform: scale(1.15);
    transition: opacity .45s ease, transform .55s cubic-bezier(0.16, 1, 0.3, 1), filter .22s ease;
  }
  /* Settles at 1.06 rather than 1 so the cursor parallax below has room to
     shift the photo without ever exposing an edge of the container. */
  .banner-scene-bg.has-image.show {
    opacity: 1;
    transform: scale(1.06) translate3d(var(--bgx, 0px), var(--bgy, 0px), 0);
  }
  /* While the pointer is inside, drop the long easing so the photo tracks the
     cursor live; it eases back to centre once the pointer leaves. */
  .banner-scene-bg.has-image.parallax-active {
    transition: opacity .45s ease, transform .12s linear;
  }
  /* Applying XP is a focused confirmation step: soften the banner artwork so
     the form stays legible without replacing the player's selected path art. */
  .season-banner.xp-apply-open .banner-scene-bg.has-image {
    filter: saturate(.82) contrast(.9) blur(5px);
    transform: scale(1.11) translate3d(0, 0, 0);
    transition: opacity .45s ease, transform .25s ease, filter .22s ease;
  }
  @media (prefers-reduced-motion: reduce) {
    .banner-scene-bg.has-image { transition: opacity .45s ease; transform: none; }
    .banner-scene-bg.has-image.show,
    .banner-scene-bg.has-image.parallax-active { transform: none; }
  }
  /* Dark scrim over the photo so the artwork stays rich and vibrant while
     white text on the left reads clearly. Darkest under the text column,
     fading toward the right where the reward panel sits. A gentle
     top-to-bottom vignette adds depth. Same treatment in both themes since
     it lives on top of the photo, not the page background. */
  .banner-scene-bg.has-image::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: 18px;
    background:
      linear-gradient(to bottom, rgba(0, 0, 0, 0.15) 0%, transparent 30%, rgba(0, 0, 0, 0.25) 100%),
      linear-gradient(to right, rgba(0, 0, 0, 0.78) 0%, rgba(0, 0, 0, 0.58) 32%, rgba(0, 0, 0, 0.28) 68%, rgba(0, 0, 0, 0.12) 100%);
  }

  .banner-scene-deco { display: none; }

  .banner-content {
    position: relative;
    z-index: 5;
    padding: 22px 32px;
    flex: 1;
    display: flex;
    /* stretch, not center: .banner-left fills the banner's height so its two
       blocks can sit at the top and bottom edges. Centring pooled all the slack
       above the title and below the XP bar instead. */
    align-items: stretch;
    justify-content: space-between;
    gap: 24px;
  }

  .banner-left {
    flex: 1; min-width: 0;
    display: flex; flex-direction: column; justify-content: space-between;
    padding-right: 230px;
  }
  /* Title + description ride the top edge, the level/XP/total block the bottom. */
  .banner-head, .banner-progress { display: flex; flex-direction: column; }
  .banner-head .banner-desc { margin-bottom: 0; }
  .banner-progress .banner-level-lbl { margin-top: 0; }
  /* Lifts the level/progress/XP block off the banner's bottom edge — .banner-left's
     space-between already pins it to the bottom, so a touch of margin here is what
     leaves breathing room below rather than fighting the space-between itself. */
  .banner-progress { margin-bottom: 16px; }

  .banner-content[data-img] {
    background: transparent;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    border: none;
    box-shadow: none;
    margin: 0;
    padding: 22px 32px;
    align-self: stretch;
    width: 100%;
  }

  .banner-content[data-img] .banner-left {
    padding-right: 250px;
  }

  /* Over-photo copy: white text with soft shadows for legibility against
     the darkened artwork (matches the reference banner treatment). */
  .banner-content[data-img] .banner-name {
    color: #fff;
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);
  }
  .banner-content[data-img] .banner-desc {
    color: rgba(255, 255, 255, 0.88);
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
  }
  .banner-content[data-img] .banner-level-lbl {
    color: #fff;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
  }
  .banner-content[data-img] .banner-total-xp {
    color: rgba(255, 255, 255, 0.82);
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
  }
  .banner-content[data-img] .banner-xp-highlight {
    color: #fff;
  }
  .banner-content[data-img] .banner-xp-track {
    background: rgba(255, 255, 255, 0.28);
  }
  /* Pills over a banner photo: same dark-chip treatment as votes.html's
     Change Character / Refresh Votes buttons over its own banner photo —
     translucent black fill, faint white border, plain white text.

     The status chips used to be in this list, which is why they went grey the
     moment a season shipped artwork — this selector is (0,3,0) and beat every
     per-type colour rule below. They carry their own solid fills now and are
     deliberately NOT flattened here; over a photo they only take the extra
     depth added further down. */
  .season-banner.has-photo .banner-xp-pill,
  .season-banner.has-photo #sbPillsRow .btn-change-path,
  .season-banner.has-photo #sbPillsRow .btn-edit-paths {
    /* A flat 35%-black scrim was the whole treatment here, which is the
       canonical .btn-on-image recipe (styles.css) minus the three properties
       that actually lift a pill off a photograph: the backdrop blur, the label
       text-shadow and the drop/inset shadow. Over a busy hero — autumn foliage,
       high-frequency and mid-bright — a plain 35% scrim just tints the leaves
       showing through, so the pill edge dissolves and the controls read as part
       of the image. These four additions bring it back in line with .btn-on-image
       (and with the Browse/Votes banner pills, which already use it). */
    background: rgba(17, 18, 24, 0.42);
    border-color: rgba(255, 255, 255, 0.32);
    color: #fff;
    backdrop-filter: blur(14px) saturate(150%);
    -webkit-backdrop-filter: blur(14px) saturate(150%);
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
    box-shadow: 0 6px 22px rgba(0, 0, 0, 0.26), inset 0 1px 0 rgba(255, 255, 255, 0.18);
  }

  /* On a flat banner, the old near-black controls looked disabled. Change Path
     is the useful next action, so it gets a quiet green tint; Edit Paths stays
     neutral and elevated because it is secondary/admin-only. Photo banners
     keep the glass treatment above so the artwork still owns the scene. */
  .season-banner:not(.has-photo) #sbPillsRow .btn-change-path {
    background: rgba(22, 163, 74, .10);
    border-color: rgba(74, 222, 128, .48);
    color: #166534;
    box-shadow: 0 5px 14px rgba(22, 163, 74, .10), inset 0 1px 0 rgba(255, 255, 255, .7);
  }
  html.dark .season-banner:not(.has-photo) #sbPillsRow .btn-change-path {
    background: rgba(22, 163, 74, .15);
    border-color: rgba(74, 222, 128, .48);
    color: #A7F3C0;
    box-shadow: 0 6px 16px rgba(0, 0, 0, .24), inset 0 1px 0 rgba(167, 243, 192, .08);
  }
  .season-banner:not(.has-photo) #sbPillsRow .btn-change-path:hover {
    background: rgba(22, 163, 74, .20);
    border-color: #4ADE80;
    color: #15803D;
    box-shadow: 0 8px 20px rgba(22, 163, 74, .18), inset 0 1px 0 rgba(255, 255, 255, .16);
  }
  html.dark .season-banner:not(.has-photo) #sbPillsRow .btn-change-path:hover {
    color: #D1FAE0;
  }
  .season-banner.has-photo .banner-xp-pill:hover { background: rgba(0, 0, 0, 0.5); border-color: #fff; }

  /* Actions, not labels. The row above mixes two different kinds of thing at
     one weight: the path-type and days chips are LABELS that only have to be
     readable, while Change Path / Edit Paths are the only two CONTROLS on the
     banner. Sharing the 42%-tinted chip recipe left them looking like more of
     the same furniture — nothing said "you can press this", and the eye went
     straight past them to the solid Apply button below, which is the reference
     for what obvious looks like here.

     So they keep the family's shape and blur but get pushed forward: a fill
     opaque enough that no foliage shows through the button (a tint over a
     high-frequency photo is what dissolved the edge in the first place), a
     border bright enough to close the shape along its whole length, and the
     heavier label weight the solid button already uses. Hover goes solid brand
     green — a colour that appears nowhere in a hero photo, so the pressed
     affordance is unmistakable — plus the same 1px lift every other button on
     the page uses. The green is --green-cta (#15803D), not --green (#16A34A):
     the label on it is white, and per the token note at the top of this file
     white on #16A34A is 3.30:1 and fails AA. */
  /* Lit gold outline on dark glass.

     Two earlier versions missed in opposite directions: opaque black landed
     like a system dialog dropped on the artwork, and near-white read as a
     different product's toolbar. What actually belongs on a warm illustrated
     banner is a control that looks lit rather than pasted — a dark glass body
     the scene shows faintly through, ringed in warm gold, with the ring itself
     casting a small glow outward.

     The contrast still comes from the button and not the photo: the body is
     dark enough at any brightness for the cream label, and the ring closes the
     shape along its whole length. The glow is the part doing the work a drop
     shadow used to — it separates the control from the artwork without adding
     weight to it. */
  /* Same glass construction as the chips, but a NEUTRAL ring and no halo.

     Ringing these in gold came from copying the reference literally, and it
     cost the thing the reference didn't have to worry about: hierarchy. A
     mock-up with three pills can afford to light all of them. This row has
     five objects, and gold is already spoken for by Apply to This Path — the
     one action worth crossing the banner for. With everything glowing gold,
     nothing was, and Edit Paths (admin-only, secondary) was shouting exactly
     as loud as the CTA.

     So the controls keep the lit-outline body and drop to a warm-white ring
     with a plain drop shadow. Still obviously pressable, still legible over any
     photo, but now clearly the quieter half of the row. */
  .season-banner.has-photo #sbPillsRow .btn-change-path,
  .season-banner.has-photo #sbPillsRow .btn-edit-paths {
    background: rgba(24, 20, 16, 0.55);
    border: 1.5px solid rgba(255, 250, 240, 0.55);
    color: #FFF6EA;
    font-weight: 800;
    backdrop-filter: blur(16px) saturate(150%);
    -webkit-backdrop-filter: blur(16px) saturate(150%);
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 245, 230, 0.18);
  }
  /* Icon and label the same colour — a gold icon beside a cream label put two
     accents inside one 38px button. */
  .season-banner.has-photo #sbPillsRow .btn-change-path svg,
  .season-banner.has-photo #sbPillsRow .btn-edit-paths svg { color: currentColor; opacity: .92; }
  /* Hover turns the ring up rather than swapping the fill for a solid colour —
     the lit-outline idea only reads if the light is what changes. */
  .season-banner.has-photo #sbPillsRow .btn-change-path:hover,
  .season-banner.has-photo #sbPillsRow .btn-edit-paths:hover {
    background: rgba(44, 38, 30, 0.66);
    border-color: rgba(255, 255, 255, 0.92);
    color: #fff;
    transform: translateY(-1px);
    box-shadow: 0 0 20px -6px rgba(255, 255, 255, 0.5), 0 12px 28px rgba(0, 0, 0, 0.42), inset 0 1px 0 rgba(255, 255, 255, 0.26);
  }
  .season-banner.has-photo #sbPillsRow .btn-change-path:active,
  .season-banner.has-photo #sbPillsRow .btn-edit-paths:active {
    transform: translateY(0);
    box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.38);
  }
  /* Keyboard parity: without this the only way to tell these two apart from the
     label chips is to hover them, which a keyboard user never does. */
  .season-banner.has-photo #sbPillsRow .btn-change-path:focus-visible,
  .season-banner.has-photo #sbPillsRow .btn-edit-paths:focus-visible {
    outline: 2px solid #fff;
    outline-offset: 2px;
  }
  /* Status chips over a photo. Two things were wrong once the buttons beside
     them went opaque. First, contrast: PERMANENT PATH was #C4B5FD over a 22%
     purple tint, so the ground under it was still whatever the season photo
     happened to be — fine over dark foliage, near-invisible over sky or snow.
     Same failure mode the buttons had, same fix: give the chip its own opaque
     ground so the label always has one known background.

     Second, the row needs to say which things are pressable. The buttons are
     dark + WHITE border + white label; the chips are dark + their own ACCENT
     border and label. Colour now carries the label/control split alongside the
     pill-vs-rect shape difference, so neither signal is doing it alone, and
     each path type still keeps the accent that identifies it. */
  /* Status chips over a photo: the Edit Paths treatment, tinted per type.

     The plate version before this went too quiet — a near-white label on dark
     glass, with the accent demoted to the icon, left PERMANENT PATH bleached
     out and unreadable wherever the photo behind it was bright. The lesson from
     the buttons applies here too: a chip cannot borrow its contrast from the
     artwork, it has to bring its own.

     So these adopt the glass-outline recipe that already works on Edit Paths —
     a light tinted fill, a bright 1.5px border closing the shape along its full
     length, a near-white label — with the type's own colour in the fill, the
     border and the icon. Bright enough to read over sky, still a pill rather
     than a rect so it never gets mistaken for a control. */

  /* Now the right-hand group of the title row: days-remaining + Unclaimed XP,
     sharing the title's baseline rather than sitting on a line of their own. */
  .banner-pills-row {
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
    margin-left: auto; margin-bottom: 0;
  }
  /* Collapses to nothing when neither the days pill nor the Unclaimed XP pill is
     showing, so a permanent path with an empty bank doesn't leave dead space. */
  .banner-pills-row:empty, .banner-pills-row.is-empty { display: none; }

  /* Phones: the row stops hugging the right edge and takes the full width, so
     the two actions can split it between them at a 46px tap height instead of
     staying finger-sized-for-a-mouse in the corner. The status chips keep their
     natural width and stay on the line above (the row already wraps), which
     preserves the label/control split the shape difference sets up. */
  @media (max-width: 640px) {
    .banner-pills-row { width: 100%; margin-left: 0; gap: 8px; }
    /* Full width, one per line. Sharing a line with the path chip left ~140px
       for "Change Path", which wrapped the label onto two lines inside a
       fixed-height button. A phone has the vertical room and not the
       horizontal, so spend it here. */
    #sbPillsRow .btn-change-path,
    #sbPillsRow .btn-edit-paths {
      flex: 1 1 100%;
      height: 46px; min-height: 46px;
      font-size: 14px;
      justify-content: center;
      white-space: nowrap;
    }
  }

  .banner-title-row { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; flex-wrap: wrap; }
  .banner-title-row .banner-name { margin-bottom: 0; }

  .sb-path-pill {
    display: inline-flex; align-items: center; gap: 6px;
    border-radius: 50px; padding: 4px 12px;
    font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .07em;
    flex-shrink: 0;
  }
  /* Fixed height (matching .banner-xp-pill) so the two pills in this row
     read as the same size regardless of their different content/icon sizes.
     The label always shows — no more icon-only-until-hover reveal. */
  /* 32px, not 28px. Deliberately still shorter than the 38px action buttons —
     a chip should not compete with a control — but 28px next to 38px read as
     an odd leftover rather than a smaller sibling, and 10px uppercase is below
     what's comfortable for a label people actually need to read. */
  /* Fully rounded, matching the controls beside them. One rounding across the
     row; the label/control split is carried by fill and height instead. */
  .banner-title-row .sb-path-pill,
  .banner-title-row .sb-days-pill { border-radius: 50px; }
  .banner-title-row .sb-path-pill { gap: 7px; padding: 0 15px; height: 34px; font-size: 11px; box-sizing: border-box; }
  .banner-title-row .sb-path-pill svg { width: 13px; height: 13px; flex-shrink: 0; }
  .banner-title-row .sb-days-pill { gap: 7px; padding: 0 14px; height: 34px; font-size: 11px; box-sizing: border-box; }
  .banner-title-row .sb-days-pill svg { width: 13px; height: 13px; flex-shrink: 0; }
  .sb-path-pill-txt { display: inline-block; white-space: nowrap; }
  /* Chips take the same lit-outline construction as the controls: a body the
     scene shows faintly through, a bright ring in the type's own colour, and
     over artwork a small halo of it. Solid fills were the version before this
     — legible, but five saturated lozenges above the season title read as a
     status bar, and this row is meant to sit under the artwork rather than on
     top of it. Outlines carry identity at a fraction of the weight.

     Purple comes back for permanent here. The hue was never the problem — a
     solid violet lozenge fought the warm artwork; the same purple as a lit
     ring sits with it. */
  /* The chip body is context-dependent; only the ring colour is constant.

     Three surfaces exist for these — a light card, a dark card, and a banner
     photo — and one body can't serve all three. The dark warm glass that reads
     as "lit over artwork" turns into a murky brown smear on a white card,
     because a backdrop blur over white is just grey. So the body and the ink
     switch per surface while the ring, the identity, stays put: each type
     declares its ring and its two inks once, and the three surface rules below
     pick from them. */
  .sb-path-pill, .sb-days-pill {
    border: 1.5px solid var(--chip-ring, rgba(255, 255, 255, 0.3));
    background: rgba(255, 255, 255, 0.92);
    color: var(--chip-ink-light, var(--text));
    text-shadow: none;
    box-shadow: 0 3px 10px rgba(0, 0, 0, 0.06), inset 0 1px 0 rgba(255, 255, 255, 0.8);
  }
  html.dark .sb-path-pill, html.dark .sb-days-pill {
    background: rgba(24, 22, 26, 0.78);
    color: var(--chip-ink-dark, #fff);
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.1);
  }
  /* Over artwork: warm glass, and the ring gets a halo. The glow is scoped
     here on purpose — over a photo it's what lifts the ring off a busy scene;
     on a flat card it has nothing to separate from and just reads as neon. */
  .season-banner.has-photo .sb-path-pill,
  .season-banner.has-photo .sb-days-pill {
    background: rgba(26, 18, 8, 0.5);
    color: var(--chip-ink-dark, #fff);
    backdrop-filter: blur(16px) saturate(150%);
    -webkit-backdrop-filter: blur(16px) saturate(150%);
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
    box-shadow: 0 0 14px -7px var(--chip-glow, rgba(255, 255, 255, 0.35)), 0 6px 18px rgba(0, 0, 0, 0.34), inset 0 1px 0 rgba(255, 255, 255, 0.14);
  }
  .sb-path-pill svg, .sb-days-pill svg { color: currentColor; opacity: .9; }
  /* #6D28D9 on white is 7.4:1; the light inks below all clear AA at this size,
     which the old #8B5CF6 (4.4:1) did not. */
  .sb-path-pill.permanent {
    --chip-ring: rgba(74, 222, 128, 0.75);
    --chip-ink-light: #15803D;
    --chip-ink-dark: #CFF7DE;
    --chip-glow: rgba(74, 222, 128, 0.55);
  }
  .sb-path-pill.seasonal {
    --chip-ring: rgba(74, 222, 128, 0.75);
    --chip-ink-light: #15803D;
    --chip-ink-dark: #CFF7DE;
    --chip-glow: rgba(74, 222, 128, 0.55);
  }
  .sb-path-pill.loading {
    background: linear-gradient(90deg, var(--border) 25%, color-mix(in srgb, var(--border) 60%, transparent) 50%, var(--border) 75%);
    background-size: 200% 100%;
    animation: rw-shimmer 1.4s infinite;
    border: 1.5px solid transparent;
    width: 100px;
  }
  .sb-path-pill.loading svg, .sb-path-pill.loading span { visibility: hidden; }
  .sb-days-pill {
    display: inline-flex; align-items: center; gap: 6px;
    border-radius: 50px; padding: 4px 10px;
    font-size: 10px; font-weight: 700; letter-spacing: .4px;
    --chip-ring: rgba(251, 191, 36, 0.8);
    --chip-ink-light: #B45309;
    --chip-ink-dark: #FFEFCC;
    --chip-glow: rgba(251, 191, 36, 0.55);
  }

  /* "Season Ended" — its own solid red pill, not the amber "N days remaining"
     pill with just its text/border tinted (that left a washed-out red-on-amber
     look, since the background never changed). Also reasserted over a photo
     banner, same pattern as .sb-path-pill.permanent above, so it doesn't get
     flattened to the generic translucent-black photo treatment. */
  .sb-days-pill.ended {
    --chip-ring: rgba(248, 113, 113, 0.8);
    --chip-ink-light: #DC2626;
    --chip-ink-dark: #FFDADA;
    --chip-glow: rgba(248, 113, 113, 0.55);
  }

  /* "N rewards to claim" — sits next to Season Ended on a closed path that
     still owes the player something, so it has to read as a prompt (green)
     rather than as more expiry warning. */
  .sb-claim-pill {
    --chip-ring: rgba(74, 222, 128, 0.8);
    --chip-ink-light: #15803D;
    --chip-ink-dark: #CFF7DE;
    --chip-glow: rgba(74, 222, 128, 0.55);
  }

  .banner-name { font-size: 32px; font-weight: 800; color: var(--text); letter-spacing: -0.02em; line-height: 1.1; margin-bottom: 6px; }
  .banner-desc { font-size: 14px; color: var(--text-muted); font-weight: 500; margin-bottom: 10px; line-height: 1.5; }

  .banner-xp-row { display: flex; align-items: center; margin-top: 10px; }
  .banner-level-lbl { font-size: 22px; font-weight: 800; color: var(--text); margin-top: 8px; display: block; }
  .banner-xp-track { position: relative; flex: 1; height: 8px; background: var(--border); border-radius: 50px; overflow: visible; }
  .banner-xp-fill { height: 100%; background: linear-gradient(90deg, #16A34A, #4ADE80); border-radius: 50px; transition: width .6s cubic-bezier(.4,0,.2,1); overflow: hidden; }
  .banner-xp-below { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 10px; flex-wrap: wrap; }
  /* Unclaimed-XP pill + "Apply to this path" button — sits to the right of the
     Level/XP text. This was once hidden outright with display:none !important
     for a cleaner banner, which also made the pool-XP feature unreachable: the
     pill is the ONLY entry point to the apply form, so XP parked in the pool
     (no path selected, path complete, season ended) could never be spent and
     looked lost. Visibility is renderPoolNudge()'s call in rewards.js — it
     shows the pill only when there's a balance to report. */
  .banner-xp-val { font-size: 13px; color: var(--text-muted); font-weight: 600; white-space: nowrap; }
  .banner-total-xp { font-size: 13px; color: var(--text-muted); font-weight: 600; margin-top: 8px; display: flex; align-items: center; gap: 4px; }
  .banner-xp-highlight { color: var(--text); font-weight: 800; }
  .banner-checkin { margin-top: 18px; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
  .btn-checkin { background: var(--green); color: #fff; border: none; border-radius: 12px; padding: 10px 22px; font-size: 13px; font-weight: 700; cursor: pointer; display: inline-flex; align-items: center; gap: 8px; box-shadow: 0 3px 12px rgba(22,163,74,.25); transition: all .15s; }
  .btn-checkin:hover:not(:disabled) { background: #15803d; transform: translateY(-1px); box-shadow: 0 5px 18px rgba(22,163,74,.35); }
  .btn-checkin:disabled { opacity: .6; cursor: not-allowed; transform: none !important; box-shadow: none; }
  .checkin-done { display: none; align-items: center; gap: 7px; font-size: 13px; font-weight: 600; color: var(--green); }
  .checkin-done.show { display: flex; }
  .checkin-reset { font-size: 11px; color: var(--text-muted); display: flex; align-items: center; gap: 5px; }

  /* Over-photo XP value: plain white text (no pill), a touch larger, with a
     soft shadow for legibility. The collected amount is bold and brightest. */
  .banner-content[data-img] .banner-xp-val {
    font-size: 15px;
    color: rgba(255, 255, 255, 0.85);
    font-weight: 600;
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
  }
  /* Total XP banked on a picker card's path. The bar and the figure beside it
     describe the CURRENT tier only, so a path holding 7,650 XP announced itself
     as "150 / 500" — indistinguishable from one barely started, and the reason
     a customer switched off the path holding all their rewards. */
  .picker-xp-total { font-size: 12px; color: var(--text-muted); font-weight: 700; white-space: nowrap; }
  .banner-content[data-img] .picker-xp-total {
    font-size: 13px;
    color: rgba(255, 255, 255, 0.9);
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
  }
  .banner-xp-cur { font-weight: 800; color: inherit; }
  .banner-content[data-img] .banner-xp-cur { color: #fff; }

  /* Inline Apply XP Mode inside banner */
  .banner-normal-content {
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    width: 100%;
    height: 100%;
  }

  /* One object — a gold ticket with a tear-off stub.

     Splitting the count from the action into two loose pills was tried and read
     as two unrelated things; welding a gold button onto a gold-tinted counter
     read as two halves of nothing in particular. A ticket is the shape that
     actually fits what this is: a stub that states what you're holding, a
     perforation, and the part you hand over. The page already speaks this
     language elsewhere (vote tickets), so it's borrowed, not invented.

     The stub is dark glass so the count never depends on the photo behind it,
     and the balance finally gets to be a number rather than one word in a
     sentence: 15px/900 gold over a 9px tracked-out caption, stacked. That also
     retires the baseline problem for good — the two were fighting to share a
     line at different sizes, and stacked type has no shared baseline to miss.
     A gold glow bleeds off the whole pill so it registers as the warm thing on
     the banner even in peripheral vision. */
  .banner-xp-pill-group {
    display: inline-flex;
    align-items: stretch;
    /* 34px was a chip's height for what is the banner's primary action. 46px
       carries the stacked stub and clears the comfortable-target line. */
    height: 46px; box-sizing: border-box;
    /* Dark warm glass, matching the chips and controls — the near-black version
       read as a hole punched in the banner, and the bronze one made the pill
       look like two different metals bolted together. */
    background: rgba(26, 18, 8, 0.55);
    border: 1.5px solid rgba(253, 224, 138, 0.8);
    border-radius: 50px;
    /* 4px of body showing all the way round the action turns the gold half
       from a welded-on panel into a key sitting in its rail. */
    padding: 4px;
    overflow: hidden;
    box-shadow: 0 0 20px -6px rgba(251, 191, 36, 0.55), 0 10px 24px rgba(0, 0, 0, 0.36), inset 0 1px 0 rgba(255, 232, 160, 0.2);
    backdrop-filter: blur(14px) saturate(160%);
    -webkit-backdrop-filter: blur(14px) saturate(160%);
  }
  /* One line: mark, count, label — read in that order, as a sentence. Stacking
     the label under the count was tried and it turned a phrase into a data
     tile, which is the wrong register for something you read once and act on.
     Both spans are the same size so the two share a baseline (see the note on
     .banner-xp-pill-amt below); colour and weight do the ranking. */
  .banner-xp-pill-badge {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 0 15px 0 14px;
    color: #FFE9A8;
  }
  /* images/xp.png is the site's one real XP mark — the same asset checkout and
     the dashboard use, so it stays. But the asset is coral, and a coral square
     inside a gold rail was the single most discordant thing on the banner: a
     small red box that reads as neither XP nor gold.

     The hue-rotate walks coral (~14°) round to the rail's gold (~45°) so the
     mark belongs to the pill it sits in. It's a tint, not a redraw — the mark
     is still the mark, and deleting this one line puts the brand coral back. */
  .banner-xp-pill-icon {
    width: 19px; height: 19px;
    object-fit: contain;
    flex-shrink: 0;
    filter: hue-rotate(32deg) saturate(1.25) brightness(1.12) drop-shadow(0 0 7px rgba(251, 191, 36, 0.45));
  }
  /* Shown in place of the Apply button when this path can't absorb the balance
     (complete / ended / none selected) — the pill without it reads as broken. */
  .banner-xp-hint {
    margin-top: 4px;
    text-align: right;
    font-size: 10px;
    font-weight: 600;
    letter-spacing: .2px;
    color: var(--text-muted);
  }
  /* The tear-off half. Gold keeps the action because gold is the XP colour and
     this is the XP action — a green button here would read as another system's
     control dropped into a gold pill. But gold only takes dark ink (white on
     #FBBF24 is 1.9:1), and flat gold under dark brown ink is exactly what made
     this look like a warning label instead of something you want to press.

     So the fill stops being a flat swatch and becomes struck metal: a
     light-to-deep gold ramp with a bright top edge and a shadowed bottom one.
     Dark ink on struck gold is what dark ink on gold is FOR — a coin, a plaque,
     a key — and the same ink that looked accidental on a flat fill now looks
     intended. The ramp is oversized and parked off to one side, so hover slides
     a brighter band across the metal instead of just swapping colours: the
     background-position move is the sheen, no overlay element needed (the
     pill's overflow:hidden would clip a lift anyway). */
  .banner-xp-apply-btn {
    position: relative;
    overflow: hidden;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    background: linear-gradient(115deg, #F2A420 0%, #FCD34D 38%, #FFF1C2 50%, #FCD34D 62%, #F2A420 100%);
    background-size: 280% 100%;
    background-position: 92% 0;
    border: none;
    /* No divider: the 4px of rail around this half already separates it. */
    border-radius: 50px;
    color: #2A1A02;
    padding: 0 22px;
    font-size: 12.5px;
    font-weight: 900;
    text-transform: uppercase;
    letter-spacing: .06em;
    cursor: pointer;
    transition: background-position .6s cubic-bezier(.4, 0, .2, 1), color .2s ease, box-shadow .2s ease;
    white-space: nowrap;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.65), inset 0 -1px 0 rgba(130, 72, 0, 0.3), 0 3px 14px rgba(251, 191, 36, 0.34);
  }
  .banner-xp-apply-btn:hover {
    background-position: 8% 0;
    color: #1F1200;
    box-shadow: inset 0 2px 0 rgba(255, 255, 255, 0.85), inset 0 -1px 0 rgba(130, 72, 0, 0.26), 0 5px 20px rgba(251, 191, 36, 0.48);
  }
  .banner-xp-apply-btn:active {
    box-shadow: inset 0 2px 6px rgba(130, 72, 0, 0.38);
  }
  .banner-xp-apply-btn::after {
    content: '';
    width: 14px; height: 14px;
    flex-shrink: 0;
    margin-left: 2px;
    background: currentColor;
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='9 18 15 12 9 6'/%3E%3C/svg%3E") center/contain no-repeat;
    mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='9 18 15 12 9 6'/%3E%3C/svg%3E") center/contain no-repeat;
    transition: transform .18s ease;
  }
  /* Same idle light sweep as the wheelspin action, softened for this smaller
     CTA. It makes one pass, pauses, then repeats instead of flashing nonstop. */
  .banner-xp-apply-btn::before {
    content: '';
    position: absolute;
    top: 0; left: -65%;
    width: 42%; height: 100%;
    background: linear-gradient(115deg, transparent, rgba(255, 255, 255, .62), transparent);
    transform: skewX(-22deg);
    pointer-events: none;
    animation: bannerXpApplyShine 3.4s ease-in-out infinite;
  }
  @keyframes bannerXpApplyShine {
    0%   { left: -65%; }
    35%  { left: 130%; }
    100% { left: 130%; }
  }
  .banner-xp-apply-btn:hover::after { transform: translateX(2px); }
  .banner-xp-apply-btn:focus-visible {
    outline: 3px solid rgba(255, 255, 255, 0.9);
    outline-offset: -4px;
  }
  /* The sheen is decoration; the fill and ink carry the meaning without it. */
  @media (prefers-reduced-motion: reduce) {
    .banner-xp-apply-btn { transition: color .2s ease, box-shadow .2s ease; }
    .banner-xp-apply-btn::before { animation: none; display: none; }
  }
  /* Badge text + button label are both white-space:nowrap and the pill has no
     max-width, so at phone widths "300 UNCLAIMED XP" + "APPLY TO THIS PATH"
     together are wider than the screen — with nothing able to shrink, the
     whole banner (and page) overflowed horizontally instead of just this
     pill. Stack badge-over-button instead of side-by-side so each half only
     ever needs to fit its own text on its own line. */
  @media (max-width: 560px) {
    .banner-xp-wrap { flex: 1 1 100%; }
    .banner-xp-pill-group {
      flex-wrap: wrap;
      height: auto;
      width: 100%;
      border-radius: 26px;
    }
    .banner-xp-pill-badge {
      width: 100%;
      justify-content: center;
      min-height: 44px;
      padding: 9px 12px;
    }
    /* Full-width and 50px tall: on a phone this is the one action in the banner
       worth making unmissable, and a stacked half is easy to under-size. The
       perforation turns to run across the ticket rather than down it. */
    .banner-xp-apply-btn {
      width: 100%;
      justify-content: center;
      border-left: none;
      min-height: 50px;
      padding: 9px 12px;
      font-size: 13.5px;
    }
  }

  /* Centered Apply XP mode inside banner */
  .banner-apply-xp-mode {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 14px;
    width: 100%;
    height: 100%;
    padding: 12px 0;
    box-sizing: border-box;
    animation: baxFadeIn .22s cubic-bezier(.4,0,.2,1);
    margin: 0 auto;
    text-align: center;
  }
  @keyframes baxFadeIn {
    from { opacity: 0; transform: translateY(6px); }
    to { opacity: 1; transform: translateY(0); }
  }
  .bax-header {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 6px;
    width: 100%;
    max-width: 440px;
  }
  .bax-title {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 16px;
    font-weight: 800;
    color: var(--text);
  }
  .season-banner.has-photo .bax-title { color: #fff; text-shadow: 0 1px 3px rgba(0,0,0,0.5); }
  .bax-avail-badge {
    font-size: 14px;
    font-weight: 800;
    padding: 0;
    background: transparent;
    color: #FCD34D;
    border: none;
    text-transform: uppercase;
    letter-spacing: .45px;
    text-shadow: 0 1px 3px rgba(0,0,0,.8), 0 0 12px rgba(251,191,36,.28);
  }

  .bax-body {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 12px;
    width: 100%;
    max-width: 440px;
  }
  /* Input + "USE MAX" merged into one bordered pill, split by a divider —
     same construction as the header's Unclaimed XP / Apply-to-path pill —
     instead of the button floating on top of an opaque white input box. */
  .bax-input-row {
    display: flex;
    align-items: stretch;
    width: 100%;
    background: rgba(0, 0, 0, 0.28);
    border: 1.5px solid rgba(255, 255, 255, 0.16);
    border-radius: 50px;
    overflow: hidden;
    transition: border-color .18s;
  }
  .bax-input-row:focus-within { border-color: rgba(255, 255, 255, 0.4); }
  .bax-input-row input {
    flex: 1;
    min-width: 0;
    background: none;
    border: none;
    padding: 12px 8px 12px 18px;
    font-family: inherit;
    font-size: 14px;
    font-weight: 700;
    color: #fff;
    -moz-appearance: textfield;
    appearance: textfield;
  }
  .bax-input-row input::placeholder { color: rgba(255,255,255,.5); }
  .bax-input-row input::-webkit-outer-spin-button,
  .bax-input-row input::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
  }
  .bax-input-row input:focus { outline: none; }
  .xpb-max-pill {
    flex-shrink: 0;
    background: none;
    border: none;
    border-left: 1.5px solid rgba(255, 255, 255, 0.16);
    color: #fff;
    padding: 0 18px;
    font-size: 11px;
    font-weight: 800;
    cursor: pointer;
    white-space: nowrap;
    transition: background .18s, color .18s;
    text-transform: uppercase;
    letter-spacing: .5px;
  }
  .xpb-max-pill:hover { background: rgba(255, 255, 255, 0.14); }

  /* Back + Apply sit side by side — same Cancel/Next pattern as the
     dashboard's character-sync step (.hero-login-btn-row): a shrink-to-fit
     secondary action beside a primary one that fills the rest of the row. */
  .bax-actions { display: flex; align-items: center; gap: 10px; width: 100%; }
  .bax-back-btn {
    flex-shrink: 0;
    background: rgba(0, 0, 0, 0.2);
    border: 1.5px solid rgba(255, 255, 255, 0.18);
    color: rgba(255, 255, 255, 0.75);
    border-radius: 12px;
    padding: 12px 22px;
    font-size: 13px;
    font-weight: 700;
    cursor: pointer;
    white-space: nowrap;
    transition: all .18s;
  }
  .bax-back-btn:hover { border-color: rgba(255,255,255,.4); color: #fff; background: rgba(0,0,0,.32); }

  .xpb-submit-btn {
    flex: 1;
    background: #FBBF24;
    color: #2D1B00;
    border: 1.5px solid #FDE68A;
    border-radius: 12px;
    padding: 12px 24px;
    font-size: 14px;
    font-weight: 800;
    cursor: pointer;
    transition: background .18s, color .18s, transform .15s, box-shadow .18s;
    letter-spacing: .3px;
    box-shadow: 0 4px 14px rgba(251,191,36,.28), 0 3px 12px rgba(0,0,0,.22);
  }
  .xpb-submit-btn:hover:not(:disabled) {
    background: #FDE68A;
    color: #211300;
    box-shadow: 0 6px 18px rgba(251,191,36,.38), 0 3px 12px rgba(0,0,0,.22);
  }
  .xpb-submit-btn:active:not(:disabled) { transform: translateY(1px); }
  .xpb-submit-btn:focus-visible { outline: 3px solid rgba(255,255,255,.9); outline-offset: 2px; }
  .xpb-submit-btn:disabled {
    opacity: .65;
    cursor: not-allowed;
    box-shadow: none;
  }

  /* Centered Next Reward panel inside the right banner card */
  .banner-right {
    position: absolute; right: 0; top: 0; bottom: 0;
    width: 240px;
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
    padding: 18px; box-sizing: border-box;
    background: rgba(255, 255, 255, 0.88);
    border-left: 1px solid rgba(0, 0, 0, 0.08);
    overflow: hidden;
    border-top-right-radius: 18px;
    border-bottom-right-radius: 18px;
  }
  .season-banner.has-photo .banner-right {
    background: rgba(15, 15, 18, 0.65);
    backdrop-filter: blur(12px);
    border-left: 1px solid rgba(255, 255, 255, 0.12);
  }
  .banner-next-lbl {
    font-size: 11px;
    font-weight: 800;
    color: #4b5563;
    letter-spacing: 1.2px;
    text-transform: uppercase;
    white-space: nowrap;
    text-align: center;
    margin-bottom: 2px;
  }
  .season-banner.has-photo .banner-next-lbl { color: rgba(255, 255, 255, 0.7); text-shadow: 0 1px 2px rgba(0,0,0,0.5); }
  /* Icon stacked above the name/level, both centered — the old side-by-side
     row squeezed the name against the icon inside this panel's fixed 240px
     width, forcing longer reward names to truncate. Stacking gives the text
     the full panel width to sit on its own line(s) instead. */
  .banner-next-body {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    justify-content: center;
    margin-top: 2px;
    width: 100%;
  }
  .banner-next-icon-wrap {
    width: 60px;
    height: 60px;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
  }
  .banner-next-icon-wrap img {
    max-width: 58px;
    max-height: 58px;
    width: auto !important;
    height: auto !important;
    object-fit: contain;
    filter: drop-shadow(0 4px 10px rgba(0, 0, 0, 0.4));
  }
  .banner-next-icon {
    font-size: 38px;
    line-height: 1;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .banner-next-info {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    min-width: 0;
    max-width: 100%;
  }
  .banner-next-name {
    font-size: 15px;
    font-weight: 800;
    color: #111827;
    line-height: 1.25;
    text-align: center;
    overflow-wrap: break-word;
  }
  .season-banner.has-photo .banner-next-name { color: #ffffff; text-shadow: 0 1px 3px rgba(0,0,0,0.6); }
  .banner-next-level {
    font-size: 13px;
    font-weight: 600;
    color: #6b7280;
    text-align: center;
  }
  .season-banner.has-photo .banner-next-level { color: rgba(255, 255, 255, 0.75); text-shadow: 0 1px 2px rgba(0,0,0,0.5); }

  /* Over-photo reward panel: dark frosted glass with light text, so the
     photo shows through and the copy reads (matches the reference). */
  .banner-content[data-img] .banner-right {
    background: rgba(18, 18, 22, 0.66);
    border-left: 1px solid rgba(255, 255, 255, 0.14);
  }
  .banner-content[data-img] .banner-next-lbl { color: rgba(255, 255, 255, 0.72); }
  .banner-content[data-img] .banner-next-name { color: #fff; }
  .banner-content[data-img] .banner-next-level { color: rgba(255, 255, 255, 0.6); }
  html.dark .banner-right {
    background: rgba(18, 18, 22, 0.85);
    border-left: 1px solid rgba(255, 255, 255, 0.08);
  }
  html.dark .banner-next-lbl {
    color: rgba(255, 255, 255, 0.65);
  }
  html.dark .banner-next-name {
    color: #ffffff;
  }
  html.dark .banner-next-level {
    color: rgba(255, 255, 255, 0.5);
  }

  /* Path picker cards — same banner, plus a "Choose"/"Active"/"Ended"
     button centered in the right panel (no Next Reward preview here).
     Drawn a little smaller than the real season banner so they read as
     "options to pick" rather than another full-size banner. */
  .picker-banner { margin: 0; min-height: 170px; }
  .picker-banner .banner-scene { min-height: 170px; }
  .picker-banner .banner-content { padding: 22px 28px; }
  .picker-banner .banner-name { font-size: 22px; margin-bottom: 4px; }
  .picker-banner .banner-desc { margin-bottom: 10px; }
  .picker-banner.picker-clickable { cursor: pointer; transition: transform .15s, box-shadow .15s; }
  .picker-banner.picker-clickable:hover { transform: translateY(-2px); box-shadow: 0 10px 28px rgba(0,0,0,.14); }
  .picker-banner.picker-clickable:active { transform: translateY(0); }
  /* Theme-aware by default — most path cards have no banner image, so this
     sits directly on var(--card-bg) (white in light mode). The white-text/
     transparent look further down is only safe on top of the dark photo
     scrim (.banner-content[data-img]) and used to apply everywhere, which
     made "Select" invisible on any path without a banner image in light mode
     — it only looked fine in dark mode because --card-bg is dark there too. */
  .picker-choose-btn {
    background: var(--chip, rgba(0,0,0,.06)); color: var(--text); border: 1.5px solid var(--border);
    border-radius: 12px;
    padding: 8px 18px; font-size: 12.5px; font-weight: 800; letter-spacing: .01em;
    cursor: pointer; display: inline-flex; align-items: center; justify-content: center; gap: 6px;
    min-width: 100px; margin: 0 auto; text-align: center;
    box-shadow: none;
    transition: all .15s;
  }
  .picker-choose-btn:hover:not(:disabled) { border-color: var(--green); color: var(--green); transform: translateY(-1px); }
  .picker-choose-btn:active:not(:disabled) { transform: translateY(0); }
  .picker-choose-btn.is-active { background: rgba(74, 222, 128, .18); color: #4ADE80; border: 1.5px solid rgba(74, 222, 128, .5); box-shadow: none; cursor: default; }
  .picker-choose-btn.is-ended { background: var(--chip, rgba(0,0,0,.06)); color: var(--text-muted); border: 1.5px solid var(--border); box-shadow: none; cursor: not-allowed; }

  /* Photo-background cards: same glass-pill recipe as .sb-path-pill so it
     reads as an actual button against a busy scene, not stray caption text. */
  .banner-content[data-img] .picker-choose-btn {
    background: rgba(26, 18, 8, 0.5); color: #fff; border: 1.5px solid rgba(255,255,255,.3);
    backdrop-filter: blur(16px) saturate(150%); -webkit-backdrop-filter: blur(16px) saturate(150%);
    text-shadow: 0 1px 3px rgba(0,0,0,.5);
  }
  .banner-content[data-img] .picker-choose-btn:hover:not(:disabled) { background: rgba(74,222,128,.22); border-color: rgba(74,222,128,.6); color: #fff; }
  .banner-content[data-img] .picker-choose-btn.is-ended { background: rgba(255, 255, 255, .08); color: rgba(255, 255, 255, .55); border: 1.5px solid rgba(255, 255, 255, .2); }
  .picker-completed {
    display: flex; flex-direction: column; align-items: center; gap: 8px;
    background: none; border: none; padding: 4px; cursor: pointer; transition: transform .15s;
  }
  .picker-completed:hover { transform: translateY(-1px); }
  .picker-completed:hover .picker-completed-icon { background: rgba(74, 222, 128, .28); border-color: rgba(74, 222, 128, .7); }
  .picker-completed-icon {
    width: 52px; height: 52px; border-radius: 50%;
    background: rgba(74, 222, 128, .18); border: 1.5px solid rgba(74, 222, 128, .5);
    display: flex; align-items: center; justify-content: center;
    transition: all .15s;
  }
  .picker-completed-lbl { font-size: 14px; font-weight: 800; color: #4ADE80; letter-spacing: .01em; }
  @media (max-width: 640px) {
    /* At this width .banner-right is a COLUMN (see the stacking block below),
       so the button's old `flex: 0 0 100%` resolved against the main axis — the
       HEIGHT — and `flex-wrap: wrap` then pushed it into a second column: Select
       came out as a full-height slab standing beside "Path Completed" instead of
       a button under it. Size it from its content and set the width explicitly. */
    .picker-banner .banner-right { flex-wrap: nowrap; }
    .picker-choose-btn {
      flex: 0 0 auto;
      width: 100%; box-sizing: border-box;
      min-height: 44px;
      justify-content: center; padding: 10px 18px; font-size: 13px;
    }
  }

  /* Stack the banner vertically once there isn't room for the left copy + right reward preview side by side */
  @media (max-width: 640px) {
    .season-banner, .banner-scene { min-height: 140px; }
    .banner-content { flex-direction: column; align-items: stretch; gap: 10px; padding: 16px; }
    .banner-content[data-img] { margin: 0; padding: 16px; width: 100%; }
    .banner-left, .banner-content[data-img] .banner-left { padding-right: 0; margin-right: 0; }
    .banner-name { font-size: 20px; margin-bottom: 2px; }
    .banner-desc { margin-bottom: 6px; }
    .banner-level-lbl { margin-top: 8px; }
    .banner-right, .banner-content[data-img] .banner-right {
      position: static;
      width: 100%;
      height: auto;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      gap: 8px;
      border-left: none;
      /* Stacked under the copy it sits on the busiest part of the artwork, and
         a bare hairline over a photo left the reward icon and its name
         competing with whatever was behind them. Its own dark panel instead —
         the same glass recipe the over-photo controls use. */
      border: 1px solid rgba(255,255,255,.10);
      border-radius: 14px;
      padding: 14px 12px;
      margin-top: 12px;
      background: rgba(10,10,12,.62);
      backdrop-filter: blur(10px) saturate(140%);
      -webkit-backdrop-filter: blur(10px) saturate(140%);
    }
    .banner-next-lbl { margin-bottom: 0; }
    .banner-next-icon-wrap {
      width: 52px;
      height: 52px;
      margin: 0;
    }
    .banner-next-icon-wrap img {
      max-width: 47px;
      max-height: 47px;
    }
    .banner-next-icon { font-size: 22px; }
    .banner-next-name { max-width: 220px; margin-bottom: 0; }
    .banner-next-level { font-size: 11px; }
  }

  /* ── Tier track ── */
  .tier-track-outer {
    background: var(--card-bg);
    border: 1.5px solid var(--border);
    border-radius: 20px;
    padding: 20px 20px 16px;
  }

  .tier-track-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 10px;
    margin-bottom: 20px;
  }
  .tier-track-header > div:last-child { flex-wrap: wrap; row-gap: 8px; }
  .tier-track-title {
    display: flex; align-items: center; gap: 8px;
    font-size: 22px; font-weight: 900; color: var(--text);
  }
  .tier-total-badge {
    display: inline-flex; align-items: center; gap: 6px;
    font-size: 16px; font-weight: 800; letter-spacing: 0;
    background: rgba(61,139,106,0.12); color: #16A34A; border: none;
    border-radius: 50px; padding: 5px 16px; transition: all 0.3s;
  }
  .tier-total-badge-dot {
    width: 7px; height: 7px; border-radius: 50%;
    background: #16A34A; flex-shrink: 0;
  }
  html.dark .tier-total-badge { background: rgba(61,139,106,0.12); color: #16A34A; }
  html.dark .tier-total-badge-dot { background: #16A34A; }
  .tier-total-badge.no-path,
  html.dark .tier-total-badge.no-path {
    background: var(--border);
    color: var(--text-soft);
  }
  .ws-tier-badge {
    display: inline-flex; align-items: center; gap: 6px;
    font-size: 16px; font-weight: 800; letter-spacing: 0.02em;
    border-radius: 50px; padding: 5px 16px;
  }
  .ws-tier-badge.bronze { background: rgba(205,127,50,.14); color: #cd7f32; }
  .ws-tier-badge.silver { background: rgba(148,163,184,.18); color: #64748b; }
  .ws-tier-badge.gold { background: rgba(245,158,11,.14); color: #f59e0b; }
  .ws-tier-badge.plat { background: linear-gradient(135deg, rgba(59,130,246,.16), rgba(139,92,246,.16), rgba(236,72,153,.16)); color: #8b5cf6; }
  .ws-tier-badge.none { background: var(--border); color: var(--text-muted); }
  html.dark .ws-tier-badge.silver { color: #94a3b8; }

  .pnl-tabs-group { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

  /* Fragments pill in the wheelspin action row — same pill shape/height as the
     tier badge (#wsTierBadge, now in #pnlActionsWs to the left of Spin
     History) and #wsHistoryBtn, pinned to an explicit height rather than
     relying on matching padding+font metrics across three different elements
     to land on the same pixel height. */
  .ws-frag-pill {
    display: inline-flex; align-items: center; gap: 6px;
    background: var(--bg); border: 1.5px solid var(--border);
    color: var(--text); font-size: 13px; font-weight: 800;
    border-radius: 50px; padding: 0 14px;
  }
  .ws-frag-pill-icon { font-size: 14px; line-height: 1; }
  /* 36px, matching the new .btn-change-path/.btn-edit-paths body size — the row
     pins all four to one height, so raising the buttons without raising this
     would have clamped them straight back to 32px. */
  #pnlActionsWs .ws-tier-badge,
  #pnlActionsWs .ws-frag-pill,
  #pnlActionsWs .btn-change-path,
  #pnlActionsWs .btn-edit-paths {
    height: 36px; min-height: 36px; box-sizing: border-box; padding-top: 0; padding-bottom: 0;
  }
  /* Phones: Spin History / Edit Wheelspin split the row at a proper tap height
     rather than staying mouse-sized. */
  @media (max-width: 640px) {
    #pnlActionsWs .btn-change-path,
    #pnlActionsWs .btn-edit-paths {
      flex: 1 1 140px; height: 44px; min-height: 44px; white-space: nowrap;
    }
  }
  /* Same solid green as the "Collect" CTA (.bp-detail-claim). Text follows
     the page theme: light ink in light mode, dark ink in dark mode. */
  .claim-ready-pill {
    display: inline-flex; align-items: center; gap: 7px;
    font-size: 13px; font-weight: 800; color: #fff;
    background: #16A34A; border-radius: 50px; padding: 7px 16px;
    cursor: pointer; border: none;
    box-shadow: 0 4px 12px rgba(16,185,129,.15);
    animation: claimPillPulse 2.5s ease-in-out infinite;
  }
  .claim-ready-pill:hover { background: #15803D; }
  html.dark .claim-ready-pill { color: #0b0b0d; }
  @keyframes claimPillPulse { 0%,100%{box-shadow:0 0 0 0 rgba(22,163,74,.35)} 50%{box-shadow:0 0 0 6px rgba(22,163,74,0)} }

  /* ── Right sidebar: Upcoming Reward + Your Rewards list ── */
  .upcoming-card {
    background: var(--card-bg); border: 1.5px solid var(--border); border-radius: 16px;
    padding: 20px; display: flex; flex-direction: column; align-items: center; gap: 12px;
  }
  /* #bannerNextReward is reused as-is from the banner's old "Next Reward"
     panel (same id, same .banner-next-* classes JS already fills in) — it
     just lives in a plain card now instead of an absolute dark/light strip,
     so it needs its own (non-absolute, centered) box here. */
  .upcoming-card #bannerNextReward {
    display: flex; flex-direction: column; align-items: center; text-align: center; width: 100%;
  }
  .upcoming-card .banner-next-name { max-width: 100%; white-space: normal; }
  .upc-view-btn {
    width: 100%; background: var(--bg); border: 1.5px solid var(--border); color: var(--text-soft);
    font-family: inherit; font-size: 12.5px; font-weight: 700; border-radius: 12px;
    padding: 9px 14px; cursor: pointer; transition: all .15s;
  }
  .upc-view-btn:hover { border-color: var(--green); color: var(--green); }

  .rewards-list-card { background: var(--card-bg); border: 1.5px solid var(--border); border-radius: 16px; padding: 18px; }
  .rewards-list-title { font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: .6px; color: var(--text-muted); margin-bottom: 12px; }
  .rewards-list-body { display: flex; flex-direction: column; gap: 8px; max-height: 460px; overflow-y: auto; }
  .rl-row {
    display: flex; align-items: center; gap: 10px;
    padding: 8px; border-radius: 12px;
    background: var(--bg); border: 1.5px solid transparent;
    transition: border-color .15s, background .15s;
  }
  .rl-row.claimable { border-color: rgba(34,197,94,.35); background: rgba(34,197,94,.06); }
  .rl-icon {
    width: 36px; height: 36px; flex-shrink: 0; border-radius: 10px;
    background: var(--card-bg); display: flex; align-items: center; justify-content: center; overflow: hidden;
  }
  .rl-icon img { width: 26px !important; height: 26px !important; object-fit: contain; }
  .path-reward-icon-wrap { position:relative;display:inline-grid;place-items:center;line-height:1; }
  .path-reward-star-badge { position:absolute;left:-7px;bottom:-6px;z-index:2;width:22px;height:22px;display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;border-radius:7px;line-height:0;color:var(--pp-star-icon,#F5B301);background:var(--pp-star-bg,#4D3B05);border:2px solid var(--pp-star-border,#F5B301);box-shadow:var(--pp-star-shadow,0 0 0 2px rgba(15,15,17,.72),0 4px 12px rgba(0,0,0,.58)); }
  .path-reward-star-badge svg { display:block;margin:0!important;vertical-align:0!important; }
  .path-reward-star-badge.compact { width:18px;height:18px;left:-6px;bottom:-5px;border-radius:6px; }
  .path-reward-star-badge.compact .path-reward-star-svg { width:10px;height:10px;display:block; }
  .path-reward-recipe-badge { position:absolute;left:-7px;bottom:-6px;z-index:2;width:22px;height:22px;display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;border-radius:7px;line-height:0;color:var(--pp-recipe-icon,#60A5FA);background:var(--pp-recipe-bg,#102A4F);border:2px solid var(--pp-recipe-border,#3B82F6);box-shadow:var(--pp-recipe-shadow,0 0 0 2px rgba(15,15,17,.72),0 4px 12px rgba(0,0,0,.58)); }
  .path-reward-recipe-badge svg { display:block;margin:0!important;vertical-align:0!important; }
  body.dark .path-reward-recipe-badge { color:var(--pp-recipe-icon,#60A5FA);background:var(--pp-recipe-bg,#102A4F);border-color:var(--pp-recipe-border,#3B82F6); }
  .path-reward-recipe-badge.after-star { left:19px; }
  .path-reward-recipe-badge.compact { width:18px;height:18px;left:-6px;bottom:-5px;border-radius:6px; }
  .path-reward-recipe-badge.compact.after-star { left:15px; }
  .path-reward-recipe-badge.compact .path-reward-recipe-svg { width:10px;height:10px; }
  .path-reward-style-badge { position:absolute;left:-7px;bottom:-6px;z-index:2;width:22px;height:22px;display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;border-radius:7px;line-height:0;color:var(--pp-style-icon,#F0ABFC);background:var(--pp-style-bg,#3B174F);border:2px solid var(--pp-style-border,#D946EF);box-shadow:var(--pp-style-shadow,0 0 0 2px rgba(15,15,17,.72),0 4px 12px rgba(0,0,0,.58)); }
  .path-reward-style-badge svg { display:block;margin:0!important;vertical-align:0!important; }
  .path-reward-style-badge.after-star { left:19px; }
  .path-reward-style-badge.compact { width:18px;height:18px;left:-6px;bottom:-5px;border-radius:6px; }
  .path-reward-style-badge.compact.after-star { left:15px; }
  .path-reward-style-badge.compact .path-reward-style-svg { width:10px;height:10px; }
  .ws-pcard-ico,#rpRIco { position:relative; }
  .wheel-recipe-badge { position:absolute;left:-5px;bottom:-5px;width:20px;height:20px;border-radius:7px;display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;line-height:0;color:var(--pp-recipe-icon,#60A5FA);background:var(--pp-recipe-bg,#102A4F);border:2px solid var(--pp-recipe-border,#3B82F6);box-shadow:var(--pp-recipe-shadow,0 0 0 2px rgba(15,15,17,.72),0 4px 12px rgba(0,0,0,.58)); }
  .wheel-recipe-badge svg { width:11px;height:11px;display:block;margin:0!important;vertical-align:0!important; }
  body.dark .wheel-recipe-badge { color:var(--pp-recipe-icon,#60A5FA);background:var(--pp-recipe-bg,#102A4F);border-color:var(--pp-recipe-border,#3B82F6); }
  .wheel-recipe-badge.result { left:0;bottom:0; }
  .wheel-style-badge { position:absolute;left:-5px;bottom:-5px;width:20px;height:20px;border-radius:7px;display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;line-height:0;color:var(--pp-style-icon,#F0ABFC);background:var(--pp-style-bg,#3B174F);border:2px solid var(--pp-style-border,#D946EF);box-shadow:var(--pp-style-shadow,0 0 0 2px rgba(15,15,17,.72),0 4px 12px rgba(0,0,0,.58)); }
  .wheel-style-badge svg { width:11px;height:11px;display:block;margin:0!important;vertical-align:0!important; }
  .wheel-style-badge.result { left:0;bottom:0; }
  .rl-info { flex: 1; min-width: 0; }
  .rl-lvl { font-size: 10px; font-weight: 800; text-transform: uppercase; letter-spacing: .4px; color: var(--text-muted); }
  .rl-name { font-size: 13px; font-weight: 800; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .rl-qty { font-size: 11px; color: var(--text-muted); font-weight: 600; }
  .rl-claim-btn {
    flex-shrink: 0; background: var(--green); color: #fff; border: none; border-radius: 12px;
    padding: 7px 14px; font-family: inherit; font-size: 11px; font-weight: 800; cursor: pointer; transition: background .15s;
  }
  .rl-claim-btn:hover { background: #15803d; }
  .rl-claimed { flex-shrink: 0; width: 26px; height: 26px; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: var(--green); background: var(--green-light); }

  /* ── Battle-pass reward track ── */
  .bp-wrap { position: relative; }
  .bp-track { position: relative; display: flex; align-items: center; gap: 6px; }
  .bp-strip {
    position: relative;
    display: flex; gap: 12px;
    overflow-x: auto; overflow-y: hidden;
    scroll-behavior: smooth;
    padding: 8px 4px 6px;
    flex: 1; min-width: 0;
    scrollbar-width: none; -ms-overflow-style: none;
  }
  .bp-strip::-webkit-scrollbar { display: none; }

  .bp-col {
    flex: 0 0 auto; width: 112px;
    position: relative;
  }

  /* Reward + level, one card — the level label used to be a separate pill
     sitting below the tile; it's now just another line of text inside the
     same bordered box. No colour tint for claimed/claimable (that's what
     the bottom progress bar + checkmark badge are for) — the card itself
     stays neutral in every state except "selected". */
  .bp-tile {
    width: 112px; height: 136px;
    background: var(--bg);
    border: 2px solid var(--border);
    border-radius: 8px;
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
    cursor: pointer; position: relative; overflow: hidden;
    padding-bottom: 8px;
    transition: transform .18s ease, border-color .2s, box-shadow .2s;
  }
  .bp-tile:hover { transform: translateY(-2px); }
  .bp-tile-icon {
    height: 54px; display: flex; align-items: center; justify-content: center;
    font-size: 40px; line-height: 1;
  }
  .bp-tile-icon img { width: 54px !important; height: 54px !important; object-fit: contain; }
  .bp-tile-qty { font-size: 13px; font-weight: 800; color: var(--text-muted); }
  .bp-tile-lvl { font-size: 11px; font-weight: 800; color: var(--text-muted); letter-spacing: .01em; }

  .bp-tile-badge {
    position: absolute; top: 6px; right: 6px;
    width: 20px; height: 20px; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
  }
  .bp-tile.claimed .bp-tile-badge { background: var(--green); color: #fff; }
  /* Gentle scale pulse on the current level's reward image — no glow, just
     motion, so it reads as "about to be yours" without the ugly halo. */
  .bp-col.current .bp-tile-icon {
    animation: bpIconPulse 2s ease-in-out infinite;
  }
  @keyframes bpIconPulse {
    0%, 100% { transform: scale(1); }
    50%      { transform: scale(1.07); }
  }
  @media (prefers-reduced-motion: reduce) {
    .bp-col.current .bp-tile-icon { animation: none; }
  }
  .bp-tile.locked { opacity: .62; }
  .bp-tile.locked .bp-tile-icon { filter: grayscale(.35); }
  .bp-tile.locked .bp-tile-badge { color: var(--text-muted); }
  /* Unclaimed / Ready to claim reward tile — no border tint (with several
     claimable at once it read as a wall of green outlines), just the reward
     image gently pulsing to draw the eye. */
  .bp-tile.claimable .bp-tile-icon {
    animation: claimableIconPulse 1.8s ease-in-out infinite;
  }
  @keyframes claimableIconPulse {
    0%, 100% { transform: scale(1); }
    50%      { transform: scale(1.1); }
  }

  /* Selected tile gets a plain green border, no glow/float */
  .bp-tile.selected {
    border-color: var(--green, #16A34A);
  }
  html.dark .bp-tile.selected {
    border-color: #4ADE80;
  }

  /* Bottom-edge progress bar — the tile's own completion state: empty/grey
     while locked, a partial fill for the level you're actively working
     toward, and a solid green bar once a level is fully reached. */
  .bp-tile-bar {
    position: absolute; left: 0; right: 0; bottom: 0; height: 4px;
    background: rgba(148,163,184,.22);
  }
  .bp-tile-bar-fill {
    height: 100%; width: 0%;
    background: linear-gradient(90deg, #16A34A, #4ADE80);
    transition: width .6s cubic-bezier(.4,0,.2,1);
  }

  /* Scroll track arrow with sliding hover & pulse effect */
  .bp-arrow {
    flex: 0 0 auto;
    width: 28px; height: 28px; border-radius: 0;
    background: none; border: none;
    color: var(--text-muted); cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    transition: color .18s,
                opacity .32s cubic-bezier(.16,1,.3,1), transform .25s cubic-bezier(.16,1,.3,1);
  }
  .bp-arrow:hover:not(:disabled) { color: var(--green, #16A34A); }
  .bp-arrow:disabled { opacity: .3; cursor: default; }

  /* Right arrow sliding effect on hover & pulse when more items available */
  .bp-arrow-r {
    transition: color .18s, opacity .32s cubic-bezier(.16,1,.3,1), transform .25s cubic-bezier(.16,1,.3,1);
  }
  .bp-arrow-r:hover:not(:disabled) {
    color: var(--green, #16A34A);
    transform: translateX(5px) scale(1.1);
  }
  .bp-arrow-r.bp-arrow-more:not(:disabled) {
    animation: arrowSlideRight 2s ease-in-out infinite;
  }
  @keyframes arrowSlideRight {
    0%, 100% { transform: translateX(0); }
    50%      { transform: translateX(6px); }
  }

  /* Left arrow sliding effect on hover & pulse when scrolled right */
  .bp-arrow-l {
    transition: color .18s, opacity .32s cubic-bezier(.16,1,.3,1), transform .25s cubic-bezier(.16,1,.3,1);
  }
  .bp-arrow-l:hover:not(:disabled) {
    color: var(--green, #16A34A);
    transform: translateX(-5px) scale(1.1);
  }
  .bp-arrow-l.bp-arrow-more:not(:disabled) {
    animation: arrowSlideLeft 2s ease-in-out infinite;
  }
  @keyframes arrowSlideLeft {
    0%, 100% { transform: translateX(0); }
    50%      { transform: translateX(-6px); }
  }
  @media (prefers-reduced-motion: reduce) {
    .bp-arrow-r.bp-arrow-more:not(:disabled), .bp-arrow-l.bp-arrow-more:not(:disabled) { animation: none; }
    .bp-tile.claimable .bp-tile-icon { animation: none; }
  }

  /* Featured detail card (borrowing the big-card polish) */
  .bp-detail {
    margin-top: 16px;
    background: var(--bg);
    border: 1.5px solid var(--border);
    border-radius: 16px;
    padding: 16px 20px;
    display: flex; align-items: center; gap: 16px;
    transition: border-color .25s, box-shadow .25s, background .25s;
  }
  .bp-detail.claimable {
    border-color: var(--green);
    background: rgba(22,163,74,.05);
    box-shadow: 0 0 0 3px rgba(22,163,74,.1);
  }
  .bp-detail.claimed { border-color: var(--green-border); background: rgba(22,163,74,.04); }
  html.dark .bp-detail.claimable { background: rgba(22,163,74,.1); }
  .bp-detail-icon {
    width: 54px; height: 54px; flex-shrink: 0;
    display: flex; align-items: center; justify-content: center;
    font-size: 38px;
  }
  .bp-detail-icon img { width: 54px !important; height: 54px !important; object-fit: contain; }
  .bp-detail-info { flex: 1; min-width: 0; }
  .bp-detail-level {
    font-size: 11px; font-weight: 800; color: var(--text-muted);
    text-transform: uppercase; letter-spacing: .6px;
  }
  .bp-detail-name { font-size: 19px; font-weight: 900; color: var(--text); line-height: 1.2; margin: 2px 0 1px; }
  .bp-detail-qty { font-size: 13px; font-weight: 700; color: var(--text-soft); }
  /* Same solid green/roundness as the site's other primary CTA (Express
     Checkout, .sidebar-action-btn.primary-btn in votes.css) — one shared
     "go" button style instead of every page inventing its own green. */
  .bp-detail-claim {
    flex-shrink: 0; min-width: 220px; text-align: center;
    background: #16A34A; color: #fff; border: none;
    border-radius: 12px; padding: 13px 30px; font-size: 14px; font-weight: 800;
    cursor: pointer; white-space: nowrap;
    box-shadow: 0 4px 12px rgba(16,185,129,.15);
    transition: all .15s;
  }
  .bp-detail-claim:hover { background: #15803D; box-shadow: 0 6px 18px rgba(16,185,129,.25); transform: translateY(-1px); }
  html.dark .bp-detail-claimed { color: #0b0b0d; }
  .bp-detail-locked {
    flex-shrink: 0; display: flex; align-items: center; gap: 7px;
    font-size: 13px; font-weight: 800; padding: 11px 22px; border-radius: 12px; white-space: nowrap;
    color: var(--text-muted); background: var(--bg); border: 1.5px solid var(--border);
  }
  /* Same solid green + 12px-radius CTA style as .bp-detail-claim — this pill used
     to be a light-tint rounded pill, which read as a different, lower-priority
     button next to the "Collect" state instead of just that state's next step. */
  .bp-detail-claimed {
    flex-shrink: 0; display: flex; align-items: center; gap: 7px;
    font-size: 13px; font-weight: 800; padding: 13px 22px; border-radius: 12px; white-space: nowrap;
    color: #fff; background: #16A34A; border: none;
  }

  /* Collected state — the "Collected" pill plus a quick jump to the
     dashboard's Rewards panel, so a claim doesn't dead-end here. */
  .bp-detail-collected-wrap { flex-shrink: 0; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }
  .bp-detail-view-btn {
    display: inline-flex; align-items: center; gap: 7px;
    font-size: 13.5px; font-weight: 800; color: var(--text-soft);
    background: var(--bg); border: 1.5px solid var(--border); border-radius: 12px;
    padding: 13px 22px; text-decoration: none; white-space: nowrap;
    transition: all .15s;
  }
  .bp-detail-view-btn:hover { border-color: var(--green); color: var(--green); background: var(--green-light); }

  /* The collected state's two pills are ~370px wide together and both
     white-space: nowrap, and .bp-detail-collected-wrap is flex-shrink: 0. In a
     row that could not wrap, that combination had nowhere to go: it squeezed
     .bp-detail-info (min-width: 0) down to LITERALLY zero on a phone and the
     reward name spilled out from under the pills, which is what the card was
     doing on mobile — 358px card, 123px of overflow, "Silver Token" printed
     underneath the green Collected pill.
     Letting the card wrap is the fix; the 200px flex-basis on the info column
     is what decides when, so it is driven by the actual name length rather
     than a guessed breakpoint. */
  .bp-detail { flex-wrap: wrap; }
  .bp-detail-info { flex: 1 1 200px; }

  /* On a phone the pills go one below the other, full width. Side by side they
     fit down to about 480px, but only by leaving each one too narrow to read
     comfortably — and a stacked pair is the same shape as every other stacked
     mobile CTA on the site. */
  @media (max-width: 640px) {
    .bp-detail-collected-wrap {
      width: 100%; flex-direction: column; align-items: stretch;
      flex-wrap: nowrap; justify-content: flex-start;
    }
    .bp-detail-claimed, .bp-detail-view-btn { justify-content: center; min-width: 0; }
  }

  @media (max-width: 480px) {
    .bp-col, .bp-tile { width: 92px; }
    .bp-tile { height: 112px; }
    .bp-tile-icon { height: 44px; font-size: 32px; }
    .bp-tile-icon img { width: 44px !important; height: 44px !important; }
    .bp-detail { padding: 14px; gap: 12px; }
    .bp-detail-name { font-size: 17px; }
    .bp-detail-claim { padding: 12px 24px; min-width: 0; }
    .bp-arrow { display: none; }
  }

  .tier-empty { text-align: center; padding: 32px; color: var(--text-muted); font-size: 14px; }


  /* ── Inline path picker ── */
  .path-rewards-section { display: flex; flex-direction: column; gap: 16px; }
  .path-picker-panel { display: none; flex-direction: column; gap: 16px; }
  .path-picker-panel.visible { display: flex; }

  .path-picker-grid { display: flex; flex-direction: column; gap: 12px; }

  .path-picker-filter { display: flex; gap: 6px; }
  .ppf-btn {
    padding: 7px 16px; border-radius: 12px; border: 1.5px solid var(--border);
    background: var(--card-bg); color: var(--text-soft);
    font-family: 'Geist', 'DM Sans', sans-serif; font-size: 12.5px; font-weight: 700;
    cursor: pointer; transition: all .15s;
  }
  .ppf-btn:hover { border-color: var(--text-muted); color: var(--text); }
  .ppf-btn.active { background: var(--green, #16A34A); border-color: var(--green, #16A34A); color: #fff; }

  /* ── Path Rewards ⇄ path picker swap transition ──
     Both panels occupy the same slot below the season banner; swapping one
     out and the other in is staggered in JS (fade+slide out, then in) so
     they never overlap mid-animation. */
  .path-rewards-section, .path-picker-panel { transition: opacity .28s ease, transform .28s ease; }
  .path-swap-out { opacity: 0; transform: translateY(-12px); }
  .path-swap-in-start { opacity: 0; transform: translateY(16px); }

  /* Pool XP header in path picker */
  .pool-xp-header { display: flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 600; color: var(--green); background: rgba(22,163,74,.1); border: 1px solid rgba(22,163,74,.25); border-radius: 50px; padding: 5px 12px; margin-top: 4px; }

  /* No paths */
  .no-paths { background: var(--card-bg); border: 1.5px solid var(--border); border-radius: var(--radius); padding: 48px 24px; text-align: center; width: 100%; }
  .no-paths-icon { width: 56px; height: 56px; background: var(--bg); border-radius: 16px; display: flex; align-items: center; justify-content: center; margin: 0 auto 16px; }

  /* Rewards locked — signed-in user with no completed purchase yet */
  .rw-locked { width: 85%; max-width: 1400px; background: var(--card-bg); border: 1.5px solid var(--border); border-radius: var(--radius); padding: 64px 24px; text-align: center; margin: 0 auto; }
  @media (max-width: 1024px) { .rw-locked { width: 100%; } }
  .rw-locked-icon { width: 64px; height: 64px; background: var(--bg); border-radius: 18px; display: flex; align-items: center; justify-content: center; margin: 0 auto 18px; }
  .rw-locked-title { font-size: 19px; font-weight: 800; color: var(--text); margin: 0 0 8px; }
  .rw-locked-sub { font-size: 13.5px; color: var(--text-muted); margin: 0 0 22px; max-width: 360px; margin-left: auto; margin-right: auto; line-height: 1.5; }
  .rw-locked-cta { display: inline-flex; align-items: center; justify-content: center; padding: 11px 22px; background: var(--green-cta); color: var(--on-green); border-radius: var(--radius-sm); font-size: 13.5px; font-weight: 700; text-decoration: none; transition: filter .15s, transform .15s; }
  .rw-locked-cta:hover { filter: brightness(1.08); transform: translateY(-1px); }


  /* ── Edit Paths admin button ── */
  /* Was a dashed, --text-muted, 12px/600 outline — the weakest treatment on the
     page for something that is still a real navigation control. The dashed
     border in particular reads as a placeholder/drop-zone rather than a button.
     Solid border, --text-soft label at the same 13px/800 as .btn-change-path
     beside it, and a 36px body so the two admin controls are one size. */
  .btn-edit-paths { display: inline-flex; align-items: center; justify-content: center; gap: 7px; width: 100%; padding: 0 16px; min-height: 36px; background: var(--card-bg); border: 1.5px solid color-mix(in srgb, var(--text-muted) 34%, var(--border)); border-radius: 50px; color: var(--text-soft); font-size: 13px; font-weight: 800; text-decoration: none; box-shadow: 0 3px 10px rgba(0, 0, 0, .08), inset 0 1px 0 rgba(255, 255, 255, .55); transition: background .18s ease, border-color .18s ease, color .18s ease, box-shadow .18s ease, transform .18s ease; }
  .btn-edit-paths:hover { border-color: var(--green); color: var(--green); background: var(--green-light); box-shadow: 0 6px 16px rgba(22, 163, 74, .14), inset 0 1px 0 rgba(255, 255, 255, .65); transform: translateY(-1px); }
  .btn-edit-paths:active { transform: translateY(0); }
  html.dark .btn-change-path,
  html.dark .btn-edit-paths {
    background: #1C1C21;
    border-color: #3A3A43;
    color: #D7D7DE;
    box-shadow: 0 5px 14px rgba(0, 0, 0, .3), inset 0 1px 0 rgba(255, 255, 255, .06);
  }
  html.dark .btn-change-path:hover,
  html.dark .btn-edit-paths:hover {
    background: var(--green-light);
    border-color: #4ADE80;
    color: #A7F3C0;
    box-shadow: 0 7px 18px rgba(22, 163, 74, .18), inset 0 1px 0 rgba(255, 255, 255, .08);
  }
  /* Keyboard parity for every instance of these two (sidebar, wheelspin action
     row, banner). The banner-over-photo variant needs its own white ring and
     has one further up; this covers the rest, where green on a card reads. */
  .btn-change-path:focus-visible,
  .btn-edit-paths:focus-visible { outline: 2.5px solid var(--green); outline-offset: 2px; }

  /* ── Footer note ── */
  .rw-footer-note { max-width: 1240px; margin: 24px auto 0; padding: 0 24px; text-align: center; font-size: 12px; color: var(--text-muted); display: flex; align-items: center; justify-content: center; gap: 6px; }

  /* ── Admin strip ── */
  .admin-strip { background: var(--bg); border: 1.5px dashed var(--border); border-radius: 14px; padding: 16px 20px; }
  .admin-strip-lbl { font-size: 11px; font-weight: 700; color: var(--text-muted); text-transform: uppercase; letter-spacing: .5px; margin-bottom: 10px; display: flex; align-items: center; gap: 6px; }

  /* ── Toast ──────────────────────────────────────────────────────────────
     The card look is copied from styles.css rather than inherited: this page
     never links that stylesheet, so .toast/.toast-icon/.toast-msg/.toast-close
     rendered completely unstyled here (bare "✓+10 XP earned!×"). Pulling the
     whole stylesheet in would drag along its .rewards-section / .tier-card /
     .spin-section rules and fight this page's own design, so the toast block is
     duplicated instead. Keep in sync with the .toast rules in styles.css.
     Bottom-right, matching every other page's toast position. */
  #toastContainer { position: fixed; bottom: 24px; right: 24px; z-index: 99999; display: flex; flex-direction: column; gap: 12px; align-items: flex-end; pointer-events: none; }
  .toast {
    background: var(--card-bg, #ffffff);
    border: 1.5px solid var(--border, #ECECEC);
    border-radius: 12px;
    padding: 10px 14px;
    font-size: 13px;
    font-weight: 700;
    color: var(--text, #111827);
    display: flex;
    align-items: center;
    gap: 10px;
    box-shadow: 0 8px 24px rgba(0,0,0,0.08);
    min-width: 200px;
    max-width: 320px;
    pointer-events: auto;
    animation: toastIn 0.2s cubic-bezier(0.16, 1, 0.3, 1);
  }
  @keyframes toastIn {
    from { opacity: 0; transform: translateY(10px) scale(0.95); }
    to   { opacity: 1; transform: translateY(0) scale(1); }
  }
  .toast-icon {
    width: 28px; height: 28px; border-radius: 50%; flex-shrink: 0;
    background: var(--bg, #F3F3F1); color: var(--text, #111827);
    display: flex; align-items: center; justify-content: center;
  }
  .toast.error .toast-icon { background: var(--coral-light, #FDECEA); color: var(--coral, #E5484D); }
  .toast-msg { flex: 1; min-width: 0; line-height: 1.4; }
  .toast-close {
    flex-shrink: 0; width: 26px; height: 26px; border: none; background: transparent;
    border-radius: 50%; color: var(--text-muted, #8A8A8A); cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    transition: background 0.15s, color 0.15s;
    font-size: 16px; line-height: 1; padding: 0;
  }
  .toast-close:hover { background: var(--bg, #F3F3F1); color: var(--text, #111827); }
  /* Dark values pinned to styles.css's rather than left to var(--card-bg), so
     this toast is the SAME chip as the one on the Pack Builder and everywhere
     else — --card-bg here is #16161A, which rendered it a shade darker than
     every other page's. */
  body.dark .toast { background: #1E1E22; color: #F0F0F8; box-shadow: 0 10px 30px rgba(0,0,0,0.4), 0 2px 8px rgba(0,0,0,0.3); }
  body.dark .toast-icon { background: rgba(255,255,255,0.08); color: #F0F0F8; }
  body.dark .toast.error .toast-icon { background: rgba(248,113,113,0.15); color: #F87171; }
  body.dark .toast-close { color: #8A8A93; }
  body.dark .toast-close:hover { background: rgba(255,255,255,0.08); color: #F0F0F8; }
  @keyframes confettiFall { to { transform: translateY(110vh) rotate(720deg); opacity: 0; } }

  /* ══ Sidebar Wheelspin entry ════════════════════════════════════════ */
  /* Style matching sidebar-card class rules to match path symmetry */

  .history-table td {
    padding: 12px 8px;
    font-size: 13px;
    border-bottom: 1px solid var(--border);
    vertical-align: middle;
  }
  .history-table tr:last-child td {
    border-bottom: none;
  }
  .history-tier-pill {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: 10px;
    font-weight: 800;
    text-transform: uppercase;
    padding: 3px 8px;
    border-radius: 50px;
  }
  .history-tier-pill.bronze { background: #fef8ee; color: #cd7f32; border: 1px solid #fbd38d; }
  .history-tier-pill.silver { background: #f1f5f9; color: #475569; border: 1px solid #cbd5e1; }
  .history-tier-pill.gold { background: #fffbeb; color: #d97706; border: 1px solid #fde68a; }
  .history-tier-pill.plat { background: #f5f3ff; color: #7c3aed; border: 1px solid #ddd6fe; }
  html.dark .history-tier-pill.bronze { background: #1c1006; border-color: #442a08; }
  html.dark .history-tier-pill.silver { background: #0f141b; border-color: #1e293b; }
  html.dark .history-tier-pill.gold { background: #1a1100; border-color: #451a03; }
  html.dark .history-tier-pill.plat { background: #120f1e; border-color: #3b0764; }

  /* What happened to a drawn prize — collected, scrapped for fragments (with
     how many), or still sitting on the reveal card. */
  .history-status-pill {
    display: inline-flex; align-items: center; gap: 4px;
    font-size: 10px; font-weight: 800; text-transform: uppercase;
    padding: 3px 8px; border-radius: 50px; white-space: nowrap;
  }
  .history-status-pill.collected { background: #ecfdf5; color: #166534; border: 1px solid #bbf7d0; }
  .history-status-pill.scrapped  { background: #eff6ff; color: #1d4ed8; border: 1px solid #bfdbfe; }
  .history-status-pill.pending   { background: #fffbeb; color: #b45309; border: 1px solid #fde68a; }
  html.dark .history-status-pill.collected { background: rgba(34,197,94,.12); color: #4ade80; border-color: rgba(74,222,128,.3); }
  html.dark .history-status-pill.scrapped  { background: rgba(37,99,235,.14); color: #60a5fa; border-color: rgba(96,165,250,.3); }
  html.dark .history-status-pill.pending   { background: rgba(217,119,6,.14); color: #fbbf24; border-color: rgba(251,191,36,.3); }

  /* ══ Wheelspin Panel (right main) ══════════════════════════════════ */
  /* container-type lets the spin-control-row react to the PANEL's width, not
     the viewport's — the panel is narrowed by the nav sidebar and the rewards
     sidebar at widths where viewport media queries still think it's roomy. */
  .ws-panel-wrap { background: var(--card-bg); border: 1.5px solid var(--border); border-radius: 16px; padding: 22px 20px 26px; display: flex; flex-direction: column; align-items: center; gap: 18px; container-type: inline-size; width: 100%; box-sizing: border-box; }
  .ws-panel-top { display: flex; align-items: center; justify-content: space-between; width: 100%; }
  .ws-panel-title { display: flex; align-items: center; gap: 8px; font-size: 20px; font-weight: 800; color: var(--text); }
  .ws-panel-back-btn { display: flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 600; color: var(--text-muted); cursor: pointer; background: var(--bg); border: 1.5px solid var(--border); border-radius: 12px; padding: 6px 13px; transition: all .15s; }
  .ws-panel-back-btn:hover { color: var(--text); border-color: var(--text-muted); }

  /* Tier picker toggle — attached tab flush with the left edge of the token counter */
  .ws-tier-toggle-btn {
    flex-shrink: 0;
    width: 44px;
    align-self: stretch;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--bg);
    border: none;
    border-right: 1.5px solid var(--border);
    color: var(--text-muted);
    cursor: pointer;
    transition: all .15s;
  }
  .ws-tier-toggle-btn svg { transition: transform .25s ease; }
  .ws-tier-toggle-btn:hover { color: var(--text); }
  .ws-tier-toggle-btn.open svg { transform: rotate(180deg); }

  /* Tier picker overlay — reveals inside the drum, over the bottom of the wheel */
  .ws-tier-overlay {
    position: absolute;
    left: 12px;
    right: 12px;
    bottom: 12px;
    z-index: 25;
    display: grid;
    grid-template-columns: repeat(4,1fr);
    gap: 8px;
    background: var(--card-bg);
    border: 1.5px solid var(--border);
    border-radius: 16px;
    padding: 12px 10px;
    box-shadow: 0 -10px 30px rgba(0,0,0,.15);
    transform: translateY(calc(100% + 12px));
    opacity: 0;
    pointer-events: none;
    transition: transform .35s cubic-bezier(.16,1,.3,1), opacity .3s ease;
  }
  .ws-tier-overlay.show {
    transform: translateY(0);
    opacity: 1;
    pointer-events: auto;
  }
  /* Desktop keeps its original in-drum picker; the close bar and dismiss layer
     belong only to the phone bottom-sheet treatment. */
  .ws-tier-sheet-head { display: none; }
  .ws-tier-backdrop { display: none; }

  /* Tier tabs — grid, image + label below */
  .ws-panel-tiers { display: grid; grid-template-columns: repeat(4,1fr); gap: 8px; width: 100%; }
  .ws-pt { background: var(--bg); border: 1.5px solid var(--border); border-radius: 16px; padding: 14px 8px 12px; cursor: pointer; display: flex; flex-direction: column; align-items: center; gap: 10px; transition: all .15s; position: relative; }
  .ws-pt:hover:not(.wt-disabled) { border-color: var(--text-muted); }
  .ws-pt.wt-active.bronze { border-color:#cd7f32;background:color-mix(in srgb,var(--bg) 94%,#cd7f32 6%); }
  .ws-pt.wt-active.silver { border-color:#94a3b8;background:color-mix(in srgb,var(--bg) 94%,#94a3b8 6%); }
  .ws-pt.wt-active.gold   { border-color:#f59e0b;background:color-mix(in srgb,var(--bg) 94%,#f59e0b 6%); }
  .ws-pt.wt-active.plat   { border-color:#a78bfa;background:color-mix(in srgb,var(--bg) 94%,#a78bfa 6%); }
  html.dark .ws-pt.wt-active.bronze { background:color-mix(in srgb,var(--bg) 92%,#cd7f32 8%); }
  html.dark .ws-pt.wt-active.silver { background:color-mix(in srgb,var(--bg) 92%,#94a3b8 8%); }
  html.dark .ws-pt.wt-active.gold   { background:color-mix(in srgb,var(--bg) 92%,#f59e0b 8%); }
  html.dark .ws-pt.wt-active.plat   { background:color-mix(in srgb,var(--bg) 92%,#a78bfa 8%); }
  /* wt-disabled means "no token to spin this tier" — a fact about the orb/
     name/count/check, not about crafting, which runs on fragments regardless
     of token count. Dimming used to be a blanket opacity on the whole card,
     which dragged the Craft button down with it and made an actually-usable
     button read as disabled. Scoped to every child except .ws-pt-craft so
     craft affordability always renders at full brightness. */
  .ws-pt.wt-disabled { cursor: not-allowed; }
  .ws-pt.wt-disabled > *:not(.ws-pt-craft) { opacity: .4; }
  .ws-pt-orb { width: 56px; height: 56px; border-radius: 50%; display: flex; align-items: center; justify-content: center; flex-shrink: 0; overflow: hidden; }
  .ws-pt-name { font-size: 11px; font-weight: 800; color: var(--text); text-align: center; }
  /* Owned-token count for this tier, at a glance without opening the card —
     the value itself was already kept live by rpUpdateTokenUI, this was just
     never shown. Used to share this corner with a green check mark (removed
     entirely — wt-active already recolors the whole card border/background,
     so the check was a redundant second "this one" signal), so it no longer
     needs to shift out of the way on the active card. */
  .ws-pt-cnt  {
    display: flex; align-items: center; justify-content: center;
    position: absolute; top: 14px; right: 16px;
    padding: 0;
    background: transparent;
    border: 0;
    border-radius: 0;
    font-size: 20px;
    font-weight: 900;
    font-variant-numeric: tabular-nums;
    letter-spacing: -.03em;
    color: var(--text);
    line-height: 1;
    gap: 1px;
  }

  /* Drum */
  .ws-pdrum-outer { width: 100%; }
  .ws-parrow { display: none; }
  .ws-pdrum-row { display: flex; align-items: center; width: 100%; }
  .ws-pdrum { flex: 1; height: 720px; overflow: hidden; position: relative; border-radius: 20px; background: var(--bg); border: 1.5px solid var(--border); perspective: 900px; perspective-origin: 50% 50%; }
  .ws-pslot { position: absolute; left: 10px; right: 10px; top: 50%; transform: translateY(-50%); height: 128px; z-index: 10; pointer-events: none; border: 2.5px solid var(--green); border-radius: 16px; box-shadow: 0 0 0 4px rgba(22,163,74,.08), inset 0 0 0 1px rgba(22,163,74,.12); }
  .ws-pdrum::before,.ws-pdrum::after { content:''; position:absolute; left:0; right:0; height:220px; z-index:9; pointer-events:none; }
  .ws-pdrum::before { top:0; background: linear-gradient(180deg, var(--bg) 0%, var(--bg) 8%, transparent 100%); }
  .ws-pdrum::after  { bottom:0; background: linear-gradient(0deg, var(--bg) 0%, var(--bg) 8%, transparent 100%); }
  .ws-drum-overlay { position:absolute; inset:0; z-index:20; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:12px; background:rgba(0,0,0,.34); backdrop-filter:blur(6px); -webkit-backdrop-filter:blur(6px); opacity:0; pointer-events:none; transition:opacity .4s; border-radius:20px; }
  .ws-drum-overlay.show { opacity:1; pointer-events:auto; }
  .ws-pstrip { position:absolute; left:0; right:0; top:0; display:flex; flex-direction:column; gap:6px; padding:0 10px; will-change:transform; }
  .ws-pcard { flex-shrink:0; width:100%; height:120px; border-radius:16px; background:var(--card-bg); border:1.5px solid var(--border); display:flex; flex-direction:column; align-items:center; justify-content:center; gap:6px; padding:0 16px; overflow:hidden; will-change:transform,opacity; text-align:center; }
  .ws-pcard-ico  { font-size:32px; line-height:1; }
  .ws-pcard-info { display:flex; flex-direction:column; align-items:center; gap:5px; min-width:0; width:100%; }
  .ws-pcard-name { font-size:14px; font-weight:800; color:var(--text); letter-spacing:-.2px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:100%; }
  /* Row instead of the rarity pill sitting alone — the star badge (starred/
     Excellent-quality prizes) sits next to it, pinned to the same 17px height
     via box-sizing:border-box rather than trusting padding+line-height to
     match between two differently-shaped elements. */
  .ws-pcard-badges-row { display:flex; align-items:center; justify-content:center; gap:5px; }
  .ws-pcard-badge { display:inline-flex; align-items:center; height:17px; box-sizing:border-box; font-size:9px; font-weight:800; padding:0 10px; border-radius:20px; letter-spacing:.8px; text-transform:uppercase; }
  /* Dark chip + gold icon, not a solid gold fill — PATH_REWARD_STAR_ICON is
     itself gold (#F5B301), so a gold background would swallow it. Matches the
     pp-starred-pill convention used for this same badge elsewhere on the site. */
  .ws-pcard-star-badge { display:inline-flex; align-items:center; justify-content:center; height:17px; width:17px; box-sizing:border-box; border-radius:50%; background:#4D3B05; border:1.5px solid #F5B301; flex-shrink:0; }
  .ws-pcard-star-badge .path-reward-star-svg { width:10px; height:10px; display:block; }
  .rp-common   .ws-pcard-badge { background:#e5e7eb; color:#6b7280; }
  .rp-uncommon .ws-pcard-badge { background:#16a34a; color:#fff; }
  .rp-rare     .ws-pcard-badge { background:#7c3aed; color:#fff; }
  .rp-epic     .ws-pcard-badge { background:#2563eb; color:#fff; }
  .rp-legendary .ws-pcard-badge { background:#d97706; color:#fff; }
  .rp-mythic   .ws-pcard-badge { background:#c026d3; color:#fff; }
  html.dark .rp-common   .ws-pcard-badge { background:#374151; color:#9ca3af; }
  html.dark .rp-uncommon .ws-pcard-badge { background:#15803d; color:#fff; }
  html.dark .rp-rare     .ws-pcard-badge { background:#6d28d9; color:#fff; }
  html.dark .rp-epic     .ws-pcard-badge { background:#1d4ed8; color:#fff; }
  html.dark .rp-legendary .ws-pcard-badge { background:#b45309; color:#fff; }
  html.dark .rp-mythic   .ws-pcard-badge { background:#a21caf; color:#fff; }
  .ws-pcard.rp-winner { border-color:var(--green) !important; box-shadow:0 0 0 3px rgba(22,163,74,.2), 0 8px 30px rgba(22,163,74,.18) !important; }

  /* Self-colored rarity pill for contexts with no .rp-<rarity> ancestor to key
     off of (Wheelspin History rows sit in a <table>, not a .ws-pcard). */
  .history-rarity-badge.common     { background:#e5e7eb; color:#6b7280; }
  .history-rarity-badge.uncommon   { background:#16a34a; color:#fff; }
  .history-rarity-badge.rare       { background:#7c3aed; color:#fff; }
  .history-rarity-badge.epic       { background:#2563eb; color:#fff; }
  .history-rarity-badge.legendary  { background:#d97706; color:#fff; }
  .history-rarity-badge.mythic     { background:#c026d3; color:#fff; }
  html.dark .history-rarity-badge.common    { background:#374151; color:#9ca3af; }
  html.dark .history-rarity-badge.uncommon  { background:#15803d; color:#fff; }
  html.dark .history-rarity-badge.rare      { background:#6d28d9; color:#fff; }
  html.dark .history-rarity-badge.epic      { background:#1d4ed8; color:#fff; }
  html.dark .history-rarity-badge.legendary { background:#b45309; color:#fff; }
  html.dark .history-rarity-badge.mythic    { background:#a21caf; color:#fff; }

  /* Spin button + result */
  .ws-spin-control-row {
    display: grid;
    /* minmax(0,1fr) lets the side columns actually shrink; a plain 1fr's
       implicit min-width:auto keeps them at content width and pushes the
       row past the panel edge on narrower panels. */
    grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
    align-items: center;
    gap: 12px;
    width: 100%;
    margin-bottom: 12px;
    box-sizing: border-box;
    min-height: 52px;
  }
  .ws-dynamic-header {
    display: none;
  }
  .ws-dynamic-title {
    font-size: 52px;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 1.5px;
    margin: 0;
    transition: all 0.3s ease;
    line-height: 1;
  }
  .ws-dynamic-title.bronze {
    color: #cd7f32;
    text-shadow: 0 0 10px rgba(205,127,50,0.15);
  }
  .ws-dynamic-title.silver {
    color: #94a3b8;
    text-shadow: 0 0 10px rgba(148,163,184,0.15);
  }
  .ws-dynamic-title.gold {
    color: #f59e0b;
    text-shadow: 0 0 10px rgba(245,158,11,0.2);
  }
  .ws-dynamic-title.plat {
    background: linear-gradient(135deg, #3b82f6, #8b5cf6, #ec4899, #3b82f6);
    background-size: 300% 300%;
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    animation: premiumGlow 6s ease infinite;
    text-shadow: none;
    font-weight: 900;
  }
  @keyframes premiumGlow {
    0% { background-position: 0% 50%; }
    50% { background-position: 100% 50%; }
    100% { background-position: 0% 50%; }
  }
  .ws-odo-counter {
    display: inline-flex;
    align-items: stretch;
    background: #fff;
    border: 1.5px solid var(--border);
    border-radius: 12px;
    height: 52px;
    box-sizing: border-box;
    justify-self: start;
    overflow: hidden;
    /* Never spill under the centered Spin button: cap at the grid column and
       let the label ellipsize instead of overlapping. */
    max-width: 100%;
  }
  .ws-odo-body {
    display: flex;
    align-items: center;
    gap: 3px;
    padding: 6px 8px;
    min-width: 0;
  }
  .ws-odo-icon {
    width: 22px;
    height: 22px;
    object-fit: contain;
    display: none;
    margin-right: 8px;
    flex-shrink: 0;
  }
  .ws-odo-digit {
    width: 20px;
    height: 36px;
    overflow: hidden;
    position: relative;
    background: #f1f5f9;
    border: 1px solid var(--border);
    border-radius: 6px;
    display: flex;
    flex-direction: column;
  }
  .ws-odo-digit-strip {
    display: flex;
    flex-direction: column;
    width: 100%;
    transition: transform 0.8s cubic-bezier(0.1, 0.9, 0.2, 1);
  }
  .ws-odo-val {
    height: 36px;
    width: 20px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #0f172a;
    font-family: 'Courier New', monospace, sans-serif;
    font-size: 20px;
    font-weight: 800;
    line-height: 36px;
    flex-shrink: 0;
  }
  .ws-odo-label {
    font-size: 11px;
    font-weight: 800;
    text-transform: uppercase;
    color: var(--text-muted);
    margin-left: 12px;
    letter-spacing: 0.5px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
  }
  /* Fragments pill now lives next to the token-count label instead of up in
     the panel action row — .ws-frag-pill's own height rule was scoped to
     #pnlActionsWs, which no longer contains it, so it needs its own sizing
     here rather than inheriting that one. */
  .ws-odo-body .ws-frag-pill {
    height: 36px; box-sizing: border-box; padding-top: 0; padding-bottom: 0;
    margin-left: 10px; flex-shrink: 0;
  }

  html.dark .ws-odo-counter {
    background: #000;
  }
  html.dark .ws-odo-digit {
    background: #0b0b0d;
    border-color: #222;
  }
  html.dark .ws-odo-val {
    color: #fff;
  }
  .ws-pspin-btn-wrap {
    width: 300px; /* fallback for browsers without container-query units */
    /* 550px ≈ odometer at its widest ("PLATINUM TOKENS" label) + fast-reveal
       toggle + grid gaps, so the button only takes width that's genuinely
       left over in the panel, capped at the 300px design width. */
    width: clamp(160px, 100cqw - 550px, 300px);
    height: 52px;
    position: relative;
    justify-self: center;
    display: flex;
    gap: 8px;
  }
  .ws-pspin-btn {
    --ws-tier-start: #16a34a;
    --ws-tier-mid: #22c55e;
    --ws-tier-end: #16a34a;
    --ws-spin-start: color-mix(in srgb, var(--card-bg) 72%, var(--ws-tier-start));
    --ws-spin-mid: color-mix(in srgb, var(--card-bg) 82%, var(--ws-tier-mid));
    --ws-spin-end: color-mix(in srgb, var(--card-bg) 72%, var(--ws-tier-end));
    --ws-spin-ink: color-mix(in srgb, var(--text) 90%, var(--ws-tier-start));
    --ws-spin-shadow: rgba(22,163,74,.3);
    position: relative;
    flex: 1 1 0;
    min-width: 0;
    height: 100%;
    z-index: 15;
    background: linear-gradient(115deg,var(--ws-spin-start),var(--ws-spin-mid) 52%,var(--ws-spin-end));
    color: var(--ws-spin-ink);
    border: 1px solid rgba(255,255,255,.18);
    border-radius: 12px;
    font-size: 18px;
    font-weight: 900;
    letter-spacing: .1em;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    transition: filter .15s ease, transform .15s ease, box-shadow .15s ease;
    box-shadow: inset 0 1px 0 rgba(255,255,255,.24), 0 4px 18px var(--ws-spin-shadow);
    box-sizing: border-box;
    overflow: hidden; /* clips the shine sweep below to the button's rounded shape */
    /* ...and this is what makes that clip unconditional. contain:paint makes the
       button the containing block for its absolutely positioned ::before and
       confines its painting to this box, so no override anywhere in the cascade
       (a `position: static` in a mobile block did exactly this) can let the
       sweep out onto the page again. */
    contain: paint;
  }
  .ws-pspin-btn:not(.collect-mode) { text-transform: uppercase; }
  .ws-pspin-btn.bronze {
    --ws-tier-start: #a95727;
    --ws-tier-mid: #d38a4a;
    --ws-tier-end: #8f481f;
    --ws-spin-shadow: rgba(154,78,34,.38);
  }
  .ws-pspin-btn.silver {
    --ws-tier-start: #64748b;
    --ws-tier-mid: #cbd5e1;
    --ws-tier-end: #78879a;
    --ws-spin-shadow: rgba(148,163,184,.34);
  }
  .ws-pspin-btn.gold {
    --ws-tier-start: #d97706;
    --ws-tier-mid: #fbbf24;
    --ws-tier-end: #e08a08;
    --ws-spin-shadow: rgba(245,158,11,.36);
  }
  .ws-pspin-btn.plat {
    --ws-tier-start: #7c3aed;
    --ws-tier-mid: #a78bfa;
    --ws-tier-end: #9333ea;
    --ws-spin-shadow: rgba(139,92,246,.38);
  }
  html.dark .ws-pspin-btn {
    --ws-spin-start: color-mix(in srgb, var(--text) 30%, var(--ws-tier-start));
    --ws-spin-mid: color-mix(in srgb, var(--text) 55%, var(--ws-tier-mid));
    --ws-spin-end: color-mix(in srgb, var(--text) 34%, var(--ws-tier-end));
    --ws-spin-ink: color-mix(in srgb, var(--bg) 88%, var(--ws-tier-start));
  }
  .ws-pspin-btn:hover:not(:disabled) {
    filter: brightness(1.06) saturate(1.06);
    transform: translateY(-1px);
    box-shadow: inset 0 1px 0 rgba(255,255,255,.3), 0 7px 24px var(--ws-spin-shadow);
  }
  .ws-pspin-btn:focus-visible {
    outline: 3px solid rgba(255,255,255,.92);
    outline-offset: 3px;
  }
  .ws-pspin-btn:disabled { opacity:1; cursor:not-allowed; transform:none; box-shadow:none; background:var(--border); color:var(--text-muted); border-color:transparent; }
  /* Idle shine — a soft diagonal highlight drifting across the button on a
     loop, so the primary action reads as alive even before it's clicked.
     Pseudo-element rather than an inner span so it never competes with the
     label/icon for layout or click targeting (pointer-events: none). Skipped
     entirely while disabled — nothing to draw attention to mid-spin. */
  /* The shine sweep. It escaped the button on mobile once already and painted a
     skewed white slab across the page: it is an absolutely positioned pseudo,
     and one override set `position: static` on the button, which moved the
     pseudo's containing block UP to an ancestor — an absolute box whose
     containing block is outside a clipper is not clipped by it, so neither the
     button's overflow:hidden nor its rounding applied any more.
     `contain: paint` on the button (below) is what makes that impossible now:
     it forces the button to be the containing block for absolute descendants
     AND clips their painting to it, whatever `position` any later rule sets. */
  .ws-pspin-btn::before {
    content: '';
    position: absolute;
    top: 0; left: -60%;
    width: 45%; height: 100%;
    background: linear-gradient(115deg, transparent, rgba(255,255,255,.55), transparent);
    transform: skewX(-22deg);
    pointer-events: none;
    animation: wsPspinShine 3.4s ease-in-out infinite;
  }
  .ws-pspin-btn:disabled::before { display: none; }
  @keyframes wsPspinShine {
    0%   { left: -60%; }
    35%  { left: 130%; }
    100% { left: 130%; }
  }
  @media (prefers-reduced-motion: reduce) {
    .ws-pspin-btn::before { animation: none; display: none; }
  }
  /* Secondary action offered alongside Collect at the reveal screen — flex
     sibling of #rpSpinBtn inside the same wrap, so the two sit side by side
     without the wrap's clamp()'d width needing to change. */
  .ws-pscrap-btn {
    flex: 1 1 0;
    min-width: 0;
    height: 100%;
    background: var(--card-bg);
    color: var(--text);
    border: 1.5px solid var(--border);
    border-radius: 12px;
    font-size: 14px;
    font-weight: 700;
    cursor: pointer;
    align-items: center;
    justify-content: center;
    gap: 6px;
    transition: all .15s;
    box-sizing: border-box;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .ws-pscrap-btn:hover:not(:disabled) { border-color: var(--text-muted); transform: translateY(-1px); }
  .ws-ptok-info { font-size:12px; color:var(--text-muted); font-weight:600; margin-top:2px; margin-bottom:8px; }
  .ws-ptok-info strong { color:var(--text); }

  /* Token crafting — lives inside each tier-picker card (.ws-pt) now, directly
     under the token it mints, rather than a separate panel: progress bar,
     have/need amount, then a compact secondary Craft action. The running Fragments
     total itself lives in the top pill next to the tier badge (#wsFragPillTop). */
  .ws-pt-craft {
    width: 55%;
    align-self: center;
    display: flex;
    flex-direction: column;
    gap: 6px;
  }
  .ws-craft-bar-track {
    width: 100%;
    height: 4px;
    border-radius: 4px;
    background: var(--border);
    overflow: hidden;
  }
  .ws-craft-bar-fill {
    height: 100%;
    /* Blue, not green — Craft sits right next to Spin, and sharing Spin's green
       made the two read as the same action. Reuses the site's existing
       "official" blue accent instead of inventing a new one. */
    background: linear-gradient(90deg,#2563EB,#3B82F6);
    transition: width .25s;
  }
  .ws-craft-progress { font-size: 12px; font-weight: 600; color: var(--text-muted); text-align: center; white-space: nowrap; }
  .ws-craft-btn {
    width: auto;
    min-width: 104px;
    align-self: center;
    padding: 7px 14px;
    border: 1px solid rgba(37,99,235,.38);
    border-radius: 8px;
    background: rgba(37,99,235,.08);
    color: #2563EB;
    text-shadow: none;
    font-size: 13.5px;
    font-weight: 700;
    cursor: pointer;
    transition: background .15s,border-color .15s,color .15s,transform .15s;
  }
  html.dark .ws-craft-btn { background:rgba(59,130,246,.1);border-color:rgba(96,165,250,.34);color:#60A5FA; }
  .ws-craft-btn:hover:not(:disabled) { transform:translateY(-1px);background:rgba(37,99,235,.14);border-color:rgba(37,99,235,.58); }
  html.dark .ws-craft-btn:hover:not(:disabled) { background:rgba(59,130,246,.17);border-color:rgba(96,165,250,.55); }
  .ws-craft-btn:disabled { opacity:1;cursor:not-allowed;background:transparent;border-color:var(--border);color:var(--text-muted);text-shadow:none; }
  /* `html.dark .ws-craft-btn` above is (0,2,1) — one type selector heavier
     than `.ws-craft-btn:disabled`'s (0,2,0) — so in dark mode it silently won
     regardless of source order, and an unaffordable Craft button kept
     rendering in the same blue "you can afford this" style as every other
     one. Same override, made theme-specific so it outranks it. */
  html.dark .ws-craft-btn:disabled { background:transparent;border-color:var(--border);color:var(--text-muted); }
  /* .ws-pt.wt-disabled dims the whole card (that's about spin availability,
     a separate concept from craft affordability) — keep the Craft button's
     own cursor correct rather than inheriting the card's not-allowed. */
  .ws-pt.wt-disabled .ws-craft-btn:not(:disabled) { cursor: pointer; }

  @media (max-width: 500px) {
    .ws-spin-control-row {
      display: flex;
      flex-direction: column;
      gap: 10px;
      align-items: center;
      height: auto !important;
    }
    .ws-odo-counter {
      /* Compact pill hugging its content — a full-width bar strands the
         tier-picker caret in the middle of empty space. */
      width: auto !important;
      align-self: center !important;
    }
    .ws-pspin-btn-wrap {
      width: 100% !important;
      height: auto !important;
      flex-direction: column !important;
    }
    /* The wrap is a COLUMN here, so the buttons' `flex: 1 1 0` put a
       flex-basis of 0 on the vertical axis — which beats the `height` below it
       for a flex item's main size. In a wrap that is itself height:auto there
       is no free space to grow into, so SPIN collapsed to its 2px of border: a
       hairline where the button should be, and no way to spin on a phone.
       Basis auto hands sizing back to the heights. */
    .ws-pspin-btn {
      /* MUST stay positioned. The shine sweep is an absolutely positioned
         ::before that the button clips with its own overflow:hidden — but a
         static button is not the pseudo's containing block, and an absolute box
         whose containing block sits ABOVE the clipper is not clipped by it. As
         `position: static` the sweep escaped the button entirely and swung
         across the page as a big skewed white slab, dragging a horizontal
         scrollbar with it. */
      position: relative !important;
      flex: 0 0 auto !important;
      width: 100% !important;
      /* 100px pushed the button under the fold on a phone once the drum, the
         odometer and the panel padding were counted. */
      height: 72px !important;
    }
    .ws-pscrap-btn {
      flex: 0 0 auto !important;
      width: 100% !important;
      height: 44px !important;
    }
    .ws-fast-toggle-wrap {
      justify-self: center !important;
      align-self: center !important;
      margin-top: 6px;
    }
  }
  /* Same stack, but keyed on the PANEL's width via container query — the panel
     can be squeezed well below 700px by the nav + rewards sidebars while the
     viewport is still wide, which the media query above can't see. The 500px
     media block stays as a fallback for browsers without container queries.
     700px = the widest odometer + fast-reveal toggle + the button's 160px
     minimum + gaps, with breathing room; below that the 3-column grid can't
     fit without overlapping or truncating labels. */
  @container (max-width: 700px) {
    .ws-spin-control-row {
      display: flex;
      flex-direction: column;
      gap: 10px;
      align-items: center;
      height: auto !important;
    }
    .ws-odo-counter {
      width: auto !important;
      align-self: center !important;
    }
    .ws-pspin-btn-wrap {
      width: 100% !important;
      height: auto !important;
      flex-direction: column !important;
    }
    /* The wrap is a COLUMN here, so the buttons' `flex: 1 1 0` put a
       flex-basis of 0 on the vertical axis — which beats the `height` below it
       for a flex item's main size. In a wrap that is itself height:auto there
       is no free space to grow into, so SPIN collapsed to its 2px of border: a
       hairline where the button should be, and no way to spin on a phone.
       Basis auto hands sizing back to the heights. */
    .ws-pspin-btn {
      /* MUST stay positioned. The shine sweep is an absolutely positioned
         ::before that the button clips with its own overflow:hidden — but a
         static button is not the pseudo's containing block, and an absolute box
         whose containing block sits ABOVE the clipper is not clipped by it. As
         `position: static` the sweep escaped the button entirely and swung
         across the page as a big skewed white slab, dragging a horizontal
         scrollbar with it. */
      position: relative !important;
      flex: 0 0 auto !important;
      width: 100% !important;
      /* 100px pushed the button under the fold on a phone once the drum, the
         odometer and the panel padding were counted. */
      height: 72px !important;
    }
    .ws-pscrap-btn {
      flex: 0 0 auto !important;
      width: 100% !important;
      height: 44px !important;
    }
    .ws-fast-toggle-wrap {
      justify-self: center !important;
      align-self: center !important;
      margin-top: 6px;
    }
  }
  /* Drum is a fixed 720px tall on desktop — far too tall for phone/tablet viewports.
     Fade overlays (::before/::after) are scaled down to match so a visible window remains. */
  @media (max-width: 820px) {
    .ws-pdrum { height: 560px; }
    .ws-pdrum::before, .ws-pdrum::after { height: 170px; }
  }
  /* Phones: the drum has to leave room for the odometer, SPIN and the foot row
     on one screen — landing on Wheelspin with the button below the fold makes
     the tab look broken. 300px still shows the winning card whole with its
     neighbours peeking in. */
  @media (max-width: 500px) {
    .ws-pdrum { height: 300px; }
    .ws-pdrum::before, .ws-pdrum::after { height: 90px; }
  }
  /* Panel-width versions of the drum scaling above, for viewports where the
     media queries don't fire but the sidebars have squeezed the panel. */
  @container (max-width: 640px) {
    .ws-pdrum { height: 560px; }
    .ws-pdrum::before, .ws-pdrum::after { height: 170px; }
  }
  @container (max-width: 460px) {
    .ws-pdrum { height: 300px; }
    .ws-pdrum::before, .ws-pdrum::after { height: 90px; }
  }
  .ws-presult-card {
    background: var(--card-bg);
    border: 1.5px solid var(--border);
    border-radius: 16px;
    padding: 36px 28px 28px;
    box-shadow: 0 20px 50px rgba(0,0,0,0.15);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 16px;
    width: 320px;
    max-width: 90%;
    text-align: center;
    box-sizing: border-box;
  }
  .ws-presult-eye {
    font-size: 11px;
    font-weight: 800;
    letter-spacing: 2.5px;
    text-transform: uppercase;
    color: var(--text-muted);
  }
  .ws-presult-ico {
    font-size: 64px;
    line-height: 1;
    margin: 8px 0;
  }
  .ws-presult-name {
    font-size: 26px;
    font-weight: 800;
    color: var(--text);
    line-height: 1.2;
  }
  .ws-presult-rar {
    display: inline-block;
    font-size: 11px;
    font-weight: 800;
    padding: 4px 12px;
    border-radius: 20px;
    letter-spacing: 1px;
    text-transform: uppercase;
    box-sizing: border-box;
  }
  .ws-presult-rar.common     { background: #e5e7eb; color: #6b7280; }
  .ws-presult-rar.uncommon   { background: #16a34a; color: #fff; }
  .ws-presult-rar.rare       { background: #2563eb; color: #fff; }
  .ws-presult-rar.epic       { background: #7c3aed; color: #fff; }
  .ws-presult-rar.legendary  { background: #d97706; color: #fff; }
  .ws-presult-rar.mythic     { background: #c026d3; color: #fff; }

  html.dark .ws-presult-rar.common    { background: #374151; color: #9ca3af; }
  html.dark .ws-presult-rar.uncommon  { background: #15803d; color: #fff; }
  html.dark .ws-presult-rar.rare      { background: #1d4ed8; color: #fff; }
  html.dark .ws-presult-rar.epic      { background: #6d28d9; color: #fff; }
  html.dark .ws-presult-rar.legendary { background: #b45309; color: #fff; }
  html.dark .ws-presult-rar.mythic    { background: #a21caf; color: #fff; }

  .ws-pbtns { display:flex; gap:12px; justify-content:center; margin-top:10px; width: 100%; box-sizing: border-box; }
  .ws-pbtns.show { display:flex; }
  .ws-pbtn {
    flex: 1;
    padding: 12px 0;
    background: var(--bg);
    border: 1.5px solid var(--border);
    color: var(--text-soft);
    border-radius: 12px;
    font-size: 11px;
    font-weight: 700;
    cursor: pointer;
    letter-spacing: 1px;
    text-transform: uppercase;
    transition: all .15s;
    justify-content: center;
    align-items: center;
    display: inline-flex;
  }
  .ws-pbtn:hover { border-color:var(--green); color:var(--green); }
  .ws-pbtn.primary { background:var(--green); border-color:var(--green); color:#fff; }
  .ws-pbtn.primary:hover { background:#15803d; }

  /* Fast Reveal Toggle */
  .ws-fast-toggle-wrap {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    background: #fff;
    border: 1.5px solid var(--border);
    border-radius: 12px;
    padding: 6px 14px;
    height: 52px;
    box-sizing: border-box;
    justify-self: end;
    user-select: none;
  }
  html.dark .ws-fast-toggle-wrap {
    background: #000;
  }
  .ws-fast-toggle-label {
    font-size: 11px;
    font-weight: 800;
    text-transform: uppercase;
    color: var(--text-muted);
    letter-spacing: 0.5px;
    white-space: nowrap;
  }
  .ws-fast-switch {
    position: relative;
    display: inline-block;
    width: 40px;
    height: 23px;
    flex-shrink: 0;
  }
  .ws-fast-switch input {
    opacity: 0;
    width: 0;
    height: 0;
  }
  /* Inset track (not a flat fill) + a springy overshoot on the thumb — reads
     closer to the rest of the site's green-gradient accents (.ws-craft-btn,
     .ws-pspin-btn) than the old plain-color iOS-style switch. */
  .ws-fast-slider {
    position: absolute;
    cursor: pointer;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: var(--border);
    box-shadow: inset 0 1px 3px rgba(0,0,0,.12);
    transition: background .3s ease, box-shadow .3s ease;
    border-radius: 999px;
  }
  .ws-fast-slider:before {
    position: absolute;
    content: "";
    height: 17px;
    width: 17px;
    left: 3px;
    bottom: 3px;
    background: #fff;
    transition: transform .32s cubic-bezier(.34,1.56,.64,1);
    border-radius: 50%;
    box-shadow: 0 2px 5px rgba(0,0,0,.2), 0 1px 2px rgba(0,0,0,.12);
  }
  html.dark .ws-fast-slider {
    background: #2e2e34;
    box-shadow: inset 0 1px 3px rgba(0,0,0,.3);
  }
  .ws-fast-switch:hover .ws-fast-slider {
    box-shadow: inset 0 1px 3px rgba(0,0,0,.12), 0 0 0 3px rgba(22,163,74,.12);
  }
  html.dark .ws-fast-switch:hover .ws-fast-slider {
    box-shadow: inset 0 1px 3px rgba(0,0,0,.3), 0 0 0 3px rgba(74,222,128,.14);
  }
  .ws-fast-switch input:focus-visible + .ws-fast-slider {
    box-shadow: inset 0 1px 3px rgba(0,0,0,.12), 0 0 0 3px rgba(22,163,74,.28);
  }
  .ws-fast-switch input:checked + .ws-fast-slider {
    background: linear-gradient(135deg,#16a34a,#22c55e);
    box-shadow: inset 0 1px 2px rgba(0,0,0,.08);
  }
  .ws-fast-switch input:checked + .ws-fast-slider:before {
    transform: translateX(17px);
  }

  /* Flash + particles */
  #rpFlash { position:fixed; inset:0; background:#fff; opacity:0; pointer-events:none; z-index:9999; transition:opacity .07s; }
  #rpFlash.pop { opacity:.3; }
  #rpPfx { position:fixed; inset:0; pointer-events:none; z-index:9998; }

  /* ── Discount reward: Website vs Etsy picker ─────────────────────────────── */
  .rw-modal-overlay {
    position: fixed; inset: 0; z-index: 10000;
    background: rgba(0,0,0,0.5); backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
    display: flex; align-items: center; justify-content: center; padding: 20px;
  }
  .rw-modal-card {
    position: relative;
    background: var(--card-bg); border: 1.5px solid var(--border); border-radius: 16px;
    padding: 28px 26px; width: 100%; max-width: 420px;
    box-shadow: 0 20px 48px rgba(0,0,0,0.18);
  }
  html.dark .rw-modal-card { box-shadow: 0 20px 48px rgba(0,0,0,0.5); }
  .rw-modal-close {
    position: absolute; top: 14px; right: 14px;
    width: 28px; height: 28px; border-radius: 50%; border: none;
    background: var(--bg); color: var(--text-muted); cursor: pointer;
    display: flex; align-items: center; justify-content: center; font-size: 13px;
    transition: background .15s, color .15s;
  }
  .rw-modal-close:hover { background: var(--border); color: var(--text); }
  .rw-modal-title { font-size: 17px; font-weight: 800; color: var(--text); margin: 0 0 6px; padding-right: 24px; }
  .rw-modal-sub { font-size: 13px; font-weight: 600; color: var(--text-muted); margin: 0 0 20px; line-height: 1.4; }

  /* Claim-from-another-path list. Scrolls inside the card rather than growing
     it — a path can hold every one of its levels unclaimed (15 of 20 is what
     sent us looking at this), and a modal taller than the viewport puts the
     close button off screen. */
  .pc-list { display: flex; flex-direction: column; gap: 8px; max-height: min(52vh, 420px); overflow-y: auto; text-align: left; }
  .pc-empty { font-size: 13px; font-weight: 600; color: var(--text-muted); padding: 18px 0; text-align: center; }
  .pc-row {
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    background: var(--card-bg); border: 1px solid var(--border);
    border-radius: 10px; padding: 10px 12px;
  }
  .pc-row-main { min-width: 0; flex: 1; }
  .pc-row-name { font-size: 13px; font-weight: 800; color: var(--text); overflow-wrap: anywhere; line-height: 1.3; }
  .pc-row-meta { font-size: 11px; font-weight: 600; color: var(--text-muted); margin-top: 2px; }
  .pc-row-btns { display: flex; gap: 6px; flex-shrink: 0; }
  .pc-claim-btn {
    flex-shrink: 0; border: 1px solid var(--border); background: transparent;
    color: var(--text); border-radius: 9px; padding: 7px 14px;
    font-size: 12px; font-weight: 700; cursor: pointer; transition: all .15s;
  }
  .pc-claim-btn:hover { border-color: var(--green); color: var(--green); }
  .pc-claim-btn.primary { background: var(--green); border-color: var(--green); color: #fff; }
  .pc-claim-btn.primary:hover { background: #15803d; border-color: #15803d; color: #fff; }
  /* The pill is the way in to the modal, so it has to look pressable. */
  .sb-claim-pill.pc-open { cursor: pointer; }
  .rw-modal-btns { display: flex; flex-direction: column; gap: 10px; }
  .rw-modal-choice-btn {
    display: flex; align-items: center; gap: 14px; width: 100%;
    padding: 14px 16px; border-radius: 12px; border: 1.5px solid var(--border);
    background: var(--bg); cursor: pointer; text-align: left; font-family: inherit;
    transition: border-color .15s, background .15s, transform .15s;
  }
  .rw-modal-choice-btn:hover:not(:disabled) { border-color: var(--green); background: var(--green-light); transform: translateY(-1px); }
  .rw-modal-choice-btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
  .rw-modal-choice-icon { font-size: 24px; flex-shrink: 0; }
  .rw-modal-choice-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
  .rw-modal-choice-text strong { font-size: 14px; font-weight: 800; color: var(--text); }
  .rw-modal-choice-text span { font-size: 12px; font-weight: 600; color: var(--text-muted); line-height: 1.3; }

  /* ── Mobile bottom tab bar ───────────────────────────────────────────────────
     Phone-only replacement for the inline .pnl-tabs pill switcher, built on the
     dashboard's .dash-mobile-tabbar. On a phone the switcher sat mid-scroll:
     after scrolling down into the wheel or the tier track, getting back to the
     other panel meant scrolling all the way up again. Pinned to the bottom it
     is always one tap away, and the page reads as an app surface the way the
     dashboard does. Desktop is untouched \u2014 everything here is inside the 640px
     query, and the bar itself is display:none above it. */
  .rw-mobile-tabbar { display: none; }
  @media (max-width: 640px) {
    /* A horizontal slide on a full-width phone panel briefly created real page
       overflow and nudged the fixed bottom navigation. Phones keep a subtle
       transition, but it is opacity-only and never leaves the viewport. */
    @keyframes rwMobilePanelFade {
      from { opacity: 0; }
      to { opacity: 1; }
    }
    .slide-in-right,
    .slide-in-left {
      animation: rwMobilePanelFade .22s ease forwards;
    }
    #rwWsPanel,
    #rwWsPanel .ws-panel-wrap,
    #rwWsPanel .ws-pdrum-outer,
    #rwWsPanel .ws-pdrum-row,
    #rwWsPanel .ws-pdrum,
    #rwWsPanel .ws-spin-control-row {
      width: 100%;
      max-width: 100%;
      min-width: 0;
    }
    /* Nothing may hide under the fixed bar \u2014 the tier track and the wheel both
       run to the bottom of the page. */
    body { padding-bottom: calc(76px + env(safe-area-inset-bottom, 0px)); }
    /* The switcher itself moves to the bar; the panel's action buttons (Claim
       ready / Spin History / Edit Wheelspin) stay in .pnl-bar where they are. */
    .pnl-tabs-group { display: none; }
    /* One tab, one screen — the rule now applies at every width, see
       `body.rw-ws-active #seasonDetail` near .pnl-bar at the top of this file. */

    /* Change Path is a pill among pills here, not a bar. At its desktop size
       (40px tall, full-width label) it broke out of the pills row onto its own
       line and read as the banner's primary action, above the path's own name
       and progress. Same row, same shape, same size as PERMANENT PATH beside
       it — it is a secondary control and should look like one. */
    #sbPillsRow .btn-change-path,
    .season-banner.has-photo #sbPillsRow .btn-change-path {
      width: auto;
      flex: 0 0 auto;
      height: 34px;
      min-height: 0;
      padding: 0 15px;
      border-radius: 50px;
      font-size: 11px;
      font-weight: 700;
      letter-spacing: .07em;
      text-transform: uppercase;
      gap: 7px;
    }
    #sbPillsRow .btn-change-path svg,
    .season-banner.has-photo #sbPillsRow .btn-change-path svg { width: 13px; height: 13px; }

    /* Tighter than the desktop rhythm: on a phone the banner and the reward
       track are the whole page, and the switcher that used to sit between them
       has moved to the bottom bar — so the 40px of stacked gaps it needed was
       just empty screen. */
    /* The tier badge is out on phones. Beside "20 LEVELS" it does not fit
       (title + both badges overflow 375px), and on its own row it pushed the
       wheel down by 40px. Nothing is lost: the wheel's tier tabs and the
       odometer's own label ("BRONZE TOKENS") both say which tier you are on. */
    .pnl-actions .ws-tier-badge { display: none !important; }
    /* The wheel's controls are one full-width stack on a phone — token counter,
       SPIN, Fast Reveal — every one of them sized from the screen rather than
       from its own content, so they line up with each other at any width.
       Spin History is not in the panel at all any more: it is the third button
       in the bottom bar, where it costs the fold nothing. */
    #pnlActionsWs #wsHistoryBtn { display: none !important; }
    .ws-odo-counter {
      width: 100% !important;
      align-self: stretch !important;
      justify-content: center;
      position: relative;
    }
    /* The phone counter is one wide control, so its three jobs get deliberate
       anchors instead of being centered as one loose cluster: tier picker at
       the left edge, token balance in the middle, fragments near the right. */
    .ws-odo-counter .ws-tier-toggle-btn {
      position: absolute;
      inset: 0 auto 0 0;
      width: 44px;
      z-index: 1;
    }
    .ws-odo-counter .ws-odo-body {
      width: 100%;
      box-sizing: border-box;
      min-width: 0;
      justify-content: center;
      padding: 6px 92px 6px 56px;
    }
    .ws-odo-counter .ws-odo-body .ws-frag-pill {
      position: absolute;
      top: 50%;
      right: 12px;
      transform: translateY(-50%);
      margin-left: 0;
    }
    .ws-fast-toggle-wrap {
      width: 100% !important;
      justify-content: center;
      justify-self: center !important;
      align-self: stretch !important;
      margin-top: 0 !important;
    }

    /* Tier picker as a bottom sheet, once rwSyncMobileChrome() has moved it out
       of the drum. Two columns instead of four — four on a phone gave each tier
       ~65px, which clipped the cost line and pushed Gold and Platinum off the
       screen entirely. Sits above the tab bar, scrolls if the four cards are
       taller than the room available.
       The id beats .ws-tier-overlay.show on specificity, so the open state has
       to be restated here or the sheet could never slide up. */
    body > #wsTierPicker {
      position: fixed;
      left: 12px;
      right: 12px;
      bottom: calc(80px + env(safe-area-inset-bottom, 0px));
      z-index: 600;
      grid-template-columns: repeat(2, minmax(0, 1fr));
      gap: 10px;
      max-height: 62vh;
      overflow-y: auto;
      -webkit-overflow-scrolling: touch;
      box-shadow: 0 -12px 40px rgba(0,0,0,.45);
      transform: translateY(calc(100% + 96px));
    }
    body > #wsTierPicker.show { transform: translateY(0); }
    .ws-tier-backdrop {
      display: block;
      position: fixed;
      inset: 0;
      z-index: 590;
      background: rgba(0,0,0,.5);
      opacity: 0;
      visibility: hidden;
      pointer-events: none;
      transition: opacity .2s ease, visibility 0s linear .2s;
      -webkit-tap-highlight-color: transparent;
    }
    .ws-tier-backdrop.show {
      opacity: 1;
      visibility: visible;
      pointer-events: auto;
      transition-delay: 0s;
    }
    body > #wsTierPicker .ws-tier-sheet-head {
      grid-column: 1 / -1;
      display: flex;
      align-items: center;
      justify-content: space-between;
      min-height: 42px;
      padding: 0 2px 4px 6px;
      color: var(--text);
      font-size: 15px;
      font-weight: 800;
    }
    .ws-tier-sheet-close {
      width: 42px;
      height: 42px;
      flex: 0 0 42px;
      display: inline-flex;
      align-items: center;
      justify-content: center;
      border: 1px solid var(--border);
      border-radius: 50%;
      background: var(--bg);
      color: var(--text);
      cursor: pointer;
      touch-action: manipulation;
    }
    .ws-tier-sheet-close:active { transform: scale(.94); }
    /* "Cost - 250 fragments" does not fit a 160px card at 12px and nowrap, so
       it was being cut mid-word. Two lines are fine here. */
    body > #wsTierPicker .ws-craft-progress {
      font-size: 10.5px;
      white-space: normal;
      line-height: 1.25;
    }
    body > #wsTierPicker .ws-craft-btn { min-width: 0; width: 100%; padding: 7px 8px; }
    .rw-main, .rw-center { gap: 12px; }
    #seasonDetail { gap: 12px !important; }
    .pnl-bar { margin: 0; }
    .rw-mobile-tabbar {
      display: grid;
      grid-template-columns: repeat(3, minmax(0, 1fr));
      align-items: stretch;
      position: fixed;
      inset-inline: 0;
      bottom: 0;
      width: 100%;
      min-height: 68px;
      z-index: 500;
      background: var(--card-bg);
      border-top: 1px solid var(--border);
      padding: 6px 4px calc(6px + env(safe-area-inset-bottom, 0px));
      box-shadow: 0 -2px 12px rgba(0,0,0,.06);
      /* Own layer: the wheel animates behind it on every spin. */
      transform: translateZ(0);
      contain: layout paint;
    }
    html.dark .rw-mobile-tabbar { box-shadow: none; }
    .rw-mtab {
      min-width: 0;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      gap: 4px;
      padding: 7px 4px;
      border: none;
      background: none;
      color: var(--text-muted);
      font-family: inherit;
      font-size: 11px;
      line-height: 1;
      font-weight: 700;
      cursor: pointer;
      border-radius: 10px;
      transition: color .15s;
    }
    .rw-mtab svg { display: block; width: 20px; height: 20px; flex: 0 0 20px; }
    .rw-mtab span {
      max-width: 100%;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }
    .rw-mtab.active { color: var(--green); }
    html.dark .rw-mtab.active { color: #4ADE80; }

    /* Toasts clear the fixed bar instead of landing on top of it — "Path
       switched to …" was sitting across Wheelspin and Spin History, hiding the
       navigation at the exact moment you might want it. Centred, full-gutter,
       and lifted to just above the bar.
       The identical layout lives in styles.css for every other page, BUT this
       page does not link styles.css (see the .toast block above, duplicated
       here for the same reason) — so it has to be spelled out again rather
       than just setting the variable. Keep the two in sync — including the
       104px resting height, which is deliberately the same on every page. */
    #toastContainer {
      left: max(12px, env(safe-area-inset-left, 0px));
      right: max(12px, env(safe-area-inset-right, 0px));
      bottom: calc(var(--toast-bottom-gap, 104px) + env(safe-area-inset-bottom, 0px));
      align-items: center;
    }
    #toastContainer .toast { width: 100%; max-width: 420px; }
  }

  /* ── Touch feedback ──────────────────────────────────────────────────────────
     Android paints a translucent blue box over whatever you touch, and a quick
     double-tap selects the label text underneath it — so tapping a tab or a
     pill left a "selected with a mouse" highlight sitting on the control. The
     controls already report their own state (the green tab, the pressed pill),
     so the browser's default flash is pure noise here.
     Text itself stays selectable: user-select is switched off only on the
     things you press, never on the page's copy. */
  * { -webkit-tap-highlight-color: transparent; }
  button,
  .rw-mtab,
  .pnl-tab,
  .sb-path-pill,
  .sb-days-pill,
  .tier-total-badge,
  .ws-pt,
  .ws-fast-toggle-wrap,
  .ws-fast-switch,
  .ws-odo-counter,
  .btn-change-path,
  .btn-edit-paths,
  .picker-banner,
  .picker-completed,
  .ppf-btn,
  .rw-modal-choice-btn,
  .claim-ready-pill,
  .ws-tier-badge {
    -webkit-user-select: none;
    -moz-user-select: none;
    user-select: none;
    -webkit-touch-callout: none;
  }

/* ── Unclaimed XP, with no path selected ──────────────────────────────────────
   Standalone twin of the banner's #xpStrip pill, for the state where
   #seasonDetail is hidden and that pill goes with it. Sits directly above the
   path picker, so the balance and the way to spend it are on screen together.
   Driven by renderPoolNudge() in rewards.js. */
.xp-bank-solo {
  display: flex;
  align-items: center;
  gap: 13px;
  margin: 0 0 14px;
  padding: 13px 16px;
  background: var(--card-bg);
  border: 1.5px solid var(--border);
  border-left: 4px solid #F2A420;
  border-radius: 12px;
}
.xp-bank-solo-icon {
  width: 30px;
  height: 30px;
  flex-shrink: 0;
  object-fit: contain;
}
.xp-bank-solo-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.xp-bank-solo-text strong {
  color: var(--text);
  font-size: 14.5px;
  font-weight: 800;
}
.xp-bank-solo-text span {
  color: var(--text-muted);
  font-size: 12.5px;
  line-height: 1.45;
}
@media (max-width: 560px) {
  .xp-bank-solo { padding: 11px 13px; gap: 11px; }
  .xp-bank-solo-text strong { font-size: 13.5px; }
  .xp-bank-solo-text span { font-size: 11.5px; }
}
/* The claim button on the standalone bar: same gold rail as the banner's, sized
   for a plain row rather than the pill group, and pushed to the right edge. */
.xp-bank-solo-btn {
  margin-left: auto;
  flex-shrink: 0;
  height: 36px;
  border-radius: 50px;
}
@media (max-width: 560px) {
  .xp-bank-solo { flex-wrap: wrap; }
  .xp-bank-solo-btn { margin-left: 0; width: 100%; justify-content: center; }
}

/* Levels with a 2nd reward option ------------------------------------------ */
.bp-tile-choice {
  position: absolute; top: 6px; left: 6px;
  padding: 1px 6px; border-radius: 999px;
  font-size: 10px; font-weight: 800; letter-spacing: .2px;
  color: var(--green); background: var(--green-light);
}
.bp-detail-alt {
  display: flex; align-items: center; gap: 6px; margin-top: 6px;
  font-size: 12.5px; font-weight: 700; color: var(--text-soft); min-width: 0;
}
.bp-detail-alt-or {
  flex-shrink: 0; padding: 1px 6px; border-radius: 999px;
  font-size: 10px; font-weight: 800; text-transform: uppercase; letter-spacing: .4px;
  color: var(--green); background: var(--green-light);
}
.bp-detail-alt-icon { flex-shrink: 0; display: inline-flex; }
.bp-detail-alt-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text); }
.bp-detail-alt-qty { flex-shrink: 0; color: var(--text-muted); }
.rl-or { font-weight: 600; color: var(--text-muted); }
