/*
  Components.

  Everything here is ours. The component library supplies behaviour that is hard to
  get right — focus traps, ARIA wiring, keyboard handling — and this file supplies
  the identity, because a game site that looks like an admin dashboard has already
  lost the argument it is trying to make.
*/

/* ── Surfaces ─────────────────────────────────────────────────────────────── */

/*
  O card É o painel do client.

  Creme, moldura marinho de quatro pixels, um anel claro por dentro e a sombra dura
  embaixo — a mesma peça da tela de login. Era um retângulo branco com fio azul,
  correto e idêntico ao de qualquer painel administrativo; trocar AQUI muda a leitura
  de todas as páginas dos quatro aplicativos de uma vez, que era o argumento inteiro.

  As cores de texto são redefinidas DENTRO dele: o muted azulado do site sobre creme
  parecia sujeira. Tudo o que estiver dentro de um painel herda a tinta do jogo sem
  ninguém precisar lembrar de trocar classe.
*/
.card {
  --text:        var(--kit-ink);
  --text-muted:  var(--kit-muted);
  --text-subtle: var(--kit-subtle);
  --line:        var(--kit-line);
  --line-strong: color-mix(in oklab, var(--kit-frame) 45%, var(--kit-line));
  --bg-inset:    var(--kit-cream-deep);
  --bg-overlay:  color-mix(in oklab, var(--kit-cream-deep) 60%, var(--kit-cream));
  --bg-raised:   var(--kit-cream);

  background: var(--kit-cream);
  color: var(--kit-ink);
  border: 4px solid var(--kit-frame);
  border-radius: var(--kit-radius);
  padding: var(--space-5);
  container-type: inline-size;
  box-shadow: inset 0 0 0 2px var(--kit-inner), var(--kit-drop);
}

/*
  Ouro significa VALOR, e só isso.

  Ranking de topo, plano premium, pacote de apoio. É a convenção que todo MMORPG usa e
  que o olho já traz aprendida — dourado numa lista quer dizer "isto vale mais". Se
  virar decoração em seis lugares, para de significar em todos.
*/
.card-prized {
  border-color: var(--accent-600);
  box-shadow:
    inset 0 0 0 2px color-mix(in oklab, var(--accent-400) 55%, var(--kit-ring)),
    0 4px 0 color-mix(in oklab, var(--accent-600) 70%, black);
}

.card-tight { padding: var(--space-4); }

.card-interactive {
  transition: transform var(--dur) var(--ease-out),
              box-shadow var(--dur) var(--ease-out);
}
.card-interactive:hover {
  transform: translateY(-2px);
  box-shadow: inset 0 0 0 2px var(--kit-inner), 0 6px 0 var(--kit-lip);
}
@media (prefers-reduced-motion: reduce) {
  .card-interactive:hover { transform: none; }
}
/* The whole card is the target, not just the title. Fitts's law: a 300px target is
   hit faster and more reliably than a 90px one, and the surrounding text was never
   doing anything else. */
.card-link { position: relative; }
.card-link a::after { content: ""; position: absolute; inset: 0; border-radius: inherit; }

/* O poço DENTRO do painel: a lista, a tabela, o bloco de informação secundária. Um
   tom abaixo do creme, borda clara — o mesmo poço onde o client põe as linhas. */
.panel {
  background: var(--bg-inset);
  border: 2px solid var(--line);
  border-radius: var(--radius-sm);
  padding: var(--space-4);
}

/* ── Buttons ──────────────────────────────────────────────────────────────── */

/*
  O botão.

  Interface de jogo tem botão que parece APERTÁVEL: uma aresta grossa embaixo, que
  some quando se pressiona. É o mesmo truque em todo jogo desde os anos 90, e é o que
  separa "controle de formulário" de "coisa para apertar".

  O vermelho e o creme são os do client — ENTRAR e "Criar nova conta" da tela de
  login —, para que a pessoa que acabou de ver o site reconheça o jogo ao abri-lo.
*/
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-5);
  /* Pílula, como "ENTRAR" e "Criar nova conta" no client. */
  border-radius: var(--radius-full);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--text-base);
  letter-spacing: -0.01em;
  text-decoration: none;
  white-space: nowrap;
  border: 1px solid transparent;
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out),
              filter var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out);
  /* 44px is the smallest reliable touch target; anything under it produces
     mis-taps that read to the user as the site being broken. */
  min-height: 44px;
}
.btn:active { transform: translateY(1px); }
.btn:disabled, .btn[aria-disabled="true"] { opacity: 0.5; cursor: not-allowed; transform: none; }

/* A ação. Caixa alta como o ENTRAR do client: é o único botão da tela que pede que se
   faça algo agora, e ele precisa ganhar do resto sem gritar em cor nova. */
.btn-primary {
  background: linear-gradient(to bottom, var(--kit-red-top), var(--kit-red-bottom));
  color: var(--text-on-brand);
  border: 0;
  font-weight: 800;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  box-shadow:
    inset 0 0 0 2px var(--kit-red-edge),
    inset 0 3px 0 rgb(255 255 255 / 0.22),
    0 4px 0 var(--kit-red-edge);
}
.btn-primary:hover:not(:disabled) { color: var(--text-on-brand); filter: brightness(1.07); }

