/* ============================================================================
   KAIRO · Sistema de componentes
   ----------------------------------------------------------------------------
   ANGULAR 21 → cada bloque marcado con `ng-component:` es un componente
   standalone. Los estilos de esos bloques se mueven al `styles: []` del
   componente; lo que queda acá (layout, tipografía, tokens) va a global.
   ============================================================================ */

/* ── Layout base ─────────────────────────────────────── ng-component: ShellComponent */
.shell { min-height: 100dvh; display: flex; flex-direction: column; }
.page {
  width: 100%; max-width: var(--maxw);
  margin-inline: auto;
  padding: var(--sp-4) var(--sp-4) calc(var(--nav-h) + var(--sp-7));
  display: grid; gap: var(--sp-5);
  /* La columna DEBE ser minmax(0,1fr) y no el `auto` implícito: con `auto` se
     dimensiona por el max-content de la sección más ancha, así que cualquier
     bloque interno que no sepa achicarse arrastra la página entera y aparece
     scroll horizontal en el teléfono. Con esto, el techo lo pone el viewport y
     cada sección se las arregla adentro. */
  grid-template-columns: minmax(0, 1fr);
}
@media (min-width: 960px) {
  .shell { flex-direction: row; }
  .shell > .main { flex: 1; min-width: 0; }
  .page { padding: var(--sp-6) var(--sp-6) var(--sp-8); gap: var(--sp-6); }
}

/* ── App bar ─────────────────────────────────────────── ng-component: AppBarComponent */
.appbar {
  position: sticky; top: 0; z-index: 40;
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  background: oklch(16% 0.045 268 / .78);
  backdrop-filter: blur(14px) saturate(140%);
  border-bottom: 1px solid var(--border-soft);
}
.appbar__brand {
  font-family: var(--font-display); font-weight: 800;
  font-size: var(--step-1); letter-spacing: -0.02em;
  display: flex; align-items: center; gap: 8px;
}
.appbar__brand b { color: var(--accent); }
.appbar__spacer { flex: 1; }

.pill {
  display: inline-flex; align-items: center; gap: 6px;
  min-height: 34px; padding: 0 12px;
  border-radius: var(--r-pill);
  background: var(--surface-2); border: 1px solid var(--border-soft);
  font-family: var(--font-mono); font-size: var(--step--1);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.pill--coins { color: var(--accent-2); }
.pill--packs { color: var(--accent); }
.pill svg { width: 15px; height: 15px; }

/* ── Navegación colapsable ────────────────────────────── ng-component: NavComponent
   Móvil  → tab bar abajo que se esconde al scrollear hacia abajo.
   Desktop → rail que se contrae de 236px a 76px (sólo iconos) con un toggle.
   El estado vive en <html data-nav-state> y se persiste; ver js/ui.js.
   OJO: NO usar `data-nav` en <html> — colisiona con el selector de montaje
   `nav[data-nav]` y mountNav() terminaría escribiendo sobre el documento. */
.nav {
  position: fixed; inset: auto 0 0 0; z-index: 45;
  display: flex; align-items: stretch;
  height: var(--nav-h);
  padding: 6px max(6px, env(safe-area-inset-left)) max(6px, env(safe-area-inset-bottom));
  background: oklch(13% 0.042 268 / .92);
  backdrop-filter: blur(16px);
  border-top: 1px solid var(--border-soft);
  transition: transform .28s var(--ease), width .24s var(--ease);
}
.nav__head { display: none; }
.nav__list { display: flex; flex: 1; min-width: 0; }
.nav__item {
  flex: 1; min-width: 0; min-height: 56px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
  border-radius: var(--r-md);
  color: var(--muted); font-size: var(--step--2); font-weight: 600;
  text-decoration: none; position: relative;
  transition: color .18s var(--ease), background .18s var(--ease);
}
.nav__item svg { width: 22px; height: 22px; flex: 0 0 22px; }
.nav__item:hover { color: var(--fg); }
.nav__item[aria-current="page"] { color: var(--accent); background: oklch(80% 0.145 205 / .10); }
.nav__badge {
  position: absolute; top: 4px; left: 50%; margin-left: 6px;
  min-width: 18px; height: 18px; padding: 0 5px;
  display: grid; place-items: center;
  border-radius: var(--r-pill);
  background: var(--accent-2); color: var(--accent-ink);
  font-family: var(--font-mono); font-size: 11px; font-weight: 700;
}
/* auto-hide en móvil: devuelve 68px de alto a la hoja del álbum */
html[data-nav-hidden="1"] .nav { transform: translateY(110%); }

@media (min-width: 960px) {
  html[data-nav-hidden="1"] .nav { transform: none; }
  .nav {
    position: sticky; inset: 0 auto 0 0; height: 100dvh; width: var(--rail-w);
    flex-direction: column; justify-content: flex-start; gap: 4px;
    padding: var(--sp-4) var(--sp-3);
    border-top: 0; border-right: 1px solid var(--border-soft);
    overflow: hidden;
  }
  .nav__head {
    display: flex; align-items: center; gap: var(--sp-2);
    padding: 0 var(--sp-2) var(--sp-4); margin-bottom: var(--sp-2);
    border-bottom: 1px solid var(--border-soft);
  }
  .nav__logo {
    flex: 1; min-width: 0; overflow: hidden; white-space: nowrap;
    font-family: var(--font-display); font-size: var(--step-2); font-weight: 800;
    padding-left: var(--sp-2);
    transition: opacity .18s var(--ease), max-width .24s var(--ease);
  }
  .nav__logo b { color: var(--accent); }
  .nav__toggle {
    flex: 0 0 44px; width: 44px; height: 44px; display: grid; place-items: center;
    border-radius: var(--r-md); border: 1px solid var(--border);
    background: var(--surface-2); color: var(--muted); cursor: pointer;
    transition: color .18s var(--ease), border-color .18s var(--ease);
  }
  .nav__toggle:hover { color: var(--fg); border-color: var(--accent); }
  .nav__toggle svg { width: 18px; height: 18px; transform: rotate(180deg); transition: transform .24s var(--ease); }
  .nav__list { flex-direction: column; flex: 0 0 auto; gap: 4px; }
  .nav__item {
    flex: 0 0 auto; flex-direction: row; justify-content: flex-start;
    gap: var(--sp-3); padding: 0 var(--sp-3); font-size: var(--step-0);
    min-height: 46px;
  }
  .nav__label { overflow: hidden; white-space: nowrap; transition: opacity .18s var(--ease); }
  .nav__badge { position: static; margin: 0 0 0 auto; }

  /* --- contraído --- */
  html[data-nav-state="collapsed"] .nav__logo { max-width: 0; opacity: 0; padding: 0; }
  html[data-nav-state="collapsed"] .nav__toggle svg { transform: rotate(0deg); }
  html[data-nav-state="collapsed"] .nav__head { justify-content: center; padding-inline: 0; }
  html[data-nav-state="collapsed"] .nav__item { justify-content: center; padding: 0; }
  html[data-nav-state="collapsed"] .nav__label { max-width: 0; opacity: 0; }
  html[data-nav-state="collapsed"] .nav__badge {
    position: absolute; top: 6px; right: 10px; margin: 0;
    min-width: 16px; height: 16px; font-size: 10px;
  }
}

/* ── Botones ─────────────────────────────────────────── ng-component: ButtonComponent */
.btn {
  --btn-bg: var(--surface-2);
  --btn-fg: var(--fg);
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-height: 46px; padding: 0 var(--sp-5);
  border: 1px solid var(--border); border-radius: var(--r-pill);
  background: var(--btn-bg); color: var(--btn-fg);
  font-family: var(--font-display); font-weight: 700; font-size: var(--step-0);
  cursor: pointer; text-decoration: none; white-space: nowrap;
  transition: transform .16s var(--ease-bounce), filter .16s var(--ease), box-shadow .16s var(--ease);
}
.btn:hover:not(:disabled) { transform: translateY(-2px); filter: brightness(1.08); }
.btn:active:not(:disabled) { transform: translateY(0) scale(.97); }
.btn:disabled { opacity: .42; cursor: not-allowed; }
.btn svg { width: 18px; height: 18px; }
.btn--primary {
  --btn-bg: linear-gradient(135deg, var(--accent-hi), var(--accent));
  --btn-fg: var(--accent-ink);
  border-color: transparent; box-shadow: var(--shadow-glow);
}
.btn--lime { --btn-bg: var(--accent-2); --btn-fg: var(--accent-ink); border-color: transparent; }
.btn--ghost { --btn-bg: transparent; border-color: var(--border); }
.btn--danger { --btn-bg: transparent; --btn-fg: var(--danger); border-color: var(--danger); }
.btn--lg { min-height: 56px; font-size: var(--step-1); padding: 0 var(--sp-6); }
.btn--block { width: 100%; }

/* ── Panel / módulo ──────────────────────────────────── ng-component: PanelComponent */
.panel {
  background: linear-gradient(180deg, var(--surface), oklch(21% 0.046 268));
  border: 1px solid var(--border-soft); border-radius: var(--r-lg);
  padding: var(--sp-5); box-shadow: var(--shadow-1);
}
.panel__head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-3); margin-bottom: var(--sp-4); }
.panel__title { font-size: var(--step-1); }
.panel__hint { font-size: var(--step--1); color: var(--muted); }
.eyebrow {
  font-family: var(--font-mono); font-size: var(--step--2);
  letter-spacing: .14em; text-transform: uppercase; color: var(--accent);
}

