/* Neon Arena components: glass cards, buttons, chips, toggles, toasts, modals,
   skeletons, connection pill, emote bubbles + motion system + view styles. */

/* === Motion system === */
.spring { transition: transform var(--dur-med) var(--spring), box-shadow var(--dur-med) var(--ease-out),
  background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out),
  color var(--dur-fast) var(--ease-out), opacity var(--dur-fast) var(--ease-out); }

@keyframes fadeUp {
  from { opacity: 0; transform: translateY(14px); }
  to { opacity: 1; transform: translateY(0); }
}
@keyframes popIn {
  0% { opacity: 0; transform: scale(0.86); }
  70% { transform: scale(1.03); }
  100% { opacity: 1; transform: scale(1); }
}
@keyframes beaconRipple {
  0% { transform: scale(0.6); opacity: 0.8; }
  100% { transform: scale(2.2); opacity: 0; }
}
@keyframes glowPulse {
  0%, 100% { filter: drop-shadow(0 0 6px rgba(241, 193, 82, 0.5)); }
  50% { filter: drop-shadow(0 0 18px rgba(241, 193, 82, 0.85)); }
}
@keyframes shake {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-7px); }
  40% { transform: translateX(6px); }
  60% { transform: translateX(-4px); }
  80% { transform: translateX(3px); }
}
@keyframes floatY {
  from { transform: translateY(-3%); }
  to { transform: translateY(3%); }
}
@keyframes shimmer {
  from { background-position: -400px 0; }
  to { background-position: 400px 0; }
}
@keyframes spinPulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.35; }
}

.anim-fade-up { animation: fadeUp var(--dur-slow) var(--ease-out) both; }
.anim-pop { animation: popIn var(--dur-med) var(--spring) both; }
.anim-shake { animation: shake 420ms var(--ease-out); }

/* Text glow utilities */
.glow-gold { color: var(--gold); text-shadow: 0 0 18px rgba(241, 193, 82, 0.55); }
.glow-danger { color: var(--danger); text-shadow: 0 0 16px rgba(255, 93, 108, 0.5); }
.glow-ok { color: var(--ok); text-shadow: 0 0 16px rgba(53, 199, 131, 0.5); }
.glow-cyan { color: var(--cyan); text-shadow: 0 0 16px rgba(103, 232, 249, 0.45); }
.glow-violet { color: var(--violet); text-shadow: 0 0 16px rgba(167, 139, 250, 0.5); }

/* === Topbar === */
.topbar {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: var(--z-topbar);
  height: var(--topbar-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 14px;
  background: var(--glass-bg);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(1.3);
  backdrop-filter: blur(var(--glass-blur)) saturate(1.3);
  border-bottom: 1px solid var(--glass-brd);
}
.topbar-logo {
  display: flex;
  align-items: center;
  gap: 10px;
  border-radius: var(--r-pill);
  padding: 6px 10px;
}
@media (hover: hover) {
  .topbar-logo:hover { background: var(--panel-soft); }
}
.topbar-crown {
  font-size: 24px;
  line-height: 1;
  animation: glowPulse 3.2s ease-in-out infinite;
}
.topbar-name {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 17px;
  letter-spacing: 0.4px;
  background: var(--neon-grad);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}
.topbar-right { display: flex; align-items: center; gap: 8px; }
/* Console icon button: a 34px round well, violet at rest, gold on hover. */
.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 34px;
  height: 34px;
  padding: 0 6px;
  border-radius: var(--r-pill);
  border: 1px solid rgba(var(--t-violet), 0.24);
  background: rgba(var(--t-violet), 0.1);
  color: var(--text-dim);
  font-size: 15px;
  transition: transform 0.18s var(--spring), background 0.16s, color 0.16s, border-color 0.16s;
}
@media (hover: hover) {
  .icon-btn:hover {
    background: rgba(var(--t-gold), 0.16);
    border-color: rgba(var(--t-gold), 0.45);
    color: var(--gold);
    transform: translateY(-1px);
  }
}
.icon-btn:active { transform: translateY(0) scale(0.94); }
.icon-btn.attention { color: var(--gold); border-color: rgba(241, 193, 82, 0.4); box-shadow: var(--shadow-glow-gold); }
.lang-btn { font-weight: 700; font-size: 13px; letter-spacing: 0.5px; }

/* SoundControl — the volume mixer (YouTube-style). A speaker button opens a
   glass panel: master row (mute + slider) plus Effects / Music channels. */