/* O secundário é o "Criar nova conta" do client: creme com moldura marinho. */
.btn-secondary {
  background: var(--kit-cream);
  color: var(--kit-ink);
  border: 0;
  box-shadow: inset 0 0 0 3px var(--kit-frame), 0 4px 0 var(--kit-frame-deep);
}
.btn-secondary:hover:not(:disabled) { color: var(--kit-ink); background: #fffdf6; }

/* A prensada. O botão afunda os 3px da própria aresta — o retorno tátil que confirma
   o clique antes de a página responder. */
.btn-primary:active:not(:disabled) {
  transform: translateY(3px);
  box-shadow: inset 0 0 0 2px var(--kit-red-edge), 0 1px 0 var(--kit-red-edge);
}
.btn-secondary:active:not(:disabled) {
  transform: translateY(3px);
  box-shadow: inset 0 0 0 3px var(--kit-frame), 0 1px 0 var(--kit-frame-deep);
}
.btn-gold:active:not(:disabled) {
  transform: translateY(3px);
  box-shadow: 0 1px 0 0 var(--accent-600);
}

@media (prefers-reduced-motion: reduce) {
  .btn-primary:active, .btn-secondary:active, .btn-gold:active { transform: none; }
}

/*
  O botão de OURO. Apoiar, premium, recompensa — nunca ação comum.
  Ouro em seis lugares deixa de ser ouro e vira amarelo.
*/
.btn-gold {
  background: linear-gradient(to bottom, var(--accent-400), var(--accent-500));
  color: oklch(0.22 0.04 60);
  border-color: var(--accent-600);
  box-shadow: var(--press) var(--accent-600), var(--panel-sheen);
}
.btn-gold:hover { filter: brightness(1.08); }

.btn-accent {
  background: var(--accent-500);
  color: oklch(0.18 0.02 60);
}
.btn-accent:hover:not(:disabled) { background: var(--accent-400); }

.btn-ghost { color: var(--text-muted); }
.btn-ghost:hover:not(:disabled) { background: var(--bg-overlay); color: var(--text); }

.btn-danger { background: var(--danger-600); color: oklch(0.99 0 0); }
.btn-danger:hover:not(:disabled) { background: var(--danger-500); }

.btn-lg { padding: var(--space-3) var(--space-6); font-size: 0.9375rem; min-height: 48px; }
.btn-sm { padding: var(--space-2) var(--space-3); font-size: var(--text-xs); min-height: 36px; }
.btn-block { width: 100%; }

/* Esperando: a Pokébola gira no lugar do rótulo, que continua lá para o leitor de
   tela e para a largura do botão não pular. */
.btn.is-busy { position: relative; color: transparent !important; pointer-events: none; }
.btn.is-busy::after {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  inline-size: 22px;
  block-size: 22px;
  background: var(--kit-pokeball) center / contain no-repeat;
  animation: pokeball-spin 0.9s linear infinite;
}

/* ── Badges & pills ───────────────────────────────────────────────────────── */

.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 0.15em 0.6em;
  border-radius: var(--radius-full);
  font-size: var(--text-xs);
  font-weight: 600;
  line-height: 1.6;
  border: 1px solid transparent;
}
.badge-brand   { background: oklch(0.42 0.06 265 / 0.12); color: var(--kit-frame); border-color: oklch(0.42 0.06 265 / 0.35); }
.badge-ok      { background: oklch(0.72 0.17 152 / 0.16); color: var(--ok-500); border-color: oklch(0.72 0.17 152 / 0.35); }
.badge-warn    { background: oklch(0.79 0.16 82 / 0.16);  color: var(--warn-500); border-color: oklch(0.79 0.16 82 / 0.35); }
.badge-danger  { background: oklch(0.63 0.21 27 / 0.16);  color: var(--danger-500); border-color: oklch(0.63 0.21 27 / 0.35); }
.badge-neutral { background: var(--bg-overlay); color: var(--text-muted); border-color: var(--line); }

/* ── Live status dot ──────────────────────────────────────────────────────── */

.status-dot {
  width: 8px; height: 8px;
  border-radius: 50%;
  flex: none;
  background: var(--text-subtle);
}
.status-dot.is-online {
  background: var(--ok-500);
  box-shadow: 0 0 0 3px oklch(0.72 0.17 152 / 0.2);
  animation: pulse 2.4s var(--ease-in-out) infinite;
}
.status-dot.is-degraded { background: var(--warn-500); box-shadow: 0 0 0 3px oklch(0.79 0.16 82 / 0.2); }
.status-dot.is-offline  { background: var(--danger-500); box-shadow: 0 0 0 3px oklch(0.63 0.21 27 / 0.2); }

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.45; }
}

/* ── Forms ────────────────────────────────────────────────────────────────── */

.field { display: flex; flex-direction: column; gap: var(--space-2); }

.field label {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text);
}

.field .hint { font-size: var(--text-xs); color: var(--text-subtle); }