/* ── Progreso ────────────────────────────────────── ng-component: ProgressBarComponent */
.bar { height: 12px; border-radius: var(--r-pill); background: var(--bg-deep); overflow: hidden; border: 1px solid var(--border-soft); }
.bar__fill {
  height: 100%; border-radius: inherit;
  background: linear-gradient(90deg, var(--accent), var(--accent-2));
  transition: width .6s var(--ease);
}
.ring { display: grid; place-items: center; position: relative; }
.ring svg { transform: rotate(-90deg); }
.ring__num { position: absolute; font-family: var(--font-display); font-weight: 800; font-size: var(--step-2); }

/* ── Chips / filtros ──────────────────────────────────── ng-component: ChipsComponent */
.chips { display: flex; gap: var(--sp-2); flex-wrap: wrap; }
.chip {
  min-height: 44px; padding: 0 14px;   /* 44px: objetivo táctil mínimo en móvil */
  display: inline-flex; align-items: center; gap: 7px;
  border-radius: var(--r-pill); border: 1px solid var(--border);
  background: transparent; color: var(--muted);
  font-size: var(--step--1); font-weight: 600; cursor: pointer;
  transition: all .18s var(--ease);
}
.chip:hover { color: var(--fg); border-color: var(--accent); }
.chip[aria-pressed="true"] { background: var(--accent); border-color: transparent; color: var(--accent-ink); }
.chip__dot { width: 9px; height: 9px; border-radius: 50%; background: currentColor; }

/* ── Figurita ───────────────────────────────────────── ng-component: StickerComponent
   La figurita es una IMAGEN, no un SVG generado. Esquinas apenas redondeadas
   (10px), filete de 1px y nada de marco blanco grueso: el peso visual lo lleva
   la ilustración, no el envoltorio.

   `--slot-fs` sale de una container query sobre el propio hueco, no de un px
   multiplicado por --zoom: la tipografía queda como una PROPORCIÓN del hueco, así
   que escala sola con el zoom y con el breakpoint sin una sola fórmula más. Con
   el valor fijo anterior, un hueco de 160px seguía con la etiqueta de 9px y los
   nombres largos se cortaban en «CHARMANDE». */