.soundctl { position: relative; display: inline-flex; }
.soundctl-btn.is-off { opacity: 0.45; }
.soundctl-panel {
  position: absolute;
  top: calc(100% + 10px);
  right: 0;
  z-index: var(--z-overlay);
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: 240px;
  padding: 14px;
  border-radius: var(--r-md);
  border: 1px solid rgba(var(--t-violet), 0.28);
  background: var(--glass-bg);
  backdrop-filter: blur(var(--glass-blur));
  -webkit-backdrop-filter: blur(var(--glass-blur));
  box-shadow: var(--shadow-pop), var(--shadow-glow-violet);
  animation: soundctl-in 0.18s var(--ease-out);
}
@keyframes soundctl-in {
  from { opacity: 0; transform: translateY(-6px) scale(0.97); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}
.soundctl-row { display: flex; align-items: center; gap: 9px; }
.soundctl-master { padding-bottom: 9px; border-bottom: 1px solid var(--hairline); }
.soundctl-mute,
.soundctl-chtoggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
  border: none;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--text-dim);
  font-size: 15px;
  cursor: pointer;
  transition: color 0.15s, background 0.15s, transform 0.15s var(--spring);
}
@media (hover: hover) {
  .soundctl-mute:hover,
  .soundctl-chtoggle:hover { color: var(--gold); background: rgba(var(--t-gold), 0.12); }
}
.soundctl-mute:active,
.soundctl-chtoggle:active { transform: scale(0.9); }
.soundctl-mute.is-off,
.soundctl-chtoggle.is-off { opacity: 0.45; }
.soundctl-label {
  flex: 0 0 56px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-soft);
}
.soundctl-slider {
  -webkit-appearance: none;
  appearance: none;
  flex: 1 1 auto;
  height: 4px;
  border-radius: var(--r-pill);
  background: linear-gradient(90deg, var(--violet-600), var(--violet));
  outline: none;
  cursor: pointer;
}
.soundctl-slider.is-off { opacity: 0.35; }
.soundctl-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  border: 1px solid rgba(var(--t-gold), 0.6);
  background: var(--gold);
  box-shadow: 0 0 10px rgba(241, 193, 82, 0.5);
  transition: transform 0.15s var(--spring), box-shadow 0.15s;
}
.soundctl-slider::-webkit-slider-thumb:hover {
  transform: scale(1.2);
  box-shadow: 0 0 16px rgba(241, 193, 82, 0.7);
}
.soundctl-slider::-moz-range-thumb {
  width: 15px;
  height: 15px;
  border-radius: 50%;
  border: 1px solid rgba(var(--t-gold), 0.6);
  background: var(--gold);
  box-shadow: 0 0 10px rgba(241, 193, 82, 0.5);
}
.soundctl-slider::-moz-range-track {
  height: 4px;
  border-radius: var(--r-pill);
  background: linear-gradient(90deg, var(--violet-600), var(--violet));
}
/* In game toolbars the panel must not overflow the screen edge. */
.soundctl-compact .soundctl-panel { width: 210px; }
/* Near the bottom of the screen the panel flips upward. */
.soundctl.opens-up .soundctl-panel { top: auto; bottom: calc(100% + 10px); }

/* Connection pill — same geometry as .pill (see below). */
.conn-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 11px;
  border-radius: var(--r-pill);
  border: 1px solid rgba(var(--t-mute), 0.3);
  background: rgba(var(--t-mute), 0.1);
  font-size: 11px;
  font-weight: 700;
  color: var(--text-dim);
}
.conn-dot { width: 6px; height: 6px; flex: 0 0 auto; border-radius: 50%; background: var(--text-faint); }
.conn-pill.ok .conn-dot { background: var(--ok); box-shadow: 0 0 10px var(--ok); animation: ac-pulse 2.4s ease-in-out infinite; }
.conn-pill.ok { color: var(--ok); background: rgba(var(--t-ok), 0.1); border-color: rgba(var(--t-ok), 0.3); }
.conn-pill.bad .conn-dot { background: var(--danger); box-shadow: 0 0 10px var(--danger); }
.conn-pill.bad { color: var(--danger); background: rgba(var(--t-danger), 0.1); border-color: rgba(var(--t-danger), 0.3); }

/* === Cards === */
/* Opaque panel instead of backdrop-filter glass: ~12 cards in lobby each
   running blur(18px) saturate(1.25) was the #1 jank source on mid-range
   Android WebView. True glass is kept for topbar + modals only. */
.glass-card {
  position: relative;
  background: var(--panel-strong);
  border: 1px solid var(--glass-brd);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-card);
  padding: 18px;
}
.glass-card.hoverable { transition: transform var(--dur-med) var(--spring), box-shadow var(--dur-med) var(--ease-out),
  border-color var(--dur-fast); }
@media (hover: hover) {
  .glass-card.hoverable:hover { transform: translateY(-3px); border-color: var(--stroke-strong); box-shadow: var(--shadow-card), var(--shadow-glow-violet); }
}
.card-title {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 15px;
  font-weight: 800;
  letter-spacing: 0.2px;
  margin-bottom: 12px;
}
.card-title .sub { font-family: var(--font-body); font-weight: 500; font-size: 12px; color: var(--text-faint); }

/* === Buttons === */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 12px 20px;
  border-radius: var(--r-md);
  border: 1px solid transparent;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 14px;
  letter-spacing: 0.2px;
  color: var(--text);
  transition: transform var(--dur-med) var(--spring), box-shadow var(--dur-med) var(--ease-out),
    background var(--dur-fast), border-color var(--dur-fast), color var(--dur-fast), opacity var(--dur-fast);
  user-select: none;
  -webkit-user-select: none;
}
.btn:active { transform: scale(0.95); }
.btn:disabled { opacity: 0.45; cursor: not-allowed; transform: none; box-shadow: none; }
/* Gold sits under DARK ink, not light. The repo used to put #201503 near-black
   on gold too, but at the console's weight the difference reads: --gold-ink is
   the one value the whole primary action rests on. */
.btn-primary {
  background: linear-gradient(135deg, var(--gold), var(--gold-deep));
  color: var(--gold-ink);
  font-family: var(--font-display);
  font-weight: 800;
  border-radius: var(--r-md);
  box-shadow: 0 8px 24px rgba(var(--t-gold), 0.3);
}
.btn-neon {
  background: var(--neon-grad);
  color: #1a0b16;
  box-shadow: 0 6px 22px rgba(244, 114, 182, 0.35);
}
.btn-ghost {
  background: rgba(20, 13, 38, 0.62);
  border-color: rgba(var(--t-violet), 0.3);
  color: var(--text);
}
.btn-danger {
  background: rgba(var(--t-danger), 0.07);
  border-color: rgba(var(--t-danger), 0.34);
  color: var(--danger-soft);
}
/* The state the repo never had a look for: an action that exists but is not
   available yet. Dashed, transparent, quiet — visibly a slot, not a button. */
.btn-dashed,
.btn:disabled.btn-dashed {
  border: 1px dashed rgba(var(--t-violet), 0.28);
  background: transparent;
  color: rgba(var(--t-mute), 0.55);
  box-shadow: none;
  cursor: not-allowed;
}
.btn-dashed:disabled { border-color: rgba(var(--t-violet), 0.2); color: rgba(var(--t-mute), 0.28); opacity: 1; }
.btn-sm { padding: 8px 14px; font-size: 13px; border-radius: var(--r-sm); }
.btn-block { width: 100%; }
.btn-lg { padding: 15px 26px; font-size: 16px; border-radius: var(--r-lg); }

