/* ============================================================
   home-fx.css — guest home visual system (Slab 4)
   Port of reference `home_referrance.html` fx-* components.
   Consumes tokens from theme-tokens.css (data-theme aware).
   Scoped: every class here is prefixed fx- / home- and used
   only by views/guest/home.php.
   ============================================================ */

.fx-shell {
  --radius-card: 18px;
  --radius-ui: 10px;
  --radius-ui: 8px;
  --lime-ink: #0a1206;
  font-family: var(--font-body);
}

.fx-shell ::selection { background: var(--lime); color: var(--lime-ink); }

.fx-shell h1,
.fx-shell h2,
.fx-shell h3,
.fx-shell .fx-display { font-family: var(--font-display); letter-spacing: -0.01em; }
.fx-shell .fx-mono { font-family: var(--font-mono); letter-spacing: 0.01em; }
.fx-shell .fx-muted { color: var(--text-2); }
.fx-shell .fx-eyebrow {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--mint);
}

.fx-shell a:focus-visible,
.fx-shell button:focus-visible,
.fx-shell input:focus-visible {
  outline: 2px solid var(--lime);
  outline-offset: 2px;
  border-radius: 6px;
}

/* ============ MATRIX EFFECT INTENSITY ============
   Driven by the `matrix_effect_intensity` setting (admin → Branding & Theme).
   The tier lands on <html data-fx-fx="none|low|medium|high"> from
   views/guest/header.php (no-JS fallback) and assets/js/fx-intensity.js
   (resolves `smart`, refines before first paint). Every decorative layer
   scales off these four multipliers, so one setting retunes the whole
   ambient effect — and the cheap tiers stop the loops that cost the most.
   `medium` is the default when the attribute is absent (user area). */
.fx-shell {
  --fx-rain-opacity: 0.5;
  --fx-grid-opacity: 1;
  --fx-scan-opacity: 0.5;
  --fx-ambient: 1;
}
[data-fx-fx="none"]   { --fx-rain-opacity: 0;    --fx-grid-opacity: 0;   --fx-scan-opacity: 0;   --fx-ambient: 0; }
[data-fx-fx="low"]    { --fx-rain-opacity: 0.22; --fx-grid-opacity: 0.6; --fx-scan-opacity: 0.35; --fx-ambient: 0; }
[data-fx-fx="medium"] { --fx-rain-opacity: 0.5;  --fx-grid-opacity: 1;   --fx-scan-opacity: 0.5;  --fx-ambient: 1; }
[data-fx-fx="high"]   { --fx-rain-opacity: 0.62; --fx-grid-opacity: 1;   --fx-scan-opacity: 0.6;  --fx-ambient: 1; }

/* Ambient loops (endless animations) are pure decoration, so the two cheap
   tiers switch them off entirely instead of slowing them down: an animation
   running at 0 opacity still costs compositing work every frame. The marquee
   (42s) and the payouts ticker (22s) are content with their own pause
   controls, so they keep looping at every tier. */
[data-fx-fx="none"] .fx-glyph img,
[data-fx-fx="low"] .fx-glyph img,
[data-fx-fx="none"] .fx-pulse-dot::after,
[data-fx-fx="low"] .fx-pulse-dot::after { animation: none; }

/* ============ BACKGROUND FX (hero only) ============ */
#fx-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: var(--fx-rain-opacity, 0.5);
}
[data-fx-fx="none"] #fx-canvas { display: none; }
.fx-grid {
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(to right, color-mix(in srgb, var(--lime) 5%, transparent) 1px, transparent 1px),
    linear-gradient(to bottom, color-mix(in srgb, var(--lime) 5%, transparent) 1px, transparent 1px);
  background-size: 42px 42px;
  mask-image: radial-gradient(ellipse 80% 60% at 50% 20%, black 40%, transparent 90%);
  opacity: var(--fx-grid-opacity, 1);
}
.fx-scanline {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: repeating-linear-gradient(to bottom, rgba(255,255,255,0.015) 0px, rgba(255,255,255,0.015) 1px, transparent 1px, transparent 3px);
  opacity: var(--fx-scan-opacity, 0.5);
}

/* ============ GLYPH (signature mark) ============ */
.fx-glyph {
  position: relative;
  width: 38px;
  height: 38px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.fx-glyph img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  filter: drop-shadow(0 0 7px color-mix(in srgb, var(--lime) 28%, transparent));
  animation: fx-brand-breathe 6.8s ease-in-out infinite;
  transform-origin: center;
  will-change: transform;
}
@keyframes fx-brand-breathe {
  0%, 100% { transform: translateY(0) scale(1); }
  50% { transform: translateY(-1px) scale(1.025); }
}

/* ============ GLITCH (rare, hero mark only) ============ */
.fx-glitchable { position: relative; display: inline-block; }
.fx-glitchable.fx-glitching::before,
.fx-glitchable.fx-glitching::after {
  content: attr(data-text);
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
}
.fx-glitchable.fx-glitching::before {
  color: var(--mint);
  transform: translate(-2px, 0);
  clip-path: inset(10% 0 60% 0);
}
.fx-glitchable.fx-glitching::after {
  color: var(--magenta);
  transform: translate(2px, 0);
  clip-path: inset(55% 0 10% 0);
}

/* ============ CARDS / SURFACES ============ */
.fx-shell .fx-card {
  background: color-mix(in srgb, var(--surface) 92%, transparent);
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-card);
}
html[data-theme='dark'] .fx-shell .fx-card {
  box-shadow: 0 0 0 1px rgba(184,255,56,0.03), 0 20px 60px -30px rgba(0,0,0,0.7);
}
html[data-theme='light'] .fx-shell .fx-card {
  box-shadow: 0 12px 30px -20px rgba(6,16,11,0.15);
}

.fx-shell .fx-card-hover { transition: transform 0.35s ease, border-color 0.35s ease; }
@media (hover: hover) {
.fx-shell .fx-card-hover:hover {
          transform: translateY(-4px);
          border-color: color-mix(in srgb, var(--lime) 45%, var(--surface-border));
    }
}

/* ---- fx-menu: dropdown surfaces (profile menu) ---------------------------
   Token-driven on purpose. The old markup used `bg-matrix-bg`, which both
   themes override with the same flat slate, so the menu lost its elevation
   and blended into the page in light mode. Everything here derives from
   --surface / --surface-2 / --surface-border, so switching data-theme
   restyles it with no extra rules. */
.fx-shell .fx-menu {
  background: color-mix(in srgb, var(--surface) 96%, transparent);
  border: 1px solid var(--surface-border);
  border-radius: var(--radius);
  backdrop-filter: blur(12px);
  overflow: hidden;
}
html[data-theme='dark'] .fx-shell .fx-menu { box-shadow: 0 18px 50px -24px rgba(0, 0, 0, 0.85); }
html[data-theme='light'] .fx-shell .fx-menu { box-shadow: 0 14px 34px -18px rgba(6, 16, 11, 0.28); }

.fx-shell .fx-menu-head {
  padding: 0.85rem 1rem;
  border-bottom: 1px solid var(--surface-border);
  background: color-mix(in srgb, var(--surface-2) 70%, transparent);
}
.fx-shell .fx-menu-name {
  font-family: var(--font-display);
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--text-1);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.fx-shell .fx-menu-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-top: 0.4rem;
  font-size: 0.8125rem;
}
.fx-shell .fx-menu-row span:first-child { color: var(--text-2); }
.fx-shell .fx-menu-row span:last-child { color: var(--text-1); font-weight: 600; }

.fx-shell .fx-menu-item {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  width: 100%;
  padding: 0.6rem 1rem;
  font-size: 0.875rem;
  color: var(--text-1);
  transition: background 150ms ease, color 150ms ease;
}
/* Split, not gated as one list: :focus-visible must survive on desktop
   keyboards, and gating the whole rule would delete that style. */
.fx-shell .fx-menu-item:focus-visible {
  background: color-mix(in srgb, var(--lime) 12%, transparent);
  color: var(--lime);
}
@media (hover: hover) {
.fx-shell .fx-menu-item:hover {
  background: color-mix(in srgb, var(--lime) 12%, transparent);
  color: var(--lime);
}
}
.fx-shell .fx-menu-icon { width: 1rem; text-align: center; color: var(--text-3); }
@media (hover: hover) {
.fx-shell .fx-menu-item:hover .fx-menu-icon { color: var(--lime); }
}

.fx-shell .fx-menu-label {
  padding: 0.7rem 1rem 0.25rem;
  font-size: 0.625rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-3);
}
.fx-shell .fx-menu-sep { height: 1px; background: var(--surface-border); }
.fx-shell .fx-menu-danger { color: var(--magenta); }
.fx-shell .fx-menu-danger .fx-menu-icon { color: var(--magenta); }
.fx-shell .fx-menu-danger:focus-visible {
  background: color-mix(in srgb, var(--magenta) 14%, transparent);
  color: var(--magenta);
}
@media (hover: hover) {
.fx-shell .fx-menu-danger:hover {
  background: color-mix(in srgb, var(--magenta) 14%, transparent);
  color: var(--magenta);
}
}
/* The menu-item hover icon rule is (0,4,0) and would otherwise recolour the
 * icon lime while the label stays red, so pin the icon to the danger colour.
 * This also absorbs the duplicate hover-only rule that used to sit here. */