.sticker {
  container-type: inline-size;
  --slot-fs: 8cqi;
  position: relative; aspect-ratio: 3 / 4;
  border-radius: 10px; overflow: hidden;
  background: var(--slot);
  border: 1px solid var(--slot-line);
  display: grid; grid-template-rows: 1fr auto;
  transition: transform .18s var(--ease), box-shadow .18s var(--ease);
  cursor: pointer;
}
.sticker:hover { transform: translateY(-2px); box-shadow: 0 6px 18px oklch(10% 0.05 260 / .38); }
.sticker__art {
  position: relative; display: grid; place-items: center; overflow: hidden;
  background: linear-gradient(168deg, var(--slot), var(--slot-2));
}
.sticker__art img {
  width: 88%; height: 88%; object-fit: contain;
  filter: drop-shadow(0 2px 3px oklch(20% 0.04 258 / .22));
}
/* El pie lleva SÓLO el nombre, como la banda impresa de una figurita de verdad.
   Antes traía además un punto de color y una letra ("• H") para la rareza: es
   información que el filete ya cuenta —neutro común, de color rara, iridiscente
   holo— y encima con un código que nadie explicó en ninguna pantalla. Duplicar
   el dato en un chip convertía la figurita en una tarjeta de UI.
   Tampoco lleva línea divisoria: la banda se apoya sobre la ilustración como
   tinta impresa, no como el footer de una card. */
.sticker__foot {
  display: flex; align-items: center; justify-content: center;
  padding: 5px 7px; background: var(--slot); color: var(--ink);
}
.sticker__name {
  font-family: var(--font-display); font-weight: 800;
  font-size: calc(var(--slot-fs, 10px) * 1.05);
  text-transform: uppercase; letter-spacing: .01em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%;
}
.sticker__count {
  position: absolute; top: 5px; left: 5px; z-index: 2;
  min-width: 26px; height: 22px; padding: 0 6px;
  display: grid; place-items: center; border-radius: var(--r-pill);
  background: var(--accent-2); color: var(--accent-ink);
  font-family: var(--font-mono); font-weight: 700; font-size: calc(var(--slot-fs, 10px) * .95);
}

/* Rarezas: un filete, no un aura. Vocabulario del ADR: common | rare | holo.
   Desde que se fue el chip del pie, ESTE es el único lugar donde se lee la
   rareza en la hoja — filete neutro, filete de color, borde iridiscente — así
   que la escala tiene que sostenerse sola. Se fue también la variable --rar:
   sólo la consumía el punto de color que ya no existe. */
.sticker[data-rarity="rare"] { border-color: var(--r-rare); }
.sticker[data-rarity="holo"] {
  border: 2px solid transparent;
  background:
    linear-gradient(var(--slot), var(--slot)) padding-box,
    conic-gradient(from 180deg, var(--r-legend-a), var(--r-legend-b),
                   var(--r-legend-c), var(--r-legend-a)) border-box;
}

/* ── Slot del álbum ─────────────────────────────────────── ng-component: SlotComponent
   Hueco impreso: filete punteado y el número en el centro, como en la hoja real. */
.slot {
  container-type: inline-size;
  --slot-fs: 8cqi;
  position: relative; aspect-ratio: 3 / 4;
  border-radius: 10px;
  border: 1px dashed color-mix(in oklab, var(--sheet-ink) 55%, transparent);
  background: color-mix(in oklab, var(--sheet-ink) 12%, transparent);
  display: grid; place-items: center;
  color: color-mix(in oklab, var(--sheet-ink) 80%, transparent);
  font-family: var(--font-mono); padding: 0; cursor: default;
}
/* Hueco listo para pegar. Antes era un "ping" de notificación; ahora respira y
   flota apenas, como una figurita apoyada esperando que la aprieten. La
   invitación es la flotación, no el destello. */
.slot--pending {
  border: 2px solid var(--accent-2); cursor: pointer;
  background: color-mix(in oklab, var(--accent-2) 16%, transparent);
  color: var(--accent-2);
  animation: pendingFloat 2.4s var(--ease) infinite;
  will-change: transform, box-shadow;
}
.slot--pending:hover { animation-play-state: paused; transform: translateY(-4px) scale(1.02); }
.slot--pending::after {
  content: "Pegar"; position: absolute; bottom: 7px;
  font-family: var(--font-display); font-weight: 800;
  font-size: calc(var(--slot-fs, 10px) * 1.05); text-transform: uppercase; letter-spacing: .04em;
}
@keyframes pendingFloat {
  0%, 100% { transform: translateY(0);      box-shadow: 0 0 0 0 oklch(86% .19 132 / .38); }
  45%      { transform: translateY(-3.5px); box-shadow: 0 5px 14px oklch(86% .19 132 / .28); }
}

/* Entrada escalonada de la hoja. `--i` lo pone el render; el retraso está
   topeado para que una hoja de 20 no tarde más que una de 6. */
@keyframes slotIn {
  from { opacity: 0; transform: translateY(12px) scale(.94); }
}
.sheet__grid > * {
  animation: slotIn .38s var(--ease-bounce) both;
  animation-delay: calc(min(var(--i, 0) * 18, 320) * 1ms);
}
@keyframes pendingPulse {
  0%, 100% { box-shadow: 0 0 0 0 oklch(86% 0.19 132 / .5); }
  50%      { box-shadow: 0 0 0 10px oklch(86% 0.19 132 / 0); }
}
.slot__n { font-size: calc(var(--slot-fs, 10px) * 1.9); font-weight: 700; opacity: .55; }

/* ── Slap: pegar la figurita ────────────────────────────────────────────────
   Es EL gesto del producto, así que tiene los tres tiempos completos:
   anticipación (entra grande, alta y desenfocada), impacto (aplasta contra el
   papel en el fotograma 58%) y asentamiento (dos rebotes que decrecen).
   El anillo va por `box-shadow` y no por un pseudo-elemento porque la figurita
   tiene `overflow:hidden` — un ::after quedaría recortado. */