.input, .textarea, .select {
  width: 100%;
  padding: var(--space-3) var(--space-4);
  /* O campo do client: branco, moldura marinho, cantos de 10px. Branco e não o poço
     creme, porque é onde se ESCREVE — o olho precisa achar o campo vazio sem ler. */
  background: #fff;
  border: 2px solid color-mix(in oklab, var(--kit-frame) 70%, white);
  border-radius: 10px;
  color: var(--kit-ink);
  min-height: 44px;
  transition: border-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}
.input::placeholder, .textarea::placeholder { color: var(--kit-subtle); }
.input:hover, .textarea:hover, .select:hover { border-color: var(--kit-frame); }
.input:focus, .textarea:focus, .select:focus {
  border-color: var(--kit-frame);
  box-shadow: 0 0 0 3px oklch(0.70 0.14 235 / 0.45);
  outline: none;
}
.textarea { min-height: 120px; resize: vertical; line-height: var(--leading-body); }

/* Invalid state is never colour alone — the message below it carries the meaning. */
.input[aria-invalid="true"], .textarea[aria-invalid="true"] { border-color: var(--danger-500); }

.field-error {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  font-size: var(--text-sm);
  color: var(--danger-500);
}

/* Password strength: four discrete segments rather than a continuous bar. A bar
   invites "how do I fill it"; segments read as a rating. */
.strength { display: flex; gap: 4px; }
.strength span {
  height: 4px;
  flex: 1;
  border-radius: var(--radius-full);
  background: var(--line);
  transition: background-color var(--dur) var(--ease-out);
}
.strength[data-score="1"] span:nth-child(-n+1) { background: var(--danger-500); }
.strength[data-score="2"] span:nth-child(-n+2) { background: var(--warn-500); }
.strength[data-score="3"] span:nth-child(-n+3) { background: var(--ok-600); }
.strength[data-score="4"] span                 { background: var(--ok-500); }

/* ── Tables ───────────────────────────────────────────────────────────────── */

.table-wrap {
  overflow-x: auto;
  border: 4px solid var(--kit-frame);
  border-radius: var(--kit-radius);
  background: var(--kit-cream);
  color: var(--kit-ink);
  box-shadow: inset 0 0 0 2px var(--kit-inner), var(--kit-drop);
}

table.data {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-sm);
}
table.data th {
  text-align: left;
  padding: var(--space-3) var(--space-4);
  background: var(--kit-cream-deep);
  color: var(--kit-muted);
  font-weight: 600;
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  white-space: nowrap;
  position: sticky;
  top: 0;
  z-index: 1;
}
table.data td {
  padding: var(--space-3) var(--space-4);
  border-top: 1px solid var(--kit-line);
  vertical-align: middle;
}
table.data tbody tr { transition: background-color var(--dur-fast) var(--ease-out); }
table.data tbody tr:hover { background: color-mix(in oklab, var(--kit-cream-deep) 55%, var(--kit-cream)); }

/* The top three get an accent because a leaderboard whose first row looks like its
   fortieth is not a leaderboard. */
table.data tbody tr.rank-1 td { background: oklch(0.78 0.16 68 / 0.08); }
table.data tbody tr.rank-2 td { background: oklch(0.75 0.012 285 / 0.06); }
table.data tbody tr.rank-3 td { background: oklch(0.63 0.17 58 / 0.06); }

.rank-badge {
  display: inline-grid;
  place-items: center;
  width: 28px; height: 28px;
  border-radius: var(--radius-full);
  font-weight: 700;
  font-size: var(--text-xs);
  background: var(--kit-cream-deep);
  color: var(--kit-frame);
}
.rank-badge.gold   { background: oklch(0.88 0.14 88); color: oklch(0.34 0.09 70); }
.rank-badge.silver { background: oklch(0.86 0.012 250); color: oklch(0.38 0.02 250); }
.rank-badge.bronze { background: oklch(0.78 0.11 55); color: oklch(0.32 0.08 45); }

.delta { font-size: var(--text-xs); font-variant-numeric: tabular-nums; }
.delta.up   { color: var(--ok-500); }
.delta.down { color: var(--danger-500); }
.delta.flat { color: var(--text-subtle); }

/* ── Empty / loading / error states ───────────────────────────────────────── */

.state {
  display: grid;
  place-items: center;
  gap: var(--space-3);
  padding: var(--space-8) var(--space-5);
  text-align: center;
  color: var(--text-muted);
}
.state .state-icon { font-size: 2.5rem; opacity: 0.6; line-height: 1; }

/* O estado vazio com um Pokémon: o "nada aqui" do jogo é uma criatura esperando, não
   um ícone cinza. Pixel art escala por vizinho mais próximo — borrada, parece erro. */
.state .state-art {
  inline-size: 96px;
  block-size: auto;
  image-rendering: pixelated;
  filter: drop-shadow(0 3px 0 rgb(30 40 70 / 0.18));
}
.state h3 { color: var(--text); }
.state p { max-width: 46ch; }

/* Skeletons match the shape of what is coming, so the page does not reflow when it
   arrives. A spinner cannot do that. */
