/*
 * Design System v1 — Agon BT (ui-spec §0). Tema único escolhido pelo usuário
 * (claro/escuro/sistema) — vale para TODO o app. Tokens em CSS custom properties
 * → base → componentes. Servido pelo Propshaft (sem build).
 */

/* ============================================================ TOKENS */
/* Tokens independentes de tema (marca, semântica, tipografia, espaço, raio):
   valem em qualquer tema. As superfícies/texto/borda ficam nos blocos abaixo. */
:root {
  /* Marca */
  --brand: #E94E1B;
  --brand-pressed: #C63F12;
  --brand-on-light: #B33A12;      /* laranja legível como texto sobre claro */
  --brand-tint-dark: #2a1d16;     /* tinta sutil de marca sobre escuro */
  --brand-tint-light: #FBE9E1;    /* tinta sutil de marca sobre claro */
  --warning-tint: #F6EEDA;        /* tinta de atenção (linha de W.O. no resumo) */

  /* Semântica */
  --success: #2F8F57;
  --warning: #B8860B; /* atenção / W.O. */
  --danger: #C0392B;  /* erro / trava */
  --gold: #C8972E;
  --silver: #9AA0A6;
  --bronze: #B07A46;
  --up: #5fbf72;
  --down: #e0775f;
  /* Linguagem de cor do gênero — uma fonte só para a silhueta do avatar e o chip
     de categoria do ranking (misto = mistura dos dois). */
  --gender-male: #3E7CB1;
  --gender-female: #C25E8B;
  --gender-mixed: #8A6FB0;

  /* A base do rem, em px. É a régua de projeto do app: tudo mede em `rem` e o
     desktop só estica esta base (ver o `clamp` do `:root` no fim do arquivo).
     Está aqui como token porque a captura das artes precisa dela para devolver a
     imagem no tamanho de projeto, e copiá-la para o JS deixava a mesma constante
     em dois arquivos (2026-08-18). */
  --rem-base: 16;

  /* Tipografia */
  --font-display: "Outfit", system-ui, sans-serif;
  --font-num: "Archivo", system-ui, sans-serif;     /* números tabulares */
  --font-body: "Schibsted Grotesk", system-ui, sans-serif;
  --font-mono: "Space Mono", ui-monospace, monospace;
  /* Tamanhos em rem (base 16px) para o scaling de desktop sair de um único
     ajuste de font-size no :root por breakpoint (M-004). No mobile, equivalem
     exatamente aos px de antes. */
  --fs-display: 2.125rem;   /* 34px */
  --fs-title: 1.375rem;     /* 22px */
  --fs-hero-num: 2.125rem;  /* 34px */
  --fs-line-num: 1.1875rem; /* 19px */
  --fs-strong: 0.875rem;    /* 14px */
  --fs-body: 0.8125rem;     /* 13px */
  /* Degrau entre o corpo e o eyebrow: texto de apoio que é FRASE (e precisa ser
     lido), não rótulo mono/maiúsculo. O eyebrow, em 10px, é pequeno demais.

     COMO ESCOLHER entre os dois (critério do M-023, escrito aqui em 2026-08-11
     porque até então ele só existia na cabeça de quem passava a régua): é o
     PAPEL do texto que decide, não o tamanho que ele aparenta precisar.
       --fs-eyebrow → rótulo: curto, mono, caixa alta, com letter-spacing. Nomeia
         uma região ("PATROCÍNIO", "HISTÓRICO"), e ninguém o lê como frase.
       --fs-meta    → frase de apoio: fonte de corpo, caixa normal, escrita para
         ser lida ("marcado por Fulano", "3 de 4 jogos", o critério de desempate).
     O que segue em `--fs-eyebrow` e NÃO é engano: rótulo mono de seção, aba da
     barra inferior, ação em texto ("Escolher foto", "limpar filtro"), carimbo de
     hora do histórico, número da vaga, selo e pílula. Nenhum deles é frase. */
  --fs-meta: 0.75rem;       /* 12px */
  --fs-eyebrow: 0.625rem;   /* 10px */
  /* Campo de digitação: 16px NÃO é escolha estética (decisão do usuário
     2026-08-07). Abaixo disso o Safari do iPhone amplia a tela sozinho ao abrir
     o teclado, e a única alternativa seria proibir a pessoa de ampliar a página,
     que é regressão de acessibilidade. Diminuir daqui traz o zoom de volta. */
  --fs-input: 1rem;         /* 16px */

  /* Espaço (base 4) e raio */
  --sp-1: 0.25rem; --sp-2: 0.5rem; --sp-3: 0.75rem; --sp-4: 1rem; --sp-5: 1.25rem;
  --sp-6: 1.5rem; --sp-8: 2rem; --sp-10: 2.5rem;
  --gutter: 1rem;
  --r-sm: 0.375rem; --r-md: 0.625rem; --r-lg: 0.875rem; --r-xl: 1.125rem; --r-pill: 1.25rem;

  --glow: 0 0 0 2px var(--brand), 0 0 12px rgba(233, 78, 27, 0.35);
  --tap: 2.75rem; /* alvo de toque mínimo (44px) */
  /* Coluna estreita das telas que chegam de fora (acesso e landing). */
  --col-max: 25.75rem;
  --avatar-lg: 56px; /* avatar grande — e o círculo do campo de imagem, que tem de bater com ele */
  --score-col: 3rem; /* coluna fixa do placar (números alinhados) */
  /* Largura da coluna do app. Um token só, para que TUDO que se alinha à coluna
     cresça junto no desktop (ver as media queries no fim do arquivo). */
  --app-w: 30rem;
}

/* Superfícies — o CLARO é o default (o app abre claro; papel de gestão). Fundo e
   card têm luminosidade próxima, então a separação vem de borda + sombra
   (variante "D" validada): fundo levemente tingido e borda perceptível (≈3:1). O
   bloco vale como default (:root) e quando o usuário escolhe "claro"
   explicitamente (sobrepondo o SO escuro ou um ancestral escuro). */
:root,
[data-theme="light"] {
  --surface-app: #EFEAE1;
  --surface-card: #ffffff;
  --surface-deep: #E4DED2;        /* fundo mais fundo: card de temporada encerrada */
  --surface-panel: #ffffff;
  --surface-chip: #EAE6DE;
  --text-1: #16130F;
  /* Escala muted (feedback 2026-08-06). O aperto de 2026-07-22 mediu contra o
     BRANCO, mas o app é papel (--surface-app): contra o fundo real, rótulo e
     hint ficavam em 3.4:1 e 2.2:1, e até o secundário reprovava (4.4:1). Os três
     agora passam de 4.5:1 no pior fundo (o chip), com a hierarquia intacta:
     2 (secundário) > 3 (rótulo/eyebrow) > 4 (meta). */
  --text-2: #4f4a41; /* 7.3:1 no papel — secundário/corpo muted */
  --text-3: #5e594d; /* 5.8:1 — rótulos, eyebrow, meta */
  --text-4: #6b6558; /* 4.8:1 — texto de apoio (o menor que ainda se lê) */
  /* Desabilitado é o único que segue apagado de propósito: ele diz "não dá para
     mexer", e a WCAG dispensa contraste em controle inativo. Antes usava o
     --text-4, que agora é texto para ler. */
  --text-disabled: #a8a190;
  --border: #CFC7B8;
  --border-strong: #a39a8a; /* mais forte que --border: contorna inputs e botões secundários no claro */
  --elev-card: 0 1px 2px rgba(0, 0, 0, 0.08);
  --brand-tint: var(--brand-tint-light); /* tinta de marca que acompanha o tema */
}

/* Modo "sistema" (sem escolha explícita no <html>): quando o aparelho pede
   escuro, seguimos o SO. `:not([data-theme])` garante que uma escolha explícita
   (claro/escuro) sempre vença o SO. Mantém as MESMAS superfícies escuras do bloco
   [data-theme="dark"] abaixo — manter os dois em sincronia. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) {
    --surface-app: #16130F;
    --surface-card: #1c1813;
    --surface-deep: #120f0b;
    --surface-panel: #211C16;
    --surface-chip: #2a2620;
    --text-1: #F4F0E9;
    --text-2: #C9C3B9;
    --text-3: #a9a090;
    --text-4: #948874;
    --text-disabled: #6f6962;
    --border: rgba(255, 255, 255, 0.08);
    --border-strong: rgba(255, 255, 255, 0.16);
    --elev-card: 0 1px 2px rgba(0, 0, 0, 0.04);
    --brand-tint: var(--brand-tint-dark);
    --warning-tint: #2a2115;
  }
}

/* Superfícies escuras — quando o usuário escolhe "escuro" (no <html>) OU quando
   um componente reabre o escuro dentro de uma página clara (pódio, pôster de
   export, card "em disputa" no dashboard). Espelha o bloco @media acima. */
[data-theme="dark"] {
  --surface-app: #16130F;
  --surface-card: #1c1813;
  --surface-deep: #120f0b;        /* preto mais fundo: card de temporada encerrada */
  --surface-panel: #211C16;
  --surface-chip: #2a2620;
  --text-1: #F4F0E9;
  --text-2: #C9C3B9;
  --text-3: #a9a090;
  --text-4: #948874;
  --text-disabled: #6f6962;
  --border: rgba(255, 255, 255, 0.08);
  --border-strong: rgba(255, 255, 255, 0.16);
  --elev-card: 0 1px 2px rgba(0, 0, 0, 0.04); /* elevação discreta dos cards */
  --brand-tint: var(--brand-tint-dark);
  --warning-tint: #2a2115;
}

/* ============================================================ BASE */
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
/* O iOS aumenta por conta própria o texto de alguns blocos, e a caixa em volta
   não cresce junto: o que mede em `ch`/rem passa a ser pequeno demais para a
   letra que ele desenha, e o conteúdo sai cortado. Aqui o texto tem o tamanho
   que a folha manda (o pinça-para-ampliar da pessoa continua valendo). */
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
/* Piso de 16px em TODO campo de digitação: abaixo disso o Safari do iPhone
   amplia a tela ao abrir o teclado (ver --fs-input). Na base, e não numa classe
   de componente, porque a restrição é do navegador sobre o elemento — um campo
   novo que não vista `.input` traria o zoom de volta sem ninguém perceber. */
input, select, textarea { font-size: var(--fs-input); }
/* iOS: dois toques seguidos no mesmo ponto dão zoom, e apertar o "+" do placar
   várias vezes é exatamente isso (relato do usuário, 2026-08-07). `manipulation`
   diz "aqui não há duplo-toque" no que é feito para tocar, sem tirar o
   pinça-para-ampliar da página — o teto de zoom no viewport resolveria também,
   mas proibindo a pessoa de ampliar, o que não se faz. */
a, button, label, summary, .btn, [role="button"] { touch-action: manipulation; }
body {
  background: var(--surface-app);
  color: var(--text-1);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: 1.45;
  -webkit-font-smoothing: antialiased;
}
h1, h2, h3 { font-family: var(--font-display); font-weight: 800; margin: 0; }
a { color: var(--brand); }
.num { font-family: var(--font-num); font-variant-numeric: tabular-nums; }

/* Ícones Lucide (SVG inline, herdam currentColor). */
svg.lucide { width: 20px; height: 20px; vertical-align: middle; flex: none; }
.bottom-nav__tab svg.lucide { width: 22px; height: 22px; }
.eyebrow {
  font-family: var(--font-mono); font-size: var(--fs-eyebrow);
  text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-3);
}

/* App frame mobile-first (sem position: relative — o menu de conta agora vive no header). */
.app-frame { max-width: var(--app-w); margin: 0 auto; min-height: 100dvh; padding-bottom: var(--sp-6); }
.stack { display: flex; flex-direction: column; gap: var(--sp-3); padding: var(--gutter); }
/* `.stack` aninhada numa tela que já tem o gutter: só o ritmo vertical. */
.stack--flush { padding: 0; gap: var(--sp-2); }
.row { display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: center; }
.linkout { word-break: break-all; font-size: var(--fs-eyebrow); color: var(--text-2); margin-bottom: var(--sp-3); }

/* ============================================================ CHIP DE CATEGORIA (§23) + LINHA DE IDENTIDADE DO RANKING */
/* Ícone Lucide + rótulo, tingido pela cor de gênero do avatar. Fundo e texto derivam
   do mesmo token via color-mix com o texto do tema, então claro, escuro e o card "em
   disputa" (que reabre o escuro por data-theme) se resolvem sozinhos, sem variante
   por contexto. Usado no card da lista e na linha de identidade dentro do ranking. */
.category-chip { display: inline-flex; align-items: center; gap: 4px; font-family: var(--font-mono); font-size: 0.5rem; letter-spacing: .08em; text-transform: uppercase; line-height: 1; padding: 5px 8px; border-radius: var(--r-pill); flex: none; background: color-mix(in srgb, var(--chip-tone) 18%, transparent); color: color-mix(in srgb, var(--chip-tone) 65%, var(--text-1)); }
.category-chip svg.lucide { width: 11px; height: 11px; flex: none; }
.category-chip--masculine { --chip-tone: var(--gender-male); }
.category-chip--feminine { --chip-tone: var(--gender-female); }
.category-chip--mixed { --chip-tone: var(--gender-mixed); }

/* Linha em que o ranking se apresenta dentro dele (shared/_ranking_eyebrow): nome +
   chip lado a lado. O nome sobe um degrau de contraste (--text-2) para não ficar mais
   apagado que o chip que o acompanha, e encolhe com reticências quando divide a linha
   com outra meta (a barra das timelines). */
.ranking-eyebrow.eyebrow { display: flex; align-items: center; gap: var(--sp-2); min-width: 0; color: var(--text-2); }
.ranking-eyebrow__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* ============================================================ BUTTON (§1) */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
  min-height: var(--tap); padding: 0 var(--sp-4);
  font-family: var(--font-body); font-size: var(--fs-strong); font-weight: 600;
  border-radius: var(--r-md); border: 1.5px solid transparent; cursor: pointer;
  background: transparent; color: var(--text-1); text-decoration: none; transition: transform .05s ease;
}
.btn:active { transform: scale(0.975); }
.btn--full { width: 100%; }
.btn--primary { background: var(--brand); color: #fff; }
.btn--primary:active { background: var(--brand-pressed); }
.btn--secondary { border-color: var(--border-strong); color: var(--text-1); }
.btn--ghost { color: var(--brand); }
.btn--danger { color: var(--danger); }
.btn--destructive { border-color: var(--down); color: var(--down); }
.btn--locked { border-style: dashed; border-color: var(--border-strong); color: var(--text-disabled); cursor: not-allowed; }
.btn--icon { width: var(--tap); min-width: var(--tap); padding: 0; color: var(--text-2); } /* ação só-ícone */
.btn--inline { padding-left: 0; padding-right: 0; font-weight: 600; } /* ação textual discreta (flush à esquerda) */
.btn--inline svg.lucide { width: 16px; height: 16px; }
.btn--sm { min-height: 36px; padding: 0 var(--sp-3); font-size: var(--fs-body); } /* ação compacta (ex.: copiar link) */
.btn--sm svg.lucide { width: 16px; height: 16px; }
.btn[disabled], .btn--disabled { background: var(--surface-chip); color: var(--text-disabled); cursor: not-allowed; border-color: transparent; }
.btn__reason { display: block; font-size: var(--fs-meta); color: var(--text-4); margin-top: var(--sp-1); }

/* ============================================================ INPUT (§2) */
/* O rótulo cola no campo dele e os campos se afastam entre si (feedback
   2026-08-06): antes tudo respirava igual, e a tela lia como uma lista solta de
   caixas em vez de campo + explicação. */
.field { display: flex; flex-direction: column; gap: var(--sp-2); }
.field__label { font-family: var(--font-mono); font-size: var(--fs-eyebrow); text-transform: uppercase; letter-spacing: .06em; font-weight: 700; color: var(--text-2); }
/* Cadeado no rótulo do campo travado ("isto já foi decidido"). */
.field__label svg.lucide { width: 11px; height: 11px; vertical-align: -1px; color: var(--text-3); }
.field__value { margin: 0; font-size: var(--fs-strong); font-weight: 600; color: var(--text-1); } /* dado só-leitura (ex.: jogador vinculado) */
/* Dado só-leitura com a ação de editar ao lado (ex.: horário): o botão fica
   colado no valor, não empurrado para a borda oposta da tela. */
.field__value-row { display: flex; align-items: center; gap: var(--sp-1); }
/* Cabeçalho da ficha só-leitura (jogador vinculado): foto + nome centralizados. */
.roster-view-head { display: flex; flex-direction: column; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-2); }
.roster-view-name { font-family: var(--font-display); font-weight: 800; font-size: var(--fs-title); text-align: center; }
.input {
  min-height: var(--tap); padding: 0 var(--sp-3);
  background: var(--surface-card); color: var(--text-1);
  border: 1.5px solid var(--border-strong); border-radius: var(--r-md);
}
.input:focus { outline: none; border-color: var(--brand); box-shadow: var(--glow); }
/* `.input` nasceu para controle de uma linha (altura pelo min-height), daí o
   padding vertical zero. No textarea isso encosta o texto na borda de cima. */
textarea.input { padding: var(--sp-2) var(--sp-3); }
/* Data e hora: largura do próprio controle (o campo tem tamanho fixo; esticar até
   a borda não ajuda a preencher). O `.field` é flex-column, daí o align-self. */
.input[type="date"], .input[type="time"] { align-self: start; }
/* Código de verificação (A3): um quadradinho por dígito (JS troca o input único
   por estes; sem JS, o campo único permanece). */
.otp { display: flex; gap: var(--sp-2); }
.otp__cell {
  flex: 1; min-width: 0; aspect-ratio: 1; padding: 0;
  text-align: center; font-family: var(--font-num); font-variant-numeric: tabular-nums;
  font-size: var(--fs-title); font-weight: 700; color: var(--text-1);
  background: var(--surface-card); border: 1px solid var(--border); border-radius: var(--r-md);
}
.otp__cell:focus { outline: none; border-color: var(--brand); box-shadow: var(--glow); }
.input--error { border-color: var(--danger); }
/* Campo de um número só, no meio de uma frase ("Set composto de [6] games").
   A fonte tabular vem do `.num`; aqui fica só a forma. */
.input--num { width: 4.5rem; flex: none; text-align: center; font-weight: 700; }
/* ---- Formato da partida (M-031, Configurar): número no meio da frase + as
   formas de terminar, cada uma com a explicação do placar que ela produz.
   As duas perguntas (o set e o tie-break) vivem numa moldura só: soltas na
   pilha, o bloco lia como se outra tela tivesse emendado ali. */
.format-box {
  background: var(--surface-card); border: 1px solid var(--border); border-radius: var(--r-lg);
  box-shadow: var(--elev-card); padding: var(--sp-4);
}
.format-row { display: flex; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-2); }
.format-row__label, .format-row__unit { font-size: var(--fs-body); color: var(--text-2); }
.format-tiebreak { margin-top: var(--sp-4); padding-top: var(--sp-3); border-top: 1px solid var(--border); }
/* Sem tie-break no set não há o que configurar sobre ele (CSS `:has`, sem JS). */
.match-format:has([data-format-ending="direct"]:checked) .format-tiebreak { display: none; }
/* Opção única com explicação embaixo: o `.segmented` (pílula) não comporta a
   frase, e o `.pick` é o cartão de escolher a dupla vencedora. */
.radio-line { display: flex; align-items: flex-start; gap: var(--sp-3); padding: var(--sp-2) 0; cursor: pointer; }
.radio-line__input { margin-top: 3px; accent-color: var(--brand); flex: none; }
.radio-line__body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.radio-line__title { font-size: var(--fs-body); color: var(--text-1); }
.radio-line:has(.radio-line__input:checked) .radio-line__title { color: var(--brand); font-weight: 700; }
/* Campo não-editável (readonly/disabled): fundo apagado + cursor bloqueado. */
.input:disabled, .input[readonly] { background: var(--surface-chip); color: var(--text-disabled); border-style: dashed; cursor: not-allowed; }
.field__hint { font-size: var(--fs-meta); color: var(--text-3); }
.field__hint--error { color: var(--danger); }
/* Aviso que não impede seguir (ex.: a soma dos totais não fechar, M-013). */
.field__hint--warn { color: var(--warning); }
/* Confirmação de que o que foi digitado está bom (ex.: link público livre, M-005). */
.field__hint--ok { color: var(--success); }
/* Empilhamento de campos dentro de um <form> (gap entre .field e o submit). */
.form-stack { display: flex; flex-direction: column; gap: var(--sp-5); }
/* Ação logo abaixo de um formulário (ex.: limpar as vitórias): o container já
   separa os dois pelo gap dele, então aqui entra só a diferença para o respiro
   ficar igual ao de dentro do formulário. */
.form-stack + form { margin-top: calc(var(--sp-4) - var(--sp-3)); }

/* Linha de gestão (lista admin): info à esquerda, ações (ícones) à direita. */
.admin-row { display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-3) var(--sp-4);
  background: var(--surface-card); border: 1px solid var(--border); border-radius: var(--r-md); box-shadow: var(--elev-card); }
.admin-row__info { flex: 1; min-width: 0; }
.admin-row__name { font-size: var(--fs-strong); font-weight: 600; }
.admin-row__meta { font-size: var(--fs-meta); color: var(--text-3); margin-top: 2px; word-break: break-word; }
.admin-row__actions { display: flex; gap: var(--sp-1); flex: none; }
.admin-row form { margin: 0; }
/* Ações da linha como ícones compactos (menores que o padrão de 20px). */
.admin-row__actions .btn--icon { width: 38px; min-width: 38px; }
.admin-row__actions svg.lucide { width: 18px; height: 18px; }

/* Aparelho na tela de Notificações (Fase 23): o campo do apelido é a linha, e a
   identificação técnica fica embaixo, em letra miúda. O campo mantém a moldura
   do DS de propósito — descascado, parecia texto e ninguém escrevia nele. */
.device-card { display: flex; flex-direction: column; gap: var(--sp-2);
  padding: var(--sp-3); background: var(--surface-card);
  border: 1px solid var(--border); border-radius: var(--r-lg); box-shadow: var(--elev-card); }
.device-card form { margin: 0; }
.device-card .admin-row__meta { margin: 0; }
/* O lápis é um `label`: clicar leva o cursor ao campo. Herda o hover do
   .input-affix, mas não é botão, então não vira alvo de tabulação. */
.device-card .input-affix { cursor: text; }

/* Telas que ensinam um caminho (instalar o app, liberar notificações — Fase 23).

   Cabeçalho: o ícone do app apresenta do que se trata antes de qualquer texto —
   é a mesma figura que a pessoa vai procurar na tela do aparelho depois. */
.howto-hero { display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-4);
  background: var(--surface-card); border: 1px solid var(--border);
  border-radius: var(--r-lg); box-shadow: var(--elev-card); }
.howto-hero__icon { width: 52px; height: 52px; border-radius: var(--r-md); flex: none; }
.howto-hero .section-intro { min-width: 0; }

/* Passo a passo: o número em círculo carrega a ordem, que é o que importa numa
   instrução seguida com o aparelho na mão. Contador próprio porque o marcador
   nativo não se estiliza.
   O número é posicionado (e não uma coluna flex) para o <li> continuar sendo
   texto corrido: com flex, cada <strong> do meio da frase virava uma coluna. */
.howto { list-style: none; counter-reset: howto; display: flex; flex-direction: column;
  gap: var(--sp-3); margin: 0; padding: 0; }
.howto li { counter-increment: howto; position: relative; padding-left: 38px;
  color: var(--text-2); font-size: var(--fs-body); }