@keyframes slap {
  0%   { transform: translateY(-22px) scale(2.05) rotate(-11deg); opacity: 0; filter: blur(4px); }
  40%  { opacity: 1; filter: blur(0); }
  58%  { transform: translateY(0) scale(.86, .94) rotate(2.5deg); }   /* squash */
  72%  { transform: translateY(-3px) scale(1.07, 1.02) rotate(-1.4deg); } /* stretch */
  86%  { transform: translateY(0) scale(.975) rotate(.5deg); }
  100% { transform: translateY(0) scale(1) rotate(0); }
}
@keyframes slapRing {
  0%   { box-shadow: 0 0 0 0 oklch(86% .19 132 / 0); }
  30%  { box-shadow: 0 0 0 0 oklch(86% .19 132 / .8); }   /* recién en el impacto */
  100% { box-shadow: 0 0 0 26px oklch(86% .19 132 / 0); }
}
.is-slapping {
  animation: slap .62s var(--ease-bounce) both, slapRing .62s var(--ease) both;
  will-change: transform, box-shadow;
  z-index: 2;
}

/* ── Destello holo ──────────────────────────────────────────────────────────
   UNA pasada de luz, no un loop. Antes esto era un cónico girando en bucle
   sobre cada holo: llamaba la atención todo el tiempo y por eso no llamaba la
   atención nunca. Ahora aparece cuando la carta ES un evento (se revela, se
   pega) o cuando la señalás con el mouse. */
.sticker[data-rarity="holo"] .sticker__art::after {
  content: ""; position: absolute; inset: -30%;
  background: linear-gradient(68deg,
    transparent 38%,
    oklch(96% .10 190 / .55) 46%,
    oklch(92% .14 320 / .65) 50%,
    oklch(96% .12 130 / .55) 54%,
    transparent 62%);
  transform: translateX(-115%);
  pointer-events: none;
}
.sticker[data-rarity="holo"]:hover .sticker__art::after,
.sticker[data-rarity="holo"].is-slapping .sticker__art::after,
.sticker[data-rarity="holo"].is-revealing .sticker__art::after {
  animation: holoSweep .95s var(--ease) both;
}
@keyframes holoSweep {
  from { transform: translateX(-115%); }
  to   { transform: translateX(115%); }
}

/* ── Grilla ───────────────────────────────────────────────────────────────── */
.grid-cards { display: grid; gap: var(--sp-3); grid-template-columns: repeat(3, 1fr); }
@media (min-width: 600px)  { .grid-cards { grid-template-columns: repeat(4, 1fr); gap: var(--sp-4); } }
@media (min-width: 1280px) { .grid-cards { grid-template-columns: repeat(6, 1fr); } }
.grid-modules { display: grid; gap: var(--sp-4); grid-template-columns: 1fr; }
@media (min-width: 600px)  { .grid-modules { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1280px) { .grid-modules { grid-template-columns: repeat(3, 1fr); } }

/* ── Modal ───────────────────────────────────────────── ng-component: DialogComponent */
.modal[open] { display: grid; }
.modal {
  position: fixed; inset: 0; z-index: 90; display: none;
  place-items: center; padding: var(--sp-4);
  background: oklch(8% 0.03 268 / .78); backdrop-filter: blur(6px);
  border: 0;
}
.modal::backdrop { background: transparent; }
.modal__card {
  width: min(520px, 100%); max-height: 88dvh; overflow: auto;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-xl); padding: var(--sp-5);
  box-shadow: var(--shadow-2);
  animation: popIn .3s var(--ease-bounce) both;
}
@keyframes popIn { from { transform: scale(.9) translateY(14px); opacity: 0; } }
.modal__close {
  min-width: 44px; min-height: 44px; border-radius: var(--r-pill);
  border: 1px solid var(--border); background: var(--surface-2); cursor: pointer;
}

/* ── Detalle de figurita ────────────────────── ng-component: CardDetailComponent
   El segundo nivel de zoom: la ilustración a tamaño grande. Se muestra el thumb
   al instante (`is-lowres`, apenas suavizado) y se cambia por la versión en alta
   cuando termina de cargar. Sin red, se queda el thumb y no rompe nada. */
.detail__art {
  margin: 0 0 var(--sp-4); position: relative;
  aspect-ratio: 1; border-radius: var(--r-lg); overflow: hidden;
  background: radial-gradient(120% 90% at 50% 20%, var(--slot), var(--slot-2));
  display: grid; place-items: center;
  border: 1px solid var(--slot-line);
}
.detail__art img {
  width: 82%; height: 82%; object-fit: contain;
  transition: filter .3s var(--ease);
  filter: drop-shadow(0 6px 12px oklch(20% 0.04 258 / .28));
}
.detail__art img.is-lowres { filter: blur(.4px) drop-shadow(0 6px 12px oklch(20% 0.04 258 / .28)); }
.detail__num {
  position: absolute; right: 10px; bottom: 8px;
  font-size: var(--step--2); color: var(--ink-soft);
}
.detail__name { font-size: var(--step-2); margin-bottom: var(--sp-2); }
.detail__desc { color: var(--muted); font-size: var(--step--1); margin-bottom: var(--sp-4); }
.detail__facts {
  display: grid; grid-template-columns: auto 1fr; gap: 6px var(--sp-4);
  margin: 0 0 var(--sp-4); font-size: var(--step--1);
}
.detail__facts dt { color: var(--muted); }
.detail__facts dd { margin: 0; }
.detail__subtitle { font-size: var(--step-0); margin-bottom: var(--sp-3); }
.evos { display: grid; gap: var(--sp-2); }
.evo {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: 6px 10px 6px 6px; border-radius: var(--r-md);
  background: var(--surface-2); border: 1px solid var(--border-soft);
}
.evo[data-goto], .evo[data-locate] { cursor: pointer; }
.evo[data-goto]:hover, .evo[data-locate]:hover { border-color: var(--accent); }
.evo:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }

/* La evolución que todavía no conseguiste. El hueco se dibuja como en la hoja
   —filete punteado y el número— para que se lea "esto te falta" sin explicarlo,
   y el destino va a la derecha porque tocarla no abre una ficha: te lleva ahí. */
