/**
 * VirusTAP visual design system.
 *
 * The game itself ships as a pre-built, minified React bundle
 * (index-*.js / index-*.css) with no source map — its markup and class names
 * are fixed, but its Tailwind output lives inside `@layer theme/components/
 * utilities`. Per the CSS cascade spec, ANY plain (non-@layer) rule beats
 * every layered rule regardless of source order or selector specificity. This
 * sheet is intentionally never wrapped in @layer, so it can restyle the
 * bundle's own class names — .screen, .balance-card, .tap-button, and so on —
 * without fighting Tailwind's specificity or needing !important anywhere.
 *
 * Load order in index.html: bundle CSS → this file → case-opening.css →
 * profile-features.css. Those last two already target `.vt-*`/`.case-*`
 * classes this file doesn't touch, and read the same custom properties
 * defined here.
 */

/* Unbounded is loaded from index.html <link>, alongside preconnect hints —
   not @import here, which would block this file behind a second DNS/TLS
   round trip with no preconnect of its own. */

/* ===========================================================================
   TOKENS
   The existing bundle hardcodes #d9ff57 in a few places CSS can't reach
   (an inline SVG `stroke` attribute, an inline `color` style on the balance
   dot). --brand-400 matches that exact value so every surface — patched and
   unpatched — reads as one colour, not two shades of "green".
   ========================================================================= */

:root {
  /* Ground */
  --bg-void: #050b06;
  --bg-void-deep: #030603;
  --surface-1: #0e1911;
  --surface-2: #142310;
  --surface-glass: rgba(16, 27, 15, 0.66);
  --hairline: rgba(201, 255, 122, 0.14);
  --hairline-strong: rgba(201, 255, 122, 0.34);
  --hairline-soft: rgba(201, 255, 122, 0.07);

  /* Brand: acid-lime, the colour VirusTAP is already known by */
  --brand-200: #eeffb8;
  --brand-300: #e4ff8f;
  --brand-400: #d9ff57;
  --brand-500: #c2f22e;
  --brand-600: #9bd516;
  --brand-900: #1b2a0c;
  --brand-rgb: 217, 255, 87;

  /* Signal: a cooler second accent reserved for "rare / live" moments only —
     the higher-tier case, the online dot, focus rings — so it reads as a
     distinct signal rather than a second brand colour. */
  --signal-400: #57ffd0;
  --signal-rgb: 87, 255, 208;

  --coral-400: #ff7a68;
  --coral-rgb: 255, 122, 104;

  /* Ink */
  --ink-000: #fbffec;
  --ink-100: #eef6da;
  --ink-300: #b9c7a9;
  --ink-400: #93a68a;
  --ink-600: #5d6b55;

  /* Shape & motion */
  --radius-xl: 28px;
  --radius-lg: 22px;
  --radius-md: 16px;
  --radius-sm: 11px;
  --ease-spring: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-out: cubic-bezier(0.19, 1, 0.22, 1);
  --font-display: "Unbounded", "Manrope", system-ui, sans-serif;
}

/* ===========================================================================
   ATMOSPHERE
   Body carries the background (not a pseudo-element) so it shows straight
   through .screen, which the bundle already renders transparent. A fixed,
   very-low-opacity grain sits above everything as a decorative screen-door —
   pointer-events:none keeps it out of the hit-testing path.
   ========================================================================= */

body {
  /*
   * Static on purpose. This used to animate background-position every frame
   * for 26s on repeat with background-attachment:fixed — animating
   * background-position is a paint-triggering property (not transform/
   * opacity), so that repainted the entire viewport-sized background on
   * every single frame, forever, on every screen. fixed attachment is its
   * own long-documented mobile jank source (WebKit/Chrome both repaint it
   * on scroll instead of compositing it). Real iPhone/Android reports of
   * constant lag and flicker traced back to this.
   */
  background:
    radial-gradient(60% 42% at 82% -6%, rgba(var(--signal-rgb), 0.1), transparent 60%),
    radial-gradient(72% 50% at 12% 4%, rgba(var(--brand-rgb), 0.16), transparent 62%),
    radial-gradient(90% 60% at 50% 112%, rgba(var(--brand-rgb), 0.08), transparent 58%),
    linear-gradient(180deg, var(--bg-void) 0%, var(--bg-void-deep) 100%);
  background-color: var(--bg-void-deep);
}

.screen,
.home-screen,
.top-page {
  background: transparent;
}

/* ===========================================================================
   TYPOGRAPHY
   Unbounded is used with restraint — wordmark, page titles, the balance
   figure, the tap label — while the bundle's own Manrope/DM Mono keep every
   other line of UI text and data. It carries the "virus / lab" character the
   default Manrope-everywhere page didn't have.
   ========================================================================= */

.brand-lockup,
.page-title,
.balance-value,
.upgrade-buy-button,
.vt-modal h2,
.case-opening-heading,
.case-opening-result h3 {
  font-family: var(--font-display);
  letter-spacing: -0.02em;
}

.page-title {
  font-weight: 800;
}

::selection {
  background: rgba(var(--brand-rgb), 0.32);
  color: var(--ink-000);
}

:focus-visible {
  outline: 2px solid var(--brand-400);
  outline-offset: 2px;
  border-radius: 8px;
}

/* ===========================================================================
   HEADER
   ========================================================================= */

.topline {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  padding: 2px 2px 4px;
}

.online {
  display: none;
}

@keyframes pulse-dot {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: 0.55; transform: scale(0.78); }
}