.howto li::before { content: counter(howto); position: absolute; left: 0; top: 0;
  display: flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: 50%;
  background: color-mix(in srgb, var(--brand) 12%, var(--surface-chip));
  color: var(--brand); font-family: var(--font-num); font-weight: 800;
  font-size: var(--fs-meta); line-height: 1; }
.howto strong { color: var(--text-1); font-weight: 700; }

/* Caminhos alternativos (cada navegador instala de um jeito): não são passos, e
   numerá-los diria para fazer os três. O nome do caminho substitui o número. */
.howto-options { display: flex; flex-direction: column; margin: 0; padding: 0; }
.howto-options__row { display: flex; flex-direction: column; gap: 2px;
  padding: var(--sp-3) 0; border-bottom: 1px solid var(--border); }
.howto-options__row:last-child { border-bottom: 0; padding-bottom: 0; }
.howto-options__row:first-child { padding-top: 0; }
.howto-options__term { display: flex; align-items: center; gap: var(--sp-2);
  font-size: var(--fs-strong); font-weight: 700; color: var(--text-1); }
.howto-options__term svg.lucide { width: 16px; height: 16px; color: var(--text-3); flex: none; }
.howto-options__desc { color: var(--text-2); font-size: var(--fs-body); }

/* Aviso de excedente de fixos (orienta, não bloqueia §6.8) — no cabeçalho do grupo. */
.roster-note { font-size: var(--fs-meta); }
.roster-note--over { color: var(--warning); font-weight: 700; }
/* Situação de acesso em linha (ícone + texto): usada na tela de calendário. */
.admin-row__info .admin-row__meta + .admin-row__meta { margin-top: 2px; }
.roster-status { display: inline-flex; align-items: center; gap: 4px; }
.roster-status--pending { color: var(--warning); }
.roster-status svg.lucide { width: 13px; height: 13px; }

/* Linha do histórico de auditoria. */
.audit-row { display: flex; flex-direction: column; gap: 2px; padding: var(--sp-2) 0; border-bottom: 1px solid var(--border); }

/* Tela de detalhe: meta no topo + seções separadas por divisória. */
.detail-meta { display: flex; justify-content: space-between; align-items: center; gap: var(--sp-3); }
/* Com a identidade do ranking de um lado, o rótulo do outro não encolhe: quem
   cede é o nome (reticências), não o contador de etapas. */
.detail-meta .ranking-eyebrow + .eyebrow { flex: none; }
/* Resumo de identidade (hub do ranking): rótulo `.eyebrow` + valor, em texto
   solto. Fora de card de propósito — na mesma tela todo card navega, e um card
   só-leitura ensinaria um toque que não existe. */
.summary-list { display: flex; flex-direction: column; gap: var(--sp-1); }
.summary-list__row { display: flex; align-items: baseline; gap: var(--sp-2); }
.summary-list__val { margin: 0; } /* mata o recuo que o navegador dá ao dd */
/* Valor ainda não definido (ex.: horário): esmaecido, para não competir com os
   dados preenchidos. */
.summary-list__val--empty { color: var(--text-disabled); }
.section { display: flex; flex-direction: column; gap: var(--sp-3); border-top: 1px solid var(--border); padding-top: var(--sp-4); }
.link-plain { color: var(--text-1); text-decoration: none; }
/* Campo com afixo à direita (ex.: toggle de senha). */
.input-group { position: relative; display: flex; }
.input-group .input { flex: 1; width: 100%; }
.input--affix { padding-right: var(--tap); }
.input-affix {
  position: absolute; right: 0; top: 0; bottom: 0; width: var(--tap);
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; border: 0; color: var(--text-3); cursor: pointer;
}
.input-affix:hover { color: var(--text-2); }
/* Campo com prefixo fixo à esquerda (o começo do link público, M-005): o começo
   do endereço é dado, a pessoa digita só o final. */
.input-prefix {
  display: inline-flex; align-items: center; padding: 0 var(--sp-2); flex: none;
  /* Em 10px o prefixo sumia ao lado do valor de 16px (2026-08-07). */
  background: var(--surface-chip); color: var(--text-3); font-size: var(--fs-meta); white-space: nowrap;
  border: 1.5px solid var(--border-strong); border-right: 0; border-radius: var(--r-md) 0 0 var(--r-md);
}
.input-prefix + .input { border-top-left-radius: 0; border-bottom-left-radius: 0; min-width: 0; }
/* Utilitário de ocultar — !important para vencer regras de componente (ex.: cards
   e opções com display:flex definido depois nesta folha). */
.is-hidden { display: none !important; }

/* Combobox (busca com lista de opções filtrável) — ex.: filtro por organização.
   Lista estilizada (não o datalist nativo); opções escondidas via .is-hidden. */
.combo { position: relative; }
.combo__list { position: absolute; left: 0; right: 0; top: calc(100% + 4px); z-index: 20; margin: 0;
  list-style: none; padding: var(--sp-1); background: var(--surface-card); border: 1px solid var(--border-strong);
  border-radius: var(--r-md); box-shadow: var(--elev-card); max-height: 260px; overflow-y: auto; }
.combo__opt { display: flex; align-items: center; min-height: var(--tap); padding: var(--sp-2) var(--sp-3);
  border-radius: var(--r-sm); cursor: pointer; color: var(--text-1); }
.combo__opt:hover { background: var(--surface-chip); }

/* ============================================================ CONTROLS (§3) */
.segmented { display: inline-flex; background: var(--surface-chip); border-radius: var(--r-pill); padding: 2px; }
.segmented__opt { display: inline-flex; align-items: center; border: 0; background: transparent; color: var(--text-3); padding: 8px var(--sp-4); border-radius: var(--r-pill); font-size: var(--fs-body); cursor: pointer; }
.segmented__opt--active { background: var(--brand); color: #fff; }
/* Variante em radios (form): o input fica oculto e o rótulo marcado pinta. */
.segmented__radio { position: absolute; opacity: 0; pointer-events: none; }
.segmented__opt:has(.segmented__radio:checked) { background: var(--brand); color: #fff; }
/* Dentro de um campo de form, ocupa a largura toda com as opções divididas. */
.field .segmented { display: flex; }
.field .segmented .segmented__opt { flex: 1; justify-content: center; }
/* Como filtro (fora de form), largura natural — sem esticar até o fim. */
.segmented[data-gender-filter] { align-self: flex-start; }
/* `button_to` traz um <form> junto; onde os botões formam uma linha (toggle de RSVP
   do cockpit, ações do logo no Configurar), esse form precisa sair do fluxo para
   não quebrar o flex do pai. */
.seg-toggle form, .logo-actions form { display: contents; }
.seg-toggle .segmented__opt { font-size: var(--fs-eyebrow); padding: 6px var(--sp-3); }
.toggle { display: inline-flex; align-items: center; gap: var(--sp-2); }
.toggle__track { width: 40px; height: 24px; border-radius: var(--r-pill); background: var(--surface-chip); position: relative; }
.toggle__track--on { background: var(--brand); }
.toggle__knob { position: absolute; top: 2px; left: 2px; width: 20px; height: 20px; border-radius: 50%; background: #fff; transition: left .12s ease; }
.toggle__track--on .toggle__knob { left: 18px; }
/* Switch de formulário: o próprio checkbox vira o trilho (sem JS), alinhado à
   direita com o rótulo à esquerda. (Idioma de input; o `.toggle`/ToggleComponent
   é o equivalente display-only para leitura — não confundir os dois.) */
.switch { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); min-height: var(--tap); cursor: pointer; }
/* Compacto: o interruptor junto do texto (em vez de jogado na outra ponta) —
   para quando o rótulo é curto e a linha é larga (filtros, "incluir todos"). */
.switch--compact { justify-content: flex-start; width: fit-content; gap: var(--sp-2); }
.switch__input { appearance: none; -webkit-appearance: none; width: 40px; height: 24px; border-radius: var(--r-pill); background: var(--surface-chip); border: 0; position: relative; cursor: pointer; flex: none; margin: 0; transition: background .12s ease; }
.switch__input::after { content: ""; position: absolute; top: 2px; left: 2px; width: 20px; height: 20px; border-radius: 50%; background: #fff; box-shadow: var(--elev-card); transition: left .12s ease; }
.switch__input:checked { background: var(--brand); }
.switch__input:checked::after { left: 18px; }
.stepper { display: inline-flex; align-items: center; gap: var(--sp-3); }
/* O ícone fica no centro do círculo (sem flex ele senta na linha de base do texto).
   `flex: none` no botão: numa fileira apertada o círculo viraria oval, como o
   campo do número viraria uma fatia. Nada do "− n +" negocia espaço. */
.stepper__btn { width: 2rem; height: 2rem; flex: none; border-radius: 50%; border: 1.5px solid var(--border-strong); background: transparent; color: var(--text-1); font-size: 1.125rem; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center; padding: 0; line-height: 1; }
/* 20px num círculo de 32 fica apertado: o sinal respira melhor em 16. */
.stepper__btn svg.lucide { width: 16px; height: 16px; }
.stepper__btn[disabled], .stepper__btn--off { color: var(--text-disabled); border-color: var(--border); cursor: not-allowed; }
.stepper__value { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-line-num); min-width: 20px; text-align: center; }

/* Ação que vem depois do formulário da folha (ex.: limpar resultado): sem o
   respiro, os dois botões cheios ficam colados. */
.sheet__after { margin-top: var(--sp-3); }

/* ============================================================ SHEET (§4) */
/* O que abre uma folha é clicável, seja botão ou não. No celular o toque
   descobre sozinho; no computador, sem a mãozinha ninguém sabe que há algo ali
   além de ler — e só descobriria clicando (2026-08-13). Vale para todo elemento
   que abre folha, não só para os que a acabaram de ganhar. */
[data-sheet-open] { cursor: pointer; }
/* Linha de lista que abre folha (os envios de lembrete): o realce no hover diz
   "isto responde ao mouse", que o cursor sozinho só conta depois de chegar. */
.admin-row[data-sheet-open]:hover { border-color: var(--border-strong); }
.sheet-backdrop { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.55); z-index: 40; }
.sheet { position: fixed; left: 0; right: 0; bottom: 0; z-index: 41; background: var(--surface-card); border-radius: 28px 28px 0 0; padding: var(--sp-4); padding-bottom: calc(var(--sp-4) + env(safe-area-inset-bottom)); max-width: var(--app-w); margin: 0 auto; max-height: 85dvh; overflow-y: auto; }
/* Trava o scroll do fundo enquanto o sheet está aberto. */
html.is-sheet-open, html.is-sheet-open body { overflow: hidden; }
.sheet__head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); margin-bottom: var(--sp-3); }
.sheet__head .sheet__title { margin: 0; }
.sheet__handle { width: 36px; height: 4px; border-radius: 2px; background: var(--border-strong); margin: 0 auto var(--sp-3); }
.sheet__title { font-family: var(--font-display); text-transform: uppercase; font-size: var(--fs-title); }
/* Linha de situação de acesso no menu do elenco (O4): entre o título e as ações,
   com o pontinho do card em versão estática (ensina o significado do ícone). */
/* O corpo vem do `.field__hint`, que anda junto nas três folhas e já lê 12px
   desde a 2ª passagem do M-023 (quem marcou a presença é frase, não rótulo). */
.sheet__status { margin: calc(-1 * var(--sp-2)) 0 var(--sp-3); display: flex; align-items: center; gap: var(--sp-2); }
.sheet__status .roster-dot { position: static; flex-shrink: 0; }
/* Bloco de uma permissão no fim da folha do jogador (M-053): título, explicação e
   as ações daquela permissão. Separado da pilha de ações gerais por um traço, que
   é o que faz os botões dele lerem como "desta permissão", não como mais dois. */
.sheet__group { margin-top: var(--sp-4); padding-top: var(--sp-3); border-top: 1px solid var(--border); display: grid; gap: var(--sp-2); }
/* Histórico das respostas de um suplente (§6.9), recolhido: a ação vem primeiro,
   e quem quiser conferir a sequência abre. O log dentro dele rola sozinho para o
   menu não crescer sem fim. */
.sheet__history { margin-bottom: var(--sp-3); }
.sheet__history summary { cursor: pointer; display: flex; align-items: center; gap: var(--sp-2); }
.sheet__history summary svg.lucide { flex: none; }
.sheet__history .log { margin-top: var(--sp-3); max-height: 14rem; overflow-y: auto; }

/* ============================================================ TOAST (§5) */
.flash { padding: var(--sp-4) var(--gutter) 0; display: flex; flex-direction: column; gap: var(--sp-2); }
.toast { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); padding: var(--sp-3) var(--sp-4); border-radius: var(--r-md); background: var(--surface-panel); color: var(--text-1); border: 1px solid var(--border); }
.toast--success { border-color: var(--success); }
.toast--info { border-color: var(--warning); }
.toast__close { background: transparent; border: 0; color: var(--text-3); cursor: pointer; display: inline-flex; padding: 2px; }
.toast__close:hover { color: var(--text-1); }

/* Menu da conta — vive na linha do header (HeaderComponent), alinhado ao título. */
.account-menu { position: relative; z-index: 30; margin-right: -6px; }
.account-menu__btn { list-style: none; cursor: pointer; color: var(--text-2); display: inline-flex; flex-direction: column; align-items: center; gap: 2px; padding: 4px 6px; min-height: var(--tap); justify-content: center; }
.account-menu__btn::-webkit-details-marker { display: none; }
.account-menu[open] .account-menu__btn { color: var(--brand); }
/* Avatar do usuário logado como gatilho do menu. Para quem acumula os dois papéis,
   um anel na cor do contexto (laranja organizador · verde jogador) + rótulo abaixo. */
.account-menu__av { width: 30px; height: 30px; }
.account-menu[open] .account-menu__av { box-shadow: 0 0 0 2px var(--brand); }
.account-menu__btn--organizer .account-menu__av,
.account-menu[open] .account-menu__btn--organizer .account-menu__av { box-shadow: 0 0 0 2px var(--brand); }
.account-menu__btn--player .account-menu__av,
.account-menu[open] .account-menu__btn--player .account-menu__av { box-shadow: 0 0 0 2px #2F855A; }
.context-label { font-size: 0.625rem; line-height: 1; font-weight: 700; letter-spacing: .01em; }
.context-label--organizer { color: var(--brand); }
.context-label--player { color: #2F855A; }
.account-menu__pop { position: absolute; right: 0; top: calc(100% + 4px); min-width: 11.25rem; background: var(--surface-card); border: 1px solid var(--border); border-radius: var(--r-md); box-shadow: var(--elev-card); padding: var(--sp-1); display: flex; flex-direction: column; }
.account-menu__pop form { margin: 0; }
.account-menu__who { padding: var(--sp-2) var(--sp-3) var(--sp-3); border-bottom: 1px solid var(--border); margin-bottom: var(--sp-1); }
.account-menu__name { font-weight: 600; }
.account-menu__email { font-size: var(--fs-eyebrow); color: var(--text-3); word-break: break-all; }
/* Cabeçalho de um bloco à parte no menu da conta (hoje, avançar o tempo numa
   organização de teste): rótulo pequeno, com divisória acima. */
.account-menu__section { display: flex; align-items: center; gap: 5px; margin-top: var(--sp-1); padding: var(--sp-2) var(--sp-3) 2px; border-top: 1px solid var(--border); font-family: var(--font-mono); font-size: var(--fs-eyebrow); letter-spacing: .06em; text-transform: uppercase; color: var(--text-4); }
.account-menu__section svg.lucide { width: 12px; height: 12px; }
.account-menu__item { display: flex; align-items: center; gap: var(--sp-2); width: 100%; text-align: left; padding: var(--sp-2) var(--sp-3); border: 0; background: transparent; color: var(--text-1); text-decoration: none; border-radius: var(--r-sm); cursor: pointer; font: inherit; white-space: nowrap; }
.account-menu__pop .account-menu__item svg.lucide { flex: none; width: 16px; height: 16px; color: var(--text-3); }
.account-menu__item--danger svg.lucide { color: var(--down); }
.account-menu__item:hover { background: var(--surface-chip); }
.account-menu__item--danger { color: var(--down); }
/* Seletor de tema (claro/escuro/sistema): rótulo à esquerda, segmento de 3 ícones
   à direita, dentro do popup da conta. */
.account-menu__theme { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); padding: var(--sp-2) var(--sp-3); }
.account-menu__theme-label { color: var(--text-3); }
.account-menu__seg .segmented__opt { padding: 6px var(--sp-2); }
.account-menu__seg svg.lucide { width: 16px; height: 16px; }

/* ============================================================ TOOLTIP / AUTORIA (§6) */
/* `.authorship` foi apagada em 2026-08-11: existia só aqui, sem uso em view
   nenhuma. A autoria que aparece na tela é a linha "<estado> · por Fulano" das
   folhas de presença (`.field__hint .sheet__status`, § SHEET) e a do resultado,
   no card do jogo (`.game-card__author`). */

/* ============================================================ AVATAR */
.avatar { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 36px; height: 36px; border-radius: 50%; background: var(--surface-chip); overflow: hidden; }
/* A foto cobre a silhueta em vez de ficar ao lado dela: a silhueta é desenhada
   sempre, por baixo, para aparecer se a foto não carregar (M-021). */
.avatar img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.avatar--lg { width: var(--avatar-lg); height: var(--avatar-lg); }
/* Silhueta padrão: ícone centralizado, tingido pelo gênero. */
.avatar__silhouette { width: 62%; height: 62%; color: #fff; }
.avatar--male { background: var(--gender-male); }
.avatar--female { background: var(--gender-female); }
/* O neutro não redeclara fundo: é o do `.avatar` base. Só a silhueta muda de cor. */
.avatar--neutral .avatar__silhouette { color: var(--text-3); }

/* ============================================================ LOGO DO RANKING (§8.4) */
/* Redondo como o avatar, mas `contain`: o logo não pode ser cortado nas pontas
   (o enquadramento já foi decidido no recorte), e um envio sem recorte pode não
   ser quadrado. Fundo transparente — logo com fundo próprio traz o dele. */
.ranking-logo { flex: none; width: 2rem; height: 2rem; border-radius: 50%; object-fit: contain; }
/* No campo de configuração vale a medida do avatar grande: com logo ou sem, o
   círculo é o mesmo e o campo não pula de tamanho quando a imagem entra. */
.ranking-logo--field { width: var(--avatar-lg); height: var(--avatar-lg); }
/* Vazio: o círculo neutro do avatar (mesma classe), só com o ícone menor. */
.ranking-logo__empty svg { width: 40%; height: 40%; }
/* Ações do logo (enviar/remover): lado a lado, discretas — o "Salvar" grande da
   tela é o da configuração, e o logo não passa por ele. (O `display: contents` do
   form do "Remover" está junto do toggle de RSVP, que resolve o mesmo.) */
.logo-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-4); }
/* Ação secundária de um campo (ex.: "Remover logo"): link discreto, não botão
   de tela. Em cinza, porque destruir não se anuncia em laranja (2026-08-06). */
.link-reset--muted { color: var(--text-3); }

/* Controle de foto do perfil (J5): preview + "Escolher foto". */
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
/* Campo de imagem (ui-spec §3.1). O logo da marca (brand_items) tem controle
   próprio: lá a imagem é uma cartela retangular, e fica ao LADO das ações. */
/* Imagem EM CIMA e origens embaixo, lado a lado, dividindo a linha em partes
   iguais. A foto centraliza sobre elas; num formulário alinhado à esquerda o
   centro só não lê como desalinho porque o bloco de baixo é cheio (trocar
   `align-items` para flex-start devolve tudo à esquerda). Quando as duas não
   cabem na largura, quebram e empilham em vez de espremer o texto. */
.photo-field__control { display: flex; flex-direction: column; align-items: center; gap: var(--sp-3); }
.photo-field__preview { flex: none; position: relative; }
.photo-field__preview .avatar { width: var(--avatar-lg); height: var(--avatar-lg); }
/* Origens da foto: são os botões do app (.btn--secondary), com o que é próprio
   delas aqui — dividir a linha e ficar MARCADA, porque origem é ação que também
   é estado. Por isso a pílula, e não o interruptor, que é idioma de preferência. */
.photo-field__sources { display: flex; flex-wrap: wrap; gap: var(--sp-2); width: 100%; min-width: 0; }
.photo-field__sources .photo-field__source { flex: 1 1 auto; min-width: fit-content; }
.photo-field__source { cursor: pointer; }
.photo-field__source svg.lucide { width: 16px; height: 16px; color: var(--brand); }
.photo-field__source:has(:checked) { border-color: var(--brand); background: color-mix(in srgb, var(--brand) 8%, transparent); }
/* A foto do Google fica empilhada sobre a silhueta e só aparece quando a origem
   dela está marcada: o avatar do campo é sempre a verdade do que vai ser salvo,
   e isso vale sem JavaScript. */
.photo-field__google { position: absolute; top: 0; left: 0; display: none; }
.photo-field:has([data-google-photo]:checked) .photo-field__google { display: inline-flex; }
.photo-field__meta { min-width: 0; }
.photo-field__hint { font-size: var(--fs-meta); }

