/*
 * Colber Design System — Component Classes (ds-*)
 *
 * Extracted VERBATIM from docs/colber-admin/docs/components.html (the design system
 * showroom). Do not invent variants that aren't in the showroom — if a
 * component you need isn't documented there, say so and ask, don't improvise
 * a Tailwind approximation from memory. See CLAUDE.md and
 * docs/DESIGN-SYSTEM-PORTING.md for the full workflow.
 *
 * Source line references are from components.html as read on 2026-07-30.
 */

/* ── Button — components.html:46-64 ────────────── */
.ds-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.4375rem 0.875rem;
  font-size: 0.875rem;
  font-weight: 500;
  border-radius: var(--radius-md);
  border: 1px solid transparent;
  cursor: pointer;
  transition: opacity 120ms ease-out;
  font-family: var(--font-family-body);
  line-height: 1;
  gap: 0.375rem;
}
.ds-btn-default { background: var(--color-primary); color: var(--color-primary-foreground); }
.ds-btn-default:hover { opacity: 0.9; }
.ds-btn-secondary { background: var(--color-secondary); color: var(--color-secondary-foreground); border-color: var(--color-border); }
.ds-btn-outline { background: transparent; color: var(--color-foreground); border-color: var(--color-border); }
.ds-btn-outline:hover { background: var(--color-muted); }
.ds-btn-ghost { background: transparent; color: var(--color-foreground); border-color: transparent; }
.ds-btn-ghost:hover { background: var(--color-muted); }
.ds-btn-destructive { background: var(--color-destructive); color: var(--color-destructive-foreground); }
.ds-btn-sm { padding: 0.25rem 0.625rem; font-size: 0.8125rem; }
.ds-btn-lg { padding: 0.625rem 1.25rem; font-size: 1rem; }
.ds-btn-icon { padding: 0.4375rem; width: 2.25rem; height: 2.25rem; }
.ds-btn-disabled { opacity: 0.5; pointer-events: none; }

/* ── Badge — components.html:65-76 ──────────────
   Radius is intentionally xs (2px) — badges read as more "square" than
   buttons per design-rules.md. Use ds-badge-success/warning/muted for
   semantic status, not raw ds-badge-default with hardcoded colors. */
.ds-badge {
  display: inline-flex;
  align-items: center;
  padding: 0.125rem 0.5rem;
  font-size: 0.75rem;
  font-weight: 500;
  border-radius: var(--radius-xs);
  border: 1px solid transparent;
  line-height: 1.4;
}
.ds-badge-default { background: var(--color-primary); color: var(--color-primary-foreground); }
.ds-badge-secondary { background: var(--color-secondary); color: var(--color-secondary-foreground); border-color: var(--color-border); }
.ds-badge-outline { background: transparent; color: var(--color-foreground); border-color: var(--color-border); }
.ds-badge-destructive { background: var(--color-destructive); color: var(--color-destructive-foreground); }
.ds-badge-success { background: var(--color-success); color: var(--color-success-foreground); }
.ds-badge-warning { background: var(--color-warning); color: var(--color-warning-foreground); }
.ds-badge-muted { background: var(--color-muted); color: var(--color-muted-foreground); border-color: var(--color-border); }

/* ── Timeline — components.html:126-143 (custom, no shadcn equivalent).
   Vertical event list with a connector line — audit logs, order history,
   wizard steps. Preview only on users/detail.ejs's Atividade tab so far
   (toggle next to the existing flat .activity-list), pending approval to
   become the default. ── */
.ds-timeline { display: flex; flex-direction: column; }
.ds-timeline-item { display: flex; gap: var(--spacing-3); }
.ds-timeline-connector { display: flex; flex-direction: column; align-items: center; flex-shrink: 0; }
.ds-timeline-dot { width: 2rem; height: 2rem; border-radius: var(--radius-full); display: flex; align-items: center; justify-content: center; flex-shrink: 0; font-size: 0.75rem; font-weight: 600; border: 2px solid transparent; }
.ds-timeline-dot.completed { background: var(--color-primary); color: var(--color-primary-foreground); border-color: var(--color-primary); }
.ds-timeline-dot.current { background: color-mix(in oklch, var(--color-primary) 14%, transparent); color: var(--color-primary); border-color: var(--color-primary); }
.ds-timeline-dot.upcoming { background: var(--color-muted); color: var(--color-muted-foreground); border-color: var(--color-border); }
.ds-timeline-dot.error { background: var(--color-destructive); color: var(--color-destructive-foreground); border-color: var(--color-destructive); }
.ds-timeline-line { width: 2px; flex: 1; min-height: 1.25rem; margin: 0.25rem 0; background: var(--color-border); }
.ds-timeline-line.completed { background: var(--color-primary); }
.ds-timeline-content { padding-bottom: var(--spacing-5); padding-top: 0.25rem; }