.brand-lockup {
  color: var(--ink-000);
  font-size: 17px;
  font-weight: 700;
}

/* Centering only applies to the home screen's .topline — .brand-lockup is
   reused as a plain inline heading inside .page-header on other screens
   (Top, Cases, Profile), where absolute positioning would overlap their
   own title/subtitle content. */
.topline .brand-lockup {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
}

.brand-lockup span {
  color: var(--brand-400);
}

.topline .mono {
  color: var(--ink-600) !important;
  font-family: "DM Mono", ui-monospace, monospace;
}

.page-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  padding-bottom: 4px;
}

.page-header .eyebrow,
.topline .eyebrow,
.vt-feature-kicker,
.modal-kicker {
  color: var(--brand-400);
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.18em;
  text-transform: uppercase;
}

.page-title {
  margin-top: 4px;
  color: var(--ink-000);
  font-size: clamp(26px, 8vw, 32px);
}

.page-subtitle {
  margin-top: 4px;
  color: var(--ink-400);
  font-size: 12px;
}

.back-button {
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  border: 1px solid var(--hairline);
  border-radius: 14px;
  color: var(--ink-100);
  background: var(--surface-glass);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  transition: transform 0.18s var(--ease-out), border-color 0.18s, background 0.18s;
}

.back-button:hover {
  border-color: var(--hairline-strong);
  background: rgba(var(--brand-rgb), 0.09);
}

.back-button:active {
  transform: scale(0.92);
}

/* ===========================================================================
   GLASS CARD SYSTEM
   Every rounded panel in the app — balance, section, case, upgrade, player
   row, voucher, history row — shares one glass treatment: a soft gradient
   fill, a hairline that's brighter along the top-left (as if lit from one
   corner), and a very faint inner highlight. This is what turns "dark mode
   with rounded corners" into something that reads as considered.
   ========================================================================= */

.balance-card,
.section-card,
.case-card,
.upgrade-card,
.player-row,
.voucher-card,
.history-row,
.wide-action,
.inventory-link,
.section-link,
.profile-card {
  position: relative;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  background:
    linear-gradient(155deg, rgba(var(--brand-rgb), 0.05), transparent 40%),
    linear-gradient(180deg, var(--surface-2), var(--surface-1));
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.03) inset,
    0 18px 40px -22px rgba(0, 0, 0, 0.7);
  transition: border-color 0.2s var(--ease-out), transform 0.2s var(--ease-out), box-shadow 0.2s var(--ease-out);
}

.case-card,
.player-row,
.voucher-card,
.history-row {
  border-radius: var(--radius-md);
}

.wide-action:active,
.section-link:active,
.inventory-link:active,
button.case-card:active {
  transform: scale(0.985);
}

/* ===========================================================================
   BALANCE CARD
   ========================================================================= */

.balance-card {
  padding: 20px 20px 18px;
  overflow: hidden;
}

.balance-card::before {
  content: "";
  position: absolute;
  top: -60%;
  right: -30%;
  width: 70%;
  height: 220%;
  background: radial-gradient(circle, rgba(var(--brand-rgb), 0.1), transparent 65%);
  pointer-events: none;
}

.balance-card .eyebrow {
  color: var(--ink-400);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

.balance-row {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-top: 10px;
}

/* The leading "●" is an inline-styled span with no class to hook — it's
   always the first child, ahead of .balance-value and .balance-currency. */
.balance-row > span:first-child {
  display: none;
}

.balance-value {
  color: var(--ink-000);
  font-size: 36px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.03em;
  text-shadow: 0 0 26px rgba(var(--brand-rgb), 0.28);
}

.balance-currency {
  color: var(--brand-400);
  font-size: 13px;
  font-weight: 800;
  letter-spacing: 0.02em;
}

.balance-footer {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 6px;
  color: var(--ink-400);
  font-size: 11px;
}

.balance-footer strong {
  color: var(--brand-300);
}

.stamina-trigger {
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--hairline);
  cursor: pointer;
}

.stamina-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 8px;
  color: var(--ink-400);
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.14em;
}

.stamina-head strong {
  color: var(--ink-100);
  font-family: "DM Mono", ui-monospace, monospace;
  font-weight: 600;
  letter-spacing: 0;
}

.stamina-track {
  position: relative;
  height: 7px;
  overflow: hidden;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.35);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.5);
}

.stamina-track > span {
  display: block;
  height: 100%;
  border-radius: inherit;
  background: linear-gradient(90deg, var(--brand-600), var(--brand-400));
  box-shadow: 0 0 12px rgba(var(--brand-rgb), 0.55);
  transition: width 0.4s var(--ease-out);
}

/* ===========================================================================
   ACTION ROWS  (Улучшения / Перевести, profile section-links)
   ========================================================================= */

.wide-action,
.section-link,
.inventory-link {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding: 15px 16px;
  cursor: pointer;
}

.action-copy {
  display: flex;
  align-items: center;
  gap: 12px;
  color: var(--ink-100);
  font-size: 14px;
  font-weight: 700;
}

.action-icon,
.inventory-link .action-icon {
  display: grid;
  flex: 0 0 auto;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 11px;
  color: var(--bg-void-deep);
  background: linear-gradient(155deg, var(--brand-300), var(--brand-600));
  box-shadow: 0 6px 16px -6px rgba(var(--brand-rgb), 0.6);
}

.action-arrow {
  color: var(--ink-600);
  transition: transform 0.2s var(--ease-out), color 0.2s;
}

