/*
 * Colber Design System — Shared Page Compositions (patterns.json)
 *
 * These are the "list" page-layout compositions documented in
 * docs/colber-admin/design-system/patterns.json ("page_layouts.list": PageHeader +
 * FilterBar + DataTable + BulkActionBar + EmptyState + ConfirmDialog) and
 * ported verbatim from docs/colber-admin/pages/usuarios.html's own <style> block —
 * NOT approximated with Tailwind utilities.
 *
 * Unlike public/css/ds-components.css (atomic components: button, badge,
 * card...), this file holds page-level COMPOSITIONS. Any future list-style
 * page (a second CRUD screen, etc.) should reuse these same classes instead
 * of re-porting them per page — see CLAUDE.md's Express/EJS-specific lessons.
 */

/* ── Page header — usuarios.html:264-269 ────────── */
/* flex-wrap prevents rather than fixes: measured at 390px the header is 348px
   and fits. It overflowed in the static prototypes, whose desktop-only shell
   leaves the content zone ~246px wide, and the approved prototypes carry the
   same rule — keeping them structurally identical is the point. Two buttons
   with longer labels would need it here too. */
.page-header { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--spacing-4); margin-bottom: var(--spacing-5); flex-wrap: wrap; }
.page-title { font-size: 1.5rem; font-weight: 600; letter-spacing: -0.015em; line-height: 1.3; margin: 0; color: var(--color-foreground); }
.page-subtitle { font-size: 0.875rem; color: var(--color-muted-foreground); margin-top: 2px; }

/* ── Filter bar — usuarios.html:292-327 ──────────── */
.filter-bar { display: flex; align-items: center; gap: var(--spacing-3); flex-wrap: wrap; margin-bottom: var(--spacing-4); }
.search-wrap { position: relative; flex: 1; min-width: 200px; max-width: 320px; }
.search-icon { position: absolute; left: var(--spacing-3); top: 50%; transform: translateY(-50%); color: var(--color-muted-foreground); pointer-events: none; }
.search-input {
  width: 100%; height: 36px; padding: 0 var(--spacing-3) 0 2.25rem;
  font-size: 0.875rem; border-radius: var(--radius-md);
  border: 1px solid var(--color-control-border); background: transparent;
  color: var(--color-foreground); font-family: var(--font-family-body); outline: none;
  transition: border-color 120ms;
}
.search-input:focus { border-color: var(--color-ring); box-shadow: 0 0 0 2px color-mix(in oklch, var(--color-ring) 20%, transparent); }
.search-clear {
  position: absolute; right: var(--spacing-2); top: 50%; transform: translateY(-50%);
  width: 20px; height: 20px; border-radius: var(--radius-sm); border: none; background: transparent;
  cursor: pointer; color: var(--color-muted-foreground); display: none; align-items: center; justify-content: center;
}
.search-clear.visible { display: flex; }
.filter-select {
  height: 36px; padding: 0 var(--spacing-3); font-size: 0.875rem;
  border-radius: var(--radius-md); border: 1px solid var(--color-control-border);
  background: var(--color-background); color: var(--color-foreground);
  font-family: var(--font-family-body); outline: none; cursor: pointer; min-width: 120px;
}
.filter-select:focus { border-color: var(--color-ring); }

/* ── Table card — usuarios.html:341-376 ──────────── */
.table-card { border: 1px solid var(--color-border); border-radius: var(--radius-md); background: var(--color-card); overflow: hidden; }
.table-wrap { overflow-x: auto; }
.table-wrap table { width: 100%; border-collapse: collapse; }
.table-wrap thead { background: var(--color-muted); }
.table-wrap th {
  padding: var(--spacing-2) var(--spacing-3); text-align: left;
  font-size: 0.75rem; font-weight: 600; color: var(--color-muted-foreground);
  white-space: nowrap; border-bottom: 1px solid var(--color-border);
}
.table-wrap th.sortable { cursor: pointer; user-select: none; }
.table-wrap th.sortable:hover { color: var(--color-foreground); }
.table-wrap td { padding: var(--spacing-2) var(--spacing-3); font-size: 0.875rem; border-bottom: 1px solid var(--color-border); height: 40px; }
.table-wrap tr:last-child td { border-bottom: none; }
.table-wrap tbody tr:hover td { background: color-mix(in oklch, var(--color-accent) 50%, transparent); }
.col-check { width: 40px; }
.col-user { min-width: 180px; }
.col-email { min-width: 200px; }
.col-role { width: 120px; }
.col-status { width: 100px; }
.col-access { width: 140px; }
.col-actions { width: 48px; }
.user-cell { display: flex; align-items: center; gap: var(--spacing-2); }
.row-avatar {
  width: 28px; height: 28px; border-radius: var(--radius-full); flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: 0.6875rem; font-weight: 700; color: var(--color-avatar-foreground);
}
/* A foto do aluno dentro do avatar que já existe — ver partials/student-avatar.ejs.
   Vale para .row-avatar e .detail-avatar, porque o que muda entre os dois é o
   tamanho da caixa e não o que acontece com a imagem.

   `cover` e NUNCA `contain`: a caixa é quadrada e a arte é um retrato, então
   `contain` deixaria tarja em volta de um rosto. (A regra oposta vale para o
   lockup da marca, que é ~3:1 num contêiner quadrado — mesma propriedade, decisão
   contrária, e é por isso que ela é dita nos dois lugares.)

   `overflow: hidden` porque o avatar é a exceção aprovada ao teto de --radius-xl e
   a imagem tem de ser recortada pelo círculo; `border-radius: inherit` para o
   recorte acompanhar quem contém. `display: block` porque o preflight do Tailwind
   já o define e uma imagem inline dentro de um flex container ganha a folga da
   linha de base, descentrando a foto por 1-2px.

   Geometria NÃO distingue `cover` de `contain` numa caixa de tamanho fixo: as duas
   medem 28x28. Quem verifica isto tem de ler o `object-fit` computado. */
.row-avatar, .detail-avatar, .detail-portrait, .enc-roll-photo { overflow: hidden; }
/* `.ds-avatar` e `.topbar-avatar` são átomos de ds-components.css e a regra da imagem
   vem para cá com as outras: o comportamento da foto dentro do avatar é um só, e
   dividi-lo em dois arquivos é como um dos dois perde `cover` numa reconciliação. */
.ds-avatar, .topbar-avatar { overflow: hidden; }
.row-avatar img, .detail-avatar img, .detail-portrait img, .enc-roll-photo img,
.ds-avatar img, .topbar-avatar img {
  width: 100%; height: 100%; object-fit: cover; border-radius: inherit; display: block;
}

/* Retrato de identificação, na ficha do aluno e em Meu Cadastro.
   ─────────────────────────────────────────────────────────────────────────
   TERCEIRO tamanho, e não um ajuste do avatar, porque a FUNÇÃO é outra. 28px na
   lista e 48px no cabeçalho marcam QUAL registro se está lendo — o nome ao lado já
   respondeu quem é. Este responde a pergunta que o Instituto trouxe: "qual das
   Anas é esta?", que é reconhecimento de rosto, e um rosto a 48px tem uns 30px de
   face. A 160px ele é reconhecível.

   NÃO é `--radius-full`. O círculo é a exceção aprovada para AVATAR — a marca de
   qual registro é. Um retrato de identificação é uma foto sendo olhada como foto,
   então segue a escala normal, e o círculo a 160px também come os cantos da
   imagem justamente onde estão o cabelo e os ombros.

   `cover` pela mesma razão dos avatares, e quadrado porque o recorte central de
   uma foto qualquer é o mais seguro: uma caixa mais alta que larga corta mais de
   uma foto na horizontal, e não há como saber o que foi enviado. */
.detail-portrait {
  width: 160px; height: 160px; flex-shrink: 0;
  border-radius: var(--radius-md);
  display: flex; align-items: center; justify-content: center;
  font-size: 2.5rem; font-weight: 700; color: var(--color-avatar-foreground);
}
/* O retrato ao lado dos campos, como uma ficha de identificação. `min-width: 0` no
   irmão porque item flex tem `min-width: auto` e se recusa a encolher abaixo do
   conteúdo — sem isso um e-mail longo empurra o cartão. */
.detail-with-portrait { display: flex; gap: var(--spacing-5); align-items: flex-start; }
.detail-with-portrait > .detail-fields { flex: 1; min-width: 0; }
@media (max-width: 640px) {
  /* Empilha, e encolhe: 160px num telefone empurraria os campos para uma coluna
     de nada. 120px continua reconhecível. */
  .detail-with-portrait { flex-direction: column; gap: var(--spacing-4); }
  .detail-portrait { width: 120px; height: 120px; font-size: 2rem; }
}
.row-name { font-weight: 500; font-size: 0.875rem; color: var(--color-foreground); }
.row-email { font-size: 0.8125rem; color: var(--color-muted-foreground); }

/* ── Badges — usuarios.html:377-389 (page-scoped variant, distinct from
   showroom's ds-badge in ds-components.css but same visual language) ── */
.badge {
  display: inline-flex; align-items: center; padding: 0.125rem var(--spacing-2);
  font-size: 0.75rem; font-weight: 500; border-radius: var(--radius-xs);
  border: 1px solid transparent; line-height: 1.4; white-space: nowrap;
}
.badge-success { background: color-mix(in oklch, var(--color-success) 15%, transparent); color: var(--color-success); border-color: color-mix(in oklch, var(--color-success) 30%, transparent); }
.badge-warning { background: color-mix(in oklch, var(--color-warning) 15%, transparent); color: var(--color-warning); border-color: color-mix(in oklch, var(--color-warning) 30%, transparent); }
.badge-muted { background: var(--color-muted); color: var(--color-muted-foreground); border-color: var(--color-border); }
.badge-primary { background: color-mix(in oklch, var(--color-primary) 15%, transparent); color: var(--color-primary); border-color: color-mix(in oklch, var(--color-primary) 30%, transparent); }
.badge-outline { background: transparent; color: var(--color-foreground); border-color: var(--color-border); }
.badge-destructive { background: color-mix(in oklch, var(--color-destructive) 15%, transparent); color: var(--color-destructive); border-color: color-mix(in oklch, var(--color-destructive) 30%, transparent); }

/* ── Table action button — usuarios.html icon-btn / table-action ── */
.table-action {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; background: transparent; border: none;
  border-radius: var(--radius-sm); color: var(--color-muted-foreground); cursor: pointer;
  transition: color 120ms, background 120ms;
}
.table-action:hover { color: var(--color-foreground); background: var(--color-muted); }
.table-action:focus-visible { outline: 2px solid var(--color-ring); outline-offset: 2px; }