/* ── Card — components.html:87-90
   No box-shadow, ever. Elevation comes from border + background contrast
   only (design-rules.md is explicit about this). */
.ds-card {
  padding: var(--spacing-4);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border);
  background: var(--color-card);
}

/* ── Checkbox — components.html:91-96 ───────────── */
.ds-checkbox {
  width: 1rem;
  height: 1rem;
  border-radius: var(--radius-xs);
  border: 1px solid var(--color-control-border);
  background: transparent;
  appearance: none;
  cursor: pointer;
  transition: all 120ms ease-out;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.ds-checkbox:checked { background: var(--color-primary); border-color: var(--color-primary); }

/* ── Switch — components.html:97-118
   IMPORTANT: this is a <div role="switch" aria-checked> toggled by adding/
   removing the .on class via JS — NOT a native <input type="checkbox">
   wrapped in spans. Thumb is a ::after pseudo-element. Do not rebuild this
   as a checkbox+track+thumb span trio; use the real showroom markup:

     <div class="ds-switch on" role="switch" aria-checked="true" tabindex="0"></div>

   Toggling: element.classList.toggle('on'); element.setAttribute('aria-checked', ...) */
.ds-switch {
  width: 2.25rem;
  height: 1.25rem;
  border-radius: var(--radius-full);
  background: var(--color-muted);
  border: 1px solid var(--color-control-border);
  cursor: pointer;
  position: relative;
  transition: background 200ms ease-out;
  display: inline-block;
  flex-shrink: 0;
}
.ds-switch::after {
  content: '';
  position: absolute;
  top: 1px;
  left: 1px;
  width: 0.875rem;
  height: 0.875rem;
  border-radius: var(--radius-full);
  background: var(--color-switch-knob);
  transition: transform 200ms ease-out;
  box-shadow: 0 1px 3px oklch(0 0 0 / 0.2);
}
.ds-switch.on { background: var(--color-primary); border-color: var(--color-primary); }
.ds-switch.on::after { transform: translateX(1rem); }

/* ── Avatar — used inline in table.html example, no dedicated section
   but referenced consistently as ds-avatar across the showroom's table demo. */
.ds-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-full);
  background: var(--color-primary);
  color: var(--color-primary-foreground);
  font-weight: 600;
  flex-shrink: 0;
  width: 2rem;
  height: 2rem;
  font-size: 0.75rem;
}

/* ── Sidebar — ported VERBATIM from docs/colber-admin/pages/dashboard.html:37-311
   (the canonical shell per docs/index.html — every page must share this
   exact shell, never a per-page variation). Class names match the
   prototype exactly: .nav-item / .nav-item-icon / .nav-item-label /
   .nav-section-label / .nav-separator / .sidebar-brand / .sidebar-nav /
   .sidebar-footer. Do not rename these — a prior version of this file
   used invented names (.sidebar-item, .sidebar-label, .sidebar-section)
   copied from docs/colber-admin/docs/assets/sidebar.js (the DOCS app's own nav,
   not the product shell) and that's why the sidebar didn't match. */

#sidebar {
  /* A largura vive num token local porque DOIS lugares precisam dela: aqui e a
     regra que neutraliza o estado recolhido abaixo de 1023px. Dois literais que
     precisam concordar é um que fica para trás. */
  --sidebar-width: 220px;
  position: fixed;
  top: 0;
  left: 0;
  height: 100vh;
  width: var(--sidebar-width);
  background: var(--color-sidebar-background);
  border-right: 1px solid var(--color-sidebar-border);
  display: flex;
  flex-direction: column;
  z-index: 50;
  transition: width var(--duration-normal) var(--ease-default);
  overflow: hidden;
}
#sidebar.collapsed { width: 56px; }