.wide-action:hover .action-arrow,
.section-link:hover .action-arrow {
  color: var(--brand-400);
  transform: translateX(3px);
}

.inventory-link {
  gap: 14px;
}

.inventory-title {
  color: var(--ink-000);
  font-size: 15px;
  font-weight: 800;
}

.inventory-link-copy {
  color: var(--ink-400);
  font-size: 11px;
}

.profile-sections {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}

.section-link {
  flex-direction: column;
  gap: 8px;
  text-align: center;
}

/* ===========================================================================
   TAP BUTTON — the signature moment.
   A living membrane: a breathing idle pulse, concentric rings that ripple
   outward continuously (not just on tap — this is the one element built to
   be looked at, not just used), a glass highlight sweep, and a firm
   squash-and-release on every real pointer interaction. --hold-progress is
   set inline by the bundle during a hold gesture; the conic-gradient ring
   reads it directly, so the charge-up state needs no extra markup.
   ========================================================================= */

/*
 * .tap-zone is already `position:relative` in the bundle's own CSS, which is
 * exactly the containing block the absolute-centering below needs — it is
 * deliberately left alone here. (An earlier draft set `display:grid` on it,
 * which turned the ring pseudo-elements below into real grid items and
 * pushed .tap-button off-centre; the fix was to stop touching zone layout
 * entirely, not to fix the pseudo-elements.)
 */

/*
 * Свечение вокруг вируса. Раньше оно жило в filter: drop-shadow() на самой
 * кнопке — а drop-shadow пересчитывается от альфа-канала при каждом
 * композите анимируемого слоя, что заметно било по iPhone. Здесь это
 * обычный градиент на неподвижном контейнере: рисуется один раз и дальше
 * ничего не стоит.
 */
.tap-zone {
  background:
    radial-gradient(
      circle at 50% 50%,
      rgba(180, 255, 50, 0.13) 0%,
      rgba(120, 200, 30, 0.06) 38%,
      transparent 68%
    );
}

.tap-zone::before,
.tap-zone::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  border-radius: 50%;
  pointer-events: none;
  transform: translate(-50%, -50%);
}

.tap-zone::before {
  width: min(88vw, 380px);
  aspect-ratio: 1;
  border: 2px solid rgba(180, 255, 50, 0.12);
  box-shadow:
    0 0 30px rgba(180, 255, 50, 0.06),
    inset 0 0 30px rgba(180, 255, 50, 0.04);
  animation: ring-breathe-toxic 4s ease-in-out infinite;
}

.tap-zone::after {
  width: min(70vw, 310px);
  aspect-ratio: 1;
  border: 1px solid rgba(255, 80, 60, 0.15);
  box-shadow: 0 0 20px rgba(255, 60, 40, 0.08);
  animation: ring-spin-toxic 18s linear infinite;
}

@keyframes ring-breathe-toxic {
  /*
   * border-color used to animate here too — that's a paint property, not a
   * compositor one, so it forced a repaint of this ring every frame right
   * next to the tap button. transform + opacity alone give the same
   * "breathing" read and stay on the compositor thread.
   */
  0%, 100% { transform: translate(-50%, -50%) scale(0.92); opacity: 0.3; }
  50% { transform: translate(-50%, -50%) scale(1.08); opacity: 1; }
}

@keyframes ring-spin-toxic {
  from { transform: translate(-50%, -50%) rotate(0deg); }
  to { transform: translate(-50%, -50%) rotate(360deg); }
}

.tap-button {
  /*
   * `position` is deliberately not set here: the bundle's own rule is
   * `.tap-button{position:absolute;inset:12%}` (10% at one breakpoint), and
   * .tap-zone is its `position:relative` containing block.
   */
  inset: 0 !important;
  margin: auto;
  width: min(80vw, 340px);
  aspect-ratio: 1;
  border: none !important;
  border-radius: 0;
  overflow: visible;
  /*
   * Картинка уже с настоящим альфа-каналом (virus-monster.webp), поэтому
   * маска тут больше не нужна.
   *
   * Раньше прозрачность вырезалась из JPG через mask-mode: luminance —
   * и это молча ломалось на iPhone: WebKit не поддерживает luminance-режим
   * и берёт маску по альфа-каналу, которого у JPG нет. Маска выходила
   * сплошь непрозрачной, и вместо вируса пользователи iOS видели чёрный
   * квадрат. Заодно маскирование большого анимируемого элемента — одна из
   * самых дорогих операций для iOS-композитора.
   */
  background: url('/assets/virus-monster.webp') center/contain no-repeat !important;
  background-color: transparent !important;

  box-shadow: none !important;

  /*
   * drop-shadow считается от альфа-канала на каждый композит анимируемого
   * слоя — на 680px картинке это заметно било по iPhone. Свечение отдано
   * статичному ::before ниже, тут остаётся только дешёвый brightness.
   */
  filter: brightness(1.15);
  cursor: pointer;
  animation: virus-float 4s ease-in-out infinite;
  transition: transform 0.15s cubic-bezier(0.34, 1.56, 0.64, 1), filter 0.2s ease;
  -webkit-tap-highlight-color: transparent;
  will-change: transform;
}