/* Cropper (bottom-sheet): canvas quadrado sob uma máscara circular. */
.cropper { position: relative; width: 100%; max-width: 17.5rem; aspect-ratio: 1; margin: 0 auto var(--sp-3); }
.cropper__canvas { width: 100%; height: 100%; display: block; border-radius: var(--r-card); background: #000; touch-action: none; cursor: grab; }
/* Ajustando um logo (fundo transparente, tinta escura): o preto esconderia o
   desenho. O fundo vira o papel creme da arte de export, que é onde ele vai cair. */
.cropper__canvas--paper { background: #FBF5EA; }
.cropper__canvas:active { cursor: grabbing; }
/* Máscara: escurece fora do círculo inscrito (sombra com grande espalhamento). */
.cropper__mask { position: absolute; inset: 0; margin: auto; width: 100%; height: 100%; border-radius: 50%; box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5); pointer-events: none; }
.cropper__zoom { display: flex; align-items: center; gap: var(--sp-2); color: var(--text-2); margin-bottom: var(--sp-3); }
.cropper__zoom input[type="range"] { flex: 1; accent-color: var(--brand); }
.cropper__actions { display: flex; gap: var(--sp-2); }

/* ============================================================ STATE BADGE (§7) */
.badge { display: inline-flex; align-items: center; gap: 6px; padding: 3px var(--sp-2); border-radius: var(--r-pill); font-size: var(--fs-eyebrow); text-transform: uppercase; letter-spacing: .04em; border: 1px solid var(--border); color: var(--text-2); }
.badge--draft { color: var(--text-3); }
.badge--ongoing { color: var(--brand); border-color: var(--brand); }
.badge--awaiting_tiebreak { color: var(--warning); border-color: var(--warning); }
.badge--closed { color: var(--text-2); }
/* Modo teste (M-030): não é estado do ranking, é um aviso. Laranja preenchido,
   como a tarja do app e o selo das artes: os três avisos falam a mesma língua, e
   o preenchido dispensa calibrar a cor do texto para fundo claro e escuro (o
   selo aparece nos dois, e o card publicado reabre o escuro). */
.badge--test { background: var(--brand); border-color: var(--brand); color: #fff; }

/* Tarja de teste (M-030): a faixa diagonal no canto superior direito de quem está
   dentro de um ranking de demonstração. Fica presa à tela (não rola) e alinhada à
   coluna do app (`--app-w`, que cresce no desktop): ancorada na janela, ela
   pousaria longe do conteúdo. O recorte quadrado com `overflow` transforma a
   faixa girada num triângulo. Fica ACIMA do menu da conta (z-index 30): a tarja
   passa por cima da foto, que é o ponto dela (decisão do usuário 2026-08-05). */
.test-ribbon { position: fixed; top: 0; left: 50%; transform: translateX(-50%); width: min(var(--app-w), 100%); height: 7rem; overflow: hidden; pointer-events: none; z-index: 35; }
/* Com o menu da conta aberto, a tarja sai da frente: passar por cima da foto é
   aviso; passar por cima da lista de opções é atrapalhar. */
body:has(.account-menu[open]) .test-ribbon { opacity: 0; }
.test-ribbon__label { position: absolute; top: 1.3rem; right: -2.6rem; width: 11rem; padding: 3px 0; transform: rotate(45deg); background: var(--brand); color: #fff; text-align: center; font-family: var(--font-mono); font-size: var(--fs-eyebrow); letter-spacing: .16em; text-transform: uppercase; box-shadow: var(--elev-card); }

/* ============================================================ PLAYER ROW */
.player-row { display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-2) var(--sp-3); background: var(--surface-card); border: 1px solid var(--border); border-radius: var(--r-md); box-shadow: var(--elev-card); }
.player-row__pos { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-line-num); width: 24px; color: var(--text-2); }
.player-row__pos svg.lucide { width: 16px; height: 16px; } /* linha sem número (ex.: data abandonada no Calendário) */
/* Estas duas sobraram para a página de contraste (`dev/contrast`), que copia a
   marcação à mão. A UI viva usa só `.player-row` e `.player-row__pos` (linhas do
   calendário e da prioridade). */
.player-row__name { flex: 1; }
.player-row__num { font-family: var(--font-num); font-variant-numeric: tabular-nums; }

/* ============================================================ LIST CARD (cockpit C1) */
/* Lista em card único com linhas divididas (design agon-substitutes / agon-player-row):
   fila de fixos e suplentes. Círculo numerado colorido por status + status inline. */
.list-card { background: var(--surface-card); border: 1px solid var(--border); border-radius: var(--r-lg); overflow: hidden; box-shadow: var(--elev-card); }
.list-card__row { display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-3); border-bottom: 1px solid var(--border); }
.list-card__row:last-child { border-bottom: 0; }
.list-card__info { flex: 1; min-width: 0; }
.list-card__name { font-size: var(--fs-strong); font-weight: 700; }
.list-card__meta { font-size: var(--fs-meta); color: var(--text-3); margin-top: 2px; display: flex; align-items: center; gap: 5px; flex-wrap: wrap; }
.list-card__actions { display: flex; align-items: center; gap: var(--sp-2); flex: none; }
.list-card__actions form { margin: 0; }
.list-card__actions .btn--icon { width: 38px; min-width: 38px; }
.list-card__actions svg.lucide { width: 18px; height: 18px; }
/* Estado da linha no calendário: já realizada esmaecida, a da vez em destaque. */
.list-card__row--queued { border-left: 3px solid var(--brand); }
.list-card__row--out { opacity: .5; }
/* Selo de recomendação (escolha da combinação) e de suplente escalado (resposta
   rápida do próprio jogador). */
.tag-called, .tag-available { display: inline-flex; align-items: center; justify-content: center; gap: 4px; line-height: 1; align-self: center; font-family: var(--font-mono); font-size: 0.5rem; letter-spacing: .06em; text-transform: uppercase; font-weight: 700; padding: 6px 10px; border-radius: var(--r-pill); flex: none; }
.tag-called svg.lucide, .tag-available svg.lucide { width: 11px; height: 11px; }
.tag-called { background: var(--brand); color: #fff; }
.tag-available { background: transparent; color: var(--brand); border: 1px solid var(--brand); }
.tag-g { font-family: var(--font-mono); font-size: 0.5625rem; letter-spacing: .06em; text-transform: uppercase; color: var(--text-3); }
.st { display: inline-flex; align-items: center; gap: 3px; }
.st svg.lucide { width: 13px; height: 13px; }
.st--ok { color: var(--success); }

/* ============================================================ STAGE CARD */
/* Card também é usado como link (dashboard): texto em tinta, sem sublinhado e
   sem herdar o laranja de link — o laranja fica só para ação/destaque. */
.stage-card { background: var(--surface-card); border: 1px solid var(--border); border-radius: var(--r-lg); padding: var(--sp-4); display: flex; flex-direction: column; gap: var(--sp-2); box-shadow: var(--elev-card); color: var(--text-1); text-decoration: none; }
.stage-card--ongoing { border-color: var(--brand); box-shadow: var(--glow); }
.stage-card--pending { border-style: dashed; }
/* Combinação já jogada (M-025): contorno verde, o mesmo "pronto" da bolinha da
   etapa realizada no trilho. */
.stage-card--done { border-color: var(--success); }
/* Destaque de necessidade de suplente no cockpit (§6.9): atenção vs tudo certo. */
.stage-card--warn { border-color: var(--warning); border-left-width: 4px; }
.stage-card--warn .stage-card__title { color: var(--warning); }
.stage-card--ok { border-color: var(--success); border-left-width: 4px; }
.stage-card--ok .stage-card__title { color: var(--success); }
.stage-card__title { font-family: var(--font-display); font-size: var(--fs-title); }
.stage-card__title--sm { font-size: var(--fs-strong); display: flex; align-items: center; gap: var(--sp-2); }
.stage-card__title--sm svg.lucide { width: 18px; height: 18px; flex: none; }
/* Data de uma etapa que ainda não aconteceu ("Próximas"): apoio do título, não
   título — a data inteira em peso de título pesava demais (2026-08-04). */
.stage-card__when { font-size: var(--fs-meta); color: var(--text-2); }
/* Como está a confirmação, no card da etapa aberta (§6.9): verde = dá para
   sortear; âmbar = falta gente. Substituiu o banner de recomendação nesta tela. */
/* Dentro dos cards quem espaça é o `gap`: a margem que o navegador dá ao <p>
   (1em em cima e embaixo) se somava a ele e esparramava o card. */
.stage-card p, .roster-group__notes p { margin: 0; }
/* Os dois lados do estado dividem a linha: quanta gente entra (esquerda) e o que
   trava (direita). Em tela estreita, quebram e empilham. */
.stage-card__statuses { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--sp-1) var(--sp-2); }
.stage-card__status { display: flex; align-items: center; gap: var(--sp-1); font-size: var(--fs-meta); color: var(--text-2); }
.stage-card__status svg.lucide { width: 15px; height: 15px; flex: none; }
.stage-card__status--ok { color: var(--success); }
.stage-card__status--warn { color: var(--warning); }
/* Atalhos do card lado a lado (confirmar presenças · histórico): empilhados,
   esticavam o card da etapa aberta sem necessidade. */
.stage-card__links { display: flex; flex-wrap: wrap; justify-content: space-between; gap: var(--sp-3); }

/* Card de regra (C3): mesmo card, mas de leitura — informa e não navega, por isso
   sem ícone no título (ícone ali vem lendo como "toque aqui"). */
.rule__body { color: var(--text-2); }
.rule__body p { margin: 0 0 var(--sp-2); }
.rule__body p:last-child { margin-bottom: 0; }
/* Título da seção do regulamento com os controles do organizador à direita. */
.rule-section__head, .rule-card__head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); }
/* Marca do card no modo de edição (só para quem é do ranking). Fica no pé do card
   e some na leitura — para quem lê, é tudo regulamento. */
.rule-card__tags { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-top: var(--sp-1); }
/* Marca do card no regulamento: é o `.badge` do DS, em mono e tingido pelo próprio
   texto (o estado é a cor). */
.rule-tag { font-family: var(--font-mono); gap: 5px; border-color: currentColor; }
.rule-tag svg.lucide { width: 12px; height: 12px; }
.rule-tag--restricted, .rule-tag--pending { color: var(--warning); }
/* A versão que os jogadores estão lendo: a única na cor da marca. */
.rule-tag--live { color: var(--brand); }
/* Botão flutuante de acrescentar, no modo de edição do regulamento: fica à mão
   sem exigir rolagem até o fim da lista. Preso à tela, mas ancorado na borda da
   COLUNA (`--app-w`), não na da janela: no desktop a coluna é centrada e um botão
   colado na janela boiaria no fundo vazio, longe da lista a que ele acrescenta.
   A conta é o gutter mais a sobra à esquerda da coluna (zero no celular, onde a
   coluna é a tela inteira). */
.fab { position: fixed; right: calc(var(--gutter) + max(0px, 50% - var(--app-w) / 2)); bottom: calc(var(--gutter) + env(safe-area-inset-bottom)); z-index: 30;
       width: 56px; height: 56px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
       background: var(--brand); color: #fff; border: 0; box-shadow: var(--elev-card); }
.fab svg.lucide { width: 26px; height: 26px; }
.rule-form__actions, .rule-card__controls { display: flex; flex-direction: column; gap: var(--sp-1); }
/* Ações do card ficam empilhadas e coladas, separadas do texto por uma linha. */
.rule-card__controls { margin-top: var(--sp-2); border-top: 1px solid var(--border); padding-top: var(--sp-2); }
/* Campo curto com o botão ao lado (nome da seção). */
.rule-inline { display: flex; align-items: flex-start; gap: var(--sp-2); }
.rule-inline .input { flex: 1; }

/* ============================================================ RANKING CARD (dashboard O1) */
/* Duas faces (design hi-fi): publicado = card escuro ("disputa"), independente
   do tema claro de gestão; rascunho = card claro tracejado ("não publicado"). */
.ranking-card { display: flex; flex-direction: column; gap: var(--sp-2); border-radius: var(--r-xl); padding: 14px 15px; text-decoration: none; box-shadow: var(--elev-card); }
.ranking-card__head { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--sp-2); }
/* Logo + nome andam juntos; o chip de categoria segue na outra ponta. */
.ranking-card__id { display: flex; align-items: center; gap: var(--sp-2); min-width: 0; }
/* Última linha: contagem à esquerda, selo de teste (quando há) à direita. */
.ranking-card__foot { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); }
/* O logo é a identidade do ranking no meio da lista: pequeno demais ele vira
   enfeite ao lado do nome. Cresce até a altura de duas linhas do título, que é o
   tamanho em que se reconhece o ranking sem ler. */
.ranking-card__mark { width: 2.75rem; height: 2.75rem; }
.ranking-card__name { font-family: var(--font-display); font-weight: 800; text-transform: uppercase; font-size: 1.0625rem; }
.ranking-card__org, .ranking-card__info { margin-top: 2px; }

/* Publicado / em disputa — reabre o tema escuro (data-theme="dark" no card), então
   usa os tokens normalmente em vez de hexes fixos. */
.ranking-card--active { background: var(--surface-card); color: var(--text-1); border: 1px solid var(--border); }
.ranking-card--active .ranking-card__name { color: #fff; }
.ranking-card--active .ranking-card__status { color: var(--text-3); }
.ranking-card--active .ranking-card__live { color: var(--brand); }
.ranking-card--active .ranking-card__count { font-size: 0.6875rem; color: var(--text-3); }
.ranking-card__live svg.lucide { width: 12px; height: 12px; vertical-align: -1px; }

/* Acento semântico por estado (mesma família escura — "tom + acento"):
   andamento = laranja (default acima); aguardando desempate = âmbar (atenção);
   encerrado = preto mais fundo + dourado/troféu e texto esmaecido (temporada campeã). */
.ranking-card--awaiting_tiebreak .ranking-card__live { color: var(--warning); }
.ranking-card--closed { background: var(--surface-deep); }
.ranking-card--closed .ranking-card__name { color: var(--text-2); }
.ranking-card--closed .ranking-card__live { color: var(--gold); }
.ranking-card--closed .ranking-card__status, .ranking-card--closed .ranking-card__count { color: var(--text-4); }

/* Rascunho / lobby — card claro tracejado, gestão. */
.ranking-card--draft { background: var(--surface-card); border: 1.5px dashed var(--border-strong); color: var(--text-1); }
.ranking-card--draft .ranking-card__meta { font-size: var(--fs-meta); color: var(--text-2); }

/* ============================================================ HUB (setup do ranking O8/setup) */
.hub-cards { display: flex; flex-direction: column; gap: var(--sp-2); }

/* ============================================================ PÓDIO (Classificação O9) */
/* Pódio de avatares com medalha (hi-fi): 1º ao centro/maior (marca + glow), 2º
   prata, 3º bronze; card com o troféu e o escopo no topo. Alinhados pela base,
   o 1º (maior) sobressai como no pódio. */
/* Card do pódio — `data-theme="dark"` no elemento o torna preto por si só, então
   fixa a cor do texto pelos tokens (senão herda o texto claro da página). */
.podium-card { background: var(--surface-card); color: var(--text-1); border: 1px solid var(--border); border-radius: var(--r-lg); padding: var(--sp-4) var(--sp-3) 0; display: flex; flex-direction: column; gap: var(--sp-3); overflow: hidden; }
/* Cor por estado (§6.10), reusada no card do pódio e na explicação dos status:
   provisório e final = marca (hi-fi); parcial oficial = verde; desempate = âmbar. */
.state-color--live, .state-color--final { color: var(--brand); }
.state-color--official { color: var(--success); }
.state-color--warn { color: var(--warning); }
/* Pódio em degraus (§6.10): 2º · 1º · 3º; cada colocação num bloco com faixa de
   cor (ouro/prata/bronze) e o número grande ao centro; líder com troféu no canto
   do avatar. Degraus proporcionais (3º = 2 unidades, 2º = 3, 1º = 4,5): a razão
   fica só no `--podium-units`, e o número acompanha a altura do degrau. */
.podium { --podium-step: 18px; --podium-num-scale: 0.9; --podium-num-nudge: 0.02em; display: flex; align-items: flex-end; justify-content: center; gap: var(--sp-2); }
.podium__spot { --podium-block-h: calc(var(--podium-step) * var(--podium-units));
  display: flex; flex-direction: column; align-items: center; width: 33%; }
.podium__person { display: flex; flex-direction: column; align-items: center; gap: 2px; margin-bottom: var(--sp-2); text-align: center; }
.podium__av-wrap { position: relative; }
.podium__av { width: 44px; height: 44px; border: 2px solid var(--border-strong); }
.podium__spot--1 .podium__av { width: 58px; height: 58px; border-color: var(--gold); }
.podium__spot--2 .podium__av { border-color: var(--silver); }
.podium__spot--3 .podium__av { border-color: var(--bronze); }
/* Selo de troféu do líder (ícone Lucide escuro sobre disco ouro). */
.podium__badge { position: absolute; right: -4px; top: -4px; width: 24px; height: 24px; border-radius: 50%; background: var(--gold); border: 2px solid var(--surface-card); display: grid; place-items: center; }
.podium__badge svg.lucide { width: 13px; height: 13px; color: var(--surface-card); }
.podium__name { font-weight: 700; font-size: var(--fs-body); }
.podium__spot--1 .podium__name { font-size: var(--fs-strong); }
.podium__wins { font-family: var(--font-num); font-weight: 800; font-size: var(--fs-strong); }
.podium__wins small { font-family: var(--font-mono); font-size: 0.5rem; color: var(--text-3); letter-spacing: 0.06em; margin-left: 2px; }
.podium__spot--1 .podium__wins { font-size: var(--fs-line-num); }
.podium__spot--1 .podium__wins, .podium__spot--1 .podium__block-num { color: var(--gold); }
.podium__spot--2 .podium__wins, .podium__spot--2 .podium__block-num { color: var(--silver); }
.podium__spot--3 .podium__wins, .podium__spot--3 .podium__block-num { color: var(--bronze); }
.podium__block { width: 100%; height: var(--podium-block-h); border-radius: 10px 10px 0 0; position: relative; overflow: hidden;
  display: grid; place-items: center; }
.podium__block::before { content: ""; position: absolute; top: 0; left: 0; right: 0; height: 4px; }
/* O número ocupa quase toda a altura do degrau, com a MESMA folga em cima e
   embaixo: quem centraliza é o `place-items` do bloco (com `line-height:1`, a
   caixa cola no texto e as sobras ficam simétricas). O `--podium-num-scale` passa
   de 1 porque o dígito ocupa ~72% do corpo da fonte; baixá-lo aumenta a folga. */
.podium__block-num { font-family: var(--font-display); font-weight: 800; opacity: 0.32;
  line-height: 1; font-size: calc(var(--podium-block-h) * var(--podium-num-scale));
  /* Centrar a CAIXA do texto não centra o dígito: pelas métricas da fonte display
     ele fica um tico alto (medido na tela: ~3px a mais embaixo). O empurrãozinho
     acerta isso — positivo desce, negativo sobe; é o valor a rever se a fonte
     mudar. */
  transform: translateY(var(--podium-num-nudge)); }
/* O "º" sobe por transform (não por vertical-align): assim ele não estica a caixa
   da linha para cima, que era o que deixava a folga de cima maior que a de baixo. */
.podium__block-num::after { content: "º"; font-size: 0.3em; display: inline-block;
  transform: translateY(-1.6em); }
.podium__spot--1 { --podium-units: 4.5; }
.podium__spot--1 .podium__block { background: color-mix(in srgb, var(--gold) 15%, var(--surface-card)); }
.podium__spot--1 .podium__block-num { opacity: 0.42; }
.podium__spot--1 .podium__block::before { background: var(--gold); }
.podium__spot--2 { --podium-units: 3; }
.podium__spot--2 .podium__block { background: color-mix(in srgb, var(--silver) 12%, var(--surface-card)); }
.podium__spot--2 .podium__block::before { background: var(--silver); }
.podium__spot--3 { --podium-units: 2; }
.podium__spot--3 .podium__block { background: color-mix(in srgb, var(--bronze) 13%, var(--surface-card)); }
.podium__spot--3 .podium__block::before { background: var(--bronze); }

/* Fundo de foto do card do pódio (M-043). A foto ocupa a FAIXA DE CIMA do card e
   morre no preto antes dos degraus: céu e sol ficam atrás dos avatares, enquanto
   nomes, vitórias e os números de ouro/prata/bronze continuam sobre preto sólido,
   com a mesma legibilidade do card preto. Foi o tratamento que ganhou entre os
   cinco testados no mock (2026-08-25). A cor do card não muda — a foto é uma
   camada por cima dela, e sem foto nada disso existe.
   `--veil-ink` são os canais do quase-preto do véu (a paleta não tem essa cor, e
   ela aparece em vários pontos com opacidades diferentes): declarada uma vez, para
   acertar o véu não virar caça a literal. */
.podium-card--photo, .podium-preview { --veil-ink: 13 10 7; }
.podium-card--photo { position: relative; }
.podium-card--photo > .podium { position: relative; z-index: 2; }
.podium-card__photo, .podium-preview__photo { background-size: cover; background-position: center; }
.podium-card__photo { position: absolute; top: 0; left: 0; right: 0; height: 64%; }
/* O véu faz duas coisas: o degradê apaga a foto antes dos degraus, e a vinheta
   escurece os cantos de cima, onde ficam os nomes do 2º e do 3º. É pseudo-elemento
   do card (e não um span) porque é decoração pura, e porque precisa cobrir o card
   inteiro, não só a faixa da foto. */
.podium-card--photo::after { content: ""; position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(180deg, rgb(var(--veil-ink) / 0.14) 0%, rgb(var(--veil-ink) / 0.30) 26%,
                              rgb(var(--veil-ink) / 0.80) 46%, var(--surface-card) 60%),
              radial-gradient(120% 70% at 50% 0%, transparent 45%, rgb(var(--veil-ink) / 0.55) 100%); }

/* Uma regra por foto do conjunto (Podium::Background::PHOTOS, que lista os cinco
   lugares a mexer ao acrescentar uma). Cada arquivo já vem recortado na faixa que
   serve; o ajuste fino de cada uma mora aqui, que é o que um conjunto fechado
   permite e um upload livre não permitiria. A mesma classe serve o card e a
   prévia da configuração. */
.podium-photo--sunset-sea { background-image: url("/assets/podium/sunset-sea-23b6c0fc.jpg"); }
.podium-photo--sunset-net { background-image: url("/assets/podium/sunset-net-4c739103.jpg"); }
.podium-photo--dawn-beach { background-image: url("/assets/podium/dawn-beach-4e5cd5b1.jpg"); }
/* A mais clara das quatro (sol de frente batendo nas árvores): sozinha ela pede
   mais escuro, senão o nome do 2º lugar cai em cima do clarão esverdeado. O
   escurecimento é uma demão empilhada na PRÓPRIA imagem, e não um `filter`: filtro
   pegaria também o véu (que é pseudo-elemento) e a prévia sairia diferente do card. */
.podium-photo--lakeside-court {
  background-image: linear-gradient(rgb(var(--veil-ink) / 0.55), rgb(var(--veil-ink) / 0.55)),
                    url("/assets/podium/lakeside-court-259a1063.jpg"); }

/* Prévia do fundo na configuração (M-043): a miniatura da foto reservada para a
   próxima etapa, escurecida o bastante para o rótulo se ler. Não é uma maquete do
   card (a faixa do card mora numa altura que 4rem não reproduzem): é o retrato da
   FOTO. As quatro ficam empilhadas e o CSS revela uma; o véu é pseudo-elemento da
   foto visível, então no preto sobra a cor real do card, sem degradê sobre nada. */
.podium-preview { position: relative; height: 4rem; margin-top: var(--sp-2); overflow: hidden;
  border-radius: var(--r-md); background: var(--surface-card); display: flex; align-items: flex-end; }
.podium-preview__photo { position: absolute; inset: 0; display: none; }
.podium-preview__photo::after { content: ""; position: absolute; inset: 0;
  background: linear-gradient(180deg, rgb(var(--veil-ink) / 0.15), rgb(var(--veil-ink) / 0.75)); }
.podium-preview__label { position: relative; padding: var(--sp-2) var(--sp-3);
  font-family: var(--font-mono); font-size: var(--fs-eyebrow); letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--text-1); }

/* A prévia acompanha o select na hora, sem JS (`:has`, como o bloco do tie-break
   e o toggle de suplentes). A foto RESERVADA vem marcada do servidor e é o padrão:
   é o que vale no sorteio (só o salvar decide qual sai) e o que aparece num
   navegador sem `:has`. Escolha explícita manda: a 1ª regra esconde todas e a 2ª
   revela a da opção marcada — as duas empatam em especificidade, então é a ORDEM
   no arquivo que faz valer; não inverta. */
.podium-preview__photo[data-reserved] { display: block; }
.podium-background-field:not(:has(option[value="random"]:checked)) .podium-preview__photo { display: none; }
.podium-background-field:has(option[value="sunset_sea"]:checked) .podium-photo--sunset-sea,
.podium-background-field:has(option[value="sunset_net"]:checked) .podium-photo--sunset-net,
.podium-background-field:has(option[value="dawn_beach"]:checked) .podium-photo--dawn-beach,
.podium-background-field:has(option[value="lakeside_court"]:checked) .podium-photo--lakeside-court { display: block; }
/* O rótulo tem três redações; o CSS decide entre sorteio e escolha, e o servidor
   já resolveu qual das duas de sorteio vai dentro (ver o parcial da prévia). */
.podium-preview__label [data-label] { display: none; }
.podium-background-field:has(option[value="random"]:checked) [data-label="random"],
.podium-background-field:not(:has(option[value="random"]:checked)) [data-label="fixed"] { display: inline; }

/* Classificação (O9): cabeçalho das colunas + tinte da linha por variação (▲ verde,
   ▼ vermelho; líder já vem do `--lead`). Toggle "Incluir suplentes" (CSS :has, sem
   JS) revela os suplentes esmaecidos + tag "Suplente". */