/* ── Empty state — usuarios.html:407-414 ─────────── */
.empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: var(--spacing-16) var(--spacing-8); text-align: center; }
.empty-icon { color: var(--color-muted-foreground); margin-bottom: var(--spacing-4); opacity: 0.5; }
.empty-title { font-size: 1rem; font-weight: 600; margin-bottom: var(--spacing-2); color: var(--color-foreground); }
.empty-desc { font-size: 0.875rem; color: var(--color-muted-foreground); margin-bottom: var(--spacing-5); max-width: 320px; }

/* ── Pagination — usuarios.html:417-431 ──────────── */
.pagination { display: flex; align-items: center; justify-content: space-between; padding: var(--spacing-3) var(--spacing-4); border-top: 1px solid var(--color-border); font-size: 0.8125rem; color: var(--color-muted-foreground); }
.pagination-controls { display: flex; align-items: center; gap: var(--spacing-1); }
.page-btn {
  min-width: 32px; height: 32px; display: flex; align-items: center; justify-content: center;
  border-radius: var(--radius-sm); border: 1px solid transparent; background: transparent;
  cursor: pointer; font-size: 0.8125rem; color: var(--color-foreground);
  transition: background 120ms; font-family: var(--font-family-body); text-decoration: none;
}
.page-btn:hover { background: var(--color-accent); }
.page-btn.active { background: var(--color-primary); color: var(--color-primary-foreground); border-color: var(--color-primary); font-weight: 600; }
.page-btn.disabled { opacity: 0.4; pointer-events: none; }

/* ═══════════════════════════════════════════════════════════════════
 * "detail" page_layout — usuario-detalhe.html (2-column: primary 65% + aside)
 * ═══════════════════════════════════════════════════════════════════ */

/* ── Card w/ header/body (complements the bare .ds-card atom) ───── */
.card { background: var(--color-card); border: 1px solid var(--color-border); border-radius: var(--radius-md); }
.card-header { padding: var(--spacing-4) var(--spacing-5); border-bottom: 1px solid var(--color-border); display: flex; align-items: center; justify-content: space-between; }
.card-title { font-size: 0.9375rem; font-weight: 600; color: var(--color-foreground); }
.card-body { padding: var(--spacing-5); }

/* ── 2-col detail layout — usuario-detalhe.html:121-124 ──────────── */
.detail-grid { display: grid; grid-template-columns: 1fr 320px; gap: var(--spacing-5); align-items: start; }
@media (max-width: 1024px) { .detail-grid { grid-template-columns: 1fr; } }
.detail-aside { display: flex; flex-direction: column; gap: var(--spacing-4); }
.page-header-left { display: flex; align-items: center; gap: var(--spacing-3); }
.detail-avatar { width: 48px; height: 48px; border-radius: var(--radius-full); display: flex; align-items: center; justify-content: center; font-size: 1rem; font-weight: 700; color: var(--color-avatar-foreground); flex-shrink: 0; }
.page-meta { font-size: 0.8125rem; color: var(--color-muted-foreground); display: flex; align-items: center; gap: var(--spacing-2); flex-wrap: wrap; margin-top: 2px; }

/* ── Tabs — usuario-detalhe.html:133-139 ─────────────────────────── */
.tabs-bar { display: flex; gap: 0; border-bottom: 1px solid var(--color-border); margin-bottom: var(--spacing-5); }
.tab-btn { padding: var(--spacing-2) var(--spacing-4); font-size: 0.875rem; font-weight: 500; cursor: pointer; border: none; background: transparent; color: var(--color-muted-foreground); border-bottom: 2px solid transparent; margin-bottom: -1px; transition: all 120ms; font-family: var(--font-family-body); }
.tab-btn:hover { color: var(--color-foreground); }
.tab-btn.active { color: var(--color-primary); border-bottom-color: var(--color-primary); }
.tab-panel { display: none; }
.tab-panel.active { display: block; }

/* ── Info rows — adapted from usuario-detalhe.html:141-145. Prototype uses
   justify-content: space-between (label far left, value far right), which
   reads fine on a narrow card but leaves an odd, huge empty gap on this
   page's wide detail-grid main column for short values — switched to a
   fixed-width label column with the value sitting right after it instead
   (definition-list style). Diverges from the delivered prototype; update
   docs/colber-admin/pages/usuario-detalhe.html's own <style> to match if
   this is kept, so the two don't silently drift apart. ── */
.info-row { display: flex; align-items: center; padding: var(--spacing-3) 0; border-bottom: 1px solid var(--color-border); gap: var(--spacing-4); }
.info-row:last-child { border-bottom: none; }
.info-label { font-size: 0.8125rem; color: var(--color-muted-foreground); flex-shrink: 0; width: 160px; }
.info-value { font-size: 0.875rem; text-align: left; color: var(--color-foreground); }

/* ── Activity feed — usuario-detalhe.html:147-154 ────────────────── */
.activity-list { display: flex; flex-direction: column; gap: 0; }
.activity-item { display: flex; gap: var(--spacing-3); padding: var(--spacing-3) 0; border-bottom: 1px solid var(--color-border); }
.activity-item:last-child { border-bottom: none; }
.activity-avatar { width: 28px; height: 28px; border-radius: var(--radius-full); display: flex; align-items: center; justify-content: center; font-size: 0.625rem; font-weight: 700; color: var(--color-avatar-foreground); flex-shrink: 0; }
.activity-content { flex: 1; min-width: 0; }
.activity-text { font-size: 0.8125rem; line-height: 1.4; color: var(--color-foreground); }
.activity-time { font-size: 0.75rem; color: var(--color-muted-foreground); margin-top: 2px; }

/* ── Permissions grid — usuario-detalhe.html:167-170 ─────────────── */
.perm-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--spacing-3); }
.perm-item { display: flex; align-items: center; justify-content: space-between; padding: var(--spacing-3); background: var(--color-muted); border-radius: var(--radius-sm); }
.perm-label { font-size: 0.8125rem; color: var(--color-foreground); }

/* ═══════════════════════════════════════════════════════════════════
 * "settings" page_layout — settings.html (card-header/body/footer,
 * form fields, feature-flag-row toggle composition)
 * ═══════════════════════════════════════════════════════════════════ */
.card-desc { font-size: 0.8125rem; color: var(--color-muted-foreground); margin-top: 2px; }
.card-footer { padding: var(--spacing-4) var(--spacing-5); border-top: 1px solid var(--color-border); display: flex; justify-content: flex-end; gap: var(--spacing-2); }

.field { display: flex; flex-direction: column; gap: var(--spacing-2); }
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--spacing-4); }
.field-label { font-size: 0.8125rem; font-weight: 500; color: var(--color-foreground); }
.field-hint { font-size: 0.75rem; color: var(--color-muted-foreground); }
.field-input { height: 36px; padding: 0 var(--spacing-3); font-size: 0.875rem; border-radius: var(--radius-md); border: 1px solid var(--color-control-border); background: transparent; color: var(--color-foreground); font-family: var(--font-family-body); outline: none; transition: border-color 120ms, box-shadow 120ms; }
.field-input:focus { border-color: var(--color-ring); box-shadow: 0 0 0 2px color-mix(in oklch, var(--color-ring) 20%, transparent); }
.field-input:disabled { opacity: 0.6; cursor: not-allowed; }
.field-select { height: 36px; padding: 0 var(--spacing-3); font-size: 0.875rem; border-radius: var(--radius-md); border: 1px solid var(--color-control-border); background: var(--color-background); color: var(--color-foreground); font-family: var(--font-family-body); outline: none; }
.field-textarea { padding: var(--spacing-2) var(--spacing-3); font-size: 0.875rem; min-height: 72px; resize: vertical; border-radius: var(--radius-md); border: 1px solid var(--color-control-border); background: transparent; color: var(--color-foreground); font-family: var(--font-family-body); outline: none; }
.field-textarea:focus { border-color: var(--color-ring); box-shadow: 0 0 0 2px color-mix(in oklch, var(--color-ring) 20%, transparent); }

/* Feature-flag / toggle row — settings.html:139-144 */
.flag-row { display: flex; align-items: center; justify-content: space-between; padding: var(--spacing-3) 0; border-bottom: 1px solid var(--color-border); gap: var(--spacing-4); }
.flag-row:last-child { border-bottom: none; }
.flag-info { flex: 1; }
.flag-title { font-size: 0.875rem; font-weight: 500; color: var(--color-foreground); }
.flag-desc { font-size: 0.75rem; color: var(--color-muted-foreground); margin-top: 2px; }

/* ═══════════════════════════════════════════════════════════════════
 * FAQ composition — faq.html (search hero, category pills, accordion)
 * ═══════════════════════════════════════════════════════════════════ */
.faq-search-hero { background: var(--color-card); border: 1px solid var(--color-border); border-radius: var(--radius-md); padding: var(--spacing-5) var(--spacing-6); margin-bottom: var(--spacing-5); }
.faq-search-label { font-size: 0.8125rem; font-weight: 500; color: var(--color-muted-foreground); display: flex; align-items: center; gap: var(--spacing-2); margin-bottom: var(--spacing-3); }
.faq-search-wrap { display: flex; align-items: center; gap: var(--spacing-3); height: 42px; padding: 0 var(--spacing-4); background: var(--color-background); border: 1px solid var(--color-border); border-radius: var(--radius-md); transition: border-color 120ms, box-shadow 120ms; }
.faq-search-wrap:focus-within { border-color: var(--color-ring); box-shadow: 0 0 0 2px color-mix(in oklch, var(--color-ring) 22%, transparent); }
.faq-search-wrap input { flex: 1; background: transparent; border: none; outline: none; font-family: inherit; font-size: 0.9375rem; color: var(--color-foreground); }
.faq-search-wrap input::placeholder { color: var(--color-muted-foreground); }
.faq-clear { background: transparent; border: none; cursor: pointer; color: var(--color-muted-foreground); display: none; align-items: center; padding: 2px; border-radius: var(--radius-xs); }
.faq-clear:hover { color: var(--color-foreground); }
.faq-clear.visible { display: flex; }

/* Category pills — radius-md (NOT full/pill-shaped), per design-rules.md's ban on pill buttons */
.category-pills { display: flex; gap: var(--spacing-2); flex-wrap: wrap; margin-bottom: var(--spacing-6); }
.category-pill { display: inline-flex; align-items: center; gap: var(--spacing-2); height: 32px; padding: 0 var(--spacing-3); background: var(--color-muted); border: 1px solid var(--color-border); border-radius: var(--radius-md); color: var(--color-muted-foreground); font-size: 0.8125rem; font-weight: 500; cursor: pointer; font-family: inherit; transition: background 120ms, color 120ms, border-color 120ms; }
.category-pill:hover { background: var(--color-accent); color: var(--color-foreground); }
.category-pill.active { background: var(--color-primary); border-color: var(--color-primary); color: var(--color-primary-foreground); }
/* O fundo do chip é ancorado em --color-foreground, NÃO em currentColor.
   Com `background: color-mix(currentColor 20%, transparent)` o número ficava
   sobre uma lavagem de si mesmo, o que limita o contraste por construção: o
   azul do boilerplate era escuro e saturado o bastante para sobreviver a
   isso, o terracota (mais claro e de croma baixo) media 3.66:1 no pill ativo
   e 3.62:1 no inativo. Reduzir a porcentagem não resolvia — o teto do estado
   inativo é 4.65:1 mesmo com lavagem 0%, e aí o chip desaparece.
   Desacoplado, os dois estados passam com folga nos três temas. */