@keyframes virus-float {
  /*
   * The drop-shadow steps used to animate here too — filter is a paint
   * property, and each of these two drop-shadows is its own blur pass, so
   * the browser re-rasterized the whole masked image twice per frame,
   * forever, on the single biggest element on the home screen. transform
   * alone (compositor-only) keeps the same floating motion for free.
   */
  0%, 100% { transform: scale(1) rotate(0deg); }
  25% { transform: scale(1.02) rotate(1.5deg); }
  50% { transform: scale(1.04) rotate(0deg); }
  75% { transform: scale(1.02) rotate(-1.5deg); }
}

.tap-button::before {
  /*
   * Блик отключён. Это прямоугольная диагональная полоса поверх кнопки: пока
   * на элементе висела маска, она обрезала и псевдоэлемент, поэтому полоса
   * повторяла силуэт вируса. После перехода на PNG/WebP с настоящей
   * прозрачностью (маска больше не нужна) блик стал видимым прямоугольником
   * вокруг вируса. У самой картинки светотень уже нарисована, так что
   * отдельный блик тут не нужен.
   */
  content: none;
}

.tap-button:active {
  /*
   * filter used to change here too (different brightness/saturate/contrast
   * + two new drop-shadow blur radii) — that's a transition on a paint
   * property firing on literally every tap, the single most frequent
   * interaction in the game. transform-only keeps the "press" feedback on
   * the compositor thread no matter how fast someone taps.
   */
  transform: scale(0.88) !important;
  animation: none !important;
}

/* ---- WOW tap pulse effect ---- */
.tap-button.tap-pulse {
  animation: tap-wow 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) !important;
}

@keyframes tap-wow {
  /*
   * brightness/saturate steps used to animate here too. This runs on every
   * tap (it's the class the JS toggles for the "wow" pop) — same fix as
   * .tap-button:active just above: transform only.
   */
  0% {
    transform: scale(0.85);
  }
  30% {
    transform: scale(1.12);
  }
  50% {
    transform: scale(0.97);
  }
  70% {
    transform: scale(1.04);
  }
  100% {
    transform: scale(1);
  }
}

/* Shockwave ring on tap */
.tap-button.tap-pulse::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 100%;
  aspect-ratio: 1;
  border-radius: 50%;
  border: 3px solid rgba(180, 255, 50, 0.7);
  transform: translate(-50%, -50%) scale(0.8);
  animation: shockwave 0.6s ease-out forwards;
  pointer-events: none;
  z-index: 3;
}

@keyframes shockwave {
  /*
   * border-width and box-shadow used to animate here too (layout + paint
   * properties) — on a class the JS toggles on every single tap. transform
   * + opacity alone still read as an expanding, fading ring, on the
   * compositor thread, no matter how fast someone taps.
   */
  0% {
    transform: translate(-50%, -50%) scale(0.7);
    opacity: 1;
  }
  100% {
    transform: translate(-50%, -50%) scale(1.9);
    opacity: 0;
  }
}

.tap-progress {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 110%;
  aspect-ratio: 1;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: conic-gradient(
    var(--brand-300) calc(var(--hold-progress, 0%)),
    transparent 0
  );
  opacity: var(--hold-progress, 0%);
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 5px), #000 calc(100% - 4px));
  mask: radial-gradient(farthest-side, transparent calc(100% - 5px), #000 calc(100% - 4px));
  transition: opacity 0.2s;
}

.bacteria-svg {
  /* Hide the old SVG icon — the virus image IS the button now */
  display: none !important;
}

.bacteria-body,
.bacteria-eye,
.bacteria-smile,
.bacteria-flagella,
.bacteria-dot {
  display: none !important;
}

.tap-label {
  /* Hide the old label — the virus speaks for itself */
  display: none !important;
}

@media (prefers-reduced-motion: reduce) {
  .tap-zone::before,
  .tap-zone::after,
  .tap-button,
  .tap-button::before,
  .tap-button::after,
  body {
    animation: none !important;
  }
}

/* ===========================================================================
   BOTTOM NAV — a floating glass dock
   ========================================================================= */

.bottom-nav {
  border: 1px solid var(--hairline);
  border-radius: 20px;
  background: rgba(9, 15, 9, 0.85);
  /*
   * This dock is on screen on every page, and it's wide — backdrop-filter
   * cost scales with the filtered area, and blur+saturate together roughly
   * double the per-pixel work of blur alone. Dropped from blur(18px)
   * saturate(1.3) and raised the background opacity a little to compensate,
   * since a slow device paying for this on every frame is worse than a
   * slightly less glassy dock.
   */
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  box-shadow: 0 20px 50px -18px rgba(0, 0, 0, 0.8);
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .bottom-nav {
    background: #0c130c;
  }
}

/*
 * Low-end devices drop the glass entirely — see telegram-bridge.js for where
 * the flag comes from. The dock is on screen on every screen of the app, so
 * on a phone that struggles this is paid on every frame of a tap streak for
 * a purely decorative blur.
 */
/*
 * На iOS размытие выключается не ради скорости, а потому что backdrop-filter
 * в WKWebView регулярно ломает композитор: страница отрисовывается кусками,
 * ниже некоторой линии остаётся пустота. Флаг data-ios ставит
 * telegram-bridge.js.
 */