.skeleton {
  background: linear-gradient(
    90deg,
    var(--bg-overlay) 25%,
    var(--bg-raised) 37%,
    var(--bg-overlay) 63%
  );
  background-size: 400% 100%;
  animation: shimmer 1.4s ease-in-out infinite;
  border-radius: var(--radius-sm);
}
@keyframes shimmer {
  0%   { background-position: 100% 50%; }
  100% { background-position: 0 50%; }
}
.skeleton-line { height: 1em; margin-block: 0.35em; }
.skeleton-line.short { width: 40%; }
.skeleton-line.medium { width: 70%; }

/* ── Progress ─────────────────────────────────────────────────────────────── */

.progress {
  height: 8px;
  border-radius: var(--radius-full);
  background: var(--bg-inset);
  overflow: hidden;
  border: 1px solid var(--line);
}
.progress > i {
  display: block;
  height: 100%;
  border-radius: inherit;
  background: linear-gradient(90deg, var(--brand-500), var(--brand-300));
  transition: width var(--dur-slow) var(--ease-out);
}

/* ── Tabs ─────────────────────────────────────────────────────────────────── */

.tabs {
  display: flex;
  gap: var(--space-1);
  border-bottom: 1px solid var(--line);
  overflow-x: auto;
  scrollbar-width: none;
}
.tabs::-webkit-scrollbar { display: none; }

.tab {
  padding: var(--space-3) var(--space-4);
  color: var(--text-muted);
  font-weight: 600;
  font-size: var(--text-sm);
  border-bottom: 2px solid transparent;
  white-space: nowrap;
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.tab:hover { color: var(--text); }
.tab[aria-selected="true"], .tab.active { color: var(--kit-frame); border-bottom-color: var(--kit-red-bottom); }

/* ── Breadcrumbs ──────────────────────────────────────────────────────────── */

.breadcrumbs { font-size: var(--text-sm); color: var(--text-subtle); }
.breadcrumbs ol { display: flex; flex-wrap: wrap; gap: var(--space-2); list-style: none; padding: 0; }
.breadcrumbs li { display: flex; align-items: center; gap: var(--space-2); }
.breadcrumbs li + li::before { content: "/"; color: var(--text-subtle); }
.breadcrumbs a { color: var(--text-muted); text-decoration: none; }
.breadcrumbs a:hover { color: var(--brand-300); text-decoration: underline; }
.breadcrumbs [aria-current="page"] { color: var(--text); }

/* ── Toasts ───────────────────────────────────────────────────────────────── */

.toast-region {
  position: fixed;
  inset-block-end: var(--space-4);
  inset-inline-end: var(--space-4);
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  max-width: min(420px, calc(100vw - var(--space-6)));
  pointer-events: none;
}
.toast {
  pointer-events: auto;
  display: flex;
  gap: var(--space-3);
  padding: var(--space-4);
  background: var(--bg-overlay);
  border: 1px solid var(--line-strong);
  border-inline-start: 3px solid var(--brand-400);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  animation: toast-in var(--dur) var(--ease-out);
}
.toast.ok      { border-inline-start-color: var(--ok-500); }
.toast.warn    { border-inline-start-color: var(--warn-500); }
.toast.danger  { border-inline-start-color: var(--danger-500); }
.toast strong  { display: block; font-size: var(--text-sm); }
.toast p       { font-size: var(--text-sm); color: var(--text-muted); }

@keyframes toast-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}

/* ── Ticker ───────────────────────────────────────────────────────────────── */

.ticker { display: flex; flex-direction: column; gap: var(--space-1); }
.ticker-item {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  animation: ticker-in var(--dur-slow) var(--ease-out);
}
.ticker-item:nth-child(odd) { background: oklch(1 0 0 / 0.02); }
.ticker-item time { color: var(--text-subtle); font-size: var(--text-xs); margin-inline-start: auto; white-space: nowrap; }

@keyframes ticker-in {
  from { opacity: 0; transform: translateX(-6px); }
  to   { opacity: 1; transform: none; }
}


/* ── Primary navigation ───────────────────────────────────────────────────────
   Global rather than scoped to NavItem.razor, and that is forced: CSS isolation
   stamps its attribute only on elements a component renders ITSELF. NavItem renders
   <NavLink>, a child component, so the <a> that reaches the DOM carries no scope and
   a NavItem.razor.css would never match it.

   The interaction is built from three layers that arrive at different speeds — a
   fill, a rising underline and a lift. One property changing on hover reads as a
   link; several, staggered, read as a control. */

.nav-item {
  position: relative;
  display: inline-flex;
  align-items: center;
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius);
  color: var(--text-muted);
  text-decoration: none;
  font-size: var(--text-sm);
  font-weight: 600;
  letter-spacing: 0.02em;
  white-space: nowrap;
  isolation: isolate;
  transition:
    color var(--dur-fast) var(--ease-out),
    transform var(--dur) var(--ease-out);
}

/* The fill grows from the centre. Behind the text via z-index so it never washes
   the label out mid-transition. */
