/* =============================================================================
   Fontes — o kit de marca (static/brand/brand.css) declara a IBM Plex Sans
   (variável 100–700), a IBM Plex Mono 400/500, a Unbounded 800 (só a marca)
   e o recurso com as métricas da Plex ('Plex Sans Fallback'): as mesmas nas
   três apps do backoffice, servidas de /brand/fonts e ligadas antes desta
   folha no index.html. Aqui fica só o que é do Genisium: o itálico da Plex
   Sans (as respostas do LLM usam *ênfase* e o faux-italic da Plex é feio).

   A versão vai no NOME do ficheiro: NUNCA sobrescrever in-place (immutable é
   irrecuperável do lado do cliente) — nome novo, mudar aqui, e a lista
   VENDOR_SHELL_ASSETS + a purga no sw.js (ver static/fonts/README.md).
   ============================================================================= */

@font-face {
    font-family: 'IBM Plex Sans';
    src: url('./fonts/ibm-plex-sans-italic-latin-v23.woff2') format('woff2');
    font-weight: 100 700;
    font-style: italic;
    font-display: swap;
}

/* =============================================================================
   Tema do highlight.js (github-dark-dimmed 11.9.0) — colado aqui de propósito.
   Era um <link> cross-origin render-blocking no <head> para 1,2 KB que só
   interessam dentro de um bloco de código no chat. Ao vir no style.css deixa de
   custar um request e uma origem, e o cdnjs sai do style-src da CSP.
   Actualizar só se o highlight.js subir de versão.
   ============================================================================= */
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
  Theme: GitHub Dark Dimmed
  Description: Dark dimmed theme as seen on github.com
  Author: github.com
  Maintainer: @Hirse
  Updated: 2021-05-15

  Colors taken from GitHub's CSS
*/.hljs{color:#adbac7;background:#22272e}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#f47067}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#dcbdfb}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#6cb6ff}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#96d0ff}.hljs-built_in,.hljs-symbol{color:#f69d50}.hljs-code,.hljs-comment,.hljs-formula{color:#768390}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag{color:#8ddb8c}.hljs-subst{color:#adbac7}.hljs-section{color:#316dca;font-weight:700}.hljs-bullet{color:#eac55f}.hljs-emphasis{color:#adbac7;font-style:italic}.hljs-strong{color:#adbac7;font-weight:700}.hljs-addition{color:#b4f1b4;background-color:#1b4721}.hljs-deletion{color:#ffd8d3;background-color:#78191b}

/* =============================================================================
   Genisium — estilos da PWA. Paleta "Gelo" e letra da marca no :root (tokens
   --brand-*, DS-05); IBM Plex para leitura e etiquetas, Unbounded só para o
   wordmark. Transições em Web Animations (app.js), sem bibliotecas.
   ============================================================================= */

/* =============================================================================
   A11y utilities (U1/U2/U6) — labels sr-only e skip-link por teclado.
   ============================================================================= */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.skip-link {
    position: absolute;
    top: -40px;
    left: 0;
    background: var(--accent);
    color: var(--on-brand);
    padding: var(--space-2) var(--space-4);
    z-index: var(--z-skip);
    text-decoration: none;
    font-weight: 600;
    border-radius: 0 0 var(--radius-xs) 0;
    transition: top var(--dur-fast) ease-out;
}
.skip-link:focus,
.skip-link:focus-visible {
    top: 0;
    outline: 2px solid #fff;
    outline-offset: 2px;
}

/* U7 — prevenir zoom automático do iOS em inputs (alternativa a user-scalable=no). */
@media (max-width: 480px) {
    input, textarea, select {
        font-size: var(--text-input);
    }
}

/* U21 — indicação visual de mensagens pendentes/falhadas + botão retry */
.message--pending {
    opacity: 0.6;
}
.message--pending .message-time::after {
    content: ' · a enviar…';
}
.message.user.message--failed {
    border-color: rgba(var(--error-rgb), 0.6);
    background: rgba(var(--error-rgb), 0.08);
}
.message-error {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-1) 0 0 0;
    font-size: var(--text-xs);
    color: var(--error-text);
}
/* OBS-07 — «ref. a1b2c3d4»: selecciona-se de uma vez para colar no pedido de ajuda */
.message-error-ref {
    font-family: var(--font-mono, ui-monospace, monospace);
    opacity: 0.8;
    user-select: all;
    white-space: nowrap;
}
/* O «Repetir»/«Tentar de novo» é um .btn--secondary pequeno (DS-12): sai o
   chip branco com tinta vermelha escrita à mão (#B91C1C, fora dos tokens). */

/* IA-12 — controlos que só o superadmin pode usar (o endpoint dá 403 aos
   outros). Fechado por omissão: só aparecem com body[data-role="superadmin"],
   que o app.js põe quando conhece o papel. */
body:not([data-role="superadmin"]) .admin-only {
    display: none !important;
}

/* =============================================================================
   Knowledge + Brain panel
   ============================================================================= */

/* .modal-card.kb-panel-card e não só .kb-panel-card: o .modal-card vem mais
   abaixo com a mesma especificidade e ganhava — o painel ficava preso nos
   380 px do modal de pairing (VIS-12). */
.modal-card.kb-panel-card {
    max-width: 920px;
    width: min(92vw, 920px);
    max-height: 85vh;
    display: flex;
    flex-direction: column;
    padding: var(--space-5) var(--space-6);
    gap: var(--space-3);
    overflow: hidden;
}

.kb-panel-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--space-3);
    border-bottom: 1px solid var(--border);
    padding-bottom: var(--space-2);
}
.kb-panel-header h3 {
    margin: 0;
    font-size: var(--text-lg);
    font-weight: 600;
}

.kb-tabs {
    display: flex;
    gap: var(--space-1);
    border-bottom: 1px solid var(--border);
    padding-bottom: 0;
}
/* 14 → 12 de lado (DS-13, empate pelo caber): com 16 os quatro separadores
   passavam o painel a 390 px e, ao focar «Os meus dados», o cartão rolava
   ~17 px na horizontal e o conteúdo todo ficava torto. */
.kb-tab {
    background: transparent;
    color: var(--text-secondary);
    border: 0;
    padding: var(--space-2) var(--space-3);
    font-weight: 600;
    font-size: var(--text-sm);
    cursor: pointer;
    border-bottom: 2px solid transparent;
    transition: color var(--dur-fast), border-color var(--dur-fast);
}
.kb-tab:hover { color: #fff; }
.kb-tab.active {
    color: #fff;
    border-bottom-color: var(--accent);
}

.kb-section {
    display: none;
    flex-direction: column;
    gap: var(--space-2);
    overflow: hidden;
    flex: 1 1 auto;
    min-height: 0;
}
.kb-section.active { display: flex; }

/* Brain */
.kb-brain-toolbar {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--space-2);
}
.kb-meta {
    font-size: var(--text-xs);
    color: var(--text-secondary);
    font-variant-numeric: tabular-nums;
}
/* Perto do limite do Cérebro, ou quando algo não ficou guardado (IA-13). */
.kb-meta.kb-meta--warn {
    color: var(--error-text);
    font-weight: 600;
}
.kb-brain-actions { display: flex; gap: var(--space-2); }

/* .field (DS-12); aqui só a medida e a letra de código do markdown. */
#kb-brain-textarea {
    flex: 1 1 auto;
    min-height: 320px;
    max-height: 55vh;
    width: 100%;
    padding: var(--space-3) var(--space-4);
    font-family: var(--font-mono);
}
.kb-hint {
    font-size: var(--text-2xs);
    color: var(--text-secondary);
    margin: 0;
}

/* KB list tab */
.kb-toolbar {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    align-items: center;
}
/* Os campos das barras do KB e das Skills são .field (DS-12); aqui só como
   se repartem pela linha. */
.kb-toolbar input[type="search"],
.kb-toolbar input[type="text"] {
    flex: 1 1 220px;
    min-width: min(200px, 100%);
}
.kb-checkbox {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1h);
    font-size: var(--text-xs);
    color: var(--text-secondary);
}

.kb-list {
    list-style: none;
    margin: 0;
    padding: 0;
    overflow-y: auto;
    flex: 1 1 auto;
    min-height: 200px;
}
.kb-list .kb-loading,
.kb-list .kb-empty {
    padding: var(--space-5);
    text-align: center;
    color: var(--text-secondary);
}
.kb-entry {
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: var(--space-3) var(--space-4);
    margin-bottom: var(--space-2);
    background: rgba(var(--abyss-rgb), 0.4);
    transition: border-color var(--dur-fast);
}
.kb-entry:hover { border-color: rgba(255, 255, 255, 0.16); }
.kb-entry.archived { opacity: 0.55; }
.kb-entry-head {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
}
.kb-kind {
    font-size: var(--text-2xs);
    font-weight: 600;
    background: rgba(var(--brand-rgb), 0.12);
    color: var(--accent);
    padding: var(--space-half) var(--space-2);
    border-radius: var(--radius-full);
    white-space: nowrap;
}
.kb-entry-title {
    font-weight: 600;
    color: #fff;
    font-size: var(--text-md);
}
.kb-archived-badge {
    font-size: var(--text-3xs);
    background: rgba(var(--brand-rgb), 0.15);
    color: var(--brand-hover);
    padding: var(--space-half) var(--space-1h);
    border-radius: var(--radius-xs);
    margin-left: auto;
}
.kb-entry-body {
    font-size: var(--text-xs);
    color: var(--text-secondary);
    margin-top: var(--space-1h);
    white-space: pre-wrap;
    word-break: break-word;
    max-height: 80px;
    overflow: hidden;
    line-height: 1.5;
}
.kb-tags {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1);
    margin-top: var(--space-1h);
}
.kb-tag {
    font-size: var(--text-2xs);
    background: rgba(255, 255, 255, 0.07);
    padding: var(--space-half) var(--space-2);
    border-radius: var(--radius-full);
    color: var(--text-secondary);
}
.kb-entry-actions {
    display: flex;
    gap: var(--space-1h);
    margin-top: var(--space-2);
    justify-content: flex-end;
}

/* Edit sub-modal */
.kb-inner-modal {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.6);
    display: flex;
    align-items: center;
    justify-content: center;
    /* Dentro do .modal-overlay do painel (contexto de empilhamento próprio):
       basta um degrau acima do cartão. */
    z-index: var(--z-raised);
}
.kb-inner-modal.hidden { display: none; }
/* Sub-modal por cima de um painel raised: sobe UM degrau (header), em vez
   de cair para quase-preto e parecer um buraco (DS-11). */
.kb-edit-card {
    background: var(--surface-header);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    padding: var(--space-5) var(--space-6);
    width: min(90vw, 600px);
    max-height: 90vh;
    overflow-y: auto;
    box-shadow: var(--shadow-lg);
}
.kb-edit-card header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: var(--space-4);
}
.kb-edit-card header h4 {
    margin: 0;
    font-size: var(--text-base);
    color: #fff;
}
#kb-edit-form label {
    display: block;
    font-size: var(--text-xs);
    color: var(--text-secondary);
    margin-bottom: var(--space-2);
}
/* O <form> é .fields (DS-12): o estilo vem do componente. */
#kb-edit-form input,
#kb-edit-form select,
#kb-edit-form textarea {
    display: block;
    width: 100%;
    margin-top: var(--space-1);
}
#kb-edit-form textarea {
    font-family: var(--font-mono);
    min-height: 160px;
}
.kb-edit-actions {
    display: flex;
    gap: var(--space-2);
    justify-content: flex-end;
    margin-top: var(--space-2);
}

/* Commit C — KB review tab (candidatos auto-extracted) */
.kb-review-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 18px;
    height: 18px;
    padding: 0 var(--space-1h);
    border-radius: var(--radius-full);
    background: var(--gradient-brand);
    color: var(--on-brand);
    font-size: var(--text-3xs);
    font-weight: 600;
    margin-left: var(--space-1h);
}
.kb-review-badge.hidden { display: none; }

/* =============================================================================
   Definições e Operações (static/settings-panel.js) — IA-05/06/07, OBS-06, PWA-03
   ============================================================================= */
/* .modal-card vem depois no ficheiro (max-width 380 px, centrado): a dupla
   classe ganha-lhe sem depender da ordem. */
.modal-card.ops-panel-card {
    text-align: left;
    max-width: 980px;
    width: min(94vw, 980px);
    height: min(86vh, 760px);
    padding: var(--space-5) var(--space-6);
}
.ops-layout {
    display: grid;
    grid-template-columns: 180px minmax(0, 1fr);
    gap: var(--space-4);
    min-height: 0;
    flex: 1 1 auto;
    overflow: hidden;
}
.ops-nav { display: flex; flex-direction: column; gap: var(--space-half); overflow-y: auto; }
.ops-nav-label {
    padding: var(--space-2) var(--space-2) var(--space-1);
    font: 600 var(--text-3xs)/1.2 var(--font-sans);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--text-muted);
}
.ops-nav-label[hidden], .ops-nav-item[hidden] { display: none; }
.ops-nav-item {
    padding: var(--space-2) var(--space-2);
    border-radius: var(--radius-xs);
    color: var(--text-primary);
    font: 500 var(--text-sm)/1.3 var(--font-sans);
    text-decoration: none;
}
.ops-nav-item:hover, .ops-nav-item:focus-visible { background: var(--accent-soft); color: var(--text-bright); outline: none; }
.ops-nav-item.active { background: var(--accent-soft); color: var(--accent); box-shadow: inset 2px 0 0 var(--accent); }
.ops-section {
    overflow-y: auto;
    min-height: 0;
    padding-right: var(--space-1);
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    outline: none;
}
.ops-card {
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: var(--space-3) var(--space-4);
    background: var(--bg-main);
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}
.ops-card-head { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.ops-card-head h4 { margin: 0; font-size: var(--text-md); color: var(--text-bright); }
.ops-desc, .ops-help { margin: 0; font-size: var(--text-sm); color: var(--text-secondary); line-height: 1.5; }
.ops-meta { margin: 0; font-size: var(--text-xs); color: var(--text-muted); font-variant-numeric: tabular-nums; }
.ops-err { display: block; margin: 0; font-size: var(--text-xs); color: var(--error-text); word-break: break-word; }
.ops-loading, .ops-empty { color: var(--text-muted); font-size: var(--text-sm); padding: var(--space-2) 0; list-style: none; }
.ops-error { color: var(--error-text); font-size: var(--text-sm); margin: 0; }
.ops-notice { margin: 0; padding: var(--space-2) var(--space-2); border-radius: var(--radius-xs); font-size: var(--text-sm); background: var(--accent-soft); color: var(--text-primary); }
.ops-notice--error { background: rgba(var(--error-rgb), 0.12); color: var(--error-text); }
.ops-notice--warning { background: rgba(var(--warning-rgb), 0.12); color: var(--warning); }
.ops-notice--ok { background: var(--accent-soft); color: var(--accent); }
.ops-steps { margin: 0; padding-left: var(--space-5); font-size: var(--text-sm); color: var(--text-primary); line-height: 1.6; }
.ops-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.ops-chip {
    display: inline-flex;
    align-items: center;
    padding: var(--space-half) var(--space-2);
    border-radius: var(--radius-full);
    font: 600 var(--text-2xs)/1.4 var(--font-sans);
    white-space: nowrap;
    border: 1px solid transparent;
}
.ops-chip--ok { background: var(--accent-soft); color: var(--accent); }
.ops-chip--warning { background: rgba(var(--warning-rgb), 0.14); color: var(--warning); }
.ops-chip--error { background: rgba(var(--error-rgb), 0.14); color: var(--error-text); }
.ops-chip--muted { border-color: var(--border); color: var(--text-muted); }
.ops-chips { display: flex; gap: var(--space-1h); flex-wrap: wrap; }
.ops-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-2); }
.ops-item {
    border: 1px solid var(--border);
    border-radius: var(--radius-xs);
    padding: var(--space-2) var(--space-3);
    display: flex;
    flex-direction: column;
    gap: var(--space-1h);
}
.ops-item--off { opacity: 0.6; }
.ops-item-head { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.ops-item-title { color: var(--text-bright); font-size: var(--text-sm); flex: 1 1 auto; min-width: 0; word-break: break-word; }
.ops-runs { margin: 0; padding-left: var(--space-4); font-size: var(--text-xs); color: var(--text-secondary); display: flex; flex-direction: column; gap: var(--space-1); }
.ops-runs[hidden] { display: none; }
.ops-toggle, .ops-check, .ops-inline { display: inline-flex; align-items: center; gap: var(--space-2); font-size: var(--text-sm); color: var(--text-primary); }
.ops-scope { align-self: flex-start; }
.ops-row { display: flex; flex-wrap: wrap; gap: var(--space-1h) var(--space-4); align-items: center; }
.ops-switch { position: relative; display: inline-flex; width: 36px; height: 20px; flex: none; }
/* F5-E4 — uma preferência por linha: a linha inteira é o alvo (≥ 44 px). */
.ops-prefs { display: flex; flex-direction: column; }
.ops-pref {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    min-height: var(--hit-min);
    padding: var(--space-1) 0;
    border-bottom: 1px solid var(--border);
    cursor: pointer;
}
.ops-prefs > .ops-pref:last-child, .ops-pref-wrap:last-child .ops-pref { border-bottom: 0; }
.ops-pref--off { cursor: default; opacity: 0.7; }
.ops-pref-text { display: flex; flex-direction: column; gap: var(--space-half); min-width: 0; }
.ops-pref-label { font-size: var(--text-sm); color: var(--text-primary); }
.ops-pref-wrap { display: flex; align-items: center; gap: var(--space-2); }
.ops-pref-wrap > .ops-pref { flex: 1; }
.ops-subhead {
    margin: var(--space-2) 0 0;
    font: 500 var(--text-2xs)/1.4 var(--font-mono);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-muted);
}
.ops-quiet { padding: var(--space-1) 0; }
.ops-switch input { position: absolute; inset: 0; opacity: 0; margin: 0; cursor: pointer; }
.ops-switch span {
    width: 100%;
    height: 100%;
    border-radius: var(--radius-full);
    background: var(--surface-header);
    border: 1px solid var(--border-strong);
    transition: background var(--dur-base);
}
.ops-switch span::after {
    content: '';
    position: absolute;
    top: 3px;
    left: 3px;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    background: var(--text-muted);
    transition: transform var(--dur-base), background var(--dur-base);
}
.ops-switch input:checked + span { background: var(--accent-soft); border-color: var(--accent); }
.ops-switch input:checked + span::after { transform: translateX(16px); background: var(--accent); }
.ops-switch input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 2px; }
.ops-form { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-3); align-items: flex-end; }
.ops-form[hidden] { display: none; }
.ops-form label { display: flex; flex-direction: column; gap: var(--space-1); font-size: var(--text-xs); color: var(--text-secondary); }
.ops-form label[hidden] { display: none; }
.ops-subform { padding-top: var(--space-1h); border-top: 1px dashed var(--border); }
/* Fase 5, E1 — Pessoas: um contentor por render (sem ouvintes acumulados),
   o PIN acabado de criar/repor com «Copiar», e os papéis por projecto. */
.ops-pessoas { display: flex; flex-direction: column; gap: var(--space-3); }
.ops-reveal {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2) var(--space-3);
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-xs);
    background: var(--accent-soft);
    color: var(--text-primary);
    font-size: var(--text-sm);
}
.ops-reveal span { flex: 1 1 16rem; min-width: 0; }
.ops-pin-code {
    font: 500 var(--text-md)/1.2 var(--font-mono);
    letter-spacing: 0.12em;
    color: var(--text-bright);
    user-select: all;
}
.ops-projects-roles .ops-inline { flex-wrap: wrap; }
.ops-help code { font-family: var(--font-mono); font-size: var(--text-xs); }
.ops-group { border: 1px solid var(--border); border-radius: var(--radius-xs); padding: var(--space-1h) var(--space-2); }
.ops-group > summary { cursor: pointer; display: flex; align-items: center; gap: var(--space-2); font-size: var(--text-sm); padding: var(--space-1) 0; }
.ops-group--error { border-color: rgba(var(--error-rgb), 0.45); }
.ops-group--warning { border-color: rgba(var(--warning-rgb), 0.4); }
.ops-checks { list-style: none; margin: var(--space-1h) 0 var(--space-1); padding: 0; display: flex; flex-direction: column; gap: var(--space-1h); font-size: var(--text-xs); color: var(--text-primary); word-break: break-word; }
.ops-health-top { display: flex; flex-direction: column; gap: var(--space-2); }
.ops-total { margin: 0; font-size: var(--text-base); color: var(--text-bright); }
.ops-table-wrap { overflow-x: auto; }
.ops-table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
.ops-table th, .ops-table td { padding: var(--space-1h) var(--space-2); border-bottom: 1px solid var(--border); text-align: left; white-space: nowrap; }
.ops-table thead th { color: var(--text-muted); font-weight: 600; font-size: var(--text-2xs); text-transform: uppercase; letter-spacing: 0.06em; }
.ops-table tbody th { color: var(--text-bright); font-weight: 600; }
.ops-table .num { text-align: right; font-variant-numeric: tabular-nums; }
.ops-table .ops-row-first td, .ops-table .ops-row-first th { border-top: 1px solid var(--border-strong); }
/* RUN-13 — tool com aviso (erros ou lenta) */
.ops-table .ops-row-warn th, .ops-table .ops-row-warn td { background: rgba(var(--warning-rgb), 0.06); }
.ops-slot[hidden] { display: none; }

@media (max-width: 720px) {
    .modal-card.ops-panel-card {
        width: 100vw;
        max-width: none;
        height: 100dvh;
        max-height: none;
        border-radius: 0;
        /* 14 → 12 de lado (empate pelo caber): com 16 o «/uso 30» do Gasto
           partia-se a meio a 390 px. */
        padding: calc(var(--space-3) + var(--safe-top)) var(--space-3) calc(var(--space-3) + var(--safe-bottom));
    }
    .ops-layout { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr); gap: var(--space-2); }
    /* F5 — desliza sem a barra de scroll (como os filtros das tarefas): a
       risca da barra parecia um sublinhado; o item aberto vem à vista no
       paintNav do settings-panel.js e o cortado na borda diz que há mais. */
    .ops-nav { flex-direction: row; overflow-x: auto; overflow-y: hidden; gap: var(--space-1); scrollbar-width: none; }
    .ops-nav::-webkit-scrollbar { display: none; }
    .ops-nav-label { display: none; }
    .ops-nav-item { white-space: nowrap; border: 1px solid var(--border); }
    .ops-nav-item.active { box-shadow: none; border-color: var(--accent); }
    .ops-form label { flex: 1 1 100%; }
}

/* Pedido suave de notificações (PWA-03): não bloqueia, some com um clique. */
.push-nudge {
    position: fixed;
    left: 50%;
    bottom: calc(var(--input-height) + 28px + var(--safe-bottom));
    transform: translateX(-50%);
    z-index: calc(var(--z-sheet) + 1);
    width: min(520px, calc(100vw - 24px));
    padding: var(--space-3) var(--space-4);
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2) var(--space-4);
    background: var(--surface-raised);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-lg);
}
.push-nudge p { margin: 0; flex: 1 1 240px; font-size: var(--text-sm); color: var(--text-primary); }
.push-nudge-actions { display: flex; gap: var(--space-2); }

/* 👍/👎 nas respostas do assistente (OBS-03) */
.btn-feedback[aria-pressed="true"] { color: var(--accent); }
.msg-feedback-note { display: flex; align-items: flex-end; gap: var(--space-1h); margin-top: var(--space-2); flex-wrap: wrap; }
.msg-feedback-note label { display: flex; flex-direction: column; gap: var(--space-1); flex: 1 1 220px; font-size: var(--text-xs); color: var(--text-secondary); }
.kb-review-intro {
    margin-bottom: var(--space-2);
}
.kb-review-candidate {
    border-left: 3px solid rgba(var(--brand-rgb), 0.5);
}

/* Skills panel (Commit F) — chips editáveis + confidence bar */
.skills-chip-row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1h);
    margin-top: var(--space-2);
    align-items: center;
}
.skills-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    background: rgba(var(--brand-rgb), 0.18);
    color: #fff;
    padding: var(--space-1) var(--space-2);
    border-radius: var(--radius-full);
    font-size: var(--text-xs);
    font-weight: 500;
}
.skills-chip-x {
    /* 24×24 de área (WCAG 2.5.8): era 12×16. A margem negativa mantém o
       chip do mesmo tamanho. */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 24px;
    min-height: 24px;
    margin: calc(-1 * var(--space-1)) calc(-1 * var(--space-2)) calc(-1 * var(--space-1)) calc(-1 * var(--space-half));
    background: transparent;
    color: #fff;
    border: 0;
    border-radius: 50%;
    font-size: var(--text-md);
    cursor: pointer;
    padding: 0;
    opacity: 0.7;
}
.skills-chip-x:hover { opacity: 1; }
.skills-chip-input {
    background: rgba(var(--abyss-rgb), 0.5);
    border: 1px dashed rgba(255, 255, 255, 0.15);
    color: var(--text-primary);
    border-radius: var(--radius-full);
    padding: var(--space-1) var(--space-3);
    font-size: var(--text-xs);
    min-width: 180px;
    outline: none;
}
.skills-chip-input:focus {
    border-color: var(--accent);
    background: rgba(var(--brand-rgb), 0.05);
}
.skills-confidence-bar {
    height: 4px;
    background: rgba(255, 255, 255, 0.08);
    border-radius: var(--radius-2xs);
    overflow: hidden;
    margin-top: var(--space-2);
}
.skills-confidence-bar > span {
    display: block;
    height: 100%;
    background: linear-gradient(90deg, var(--brand-deep), var(--brand-hover));
    transition: width var(--dur-base) ease-out;
}
.skills-confidence-bar.low > span { background: rgba(var(--brand-rgb), 0.8); }
.skills-confidence-bar.mid > span { background: linear-gradient(90deg, var(--warning), var(--brand-hover)); }