/* Hover is a pointer affordance; on touch it sticks after a tap and reads as
   a stuck selection. Every hover in the console vocabulary is gated, and every
   gated hover has an :active counterpart that fires on touch too. */
@media (hover: hover) {
  .btn-primary:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 10px 28px rgba(var(--t-gold), 0.46), var(--shadow-glow-gold); }
  .btn-neon:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 10px 30px rgba(var(--t-violet), 0.5), var(--shadow-glow-violet); }
  .btn-ghost:hover:not(:disabled) { border-color: var(--stroke-strong); background: var(--row-hover); transform: translateY(-2px); }
  .btn-danger:hover:not(:disabled) { background: rgba(var(--t-danger), 0.14); box-shadow: var(--shadow-glow-danger); transform: translateY(-2px); }
  .btn-dashed:hover { border-color: rgba(var(--t-violet), 0.28); background: transparent; transform: none; box-shadow: none; }
}
.btn-primary:active:not(:disabled),
.btn-neon:active:not(:disabled),
.btn-ghost:active:not(:disabled),
.btn-danger:active:not(:disabled) { transform: scale(0.96); }

/* === Chips & pills === */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 13px;
  border-radius: var(--r-pill);
  border: 1px solid var(--stroke);
  background: var(--panel-soft);
  font-size: 13px;
  font-weight: 600;
  color: var(--text-dim);
}
.chip strong { color: var(--text); font-weight: 800; }
.chip-gold { border-color: rgba(241, 193, 82, 0.35); color: var(--gold); }
.chip-cool { border-color: rgba(124, 201, 255, 0.3); color: var(--cool); }
/* Status pill: one geometry, one tone-driven skin, an optional live dot.
   .pill and .conn-pill are the same object at two sizes — aligned here so a
   status reads identically in the HUD and in a list. */
.pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 5px 11px;
  border-radius: var(--r-pill);
  border: 1px solid rgba(var(--t-mute), 0.3);
  background: rgba(var(--t-mute), 0.1);
  font-size: 11px;
  font-weight: 700;
  line-height: 1.2;
}
.pill-wait { background: rgba(var(--t-gold), 0.1); color: var(--gold); border-color: rgba(var(--t-gold), 0.3); }
.pill-live { background: rgba(var(--t-danger), 0.1); color: var(--danger); border-color: rgba(var(--t-danger), 0.3); }
.pill-ok { background: rgba(var(--t-ok), 0.1); color: var(--ok); border-color: rgba(var(--t-ok), 0.3); }
.pill-cool { background: rgba(124, 201, 255, 0.1); color: var(--cool); border-color: rgba(124, 201, 255, 0.3); }
/* The dot a pill carries when the thing it names is happening right now. */
.pill-dot {
  width: 6px;
  height: 6px;
  flex: 0 0 auto;
  border-radius: 50%;
  background: currentColor;
  animation: ac-pulse 2s ease-in-out infinite;
}