.pill-count { display: inline-flex; align-items: center; justify-content: center; min-width: 18px; height: 18px; padding: 0 4px; border-radius: var(--radius-full); font-size: 0.625rem; font-weight: 700; background: color-mix(in oklch, var(--color-foreground) 18%, transparent); color: var(--color-foreground); }
.category-pill.active .pill-count { color: var(--color-primary-foreground); }

.faq-section { margin-bottom: var(--spacing-8); }
.faq-section[hidden] { display: none; }
.faq-section-header { display: flex; align-items: center; gap: var(--spacing-3); margin-bottom: var(--spacing-3); }
.faq-section-icon { display: flex; align-items: center; justify-content: center; width: 32px; height: 32px; border-radius: var(--radius-md); background: color-mix(in oklch, var(--color-primary) 14%, transparent); color: var(--color-primary); flex-shrink: 0; }
.faq-section-title { font-size: 1rem; font-weight: 600; color: var(--color-foreground); margin: 0; }
.faq-section-count { font-size: 0.75rem; color: var(--color-muted-foreground); }
.faq-list { display: flex; flex-direction: column; gap: var(--spacing-2); }
.faq-item { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; }
.faq-item[hidden] { display: none; }
.faq-question { display: flex; align-items: center; justify-content: space-between; gap: var(--spacing-4); padding: var(--spacing-4) var(--spacing-5); cursor: pointer; background: var(--color-card); border: none; width: 100%; text-align: left; font-family: inherit; font-size: 0.9375rem; font-weight: 500; color: var(--color-foreground); transition: background 120ms; }
.faq-question:hover { background: var(--color-accent); }
.faq-chevron { flex-shrink: 0; color: var(--color-muted-foreground); transition: transform 200ms var(--ease-default); }
.faq-item.open .faq-chevron { transform: rotate(180deg); }
.faq-answer { max-height: 0; overflow: hidden; transition: max-height 200ms var(--ease-default); background: var(--color-background); border-top: 0px solid var(--color-border); }
.faq-item.open .faq-answer { max-height: 600px; border-top-width: 1px; }
.faq-answer-inner { padding: var(--spacing-4) var(--spacing-5) var(--spacing-5); font-size: 0.875rem; color: var(--color-muted-foreground); line-height: 1.75; }
.faq-answer-inner a { color: var(--color-primary); text-decoration: none; }
.faq-answer-inner a:hover { text-decoration: underline; }
.faq-answer-inner code { font-family: var(--font-family-mono); font-size: 0.8125rem; background: var(--color-muted); border: 1px solid var(--color-border); border-radius: var(--radius-xs); padding: 1px 5px; color: var(--color-foreground); }
.faq-answer-inner ul { margin: var(--spacing-2) 0; padding-left: var(--spacing-5); }
.faq-answer-inner li { margin-bottom: var(--spacing-1); }
.faq-highlight { display: inline-flex; align-items: center; gap: var(--spacing-2); padding: var(--spacing-3) var(--spacing-4); margin-top: var(--spacing-3); background: color-mix(in oklch, var(--color-primary) 8%, transparent); border: 1px solid color-mix(in oklch, var(--color-primary) 25%, transparent); border-radius: var(--radius-md); font-size: 0.8125rem; color: var(--color-primary); }

.support-cta { display: flex; align-items: center; justify-content: space-between; gap: var(--spacing-6); padding: var(--spacing-5) var(--spacing-6); background: var(--color-card); border: 1px solid var(--color-border); border-radius: var(--radius-md); margin-top: var(--spacing-6); }
.support-cta-left { display: flex; align-items: center; gap: var(--spacing-4); }
.support-cta-icon { display: flex; align-items: center; justify-content: center; width: 44px; height: 44px; border-radius: var(--radius-md); background: color-mix(in oklch, var(--color-primary) 14%, transparent); color: var(--color-primary); flex-shrink: 0; }
.support-cta-title { font-size: 0.9375rem; font-weight: 600; color: var(--color-foreground); margin-bottom: 2px; }
.support-cta-desc { font-size: 0.8125rem; color: var(--color-muted-foreground); }

/* ═══════════════════════════════════════════════════════════════════
 * suporte-interno.html — stats row, data-table toolbar, side sheet
 * with meta-grid + timeline (used by admin/messages.ejs)
 * ═══════════════════════════════════════════════════════════════════ */
/* ── .stats-grid: o número de cartões é VARIÁVEL, então a grade não pode ser fixa
 *
 * Era `repeat(4, 1fr)`, partindo de uma suposição falsa: que a grade tem quatro
 * cartões. Ela não tem — os TRÊS consumidores montam os cartões por permissão, então
 * a contagem depende de quem está olhando. O painel do Instituto chegou a cinco e o
 * quinto caiu sozinho numa segunda linha com um quarto da largura: lê-se como render
 * quebrado, não como cartão.
 *
 * Trocar o 4 por 5 só moveria o problema — voltaria com seis, e com um papel
 * construído que veja quatro. `repeat(auto-fit, minmax())` também NÃO resolve, e isto
 * foi medido: a grade adapta o número de colunas, mas a última linha continua com os
 * itens na largura da coluna, então a 1440 e a 1280 o órfão estreito reaparecia.
 *
 * FLEX COM `flex-grow` é o que faz a linha incompleta ENCHER. Os itens da última
 * linha crescem e ocupam a largura toda, então não existe cartão estreito e sozinho
 * em nenhuma largura.
 *
 * ── O QUE ISTO CUSTA, e o TETO que apareceu com o sexto cartão ─────────────
 *
 * Com linha sempre cheia, um único cartão sobrando fica LARGO. Escolhida a linha
 * cheia, aceitando isso — e o sexto indicador (comprovantes pendentes, migration
 * 021) mostrou que a aceitação tinha um limite que ninguém tinha medido: a 1440 a
 * grade quebra em 5+1 e o órfão media **1178px**, uma faixa da largura da tela com
 * um número pequeno no canto. Não é o mesmo caso de "um pouco mais largo".
 *
 * Daí `max-width: 480px` no item — o TETO do crescimento, não o fim dele. A última
 * linha continua enchendo enquanto isso mantém os cartões parecidos com cartões, e
 * para de encher quando encheria produzindo uma faixa. Medido: a 1712 os seis ficam
 * em uma linha (230px cada, abaixo do teto, então o teto é inerte ali); a 1440 o
 * órfão fica com 480 em vez de 1178; a 1280 os dois da última linha ficam com 480 em
 * vez de 502 — ou seja o teto só age onde havia faixa.
 *
 * NÃO reduzir a base para caber seis a 1440: precisaria de ~185px, e 186 já foi
 * recusado depois de renderizar (a 192 o título quebra em três cartões e os números
 * desalinham). O teto resolve o caso ruim sem tocar no caso comum.
 *
 * CONSEQUÊNCIA para quem testa: com o teto, os cartões de uma linha incompleta NÃO
 * têm a mesma largura dos da linha cheia. Uma asserção de "todas as larguras iguais"
 * só vale por LINHA, e é assim que o teste a mede.
 *
 * ── A BASE DE 200px, e por que não menos ──────────────────────────────────
 *
 * É o valor que põe os cinco em UMA linha de 1440px para cima, que é a faixa em que
 * o Instituto trabalha. Medido a 1712 (279px cada) e a 1440 (236px cada).
 *
 * 186px também caberia a 1280, e foi RECUSADO depois de renderizar: a 192px o título
 * quebra em duas linhas em três dos cinco cartões, e os valores deixam de se alinhar
 * entre cartões vizinhos. Cabe não é o mesmo que serve.
 *
 * O breakpoint de 1024 saiu — o `flex-basis` faz esse trabalho por largura real
 * disponível, e não por largura de viewport, que ignora a sidebar. O de 640 FICA: é
 * decisão registrada ("uma coluna no telefone") e não consequência de aritmética. */
.stats-grid {
  display: flex; flex-wrap: wrap;
  gap: var(--spacing-4);
  margin-bottom: var(--spacing-6);
}
.stats-grid > * { flex: 1 1 200px; min-width: 0; max-width: 480px; }
/* One column on a phone. The local db-stats-grid copy on the dashboard already
   had this rule; the shared class did not, so migrating the dashboard onto the
   shared class would have made it WORSE at 390px. Added here instead of keeping
   the duplicate — /admin/messages is the only other consumer and it gains.
   `flex-basis` e não `grid-template-columns`: a regra antiga virou letra morta no
   instante em que a grade deixou de ser `display: grid`, e regra que não casa com
   nada é a que o próximo leitor encontra e lê como correta. */
/* No telefone o TETO tem de sair junto: com `flex-basis: 100%` e `max-width: 480px`
   o cartão pararia em 480px numa viewport de 390 — inerte — mas numa de 600 deixaria
   uma faixa vazia à direita, que é o oposto do que a regra de uma coluna quer. */
@media (max-width: 640px) { .stats-grid > * { flex-basis: 100%; max-width: none; } }
.stat-card { background: var(--color-card); border: 1px solid var(--color-border); border-radius: var(--radius-md); padding: var(--spacing-4) var(--spacing-5); position: relative; overflow: hidden; }
/* SEM `padding-right` para reservar o espaço do `.stat-icon`, e a ausência é MEDIDA.
   Eu o acrescentei, e ele piorou a tela. O ícone é `position: absolute` e portanto
   invisível ao fluxo, então a preocupação era real — mas nas larguras que a grade
   produz o título NÃO alcança o ícone: medi todos os nove cartões dos dois grids a
   1712, 1440, 1280 e 1024, e o mais apertado ("Clientes em Atendimento" a 224px)
   termina 12px ANTES do ícone. O que o padding fazia era FORÇAR aquele título a
   quebrar em duas linhas, o que desalinhava o número daquele cartão dos outros — e
   aí eu acrescentei um `min-height` no rótulo para compensar um problema que eu mesmo
   havia criado. Removidos os dois, os cartões ficam mais baixos e os números alinham
   sozinhos.
   A colisão continua LATENTE: um rótulo mais longo alcançaria o ícone. É defeito do
   protótipo, presente em todo `.stat-card` desde sempre, e quem o guarda agora é uma
   asserção de geometria em `tests/session-reports.spec.js` que mede o fim do texto
   contra o começo do ícone a 1712 e a 1440. No dia em que um rótulo crescer, o teste
   falha e quem o ler decide — em vez de o texto passar por baixo do ícone em silêncio. */