:root {
    /* DS-14 — a app é escura: os controlos nativos (popup do <select>,
       calendário do input date, checkboxes, scrollbars do Firefox) e o
       autofill seguem o esquema escuro em vez de pintar branco por cima do
       vidro. O acento das checkboxes/radios é a marca. */
    color-scheme: dark;
    accent-color: var(--accent);

    /* Cores — paleta "Gelo" (decisão do dono, out 2026): chão quase-preto
       azulado, texto de gelo pálido e um único acento ciano (#6FD3FF) que vive
       na interacção e nas partículas em movimento. Substitui o "construct
       Matrix" verde (#00FF41): cliché de terminal para uma ferramenta de
       operação de lojas, cansava em áreas grandes e colidia com o "sucesso".

       ESCALA DE SUPERFÍCIES — três degraus, não seis.

       O rácio WCAG não mede separação entre superfícies escuras: abaixo de
       Y=0,05 o termo +0,05 da fórmula (o flare que modela luz ambiente)
       domina numerador e denominador. A métrica certa aqui é ΔL*, que é
       perceptualmente uniforme. Alvo: ΔL* ≥ 6 entre camadas adjacentes.

         canvas  #171E24  L*=11,0  C=5  h=250
         raised  #283035  L*=19,2  C=5  h=250   canvas→raised  ΔL* 8,2
         header  #363D43  L*=25,4  C=5  h=250   raised→header  ΔL* 6,2

       As MESMAS luminâncias da escala verde (que já tinha sido afinada a olho
       e medida): só a matiz roda para o azul do gelo. O CROMA fica na banda
       C≈5 — a L* 11-25 um croma de 10 já se lê como tinta, e a lição da escala
       anterior foi que as superfícies pintadas parecem sujas. A cor vive nos
       acentos, não nas superfícies.

       Espalhar mais degraus por um intervalo perceptível levaria o topo a
       cinzento médio e deixaria de ser um tema escuro. Três é o que se lê.

       Rácios WCAG nos comentários: "canvas · raised · header", medidos com a
       cor composta sobre a superfície. tests/test_contrast_tokens.py recalcula
       cada um e falha se o número escrito deixar de bater certo. */
    --surface-canvas: #171E24;
    --surface-raised: #283035;
    --surface-header: #363D43;

    /* Tipografia — três papéis, duas faces. Sans = leitura e display (600 com
       tracking apertado é a "voz do organismo", sem serifa); Mono = estado,
       etiquetas e dados. */
    --font-sans: 'IBM Plex Sans', 'Plex Sans Fallback', system-ui, -apple-system, 'Segoe UI', sans-serif;
    --font-mono: 'IBM Plex Mono', ui-monospace, 'Cascadia Mono', Consolas, monospace;
    --font-display: var(--font-sans);
    /* A letra da marca: só o wordmark e o monograma, sempre a 800. */
    --font-brand: 'Unbounded', 'Arial Black', system-ui, sans-serif;
    --tracking-display: -0.025em;
    --tracking-label: 0.14em;

    /* Aliases legados — não usar em código novo.
       secondary e elevated colapsam em raised DE PROPÓSITO: a sidebar e a
       bolha do assistente nunca partilham aresta, portanto distingui-las
       gastava um degrau a troco de zero informação. */
    --bg-primary: var(--surface-canvas);    /* fundo app */
    --bg-main: var(--surface-canvas);       /* "poço": pre, inputs, thumbs */
    --bg-secondary: var(--surface-raised);  /* sidebar / painéis */
    --bg-elevated: var(--surface-raised);   /* bubbles bot, cards */
    --bg-tertiary: var(--surface-header);   /* barras de cabeçalho / hover */
    --bg-glass: rgba(54, 61, 67, 0.80);         /* = header a 80 % */
    --bg-glass-strong: rgba(40, 48, 53, 0.94);  /* = raised a 94 % */

    /* PR-M: o utilizador envia notas — uma pílula translúcida da cor da marca,
       não um bloco. O texto é --text-bright: 12,79 sobre a pílula no canvas,
       10,10 no raised. */
    --bg-message-user: rgba(var(--brand-rgb), 0.10);
    --bg-message-assistant: var(--surface-raised);

    --text-primary: #DCE6F0;     /* 13,32 canvas · 10,62 raised · 8,72 header */
    --text-secondary: #9BAEBC;   /*  7,35 canvas ·  5,86 raised · 4,81 header */
    /* REGRA (DS-06): --text-muted só sobre canvas ou raised. No header fica
       abaixo dos 4,5 — aí usa-se --text-secondary. */
    --text-muted: #8B9EAC;       /*  6,08 canvas ·  4,85 raised · 3,98 header */
    --text-bright: #F3F9FD;      /* 15,85 canvas · 12,64 raised · 10,38 header */

    /* ---------------------------------------------------------------------
       MARCA — a cor da marca vive SÓ aqui (DS-05). O resto do CSS usa
       var(--brand) / rgba(var(--brand-rgb), a) e os canvases lêem-na com
       getComputedStyle(document.documentElement).getPropertyValue('--brand').
       Trocar de paleta é mudar este bloco; tests/test_brand_tokens.py falha se
       um literal da marca voltar a aparecer fora do :root.
       --------------------------------------------------------------------- */
    --brand: #6FD3FF;               /*  9,97 canvas ·  7,95 raised · 6,53 header */
    --brand-rgb: 111, 211, 255;
    --brand-hover: #ABE5FE;         /* 12,32 canvas ·  9,83 raised · 8,07 header */
    --brand-deep: #3EB7E7;          /* fim de gradientes, botões premidos    */
    --brand-spark: #E3F7FF;         /* a cabeça luminosa de glifos/partículas */
    --brand-soft: rgba(var(--brand-rgb), 0.10);
    /* Tinta sobre o acento: 11,24 sobre --brand, 13,89 sobre --brand-hover e
       8,25 sobre --brand-deep (o gradiente inteiro passa AA). */
    --on-brand: #04121A;
    --brand-gradient: linear-gradient(135deg, var(--brand-deep) 0%, var(--brand-hover) 100%);
    --focus-ring: var(--brand);
    /* O abismo do portão e da voz: fundo radial topo → base. Sobre o topo (o
       pior caso), --text-primary a 14,86 e --text-secondary a 8,20. */
    --brand-bg-top: #0D121A;
    --brand-bg-bot: #06080B;
    --brand-glow: var(--brand);
    /* Partículas em repouso têm a cor do texto; o acento só aparece no
       movimento (wordmark, olho, orbe). */
    --brand-particle: #DCE6F0;
    --brand-particle-rgb: 220, 230, 240;
    /* Quase-preto da paleta para scrims, vidros e sombras. */
    --abyss-rgb: 4, 8, 12;
    /* Quem fala na voz: o utilizador tem cor própria (orbe, legendas) — um
       lilás, longe do ciano do Genisium. */
    --voice-user: #A99BFF;          /*  7,07 canvas ·  5,64 raised · 4,63 header */
    --voice-user-rgb: 169, 155, 255;
    --voice-user-ink: #DCD5FF;      /* 12,03 canvas ·  9,59 raised · 7,87 header */

    /* Avatares dos colegas (DS-06/DS-16): seis tons fixos com iniciais brancas
       a ≥ 4,5:1 — era hsl(250–320, 60 %, 55 %), a 3,80:1 no pior caso e num
       violeta "para harmonia" com uma paleta que já não existe. */
    --avatar-1: #296D8E;            /* branco 5,72 */
    --avatar-2: #3044A6;            /* branco 8,39 */
    --avatar-3: #6130A6;            /* branco 8,46 */
    --avatar-4: #9E2E9E;            /* branco 6,29 */
    --avatar-5: #A63061;            /* branco 6,53 */
    --avatar-6: #9E4A2E;            /* branco 6,05 */

    /* Aliases legados do acento — código novo usa --brand-*. */
    --accent: var(--brand);
    --accent-hover: var(--brand-hover);
    --accent-soft: var(--brand-soft);
    --accent-glow: rgba(var(--brand-rgb), 0.25);
    --accent-rgb: var(--brand-rgb);
    --accent-secondary: var(--brand-deep);
    --gradient-brand: var(--brand-gradient);
    --gradient-subtle: linear-gradient(135deg, rgba(var(--brand-rgb), 0.08) 0%, rgba(var(--brand-rgb), 0.05) 100%);

    /* Estados (DS-15): o "correu bem" tem cor própria e deixa de ser o acento —
       antes --success era exactamente o #00FF41 da marca, e "activo",
       "seleccionado" e "sucesso" liam-se como a mesma coisa. */
    --success: #4ADE80;             /*  9,66 canvas ·  7,70 raised · 6,32 header */
    --success-rgb: 74, 222, 128;
    /* --error é para fundos, bordas e badges com tinta preta (6,42:1); como
       TEXTO falha no raised e no header (4,10 / 3,37). Texto de erro usa
       --error-text (DS-06), que passa também sobre a tinta vermelha a 15 %. */
    --error: #FF4D4D;               /*  5,15 canvas ·  4,10 raised · 3,37 header */
    --error-rgb: 255, 77, 77;
    --error-text: #FF8585;          /*  7,17 canvas ·  5,72 raised · 4,69 header */
    --warning: #F5C542;             /* 10,38 canvas ·  8,28 raised · 6,80 header */
    --warning-rgb: 245, 197, 66;

    /* Bordas. O 3:1 da WCAG 1.4.11 só é exigido onde a borda É a informação
       que identifica o componente — inputs e botões de fundo transparente.
       Divisores e molduras de conteúdo são estrutura, não controlo, e ficam
       na hairline. Contraste sobre canvas / raised / header: */
    --border: rgba(var(--brand-rgb), 0.28);        /* 1,98 canvas · 1,92 raised · 1,82 header — hairline  */
    --border-strong: rgba(var(--brand-rgb), 0.54); /* 3,85 canvas · 3,43 raised · 3,06 header — controlos */
    --border-hover: rgba(var(--brand-rgb), 0.80);  /* 6,84 canvas · 5,68 raised · 4,80 header — estado    */
    --border-glass: rgba(var(--brand-particle-rgb), 0.14);
    --shadow-sm: 0 1px 2px rgba(var(--abyss-rgb), 0.30);
    --shadow-md: 0 4px 12px rgba(var(--abyss-rgb), 0.38);
    --shadow-lg: 0 8px 32px rgba(var(--abyss-rgb), 0.50);
    /* REGRA: brilha o que muda de estado e existe UMA vez. Nunca em selectores
       cujo número de instâncias é função do conteúdo — N halos sobrepostos são
       a lavagem colorida, e o custo de compositing escala com o histórico. */
    --shadow-glow: 0 0 20px rgba(var(--brand-rgb), 0.20);

    /* Sizing */
    --header-height: 56px;
    --input-height: 64px;

    /* ---------------------------------------------------------------------
       ESCALAS (DS-13). O resto da folha usa estes nomes, não números: mudar
       a densidade ou o tamanho base é mudar este bloco, não centenas de
       linhas. Tipografia, espaço (padding, margin, gap), raio, duração das
       transições, elevação, anéis e z-index já só por nome —
       tests/test_ds_componentes.py falha se um literal voltar.
       --------------------------------------------------------------------- */

    /* Tipografia. 11 px é o mínimo para texto; os 10,5 px são SÓ para
       etiquetas em Mono 500 maiúsculas, onde o tracking compensa o corpo.
       16 px é o mínimo dos campos no telemóvel: abaixo disso o iOS faz zoom
       ao focar. Px e não rem: a app não muda de corpo base, e o rem misturado
       com px dava 0,8rem = 12,8 px ao lado de 13 px. */
    --text-3xs: 10.5px;
    --text-2xs: 11px;
    --text-xs: 12px;
    --text-sm: 13px;
    --text-md: 14px;
    --text-base: 15px;
    --text-input: 16px;
    --text-lg: 17px;
    --text-xl: 20px;
    --text-2xl: 24px;
    --text-3xl: 28px;
    --text-4xl: 32px;
    --text-5xl: 48px;

    /* Espaço — grelha de 4 px (padding, margin, gap). Os dois meios degraus
       são do desenho fino (chips, badges, fios entre linhas); o 12 é o respiro
       dos estados vazios. Um valor fora daqui é uma excepção com motivo em
       tests/test_ds_componentes.py (EXCECOES_ESPACO), não um número à mão. */
    --space-half: 2px;
    --space-1: 4px;
    --space-1h: 6px;
    --space-2: 8px;
    --space-3: 12px;
    --space-4: 16px;
    --space-5: 20px;
    --space-6: 24px;
    --space-7: 28px;
    --space-8: 32px;
    --space-12: 48px;

    /* Raio — cinco degraus e a pílula. */
    --radius-2xs: 2px;      /* trilhos e barras finas */
    --radius-xs: 6px;       /* etiquetas, chips, código inline */
    --radius-sm: 10px;      /* controlos: botões, campos, itens, toasts */
    --radius-md: 14px;      /* contentores: compositor, popovers, diálogos */
    --radius-lg: 18px;      /* a bolha das mensagens */
    --radius-full: 9999px;
    --radius: var(--radius-md);   /* alias legado */

    /* Duração. Rápido = hover e toque; base = mudar de estado, entrar; lento
       = entradas grandes (cartões da parede, ecrãs). Os loops (o respirar de
       3,8 s da criatura, os pulsos) não são durações: têm o seu período. */
    --dur-fast: 120ms;
    --dur-base: 200ms;
    --dur-slow: 450ms;

    /* Anéis (box-shadow sem desfoque): foco dos campos e estado activo. As
       elevações são as --shadow-* acima. */
    --ring-focus: 0 0 0 3px rgba(var(--brand-rgb), 0.22);
    --ring-error: 0 0 0 3px rgba(var(--error-rgb), 0.22);
    --ring-active: inset 0 0 0 1px rgba(var(--brand-rgb), 0.35);

    /* Altura dos controlos (botões e campos): sm, md, lg. */
    --control-sm: 32px;
    --control-md: 36px;
    --control-lg: 48px;

    /* Alvo de toque mínimo (DS-08): 44 px (Apple HIG / WCAG 2.5.5). Quando o
       desenho é mais pequeno, a área cresce por um ::after invisível. */
    --hit-min: 44px;

    /* ESCADA DE Z-INDEX (DS-17) — a ordem do ecrã, de baixo para cima. Um
       componente novo escolhe um degrau, nunca um número. Os quatro primeiros
       são locais: ordenam peças DENTRO de um componente que isola o seu
       empilhamento (isolation, ou position + z-index). */
    --z-behind: -1;         /* ::before/::after decorativos por trás */
    --z-base: 0;
    --z-raised: 1;          /* por cima do irmão anterior */
    --z-float: 5;           /* barras e chips a flutuar dentro de um ecrã */
    --z-dropdown: 10;       /* popover ancorado dentro de um componente */
    --z-cards: 15;          /* cartões ambiente por cima do feed */
    --z-fab: 30;            /* botão flutuante das tarefas (telemóvel) */
    --z-orb: 900;           /* a orbe da voz */
    --z-chat-header: 920;   /* o header sobe acima da orbe */
    --z-sheet: 940;         /* folhas do telemóvel (tarefas, conversas) */
    --z-rtv-panel: 950;
    --z-thread: 960;        /* a thread: acima das folhas, ABAIXO dos modais */
    --z-seed: 970;          /* o olho que viaja até à esfera (~1 s) */
    --z-modal: 1000;        /* diálogos: .modal-overlay, lightbox, nova tarefa */
    --z-popover: 1100;      /* o autocomplete de @, ancorado ao campo com foco */
    --z-toast: 1200;        /* a pilha de toasts, por cima de qualquer diálogo */
    --z-skip: 1300;         /* o skip-link é o primeiro Tab da página */

    /* Safe areas (iOS) */
    --safe-top: env(safe-area-inset-top);
    --safe-bottom: env(safe-area-inset-bottom);

    /* Transitions */
    --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
    --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* =============================================================================
   Reset & Base
   ============================================================================= */

*, *::before, *::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

html, body {
    height: 100%;
    overflow: hidden;
}

body {
    font-family: var(--font-sans);
    font-size: var(--text-base);
    line-height: 1.6;
    background: var(--bg-primary);
    color: var(--text-primary);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    letter-spacing: 0;
}

#app {
    height: 100%;
    display: flex;
    flex-direction: column;
}

.screen {
    height: 100%;
    display: flex;
    flex-direction: column;
    position: relative;
}

.hidden {
    display: none !important;
}

/* DS-14 — autofill do browser (o login lembra utilizador e PIN): o Chrome
   pinta o campo com o fundo dele e o texto escuro, mesmo com color-scheme
   dark. Um inset do fundo dos campos tapa-o; o texto é o claro da escala.
   Com foco, o anel volta por cima do inset. */
input:-webkit-autofill,
input:-webkit-autofill:hover,
textarea:-webkit-autofill,
select:-webkit-autofill {
    -webkit-text-fill-color: var(--text-bright);
    caret-color: var(--text-bright);
    -webkit-box-shadow: 0 0 0 100px var(--surface-canvas) inset;
    box-shadow: 0 0 0 100px var(--surface-canvas) inset;
}
input:-webkit-autofill:focus {
    -webkit-text-fill-color: var(--text-bright);
    -webkit-box-shadow: 0 0 0 100px var(--surface-canvas) inset, var(--ring-focus);
    box-shadow: 0 0 0 100px var(--surface-canvas) inset, var(--ring-focus);
}


/* =============================================================================
   COMPONENTES (DS-12) — UM botão, UM fecho, UM campo, UM toast.

   Antes cada funcionalidade tinha o seu: onze botões primários com dois
   gradientes, três tintas e quatro raios; sete fechos de 20 a 44 px; três
   sistemas de toast em três cantos; dez estilos de campo. Agora:

     .btn  + --primary | --secondary | --ghost | --danger
           + --sm | (md) | --lg,  --block, --icon, --round
     .btn-close  (+ --sm dentro de chips, --on-media por cima de fotos)
     .field  (+ --sm | --lg, --code)  — ou .fields num <form>: todos os
             controlos lá dentro ficam com o mesmo estilo
     .toast  — pintado SÓ pelo static/toast.js (window.GenisiumToast)

   Ficam aqui, logo a seguir ao reset e antes dos ecrãs: uma regra de
   contexto mais abaixo (com a mesma especificidade) ganha-lhes, e é assim
   que um ecrã ajusta um componente sem o reescrever. EXCEPÇÃO: os painéis do
   Cérebro/KB e das Definições estão mais ACIMA na folha — aí um ajuste a um
   componente precisa de um selector mais específico (ex.: .ap-actions .btn),
   uma classe sozinha perde para a camada. Referência viva com todos os
   estados: /kit.html (fora do service worker e da navegação).
   ============================================================================= */

/* ---- Botão ---------------------------------------------------------------- */
.btn {
    --btn-h: var(--control-md);
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    min-height: var(--btn-h);
    padding: 0 var(--space-4);
    font: 600 var(--text-sm)/1.2 var(--font-sans);
    letter-spacing: 0;
    text-align: center;
    text-decoration: none;
    white-space: nowrap;
    color: var(--text-primary);
    background: transparent;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    transition: background-color var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out),
                transform var(--dur-fast) var(--ease-out);
}
.btn[hidden] { display: none; }
.btn:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.btn:active:where(:not(:disabled, [aria-disabled="true"])) { transform: scale(0.98); }
.btn:disabled,
.btn[aria-disabled="true"] { opacity: 0.55; cursor: not-allowed; }
.btn[aria-busy="true"] { cursor: progress; }

/* Primário: o acento sólido com a tinta da marca (11,24:1; 13,89 no hover). */
.btn--primary { background: var(--brand); color: var(--on-brand); }
.btn--primary:hover:where(:not(:disabled, [aria-disabled="true"])) { background: var(--brand-hover); }
/* Secundário: contorno — a borda É o controlo (≥ 3:1, DS-07). */
.btn--secondary { border-color: var(--border-strong); color: var(--text-primary); }
.btn--secondary:hover:where(:not(:disabled, [aria-disabled="true"])) {
    border-color: var(--border-hover);
    background: var(--accent-soft);
    color: var(--text-bright);
}
/* Fantasma: sem caixa até ao hover — acções de baixo peso. */
.btn--ghost { color: var(--text-secondary); }
.btn--ghost:hover:where(:not(:disabled, [aria-disabled="true"])) { background: var(--accent-soft); color: var(--text-bright); }
/* Perigo: apagar, terminar, sair. Texto de erro (5,72:1 no raised). */
.btn--danger { border-color: rgba(var(--error-rgb), 0.45); color: var(--error-text); }
.btn--danger:hover:where(:not(:disabled, [aria-disabled="true"])) {
    border-color: rgba(var(--error-rgb), 0.7);
    background: rgba(var(--error-rgb), 0.12);
}

.btn--sm { --btn-h: var(--control-sm); padding: 0 var(--space-3); font-size: var(--text-xs); }
/* Grande = a acção principal de um ecrã (entrar, falar): em pílula. */
.btn--lg {
    --btn-h: var(--control-lg);
    padding: 0 var(--space-6);
    font-size: var(--text-base);
    border-radius: var(--radius-full);
}
.btn--block { width: 100%; }
/* Só ícone: quadrado, com o nome no aria-label. */
.btn--icon { width: var(--btn-h); padding: 0; }
.btn--round { border-radius: var(--radius-full); }

/* ---- Fechar --------------------------------------------------------------- */
/* Um só ×: modais, painéis, thread, toasts, lightbox, folhas do telemóvel.
   Sem display próprio a ganhar ao .mobile-only (que vem depois na folha). */
.btn-close {
    position: relative;
    flex: none;
    display: inline-grid;
    place-items: center;
    justify-content: center;   /* também centra quando o .mobile-only o põe em flex */
    width: var(--control-md);
    height: var(--control-md);
    padding: 0;
    border: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text-secondary);
    font: 400 var(--text-2xl)/1 var(--font-sans);
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    transition: background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.btn-close[hidden] { display: none; }
.btn-close:hover { background: var(--accent-soft); color: var(--text-bright); }
.btn-close:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
/* Dentro de um chip, de uma miniatura ou de um toast. */
.btn-close--sm {
    width: var(--space-6);
    height: var(--space-6);
    border-radius: var(--radius-xs);
    font-size: var(--text-lg);
}
/* Por cima de uma fotografia (lightbox, miniatura de anexo): um disco do
   abismo, para o × se ler sobre qualquer imagem. */
.btn-close--on-media {
    border-radius: var(--radius-full);
    background: rgba(var(--abyss-rgb), 0.6);
    color: var(--text-bright);
}
.btn-close--on-media:hover { background: rgba(var(--abyss-rgb), 0.85); }

/* ---- Campo ---------------------------------------------------------------- */
/* .field num controlo, ou .fields num <form> (todos os campos lá dentro). O
   :where() deixa a variante de contentor com a especificidade de uma classe. */
.field,
.fields :where(input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="range"]), select, textarea) {
    min-width: 0;
    min-height: var(--control-md);
    padding: 0 var(--space-3);
    font: 400 var(--text-sm)/1.4 var(--font-sans);
    color: var(--text-primary);
    background: var(--bg-main);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    outline: none;
    transition: border-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}
textarea.field,
.fields :where(textarea) {
    padding: var(--space-2) var(--space-3);
    line-height: 1.5;
    resize: vertical;
}
select.field,
.fields :where(select) { cursor: pointer; }
.field::placeholder,
.fields :where(input, textarea)::placeholder { color: var(--text-muted); opacity: 1; }
.field:focus,
.fields :where(input, select, textarea):focus { border-color: var(--brand); box-shadow: var(--ring-focus); }
.field:disabled,
.fields :where(input, select, textarea):disabled { opacity: 0.55; cursor: not-allowed; }
.field[aria-invalid="true"],
.fields :where([aria-invalid="true"]) { border-color: rgba(var(--error-rgb), 0.7); }
.field[aria-invalid="true"]:focus,
.fields :where([aria-invalid="true"]):focus { box-shadow: var(--ring-error); }
.field--sm,
.fields--sm :where(input:not([type="checkbox"]):not([type="radio"]), select, textarea) {
    min-height: var(--control-sm);
    padding-inline: var(--space-2);
    font-size: var(--text-xs);
}
.field--lg {
    min-height: var(--control-lg);
    padding-inline: var(--space-4);
    font-size: var(--text-input);
    border-radius: var(--radius-md);
}
/* Códigos e PINs: mono e espaçado; o placeholder volta ao texto normal. */
.field--code { font-family: var(--font-mono); letter-spacing: 0.25em; }
.field--code::placeholder { font-family: var(--font-sans); letter-spacing: normal; }
/* Ecrãs tácteis: abaixo de 16 px o iOS faz zoom ao focar (U7). A regra antiga
   era por elemento (0,0,1) e perdia para qualquer classe — nunca valia. */
@media (pointer: coarse) {
    .field,
    .fields :where(input:not([type="checkbox"]):not([type="radio"]), select, textarea) { font-size: var(--text-input); }
}

/* ---- Alvos de toque (DS-08) ----------------------------------------------- */
/* Em ecrãs tácteis cada controlo pequeno tem 44 × 44 px de área (--hit-min),
   sem mudar o desenho: um ::after invisível, centrado, que só cresce quando o
   controlo é mais pequeno (min(0, (100% − 44px) / 2) — negativo na altura ou
   na largura que falta, zero na que já chega). O enviar, anexar e ditar
   tinham 32 px; o «+» das conversas e o actualizar do Pulso 28; o × dos anexos
   20 (o único a falhar 2.5.8 AA); os × da thread, da tarefa e dos toasts 24–32.
   Onde dois vizinhos ficavam a disputar a mesma faixa, o intervalo cresce só
   em touch (o anexar ↔ ditar, as acções da tarefa, o «⋯» ↔ «+»). */
@media (pointer: coarse) {
    .btn-attach,
    .btn-mic,
    .conv-new-btn,
    .conv-rail-btn,
    .pp-refresh,
    .tp-collapse-btn,
    .tp-task-btn,
    .tp-steps-toggle,
    .ts-step-btn,
    .msg-replies-link,
    .msg-toolbar-more { position: relative; }

    .btn:not(.btn--lg)::after,
    .btn-close::after,
    .btn-attach::after,
    .btn-mic::after,
    .conv-new-btn::after,
    .conv-rail-btn::after,
    .pp-refresh::after,
    .tp-collapse-btn::after,
    .tp-task-btn::after,
    .tp-steps-toggle::after,
    .ts-step-btn::after,
    .msg-replies-link::after,
    .msg-toolbar-more::after {
        content: '';
        position: absolute;
        inset: min(0px, calc((100% - var(--hit-min)) / 2));
    }
}

/* ---- Toast ---------------------------------------------------------------- */
/* UMA pilha, por baixo do header (nunca tapa os controlos de cima nem o
   compositor de baixo): à direita no desktop, a toda a largura no telemóvel.
   O mais recente fica no topo; o static/toast.js guarda no máximo três. */
.toast-stack {
    position: fixed;
    z-index: var(--z-toast);
    top: calc(var(--safe-top, 0px) + var(--header-height) + var(--space-3));
    right: var(--space-4);
    width: min(360px, calc(100vw - 2 * var(--space-4)));
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    pointer-events: none;
}
@media (max-width: 600px) {
    .toast-stack {
        top: calc(var(--safe-top, 0px) + var(--header-height) + var(--space-2));
        left: var(--space-2);
        right: var(--space-2);
        width: auto;
    }
}
.toast {
    pointer-events: auto;
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: start;
    gap: var(--space-2) var(--space-2);
    padding: var(--space-3) var(--space-2) var(--space-3) var(--space-4);
    background: var(--surface-raised);
    color: var(--text-primary);
    border: 1px solid var(--border-strong);
    border-left: 3px solid var(--brand);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-lg);
    font: 400 var(--text-sm)/1.4 var(--font-sans);
    animation: toast-in var(--dur-base) var(--ease-out);
}
.toast.is-leaving { animation: toast-out var(--dur-base) ease-in forwards; }
.toast--success { border-left-color: var(--success); }
.toast--warning { border-left-color: var(--warning); }
.toast--error { border-left-color: var(--error); }
.toast-main {
    grid-column: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-half);
    padding: 0;
    border: 0;
    background: none;
    color: inherit;
    font: inherit;
    text-align: left;
}
button.toast-main { cursor: pointer; border-radius: var(--radius-xs); }
button.toast-main:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 4px; }
.toast-title { font-weight: 600; color: var(--text-bright); }
.toast-body {
    font-size: var(--text-xs);
    color: var(--text-secondary);
    word-break: break-word;
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.toast > .btn-close { grid-column: 2; grid-row: 1; margin-top: calc(-1 * var(--space-1)); }
.toast-actions {
    grid-column: 1 / -1;
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: var(--space-2);
    padding-right: var(--space-2);
}
@keyframes toast-in {
    from { opacity: 0; transform: translateY(-8px); }
}
@keyframes toast-out {
    to { opacity: 0; transform: translateY(-8px); }
}


/* =============================================================================
   Portão (LAND-01, U1) — as regras vivem no kit de marca: static/brand/
   brand.css (classes .gn-*, o MESMO portão nas três apps do backoffice), que o
   index.html liga antes desta folha. Aqui não se repete nenhuma regra do
   portão (tests/test_brand_gate.py); fica só o que é do Genisium.
   ============================================================================= */

/* LAND-06 — a pista de sessão (has-session no <html>, posta pelo script do
   <head> antes do primeiro paint): quem já entrou neste browser vê só o
   wordmark enquanto o /api/verify responde. O kit já esconde as portas e as
   acções até data-gn-ready; aqui saem também a linha e o rodapé, com a mesma
   rede de 3 s do kit (sem o app.js aparecem na mesma). */
html.has-session #gate-screen:not([data-gn-ready]) .gn-gate__line,
html.has-session #gate-screen:not([data-gn-ready]) .gn-gate__foot {
    visibility: hidden;
    animation: gn-gate-failsafe 0s linear var(--gn-failsafe) forwards;
}

/* U3-G — entrada automática: a linha do portão passa a dizer
   «A entrar com a Conta Genisium…» e fica à vista, mesmo com a pista de sessão —
   é o único sinal enquanto o browser redirecciona (o resto do portão só se
   revela se a entrada automática não avançar). */
html #gate-screen[data-sso-auto]:not([data-gn-ready]) .gn-gate__line {
    visibility: visible;
    animation: none;
}

/* A rede de segurança do kit revela o portão depois de 3 s. Depois de começar
   o redirect, o formulário já não é uma acção válida, mesmo numa rede lenta
   ou com movimento reduzido. pageshow remove data-sso-auto ao voltar atrás. */
html #gate-screen[data-sso-auto] .gn-gate__actions,
html #gate-screen[data-sso-auto] .gn-doors {
    animation: none;
    visibility: hidden;
    opacity: 0;
}

/* =============================================================================
   Chat Screen
   ============================================================================= */

/* Uma só regra (DS-20): havia uma segunda, mais abaixo, com height: 100% que
   ganhava fora do desktop e anulava esta intenção documentada. */
#chat-screen {
    display: flex;
    flex-direction: column;
    position: relative;
    overflow: hidden;
    padding-top: var(--safe-top);
    padding-bottom: var(--safe-bottom);
    background: var(--bg-primary);
    /* dvh segue o viewport dinâmico (teclado virtual/barras do Safari);
       100% fica como recurso para quem não conhece dvh. */
    height: 100%;
    height: 100dvh;
}

/* Header */
.chat-header {
    height: var(--header-height);
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 var(--space-4);
    background: var(--bg-glass-strong);
    backdrop-filter: blur(20px);
    -webkit-backdrop-filter: blur(20px);
    border-bottom: 1px solid var(--border);
    flex-shrink: 0;
}

.header-left {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    /* (fecho da fase 6) Pode encolher: numa DM com um nome comprido e o estado
       do colega ao lado, a 768 e a 1024 px o cabeçalho passava da largura e o
       ⋯ saía do ecrã. Quem cede é o nome (reticências); botões e marca não. */
    min-width: 0;
}
.header-left > .btn-icon { flex-shrink: 0; }

.logo-small {
    width: 32px;
    height: 32px;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    position: relative;   /* o ponto de ligação ancora ao canto do olho */
}

/* É o título da CONVERSA (o app.js reescreve-o em conv:selected), não o
   wordmark — a marca é o olho de partículas ao lado. Sem gradiente. */
.header-title {
    font: 500 var(--text-md)/1 var(--font-sans);
    color: var(--text-primary);
    letter-spacing: 0;
    max-width: 28ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.header-right {
    display: flex;
    align-items: center;
    gap: var(--space-1h);
}

/* Troca entre as três apps do backoffice (U1): Assistente · Inteligência ·
   Operações, a partir do kit (static/brand/apps.js, desenhada pelo
   static/brand-boot.js). Fila de pastilhas e não dropdown: com três destinos
   um menu custa dois cliques e esconde onde estás. Vazia (o módulo não
   correu), não aparece. */
.app-switcher {
    display: flex;
    gap: var(--space-half);
    padding: var(--space-half);
    background: var(--bg-primary);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    flex-shrink: 0;
}

.app-switcher:empty { display: none; }

.app-switcher-item {
    font-family: var(--font-mono);
    font-size: var(--text-3xs);
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    /* 5 → 6 em cima/baixo: empate decidido pelo alvo de toque (DS-08). */
    padding: var(--space-1h) var(--space-2);
    border-radius: calc(var(--radius-sm) - 3px);
    color: var(--text-secondary);
    text-decoration: none;
    white-space: nowrap;
    transition: background var(--dur-base) var(--ease-out), color var(--dur-base) var(--ease-out);
}

.app-switcher-item:hover {
    background: var(--accent-soft);
    color: var(--text-bright);
}

.app-switcher-item:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}

/* Onde estás: fundo do acento + fio no topo, o mesmo vocabulário que o resto
   do painel usa para "activo". */
.app-switcher-item.is-current {
    background: var(--accent-soft);
    color: var(--text-bright);
    font-weight: 600;
    box-shadow: inset 0 1px 0 var(--accent);
}

/* Escondida a ≤767px. A medida original (header-right com oito botões) já não
   vale — desde o menu ⋯ o header-right tem o Chat|Voz, as aprovações, o lápis
   e o ⋯ — mas a 375 px a barra (≈200 px) continua a não caber ao lado deles
   e do título. IA-18: abaixo disto as mesmas apps estão no topo do menu ⋯
   (.header-menu-apps), em vez de desaparecerem sem alternativa. */
@media (max-width: 767px) {
    .app-switcher { display: none; }
}
.header-menu-apps { display: none; }
@media (max-width: 767px) {
    .header-menu-apps { display: block; }
}
/* U1: os itens vêm do static/brand-boot.js (o kit). Sem o módulo (rede,
   browser antigo) o grupo ficava só com o rótulo «Aplicações» — como o
   .app-switcher:empty, não aparece. */
.header-menu-apps:not(:has(a)) { display: none; }
a.header-menu-item { text-decoration: none; }
.header-menu-sub { font-size: var(--text-2xs); color: var(--text-muted); }