/* Segmented control (variant / tabs) — wraps on narrow viewports */
.segmented {
  display: inline-flex;
  flex-wrap: wrap;
  padding: 4px;
  gap: 4px;
  border-radius: var(--r-pill);
  background: var(--panel-soft);
  border: 1px solid var(--stroke);
  max-width: 100%;
}
.segmented button {
  padding: 8px 16px;
  border-radius: var(--r-pill);
  font-weight: 700;
  font-size: 13px;
  color: var(--text-dim);
  transition: background var(--dur-fast), color var(--dur-fast), transform var(--dur-med) var(--spring),
    box-shadow var(--dur-med);
  white-space: nowrap;
}
@media (hover: hover) {
  .segmented button:hover { color: var(--text); }
}
.segmented button.active {
  background: var(--gold-grad);
  color: #201503;
  box-shadow: 0 4px 14px rgba(224, 162, 58, 0.4);
}
@media (hover: hover) {
  .segmented button.active:hover { color: #201503; }
}

/* === Toggle switch === */
.tgl {
  position: relative;
  width: 46px;
  height: 26px;
  border-radius: var(--r-pill);
  background: var(--panel-soft);
  border: 1px solid var(--stroke-strong);
  transition: background var(--dur-fast), border-color var(--dur-fast), box-shadow var(--dur-fast);
  flex: 0 0 auto;
}
.tgl::after {
  content: "";
  position: absolute;
  top: 2px; left: 2px;
  width: 20px; height: 20px;
  border-radius: 50%;
  background: var(--text-dim);
  transition: transform var(--dur-med) var(--spring), background var(--dur-fast);
}
.tgl.on {
  background: var(--gold-grad);
  border-color: transparent;
  box-shadow: 0 0 14px rgba(241, 193, 82, 0.4);
}
.tgl.on::after { transform: translateX(20px); background: #201503; }
.tgl:disabled { opacity: 0.45; cursor: not-allowed; }

/* === Toasts === */
.toast-root {
  position: fixed;
  /* The console's floor is not the window's: #marquee, #act and (on a phone)
     #tabbar sit under the scene, and `--shell-foot` is how tall that stack is
     right now (ui/shell.js:syncFoot). Pinned to the window, every toast landed
     on the tab bar — and `.toast` is pointer-events:auto, so it ate the tap.
     The fallback is the old value, for a page rendered without the shell. */
  bottom: calc(20px + var(--shell-foot, 0px));
  left: 50%;
  transform: translateX(-50%);
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  width: min(92vw, 440px);
  pointer-events: none;
}
.toast {
  pointer-events: auto;
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 12px 16px;
  border-radius: var(--r-md);
  background: var(--panel-strong);
  -webkit-backdrop-filter: blur(var(--glass-blur));
  backdrop-filter: blur(var(--glass-blur));
  border: 1px solid var(--stroke-strong);
  box-shadow: var(--shadow-pop);
  font-size: 14px;
  font-weight: 600;
  animation: popIn var(--dur-med) var(--spring) both;
  cursor: pointer;
}
.toast.leaving { opacity: 0; transform: translateY(8px); transition: opacity var(--dur-fast), transform var(--dur-fast); }
.toast-ok { border-color: rgba(53, 199, 131, 0.45); }
.toast-err { border-color: rgba(255, 93, 108, 0.5); }
.toast-gold { border-color: rgba(241, 193, 82, 0.5); }

/* === Modals === */
.modal-root:empty { display: none; }
.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: flex;
  align-items: center;
  justify-content: center;
  /* The notch and the home bar are part of `inset: 0` under viewport-fit=cover,
     so the 20px breathing room has to be measured from the SAFE edge, not the
     glass one — otherwise a capped modal's top line sits under the clock. */
  padding: max(20px, env(safe-area-inset-top)) 20px max(20px, env(safe-area-inset-bottom));
  background: rgba(4, 7, 11, 0.62);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  animation: fadeUp var(--dur-fast) var(--ease-out) both;
}
.modal {
  width: min(94vw, 460px);
  background: var(--panel-strong);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(1.3);
  backdrop-filter: blur(var(--glass-blur)) saturate(1.3);
  border: 1px solid var(--stroke-strong);
  border-radius: var(--r-xl);
  box-shadow: var(--shadow-card), var(--shadow-glow-violet);
  padding: 26px;
  animation: popIn var(--dur-med) var(--spring) both;
  /* The cap achievements.css already states for its sheet, made the default.
     Without it a modal taller than the window centres in an `align-items:
     center` flex backdrop and pushes its TOP edge above the viewport — and
     html{overflow:hidden} (base.css) means nothing scrolls it back: the title
     and the close affordance become unreachable on the screen a modal exists
     to be dismissed from. `overscroll-behavior` keeps the inner scroll from
     chaining into the scene behind the backdrop. */
  max-height: min(88dvh, 760px);
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}

/* === Skeletons === */
.skeleton {
  border-radius: var(--r-sm);
  background: linear-gradient(90deg, var(--panel-soft) 25%, var(--stroke) 50%, var(--panel-soft) 75%);
  background-size: 400px 100%;
  animation: shimmer 1.4s linear infinite;
}
.skeleton-line { height: 14px; margin: 8px 0; }
.skeleton-card { height: 74px; margin: 10px 0; border-radius: var(--r-md); }

/* === Emote bubbles === */
.emote-bubble {
  position: absolute;
  padding: 8px 12px;
  border-radius: var(--r-md) var(--r-md) var(--r-md) 4px;
  background: var(--panel-strong);
  border: 1px solid var(--stroke-strong);
  box-shadow: var(--shadow-pop);
  font-size: 20px;
  animation: popIn var(--dur-med) var(--spring) both;
  pointer-events: none;
  z-index: var(--z-overlay);
}

/* === Beacon (queue / waiting indicator) === */
.beacon {
  position: relative;
  width: 54px;
  height: 54px;
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.beacon::before, .beacon::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 2px solid var(--gold);
  animation: beaconRipple 1.8s var(--ease-out) infinite;
}
.beacon::after { animation-delay: 0.9s; }
.beacon-core {
  width: 18px; height: 18px;
  border-radius: 50%;
  background: var(--gold-grad);
  box-shadow: var(--shadow-glow-gold);
}

/* === Views === */
.view { display: flex; flex-direction: column; gap: 16px; animation: fadeUp var(--dur-slow) var(--ease-out) both; }

/* Hero */
.hero { text-align: center; padding: 26px 10px 10px; }
.hero-crown { font-size: 58px; line-height: 1; display: inline-block; animation: glowPulse 3.2s ease-in-out infinite, floatY 5s ease-in-out infinite alternate; }
.hero-title {
  font-size: clamp(30px, 6vw, 44px);
  font-weight: 800;
  letter-spacing: 0.5px;
  margin-top: 8px;
  background: var(--neon-grad);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}
.hero-sub { color: var(--text-dim); margin-top: 8px; font-size: 15px; }
.hero-counters { display: flex; justify-content: center; flex-wrap: wrap; gap: 10px; margin-top: 18px; }

/* Featured banner */
.featured {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding: 13px 16px;
  border-radius: var(--r-md);
  border: 1px solid rgba(241, 193, 82, 0.28);
  background: linear-gradient(120deg, rgba(241, 193, 82, 0.09), rgba(244, 114, 182, 0.07));
}
.featured-text { font-weight: 700; font-size: 14px; }
.featured .live-dot { color: var(--danger); animation: spinPulse 1.6s ease-in-out infinite; }

/* TC grid */
.tc-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.tc-btn {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  padding: 14px 6px 12px;
  border-radius: var(--r-md);
  border: 1px solid var(--stroke);
  background: var(--panel-soft);
  transition: transform var(--dur-med) var(--spring), border-color var(--dur-fast), background var(--dur-fast),
    box-shadow var(--dur-med);
}
@media (hover: hover) {
  .tc-btn:hover:not(:disabled) { transform: translateY(-3px); border-color: rgba(241, 193, 82, 0.5); background: var(--panel); box-shadow: var(--shadow-glow-gold); }
}
.tc-btn:active:not(:disabled) { transform: scale(0.95); }
.tc-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.tc-btn.busy { border-color: var(--gold); box-shadow: var(--shadow-glow-gold); }
.tc-label { font-family: var(--font-display); font-weight: 800; font-size: 16px; }
.tc-speed { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.8px; color: var(--text-faint); }
.tc-pills { display: flex; gap: 4px; min-height: 20px; }

/* Lists.
   THE CONSOLE ROW IS `.list-row`, not `.row`. `.row` is an existing bare-flex
   layout utility (base.css) used in seven places; giving it a border and
   padding would put a card frame round three centred buttons. Every
   leaderboard, roster, feed and archive row in the console is
   `.list-row` plus at most one tone modifier below. */
.list { display: flex; flex-direction: column; gap: 8px; }
.list-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border-radius: var(--r-md);
  border: 1px solid rgba(var(--t-violet), 0.14);
  background: var(--row);
  transition: transform 0.18s var(--spring), border-color 0.16s, background 0.16s;
}
@media (hover: hover) {
  .list-row:hover { border-color: var(--stroke-strong); background: var(--row-hover); transform: translateX(3px); }
}
.list-row:active { background: var(--row-hover); }