.stat-label { font-size: 0.75rem; font-weight: 500; color: var(--color-muted-foreground); display: flex; align-items: center; gap: var(--spacing-2); margin-bottom: var(--spacing-3); }
.stat-value { font-size: 1.5rem; font-weight: 600; color: var(--color-foreground); font-family: var(--font-family-mono); margin-bottom: var(--spacing-2); }
.stat-sub { font-size: 0.75rem; color: var(--color-muted-foreground); }
.stat-icon { position: absolute; top: var(--spacing-4); right: var(--spacing-4); color: var(--color-muted-foreground); opacity: 0.25; }

/* ── Message badges — suporte-interno.html:431-447. Prefixed msg- because this
   page's own .badge/.badge-success/.badge-warning/.badge-muted are NOT the
   same composition as usuarios.html's .badge (different padding/font-size/
   uppercase/dot vs pill+border) even though the prototypes reuse the same
   bare class name — colliding them silently made messages.ejs render with
   usuarios.html's pill style instead of its own uppercase dot-badge. ── */
.msg-badge {
  display: inline-flex; align-items: center; gap: var(--spacing-1);
  padding: 2px var(--spacing-2); border-radius: var(--radius-sm);
  font-size: 0.6875rem; font-weight: 600;
  letter-spacing: 0.03em; text-transform: uppercase; white-space: nowrap;
}
.msg-badge-dot::before { content: ''; width: 5px; height: 5px; border-radius: var(--radius-full); background: currentColor; flex-shrink: 0; }
.msg-badge-success { color: var(--color-success); background: color-mix(in oklch, var(--color-success) 14%, transparent); }
.msg-badge-warning { color: var(--color-warning); background: color-mix(in oklch, var(--color-warning) 14%, transparent); }
.msg-badge-danger { color: var(--color-destructive); background: color-mix(in oklch, var(--color-destructive) 14%, transparent); }
.msg-badge-primary { color: var(--color-primary); background: color-mix(in oklch, var(--color-primary) 14%, transparent); }
.msg-badge-muted { color: var(--color-muted-foreground); background: var(--color-muted); }

.table-toolbar { display: flex; align-items: center; justify-content: space-between; gap: var(--spacing-3); padding: var(--spacing-3) var(--spacing-5); border-bottom: 1px solid var(--color-border); }
.table-toolbar-left { display: flex; align-items: center; gap: var(--spacing-3); flex: 1; }
.table-toolbar-right { display: flex; align-items: center; gap: var(--spacing-2); }
.table-search { display: flex; align-items: center; gap: var(--spacing-2); height: 32px; padding: 0 var(--spacing-3); background: var(--color-muted); border: 1px solid var(--color-border); border-radius: var(--radius-md); color: var(--color-muted-foreground); width: 240px; transition: border-color 120ms; }
.table-search:focus-within { border-color: var(--color-ring); }
.table-search input { background: transparent; border: none; outline: none; font-family: inherit; font-size: 0.8125rem; color: var(--color-foreground); width: 100%; }
.table-search input::placeholder { color: var(--color-muted-foreground); }
.mono { font-family: var(--font-family-mono); font-size: 0.75rem; }
.ticket-title { font-weight: 500; color: var(--color-foreground); }
.ticket-meta { font-size: 0.75rem; color: var(--color-muted-foreground); margin-top: 2px; }

/* ── Sheet (side drawer) ──────────────────────────── */
.sheet-overlay { position: fixed; inset: 0; z-index: 200; display: flex; justify-content: flex-end; pointer-events: none; visibility: hidden; }
.sheet-overlay.open { pointer-events: all; visibility: visible; }
.sheet-backdrop { position: absolute; inset: 0; background: oklch(0 0 0 / 0.45); opacity: 0; transition: opacity 200ms; }
.sheet-overlay.open .sheet-backdrop { opacity: 1; }
.sheet { position: relative; z-index: 1; width: 520px; max-width: 100vw; height: 100vh; background: var(--color-card); border-left: 1px solid var(--color-border); display: flex; flex-direction: column; transform: translateX(100%); transition: transform 200ms var(--ease-default); box-shadow: var(--shadow-md); }
.sheet-overlay.open .sheet { transform: translateX(0); }
.sheet-header { display: flex; align-items: center; justify-content: space-between; padding: var(--spacing-4) var(--spacing-5); border-bottom: 1px solid var(--color-border); flex-shrink: 0; }
.sheet-title-group { display: flex; flex-direction: column; gap: 2px; }
.sheet-title { font-size: 1rem; font-weight: 600; color: var(--color-foreground); }
.sheet-subtitle { font-size: 0.75rem; color: var(--color-muted-foreground); }
.sheet-close { display: flex; align-items: center; justify-content: center; width: 32px; height: 32px; background: transparent; border: none; border-radius: var(--radius-sm); color: var(--color-muted-foreground); cursor: pointer; flex-shrink: 0; transition: color 120ms, background 120ms; }
.sheet-close:hover { color: var(--color-foreground); background: var(--color-accent); }
.sheet-body { flex: 1; overflow-y: auto; padding: var(--spacing-5); display: flex; flex-direction: column; gap: var(--spacing-5); }
.sheet-footer { display: flex; align-items: center; justify-content: flex-end; gap: var(--spacing-3); padding: var(--spacing-4) var(--spacing-5); border-top: 1px solid var(--color-border); flex-shrink: 0; }

/* ── Form elements (reused inside sheets) ──────────── */
.form-group { display: flex; flex-direction: column; gap: var(--spacing-2); }
.form-label { font-size: 0.8125rem; font-weight: 500; color: var(--color-foreground); }
.form-label .req { color: var(--color-destructive); margin-left: 2px; }
.form-input, .form-select, .form-textarea { font-family: inherit; font-size: 0.875rem; color: var(--color-foreground); background: var(--color-background); border: 1px solid var(--color-border); border-radius: var(--radius-md); width: 100%; transition: border-color 120ms, box-shadow 120ms; }
.form-input, .form-select { height: 36px; padding: 0 var(--spacing-3); }
.form-input:focus, .form-select:focus, .form-textarea:focus { outline: none; border-color: var(--color-ring); box-shadow: 0 0 0 2px color-mix(in oklch, var(--color-ring) 22%, transparent); }
.form-input::placeholder, .form-textarea::placeholder { color: var(--color-muted-foreground); }
.form-textarea { padding: var(--spacing-3); resize: vertical; min-height: 100px; line-height: 1.6; }
.form-hint { font-size: 0.75rem; color: var(--color-muted-foreground); }
.form-error { font-size: 0.75rem; color: var(--color-destructive); display: none; }
.form-group.has-error .form-input, .form-group.has-error .form-select, .form-group.has-error .form-textarea { border-color: var(--color-destructive); }
.form-group.has-error .form-error { display: block; }
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--spacing-4); }
.form-section-title { font-size: 0.75rem; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--color-muted-foreground); padding-bottom: var(--spacing-3); border-bottom: 1px solid var(--color-border); }

/* ── Ticket detail: meta grid + timeline ────────────── */
.detail-meta-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--spacing-3); }
.detail-meta-item { display: flex; flex-direction: column; gap: 4px; }
.detail-meta-label { font-size: 0.6875rem; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--color-muted-foreground); }
.detail-meta-value { font-size: 0.875rem; color: var(--color-foreground); }
.timeline { display: flex; flex-direction: column; gap: 0; }
.timeline-item { display: flex; gap: var(--spacing-3); padding-bottom: var(--spacing-4); position: relative; }
.timeline-item:not(:last-child)::before { content: ''; position: absolute; left: 15px; top: 34px; bottom: 0; width: 1px; background: var(--color-border); }
.timeline-avatar { width: 32px; height: 32px; border-radius: var(--radius-full); background: var(--color-primary); color: var(--color-primary-foreground); display: flex; align-items: center; justify-content: center; font-size: 0.6875rem; font-weight: 600; flex-shrink: 0; z-index: 1; border: 2px solid var(--color-card); }
.timeline-avatar.system { background: var(--color-muted); color: var(--color-muted-foreground); }
.timeline-bubble { flex: 1; background: var(--color-background); border: 1px solid var(--color-border); border-radius: var(--radius-md); padding: var(--spacing-3) var(--spacing-4); }
.timeline-meta { display: flex; align-items: center; gap: var(--spacing-2); margin-bottom: var(--spacing-2); }
.timeline-author { font-size: 0.8125rem; font-weight: 500; color: var(--color-foreground); }
.timeline-time { font-size: 0.75rem; color: var(--color-muted-foreground); }
.timeline-text { font-size: 0.875rem; color: var(--color-foreground); line-height: 1.65; }
.reply-area { display: flex; flex-direction: column; gap: var(--spacing-3); padding-top: var(--spacing-4); border-top: 1px solid var(--color-border); }
@media (max-width: 768px) { .support-cta { flex-direction: column; align-items: flex-start; } }

/* ═══════════════════════════════════════════════════════════════════
 * OTP digit-box composition — otp-validator.html. Shared between
 * /verify (login) and /register/verify (signup) — same composition,
 * do not duplicate it per page.
 * ═══════════════════════════════════════════════════════════════════ */
.otp-group { display: flex; align-items: center; gap: var(--spacing-3); justify-content: center; }
.otp-input {
  width: 48px;
  height: 56px;
  text-align: center;
  font-family: var(--font-family-mono);
  font-size: 1.5rem;
  font-weight: 500;
  background: var(--color-input);
  border: 1px solid var(--color-control-border);
  border-radius: var(--radius-md);
  color: var(--color-foreground);
  outline: none;
  caret-color: var(--color-primary);
  transition: border-color 120ms var(--ease-default), box-shadow 120ms var(--ease-default), background 120ms var(--ease-default);
  -moz-appearance: textfield;
}
.otp-input::-webkit-outer-spin-button, .otp-input::-webkit-inner-spin-button { -webkit-appearance: none; }
.otp-input:focus {
  border-color: var(--color-ring);
  box-shadow: 0 0 0 2px color-mix(in oklch, var(--color-ring) 28%, transparent);
  background: color-mix(in oklch, var(--color-input) 80%, var(--color-accent));
}
.otp-input.filled { border-color: color-mix(in oklch, var(--color-primary) 60%, var(--color-border)); }
.otp-input.error { border-color: var(--color-destructive); background: color-mix(in oklch, var(--color-destructive) 10%, var(--color-input)); }
.otp-sep { width: 6px; height: 6px; border-radius: var(--radius-full); background: var(--color-muted-foreground); opacity: 0.4; flex-shrink: 0; }

@keyframes otp-shake {
  0%, 100% { transform: translateX(0); }
  15% { transform: translateX(-6px); }
  30% { transform: translateX(6px); }
  45% { transform: translateX(-5px); }
  60% { transform: translateX(5px); }
  75% { transform: translateX(-3px); }
  90% { transform: translateX(3px); }
}
.otp-shake { animation: otp-shake 0.45s var(--ease-default); }