/* =============================================================================
   Aprovações (IA-02) — indicador no header e gaveta
   O indicador só existe com pendentes: um número âmbar no escudo. É a única
   coisa do header que pede uma decisão, por isso não é verde.
   ============================================================================= */
.approvals-btn { position: relative; color: var(--warning); }
.approvals-btn[hidden] { display: none; }
.approvals-badge {
    position: absolute;
    top: 3px;
    right: 2px;
    min-width: 16px;
    height: 16px;
    padding: 0 var(--space-1);
    border-radius: var(--radius-full);
    background: var(--warning);
    color: var(--on-brand);
    font: 500 var(--text-3xs)/16px var(--font-mono);
    text-align: center;
    pointer-events: none;
}
.approvals-menu-count {
    margin-left: auto;
    min-width: 18px;
    padding: 0 var(--space-1h);
    border-radius: var(--radius-full);
    background: var(--warning);
    color: var(--on-brand);
    font: 500 var(--text-2xs)/18px var(--font-mono);
    text-align: center;
}
.approvals-menu-count[hidden] { display: none; }

/* #id: o .modal-card (centrado, 90%/380px) vem depois na folha. */
#approvals-modal .approvals-card { text-align: left; max-width: 720px; width: min(94vw, 720px); padding: var(--space-5) var(--space-6); }
.approvals-all {
    display: flex;
    align-items: center;
    gap: var(--space-1h);
    margin-left: auto;
    white-space: nowrap;
    font-size: var(--text-xs);
    color: var(--text-secondary);
    cursor: pointer;
}
.approvals-all[hidden] { display: none; }
.approvals-intro { font-size: var(--text-sm); color: var(--text-secondary); margin: 0; }
.approvals-list { min-height: 120px; }
.approvals-recent { min-height: 0; flex: 0 1 auto; max-height: 32vh; }
.approvals-subtitle {
    margin: var(--space-1) 0 0;
    font: 500 var(--text-2xs)/1.4 var(--font-mono);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--text-muted);
}
.approvals-subtitle[hidden] { display: none; }

.ap-item { display: flex; flex-direction: column; gap: var(--space-1h); }
.ap-item--critical { border-color: rgba(var(--error-rgb), 0.55); }
.ap-item--hl { box-shadow: 0 0 0 2px var(--warning) inset; }
.ap-item--done { opacity: 0.85; }
.ap-label {
    font: 500 var(--text-2xs)/1.3 var(--font-mono);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--warning);
}
.ap-summary { font-size: var(--text-md); font-weight: 600; color: var(--text-bright); line-height: 1.4; }
.ap-preview { list-style: none; margin: 0; padding: 0; font-size: var(--text-sm); color: var(--text-primary); }
.ap-preview li { padding: var(--space-half) 0; font-variant-numeric: tabular-nums; }
.ap-preview li::before { content: '· '; color: var(--text-muted); }
.ap-meta { display: flex; flex-wrap: wrap; gap: var(--space-1h) var(--space-2); font: 500 var(--text-2xs)/1.4 var(--font-mono); color: var(--text-secondary); }
.ap-code { color: var(--warning); }
.ap-risk--critical { color: var(--error-text); }
.ap-rev--irreversible { color: var(--error-text); font-weight: 600; }
.ap-rev--reversible { color: var(--text-secondary); }
.ap-orphan { color: var(--warning); }
.ap-status {
    font: 500 var(--text-2xs)/1.3 var(--font-mono);
    text-transform: uppercase;
    letter-spacing: var(--tracking-label);
    color: var(--text-muted);
}
.ap-item--completed .ap-status { color: var(--success); }
.ap-item--failed .ap-status { color: var(--error-text); }
.ap-result { font-size: var(--text-xs); color: var(--text-secondary); white-space: pre-wrap; word-break: break-word; }
.ap-actions[hidden], .ap-reject-form[hidden], .ap-outcome[hidden] { display: none; }
.ap-approve.is-armed { background: var(--warning); color: var(--on-brand); }
.ap-reject-form { display: flex; gap: var(--space-2); flex-wrap: wrap; justify-content: flex-end; }
.ap-reason { flex: 1 1 220px; }
.ap-outcome { font-size: var(--text-sm); padding: var(--space-1h) 0 0; color: var(--text-primary); }
.ap-outcome--completed, .ap-outcome--ok { color: var(--success); }
.ap-outcome--error, .ap-outcome--failed { color: var(--error-text); }

/* Telemóvel: a gaveta é uma folha que sobe do fundo, a toda a largura. */
@media (max-width: 600px) {
    #approvals-modal { align-items: flex-end; }
    #approvals-modal .approvals-card {
        width: 100%;
        max-width: none;
        max-height: 88vh;
        border-radius: var(--radius-md) var(--radius-md) 0 0;
        padding: var(--space-4) var(--space-4) calc(var(--space-4) + var(--safe-bottom, 0px));
    }
    .ap-actions { justify-content: stretch; }
    .ap-actions .btn { flex: 1; --btn-h: var(--hit-min); }
}

/* Logout: hover destrutivo (é o último item do menu) */
#logout-btn:hover {
    background: rgba(var(--error-rgb), 0.12);
    color: var(--error-text);
}

/* O pulso da marca: um ponto de 7 px no canto do olho, com um aro da cor do
   header para não se colar aos pontos da criatura. */
.status-dot {
    position: absolute;
    right: -1px;
    bottom: -1px;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--text-muted);
    border: 2px solid var(--surface-header);
    box-sizing: content-box;
    transition: all var(--dur-slow) var(--ease-out);
}

.status-dot.connected {
    background: var(--success);
    box-shadow: 0 0 8px rgba(var(--success-rgb), 0.4);
    animation: statusPulse 3s ease-in-out infinite;
}

@keyframes statusPulse {
    0%, 100% { box-shadow: 0 0 4px rgba(var(--success-rgb), 0.3); }
    50% { box-shadow: 0 0 12px rgba(var(--success-rgb), 0.6); }
}

.status-dot.connecting {
    background: var(--warning);
    animation: pulse 1.2s ease-in-out infinite;
}

@keyframes pulse {
    /* semantic warning (amber) — matches --warning background of .status-dot.connecting */
    0%, 100% { opacity: 1; box-shadow: 0 0 4px rgba(var(--warning-rgb), 0.25); }
    50% { opacity: 0.4; box-shadow: 0 0 12px rgba(var(--warning-rgb), 0.45); }
}

.btn-icon {
    width: 38px;
    height: 38px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: none;
    border-radius: var(--radius-sm);
    color: var(--text-secondary);
    cursor: pointer;
    transition: all var(--dur-base) var(--ease-out);
}

.btn-icon:hover {
    background: var(--accent-soft);
    color: var(--text-bright);
}

.btn-icon:active {
    transform: scale(0.92);
}

.btn-icon.active {
    color: var(--accent);
    background: var(--accent-soft);
    box-shadow: var(--ring-active);
}

.btn-icon.disabled {
    opacity: 0.35;
    cursor: not-allowed;
}

.btn-icon.disabled:hover {
    background: transparent;
    color: var(--text-secondary);
}

/* Menu ⋯ do header: o que não é diário (skills, cérebro, Telegram, sair) */
.header-menu { position: relative; }
#header-menu-btn[aria-expanded="true"] {
    color: var(--accent);
    background: var(--accent-soft);
    box-shadow: var(--ring-active);
}
.header-menu-list {
    position: absolute;
    top: calc(100% + 6px);
    right: 0;
    min-width: 232px;
    padding: var(--space-1h);
    background: var(--surface-raised);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-lg);
    z-index: calc(var(--z-chat-header) + 1);
}
.header-menu-list[hidden] { display: none; }
.header-menu-item {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    width: 100%;
    padding: var(--space-2) var(--space-2);
    font: 500 var(--text-sm)/1.2 var(--font-sans);
    color: var(--text-primary);
    background: transparent;
    border: 0;
    border-radius: var(--radius-xs);
    text-align: left;
    cursor: pointer;
    white-space: nowrap;
}
.header-menu-item:hover,
.header-menu-item:focus-visible {
    background: var(--accent-soft);
    color: var(--text-bright);
    outline: none;
}
.header-menu-item svg { flex: none; color: var(--text-secondary); }
.header-menu-item:hover svg { color: currentColor; }
.header-menu-sep {
    border: 0;
    border-top: 1px solid var(--border);
    margin: var(--space-1h) var(--space-1);
}

/* IA-08 — o menu diz quem está ligado, arruma-se por grupos e mostra o que
   está à espera (candidatos «Para rever», saúde com erros) no próprio ⋯. */
#header-menu-btn { position: relative; }
.header-menu-badge {
    position: absolute;
    top: 3px;
    right: 3px;
    min-width: 16px;
    height: 16px;
    padding: 0 var(--space-1);
    border-radius: var(--radius-full);
    background: var(--accent);
    color: var(--surface-canvas);
    font: 700 var(--text-3xs)/16px var(--font-sans);
    text-align: center;
    pointer-events: none;
}
.header-menu-badge[hidden] { display: none; }
.header-menu-badge--alert { background: var(--error); color: var(--text-bright); }
.header-menu-list { max-height: calc(100dvh - var(--header-height) - 24px); overflow-y: auto; }
.header-menu-identity {
    display: flex;
    flex-direction: column;
    gap: var(--space-half);
    padding: var(--space-2) var(--space-2) var(--space-2);
    border-bottom: 1px solid var(--border);
    margin-bottom: var(--space-1);
}
.hmi-name { font: 600 var(--text-sm)/1.3 var(--font-sans); color: var(--text-bright); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hmi-role { font: 500 var(--text-2xs)/1.2 var(--font-sans); color: var(--text-muted); letter-spacing: 0.04em; }
.header-menu-group[hidden] { display: none; }
.header-menu-label {
    display: block;
    padding: var(--space-2) var(--space-2) var(--space-half);
    font: 600 var(--text-3xs)/1.2 var(--font-sans);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--text-muted);
}
.header-menu-item[hidden] { display: none; }
.header-menu-item > span:first-of-type { flex: 1; min-width: 0; }
/* DS-08 — em touch cada item do menu ⋯ (incluindo «Esta conversa», F6-G1)
   tem 44 px de altura: com o padding de base ficava nos 34 px, e os itens
   estão encostados uns aos outros (um ::after a crescer pisava o vizinho). */
@media (pointer: coarse) {
    .header-menu-item { min-height: var(--hit-min); }
}
.header-menu-count {
    min-width: 18px;
    height: 18px;
    padding: 0 var(--space-1h);
    border-radius: var(--radius-full);
    background: var(--accent);
    color: var(--surface-canvas);
    font: 700 var(--text-3xs)/18px var(--font-sans);
    text-align: center;
}
.header-menu-hint { font-size: var(--text-2xs); color: var(--warning); }
.header-menu-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--error);
}
.header-menu-count[hidden],
.header-menu-hint[hidden],
.header-menu-dot[hidden] { display: none; }

/* Messages Container */
.messages-container {
    flex: 1;
    overflow-y: auto;
    overflow-x: hidden;
    /* 16px em cima e não 8: o feed passa por baixo da .chat-header, que é de
       vidro (backdrop-filter), e 8px deixava a primeira bolha colada a ela.
       Antes isto não se via porque a criatura reservava 104px. */
    padding: var(--space-4) var(--space-3) var(--space-2);
    scroll-behavior: smooth;
}

.messages {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    min-height: 100%;
    padding-bottom: var(--space-2);
}

/* Phase 1.1 — wrapper de cada mensagem; permite separator de data acima e
 * mantém a 1:1 alinhamento com state.messages no diff renderer. */
.msg-stream-item {
    display: flex;
    flex-direction: column;
    width: 100%;
}

/* Phase 1.2 — msg-row contém avatar + bubble. Avatar à esquerda em peer/bot;
 * self não tem avatar e a bubble alinha à direita. */
.msg-row {
    display: flex;
    align-items: flex-end;
    gap: var(--space-2);
    width: 100%;
}
.msg-stream-item.is-self .msg-row {
    justify-content: flex-end;
}
.msg-stream-item.is-peer .msg-row {
    justify-content: flex-start;
}

/* Avatar circular (32x32) com iniciais, num dos seis tons --avatar-* (DS-06);
   o bot é o anel da marca (.msg-avatar--bot). */
.msg-avatar {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    font-weight: 600;
    font-size: var(--text-2xs);
    letter-spacing: 0.3px;
    user-select: none;
    align-self: flex-start;  /* topo da linha quando bubble é multi-linha */
    margin-bottom: var(--space-half);
}
/* Bot: um anel de 24 px com o ponto da marca ao centro, desenhado no mesmo
   quadrado de 32 px (a coluna não mexe). Sem halo — multiplicava-se por
   cada resposta no histórico. */
.msg-avatar--bot {
    background:
        radial-gradient(circle at center,
            var(--accent) 0 3px,
            transparent 3.5px 9.5px,
            rgba(var(--accent-rgb), 0.55) 10px 11px,
            transparent 11.5px);
    color: transparent;
    font-size: 0;
}
/* Placeholder mantém o mesmo width para indentação consistente em mensagens
 * agrupadas, mas é invisível. */
.msg-avatar--placeholder {
    visibility: hidden;
}

/* Mensagens agrupadas: sender label oculta + avatar invisível (mas com space).
 * Margem reduzida para colar mais perto da anterior. */
.msg-stream-item.is-grouped .msg-sender {
    display: none;
}
.msg-stream-item.is-grouped .msg-row {
    margin-top: 0;
}
.msg-stream-item.is-grouped .message {
    margin-top: var(--space-half);
}

/* Date separator (Hoje / Ontem / sexta-feira / 12 abril) */
.msg-date-sep {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    margin: var(--space-4) 0 var(--space-2);
    color: var(--text-muted);
    font-size: var(--text-2xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    user-select: none;
}
.msg-date-sep::before,
.msg-date-sep::after {
    content: '';
    flex: 1;
    height: 1px;
    background: var(--border);
}
.msg-date-sep span {
    background: var(--bg-primary);
    padding: 0 var(--space-1);
    text-transform: capitalize; /* "sexta-feira" capitalize */
}
/* Primeiro separator do stream — sem margem grande no topo */
.msg-stream-item:first-child .msg-date-sep {
    margin-top: var(--space-1);
}

/* Message Bubble — Teams-style compact */
.message {
    max-width: 82%;
    padding: var(--space-2) var(--space-4);
    border-radius: var(--radius-md);
    word-wrap: break-word;
    position: relative;
    line-height: 1.5;
}

/* CHAT-10 — a entrada anima só mensagens NOVAS (.is-new, posta pelo
   renderMessages ao acrescentar). Antes a animação era do .message: cada nó
   substituído (um chunk, uma reacção) voltava a piscar de opacidade 0. */
.msg-stream-item.is-new .message {
    opacity: 0;
    transform: translateY(8px);
    animation: msgIn var(--dur-base) var(--ease-out) forwards;
}

/* Bubbles consecutivas do mesmo emissor — agrupar sem espaço.
 * Aplicado no nível .msg-stream-item para sobreviver ao wrap. */
.msg-stream-item + .msg-stream-item .message {
    margin-top: var(--space-half);
}

@keyframes msgIn {
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

/* Tu envias notas: pílula compacta, 60ch no máximo, sem sombra. */
.message.user {
    align-self: flex-end;
    background: var(--bg-message-user);
    color: var(--text-bright);
    border: 1px solid rgba(var(--accent-rgb), 0.28);
    border-radius: var(--radius-lg) var(--radius-lg) var(--radius-xs) var(--radius-lg);
    padding: var(--space-2) var(--space-4);
    max-width: min(60ch, 90%);
    box-shadow: none;
}
.message.user .message-content {
    font-size: var(--text-md);
    line-height: 1.5;
}

/* O assistente escreve documentos: sem caixa, sem borda, a coluna de leitura
   é a própria página. A autoria vem do anel do avatar e da byline. */
.message.assistant {
    align-self: flex-start;
    background: transparent;
    border: 0;
    border-radius: 0;
    padding: var(--space-half) 0 0;
    box-shadow: none;
}
.message.assistant .message-meta {
    justify-content: flex-start;   /* byline à esquerda, como o texto */
}

/* =============================================================================
 * Bubble distinction — self / peer / bot
 *
 * Problema: em DMs e grupos com 2+ members + bot, todas as bubbles do tipo
 * "user" ficavam visualmente idênticas, tornando impossível distinguir o que
 * disse o próprio user vs o que disse o outro member. Resolvido cruzando
 * state.user.member_id com msg.sender_member_id.
 *
 * - bubble-self: a pílula translúcida do .message.user (à direita)
 * - bubble-peer: superfície raised + borda, à esquerda — o avatar colorido
 *                com as iniciais já diz quem é
 * - bubble-bot:  documento sem caixa (.message.assistant) + anel do avatar
 * ============================================================================= */

/* Self: já é a pílula do .message.user — sem override necessário */

/* Peer (outro membro): neutro. O gradiente verde saiu — competia com a
   pílula do self e com a marca; a autoria vem do avatar e da byline. */
.message.user.bubble-peer {
    align-self: flex-start;
    background: var(--surface-raised);
    color: var(--text-primary);
    border: 1px solid var(--border);
    border-radius: var(--radius-xs) var(--radius-lg) var(--radius-lg) var(--radius-lg);
    box-shadow: none;
}

/* Sender label (nome do peer) — Phase 1.2: visível na primeira mensagem de
 * uma sequência peer/bot, oculta nas mensagens agrupadas. */
.msg-sender {
    font: 500 var(--text-3xs)/1.2 var(--font-mono);
    text-transform: uppercase;
    letter-spacing: var(--tracking-label);
    color: var(--text-secondary);
    margin-bottom: var(--space-1);
}

/* Message Content — Markdown */
/* 15px/1.6 e não 14px/1.55. Para "ok" e "feito" tanto faz; para uma resposta
   de quinze linhas sobre stock e margens — que é o uso real desta app — 14px
   apertado é cansativo. O custo é ~2,3px por linha, e há espaço de sobra
   depois de a criatura ter libertado os 104px do topo. */
.message-content {
    font-size: var(--text-base);
    line-height: 1.6;
}

/* 8px e não 3: com 3px os parágrafos colavam-se e uma resposta longa lia-se
   como uma parede de texto. É a mudança que mais se nota em respostas grandes. */
.message-content p {
    margin-bottom: var(--space-2);
}

.message-content p:last-child {
    margin-bottom: 0;
}

.message-content code {
    background: var(--accent-soft);
    padding: var(--space-half) var(--space-1h);
    border-radius: var(--radius-xs);
    font-family: var(--font-mono);
    font-size: 0.9em;
    color: var(--accent-hover);
}

.message.user .message-content code {
    background: rgba(var(--accent-rgb), 0.14);
    color: var(--accent-hover);
}

.message-content pre {
    background: var(--bg-primary);
    padding: var(--space-4) var(--space-4);
    border-radius: var(--radius-sm);
    overflow-x: auto;
    margin: var(--space-2) 0;
    border: 1px solid var(--border);
}

.message-content pre code {
    background: none;
    padding: 0;
    /* tinta clara sobre o pre escuro (#050A05) — casa com o tema hljs
       github-dark-dimmed carregado no head; era texto escuro invisível */
    color: var(--text-secondary);
    font-size: var(--text-sm);
    line-height: 1.55;
}

.message-content ul, .message-content ol {
    padding-left: var(--space-6);
    margin: var(--space-1h) 0;
}

.message-content li {
    margin-bottom: var(--space-1);
}

.message-content strong {
    color: var(--text-bright);
    font-weight: 600;
}

.message.user .message-content strong {
    color: var(--text-bright);
}

/* Ritmo de secções numa resposta longa: h1 é o título do documento, cada h2
   abre uma secção com uma régua por cima, h3 é um sub-título. */
.message-content h1, .message-content h2, .message-content h3 {
    color: var(--text-bright);
    margin: var(--space-3) 0 var(--space-1h);
    font-weight: 600;
    line-height: 1.25;
}

.message-content h1 {
    font-family: var(--font-display);
    font-size: var(--text-xl);
    letter-spacing: var(--tracking-display);
    text-wrap: balance;
}
.message-content h2 {
    font-size: var(--text-lg);
    border-top: 1px solid var(--border);
    padding-top: var(--space-3);
    margin-top: var(--space-4);
}
.message-content h2:first-child {
    border-top: 0;
    padding-top: 0;
    margin-top: 0;
}
.message-content h3 { font-size: var(--text-base); }

.message-content blockquote {
    border-left: 2px solid var(--accent);
    padding-left: var(--space-3);
    margin: var(--space-2) 0;
    color: var(--text-secondary);
    font-style: normal;
}

/* As tabelas de vendas leem-se como um relatório de terminal: números
   tabulares, cabeçalhos em eyebrow mono. */
.message-content table {
    border-collapse: collapse;
    width: 100%;
    margin: var(--space-2) 0;
    font-size: var(--text-sm);
    font-variant-numeric: tabular-nums;
}

/* VIS-07 — o formatMessage() põe cada tabela num .table-scroll: no telemóvel
   as colunas que não cabem ficam a um deslize de distância, em vez de
   cortadas pelo overflow-x:hidden do #messages-container. */
.message-content .table-scroll {
    max-width: 100%;
    overflow-x: auto;
    margin: var(--space-2) 0;
    -webkit-overflow-scrolling: touch;
}
.message-content .table-scroll > table { margin: 0; }
.message-content .table-scroll:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.message-content th, .message-content td {
    border: 1px solid var(--border);
    padding: var(--space-1h) var(--space-2);
    text-align: left;
}
/* Colunas `---:` do markdown: o marked emite align="right", mas o
   text-align:left de cima (CSS de autor) ganhava ao atributo. Valores
   alinhados à direita e sem partir "1 240,50" do "€". */
.message-content :is(th, td)[align="right"] {
    text-align: right;
    white-space: nowrap;
}
.message-content :is(th, td)[align="center"] { text-align: center; }
/* Sem `---:` (o caso normal), as células só com números/moeda também não
   partem: o _wrapTables() marca-as com .cell-num. O contentor faz o scroll. */
.message-content td.cell-num { white-space: nowrap; }
@media (max-width: 600px) {
    .message-content table { font-size: var(--text-xs); }
    .message-content th, .message-content td { padding: var(--space-1) var(--space-2); }
}

.message-content th {
    background: rgba(var(--brand-rgb), 0.08);
    font: 500 var(--text-3xs)/1.4 var(--font-mono);
    text-transform: uppercase;
    letter-spacing: var(--tracking-label);
    color: var(--text-bright);
}

.message-content a {
    color: var(--accent-hover);
    /* DS-06 (WCAG 1.4.1): a 1,0x:1 do texto à volta, só a matiz distinguia um
       link de uma encomenda do resto da resposta. Sublinhado sempre. */
    text-decoration: underline;
    text-underline-offset: 2px;
    text-decoration-thickness: 1px;
}

.message-content a:hover {
    text-decoration-thickness: 2px;
}

.message-meta {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--space-1);
    margin-top: var(--space-half);
    min-height: 0;
    height: 16px;
    position: relative;
}

.message-time {
    font: 400 var(--text-2xs)/1 var(--font-mono);
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}

.message.user .message-time {
    color: var(--text-muted);
}

/* F5-E4 — mensagem do grupo gravada no Genisium que o grupo Telegram ligado
   não recebeu (antes perdia-se com um 502). */
.message-tg-failed {
    font: 500 var(--text-2xs)/1 var(--font-sans);
    color: var(--warning);
    white-space: nowrap;
}

/* F6-G4 — «editada» ao lado da hora; «Mensagem apagada» no sítio do texto
   (a bolha fica, sem anexos nem reacções, e a thread continua). */
.message-edited {
    font: 400 var(--text-2xs)/1 var(--font-sans);
    color: var(--text-muted);
    font-style: italic;
}
.msg-deleted {
    margin: 0;
    font-size: var(--text-sm);
    font-style: italic;
    color: var(--text-muted);
}
.message.message--deleted {
    background: transparent;
    border: 1px dashed var(--border);
    box-shadow: none;
}

/* F6-G4 — editar no sítio: a caixa ocupa a largura da bolha; Guardar/Cancelar
   à direita; a dica (Enter/Esc) por baixo. */
.msg-edit {
    display: flex;
    flex-direction: column;
    gap: var(--space-1h);
    /* a bolha cresce até ao máximo dela enquanto se edita */
    width: 60ch;
    max-width: 100%;
}
.msg-edit-input {
    width: 100%;
    min-height: 64px;
    resize: vertical;
}
.msg-edit-actions {
    display: flex;
    justify-content: flex-end;
    gap: var(--space-2);
}
.msg-edit-hint {
    margin: 0;
    font-size: var(--text-2xs);
    color: var(--text-muted);
}
.mt-btn.msg-delete-btn.is-armed {
    width: auto;
    padding: 0 var(--space-2);
    color: var(--error-text);
    background: rgba(var(--error-rgb), 0.15);
    font: 600 var(--text-2xs)/1 var(--font-sans);
}

/* F5 — «N respostas» por baixo da mensagem, sempre à vista (no telemóvel a
   contagem só existia no botão da barra, escondido no «⋯»). Abre a thread;
   em touch o alvo de 44 px vem do DS-08. */
.msg-replies-link {
    display: flex;
    width: fit-content;
    align-items: center;
    gap: var(--space-1);
    margin-top: var(--space-1);
    padding: 0;
    background: transparent;
    border: 0;
    color: var(--accent);
    font: 500 var(--text-xs)/1.4 var(--font-sans);
    cursor: pointer;
}
.msg-replies-link:hover { text-decoration: underline; }
.msg-replies-link:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: var(--radius-xs);
}

/* =============================================================================
   Toolbar flutuante de acções — revelada em hover E focus-within (corrige o
   bug de touch/teclado dos botões opacity:0). Em touch colapsa num "⋯".
   O meta row fica só com estado (tempo/receipts); acções efémeras vivem aqui.
   ============================================================================= */
.msg-toolbar {
    position: absolute;
    top: -14px;
    right: 8px;
    z-index: var(--z-float);
    display: flex;
    align-items: center;
    gap: var(--space-half);
    padding: var(--space-half);
    background: var(--bg-primary);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-sm);
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--dur-fast) ease;
}
.message:hover .msg-toolbar,
.message:focus-within .msg-toolbar,
.msg-toolbar:has(.emoji-picker:not([hidden])) {
    opacity: 1;
    pointer-events: auto;
}

.msg-toolbar-actions {
    display: inline-flex;
    align-items: center;
    gap: var(--space-half);
}

.mt-btn {
    width: 28px;
    height: 28px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-1);
    padding: 0;
    background: transparent;
    border: none;
    border-radius: var(--radius-sm);
    color: var(--text-muted);
    font-size: var(--text-sm);
    cursor: pointer;
    transition: background var(--dur-fast), color var(--dur-fast);
}
.mt-btn:hover {
    background: var(--accent-soft);
    color: var(--accent);
}
.mt-btn:focus-visible,
.code-copy-btn:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}
.mt-btn.copied {
    color: var(--accent);
}

/* Bloco de código com "Copiar" no canto (CHAT-09). O wrapper leva a margem do
   <pre> e o posicionamento: dentro do <pre> o botão rolava com o scroll. */
.message-content .code-block {
    position: relative;
    margin: var(--space-2) 0;
}
.message-content .code-block > pre {
    margin: 0;
}
.code-copy-btn {
    position: absolute;
    top: 6px;
    right: 6px;
    padding: var(--space-half) var(--space-2);
    font: 500 var(--text-2xs)/1.6 var(--font-mono);
    color: var(--text-secondary);
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-xs);
    cursor: pointer;
    opacity: 0;
    transition: opacity var(--dur-fast) ease;
}
.code-block:hover .code-copy-btn,
.code-copy-btn:focus-visible,
.code-copy-btn.copied {
    opacity: 1;
}

/* O "⋯" só existe em touch */
.msg-toolbar-more { display: none; }

@media (hover: none) {
    .msg-toolbar {
        opacity: 1;
        pointer-events: auto;
    }
    .code-copy-btn { opacity: 1; }
    .msg-toolbar-more { display: inline-flex; }
    .msg-toolbar:not(.expanded) .msg-toolbar-actions { display: none; }
    /* F6-G4 — aberta em touch, nas bolhas das pessoas (as tuas têm Editar e
       Apagar) cada acção é um alvo de 44 px: eram 28 px a 2 px uns dos outros.
       A barra abre por cima da bolha, alinhada à direita, e cabe a 375 px. A
       do Genisium (7 acções, abre para a direita sobre a hora) fica como era. */
    .message.user .msg-toolbar.expanded .msg-toolbar-actions .mt-btn {
        min-width: var(--hit-min);
        height: var(--hit-min);
    }

    /* F5-E4 — na bolha do próprio (à direita) o «⋯» ficava no canto de cima,
       por cima do texto. Sai para fora dela, à esquerda e ao fundo, num alvo de
       44 × 44 px; a bolha deixa esse espaço livre. Aberta, a barra sobe para
       cima da bolha (alinhada à direita) em vez de crescer sobre o texto. */
    .message.user { max-width: min(60ch, calc(100% - var(--hit-min) - var(--space-1))); }
    .message.user .msg-toolbar:not(.expanded) {
        top: auto;
        bottom: 0;
        right: calc(100% + var(--space-1));
        padding: 0;
        background: transparent;
        border-color: transparent;
        box-shadow: none;
    }
    .message.user .msg-toolbar:not(.expanded) .msg-toolbar-more {
        width: var(--hit-min);
        height: var(--hit-min);
        font-size: var(--text-lg);
    }
    .message.user .msg-toolbar.expanded {
        top: auto;
        right: 0;
        bottom: calc(100% + var(--space-1));
    }
    /* F5 — nas respostas do Genisium o «⋯» ficava no canto de cima e, numa
       resposta curta, caía em cima da byline («GEN⋯M»). Vai para a coluna do
       anel, ao fundo, na linha da hora, como o dos colegas (à direita, na linha
       da hora, ficava por baixo do botão flutuante das tarefas). Aberta, a
       barra abre onde se tocou, a crescer para a direita por cima da hora:
       acima da resposta, numa resposta mais alta do que o ecrã, ia parar fora
       da vista. */
    .message.assistant .msg-toolbar:not(.expanded) {
        top: auto;
        /* o centro do alvo de 44 px na linha da hora (a resposta não tem caixa
           nem padding por baixo, ao contrário das bolhas) */
        bottom: calc(-1 * var(--space-3));
        right: calc(100% + var(--space-1));
        padding: 0;
        background: transparent;
        border-color: transparent;
        box-shadow: none;
    }
    .message.assistant .msg-toolbar:not(.expanded) .msg-toolbar-more {
        width: var(--hit-min);
        height: var(--hit-min);
        font-size: var(--text-lg);
    }
    .message.assistant .msg-toolbar.expanded {
        top: auto;
        right: auto;
        /* o «⋯» fica onde estava (a coluna do anel) e a barra tapa só a hora,
           não a última linha da resposta */
        left: calc(-1 * (var(--hit-min) + var(--space-1)));
        bottom: calc(-1 * var(--space-3));
    }
}

