/*
  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 ─────────────────────────────────────────────────────────────── */

.card {
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: var(--space-5);
  container-type: inline-size;
}

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

.card-interactive {
  transition: border-color var(--dur) var(--ease-out),
              transform var(--dur) var(--ease-out),
              box-shadow var(--dur) var(--ease-out);
}
.card-interactive:hover {
  border-color: var(--line-strong);
  transform: translateY(-2px);
  box-shadow: var(--shadow);
}
/* 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; }

.panel {
  background: var(--bg-inset);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--space-4);
}

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

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-5);
  border-radius: var(--radius);
  font-weight: 600;
  font-size: var(--text-sm);
  text-decoration: none;
  white-space: nowrap;
  border: 1px solid transparent;
  transition: background-color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              color 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; }

.btn-primary {
  background: var(--brand-500);
  color: var(--text-on-brand);
}
.btn-primary:hover:not(:disabled) { background: var(--brand-400); color: var(--text-on-brand); }

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

.btn-secondary {
  background: var(--bg-overlay);
  border-color: var(--line-strong);
  color: var(--text);
}
.btn-secondary:hover:not(:disabled) { background: var(--bg-raised); border-color: var(--brand-400); color: var(--text); }

.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-4) var(--space-6); font-size: var(--text-base); min-height: 52px; }
.btn-sm { padding: var(--space-2) var(--space-3); font-size: var(--text-xs); min-height: 36px; }
.btn-block { width: 100%; }

/* ── 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.58 0.21 285 / 0.16); color: var(--brand-200); border-color: oklch(0.58 0.21 285 / 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);
  background: var(--bg-inset);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  color: var(--text);
  min-height: 44px;
  transition: border-color var(--dur-fast) var(--ease-out), background-color var(--dur-fast) var(--ease-out);
}
.input::placeholder, .textarea::placeholder { color: var(--text-subtle); }
.input:hover, .textarea:hover, .select:hover { border-color: var(--brand-400); }
.input:focus, .textarea:focus, .select:focus { border-color: var(--brand-400); background: var(--bg-raised); }
.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: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--bg-raised);
}

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(--bg-inset);
  color: var(--text-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(--line);
  vertical-align: middle;
}
table.data tbody tr { transition: background-color var(--dur-fast) var(--ease-out); }
table.data tbody tr:hover { background: var(--bg-overlay); }

/* 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(--bg-overlay);
  color: var(--text-muted);
}
.rank-badge.gold   { background: oklch(0.78 0.16 68 / 0.22);  color: var(--accent-300); }
.rank-badge.silver { background: oklch(0.85 0.01 285 / 0.18); color: oklch(0.88 0.01 285); }
.rank-badge.bronze { background: oklch(0.63 0.17 58 / 0.22);  color: var(--accent-400); }

.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; }
.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(--brand-300); border-bottom-color: var(--brand-400); }

/* ── 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.58 0.21 285 / 0.22), oklch(0.58 0.21 285 / 0.08));
  border: 1px solid oklch(0.58 0.21 285 / 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(--brand-200); }
.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.58 0.21 285 / 0.5);
  background: oklch(0.58 0.21 285 / 0.16);
  color: var(--brand-200);
}

/* ── 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: oklch(0.12 0 0); }
.step.is-current { color: var(--brand-200); border-color: oklch(0.58 0.21 285 / 0.5); background: oklch(0.58 0.21 285 / 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.58 0.21 285 / 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(0.16 0.015 285 / 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; }