.sidebar-brand {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 var(--spacing-4);
  height: 56px;
  border-bottom: 1px solid var(--color-sidebar-border);
  flex-shrink: 0;
}
.sidebar-brand-inner { display: flex; align-items: center; gap: var(--spacing-3); min-width: 0; }
/* DIVERGÊNCIA DELIBERADA do protótipo do fornecedor, no mesmo espírito de
   .nav-item mais abaixo: o nome portado fica, a APARÊNCIA muda.

   O showroom desenha .brand-logo como quadrado PREENCHIDO com a cor primária,
   porque o conteúdo dele lá é um glifo de 16px que precisa de corpo. Aqui o
   conteúdo é a marca do Instituto, que é arte transparente com cor própria —
   o preenchimento vira uma laje terracota atrás dela.

   `object-fit: contain` e nunca `cover`: os contêineres de marca são
   quadrados, o lockup é ~3:1, e `cover` o recortaria pelo meio da palavra.
   Uma marca é renderizada inteira ou não é aquela marca. */
.brand-logo {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: var(--radius-md);
  flex-shrink: 0;
  object-fit: contain;
}

/* Troca de variante por tema.

   Duas <img> no DOM e o CSS mostra uma — mesmo padrão que auth.ejs já usa nos
   ícones do alternador de tema (.theme-icon-dark / -light / -high-contrast).
   O tema é resolvido ANTES da primeira pintura pelo script inline em
   head.ejs, então a imagem certa já é a visível na primeira pintura e não há
   troca a fotografar.

   `high-contrast` tem fundo branco e por isso cai na regra padrão (onLight),
   sem precisar de seletor próprio — é a razão de a regra escura ser `.dark` e
   não `:not(.light)`.

   A que está oculta sai da árvore de acessibilidade por `display: none`, então
   o alt é anunciado uma vez e não duas.

   O ESTADO VISÍVEL É `block` NOS DOIS TEMAS, e isto não é detalhe: o preflight
   do Tailwind já declara `img { display: block }`, então a variante clara é
   bloco por herança. A primeira versão desta regra revelava a escura como
   `inline-block` — e `text-align: center` centra inline-block mas não centra
   bloco, então o lockup do login aparecia CENTRADO no tema escuro e ENCOSTADO
   À ESQUERDA no claro. Nenhum teste pegou, porque nenhum media posição
   horizontal; apareceu ao olhar as duas capturas lado a lado. Quem quer
   centrar usa `margin-inline: auto`, como .auth-brand-lockup faz. */
.brand-on-dark { display: none; }
.dark .brand-on-light { display: none; }
.dark .brand-on-dark { display: block; }
.brand-name {
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--color-sidebar-foreground);
  white-space: nowrap;
  overflow: hidden;
  transition: opacity var(--duration-fast) var(--ease-default), width var(--duration-normal) var(--ease-default);
}
#sidebar.collapsed .brand-name { opacity: 0; width: 0; }

.sidebar-nav {
  flex: 1;
  overflow-y: auto;
  overflow-x: hidden;
  padding: var(--spacing-3) 0;
  scrollbar-width: thin;
  scrollbar-color: var(--color-sidebar-border) transparent;
  /* The scrollbar must never sit on a label.
     Measured: "Lista de Espera — Clientes" is 155px of text in a 158.5px box —
     3.5px of slack. A scrollbar that TAKES layout space (WebKit's 4px custom bar,
     Firefox's ~12px `thin`) eats that slack and clips the final letter, which
     reads as a broken render rather than as truncation. Headless Chromium uses
     overlay scrollbars and reserves 0px, so this never showed up in a test.
     `scrollbar-gutter: stable` reserves the space up front, on every engine,
     instead of letting it be taken from the text. */
  scrollbar-gutter: stable;
}
.sidebar-nav::-webkit-scrollbar { width: 4px; }
.sidebar-nav::-webkit-scrollbar-track { background: transparent; }
.sidebar-nav::-webkit-scrollbar-thumb { background: var(--color-sidebar-border); border-radius: var(--radius-full); }