@media (hover: hover) {
.fx-shell .fx-menu-danger:hover .fx-menu-icon { color: var(--magenta); }
}
.fx-shell .fx-menu-danger:focus-visible .fx-menu-icon { color: var(--magenta); }

.fx-shell .fx-card-featured {
  position: relative;
  overflow: hidden;
  background:
    linear-gradient(135deg, color-mix(in srgb, var(--lime) 8%, transparent), transparent 50%),
    color-mix(in srgb, var(--surface) 94%, transparent);
}
.fx-shell .fx-card-featured::after {
  content: '';
  position: absolute;
  width: 160px;
  height: 160px;
  right: -90px;
  bottom: -110px;
  border: 1px solid color-mix(in srgb, var(--lime) 20%, transparent);
  border-radius: 50%;
  box-shadow: 0 0 70px color-mix(in srgb, var(--lime) 9%, transparent);
  pointer-events: none;
}

.fx-shell .fx-icon-box {
  width: 42px;
  height: 42px;
  border-radius: 12px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--mint);
  background: color-mix(in srgb, var(--mint) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--mint) 22%, transparent);
}

.fx-shell .fx-network-card {
  background:
    radial-gradient(circle at 90% 0%, color-mix(in srgb, var(--mint) 12%, transparent), transparent 36%),
    color-mix(in srgb, var(--surface) 94%, transparent);
}
.fx-shell .fx-network-metric {
  min-width: 0;
  padding: 12px;
  border-radius: 12px;
  background: color-mix(in srgb, var(--bg) 55%, transparent);
  border: 1px solid var(--surface-border);
}
.fx-shell .fx-rail-logo {
  min-height: 52px;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px;
  border-radius: 12px;
  background: color-mix(in srgb, var(--bg) 52%, transparent);
  border: 1px solid var(--surface-border);
}
.fx-shell .fx-logo-plate {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  border-radius: 8px;
  overflow: hidden;
  color: var(--text-1);
  text-decoration: none;
  letter-spacing: 0.02em;
  transition: color 0.2s ease;
}
@media (hover: hover) {
.fx-shell .fx-logo-plate:hover { color: var(--mint); }
}
.fx-shell .fx-logo-plate img { object-fit: contain; }

/* ============ WAYS TO EARN SLIDER ============ */
.fx-shell .fx-slider-shell { position: relative; }
.fx-shell .fx-slider-viewport { overflow: hidden; }
.fx-shell .fx-slider-track {
  display: flex;
  gap: 16px;
  transition: transform 0.65s var(--ease-out);
  will-change: transform;
  padding: 0.25rem 0rem;
}
.fx-shell .fx-earn-slide { flex: 0 0 calc((100% - 32px) / 3); min-width: 0; }
.fx-shell .fx-slider-control {
  width: 44px;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 12px;
  border: 1px solid var(--surface-border);
  color: var(--text-1);
  background: color-mix(in srgb, var(--surface) 90%, transparent);
  transition: border-color 0.2s ease, background 0.2s ease, color 0.2s ease;
}
@media (hover: hover) {
.fx-shell .fx-slider-control:hover { border-color: var(--mint); color: var(--mint); }
}
.fx-shell .fx-slider-dots { display: flex; align-items: center; gap: 7px; }
.fx-shell .fx-slider-dot {
  width: 7px;
  height: 7px;
  border-radius: 999px;
  background: var(--surface-border);
  transition: width 0.25s ease, background 0.25s ease;
}
.fx-shell .fx-slider-dot.active { width: 22px; background: var(--lime); }

/* ============ CURRENCY MARQUEE ============ */
.fx-shell .fx-currency-marquee {
  position: relative;
  overflow: hidden;
  mask-image: linear-gradient(to right, transparent, black 7%, black 93%, transparent);
}
.fx-shell .fx-currency-track {
  display: flex;
  width: max-content;
  animation: fx-currency-scroll 42s linear infinite;
}
.fx-shell .fx-currency-group { display: flex; gap: 14px; padding-right: 14px; }
/* :focus-within and .is-paused are not hover, so they stay outside the gate. */
.fx-shell .fx-currency-marquee:focus-within .fx-currency-track,
.fx-shell .fx-currency-marquee.is-paused .fx-currency-track { animation-play-state: paused; }
@media (hover: hover) {
.fx-shell .fx-currency-marquee:hover .fx-currency-track { animation-play-state: paused; }
}
.fx-shell .fx-currency-marquee:focus-visible {
  outline: 2px solid var(--lime);
  outline-offset: 5px;
  border-radius: 14px;
}
@keyframes fx-currency-scroll { to { transform: translateX(-50%); } }
.fx-shell .fx-currency-item {
  min-width: 158px;
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 12px 14px;
  border-radius: 14px;
  border: 1px solid var(--surface-border);
  background: color-mix(in srgb, var(--surface) 92%, transparent);
}
.fx-shell .fx-currency-item img { width: 38px; height: 38px; object-fit: contain; }
.fx-shell .fx-currency-item strong { display: block; font-family: var(--font-mono); font-size: 12px; }
.fx-shell .fx-currency-item span { display: block; color: var(--text-2); font-size: 11.5px; margin-top: 1px; }

/* ============ PAYOUTS FEED (vertical ticker) ============ */
.fx-shell .fx-ticker {
  position: relative;
  overflow: hidden;
}
.fx-shell .fx-ticker-track {
  display: flex;
  flex-direction: column;
  width: 100%;
}
.fx-shell .fx-ticker-track.is-anim {
  animation: fx-ticker-loop 22s linear infinite;
}
/* :focus-within is not hover and must keep pausing the ticker for keyboards. */
.fx-shell .fx-ticker:focus-within .fx-ticker-track.is-anim { animation-play-state: paused; }
@media (hover: hover) {
.fx-shell .fx-ticker:hover .fx-ticker-track.is-anim { animation-play-state: paused; }
}
@keyframes fx-ticker-loop { to { transform: translateY(-50%); } }
.fx-shell .fx-payout-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 6px;
  border-bottom: 1px solid var(--surface-border);
  min-height: 48px;
}
.fx-shell .fx-payout-row:last-child { border-bottom: none; }
.fx-shell .fx-payout-avatar {
  position: relative;
  width: 34px;
  height: 34px;
  flex: 0 0 auto;
  border-radius: 999px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 600;
  overflow: visible;
  background: color-mix(in srgb, var(--lime) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--lime) 30%, transparent);
  color: var(--text-1);
}
.fx-shell .fx-payout-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 999px;
}
.fx-shell .fx-payout-flag {
  position: absolute;
  right: -2px;
  bottom: -2px;
  width: 15px;
  height: 15px;
  border-radius: 999px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 9px;
  font-weight: 700;
  font-family: var(--font-mono);
  background: var(--bg);
  border: 1px solid var(--surface-border);
  color: var(--mint);
  line-height: 1;
}
.fx-shell .fx-payout-who { min-width: 0; flex: 1 1 auto; }
/* Badge after the username in the payout ticker. flex + min-width:0 so the
   username keeps its ellipsis instead of the badge being clipped away. */
.fx-shell .fx-payout-name {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  min-width: 0;
}
.fx-shell .fx-payout-badge {
  flex: none;
  width: 1rem;
  height: 1rem;
  object-fit: contain;
}
/* Profile header badge. Same accent role as the leaderboard badge: a small
   round mark beside the name, never a second avatar. */
.fx-profile-badge {
  flex: none;
  width: 1.375rem;
  height: 1.375rem;
  object-fit: contain;
}
.fx-shell .fx-payout-amount {
  font-family: var(--font-mono);
  font-size: 12.5px;
  color: var(--lime);
  white-space: nowrap;
  text-align: right;
}

/* ============ AD SLOT ============ */
.fx-shell .fx-ad-slot {
  width: min(100%, var(--ad-width, 728px));
  min-height: var(--ad-height, 90px);
  margin-inline: auto;
  display: grid;
  place-items: center;
  padding: 12px;
  color: var(--text-2);
  font: 11px/1.4 var(--font-mono);
  text-align: center;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  border: 1px dashed color-mix(in srgb, var(--mint) 28%, var(--surface-border));
  border-radius: 12px;
  background: color-mix(in srgb, var(--surface) 60%, transparent);
}
.fx-shell .fx-ad-slot strong { color: var(--mint); font-weight: 500; }

/* ============ BUTTONS ============
   The same styles drive the signed-in shell and the JS-built modals (present
   modals mount on <body>, outside .fx-shell, so they are named explicitly).
   Every variant always carries a background and a hover accent. */