.btn-speak.speaking {
    opacity: 1;
    color: var(--accent);
    animation: speakPulse 1.2s ease-in-out infinite;
}

@keyframes speakPulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.5; }
}

/* Input Container */
.input-container {
    padding: var(--space-3) var(--space-4) var(--space-4);
    background: var(--bg-glass-strong);
    backdrop-filter: blur(20px);
    -webkit-backdrop-filter: blur(20px);
    border-top: 1px solid var(--border);
    flex-shrink: 0;
}

/* Compositor: uma caixa, três ícones sem moldura, um botão de enviar sólido.
   Os anéis de 40 px à volta do clipe e do micro competiam com o campo. */
.input-wrapper {
    display: flex;
    align-items: flex-end;
    gap: var(--space-1h);
    background: var(--surface-raised);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-md);
    padding: var(--space-1h) var(--space-1h) var(--space-1h) var(--space-2);
    transition: border-color var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out);
}

.input-wrapper:focus-within {
    border-color: var(--accent);
    box-shadow: var(--ring-focus);
}

#message-input {
    flex: 1;
    min-width: 0;
    background: transparent;
    border: none;
    color: var(--text-primary);
    font: 400 var(--text-base)/1.5 var(--font-sans);
    resize: none;
    outline: none;
    /* Uma linha + padding: o auto-resize nunca a deixa abaixo disto (VIS-06) */
    min-height: calc(1.5em + 12px);
    max-height: 160px;
    padding: var(--space-1h) var(--space-1);
}

#message-input::placeholder {
    color: var(--text-muted);
    /* F5 — «Mensagem para Operações Eupoupo…» numa linha só, com reticências:
       partido em duas, a caixa vazia crescia para duas linhas no telemóvel.
       O Chromium só desenha as reticências com o text-overflow também na
       caixa (só enquanto está vazia: o texto escrito parte linha como sempre). */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
#message-input:placeholder-shown { text-overflow: ellipsis; }

/* Enviar: .btn.btn--primary.btn--sm.btn--icon.btn--round (DS-12) — o
   disco sólido do acento. Sem nada para enviar fica só o ícone, apagado. */
.btn-send { flex-shrink: 0; }
.btn-send:disabled {
    background: transparent;
    color: var(--text-muted);
    opacity: 0.5;
}

/* Attach Button */
.btn-attach {
    width: 32px;
    height: 32px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: 0;
    border-radius: var(--radius-full);
    color: var(--text-muted);
    cursor: pointer;
    transition: color var(--dur-base) var(--ease-out), background var(--dur-base) var(--ease-out);
    flex-shrink: 0;
}
.btn-attach:hover {
    color: var(--accent);
    background: var(--accent-soft);
}

/* Dica de teclado — só faz sentido com teclado físico */
.composer-hint {
    margin: var(--space-1h) var(--space-1) 0;
    text-align: right;
    font: 400 var(--text-2xs)/1 var(--font-mono);
    letter-spacing: 0.02em;
    color: var(--text-muted);
    user-select: none;
}
@media (pointer: coarse) {
    .composer-hint { display: none; }
}

/* CHAT-19 — a editar a última pergunta: uma faixa por cima da caixa. */
.composer-edit {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
    margin: 0 var(--space-1) var(--space-1h);
    padding: var(--space-1h) var(--space-2);
    border-left: 2px solid var(--accent);
    background: var(--accent-soft);
    border-radius: var(--radius-xs);
    font-size: var(--text-xs);
    color: var(--text-primary);
}
.composer-edit[hidden] { display: none; }
.composer-edit-cancel {
    flex: none;
    padding: var(--space-half) var(--space-2);
    border: 1px solid var(--border);
    border-radius: var(--radius-xs);
    background: transparent;
    color: var(--text-secondary);
    font: 500 var(--text-2xs)/1.4 var(--font-sans);
    cursor: pointer;
}
.composer-edit-cancel:hover { color: var(--accent); border-color: var(--accent); }
.composer-edit-cancel:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* Attach Preview */
.attach-preview {
    display: flex;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-4);
    overflow-x: auto;
    border-top: 1px solid var(--border);
    background: var(--bg-elevated);
}
.attach-thumb {
    position: relative;
    flex-shrink: 0;
    border-radius: var(--radius-sm);
    /* Sem overflow:hidden (DS-08): cortava a área de toque do × ao tamanho
       da miniatura. O arredondado passa para a imagem e para a legenda. */
    border: 1px solid var(--border);
    background: var(--bg-main);
}
.attach-thumb img {
    width: 80px;
    height: 80px;
    object-fit: cover;
    display: block;
    border-radius: calc(var(--radius-sm) - 1px);
}
.attach-thumb.attach-file {
    padding: var(--space-3) var(--space-4);
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-size: var(--text-xs);
    color: var(--text-secondary);
}
/* Tirar o anexo: o .btn-close--sm--on-media (DS-12) no canto da miniatura. */
.attach-remove {
    position: absolute;
    top: 2px;
    right: 2px;
}
.attach-name {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    padding: var(--space-half) var(--space-1);
    border-radius: 0 0 calc(var(--radius-sm) - 1px) calc(var(--radius-sm) - 1px);
    background: rgba(var(--abyss-rgb), 0.6);
    color: var(--text-bright);
    font-size: var(--text-2xs);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Images in messages */
.message-content img {
    max-width: 300px;
    max-height: 300px;
    border-radius: var(--radius-sm);
    margin: var(--space-2) 0;
    cursor: pointer;
    transition: transform var(--dur-base);
}
.message-content img:hover {
    transform: scale(1.02);
}

/* Mic Button */
.btn-mic {
    width: 32px;
    height: 32px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: 0;
    border-radius: var(--radius-full);
    color: var(--text-muted);
    cursor: pointer;
    transition: color var(--dur-base) var(--ease-out), background var(--dur-base) var(--ease-out);
    flex-shrink: 0;
}

.btn-mic:hover,
.btn-mic[aria-pressed="true"] {
    color: var(--accent);
    background: var(--accent-soft);
}

.btn-mic.recording {
    color: var(--error-text);
    background: rgba(var(--error-rgb), 0.12);
    animation: micPulse 1.5s ease-in-out infinite;
}

@keyframes micPulse {
    0% { box-shadow: 0 0 0 0 rgba(var(--error-rgb), 0.4); }
    70% { box-shadow: 0 0 0 10px rgba(var(--error-rgb), 0); }
    100% { box-shadow: 0 0 0 0 rgba(var(--error-rgb), 0); }
}

.btn-mic.processing {
    border-color: var(--accent);
    color: var(--accent);
    animation: micSpin 1s linear infinite;
}

@keyframes micSpin {
    0% { opacity: 1; }
    50% { opacity: 0.4; }
    100% { opacity: 1; }
}

/* =============================================================================
   Welcome Message
   ============================================================================= */

.welcome-message {
    text-align: center;
    padding: var(--space-5) var(--space-6) 60px;
    color: var(--text-secondary);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-height: 100%;
}

/* O wordmark pequeno e parado (LAND-04): partículas desenhadas uma vez, com
   o SVG de texto por baixo como recurso. */
.welcome-wordmark {
    width: min(240px, 62vw);
    margin-bottom: var(--space-5);
}

/* Sem o gradiente: o wordmark por cima já é a marca; o título é texto. */
.welcome-message h2 {
    font-family: var(--font-display);
    font-weight: 600;
    letter-spacing: var(--tracking-display);
    text-wrap: balance;
    font-size: var(--text-2xl);
    line-height: 1.15;
    color: var(--text-bright);
    margin-bottom: var(--space-2);
}

/* "O que o Genisium faz" — veio da landing (LAND-01/LAND-12). */
.welcome-feats {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-4) var(--space-7);
    max-width: 52ch;
    margin: var(--space-6) 0 0;
    text-align: left;
}
.welcome-feats dt {
    margin-bottom: var(--space-1);
    font: 500 var(--text-2xs)/1.3 var(--font-mono);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--brand);
}
.welcome-feats dd {
    margin: 0;
    font-size: var(--text-sm);
    line-height: 1.5;
    color: var(--text-secondary);
}
@media (max-width: 480px) {
    .welcome-feats { grid-template-columns: minmax(0, 1fr); gap: var(--space-2); }
}

.welcome-message p {
    font-size: var(--text-md);
    max-width: 40ch;
    line-height: 1.6;
}

.welcome-suggestions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    justify-content: center;
    margin-top: var(--space-6);
    max-width: 380px;
}

/* Sugestões como comandos: eyebrow mono, sem fundo — o "> " já é o convite */
.welcome-chip {
    padding: var(--space-2) var(--space-4);
    background: transparent;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-full);
    color: var(--text-secondary);
    font: 500 var(--text-2xs)/1.4 var(--font-mono);
    text-transform: uppercase;
    letter-spacing: var(--tracking-label);
    cursor: pointer;
    transition: all var(--dur-base) var(--ease-out);
    white-space: nowrap;
}

/* Prompt de terminal — o "> " é decorativo, não entra no texto enviado */
.welcome-chip::before {
    content: '> ';
    font-family: var(--font-mono);
    color: var(--accent);
    opacity: 0.8;
}

.welcome-chip:hover {
    border-color: var(--border-hover);
    color: var(--text-bright);
    background: var(--accent-soft);
    transform: translateY(-1px);
}
/* <button> desde DS-02: sem a aparência nativa nem o alinhamento do UA. */
button.welcome-chip {
    appearance: none;
    -webkit-appearance: none;
    max-width: 100%;
    text-align: center;
}
/* Num telemóvel a 360 px a sugestão mais longa (~340 px em mono maiúsculo)
   não cabia numa linha e ficava cortada pelo overflow do contentor. */
@media (max-width: 480px) {
    .welcome-chip {
        white-space: normal;
        text-wrap: balance;
    }
}
.welcome-message .welcome-status {
    min-height: 1.4em;
    margin-top: var(--space-3);
    font-size: var(--text-xs);
    color: var(--text-secondary);
}

/* =============================================================================
   Scrollbar
   ============================================================================= */

::-webkit-scrollbar {
    width: 5px;
}

::-webkit-scrollbar-track {
    background: transparent;
}

::-webkit-scrollbar-thumb {
    /* era branco-alpha (sobra do tema escuro) — invisível no fundo claro */
    background: rgba(var(--brand-rgb), 0.25);
    border-radius: var(--radius-xs);
}

::-webkit-scrollbar-thumb:hover {
    background: rgba(var(--brand-rgb), 0.40);
}

/* =============================================================================
   Selection
   ============================================================================= */

/* Selecção na cor da marca */
::selection {
    background: rgba(var(--brand-rgb), 0.30);
    color: var(--text-bright);
}

/* =============================================================================
   Responsive
   ============================================================================= */

/* iOS: inputs <16px provocam auto-zoom ao focar (promessa do comentário do
   index.html finalmente cumprida). Só em dispositivos touch. */
@media (pointer: coarse) {
    /* Os .field e os campos dentro de .fields já sobem sozinhos (DS-12): a
       pesquisa de conversas, a do colega e a nova tarefa. Falta o campo sem
       caixa — o compositor (o terminal de vendas saiu com o Pulso, IA-10). */
    #message-input {
        font-size: var(--text-input);
    }
}

/* Elementos exclusivos de mobile (hamburger, botões X dos overlays) */
.mobile-only { display: none; }
@media (max-width: 1023px) {
    .mobile-only { display: inline-flex; }
}

/* O × das folhas do telemóvel é o .btn-close (DS-12), aqui com os 44 px de
   alvo. Sem display aqui: quem o mostra é o .mobile-only (≤1023 px). Com um
   display:inline-flex nesta regra — mesma especificidade, mais abaixo — o ×
   aparecia no desktop sem fazer nada (VIS-17). */
.mobile-close-btn {
    width: var(--hit-min);
    height: var(--hit-min);
    margin-left: var(--space-1);
}

/* Header mobile: sem media query os 8 botões comprimiam-se até ~20px em 390px
   (mínimo iOS HIG: 44px). */
@media (max-width: 600px) {
    /* F5 — o nome da conversa fica à vista (sem ele não se sabia em que grupo
       ou DM se estava): ocupa o que sobra, com reticências, e o olho da marca
       cede-lhe o lugar. */
    .header-left { flex: 1 1 auto; min-width: 0; }
    .header-title { flex: 1 1 auto; min-width: 0; max-width: none; }
    .header-left > .logo-small { display: none; }
    .chat-header { padding: 0 var(--space-2); }
    .header-right {
        gap: var(--space-half);
        flex: 0 0 auto;
        justify-content: flex-end;
    }
    .header-menu-list { min-width: 220px; }
    .btn-icon {
        flex-shrink: 0;
        width: 44px;
        height: 44px;
        /* min-* vence qualquer pressão de shrink do flexbox */
        min-width: 44px;
        min-height: 44px;
    }
}

@media (max-width: 480px) {
    .message {
        max-width: 90%;
    }

    .welcome-suggestions {
        padding: 0 var(--space-2);
    }
}

/* Coluna de leitura centrada — substitui os paddings percentuais herdados
   do layout full-width, que sobre a coluna 1fr da grelha estrangulavam as
   respostas do bot para ~288px em ecrãs 1440px (dupla penalização: % + %) */
@media (min-width: 768px) {
    .messages {
        max-width: 76ch;
        margin-inline: auto;
        width: 100%;
    }

    .messages-container {
        padding: var(--space-7) var(--space-6);
    }

    /* Medida de leitura do bot: 72ch a 15px/1.6 — é um documento, não uma
       bolha, e a coluna .messages já é de 76ch */
    .message.assistant {
        max-width: 72ch;
    }

    /* Composer alinhado à mesma coluna de leitura */
    .input-container {
        padding: var(--space-3) var(--space-6) var(--space-5);
    }
    .input-container form {
        max-width: 76ch;
        margin-inline: auto;
    }
}

/* =============================================================================
   Focus visible (accessibility)
   ============================================================================= */

:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* =============================================================================
   Modal Overlay (Telegram Pairing, etc.)
   ============================================================================= */

.modal-overlay {
    position: fixed;
    inset: 0;
    z-index: var(--z-modal);
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, 0.6);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    animation: modalFadeIn var(--dur-base) ease-out;
}
.modal-overlay.hidden {
    display: none;
}
/* DS-09 — sem campo para focar (ecrã táctil), o focus-trap.js foca o próprio
   diálogo: é a porta de entrada do leitor de ecrã, não precisa de anel. */
[role="dialog"][tabindex="-1"]:focus {
    outline: none;
}
@keyframes modalFadeIn {
    from { opacity: 0; }
    to { opacity: 1; }
}

.modal-card {
    position: relative;
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    padding: var(--space-8);
    width: 90%;
    max-width: 380px;
    box-shadow: var(--shadow-lg);
    animation: modalSlideUp var(--dur-base) ease-out;
}
/* Centrado só onde o conteúdo é um cartão de acção curto (o pairing do
   Telegram). Antes estava no .modal-card e o KB, as Skills e o picker
   herdavam-no — entradas da base de conhecimento centradas (DS-11). */
.modal-card--centered {
    text-align: center;
}
@keyframes modalSlideUp {
    from { opacity: 0; transform: translateY(20px); }
    to { opacity: 1; transform: translateY(0); }
}

/* Fase 5, E1 — «Escolhe o teu PIN»: não se fecha (sem ×, sem Escape); por
   cima do portão ou do chat. O formulário é o de Conta e PIN, em coluna. */
.pin-change-card { display: flex; flex-direction: column; gap: var(--space-3); max-height: calc(100dvh - var(--space-8)); overflow-y: auto; }
.pin-change-card .modal-title,
.pin-change-card .modal-desc { margin: 0; }
.pin-change-card .ops-form { flex-direction: column; align-items: stretch; }
.pin-change-card .ops-form .btn { align-self: stretch; min-height: var(--hit-min); }
.pin-change-card .ops-notice[hidden] { display: none; }
.pin-change-logout { align-self: center; }

/* O × do pairing é o .btn-close (DS-12), no canto do cartão. */
.modal-close {
    position: absolute;
    top: var(--space-3);
    right: var(--space-3);
}

.modal-icon {
    margin-bottom: var(--space-3);
}

.modal-title {
    font-size: var(--text-xl);
    font-weight: 600;
    color: var(--text-primary);
    margin: 0 0 var(--space-2);
}

.modal-desc {
    font-size: var(--text-md);
    color: var(--text-secondary);
    margin: 0 0 var(--space-5);
    line-height: 1.5;
}
.modal-desc code {
    background: var(--bg-main);
    padding: var(--space-half) var(--space-1h);
    border-radius: var(--radius-xs);
    font-size: var(--text-sm);
}

/* O código de ligação: .field.field--lg.field--code (DS-12), centrado e em
   maiúsculas como o bot o escreve. */
#pair-code-input { width: 100%; text-align: center; text-transform: uppercase; }
#pair-modal .btn { margin-top: var(--space-4); }

/* AUTH-09 — o link para o bot É o botão principal (.btn.btn--primary); sem
   link (aria-disabled) não se clica. */
.pair-open-link[aria-disabled="true"] { pointer-events: none; }
.pair-manual {
    margin-top: var(--space-4);
    text-align: left;
}
.pair-manual summary {
    cursor: pointer;
    font-size: var(--text-md);
    color: var(--text-secondary);
    margin-bottom: var(--space-3);
}
.pair-status {
    min-height: 20px;
    margin-top: var(--space-2);
    font-size: var(--text-sm);
    line-height: 1.4;
}
.pair-status.success {
    color: var(--success);
}
.pair-status.error {
    color: var(--error-text);
}

/* =============================================================================
   O #character-stage SAIU daqui.

   Eram dois olhos em CSS de 34x42px com quatro camadas de box-shadow cada
   (uma a animar em loop — animar box-shadow força repaint por frame), mais
   aura, boca, e as keyframes charFloat/auraPulse/auraRing/auraFlash. Vivia
   num palco absoluto que obrigava o #messages-container a reservar
   104-128px de padding-top em TODOS os breakpoints, e desvanecia para
   opacity 0.45 assim que havia mensagens: custava um sexto do ecrã para
   não se ver.

   A criatura recolheu ao header e é agora a própria marca — um olho de
   partículas onde a pupila é densidade, desenhado em canvas por
   static/character.js. Ver .logo-small mais abaixo.
   ============================================================================= */



#messages-container {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
}

/* Action pills */
.action-pills {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    padding: var(--space-1h) var(--space-3) var(--space-2);
}

.action-pill {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: var(--space-1h) var(--space-4);
    background: var(--accent-soft);
    border: 1px solid rgba(var(--accent-rgb), 0.25);
    border-radius: var(--radius-full);
    color: var(--accent-hover);
    font-size: var(--text-sm);
    font-weight: 500;
    cursor: pointer;
    transition: background var(--dur-base), border-color var(--dur-base), transform var(--dur-fast);
    white-space: nowrap;
    -webkit-tap-highlight-color: transparent;
}

.action-pill:hover {
    background: rgba(var(--accent-rgb), 0.18);
    border-color: var(--border-hover);
}

.action-pill:active {
    transform: scale(0.96);
}

/* =============================================================================
   Ritmos partilhados (voz). Eram da landing; a criatura saiu e ficou a voz.
   ============================================================================= */

@keyframes gl-mesh-drift {
    0%   { transform: translate3d(0, 0, 0)     scale(1)    rotate(0deg); }
    50%  { transform: translate3d(2%, -3%, 0)  scale(1.05) rotate(2deg); }
    100% { transform: translate3d(-2%, 2%, 0)  scale(1.02) rotate(-1deg); }
}

/* Ritmo vital — 3,8 s, o mesmo período da esfera em repouso (voice-orb.js). */
@keyframes breathe-dot {
    0%, 100% { opacity: 0.45; transform: scale(0.9); }
    50%      { opacity: 1;    transform: scale(1.1); }
}
/* Brilho que respira por opacity (compositor), nunca por box-shadow — animar
   box-shadow força repaint a cada frame (LAND-11). */
@keyframes breathe-glow {
    0%, 100% { opacity: 0.45; }
    50%      { opacity: 1; }
}

/* =============================================================================
   Tasks Panel — split layout desktop + bottom sheet mobile
   ============================================================================= */

/* Default: painel escondido em mobile (aparece via .open bottom sheet) */
.tasks-panel {
    display: none;
    background: var(--bg-glass);
    backdrop-filter: blur(8px);
    border: 1px solid var(--border);
}

/* Desktop: 3 colunas — sidebar (conversas) | chat | right-panel (tasks + sales) */
/* IMPORTANT: usar :not(.hidden) para não vencer .hidden { display: none !important } */
@media (min-width: 1024px) {
    #chat-screen:not(.hidden) {
        display: grid;
        /* minmax(0,1fr) na coluna do meio é obrigatório: sem ele, conteúdo
           largo (pre/code) forçava a coluna a crescer e partia o layout */
        grid-template-columns: minmax(248px, 280px) minmax(0, 1fr) minmax(300px, 340px);
        grid-template-rows: auto 1fr auto;
        grid-template-areas:
            "sidebar header header"
            "sidebar main   right"
            "sidebar input  right";
        height: 100vh;
        height: 100dvh;
    }
    #chat-screen > .chat-header { grid-area: header; }
    #chat-screen > #conversations-sidebar { grid-area: sidebar; }
        #chat-screen > #messages-container { grid-area: main; }
    #chat-screen > .input-container { grid-area: input; }
    #chat-screen > #right-panel {
        grid-area: right;
        display: grid;
        /* auto (separadores) + 1fr (o painel activo). Era `2fr 1fr` com os dois
           empilhados — e com os dois campos de escrita visíveis ao mesmo tempo. */
        grid-template-rows: auto 1fr;
        min-height: 0;
        overflow: hidden;
        border-left: 1px solid var(--border);
        background: var(--bg-secondary);
    }
    #chat-screen > #right-panel > .tasks-panel {
        display: flex;
        flex-direction: column;
        border: none;
        border-radius: 0;
        background: var(--bg-secondary);
        overflow: hidden;
        min-height: 0;
    }
    #chat-screen > #right-panel > .pulse-panel {
        display: flex;
        flex-direction: column;
        background: var(--bg-secondary);
        overflow: hidden;
        min-height: 0;
    }
    /* Rails: recolher uma coluna não a esconde — deixa um rail de ícones
       (56 px à esquerda com os avatares das conversas, 48 px à direita com
       os dois separadores). As quatro combinações são só grelha. */
    #chat-screen:not(.hidden).sidebar-rail {
        grid-template-columns: 56px minmax(0, 1fr) minmax(300px, 340px);
    }
    #chat-screen:not(.hidden).tasks-collapsed {
        grid-template-columns: minmax(248px, 280px) minmax(0, 1fr) 48px;
    }
    #chat-screen:not(.hidden).sidebar-rail.tasks-collapsed {
        grid-template-columns: 56px minmax(0, 1fr) 48px;
    }

    /* Rail direito: separadores em coluna, painéis fechados, ponto de atraso */
    #chat-screen.tasks-collapsed > #right-panel {
        grid-template-rows: 1fr;
    }
    #chat-screen.tasks-collapsed > #right-panel > .rp-tabs {
        flex-direction: column;
        padding: var(--space-2) var(--space-1);
        gap: var(--space-1h);
        border-bottom: 0;
    }
    #chat-screen.tasks-collapsed > #right-panel > .rp-tabs .rp-tablist {
        flex-direction: column;
        flex: none;
        gap: var(--space-1);
    }
    #chat-screen.tasks-collapsed > #right-panel > .rp-tabs .rp-tab {
        flex: none;
        width: 40px;
        height: 40px;
        padding: 0;
        border-bottom: 0;
        border-radius: var(--radius-xs);
    }
    #chat-screen.tasks-collapsed > #right-panel > .rp-tabs .rp-tab[aria-selected="true"] {
        background: var(--accent-soft);
    }
    #chat-screen.tasks-collapsed > #right-panel > .rp-tabs .rp-tab-label { display: none; }
    #chat-screen.tasks-collapsed > #right-panel > .rp-tabs .tp-collapse-btn {
        margin: auto 0 0;
    }
    #chat-screen.tasks-collapsed > #right-panel > .rp-tabs .tp-collapse-btn svg {
        transform: rotate(180deg);
    }
    #chat-screen.tasks-collapsed > #right-panel > [role="tabpanel"] { display: none !important; }

    /* Rail esquerdo: só os avatares (com o contador de não lidas no canto) e
       os dois botões; o título da conversa vai para o tooltip do item. */
    #chat-screen.sidebar-rail > .conv-sidebar .conv-title,
    #chat-screen.sidebar-rail > .conv-sidebar .conv-search,
    #chat-screen.sidebar-rail > .conv-sidebar .conv-name,
    #chat-screen.sidebar-rail > .conv-sidebar .conv-preview,
    #chat-screen.sidebar-rail > .conv-sidebar .conv-time,
    #chat-screen.sidebar-rail > .conv-sidebar .conv-pinned-tag,
    #chat-screen.sidebar-rail > .conv-sidebar .conv-section-title { display: none; }
    #chat-screen.sidebar-rail > .conv-sidebar .conv-header { padding: var(--space-2) var(--space-2); }
    #chat-screen.sidebar-rail > .conv-sidebar .conv-header-row {
        flex-direction: column;
        gap: var(--space-2);
    }
    #chat-screen.sidebar-rail > .conv-sidebar .conv-item-btn {
        grid-template-columns: 36px;
        padding: var(--space-2) var(--space-1h) var(--space-2) 7px;
        position: relative;
    }
    #chat-screen.sidebar-rail > .conv-sidebar .conv-unread {
        position: absolute;
        top: 4px;
        right: 4px;
        grid-column: auto;
        grid-row: auto;
        min-width: 14px;
        padding: 0 var(--space-1);
        font-size: var(--text-3xs);
        line-height: 14px;
    }
    #chat-screen.sidebar-rail > .conv-sidebar .conv-rail-btn svg { transform: rotate(180deg); }

    /* Em desktop o separador já diz "Pulso" */
    #chat-screen > #right-panel > .pulse-panel > .pp-header { display: none; }

    /* F5 — a thread aberta fica com a coluna da direita: era um painel fixo
       de 380 px por cima de tudo, que tapava o fim das bolhas e o menu ⋯ do
       cabeçalho. Enquanto está aberta o painel das tarefas sai da grelha e
       volta como estava (o estado guardado não muda). */
    #chat-screen:not(.hidden):has(> #thread-pane:not([hidden])) {
        grid-template-columns: minmax(248px, 280px) minmax(0, 1fr) minmax(320px, 380px);
        grid-template-areas:
            "sidebar header header"
            "sidebar main   thread"
            "sidebar input  thread";
    }
    #chat-screen:not(.hidden).sidebar-rail:has(> #thread-pane:not([hidden])) {
        grid-template-columns: 56px minmax(0, 1fr) minmax(320px, 380px);
    }
    #chat-screen:has(> #thread-pane:not([hidden])) > #right-panel { display: none; }
    /* Também só com o :has(): sem ele (Firefox < 121, Safari < 15.4) a grelha
       não tem a área «thread» e a thread fica o painel fixo de antes. Na grelha
       o z-index volta a auto: um item de grelha com o --z-thread (960) ficava
       acima do cabeçalho (920) e o menu ⋯ abria por baixo da thread. */
    #chat-screen:not(.hidden):has(> #thread-pane:not([hidden])) > #thread-pane {
        grid-area: thread;
        position: static;
        width: auto;
        height: auto;
        min-height: 0;
        box-shadow: none;
        z-index: auto;
    }
    /* Na grelha o #chat-screen já desconta as margens seguras (iPad em PWA):
       somadas outra vez, a caixa de resposta da thread ficava mais alta do que
       a do compositor. */
    #chat-screen:not(.hidden):has(> #thread-pane:not([hidden])) > #thread-pane .thread-pane-head { padding-top: var(--space-4); }
    #chat-screen:not(.hidden):has(> #thread-pane:not([hidden])) > #thread-pane .thread-pane-foot { padding-bottom: var(--space-3); }

    #tp-mobile-toggle { display: none !important; }
}

/* Header do painel. Com o toggle Minhas/Equipa e o select de membros (só o
   superadmin os vê) a linha única transbordava: o × ficava espremido e o
   select saía do painel. Agora parte em duas — o select vai para baixo, a
   toda a largura, e o × nunca encolhe (VIS-13). */
.tp-header {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-4) var(--space-4);
    border-bottom: 1px solid var(--border);
    background: var(--bg-tertiary);
}
.tp-title {
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    color: var(--text-secondary);
    margin: 0;
    flex: 1 0 auto;
}
.tp-header .mobile-close-btn {
    flex: none;
}
.tp-scope {
    display: inline-flex;
    gap: var(--space-half);
    background: var(--bg-primary);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: var(--space-half);
}
/* O display acima ganhava ao [hidden] do UA: o Minhas/Equipa (e o formulário
   de nova tarefa) aparecia a quem o JS tinha escondido (IA-12). */
.tp-scope[hidden],
.tp-quick-create[hidden] { display: none; }
.tp-scope-btn {
    min-height: 32px;
    background: transparent;
    color: var(--text-secondary);
    border: none;
    padding: var(--space-1) var(--space-3);
    font-size: var(--text-xs);
    font-weight: 500;
    border-radius: var(--radius-xs);
    cursor: pointer;
    transition: all var(--dur-base) var(--ease-out);
}
.tp-scope-btn.active {
    background: var(--accent-soft);
    color: var(--accent);
}
/* .field.field--sm (DS-12): parte para a linha de baixo, a toda a largura. */
.tp-select {
    order: 1;
    flex: 1 0 100%;
}
/* IA-19 — «Resumo do dia»: linha própria por baixo do título (como o filtro). */
.tp-digest-btn {
    order: 2;
    flex: 1 0 100%;
    min-height: 30px;
    padding: var(--space-1) var(--space-2);
    border: 1px dashed var(--border-strong);
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text-secondary);
    font: 500 var(--text-xs)/1.3 var(--font-sans);
    text-align: left;
    cursor: pointer;
}
.tp-digest-btn[hidden] { display: none; }
/* Com o filtro de membro à vista (superadmin), o «Resumo do dia» fica na linha
   dele em vez de abrir uma terceira: no telemóvel o cabeçalho chegava aos
   150 px e, com o stand-up por responder, a lista de tarefas ficava com ~70 px
   — os alvos de 44 px das acções da 1.ª tarefa (DS-08) caíam no formulário. */