.nav-section-label {
  display: block;
  /* The same 2px transparent border and the same horizontal padding as
     .nav-item, so the section heading aligns with the icons below it BY
     CONSTRUCTION rather than by two numbers that happen to match. Without the
     border the heading sat 2px to the left of every item under it. */
  border-left: 2px solid transparent;
  padding: var(--spacing-3) var(--spacing-3) var(--spacing-1);
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-sidebar-muted);
  white-space: nowrap;
  overflow: hidden;
  transition: opacity var(--duration-fast) var(--ease-default);
}
#sidebar.collapsed .nav-section-label { opacity: 0; }

.nav-separator {
  height: 1px;
  background: var(--color-sidebar-border);
  margin: var(--spacing-2) var(--spacing-4);
  transition: margin var(--duration-normal) var(--ease-default);
}
#sidebar.collapsed .nav-separator { margin: var(--spacing-2) var(--spacing-3); }

/* DIVERGÊNCIA do protótipo do fornecedor: lá o item inativo é
   --color-sidebar-muted. Sobre o marrom escuro da nossa sidebar isso lia como
   cinza apagado (passava AA em 6.16:1, mas parecia desabilitado). O item usa o
   mesmo quase-branco do estado ativo; o ativo continua distinto por fundo,
   pela barra terracota de 2px à esquerda e pelo peso 500 — nunca dependeu só
   da cor. --color-sidebar-muted segue nos rótulos de seção, sub-itens e
   chevrons, que são a camada secundária. */
/* DELIBERATE DEVIATION from the vendor showroom, which uses --spacing-4 for the
   horizontal padding and --spacing-3 for the gap.
   The Institute's longest label, "Lista de Espera — Clientes", is 155px of text.
   The vendor's rhythm leaves it a 159px box, which survives an overlay scrollbar
   and does NOT survive one that takes layout space — 155.0px on WebKit's 4px bar
   (zero slack) and 147.0px on Firefox's `thin` (8px short). Tightening the
   padding and the gap by one step each returns 10.5px, which fits on every
   engine with the gutter reserved. Both values stay on the spacing scale, and
   tighter is the direction the DNA asks for ("density compact — when in doubt,
   tighter and quieter"). Vertical padding is untouched, so the row height and
   the list's rhythm are unchanged.

   Reverting these two values to the vendor's fails `tests/sidebar.spec.js` —
   "nenhum rótulo da sidebar é cortado". Verified by mutation, so a reconciliation
   after the next vendor drop finds out mechanically and not from a screenshot. */
.nav-item {
  display: flex;
  align-items: center;
  gap: var(--spacing-2);
  padding: var(--spacing-2) var(--spacing-3);
  color: var(--color-sidebar-foreground);
  text-decoration: none;
  font-size: 0.875rem;
  font-weight: 400;
  white-space: nowrap;
  border-left: 2px solid transparent;
  cursor: pointer;
  background: transparent;
  width: 100%;
  text-align: left;
  font-family: inherit;
  transition: color var(--duration-fast) var(--ease-default),
              background var(--duration-fast) var(--ease-default),
              border-left-color var(--duration-fast) var(--ease-default);
  position: relative;
}
.nav-item:hover {
  color: var(--color-sidebar-foreground);
  background: color-mix(in oklch, var(--color-sidebar-accent) 60%, transparent);
}
.nav-item.active {
  color: var(--color-sidebar-foreground);
  background: var(--color-sidebar-accent);
  border-left-color: var(--color-sidebar-primary);
  font-weight: 500;
}
.nav-item:focus-visible { outline: 2px solid var(--color-sidebar-primary); outline-offset: -2px; }

.nav-item-icon { display: flex; align-items: center; justify-content: center; width: 20px; height: 20px; flex-shrink: 0; }
/* `min-width: 0` so the label may actually shrink — a flex item defaults to
   `min-width: auto`, which refuses to go below its content and pushes the row
   wider instead of letting `overflow: hidden` do its job.
   `text-overflow: ellipsis` so a label that genuinely does not fit reads as
   shortened rather than as a rendering fault: the defect this replaces showed
   "Lista de Espera — Cliente" with no sign that anything was missing. */