.nav-item::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  background: linear-gradient(180deg, oklch(0.42 0.06 265 / 0.22), oklch(0.42 0.06 265 / 0.08));
  border: 1px solid oklch(0.42 0.06 265 / 0.28);
  opacity: 0;
  transform: scale(0.86);
  transition: opacity var(--dur-fast) var(--ease-out), transform var(--dur) var(--ease-out);
}

/* The underline sweeps in from the left, and is what the current page keeps. */
.nav-item::after {
  content: "";
  position: absolute;
  inset-inline: var(--space-4);
  inset-block-end: 4px;
  height: 2px;
  border-radius: var(--radius-full);
  background: linear-gradient(90deg, var(--brand-300), var(--accent-400));
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--dur) var(--ease-out);
}

.nav-item:hover,
.nav-item:focus-visible {
  color: var(--text);
  text-decoration: none;
  transform: translateY(-1px);
}
.nav-item:hover::before,
.nav-item:focus-visible::before { opacity: 1; transform: scale(1); }
.nav-item:hover::after,
.nav-item:focus-visible::after { transform: scaleX(1); }

.nav-item:active { transform: translateY(0); }

/* Current page: colour, a permanent underline, and a marker dot. Never colour
   alone — roughly one man in twelve cannot separate these two hues. */
.nav-item.is-current { color: var(--kit-frame); }
.nav-item.is-current::after { transform: scaleX(1); }
.nav-item.is-current::before { opacity: 0.55; transform: scale(1); }

/* ── Chips (filters) ──────────────────────────────────────────────────────────
   Links, not buttons: a filter is part of the address, so it must survive a
   refresh and be shareable. */

.chip {
  display: inline-flex;
  align-items: center;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-full);
  border: 1px solid var(--line);
  background: var(--bg-inset);
  color: var(--text-muted);
  font-size: var(--text-sm);
  text-decoration: none;
  min-height: 36px;
  transition: border-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out);
}
.chip:hover { border-color: var(--brand-400); color: var(--text); text-decoration: none; }
.chip.is-on {
  border-color: oklch(0.42 0.06 265 / 0.5);
  background: oklch(0.42 0.06 265 / 0.16);
  color: var(--kit-frame);
}

/* ── Reveal on scroll ─────────────────────────────────────────────────────────
   Sections rise slightly as they enter the viewport. Applied through an
   IntersectionObserver rather than on load, so content below the fold animates when
   it is actually seen instead of all at once behind the hero.

   The animation is a progressive enhancement in the strict sense: without the
   observer the element simply stays visible, and the reduced-motion query below
   disables it outright. */

.reveal {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 520ms var(--ease-out), transform 520ms var(--ease-out);
  will-change: opacity, transform;
}
.reveal.is-in { opacity: 1; transform: none; }

/* Staggered children: each card arrives a beat after the one before it, which reads
   as the page assembling itself rather than blinking into place. */
.reveal.is-in > * { animation: rise 520ms var(--ease-out) backwards; }
.reveal.is-in > *:nth-child(2) { animation-delay: 70ms; }
.reveal.is-in > *:nth-child(3) { animation-delay: 140ms; }
.reveal.is-in > *:nth-child(4) { animation-delay: 210ms; }

@keyframes rise {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1; transform: none; transition: none; }
  .reveal.is-in > * { animation: none; }
}

/* ── Alerts ───────────────────────────────────────────────────────────────────
   An inline banner tied to the thing that failed, not a toast. A toast for a form
   error disappears while the reader is still looking at the form. */

.alert {
  display: flex;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius);
  border: 1px solid var(--line);
  border-inline-start-width: 3px;
  font-size: var(--text-sm);
  background: var(--bg-inset);
}
.alert-danger { border-inline-start-color: var(--danger-500); color: var(--danger-500); background: oklch(0.63 0.21 27 / 0.08); }
.alert-ok     { border-inline-start-color: var(--ok-500); color: var(--ok-500); background: oklch(0.72 0.17 152 / 0.08); }
.alert-warn   { border-inline-start-color: var(--warn-500); color: var(--warn-500); background: oklch(0.79 0.16 82 / 0.08); }
.alert-info   { border-inline-start-color: var(--info-500); color: var(--text-muted); }

/* ── Wizard steps ─────────────────────────────────────────────────────────── */

.steps { display: flex; gap: var(--space-2); align-items: center; flex-wrap: wrap; }
.step {
  display: flex; align-items: center; gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-full);
  font-size: var(--text-sm);
  color: var(--text-subtle);
  border: 1px solid var(--line);
}
.step-num {
  display: grid; place-items: center;
  width: 22px; height: 22px;
  border-radius: 50%;
  background: var(--bg-overlay);
  font-size: var(--text-xs); font-weight: 700;
  flex: none;
}
.step.is-done { color: var(--ok-500); border-color: oklch(0.72 0.17 152 / 0.35); }
.step.is-done .step-num { background: var(--ok-600); color: var(--text); }
.step.is-current { color: var(--kit-frame); border-color: oklch(0.42 0.06 265 / 0.5); background: oklch(0.42 0.06 265 / 0.12); }
.step.is-current .step-num { background: var(--brand-500); color: var(--text-on-brand); }