:root[data-low-power="true"] .bottom-nav,
:root[data-low-power="true"] .back-button,
:root[data-ios="true"] .bottom-nav,
:root[data-ios="true"] .back-button {
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

:root[data-low-power="true"] .bottom-nav,
:root[data-ios="true"] .bottom-nav {
  background: #0c130c;
}

.nav-button {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  border-radius: 15px;
  color: var(--ink-600);
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 0.08em;
  transition: color 0.2s, background 0.25s var(--ease-out);
}

.nav-icon {
  transition: transform 0.25s var(--ease-spring);
}

.nav-button.active {
  color: var(--bg-void-deep);
  background: linear-gradient(155deg, var(--brand-300), var(--brand-500));
  box-shadow: 0 8px 20px -6px rgba(var(--brand-rgb), 0.6);
}

.nav-button.active .nav-icon {
  transform: translateY(-1px) scale(1.08);
  color: var(--bg-void-deep) !important;
}

.nav-button:not(.active):hover {
  color: var(--ink-300);
}

.nav-button:active {
  transform: scale(0.94);
}

/* ===========================================================================
   BUTTONS
   ========================================================================= */

.primary-button,
.upgrade-buy-button {
  position: relative;
  overflow: hidden;
  border: none;
  border-radius: var(--radius-sm);
  padding: 14px 18px;
  color: var(--bg-void-deep);
  background: linear-gradient(135deg, var(--brand-300), var(--brand-500));
  box-shadow: 0 14px 30px -12px rgba(var(--brand-rgb), 0.7);
  font-weight: 800;
  transition: transform 0.15s var(--ease-out), box-shadow 0.15s var(--ease-out), filter 0.15s;
}

.primary-button:hover,
.upgrade-buy-button:not(:disabled):hover {
  filter: brightness(1.06);
  transform: translateY(-1px);
}

.primary-button:active,
.upgrade-buy-button:active {
  transform: translateY(0) scale(0.98);
}

.upgrade-buy-button:disabled {
  opacity: 0.45;
  filter: grayscale(0.4);
  cursor: not-allowed;
  box-shadow: none;
}

.category-tab {
  border-radius: 999px;
  color: var(--ink-400);
  font-weight: 700;
  transition: color 0.2s, background 0.2s;
}

.category-tab.active {
  color: var(--bg-void-deep);
  background: linear-gradient(135deg, var(--brand-300), var(--brand-500));
  box-shadow: 0 8px 18px -8px rgba(var(--brand-rgb), 0.6);
}

.category-tabs {
  border: 1px solid var(--hairline);
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.22);
  padding: 4px;
}

/* ===========================================================================
   CASES — treated as vials on a rack: a rarity edge tells the case apart
   before the copy does.
   ========================================================================= */

.case-card {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 16px;
  overflow: hidden;
}

.case-card::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 3px;
  background: linear-gradient(180deg, var(--ink-600), transparent);
}

.case-card.featured::before {
  background: linear-gradient(180deg, var(--brand-300), var(--brand-600));
}

.case-card[data-vt-enhanced^="lab"]::before {
  background: linear-gradient(180deg, var(--brand-300), var(--brand-600));
}

.case-card[data-vt-enhanced^="reserve"]::before {
  background: linear-gradient(180deg, var(--signal-400), #1a8f6f);
}

.case-art {
  display: grid;
  flex: 0 0 auto;
  place-items: center;
  width: 56px;
  height: 56px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-md);
  color: var(--ink-400);
  background: linear-gradient(155deg, rgba(255, 255, 255, 0.04), transparent);
}

.case-card.featured .case-art,
.case-card[data-vt-enhanced^="lab"] .case-art {
  color: var(--brand-400);
  background: linear-gradient(155deg, rgba(var(--brand-rgb), 0.22), rgba(var(--brand-rgb), 0.04));
  box-shadow: 0 0 20px -6px rgba(var(--brand-rgb), 0.5);
}

.case-card[data-vt-enhanced^="reserve"] .case-art {
  color: var(--signal-400);
  background: linear-gradient(155deg, rgba(var(--signal-rgb), 0.22), rgba(var(--signal-rgb), 0.04));
  box-shadow: 0 0 20px -6px rgba(var(--signal-rgb), 0.5);
}

.case-copy {
  flex: 1 1 auto;
  min-width: 0;
}

.case-copy h2 {
  color: var(--ink-000);
  font-size: 15px;
  font-weight: 700;
}

.case-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 10px;
  margin: 4px 0 10px;
  color: var(--ink-400);
  font-size: 11px;
}

.case-card .primary-button,
.vt-case-buy {
  width: 100%;
}

/* ===========================================================================
   UPGRADES MODAL
   ========================================================================= */

.upgrade-list {
  display: grid;
  gap: 10px;
  margin-top: 16px;
}

.upgrade-card {
  padding: 14px;
}

.upgrade-card-top {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 10px;
}

.upgrade-icon {
  display: grid;
  flex: 0 0 auto;
  place-items: center;
  width: 36px;
  height: 36px;
  border-radius: 11px;
  color: var(--bg-void-deep);
  background: linear-gradient(155deg, var(--brand-300), var(--brand-600));
}

.upgrade-card-top h3 {
  color: var(--ink-000);
  font-size: 13px;
  font-weight: 700;
}

.upgrade-card-top p {
  margin-top: 1px;
  color: var(--ink-400);
  font-size: 10px;
}

.upgrade-level {
  margin-left: auto;
  color: var(--brand-400);
  font-family: "DM Mono", ui-monospace, monospace;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.04em;
}

.upgrade-next {
  margin-bottom: 10px;
  color: var(--ink-300);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}

/* ===========================================================================
   MODALS — bottom sheet with a drag handle and a spring entrance
   ========================================================================= */

.modal-backdrop {
  background: rgba(2, 5, 3, 0.72) !important;
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  animation: sheet-backdrop-in 0.25s var(--ease-out) both;
}