.fx-shell .fx-btn-primary,
.fx-modal--present .fx-btn-primary {
  background: var(--lime);
  color: var(--lime-ink);
  font-weight: 600;
  border-radius: var(--radius-ui);
  padding: 12px 22px;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  transition: transform 0.2s ease, box-shadow 0.2s ease, filter 0.2s ease;
}

.fx-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: .375rem;
    color: var(--ct-primary, #00ff66);
    background: transparent;
    cursor: pointer;
    padding: 0;
    font-size: .8rem;
    line-height: 1;
    transition: background .15s ease;
}

@media (hover: hover) {
.fx-shell .fx-btn-primary:hover,
.fx-modal--present .fx-btn-primary:hover {
          transform: translateY(-1px);
          filter: brightness(1.06);
          box-shadow: 0 8px 24px -8px color-mix(in srgb, var(--lime) 55%, transparent);
    }
}
.fx-shell .fx-btn-secondary,
.fx-modal--present .fx-btn-secondary {
  background: color-mix(in srgb, var(--lime) 8%, transparent);
  color: var(--text-1);
  border: 1px solid color-mix(in srgb, var(--lime) 45%, var(--surface-border));
  border-radius: var(--radius-ui);
  padding: 12px 22px;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  transition: background 0.2s ease, box-shadow 0.2s ease;
}
@media (hover: hover) {
.fx-shell .fx-btn-secondary:hover,
.fx-modal--present .fx-btn-secondary:hover {
          background: color-mix(in srgb, var(--lime) 16%, transparent);
          box-shadow: 0 0 0 1px color-mix(in srgb, var(--lime) 30%, transparent);
    }
}
.fx-shell .fx-btn-ghost,
.fx-modal--present .fx-btn-ghost {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 10px 16px;
  border-radius: var(--radius-ui);
  color: var(--text-1);
  background: color-mix(in srgb, var(--text-1) 7%, transparent);
  border: 1px solid var(--surface-border);
  transition: color 0.2s ease, background 0.2s ease, border-color 0.2s ease;
}
@media (hover: hover) {
.fx-shell .fx-btn-ghost:hover,
.fx-modal--present .fx-btn-ghost:hover {
          color: var(--text-1);
          background: color-mix(in srgb, var(--text-1) 13%, transparent);
          border-color: color-mix(in srgb, var(--lime) 35%, var(--surface-border));
    }
}
.fx-shell .fx-btn-ghost:disabled,
.fx-modal--present .fx-btn-ghost:disabled { opacity: 0.5; cursor: not-allowed; }

/* Destructive / "undo" action (e.g. unequip). Red-tinted so it never reads as
   the happy-path CTA, but keeps the same box model as the other variants. */
.fx-shell .fx-btn-danger,
.fx-modal--present .fx-btn-danger {
  background: color-mix(in srgb, #e5484d 15%, transparent);
  color: #ff8f94;
  border: 1px solid color-mix(in srgb, #e5484d 50%, var(--surface-border));
  border-radius: var(--radius-ui);
  padding: 12px 22px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  transition: background 0.2s ease, box-shadow 0.2s ease, color 0.2s ease;
}
@media (hover: hover) {
.fx-shell .fx-btn-danger:hover,
.fx-modal--present .fx-btn-danger:hover {
          background: color-mix(in srgb, #e5484d 26%, transparent);
          color: #ffb0b3;
          box-shadow: 0 0 0 1px color-mix(in srgb, #e5484d 45%, transparent);
    }
}
.fx-shell .fx-btn-danger:disabled,
.fx-modal--present .fx-btn-danger:disabled { opacity: 0.5; cursor: not-allowed; }

/* ============ MISC ============ */
.fx-shell .fx-pulse-dot {
  position: relative;
  width: 8px;
  height: 8px;
  border-radius: 999px;
  background: var(--mint);
  flex: 0 0 auto;
}
.fx-shell .fx-pulse-dot::after {
  content: '';
  position: absolute;
  inset: -4px;
  border-radius: 999px;
  border: 1px solid var(--mint);
  animation: fx-ring 2s ease-out infinite;
}
@keyframes fx-ring {
  0% { transform: scale(0.6); opacity: 0.8; }
  100% { transform: scale(2.1); opacity: 0; }
}

.fx-shell .fx-divider {
  height: 1px;
  background: linear-gradient(to right, transparent, var(--surface-border), transparent);
}

.fx-shell .fx-reveal {
  opacity: 1;
  transform: none;
}

/* ============ SUPPORTED CURRENCIES GRID (Slab 6 token) ============ */
.fx-shell .fx-currency-card {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 16px;
}
.fx-shell .fx-currency-card img {
  width: 34px;
  height: 34px;
  object-fit: contain;
}
.fx-shell .fx-progress {
  height: 6px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--text-2) 14%, transparent);
  overflow: hidden;
}
.fx-shell .fx-progress > span {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: linear-gradient(to right, var(--mint), var(--lime));
  transition: width 0.6s ease;
}
.fx-shell .fx-card.is-collapsed { display: none; }
.fx-shell .fx-more-btn {
  border: 1px dashed var(--surface-border);
  border-radius: 12px;
  color: var(--text-2);
  font-family: var(--font-mono);
  font-size: 13px;
  transition: border-color 0.2s ease, color 0.2s ease;
}
@media (hover: hover) {
.fx-shell .fx-more-btn:hover { border-color: var(--mint); color: var(--mint); }
}

/* ============ USER SHELL — SIDEBAR + HEADER + BACKGROUND ============ */
/* Restyle-in-place: same fx-card-featured gradient language, nav logic untouched. */
.fx-shell #sidebar {
  background:
    linear-gradient(160deg, color-mix(in srgb, var(--lime) 7%, transparent), transparent 45%),
    color-mix(in srgb, var(--surface) 84%, transparent);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}
.fx-shell #sidebar .fx-nav-item {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.55rem 0.9rem;
  margin: 0.15rem 0.5rem;
  border-radius: 0.65rem;
  min-height: 44px;
  font-size: 0.875rem;
  color: var(--text-2);
  transition: background 0.15s ease, color 0.15s ease;
}
@media (hover: hover) {
.fx-shell #sidebar .fx-nav-item:hover {
          color: var(--lime);
          background: color-mix(in srgb, var(--lime) 8%, transparent);
    }
}
.fx-shell #sidebar .fx-nav-item.active {
  color: var(--lime);
  background: linear-gradient(90deg, color-mix(in srgb, var(--lime) 20%, transparent), var(--surface) 85%);
  box-shadow: inset 2px 0 0 var(--lime);
}
.fx-shell #sidebar .fx-nav-item.active::after {
  content: '';
  position: absolute;
  right: 0.75rem;
  top: 50%;
  translate: 0 -50%;
  width: 6px;
  height: 6px;
  border-radius: 999px;
  background: var(--lime);
  box-shadow: 0 0 8px color-mix(in srgb, var(--lime) 70%, transparent);
}
.fx-shell #sidebar .fx-nav-section-label {
  color: var(--text-3);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 1.1rem 1.35rem 0.45rem;
  display: block;
}
.fx-shell header.fx-topbar {
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--lime) 5%, transparent), transparent 70%),
    color-mix(in srgb, var(--surface) 86%, transparent);
  border-bottom: 1px solid color-mix(in srgb, var(--lime) 12%, transparent);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}
html[data-theme="light"] .fx-shell header.fx-topbar {
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--lime) 7%, #fff 93%), transparent 70%),
    var(--matrix-bg, #d3dcd3);
}

/* Signed-in area: the top bar and the sidebar brand box share one height, so
   their bottom borders sit on the same line across the top of the page and the
   two read as a single rule. The height is a variable rather than padding maths
   because the brand box is content-sized (32px logo + py-4) while the top bar is
   sized by its 36px controls: pinning both to 65px keeps them locked together
   even if the site name wraps or a control grows. Scoped to .fx-shell-user so
   the guest top bar (61px, px-4 py-3) is untouched. */
.fx-shell-user {
  --fx-brandbar-h: 65px;
}
.fx-shell-user header.fx-topbar,
.fx-shell-user #sidebar .fx-brandbar {
  height: var(--fx-brandbar-h);
}
.fx-shell-user #sidebar .fx-brandbar {
  display: flex;
  align-items: center;
}
.fx-shell-user header.fx-topbar > .fx-topbar-row {
  height: 100%;
}