.evo--falta .evo__ph {
  background: transparent;
  border: 1px dashed color-mix(in oklab, var(--muted) 60%, transparent);
}
.evo__go {
  margin-left: auto; flex: 0 0 auto; padding-left: var(--sp-3);
  color: var(--accent); font-size: var(--step--2); white-space: nowrap;
}
.evo__go::after { content: " →"; }
.evo img { width: 44px; height: 44px; object-fit: contain; flex: 0 0 44px; }
.evo__ph {
  width: 44px; height: 44px; flex: 0 0 44px; display: grid; place-items: center;
  border-radius: var(--r-sm); background: var(--bg-deep); color: var(--muted); font-size: var(--step--2);
}
.evo__meta { display: grid; }
.evo__meta b { font-family: var(--font-display); font-size: var(--step--1); }
.evo__meta small { color: var(--muted); font-size: var(--step--2); }
/* Píldora de tipo. El color es dato (Fuego naranja, Agua azul: convención de la
   franquicia), así que se sale de la paleta fría a propósito — pero acotado a un
   punto de 8px y un borde teñido, nunca a un relleno que compita con la rareza. */
.tpill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 2px 10px 2px 7px; border-radius: var(--r-pill);
  background: oklch(70% .16 var(--t-hue) / .13);
  border: 1px solid oklch(70% .16 var(--t-hue) / .5);
  font-size: var(--step--1);
}
.tpill i { width: 8px; height: 8px; border-radius: 50%; background: oklch(70% .16 var(--t-hue)); }

.detail__credit { margin-top: var(--sp-4); color: var(--muted); font-size: var(--step--2); }

/* ── Control de zoom ──────────────────────────── ng-component: ZoomControlComponent */
.zoom {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: 6px; border-radius: var(--r-pill);
  background: var(--surface-2); border: 1px solid var(--border-soft);
}
.zoom__btn {
  width: 44px; height: 44px; flex: 0 0 44px; display: grid; place-items: center;
  border-radius: var(--r-pill); border: 1px solid var(--border);
  background: transparent; color: var(--fg); cursor: pointer;
  font-family: var(--font-display); font-size: var(--step-1); line-height: 1;
}
.zoom__btn:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
.zoom__btn:disabled { opacity: .35; cursor: not-allowed; }
.zoom__range { flex: 1 1 110px; min-width: 90px; accent-color: var(--accent); height: 44px; }
.zoom__val { font-family: var(--font-mono); font-size: var(--step--1); color: var(--muted); min-width: 46px; text-align: right; }

/* ── Toast ────────────────────────────────────────────── ng-component: ToastComponent */
.toasts {
  position: fixed; z-index: 95; left: 50%; transform: translateX(-50%);
  bottom: calc(var(--nav-h) + 12px);
  display: grid; gap: 8px; width: min(420px, calc(100% - 24px));
}
@media (min-width: 960px) { .toasts { bottom: 20px; left: auto; right: 20px; transform: none; } }
.toast {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 16px; border-radius: var(--r-pill);
  background: var(--surface-3); border: 1px solid var(--border);
  box-shadow: var(--shadow-2); font-size: var(--step--1); font-weight: 600;
  animation: toastIn .32s var(--ease-bounce) both;
}
.toast[data-type="ok"] { border-color: var(--ok); color: var(--ok); }
.toast[data-type="warn"] { border-color: var(--warn); color: var(--warn); }
@keyframes toastIn { from { transform: translateY(16px); opacity: 0; } }

/* ── Sobres ─────────────────────────────────────────── ng-component: PackCardComponent */
.pack {
  position: relative; aspect-ratio: 3 / 4; border-radius: var(--r-lg);
  border: 1px solid var(--border); overflow: hidden;
  display: grid; place-items: center;
  background: linear-gradient(160deg, var(--surface-3), var(--bg-deep));
}
.pack::before {
  content: ""; position: absolute; inset: -40%;
  background: linear-gradient(115deg, transparent 42%, oklch(100% 0 0 / .22) 50%, transparent 58%);
  animation: shimmer 3.4s var(--ease) infinite;
}
@keyframes shimmer { from { transform: translateX(-60%); } to { transform: translateX(60%); } }
.pack[data-kind="elite"]  { background: linear-gradient(160deg, oklch(40% 0.13 210), var(--bg-deep)); }
.pack[data-kind="prisma"] { background: linear-gradient(160deg, oklch(42% 0.16 305), var(--bg-deep)); }
.pack__label {
  position: relative; z-index: 1; text-align: center;
  font-family: var(--font-display); font-weight: 800; font-size: var(--step-2);
  text-transform: uppercase; letter-spacing: .04em;
}

/* ── Lista de items (repetidas / ofertas) ──────────── ng-component: ListRowComponent */
.rowcard {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3); border-radius: var(--r-md);
  background: var(--surface-2); border: 1px solid var(--border-soft);
  transition: border-color .18s var(--ease);
}
.rowcard:hover { border-color: var(--accent); }
.rowcard__thumb { width: 54px; flex: 0 0 54px; }
.rowcard__thumb .sticker { --slot-fs: 7px; }
.rowcard[aria-selected="true"] { border-color: var(--accent-2); background: oklch(30% 0.06 150 / .3); }

/* ── Chat ──────────────────────────────────────────── ng-component: TradeChatComponent */
.chat { display: grid; gap: var(--sp-3); max-height: 320px; overflow-y: auto; padding-right: 4px; }
.msg { max-width: 82%; padding: 10px 14px; border-radius: var(--r-lg); font-size: var(--step--1); }
.msg--them { background: var(--surface-2); border-bottom-left-radius: 6px; justify-self: start; }
.msg--me { background: var(--accent); color: var(--accent-ink); border-bottom-right-radius: 6px; justify-self: end; }
.chat-form { display: flex; gap: var(--sp-2); }
.input {
  flex: 1; min-height: 46px; padding: 0 var(--sp-4);
  border-radius: var(--r-pill); border: 1px solid var(--border);
  background: var(--bg-deep); color: var(--fg);
}
.input:focus { border-color: var(--accent); outline: none; }