.nav-item-label {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: opacity var(--duration-fast) var(--ease-default), width var(--duration-normal) var(--ease-default);
}
/* `flex: none` matters and `width: 0` alone is not enough: .nav-item-label is
   `flex: 1`, so flex-grow keeps expanding it past the zero base width and pushes
   the icon to the left edge. Measured: the icon landed at x=12 on a 56px rail
   whose centre is 28. This was already wrong before the padding was tightened —
   the wider padding happened to park the icon at 26, near enough that nobody
   looked. */
#sidebar.collapsed .nav-item-label { opacity: 0; width: 0; flex: none; }

/* Collapsed, the icon IS the item, so it sits on the rail's centre line. */
#sidebar.collapsed .nav-item {
  justify-content: center;
  padding-left: 0;
  padding-right: 0;
  gap: 0;
}
/* And no gutter is reserved when there is no label to protect — 10px out of a
   56px rail is what would keep the icon off centre. */
#sidebar.collapsed .sidebar-nav { scrollbar-gutter: auto; }

.nav-item-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 var(--spacing-1);
  background: var(--color-sidebar-primary);
  color: var(--color-primary-foreground);
  border-radius: var(--radius-full);
  font-size: 0.625rem;
  font-weight: 600;
  flex-shrink: 0;
  transition: opacity var(--duration-fast) var(--ease-default);
}
#sidebar.collapsed .nav-item-badge { opacity: 0; }

.nav-item-chevron {
  color: var(--color-sidebar-muted);
  flex-shrink: 0;
  transition: transform var(--duration-normal) var(--ease-default), opacity var(--duration-fast) var(--ease-default);
}
.nav-item-chevron.open { transform: rotate(180deg); }
#sidebar.collapsed .nav-item-chevron { opacity: 0; }

/* Tooltip when sidebar is collapsed (icon-only)
 *
 * ── THIS RULE IS DEAD. DO NOT TRY TO FIX IT. ──────────────────────────────
 *
 * It generates correctly — measured: content set, `left: 65px`, width 152px —
 * and is clipped away, because the collapsed rail ends at 56px. It cannot be
 * rescued by any overflow value:
 *
 *   - `.nav-item` lives inside `.sidebar-nav`, which is `overflow-y: auto`.
 *   - CSS computes `overflow-x: visible` to `auto` whenever the other axis
 *     scrolls (measured, not assumed). So the nav always clips it.
 *   - Removing `overflow: hidden` from `#sidebar` changes nothing: the nav
 *     clips it first.
 *
 * A pseudo-element cannot leave a scrolling ancestor. The collapsed rail is
 * named by a `title` attribute on the item instead — see the comment in
 * src/views/partials/sidebar.ejs.
 *
 * The rule is KEPT rather than deleted because it is part of the vendor shell
 * (docs/colber-admin/pages/dashboard.html carries it, with the same defect, so
 * it has never worked in the delivered package) and the next drop reinstates
 * it. Deleting it here would only make the next reconciliation add it back with
 * no note. This comment is the note. */
#sidebar.collapsed .nav-item[data-tooltip]:hover::after {
  content: attr(data-tooltip);
  position: absolute;
  left: calc(100% + 12px);
  top: 50%;
  transform: translateY(-50%);
  background: var(--color-popover);
  color: var(--color-popover-foreground);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--spacing-1) var(--spacing-3);
  font-size: 0.75rem;
  font-weight: 500;
  white-space: nowrap;
  pointer-events: none;
  z-index: 100;
  box-shadow: var(--shadow-md);
}

/* Submenu (item-level, not section-level) — unused today (no sub-routes
   exist yet) but kept so a future item can adopt it without reinventing it */