/* ── Status badge — dashboard.html:1064-1101. This is the canonical shell
   page's OWN activity-table badge (uppercase + dot), distinct from the
   showroom's generic .ds-badge (docs/components.html#status-badge, which uses
   literal "● " text prefix instead of a dot and is not uppercase) — dashboard's
   recent-activity table must use this literal class, not ds-badge. ── */
.status-badge {
  display: inline-flex; align-items: center; gap: var(--spacing-1);
  padding: 2px var(--spacing-2); border-radius: var(--radius-sm);
  font-size: 0.6875rem; font-weight: 600;
  letter-spacing: 0.03em; text-transform: uppercase;
}
.status-badge::before { content: ''; width: 5px; height: 5px; border-radius: var(--radius-full); background: currentColor; }
.status-success { color: var(--color-success); background: color-mix(in oklch, var(--color-success) 14%, transparent); }
.status-warning { color: var(--color-warning); background: color-mix(in oklch, var(--color-warning) 14%, transparent); }
.status-destructive { color: var(--color-destructive); background: color-mix(in oklch, var(--color-destructive) 14%, transparent); }
.status-muted { color: var(--color-muted-foreground); background: var(--color-muted); }


/* ── Calendar (cal-*) — ported from docs/colber-admin/pages/ingresso-entrevistas.html
   There is NO Calendar component: components-registry.json lists it under
   removed_from_shadcn, so this is a custom composition built from the token
   scale. It lives here rather than in a page because it has TWO consumers:
   /ingresso/entrevistas and /academico/calendario. Editing any cal-* rule
   changes both pages — run tests/waiting-list-screens.spec.js as well as the
   academic ones before assuming a change is local.
   Class names were grepped across docs/colber-admin/pages/*.html and
   src/views/ before being added — the rule that exists because of the
   historical .badge collision. ── */
.cal-toolbar { display: flex; align-items: center; justify-content: space-between; gap: var(--spacing-4); flex-wrap: wrap; margin-bottom: var(--spacing-4); }
.cal-nav { display: flex; align-items: center; gap: var(--spacing-2); }
.cal-period { font-size: 1.125rem; font-weight: 600; letter-spacing: -0.010em; margin: 0 0 0 var(--spacing-2); color: var(--color-foreground); }

.cal-views { display: inline-flex; border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; }
.cal-view-btn {
  padding: var(--spacing-1) var(--spacing-3); background: transparent; border: none;
  border-right: 1px solid var(--color-border); font-family: inherit; font-size: 0.8125rem;
  font-weight: 500; color: var(--color-muted-foreground); cursor: pointer; height: 32px;
  transition: background var(--duration-fast) var(--ease-default), color var(--duration-fast) var(--ease-default);
}
.cal-view-btn:last-child { border-right: none; }
.cal-view-btn:hover { color: var(--color-foreground); background: var(--color-muted); }
.cal-view-btn.active { background: var(--color-primary); color: var(--color-primary-foreground); }
.cal-view-btn:focus-visible { outline: 2px solid var(--color-ring); outline-offset: -2px; }

.cal-conflict-banner {
  display: flex; align-items: center; gap: var(--spacing-2);
  padding: var(--spacing-2) var(--spacing-3); margin-bottom: var(--spacing-4);
  border-radius: var(--radius-md); font-size: 0.8125rem;
  background: color-mix(in oklch, var(--color-warning) 12%, transparent);
  border: 1px solid color-mix(in oklch, var(--color-warning) 30%, transparent);
  color: var(--color-warning);
}

.cal-card { padding: 0; overflow: hidden; }

/* Month grid */
.cal-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); }
.cal-weekday {
  padding: var(--spacing-2); text-align: center; font-size: 0.6875rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--color-muted-foreground); background: var(--color-muted);
  border-bottom: 1px solid var(--color-border);
}
.cal-day {
  min-height: 92px; padding: var(--spacing-1); display: flex; flex-direction: column; gap: 2px;
  border-right: 1px solid var(--color-border); border-bottom: 1px solid var(--color-border);
}
.cal-day:nth-child(7n+7) { border-right: none; }
.cal-day-weekend { background: color-mix(in oklch, var(--color-muted) 40%, transparent); }
.cal-day-out { opacity: 0.45; }
.cal-day-today { background: color-mix(in oklch, var(--color-primary) 8%, transparent); }
.cal-day-today .cal-daynum {
  background: var(--color-primary); color: var(--color-primary-foreground);
  border-radius: var(--radius-full); width: 20px; height: 20px;
  display: inline-flex; align-items: center; justify-content: center;
}
.cal-daynum { font-size: 0.75rem; font-weight: 500; color: var(--color-foreground); font-family: var(--font-family-mono); }

.cal-event {
  display: flex; align-items: center; gap: var(--spacing-1); width: 100%; text-align: left;
  padding: 2px var(--spacing-1); border-radius: var(--radius-xs); border: 1px solid transparent;
  background: color-mix(in oklch, var(--color-primary) 15%, transparent);
  color: var(--color-foreground); font-family: inherit; font-size: 0.6875rem; cursor: pointer;
  border-color: color-mix(in oklch, var(--color-primary) 30%, transparent);
}
.cal-event:hover { background: color-mix(in oklch, var(--color-primary) 25%, transparent); }
.cal-event:focus-visible { outline: 2px solid var(--color-ring); outline-offset: 1px; }
.cal-event-conflict {
  background: color-mix(in oklch, var(--color-warning) 15%, transparent);
  border-color: color-mix(in oklch, var(--color-warning) 35%, transparent);
  color: var(--color-warning);
}
.cal-event-time { font-family: var(--font-family-mono); font-weight: 500; flex-shrink: 0; }
.cal-event-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cal-event-meta { display: block; font-size: 0.625rem; color: var(--color-muted-foreground); }
.cal-more { font-size: 0.625rem; color: var(--color-muted-foreground); padding-left: var(--spacing-1); }

/* Week view */
.cal-week { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); }
.cal-week-col {
  min-height: 220px; padding: var(--spacing-1); display: flex; flex-direction: column; gap: var(--spacing-1);
  border-right: 1px solid var(--color-border);
}
.cal-week-col:last-child { border-right: none; }
.cal-week-today { background: color-mix(in oklch, var(--color-primary) 8%, transparent); }
.cal-week-head { display: flex; flex-direction: column; align-items: center; padding: var(--spacing-2) 0; border-bottom: 1px solid var(--color-border); margin-bottom: var(--spacing-1); }
.cal-week-dow { font-size: 0.625rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--color-muted-foreground); font-weight: 600; }
.cal-week-num { font-size: 1rem; font-weight: 600; font-family: var(--font-family-mono); color: var(--color-foreground); }

/* Day view */
.cal-day-list { display: flex; flex-direction: column; }
.cal-slot { display: flex; gap: var(--spacing-4); padding: var(--spacing-3) var(--spacing-4); border-bottom: 1px solid var(--color-border); }
.cal-slot:last-child { border-bottom: none; }
.cal-slot-time { font-family: var(--font-family-mono); font-size: 0.8125rem; color: var(--color-muted-foreground); width: 48px; flex-shrink: 0; padding-top: 2px; }
.cal-slot-events { display: flex; flex-direction: column; gap: var(--spacing-2); flex: 1; min-width: 0; }
.cal-slot-card { padding: var(--spacing-2) var(--spacing-3); border: 1px solid var(--color-border); border-radius: var(--radius-md); background: var(--color-muted); }
.cal-slot-conflict { border-color: color-mix(in oklch, var(--color-warning) 40%, transparent); }
.cal-slot-name { display: flex; align-items: center; gap: var(--spacing-2); font-size: 0.875rem; font-weight: 500; color: var(--color-foreground); }
/* Same reason as .cal-event > svg: the type icon is the non-colour carrier of
   which layer an entry belongs to, so it must never be the flex child that
   gives way. The day view carries three types now. */
.cal-slot-name > svg { flex-shrink: 0; }
.cal-slot-meta { font-size: 0.75rem; color: var(--color-muted-foreground); margin-top: 2px; }

/* ── Calendar entry types (/academico/calendario) ─────────────────────────
   An entrevista keeps the base .cal-event, so /ingresso/entrevistas needs no
   markup change and cannot regress. An aula changes BACKGROUND AND BORDER
   ONLY.

   It deliberately does NOT copy the .cal-event-conflict shape below, which
   also sets `color` to the same hue as its own wash. Text on a color-mix() of
   itself caps contrast structurally — that is what held .pill-count at 3.66:1
   on /faq, and no percentage fixes it.

   --chart-2 is the categorical palette, the documented exception for
   differentiating series (design-rules.md, Known Exceptions). Colour is never
   the only signal: each entry also carries a type icon and an .sr-only label. */
.cal-event-aula {
  background: color-mix(in oklch, var(--chart-2) 15%, transparent);
  border-color: color-mix(in oklch, var(--chart-2) 30%, transparent);
}
.cal-event-aula:hover { background: color-mix(in oklch, var(--chart-2) 25%, transparent); }

/* The third layer, added when the calendar grew from two to three. Same shape
   as the aula: BACKGROUND AND BORDER ONLY, never `color` — see the note above
   about text on a color-mix() of itself.

   --chart-5 (petrol, hue 215) and NOT --chart-3. The obvious choice was wrong:
   --chart-3 is amber at hue 80 and --color-warning is amber at hue 75, five
   degrees apart, so an encontro chip would have been indistinguishable from a
   .cal-event-conflict one — and the two share a cell. Caught by rendering the
   prototype, not by the token check, which only measures the pairs it is told to
   compare. 215 sits 140 degrees from warning, 160 from primary (the entrevista)
   and 65 from --chart-2 (the aula): the widest separation this palette offers.

   The PROTOTYPES use --chart-4 for the same chip, and that is not drift. In the
   vendor palette every chart slot has a semantic twin except --chart-4 (hue 195);
   in this one the free slot is --chart-5 (hue 215). Teal 195 and petrol 215 are
   the same visual family, so the two look alike — only the slot number differs,
   because the palettes number them differently. */
.cal-event-encontro {
  background: color-mix(in oklch, var(--chart-5) 15%, transparent);
  border-color: color-mix(in oklch, var(--chart-5) 30%, transparent);
}
.cal-event-encontro:hover { background: color-mix(in oklch, var(--chart-5) 25%, transparent); }

/* The type icon must never be the thing that gets squeezed out: it is the
   non-colour carrier of which layer an entry belongs to, and the spec forbids
   colour being the only signal. Without this the chip collapses to 41px at a
   390px viewport and pushes the icon outside its own box, leaving sighted
   users on mobile with the tint alone. The name's ellipsis absorbs the loss
   instead. */
.cal-event > svg { flex-shrink: 0; }