/* Page background — same gradient language as fx-card-featured, theme-aware. */
body.fx-body {
  background:
    radial-gradient(900px 480px at 78% -12%, color-mix(in srgb, var(--lime) 9%, transparent), transparent 62%),
    radial-gradient(720px 420px at 4% 108%, color-mix(in srgb, var(--mint) 7%, transparent), transparent 60%),
    linear-gradient(135deg, color-mix(in srgb, var(--lime) 5%, transparent), transparent 40%),
    var(--bg);
}
html[data-theme="light"] body.fx-body {
  background:
    radial-gradient(900px 480px at 78% -12%, color-mix(in srgb, var(--lime) 12%, #fff), transparent 62%),
    radial-gradient(720px 420px at 4% 108%, color-mix(in srgb, var(--mint) 9%, transparent), transparent 60%),
    linear-gradient(135deg, color-mix(in srgb, var(--lime) 8%, transparent), transparent 40%),
    var(--matrix-bg, #d3dcd3);
}

/* Guest static pages (FAQ / Updates) — same panel language as fx-card */
.fx-panel {
  background: var(--surface);
  border: var(--edge);
  border-radius: var(--radius);
}
@media (hover: hover) {
.fx-panel:hover {
          border-color: color-mix(in srgb, var(--lime) 30%, var(--surface-border));
    }
}

/* ============ GUEST PAGE HEADER + EMPTY STATE ============ */
/* ============ GUEST NAV CHIPS + ICON BUTTON ============ */
.fx-shell .fx-topbar {
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--surface-border);
}
.fx-shell .fx-nav-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.4rem 0.7rem;
  border-radius: 0.55rem;
  font-size: 0.82rem;
  font-weight: 500;
  color: var(--text-2);
  text-decoration: none;
  border: 1px solid transparent;
  transition: color 0.2s ease, background 0.2s ease, border-color 0.2s ease;
}
@media (hover: hover) {
.fx-shell .fx-nav-chip:hover {
          color: var(--text-1);
          background: color-mix(in srgb, var(--text-1) 6%, transparent);
          border-color: var(--surface-border);
    }
}
.fx-shell .fx-nav-chip i { font-size: 0.72rem; opacity: 0.8; }
.fx-shell .fx-icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 0.55rem;
  cursor: pointer;
  color: var(--text-2);
  border: 1px solid var(--surface-border);
  background: color-mix(in srgb, var(--text-1) 4%, transparent);
  transition: color 0.2s ease, border-color 0.2s ease, background 0.2s ease;
}
@media (hover: hover) {
.fx-shell .fx-icon-btn:hover {
          color: var(--lime);
          border-color: color-mix(in srgb, var(--lime) 45%, transparent);
    }
}
/* Tailwind's Play CDN injects its stylesheet before this file is parsed, so at
 * equal specificity the `display: inline-flex` above outranked Tailwind's
 * responsive `hidden` and the guest menu button stayed visible on desktop.
 * Restore the utility's intent for any responsive-hidden icon button. The
 * sidebar and top-bar nav swap places at lg (1024px) — 8 nav chips only fit
 * from there up. */
@media (min-width: 1024px) {
  .fx-shell .fx-icon-btn.lg\:hidden { display: none; }
}

.fx-shell .fx-page-head {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-lg, 20px);
  padding: 2rem 1.75rem;
  margin-bottom: 1.75rem;
  background:
    radial-gradient(120% 140% at 0% 0%,
      color-mix(in srgb, var(--lime) 14%, transparent) 0%, transparent 58%),
    radial-gradient(90% 120% at 100% 0%,
      color-mix(in srgb, var(--mint) 12%, transparent) 0%, transparent 55%),
    var(--surface);
  border: var(--edge);
}
.fx-shell .fx-page-head::after {
  content: '';
  position: absolute;
  inset: auto 0 0 0;
  height: 2px;
  background: linear-gradient(90deg, transparent, var(--lime), var(--mint), transparent);
  opacity: 0.65;
}
.fx-shell .fx-page-head h1 {
  font-size: clamp(1.5rem, 1.1rem + 1.6vw, 2.1rem);
  line-height: 1.15;
  margin: 0;
  display: flex;
  align-items: center;
  gap: 0.6rem;
}
.fx-shell .fx-page-head p {
  margin: 0.5rem 0 0;
  color: var(--text-2);
  font-size: 0.875rem;
  max-width: 100%;
}
.fx-shell .fx-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.6rem;
  text-align: center;
  padding: 2.5rem 1.25rem;
  border: 1px dashed var(--surface-border);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--text-1) 3%, transparent);
  color: var(--text-2);
  font-size: 0.875rem;
}
.fx-shell .fx-empty i { font-size: 1.5rem; opacity: 0.6; }
/* `.hidden` must always win over this block's own `display:flex` above —
   otherwise JS toggling `.hidden` off once data loads has no visible effect,
   and the empty-state card stays shown on top of/alongside the loaded grid
   (PTC / Shortlinks provider panels being the case that surfaced this). */
.fx-shell .fx-empty.hidden { display: none; }
.fx-shell .fx-footer-link {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  color: var(--text-2);
  text-decoration: none;
  transition: color 0.2s ease;
}
@media (hover: hover) {
.fx-shell .fx-footer-link:hover { color: var(--lime); }
}
.fx-shell .fx-footer-link i { font-size: 0.7rem; opacity: 0.75; }

/* ============ PHASE 4 — PAGE TITLE BLOCKS ============ */
.fx-page-title {
  display: flex;
  align-items: flex-start;
  gap: 0.9rem;
  margin-bottom: 1.75rem;
}
.fx-page-title .fx-pt-icon {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.8125rem;
  height: 2.8125rem;
  border-radius: 0.9rem;
  color: var(--lime);
  background:
    linear-gradient(160deg, color-mix(in srgb, var(--lime) 16%, transparent), var(--surface) 70%);
  border: 1px solid color-mix(in srgb, var(--lime) 28%, var(--surface-border));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--surface) 40%, transparent);
  font-size: 1rem;
}
.fx-page-title h1 {
  font-family: var(--font-display, 'Space Grotesk', ui-sans-serif, sans-serif);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.1;
  font-size: clamp(1.375rem, 2.6vw, 1.7rem);
  color: var(--lime);
}
.fx-page-title .fx-pt-sub {
  margin-top: 0.4rem;
  font-size: 0.8125rem;
  color: var(--text-2);
}

/* ============ PHASE 4 — RESPONSIVE NUMBERS ============ */
.fx-num {
  display: inline-block;
  max-width: 100%;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: bottom;
}
.fx-stat {
  font-size: clamp(1.15rem, 2.2vw, 1.7rem);
  line-height: 1.1;
}

/* Balance widget in the top bar: always ONE line. The number is the only
   shrinkable part (ellipsis), the toggles never wrap or get pushed out. */
.fx-balance-widget {
  display: inline-flex;
  align-items: center;
  flex-wrap: nowrap;
  white-space: nowrap;
  gap: 0.375rem;
  min-width: 0;
  max-width: 100%;
}
.fx-balance-widget > .fx-num {
  flex: 0 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.fx-balance-widget > .fx-btn {
  flex: 0 0 auto;
}
@media (max-width: 420px) {
  .fx-balance-widget { gap: 0.25rem; }
}

/* ============ PHASE 4 — WITHDRAW FAUCETPAY WALLET CARDS ============ */
.fx-shell .fx-currency-card {
  cursor: pointer;
  position: relative;
  transition: border-color 0.18s ease, transform 0.18s ease;
}
@media (hover: hover) {
.fx-shell .fx-currency-card:hover {
          border-color: color-mix(in srgb, var(--lime) 45%, var(--surface-border));
          transform: translateY(-1px);
    }
}
.fx-shell .fx-currency-card.is-selected {
  border-color: var(--lime);
  box-shadow: 0 0 0 1px var(--lime), 0 8px 24px -12px color-mix(in srgb, var(--lime) 45%, transparent);
}
.fx-shell .fx-currency-card.is-selected::after {
  content: '';
  position: absolute;
  top: 0.45rem;
  right: 0.45rem;
  width: 10px;
  height: 10px;
  border-radius: 999px;
  background: var(--lime);
  box-shadow: 0 0 10px color-mix(in srgb, var(--lime) 75%, transparent);
}
.fx-coin {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.125rem;
  height: 2.125rem;
  border-radius: 0.7rem;
  font-weight: 700;
  font-size: 0.8125rem;
  color: var(--lime);
  background:
    linear-gradient(150deg, color-mix(in srgb, var(--lime) 18%, transparent), var(--surface) 72%);
  border: 1px solid color-mix(in srgb, var(--lime) 30%, var(--surface-border));
}

/* ============ SHARED UI PRIMITIVES (UI pass) ============ */

/* Compact metric pill: replaces bare "Label: value" text lines. */
.fx-shell .fx-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.3rem 0.7rem;
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 600;
  line-height: 1.1;
  white-space: nowrap;
  color: var(--pill-tone, var(--lime));
  background: color-mix(in srgb, var(--pill-tone, var(--lime)) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--pill-tone, var(--lime)) 34%, transparent);
}
.fx-shell .fx-pill > i { font-size: 0.68rem; opacity: 0.85; }

/* Coin logo tile for the dashboard currency watch strip. */
.fx-shell .fx-coin-logo {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 0.85rem;
  flex: none;
  overflow: hidden;
  background:
    linear-gradient(150deg, color-mix(in srgb, var(--lime) 14%, transparent), var(--surface) 74%);
  border: 1px solid color-mix(in srgb, var(--lime) 26%, var(--surface-border));
  color: var(--lime);
  font-weight: 700;
  font-size: 0.8125rem;
}
.fx-shell .fx-coin-logo img { width: 100%; height: 100%; object-fit: contain; padding: 4px; }