.tp-select:has(~ .tp-digest-btn:not([hidden])) { flex: 1 1 55%; min-width: 0; }
.tp-select:not([hidden]) ~ .tp-digest-btn { flex: 0 0 auto; }
/* E2 — membro e projecto à vista (superadmin com 2+ projectos): os dois
   selects partilham a linha e o «Resumo do dia» desce para a sua.
   F5 — só a partir de 481 px: no telemóvel o .field sobe para 16 px (anti-zoom
   do iOS) e lado a lado cortavam «Todos os membro…»; aí fica um por linha. */
@media (min-width: 481px) {
    .tp-header:has(#tp-member-filter:not([hidden])):has(#tp-project-filter:not([hidden])) .tp-select {
        flex: 1 1 40%;
        min-width: 0;
    }
    .tp-header:has(#tp-member-filter:not([hidden])):has(#tp-project-filter:not([hidden])) .tp-digest-btn {
        flex: 1 0 100%;
    }
}
.tp-digest-btn:hover { color: var(--accent); border-color: var(--accent); }
.tp-digest-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* IA-19 — modal do resumo da equipa */
.team-digest-card { max-width: 640px; }
.td-status:empty { display: none; }
.td-status {
    margin: 0 var(--space-4) var(--space-2);
    font-size: var(--text-xs);
    color: var(--text-secondary);
}
.td-body {
    padding: 0 var(--space-4) var(--space-4);
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
}
.td-totals {
    margin: 0;
    font: 500 var(--text-xs)/1.4 var(--font-mono);
    color: var(--text-secondary);
}
.td-muted { margin: 0; font-size: var(--text-xs); color: var(--text-muted); }
.td-project {
    display: flex;
    flex-direction: column;
    gap: var(--space-1h);
    padding-top: var(--space-2);
    border-top: 1px solid var(--border);
}
.td-project-head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: var(--space-1) var(--space-3); }
.td-project-name {
    margin: 0;
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--text-bright);
}
.td-project-facts { font-size: var(--text-xs); color: var(--text-secondary); }
.td-actions { display: flex; flex-wrap: wrap; gap: var(--space-1h); }
.td-actions .is-armed,
.td-send.is-armed { border-color: var(--warning); color: var(--warning); }
.td-standup-list,
.td-overdue-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-1h); }
.td-standup-item {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: var(--space-half) var(--space-2);
    padding: var(--space-1h) 0 var(--space-1h) var(--space-2);
    border-left: 2px solid var(--border);
    font-size: var(--text-xs);
}
.td-standup-item.is-answered { border-left-color: var(--accent); }
.td-standup-item.is-pending { border-left-color: var(--warning); }
.td-standup-who { color: var(--text-bright); font-weight: 600; }
.td-standup-state { font: 500 var(--text-3xs)/1.6 var(--font-mono); text-transform: uppercase; color: var(--text-secondary); }
.td-standup-answer { grid-column: 1 / -1; margin: 0; color: var(--text-primary); white-space: pre-wrap; }
.td-overdue-item { font-size: var(--text-xs); color: var(--text-primary); }
.td-briefing-sec { display: flex; flex-direction: column; gap: var(--space-2); padding-top: var(--space-2); border-top: 1px solid var(--border); }
.td-briefing-sec > button { align-self: flex-start; }
.td-briefing-box { display: flex; flex-direction: column; gap: var(--space-2); }
.td-briefing { border: 1px solid var(--border); border-radius: var(--radius-sm); padding: var(--space-2) var(--space-2); }
.td-briefing-who { margin: 0 0 var(--space-1); font-size: var(--text-xs); color: var(--text-bright); }
.td-briefing-msg {
    margin: 0;
    white-space: pre-wrap;
    font: 400 var(--text-xs)/1.45 var(--font-sans);
    color: var(--text-primary);
}
.td-send { align-self: flex-start; }

.tp-collapse-btn {
    flex: none;
    width: 32px;
    height: 32px;
    align-self: center;
    margin-left: var(--space-1);
    display: grid;
    place-items: center;
    background: transparent;
    color: var(--text-muted);
    border: 0;
    border-radius: var(--radius-xs);
    cursor: pointer;
    transition: color var(--dur-base) var(--ease-out), background var(--dur-base) var(--ease-out);
}
.tp-collapse-btn:hover { color: var(--text-primary); background: var(--accent-soft); }
@media (max-width: 1023px) {
    .rp-tabs .tp-collapse-btn { display: none; }   /* o rail é coisa de desktop */
}

/* Filtros */
.tp-filters {
    display: flex;
    gap: var(--space-1);
    padding: var(--space-2) var(--space-3);
    border-bottom: 1px solid var(--border);
    overflow-x: auto;
    scrollbar-width: none;
}
.tp-filters::-webkit-scrollbar { display: none; }
.tp-filter {
    background: transparent;
    color: var(--text-secondary);
    border: 1px solid var(--border-strong);
    /* 5 → 6 em cima/baixo: empate decidido pelo alvo de toque (DS-08). */
    padding: var(--space-1h) var(--space-3);
    font-size: var(--text-xs);
    font-weight: 500;
    border-radius: var(--radius-full);
    cursor: pointer;
    white-space: nowrap;
    transition: all var(--dur-base) var(--ease-out);
}
.tp-filter:hover {
    color: var(--text-primary);
    border-color: var(--border-hover);
}
.tp-filter.active {
    background: var(--accent-soft);
    color: var(--accent);
    border-color: var(--accent);
}
.tp-inline-badge {
    display: inline-block;
    background: var(--error);
    color: #000;
    font-size: var(--text-3xs);
    font-weight: 600;
    min-width: 16px;
    height: 16px;
    line-height: 16px;
    padding: 0 var(--space-1);
    border-radius: var(--radius-sm);
    margin-left: var(--space-1);
    vertical-align: middle;
}
.tp-inline-badge.hidden { display: none; }

/* Banner (not-linked / vazio) */
.tp-banner {
    padding: var(--space-4);
    margin: var(--space-2);
    background: var(--accent-soft);
    border: 1px solid var(--accent);
    border-radius: var(--radius-sm);
    color: var(--accent);
    font-size: var(--text-xs);
    line-height: 1.5;
}
.tp-banner.hidden { display: none; }
/* E2 — superadmin sem membro de equipa: o botão para o criar */
.tp-setup {
    display: grid;
    gap: var(--space-2);
    padding: var(--space-3);
    margin: var(--space-2);
    border: 1px solid var(--accent);
    border-radius: var(--radius-sm);
    background: var(--accent-soft);
}
.tp-setup[hidden] { display: none; }
.tp-setup-text { margin: 0; font-size: var(--text-xs); line-height: 1.5; color: var(--text-primary); }
.tp-setup .btn { justify-self: start; min-height: var(--hit-min); }

/* Stand-up de hoje respondido pela app (EQP-06) */
.tp-standup-wrap { padding: var(--space-2) var(--space-2) 0; }
.tp-standup-wrap[hidden] { display: none; }
.tp-standup-form {
    display: grid;
    gap: var(--space-1h);
    padding: var(--space-2);
    border: 1px solid var(--accent);
    border-radius: var(--radius-sm);
    background: var(--accent-soft);
    margin-bottom: var(--space-1h);
}
.tp-standup-title { font-size: var(--text-xs); font-weight: 600; color: var(--text-primary); }
.tp-standup-send { justify-self: end; }
.tp-standup-done { font-size: var(--text-xs); color: var(--success, var(--accent)); margin: 0 0 var(--space-1h); }

/* Lista de tasks */
.tp-list {
    flex: 1;
    overflow-y: auto;
    padding: var(--space-2) var(--space-2);
    margin: 0;
    list-style: none;
    min-height: 0;
}
/* Estado vazio escrito pelo tasks-panel.js conforme o filtro e o papel (IA-03):
   o antigo "Diz «nova tarefa» ao Genisium" prometia algo que o chat não fazia. */
.tp-empty {
    display: block;
    text-align: center;
    /* --text-muted sobre a tinta do acento ficava a 3,75:1 (DS-06). */
    color: var(--text-secondary);
    font-size: var(--text-sm);
    line-height: 1.5;
    padding: var(--space-8) var(--space-5);
    margin: var(--space-3) var(--space-1h);
    border: 1px dashed var(--border-hover);
    border-radius: var(--radius-sm);
    background: var(--accent-soft);
}
.tp-task {
    display: grid;
    grid-template-columns: 18px 1fr auto;
    grid-template-rows: auto auto;
    gap: var(--space-half) var(--space-2);
    padding: var(--space-1h) var(--space-2);
    margin-bottom: var(--space-1);
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    transition: all var(--dur-base) var(--ease-out);
    cursor: default;
}
.tp-task:hover {
    background: var(--bg-tertiary);
    border-color: var(--border-hover);
}
.tp-task.done {
    opacity: 0.5;
}
.tp-task.done .tp-task-title {
    text-decoration: line-through;
}
/* DS-15 — um canal por eixo. A borda esquerda codificava a DATA (atrasada,
   hoje, amanhã, futuro) e depois era reescrita pela PRIORIDADE: um vermelho
   tanto era "atrasada" como "urgente", e uma atrasada de prioridade baixa
   perdia o vermelho. Agora:
     - prioridade → só a borda esquerda (e o badge);
     - data       → a cor e o peso do .tp-due, e a tinta de fundo.
   Hoje leva o acento no prazo; "futuro" deixou de ser o verde do sucesso. */
.tp-task { border-left: 3px solid transparent; }
.tp-task.overdue { background: rgba(var(--error-rgb), 0.06); }
.tp-task.today   { background: rgba(var(--brand-rgb), 0.06); }
.tp-task.priority-urgent { border-left-color: var(--error); }
.tp-task.priority-high   { border-left-color: var(--warning); }
.tp-task.priority-low    { border-left-color: var(--text-muted); }
/* 18 px à vista, 44×44 ao toque: o ::before invisível alarga a área de
   clique sem mexer na grelha (DS-03, VIS-13). */
.tp-check {
    appearance: none;
    -webkit-appearance: none;
    width: 18px;
    height: 18px;
    border: 1.5px solid var(--accent);
    border-radius: 50%;
    cursor: pointer;
    flex-shrink: 0;
    margin: 0;
    position: relative;
    grid-row: 1;
    align-self: start;
    transition: all var(--dur-base) var(--ease-out);
}
.tp-check::before {
    content: "";
    position: absolute;
    inset: -13px;
    border-radius: 50%;
}
.tp-check:checked {
    background: var(--accent);
}
.tp-check:checked::after {
    content: "";
    position: absolute;
    top: 2px;
    left: 5px;
    width: 4px;
    height: 8px;
    border: solid var(--on-brand);
    border-width: 0 1.5px 1.5px 0;
    transform: rotate(45deg);
}
.tp-task-title {
    grid-column: 2;
    grid-row: 1;
    font-size: var(--text-xs);
    color: var(--text-primary);
    line-height: 1.3;
    word-break: break-word;
}
/* Título de uma tarefa de equipa: botão que abre a edição (IA-15) */
.tp-task-edit {
    background: none;
    border: 0;
    padding: 0;
    margin: 0;
    font: inherit;
    font-size: var(--text-xs);
    text-align: left;
    cursor: pointer;
}
.tp-task-edit:hover { color: var(--accent); text-decoration: underline; }
.tp-task-edit:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--radius-2xs); }
/* E2 — a descrição resumida (2 linhas) entre o título e os metadados */
.tp-task-desc {
    grid-column: 2;
    grid-row: 2;
    margin: 0;
    font-size: var(--text-2xs);
    line-height: 1.4;
    color: var(--text-secondary);
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    word-break: break-word;
}
.tp-task-desc ~ .tp-task-meta { grid-row: 3; }
.tp-task-meta .tp-personal-badge {
    display: inline-block;
    padding: var(--space-half) var(--space-1h);
    border-radius: var(--radius-xs);
    font-size: var(--text-3xs);
    font-weight: 600;
    background: var(--accent-soft);
    color: var(--accent);
}
.tp-task-meta {
    grid-column: 2;
    grid-row: 2;
    display: flex;
    gap: var(--space-2);
    /* VIS-20: prazo e prioridade são o que se procura no painel — 11 px
       (12 no telemóvel), nunca 10. */
    font-size: var(--text-2xs);
    color: var(--text-secondary);
    align-items: center;
    flex-wrap: wrap;
}
.tp-task-meta .tp-due { color: var(--text-secondary); }
.tp-task.overdue .tp-task-meta .tp-due { color: var(--error-text); font-weight: 600; }
.tp-task.today .tp-task-meta .tp-due { color: var(--brand); font-weight: 600; }
.tp-task-meta .tp-priority-badge {
    display: inline-block;
    padding: var(--space-half) var(--space-1h);
    border-radius: var(--radius-xs);
    font-size: var(--text-2xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
}
.tp-task-meta .tp-priority-urgent { background: rgba(var(--error-rgb), 0.15); color: var(--error-text); }
.tp-task-meta .tp-priority-high   { background: rgba(var(--warning-rgb), 0.15); color: var(--warning); }
.tp-task-meta .tp-priority-normal { background: var(--bg-primary); color: var(--text-secondary); }
.tp-task-meta .tp-priority-low    { background: var(--bg-primary); color: var(--text-muted); }
.tp-task-meta .tp-project { color: var(--text-muted); }
/* No hover a tarefa passa ao fundo do header, onde o muted fica a 3,98:1. */
.tp-task:hover .tp-task-meta .tp-project { color: var(--text-secondary); }
/* VIS-20: no telemóvel os metadados com significado sobem para 12 px. */
@media (max-width: 767px) {
    .tp-task-meta,
    .tp-task-meta .tp-priority-badge,
    .conv-time { font-size: var(--text-xs); }
}
.tp-task-actions {
    grid-column: 3;
    grid-row: 1 / span 2;
    display: flex;
    align-items: center;
    gap: var(--space-1);
    opacity: 0;
    transition: opacity var(--dur-base) var(--ease-out);
}
/* Com o teclado (o foco num botão escondido era invisível: a opacidade do
   pai apagava o anel) e em touch (não há hover) as acções ficam à vista. */
.tp-task:hover .tp-task-actions,
.tp-task:focus-within .tp-task-actions { opacity: 1; }
.tp-task-btn {
    width: 28px;
    height: 28px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: none;
    color: var(--text-muted);
    padding: 0;
    cursor: pointer;
    border-radius: var(--radius-xs);
    transition: all var(--dur-base) var(--ease-out);
    line-height: 1;
}
.tp-task-btn:hover { color: var(--accent); background: var(--accent-soft); }
/* Archive btn: hover vermelho para sinalizar destructive action */
.tp-task-btn-archive:hover {
    color: var(--error-text);
    background: rgba(var(--error-rgb), 0.12);
}
/* E2 — 1.º toque arma («Toca outra vez para confirmar»), o 2.º arquiva */
.tp-task-btn-archive.is-armed {
    color: var(--error-text);
    background: rgba(var(--error-rgb), 0.18);
    box-shadow: inset 0 0 0 1px rgba(var(--error-rgb), 0.55);
}
/* Armado, fica à vista mesmo sem hover (o 2.º toque tem de o encontrar) */
.tp-task:has(.is-armed) .tp-task-actions { opacity: 1; }

/* ---- E3 — passos (checklist) e rotinas ---------------------------------- */
/* «☑ 2/5»: abre os passos da tarefa para os marcar sem o diálogo. */
.tp-steps-toggle {
    display: inline-flex;
    align-items: center;
    gap: var(--space-half);
    padding: var(--space-half) var(--space-1h);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-xs);
    background: transparent;
    color: var(--text-secondary);
    font: inherit;
    font-weight: 600;
    cursor: pointer;
    transition: all var(--dur-base) var(--ease-out);
}
.tp-steps-toggle:hover { color: var(--accent); border-color: var(--accent); }
.tp-steps-toggle[aria-expanded="true"] { background: var(--accent-soft); color: var(--accent); border-color: var(--accent); }
.tp-steps-toggle.is-complete { color: var(--success); border-color: rgba(var(--success-rgb), 0.55); }
.tp-task-meta .tp-routine-badge {
    display: inline-block;
    padding: var(--space-half) var(--space-1h);
    border-radius: var(--radius-xs);
    font-size: var(--text-3xs);
    font-weight: 600;
    background: rgba(var(--brand-rgb), 0.12);
    color: var(--brand);
}
.tp-steps {
    grid-column: 2 / -1;
    list-style: none;
    margin: var(--space-1) 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-half);
}
.tp-step label {
    display: flex;
    align-items: flex-start;
    gap: var(--space-2);
    min-height: var(--control-sm);
    padding: var(--space-1) var(--space-1h);
    border-radius: var(--radius-xs);
    font-size: var(--text-xs);
    color: var(--text-primary);
    line-height: 1.35;
    cursor: pointer;
}
.tp-step label:hover { background: var(--bg-tertiary); }
.tp-step-check { flex-shrink: 0; width: 18px; height: 18px; margin: 0; cursor: pointer; }
.tp-step.done span { color: var(--text-muted); text-decoration: line-through; }
/* Fechar com passos por fazer: o 1.º toque arma a caixa (o 2.º fecha) */
.tp-check.is-armed { box-shadow: 0 0 0 3px rgba(var(--warning-rgb), 0.45); border-color: var(--warning); }

/* Vista «Rotinas»: os modelos (o processo), não tarefas abertas */
.tasks-panel[data-view="routines"] .tp-quick-create { display: none; }
.tp-routines-head {
    display: flex;
    justify-content: flex-end;
    padding: var(--space-1) 0 var(--space-2);
}
.tp-routine {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    padding: var(--space-2);
    margin-bottom: var(--space-1);
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-left: 3px solid var(--brand);
    border-radius: var(--radius-sm);
}
.tp-routine.is-paused { border-left-color: var(--text-muted); }
.tp-routine.is-paused .tp-task-title { color: var(--text-secondary); }
.tp-routine .tp-task-meta { gap: var(--space-1) var(--space-2); }
.tp-routine-freq { color: var(--text-primary); font-weight: 600; }
.tp-routine-state {
    padding: var(--space-half) var(--space-1h);
    border-radius: var(--radius-xs);
    font-size: var(--text-3xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    background: rgba(var(--success-rgb), 0.15);
    color: var(--success);
}
.tp-routine-state.is-paused { background: var(--bg-primary); color: var(--text-secondary); }
.tp-routine-today.is-done { color: var(--success); font-weight: 600; }
.tp-routine-actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--space-2);
}
/* Depois do .tp-task-btn: com a mesma especificidade, antes dele perdia e os
   botões ficavam nos 28 px em touch. */
@media (hover: none) {
    .tp-task-actions { opacity: 1; }
    .tp-task-btn { width: 36px; height: 36px; }
}

/* Quick create */
/* Duas linhas: título em cima (largura total), projecto + botão em baixo —
   a linha única espremia o select para ~100px e cortava os nomes */
.tp-quick-create {
    display: grid;
    grid-template-columns: 1fr auto auto;
    gap: var(--space-1h);
    padding: var(--space-2) var(--space-3);
    border-top: 1px solid var(--border);
    background: var(--bg-tertiary);
}
/* O <form> é .fields.fields--sm e os botões são .btn (DS-12): aqui só a
   grelha. O "+" e o "⋯" (o diálogo completo — prazo, prioridade, descrição;
   IA-15) são glifos: maiores que o texto do botão pequeno. */
.tp-quick-create input { grid-column: 1 / -1; }
.tp-add-btn,
.tp-more-btn { font-size: var(--text-lg); line-height: 1; }

/* Mobile: o FAB abre o painel direito como bottom sheet. A folha é o
   #right-panel inteiro e não só o .tasks-panel: os separadores Tarefas/Vendas
   vêm com ela, e o Vendas passa a ter um acesso explícito no telemóvel. */
@media (max-width: 1023px) {
    #chat-screen > #right-panel:has(> .tasks-panel.open) {
        display: grid;
        grid-template-rows: auto minmax(0, 1fr);
        position: fixed;
        left: 0;
        right: 0;
        bottom: 0;
        min-height: 45vh;
        max-height: 70vh;
        overflow: hidden;
        border-top: 1px solid var(--accent);
        border-radius: var(--radius) var(--radius) 0 0;
        background: var(--bg-secondary);
        z-index: var(--z-sheet);
        box-shadow: var(--shadow-lg);
        animation: tp-sheet-up var(--dur-base) var(--ease-out);
        padding-bottom: var(--safe-bottom);
    }
    #chat-screen > #right-panel > .tasks-panel.open,
    #chat-screen > #right-panel:has(> .tasks-panel.open) > .pulse-panel {
        display: flex;
        flex-direction: column;
        min-height: 0;
        overflow: hidden;
        border: none;
        border-radius: 0;
        background: var(--bg-secondary);
    }
    /* Scrim: escurece o chat por baixo da folha e um toque fecha-a (o alvo do
       clique é o #chat-screen — ver initMobileNav no app.js). */
    #chat-screen:has(> #right-panel > .tasks-panel.open)::after {
        content: '';
        position: fixed;
        inset: 0;
        background: rgba(0, 0, 0, 0.5);
        z-index: calc(var(--z-sheet) - 1);
    }
    /* O FAB (--z-fab) ficava tapado pela própria folha (--z-sheet): com ela
       aberta sai de cena, e fecha-se pelo ×, pelo scrim, Escape ou Voltar. */
    #chat-screen:has(> #right-panel > .tasks-panel.open) > .tp-mobile-toggle {
        display: none;
    }
    .tp-mobile-toggle {
        position: fixed;
        right: 16px;
        bottom: calc(80px + var(--safe-bottom));
        width: 48px;
        height: 48px;
        border-radius: 50%;
        background: var(--gradient-brand);
        color: var(--on-brand);
        border: none;
        z-index: var(--z-fab);
        cursor: pointer;
        display: flex;
        align-items: center;
        justify-content: center;
        box-shadow: var(--shadow-md);
    }
    /* (fecho da fase 6) O FAB é fixo e fica mesmo por cima do compositor, à
       direita: com a conversa no fundo tapava o fim da última bolha — as do
       Genisium acabam em botões e rodapés que chegam à margem direita (o
       «Criar: …» das notas da reunião, a última linha do resumo do dia). O
       fim da lista ganha a altura dele (48 px) + respiro; só aqui, onde o
       botão existe (acima de 1023 px a lista fica como estava). Na lista e
       não no contentor: o padding de baixo de um contentor com scroll nem
       sempre entra no scroll. */
    #messages-container > .messages {
        padding-bottom: calc(var(--space-12) + var(--space-2));
    }
}
.tp-mobile-toggle { display: none; }
.mobile-sidebar-toggle { display: none; }
@media (max-width: 1023px) {
    .tp-mobile-toggle { display: flex; }
    .mobile-sidebar-toggle { display: flex; }
}
.tp-mobile-toggle .tp-badge {
    position: absolute;
    top: -4px;
    right: -4px;
    background: var(--error);
    color: #000;
    border-radius: var(--radius-full);
    padding: var(--space-half) var(--space-1h);
    font-size: var(--text-3xs);
    font-weight: 600;
    min-width: 18px;
    text-align: center;
}
.tp-mobile-toggle .tp-badge.hidden { display: none; }

@keyframes tp-sheet-up {
    from { transform: translateY(100%); }
    to   { transform: translateY(0); }
}

/* =============================================================================
   Conversations Sidebar (esquerda) — redesign Teams-like
   ============================================================================= */

.conv-sidebar {
    display: none;
    background: var(--bg-secondary);
    border-right: 1px solid var(--border);
    flex-direction: column;
    overflow: hidden;
    min-height: 0;
}

@media (min-width: 1024px) {
    #chat-screen > .conv-sidebar {
        display: flex;
    }
}

.conv-header {
    padding: var(--space-4) var(--space-4) var(--space-2);
    border-bottom: 1px solid var(--border);
    background: var(--bg-tertiary);
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}
.conv-header-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
}
.conv-title {
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    color: var(--text-secondary);
    margin: 0;
}
.conv-new-btn {
    width: 28px;
    height: 28px;
    border-radius: 50%;
    border: 1px solid var(--border-strong);
    background: var(--bg-primary);
    color: var(--text-bright);
    font-size: var(--text-lg);
    line-height: 1;
    cursor: pointer;
    display: grid;
    place-items: center;
    transition: background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
}
.conv-new-btn:hover {
    background: linear-gradient(135deg, var(--accent-hover), var(--accent));
    border-color: transparent;
    color: var(--on-brand);
    transform: scale(1.05);
}
.conv-picker-hint {
    margin: var(--space-2) 0 0;
    font-size: var(--text-xs);
    color: var(--text-muted);
}
.conv-rail-btn {
    width: 28px;
    height: 28px;
    border-radius: var(--radius-xs);
    border: 1px solid var(--border-strong);
    background: var(--bg-primary);
    color: var(--text-secondary);
    display: grid;
    place-items: center;
    cursor: pointer;
    transition: color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.conv-rail-btn:hover {
    color: var(--accent);
    border-color: var(--accent);
}
@media (max-width: 1023px) {
    .conv-rail-btn { display: none; }   /* em mobile a lista é um ecrã inteiro */
}

/* DS-08: com 44 px de área cada (ver os alvos de toque no topo), dois
   vizinhos não podem disputar a mesma faixa: o intervalo cresce só em touch —
   as duas acções de cada tarefa, o «⋯» ↔ «+» da nova tarefa, o anexar ↔
   ditar do compositor e o «+» ↔ recolher da lista de conversas. Fica depois
   das regras de base dos quatro, para lhes ganhar. */
@media (pointer: coarse) {
    .tp-task-actions { gap: var(--space-2); }
    .tp-quick-create { column-gap: var(--space-3); }
    .btn-attach { margin-right: var(--space-1h); }
    .conv-header-row { gap: var(--space-4); }
}

/* Picker modal */
/* F6-G1 — definições da conversa aberta (static/conversation-settings.js):
   o botão do cabeçalho só nos grupos e nas DMs, e o diálogo com o
   interruptor «O Genisium acompanha» e os três níveis de avisos. */
.conv-settings-btn[hidden] { display: none; }
/* No telemóvel as definições da conversa passam para o menu ⋯ (o nome da
   conversa precisa do espaço, sobretudo com «Chat | Voz» à vista). O #id
   ganha ao .btn-icon (display: flex) sem depender da ordem. */
@media (max-width: 600px) {
    #conv-settings-btn { display: none; }
}
.modal-card.conv-settings-card { text-align: left; }
.conv-settings-sub { margin: 0; font-size: var(--text-sm); color: var(--text-secondary); overflow-wrap: anywhere; }
.conv-settings-sub:empty { display: none; }
.conv-settings-body { display: flex; flex-direction: column; gap: var(--space-3); min-width: 0; }
.conv-settings-section { display: flex; flex-direction: column; gap: var(--space-1); margin: 0; padding: 0; border: 0; min-width: 0; }
.conv-settings-notify > .ops-subhead { padding: 0; margin: 0 0 var(--space-1); }
.conv-settings-option {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    min-height: var(--hit-min);
    padding: var(--space-1) 0;
    border-bottom: 1px solid var(--border);
    cursor: pointer;
}
.conv-settings-option.is-locked { cursor: default; opacity: 0.7; }
.conv-settings-option input { flex: none; width: 18px; height: 18px; margin: 0; }
.conv-settings-option-text { display: flex; flex-direction: column; gap: var(--space-half); min-width: 0; }
.conv-settings-option-label { font-size: var(--text-sm); color: var(--text-primary); }
.conv-settings-lock { padding-bottom: var(--space-1); }

/* F6-G1 — os botões de acção que o Genisium põe nas mensagens dele
   (src/team/bot_post.py): uma fila por grupo de opções; decidido, o grupo
   dá lugar a «✔ <o que se fez> — por X». Componentes .btn do DS. */
.msg-bot-actions { display: flex; flex-direction: column; gap: var(--space-2); margin-top: var(--space-2); }
.msg-bot-actions-row { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.msg-bot-actions .msg-bot-action { white-space: normal; text-align: left; }
.msg-bot-action-done { margin: 0; font-size: var(--text-sm); color: var(--text-secondary); overflow-wrap: anywhere; }
.msg-bot-action-tick { color: var(--accent); font-weight: 600; }
.thread-reply .msg-bot-actions { margin-top: var(--space-1h); }
/* DS-08 — em touch o .btn--sm (32 px) ganha 6 px de alvo em cima e em baixo:
   com 8 px entre filas (ou entre botões que quebram linha) os alvos de dois
   botões pisavam-se. 12 px chega para não se tocarem. */
@media (pointer: coarse) {
    .msg-bot-actions,
    .msg-bot-actions-row { gap: var(--space-3); }
}

/* F6-G2 — «📝 Notas da reunião» (static/meeting-notes.js): o botão do
   compositor (o .btn-attach, só nos grupos), o diálogo e, na mensagem do
   Genisium, as notas originais num <details>. */
.btn-meeting-notes[hidden],
#meeting-notes-menu-item[hidden] { display: none; }
@media (pointer: coarse) {
    .btn-meeting-notes { margin-right: var(--space-1h); }
}
.modal-card.meeting-notes-card {
    max-width: 560px;
    max-height: calc(100dvh - var(--space-8));
    overflow-y: auto;
    text-align: left;
}
.meeting-notes-form { display: flex; flex-direction: column; gap: var(--space-3); min-width: 0; }
.meeting-notes-row { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-3); }
.meeting-notes-form .ops-subhead { margin: 0; padding: 0; }
.meeting-notes-people { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; margin: 0; padding: 0; border: 0; }
.meeting-notes-people-list { display: flex; flex-wrap: wrap; gap: 0 var(--space-4); }
.meeting-notes-person {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    min-height: var(--hit-min);
    font-size: var(--text-sm);
    color: var(--text-primary);
    cursor: pointer;
}
.meeting-notes-person input { flex: none; width: 18px; height: 18px; margin: 0; }
.meeting-notes-text { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }
.meeting-notes-text-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); }
.meeting-notes-text-head [hidden] { display: none; }
.meeting-notes-text textarea { min-height: 160px; width: 100%; }
.meeting-notes-count:empty,
.meeting-notes-status:empty { display: none; }
.meeting-notes-status.is-error { color: var(--error-text); }
.meeting-notes-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: var(--space-2); }
#meeting-notes-dictate.recording { color: var(--error-text); border-color: rgba(var(--error-rgb), 0.6); }
@media (max-width: 600px) {
    .meeting-notes-actions .btn { flex: 1 1 auto; }
}
/* Em touch os dois botões do fim e a data são alvos inteiros de 44 px (DS-08;
   um <input> não tem ::after, por isso a data cresce mesmo). */