.sec-title { font-family: var(--font-display); font-weight: 800; text-transform: uppercase; font-size: var(--fs-title); margin: 0; }
.rank-block .sec-title { margin-bottom: var(--sp-4); } /* respiro entre "Classificação" e a tabela */
/* Navegação entre as classificações das etapas (§6.10): ‹ Etapa N de T ›. */
.stage-nav { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); background: var(--surface-panel); border: 1px solid var(--border); border-radius: var(--r-pill); padding: 3px 5px; }
.stage-nav__btn { display: inline-flex; align-items: center; justify-content: center; width: 38px; height: 38px; border-radius: 50%; color: var(--text-1); }
.stage-nav__btn--off { color: var(--text-disabled); pointer-events: none; }
.stage-nav__label { font-family: var(--font-mono); font-size: var(--fs-eyebrow); text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-2); display: inline-flex; align-items: center; gap: 6px; }
.stage-nav__sep { color: var(--text-4); }
.stage-nav__status { font-weight: 700; } /* cor via state-color--* */
/* Cabeçalho da classificação pública (P1): a página chega por link e não tem barra
   de app, então o nome do ranking é o título dela (não a linha de identidade discreta
   das telas de dentro). Layout pelo `.row`; aqui o nome longo QUEBRA em mais linhas
   com o chip ao lado (na linha de identidade ele trunca — lá é apoio, aqui é título). */
.public-head { justify-content: center; text-align: center; }
.public-head .eyebrow { flex-basis: 100%; margin: 0; }
/* Marca + nome centrados e grandes, com a categoria embaixo: o topo desta página
   é a identidade do ranking, como no cabeçalho das artes (decisão do usuário
   2026-08-05). Aqui o nome pode quebrar à vontade — é HTML, não imagem. */
.public-head__id { flex-basis: 100%; display: flex; align-items: center; justify-content: center; gap: var(--sp-3); }
/* O corpo do nome e a marca acompanham a largura da tela (decisão do usuário
   2026-08-05): no celular ficam perto do tamanho de antes, para um nome comprido
   não virar três linhas ao lado da marca; da tela média em diante crescem até o
   corpo de destaque. `min-width: 0` porque o nome, como item flex, se recusaria a
   encolher e transbordaria. */
.public-head__name { font-size: clamp(var(--fs-title), 6vw, var(--fs-display)); line-height: 1.05; min-width: 0; }
.public-head__mark { width: clamp(2.5rem, 9vw, 3.25rem); height: clamp(2.5rem, 9vw, 3.25rem); }
/* Aviso de ranking de teste (M-030): quem chega aqui pelo link não conhece o
   app, então o selo vem acompanhado da frase. Compõe `.row` (a linha padrão);
   aqui só o que é próprio: centralizar e o corpo de legenda. */
.public-test-note { justify-content: center; font-size: var(--fs-caption); color: var(--text-2); text-align: center; }
/* Topo da página que chega por link: o rótulo à esquerda e, à direita, o "Entrar"
   de quem joga no ranking (M-026). Discreto — a pessoa veio ver a tabela. A área
   de toque vem do padding, não do tamanho do texto. */
.public-head__top { flex-basis: 100%; display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); }
.public-head__top .eyebrow { flex-basis: auto; }
.public-head__enter { flex: none; padding: var(--sp-1) 0 var(--sp-1) var(--sp-3); margin: calc(-1 * var(--sp-1)) 0; font-size: var(--fs-eyebrow); font-weight: 700; color: var(--brand); text-decoration: none; }
/* Explicação do status, logo abaixo da barra (§6.10). */
.stage-nav__help { margin: var(--sp-2) 2px 0; font-size: var(--fs-caption); color: var(--text-2); line-height: 1.45; }
/* Ações da Classificação (compartilhar/exportar): respiro maior da tabela acima
   (a margem soma ao gap do .stack). */
.board-actions { display: flex; flex-direction: column; gap: var(--sp-2); margin-top: var(--sp-4); }
/* "Critério de desempate (?)": expander (sem JS) com a cadeia de critérios (§6.10).
   Respiro maior em relação à tabela: é rodapé de ajuda, não parte da lista. */
.sec-help { margin-top: var(--sp-5); }
.sec-help summary { list-style: none; cursor: pointer; display: inline-flex; align-items: center; gap: 5px; color: var(--text-3); font-family: var(--font-mono); font-size: var(--fs-eyebrow); letter-spacing: .06em; text-transform: uppercase; }
.sec-help summary::-webkit-details-marker { display: none; }
.sec-help summary svg.lucide { width: 13px; height: 13px; }
.sec-help__list { margin: var(--sp-2) 0 var(--sp-1); padding-left: 18px; font-size: var(--fs-meta); color: var(--text-2); display: flex; flex-direction: column; gap: 2px; }
.sec-help__list .is-current { font-weight: 700; } /* status atual em destaque (a cor vem do estado) */
.sec-help > p { margin: 0; font-size: var(--fs-meta); color: var(--text-3); }
/* Posições ordinais (1º, 2º… 10º) e pódio prata/bronze — escopados ao container
   `.standings`, então valem igual na aba Classificação E na parcial do cockpit
   (mesma tabela). O suplente não tem posição, então fica de fora. */
.standings .standings__pos { width: 26px; }
.standings .standings__row:not(.standings__row--sub) .standings__pos::after { content: "º"; }
/* Sobe/desce não tinge a linha — a variação fica só na setinha ▲▼ (decisão 2026-07-21). */
/* Pódio na lista: posição, contorno da foto e vitórias em prata (2º) / bronze
   (3º) — iguais ao pódio, no mesmo padrão do 1º (marca). */
.standings .standings__row--p2 .standings__pos,
.standings .standings__row--p2 .standings__wins { color: var(--silver); }
.standings .standings__row--p2 .standings__av { border: 2px solid var(--silver); }
.standings .standings__row--p3 .standings__pos,
.standings .standings__row--p3 .standings__wins { color: var(--bronze); }
.standings .standings__row--p3 .standings__av { border: 2px solid var(--bronze); }
/* Faixa lateral na cor da medalha no top 3 (1º ouro via --lead) — decisão 2026-07-21. */
.standings .standings__row--lead { box-shadow: inset 3px 0 0 var(--gold); }
.standings .standings__row--p2 { box-shadow: inset 3px 0 0 var(--silver); }
.standings .standings__row--p3 { box-shadow: inset 3px 0 0 var(--bronze); }
.rank-block .switch { margin: var(--sp-1) 0 var(--sp-2); }
.standings__row--sub { opacity: .68; }
/* Ocultos por padrão (2 classes p/ vencer o `.standings__row { display:grid }`);
   o toggle os revela via :has. */
.rank-block .standings__row--sub { display: none; }
.rank-block:has(.switch__input:checked) .standings__row--sub { display: grid; }
.tag-sub { font-family: var(--font-mono); font-size: 0.5rem; letter-spacing: .06em; text-transform: uppercase; color: var(--text-3); background: var(--surface-chip); border-radius: var(--r-pill); padding: 1px 5px; margin-left: 6px; }

/* ============================================================ NAV (header / bottom nav) */
.app-header { display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-3) var(--gutter); }
/* Ícones do header (voltar e conta) maiores, equilibrados com o título nas duas pontas. */
.app-header svg.lucide { width: 26px; height: 26px; }
.app-header__back { display: inline-flex; align-items: center; background: transparent; border: 0; color: var(--text-2); cursor: pointer; }
.app-header__title { font-family: var(--font-display); font-size: var(--fs-title); flex: 1; }
/* A nav usa a MESMA cor de fundo da tela ativa (DS §17: a cor segue o tema da
   tela) — creme na gestão, escuro na disputa; a separação vem só da borda. */
.bottom-nav { position: fixed; left: 0; right: 0; bottom: 0; max-width: var(--app-w); margin: 0 auto; display: flex; background: var(--surface-app); border-top: 1px solid var(--border); }
.bottom-nav__tab { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 2px; padding: var(--sp-2) 0; color: var(--text-3); text-decoration: none; font-size: var(--fs-eyebrow); }
.bottom-nav__tab--active { color: var(--brand); }

/* ============================================================ STATES (transversais) */
.state { display: flex; flex-direction: column; align-items: center; gap: var(--sp-2); padding: var(--sp-8) var(--sp-4); text-align: center; color: var(--text-3); }
.state__icon { width: 56px; height: 56px; border-radius: 50%; background: var(--surface-chip); display: flex; align-items: center; justify-content: center; color: var(--text-3); margin-bottom: var(--sp-1); }
.state__icon svg.lucide { width: 26px; height: 26px; }
/* Ícone em destaque (marca) — ex.: troféu da temporada encerrada. */
.state__icon--brand { color: var(--brand); background: color-mix(in srgb, var(--brand) 16%, var(--surface-chip)); box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand) 22%, transparent); }
.state__title { color: var(--text-2); font-size: var(--fs-strong); }
.state--error .state__title { color: var(--danger); }
.spinner { width: 24px; height: 24px; border-radius: 50%; border: 3px solid var(--surface-chip); border-top-color: var(--brand); animation: spin .8s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }

/* ============================================================ AUTH (login / onboarding — tema claro) */
.auth-shell {
  max-width: var(--col-max); margin: 0 auto; min-height: 100dvh;
  display: flex; flex-direction: column; justify-content: center;
  gap: var(--sp-6); padding: var(--gutter);
}
.auth-brand { display: flex; flex-direction: column; align-items: center; gap: var(--sp-2); }
.auth-logo { width: 4.5rem; height: 4.5rem; }
.auth-wordmark { font-family: var(--font-display); font-weight: 800; font-size: var(--fs-title); letter-spacing: .01em; color: var(--text-1); }
.auth-card {
  background: var(--surface-card); border: 1px solid var(--border);
  border-radius: var(--r-lg); box-shadow: var(--elev-card);
  padding: var(--sp-5); display: flex; flex-direction: column; gap: var(--sp-4);
}
.auth-card__title { font-family: var(--font-display); font-size: var(--fs-title); }
.auth-actions { display: flex; flex-direction: column; align-items: center; }
.auth-error { background: #FEF1EB; border: 1px solid #f0c9bb; color: #8a2b1d; border-radius: var(--r-md); padding: var(--sp-3); font-size: var(--fs-body); }
.auth-note { background: var(--surface-chip); border: 1px solid var(--border); color: var(--text-2); border-radius: var(--r-md); padding: var(--sp-3); font-size: var(--fs-body); }
/* Conta reconhecida no aceite do convite (A3): avatar + nome + e-mail. */
.auth-identity { display: flex; align-items: center; gap: var(--sp-3); }
.auth-identity__info { display: flex; flex-direction: column; min-width: 0; }
.auth-identity__name { color: var(--text-1); font-weight: 700; font-size: var(--fs-strong); }
.auth-identity__email { overflow-wrap: anywhere; }

/* Entrar com o Google (M-039): botão de contorno com a marca oficial, e a linha
   "ou" separando do caminho por e-mail e senha. A marca tem cor própria (não
   segue o tema), exigência do Google para o botão dele. */
.google-mark { width: 18px; height: 18px; flex: none; }
.auth-divider { display: flex; align-items: center; gap: var(--sp-3); color: var(--text-4); font-size: var(--fs-meta); }
.auth-divider::before, .auth-divider::after { content: ""; flex: 1; height: 1px; background: var(--border); }


/* ============================================================ DOCUMENTOS (privacidade e termos, M-039) */
/* Texto corrido de leitura longa, o único do app: títulos de seção, parágrafos e
   listas com respiro maior que o dos formulários, porque aqui a pessoa lê em vez
   de preencher. */
.legal { gap: var(--sp-3); }
.legal h2 { font-family: var(--font-display); font-size: var(--fs-strong); color: var(--text-1); margin-top: var(--sp-4); }
.legal p, .legal li { font-size: var(--fs-body); color: var(--text-2); line-height: 1.55; }
.legal ul { display: flex; flex-direction: column; gap: var(--sp-2); padding-left: var(--sp-4); list-style: disc; }
.legal strong { color: var(--text-1); }
.legal__updated { font-size: var(--fs-meta); color: var(--text-4); }

/* O par de links no pé de quem chega de fora (telas de acesso e páginas públicas)
   e no fim do menu da conta. Sublinhados: discretos é o tom certo aqui, mas eles
   precisam parecer o que são. */
.legal-links { display: flex; justify-content: center; gap: var(--sp-4); font-size: var(--fs-meta); }
.legal-links a { color: var(--text-3); text-decoration: underline; }

/* Catálogo /dev */
.dev-index { padding: var(--sp-6); display: flex; flex-direction: column; gap: var(--sp-3); }
.dev-index a { display: block; padding: var(--sp-3); background: var(--surface-card); border-radius: var(--r-md); text-decoration: none; }

/* ============================================================ TIMELINE (aba Etapa, DS §12) */
/* Espaço extra para a bottom nav fixa não cobrir o fim do conteúdo. */
.app-frame--tabbed { padding-bottom: 88px; }

/* Barra da temporada: uma casa por etapa (verde = realizada, laranja = atual). */
/* Barra de progresso contínua (etapa em andamento): o <progress> nativo, para o
   preenchimento não precisar de estilo em linha. */
.progress { appearance: none; -webkit-appearance: none; display: block; width: 100%; height: 6px;
  border: 0; border-radius: 6px; background: var(--surface-chip); overflow: hidden; }
.progress::-webkit-progress-bar { background: var(--surface-chip); }
.progress::-webkit-progress-value { background: var(--brand); border-radius: 6px; }
.progress::-moz-progress-bar { background: var(--brand); border-radius: 6px; }
.season-progress { display: flex; gap: 4px; }
.season-progress__seg { flex: 1; height: 6px; border-radius: 6px; background: var(--surface-chip); }
.season-progress__seg--done { background: var(--success); }
.season-progress__seg--current { background: var(--brand); }
/* Nas telas da etapa o preenchido é a marca (o verde fica para a temporada). */
.season-progress--brand .season-progress__seg--done { background: var(--brand); }

/* Trilho vertical com um nó por item (realizada = verde; pote = apagado). */
.timeline { position: relative; display: flex; flex-direction: column; gap: var(--sp-2); padding-left: 22px; }
.timeline::before { content: ""; position: absolute; left: 7px; top: 6px; bottom: 6px; width: 2px; background: var(--surface-chip); }
.timeline__item { position: relative; }
/* Nós do trilho (protótipo): vazada = combinação no pote; verde = realizada;
   laranja (maior) = etapa de agora. */
.timeline__dot { position: absolute; left: -19px; top: 16px; width: 10px; height: 10px; border-radius: 50%; background: var(--surface-app); border: 2px solid var(--border-strong); }
.timeline__dot--done { background: var(--success); border-color: var(--surface-app); }
.timeline__dot--now { background: var(--brand); border-color: var(--surface-app); width: 13px; height: 13px; left: -20px; }

/* ============================================================ AÇÕES DA ETAPA (aba Etapa) */
/* Link de texto discreto (ação secundária ao lado de um botão: "Confirmar
   presenças", "Histórico da etapa"). */
.link-quiet { color: var(--text-2); font-size: var(--fs-body); }
/* Link de texto com ícone antes da palavra (histórico da etapa, por ex.). */
.link-icon { display: inline-flex; align-items: center; gap: var(--sp-1); }
.link-icon svg.lucide { width: 14px; height: 14px; }
/* Ação de texto logo abaixo do card da etapa em andamento: fora do card (que é
   um link inteiro), então o respiro vem daqui — dentro do card, o gap dele basta. */
.timeline__item > .link-quiet { display: inline-block; margin-top: var(--sp-2); }
/* Ações de uma etapa realizada (placares, histórico): só os ícones, à direita,
   para o card ficar limpo. O respiro vem do gap do próprio card. */
.stage-actions { display: flex; justify-content: flex-end; gap: var(--sp-1); }

/* ============================================================ CARD DE JOGO (DS §7) */
.game-card { background: var(--surface-card); border: 1px solid var(--border); border-radius: var(--r-lg); padding: var(--sp-3) var(--sp-4); display: flex; flex-direction: column; gap: var(--sp-2); box-shadow: var(--elev-card); }
/* Selo de W.O. (atenção, ui-spec): dupla incompleta no confronto. */
.tag-wo { display: inline-flex; align-items: center; gap: 4px; color: var(--warning); font-family: var(--font-mono); font-size: var(--fs-eyebrow); letter-spacing: .06em; text-transform: uppercase; font-weight: 700; }
.tag-wo svg.lucide { width: 13px; height: 13px; }
/* Cabeçalho em linha (título à esquerda, meta à direita) — distinto do
   .section-head (empilhado). Usado no progresso, no placar e na parcial. */
.head-row { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-2); }

/* ============================================================ RESULTADO (cockpit O8c) */
/* Jogo pendente: borda de marca destacando o que falta lançar (DS §7). */
.game-card--pending { border: 1.5px solid var(--brand); background: var(--surface-panel); }
.game-card__done { display: inline-flex; align-items: center; gap: 4px; color: var(--success); }
.game-card__waiting { display: inline-flex; align-items: center; gap: 4px; color: var(--warning); }
.game-card__done svg.lucide, .game-card__waiting svg.lucide { width: 13px; height: 13px; }
/* Legenda "Decidido no tie-break" abaixo do placar (M-010): na cor da marca para destacar. */
.game-card__tiebreak { display: inline-flex; align-items: center; gap: var(--sp-1); color: var(--brand); font-weight: 700; font-size: var(--fs-body); }
.game-card__tiebreak svg.lucide { width: 15px; height: 15px; }
/* O CONFRONTO, LADO A LADO (M-044, decisão do usuário 2026-08-27): uma coluna por
   dupla, um jogador por linha e o placar no pé da coluna. O que se lê num jogo são
   as PESSOAS — a linha corrida colava quatro nomes em duas frases ligadas por "&".
   1fr 1fr fixo (e não auto): as duas colunas têm a mesma largura independente do
   tamanho dos nomes, senão a dupla de nomes curtos parecia menos importante. */
.game-card__vs { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-2); }
.game-card__side { display: flex; flex-direction: column; align-items: center; gap: var(--sp-1);
                   padding: var(--sp-2); border-radius: var(--r-md); text-align: center; min-width: 0; }
/* A vencedora sobre tinta de marca: é o destaque que a coroa sozinha não dava
   quando as duplas deixaram de estar uma sob a outra. */
.game-card__side--win { background: var(--brand-tint); }
/* O nome quebra DENTRO da coluna (meia largura, e o apelido vai a 15 caracteres):
   `anywhere` porque nome próprio não tem hífen onde quebrar. */
.game-card__player { font-weight: 700; font-size: var(--fs-strong); overflow-wrap: anywhere; line-height: 1.25; }
.game-card__side--lose .game-card__player { font-weight: 600; color: var(--text-3); }
/* Pé da coluna: o placar do jogo lançado ou, pendente, o círculo de escolha. */
.game-card__base { display: flex; align-items: center; justify-content: center; gap: var(--sp-1); margin-top: var(--sp-1); }
/* Sobre a utilitária `.num` (fonte numérica tabular): aqui só peso, tamanho e cor. */
.game-card__num { font-weight: 800; font-size: var(--fs-line-num); line-height: 1; color: var(--text-3); }
.game-card__side--win .game-card__num { color: var(--brand); }
/* Jogo lançado sem placar informado (ele é opcional): o traço marca o pé da
   coluna sem competir com os números. */
.game-card__num--empty { color: var(--border); }
.game-card__crown { color: var(--brand); display: inline-flex; }
.game-card__crown svg.lucide { width: 15px; height: 15px; }
/* Pendente: o círculo vazio ocupa o lugar da coroa em CADA coluna, apagado — é o
   "escolha uma das duas" (coroa é prêmio de quem já venceu). Um pouco maior que a
   coroa porque ali ele é o alvo, não o enfeite. */
.game-card__crown--empty { color: var(--border); }
.game-card__crown--empty svg.lucide { width: 18px; height: 18px; }
/* Rodapé do jogo concluído: autoria à esquerda, "Editar" à direita. */
.game-card__foot { display: flex; align-items: center; justify-content: flex-end; gap: var(--sp-2); border-top: 1px solid var(--border); padding-top: var(--sp-2); }
.game-card__foot .game-card__author, .game-card__note { margin-right: auto; text-align: left; }
/* Por que o jogo veio fechado (W.O.): mesma discrição da autoria, no rodapé. */
.game-card__note { font-size: var(--fs-meta); color: var(--text-3); }
/* Autoria do resultado: abre o histórico do jogo (§6.9). Discreta como a legenda
   que era antes, mas com o ícone dizendo que dá para tocar. */
.game-card__author, .score-card__author { display: inline-flex; align-items: center; gap: 4px; text-align: left;
  background: none; border: 0; padding: 0; cursor: pointer;
  font-family: var(--font-body); font-size: var(--fs-meta); color: var(--text-3); }
.game-card__author svg.lucide, .score-card__author svg.lucide { width: 13px; height: 13px; flex: none; }
.score-card__author { margin-top: var(--sp-2); }

/* Histórico do lançamento (log da etapa): uma entrada por lançamento/correção. */
.log { list-style: none; display: flex; flex-direction: column; gap: var(--sp-3); margin: var(--sp-3) 0 0; padding: 0; }
.log__item { display: flex; gap: var(--sp-2); align-items: flex-start; }
.log__av { width: 28px; height: 28px; flex: none; }
.log__body { flex: 1; min-width: 0; }
.log__head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-2); }
.log__who { font-weight: 700; font-size: var(--fs-body); color: var(--text-1); }
.log__when { font-size: var(--fs-eyebrow); color: var(--text-4); white-space: nowrap; }
.log__line { margin: 2px 0 0; font-size: var(--fs-meta); color: var(--text-2); }

/* Escolha da dupla vencedora (bottom-sheet): radios como cartões. */
.pick-group { display: flex; flex-direction: column; gap: var(--sp-2); }
.pick { display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-3); border: 1px solid var(--border-strong); border-radius: var(--r-md); background: var(--surface-card); cursor: pointer; }
.pick__radio { position: absolute; opacity: 0; width: 0; height: 0; }
.pick:has(.pick__radio:checked) { border: 1.5px solid var(--brand); background: var(--surface-panel); }
.pick__av { width: 34px; height: 34px; flex: none; }
.pick__body { flex: 1; display: flex; flex-direction: column; gap: 1px; }
.pick__name { font-weight: 700; font-size: var(--fs-strong); }
.pick__meta { font-family: var(--font-mono); font-size: var(--fs-eyebrow); text-transform: uppercase; letter-spacing: .06em; color: var(--brand); }
.pick__check { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; border-radius: 50%; border: 2px solid var(--border-strong); color: transparent; flex: none; }
.pick:has(.pick__radio:checked) .pick__check { background: var(--brand); border-color: var(--brand); color: #fff; }
.pick__check svg.lucide { width: 14px; height: 14px; }

/* "obrigatório" ao lado do título Placar, quando o ranking exige. */
.score-block__required { color: var(--danger); }
/* Onde o ranking NÃO exige placar, o "obrigatório" é do tie-break: some enquanto a
   chave está desligada (M-045). Esconder com `:not(:has(...))`, e não mostrar com
   `:has(...)`, é o padrão das duas linhas abaixo: assim o `display` do rótulo
   continua sendo declarado num lugar só. */
.score-block:not(:has([data-tiebreak-toggle]:checked)) .score-block__required--tiebreak { display: none; }
/* A chave "decidido no tie-break" escolhe qual bloco de números aparece (CSS
   `:has`, sem JS). Games some quando o jogo foi ao tie-break; os pontos do
   tie-break somem quando não foi. */
.score-block:has([data-tiebreak-toggle]:checked) [data-score-fields="score"],
.score-block:not(:has([data-tiebreak-toggle]:checked)) [data-score-fields="tiebreak"] { display: none; }
/* Placar (opcional): um stepper por dupla. */
.score-block { display: flex; flex-direction: column; gap: var(--sp-3); }
/* As duas linhas do placar precisam de distância no dedo (feedback 2026-08-06):
   os "+/−" de uma ficavam a 8px dos da outra, e no celular isso é toque errado
   garantido. A legenda fica colada no bloco; são as LINHAS que se afastam. */
.score-fields { display: flex; flex-direction: column; gap: var(--sp-2); }
.score-rows { display: flex; flex-direction: column; gap: var(--sp-4); }
.score-row { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); }
/* O grupo "− n +" anda junto: só o nome da dupla estica. */
.score-row .stepper { flex: none; gap: var(--sp-2); }
/* O nome numa linha só: sem a coroa e com o "− +" mais junto, cabe num corpo
   maior que a legenda; se ainda faltar espaço, corta no fim em vez de quebrar. */