/* Stat tile: icon + label + value. The default answer to plain text info. */
.fx-shell .fx-stat-tile {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.7rem 0.85rem;
  border-radius: var(--radius-ui);
  background: color-mix(in srgb, var(--text-1) 4%, transparent);
  border: 1px solid var(--surface-border);
  min-width: 0;
}
.fx-shell .fx-stat-tile > .fx-stat-tile-body { min-width: 0; }
.fx-shell .fx-stat-tile .fx-stat-tile-label {
  display: block;
  font-size: 0.66rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-2);
}
.fx-shell .fx-stat-tile .fx-stat-tile-value {
  display: block;
  font-family: var(--font-mono);
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--text-1);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ============ STICKY WALLET BAR (balance + gems) ============ */
.fx-wallet-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 35;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.75rem calc(0.5rem + env(safe-area-inset-bottom, 0px));
  background: color-mix(in srgb, var(--surface) 92%, transparent);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border-top: 1px solid var(--surface-border);
  transition: transform 0.3s ease, opacity 0.3s ease;
}
.fx-wallet-bar[hidden] { display: none; }
.fx-wallet-bar .fx-wallet-item {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  flex: 1 1 0;
  min-width: 0;
  padding: 0.35rem 0.6rem;
  border-radius: var(--radius-ui);
  border: 1px solid var(--surface-border);
  background: color-mix(in srgb, var(--text-1) 4%, transparent);
  color: var(--text-1);
  font-family: var(--font-mono);
  font-size: 0.78rem;
  font-weight: 600;
  text-decoration: none;
  transition: border-color 0.2s ease, transform 0.2s ease;
}
@media (hover: hover) {
.fx-wallet-bar .fx-wallet-item:hover { border-color: color-mix(in srgb, var(--lime) 45%, transparent); transform: translateY(-1px); }
}
.fx-wallet-bar .fx-wallet-item > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fx-wallet-bar .fx-wallet-item .fx-wallet-icon { font-size: 0.8rem; color: var(--lime); flex: none; }
.fx-wallet-bar .fx-wallet-item.is-gems .fx-wallet-icon { color: var(--mint); }
.fx-wallet-bar .fx-wallet-item img { width: 14px; height: 14px; flex: none; }
/* Keep the bar from covering page content or a modal. */
body.has-fx-wallet-bar { padding-bottom: 4.25rem; }
body.fx-modal-open .fx-wallet-bar { transform: translateY(120%); opacity: 0; pointer-events: none; }
/* Mobile-only: the top bar already shows both figures on desktop, so a
   second fixed bar would just cover content. */
@media (min-width: 640px) {
  .fx-wallet-bar { display: none; }
  body.has-fx-wallet-bar { padding-bottom: 0; }
}
@media (max-width: 639px) {
  .fx-wallet-bar { font-size: 0.72rem; padding-inline: 0.5rem; }
  .fx-wallet-bar .fx-wallet-item { padding: 0.3rem 0.45rem; }
}

/* The top-bar balance widget shows at EVERY size so the unit switch and the eye
   are always reachable. On a phone the number is the only shrinkable part (the
   ellipsis rule above) and the two toggles never wrap or get pushed out;
   formatfx.js drops the FTRX/USD unit text below 640px, so the icon alone tells
   you which unit the figure is in. Padding/size are trimmed here (the widget
   carries an inline padding, hence !important) to keep the three grid columns
   (menu / balance / controls) from colliding on a narrow screen. */
@media (max-width: 639px) {
  .fx-top-balance .fx-balance-widget {
    padding-inline: .4rem !important;
    gap: .3rem;
  }
  .fx-top-balance .fx-balance-widget > .fx-num { font-size: .78rem; }
}

/* ============ FLAT CARD GROUPS (PTC / Shortlinks / Achievements) ============
   Opt-in per page via .fx-flat-tasks. A grid of cards is a layout, not a
   container: these pages render the cards straight onto the page background
   with no fill, no border and no shadow, so the group never reads as one big
   box. Hover keeps a faint tint so the cards stay discoverable and clickable
   - no border, no shadow. Scoped to the three pages that asked for it; every
   other card in the app keeps the standard .fx-card treatment. */
.fx-flat-tasks .task-card,
.fx-flat-tasks .achievement-card {
    background: none;
    border: 0;
    box-shadow: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    transition: background-color 0.2s ease, transform 0.2s ease;
}
@media (hover: hover) {
.fx-flat-tasks .task-card:hover,
    .fx-flat-tasks .achievement-card:hover {
            background: color-mix(in srgb, var(--lime) 6%, transparent);
            border: 0;
            box-shadow: none;
            transform: translateY(-1px);
    }
}
/* Light theme: the same flat treatment, so the group is background-free in
   both themes instead of only in dark. */
html[data-theme="light"] .fx-flat-tasks .task-card,
html[data-theme="light"] .fx-flat-tasks .achievement-card {
    background-color: transparent;
    border-color: transparent;
}
@media (hover: hover) {
html[data-theme="light"] .fx-flat-tasks .task-card:hover,
    html[data-theme="light"] .fx-flat-tasks .achievement-card:hover {
            background-color: color-mix(in srgb, var(--ct-primary, #00ff66) 8%, transparent);
            border-color: transparent;
            box-shadow: none;
    }
}
/* The skeleton placeholders must not become the only boxes on the page while
   the real cards load. */
.fx-flat-tasks .task-skeleton {
  background: none;
  border: 1px dashed var(--surface-border);
}

/* theme-tokens.css ships an app-wide surface primitive:
     .card, .panel, [class*='card'], [class*='panel'] { background; border; radius }
   The two attribute wildcards are substring matches, so they also catch the
   LAYOUT containers on these pages - .task-card-grid, .task-panel, .ach-panel -
   and wrap each offers grid in its own surface box. The card rules above only
   reset the cards, so the box around them survived. Neutralize the containers
   here as well so the group really is background-free. Only the specific
   classes below are listed; .fx-card keeps the standard treatment elsewhere. */
.fx-flat-tasks .task-card-grid,
.fx-flat-tasks .task-skeleton-grid,
.fx-flat-tasks .task-panel,
.fx-flat-tasks .ach-panel {
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}
html[data-theme="light"] .fx-flat-tasks .task-card-grid,
html[data-theme="light"] .fx-flat-tasks .task-skeleton-grid,
html[data-theme="light"] .fx-flat-tasks .task-panel,
html[data-theme="light"] .fx-flat-tasks .ach-panel {
  background-color: transparent;
  border-color: transparent;
}

/* ============ MOTION PRIMITIVES (disabled) ============ */
.fx-shell .fx-rise,
.fx-shell .fx-reveal { opacity: 1; transform: none; }

/* ============ FX-LOAD ENTRANCE ANIMATIONS (Animate.css) ============
   Markup contract: elements opt in with `data-fx-anim="<entrance>"` plus an
   optional `data-fx-delay="<ms>"`, and parents cascade siblings with
   `data-fx-stagger="<ms>"` (see MD_files/ANIMATION_SYSTEM.md).
   fx-load.js Appends the animate.css classes. Elements are pre-hidden ONLY
   while html.fx-anim-on is present (JS on and running), so no-JS and
   CDN-failure degrade to fully visible markup — nothing ever strands. */
html.fx-anim-on [data-fx-anim] { opacity: 0; }
html.fx-anim-on [data-fx-anim].fx-anim-play { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  html.fx-anim-on [data-fx-anim] { opacity: 1 !important; }
}

/* ============ SECTION ANCHORS ============
   `scroll-behavior` is intentionally left at the browser default (auto): smooth
   anchor scrolling is one of the main causes of the page feeling laggy on
   trackpads and low-power devices. scroll-margin keeps the sticky top bar from
   covering the anchored heading. */
#fx-payouts,
#fx-ways,
#fx-stats,
#fx-coins,
#how-it-works,
#fx-currencies,
section[id] { scroll-margin-top: 76px; }

/* ============ MODAL SYSTEM ============ */
.fx-modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 60;
  background: rgba(0, 0, 0, 0.72);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
}
.fx-modal-panel {
  position: fixed;
  z-index: 61;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  /* 100vw counts the scrollbar gutter on desktop, so a centred fixed element
     drifts ~8px left. 100% is the layout viewport. The safe-area terms keep the
     panel clear of the notch and home indicator once viewport-fit=cover is on
     (Phase 0, matrix-theme.css). */
  width: min(560px, calc(100% - 2rem - env(safe-area-inset-left) - env(safe-area-inset-right)));
  /* vh is the classic mobile bug: 84vh is measured against the viewport with
     browser chrome hidden, so the bottom of the panel lands under the URL bar.
     dvh tracks the visible viewport. The vh line stays as the fallback for
     browsers without dvh. */
  max-height: min(84vh, 720px);
  max-height: min(84dvh, 720px);
  overflow-y: auto;
  /* Without this, reaching the end of a long modal keeps chaining scroll to
     the page behind it and the page appears to drift under the backdrop. */
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  padding: 1.25rem;
  /* Tokens stopped filling every [class*='panel'] by default (flat from
     theme-tokens.css); the modal is a true surface, so it declares its own. */
  background: var(--surface);
  border: 1px solid var(--surface-border);
  border-radius: var(--radius);
  box-shadow: 0 24px 60px -12px rgba(0, 0, 0, 0.7);
}
#watch-picker.hidden { display: none; }
body.fx-modal-open { overflow: hidden; }

/* Legacy server-rendered dialogs toggle `.hidden`; these classes give them the
   same fade as the JS-built present modals. Only opacity animates, so a panel's
   own positioning (fixed-centered vs flex-centered) is never disturbed. */
.fx-dialog-entering .fx-modal-backdrop,
.fx-dialog-leaving .fx-modal-backdrop,
.fx-dialog-entering .fx-modal-panel,
.fx-dialog-leaving .fx-modal-panel { opacity: 0; }

/* --- FXModal present layer ------------------------------------------------
   JS-built modals (level-up, daily login) use the same backdrop/panel surfaces
   as the legacy modals, plus a real entrance/exit (the modal recipe: opacity +
   scale 0.96, centred, so transform-origin stays the default). They stack at
   70/71 so they sit above the ad overlay, which owns z 60. */
.fx-modal--present .fx-modal-backdrop { z-index: 70; }
.fx-modal--present .fx-modal-panel { z-index: 71; }
.fx-modal-backdrop,
.fx-modal-panel {
  transition:
    opacity var(--dur-modal) var(--ease-out),
    transform var(--dur-modal) var(--ease-out);
}
.fx-modal--present.fx-entering .fx-modal-backdrop,
.fx-modal--present.fx-leaving .fx-modal-backdrop { opacity: 0; }
.fx-modal--present.fx-entering .fx-modal-panel,
.fx-modal--present.fx-leaving .fx-modal-panel {
  opacity: 0;
  transform: translate(-50%, -50%) scale(0.96);
}
.fx-modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 0.75rem;
}
.fx-modal-title {
  font-size: 1.05rem;
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0;
}
.fx-shell .fx-modal-x,
.fx-modal--present .fx-modal-x { padding: 0.35rem 0.5rem; line-height: 1; }
.fx-modal-body { color: var(--text-2); }
.fx-modal-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  justify-content: flex-end;
  margin-top: 1.25rem;
}
@media (max-width: 420px) {
  .fx-modal-actions > * { flex: 1 1 100%; }
}