@media (pointer: coarse) {
    .meeting-notes-actions .btn,
    #meeting-notes-date { min-height: var(--hit-min); }
}
.message-content details,
.thread-reply details {
    margin-top: var(--space-2);
    padding-left: var(--space-3);
    border-left: 2px solid var(--border);
}
.message-content details > summary,
.thread-reply details > summary {
    display: flex;
    align-items: center;
    min-height: var(--hit-min);
    font-size: var(--text-sm);
    color: var(--text-secondary);
    cursor: pointer;
}
.message-content details > summary::before,
.thread-reply details > summary::before { content: '▸'; margin-right: var(--space-1h); }
.message-content details[open] > summary::before,
.thread-reply details[open] > summary::before { content: '▾'; }
.message-content details > summary::-webkit-details-marker,
.thread-reply details > summary::-webkit-details-marker { display: none; }
.message-content details > summary:focus-visible,
.thread-reply details > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.message-content details blockquote,
.thread-reply details blockquote { overflow-wrap: anywhere; }

.conv-picker-card {
    max-width: 420px;
    width: 92vw;
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    padding: var(--space-4) var(--space-5) var(--space-4);
}
.conv-picker-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
}
.conv-picker-header h3 {
    margin: 0;
    font-size: var(--text-base);
    font-weight: 600;
    color: var(--text-bright);
}
.conv-picker-list {
    list-style: none;
    padding: 0;
    margin: 0;
    max-height: 50vh;
    overflow-y: auto;
}
.conv-picker-item {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    width: 100%;
    padding: var(--space-2) var(--space-3);
    background: none;
    border: 0;
    color: inherit;
    font: inherit;
    text-align: left;
    border-radius: var(--radius-sm);
    cursor: pointer;
    transition: background var(--dur-fast) var(--ease-out);
}
.conv-picker-item:hover,
.conv-picker-item:focus-visible {
    background: var(--bg-glass);
}
.conv-picker-item .conv-avatar {
    width: 32px;
    height: 32px;
    font-size: var(--text-xs);
}
.conv-picker-item .conv-picker-name {
    font-size: var(--text-sm);
    color: var(--text-bright);
}
.conv-picker-item .conv-picker-role {
    margin-left: auto;
    font-size: var(--text-3xs);
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.06em;
}
/* .field (DS-12) a toda a largura da coluna. */
.conv-search { width: 100%; }

.conv-sections {
    flex: 1;
    overflow-y: auto;
    padding: var(--space-2) 0;
    min-height: 0;
}
.conv-loading,
.conv-empty {
    padding: var(--space-5) var(--space-4);
    color: var(--text-secondary);
    font-size: var(--text-sm);
    text-align: center;
}

/* Skeleton rows enquanto a lista carrega — shimmer ténue na cor da marca */
.conv-loading { padding: var(--space-3); }
.conv-skel {
    height: 44px;
    margin-bottom: var(--space-2);
    border-radius: var(--radius-sm);
    background: linear-gradient(
        100deg,
        var(--bg-tertiary) 40%,
        rgba(var(--brand-rgb), 0.07) 50%,
        var(--bg-tertiary) 60%
    );
    background-size: 200% 100%;
    animation: convSkelShimmer 1.4s ease-in-out infinite;
}
@keyframes convSkelShimmer {
    from { background-position: 120% 0; }
    to   { background-position: -80% 0; }
}

/* Estado de erro com retry — substitui o "A carregar..." eterno */
.conv-error {
    margin: var(--space-3);
    padding: var(--space-4) var(--space-4);
    border: 1px dashed var(--border-hover);
    border-radius: var(--radius-sm);
    color: var(--text-secondary);
    font-size: var(--text-xs);
    text-align: center;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    align-items: center;
}
.conv-section-title {
    padding: var(--space-3) var(--space-4) var(--space-1h);
    font-family: var(--font-mono);
    font-size: var(--text-3xs);
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    color: var(--text-muted);
}
.conv-section-title::before {
    content: '';
    display: inline-block;
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: var(--accent);
    margin-right: var(--space-2);
    vertical-align: 1px;
    opacity: 0.7;
}

.conv-list {
    list-style: none;
    padding: 0;
    margin: 0;
}
/* O <li> leva o estado (hover, .active, a barra à esquerda); o <button> lá
   dentro leva a grelha, o clique e o teclado (DS-02). */
.conv-item {
    cursor: pointer;
    border-left: 3px solid transparent;
    transition: background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.conv-item-btn {
    display: grid;
    grid-template-columns: 36px 1fr auto;
    grid-template-rows: auto auto;
    grid-gap: var(--space-half) var(--space-2);
    align-items: center;
    width: 100%;
    padding: var(--space-2) var(--space-3);
    background: none;
    border: 0;
    color: inherit;
    font: inherit;
    text-align: left;
    cursor: pointer;
}
.conv-item-btn:focus-visible {
    outline-offset: -2px;
}
.conv-item:hover {
    background: var(--bg-glass);
}
.conv-item.active {
    background: linear-gradient(90deg, rgba(var(--brand-rgb), 0.08), rgba(var(--brand-rgb), 0.04));
    border-left-color: var(--accent);
}
.conv-avatar {
    position: relative;
    grid-row: 1 / 3;
    width: 36px;
    height: 36px;
    border-radius: 50%;
    display: grid;
    place-items: center;
    font-weight: 600;
    font-size: var(--text-sm);
    color: var(--on-brand);
    background: linear-gradient(135deg, var(--accent-hover), var(--accent));
    flex-shrink: 0;
    user-select: none;
}
/* O Genisium: o mesmo anel com o ponto da marca do .msg-avatar--bot, sobre o
   fundo do header (era um emoji num gradiente — DS-04/LAND-04). */
.conv-avatar.avatar-bot {
    background:
        radial-gradient(circle at center,
            var(--brand) 0 3.5px,
            transparent 4px 10.5px,
            rgba(var(--brand-rgb), 0.55) 11px 12.5px,
            transparent 13px),
        var(--surface-header);
}
.conv-avatar.avatar-group {
    /* Era um gradiente verde-escuro com a tinta escura do acento por cima
       (iniciais ilegíveis). Fica um chip neutro com as iniciais na cor da marca. */
    background: var(--surface-header);
    color: var(--brand);
    box-shadow: inset 0 0 0 1px var(--border-strong);
}
.conv-avatar.avatar-member {
    background: linear-gradient(135deg, var(--brand-hover), var(--brand-deep));
}
.conv-name {
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--text-bright);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
}
.conv-preview {
    font-size: var(--text-2xs);
    color: var(--text-secondary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
    grid-column: 2;
}
.conv-time {
    grid-column: 3;
    grid-row: 1;
    /* DS-06/VIS-20: 10 px a opacity .7 dava 3,6:1. Metadado com significado:
       ≥ 11 px (12 no telemóvel) e --text-secondary sem opacidade (5,86 raised). */
    font-size: var(--text-2xs);
    color: var(--text-secondary);
}
.conv-unread {
    grid-column: 3;
    grid-row: 2;
    background: var(--accent);
    color: var(--on-brand);
    border-radius: var(--radius-full);
    padding: var(--space-half) var(--space-1h);
    font-size: var(--text-3xs);
    font-weight: 600;
    min-width: 18px;
    text-align: center;
}
.conv-unread.hidden { display: none; }
.conv-pinned-tag {
    grid-column: 3;
    grid-row: 1;
    font-size: var(--text-2xs);
    color: var(--accent);
    opacity: 0.7;
}

/* =============================================================================
   Pulso (IA-10) — separador só de leitura do painel direito
   Os cartões são os do chat (.ac-card--static, ambient-cards.js); aqui só o
   enquadramento: loja, «Perguntar no chat», alertas e «Abrir no Pulse».
   ============================================================================= */

.pulse-panel {
    display: none;  /* desktop override abre; em mobile fica escondido por default */
}

.pp-header {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-4);
    border-bottom: 1px solid var(--border);
    background: var(--bg-tertiary);   /* igual ao .tp-header — headers gémeos */
}
.pp-title {
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    color: var(--text-secondary);
    margin: 0;
    flex: 1;
}

.pp-toolbar {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-3) 0;
}
.pp-asof {
    flex: 1;
    font: 400 var(--text-2xs)/1.4 var(--font-mono);
    color: var(--text-muted);
}
.pp-refresh {
    width: 28px;
    height: 28px;
    border-radius: var(--radius-xs);
    background: transparent;
    border: 0;
    color: var(--text-secondary);
    cursor: pointer;
    display: grid;
    place-items: center;
    transition: color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
}
.pp-refresh:hover { color: var(--accent); background: var(--accent-soft); }
.pp-refresh:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.pp-refresh[disabled] { opacity: 0.4; cursor: wait; }

.pp-body {
    flex: 1;
    overflow-y: auto;
    padding: var(--space-2) var(--space-3) var(--space-3);
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    min-height: 0;
}
.pp-store { display: flex; flex-direction: column; gap: var(--space-2); }
.pp-store-name {
    margin: 0;
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    color: var(--text-secondary);
}
.pp-store-error,
.pp-empty {
    margin: 0;
    font-size: var(--text-xs);
    line-height: 1.45;
    color: var(--text-muted);
}
.pp-card {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}
.pp-card .ac-card--static { margin: 0; }
.pp-ask,
.pp-retry {
    align-self: flex-start;
    padding: var(--space-1) var(--space-2);
    border: 1px solid var(--border);
    border-radius: var(--radius-xs);
    background: transparent;
    color: var(--text-secondary);
    font: 500 var(--text-2xs)/1.3 var(--font-sans);
    cursor: pointer;
    transition: color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.pp-ask:hover,
.pp-retry:hover { color: var(--accent); border-color: var(--accent); }
.pp-ask:focus-visible,
.pp-retry:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.pp-card-fallback {
    padding: var(--space-2) var(--space-2);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    font-size: var(--text-xs);
    color: var(--text-primary);
}
.pp-card-fallback-title { margin: 0 0 var(--space-1); font-weight: 600; }
.pp-card-fallback ul { margin: 0; padding-left: var(--space-4); }

.pp-alert-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-1h); }
.pp-alert {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: var(--space-half) var(--space-2);
    padding: var(--space-1h) 0 var(--space-1h) var(--space-2);
    border-left: 2px solid var(--border);
    font-size: var(--text-xs);
    line-height: 1.4;
}
.pp-alert.is-warning { border-left-color: var(--warning); }
.pp-alert.is-critical { border-left-color: rgba(var(--error-rgb), 0.8); }
.pp-alert-sev {
    font: 500 var(--text-3xs)/1.6 var(--font-mono);
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--text-secondary);
}
.pp-alert-title { color: var(--text-bright); }
.pp-alert-meta { grid-column: 2; font-size: var(--text-2xs); color: var(--text-muted); }

.pp-open-pulse {
    display: block;
    /* 10 → 12 em cima/baixo: empate decidido pelo alvo de toque (DS-08). */
    padding: var(--space-3) var(--space-4);
    border-top: 1px solid var(--border);
    font: 500 var(--text-xs)/1.4 var(--font-sans);
    color: var(--accent);
    text-decoration: none;
}
.pp-open-pulse:hover { text-decoration: underline; }
.pp-open-pulse:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* Mobile: 3 ecras navegaveis (sidebar / chat / right-panel) */
@media (max-width: 1023px) {
    #chat-screen:not(.hidden) {
        position: relative;
        overflow: hidden;
    }
    /* Por defeito, sidebar e right-panel ficam escondidos em mobile;
       o JS (MobileNav) comutara adicionando classes `.mobile-view-*` */
    #chat-screen > #conversations-sidebar,
    #chat-screen > #right-panel {
        display: none;
    }
    #chat-screen.mobile-view-sidebar > #conversations-sidebar {
        display: flex;
        position: fixed;
        inset: 0;
        z-index: var(--z-sheet);
        background: var(--bg-primary);
        flex-direction: column;
    }
    #chat-screen.mobile-view-right > #right-panel {
        display: grid;
        grid-template-rows: auto 1fr;
        position: fixed;
        inset: 0;
        z-index: var(--z-sheet);
        background: var(--bg-primary);
    }
    #chat-screen.mobile-view-right > #right-panel > .tasks-panel,
    #chat-screen.mobile-view-right > #right-panel > .pulse-panel {
        display: flex;
        flex-direction: column;
        min-height: 0;
        overflow: hidden;
    }
}

/* =============================================================================
   Commit G — Reactions em mensagens
   ============================================================================= */

.message-reactions {
    position: relative;
    /* Por cima do alvo de 44 px do «N respostas» logo abaixo (DS-08, cresce
       para cima): sem isto um toque no 👍 abria a thread. */
    z-index: var(--z-raised);
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1h);
    margin-top: var(--space-1h);
    align-items: center;
}

.reaction-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: var(--space-half) var(--space-2);
    font-size: var(--text-sm);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    background: var(--bg-elevated);
    color: var(--text-primary);
    cursor: pointer;
    transition: background var(--dur-fast), border-color var(--dur-fast), transform var(--dur-fast);
}

.reaction-chip:hover {
    background: var(--accent-soft);
    border-color: var(--accent);
}

.reaction-chip:active {
    transform: scale(0.96);
}

.reaction-chip .reaction-count {
    font-size: var(--text-2xs);
    opacity: 0.8;
    font-variant-numeric: tabular-nums;
}

/* Picker: respeita o atributo hidden e usa a superfície elevada quando visível */
.emoji-picker[hidden] {
    display: none !important;
}

.emoji-picker {
    /* Abre para cima/esquerda — o anchor é o "Reagir" da .msg-toolbar, colada
       à borda direita da bolha; para baixo/direita transbordava o ecrã */
    position: absolute;
    bottom: 100%;
    right: 0;
    margin-bottom: var(--space-1h);
    display: flex;
    gap: var(--space-1);
    padding: var(--space-1h) var(--space-2);
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-md);
    z-index: var(--z-dropdown);
}

.reaction-preset {
    width: 30px;
    height: 30px;
    padding: 0;
    border: none;
    background: transparent;
    border-radius: var(--radius-sm);
    font-size: var(--text-lg);
    cursor: pointer;
    transition: background var(--dur-fast), transform var(--dur-fast);
}

.reaction-preset:hover {
    background: var(--accent-soft);
    transform: scale(1.15);
}

/* =============================================================================
   Commit H — Thread pane + botão 💬 nas mensagens
   ============================================================================= */

/* .btn-thread dimensiona-se por .mt-btn (toolbar flutuante) */
.btn-thread .thread-count {
    font-size: var(--text-2xs);
    padding: 0 var(--space-1);
    background: var(--accent-soft);
    border-radius: var(--radius-sm);
    color: var(--accent-hover);
    font-variant-numeric: tabular-nums;
}

.thread-pane {
    position: fixed;
    top: 0;
    right: 0;
    width: 380px;
    /* F5-E4 — dvh segue o viewport dinâmico (teclado virtual e barras do
       Safari): com 100vh o pé com a caixa ficava por baixo do teclado. */
    height: 100vh;
    height: 100dvh;
    background: var(--surface-raised);
    border-left: 1px solid var(--border);
    box-shadow: var(--shadow-lg);
    backdrop-filter: blur(12px);
    display: flex;
    flex-direction: column;
    /* DS-17 — abaixo dos modais: era 1050, por cima do fundo do .modal-overlay
       (1000), e ficava clicável fora do focus-trap do "Nova mensagem". */
    z-index: var(--z-thread);
    color: inherit;
}

.thread-pane[hidden] { display: none; }

.thread-pane-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: calc(var(--space-4) + var(--safe-top, 0px)) var(--space-4) var(--space-4);
    border-bottom: 1px solid var(--border);
}

.thread-pane-head h3 {
    margin: 0;
    font-size: var(--text-base);
}

.thread-parent {
    padding: var(--space-3) var(--space-4);
    background: rgba(255, 255, 255, 0.03);
    border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}

.thread-parent-head {
    font-size: var(--text-xs);
    opacity: 0.75;
    margin-bottom: var(--space-1);
}

.thread-parent-body {
    font-size: var(--text-md);
    /* HTML do formatter (marked): pre-wrap mostrava as quebras entre blocos */
    white-space: normal;
    word-break: break-word;
}

.thread-body {
    flex: 1;
    overflow-y: auto;
    padding: var(--space-3) var(--space-4);
}

.thread-count-label {
    font-size: var(--text-2xs);
    text-transform: uppercase;
    letter-spacing: 0.5px;
    opacity: 0.6;
    margin: 0 0 var(--space-2);
}

.thread-replies {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.thread-reply {
    padding: var(--space-2) var(--space-2);
    background: rgba(255, 255, 255, 0.04);
    border-radius: var(--radius-sm);
}

.thread-reply-head {
    display: flex;
    justify-content: space-between;
    font-size: var(--text-xs);
    margin-bottom: var(--space-1);
}

.thread-reply-time {
    opacity: 0.6;
}
/* F6-G4 — «editada» ao lado da hora; Editar/Apagar do autor por baixo. */
.thread-reply-head .message-edited { margin-left: auto; padding-left: var(--space-2); }
.thread-reply-own {
    display: flex;
    justify-content: flex-end;
    gap: var(--space-2);
    margin-top: var(--space-1);
}
.thread-reply--deleted { background: transparent; border: 1px dashed var(--border); }
.thread-reply .msg-edit { margin-top: var(--space-1); }

.thread-reply-body {
    font-size: var(--text-md);
    /* HTML do formatter (marked): pre-wrap mostrava as quebras entre blocos */
    white-space: normal;
    word-break: break-word;
}

.thread-empty {
    opacity: 0.5;
    font-size: var(--text-sm);
    text-align: center;
    margin-top: var(--space-6);
}

.thread-pane-foot {
    padding: var(--space-3) var(--space-4) calc(var(--space-3) + var(--safe-bottom, 0px));
    border-top: 1px solid var(--border);
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}

/* Campo e «Enviar» são o .field e o .btn--primary (DS-12). */
.thread-compose {
    display: flex;
    gap: var(--space-2);
    align-items: flex-end;
}
.thread-compose textarea { flex: 1; resize: none; }
.thread-hint {
    margin: 0;
    font-size: var(--text-xs);
    color: var(--text-muted);
}
.thread-retry { display: flex; margin: var(--space-2) auto 0; }

/* Abaixo da grelha de três colunas (o mesmo corte do resto da app) a thread é
   um ecrã inteiro: entre 769 e 1023 px ficava a 380 px por cima do chat. */
@media (max-width: 1023px) {
    .thread-pane {
        width: 100%;
        left: 0;
        right: 0;
    }
}

/* =============================================================================
   Commit I — Presence dots + typing banner
   ============================================================================= */

.conv-presence-dot {
    position: absolute;
    bottom: -1px;
    right: -1px;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    border: 2px solid var(--bg-secondary);
    background: rgba(255, 255, 255, 0.25);
    pointer-events: none;
}

/* Sem halo: é um ponto por CONVERSA na sidebar — com 15 contactos online eram
   15 halos. A cor sólida sobre a superfície nova já lê. */
.conv-presence-dot.online {
    background: var(--success);
}

.conv-presence-dot.away {
    background: var(--warning);
}

.conv-presence-dot.offline {
    background: rgba(255, 255, 255, 0.18);
    border-color: rgba(0, 0, 0, 0.2);
}

/* F6-G4 — o estado manual por cima da presença: «Ocupado» a vermelho e «Não
   incomodar» a vermelho com o traço branco (como no Teams). */
.conv-presence-dot.busy,
.conv-presence-dot.dnd {
    background: var(--error);
}
.conv-presence-dot.dnd::after,
.member-status-dot[data-status="dnd"]::after {
    content: '';
    position: absolute;
    left: 20%;
    right: 20%;
    top: calc(50% - 1px);
    height: 2px;
    border-radius: var(--radius-xs);
    background: var(--text-bright);
}
/* (revisão) classe própria: a `.status-dot` é o ponto de ligação do header
   (7 px, absoluto, cinzento sem ligação) e esta regra pintava-o de verde e
   tirava-o do sítio. */
.member-status-dot {
    position: relative;
    flex: none;
    width: 12px;
    height: 12px;
    border-radius: 50%;
    background: var(--success);
}
.member-status-dot[data-status="busy"],
.member-status-dot[data-status="dnd"] { background: var(--error); }
.conv-status {
    font-weight: 600;
    color: var(--error-text);
}
.conv-status::after { content: ' ·'; color: var(--text-secondary); font-weight: 400; }
.header-peer-status {
    flex: none;
    max-width: 40%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    padding: var(--space-half) var(--space-2);
    border-radius: var(--radius-full);
    background: rgba(var(--error-rgb), 0.15);
    color: var(--error-text);
    font: 600 var(--text-2xs)/1.4 var(--font-sans);
}
/* (fecho da fase 6) No telemóvel o estado passa para debaixo do nome, como
   um subtítulo. Ao lado dele, com o max-width de 40 %, a 375 px só se lia
   «Ocupado até às…»: a hora, que é o que interessa, ficava cortada. Por baixo
   tem a largura toda do título; o nome continua com reticências se não
   couber. Só com o estado à vista — sem ele o cabeçalho é o de sempre. */
@media (max-width: 600px) {
    .header-left:has(> .header-peer-status:not([hidden])) {
        display: grid;
        grid-template-columns: auto minmax(0, 1fr);
        align-items: center;
        column-gap: var(--space-2);
        row-gap: 0;
    }
    .header-left:has(> .header-peer-status:not([hidden])) > #mobile-menu-btn { grid-row: 1 / span 2; }
    .header-left:has(> .header-peer-status:not([hidden])) > .header-title {
        grid-area: 1 / 2;
        align-self: end;
        /* com o 1 do título o overflow cortava as pernas do g/p mesmo por
           cima da pastilha; em duas linhas há altura para elas (44 px) */
        line-height: 1.25;
    }
    .header-left > .header-peer-status:not([hidden]) {
        grid-area: 2 / 2;
        align-self: start;
        justify-self: start;
        max-width: 100%;
        margin-top: var(--space-half);
    }
}
.status-option { align-items: center; }
.status-duration[hidden] { display: none; }
.status-actions { justify-content: flex-end; }
.status-current { margin: 0 0 var(--space-2); }

.typing-banner {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-1h) var(--space-4);
    font-size: var(--text-xs);
    color: rgba(255, 255, 255, 0.75);
    background: rgba(var(--brand-rgb), 0.06);
    border-bottom: 1px solid rgba(var(--brand-rgb), 0.15);
    font-style: italic;
}

.typing-banner[hidden] { display: none; }

.typing-banner-dots {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
}

.typing-banner-dots span {
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: var(--accent);
    animation: typing-dot 1s infinite ease-in-out;
}

.typing-banner-dots span:nth-child(2) { animation-delay: 0.2s; }
.typing-banner-dots span:nth-child(3) { animation-delay: 0.4s; }

@keyframes typing-dot {
    0%, 80%, 100% { opacity: 0.3; transform: translateY(0); }
    40% { opacity: 1; transform: translateY(-3px); }
}

/* =============================================================================
   Commit J — Attachments inline + lightbox + drag-drop
   ============================================================================= */

.message-attachments {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-top: var(--space-2);
}

.msg-attach {
    display: inline-flex;
    align-items: center;
    text-decoration: none;
    color: inherit;
    border-radius: var(--radius-sm);
    overflow: hidden;
    max-width: 100%;
}

.msg-attach-image img {
    display: block;
    max-width: 400px;
    max-height: 320px;
    border-radius: var(--radius-sm);
    cursor: zoom-in;
    transition: transform var(--dur-base);
}

.msg-attach-image img:hover {
    transform: scale(1.01);
}

.msg-attach-video {
    max-width: 500px;
    max-height: 360px;
    border-radius: var(--radius-sm);
    background: #000;
}

.msg-attach-audio {
    width: 320px;
    max-width: 100%;
}

.msg-attach-file {
    gap: var(--space-2);
    padding: var(--space-2) var(--space-3);
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid rgba(255, 255, 255, 0.1);
    font-size: var(--text-sm);
    transition: background var(--dur-fast), border-color var(--dur-fast);
}

.msg-attach-file:hover {
    background: rgba(var(--brand-rgb), 0.08);
    border-color: rgba(var(--brand-rgb), 0.35);
}

.msg-attach-icon {
    font-size: var(--text-xl);
}

.msg-attach-name {
    max-width: 220px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.msg-attach-size {
    opacity: 0.55;
    font-size: var(--text-2xs);
    font-variant-numeric: tabular-nums;
    margin-left: auto;
}

.lightbox-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.85);
    backdrop-filter: blur(8px);
    display: none;
    align-items: center;
    justify-content: center;
    z-index: var(--z-modal);
    padding: var(--space-8);
}

.lightbox-overlay.open { display: flex; }

.lightbox-overlay img {
    max-width: 90vw;
    max-height: 90vh;
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-lg);
}

/* O × por cima da foto é o .btn-close--on-media (DS-12), com 44 px. */
.lightbox-overlay > .btn-close {
    position: absolute;
    top: var(--space-5);
    right: var(--space-6);
    width: var(--hit-min);
    height: var(--hit-min);
}

.drop-zone-active {
    outline: 2px dashed var(--accent);
    outline-offset: -8px;
    background: rgba(var(--brand-rgb), 0.05);
}

/* =============================================================================
   Commit K — Task shortcut from message
   ============================================================================= */

/* .btn-task-from-msg dimensiona-se por .mt-btn (toolbar flutuante) */

.task-shortcut-modal {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.65);
    backdrop-filter: blur(6px);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: var(--z-modal);
    padding: var(--space-5);
}

.task-shortcut-modal[hidden] { display: none; }

.task-shortcut-card {
    width: 100%;
    max-width: 480px;
    background: var(--surface-raised);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-lg);
    color: inherit;
    overflow: hidden;
    /* E3 — com «Repetir» e os passos o diálogo cresce: rola por dentro (375 px) */
    max-height: 100%;
    display: flex;
    flex-direction: column;
}
.task-shortcut-card .task-shortcut-form { overflow-y: auto; min-height: 0; }

.task-shortcut-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--space-4) var(--space-5);
    border-bottom: 1px solid var(--border);
}

.task-shortcut-head h3 {
    margin: 0;
    font-size: var(--text-base);
}

.task-shortcut-form {
    padding: var(--space-4) var(--space-5);
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

.task-shortcut-field {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    font-size: var(--text-xs);
    color: var(--text-secondary);
}

.task-shortcut-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--space-2);
}
/* E3 — o display:flex do campo ganhava ao [hidden] do browser */
.task-shortcut-field[hidden],
.ts-repeat[hidden] { display: none; }
.ts-repeat {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}
.ts-hint {
    margin: 0;
    font-size: var(--text-2xs);
    color: var(--text-secondary);
    line-height: 1.4;
}
.ts-steps {
    margin: 0;
    padding: var(--space-2) var(--space-3) var(--space-3);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    min-width: 0;
}
.ts-steps legend {
    padding: 0 var(--space-1);
    font-size: var(--text-xs);
    color: var(--text-secondary);
}
.ts-steps-count { color: var(--accent); font-weight: 600; }
.ts-steps-list {
    list-style: none;
    margin: 0 0 var(--space-2);
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}
.ts-step {
    display: grid;
    grid-template-columns: auto 1fr auto auto auto;
    align-items: center;
    gap: var(--space-1);
    font-size: var(--text-sm);
}
.ts-step-text { min-width: 0; word-break: break-word; line-height: 1.35; }
.ts-step.done .ts-step-text { color: var(--text-muted); text-decoration: line-through; }
.ts-step-bullet { color: var(--text-muted); padding: 0 var(--space-1); }
.ts-step-check { width: 18px; height: 18px; margin: 0; cursor: pointer; }
.ts-step-btn {
    width: 28px;
    height: 28px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    background: transparent;
    border: 1px solid transparent;
    border-radius: var(--radius-xs);
    color: var(--text-secondary);
    font: inherit;
    line-height: 1;
    cursor: pointer;
}
.ts-step-btn:hover:not(:disabled) { color: var(--accent); background: var(--accent-soft); }
.ts-step-remove:hover:not(:disabled) { color: var(--error-text); background: rgba(var(--error-rgb), 0.12); }
.ts-step-btn:disabled { opacity: 0.35; cursor: default; }
.ts-step-add {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: var(--space-2);
}
/* Em touch: cada passo numa linha de 44 px e os ↑ ↓ × a 44 px de distância
   (36 + o intervalo), para os 44 × 44 de área não se sobreporem ao vizinho. */
@media (pointer: coarse) {
    .ts-steps-list { gap: 0; }
    .ts-step { min-height: var(--hit-min); gap: var(--space-2); }
    .ts-step-btn { width: 36px; height: 36px; }
    .tp-steps { gap: 0; }
    .tp-step label { min-height: var(--hit-min); align-items: center; }
}

.task-shortcut-form textarea { min-height: 40px; }

.task-shortcut-foot {
    display: flex;
    gap: var(--space-2);
    justify-content: flex-end;
    margin-top: var(--space-2);
}

.task-shortcut-status {
    margin: var(--space-1h) 0 0;
    font-size: var(--text-xs);
    padding: var(--space-1h) var(--space-2);
    border-radius: var(--radius-xs);
}

.task-shortcut-status.success {
    background: rgba(var(--success-rgb), 0.15);
    color: var(--success);
}

.task-shortcut-status.error {
    background: rgba(var(--error-rgb), 0.15);
    color: var(--error-text);
}

.task-shortcut-status.warning {
    background: rgba(var(--warning-rgb), 0.15);
    color: var(--warning);
}

/* =============================================================================
   Commit L — Read receipts em DMs ("✓✓ visto")
   ============================================================================= */

.msg-read-receipt {
    margin-top: var(--space-1);
    padding: var(--space-half) var(--space-1h);
    font-size: var(--text-2xs);
    color: rgba(var(--success-rgb), 0.9);
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    align-self: flex-end;
    opacity: 0.85;
}