.cal-legend { display: flex; align-items: center; gap: var(--spacing-4); margin-bottom: var(--spacing-3); flex-wrap: wrap; }
.cal-layer { display: inline-flex; align-items: center; gap: var(--spacing-2); font-size: 0.75rem; color: var(--color-muted-foreground); cursor: pointer; }
.cal-swatch {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; border-radius: var(--radius-xs);
  border: 1px solid transparent; color: var(--color-foreground);
}
.cal-swatch-aula {
  background: color-mix(in oklch, var(--chart-2) 15%, transparent);
  border-color: color-mix(in oklch, var(--chart-2) 30%, transparent);
}
.cal-swatch-entrevista {
  background: color-mix(in oklch, var(--color-primary) 15%, transparent);
  border-color: color-mix(in oklch, var(--color-primary) 30%, transparent);
}
.cal-swatch-encontro {
  background: color-mix(in oklch, var(--chart-5) 15%, transparent);
  border-color: color-mix(in oklch, var(--chart-5) 30%, transparent);
}

/* ── Certificate condition panel (/academico/cursos, student view) ────────
   Text stays on --color-foreground and only the icon carries the semantic
   colour: putting the text on the same hue as its own wash caps contrast
   structurally, which is what held .pill-count at 3.66:1. */
.cert-panel {
  display: flex; gap: var(--spacing-3); align-items: flex-start;
  padding: var(--spacing-4); border-radius: var(--radius-md);
  border: 1px solid var(--color-border); margin-bottom: var(--spacing-5);
}
.cert-pending {
  background: color-mix(in oklch, var(--color-warning) 8%, transparent);
  border-color: color-mix(in oklch, var(--color-warning) 30%, transparent);
}
.cert-ready {
  background: color-mix(in oklch, var(--color-success) 8%, transparent);
  border-color: color-mix(in oklch, var(--color-success) 30%, transparent);
}
.cert-icon { flex-shrink: 0; color: var(--color-warning); margin-top: 1px; }
.cert-ready .cert-icon { color: var(--color-success); }
.cert-title { font-size: 0.875rem; font-weight: 600; color: var(--color-foreground); margin: 0 0 var(--spacing-1); }
.cert-desc { font-size: 0.8125rem; color: var(--color-muted-foreground); line-height: 1.6; margin: 0; }
.cert-month { font-family: var(--font-family-mono); color: var(--color-foreground); }

/* ── Notice panel + full-page form (/academico/alunos) ────────────────────
   Generalises the certificate panel above: text on --color-foreground /
   --color-muted-foreground with only the icon carrying the semantic colour,
   because text on a color-mix() of its own hue caps contrast structurally. */
.notice-panel {
  display: flex; gap: var(--spacing-3); align-items: flex-start;
  padding: var(--spacing-4); border-radius: var(--radius-md);
  border: 1px solid var(--color-border); margin-bottom: var(--spacing-5);
}
.notice-primary {
  background: color-mix(in oklch, var(--color-primary) 8%, transparent);
  border-color: color-mix(in oklch, var(--color-primary) 30%, transparent);
}
.notice-primary > svg { color: var(--color-primary); }
.notice-destructive {
  background: color-mix(in oklch, var(--color-destructive) 8%, transparent);
  border-color: color-mix(in oklch, var(--color-destructive) 30%, transparent);
  margin-bottom: 0;
}
.notice-destructive > svg { color: var(--color-destructive); }
/* Warning: something is missing or contradictory but nothing is blocked — the
   supervision shortfall, a class encounter on an unregistered date, a student
   marked absent who is also recorded as having attended. Same 8%/30% shape as
   the other two, and the same rule: the TEXT stays muted-foreground and only
   the icon carries the hue, because text on a color-mix() of its own colour
   caps contrast structurally. */
.notice-warning {
  background: color-mix(in oklch, var(--color-warning) 8%, transparent);
  border-color: color-mix(in oklch, var(--color-warning) 30%, transparent);
}
.notice-warning > svg { color: var(--color-warning); }
.notice-panel > svg { flex-shrink: 0; margin-top: 1px; }
.notice-title { font-size: 0.875rem; font-weight: 600; color: var(--color-foreground); margin: 0 0 var(--spacing-1); }
.notice-desc { font-size: 0.8125rem; color: var(--color-muted-foreground); line-height: 1.6; margin: 0; }

/* Forms of eight fields or more get a page, per patterns.json. */
.form-page { max-width: 720px; }
.form-section { margin-bottom: var(--spacing-6); }
.form-section-title { font-size: 0.875rem; font-weight: 600; color: var(--color-foreground); margin-bottom: var(--spacing-1); }
.form-section-desc { font-size: 0.75rem; color: var(--color-muted-foreground); margin-bottom: var(--spacing-4); line-height: 1.6; }
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--spacing-4); }
.form-grid-full { grid-column: 1 / -1; }
@media (max-width: 640px) { .form-grid { grid-template-columns: 1fr; } }
.form-actions { display: flex; gap: var(--spacing-2); justify-content: flex-end; padding-top: var(--spacing-4); border-top: 1px solid var(--color-border); }
.missing-mark { font-size: 0.625rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--color-warning); margin-left: var(--spacing-1); }

/* Training-window bar */
.janela-bar { height: 6px; border-radius: var(--radius-xs); background: var(--color-muted); overflow: hidden; margin-top: var(--spacing-3); }
.janela-fill { height: 100%; background: var(--color-primary); }
.janela-legend { display: flex; justify-content: space-between; gap: var(--spacing-2); font-size: 0.6875rem; color: var(--color-muted-foreground); margin-top: var(--spacing-1); font-family: var(--font-family-mono); }

/* The aside track is 320px and an e-mail does not break, so a badge beside a
   long one pushed the card past its own column — measured at 3px over. */
.account-row { display: flex; align-items: center; gap: var(--spacing-2); flex-wrap: wrap; }
.account-mail { font-size: 0.8125rem; color: var(--color-foreground); overflow-wrap: anywhere; }

/* ── Case members and the attending pair (/atendimento/clientes) ──────────
   Ported from the approved prototype. Missing these produced a record where a
   member's name and contact ran together on one line — the view referenced
   classes that only existed in the self-contained prototype. */
.member-row, .pair-row {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--spacing-3); padding: var(--spacing-3) 0;
  border-bottom: 1px solid var(--color-border);
}
.pair-row { align-items: center; }
.member-row:last-child, .pair-row:last-child { border-bottom: none; }
.member-name { display: block; font-size: 0.875rem; color: var(--color-foreground); }
.member-meta { display: block; font-size: 0.75rem; color: var(--color-muted-foreground); margin-top: 2px; overflow-wrap: anywhere; }

/* ── Profissionais que acompanham o cliente (ficha do caso) ─────────────────
   Um subcabeçalho por membro, e as linhas de contato reusando .member-row.
   O contato é do MEMBRO, e o subcabeçalho é o que faz isso visível em vez de
   inferido — a alternativa era engordar a linha do membro, que já carrega
   WhatsApp, e-mail, CPF e as sessões. */
.prov-group { padding-top: var(--spacing-3); }
.prov-group:first-child { padding-top: 0; }
.prov-group + .prov-group { border-top: 1px solid var(--color-border); }
.prov-member { display: flex; align-items: center; gap: var(--spacing-2); font-size: 0.75rem; font-weight: 500; color: var(--color-muted-foreground); margin-bottom: var(--spacing-1); }
.prov-none { font-size: 0.75rem; color: var(--color-muted-foreground); margin: 0 0 var(--spacing-2); }
/* `flex-shrink: 0` porque a caixa dos botões é o primeiro item a ser esmagado
   quando o nome do profissional é longo — o ícone sairia da própria caixa, que
   foi o defeito medido em `.cal-event` a 390px. */
.prov-actions { display: flex; gap: var(--spacing-1); flex-shrink: 0; }

.cal-slot-warn { display: flex; align-items: center; gap: var(--spacing-1); font-size: 0.6875rem; color: var(--color-warning); margin-top: var(--spacing-1); }
.cal-slot-free { font-size: 0.75rem; color: var(--color-muted-foreground); opacity: 0.6; }

/* Narrow viewports: the month grid stops being a grid rather than
   forcing the page to scroll sideways. */
@media (max-width: 640px) {
  .cal-grid { grid-template-columns: repeat(7, minmax(0, 1fr)); }
  .cal-day { min-height: 56px; }
  .cal-event-name, .cal-more { display: none; }
  .cal-event { justify-content: center; }
  .cal-week { grid-template-columns: 1fr; }
  .cal-week-col { min-height: 0; border-right: none; border-bottom: 1px solid var(--color-border); }
  .cal-week-head { flex-direction: row; gap: var(--spacing-2); justify-content: flex-start; }
  .cal-toolbar { align-items: flex-start; }
}

/* ── Encontros ────────────────────────────────────────────────────────────
   Shared by four views (encontros, encontro, encontro-form, meus-encontros),
   so this is one implementation rather than four page-local copies.

   The `enc-` prefix was checked against every docs/colber-admin/pages/*.html
   and against this file before being introduced: free in both. Generic names
   were avoided on purpose — a generic class added here for one screen has
   broken another screen that used the same name ad hoc.

   The notice panels reuse .notice-panel/.notice-warning above; there is no
   separate panel composition here. */

/* When and how long. The duration sits next to the hours because the required
   supervision is derived from it — the number and its reason belong together. */
.enc-date { font-family: var(--font-family-mono); font-size: 0.8125rem; color: var(--color-foreground); display: block; }
.enc-dow { font-size: 0.6875rem; color: var(--color-muted-foreground); }
.enc-hours { font-family: var(--font-family-mono); font-size: 0.8125rem; color: var(--color-foreground); display: block; }
.enc-dur { font-size: 0.6875rem; color: var(--color-muted-foreground); }
.enc-dur-unknown { font-size: 0.6875rem; color: var(--color-warning); }

/* Supervision is signalled, never enforced: a date is set before the
   supervisor is known, so a shortfall is information, not an error. */
.enc-sup { font-family: var(--font-family-mono); font-size: 0.8125rem; display: block; }
.enc-sup-ok { color: var(--color-foreground); }
.enc-sup-short { color: var(--color-warning); font-weight: 500; }
.enc-sup-note { font-size: 0.6875rem; color: var(--color-warning); display: block; }

/* "No roll taken" and "everyone was absent" must never look alike. */
.enc-roll { font-size: 0.75rem; color: var(--color-muted-foreground); }
.enc-roll-none { font-size: 0.75rem; color: var(--color-warning); font-weight: 500; }
.enc-count { font-family: var(--font-family-mono); font-size: 0.8125rem; }

/* A boolean filter has to read as a control next to the selects, not as loose
   body text — same 36px height and border as .filter-select. */