.modal-card {
  position: relative;
  border: 1px solid var(--hairline-strong);
  border-radius: 28px 28px 0 0;
  background:
    linear-gradient(180deg, rgba(var(--brand-rgb), 0.05), transparent 18%),
    linear-gradient(180deg, var(--surface-2), var(--surface-1));
  box-shadow: 0 -30px 90px -30px rgba(0, 0, 0, 0.85);
  animation: sheet-in 0.42s var(--ease-spring) both;
}

.modal-card::before {
  content: "";
  position: absolute;
  top: 10px;
  left: 50%;
  width: 40px;
  height: 4px;
  border-radius: 999px;
  background: var(--hairline-strong);
  transform: translateX(-50%);
}

@keyframes sheet-backdrop-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes sheet-in {
  from { opacity: 0; transform: translateY(26px) scale(0.98); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}

.modal-close {
  border: 1px solid var(--hairline);
  border-radius: 50%;
  color: var(--ink-100);
  background: rgba(0, 0, 0, 0.25);
  transition: background 0.2s, border-color 0.2s, transform 0.15s;
}

.modal-close:hover {
  border-color: var(--hairline-strong);
  background: rgba(var(--brand-rgb), 0.12);
}

.modal-close:active {
  transform: scale(0.9);
}

.modal-description {
  color: var(--ink-400);
}

/* ===========================================================================
   FORMS
   ========================================================================= */

.form-label {
  color: var(--ink-400);
  font-size: 11px;
  font-weight: 700;
}

.form-input {
  border: 1px solid var(--hairline);
  border-radius: var(--radius-sm);
  background: rgba(0, 0, 0, 0.28);
  color: var(--ink-000);
  transition: border-color 0.2s, background 0.2s;
}

.form-input:focus {
  border-color: var(--hairline-strong);
  background: rgba(0, 0, 0, 0.4);
  outline: none;
}

.form-message.success {
  color: var(--brand-300);
}

.form-message.error {
  color: var(--coral-400);
}

/* ===========================================================================
   LEADERBOARD
   ========================================================================= */

.player-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
}

.player-place {
  flex: 0 0 auto;
  width: 22px;
  color: var(--ink-600);
  font-family: "DM Mono", ui-monospace, monospace;
  font-weight: 700;
  text-align: center;
}

.player-row[data-testid$="-1"] .player-place,
.player-row[data-testid$="-2"] .player-place,
.player-row[data-testid$="-3"] .player-place {
  color: var(--brand-400);
}

.player-avatar {
  display: grid;
  flex: 0 0 auto;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: 13px;
  color: var(--bg-void-deep);
  background: linear-gradient(155deg, var(--brand-300), var(--brand-600));
  font-weight: 800;
}

.player-info {
  min-width: 0;
}

.player-name {
  overflow: hidden;
  color: var(--ink-100);
  font-weight: 700;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.player-value {
  margin-top: 1px;
  color: var(--ink-400);
  font-family: "DM Mono", ui-monospace, monospace;
  font-size: 11px;
}

.lucide-medal {
  stroke: var(--brand-400);
  filter: drop-shadow(0 0 6px rgba(var(--brand-rgb), 0.6));
}

/* ===========================================================================
   INVENTORY / HISTORY / SECTION CARDS
   ========================================================================= */

.section-card {
  padding: 22px 20px;
}

.empty-icon {
  display: grid;
  place-items: center;
  width: 52px;
  height: 52px;
  margin-bottom: 14px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-md);
  color: var(--brand-400);
  background: rgba(var(--brand-rgb), 0.08);
}

.section-card h2 {
  color: var(--ink-000);
  font-size: 19px;
  font-weight: 700;
}

.section-card > p {
  margin-top: 6px;
  color: var(--ink-400);
  font-size: 12px;
  line-height: 1.5;
}

.voucher-card,
.history-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 14px;
  margin-top: 16px;
}