.msg-read-receipt-label {
    font-size: var(--text-2xs);
    letter-spacing: 0.2px;
    text-transform: lowercase;
}

/* =============================================================================
   Commit N — @mentions: toast + autocomplete
   ============================================================================= */

/* Autocomplete popup */
.mention-autocomplete {
    position: fixed;
    background: var(--surface-raised);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-lg);
    backdrop-filter: blur(10px);
    overflow: hidden;
    z-index: var(--z-popover);
}

.mention-autocomplete[hidden] { display: none; }

.mention-ac-item {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-3);
    cursor: pointer;
    transition: background var(--dur-fast);
}

.mention-ac-item:hover,
.mention-ac-item.selected {
    background: rgba(var(--brand-rgb), 0.12);
}

.mention-ac-avatar {
    width: 30px;
    height: 30px;
    border-radius: 50%;
    background: var(--gradient-brand);
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--on-brand);
    font-weight: 600;
    font-size: var(--text-sm);
    flex-shrink: 0;
}

.mention-ac-text {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.mention-ac-name {
    font-size: var(--text-sm);
    font-weight: 600;
    color: #fff;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.mention-ac-hint {
    font-size: var(--text-2xs);
    opacity: 0.6;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Colega virtual (bot) — destacado com glow accent */
.mention-ac-item-bot {
    background: linear-gradient(90deg, rgba(var(--brand-rgb), 0.06), transparent);
}

.mention-ac-item-bot.selected,
.mention-ac-item-bot:hover {
    background: linear-gradient(90deg, rgba(var(--brand-rgb), 0.22), rgba(var(--brand-rgb), 0.08));
}

.mention-ac-avatar-bot {
    background:
        radial-gradient(circle at center,
            var(--brand) 0 3px,
            transparent 3.5px 9px,
            rgba(var(--brand-rgb), 0.55) 9.5px 10.5px,
            transparent 11px),
        var(--surface-header);
    color: transparent;
    font-size: 0;
}

.mention-ac-bot-tag {
    display: inline-block;
    margin-left: var(--space-1h);
    padding: var(--space-half) var(--space-1h);
    font-size: var(--text-3xs);
    font-weight: 600;
    letter-spacing: 0.6px;
    background: rgba(var(--brand-rgb), 0.22);
    color: var(--accent);
    border-radius: var(--radius-xs);
    vertical-align: 2px;
}

/* =============================================================================
   Ambient Cards — cartões de vendas com chuva de glifos (camada sobre a esfera).
   A cor vem dos tokens --brand-* do :root (o ambient-cards.js lê o --brand e o
   --brand-spark para a chuva em canvas).
   ============================================================================= */
#ambient-cards {
    position: relative;
    pointer-events: none;
    z-index: var(--z-cards);
    --ac-card-bg: rgba(var(--abyss-rgb), 0.55);
    --ac-card-border: rgba(var(--brand-rgb), 0.35);
}
@media (min-width: 1024px) {
    #chat-screen > #ambient-cards { grid-area: main; }
}
#ambient-cards.ac-hidden { display: none; }

/* Coluna esquerda — espelho do painel de voz (right:16/bottom:88).
   column-reverse: o card mais recente entra em BAIXO e empurra os antigos
   para cima; alturas variáveis empilham sem sobreposição. */
.ac-column {
    position: absolute;
    left: 16px;
    bottom: 88px;
    width: 232px;
    max-width: 42vw;
    display: flex;
    flex-direction: column-reverse;
    gap: var(--space-3);
    pointer-events: none;
}

.ac-card {
    position: relative;
    width: 100%;
    pointer-events: none;
    overflow: hidden;
    color: var(--text-bright);
    font-family: var(--font-mono);
    background: var(--ac-card-bg);
    border: 1px solid var(--ac-card-border);
    border-radius: var(--radius-sm);
    /* Só o inset. O halo externo saía para fora do cartão e somava-se ao dos
       vizinhos — com 3 ou 4 cartões empilhados, a coluna virava uma nódoa
       de cor. O inset fica contido e continua a dar o brilho de tubo. */
    box-shadow: inset 0 0 24px rgba(var(--brand-rgb), 0.06);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    opacity: 0;
    transform: translateY(10px) scale(0.98);
    transition: opacity var(--dur-slow) var(--ease-out, ease), transform var(--dur-slow) var(--ease-out, ease);
}
.ac-card.ac-in { opacity: 1; transform: none; animation: acFlicker 2.4s steps(1, end) 1; }
.ac-card.ac-dissolving { animation: acDissolve var(--dur-slow) forwards; }

.ac-rain {
    position: absolute; inset: 0; width: 100%; height: 100%;
    opacity: 0.22; z-index: var(--z-base); pointer-events: none;
}
.ac-scanlines {
    position: absolute; inset: 0; z-index: var(--z-raised); pointer-events: none;
    background: repeating-linear-gradient(to bottom,
        rgba(0, 0, 0, 0) 0, rgba(0, 0, 0, 0) 2px,
        rgba(0, 0, 0, 0.22) 3px, rgba(0, 0, 0, 0) 4px);
    opacity: 0.5;
    animation: acScanline 6s linear infinite;
}
.ac-inner { position: relative; z-index: calc(var(--z-raised) + 1); padding: var(--space-3); }
.ac-head { margin-bottom: var(--space-2); }
/* Sem text-shadow: mono + maiúsculas + accent já lê como título. Era um halo
   por cartão. */
.ac-title {
    font-size: var(--text-xs); letter-spacing: 0.06em; text-transform: uppercase;
    color: var(--brand);
}
.ac-caption { font-size: var(--text-2xs); color: var(--text-secondary); margin-top: var(--space-half); }
.ac-value {
    font-size: var(--text-3xl); font-weight: 500; line-height: 1.1; color: var(--brand-spark);
    /* Uma camada em vez de duas — o número é o herói do cartão, mantém glow. */
    text-shadow: 0 0 8px var(--brand);
    font-variant-numeric: tabular-nums; white-space: nowrap;
    /* nunca cortar um número a meio: reticências, e o JS encolhe (--long) */
    max-width: 100%; overflow: hidden; text-overflow: ellipsis;
}
.ac-value-sm { font-size: var(--text-xl); }
.ac-value--long { font-size: var(--text-xl); }
.ac-neutral { color: var(--text-secondary); }
.ac-row-qty { color: var(--text-secondary); font-size: var(--text-2xs); white-space: nowrap; }
.ac-anomaly-items { margin-top: var(--space-1); }
.ac-sub { font-size: var(--text-xs); color: var(--text-bright); margin-top: var(--space-1); display: flex; gap: var(--space-1h); align-items: center; flex-wrap: wrap; }
.ac-dot { color: var(--text-secondary); }
.ac-badge { margin-top: var(--space-2); font-size: var(--text-3xs); color: var(--warning); text-transform: uppercase; letter-spacing: 0.05em; }

.ac-spark { width: 100%; height: 46px; margin: var(--space-1h) 0 var(--space-half); display: block; overflow: visible; }
.ac-spark-line { stroke: var(--brand); stroke-width: 2; vector-effect: non-scaling-stroke; }
.ac-spark-dot { fill: var(--brand-spark); }
.ac-spark--empty { height: 0; margin: 0; }
.ac-spark-value { fill: var(--brand-spark); font-size: var(--text-3xs); font-family: inherit; }

.ac-list { list-style: none; margin: 0; padding: 0; }
.ac-row { display: flex; align-items: center; gap: var(--space-2); font-size: var(--text-xs); padding: var(--space-half) 0; }
.ac-rank { color: var(--brand); width: 14px; text-align: right; opacity: 0.8; }
.ac-row-title { flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--text-bright); }
.ac-row-val { color: var(--brand-spark); font-variant-numeric: tabular-nums; }
/* Cartões de operações: nome + detalhe empilhados, valor à direita sem partir */
.ac-row--ops { align-items: flex-start; padding: var(--space-1) 0; }
.ac-row-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.ac-row-sub { color: var(--text-secondary); font-size: var(--text-3xs); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ac-row--ops .ac-row-val { flex-shrink: 0; white-space: nowrap; }

/* Barras horizontais (vendas por país) */
.ac-bars { display: flex; flex-direction: column; gap: var(--space-1); }
.ac-bar-row { display: flex; align-items: center; gap: var(--space-1h); font-size: var(--text-3xs); }
.ac-bar-lbl { width: 72px; color: var(--text-secondary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ac-bar-track { flex: 1; height: 7px; background: rgba(var(--brand-rgb), 0.12); border-radius: var(--radius-2xs); overflow: hidden; }
.ac-bar-fill { display: block; height: 100%; background: var(--brand); opacity: 0.85; border-radius: var(--radius-2xs); transition: width var(--dur-slow) var(--ease-out, ease); }
.ac-bar-val { min-width: 52px; text-align: right; color: var(--brand-spark); font-variant-numeric: tabular-nums; font-size: var(--text-3xs); }

/* Deltas semana vs semana */
.ac-up { color: var(--brand); }
.ac-down { color: var(--error-text); text-shadow: 0 0 8px rgba(var(--error-rgb), 0.4); }
.ac-delta-rows { margin-top: var(--space-1); }

/* Comparação de lojas: 2ª linha mais ténue + legenda */
.ac-spark-line-2 { stroke: var(--text-secondary); stroke-dasharray: 2 3; }
.ac-legend-chip { width: 10px; height: 2px; background: var(--brand); flex: none; }
.ac-legend-chip-2 { background: var(--text-secondary); }

/* Hero: produto destaque com foto */
.ac-hero { display: flex; align-items: center; gap: var(--space-2); margin-bottom: var(--space-1h); }
.ac-hero-img {
    width: 52px; height: 52px; object-fit: cover; flex: none;
    border-radius: var(--radius-xs); border: 1px solid var(--ac-card-border);
    /* Foto tingida na cor da marca (gelo): o sépia (~35°) rodado até ao ciano.
       Era hue-rotate(70deg), que a pintava de verde Matrix. */
    filter: sepia(1) hue-rotate(160deg) saturate(1.4) brightness(0.95);
}
.ac-hero-info { min-width: 0; }
.ac-hero-title {
    font-size: var(--text-sm); color: var(--brand-spark); line-height: 1.25;
    overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}
.ac-meta { margin-top: var(--space-1h); border-top: 1px solid rgba(var(--brand-rgb), 0.15); padding-top: var(--space-1); }

/* Anomalia proactiva: âmbar (warning) / vermelho (critical) */
.ac-card--alert { border-color: rgba(var(--warning-rgb), 0.6); box-shadow: 0 0 18px rgba(var(--warning-rgb), 0.22), inset 0 0 24px rgba(var(--warning-rgb), 0.06); }
.ac-card--alert .ac-title { color: var(--warning); text-shadow: 0 0 6px rgba(var(--warning-rgb), 0.4); }
.ac-card--critical { border-color: rgba(var(--error-rgb), 0.65); box-shadow: 0 0 18px rgba(var(--error-rgb), 0.25), inset 0 0 24px rgba(var(--error-rgb), 0.07); }
.ac-card--critical .ac-title { color: var(--error-text); text-shadow: 0 0 6px rgba(var(--error-rgb), 0.45); }
.ac-anomaly-title { font-size: var(--text-sm); color: var(--brand-spark); line-height: 1.35; }
.ac-anomaly-desc { font-size: var(--text-2xs); color: var(--text-bright); margin-top: var(--space-1); line-height: 1.4; }
.ac-anomaly-vals { color: var(--warning); }

/* Cartão de aprovação: a dica do código é a linha que o utilizador procura */
.ac-approval-hint { color: var(--warning); margin-top: var(--space-1); font-weight: 500; }
/* Botões do cartão (IA-02): o cartão é uma camada sem eventos (não tapa o
   chat); só a fila de botões recebe toques. */
.ac-actions { display: flex; gap: var(--space-1h); margin-top: var(--space-2); pointer-events: auto; }
.ac-btn {
    pointer-events: auto;
    flex: 1;
    min-height: 32px;
    padding: var(--space-1h) var(--space-2);
    border-radius: var(--radius-xs);
    border: 1px solid rgba(var(--warning-rgb), 0.6);
    background: rgba(var(--warning-rgb), 0.12);
    color: var(--warning);
    font: 500 var(--text-2xs)/1.2 var(--font-mono);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    cursor: pointer;
}
.ac-btn:hover, .ac-btn:focus-visible { background: rgba(var(--warning-rgb), 0.22); outline: none; }
.ac-btn.is-armed { background: var(--warning); color: var(--on-brand); }
.ac-btn--ghost { border-color: rgba(var(--brand-rgb), 0.4); background: transparent; color: var(--text-primary); }
.ac-btn:disabled { opacity: 0.6; cursor: wait; }
.ac-rev { font-size: var(--text-3xs); letter-spacing: 0.05em; text-transform: uppercase; }
.ac-rev--irreversible { color: var(--error-text); font-weight: 600; }
.ac-rev--reversible { color: var(--text-secondary); }
.ac-preview { margin: var(--space-1) 0 0; padding: 0; list-style: none; font-size: var(--text-2xs); color: var(--text-primary); }
.ac-preview li { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ac-code--busy { border-color: rgba(var(--warning-rgb), 0.6); }
.ac-code--busy .ac-code-digit { color: var(--warning); font-size: var(--text-2xl); }

/* O código é o herói do cartão de aprovação: é o que se lê e se diz. */
.ac-approval-main { display: flex; align-items: center; gap: var(--space-3); margin-bottom: var(--space-1h); }
.ac-approval-info { min-width: 0; }
.ac-code {
    flex: none;
    width: 56px; height: 56px;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    border-radius: var(--radius-sm);
    border: 1px solid rgba(var(--warning-rgb), 0.6);
    background: rgba(var(--warning-rgb), 0.06);
}
.ac-code-label { font-size: var(--text-3xs); letter-spacing: 0.16em; text-transform: uppercase; color: rgba(var(--warning-rgb), 0.8); }
.ac-code-digit {
    font-size: var(--text-3xl); line-height: 1; font-weight: 600;
    color: var(--warning); text-shadow: 0 0 14px rgba(var(--warning-rgb), 0.5);
    font-variant-numeric: tabular-nums;
}
.ac-code--done { border-color: rgba(var(--brand-rgb), 0.55); background: rgba(var(--brand-rgb), 0.06); }
.ac-code--done .ac-code-digit { color: var(--brand); text-shadow: 0 0 12px rgba(var(--brand-rgb), 0.45); }
.ac-code--fail { border-color: rgba(var(--error-rgb), 0.6); background: rgba(var(--error-rgb), 0.06); }
.ac-code--fail .ac-code-digit { color: var(--error-text); text-shadow: none; }
/* A decisão à espera respira (brilho interior — o cartão corta o exterior). */
.ac-card--pinned::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: calc(var(--z-raised) + 2);
    border-radius: inherit;
    pointer-events: none;
    box-shadow: inset 0 0 22px rgba(var(--warning-rgb), 0.35);
    animation: ac-pin-breathe 3.8s ease-in-out infinite;
}
@keyframes ac-pin-breathe { 0%, 100% { opacity: 0.35; } 50% { opacity: 0.9; } }

@keyframes acScanline { from { background-position: 0 0; } to { background-position: 0 200px; } }
@keyframes acFlicker { 0% { opacity: 0.55; } 8% { opacity: 1; } 12% { opacity: 0.7; } 20% { opacity: 1; } 100% { opacity: 1; } }
@keyframes acDissolve { to { opacity: 0; transform: translateY(-6px) scale(0.98); filter: blur(4px); } }

/* Entre 768 e 1023 px o chat não tem a grelha de 3 colunas: a coluna de
   cartões ancora ao viewport (o contentor tem altura 0). Abaixo de 768 px
   não há espaço para uma coluna de 232 px ao lado do feed. */
@media (min-width: 768px) and (max-width: 1023px) {
    #chat-screen > #ambient-cards { position: fixed; left: 0; bottom: 0; width: 0; height: 0; }
}
@media (max-width: 767px) {
    #chat-screen > #ambient-cards { display: none !important; }
}


/* =============================================================================
   Voz — orbe e painel de estado

   Este bloco existe porque até aqui NÃO havia uma única linha de CSS para a
   orbe: tudo vinha de style.cssText em static/voice-orb.js e
   static/realtime-voice.js. Estilos inline ganham sempre a regras de folha sem
   !important, portanto era impossível ajustar a orbe a partir daqui.

   Fase 0: extracção pura, sem mudança de aparência. A geometria (que depende do
   tamanho calculado em JS) passa pela custom property --vo-size.
   ============================================================================= */

/* ESCADA DE Z-INDEX — e um bug a sério que estava aqui escondido.
   Inventário antes: modais 1000, bottom sheets mobile 40, este overlay 9000,
   #rtv-panel 9999. Ou seja: abrir um modal ou o painel de tarefas DURANTE a
   voz dava ecrã preto, porque o overlay tapava tudo o que estava entre 40 e
   1000. Não era hipotético.
   Agora a voz vive abaixo de 1000 e os modais ficam naturalmente por cima.
   Os degraus (--z-orb, --z-chat-header, --z-sheet, --z-rtv-panel) estão na
   escada completa do :root (DS-17). */

#voice-orb-overlay {
    position: absolute;
    inset: 0;
    z-index: var(--z-orb);
    pointer-events: none;
    opacity: 0;
    /* SEM background. O scrim de ecrã inteiro era o verdadeiro culpado de a
       voz tapar o chat — e estava invertido: 0,96 de opacidade nas BORDAS
       contra 0,72 no centro, ou seja escurecia 96% exactamente onde o chat
       vive (header, sidebar, input) e menos onde a orbe está. Passou a ser um
       poço circular local, ver .vo-pool. */
    transition: opacity var(--dur-slow) ease;
}

/* Centrada no anfitrião — o palco do ecrã de voz decide o tamanho (o JS só
   escreve --vo-size a partir da caixa dele). */
.vo-orb {
    position: absolute;
    left: 50%;
    top: 50%;
    width: var(--vo-size, 480px);
    height: var(--vo-size, 480px);
    transform: translate(-50%, -50%);
}

/* O poço: escuro no centro, porque o additive blending precisa de fundo para
   ler, e transparente muito antes de tocar seja no que for. */
.vo-pool {
    position: absolute;
    left: 50%;
    top: 50%;
    width: 190%;
    height: 190%;
    transform: translate(-50%, -50%);
    border-radius: 50%;
    pointer-events: none;
    background: radial-gradient(circle,
        rgba(var(--abyss-rgb), 0.88) 0%,
        rgba(var(--abyss-rgb), 0.72) 30%,
        rgba(var(--abyss-rgb), 0.34) 48%,
        rgba(var(--abyss-rgb), 0.00) 66%);
    transition: opacity var(--dur-slow) ease;
}

/* O header tem de subir acima da orbe, senão o botão que DESLIGA a voz fica
   por baixo dela e clica-se às cegas. */
.chat-header { position: relative; z-index: var(--z-chat-header); }

/* Halo difuso (bloom) ATRÁS da nuvem — dá vida e, com a máscara radial do
   canvas, elimina a sensação de "esfera dentro de um quadrado". */
.vo-glow {
    position: absolute;
    left: 50%;
    top: 50%;
    width: calc(var(--vo-size, 480px) * 1.6);
    height: calc(var(--vo-size, 480px) * 1.6);
    pointer-events: none;
    border-radius: 50%;
    transform: translate(-50%, -50%) scale(1);
    opacity: 0.5;
    will-change: transform, opacity;
    background: radial-gradient(circle,
        rgba(var(--brand-rgb), 0.50) 0%,
        rgba(var(--voice-user-rgb), 0.24) 44%,
        rgba(0, 0, 0, 0) 72%);
    filter: blur(30px);
}

/* Máscara radial: o quadrado do canvas desvanece numa borda circular suave.
   NÃO mexer em width/height aqui — o voice-orb.js escreve-os inline a partir
   da caixa do palco (sizeCanvas). */
.vo-canvas {
    display: block;
    position: relative;
    -webkit-mask-image: radial-gradient(circle, #000 66%, rgba(0, 0, 0, 0) 84%);
    mask-image: radial-gradient(circle, #000 66%, rgba(0, 0, 0, 0) 84%);
}

/* Painel de estado da voz — "falas com a criatura, abre-se uma janela para a
   noite". Estava em cssText no realtime-voice.js:85-90. */
#rtv-panel {
    position: fixed;
    right: 16px;
    bottom: 88px;
    z-index: var(--z-rtv-panel);
    max-width: 320px;
    background: rgba(var(--abyss-rgb), 0.94);
    color: var(--text-primary);
    border: 1px solid rgba(var(--brand-rgb), 0.35);
    border-radius: var(--radius-md);
    padding: var(--space-3) var(--space-4);
    font: var(--text-sm)/1.4 var(--font-sans);
    box-shadow: var(--shadow-lg), var(--shadow-glow);
    backdrop-filter: blur(10px);
    display: none;
}

/* =============================================================================
   O olho do agente — marca e estado no header

   Substitui o SVG de ~130 <circle> que era o logótipo (12,4 KB de markup) e o
   antigo #character-stage. É um canvas desenhado por static/character.js: uma
   nuvem de pontos numa esfera de Fibonacci onde a pupila é DENSIDADE, não um
   círculo por cima. O mesmo material da orbe de voz.
   ============================================================================= */

.logo-small {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
}

#agent-eye {
    display: block;
    /* Sem box-shadow. O brilho vem dos próprios pontos, e um halo aqui seria
       mais uma camada de desfoque permanente no header — exactamente o que a
       regra do :root proíbe. */
    border-radius: 50%;
}


/* =============================================================================
   Separadores do painel direito

   Substituem o empilhamento `2fr 1fr` de Tarefas + Vendas. O problema não era
   o espaço: era haver DOIS campos de conversa visíveis ao mesmo tempo — o
   composer do chat e o do assistente de vendas — e ter de decidir em qual
   escrever a cada mensagem.
   ============================================================================= */

.rp-tabs {
    display: flex;
    align-items: stretch;
    gap: var(--space-half);
    padding: var(--space-2) var(--space-2) 0;
    background: var(--bg-tertiary);
    border-bottom: 1px solid var(--border);
}
.rp-tablist {
    display: flex;
    flex: 1;
    gap: var(--space-half);
    min-width: 0;
}

.rp-tab {
    flex: 1;
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-1h);
    appearance: none;
    cursor: pointer;
    border: none;
    background: transparent;
    color: var(--text-secondary);
    font-family: inherit;
    font-size: var(--text-xs);
    font-weight: 600;
    letter-spacing: 0.04em;
    padding: var(--space-2) var(--space-2);
    border-radius: var(--radius-xs) var(--radius-xs) 0 0;
    border-bottom: 2px solid transparent;
    transition: color var(--dur-base) ease, background var(--dur-base) ease, border-color var(--dur-base) ease;
}

@media (hover: hover) {
    .rp-tab:hover { color: var(--text-primary); background: var(--accent-soft); }
}

.rp-tab[aria-selected="true"] {
    color: var(--accent);
    border-bottom-color: var(--accent);
    background: var(--bg-secondary);
}

.rp-tab:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}
.rp-tab svg { flex: none; }
/* Ponto de atraso: o tasks-panel.js põe data-badge no separador das tarefas
   quando há tarefas em atraso — visível mesmo com o painel recolhido. */
.rp-tab[data-badge]::after {
    content: '';
    position: absolute;
    top: 5px;
    right: 6px;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--error);
    box-shadow: 0 0 0 2px var(--bg-tertiary);
}

/* O painel inactivo leva [hidden]; isto garante que ganha ao display:flex das
   regras da grelha, que são mais específicas. */
#right-panel > [role="tabpanel"][hidden] { display: none !important; }


/* =============================================================================
   Ecrã de voz — o organismo e a parede

   Um ecrã próprio (/#voz), irmão do chat — já não uma camada por cima dele.
   Mesmo abismo do login: aquário negro, um organismo bioluminescente ao
   centro-esquerda e os números atirados para a parede à direita. Todo o
   estado vem de [data-vs-state] no root; nada de classes no <body>.
   ============================================================================= */
#voice-screen {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    height: 100dvh;
    color: var(--text-primary);
    background: radial-gradient(ellipse 100% 70% at 50% 15%, var(--brand-bg-top) 0%, var(--brand-bg-bot) 70%);
}
#voice-screen:not(.hidden) {
    display: grid;
    grid-template-columns: minmax(0, 1fr) clamp(300px, 26vw, 380px);
    grid-template-rows: 56px minmax(0, 1fr);
    grid-template-areas: "bar bar" "stage wall";
}
#voice-screen::before {
    content: '';
    position: absolute;
    inset: -20%;
    z-index: calc(var(--z-behind) - 1);
    background: radial-gradient(45vw 45vw at 30% 55%, rgba(var(--brand-rgb), 0.12) 0%, transparent 65%);
    filter: blur(60px) saturate(140%);
    animation: gl-mesh-drift 32s ease-in-out infinite alternate-reverse;
    pointer-events: none;
}
#voice-screen::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: var(--z-behind);
    background-image: url("data:image/svg+xml;utf8,<svg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 1  0 0 0 0 1  0 0 0 0 1  0 0 0 1 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)' opacity='1'/></svg>");
    background-size: 220px 220px;
    opacity: 0.05;
    mix-blend-mode: overlay;
    pointer-events: none;
}
/* A chuva abre-se à volta da esfera: ténue atrás dela, cheia nas bordas. */
#voice-screen > .matrix-rain-canvas {
    -webkit-mask-image: radial-gradient(ellipse 42% 48% at 37% 46%, rgba(0, 0, 0, 0.2) 0%, #000 75%);
    mask-image: radial-gradient(ellipse 42% 48% at 37% 46%, rgba(0, 0, 0, 0.2) 0%, #000 75%);
}

.vs-bar {
    grid-area: bar;
    position: relative;
    z-index: var(--z-float);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    padding: 0 var(--space-6);
}
/* O lettering da marca em texto (Unbounded 800): o mesmo wordmark do portão
   a uma escala onde as partículas seriam mancha (LAND-10). */
.brand-lettering {
    font-family: var(--font-brand);
    font-weight: 800;
    font-size: var(--text-sm);
    line-height: 1;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--brand-particle);
}
.vs-wordmark { position: static; }
.vs-bar-right { display: flex; align-items: center; gap: var(--space-3); min-width: 0; }
/* Duração da chamada (e custo, para superadmin) — VOZ-05. Era um espécime
   aria-hidden no canto, escondido abaixo de 1024 px: no telemóvel não se via
   tempo nenhum. Fica na barra, em todos os tamanhos. */
.vs-meter {
    margin: 0;
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    white-space: nowrap;
    font: 500 var(--text-2xs)/1 var(--font-mono);
    letter-spacing: 0.08em;
    font-variant-numeric: tabular-nums;
    color: var(--text-secondary);
}
.vs-meter[hidden] { display: none; }
.vs-meter::before {
    content: '';
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--brand);
    box-shadow: 0 0 8px var(--brand-glow);
}
.vs-meter--ended { opacity: 0.6; }
/* A sessão vai acabar (VOZ-13): o temporizador conta para trás, num badge
   âmbar que fica até ao fim — o aviso no rótulo era apagado pela fala seguinte. */
.vs-meter--expiring {
    color: var(--warning);
    padding: var(--space-1) var(--space-2);
    border: 1px solid rgba(var(--warning-rgb), 0.55);
    border-radius: var(--radius-full);
}
.vs-meter--expiring::before { background: var(--warning); box-shadow: 0 0 8px rgba(var(--warning-rgb), 0.6); }
.vs-meter--ended::before { background: var(--text-secondary); box-shadow: none; }
.vs-cost { color: var(--text-secondary); }
.vs-cost[hidden] { display: none; }

.vs-stage {
    grid-area: stage;
    position: relative;
    z-index: var(--z-raised);
    min-height: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    /* safe: num ecrã baixo o excesso cai para baixo (com scroll) em vez de
       subir por baixo da barra. */
    justify-content: center;
    justify-content: safe center;
    overflow-y: auto;
    padding: 0 var(--space-6) 40px;
}
.vs-orb-host {
    position: relative;
    isolation: isolate;
    width: 100%;
    flex: 1 1 auto;
    min-height: 220px;
    max-height: min(62vh, 600px);
}

/* Eyebrow: estado em mono, com o ponto que respira no relógio da criatura. */
.vs-eyebrow {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    min-height: 16px;
    margin: var(--space-1) 0 0;
    font: 500 var(--text-xs)/1 var(--font-mono);
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--text-secondary);
}
.vs-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    flex: none;
    border: 1.5px solid currentColor;
    background: transparent;
}
#voice-screen[data-vs-state="connecting"] .vs-eyebrow { color: var(--warning); }
#voice-screen[data-vs-state="connecting"] .vs-dot { background: currentColor; animation: vs-pulse 1.2s ease-in-out infinite; }
#voice-screen[data-vs-state="live"] .vs-eyebrow,
#voice-screen[data-vs-state="speaking"] .vs-eyebrow,
#voice-screen[data-vs-state="thinking"] .vs-eyebrow,
#voice-screen[data-vs-state="thinking-slow"] .vs-eyebrow { color: var(--accent); }
#voice-screen[data-vs-state="live"] .vs-dot,
#voice-screen[data-vs-state="speaking"] .vs-dot {
    background: currentColor;
    box-shadow: 0 0 10px currentColor;
    animation: breathe-dot 3.8s ease-in-out infinite;
}
#voice-screen[data-vs-state="thinking"] .vs-dot,
#voice-screen[data-vs-state="thinking-slow"] .vs-dot { background: currentColor; animation: vs-pulse 1.9s ease-in-out infinite; }
#voice-screen[data-vs-state="user"] .vs-eyebrow { color: var(--voice-user); }
#voice-screen[data-vs-state="user"] .vs-dot { background: currentColor; box-shadow: 0 0 10px currentColor; }
#voice-screen[data-vs-state="approval"] .vs-eyebrow { color: var(--warning); }
#voice-screen[data-vs-state="approval"] .vs-dot { background: currentColor; animation: breathe-dot 3.8s ease-in-out infinite; }
/* Microfone silenciado: âmbar e ponto parado — nunca o acento de 'a ouvir'. */
#voice-screen[data-vs-state="muted"] .vs-eyebrow { color: var(--warning); }
#voice-screen[data-vs-state="muted"] .vs-dot { background: currentColor; }
/* Falha momentânea do canal com a chamada viva (VOZ-13): âmbar, não vermelho. */
#voice-screen[data-vs-state="glitch"] .vs-eyebrow { color: var(--warning); }
#voice-screen[data-vs-state="glitch"] .vs-dot { background: currentColor; }
/* Aviso de inactividade (VOZ-04): âmbar, o ponto a apagar-se devagar. */
#voice-screen[data-vs-state="idle-warning"] .vs-eyebrow { color: var(--warning); }
#voice-screen[data-vs-state="idle-warning"] .vs-dot { background: currentColor; animation: vs-pulse 1.9s ease-in-out infinite; }
#voice-screen[data-vs-state="error"] .vs-eyebrow,
#voice-screen[data-vs-state="unsupported"] .vs-eyebrow { color: var(--error-text); }
#voice-screen[data-vs-state="error"] .vs-dot { background: currentColor; }
@keyframes vs-pulse {
    0%, 100% { opacity: 0.35; transform: scale(0.85); }
    50%      { opacity: 1;    transform: scale(1.15); }
}