.nav-submenu { overflow: hidden; max-height: 0; transition: max-height var(--duration-normal) var(--ease-default); }
.nav-submenu.open { max-height: 500px; }
#sidebar.collapsed .nav-submenu { max-height: 0 !important; }
.nav-sub-item {
  display: flex;
  align-items: center;
  gap: var(--spacing-2);
  padding: 6px var(--spacing-4) 6px 3rem;
  color: var(--color-sidebar-muted);
  text-decoration: none;
  font-size: 0.8125rem;
  white-space: nowrap;
  transition: color var(--duration-fast) var(--ease-default), background var(--duration-fast) var(--ease-default);
}
.nav-sub-item::before { content: ''; width: 5px; height: 5px; border-radius: var(--radius-full); background: currentColor; opacity: 0.5; flex-shrink: 0; }
.nav-sub-item:hover { color: var(--color-sidebar-foreground); background: color-mix(in oklch, var(--color-sidebar-accent) 40%, transparent); }
.nav-sub-item.active { color: var(--color-sidebar-primary); }
.nav-sub-item.active::before { opacity: 1; }

.sidebar-footer { border-top: 1px solid var(--color-sidebar-border); flex-shrink: 0; height: 52px; }

#sidebar-overlay { z-index: 49; }
@media (max-width: 1023px) {
  #sidebar { transform: translateX(-100%); transition: transform var(--duration-normal) var(--ease-default); }
  #sidebar.open { transform: translateX(0); }
}

/* ── Topbar — ported verbatim from dashboard.html:389-513, 565-714 ──── */
#topbar {
  position: sticky;
  top: 0;
  z-index: 40;
  height: 56px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 var(--spacing-6);
  background: var(--color-card);
  border-bottom: 1px solid var(--color-border);
}
.topbar-left { display: flex; align-items: center; gap: var(--spacing-4); }
.topbar-right { display: flex; align-items: center; gap: var(--spacing-1); }

.breadcrumb { display: flex; align-items: center; gap: var(--spacing-2); font-size: 0.75rem; }
.breadcrumb-item { color: var(--color-muted-foreground); text-decoration: none; }
.breadcrumb-item:hover { color: var(--color-foreground); }
.breadcrumb-sep { color: var(--color-muted-foreground); opacity: 0.4; }
.breadcrumb-current { color: var(--color-foreground); font-weight: 500; }

.topbar-btn {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  background: transparent;
  border: none;
  border-radius: var(--radius-md);
  color: var(--color-muted-foreground);
  cursor: pointer;
  transition: color var(--duration-fast) var(--ease-default), background var(--duration-fast) var(--ease-default);
}
.topbar-btn:hover { color: var(--color-foreground); background: var(--color-muted); }
.topbar-btn:focus-visible { outline: 2px solid var(--color-ring); outline-offset: 2px; }

.icon-badge {
  position: absolute;
  top: 5px;
  right: 4px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  background: var(--color-primary);
  color: var(--color-primary-foreground);
  border-radius: var(--radius-full);
  font-size: 0.5625rem;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1.5px solid var(--color-card);
}
.icon-badge.destructive { background: var(--color-destructive); }

.topbar-user {
  display: flex;
  align-items: center;
  gap: var(--spacing-2);
  height: 36px;
  padding: 0 var(--spacing-2) 0 var(--spacing-1);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-default), border-color var(--duration-fast) var(--ease-default);
  font-family: inherit;
}
.topbar-user:hover { background: var(--color-muted); border-color: var(--color-border); }
.topbar-user:focus-visible { outline: 2px solid var(--color-ring); outline-offset: 2px; }

.topbar-avatar {
  width: 28px;
  height: 28px;
  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;
}
.topbar-user-name { font-size: 0.8125rem; font-weight: 500; color: var(--color-foreground); white-space: nowrap; }
.mobile-menu-btn { display: none; }
@media (max-width: 1023px) {
  .mobile-menu-btn { display: flex; }
  .topbar-collapse-btn { display: none; }

  /* O estado recolhido NÃO vale aqui, e isto anda junto com a linha acima.
     ─────────────────────────────────────────────────────────────────────────
     A preferência é persistida em localStorage, e `#sidebar.collapsed` está
     declarado fora desta media query. Medido: com a preferência ligada, a gaveta
     abria com 56px de largura e todo rótulo em width 0 / opacity 0 — menu
     inutilizável. Enquanto o botão aparecia, dava para desfazer; escondendo o
     botão (linha acima) o estado ficaria INALCANÇÁVEL.

     A classe e o localStorage ficam intactos de propósito: alargar a janela
     restaura o recolhido que a pessoa escolheu. O que se neutraliza é o efeito
     aqui, não a escolha. */
  #sidebar.collapsed { width: var(--sidebar-width); }
  #sidebar.collapsed .brand-name,
  #sidebar.collapsed .nav-section-label,
  #sidebar.collapsed .nav-item-badge,
  #sidebar.collapsed .nav-item-chevron { opacity: 1; }
  #sidebar.collapsed .nav-item-label { opacity: 1; width: auto; flex: 1; }
  #sidebar.collapsed .nav-submenu { max-height: none !important; }
}