/* The five states a row can be in, and there are only five. Each is the tone
   triplet at a border alpha and a background alpha — nothing else changes, so
   a list of mixed states still reads as one list. */
.list-row.is-hot  { border-color: rgba(var(--t-gold), 0.3);   background: rgba(var(--t-gold), 0.07); }
.list-row.is-win  { border-color: rgba(var(--t-ok), 0.24);    background: rgba(var(--t-ok), 0.05); }
.list-row.is-loss { border-color: rgba(var(--t-danger), 0.22); background: rgba(var(--t-danger), 0.045); }
.list-row.is-dead { border-color: transparent; background: var(--row); color: var(--text-faint); }
.list-row.is-dead .names,
.list-row.is-dead .rating-val { color: var(--text-faint); }
.list-row .names { font-weight: 700; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.list-row .meta { font-size: 12px; color: var(--text-faint); }
.rank-num { font-family: var(--font-display); font-weight: 800; width: 30px; text-align: center; color: var(--text-dim); }
.rank-num.top { color: var(--gold); text-shadow: 0 0 12px rgba(241, 193, 82, 0.5); }
.rating-val { font-family: var(--font-display); font-weight: 800; font-size: 15px; }
.me-row { border-color: rgba(241, 193, 82, 0.45); background: rgba(241, 193, 82, 0.07); }

/* Login */
.login-wrap {
  min-height: calc(100dvh - var(--topbar-h) - 120px);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  padding: 0 12px;
  box-sizing: border-box;
}
.login-card { width: min(100%, 420px); max-width: calc(100vw - 24px); box-sizing: border-box; text-align: center; padding: 34px 26px; }
.login-crown { font-size: 64px; line-height: 1; display: inline-block; animation: glowPulse 3.2s ease-in-out infinite, floatY 5s ease-in-out infinite alternate; }
.login-title { font-size: 28px; font-weight: 800; margin-top: 12px; }
.login-sub { color: var(--text-dim); margin: 10px 0 24px; }
.login-hint { font-size: 12.5px; color: var(--text-faint); margin-top: 14px; }
.login-error {
  margin-top: 14px;
  padding: 10px 12px;
  border-radius: var(--r-sm);
  background: rgba(255, 93, 108, 0.1);
  border: 1px solid rgba(255, 93, 108, 0.4);
  color: var(--danger);
  font-size: 13px;
  font-weight: 600;
}
.login-divider { display: flex; align-items: center; gap: 12px; margin: 22px 0; color: var(--text-faint); font-size: 12px; }
.login-divider::before, .login-divider::after { content: ""; flex: 1; height: 1px; background: var(--stroke); }
.login-beacon-wrap { display: flex; flex-direction: column; align-items: center; gap: 18px; padding: 14px 0 6px; }

/* Status card («Твоё место») */
.status-body { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.status-text { font-weight: 700; font-size: 15px; }
.status-sub { font-size: 13px; color: var(--text-dim); font-weight: 500; }

/* Push rows */
.push-row { display: flex; align-items: center; gap: 12px; padding: 9px 0; }
.push-row + .push-row { border-top: 1px solid var(--stroke); }
.push-row .lbl { font-weight: 600; font-size: 14px; }

/* Onboarding */
.onb-emoji { font-size: 52px; text-align: center; display: block; }
.onb-title { text-align: center; font-size: 22px; font-weight: 800; margin: 14px 0 10px; }
.onb-body { text-align: center; color: var(--text-dim); font-size: 14.5px; min-height: 66px; }
.onb-dots { display: flex; justify-content: center; gap: 8px; margin: 18px 0; }
.onb-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--stroke-strong); transition: transform var(--dur-med) var(--spring), background var(--dur-fast); }
.onb-dot.active { background: var(--gold); transform: scale(1.35); box-shadow: var(--shadow-glow-gold); }
.onb-actions { display: flex; gap: 10px; align-items: center; justify-content: space-between; }

/* Stub views (future slices) */
.stub-wrap { min-height: 52dvh; display: flex; align-items: center; justify-content: center; }
.stub-card { text-align: center; width: min(94vw, 440px); padding: 36px 26px; }
.stub-emoji { font-size: 56px; display: inline-block; animation: floatY 4s ease-in-out infinite alternate; }

/* neon icon assets (iconNode <img>) sized to the glyph box via em — covers the
   dedicated icon slots whose PNG was wired without a per-view px rule. 1.2em
   tracks each slot's own font-size, so the art fills the box the glyph used. */
img.trn-head-icon, img.sett-head-mark, img.sett-nav-icon, img.sett-sec-icon,
img.st-head-icon, img.st-row-av, img.st-noboard-glyph, img.st-seat-av,
img.st-empty-glyph, img.tbl-seat-avatar, img.arv-row-icon, img.opp-h2h-icon,
img.opp-rate-icon, img.opp-play-icon, img.ev-rc-thumb, img.al-empty-icon,
img.avatar-tile-xs, img.stub-emoji, img.ctx-rival-chip-icon, img.ctx-rival-go-icon,
img.ctx-pres-go-icon, img.ach-digest-mark, img.ach-digest-icon, img.shop-icon,
img.pg-quest-icon, img.pr-quest-icon, img.ev-day-icon, img.rpa-note-icon,
img.rail-lockmark, img.tab-lockmark, img.sheet-lock, img.ctx-icon,
img.ctx-feed-icon, img.dkm-spec-idle, img.dk-table-empty, img.tc-back-mark,
img.tc-flag, img.ch-head-icon, img.hud-fire-glyph, img.fire-glyph, img.pf-fire-glyph, img.cl-none-glyph, img.tc-event-ico { width: 1.2em; height: 1.2em; object-fit: contain; vertical-align: middle; }
img.watch-ico { width: 30px; height: 30px; object-fit: contain; display: block; }
img.ai-think { object-fit: contain; }