/* The level-up "new level" modal reads as a distinct celebration card, so its
   panel rounds more than the default modal radius (which was easy to read as
   square against the dark backdrop). */
.fx-levelup-modal .fx-modal-panel { border-radius: 20px; }

/* --- Level-up + daily-login content --------------------------------------- */
.fx-levelup-list {
  display: grid;
  gap: 0.75rem;
  max-height: 46dvh;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.fx-levelup .fx-reveal-art,
.fx-daily .fx-reveal-art { animation: fx-levelup-pop 460ms var(--ease-out) both; }
.fx-levelup-flags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  justify-content: center;
  margin: 0.6rem 0 0;
}
.fx-levelup-rewards {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  justify-content: center;
  margin-top: 0.55rem;
}
.fx-levelup-reward {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.35rem 0.7rem;
  border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--lime) 30%, var(--surface-border));
  background: color-mix(in srgb, var(--lime) 8%, transparent);
  font-size: 0.82rem;
  color: var(--text-1);
}
.fx-levelup-cosmetic {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.75rem;
}
.fx-levelup-cosmetic img { width: 3.25rem; height: 3.25rem; object-fit: contain; }

/* Several levels at once: one card each, stacked vertically with centered
   content (art on top, level + rewards, then the Equip button). */
.fx-levelup { text-align: center; }
.fx-levelup-grid { grid-template-columns: 1fr; }
.fx-levelup-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 0.5rem;
  padding: 1rem;
  border: 1px solid var(--surface-border);
  border-radius: 0.85rem;
  background: color-mix(in srgb, var(--text-1) 4%, transparent);
}
.fx-levelup-card-art {
  width: 3.5rem;
  height: 3.5rem;
  display: grid;
  place-items: center;
  border-radius: 0.8rem;
  background: radial-gradient(120% 120% at 50% 0%, color-mix(in srgb, var(--lime) 14%, transparent), transparent 62%);
  border: 1px solid color-mix(in srgb, var(--lime) 20%, var(--surface-border));
}
.fx-levelup-card-art img { width: 2.75rem; height: 2.75rem; object-fit: contain; }
.fx-levelup-card-meta { width: 100%; }
.fx-levelup-card-level { margin: 0; font-weight: 700; color: var(--text-1); font-size: 0.95rem; }
.fx-levelup-card-chips { display: flex; flex-wrap: wrap; gap: 0.3rem; justify-content: center; margin-top: 0.35rem; }
.fx-levelup-card-chips .fx-levelup-reward { padding: 0.2rem 0.5rem; font-size: 0.72rem; }
.fx-levelup-card .fx-levelup-card-equip { width: 100%; padding: 0.5rem 0.8rem; font-size: 0.82rem; }
.fx-levelup-card-equip.is-equipped {
  color: var(--lime);
  border-color: color-mix(in srgb, var(--lime) 55%, var(--surface-border));
  background: color-mix(in srgb, var(--lime) 10%, transparent);
}
.fx-levelup-card-unlocked {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
  width: 100%;
  padding: 0.5rem 0.8rem;
  font-size: 0.82rem;
  font-weight: 600;
  border-radius: 0.6rem;
  color: var(--lime);
  border: 1px solid color-mix(in srgb, var(--lime) 45%, transparent);
  background: color-mix(in srgb, var(--lime) 10%, transparent);
}
@keyframes fx-levelup-pop {
  from { opacity: 0; transform: scale(0.9) translateY(6px); }
  to   { opacity: 1; transform: scale(1) translateY(0); }
}

/* ============ AUTH MODAL REFRESH ============
   Only the surface is restyled. The .is-open / opacity / transform contract
   in matrix-theme.css is what login.js drives, so it is left untouched. */
.fx-shell .auth-modal-card {
  position: relative;
  overflow: hidden;
  background:
    radial-gradient(130% 120% at 0% 0%,
      color-mix(in srgb, var(--lime) 12%, transparent) 0%, transparent 60%),
    var(--surface);
  border-color: color-mix(in srgb, var(--lime) 28%, var(--surface-border));
  box-shadow: 0 28px 70px -18px rgba(0, 0, 0, 0.7);
}
.fx-shell .auth-modal-card::after {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  height: 2px;
  background: linear-gradient(90deg, transparent, var(--lime), var(--mint), transparent);
  opacity: 0.7;
  pointer-events: none;
}
.fx-shell .auth-modal input[type='email'],
.fx-shell .auth-modal input[type='text'],
.fx-shell .auth-modal input[type='password'] {
  width: 100%;
  border-radius: 0.6rem;
  padding: 0.65rem 1rem;
  color: var(--text-1);
  background: color-mix(in srgb, var(--text-1) 5%, transparent);
  border: 1px solid var(--surface-border);
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.fx-shell .auth-modal input:focus {
  outline: none;
  border-color: color-mix(in srgb, var(--lime) 55%, transparent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--lime) 15%, transparent);
}
.fx-shell .auth-modal input::placeholder { color: var(--text-2); opacity: 0.7; }
.fx-shell .auth-modal label { color: var(--text-2); font-size: 0.8125rem; }
.fx-shell .auth-modal .text-matrix-green { color: var(--lime); }


@media (max-width: 1023px) {
  .fx-shell .fx-earn-slide { flex-basis: calc((100% - 16px) / 2); }
}
@media (max-width: 639px) {
  .fx-shell .fx-earn-slide { flex-basis: 100%; }
  .fx-shell .fx-slider-viewport { overflow: visible; }
  .fx-shell .fx-slider-shell { overflow: hidden; }
  .fx-shell .fx-payment-name { display: none; }
}

/* ============ PACK SHOP ============
   Loot-pack cards and the drop-table / reveal modals. Rarity is carried by ring
   weight and glow only - never by hue - so the palette stays a single lime. */
.fx-pack-card {
  position: relative;
  overflow: hidden;
  border-radius: 1rem;
  display: flex;
  flex-direction: column;
  isolation: isolate;
  transition: transform 320ms var(--ease-out), border-color 320ms var(--ease-out), box-shadow 320ms var(--ease-out);
}
@media (hover: hover) {
  .fx-pack-card:hover {
    transform: translateY(-3px);
    border-color: color-mix(in srgb, var(--lime) 45%, var(--surface-border));
    box-shadow: 0 18px 40px -22px color-mix(in srgb, var(--lime) 55%, transparent);
  }
}
.fx-pack-card:focus-within {
  border-color: color-mix(in srgb, var(--lime) 55%, var(--surface-border));
}