/* ── Choice cards (radio group) ───────────────────────────────────────────────
   A real radio input, visually hidden, with the card as its label. Keeps arrow-key
   navigation, the required validation and the announced group semantics that a div
   with a click handler throws away. */

.choices { display: grid; gap: var(--space-3); grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr)); }

.choice { position: relative; display: block; cursor: pointer; }
.choice input { position: absolute; opacity: 0; width: 0; height: 0; }
.choice-body {
  height: 100%;
  padding: var(--space-4);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--bg-raised);
  transition: border-color var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out);
}
.choice:hover .choice-body { border-color: var(--line-strong); transform: translateY(-2px); }
.choice input:checked + .choice-body {
  border-color: var(--brand-400);
  background: linear-gradient(140deg, oklch(0.42 0.06 265 / 0.16), transparent 60%), var(--bg-raised);
  box-shadow: 0 0 0 1px var(--brand-400);
}
.choice input:focus-visible + .choice-body { outline: 2px solid var(--brand-400); outline-offset: 2px; }
.choice-body h3 { font-size: var(--text-base); margin-block-end: var(--space-1); }
.choice-body p { font-size: var(--text-sm); color: var(--text-muted); }

/* ── Credential pages ─────────────────────────────────────────────────────────
   Global rather than scoped, because five pages share them and CSS isolation would
   need the same file copied into each one. */

.auth-form h1 { font-size: var(--text-2xl); }
.auth-lede { color: var(--text-muted); font-size: var(--text-sm); margin-block: var(--space-2) var(--space-5); }
.auth-fields { margin-block-start: var(--space-4); }

/* "Esqueci" sits on the password label's own line: it is only ever wanted at the
   moment the field is being filled, and putting it there costs no vertical space. */
.field-link { font-size: var(--text-xs); color: var(--text-muted); text-decoration: none; }
.field-link:hover { color: var(--brand-300); text-decoration: underline; }

.auth-switch { text-align: center; font-size: var(--text-sm); color: var(--text-muted); margin-block-start: var(--space-5); }

.auth-stat {
  display: inline-flex; align-items: center; gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-full);
  border: 1px solid oklch(0.72 0.17 152 / 0.35);
  background: oklch(1 0 0 / 0.7);
  backdrop-filter: blur(8px);
  font-size: var(--text-sm);
}

.auth-benefits { display: flex; flex-direction: column; gap: var(--space-3); list-style: none; padding: 0; }
.auth-benefits li { display: flex; gap: var(--space-3); align-items: flex-start; }
.auth-benefits .tick { color: var(--ok-500); flex: none; }

/* ══ RITMO DA PÁGINA ═════════════════════════════════════════════════════════════

   O que a captura mostrava: uma folha preta única, do topo ao rodapé, com um botão
   vermelho no meio. Isso é página de produto, não mundo.

   Uma página de jogo respira em BANDAS — o olho desce e a cor troca, e cada troca diz
   "aqui começa outro assunto". Custa uma classe por seção. */

.band       { background: var(--bg-band); border-block: 2px solid var(--line); }
.band-warm  { background: var(--bg-band-warm); border-block: 2px solid var(--line); }
.band-inset { background: var(--bg-inset); border-block: 2px solid var(--line); }

/* ── Títulos com voz ────────────────────────────────────────────────────────────
   Display arredondada e pesada. A sans fina de 650 era metade do ar corporativo. */

h1, h2, h3 {
    font-family: var(--font-display);
    font-weight: 800;
    letter-spacing: -0.025em;
}

h1 { font-weight: 850; }

/* ── Estatísticas: números de jogo, não de painel ──────────────────────────────
   A faixa mostrava ONLINE AGORA / RECORDE / DISPONIBILIDADE / LATÊNCIA em caixa alta
   minúscula — a linguagem de um quadro de SRE. Aqui o número manda, grande e redondo,
   e o rótulo vem embaixo em tamanho de gente. */

.stat-row .stat, .stat-card {
    text-align: center;
    border: 2px solid var(--line);
    border-radius: var(--radius-lg);
    background: var(--bg-raised);
    box-shadow: var(--panel-sheen), var(--panel-edge);
    padding: var(--space-4);
}

.stat-row .stat strong, .stat-card strong, .stat-card dd {
    font-family: var(--font-display);
    font-weight: 850;
    font-size: var(--text-3xl);
    line-height: 1;
    display: block;
}

/* ── Motivo de Pokébola ────────────────────────────────────────────────────────
   Marca d'água atrás de uma seção. Desenhada em CSS: nada para baixar, nada para
   faltar, e acompanha o tema sozinha. Discreta de propósito — decoração que compete
   com o texto vira ruído. */

.pokeball-motif {
    position: relative;
    overflow: hidden;
    isolation: isolate;
}

.pokeball-motif::before {
    content: "";
    position: absolute;
    z-index: -1;
    inset-block-start: -12%;
    inset-inline-end: -6%;
    inline-size: min(34rem, 45%);
    aspect-ratio: 1;
    border-radius: var(--radius-full);
    background:
        linear-gradient(to bottom,
            var(--brand-400) 0 46%,
            var(--text) 46% 54%,
            oklch(1 0 0) 54% 100%);
    opacity: 0.10;
    pointer-events: none;
}