.score-row__name { font-size: var(--fs-meta); color: var(--text-2); display: block; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Dupla vencedora na seção de placar: o nome e o "− +" na marca, que já bastam
   para achar a linha da vencedora (a coroa saiu para o nome caber numa linha). */
.score-row--win .score-row__name { color: var(--brand); font-weight: 700; }
.score-row--win .stepper__btn { color: var(--brand); border-color: var(--brand); }
.score-row--win .stepper__input { color: var(--brand); }
/* Largura própria, em rem, com folga de sobra para os dois dígitos do tie-break.
   O campo já se mediu pelo conteúdo (`field-sizing`), para não sobrar vazio entre
   o "−" e o "+": o WebKit erra essa medida (23,6px para um número que ocupa 26px)
   e corta o dígito ao meio; no iPhone o corte pegou até o número de um dígito
   (relato do usuário, 2026-08-26). A folga é de propósito — ela absorve fonte de
   reserva, negrito de acessibilidade e texto inflado pelo iOS. `flex: none`
   porque o campo mora numa fileira flex e não pode encolher para caber. */
.stepper__input { width: 2.25rem; flex: none; padding: 0; text-align: center; background: transparent; border: 0; color: var(--text-1); -moz-appearance: textfield; }
.stepper__input::-webkit-outer-spin-button, .stepper__input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.link-reset { background: none; border: 0; padding: 0; cursor: pointer; color: var(--brand); font-family: var(--font-body); font-size: var(--fs-eyebrow); text-decoration: underline; }

.author-foot { text-align: center; }

/* Classificação parcial inline (§6.12): linhas enxutas, recalculam ao vivo. */
.standings__live { display: inline-flex; align-items: center; gap: 4px; color: var(--warning); }
.standings__live svg.lucide { width: 12px; height: 12px; }
.standings { background: var(--surface-card); border: 1px solid var(--border); border-radius: var(--r-lg); overflow: hidden; }
/* Duas faixas: nome + variação/VIT em cima, jogos·aproveitamento·faltas embaixo
   ocupando a largura toda. A meta sozinha na faixa cabe em uma linha só com
   corpo legível; espremida ao lado do VIT ela só caberia em ~10px. Posição e
   foto atravessam as duas faixas, coladas uma na outra (coluna estreita). */
.standings__row { display: grid; grid-template-columns: auto auto 1fr auto auto;
  grid-template-areas: "pos av name trend wins" "pos av meta trend wins";
  align-items: center; column-gap: var(--sp-2); row-gap: 2px; padding: var(--sp-2) var(--sp-3); border-bottom: 1px solid var(--border); }
/* `.standings__trend` e `.standings__wins` também vivem fora da tabela (placar da
   etapa, duplas em andamento, arte de export), por isso estas duas ficam presas ao
   `.standings__row`. As demais só existem aqui: a área vai na regra da própria
   classe, mais abaixo. Nas vitórias o escopo é o que importa de verdade: só na
   classificação elas vêm empilhadas (número em cima, "vit" embaixo, atravessando
   as duas faixas), o que tira o rótulo da horizontal e devolve ~15px para o nome
   sem perder a legenda do número. Nos outros usos o badge segue deitado. */
.standings__row > .standings__trend { grid-area: trend; }
.standings__row > .standings__wins { grid-area: wins; flex-direction: column; align-items: center; gap: 0; line-height: 1.1; }
.standings__row:last-child { border-bottom: 0; }
/* Alinhado à direita: encosta na foto (e o "º" fica na mesma coluna do 1º ao 14º,
   em vez de balançar como no centralizado). */
.standings__pos { grid-area: pos; width: 16px; text-align: right; font-family: var(--font-num); font-weight: 800; color: var(--text-3); }
/* Líder (1º): número, contorno da foto e vitórias em OURO — mesma cor de medalha
   do pódio (1/2/3 = ouro/prata/bronze na tabela também). Sem fundo/barra. */
.standings__row--lead .standings__pos { color: var(--gold); }
.standings__row--lead .standings__av { border: 2px solid var(--gold); }
.standings__row--lead .standings__wins { color: var(--gold); }
.standings__av { grid-area: av; width: 40px; height: 40px; }
.standings__name { grid-area: name; min-width: 0; font-weight: 700; font-size: var(--fs-strong); }
/* Legível de verdade (o eyebrow de 10px não era) sem competir com o nome: a
   hierarquia fica por tamanho, peso e cor (o nome é 14px/700 em --text-1). */
.standings__meta { grid-area: meta; min-width: 0; font-size: var(--fs-meta); color: var(--text-3); }
/* Falta pesa (desempate, §6.10): vermelha quando existe. A arte de export usa a
   mesma classe, recolorida no tom de papel (ver .export-poster). */
.standings__f { color: var(--down); }
.standings__trend { display: inline-flex; flex: none; }
.standings__trend svg.lucide { width: 14px; height: 14px; }
.standings__trend--up { color: var(--up); }
.standings__trend--down { color: var(--down); }
.standings__trend--same { color: var(--text-4); }
.standings__wins { font-family: var(--font-num); font-weight: 800; font-size: var(--fs-line-num); color: var(--text-1); display: flex; align-items: baseline; gap: 3px; }
.standings__wins small { font-family: var(--font-mono); font-size: var(--fs-eyebrow); text-transform: uppercase; color: var(--text-4); font-weight: 400; }

/* Placar da etapa realizada (timeline): as duplas (formato do pote) com as
   vitórias da etapa à direita, no mesmo <details>. Trilho de linhas como o pote. */
.stage-scoreboard { margin-top: var(--sp-2); border-top: 1px solid var(--border); }
.stage-line { display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-2) 0; border-bottom: 1px solid var(--border); }
.stage-line:last-child { border-bottom: 0; padding-bottom: 0; }
/* Os nomes tomam a linha, o número (vitórias) fica na coluna à direita — vale
   para o placar da etapa realizada e para as duplas da etapa em andamento. */
.stage-line .combo__pair,
.stage-pairs__line .combo__pair { flex: 1; }

/* Placares (tela consolidada / link da etapa): reusa .stage-line + .standings__wins
   do scoreboard, agrupando as duas duplas de cada partida com um respiro entre elas;
   a dupla vencedora tinge o placar na marca. Filtro por jogador = select + botão. */
/* Cada partida é um card (dupla × dupla): o selo "VS" à esquerda entre as duas
   duplas deixa claro que é um confronto; o placar fica numa coluna fixa à direita
   (números alinhados, com espaço reservado para a coroa da vencedora). */
.score-card { background: var(--surface-card); border: 1px solid var(--border); border-radius: var(--r-md); padding: var(--sp-1) var(--sp-3); margin-bottom: var(--sp-2); }
.score-card__row { display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-2) 0; }
/* Nomes alinhados à esquerda: o grid centralizado do `_pair_row` (feito para a
   combinação simétrica do pote) fica escalonado numa lista de partidas. */
.score-card .combo__pair { display: flex; align-items: baseline; flex-wrap: wrap; column-gap: var(--sp-1); flex: 1; }
.score-card .combo__side--a { text-align: left; }
.score-card .combo__side--a .combo__swap { justify-content: flex-start; }
/* Coluna do placar: número à direita + espaço fixo da coroa → tudo alinhado. */
/* A coluna do placar cresce no jogo decidido no tie-break (M-031), onde cada
   lado leva os pontos entre parênteses: 6(7) contra 5(4). Uma variável por
   cartão, para a régua do "VS" parar no mesmo lugar que a coluna. */
.score-card { --score-col-total: var(--score-col); }
.score-card--tb { --score-col-total: calc(var(--score-col) + 1.6rem); }
.score-card__score { width: var(--score-col-total); display: flex; align-items: baseline; justify-content: flex-end; }
.score-card__num { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-weight: 800; font-size: var(--fs-line-num); color: var(--text-1); }
.score-card__row--win .score-card__num { color: var(--brand); }
/* Os pontos do tie-break (M-031), nos dois cartões: menores e apagados, para o
   número de games seguir sendo o placar que se lê primeiro. */
.score-tb { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-weight: 700; font-size: var(--fs-meta); color: var(--text-3); }
.score-card__row--win .score-tb { color: var(--brand); }
.score-card__crown { width: 16px; margin-left: 4px; display: inline-flex; justify-content: center; color: var(--brand); }
.score-card__crown svg.lucide { width: 15px; height: 15px; }
/* Divisor "VS" à esquerda; a régua para antes da coluna do placar (o pad reserva
   a mesma largura). */
/* padding à direita = coluna do placar + o gap da linha → a régua para exatamente
   onde a coluna do placar começa. */
.score-card__vs { display: flex; align-items: center; gap: var(--sp-2); padding: 2px calc(var(--score-col-total) + var(--sp-3)) 2px 0; }
.score-card__vs-tag { display: inline-flex; align-items: center; background: var(--brand-tint); color: var(--brand); font-family: var(--font-num); font-weight: 800; font-size: var(--fs-eyebrow); letter-spacing: .08em; padding: 3px 9px; border-radius: var(--r-pill); }
.score-card__vs-rule { flex: 1; height: 1px; background: var(--border); }
/* W.O. / tie-break: nota discreta abaixo. */
.score-card__note { display: flex; align-items: center; gap: var(--sp-1); color: var(--text-4); font-size: var(--fs-eyebrow); text-transform: uppercase; letter-spacing: .04em; margin: 2px 0 var(--sp-1); }
.score-card__note--right { justify-content: flex-end; } /* tie-break à direita, junto do placar */
.score-card__note svg.lucide { width: 12px; height: 12px; }
/* Sem placar informado: rótulo apagado alinhado à direita (não é um valor). */
.score-card__no-score { text-align: right; color: var(--text-4); font-size: var(--fs-eyebrow); text-transform: uppercase; letter-spacing: .04em; margin: 0 0 var(--sp-1); }
/* Barra de filtro (seletores + chave, aplica sozinha): placares e jogos da etapa. */
.filter-bar { display: flex; flex-direction: column; gap: var(--sp-2); margin-bottom: var(--sp-4); }
.filter-bar .field__label { margin-bottom: calc(-1 * var(--sp-1)); }
.scores-empty { color: var(--text-3); text-align: center; padding: var(--sp-6) 0; }

/* Combinação expansível no pote (<details>, sem JS): toque abre as duplas. */
.combo__summary { cursor: pointer; list-style: none; display: flex; flex-direction: column; gap: var(--sp-1); }
.combo__summary::-webkit-details-marker { display: none; }
.combo__head { display: flex; justify-content: space-between; align-items: center; gap: var(--sp-2); }
.combo__chevron { color: var(--text-3); display: inline-flex; transition: transform .15s ease; }
details[open] > .combo__summary .combo__chevron { transform: rotate(180deg); }
/* Selo do estado da combinação na aba Combinações (M-025): a cor vem do estado do
   card (jogada · em quadra · ainda no pote), para os dois nunca divergirem. O
   tamanho já vem de `.stage-card__title--sm svg.lucide`. */
.stage-card--done .combo__flag { color: var(--success); }
.stage-card--ongoing .combo__flag { color: var(--brand); }
.stage-card--pending .combo__flag { color: var(--text-3); }
.combo__partner { font-size: var(--fs-body); color: var(--text-2); }
.combo__partner b { color: var(--text-1); }
/* Duplas como linhas: fileiras separadas por borda, como as listas do app. */
.combo__pairs { display: flex; flex-direction: column; margin-top: var(--sp-2); border-top: 1px solid var(--border); }
.combo__pairs .combo__pair { padding: var(--sp-2) 0; border-bottom: 1px solid var(--border); }
.combo__pairs .combo__pair:last-child { border-bottom: 0; padding-bottom: 0; }
/* Na tela de escolha, a lista fecha com linha antes do botão + respiro. */
.combo__pairs--framed .combo__pair:last-child { border-bottom: 1px solid var(--border); padding-bottom: var(--sp-2); }
.combo__pairs--framed { margin-bottom: var(--sp-1); }
/* Dupla do usuário logado em destaque na lista da combinação. */
.combo__pair--mine { color: var(--brand); font-weight: 700; }
/* Cabeçalho da etapa em andamento (M-017): o título é da página (sem card), e a
   ficha do dia vem como texto de apoio. */
.stage-head__title { font-family: var(--font-num); font-weight: 800; font-size: 1.5rem; letter-spacing: -.01em;
  color: var(--text-1); margin: 0; }
.stage-head__state { color: var(--brand); }
.stage-head__meta { display: flex; flex-direction: column; gap: 2px; font-size: var(--fs-body); color: var(--text-3); }
/* Sem flex: a linha é uma frase. Com `display:flex`, o `<strong>` da combinação
   e o resto do texto viravam itens e o `gap` abria um vão dentro da frase. O
   espaçamento de quem tem ícone vive no `.stage-head__tap`. */
.stage-head__line { display: block; }
/* Linha da ficha que leva a algum lugar (a combinação abre a tela do sorteio, os
   suplentes abrem a lista de trocas): texto do próprio bloco, sublinhado
   pontilhado e o ícone na marca dizendo que há o que tocar. */
.stage-head__tap { display: inline-flex; align-items: center; gap: 5px; background: none; border: 0; padding: 0;
  cursor: pointer; color: inherit; font: inherit; text-decoration: underline; text-decoration-style: dotted;
  text-underline-offset: 3px; text-decoration-color: var(--border-strong); }
.stage-head__tap svg.lucide { width: 13px; height: 13px; color: var(--brand); }
/* Falta sem suplente: fato do dia que muda o resultado, então sai em atenção
   (só texto: o ícone saiu por decisão do usuário em 2026-07-31). */
.stage-head__line--warn { color: var(--warning); }
/* Quem entrou (verde) e quem faltou (vermelho) na folha das trocas do dia. */
.roster-in { color: var(--success); font-weight: 700; }
.roster-out { color: var(--down); }
/* A ficha do dia encosta na seção seguinte: o respiro vem do gutter dela, senão
   os dois gutters somam e abrem um vão grande demais. */
.stage-head { padding-bottom: 0; }
/* Título de seção da etapa: mesmo desenho do título da tela de lançar (sans,
   negrito, cor do texto), um degrau menor para não competir com ele. */
.section-title { font-family: var(--font-num); font-size: 1.25rem; font-weight: 800;
  letter-spacing: -.01em; color: var(--text-1); margin: 0; }
/* Mesmo título, um corpo abaixo: cabe ao lado de um ícone, sem competir com ele. */
.section-title--sm { font-size: 1.0625rem; }
/* Título e a linha que o explica andam juntos: a dica é do título, não um item
   solto da seção (por isso fora do gap da `.stack`). */
.section-intro { display: flex; flex-direction: column; gap: 2px; }
/* Ênfase dentro de uma dica: o verbo que a pessoa precisa ler. */
.field__hint strong { color: var(--text-2); font-weight: 700; }


/* Lançamento por totais (M-013): uma linha por dupla, o nome à esquerda e o campo
   das vitórias à direita, na régua do card das duplas. */
.totals { display: flex; flex-direction: column; margin-top: var(--sp-2); border-top: 1px solid var(--border); }
.totals__row { display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-3); padding: var(--sp-2) 0; border-bottom: 1px solid var(--border); }
.totals__row:last-child { border-bottom: 0; }
/* Dupla que torna o lançamento impossível: nome e número em vermelho, para a
   frase do erro poder falar só da conta. */
.totals__row--over .totals__pair, .totals__row--over .stepper__input { color: var(--danger); }
.totals__row--over .stepper { border-color: var(--danger); }
/* Dupla incompleta no lançamento por totais: leitura apenas, como no resumo. */
.totals__row--wo { background: var(--warning-tint); border-radius: var(--r-sm); padding: var(--sp-2); }
/* Por que o número não passa dali: aparece ao tocar no botão bloqueado. */
.totals__reason { flex-basis: 100%; margin: 0; font-size: var(--fs-meta); color: var(--warning); }
.totals__wo { margin: 0; font-family: var(--font-mono); font-size: var(--fs-eyebrow); letter-spacing: .04em; color: var(--warning); }
.wins-zero { font-weight: 800; font-size: var(--fs-line-num); color: var(--text-3); white-space: nowrap; }
/* O nome pode ocupar duas linhas; o "− +" não encolhe. Abaixo do nome, a barra
   dos jogos já lançados daquela dupla (a mesma do resumo da etapa). */
.totals__pair { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--sp-1); }
.totals__done { display: flex; align-items: center; gap: var(--sp-2); }
.totals__done .pair-bar { flex: 1; }
/* O apelido é indivisível: a quebra acontece entre os nomes, nunca no meio de um. */
.totals__name { white-space: nowrap; }
/* "− 6 vit +" tem de ler como uma coisa só: os respiros ficam mínimos e o grupo
   não encolhe dentro da linha da dupla. */
.totals__stepper { flex: none; gap: 2px; }
/* O número em negrito (a tela toda já fala de vitórias, então o "vit" de cada
   linha saiu — decisão do usuário 2026-07-31). A largura e a folga em volta vêm
   da regra base do campo, uma medida só para os dois lançamentos. */
.totals__stepper .stepper__input { font-weight: 800; }

/* Duplas da etapa em andamento: a linha inteira é tocável (filtra os jogos da
   dupla, M-014) e traz as vitórias do dia à direita (M-015). Régua igual à do
   pote, mas morando no link — a linha em si é o conteúdo do <a>. */
.stage-pairs { display: flex; flex-direction: column; margin-top: var(--sp-2); border-top: 1px solid var(--border); }
/* A régua fecha a lista também embaixo da última dupla: o card abre com régua em
   cima, e sem a de baixo a lista ficava sem arremate. */
/* Cada dupla é um link para o resultado dos jogos dela (2026-07-31): nomes e a
   barra dos jogos à esquerda, vitórias e o "N de 6" à direita. */
.stage-pairs__line { display: flex; align-items: center; gap: var(--sp-2); padding: var(--sp-2) 0;
  color: inherit; text-decoration: none; border-bottom: 1px solid var(--border); }
.stage-pairs > .stage-pairs__line:last-child { border-bottom: 0; }
.stage-pairs__main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--sp-1); }
/* Um traço por jogo da dupla, preenchido quando o jogo já tem resultado. */
.pair-bar { display: flex; gap: 3px; }
.pair-bar__seg { height: 4px; flex: 1; border-radius: 4px; background: var(--surface-chip); }
.pair-bar__seg--on { background: var(--brand); }
.pair-bar__seg--wo { background: var(--warning); opacity: .55; }
/* Dupla incompleta: os jogos fecharam por W.O., não há o que lançar. */
.stage-pairs__line--wo { background: var(--warning-tint, var(--surface-chip)); border-radius: var(--r-sm);
  padding: var(--sp-2); margin: var(--sp-1) calc(-1 * var(--sp-2)); border-bottom: 0; }
.stage-pairs__wo { display: flex; align-items: center; gap: 4px; margin: 0;
  font-family: var(--font-mono); font-size: var(--fs-eyebrow); letter-spacing: .04em; color: var(--warning); }
.stage-pairs__wo svg.lucide { width: 12px; height: 12px; }
/* Apelido vai até 15 caracteres: sem o espaçamento extra e com a coluna do "&"
   mais justa, a dupla inteira cabe numa linha (e a linha não estica quando entra
   um suplente na troca). Vale só neste card. */
.stage-pairs__line .combo__pair { letter-spacing: 0; column-gap: var(--sp-1); }
/* Vitórias e jogos feitos ficam juntos numa coluna à direita: os nomes ocupam a
   linha inteira e seguem centrados no "&", sem disputar centro com o texto. */
.stage-pairs__stats { display: flex; flex-direction: column; align-items: flex-end; }
.stage-pairs__meta { font-size: var(--fs-meta); color: var(--text-3); margin-top: 1px; white-space: nowrap; line-height: 1.2; }
/* O apelido de 15 caracteres tem de caber na linha, com a seta ao lado: em vez de
   quebrar em duas linhas, corta no fim com reticências. Vale só neste card. */