/* Art sits in its own inset well - the double-bezel look, so the card reads as
   machined rather than a flat surface with a picture glued on. */
.fx-pack-art {
  position: relative;
  margin: 0 auto;
  width: 8.5rem;
  height: 8.5rem;
  display: grid;
  place-items: center;
  border-radius: 1.15rem;
  background:
    radial-gradient(120% 120% at 50% 0%, color-mix(in srgb, var(--lime) 14%, transparent) 0%, transparent 62%),
    color-mix(in srgb, var(--text-1) 4%, transparent);
  border: 1px solid color-mix(in srgb, var(--lime) 22%, var(--surface-border));
  box-shadow: inset 0 1px 1px color-mix(in srgb, #ffffff 12%, transparent);
}
.fx-pack-art::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  box-shadow: 0 0 34px -6px color-mix(in srgb, var(--lime) 45%, transparent);
  opacity: 0.55;
}
.fx-pack-art img {
  width: 6.5rem;
  height: 6.5rem;
  object-fit: contain;
  position: relative;
  z-index: 1;
  filter: drop-shadow(0 6px 14px rgba(0, 0, 0, 0.45));
}
.fx-pack-card[data-pity-ready='1'] .fx-pack-art::after { opacity: 1; animation: fx-pack-pulse 2.4s var(--ease-out) infinite; }

.fx-pack-price {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.2rem 0.55rem;
  border-radius: 999px;
  font-size: 0.8rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--lime);
  background: color-mix(in srgb, var(--lime) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--lime) 32%, transparent);
  white-space: nowrap;
}
.fx-pack-price img { width: 14px; height: 14px; }

/* Primary CTA with a nested icon circle flush to the inner padding. */
.fx-pack-cta {
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}
.fx-pack-cta-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.22);
  flex: 0 0 auto;
}

.fx-pack-meta {
  font-size: 0.72rem;
  color: var(--text-3);
  letter-spacing: 0.02em;
}
.fx-pack-meta strong { color: var(--text-2); font-weight: 600; }

/* Pity meter. Animated with scaleX, not width: width would trigger layout. */
.fx-pack-pity { margin-top: 0.75rem; }
.fx-pack-pity-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 0.68rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--text-3);
  margin-bottom: 0.3rem;
}
.fx-meters {
  height: 0.3rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--text-1) 8%, transparent);
  overflow: hidden;
}
.fx-meters > span {
  display: block;
  height: 100%;
  border-radius: inherit;
  background: linear-gradient(90deg, var(--lime), var(--mint));
  transform-origin: left center;
  transition: transform 480ms var(--ease-out);
}
.fx-pack-card[data-pity-ready='1'] .fx-pack-pity-head { color: var(--lime); }

/* Drop-table rows. Bars show relative weight; scaleX again, no layout thrash. */
.fx-drop-row {
  display: grid;
  grid-template-columns: 1.5rem 1fr auto;
  align-items: center;
  gap: 0.6rem;
  padding: 0.4rem 0.25rem;
  border-bottom: 1px solid color-mix(in srgb, var(--text-1) 6%, transparent);
}
.fx-drop-row:last-child { border-bottom: 0; }
.fx-drop-art { width: 1.5rem; height: 1.5rem; object-fit: contain; }
.fx-drop-name { font-size: 0.82rem; color: var(--text-1); display: flex; align-items: center; gap: 0.5rem; }
.fx-drop-odds { font-size: 0.78rem; font-variant-numeric: tabular-nums; color: var(--text-2); white-space: nowrap; }

.fx-rarity {
  font-size: 0.6rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 1px 7px;
  border-radius: 999px;
  color: var(--lime);
  border: 1px solid color-mix(in srgb, var(--lime) 30%, transparent);
  background: color-mix(in srgb, var(--lime) 8%, transparent);
}
.fx-rarity[data-rarity='common']    { opacity: 0.6; }
.fx-rarity[data-rarity='uncommon']  { opacity: 0.78; }
.fx-rarity[data-rarity='rare']      { opacity: 0.9; }
.fx-rarity[data-rarity='epic']      { box-shadow: 0 0 12px -3px color-mix(in srgb, var(--lime) 70%, transparent); }
.fx-rarity[data-rarity='legendary'] { box-shadow: 0 0 16px -2px color-mix(in srgb, var(--lime) 85%, transparent); }

/* Reveal modal: the granted drop, centred and larger. */
.fx-reveal {
  text-align: center;
  padding: 0.5rem 0 0;
}
.fx-reveal-art {
  width: 9rem;
  height: 9rem;
  margin: 0 auto 0.5rem;
  display: grid;
  place-items: center;
  border-radius: 1.25rem;
  background: radial-gradient(120% 120% at 50% 30%, color-mix(in srgb, var(--lime) 18%, transparent), transparent 65%);
}
.fx-reveal-art img { width: 7rem; height: 7rem; object-fit: contain; filter: drop-shadow(0 10px 22px rgba(0, 0, 0, 0.5)); }
.fx-reveal-kind { font-size: 0.72rem; letter-spacing: 0.16em; text-transform: uppercase; color: var(--text-3); }
.fx-reveal-title { font-size: 1.15rem; font-weight: 700; color: var(--text-1); margin: 0.15rem 0 0; }
.fx-reveal-pity { margin-top: 0.6rem; font-size: 0.74rem; color: var(--lime); }

@keyframes fx-pack-pulse {
  0%, 100% { opacity: 0.6; }
  50%      { opacity: 1; }
}
@keyframes fx-pack-in {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: translateY(0); }
}
.fx-pack-card { animation: fx-pack-in 420ms var(--ease-out) both; }
/* Stagger the reveal 45ms per card, capped at the 9th so a full page never
   waits longer than ~0.4s to finish. */
.fx-pack-card:nth-child(1) { animation-delay: 0ms; }
.fx-pack-card:nth-child(2) { animation-delay: 45ms; }
.fx-pack-card:nth-child(3) { animation-delay: 90ms; }
.fx-pack-card:nth-child(4) { animation-delay: 135ms; }
.fx-pack-card:nth-child(5) { animation-delay: 180ms; }
.fx-pack-card:nth-child(6) { animation-delay: 225ms; }
.fx-pack-card:nth-child(7) { animation-delay: 270ms; }
.fx-pack-card:nth-child(8) { animation-delay: 315ms; }
.fx-pack-card:nth-child(9) { animation-delay: 360ms; }

/* ============ INVENTORY ============
   Owned cosmetics and pack history. Same card language as the Shop, with a
   smaller art well and an equipped flag. Rarity carries in ring/glow only. */
.fx-inv-card {
  position: relative;
  border-radius: 1rem;
  display: flex;
  flex-direction: column;
  animation: fx-pack-in 400ms var(--ease-out) both;
  transition: transform 300ms var(--ease-out), border-color 300ms var(--ease-out), box-shadow 300ms var(--ease-out);
}
@media (hover: hover) {
  .fx-inv-card:hover {
    transform: translateY(-2px);
    border-color: color-mix(in srgb, var(--lime) 40%, var(--surface-border));
    box-shadow: 0 16px 34px -22px color-mix(in srgb, var(--lime) 50%, transparent);
  }
}
.fx-inv-card:nth-child(1) { animation-delay: 0ms; }
.fx-inv-card:nth-child(2) { animation-delay: 45ms; }
.fx-inv-card:nth-child(3) { animation-delay: 90ms; }
.fx-inv-card:nth-child(4) { animation-delay: 135ms; }
.fx-inv-card:nth-child(5) { animation-delay: 180ms; }
.fx-inv-card:nth-child(6) { animation-delay: 225ms; }
.fx-inv-card:nth-child(7) { animation-delay: 270ms; }
.fx-inv-card:nth-child(8) { animation-delay: 315ms; }
.fx-inv-card:nth-child(9) { animation-delay: 360ms; }