.enc-filter-check {
  display: inline-flex; align-items: center; gap: var(--spacing-2);
  height: 36px; padding: 0 var(--spacing-3);
  font-size: 0.875rem; color: var(--color-foreground);
  border: 1px solid var(--color-control-border); border-radius: var(--radius-md);
  background: var(--color-background); cursor: pointer; white-space: nowrap;
}
.enc-filter-check:hover { background: var(--color-accent); }
.enc-filter-check:focus-within { border-color: var(--color-ring); }

/* The roll: three states, visibly three. A student with no record is NOT an
   absence — collapsing them reintroduces what migration 009 removed. */
.enc-roll-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--spacing-3); padding: var(--spacing-2) 0;
  border-bottom: 1px solid var(--color-border);
}
.enc-roll-row:last-child { border-bottom: none; }
.enc-roll-name { font-size: 0.8125rem; color: var(--color-foreground); }
.enc-roll-choice { display: flex; gap: var(--spacing-1); flex-shrink: 0; }
.enc-roll-opt { position: relative; display: inline-flex; }
/* The visible control is the label; the radio itself is off-screen but
   focusable, so keyboard selection and :focus-visible still work. */
.enc-roll-opt input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.enc-roll-btn {
  padding: 0.1875rem var(--spacing-2); font-size: 0.6875rem; font-weight: 500;
  border-radius: var(--radius-xs); border: 1px solid var(--color-control-border);
  background: transparent; color: var(--color-muted-foreground);
  cursor: pointer; font-family: inherit; white-space: nowrap;
}
.enc-roll-btn:hover { background: var(--color-accent); color: var(--color-foreground); }
.enc-roll-opt input:focus-visible + .enc-roll-btn { outline: 2px solid var(--color-ring); outline-offset: 2px; }
.enc-roll-opt input:checked + .enc-roll-btn[data-status="presente"] {
  background: color-mix(in oklch, var(--color-success) 15%, transparent);
  border-color: color-mix(in oklch, var(--color-success) 30%, transparent);
  color: var(--color-success);
}
.enc-roll-opt input:checked + .enc-roll-btn[data-status="ausente"] {
  background: color-mix(in oklch, var(--color-destructive) 15%, transparent);
  border-color: color-mix(in oklch, var(--color-destructive) 30%, transparent);
  color: var(--color-destructive);
}
.enc-roll-summary {
  display: flex; flex-wrap: wrap; gap: var(--spacing-4);
  font-size: 0.75rem; color: var(--color-muted-foreground); margin-bottom: var(--spacing-3);
}
.enc-roll-summary strong { font-family: var(--font-family-mono); color: var(--color-foreground); font-weight: 600; }

/* The roll per two-hour block, with each student's photo.
   ─────────────────────────────────────────────────────────────────────────
   The photo is a PORTRAIT in function, not an avatar: whoever takes the roll
   needs to recognise a face, and 48px was measured as not enough. 64px is the
   size that still fits a class of twelve on a screen or two. --radius-md and
   not the round avatar exception, which crops the corners of a portrait.

   The row wraps: at 390px the block groups drop below the name instead of
   squeezing it. Each block carries its own time label, so a wrapped group
   still says which two hours it records. */
.enc-roll-row { flex-wrap: wrap; }
.enc-roll-who { display: flex; align-items: center; gap: var(--spacing-3); flex: 1 1 220px; }
.enc-roll-photo {
  width: 64px; height: 64px; flex-shrink: 0;
  border-radius: var(--radius-md);
  display: flex; align-items: center; justify-content: center;
  font-size: 1.125rem; font-weight: 700; color: var(--color-avatar-foreground);
}
.enc-roll-blocks { display: flex; flex-wrap: wrap; gap: var(--spacing-4); }
.enc-roll-block { display: flex; flex-direction: column; gap: var(--spacing-1); }
.enc-roll-block-label {
  font-family: var(--font-family-mono); font-size: 0.6875rem; color: var(--color-muted-foreground);
}
.enc-roll-blocks-summary .enc-roll-summary { margin-bottom: var(--spacing-1); }
.enc-roll-blocks-summary { margin-bottom: var(--spacing-3); }

/* Who attended on the day — NOT the pair assigned to the case. */
.enc-apt-students { display: block; font-size: 0.6875rem; color: var(--color-muted-foreground); margin-top: 1px; }
.enc-sup-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--spacing-3); padding: var(--spacing-2) 0;
  border-bottom: 1px solid var(--color-border);
}
.enc-sup-row:last-child { border-bottom: none; }
.enc-sup-name { font-size: 0.8125rem; color: var(--color-foreground); }
.enc-sup-role { font-size: 0.6875rem; color: var(--color-muted-foreground); }
.enc-empty-inline { font-size: 0.8125rem; color: var(--color-muted-foreground); padding: var(--spacing-3) 0; }

/* One proposed appointment on the form. It is a proposal: nothing is written
   until submit, so abandoning the page creates nothing. */
.enc-apt-edit {
  border: 1px solid var(--color-border); border-radius: var(--radius-md);
  padding: var(--spacing-3) var(--spacing-4); margin-bottom: var(--spacing-3);
}
.enc-apt-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--spacing-3); margin-bottom: var(--spacing-3); }
.enc-apt-case { font-size: 0.875rem; font-weight: 500; color: var(--color-foreground); }
.enc-apt-grid { display: grid; grid-template-columns: 110px minmax(0, 1fr); gap: var(--spacing-3); align-items: start; }
@media (max-width: 620px) { .enc-apt-grid { grid-template-columns: minmax(0, 1fr); } }
.enc-student-picks { display: flex; flex-wrap: wrap; gap: var(--spacing-2); }
.enc-student-pick {
  display: inline-flex; align-items: center; gap: var(--spacing-2);
  font-size: 0.8125rem; padding: 0.1875rem var(--spacing-2);
  border: 1px solid var(--color-control-border); border-radius: var(--radius-sm); cursor: pointer;
}
.enc-student-pick:has(input:checked) {
  background: color-mix(in oklch, var(--color-primary) 12%, transparent);
  border-color: color-mix(in oklch, var(--color-primary) 30%, transparent);
}
.enc-student-pick:focus-within { outline: 2px solid var(--color-ring); outline-offset: 2px; }

/* Turma vs Manutenção. The CARD is the control: RadioGroup is in the registry
   but is rendered nowhere in the package, and a circular 16px indicator would
   break "no border-radius above --radius-xl except avatars". The native radio
   stays for semantics, off-screen but focusable. */
.enc-kind-choice { display: flex; gap: var(--spacing-3); flex-wrap: wrap; }
.enc-kind-opt {
  flex: 1; min-width: 200px; padding: var(--spacing-3);
  border: 1px solid var(--color-control-border); border-radius: var(--radius-md); cursor: pointer;
}
.enc-kind-opt input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.enc-kind-opt:has(input:checked) {
  border-color: var(--color-primary);
  background: color-mix(in oklch, var(--color-primary) 8%, transparent);
}
.enc-kind-opt:focus-within { outline: 2px solid var(--color-ring); outline-offset: 2px; }
.enc-kind-label { font-size: 0.8125rem; font-weight: 500; color: var(--color-foreground); display: block; }
.enc-kind-desc { font-size: 0.6875rem; color: var(--color-muted-foreground); margin-top: 2px; line-height: 1.4; display: block; }

/* Why an encounter is in "Meus Encontros" — the list unions two sources, so it
   has to say which one put each row there. */
.enc-mine-cases { display: block; font-size: 0.6875rem; color: var(--color-muted-foreground); margin-top: 1px; }

/* Small cell treatments the encounter tables share. */
.enc-turma { font-size: 0.8125rem; color: var(--color-muted-foreground); }
.enc-note {
  display: flex; align-items: center; gap: var(--spacing-2);
  margin-top: var(--spacing-4); font-size: 0.75rem; color: var(--color-muted-foreground);
}

/* ── Faixa de impersonação ──────────────────────────────────────────────────
   Um ESTADO, não um erro — daí `--color-warning` e não a paleta destrutiva.
   Nada deu errado: o sistema está fazendo o que foi pedido.

   Sem botão de fechar, por decisão. Quem esquece que está apresentando-se como
   outra pessoa lê o sistema como se fosse a própria visão, e aviso que pode ser
   fechado é aviso que será. A saída é o único controle, e é a que resolve.

   O fundo é o token cheio e não um `color-mix()` de si mesmo: texto sobre uma
   lavagem da própria cor tem teto estrutural de contraste, defeito já registrado
   neste projeto (`.pill-count` no /faq). Aqui o par é
   warning / warning-foreground, que o check:contrast mede. */
.imp-banner {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--spacing-4); flex-wrap: wrap;
  padding: var(--spacing-2) var(--spacing-6);
  background: var(--color-warning);
  color: var(--color-warning-foreground);
  border-bottom: 1px solid var(--color-warning);
}
.imp-banner-msg {
  display: flex; align-items: center; gap: var(--spacing-2);
  font-size: 0.8125rem; line-height: 1.4; min-width: 0;
}
/* Sem isto o ícone é a primeira coisa a sair da caixa num viewport estreito,
   mesmo defeito já corrigido no `.cal-event`. */
.imp-banner-icon { width: 16px; height: 16px; flex-shrink: 0; }
.imp-banner-msg strong { font-weight: 600; }
.imp-banner-exit { flex-shrink: 0; }
/* O botão herda a cor do texto da faixa em vez de trazer a própria: sobre um
   fundo warning, um `ds-btn-default` (primary) some ou grita. */
.imp-banner-btn {
  background: transparent;
  border: 1px solid var(--color-warning-foreground);
  color: var(--color-warning-foreground);
}
.imp-banner-btn:hover {
  background: color-mix(in oklch, var(--color-warning-foreground) 12%, transparent);
}

/* ── .file-field: escolher um arquivo com o nome LEGÍVEL ──────────────────
 *
 * O CONTROLE NATIVO NÃO SERVE AQUI, e o motivo é medido.
 *
 * `<input type="file">` desenha um botão e, ao lado dele, o nome do arquivo —
 * e a repartição entre os dois é do navegador, não nossa. Numa célula de tabela
 * a caixa é estreita, o botão fica com quase tudo, e o nome é truncado a uns
 * 40px: "Ne…hido". O Instituto relatou justamente isso — só passando o mouse em
 * cima para ver o tooltip é que se sabe qual arquivo foi escolhido. Nenhum valor
 * de largura no input resolve, porque o espaço que sobra para o nome não é
 * controlável por CSS.
 *
 * A COMPOSIÇÃO: o input nativo fica invisível DENTRO do label, e o label desenha
 * o gatilho e o nome. Clicar em qualquer parte do label abre o seletor — é o
 * comportamento nativo do label, sem JS. O JS só escreve o nome escolhido; ver
 * `initFileFields()` em app.js.
 *
 * `opacity: 0` e NÃO `display: none`, mesma razão dos cartões-como-radio da fase
 * 6: um input com `display: none` sai da ordem de tabulação e o controle deixa de
 * ser alcançável pelo teclado. Assim ele continua focável, e `:focus-within`
 * mostra o foco no gatilho.
 *
 * ONDE ISTO VIVE: composição compartilhada, com DOIS consumidores (a ficha do
 * caso e Meus Encontros), então uma implementação só aqui — nunca uma cópia por
 * página. */