/* 3b: screen-title neon icon slots (existing section PNGs; :has keeps the
   shared card-title untouched unless it actually carries an icon img) */
.anl-view .anl-title { display: flex; align-items: center; gap: 8px; }
.card-title:has(> img) { display: flex; align-items: center; gap: 8px; }
img.anl-title-ico, img.card-title-ico { width: 1.25em; height: 1.25em; object-fit: contain; flex: 0 0 auto; }
.stub-title { font-size: 24px; font-weight: 800; margin: 14px 0 8px; }
.stub-sub { color: var(--text-dim); margin-bottom: 22px; }

/* Footer */
.lobby-footer {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 8px 18px;
  padding: 8px 0 22px;
  font-size: 12.5px;
  color: var(--text-faint);
}
/* Measured 76x18 at 1280 — below 904px the touch floor in base.css saves it,
   above that it does not, and on /analytics and /clans this underlined line of
   12.5px text is the only way back. */
/* 44px, not 40: this rule is more specific than base.css's touch floor, so the
   four pixels it shaved off were shaved off a real control on a phone — the
   only way back to /profile from the analytics screen, and the only way out of
   the dashboard footer. A footer row has the room. */
.lobby-footer button {
  color: var(--text-faint);
  font-size: 12.5px;
  text-decoration: underline;
  min-height: 44px;
  padding: 0 6px;
}
@media (hover: hover) {
  .lobby-footer button:hover { color: var(--text-dim); }
}

/* Section entrance stagger */
.view > * { animation: fadeUp var(--dur-slow) var(--ease-out) both; }
.view > *:nth-child(2) { animation-delay: 60ms; }
.view > *:nth-child(3) { animation-delay: 110ms; }
.view > *:nth-child(4) { animation-delay: 160ms; }
.view > *:nth-child(5) { animation-delay: 210ms; }
.view > *:nth-child(6) { animation-delay: 260ms; }
.view > *:nth-child(7) { animation-delay: 300ms; }
.view > *:nth-child(8) { animation-delay: 340ms; }

@media (max-width: 520px) {
  .container { padding-left: 10px; padding-right: 10px; }
  .glass-card { padding: 14px; }
  .tc-label { font-size: 14px; }
  .topbar-name { display: none; }
}

/* Season card (lobby banner + profile section) */
.season-card .card-title { align-items: center; }
.season-toggle { padding: 2px 10px; line-height: 1; }
.season-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
  margin-top: 10px;
}
.season-lbl { color: var(--text-dim); font-size: 12.5px; margin-right: 2px; }
.season-hint { margin-top: 10px; font-size: 12.5px; }
.season-champs { margin-top: 12px; }
.season-champs > .season-lbl { display: block; margin-bottom: 2px; }

.icon-btn.is-off { opacity: .45; }

/* ==========================================================================
   ARENA CONSOLE — shared primitives
   ==========================================================================
   The vocabulary every screen lane builds on. Two vocabularies coexist during
   the migration: `.glass-card` is the LEGACY card and is deliberately left
   exactly as it was, so an unmigrated view keeps rendering as it did. `.panel`
   and `.card` below are the console's; a view is migrated when it stops using
   the former.

   Nothing in this block sets a hover outside @media (hover: hover), and every
   gated hover has an :active counterpart, because a hover that sticks on
   touch reads as a stuck selection.
   ========================================================================== */

/* --- panel: a titled box that sizes to its content; the scene scrolls ---
   The old rule made the BODY scroll, not the page — a fixed-height prison
   with an inner scroller invisible on macOS, stranding content below the
   fold. The inversion: the panel grows to content, the scene scrolls the
   page. `overflow: hidden` stays for border-radius clipping only; it is
   inert when nothing constrains the panel's height. Inner scroll is the
   exception a view opts into for a genuinely long list, never the default. */
.panel {
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
  border-radius: var(--r-panel);
  border: 1px solid rgba(var(--t-violet), 0.18);
  background: rgba(18, 12, 34, 0.56);
}
.panel-head {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 11px 14px;
  border-bottom: 1px solid rgba(var(--t-violet), 0.14);
  font-family: var(--font-display);
  font-size: var(--fs-panel-head);
  font-weight: var(--fw-panel-head);
  letter-spacing: var(--ls-panel-head);
  text-transform: uppercase;
  color: var(--text-dim);
}
.panel-body {
  flex: 1;
  min-height: 0;
}

/* --- card: the same shell without a header ------------------------------ */
.card {
  display: flex;
  flex-direction: column;
  gap: 9px;
  min-width: 0;
  padding: 14px 15px;
  border-radius: var(--r-lg);
  border: 1px solid rgba(var(--t-violet), 0.18);
  background: rgba(18, 12, 34, 0.56);
}

/* --- section label: the most repeated token in the console -------------- */
.section-label {
  font-family: var(--font-display);
  font-size: var(--fs-section);
  font-weight: var(--fw-section);
  letter-spacing: var(--ls-section);
  text-transform: uppercase;
  color: rgba(var(--t-gold), 0.72);
}
.kicker {
  font-family: var(--font-display);
  font-size: var(--fs-kicker);
  font-weight: var(--fw-kicker);
  letter-spacing: var(--ls-kicker);
  text-transform: uppercase;
  color: var(--text-soft);
}

/* --- sub: secondary prose. Quieter than dim, still meant to be read. ----- */
.sub {
  font-size: 11px;
  line-height: 1.5;
  color: var(--text-soft);
  text-wrap: pretty;
}

/* --- mono: every quantity in the console -------------------------------- */
.mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }

/* --- avatar tile: the BORDER is the status, there is no separate badge --- */
.avatar-tile {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
  border-radius: 10px;
  border: 1px solid rgba(var(--t-violet), 0.16);
  background: rgba(var(--t-violet), 0.16);
  font-size: 14px;
  line-height: 1;
  overflow: hidden;
}
.avatar-tile img { width: 100%; height: 100%; object-fit: cover; }
.avatar-tile-xs { width: 26px; height: 26px; border-radius: 8px; font-size: 12px; }
.avatar-tile-sm { width: 28px; height: 28px; border-radius: 9px; font-size: 13px; }
.avatar-tile-lg { width: 38px; height: 38px; border-radius: 12px; font-size: 17px; }
.avatar-tile-solid { background: linear-gradient(135deg, var(--violet-600), #241638); }
/* Online: they are here. In game: they are busy — the louder of the two. */
.avatar-tile.is-online { border-color: rgba(var(--t-ok), 0.4); }
.avatar-tile.is-playing { border-color: rgba(var(--t-gold), 0.5); }

/* --- squared chip: the console's filter/tag control ---------------------
   Sits beside the existing rounded `.chip`, which keeps its pill shape. */
.chip-sq {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 11px;
  border-radius: var(--r-sm);
  border: 1px solid rgba(var(--t-mute), 0.34);
  background: rgba(var(--t-mute), 0.09);
  font-size: 10px;
  font-weight: 700;
  color: var(--text-dim);
  transition: background 0.16s, border-color 0.16s, color 0.16s;
}
.chip-sq.is-selected {
  border-color: rgba(var(--t-gold), 0.5);
  background: rgba(var(--t-gold), 0.13);
  color: var(--gold);
}
.chip-sq-gold   { border-color: rgba(var(--t-gold), 0.34);   background: rgba(var(--t-gold), 0.09);   color: var(--gold); }
.chip-sq-ok     { border-color: rgba(var(--t-ok), 0.34);     background: rgba(var(--t-ok), 0.09);     color: var(--ok); }
.chip-sq-danger { border-color: rgba(var(--t-danger), 0.34); background: rgba(var(--t-danger), 0.09); color: var(--danger-soft); }
.chip-sq-violet { border-color: rgba(var(--t-violet), 0.34); background: rgba(var(--t-violet), 0.09); color: var(--violet-300); }
@media (hover: hover) {
  .chip-sq:hover { border-color: rgba(var(--t-gold), 0.4); color: var(--text); }
}
.chip-sq:active { background: rgba(var(--t-gold), 0.13); }

/* --- progress: THREE treatments and no more -----------------------------
   (1) .bar        horizontal fraction of a known whole
   (2) .spark      a run of days / rounds side by side
   (3) .vfill      a gauge read against a ruled scale (towers, walls, stacks)
   A fourth invented by a screen is a fourth thing the player has to learn. */
.bar {
  height: 4px;
  border-radius: 2px;
  overflow: hidden;
  background: rgba(var(--t-violet), 0.14);
}
.bar-fill {
  height: 100%;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--violet), var(--gold));
  box-shadow: 0 0 14px rgba(var(--t-gold), 0.4);
  transition: width var(--dur-med) var(--ease-out);
}

.spark {
  display: flex;
  align-items: flex-end;
  gap: 3px;
  height: 62px;
}
.spark-day {
  flex: 1 1 0;
  min-width: 0;
  border-radius: 2px;
  background: rgba(var(--t-violet), 0.24);
}
.spark-day.is-win  { background: rgba(var(--t-ok), 0.6); }
.spark-day.is-loss { background: rgba(var(--t-danger), 0.55); }
.spark-day.is-hot  { background: rgba(var(--t-gold), 0.7); }
/* Time tape: the same object measuring a clock rather than a result. */
.spark-tape { height: 46px; }
.spark-tape .spark-day { border-radius: 3px 3px 0 0; }

.vfill {
  position: relative;
  overflow: hidden;
  border-radius: var(--r-xs);
  background:
    repeating-linear-gradient(180deg, rgba(255, 255, 255, 0.045) 0 1px, transparent 1px 19px);
}
.vfill-fill {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  background: linear-gradient(180deg, var(--accent, var(--violet)) 87%, transparent);
  transition: height var(--dur-med) var(--ease-out);
}

/* --- banner: the thing the player must notice, WITHOUT a modal ----------
   It renders at the TOP OF the scene, never over it, so it can never cover a
   board. One icon, one title, one sub, one quantity, exactly one action. */
.banner {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 12px 14px 0;
  padding: 13px 15px;
  border-radius: var(--r-lg);
  border: 1px solid rgba(var(--t-danger), 0.5);
  background: linear-gradient(115deg, rgba(58, 12, 22, 0.96), rgba(16, 11, 30, 0.96));
  box-shadow: 0 20px 54px rgba(0, 0, 0, 0.6), 0 0 40px rgba(var(--t-danger), 0.14);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  animation: ac-rise 0.2s var(--ease-out) both;
}
.banner-warn {
  border-color: rgba(var(--t-gold), 0.5);
  background: linear-gradient(115deg, rgba(50, 33, 12, 0.96), rgba(16, 11, 30, 0.96));
  box-shadow: 0 20px 54px rgba(0, 0, 0, 0.6), 0 0 40px rgba(var(--t-gold), 0.14);
}
.banner-icon {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 38px;
  height: 38px;
  border-radius: var(--r-md);
  background: rgba(255, 255, 255, 0.06);
  font-size: 19px;
  line-height: 1;
}
.banner.is-urgent .banner-icon { animation: ac-breathe 1.6s ease-in-out infinite; }
.banner-text { flex: 1; min-width: 0; }
.banner-title { font-family: var(--font-display); font-size: 14px; font-weight: 800; }
.banner-sub { font-size: 11px; line-height: 1.45; color: var(--text-soft); text-wrap: pretty; }
.banner-count { font-family: var(--font-mono); font-size: 21px; font-weight: 700; font-variant-numeric: tabular-nums; }
.banner-compact {
  margin: 0 15px 10px;
  padding: 10px 12px;
  border-radius: var(--r-md);
}
.banner-compact .banner-icon { width: 30px; height: 30px; font-size: 16px; }
.banner-compact .banner-count { font-size: 17px; }