.fx-inv-art {
  position: relative;
  margin: 0 auto;
  width: 6.5rem;
  height: 6.5rem;
  display: grid;
  place-items: center;
  border-radius: 1rem;
  background:
    radial-gradient(120% 120% at 50% 0%, color-mix(in srgb, var(--lime) 12%, transparent) 0%, transparent 62%),
    color-mix(in srgb, var(--text-1) 4%, transparent);
  border: 1px solid color-mix(in srgb, var(--lime) 20%, var(--surface-border));
  box-shadow: inset 0 1px 1px color-mix(in srgb, #ffffff 12%, transparent);
}
.fx-inv-art img {
  width: 5rem;
  height: 5rem;
  object-fit: contain;
  position: relative;
  z-index: 1;
  filter: drop-shadow(0 6px 14px rgba(0, 0, 0, 0.45));
}
.fx-inv-art > i { font-size: 2.5rem; color: var(--lime); }
.fx-inv-art[data-rarity='epic'] {
  box-shadow: inset 0 1px 1px color-mix(in srgb, #ffffff 12%, transparent),
              0 0 22px -6px color-mix(in srgb, var(--lime) 70%, transparent);
}
.fx-inv-art[data-rarity='legendary'] {
  box-shadow: inset 0 1px 1px color-mix(in srgb, #ffffff 12%, transparent),
              0 0 28px -4px color-mix(in srgb, var(--lime) 90%, transparent);
}
.fx-inv-equipped {
  position: absolute;
  top: 0.35rem;
  right: 0.35rem;
  z-index: 2;
  font-size: 0.58rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  padding: 2px 6px;
  border-radius: 999px;
  color: var(--lime);
  background: color-mix(in srgb, var(--lime) 16%, #000 30%);
  border: 1px solid color-mix(in srgb, var(--lime) 45%, transparent);
}
.fx-inv-limited {
  position: absolute;
  top: 0.35rem;
  left: 0.35rem;
  z-index: 2;
  font-size: 0.58rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  padding: 2px 6px;
  border-radius: 999px;
  color: #ff8fae;
  background: color-mix(in srgb, #ff3b6b 20%, #000 34%);
  border: 1px solid color-mix(in srgb, #ff3b6b 55%, transparent);
}
.fx-inv-limited > i { font-size: 0.55rem; margin-right: 1px; }
.fx-inv-card .fx-drop-row { grid-template-columns: 1.5rem 1fr auto; margin-top: 0.35rem; padding-left: 0; padding-right: 0; }
.fx-inv-card .fx-drop-art { display: grid; place-items: center; font-size: 1rem; color: var(--lime); }

/* ============ AVATAR / BADGE PICKER ============
   Shared square choice tile for the profile avatar picker and the level badge
   picker. Owned tiles are interactive; locked tiles are dimmed and inert. */
.fx-avatar-choice {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  padding: 0.75rem;
  border-radius: 1rem;
  border: 1px solid var(--surface-border);
  background: color-mix(in srgb, var(--text-1) 3%, transparent);
  color: var(--text-2);
  text-align: center;
  transition: transform 220ms var(--ease-out), border-color 220ms var(--ease-out), box-shadow 220ms var(--ease-out), background 220ms var(--ease-out);
}
button.fx-avatar-choice { cursor: pointer; font: inherit; }
@media (hover: hover) {
  button.fx-avatar-choice:hover {
    transform: translateY(-2px);
    border-color: color-mix(in srgb, var(--lime) 45%, var(--surface-border));
    background: color-mix(in srgb, var(--lime) 8%, transparent);
    box-shadow: 0 14px 30px -20px color-mix(in srgb, var(--lime) 55%, transparent);
  }
}
.fx-avatar-choice-art {
  width: 4.25rem;
  height: 4.25rem;
  display: grid;
  place-items: center;
  border-radius: 0.85rem;
  background:
    radial-gradient(120% 120% at 50% 0%, color-mix(in srgb, var(--lime) 12%, transparent) 0%, transparent 62%),
    color-mix(in srgb, var(--text-1) 5%, transparent);
  border: 1px solid color-mix(in srgb, var(--lime) 18%, var(--surface-border));
}
.fx-avatar-choice-art img { width: 3.25rem; height: 3.25rem; object-fit: contain; }
.fx-avatar-choice-name { font-size: 0.78rem; font-weight: 600; line-height: 1.2; }
.fx-avatar-choice-state {
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-3);
}
.fx-avatar-choice.is-equipped {
  border-color: color-mix(in srgb, var(--lime) 55%, var(--surface-border));
  background: color-mix(in srgb, var(--lime) 10%, transparent);
}
.fx-avatar-choice.is-equipped .fx-avatar-choice-state { color: #ff8f94; }
.fx-avatar-choice.is-locked { opacity: 0.5; filter: grayscale(0.35); cursor: not-allowed; }

/* ============ CASE-OPENING REEL ============
   CS:GO-style horizontal reel. The server resolves the winner before the spin
   starts; the reel is decoration that always lands on that winner. Mounted on
   <body> (role=dialog) so the level-up modal queues behind it instead of
   stacking. */
.fx-case {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1.5rem;
  position: fixed;
  inset: 0;
  z-index: 80;
  padding: 1.5rem;
  background: rgba(2, 6, 4, 0.94);
  opacity: 0;
  transition: opacity var(--dur-modal) var(--ease-out);
  contain: layout paint;
  touch-action: none;
}
.fx-case.hidden { display: none; }
.fx-case.is-open { opacity: 1; }
.fx-case-stage {
  position: relative;
  width: min(100%, 900px);
  overflow: hidden;
  padding: 1rem 0;
  border-top: 1px solid var(--surface-border);
  border-bottom: 1px solid var(--surface-border);
  contain: layout paint;
}
/* Edge fade painted with gradients instead of a compositing mask - the mask
   forces an extra layer pass every frame on weak GPUs. */
.fx-case-stage::before,
.fx-case-stage::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  width: 72px;
  z-index: 2;
  pointer-events: none;
}
.fx-case-stage::before { left: 0; background: linear-gradient(90deg, rgba(2, 6, 4, 0.94), transparent); }
.fx-case-stage::after { right: 0; background: linear-gradient(270deg, rgba(2, 6, 4, 0.94), transparent); }
.fx-case-marker {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: 2px;
  transform: translateX(-50%);
  background: var(--lime);
  box-shadow: 0 0 18px color-mix(in srgb, var(--lime) 70%, transparent);
  z-index: 3;
}
.fx-case-track { display: flex; gap: 12px; }
/* Promote to its own layer only while spinning, then release it - a permanent
   will-change on a long track wastes GPU memory on low-end devices. */
.fx-case-track.is-spinning { will-change: transform; }
.fx-case-item {
  flex: 0 0 auto;
  width: 130px;
  height: 152px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  padding: 0.6rem;
  border-radius: 12px;
  border: 1px solid var(--surface-border);
  background: color-mix(in srgb, var(--surface) 94%, transparent);
  text-align: center;
  contain: content;
}
.fx-case-item-art { width: 4.25rem; height: 4.25rem; display: grid; place-items: center; }
.fx-case-item-art img { width: 100%; height: 100%; object-fit: contain; }
.fx-case-item-art i { font-size: 2.75rem; }
.fx-case-item-name {
  font-size: 0.72rem;
  font-weight: 600;
  line-height: 1.2;
  color: var(--text-1);
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}
/* Rarity is carried by border colour only (cheap paint). The glow is reserved
   for the single winning card, so we never paint 60 inner shadows mid-spin. */
.fx-case-item[data-rarity='uncommon'] { border-color: color-mix(in srgb, var(--mint) 55%, var(--surface-border)); }
.fx-case-item[data-rarity='rare'] { border-color: color-mix(in srgb, var(--mint) 80%, var(--surface-border)); }
.fx-case-item[data-rarity='epic'] { border-color: color-mix(in srgb, var(--magenta) 70%, var(--surface-border)); }
.fx-case-item[data-rarity='legendary'] { border-color: color-mix(in srgb, var(--gold) 75%, var(--surface-border)); }
.fx-case-item.is-winner {
  border-color: var(--lime);
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--lime) 60%, transparent),
    0 0 28px -6px color-mix(in srgb, var(--lime) 80%, transparent);
  animation: fx-case-win 900ms var(--ease-out) both;
}
@keyframes fx-case-win {
  0% { transform: scale(1); }
  45% { transform: scale(1.13); }
  100% { transform: scale(1.05); }
}
.fx-case-result { font-size: 1.05rem; font-weight: 700; color: var(--text-1); margin: 0; }
.fx-case-hint {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 2rem;
  text-align: center;
  color: var(--text-2);
  font-size: 0.875rem;
  margin: 0;
  animation: fx-case-hint 1.6s ease-in-out infinite;
  pointer-events: none;
}
@keyframes fx-case-hint { 0%, 100% { opacity: 0.5; } 50% { opacity: 1; } }
.fx-case-skip {
  position: absolute;
  bottom: 1.75rem;
  left: 50%;
  transform: translateX(-50%);
  padding: 10px 28px;
  border-radius: var(--radius-ui);
  background: #e5484d;
  color: #fff;
  font-size: 0.875rem;
  font-weight: 600;
  border: 0;
  cursor: pointer;
  opacity: 0;
  z-index: 10;
  transition: opacity 0.25s ease, background 0.2s ease, transform 0.2s ease;
}
.fx-case-skip.is-shown { opacity: 1; }
@media (hover: hover) { .fx-case-skip:hover { background: #f0575c; transform: translateX(-50%) translateY(-2px); } }

@media (prefers-reduced-motion: reduce) {
  .fx-shell *,
  /* Present modals and the case reel mount on <body>, outside .fx-shell. */
  .fx-modal--present,
  .fx-modal--present *,
  .fx-case,
  .fx-case * {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  .fx-pack-card[data-pity-ready='1'] .fx-pack-art::after { animation: none; opacity: 0.9; }
}