.stage-pairs__line .combo__side { min-width: 0; }
.stage-pairs__line .combo__side > span { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* O cartão do jogo é o gatilho do lançamento: tocar em qualquer parte abre a folha. */
.game-card--tap { cursor: pointer; }
/* Seta da linha: gira ao abrir o menu do que lançar naquela dupla. */
.stage-pairs__caret { width: 16px; height: 16px; color: var(--text-3); flex: none; transition: transform .18s ease; }
/* Aberto (padrão), as duplas já respondem — o resumo "Você joga com…" só
   aparece com o card fechado. */
details[open] > .combo__summary .combo__partner { display: none; }
/* Linha da dupla em grade: nome | & | nome — todos os "&" alinhados na coluna. */
.combo__pair { display: grid; grid-template-columns: 1fr auto 1fr; column-gap: var(--sp-2); align-items: center; }
.combo__side--a { text-align: right; }
/* O "&" é o eixo da dupla (todos alinhados na mesma coluna): negrito, um tom mais
   firme e um ponto maior que os nomes, sem esticar a linha. Sai da fonte
   monoespaçada porque ali o ampersand tem um desenho estilizado que nem parece um
   "&"; na fonte de texto ele volta a ser o de sempre. */
.combo__amp { font-family: var(--font-body); color: var(--text-2); font-weight: 700; font-size: 0.6875rem;
  background: var(--surface-chip); border-radius: var(--r-pill); padding: 1px 6px; line-height: 1.3; justify-self: center; }
/* Preview da confirmação (§6.9): fixo que marcou falta sai riscado, com o
   ícone de falta à frente; o risco de W.O. é avisado no rodapé da combinação. */
.combo__side--absent { text-decoration: line-through; color: var(--down); }
/* Vaga aberta na combinação (§6.6a): quem saiu ainda referenciado, mas não joga. */
.combo__side--open { display: inline-flex; align-items: center; gap: 3px; color: var(--danger); font-weight: 700; }
.combo__side--open svg.lucide { width: 12px; height: 12px; }
.combo__absent { display: inline-flex; color: var(--down); }
/* Na dupla do usuário logado, a falta mantém o destaque (laranja) — fica só o
   tachado + ícone sinalizando a ausência. */
.combo__pair--mine .combo__side--absent,
.combo__pair--mine .combo__absent { color: var(--brand); }
.combo__absent svg.lucide { width: 12px; height: 12px; }
/* Cabeçalho de seção da timeline: explicação colada no título; o respiro fica
   ANTES do título (separando as seções), não entre título e explicação. */
.section-head { display: flex; flex-direction: column; gap: var(--sp-1); margin-top: var(--sp-3); }
.section-head p { margin: 0; }
/* Resposta rápida do usuário na timeline (§6.9): pergunta + segmented lado a lado. */
.quick-rsvp { flex-direction: row; justify-content: space-between; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }
/* Respiro extra entre o detalhe da etapa e a resposta rápida. */
.timeline__item--spaced { margin-top: var(--sp-2); }
/* Troca prevista na dupla (tela de escolha, §6.8b): "⇄ Suplente N" entra no
   lugar do faltante (verde); sem suplente para cobrir, "W.O." (âmbar). Linha
   própria abaixo do nome, sem herdar o tachado. */
.combo__swap { display: flex; align-items: center; gap: 3px; font-size: 0.5625rem; color: var(--success); }
.combo__swap svg.lucide { width: 11px; height: 11px; }
.combo__swap--wo { color: var(--warning); }
.combo__side--a .combo__swap { justify-content: flex-end; }

/* Banner de recomendação do app (§6.8c): conselho curto + (?) que abre a premissa.
   Tom por severidade (ok · warn · danger) ou neutro (info, para explicar),
   sempre discreto: é orientação, não alarme. */
.reco { display: flex; align-items: flex-start; gap: var(--sp-2); padding: var(--sp-3); border-radius: var(--r-md); border: 1px solid var(--border); background: var(--surface-card); }
.reco__body { display: flex; align-items: flex-start; gap: var(--sp-2); flex: 1; }
.reco__body > svg.lucide { flex: none; margin-top: 1px; }
.reco__label { font-family: var(--font-mono); font-size: var(--fs-eyebrow); text-transform: uppercase; letter-spacing: .06em; }
.reco--ok { border-left: 3px solid var(--success); }
.reco--ok > .reco__body > svg.lucide, .reco--ok .reco__label { color: var(--success); }
.reco--warn { border-left: 3px solid var(--warning); }
.reco--warn > .reco__body > svg.lucide, .reco--warn .reco__label { color: var(--warning); }
.reco--danger { border-left: 3px solid var(--danger); }
.reco--danger > .reco__body > svg.lucide, .reco--danger .reco__label { color: var(--danger); }
/* Explicação (não é aviso): tom neutro, para informar sem alarmar. */
.reco--info { border-left: 3px solid var(--border-strong); }
.reco--info > .reco__body > svg.lucide, .reco--info .reco__label { color: var(--text-3); }
.reco__help { flex: none; }
.reco__help summary { list-style: none; cursor: pointer; color: var(--text-3); display: inline-flex; }
.reco__help summary::-webkit-details-marker { display: none; }
.reco__help-body { margin-top: var(--sp-2); font-size: var(--fs-meta); color: var(--text-2); }
.reco__help-body p { margin: var(--sp-1) 0 0; }
/* Aberto, o (?) vira um painel de largura total abaixo do banner. */
.reco:has(.reco__help[open]) { flex-wrap: wrap; }
.reco:has(.reco__help[open]) .reco__help { width: 100%; order: 3; }

/* Overlay do sorteio (O8b): momento de "sorteando" por ~3s antes de iniciar a
   etapa. Fundo escuro fixo (independe do tema da tela). */
.draw-overlay { position: fixed; inset: 0; z-index: 100; display: flex; align-items: center; justify-content: center; background: rgba(22, 19, 15, 0.94); }
.draw-overlay__box { display: flex; flex-direction: column; align-items: center; gap: var(--sp-3); color: #F4F0E9; text-align: center; }
.draw-overlay__spin { width: 56px; height: 56px; border-radius: 50%; border: 4px solid rgba(255, 255, 255, 0.15); border-top-color: var(--brand); animation: draw-spin .7s linear infinite; }
@keyframes draw-spin { to { transform: rotate(360deg); } }
.draw-overlay__label { font-family: var(--font-display); font-weight: 800; font-size: var(--fs-title); }
.draw-overlay__combo { font-family: var(--font-mono); color: var(--brand); font-variant-numeric: tabular-nums; letter-spacing: .04em; }

/* ============================================================ ELENCO GRID (E1)
   Escalação em grade: vagas numeradas (nº = prioridade global, §6.10), foto e
   estado de acesso no próprio card. Um grupo por gênero (misto) ou único (masc/
   fem). Resumo/limites vêm de Roster::Lineup; a view só formata. */
.roster-group + .roster-group { margin-top: var(--sp-4); }
.roster-group__head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-3); margin-bottom: var(--sp-1); }
.roster-group__label { font-family: var(--font-mono); font-size: var(--fs-eyebrow); text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-3); }
.roster-group__count { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-weight: 700; font-size: var(--fs-strong); color: var(--text-1); flex: none; }
.roster-group__count--over { color: var(--warning); }
.roster-group__count .lim { color: var(--text-3); font-weight: 600; }
.roster-bar { height: 4px; border-radius: 2px; background: var(--surface-chip); overflow: hidden; display: flex; margin-bottom: var(--sp-3); }
.roster-bar__seg { height: 100%; }
.roster-bar__seg--fill { background: var(--brand); }
.roster-bar__seg--complete { background: var(--success); }
.roster-bar__seg--over { background: var(--warning); }
.roster-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-2); }
.roster-cell { position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--sp-1);
  width: 100%; min-height: 108px; padding: var(--sp-3) var(--sp-2); text-align: center; text-decoration: none; color: var(--text-1);
  background: var(--surface-card); border: 1px solid var(--border); border-radius: var(--r-md); box-shadow: var(--elev-card);
  font-family: inherit; font-size: var(--fs-body); cursor: pointer; }
.roster-cell--empty { background: transparent; border-style: dashed; border-color: var(--border-strong); box-shadow: none; }
.roster-cell--over { border-color: var(--warning); }
/* Vaga aberta (§6.6a): mostra de quem era (esmaecido) + "Substituir" em vermelho. */
.roster-cell--open { border-color: var(--danger); background: var(--brand-tint-dark); box-shadow: none; }
.roster-cell--open .roster-cell__n { color: var(--danger); }
.roster-cell--open .roster-cell__ava { opacity: .5; filter: grayscale(1); }
.roster-cell--open .roster-cell__name { color: var(--text-2); }
[data-theme="light"] .roster-cell--open { background: #FBEBE8; }
.roster-cell__sub { font-family: var(--font-mono); font-size: 0.5625rem; letter-spacing: 0.04em; text-transform: uppercase; font-weight: 700; color: var(--danger); }
/* Card de Desistências: fora da disputa, esmaecido, com a ação de voltar. */
.roster-cell--desisted { background: var(--surface-chip); border-style: dashed; box-shadow: none; opacity: 0.8; }
.roster-cell__n { position: absolute; top: var(--sp-1); left: var(--sp-2); font-family: var(--font-num); font-variant-numeric: tabular-nums; font-weight: 700; font-size: var(--fs-eyebrow); color: var(--text-3); }
.roster-cell--over .roster-cell__n { color: var(--warning); }
.roster-cell__ava { position: relative; flex: none; }
/* `break-word` e não `anywhere`, pelo mesmo motivo da arte (ver
   `.export-poster__member-name`): o corte de duas linhas já segura o transbordo,
   e `anywhere` cortaria nome no meio sem precisar. */
.roster-cell__name { font-size: var(--fs-body); font-weight: 600; line-height: 1.2; max-height: 2.4em; overflow: hidden;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow-wrap: break-word; }
.roster-cell--empty .roster-cell__name { color: var(--text-2); font-weight: 500; }
/* Círculo tracejado no lugar do avatar: vaga a preencher (o ícone é de quem chama
   — "+" no organizador, silhueta na leitura do jogador). */
.roster-cell__add { width: 46px; height: 46px; border-radius: 50%; display: grid; place-items: center; border: 1.5px dashed var(--border-strong); color: var(--text-3); }
.roster-cell__add svg.lucide { width: 20px; height: 20px; }
/* Pontinho de estado de acesso (§3.2), sobre o avatar. */
.roster-dot { position: absolute; right: -2px; bottom: -2px; width: 15px; height: 15px; border-radius: 50%; border: 2px solid var(--surface-card); display: grid; place-items: center; }
.roster-dot svg.lucide { width: 9px; height: 9px; color: #fff; }
.roster-dot--accepted { background: var(--success); }
.roster-dot--pending { background: var(--warning); }
.roster-dot--no_invite { background: var(--text-3); }

/* ------------------------------------------- CONFIRMAÇÃO DA ETAPA (§6.9, M-009)
   A mesma grade do Elenco, com o estado da presença no lugar do estado de acesso.
   São TRÊS estados, e não dois: quem ainda não respondeu fica tracejado e com o
   pontinho vazado — a tela não responde por ninguém. Quem disse que falta sai da
   disputa do dia e recua (foto cinza, nome apagado). */
.roster-cell--going { border-color: var(--success); }
/* O rótulo do card nasceu vermelho (vaga aberta); no confirmado, ele é verde. */
.roster-cell__sub--going { color: var(--success); }
/* Quem ainda não respondeu: card cheio em tom neutro, esperando resposta. */
.roster-cell--unanswered { background: var(--surface-chip); box-shadow: none; }
/* Quem disse que falta sai da disputa do dia: contorno vermelho, fundo vazado e
   a pessoa recuada (foto em cinza, nome apagado). */
.roster-cell--missing { background: var(--brand-tint); border-color: var(--danger); box-shadow: none; }
/* Foto tingida de vermelho (duotone), e não em cinza: o cinza dizia "apagado", o
   vermelho diz "fora hoje", junto do contorno e do rótulo. O filtro fica na foto,
   não no invólucro — senão tingiria o pontinho de estado, que é irmão dela. */
.roster-cell--missing .avatar { filter: grayscale(1) sepia(1) saturate(4) hue-rotate(-35deg); }
.roster-cell--missing .roster-cell__name { color: var(--text-2); }
.roster-dot--going { background: var(--success); }
.roster-dot--missing { background: var(--danger); }
/* O "?" do Lucide preenche o pontinho: o círculo do próprio ícone vira o anel. */
.roster-dot--unanswered, .roster-dot--sub-out { background: var(--surface-card); }
.roster-dot--unanswered svg.lucide,
.roster-dot--sub-out svg.lucide { width: 11px; height: 11px; color: var(--text-3); }
/* Alcance do push no quadro de Lembretes (§5.2), sobre o mesmo card do Elenco:
   quem recebe fica em pé (contorno verde), quem já ativou e não recebe mais fica
   em vermelho (é a perda que o organizador precisa enxergar) e quem nunca ativou
   fica apagado — não é falha dele, é só ausência. */
/* O `.roster-cell` nasceu botão (na confirmação, tocar abre o menu da pessoa) e
   traz `cursor: pointer` na base. Aqui o card não faz nada ao clique — quem liga
   a notificação é a própria pessoa, no aparelho dela —, então a mãozinha seria
   promessa falsa. */
.roster-cell--push-on, .roster-cell--push-off, .roster-cell--push-never { cursor: default; }
.roster-cell--push-on { border-color: var(--success); }
.roster-cell--push-off { border-color: var(--danger); }
.roster-cell--push-never { background: var(--surface-chip); border-style: dashed; box-shadow: none; }
.roster-cell--push-never .roster-cell__ava { opacity: .5; filter: grayscale(1); }
.roster-cell--push-never .roster-cell__name { color: var(--text-2); }
.roster-dot--push-on { background: var(--success); }
.roster-dot--push-off { background: var(--danger); }
.roster-dot--push-never { background: var(--surface-card); }
.roster-dot--push-never svg.lucide { width: 11px; height: 11px; color: var(--text-3); }
/* Título de grupo desta tela: preto e mais forte que o rótulo do Elenco, porque
   aqui os grupos são a estrutura da tela (fixos · convocados · disponíveis). */
.roster-group__label--strong { font-size: var(--fs-body); font-weight: 800; color: var(--text-1); letter-spacing: .02em; text-transform: none; font-family: inherit; }
/* Apoio do grupo: a 1ª linha diz o que o grupo é, as seguintes falam do dia. */
.roster-group__notes { display: flex; flex-direction: column; gap: var(--sp-1); margin: calc(var(--sp-1) * -1) 0 var(--sp-2); }
/* Menu de ações da vaga (bottom-sheet): botões empilhados. */
/* Compartilhar um link de dentro de um sheet que oferece outras coisas junto
   (§5.1): os dois caminhos vêm no HTML e o aparelho decide qual serve. A classe
   da raiz é carimbada uma vez pelo `layouts/_ui_js`; fazer por CSS é o que faz a
   regra valer também para o pedaço que o pulso ao vivo traz depois. */
[data-share-native] { display: none; }              /* sem folha nativa: só o endereço à mostra */
.can-share [data-share-native] { display: revert; }
.can-share [data-share-fallback] { display: none; } /* com folha nativa: ela já copia */

.sheet-actions { display: flex; flex-direction: column; gap: var(--sp-2); }
.sheet-actions .btn { justify-content: flex-start; }
.sheet-actions .btn--on { border-color: var(--brand); color: var(--brand-on-light); } /* permissão ativa (ex.: pode sortear) */
.sheet-actions form { margin: 0; }

/* Modal de confirmação centrado (substitui o window.confirm nativo) — janela
   flutuante clara sobre tudo, inclusive sobre um bottom-sheet aberto (z acima de .sheet). */
/* A largura é em `rem` (21.25rem = os mesmos 340px no mobile), e NÃO em px fixo:
   no desktop o `:root` estica a base do rem (M-004), e tudo que a janela guarda
   por dentro (o padding dela, o padding e a fonte dos botões) estica junto. Presa
   em 340px, a janela ficava do tamanho de celular enquanto o conteúdo crescia, e
   a fileira "Cancelar / Confirmar" estourava a caixa — como o `overflow-y:auto`
   aqui também recorta na horizontal, o "Confirmar" saía com a borda cortada.
   Reportado em 2026-08-24, numa tela grande. */
.modal-backdrop { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.6); z-index: 50; }
.modal { position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%); z-index: 51;
  width: min(21.25rem, calc(100vw - 2 * var(--gutter))); max-height: 80dvh; overflow-y: auto;
  background: var(--surface-card); border: 1px solid var(--border); border-radius: var(--r-lg);
  box-shadow: 0 24px 60px -12px rgba(0, 0, 0, 0.5); padding: var(--sp-4); }
.modal__message { margin: 0 0 var(--sp-4); color: var(--text-1); line-height: 1.4; }
.modal__actions { display: flex; gap: var(--sp-2); }
.modal__actions .btn { flex: 1; justify-content: center; }

/* ============================================================ MARCA & PATROCÍNIO (M-033, §8.4)
   A cartela de uma marca, do jeito que o usuário calibrou em 2026-08-07 olhando
   a arte pronta:
   - o espaço reservado é FIXO por formato (quadrado e círculo 1:1, retângulo
     2:1) e a imagem ocupa o máximo que couber dentro dele, SEM cortar. É o que
     faz patrocinadores de arquivos diferentes pesarem igual na fileira;
   - só o círculo recorta, que é o que ele existe para fazer;
   - sem caixa, sem contorno, sem canto arredondado e sem margem: o logo entra
     como ele é.
   O tamanho de cada contexto vem do `--tile`. Vale para a tela do organizador,
   para a classificação e para as artes — a prévia do cadastro só vale se for o
   mesmo desenho. */
.brand-tile { --tile: 3rem;
  flex: none; width: var(--tile); height: var(--tile);
  /* Flex, não grid: com `place-items:center` a faixa da grade se dimensiona pelo
     conteúdo, e aí a imagem saía no tamanho natural e vazava a cartela. */
  display: flex; align-items: center; justify-content: center; }
.brand-tile img { width: 100%; height: 100%; object-fit: contain; display: block; }
/* 2:1, a mesma proporção que o cadastro recomenda para o arquivo. */
.brand-tile--rectangle { width: calc(var(--tile) * 2); }
/* O que faz o círculo ser círculo, nas três linhas juntas: ele é o único que
   recorta, e é ele que salva o logo que veio com moldura quadrada em volta. */
.brand-tile--circle { border-radius: 50%; overflow: hidden; }
.brand-tile--circle img { object-fit: cover; }
/* A exceção: logo claro sem fundo, que sumiria no papel. Escuro fixo, não token:
   o fundo da arte é papel claro nos dois temas, e a cartela acompanha a arte. */
.brand-tile--dark { background: #16130F; }
/* Sem logo, o nome ocupa o lugar da cartela, na mesma altura da fileira. A
   largura acompanha o nome (a forma é do logo, não do texto) e a quebra só
   acontece entre palavras: era o que partia "Pinheiro" no meio (2026-08-07). */
.brand-tile--text { width: auto; min-width: var(--tile); padding-inline: var(--sp-2);
  font-family: var(--font-display); font-weight: 800; font-size: 0.62rem;
  line-height: 1.1; text-transform: uppercase; text-align: center; color: var(--text-1);
  overflow-wrap: normal; hyphens: none; }
/* As ações do logo ficam ao lado da prévia, empilhadas e alinhadas à esquerda:
   escolher/trocar em cima, remover embaixo. */
/* Logo da marca: a cartela fica ao lado das ações (é retângulo, não avatar). */
.brand-logo__control { display: flex; align-items: center; gap: var(--sp-3); }
.brand-logo__actions { display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-2); }
.brand-tile--sm { --tile: 2.4rem; }
.brand-tile--lg { --tile: 5rem; }
.brand-tile--lg.brand-tile--text { font-size: 0.85rem; }

/* O "pegador" que diz que a linha se move. A mecânica de arrastar (ver
   layouts/_ui_js) é genérica: quem tem lista ordenável usa este mesmo grip. */
.drag-grip { display: inline-flex; color: var(--text-4); cursor: grab; flex: none;
  /* Só o pegador desiste do gesto do navegador: com isto na linha inteira, o
     dedo não rolaria mais a página a partir dela. */
  touch-action: none; }
.drag-grip svg.lucide { width: 16px; height: 16px; }

.is-dragging { opacity: 0.5; }

/* ==========================================================================
   Arte de export da classificação (Pós-MVP, produto §5.2)
   Board de torneio estático para WhatsApp/Instagram — não é a tela do app.
   Direção aprovada: .interface-design/system.md. Server-rendered; a captura
   em PNG é client-side (shared/_export_js). Paleta local (--ep-*), fontes do DS.

   RELEVO EM ARTE CAPTURADA NÃO USA `box-shadow`. Na captura a arte é clonada
   para dentro de um SVG, e nesse contexto o WebKit (Safari, iPhone) desenha
   `box-shadow` DESLOCADO e com borda dura — vira uma meia-lua ao lado do
   elemento. O Chrome desenha certo, então isso não aparece em teste nenhum
   nosso. Use `filter: drop-shadow(...)`, que segue o recorte; para anel puro
   (sem desfoque), use `border` com `box-sizing:content-box`.
   Reportado por um cliente de iPhone e reproduzido no Safari em 2026-08-19.
   Vale para TODAS as artes desta seção, inclusive as que vierem depois.
   ========================================================================== */