/* ── Estado vacío ──────────────────────────────────────────────────────────── */
.empty { display: grid; gap: var(--sp-3); place-items: center; padding: var(--sp-7) var(--sp-4); text-align: center; color: var(--muted); }
.empty__mark { width: 64px; height: 64px; opacity: .45; }

/* ══ FX · las celebraciones ═══════════════════════════ ng-service: FxService
   Todo vive en un host fijo sobre el resto de la app, sin capturar clics y
   fuera del árbol de accesibilidad: es decoración, y la información ya la dio
   el toast, la cinta o el número de la billetera. */
.fx-host {
  position: fixed; inset: 0; z-index: 90;
  pointer-events: none; overflow: hidden;
}

/* --- monedas volando --------------------------------------------------------
   Tres puntos, no dos: origen, cima del arco y destino. Con una interpolación
   recta la moneda viaja como un misil; el arco es lo que la hace pesar. */
.fx-coin {
  position: absolute; left: 0; top: 0; width: 22px; height: 22px; border-radius: 50%;
  background:
    radial-gradient(circle at 34% 30%, oklch(96% .14 95), oklch(80% .17 88) 58%, oklch(64% .15 80));
  box-shadow: 0 0 12px oklch(88% .17 95 / .6), inset 0 -2px 0 oklch(58% .13 78 / .8);
  animation: fxCoin 780ms cubic-bezier(.4, 0, .5, 1) both;
  will-change: transform, opacity;
}
@keyframes fxCoin {
  0%   { transform: translate(var(--x0), var(--y0)) translate(-50%, -50%) scale(.4); opacity: 0; }
  14%  { opacity: 1; transform: translate(var(--x0), var(--y0)) translate(-50%, -50%) scale(1); }
  55%  { transform: translate(var(--xm), var(--ym)) translate(-50%, -50%) scale(1.1) rotate(180deg); }
  100% { transform: translate(var(--x1), var(--y1)) translate(-50%, -50%) scale(.3) rotate(360deg); opacity: 0; }
}
/* El latido de la billetera: llega la moneda, el número acusa recibo. */
.fx-pulse { animation: fxPulse 380ms var(--ease-bounce); }
@keyframes fxPulse { 0%, 100% { transform: none; } 40% { transform: scale(1.22); } }

/* --- papelitos -------------------------------------------------------------- */
.fx-bit {
  position: absolute; left: 0; top: 0; width: 9px; height: 14px; border-radius: 2px;
  animation-name: fxBit; animation-timing-function: cubic-bezier(.2, .6, .3, 1);
  animation-fill-mode: both; will-change: transform, opacity;
}
@keyframes fxBit {
  0%   { transform: translate(var(--x0), var(--y0)) scale(.5); opacity: 0; }
  12%  { opacity: 1; }
  /* Cae más de lo que sube: sin esa asimetría el papelito flota y no pesa. */
  100% { transform: translate(calc(var(--x0) + var(--dx)), calc(var(--y0) + var(--dy) + 300px))
                    rotate(var(--rot)) scale(.85); opacity: 0; }
}

/* --- campeones -------------------------------------------------------------- */
.fx-champs {
  position: absolute; inset: 0; display: grid; align-content: center; justify-items: center;
  gap: var(--sp-6); padding: var(--sp-5);
  background: radial-gradient(80% 60% at 50% 45%, oklch(20% .08 262 / .82), oklch(10% .04 260 / .94));
  backdrop-filter: blur(4px);
  pointer-events: auto; cursor: pointer;      /* tocar y salir: nadie espera 4 s */
  animation: fxFade 320ms var(--ease) both;
}
@keyframes fxFade { from { opacity: 0; } }
.fx-champs__row { display: flex; gap: clamp(6px, 2.4vw, 22px); align-items: flex-end; justify-content: center; flex-wrap: nowrap; }
.fx-champ {
  margin: 0; display: grid; justify-items: center; gap: 6px;
  width: clamp(70px, 20vw, 172px);
  /* Entran de a una, alternando de qué lado: cuatro llegando juntas se leen
     como una tira, no como cuatro que te vienen a felicitar. */
  animation: fxChamp 620ms var(--ease-bounce) both;
  animation-delay: calc(var(--i) * 170ms);
}
.fx-champ:nth-child(odd)  { --lado: -1; }
.fx-champ:nth-child(even) { --lado: 1; }
.fx-champ img {
  width: 100%; height: auto; display: block;
  filter: drop-shadow(0 10px 26px oklch(8% .04 260 / .8));
}
.fx-champ figcaption {
  font-family: var(--font-display); font-weight: 800; text-transform: uppercase;
  font-size: clamp(9px, 2vw, 14px); letter-spacing: .04em; color: var(--fg);
}
@keyframes fxChamp {
  from { opacity: 0; transform: translate(calc(var(--lado, 1) * 120px), 40px) scale(.6) rotate(calc(var(--lado, 1) * 14deg)); }
  to   { opacity: 1; transform: none; }
}
.fx-champs__msg { display: grid; justify-items: center; gap: 6px; text-align: center;
  animation: fxChamp 620ms var(--ease-bounce) 700ms both; --lado: 0; }
.fx-champs__msg b {
  font-family: var(--font-display); font-weight: 800; line-height: .95;
  font-size: clamp(30px, 8vw, 68px); color: var(--accent-2);
  text-shadow: 0 0 30px oklch(86% .19 132 / .45);
}
.fx-champs__msg span { color: var(--fg); opacity: .85; font-size: var(--step-0); max-width: 34ch; }

.fx-champs[data-quieto] .fx-champ,
.fx-champs[data-quieto] .fx-champs__msg { animation: none; opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  .fx-champs { animation: none; }
}

/* --- lluvia por rareza -------------------------------------------------------
   Cae de arriba, no estalla desde un punto: la diferencia de FORMA es la que la
   distingue del estallido de la hoja completa. Dos cosas distintas se separan
   mejor por su gesto que por su tamaño.
   El dorado es el mismo de las monedas a propósito — en este álbum el oro
   significa valor, y la holo llueve el mismo metal que cobrás. */