/* The breadcrumb never shrank, and neither .topbar-left nor .topbar-right
   carried min-width:0, so on a narrow screen the trail pushed the whole
   document wider than the viewport — 484px on /ingresso/entrevistas and 498px
   on /academico/calendario, at a 390px viewport. Measured by hiding #topbar,
   which dropped both to exactly 390.

   Hidden rather than truncated below 640px: the page title says where you are,
   and an ellipsised trail costs the same width while saying less. */
@media (max-width: 640px) {
  .breadcrumb { display: none; }
}
.topbar-left, .topbar-right { min-width: 0; }

/* Dropdowns */
.has-dropdown { position: relative; }
.dropdown {
  position: absolute;
  top: calc(100% + var(--spacing-2));
  right: 0;
  width: 320px;
  background: var(--color-popover);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
  z-index: 200;
  display: none;
  overflow: hidden;
}
.dropdown.open { display: block; }
.dropdown-header { display: flex; align-items: center; justify-content: space-between; padding: var(--spacing-3) var(--spacing-4); border-bottom: 1px solid var(--color-border); }
.dropdown-title { font-size: 0.875rem; font-weight: 600; color: var(--color-foreground); }
.dropdown-item {
  display: flex;
  align-items: flex-start;
  gap: var(--spacing-3);
  padding: var(--spacing-3) var(--spacing-4);
  border-bottom: 1px solid var(--color-border);
  text-decoration: none;
  transition: background var(--duration-fast) var(--ease-default);
  cursor: pointer;
}
.dropdown-item:last-child { border-bottom: none; }
.dropdown-item:hover { background: var(--color-muted); }
.dropdown-item-icon { display: flex; align-items: center; justify-content: center; width: 32px; height: 32px; border-radius: var(--radius-full); flex-shrink: 0; }
.dropdown-item-body { flex: 1; min-width: 0; }
.dropdown-item-title { font-size: 0.8125rem; font-weight: 500; color: var(--color-foreground); margin-bottom: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dropdown-item-desc { font-size: 0.75rem; color: var(--color-muted-foreground); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.user-dropdown { width: 220px; }
.user-dropdown-header { padding: var(--spacing-4); border-bottom: 1px solid var(--color-border); }
.user-dropdown-name { font-size: 0.875rem; font-weight: 600; color: var(--color-foreground); }
.user-dropdown-email { font-size: 0.75rem; color: var(--color-muted-foreground); margin-top: 1px; }
.user-menu-item {
  display: flex;
  align-items: center;
  gap: var(--spacing-3);
  padding: var(--spacing-2) var(--spacing-3);
  font-size: 0.875rem;
  color: var(--color-foreground);
  cursor: pointer;
  text-decoration: none;
  background: transparent;
  border: none;
  width: 100%;
  text-align: left;
  font-family: inherit;
  transition: background var(--duration-fast) var(--ease-default);
}
.user-menu-item:hover { background: var(--color-muted); }
.user-menu-item.destructive { color: var(--color-destructive); }
.user-menu-separator { height: 1px; background: var(--color-border); margin: var(--spacing-1) 0; }

/* ── Link — otp-validator.html / login-otp.html (footer legal links, resend buttons) ── */
.ds-link {
  color: var(--color-primary);
  text-decoration: none;
  font-size: 0.75rem;
  font-weight: 500;
  transition: opacity var(--duration-fast) var(--ease-default);
}
.ds-link:hover { opacity: 0.75; text-decoration: underline; text-underline-offset: 2px; }
.ds-link:disabled { color: var(--color-muted-foreground); cursor: default; opacity: 1; text-decoration: none; }