.export-poster{
  /* O pódio usa os tokens do DS direto (--gold/--silver/--bronze), para a arte e
     a tabela não divergirem. Os --ep-* abaixo são o que só existe aqui: papel e
     tinta, as rampas dos anéis/faixas e o verde/vermelho calibrados para papel
     (os --up/--down do DS são para fundo escuro). Os tingimentos das linhas do
     pódio são arte, não token: valores próprios, mais claros que a medalha. */
  --ep-gold-hi:#f6dc9a; --ep-gold-deep:#b98f34;
  --ep-silver-hi:#eef2f6; --ep-silver-deep:#9aa0a6;
  --ep-bronze-hi:#d9a86f; --ep-bronze-deep:#8a5a2b;
  --ep-ink:#241d15; --ep-sand:#5c554a; --ep-clay:#6b6456; --ep-faint:#9a9384;
  --ep-blue:#3f7fc4; --ep-green:#2F8F57; --ep-red:#c2482c;
  --ep-line:rgba(0,0,0,.10);
  /* Marca do ranking no cabeçalho: a altura de DUAS linhas do título
     (2 × 1.02 × 1.72rem), sempre — com nome de uma linha ou de duas, a marca não
     muda de tamanho (decisão do usuário 2026-08-05). */
  --ep-mark:3.5rem;
  /* Posição · foto (2.5rem + o anel de 2px de cada lado) · nome · resultado
     (setinha + vitórias, que andam juntas — ver .export-poster__result). */
  --ep-grid:1.5rem 2.8rem minmax(0,1fr) auto;
  /* Altura do CONTEÚDO da faixa de assinatura (ver .export-poster__foot): é dela
     que a marca tira os 80%. A faixa ocupa um tanto a mais, o respiro entre o fio
     e a marca, que mora na própria regra. Esta metade mora AQUI, com os outros
     measures, e não na faixa: declarada no consumidor, um
     `.export-poster--grid{--ep-foot:…}` seria sombreado pela declaração do
     elemento e não faria nada — calado, que é o tipo de erro contra o qual esta
     seção já se defendeu antes. */
  --ep-foot:.9rem;
  /* Largura FIXA (não max-width): a arte deixa de encolher junto com a tela de
     quem exporta — era isso que quebrava a linha de meta no celular. Segue a
     escala de tipografia do desktop (:root cresce acima de 700px), então a arte
     inteira cresce junto, proporcional: muda a resolução do PNG, não o layout.
     ÚNICA exceção: a grade de combinações (.export-poster--grid, lá embaixo), que
     não cabe em 30rem — mas a largura dela também não vem da tela: vem do número
     de colunas, decidido no servidor pelo tamanho do ranking. */
  position:relative; width:30rem; margin:0 auto;
  border:1px solid var(--ep-line); border-radius:18px; overflow:hidden;
  color:var(--ep-ink); font-family:var(--font-body);
  background:
    radial-gradient(140% 40% at 82% 2%, rgba(233,78,27,.10), transparent 46%),
    radial-gradient(120% 34% at 6% 6%, rgba(233,196,106,.16), transparent 52%),
    linear-gradient(180deg,#FFFDF8 0%,#FBF5EA 46%,#F5EEDF 100%);
  /* A ÚNICA `box-shadow` da seção, e ela é da PRÉVIA na tela, não da imagem: o
     papel flutua sobre o fundo enquanto a arte está à mostra. Como este é
     justamente o nó que a captura fotografa, ela é anulada em dois pontos, e os
     dois têm guarda no `spec/design/export_art_shadows_spec.rb`: no clone que
     vira imagem (`shared/_export_js`) e dentro do story (logo abaixo), onde o
     papel não é a raiz da captura. Relevo NOVO não segue por aqui: vale a regra
     do cabeçalho da seção. */
  box-shadow:0 12px 34px -18px rgba(60,44,20,.45);
}
/* Prévia na tela: rola de lado quando a tela é menor que a arte (o padding evita
   que o overflow corte a sombra do card). */
.export-preview{ overflow-x:auto; padding:.35rem .35rem 2rem; }
/* Arte que não se mostra na tela, só vai para a imagem (tela do sorteio, M-019):
   sai de vista sem sair do desenho — `display:none` não tem layout, e a captura
   precisa de layout.
   `fixed`, e não `absolute`: a arte é alta (um pôster inteiro) e, ancorada no
   ancestral posicionado, ela entrava na conta de rolagem dele. Dentro da folha
   de compartilhar (que é `position:fixed` e rola sozinha) isso dava uma barra de
   rolagem de vários "quilômetros" para um conteúdo de meia dúzia de linhas — à
   mostra no Firefox, e escondida atrás da barra sobreposta no Chrome e no Safari
   (2026-08-19). Presa à janela, a arte não pertence a nenhum rolável — e isso
   vale ENQUANTO nenhum ancestral dela posicionar por conta própria (`transform`,
   `filter`, `contain:paint`, `will-change` desses): qualquer um deles vira o
   bloco de contenção do `fixed` e a barra quilométrica volta calada. Hoje a
   folha não tem nenhum; o `.modal` tem (`transform` de centragem), então arte de
   export dentro de modal pede outra saída. */
.export-offscreen{ position:fixed; left:-200vw; top:0; pointer-events:none; }

.export-poster__head{ padding:1.5rem 1.25rem 0; text-align:center; }
.export-poster__eyebrow{ display:flex; align-items:center; justify-content:center; gap:.5rem;
  font-family:var(--font-mono); font-size:.66rem; font-weight:700; letter-spacing:.13em;
  text-transform:uppercase; color:var(--gold); }
/* O halo do ponto sai por `border` com `content-box`, e NÃO por `box-shadow`
   (ver a nota da seção): o anel fica idêntico e o desenho não depende de como o
   motor trata sombra no clone. `drop-shadow` não serve aqui — ele não tem spread,
   e o halo é anel puro, sem desfoque. */
.export-poster__dot{ box-sizing:content-box; width:.42rem; height:.42rem; border-radius:50%;
  background:var(--brand); border:.22rem solid rgba(233,78,27,.16); }
.export-poster__title{ margin-top:.6rem; }
/* Logo do ranking centrado acima do nome: o cabeçalho é centrado e um nome
   comprido não briga com ele. Sem logo, nada muda (o elemento nem sai). */
/* Logo e nome se lêem juntos ("quem é este ranking"), como no card da lista: o
   logo à esquerda do nome e o PAR centrado, para o cabeçalho seguir simétrico.
   `align-items:center` alinha o logo pelo meio do nome, inclusive quando ele
   quebra em duas linhas. Sem logo, sobra só o `h2` e nada muda. */
/* Marca + nome como um conjunto só, centrado no papel: a marca fica COLADA ao
   nome (decisão do usuário 2026-08-05), não reservada num canto. O `max-width`
   é para a arte da grade, que é a mais larga (~64rem): sem teto, o conjunto se
   espalharia. */
.export-poster__brandline{ display:flex; align-items:center; justify-content:center; gap:.6rem;
  max-width:30rem; margin-inline:auto; }
.export-poster__mark{ width:var(--ep-mark); height:var(--ep-mark); flex:none; }
.export-poster__brandline h2{ min-width:0; } /* deixa o nome quebrar dentro do conjunto */
/* Linhas e corpo vêm calculados do servidor (ApplicationHelper#poster_title_fit):
   a arte vira imagem, então quem decide onde o nome quebra — e em que corpo — é a
   conta, não o acaso. O valor de projeto (1.72rem) é o padrão. */
.export-poster__title h2{ margin:0; font-family:var(--font-display); font-size:var(--ep-title-size,1.72rem);
  line-height:1.02; font-weight:800; letter-spacing:.01em; text-transform:uppercase; color:#1b160f; }
/* Cada linha já veio pronta: `nowrap` impede uma segunda quebra por cima da
   nossa, e é o que faz a caixa do título ter a largura da MAIOR linha — sem isso
   ela ocuparia todo o espaço livre e a marca ficaria longe do nome. */
.export-poster__title-line{ display:block; white-space:nowrap; }
.export-poster__cat{ --cat-track:.18em;
  display:inline-block; margin-top:.55rem; font-family:var(--font-mono);
  font-size:.6rem; font-weight:700; letter-spacing:var(--cat-track); text-transform:uppercase;
  color:var(--brand-on-light); background:rgba(233,78,27,.08); border:1px solid rgba(233,78,27,.35);
  /* O espaçamento entre letras sobra depois da última letra: desconta à direita
     (mesma medida, daí a variável). Com o line-height colado (como nos demais
     chips), o padding igual centra o maiúsculo também na vertical. */
  line-height:1; padding:.34rem calc(.6rem - var(--cat-track)) .34rem .6rem; border-radius:6px; }

.export-poster__board{ margin:1.15rem .9rem 0; border:1px solid var(--ep-line); border-radius:14px;
  overflow:hidden; background:rgba(255,255,255,.55); }
.export-poster__cols{ display:grid; grid-template-columns:var(--ep-grid); gap:.5rem; align-items:center;
  padding:.65rem .85rem .5rem; font-family:var(--font-mono); font-size:.62rem; font-weight:700;
  letter-spacing:.1em; text-transform:uppercase; color:var(--ep-faint); border-bottom:1px solid var(--ep-line); }
.export-poster__cols-player{ grid-column:2 / span 2; }      /* começa na foto */
.export-poster__cols-wins{ grid-column:4; text-align:right; }

.export-poster__row{ position:relative; display:grid; grid-template-columns:var(--ep-grid);
  gap:.5rem; align-items:center; padding:.5rem .85rem; }
.export-poster__row + .export-poster__row{ border-top:1px solid rgba(0,0,0,.06); }
.export-poster__row::before{ content:""; position:absolute; left:0; top:0; bottom:0; width:.28rem; background:transparent; }
.export-poster__row--r1{ background:linear-gradient(102deg, rgba(233,196,106,.14), rgba(233,196,106,.42) 42%, rgba(233,196,106,.16) 72%, transparent); }
.export-poster__row--r1::before{ background:linear-gradient(180deg,var(--ep-gold-hi),var(--ep-gold-deep)); }
.export-poster__row--r2{ background:linear-gradient(102deg, rgba(150,160,172,.10), rgba(150,160,172,.28) 42%, rgba(150,160,172,.10) 72%, transparent); }
.export-poster__row--r2::before{ background:linear-gradient(180deg,var(--ep-silver-hi),var(--ep-silver-deep)); }
.export-poster__row--r3{ background:linear-gradient(102deg, rgba(176,122,70,.10), rgba(176,122,70,.28) 42%, rgba(176,122,70,.10) 72%, transparent); }
.export-poster__row--r3::before{ background:linear-gradient(180deg,var(--ep-bronze-hi),var(--ep-bronze-deep)); }

.export-poster__pos{ font-family:var(--font-num); font-size:1.05rem; font-weight:800;
  font-variant-numeric:tabular-nums; text-align:center; color:var(--ep-clay); }
.export-poster__pos::after{ content:"º"; } /* 1º, 2º… como na tabela */
.export-poster__row--r1 .export-poster__pos{ color:var(--gold); }
.export-poster__row--r2 .export-poster__pos{ color:var(--silver); }
.export-poster__row--r3 .export-poster__pos{ color:var(--bronze); }

/* Relevo por `drop-shadow` — ver a nota do cabeçalho desta seção. */
.export-poster__ava{ display:inline-flex; width:max-content; padding:2px; border-radius:50%; background:var(--ep-blue);
  filter:drop-shadow(0 2px 5px rgba(60,44,20,.22)); }
/* 2.5rem = os mesmos 40px da foto na tabela do app (a arte é toda em rem e cresce
   junto com a escala do desktop, então a proporção se mantém). */
.export-poster__ava .avatar{ width:2.5rem; height:2.5rem; }
.export-poster__ava--r1{ background:linear-gradient(160deg,var(--ep-gold-hi),var(--ep-gold-deep)); }
.export-poster__ava--r2{ background:linear-gradient(160deg,var(--ep-silver-hi),var(--ep-silver-deep)); }
.export-poster__ava--r3{ background:linear-gradient(160deg,var(--ep-bronze-hi),var(--ep-bronze-deep)); }

.export-poster__who{ min-width:0; }
.export-poster__who b{ display:block; font-family:var(--font-display); font-size:.9rem;
  font-weight:800; letter-spacing:.01em; text-transform:uppercase; color:var(--ep-ink); line-height:1.12; }
/* Legibilidade da imagem: 0.56rem (~9px) em mono MAIÚSCULO era o pior caso — a
   arte ainda é recomprimida pelo WhatsApp e vista no celular. As colunas são em
   `ch`, então acompanham o corpo sozinhas; a coluna do nome tem folga de sobra. */
.export-poster__meta{ display:grid; grid-template-columns:10ch 11ch auto; gap:.5rem;
  margin-top:.2rem; font-family:var(--font-mono); font-size:.72rem;
  letter-spacing:.02em; text-transform:uppercase; color:var(--ep-clay); font-variant-numeric:tabular-nums; }
.export-poster .standings__f{ color:var(--ep-red); } /* mesmo destaque da tabela, no vermelho de papel */

/* Setinha colada no número, o bloco todo encostado na direita. */
.export-poster__result{ display:flex; align-items:center; justify-content:flex-end; gap:.3rem; }
/* A seta vem do partial da tabela (shared/_standings_trend); aqui só o tamanho e
   as cores calibradas para papel (as do DS são para fundo escuro). */
/* A setinha na arte: o mesmo ícone da tabela, recolorido para papel. Sem
   invólucro próprio — ele existia só para pendurar estas cores, e obrigava a
   regra "sem variação, nada" a ser repetida no chamador. */
.export-poster__result .standings__trend svg.lucide{ width:1rem; height:1rem; }
.export-poster__result .standings__trend--up{ color:var(--ep-green); }
.export-poster__result .standings__trend--down{ color:var(--ep-red); }
.export-poster__result .standings__trend--same{ color:var(--ep-faint); }

/* Sem largura reservada: reservar dois dígitos deixava quase um dígito de vão à
   esquerda do número, e a setinha ficava do outro lado dele. O número sai do
   tamanho do que tem, a setinha encosta. Os números seguem alinhados (a coluna é
   a última, colada na direita); num ranking que passe de 9 vitórias, só a setinha
   das linhas de dois dígitos anda um dígito para a esquerda. */
.export-poster__wins{ font-family:var(--font-num); font-size:1.42rem; font-weight:800;
  font-variant-numeric:tabular-nums; letter-spacing:-.02em; color:var(--ep-ink); white-space:nowrap; }
.export-poster__row--r1 .export-poster__wins{ color:var(--gold); }
.export-poster__row--r2 .export-poster__wins{ color:var(--silver); }
.export-poster__row--r3 .export-poster__wins{ color:var(--bronze); }

.export-poster__note{ margin:.75rem .9rem 0; font-size:.62rem; font-style:italic;
  color:var(--ep-clay); text-align:center; }

/* ---------- Créditos: o mesmo bloco na arte e nas telas (M-033, §8.4) ---------- */
/* Quem realiza, quem patrocina, quem apoia e onde se joga. Na tela é um card no
   fim da classificação; na arte (modificador `--poster`) é o pé do papel, que
   vem da moldura e vale para as quatro imagens de uma vez. Uma definição só:
   é ela que faz a prévia do cadastro valer. */
.brand-credits { display:flex; flex-direction:column; gap:var(--sp-4);
  background:var(--surface-card); border:1px solid var(--border); border-radius:var(--r-lg);
  box-shadow:var(--elev-card); padding:var(--sp-4); }
.brand-credits__group { display:flex; flex-direction:column; align-items:center; gap:var(--sp-2); }
.brand-credits__label { font-family:var(--font-mono); font-size:var(--fs-eyebrow); font-weight:700;
  letter-spacing:.16em; text-transform:uppercase; color:var(--text-3); }
/* A fileira quebra em linhas quando não cabe mais (o tamanho da cartela é fixo,
   ver o bloco MARCA & PATROCÍNIO). */
.brand-credits__marks { display:flex; flex-wrap:wrap; align-items:center; justify-content:center; gap:var(--sp-2); }
/* A proporção entre as famílias é decisão do usuário, calibrada sobre a arte
   pronta (2026-08-07): patrocínio pesa IGUAL à realização (quem paga cota aparece
   do mesmo tamanho de quem organiza) e só o apoio fica um degrau abaixo — por
   isso só ele tem regra; os outros dois ficam no tamanho base da cartela. */
.brand-credits__group--support { gap:var(--sp-1); }
.brand-credits__group--support .brand-tile { --tile:2.4rem; }
/* O local fecha o bloco, com o risco em cima separando a área dele: é onde se
   joga, não mais uma família de marca. O risco sai de quem vem ANTES — num
   ranking que só tem local, ele seria um traço solto. */
.brand-credits__venue { display:flex; flex-direction:column; align-items:center; gap:2px; text-align:center; }
.brand-credits__group + .brand-credits__venue { padding-top:var(--sp-3); border-top:1px solid var(--border); }
.brand-credits__venue-name { display:flex; align-items:center; justify-content:center; gap:6px;
  max-width:100%; line-height:1.15; font-family:var(--font-display); font-size:var(--fs-strong);
  font-weight:800; letter-spacing:.02em; text-transform:uppercase; color:var(--text-1); }
.brand-credits__venue-name svg.lucide { width:16px; height:16px; color:var(--brand); flex:none; }
.brand-credits__venue-addr { font-family:var(--font-mono); font-size:var(--fs-eyebrow); color:var(--text-3); }

/* ---------- O mesmo bloco no papel da arte ---------- */
/* Sem card: no papel ele é o pé da peça, separado por um fio. O `max-width` não é
   estética — a grade de combinações se dimensiona pelo conteúdo, e uma fileira
   que quebra em linhas conta pela SOMA nessa medição: sem o teto, um ranking com
   muitos patrocinadores alargaria a arte inteira e as colunas deixariam de bater
   com o que o servidor decidiu. De quebra, o bloco para de virar uma fita fina
   na arte da grade, que é bem mais larga (28.2rem é a largura útil da canônica). */
.brand-credits--poster { background:transparent; border:0; border-radius:0; box-shadow:none;
  margin:1rem auto 0; padding:.85rem 0 0; border-top:1px solid var(--ep-line);
  max-width:28.2rem; gap:.7rem; }
.brand-credits--poster .brand-credits__group { gap:.3rem; }
.brand-credits--poster .brand-credits__label { font-size:.52rem; color:var(--ep-faint); }
.brand-credits--poster .brand-credits__marks { gap:.4rem; }
.brand-credits--poster .brand-credits__group--support { gap:.22rem; }
.brand-credits--poster .brand-tile--text { color:var(--ep-ink); font-size:.82rem; }
.brand-credits--poster .brand-credits__group--support .brand-tile--text { font-size:.66rem; }
.brand-credits--poster .brand-credits__group + .brand-credits__venue { padding-top:.7rem;
  border-top:1px solid var(--ep-line); }
.brand-credits--poster .brand-credits__venue { gap:.1rem; }
/* Uma linha SEMPRE aqui (decisão do usuário 2026-08-07): quebrado, o nome
   comprido montava em cima do endereço na imagem gerada. O `nowrap` garante a
   linha e o corpo vem calculado do servidor (`Export::VenueName`) — na imagem não
   há como ajustar depois de ver. Na tela, que rola e reflui, quebrar não machuca. */
.brand-credits--poster .brand-credits__venue-name { white-space:nowrap; font-size:var(--venue-size,.92rem);
  color:var(--ep-ink); }
.brand-credits--poster .brand-credits__venue-name svg.lucide { width:.9rem; height:.9rem; }
.brand-credits--poster .brand-credits__venue-addr { font-size:.6rem; color:var(--ep-clay); }

/* ==========================================================================
   A classificação no formato de story do Instagram (M-040)
   ==========================================================================
   O mesmo pôster da arte canônica, desenhado num quadro 9:16. O que muda aqui é
   só o QUADRO e alguns cortes: a peça de story é vitrine, não consulta, então
   sai o que não se lê de longe (a categoria, o cabeçalho de colunas) e o que
   sobra se arruma para ocupar a largura.

   O posicionamento NÃO está aqui: a arte é ancorada por zonas (cabeçalho na
   faixa de cima, lista começando na área visível, primeira linha dos créditos
   acima da barra de responder) e a conta depende de fonte carregada e de quebra
   de linha — vive no `story-fit.js`, com o porquê de cada zona e a medida das
   faixas tirada de um story de verdade.
   Desenho aprovado sobre `docs/design/prototype/export-standings-story.html`. */
.export-story{
  /* 1080x1920 na imagem. Aqui o quadro tem a largura canônica da arte e o 9:16
     sai do `aspect-ratio`; a captura recebe a largura-alvo (`data-export-width`)
     e resolve o fator pelo tamanho real do quadro. O papel vaza de ponta a ponta
     — é isso que acaba com a faixa vazia que sobrava ao publicar. */
  position:relative; width:30rem; aspect-ratio:9 / 16; margin:0 auto; overflow:hidden;
  border-radius:26px;
  background:
    radial-gradient(140% 34% at 82% 3%, rgba(233,78,27,.12), transparent 48%),
    radial-gradient(120% 30% at 6% 6%, rgba(233,196,106,.20), transparent 54%),
    linear-gradient(180deg,#F6EEDC 0%,#EFE4CE 52%,#E7D9BE 100%);
}
/* A caixa que o script posiciona: o `top` vem de lá, a centralização é daqui. */
.export-story__fit{ position:absolute; left:0; right:0; display:flex; justify-content:center; }
.export-story__fit > *{ transform-origin:top center; }
/* Papel dentro de papel não flutua. E não é só desenho: na arte canônica a captura
   anula a sombra do papel no clone, porque o papel É a raiz fotografada — aqui
   quem vira imagem é a MOLDURA, e o pôster é emprestado para dentro dela, fora do
   alcance daquela anulação. Sem esta linha, a sombra é interna ao PNG e o Safari
   a desenha deslocada: o story sai com a sombra torta (2026-08-19). */
.export-story__fit .export-poster{ box-shadow:none; }

/* Fora da peça de vitrine: a categoria e o cabeçalho de colunas cabiam na arte
   que se lê parada, não na que passa num story (decisão do usuário 2026-08-18). */
.export-story .export-poster__cat,
.export-story .export-poster__cols{ display:none; }

/* Cabeçalho em duas áreas: marca à esquerda, nome centrado no espaço dele, etapa
   à direita. A etapa deixa de ocupar uma linha inteira só para ela. */
.export-story .export-poster__head{ display:flex; align-items:center; gap:1rem;
  text-align:left; padding:1.05rem 1.15rem .95rem; }
.export-story .export-poster__title{ margin-top:0; min-width:0; flex:1 1 auto; }
.export-story .export-poster__brandline{ justify-content:flex-start; }
.export-story .export-poster__brandline h2{ flex:1 1 auto; }
/* O nome já vem quebrado do servidor (`Export::PosterTitle`), medido para a
   largura CANÔNICA da arte. Aqui a caixa dele é menor (divide a linha com a marca
   e com a etapa), então o corpo cai um degrau e as linhas do servidor continuam
   valendo. Deixar o navegador requebrar era o caminho óbvio e é o errado: o nome
   ficava a um milímetro de virar três linhas, e a mesma arte quebrava diferente
   conforme a fonte tivesse chegado ou não. */
.export-story .export-poster__title h2{ text-align:center;
  font-size:calc(var(--ep-title-size,1.72rem) * .8); }
.export-story .export-poster__eyebrow{ order:2; flex:0 0 auto; align-self:stretch;
  padding-left:1rem; border-left:1px solid var(--ep-line); }

/* Créditos em duas áreas: o local à esquerda e o primeiro grupo de marcas ao
   lado; grupo que sobra desce e ocupa a largura toda. É essa primeira linha que
   o `story-fit.js` mantém acima da barra de responder. */
.export-story .brand-credits{ display:grid; grid-template-columns:1fr 1fr;
  align-items:center; gap:.55rem .85rem; }
/* O fio curto que a arte canônica desenha sobre o local não cabe aqui: em duas
   colunas ele nasce por cima de uma coluna só, e o filete vertical entre elas já
   separa local e marcas. O `--poster` entra no seletor para empatar com a regra
   que desenha o fio e ganhar dela por ordem (o story é sempre a arte canônica
   emprestada, então a classe está lá) — é como as outras correções do story
   também vencem a arte canônica, logo abaixo. */
.export-story .brand-credits--poster .brand-credits__venue{ grid-column:1; grid-row:1;
  padding-top:0; border-top:0; }
.export-story .brand-credits__group:first-of-type{ grid-column:2; grid-row:1;
  padding-left:.85rem; border-left:1px solid var(--ep-line); }
.export-story .brand-credits__group:nth-of-type(n+2){ grid-column:1 / -1; }
/* Sozinho de um lado ou de outro, volta a ser uma linha de ponta a ponta. */
.export-story .brand-credits__venue:only-child,
.export-story--no-venue .brand-credits__group:first-of-type{
  grid-column:1 / -1; padding-left:0; border-left:0; }
/* O local agora tem metade da largura: perde um degrau de corpo e, ao contrário
   da arte canônica, PODE quebrar em duas linhas. Lá o `nowrap` existe porque o
   corpo vem calculado do servidor para caber em uma; aqui a largura é outra, e
   deixar quebrar é mais seguro que estourar a coluna. */
.export-story .brand-credits__venue-name{ white-space:normal;
  font-size:calc(var(--venue-size,.92rem) * .85); }
.export-story .brand-credits__venue-addr{ font-size:.52rem; line-height:1.35; }

/* ---------- Arte do sorteio (M-019): a combinação do dia e as duplas ---------- */
/* Sem as colunas da classificação, o board respira mais (vale para o elenco). */
.export-poster--pairs .export-poster__board,
.export-poster--roster .export-poster__board{ margin:1rem .9rem 0; }
/* O que a imagem vem dizer: a combinação, no corpo de um título. */
.export-poster__draw{ margin-top:1.15rem; text-align:center; }
.export-poster__combo{ margin:0; font-family:var(--font-display); font-size:1.15rem; font-weight:800;
  letter-spacing:.02em; text-transform:uppercase; color:var(--brand-on-light); }
.export-poster__by{ margin:.15rem 0 0; font-family:var(--font-mono); font-size:.62rem;
  letter-spacing:.06em; text-transform:uppercase; color:var(--ep-faint); }
/* Uma dupla por linha, com o "&" na coluna do meio: os nomes ficam alinhados de
   cima a baixo, como no card das duplas no app. */
.export-poster__pair{ display:grid; grid-template-columns:minmax(0,1fr) auto minmax(0,1fr);
  column-gap:.7rem; align-items:center; padding:.6rem .85rem; border-bottom:1px solid var(--ep-line); }
.export-poster__pair:last-child{ border-bottom:0; }
/* Um apelido comprido SEM espaço ("MarcosOliveiraJunior") não tem onde quebrar e
   vazaria para fora do papel na imagem gerada. `break-word` cobre isso sem o
   efeito colateral do `anywhere` (ver `.export-poster__member-name`). */
.export-poster__player{ font-family:var(--font-display); font-size:.86rem; font-weight:800;
  letter-spacing:.01em; text-transform:uppercase; color:var(--ep-ink); line-height:1.15;
  overflow-wrap:break-word; }
.export-poster__pair .export-poster__player:first-child{ text-align:right; }
.export-poster__amp{ font-family:var(--font-body); font-size:.72rem; font-weight:700; color:var(--ep-faint); }
/* Dupla incompleta: a arte diz que aquele lado está sem ninguém, em vez de
   apresentar um jogador que não vai entrar em quadra. */
.export-poster__wo{ display:block; font-family:var(--font-mono); font-size:.56rem; font-weight:700;
  letter-spacing:.08em; text-transform:uppercase; color:var(--ep-red); }
/* Arte do fim da etapa: a mesma linha de dupla com o que ela fez no dia numa
   quarta coluna, à direita (é o número que a imagem vem contar). */
.export-poster__pair--result{ grid-template-columns:minmax(0,1fr) auto minmax(0,1fr) 3.4rem; }
.export-poster__pair-wins{ font-family:var(--font-num); font-variant-numeric:tabular-nums;
  font-size:1.28rem; font-weight:800; color:var(--brand-on-light); text-align:right; line-height:1; }
.export-poster__pair-wins small{ margin-left:.16rem; font-family:var(--font-mono); font-size:.5rem;
  font-weight:700; letter-spacing:.08em; text-transform:uppercase; color:var(--ep-faint); }

/* ---------- Arte do elenco: quem está no ranking e as vagas que faltam ---------- */
/* Seções empilhadas (Fixos, Suplentes; no misto, por gênero) dentro do mesmo board. */
.export-poster__group{ padding:.75rem .85rem; }
.export-poster__group + .export-poster__group{ border-top:1px solid var(--ep-line); }
.export-poster__group-label{ display:flex; align-items:baseline; justify-content:space-between; gap:.7rem;
  margin:0 0 .6rem; font-family:var(--font-mono); font-size:.62rem; font-weight:700;
  letter-spacing:.13em; text-transform:uppercase; color:var(--ep-clay); }
.export-poster__group-count{ font-family:var(--font-num); font-variant-numeric:tabular-nums;
  font-size:.78rem; color:var(--ep-ink); flex:none; }
/* Duas colunas, como o grid do elenco no app. */
.export-poster__members{ display:grid; grid-template-columns:1fr 1fr; gap:.55rem .8rem; }
.export-poster__member{ display:flex; align-items:center; gap:.5rem; min-width:0; }
.export-poster__member .avatar{ width:2.1rem; height:2.1rem; flex:none; }
/* `flex:1` para a caixa do nome valer a COLUNA, e não o texto (2026-08-11). Como
   item flexível ela se encolhia até o tamanho exato das letras, ficando com zero
   folga: bastava o texto sair um fio mais largo na hora de virar imagem (as
   fontes vêm de outro domínio e o capturador pode trocá-las — pendência do export
   desde 2026-07-22) para a ÚLTIMA letra não caber e descer sozinha. Era isso que
   cortava "KLEBER" em "KLEBE" + "R" na imagem que vai para o grupo, sobrando
   espaço vazio ao lado. Com a coluna inteira, um fio a mais não muda nada.
   `break-word` e não `anywhere`: `anywhere` derruba a largura mínima da caixa
   para uma letra, o que reabriria o mesmo buraco por outro caminho. */
.export-poster__member-name{ flex:1; min-width:0;
  font-family:var(--font-display); font-size:.8rem; font-weight:800;
  letter-spacing:.01em; text-transform:uppercase; color:var(--ep-ink); line-height:1.15;
  overflow-wrap:break-word; }
/* Vaga a preencher: mesmo círculo tracejado da tela, na tinta de papel. */
.export-poster__member--slot .export-poster__member-name{ color:var(--ep-faint); }
.export-poster__slot{ width:2.1rem; height:2.1rem; border-radius:50%; flex:none; display:grid;
  place-items:center; border:1.5px dashed rgba(0,0,0,.22); color:var(--ep-faint); }
.export-poster__slot svg.lucide{ width:1rem; height:1rem; }

/* ---------- Arte da grade de combinações (M-028) ---------- */
/* O mapa da temporada inteira numa imagem — o formato que os grupos montavam na
   mão em planilha e mandavam no WhatsApp. É a única arte que NÃO cabe na largura
   fixa de 30rem: ela cresce com o número de colunas, e a prévia na tela rola de
   lado (.export-preview). O número de colunas é do servidor
   (`combinations_grid_columns`), não de media query: quem decide é o tamanho do
   ranking, não o tamanho da tela de quem exporta. */
/* `margin-inline:0` desfaz o `margin:0 auto` da moldura: numa arte MAIS LARGA que
   a coluna do app, centralizar joga metade do transbordo para a esquerda, e essa
   metade o navegador não deixa alcançar pela rolagem (só a da direita rola). */
.export-poster--grid{ width:max-content; margin-inline:0; }
/* A largura do card é a largura que sobra para os NOMES (ver `Export::ComboName`,
   que calcula o corpo em que eles cabem). Alargar aqui não substitui o cálculo:
   ele só faz o encolhimento quase nunca precisar entrar. Barato porque a arte é
   `max-content` e não disputa largura com a coluna do app. Até 2026-08-11 eram
   13,5rem/15rem, e ali um nome de 15 letras já batia no limite. */
/* `grid-auto-rows:1fr`: toda fileira tem a altura da mais alta, e o card ocupa a
   célula inteira. É o que iguala o tamanho de TODOS os cards da arte, e não só
   dos que dividem a fileira. */
.export-grid{ display:grid; gap:.55rem; margin:1.15rem .9rem 0; grid-auto-rows:1fr; }
.export-grid--3{ grid-template-columns:repeat(3,16.5rem); }
.export-grid--4{ grid-template-columns:repeat(4,15.25rem); }

/* Uma combinação. Todos os cards têm o MESMO tamanho (decisão do usuário
   2026-08-11), e não o tamanho do próprio conteúdo: a sorteada carrega a data no
   cabeçalho e pode ter uma troca do dia em duas linhas, então ela crescia e a
   grade virava um mosaico irregular, com o card do pote parecendo menor por
   estar vazio. Esticar é o que faz a arte ler como tabela.
   *(Revê o `align-self:start` que existia até aqui, que segurava o card no alto
   da fileira justamente para ele NÃO esticar.)* */
.export-combo{ border:1px solid var(--ep-line); border-radius:10px; overflow:hidden;
  background:rgba(255,255,255,.55); display:flex; flex-direction:column; }
/* Já sorteada: faixa na laranja da marca. O que ainda está no pote fica
   esmaecido — o olho vai primeiro ao que aconteceu, e a arte "acende" ao longo
   da temporada em vez de apagar (o inverso da planilha que ela substitui). */
.export-combo--drawn{ background:#fff; border-color:rgba(233,78,27,.35);
  filter:drop-shadow(0 1px 3px rgba(60,44,20,.14)); }
/* O contorno tem de aguentar o esmaecido: a linha padrão do papel (10% de preto)
   com `opacity:.62` por cima chega ao PNG em ~6% e some. Aqui ela nasce mais
   forte para chegar visível do outro lado, e o card no pote continua um card, não
   um borrão. O mesmo vale para o filete sob o cabeçalho. */
.export-combo--pending{ opacity:.62; border-color:rgba(0,0,0,.30); }
.export-combo--pending .export-combo__head{ border-bottom-color:rgba(0,0,0,.22); }

/* "Combinação N" à esquerda e, na que já caiu, "Etapa N" à direita — a data cai
   na linha de baixo, encostada na direita. Os três numa linha só não caberiam na
   largura do card, e o que a imagem precisa dizer é, nesta ordem: qual
   combinação é, que etapa ela virou, e quando. */
.export-combo__head{ display:grid; grid-template-columns:minmax(0,1fr) auto; align-items:baseline;
  gap:0 .4rem; padding:.4rem .5rem .3rem; border-bottom:1px solid var(--ep-line);
  background:rgba(0,0,0,.025); }
.export-combo--drawn .export-combo__head{ background:var(--brand); border-bottom-color:var(--brand);
  padding:.38rem .5rem .34rem; }
.export-combo__n{ font-family:var(--font-mono); font-size:.6rem; font-weight:700; letter-spacing:.05em;
  text-transform:uppercase; color:var(--ep-clay); white-space:nowrap; }
.export-combo--drawn .export-combo__n{ color:#fff; }
.export-combo__stage{ font-family:var(--font-mono); font-size:.6rem; font-weight:700;
  letter-spacing:.05em; text-transform:uppercase; color:#fff; text-align:right; white-space:nowrap; }
/* A data ocupa a linha inteira abaixo, encostada na direita. */
.export-combo__when{ grid-column:1 / -1; font-family:var(--font-mono); font-size:.58rem;
  letter-spacing:.03em; color:rgba(255,255,255,.82); text-align:right; white-space:nowrap; }

/* Uma dupla: NOME & NOME | vitórias. Os nomes encostam no "&", que fica na coluna
   do meio — é o que alinha as duplas de cima a baixo dentro do card. Ordem
   natural da combinação, não por vitórias: a mesma dupla não pode mudar de linha
   de uma imagem para a outra. */
.export-combo__pair{ display:grid; grid-template-columns:minmax(0,1fr) .7rem minmax(0,1fr) 1.15rem;
  align-items:center; gap:.15rem; padding:.22rem .5rem; line-height:1.15;
  font-size:var(--ep-combo-name, .66rem); }
/* Com os cards todos do mesmo tamanho, o que sobra é dividido entre as linhas em
   vez de virar um vão morto no pé do card. */
.export-combo__pair{ flex:1; }
.export-combo__pair + .export-combo__pair{ border-top:1px solid rgba(0,0,0,.055); }
/* `break-word` pelo mesmo motivo do `.export-poster__member-name`; e aqui o corpo
   já vem calculado (`Export::ComboName`), então quebrar é o último dos recursos. */
.export-combo__side{ min-width:0; font-weight:600; color:var(--ep-ink); overflow-wrap:break-word; }
.export-combo__side--a{ text-align:right; }
.export-combo__amp{ text-align:center; font-size:.6rem; color:var(--ep-faint); }
/* Troca do dia, na convenção do app: quem faltou riscado em vermelho, quem entrou
   em verde logo abaixo (sem ícone — a largura é do apelido). */
.export-combo__out{ display:block; text-decoration:line-through; color:var(--ep-red); opacity:.85; }
.export-combo__in{ display:block; color:var(--ep-green); }
/* Fixo que saiu e ainda não foi substituído: destacado, mas SEM risco — ninguém
   faltou ali, o lugar é que está vago (mesma leitura do `.combo__side--open` na
   tela, na tinta de papel). */
.export-combo__open{ display:block; color:var(--ep-red); font-weight:700; }
.export-combo__wins{ font-family:var(--font-num); font-size:.86rem; font-weight:800;
  font-variant-numeric:tabular-nums; text-align:right; color:var(--ep-ink); }
/* Sem número ainda (no pote, ou etapa correndo — provisório não vai para a arte):
   o traço segura a coluna, para os nomes não mudarem de largura entre os cards. */
.export-combo__wins--none{ color:rgba(0,0,0,.14); }

/* Rodapé apertado: a assinatura é assinatura, não chamada. A marca abre a faixa,
   o endereço encosta nela e o selo vai para a ponta (quem o empurra é o
   `margin-left:auto` dele, mais abaixo). O desenho nasceu no story e virou o
   padrão das quatro artes.
   A altura é FIXA (`--ep-foot`), não a soma do que está dentro: a assinatura tem
   o mesmo fio em toda arte, e nada que caia ali (a marca, um endereço mais
   comprido, o selo de teste) pode fazê-la crescer. Daí `height` em vez de
   `padding`. Em troca, a altura e os corpos de dentro são um CONJUNTO: só a
   marca acompanha a faixa sozinha (`height:80%`); o endereço e o selo são
   medidas próprias, folgadas para caber em .9rem. Mexer no `--ep-foot` para
   baixo é mexer nos três — o papel tem `overflow:hidden`, então o que não couber
   sai cortado no PNG, sem aviso. (Decisão do usuário 2026-08-19.)
   O ar entre o fio e a marca é `padding-top`, e a caixa é `content-box` para ele
   não comer a altura de onde a marca tira os 80% (o `border-box` global a
   encolheria, calada). Quem for mexer na altura mexe nos dois: eles não andam
   juntos. (Decisão do usuário 2026-08-20.) */
.export-poster__foot{ display:flex; align-items:center; gap:.45rem;
  box-sizing:content-box; height:var(--ep-foot); margin-top:.35rem; padding:.5rem .9rem 0;
  border-top:1px solid var(--ep-line); }
/* A marca é SVG embutido (ver `inline_svg`), não `<img>`: aí `width:auto` sozinho
   deixaria de tirar a largura da proporção do desenho — daí o `aspect-ratio` (a
   caixa do logo.svg), que a mantém no lugar. %-da-faixa e não rem solto: a marca
   acompanha a faixa se ela mudar, em vez de estourá-la calada. */
.export-poster__logo{ height:80%; width:auto; aspect-ratio:965 / 935; flex:none; }
.export-poster__link{ font-family:var(--font-mono); font-size:.54rem; line-height:1;
  color:var(--ep-clay); }
/* Selo de ambiente de teste (M-030) no rodapé da arte: a imagem viaja sozinha
   pelo grupo, e este é o único lugar onde ela pode se identificar. Laranja
   preenchido, como a tarja de dentro do app (decisão do usuário 2026-08-05): o
   aviso não pode se esconder na discrição do rodapé. O `margin-left:auto` é o que
   o joga para a ponta: a faixa em si empacota tudo à esquerda. */
.export-poster__seal{ font-size:.46rem; line-height:1; text-transform:uppercase; letter-spacing:.08em;
  white-space:nowrap; margin-left:auto; padding:.16rem .4rem; border-radius:999px;
  background:var(--brand); color:#fff; flex:none; }

/* ============================================================ LANDING PÚBLICA (M-042) */
/* A única tela feita para quem ainda NÃO é usuário. Alterna blocos de foto
   (largura cheia, escuro por dentro) e de papel (o fundo normal do app); cada
   foto abre o assunto que vem embaixo dela. Direção acertada no protótipo
   docs/design/prototype/landing.html.

   SOBRE `color: var(--text-1)` NOS BLOCOS DE FOTO: parece redundante (o `body`
   já declara a mesma coisa) e não é. A cor se herda já CALCULADA, não como
   token: dentro de um bloco que reabre o escuro, quem não redeclara herda a
   tinta escura do papel e some sobre a foto. No papel, aí sim é redundante, e
   por isso não aparece nas regras de baixo. */

/* A coluna do conteúdo é a mesma das telas de acesso (--col-max). Só ela respeita
   a margem: os blocos de foto sangram até a borda da tela, e isso não se faz de
   dentro de um contêiner com padding — daí a página não ter um shell único. */
/* Gancho da página inteira (`<body class="landing">`): é nele que os degraus de
   tela grande mexem, sobrescrevendo TOKEN em vez de reescrever cada regra —
   mesma forma do `--app-w` do M-004, no fim deste arquivo. */
.landing { --landing-cover-h: 14rem; }
.landing-inner { max-width: var(--col-max); margin: 0 auto; }

/* Bloco de papel: o fundo normal do app, onde mora o texto que se lê. */
.landing-slab { padding: var(--sp-8) var(--gutter); }

/* Bloco de foto que abre um assunto. A foto entra por variável (--band-photo,
   posta na view com o caminho do asset), para uma regra só servir a todos.
   Altura mínima para a FOTO aparecer: sem ela o bloco encolhe até a altura do
   título e a imagem vira tarja escura sem assunto. O título ancora embaixo,
   deixando a parte de cima da foto à mostra. */
.landing-cover {
  min-height: var(--landing-cover-h); display: flex; align-items: flex-end;
  padding: var(--sp-8) var(--gutter) var(--sp-6);
  background-image:
    linear-gradient(180deg, rgba(11, 8, 5, .30) 0%, rgba(11, 8, 5, .50) 45%, rgba(11, 8, 5, .88) 100%),
    var(--band-photo);
  background-size: cover; background-position: center;
}
.landing-cover .landing-inner { width: 100%; }

/* Título de assunto e frase de apoio: os mesmos no papel e sobre a foto. Só a
   cor muda de lugar (ver a nota sobre herança no topo do bloco), e a abertura
   sobe um degrau no corpo por ser a primeira coisa que se lê. */
.landing-title { font-size: var(--fs-title); }
.landing-lead { margin: 0; font-size: var(--fs-body); color: var(--text-2); line-height: 1.55; }
.landing-cover .landing-title { color: var(--text-1); }
.landing-hero .landing-lead { font-size: var(--fs-strong); }

/* Ritmo interno de um assunto no papel: título e corpo. */
.landing-section { display: flex; flex-direction: column; gap: var(--sp-4); }

/* A abertura é o mesmo bloco de foto, com a barra do topo empilhada sobre a
   mensagem — daí `block` em vez do alinhamento ao pé dos demais. */
.landing-hero { display: block; padding: var(--sp-4) var(--gutter) var(--sp-8); background-position: center 62%; }
/* A mensagem da abertura, e NÃO qualquer `.landing-inner` daqui: a barra do topo
   também usa a coluna, e uma regra por descendência a empilharia no centro. */
.landing-hero__message {
  text-align: center; display: flex; flex-direction: column; align-items: center; gap: var(--sp-4);
}
.landing-hero__title { font-size: var(--fs-display); line-height: 1.1; color: var(--text-1); }

/* Barra do topo: a marca à esquerda e o Entrar à direita, onde quem já tem conta
   procura antes de ler qualquer coisa. Vive dentro da foto, sem fundo próprio,
   então não rouba altura da abertura. */
.landing-top {
  margin-bottom: var(--sp-8);
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
}
.landing-top__brand { display: flex; align-items: center; gap: var(--sp-2); }
.landing-top__logo { width: 2rem; height: 2rem; }
/* O "Entrar" precisa ser achado por quem já tem conta, e um contorno fino sobre
   foto passa batido (relato do usuário, 2026-08-26). Ganha corpo sólido e um
   ícone, mas NÃO o laranja: essa cor é do "quero usar no meu grupo", e dois
   botões laranja na mesma tela deixam de dizer qual é o caminho principal. */
.landing-top__enter { background: var(--surface-card); box-shadow: var(--elev-card); }
.landing-top__name { font-family: var(--font-display); font-weight: 800; font-size: var(--fs-strong); color: var(--text-1); }

/* Bloco de foto que carrega uma frase, e não um título de seção. */
.landing-cover--saying { align-items: center; text-align: center; }
.landing-cover__saying {
  margin: 0; font-family: var(--font-display); font-weight: 800;
  font-size: var(--fs-title); line-height: 1.25; color: var(--text-1);
}

/* A dor, antes da solução: fundo de chip para ler como "hoje é assim", e não
   como mais uma promessa do app. */
.landing-pain {
  background: var(--surface-chip); border: 1px solid var(--border);
  border-radius: var(--r-lg); padding: var(--sp-4);
  display: flex; flex-direction: column; gap: var(--sp-2);
}
.landing-pain p { margin: 0; font-size: var(--fs-body); color: var(--text-2); line-height: 1.55; }
.landing-pain strong { color: var(--text-1); }

/* Lista de itens da landing: o que o app faz (cada um num cartão) e como
   funciona (passos numerados, sem cartão). Um componente só, porque a diferença
   entre os dois é a moldura e o respiro, não a estrutura. */
.landing-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-3); }
.landing-list--steps { gap: var(--sp-4); }
.landing-item { display: flex; gap: var(--sp-3); align-items: flex-start; }
.landing-item--card {
  background: var(--surface-card); border: 1px solid var(--border);
  border-radius: var(--r-lg); box-shadow: var(--elev-card); padding: var(--sp-4);
}
.landing-item__body { display: flex; flex-direction: column; gap: var(--sp-1); min-width: 0; }
.landing-item__title { font-family: var(--font-display); font-weight: 800; font-size: var(--fs-strong); }
.landing-item__text { margin: 0; font-size: var(--fs-body); color: var(--text-2); line-height: 1.5; }
/* Ícone tingido de marca (capacidade) e número do passo: mesmo tamanho, formas
   diferentes — o ícone é caixa de toque, o número é bolinha cheia. */
.landing-item__icon {
  flex: none; width: var(--tap); height: var(--tap); border-radius: var(--r-md);
  display: grid; place-items: center;
  color: var(--brand); background: color-mix(in srgb, var(--brand) 14%, var(--surface-chip));
}
.landing-item__num {
  flex: none; width: var(--sp-8); height: var(--sp-8); border-radius: 50%;
  display: grid; place-items: center;
  font-family: var(--font-num); font-variant-numeric: tabular-nums; font-weight: 700;
  font-size: var(--fs-strong); color: #fff; background: var(--brand);
}

/* Frase de apoio embaixo do botão de enviar (o que a pessoa recebe de volta). */
.landing-hint { margin: 0; text-align: center; font-size: var(--fs-meta); color: var(--text-4); }

/* ---- Telas grandes -------------------------------------------------------
   O app é de coluna única mobile-first, e no desktop isso deixa a landing com
   uma faixa estreita de texto no meio de fotos que ocupam a tela inteira: tudo
   parece pequeno (relato do usuário, 2026-08-26). Aqui a coluna cresce e os
   itens se espalham em grade — é a página pública, aberta no computador por
   quem recebeu o link, e ela não pode parecer um app de celular esticado.

   Vale SÓ para a landing. Reorganizar as telas de dentro do app para o desktop é
   outro trabalho, registrado no Bloco F ("Layout multi-coluna no desktop").

   Degraus PRÓPRIOS (768/1024), diferentes dos do app (700/1280/2000, no bloco
   M-004 no fim do arquivo): lá o que cresce é a fonte, aqui é o layout. Os dois
   convivem sem brigar — a escala de fonte só sai do piso perto dos 853px. */
@media (min-width: 768px) {
  .landing { --col-max: 34rem; --landing-cover-h: 18rem; }
}

@media (min-width: 1024px) {
  .landing { --col-max: 46rem; --landing-cover-h: 22rem; }
  .landing-slab { padding: var(--sp-10) var(--gutter); }

  /* A abertura ganha o corpo que uma tela grande pede. Estes tamanhos são crus
     de propósito e MULTIPLICAM a escala fluida do M-004 (que já cresceu o rem):
     é o único lugar do app onde a tipografia é peça de vitrine, não de leitura.
     A frase sobre a foto fica um degrau acima do título de assunto porque ela
     está sozinha numa faixa inteira, e não anunciando o que vem abaixo. */
  .landing-hero { padding: var(--sp-6) var(--gutter) var(--sp-10); }
  .landing-hero__title { font-size: 3.25rem; }
  .landing-hero .landing-lead { font-size: 1.0625rem; }
  .landing-title { font-size: 1.75rem; }
  .landing-cover__saying { font-size: 2rem; }

  /* Linha comprida cansa de ler, mas encolher o bloco o desalinhava de tudo que
     ocupa a coluna inteira (relato do usuário, 2026-08-26). Quem encolhe é a
     LINHA, não a caixa: a frase de apoio para nas 34rem e o quadro da planilha,
     que são dois parágrafos, os põe lado a lado — enche a largura pela mesma
     lógica dos cartões logo abaixo, e cada linha continua curta. */
  .landing-lead { max-width: 34rem; }
  .landing-pain { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--sp-6); padding: var(--sp-6); }

  /* O botão da abertura para de crescer com a coluna: faixa laranja de ponta a
     ponta não lê como botão. */
  .landing-cta { width: auto; min-width: 20rem; padding: 0 var(--sp-8); }

  /* O que sobra de largura vai para a grade: capacidades em duas colunas, os
     três passos lado a lado. É o único lugar do app com layout de duas colunas,
     e cabe aqui porque são cartões independentes, não uma tela de trabalho. */
  .landing-list { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--sp-4); }
  .landing-list--steps { grid-template-columns: repeat(3, 1fr); }

  /* O formulário não vira uma caixa de 1000px (campo largo demais atrapalha em
     vez de ajudar), mas começa na MARGEM da coluna, não centralizado: assim ele
     nasce debaixo do título da faixa que o anuncia. */
  .landing-form { max-width: 32rem; }
}

/* Desktop/telas grandes (M-004): escala fluida e contínua com a largura da
   viewport, sem degraus. Abaixo de 700px fica em 16px (mobile/tablet intactos);
   acima cresce e satura em 1.6rem (~26px). O teto é conservador de propósito: a
   fonte cresce via :root, mas caixas pequenas com tamanho em px fixo (badges,
   ícones) não acompanham; ~1.6x é o limite em que o texto ainda cabe nelas. O
   motor de preencher a tela é o alargamento da coluna (não quebra elemento algum).
   Um app de coluna única não enche um 4K por completo sem virar multi-coluna (outro
   trabalho). Knobs: slope (0.6vw), teto (1.6rem) e o `--app-w` abaixo.

   EXCEÇÃO: a landing pública (M-042) tem degraus próprios (768/1024) logo acima,
   porque lá o que muda é o layout, não só o tamanho da letra. Antes de criar um
   terceiro conjunto de breakpoints, olhe os dois que já existem. */
@media (min-width: 700px) {
  :root { font-size: clamp(1rem, 0.68rem + 0.6vw, 1.6rem); }
}
@media (min-width: 1280px) {
  :root { --app-w: 42rem; }
}
@media (min-width: 2000px) {
  :root { --app-w: 52rem; }
}