.voucher-label {
  color: var(--ink-400);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.voucher-claim {
  border-radius: var(--radius-sm);
  padding: 10px 16px;
  color: var(--bg-void-deep);
  background: linear-gradient(135deg, var(--brand-300), var(--brand-500));
  font-weight: 800;
}

.history-copy strong {
  color: var(--ink-100);
}

.history-copy small {
  color: var(--ink-400);
}

.history-amount {
  color: var(--brand-300);
  font-family: "DM Mono", ui-monospace, monospace;
  font-weight: 700;
}

.history-amount.out {
  color: var(--coral-400);
}

/* ===========================================================================
   PROFILE CARD
   ========================================================================= */

.profile-card {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 18px;
  overflow: hidden;
}

.avatar {
  display: grid;
  flex: 0 0 auto;
  place-items: center;
  width: 58px;
  height: 58px;
  border-radius: 18px;
  color: var(--bg-void-deep);
  background: linear-gradient(155deg, var(--brand-300), var(--brand-600));
  font-size: 22px;
  font-weight: 800;
  box-shadow: 0 10px 24px -10px rgba(var(--brand-rgb), 0.7);
}

.profile-name {
  color: var(--ink-000);
  font-size: 18px;
  font-weight: 800;
}

.profile-username {
  color: var(--brand-400);
  font-size: 12px;
  font-weight: 700;
}

.profile-id {
  margin-top: 2px;
  color: var(--ink-600);
  font-family: "DM Mono", ui-monospace, monospace;
  font-size: 10px;
}

/* ===========================================================================
   SCROLLBAR (webkit) — thin and on-brand rather than the OS default
   ========================================================================= */

::-webkit-scrollbar {
  width: 6px;
}

::-webkit-scrollbar-thumb {
  border-radius: 999px;
  background: rgba(var(--brand-rgb), 0.28);
}

/* ===========================================================================
   ICONS (Hugeicons, stroke-rounded free set)
   The bundle renders its icons as inline lucide-react SVGs with fixed class
   names (lucide-<name>) — there is no prop to swap the icon set at the
   React level since we only have the compiled bundle. Instead each icon is
   masked out: the original stroke paths are hidden (transparent), and a
   Hugeicons glyph is painted back in via mask-image using background-color:
   currentColor, so existing color rules (active nav state, hover, etc.)
   keep working untouched. -webkit-mask-image uses the source alpha channel
   directly (not luminance), so plain black-stroke SVGs work as-is here.
   ========================================================================= */

:root {
  --hi-home: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%3E%3Cpath%20d%3D%22M13%2013.5032H11C10.1716%2013.5032%209.5%2014.1748%209.5%2015.0032V20.5012C9.5%2021.0539%209.05114%2021.5032%208.49854%2021.4977C6.17337%2021.4744%204.88133%2021.3333%204.02513%2020.4771C3%2019.452%203%2017.8021%203%2014.5023V11.9918C3%2010.3106%203%209.46999%203.3559%208.74231C3.71179%208.01463%204.37533%207.49854%205.70241%206.46637L7.70242%204.91081C9.76689%203.30511%2010.7991%202.50226%2012%202.50226C13.2009%202.50226%2014.2331%203.30511%2016.2976%204.91081L18.2976%206.46636C19.6247%207.49854%2020.2882%208.01463%2020.6441%208.74231C21%209.46999%2021%2010.3106%2021%2011.9918V14.5023C21%2017.8021%2021%2019.452%2019.9749%2020.4771C19.1187%2021.3333%2017.8266%2021.4744%2015.5015%2021.4977C14.9489%2021.5032%2014.5%2021.0539%2014.5%2020.5012V15.0032C14.5%2014.1748%2013.8284%2013.5032%2013%2013.5032Z%22%20stroke%3D%22%23000%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%221.5%22%2F%3E%3C%2Fsvg%3E");
  --hi-trophy: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%3E%3Cpath%20d%3D%22M15.9349%2021.033C16.1789%2021.5331%2015.699%2022%2015.1239%2022H8.87606C8.30096%2022%207.82107%2021.5331%208.06509%2021.033C8.63149%2019.8722%2010.1793%2019%2012%2019C13.8207%2019%2015.3685%2019.8722%2015.9349%2021.033Z%22%20stroke%3D%22%23000%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%221.5%22%2F%3E%3Cpath%20d%3D%22M12%2016L11.9883%2019%22%20stroke%3D%22%23000%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%221.5%22%2F%3E%3Cpath%20d%3D%22M17.9866%204.49139C17.992%204.30176%2017.9963%204.11477%2017.9997%203.93095C18.0194%202.86463%2017.1162%202.00002%2016.0093%202.00002L7.99052%202C6.88381%202%205.98074%202.86438%206.00031%203.93054C6.00358%204.10833%206.00768%204.28907%206.01283%204.47231C6.05734%206.05627%206.18024%207.82593%206.51875%209.47415C7.18043%2012.6958%208.66606%2016%2012.0001%2016C15.3341%2016%2016.8193%2012.6958%2017.481%209.47415C17.8181%207.83255%2017.9415%206.07048%2017.9866%204.49139Z%22%20stroke%3D%22%23000%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%221.5%22%2F%3E%3Cpath%20d%3D%22M18%204.5L19.4447%204.68748C20.9062%204.87714%2022%206.122%2022%207.59578C22%208.99373%2021.0133%2010.1973%2019.6425%2010.4715L17.5%2011%22%20stroke%3D%22%23000%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%221.5%22%2F%3E%3Cpath%20d%3D%22M6%204.5L4.55527%204.68748C3.09375%204.87714%202%206.122%202%207.59578C2%208.99373%202.98673%2010.1973%204.35754%2010.4715L6.5%2011%22%20stroke%3D%22%23000%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%221.5%22%2F%3E%3C%2Fsvg%3E");
  --hi-user: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%3E%3Cpath%20d%3D%22M20%2021.0001C19.713%2017.269%2016.7289%2014.3151%2012.995%2014.0662L12%2013.9999C11.6446%2014.0096%2011.3134%2014.0225%2011.0008%2014.0378C7.3%2014.2192%204.28417%2017.3057%204%2021.0001%22%20stroke%3D%22%23000%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%221.5%22%2F%3E%3Ccircle%20cx%3D%2212%22%20cy%3D%226.99988%22%20r%3D%224%22%20stroke%3D%22%23000%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%221.5%22%2F%3E%3C%2Fsvg%3E");
  --hi-flask: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%3E%3Cpath%20d%3D%22M11.223%206V7.98898C11.223%209.38733%2011.223%2010.0865%2011.4102%2010.7577C11.5974%2011.4289%2011.9644%2012.0452%2012.6984%2013.2779L13.6929%2014.9482C15.5632%2018.0892%2016.4984%2019.6597%2015.7312%2020.824L15.7193%2020.8418C14.9409%2022%2012.9606%2022%209%2022C5.03938%2022%203.05907%2022%202.2807%2020.8418L2.26884%2020.824C1.50164%2019.6597%202.43679%2018.0892%204.30708%2014.9482L5.30163%2013.2779C6.03558%2012.0452%206.40256%2011.4289%206.5898%2010.7577C6.77704%2010.0865%206.77704%209.38733%206.77704%207.98898V6%22%20stroke%3D%22%23000%22%20stroke-width%3D%221.5%22%2F%3E%3Cpath%20d%3D%22M6%206L12%206%22%20stroke%3D%22%23000%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%221.5%22%2F%3E%3Cpath%20d%3D%22M5.5%2013.2803C6.16667%2012.699%207.73448%2013.1181%209.00155%2013.6643C10.6682%2014.3828%2012.1667%2013.8019%2012.5%2013.2803%22%20stroke%3D%22%23000%22%20stroke-linecap%3D%22round%22%20stroke-width%3D%221.5%22%2F%3E%3Cpath%20d%3D%22M11.5%207C11.5%209%2013.8624%2011.2121%2015.4728%2011C15.4728%2012.6569%2016.934%2014%2018.7364%2014C20.5388%2014%2022%2012.6569%2022%2011C22%209.34315%2021%208%2019%208C19%206%2017.5%204%2015.3799%204C15.3799%202.45687%2014.5%202%2013.5%202C12.5%202%2012%203%2012%203C12%203%209%203%209%206%22%20stroke%3D%22%23000%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%221.5%22%2F%3E%3C%2Fsvg%3E");
  --hi-send: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%3E%3Cpath%20d%3D%22M21.0477%203.05293C18.8697%200.707363%202.48648%206.4532%202.50001%208.551C2.51535%2010.9299%208.89809%2011.6617%2010.6672%2012.1581C11.7311%2012.4565%2012.016%2012.7625%2012.2613%2013.8781C13.3723%2018.9305%2013.9301%2021.4435%2015.2014%2021.4996C17.2278%2021.5892%2023.1733%205.342%2021.0477%203.05293Z%22%20stroke%3D%22%23000%22%20stroke-width%3D%221.5%22%2F%3E%3Cpath%20d%3D%22M11.4999%2012.5L14.9999%209%22%20stroke%3D%22%23000%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%221.5%22%2F%3E%3C%2Fsvg%3E");
  --hi-medal: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%3E%3Cpath%20d%3D%22M8.5%202V10.5M15.5%202V10.5%22%20stroke%3D%22%23000%22%20stroke-linecap%3D%22round%22%20stroke-width%3D%221.5%22%2F%3E%3Cpath%20d%3D%22M17.9162%202.01166H6.0838C5.17286%202.01166%203.96696%201.85424%203.34398%202.69602C3%203.1608%203%203.83334%203%205.17844C3%206.32524%203%206.89864%203.23194%207.38174C3.62807%208.20684%204.51377%208.56526%205.27291%208.95504L8.98131%2010.8591C10.4626%2011.6197%2011.2033%2012%2012%2012C12.7967%2012%2013.5374%2011.6197%2015.0187%2010.8591L18.7271%208.95504C19.4862%208.56526%2020.3719%208.20684%2020.7681%207.38174C21%206.89864%2021%206.32524%2021%205.17844C21%203.83334%2021%203.1608%2020.656%202.69602C20.033%201.85424%2018.8271%202.01166%2017.9162%202.01166Z%22%20stroke%3D%22%23000%22%20stroke-width%3D%221.5%22%2F%3E%3Cpath%20d%3D%22M10.5292%2013.6376C11.2478%2013.2125%2011.6071%2013%2012%2013C12.3929%2013%2012.7522%2013.2125%2013.4708%2013.6376L14.4708%2014.2292C15.2167%2014.6704%2015.5896%2014.891%2015.7948%2015.26C16%2015.6289%2016%2016.0789%2016%2016.979V18.021C16%2018.9211%2016%2019.3711%2015.7948%2019.74C15.5896%2020.109%2015.2167%2020.3296%2014.4708%2020.7708L13.4708%2021.3624C12.7522%2021.7875%2012.3929%2022%2012%2022C11.6071%2022%2011.2478%2021.7875%2010.5292%2021.3624L9.52922%2020.7708C8.78332%2020.3296%208.41037%2020.109%208.20519%2019.74C8%2019.3711%208%2018.9211%208%2018.021V16.979C8%2016.0789%208%2015.6289%208.20519%2015.26C8.41037%2014.891%208.78332%2014.6704%209.52922%2014.2292L10.5292%2013.6376Z%22%20stroke%3D%22%23000%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%221.5%22%2F%3E%3C%2Fsvg%3E");
}

.lucide-home,
.lucide-trophy,
.lucide-radio,
.lucide-user-round,
.lucide-flask-conical,
.lucide-send,
.lucide-medal {
  stroke: transparent !important;
  fill: transparent !important;
  background-color: currentColor;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: contain;
  mask-size: contain;
}

.lucide-trophy { -webkit-mask-image: var(--hi-trophy); mask-image: var(--hi-trophy); }
.lucide-radio { -webkit-mask-image: var(--hi-home); mask-image: var(--hi-home); }
.lucide-user-round { -webkit-mask-image: var(--hi-user); mask-image: var(--hi-user); }
.lucide-flask-conical { -webkit-mask-image: var(--hi-flask); mask-image: var(--hi-flask); }
.lucide-send { -webkit-mask-image: var(--hi-send); mask-image: var(--hi-send); }
.lucide-medal { -webkit-mask-image: var(--hi-medal); mask-image: var(--hi-medal); }