.file-field {
  display: inline-flex; align-items: center; gap: var(--spacing-2);
  /* `position: relative` faz DESTE elemento o bloco contentor do input absoluto,
     e isso não é decoração — é metade da correção de um transbordo medido.

     O DEFEITO: o input é `position: absolute`; sem ancestral posicionado o bloco
     contentor dele é o DOCUMENTO, então ele fica na posição estática dentro da
     tabela larga e o `overflow: auto` do `.table-wrap` NÃO o recorta — o overflow
     de um ancestral só recorta descendente posicionado quando aquele ancestral É o
     bloco contentor dele. Medido: o input de 1px punha o documento em 493px num
     viewport de 390 em Meus Encontros, com a tabela rolando corretamente. Terceira
     ocorrência desta família aqui, depois do `.sr-only` de /users.

     E O QUE A MEDIÇÃO CORRIGIU NA MINHA PRIMEIRA EXPLICAÇÃO: as quatro combinações
     de `position` e `left` dão 390/390/390/493 — ou seja, esta linha OU o `left: 0`
     do input, qualquer uma sozinha, já evita o transbordo. As duas ficam porque
     resolvem coisas diferentes: `left: 0` sozinho põe a caixa invisível na origem do
     documento (é a correção do `.sr-only`), enquanto esta a mantém SOBRE o controle,
     onde ela pertence. Atribuir o conserto a uma só delas seria descrever errado o
     que foi medido. */
  position: relative;
  /* SEM `min-width: 0` aqui, e a ausência é medida, não esquecimento.
     Escrevi as duas (campo e nome) por reflexo — é o conserto do rótulo da sidebar,
     onde um item flex com `min-width: auto` se recusava a encolher e o
     `text-overflow` nunca agia. Aqui não é o caso: neutralizando cada uma e as duas
     juntas, a 390px e a 1280px, as quatro medições dão largura e transbordo
     IDÊNTICOS. Quem limita o nome é o `max-width` dele, e a célula da tabela absorve
     o resto. Uma declaração que mede igual nos dois estados é uma declaração que o
     próximo leitor confia pelo motivo errado. */
  max-width: 100%;
  cursor: pointer;
}
.file-field-input {
  /* `left`/`top` explícitos, ancorados no campo: um absoluto sem offsets fica na
     posição estática, e é isso que o fazia escapar do clip. Ver `.file-field`. */
  position: absolute; left: 0; top: 0;
  opacity: 0; width: 1px; height: 1px;
  /* Não `display: none`: ver o cabeçalho — o teclado precisa alcançá-lo. */
  pointer-events: none;
}
/* O gatilho tem a forma de `.ds-btn-outline`, mas é um `<span>` dentro do label:
   um `<button>` aqui capturaria o clique e impediria o label de abrir o seletor. */
.file-field-trigger {
  display: inline-flex; align-items: center; gap: var(--spacing-1);
  flex-shrink: 0;
  height: 32px; padding: 0 var(--spacing-3);
  font-size: 0.8125rem; font-weight: 500; line-height: 1;
  border: 1px solid var(--color-control-border); border-radius: var(--radius-md);
  background: transparent; color: var(--color-foreground);
  transition: background var(--duration-fast), border-color var(--duration-fast);
}
.file-field:hover .file-field-trigger { background: var(--color-muted); }
/* O foco vive no gatilho porque o input real é invisível. 2px, como todo foco
   deste projeto — a exceção aprovada sobre a borda de 1px. */
.file-field:focus-within .file-field-trigger {
  border-color: var(--color-ring);
  outline: 2px solid var(--color-ring); outline-offset: 2px;
}
/* O NOME, que é o ponto de tudo isto: ocupa o espaço livre e trunca com elipse
   em vez de ser cortado no meio de uma letra. `min-width: 0` de novo, pelo mesmo
   motivo, e `flex: 1` para ele — e não o gatilho — receber a folga. */
.file-field-name {
  /* PISO E TETO, e cada um resolve metade do problema relatado.
     Numa célula de tabela a caixa é shrink-to-fit, então sem BASE o nome ficaria com
     a largura do texto: legível, mas um nome de 120 caracteres alargaria a tabela e
     forçaria rolagem horizontal (medido — a célula cresce e o `.table-wrap` rola).
     E sem TETO ele nunca truncaria. Com 240px de base o nome tem espaço de sobra
     para ser lido; com 320px de teto ele trunca com elipse em vez de crescer.
     Os dois números são verificados por mutação: removendo o teto o nome deixa de
     truncar, removendo o piso ele volta à caixa estreita que o Instituto relatou. */
  flex: 1 1 240px; max-width: 320px;
  font-size: 0.8125rem; line-height: 1.3;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--color-foreground);
}
/* Sem arquivo escolhido é ESTADO, não erro: fica em muted e diz o que falta. */
.file-field-name.is-empty { color: var(--color-muted-foreground); }
/* Num viewport estreito o nome desce para a própria linha em vez de sumir: numa
   célula de 150px, gatilho e nome lado a lado não caberiam nem com elipse. */
@media (max-width: 640px) {
  .file-field { flex-wrap: wrap; }
  .file-field-name { flex-basis: 100%; }
}

/* ── .fin-*: as ações de lançamento numa célula de tabela ─────────────────
 *
 * DUAS páginas as usam (a lista de pendências e o ledger do aluno), então vivem
 * aqui e não num `<style>` de página: CSS bespoke a UMA tela fica na própria view,
 * composição com dois consumidores fica compartilhada. O prefixo `fin-` foi
 * conferido por grep contra todo stylesheet, view e protótipo antes de entrar.
 *
 * `flex-wrap` porque a célula carrega até quatro controles (mês, arquivo, anexar,
 * atestar) e a 390px eles não caberiam em linha — sem o wrap a tabela alarga e o
 * `.table-wrap` rola horizontalmente por causa de uma célula só.
 *
 * O `<select>` do mês tem largura FIXA: sem ela ele encolhe até o texto do mês
 * ficar cortado, e é justamente o valor que decide em qual mês o lançamento cai.
 */
.fin-row-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--spacing-2);
}

.fin-row-form {
  display: flex;
  align-items: center;
  gap: var(--spacing-2);
  /* `min-width: 0` aqui NÃO é reflexo: o formulário é item flex do
     `.fin-row-actions` e contém o `.file-field`, que tem `max-width` próprio —
     sem isso o form se recusa a encolher abaixo do conteúdo e reintroduz o
     transbordo que o `max-width` do nome existe para evitar. */
  min-width: 0;
}

.fin-mes {
  width: 8.5rem;
  flex: none;
}

/* ── .fin-control: select + botão numa COLUNA ESTREITA ────────────────────
 *
 * O DEFEITO, MEDIDO: na lateral de 320px da ficha, um `<select>` com `flex: 1` ao
 * lado de um botão transbordava o card em **146px** — "Conceder" ficava metade fora
 * da borda.
 *
 * A CAUSA NÃO É O BOTÃO. Um item flex tem `min-width: auto`, e a largura mínima
 * automática de um `<select>` é a da sua OPÇÃO MAIS LONGA. Com "Fundamentos da
 * Terapia Familiar Sistêmica Avançada" no catálogo, o select reivindica aquela
 * largura como piso, e o que não cabe é empurrado para fora. O par "Nota fiscal +
 * Salvar" logo acima sobrevivia por SORTE — as opções dele são curtas —, e por isso
 * os dois usam esta composição, não só o que quebrou.
 *
 * A CORREÇÃO É TROCAR O EIXO. A largura mínima automática vale para o eixo
 * PRINCIPAL do flex; numa coluna o eixo principal é o vertical, então a largura fica
 * livre para encolher e o transbordo deixa de ser possível POR CONSTRUÇÃO, não por o
 * catálogo ter nomes curtos. Verificado com uma opção de 160 caracteres.
 *
 * ── O QUE NÃO ESTÁ AQUI, E POR QUE ───────────────────────────────────────
 *
 * Escrevi primeiro `min-width: 0` e `width: 100%` nos controles, e afirmei no
 * comentário que as duas eram necessárias. MEDIDAS, as duas são INERTES: com e sem
 * elas, os selects medem 283px e o transbordo é zero, inclusive com a opção de 160
 * caracteres — porque uma coluna flex já estica os filhos (`align-items: stretch`) e
 * já libera a largura. Removidas.
 *
 * É a mesma família do `min-width: 0` inerte que saiu do `.file-field`: declaração
 * que mede igual nos dois estados é declaração que o próximo leitor confia pelo
 * motivo errado — e, aqui, um comentário afirmando o oposto do que a medição diz
 * seria pior que a declaração.
 *
 * `align-self: flex-start` no botão é a única que faz trabalho: sem ela o `stretch`
 * o leva a 283px, e botão da largura do card lê-se como ação primária da tela —
 * conceder um curso não é isso. Medido: 56/74px com, 283px sem.
 */
.fin-control {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-2);
  margin: 0;
}
.fin-control > button {
  align-self: flex-start;
}

/* ── .rec-*: a célula de um comprovante na lista de documentos da ficha ────
 *
 * O NOME DE ARQUIVO É TEXTO LONGO numa célula estreita, e foi isso que a tela
 * renderizada mostrou: com ícone, nome e link "ver" soltos na mesma linha, cada um
 * quebrava numa linha própria e a célula lia-se como três fragmentos empilhados.
 *
 * `inline-flex` com o ícone `flex-shrink: 0` mantém ícone e nome como uma coisa; o
 * `overflow-wrap` deixa o nome quebrar por dentro em vez de alargar a tabela, que é
 * o outro extremo. A ação secundária desce para `.rec-meta`, junto do tamanho.
 */
.rec-file {
  display: inline-flex;
  align-items: baseline;
  gap: var(--spacing-1);
  font-size: 0.8125rem;
  overflow-wrap: anywhere;
}
.rec-file > i { flex-shrink: 0; }

.rec-meta {
  display: flex;
  align-items: center;
  gap: var(--spacing-1);
  margin-top: 2px;
  font-size: 0.6875rem;
  color: var(--color-muted-foreground);
}
/* O LINK DA META TAMBÉM É FLEX, e isto não é simetria gratuita.
   O preflight do Tailwind põe `display: block` em `svg`, então o ícone que o Lucide
   injeta dentro de um `<a>` inline empilha ACIMA do texto — medido na tela: o olho
   ficava sobre a palavra "ver". É a mesma família do `img { display: block }` que já
   descentrou o lockup do login neste projeto, agora em SVG. */
.rec-meta a {
  display: inline-flex;
  align-items: center;
  gap: 3px;
}
.rec-meta a > svg { flex-shrink: 0; }