/* ── Faixa de tipos ────────────────────────────────────────────────────────────
   As dezoito cores existem como token; aqui elas viram uma régua fina que assina a
   página como "isto é Pokémon" sem uma linha de texto. */

.type-rule {
    block-size: 4px;
    border: 0;
    margin: 0;
    background: linear-gradient(to right,
        var(--type-grass), var(--type-fire), var(--type-water), var(--type-electric),
        var(--type-psychic), var(--type-dragon), var(--type-fairy), var(--type-ice));
}

/* ══ DIÁLOGO ═════════════════════════════════════════════════════════════════════

   ── Por que ele existe ─────────────────────────────────────────────────────────
   Não existia. A tela de personagens montou um usando as classes do CONSOLE — que
   vive noutro aplicativo, com outro CSS — e o resultado foi o pior possível: sem
   `position: fixed`, o "diálogo" virou conteúdo comum, empilhado abaixo da lista,
   fora do container, com os campos colados na borda da janela.

   O defeito é instrutivo: marcação de sobreposição SEM o CSS de sobreposição não
   quebra visivelmente — ela simplesmente vira parte da página, e parece que alguém
   esqueceu de estilizar. Por isso o par (backdrop + caixa) mora aqui, no arquivo que
   o sync-design leva para os quatro.

   ── Opaco, e centrado ──────────────────────────────────────────────────────────
   Fundo escurecido para tirar a página da frente, caixa opaca por cima. Translúcida
   sobre uma lista deixa as duas metade legíveis — e é dentro desta caixa que se
   confirma remover um personagem. */

.dialog-backdrop {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: grid;
  place-items: center;
  padding: var(--space-4);
  background: oklch(0.2 0.03 250 / 0.45);
  /* Desfoque só no fundo: dá profundidade sem cobrar nitidez do que se vai ler. */
  backdrop-filter: blur(3px);
  animation: dialog-in var(--dur-fast, 160ms) var(--ease-out, ease-out);
}

.dialog {
  inline-size: min(34rem, 100%);
  /* Teto de altura com rolagem PRÓPRIA: num formulário longo em tela baixa, sem isto
     o botão de confirmar fica abaixo da dobra e a página inteira rola por baixo do
     fundo escurecido. */
  max-block-size: min(85vh, 44rem);
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-5);
  border-radius: var(--radius-lg);
  border: 1px solid var(--line);
  background: var(--bg-raised);
  box-shadow: 0 1.5rem 3rem oklch(0.2 0.03 250 / 0.28);
  animation: dialog-rise var(--dur-fast, 160ms) var(--ease-out, ease-out);
}

.dialog h2 { margin: 0; }
.dialog .hint { margin: 0; }

.dialog-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-block-start: var(--space-2);
}

/* Empurra o que vier depois para a direita: é onde fica confirmar, e a ação
   destrutiva fica sozinha na esquerda, longe dela. */
.dialog-actions .spacer { flex: 1; }

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

@keyframes dialog-rise {
  from { opacity: 0; transform: translateY(0.75rem) scale(0.98); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .dialog-backdrop, .dialog { animation: none; }
}

/* ── Exigências de senha ─────────────────────────────────────────────────────────
 *
 * A lista fica visível o tempo todo, não só depois de errar. Uma regra que só
 * aparece como mensagem de erro é uma regra que a pessoa descobre sendo recusada —
 * e a reação a isso é tentar de novo no escuro, não escolher melhor.
 *
 * Sem script ela continua legível: os itens nascem no estado "não cumprido", que é
 * o correto para um campo vazio, e a marca é forma além de cor (o traço vira ✓), o
 * que mantém a informação para quem não distingue verde de cinza. */
.pw-rules {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 1rem;
    margin: 0.5rem 0 0;
    padding: 0;
    list-style: none;
    font-size: var(--text-sm);
    color: var(--muted);
}

.pw-rules li {
    display: flex;
    align-items: center;
    gap: 0.35rem;
}

.pw-rules li::before {
    content: "–";
    display: inline-flex;
    justify-content: center;
    inline-size: 1em;
    font-weight: 700;
}

.pw-rules li.ok {
    color: var(--ok, #1a7f37);
}

.pw-rules li.ok::before {
    content: "✓";
}

/* ══ PEÇAS DO KIT ════════════════════════════════════════════════════════════════

   O que o client tem e o site não tinha. Moram aqui, e não no CSS de uma página,
   porque os quatro aplicativos as usam — o título com Pokébola aparece na conta, no
   fórum e no apoio do mesmo jeito que na página inicial. */

/* ── O foco que a navegação põe no título ────────────────────────────────────────
   O Blazor move o foco para o <h1> a cada navegação (FocusOnNavigate) e para o <main>
   pelo link de pular, e os dois recebem tabindex="-1" para poder ser focados. O anel
   de :focus-visible pintava um retângulo vermelho em volta de TODO título de página —
   parecia erro de validação.

   O foco continua indo para lá — é o que faz o leitor de tela anunciar a página nova
   —; só o contorno some, e apenas em elemento que não é alvo de teclado. Link, botão
   e campo continuam com o anel inteiro. */
[tabindex="-1"]:focus,
h1:focus,
h2:focus { outline: none; }

/* ── Título com Pokébola ─────────────────────────────────────────────────────────
   O "Entrar no jogo" do client: a bola e o texto em negrito, na mesma linha. */
.kit-title {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--font-display);
  font-weight: 800;
}
.kit-title::before {
  content: "";
  flex: none;
  inline-size: 1.15em;
  block-size: 1.15em;
  background: var(--kit-pokeball) center / contain no-repeat;
}

/* O rótulo pequeno em caixa alta — NOTÍCIAS, EVENTOS, DESTAQUE DO DIA no client. */
.kit-label {
  margin: 0;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--kit-frame);
}