.fx-drop {
  position: absolute; left: 0; top: 0; border-radius: 2px;
  animation-name: fxDrop; animation-timing-function: cubic-bezier(.35, .1, .6, 1);
  animation-fill-mode: both; will-change: transform, opacity;
}
.fx-drop--rara {
  background: linear-gradient(150deg, oklch(92% .09 205), oklch(74% .15 210));
  box-shadow: 0 0 7px oklch(80% .15 205 / .5);
}
.fx-drop--holo {
  background: linear-gradient(150deg, oklch(96% .14 95), oklch(80% .17 88) 55%, oklch(64% .15 80));
  box-shadow: 0 0 10px oklch(88% .17 95 / .65);
}
@keyframes fxDrop {
  0%   { transform: translate(var(--x), -8vh) rotate(0deg); opacity: 0; }
  8%   { opacity: 1; }
  85%  { opacity: 1; }
  /* 112vh y no 100: tiene que salir de cuadro, no desvanecerse a mitad de la
     pantalla como si se hubiera arrepentido. */
  100% { transform: translate(calc(var(--x) + var(--drift)), 112vh) rotate(var(--rot)); opacity: 0; }
}

/* ══ Vitrina ══════════════════════════════════ ng-component: TrophyCaseComponent
   Una pieza en exhibición: base de metal, campana de vidrio y chapa grabada.
   Vive acá y no en favoritos.html porque la usan DOS lugares —el estante y el
   modal— y si el vidrio se define dos veces, un día dejan de ser el mismo vidrio.

   El metal sigue la RAREZA: acero para común, acero cian para rara, dorado para
   holo. Así el estante se lee de un vistazo y reusa el vocabulario que el álbum
   ya tiene, en vez de inventar una jerarquía nueva para la misma colección. */
.trofeo {
  --metal-a: oklch(78% .012 250);
  --metal-b: oklch(52% .014 250);
  --metal-c: oklch(38% .016 250);
  --vidrio:  oklch(86% .04 210);
  position: relative; display: grid; justify-items: center;
}
.trofeo[data-rar="rare"] {
  --metal-a: oklch(84% .06 205); --metal-b: oklch(58% .09 205); --metal-c: oklch(40% .07 205);
  --vidrio:  oklch(88% .07 205);
}
.trofeo[data-rar="holo"] {
  --metal-a: oklch(92% .11 92); --metal-b: oklch(70% .15 86); --metal-c: oklch(48% .12 80);
  --vidrio:  oklch(92% .08 95);
}

/* La campana. El vidrio se construye con tres capas: el tinte del cuerpo, un
   brillo especular en diagonal y el filo del borde. Con una sola no hay vidrio,
   hay un rectángulo translúcido. */
.trofeo__campana {
  position: relative; width: 100%; aspect-ratio: 3 / 3.4;
  border-radius: 14px 14px 4px 4px;
  background:
    linear-gradient(155deg, oklch(100% 0 0 / .16) 0%, oklch(100% 0 0 / .03) 38%, transparent 55%),
    linear-gradient(200deg, color-mix(in oklab, var(--vidrio) 16%, transparent), transparent 70%);
  border: 1px solid oklch(100% 0 0 / .22);
  border-bottom: 0;
  box-shadow:
    inset 0 1px 0 oklch(100% 0 0 / .5),          /* filo superior */
    inset -1px 0 0 oklch(100% 0 0 / .14),
    inset 1px 0 0 oklch(100% 0 0 / .14),
    0 10px 30px oklch(8% .04 260 / .5);
  backdrop-filter: blur(1.5px);
  display: grid; place-items: center; overflow: hidden;
}
/* El reflejo que barre el vidrio. Es lo que lo delata como vidrio y no como
   plástico: un plano de luz que cruza y no se mueve con el contenido. */
.trofeo__campana::after {
  content: ""; position: absolute; inset: -30% -10%;
  background: linear-gradient(74deg, transparent 40%, oklch(100% 0 0 / .18) 49%, transparent 57%);
  pointer-events: none;
}
.trofeo__campana img {
  width: 78%; height: 78%; object-fit: contain;
  filter: drop-shadow(0 6px 14px oklch(8% .04 260 / .6));
}

/* La base: tres bandas de metal cepillado. Una sola banda se lee plano; el
   contraste entre el filo claro de arriba y el zócalo oscuro es lo que le da
   volumen sin una sola sombra externa. */
.trofeo__base {
  width: 100%; border-radius: 3px 3px 8px 8px;
  padding: 7px 10px 11px;
  /* El zócalo oscuro va al FONDO, no a media chapa. Con el corte al 62% el
     número quedaba partido justo sobre el borde claro→oscuro y se leía como
     recortado: media cifra en gris claro y media en gris oscuro. El zócalo es
     el canto de la base, no la mitad de la placa. */
  background:
    linear-gradient(180deg, var(--metal-a) 0 2px, var(--metal-b) 2px 86%, var(--metal-c) 86% 100%);
  box-shadow: 0 6px 16px oklch(8% .04 260 / .55), inset 0 1px 0 oklch(100% 0 0 / .55);
  text-align: center;
}
/* La chapa grabada: texto oscuro con un realce claro un pixel abajo. Es la
   ilusión de bajorrelieve más barata que existe y la única que funciona a
   este tamaño. */