/* Legenda: subtítulo de cinema. Sans 500 = transcrição literal; o display
   (Sans 600, maior, tracking apertado) = o Genisium a falar por si
   (saudação, repouso, erros). Já não há serifa (VOZ-15): a distinção é o
   peso e o tamanho. */
.vs-caption {
    margin: var(--space-3) 0 0;
    max-width: 52ch;
    min-height: 2.7em;
    text-align: center;
    text-wrap: balance;
    font: 500 clamp(18px, 1.3vw, 22px)/1.35 var(--font-sans);
    color: var(--text-bright);
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.9), 0 0 18px rgba(var(--abyss-rgb), 0.9);
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.vs-caption[data-voice="system"] {
    font-family: var(--font-display);
    font-weight: 600;
    letter-spacing: var(--tracking-display);
    text-wrap: balance;
    font-size: clamp(26px, 2.4vw, 36px);
    line-height: 1.15;
    color: rgba(255, 255, 255, 0.92);
}
.vs-caption[data-voice="user"] { color: var(--voice-user-ink); }

/* A transcrição inteira: no fim da chamada e, com «Legendas» (VOZ-08),
   durante — sem o corte a 2 linhas da legenda grande. */
.vs-log {
    display: none;
    list-style: none;
    margin: var(--space-4) 0 0;
    padding: 0;
    width: min(560px, 100%);
    max-height: 26vh;
    overflow: auto;
    font: var(--text-sm)/1.5 var(--font-sans);
}
#voice-screen[data-vs-state="ended"] .vs-log:not(:empty),
#voice-screen[data-vs-state="error"] .vs-log:not(:empty),
#voice-screen[data-captions="on"] .vs-log:not(:empty) { display: block; }
/* Com as legendas abertas a legenda grande repetia a última linha do log. */
#voice-screen[data-captions="on"] .vs-caption[data-voice="user"],
#voice-screen[data-captions="on"] .vs-caption[data-voice="assistant"] { display: none; }
.vs-log-item { padding: var(--space-1) 0; color: var(--text-secondary); }
.vs-log-item::before {
    font: 500 var(--text-3xs)/1 var(--font-mono);
    letter-spacing: 0.12em;
    text-transform: uppercase;
    margin-right: var(--space-2);
    opacity: 0.7;
}
.vs-log-item--user { color: var(--voice-user-ink); }
.vs-log-item--user::before { content: 'tu'; }
.vs-log-item--assistant::before { content: 'genisium'; color: var(--accent); }
/* VOZ-09 — as chamadas anteriores ficam, separadas; e a resposta completa do
   Genisium (com tabelas e números) num <details>, pelo markdown do chat. */
.vs-log-sep {
    margin: var(--space-2) 0 var(--space-1h);
    padding-top: var(--space-2);
    border-top: 1px solid var(--border);
    font: 500 var(--text-3xs)/1 var(--font-mono);
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--text-muted);
}
.vs-log-sep:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
.vs-log-result { padding: var(--space-half) 0 var(--space-1h); }
.vs-log-result summary {
    cursor: pointer;
    font: 500 var(--text-3xs)/1.6 var(--font-mono);
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--accent);
}
.vs-log-result summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.vs-log-md { margin-top: var(--space-1h); color: var(--text-primary); overflow-x: auto; }
.vs-log-md p { margin: 0 0 var(--space-1h); }
.vs-log-md table { border-collapse: collapse; font-size: var(--text-xs); }
.vs-log-md th,
.vs-log-md td { padding: var(--space-half) var(--space-2); border-bottom: 1px solid var(--border); text-align: left; }
.vs-log-actions { margin-top: var(--space-2); display: flex; gap: var(--space-2); flex-wrap: wrap; justify-content: center; }
.vs-log-actions[hidden] { display: none; }

.vs-actions { margin-top: var(--space-6); display: flex; align-items: center; gap: var(--space-3); }
/* «Falar»: .btn.btn--lg (DS-12); a variante segue a acção (voice-screen.js
   renderControls) — primário a falar, secundário a cancelar, perigo a
   terminar. Daqui só o brilho que respira por baixo do primário. */
.vs-cta {
    isolation: isolate;
    padding-inline: var(--space-7);
}
/* O brilho que respira: a sombra é estática num ::after e só a OPACIDADE anima
   (compositor). Era um box-shadow animado em loop — repaint a cada frame
   (LAND-11). */
.vs-cta::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: var(--z-behind);
    border-radius: inherit;
    box-shadow: 0 10px 40px -6px rgba(var(--brand-rgb), 0.75), 0 0 26px -2px rgba(var(--brand-rgb), 0.28);
    animation: breathe-glow 3.8s ease-in-out infinite;
    pointer-events: none;
}
.vs-cta:not(.btn--primary)::after,
.vs-cta:disabled::after { display: none; }
.vs-mute {
    appearance: none;
    cursor: pointer;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    display: grid;
    place-items: center;
    background: transparent;
    color: var(--text-secondary);
    border: 1px solid var(--border-strong);
}
/* display:grid vence o atributo hidden — sem isto o mute aparecia em repouso. */
.vs-mute[hidden] { display: none; }
.vs-captions {
    appearance: none;
    cursor: pointer;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    display: grid;
    place-items: center;
    background: transparent;
    color: var(--text-secondary);
    border: 1px solid var(--border-strong);
    font: 500 var(--text-xs)/1 var(--font-mono);
    letter-spacing: 0.06em;
}
.vs-captions[aria-pressed="true"] { color: var(--accent); border-color: var(--border-hover); background: var(--accent-soft); }
.vs-captions:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.vs-mute[aria-pressed="true"] { color: var(--warning); border-color: rgba(var(--warning-rgb), 0.55); }
.vs-mute:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.vs-pref {
    margin-top: var(--space-4);
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    font: 500 var(--text-2xs)/1 var(--font-mono);
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--text-secondary);
    cursor: pointer;
}
#voice-screen:not([data-vs-state="gate"]):not([data-vs-state="ended"]) .vs-pref { visibility: hidden; }

.vs-confirm {
    margin-top: var(--space-4);
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
    justify-content: center;
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius-md);
    background: rgba(var(--abyss-rgb), 0.9);
    border: 1px solid rgba(var(--warning-rgb), 0.45);
}
.vs-confirm[hidden] { display: none; }
.vs-confirm p { margin: 0; color: var(--text-bright); font-size: var(--text-md); }

/* A parede: saída principal do ecrã. Cartões maiores, mais recente no topo. */
.vs-wall {
    grid-area: wall;
    position: relative;
    z-index: calc(var(--z-raised) + 1);
    min-height: 0;
    overflow: hidden;
    padding: var(--space-2) var(--space-6) var(--space-6) var(--space-2);
}
#voice-cards > #ambient-cards {
    position: relative;
    z-index: auto;
    height: 100%;
    --ac-card-bg: rgba(var(--abyss-rgb), 0.78);
}
#voice-cards .ac-column {
    position: static;
    width: 100%;
    max-width: none;
    height: 100%;
    flex-direction: column-reverse;
    justify-content: flex-end;
    gap: var(--space-4);
}
#voice-cards .ac-card { backdrop-filter: none; -webkit-backdrop-filter: none; }
#voice-cards .ac-inner { padding: var(--space-4); }
#voice-cards .ac-value { font-size: var(--text-4xl); }
#voice-cards .ac-value--long { font-size: var(--text-2xl); }
#voice-cards .ac-spark { height: 64px; }
/* column-reverse: order maior = mais acima. A aprovação pendente fica no topo. */
#voice-cards .ac-card--pinned { order: 1; }
#voice-cards .ac-code { width: 72px; height: 72px; border-radius: var(--radius-md); }
#voice-cards .ac-code-digit { font-size: var(--text-5xl); }

/* Sem canvas 2D: um halo CSS no lugar da esfera, para o palco nunca ficar vazio. */
.vo-fallback::after {
    content: '';
    position: absolute;
    left: 50%;
    top: 50%;
    width: min(70%, 60vh);
    aspect-ratio: 1;
    transform: translate(-50%, -50%);
    border-radius: 50%;
    background: radial-gradient(circle, rgba(var(--brand-rgb), 0.35) 0%, rgba(var(--voice-user-rgb), 0.14) 45%, transparent 70%);
    filter: blur(24px);
    animation: breathe-dot 3.8s ease-in-out infinite;
}

/* Toggle Chat | Voz — o mesmo controlo, no mesmo canto, nos dois ecrãs. */
.mode-switch {
    display: inline-flex;
    gap: var(--space-half);
    padding: var(--space-half);
    flex-shrink: 0;
    background: var(--bg-primary);
    border: 1px solid var(--border);
    border-radius: var(--radius-full);
}
.mode-switch[hidden] { display: none; }
.mode-switch-item {
    font: 500 var(--text-2xs)/1 var(--font-mono);
    letter-spacing: 0.12em;
    text-transform: uppercase;
    padding: var(--space-2) var(--space-4);
    border-radius: var(--radius-full);
    color: var(--text-secondary);
    text-decoration: none;
    white-space: nowrap;
    transition: background var(--dur-base) var(--ease-out), color var(--dur-base) var(--ease-out);
}
.mode-switch-item:hover { background: var(--accent-soft); color: var(--text-bright); }
.mode-switch-item:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.mode-switch-item.is-current {
    background: var(--accent-soft);
    color: var(--accent);
    box-shadow: inset 0 0 0 1px var(--border-hover);
}
/* No cabeçalho do chat a ≤ 360 px (Androids pequenos, iPhone SE da 1.ª
   geração), com o escudo das aprovações à vista, o lado direito (268 px)
   passava por cima do ☰ e o nome da conversa ficava só com as reticências:
   o par Chat|Voz encolhe. */
@media (max-width: 360px) {
    .chat-header .mode-switch-item { padding: var(--space-2) var(--space-2); }
}

@media (max-width: 1023px) {
    #voice-screen:not(.hidden) {
        grid-template-columns: minmax(0, 1fr);
        grid-template-rows: 52px minmax(0, 1fr) auto;
        grid-template-areas: "bar" "stage" "wall";
    }
    .vs-bar { padding: 0 var(--space-4); }
    .vs-stage { padding: 0 var(--space-4) var(--space-4); }
    .vs-orb-host { max-height: 48vh; }
    /* VOZ-11 — a parede rola: as aprovações pendentes nunca são despejadas
       (o trimOverflow pára nelas), e com overflow:hidden a segunda ficava
       cortada, com o código fora do ecrã. */
    .vs-wall {
        max-height: 34vh;
        overflow-y: auto;
        overscroll-behavior: contain;
        padding: 0 var(--space-4) calc(var(--space-3) + var(--safe-bottom, 0px));
    }
    /* Um cartão nunca é espremido para caber: rola-se até ele. */
    #voice-cards .ac-card { flex-shrink: 0; }
}
/* Telemóvel estreito com a chamada a contar: o nome cede o lugar ao tempo. */
@media (max-width: 480px) {
    .vs-bar:has(.vs-meter:not([hidden])) .vs-wordmark { display: none; }
}
/* No telemóvel só passam as decisões (aprovações, anomalias); num tablet ou
   numa janela estreita de desktop a parede continua a mostrar as vendas. */
@media (max-width: 767px) {
    #voice-cards .ac-card:not(.ac-card--alert) { display: none; }
    /* VOZ-09 — mas o último cartão de resposta fica, compacto: os números
       ditos de passagem ("quanto vendemos ontem?") têm de ficar em algum lado. */
    #voice-cards .ac-card:nth-last-child(1 of .ac-card:not(.ac-card--alert):not(.ac-dissolving)) { display: block; }
    #voice-cards .ac-card:nth-last-child(1 of .ac-card:not(.ac-card--alert):not(.ac-dissolving)) .ac-spark { display: none; }
    #voice-cards .ac-card:nth-last-child(1 of .ac-card:not(.ac-card--alert):not(.ac-dissolving)) .ac-inner { padding: var(--space-2) var(--space-3); }
    #voice-cards .ac-card:nth-last-child(1 of .ac-card:not(.ac-card--alert):not(.ac-dissolving)) .ac-value { font-size: var(--text-2xl); }
}
@media (max-width: 767px) {
    /* VOZ-11 — a aprovação compacta (~140 px em vez de ~290): a tecla do
       código a 48 px, o resumo em duas linhas e os botões; o detalhe (pré-
       visualização, risco, id, a dica) fica na gaveta de Aprovações. Duas
       pendentes cabem quase inteiras nos 34vh, e o estado diz os códigos.
       Excepção: uma acção IRREVERSÍVEL mantém a linha «risco · irreversível»
       — aprova-se aqui com dois toques, e isso não pode ficar na gaveta. */
    #voice-cards .ac-card--pinned .ac-inner { padding: var(--space-2) var(--space-3); }
    #voice-cards .ac-card--pinned .ac-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-half) var(--space-2); margin-bottom: var(--space-1h); }
    #voice-cards .ac-card--pinned .ac-caption { margin-top: 0; }
    #voice-cards .ac-card--pinned .ac-code { width: 48px; height: 48px; border-radius: var(--radius-sm); }
    #voice-cards .ac-card--pinned .ac-code-digit { font-size: var(--text-4xl); }
    #voice-cards .ac-card--pinned .ac-code-label { letter-spacing: 0.06em; }
    #voice-cards .ac-card--pinned .ac-approval-main { margin-bottom: var(--space-1h); }
    #voice-cards .ac-card--pinned .ac-anomaly-title {
        display: -webkit-box;
        -webkit-line-clamp: 2;
        -webkit-box-orient: vertical;
        overflow: hidden;
    }
    #voice-cards .ac-card--pinned .ac-preview,
    #voice-cards .ac-card--pinned .ac-sub:not(.ac-approval-hint):not(:has(.ac-rev--irreversible)),
    #voice-cards .ac-card--pinned .ac-approval-hint:not([role="status"]) { display: none; }
}

/* Portáteis baixos e painéis pequenos: a esfera cede primeiro, a legenda
   encolhe, e o palco nunca empurra os controlos para fora do ecrã. */
@media (max-height: 640px) {
    .vs-orb-host { min-height: 140px; }
    .vs-caption { min-height: 0; }
    .vs-caption[data-voice="system"] { font-size: clamp(20px, 4.2vh, 28px); }
    .vs-actions { margin-top: var(--space-4); }
    .vs-pref { margin-top: var(--space-2); }
}

/* ---- Cinema ------------------------------------------------------------ */

/* "O olho torna-se a esfera": clone de pontos que viaja do olho do header até
   ao palco (app.js transitionToVoice). Só existe durante ~1 s. */
.vs-seed {
    position: fixed;
    z-index: var(--z-seed);
    pointer-events: none;
    border-radius: 50%;
    filter: drop-shadow(0 0 16px rgba(var(--brand-rgb), 0.5));
    will-change: left, top, width, height, opacity;
}
/* Enquanto o olho viaja, a esfera espera às escuras. */
#voice-screen.vs-entering #voice-orb-overlay { opacity: 0 !important; }

/* O feixe de glifos da esfera até ao cartão (voice-screen.js drawBeam). */
.vs-beam {
    position: absolute;
    inset: 0;
    z-index: calc(var(--z-raised) + 2);
    width: 100%;
    height: 100%;
    pointer-events: none;
}

/* Sonar: enquanto a consulta demora, a cada 5 s um anel sai da esfera. */
#voice-screen[data-vs-state="thinking-slow"] #voice-stage::after {
    content: '';
    position: absolute;
    left: 50%;
    top: 50%;
    width: calc(var(--vo-size, 480px) * 0.71);
    height: calc(var(--vo-size, 480px) * 0.71);
    border-radius: 50%;
    border: 1px solid rgba(var(--brand-rgb), 0.5);
    transform: translate(-50%, -50%) scale(1);
    opacity: 0;
    pointer-events: none;
    animation: vs-sonar 5s cubic-bezier(0.16, 1, 0.3, 1) infinite;
}
@keyframes vs-sonar {
    0%   { opacity: 0.6; transform: translate(-50%, -50%) scale(1); }
    24%  { opacity: 0;   transform: translate(-50%, -50%) scale(1.5); }
    100% { opacity: 0;   transform: translate(-50%, -50%) scale(1.5); }
}

/* Erro: um soluço de 240 ms na imagem, antes de assentar no vermelho baço. */
#voice-screen[data-vs-state="error"] .vo-canvas { animation: vs-glitch var(--dur-base) linear 1; }
@keyframes vs-glitch {
    0%   { transform: translateX(0); }
    25%  { transform: translateX(3px); }
    50%  { transform: translateX(-3px); }
    75%  { transform: translateX(2px); }
    100% { transform: translateX(0); }
}

/* Repouso prolongado: um sussurro, não uma pergunta. */
.vs-caption--whisper { opacity: 0.55; color: var(--text-secondary) !important; transition: opacity var(--dur-slow) ease; }
.vs-tick { letter-spacing: 0.1em; }

/* Os meus dados (PRIV-07) */
.kb-me-intro code { font-family: var(--font-mono); font-size: var(--text-2xs); }
.kb-me-title {
    font-size: var(--text-xs);
    font-weight: 600;
    color: var(--text-bright);
    margin: var(--space-4) 0 var(--space-1h);
}
.kb-me-title:first-child { margin-top: 0; }
.kb-me-turn .kb-entry-body { max-height: 48px; }
#kb-me-section .kb-toolbar { justify-content: space-between; }
/* PRIV-10 — «Onde vivem os teus dados» */
.kb-me-where { margin-top: var(--space-2); font-size: var(--text-2xs); color: var(--text-secondary); }
.kb-me-where > summary { cursor: pointer; color: var(--text-bright); font-weight: 600; padding: var(--space-1) 0; }
.kb-me-where ul { margin: var(--space-1) 0 0; padding-left: var(--space-4); display: grid; gap: var(--space-1); }
.kb-me-where strong { color: var(--text-bright); font-weight: 600; }
/* PRIV-05 — quem abriu as conversas privadas */
.kb-me-access-hint { margin: 0 0 var(--space-1h); }
/* =============================================================================
   Fase 2 — chat num só caminho: progresso, parar, erros, histórico, 1.º uso
   ============================================================================= */

/* CHAT-18 — "a enviar…" só até ao ack do servidor; depois quem fala é o
   rascunho do assistente ("a pensar…"). */
.message--streaming .message-content:empty { display: none; }
.msg-thinking {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    color: var(--text-muted);
    font: 500 var(--text-xs)/1.4 var(--font-mono);
}
.msg-thinking-dots { display: inline-flex; gap: var(--space-1); }
.msg-thinking-dots span {
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: var(--accent);
    opacity: 0.4;
    animation: msgThinking 1.2s ease-in-out infinite;
}
.msg-thinking-dots span:nth-child(2) { animation-delay: 0.15s; }
.msg-thinking-dots span:nth-child(3) { animation-delay: 0.3s; }
@keyframes msgThinking { 0%, 100% { opacity: 0.25; } 50% { opacity: 1; } }

/* CHAT-03 — passos das tools por baixo da bolha, e o rodapé de fontes. */
.msg-steps {
    list-style: none;
    margin: var(--space-1h) 0 var(--space-half);
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    font: 400 var(--text-xs)/1.4 var(--font-mono);
    color: var(--text-secondary);
}
.msg-step { display: flex; justify-content: space-between; gap: var(--space-3); }
.msg-step-meta { color: var(--text-muted); white-space: nowrap; font-variant-numeric: tabular-nums; }
.msg-step.is-ok .msg-step-meta { color: var(--accent-secondary); }
.msg-step.is-error,
.msg-step.is-error .msg-step-meta { color: var(--error-text); }
.msg-sources {
    margin-top: var(--space-1h);
    font: 400 var(--text-2xs)/1.4 var(--font-mono);
    color: var(--text-muted);
    letter-spacing: 0.02em;
}
.msg-sources--aborted,
.message--aborted .message-content { color: var(--text-muted); font-style: italic; }
.message.assistant.message--failed {
    border-color: rgba(var(--error-rgb), 0.6);
    background: rgba(var(--error-rgb), 0.08);
}

/* CHAT-07 — "Nova conversa" com o assistente deixa um separador. */
.msg-stream-item.is-system { display: flex; justify-content: center; }
.msg-context-reset {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    width: 100%;
    margin: var(--space-1h) 0;
    color: var(--text-muted);
    font: 500 var(--text-2xs)/1.4 var(--font-mono);
    text-transform: uppercase;
    letter-spacing: var(--tracking-label);
}
.msg-context-reset::before,
.msg-context-reset::after { content: ''; flex: 1; border-top: 1px dashed var(--border); }

/* CHAT-06 — com uma resposta a chegar, o enviar é "Parar". */
.btn-send .icon-stop { display: none; }
.btn-send.is-stop .icon-send { display: none; }
.btn-send.is-stop .icon-stop { display: block; }
.btn-send.is-stop:not(:disabled) { background: var(--text-bright); color: var(--on-brand); }

/* CHAT-10 — "↓ Novas mensagens" quando se está a ler histórico. */
.new-messages-chip {
    position: sticky;
    bottom: 8px;
    display: block;
    margin: 0 auto;
    padding: var(--space-1h) var(--space-4);
    background: var(--surface-header);
    color: var(--text-bright);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-full);
    font: 500 var(--text-xs)/1.4 var(--font-sans);
    box-shadow: var(--shadow-md);
    cursor: pointer;
    z-index: var(--z-raised);
}
.new-messages-chip[hidden] { display: none; }

/* CHAT-13 — pesquisa no conteúdo das mensagens (lista de conversas). O
   resultado é um botão em duas linhas: onde · quando, e o excerto com as
   palavras encontradas em <mark>. */
.conv-search-results { padding-bottom: var(--space-1h); }
.conv-search-btn {
    display: flex;
    flex-direction: column;
    gap: var(--space-half);
    width: 100%;
    padding: var(--space-2) var(--space-2);
    border: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    color: inherit;
    font: inherit;
    text-align: left;
    cursor: pointer;
}
.conv-search-btn:hover { background: var(--accent-soft); }
.conv-search-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.conv-search-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-2);
    min-width: 0;
}
.conv-search-where {
    font-size: var(--text-xs);
    font-weight: 600;
    color: var(--text-bright);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.conv-search-snippet {
    font-size: var(--text-xs);
    line-height: 1.4;
    color: var(--text-secondary);
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.conv-search-who { color: var(--text-primary); }
.conv-search-snippet mark {
    background: var(--accent-soft);
    color: var(--text-bright);
    border-radius: var(--radius-2xs);
    padding: 0 var(--space-half);
}

/* CHAT-13 — a mensagem para onde a pesquisa saltou: um anel por instantes. */
.msg-stream-item.is-jump-target .message {
    box-shadow: 0 0 0 2px var(--accent);
}
.msg-stream-item:focus { outline: none; }

/* CHAT-13 — topo do histórico paginado. */
.history-status {
    text-align: center;
    padding: var(--space-1) 0 var(--space-2);
    color: var(--text-muted);
    font: 500 var(--text-2xs)/1.4 var(--font-mono);
    text-transform: uppercase;
    letter-spacing: var(--tracking-label);
}

/* CHAT-07/12 — enquanto a conversa carrega (skeleton) e quando falha. */
.feed-skeleton { display: flex; flex-direction: column; gap: var(--space-3); padding-top: var(--space-2); }
.feed-skel {
    height: 44px;
    width: 46%;
    border-radius: var(--radius-md);
    background: var(--surface-raised);
    opacity: 0.55;
    animation: feedSkel 1.4s ease-in-out infinite;
}
.feed-skel--self { align-self: flex-end; width: 32%; }
.feed-skel--long { height: 88px; width: 70%; }
@keyframes feedSkel { 0%, 100% { opacity: 0.35; } 50% { opacity: 0.65; } }
.feed-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-12) var(--space-4);
    color: var(--text-secondary);
    text-align: center;
}
.welcome-message--peer { min-height: 0; padding-top: var(--space-12); }

/* CHAT-17 — os chips são <button>: sem o estilo nativo do browser. */
button.welcome-chip { font: 500 var(--text-2xs)/1.4 var(--font-mono); }
.welcome-chip:focus-visible,
.action-pill:focus-visible,
.onb-action:focus-visible,
.table-copy-btn:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* VIS-10 — sugestões numa faixa por cima do compositor (não abaixo da dobra). */
#message-form > .action-pills { padding: 0 0 var(--space-2); }
.action-pills[hidden] { display: none; }

/* VIS-07 — "Copiar tabela" por baixo de cada tabela. */
.table-copy-btn {
    display: inline-block;
    margin: var(--space-1) 0 var(--space-2);
    padding: var(--space-half) var(--space-2);
    background: transparent;
    border: 1px solid var(--border);
    border-radius: var(--radius-full);
    color: var(--text-muted);
    font: 500 var(--text-2xs)/1.6 var(--font-mono);
    cursor: pointer;
}
.table-copy-btn:hover,
.table-copy-btn.copied { color: var(--text-bright); border-color: var(--border-hover); }

/* RUN-14 — cartões do Canvas dentro da mensagem: estáticos e clicáveis. */
.msg-cards {
    --ac-card-bg: rgba(var(--abyss-rgb), 0.55);
    --ac-card-border: rgba(var(--brand-rgb), 0.35);
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: var(--space-2);
    margin: var(--space-2) 0 var(--space-1);
}
.msg-cards:empty { display: none; }
.ac-card.ac-card--static {
    opacity: 1;
    transform: none;
    animation: none;
    pointer-events: auto;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}
.ac-card--static .ac-value { position: static; }

/* CHAT-12 — anexos com estado: a carregar e com erro. */
.attach-thumb.is-uploading,
.attach-thumb.is-error { padding-right: var(--space-7); }
.attach-thumb.is-uploading .attach-name,
.attach-thumb.is-error .attach-name {
    position: static;
    background: none;
    color: var(--text-secondary);
    font-size: var(--text-xs);
    padding: 0;
}
.attach-thumb.is-error { border-color: rgba(var(--error-rgb), 0.6); flex-direction: column; align-items: flex-start; gap: var(--space-half); }
.attach-error { color: var(--error-text); font-size: var(--text-2xs); }
.attach-spinner {
    width: 14px;
    height: 14px;
    border-radius: 50%;
    border: 2px solid var(--border);
    border-top-color: var(--accent);
    animation: attachSpin 0.8s linear infinite;
    flex-shrink: 0;
}
@keyframes attachSpin { to { transform: rotate(360deg); } }

/* AUTH-11 — primeiros passos no topo da conversa com o assistente. */
.onboarding {
    margin: 0 auto var(--space-4);
    max-width: 560px;
    padding: var(--space-3) var(--space-4);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface-raised);
}
.onboarding[hidden] { display: none; }
.onboarding-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--space-2); }
.onboarding-head h3 {
    margin: 0;
    font: 500 var(--text-2xs)/1.4 var(--font-mono);
    text-transform: uppercase;
    letter-spacing: var(--tracking-label);
    color: var(--accent);
}
.onboarding-steps { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-1h); }
.onb-step { display: flex; align-items: baseline; gap: var(--space-2); flex-wrap: wrap; font-size: var(--text-sm); color: var(--text-primary); }
.onb-check {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-xs);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: var(--text-2xs);
    color: var(--on-brand);
}
.onb-step.is-done .onb-check { background: var(--accent); border-color: var(--accent); }
.onb-step.is-done .onb-label { color: var(--text-muted); text-decoration: line-through; }
.onb-action {
    background: transparent;
    border: 0;
    padding: 0;
    color: var(--accent-hover);
    font: inherit;
    text-decoration: underline;
    text-underline-offset: 2px;
    cursor: pointer;
}
.onb-hint { color: var(--text-muted); font-size: var(--text-xs); }

/* =============================================================================
   Movimento reduzido (DS-18) — UMA regra global, com excepções explícitas.

   Antes eram cinco blocos espalhados que enumeravam selectores, e escapavam
   animações infinitas de controlos do dia-a-dia: o anel do micro a gravar
   (micPulse), o piscar a processar, o pulso do «Ouvir», os pontos do banner
   «a escrever». Cada componente novo arriscava ficar de fora. Agora a rede
   apanha tudo: cada animação corre uma vez, quase instantânea, e cada
   transição também — os eventos animationend/transitionend continuam a
   disparar, e o animation-delay mantém-se (a rede de 3 s do portão).

   Excepções, só onde é preciso um estado final concreto ou onde um fade de
   opacidade (sem deslocamento) ainda comunica. Este bloco é o ÚNICO
   @media (prefers-reduced-motion) da folha — fica no fim para ganhar à
   cascata. As animações em JS (Web Animations, canvases) lêem a mesma media
   query no app.js, no voice-orb.js e no static/brand/particles.js.
   ============================================================================= */
@media (prefers-reduced-motion: reduce) {
    /* ARMADILHA: .message nova é `opacity: 0` + `animation: msgIn forwards`
       — é a animação que a torna visível. O estado final fica escrito. */
    .message { animation: none; opacity: 1; transform: none; }
    .msg-stream-item.is-new .message { animation: none; opacity: 1; transform: none; }

    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }

    /* Cartões da voz e do chat: entram por fade de opacidade, sem subir nem
       escalar — a excepção que mantém movimento (só opacidade). */
    .ac-card {
        transform: none;
        transition-property: opacity !important;
        transition-duration: var(--dur-base) !important;
    }
    .ac-card.ac-dissolving { opacity: 0; }
    .ac-scanlines { opacity: 0.25; }
    .ac-rain { display: none; }
    /* O sonar e o olho que viaja até à esfera são só movimento: saem. */
    #voice-screen[data-vs-state="thinking-slow"] #voice-stage::after,
    .vs-seed { display: none; }
}