/* ── Pílula de evento ────────────────────────────────────────────────────────────
   Texto branco em negrito sobre a cor da categoria, com a aresta de baixo do client.
   A cor nunca é a única pista: o título vai escrito, e a categoria por extenso no
   atributo title e na lista. */
.event-pill {
  display: block;
  min-inline-size: 0;
  padding: 3px 10px;
  border-radius: 8px;
  color: #fff;
  font-size: 0.8rem;
  font-weight: 700;
  line-height: 1.45;
  text-decoration: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  background: var(--event-community);
  box-shadow: inset 0 -2px 0 rgb(0 0 0 / 0.18);
}
a.event-pill:hover { color: #fff; filter: brightness(1.1); }
.event-pill.ev-xp          { background: var(--event-xp); }
.event-pill.ev-tournament  { background: var(--event-tournament); }
.event-pill.ev-seasonal    { background: var(--event-seasonal); }
.event-pill.ev-community   { background: var(--event-community); }
.event-pill.ev-raid        { background: var(--event-raid); }
.event-pill.ev-maintenance { background: var(--event-maintenance); }

/* ── A pílula de online ──────────────────────────────────────────────────────────
   A da barra do login do client: escura, ponto verde aceso. Escura de propósito
   num site claro — é a peça que diz "isto é o jogo agora", e precisa ser achada. */
.online-pill {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 4px 14px 4px 12px;
  border-radius: var(--radius-full);
  background: var(--kit-night);
  border: 2px solid var(--kit-night-line);
  color: #fff;
  font-size: var(--text-sm);
  font-weight: 700;
  line-height: 1.4;
  text-decoration: none;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.online-pill::before {
  content: "";
  flex: none;
  inline-size: 10px;
  block-size: 10px;
  border-radius: 50%;
  background: var(--kit-go);
  box-shadow: 0 0 0 2px #1d6b2a;
}
a.online-pill:hover { color: #fff; border-color: #8590ad; }

/* ── Carregando ──────────────────────────────────────────────────────────────────
   A Pokébola girando do client no lugar do spinner genérico. Com movimento reduzido
   ela fica parada — o texto ao lado continua dizendo que está carregando. */
.pokeball-loader {
  display: inline-block;
  inline-size: 34px;
  block-size: 34px;
  background: var(--kit-pokeball) center / contain no-repeat;
  animation: pokeball-spin 1.1s linear infinite;
}
@keyframes pokeball-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .pokeball-loader, .btn.is-busy::after { animation: none; }
}

/* ── Sprite ──────────────────────────────────────────────────────────────────────
   Pixel art nunca é suavizada ao ampliar: borrada, parece imagem quebrada. */
.sprite { image-rendering: pixelated; }

/* ══ A BASE QUE VALE NOS QUATRO ══════════════════════════════════════════════════

   Link, anel de foco e o topo de página moram no app.css de cada aplicativo, que NÃO é
   sincronizado — e cada um tinha a versão antiga: link vermelho, anel vermelho, brilho
   roxo da paleta escura aposentada. Declarados aqui com um seletor um grau mais
   específico, eles valem nos quatro sem que ninguém precise lembrar de editar quatro
   arquivos — e o app.css continua livre para o que é só daquele aplicativo. */

/* Link azul só dentro de TEXTO (formulários e avisos): cartões, menus e títulos-link
   herdam a tinta da peça. A regra global anterior (html a) pintava de azul todo título
   clicável das páginas novas. */
:where(p, .hint, label, .notice, .alert, .form-error) > a:not(.btn):not(.chip) { color: var(--sky-600); text-decoration: underline; text-decoration-color: oklch(0.52 0.15 243 / 0.45); }

/* Azul e não vermelho: um anel vermelho em volta de um campo se lê como "errado". */
html :focus-visible { outline: 3px solid var(--sky-500); outline-offset: 2px; }


/* Abaixo de 480px os botões de uma mesma ação ficam com a largura toda, iguais entre
   si, como no banner de cookies. */
@media (max-width: 480px) {
  .row > .btn-lg, .hero-actions > .btn-lg { flex: 1 1 100%; }
}

/* Dentro de um painel o estado vazio não precisa de tanto ar: o painel já o separa. */
.card .state { padding-block: var(--space-5); }