/* --- load failure: the one block that says «could not read» -------------
   Deliberately NOT the empty state. An empty list is a fact about the player
   and this is a fact about the request; a player who cannot tell them apart
   has been told something untrue about themselves. So it is quieter than a
   banner (nothing is on fire, and it can appear four times on one screen) and
   louder than `.sub` (it is not prose, it is the reason the block is blank),
   and there is exactly one of it across all eight lanes.
   Built by js/ui/load-fail.js — do not hand-roll a second one. */
.load-fail {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 7px;
  padding: 16px 14px;
  border-radius: var(--r-md);
  border: 1px dashed rgba(255, 255, 255, 0.13);
  background: rgba(255, 255, 255, 0.025);
  text-align: center;
}
.load-fail-title {
  font-family: var(--font-display);
  font-size: 13.5px;
  font-weight: 800;
  color: var(--text-soft);
}
.load-fail-sub {
  max-width: 34ch;
  font-size: 11px;
  line-height: 1.5;
  color: var(--text-faint);
  text-wrap: pretty;
}
.load-fail-acts {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: center;
  margin-top: 3px;
}
/* Inside a card that already carries the title: one line and the way out.
   ONE LINE WHERE ONE LINE FITS. `.load-fail` above is `display: flex` with no
   `flex-wrap`, so the row inherited `nowrap` and simply ran off the end of any
   box narrower than title + gap + button. The context column is that box: it is
   `clamp`ed and comes out 252px at an 800..904px window, where the row needs
   237 inside 227 of content — measured at 860x844 with /arena/api/head2head
   answering 500, «Попробовать снова» laid out 726..870 against a 860px window,
   22px past the panel and clipped by `.ctx-body`'s `overflow-x: hidden`. The
   part of the button that was cut off was also the part that could not be
   pressed, on every authed route at that width, because «Счёты» is the
   column's default tab and this is its failure state.
   Wrapping is the fix rather than shrinking the button: the retry is the only
   way out of the panel and an ellipsised «Попроб…» is not a control. Inert
   wherever the row already fits, which is every other caller and the same
   column at 1280. */
.load-fail-inline {
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 10px;
  padding: 11px 12px;
  text-align: left;
}
.load-fail-inline .load-fail-title { font-size: 12.5px; }

/* Game-assigned nickname («Прозвища»): a verdict, not a medal. Lowercase,
   quoted, visually distinct from the struck-metal title — muted italic, never
   the gold/gradient title treatment. Rides the adorn channel server-side. */
.arena-nick { font-style: italic; opacity: 0.72; font-weight: 400; }

/* === search beacon: the bot warm-up's window into the real queue ===
   A floating pill under the top bar: «🔎 Ищем соперника · Дурак · 1:23».
   Visible from every corner of the board but owns no layout — fixed, so no
   view had to give it a row. The green .is-found state is the two-beat
   flash the player sees right before match-kick flies them out (0.7s x2 =
   the 1400ms KICK_DELAY_MS in ui/match-kick.js). */
.search-beacon {
  /* Parked in the top bar's EMPTY middle (logo left, counters right):
     visible from every corner of the board and owning zero board pixels —
     «на видном, но не мешающем игре месте». Single line, ellipsized;
     narrow screens drop the pool label (media rule below). */
  position: fixed;
  top: calc(env(safe-area-inset-top, 0px) + 12px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 60;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  max-width: min(46vw, 380px);
  white-space: nowrap;
  padding: 6px 12px;
  border-radius: 999px;
  border: 1px solid rgba(241, 193, 82, 0.35);
  background: rgba(12, 8, 24, 0.86);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  color: var(--text);
  font-size: 11px;
  font-weight: 700;
  cursor: pointer;
  box-shadow: 0 4px 18px rgba(0, 0, 0, 0.45);
}
.sb-text { flex: 0 0 auto; }
.sb-pool {
  color: var(--text-dim);
  overflow: hidden;
  text-overflow: ellipsis;
}
@media (max-width: 560px) {
  .sb-pool { display: none; }
  .search-beacon { max-width: 40vw; }
}
.sb-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #f1c152;
  animation: sbPulse 1.6s ease-in-out infinite;
}
@keyframes sbPulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: 0.35; transform: scale(0.72); }
}
.sb-time { color: var(--text-dim); font-weight: 800; }
.sb-bell {
  border: 0;
  background: transparent;
  cursor: pointer;
  font-size: 13px;
  line-height: 1;
  padding: 0 0 0 2px;
  opacity: 0.85;
}
.sb-bell:disabled { cursor: default; }
.search-beacon.is-found {
  border-color: rgba(53, 199, 131, 0.85);
  animation: sbFound 0.7s ease-in-out 2;
}
.search-beacon.is-found .sb-dot { background: #35c783; animation: none; }
@keyframes sbFound {
  0%, 100% { background: rgba(12, 8, 24, 0.86); box-shadow: 0 4px 18px rgba(0, 0, 0, 0.45); }
  50% { background: rgba(23, 84, 58, 0.92); box-shadow: 0 0 22px rgba(53, 199, 131, 0.55); }
}

/* === queue push promo: the searching block sells exactly one thing === */
.al-push-promo {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 10px;
  padding: 9px 12px;
  border-radius: 12px;
  border: 1px dashed rgba(241, 193, 82, 0.4);
  background: rgba(241, 193, 82, 0.07);
  font-size: 12px;
  line-height: 1.4;
}
.al-push-promo .grow { flex: 1 1 auto; }
.al-push-promo-x {
  border: 0;
  background: transparent;
  color: var(--text-faint);
  cursor: pointer;
  font-size: 14px;
  line-height: 1;
  padding: 2px 4px;
}

/* The hub search card's extras row: warm-up door + push promo, stacked under
   the search note inside the main column. */
.l3-search-extras { display: flex; flex-direction: column; gap: 8px; margin-top: 10px; max-width: 430px; }
.l3-search-extras:empty { display: none; }