.trofeo__chapa {
  display: block; font-family: var(--font-display); font-weight: 800;
  text-transform: uppercase; letter-spacing: .06em;
  font-size: clamp(9px, 2.1cqi, 13px); line-height: 1.15;
  color: oklch(24% .02 250);
  text-shadow: 0 1px 0 oklch(100% 0 0 / .45);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.trofeo__num {
  display: block; font-family: var(--font-mono); font-size: clamp(7px, 1.7cqi, 10px);
  color: oklch(30% .02 250); margin-top: 2px;
  text-shadow: 0 1px 0 oklch(100% 0 0 / .35);   /* mismo grabado que la chapa */
}

/* La ficha de pieza: el objeto manda, el dato se corre al pie. */
.pieza-detalle { container-type: inline-size; display: grid; gap: var(--sp-5); justify-items: center; }
.pieza-detalle .trofeo { width: min(300px, 82%); }
.pieza-detalle .trofeo__campana { aspect-ratio: 3 / 3.2; }
.pieza-detalle .trofeo__chapa { font-size: clamp(14px, 5cqi, 22px); }
.pieza-detalle .trofeo__num { font-size: clamp(10px, 3cqi, 13px); }
.pieza-detalle__tipos { display: flex; gap: var(--sp-2); flex-wrap: wrap; justify-content: center; }

/* ══ Dorso ═══════════════════════════════════════════ ng-component: CardBackComponent
   La cara de atrás de una figurita. Vive acá porque la usan la apertura de
   sobre y el memotest, y una carta dada vuelta tiene que ser LA MISMA carta
   en los dos lados de la app — si cada pantalla dibuja su propio dorso, un día
   dejan de ser el mismo mazo.

   No es un color liso ni una inicial: es el cartón de la tapa del álbum con el
   emblema de la serie. Eso es lo que lo vuelve referente — la carta boca abajo
   sigue diciendo de qué álbum es. */
.dorso {
  --sec-hue: 262;
  position: relative; display: grid; place-items: center; overflow: hidden;
  background:
    repeating-linear-gradient(58deg, oklch(100% 0 0 / .05) 0 2px, transparent 2px 9px),
    radial-gradient(120% 80% at 78% 0%, oklch(58% .17 var(--sec-hue) / .85), transparent 60%),
    linear-gradient(165deg, oklch(38% .15 var(--sec-hue)), oklch(24% .11 var(--sec-hue)));
  color: oklch(100% 0 0 / .55);
}
/* Filete interior: el troquel del dorso impreso. Sin él el cartón se lee como
   un fondo y no como el reverso de algo. */
.dorso::before {
  content: ""; position: absolute; inset: 7%;
  border: 1px solid oklch(100% 0 0 / .22); border-radius: 6px;
}
.dorso__emblema { position: relative; width: 42%; max-width: 92px; }
.dorso__emblema svg { width: 100%; display: block; }
.dorso__serie {
  position: absolute; bottom: 8%; left: 0; right: 0;
  font-family: var(--font-mono); font-size: clamp(7px, 2.4cqi, 11px);
  letter-spacing: .22em; text-transform: uppercase; text-align: center;
  color: oklch(100% 0 0 / .42);
}

/* ══ Chasquido ══════════════════════════════════════ ng-component: SnapOpenComponent
   El arranque de la función. Dos bloques entran —uno de arriba, otro de abajo—,
   se encuentran de golpe en el medio, y al abrirse dejan la primera figurita
   dando la vuelta.

   Toda la fuerza está en el ENCUENTRO: los bloques ACELERAN al final en vez de
   frenar, y en el impacto tiembla la pantalla entera. Si llegaran suave sería un
   telón cerrándose; lo que lo vuelve un chasquido es que lleguen de golpe y algo
   se sacuda.

   Y se abren MÁS LENTO de lo que se cerraron: cerrar es el golpe, abrir es
   mostrar. Al mismo ritmo las dos mitades del gesto se pisarían. */
.chasquido {
  position: absolute; inset: 0; z-index: 30; pointer-events: none;
  animation: chasGolpe 260ms 320ms var(--ease) both;
}
/* Los bloques son el CARTÓN DEL ÁLBUM, el mismo del dorso de las figuritas: dos
   tapas que se cierran sobre la pantalla. En un tono oscuro cualquiera no se
   leen como objetos —se confunden con el fondo y sólo se ve la juntura brillar,
   que es un efecto de luz, no dos cosas chocando—. Con el cartón se ve QUÉ se
   está cerrando. */
.chasquido__bloque {
  position: absolute; left: 0; right: 0; height: 50%;
  background:
    repeating-linear-gradient(58deg, oklch(100% 0 0 / .045) 0 2px, transparent 2px 9px),
    linear-gradient(var(--dir, 180deg), oklch(40% .15 262), oklch(22% .10 262));
  box-shadow: 0 0 46px oklch(8% .04 262 / .85);
}
/* Filo de luz sobre el canto: el grosor de la tapa. Sin él el bloque es una
   mancha plana y el choque no tiene contra qué pegar. */
.chasquido__bloque::before {
  content: ""; position: absolute; left: 0; right: 0; height: 2px;
  background: oklch(100% 0 0 / .3);
}
.chasquido__bloque[data-lado="arriba"] {
  top: 0; --dir: 0deg;
  animation: chasCierraArriba 320ms cubic-bezier(.55, 0, .95, 1) both,
             chasAbreArriba 460ms 560ms cubic-bezier(.35, 0, .2, 1) forwards;
}
.chasquido__bloque[data-lado="arriba"]::before { bottom: 0; }
.chasquido__bloque[data-lado="abajo"] {
  bottom: 0; --dir: 180deg;
  animation: chasCierraAbajo 320ms cubic-bezier(.55, 0, .95, 1) both,
             chasAbreAbajo 460ms 560ms cubic-bezier(.35, 0, .2, 1) forwards;
}
.chasquido__bloque[data-lado="abajo"]::before { top: 0; }

@keyframes chasCierraArriba { from { transform: translateY(-100%); } }
@keyframes chasCierraAbajo  { from { transform: translateY(100%); } }
@keyframes chasAbreArriba   { to { transform: translateY(-100%); } }
@keyframes chasAbreAbajo    { to { transform: translateY(100%); } }
@keyframes chasGolpe {
  0%, 100% { transform: none; }
  18% { transform: translate(-5px, 4px) rotate(-.5deg); }
  44% { transform: translate(4px, -3px) rotate(.4deg); }
  72% { transform: translate(-2px, 1px); }
}
/* Sin movimiento no hay chasquido que valga: la pantalla no se tapa, la figurita
   aparece y listo. El JS ni monta los bloques. */
@media (prefers-reduced-motion: reduce) {
  .chasquido, .chasquido__bloque { animation: none; }
}
