/* ==========================================================================
   Tipografia auto-hospedada
   --------------------------------------------------------------------------
   Ate a feature 003 o CSS pedia `Bahnschrift` e `Cascadia Mono`, que so
   existem no Windows. Em macOS e Linux a cadeia caia para `Trebuchet MS`,
   humanista e arredondada — o oposto da leitura de painel tecnico da marca.
   A tela ficava diferente dependendo de quem abria.

   Ambas sao VARIAVEIS: um arquivo por subconjunto cobre todo o eixo de peso,
   e por isso ha dois arquivos por familia em vez de um por peso.
   Procedencia e licenca em static/fonts/README.md.
   ========================================================================== */

@font-face {
    font-family: "Archivo";
    font-style: normal;
    font-weight: 100 900;
    font-display: swap;
    src: url("../fonts/archivo-latin.cd56e2ec63d7.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
        U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
        U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: "Archivo";
    font-style: normal;
    font-weight: 100 900;
    font-display: swap;
    src: url("../fonts/archivo-latin-ext.c5e41aa17045.woff2") format("woff2");
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
        U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
        U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
        U+A720-A7FF;
}

@font-face {
    font-family: "JetBrains Mono";
    font-style: normal;
    font-weight: 100 800;
    font-display: swap;
    src: url("../fonts/jetbrains-mono-latin.b636a65da4f0.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
        U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
        U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: "JetBrains Mono";
    font-style: normal;
    font-weight: 100 800;
    font-display: swap;
    src: url("../fonts/jetbrains-mono-latin-ext.bd4781a31b42.woff2") format("woff2");
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
        U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
        U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
        U+A720-A7FF;
}

/* ==========================================================================
   Tokens de design — a fonte unica de verdade
   --------------------------------------------------------------------------
   DESIGN.md descreve esta lista item a item, e tests/test_design_tokens.py
   falha quando os dois divergem. Quem acrescenta um token aqui acrescenta a
   linha la, no mesmo commit.

   Valor de cor escrito a mao fora deste bloco e proibido. Foi assim que a
   tinta de erro acabou repetida em 8 regras e o contorno de atencao em 5:
   bastava uma delas divergir para dois estados de erro terem tons diferentes
   sem ninguem notar. O teste tambem cobra isso — e cobra pelo nome do token,
   nao repetindo o hexadecimal aqui, senao este comentario seria a primeira
   violacao da propria regra que enuncia.
   ========================================================================== */

:root {
    /* --- Superficies: a escada tonal que cria profundidade sem sombra --- */
    --bg: #0f1115;
    --sidebar: #11161d;
    --surface: #171d25;
    --surface-raised: #1e2631;
    --surface-hover: #242e3a;
    --border: #2c3541;
    --border-strong: #3c4856;

    /* Campo AFUNDA em vez de elevar: um input mais claro que o card parece
       um botao. Mais escuro, le-se como area de entrada. */
    --field-bg: #12171e;

    /* --- Tinta: a rampa de texto. `--ink-faint` e o piso. --- */
    --ink: #f4f7fa;
    --ink-soft: #aeb9c6;
    --ink-faint: #72808f;
    --text: var(--ink);

    /* --- Verde de sinalizacao: a UNICA cor de acao --- */
    --accent: #1d8f45;
    --accent-strong: #166b35;
    --accent-hover: #249f4e;
    --accent-ink: #82e5a0;
    --accent-soft: #102a1a;
    --accent-border: rgb(29 143 69 / 40%);
    --accent-contrast: #f7fff9;

    /* --- Status: cheio, fundo de chip, contorno e tinta sobre o fundo --- */
    --negative: #ff6b76;
    --negative-soft: #351b22;
    --negative-ink: #ffb7bd;
    --negative-border: rgb(255 107 118 / 40%);
    --danger: var(--negative);
    --warning: #f5c764;
    --warning-soft: #302714;
    --warning-border: #765c27;
    --info: #58a6ff;
    --info-soft: #142236;
    --info-border: rgb(88 166 255 / 35%);
    --neutro-bg: rgb(114 128 143 / 16%);
    --neutro-border: rgb(174 185 198 / 18%);

    --focus-ring: 0 0 0 3px rgb(29 143 69 / 20%);
    --backdrop: rgb(6 8 11 / 65%);

    /* --- Tipografia --- */
    --font-sans: "Archivo", "Bahnschrift", "Segoe UI Variable",
        "Trebuchet MS", sans-serif;
    --font-mono: "JetBrains Mono", "Cascadia Mono", "Consolas", monospace;

    /* Escala fixa em rem, nao fluida: o operador ve em DPI constante, e um
       titulo que encolhe com a janela pioraria a leitura sem resolver nada.
       `--text-2xs` e o PISO — densidade se conquista com espacamento e altura
       de linha, nao encolhendo a letra ate ela nao se ler. */
    --text-2xs: 0.6875rem;
    --text-xs: 0.75rem;
    --text-sm: 0.8125rem;
    --text-md: 0.875rem;
    --text-lg: 1rem;
    --text-xl: 1.125rem;
    --text-2xl: 1.375rem;
    --leading-tight: 1.25;
    --leading-normal: 1.5;
    --weight-medium: 500;
    --weight-semibold: 650;
    --weight-bold: 700;
    --text-muted: var(--ink-faint);

    /* --- Espacamento: escala de 4px --- */
    --space-3xs: 2px;
    --space-2xs: 4px;
    --space-xs: 6px;
    --space-sm: 8px;
    --space-md: 12px;
    --space-lg: 16px;
    --space-xl: 20px;
    --space-2xl: 24px;
    --space-3xl: 32px;
    --space-4xl: 40px;

    /* --- Raio --- */
    --radius-xs: 4px;
    --radius-sm: 8px;
    --radius-md: 12px;
    --radius-lg: 16px;
    --radius-xl: 20px;
    --radius-pill: 999px;

    /* --- Elevacao: reforco da escada tonal, nunca substituto dela --- */
    --shadow-1: 0 2px 8px rgb(0 0 0 / 25%);
    --shadow-2: 0 12px 32px rgb(0 0 0 / 45%);
    --shadow-3: 0 18px 42px rgb(0 0 0 / 20%);
    --shadow: var(--shadow-3);

    /* --- Movimento: nove usos no orcamento inteiro, todos daqui --- */
    --dur-1: 120ms;
    --dur-2: 160ms;
    --dur-3: 220ms;
    --dur-4: 320ms;
    --dur-spinner: 900ms;
    --ease-standard: cubic-bezier(0.2, 0, 0, 1);
    --ease-out: cubic-bezier(0, 0, 0.2, 1);
    --ease-in: cubic-bezier(0.4, 0, 1, 1);

    /* --- Camadas --- */
    --z-sticky: 10;
    --z-menu: 20;
    --z-topbar: 30;
    --z-toast: 60;

    /* Custom property nao funciona dentro de `@media`, entao estes tres sao
       DOCUMENTACAO: registram os unicos cortes que o projeto usa, para que o
       proximo `@media` nao invente um setimo valor como ja aconteceu. */
    --bp-sm: 640px;
    --bp-md: 960px;
    --bp-lg: 1280px;

    color: var(--ink);
    color-scheme: dark;
    font-family: var(--font-sans);
}

* {
    box-sizing: border-box;
}

html {
    background: var(--bg);
}

body {
    margin: 0;
    background: var(--bg);
    color: var(--ink);
    font-family: var(--font-sans);
}

button,
input,
select,
textarea {
    font: inherit;
}

button {
    cursor: pointer;
}

button:focus-visible,
a:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
    outline: 2px solid var(--accent-ink);
    outline-offset: 3px;
}

a {
    color: inherit;
    text-decoration: none;
}

h1,
h2,
p {
    margin-top: 0;
}

.sr-only {
    clip: rect(0, 0, 0, 0);
    clip-path: inset(50%);
    height: 1px;
    overflow: hidden;
    position: absolute;
    white-space: nowrap;
    width: 1px;
}

.eyebrow {
    color: var(--ink-faint);
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    margin-bottom: 8px;
    text-transform: uppercase;
}

.auth-page {
    min-height: 100vh;
}

.auth-layout {
    align-items: center;
    background: linear-gradient(135deg, #0f1115, #121820);
    display: grid;
    justify-items: center;
    min-height: 100dvh;
    padding: 24px;
}

.login-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow);
    padding: 36px;
    width: min(100%, 430px);
}

.brand {
    align-items: center;
    display: inline-flex;
}

/* Altura fixa, largura livre: o logo real (mp-transporte-express.png) é mais
   compacto/quadrado que a faixa larga do SVG antigo -- travar pela largura
   deixaria a marca alta demais para a sidebar. */
.brand-logo {
    display: block;
    height: 46px;
    width: auto;
}

.brand--login {
    /* `.brand` é `inline-flex` (encolhe ao tamanho do conteúdo), então sem
       isto o logo fica preso na borda esquerda do card, com o espaço vazio
       sobrando à direita. `display: flex` (bloco cheio) + `justify-content`
       centraliza a marca em cima do formulário de login. */
    display: flex;
    justify-content: center;
    margin-bottom: 30px;
}

.brand--login .brand-logo {
    height: 64px;
}

.login-form {
    width: 100%;
}

.login-form h1 {
    font-size: 1.65rem;
    font-weight: 700;
    line-height: 1.2;
    margin-bottom: 8px;
}

.form-description {
    color: var(--ink-soft);
    font-size: 0.9rem;
    line-height: 1.5;
    margin-bottom: 26px;
}

.form-field {
    /* `align-content: start` e obrigatorio: numa grade, o `.form-field` e
       esticado ate a altura do campo mais alto da linha (um vizinho com
       dica embaixo, por exemplo) e, sem isto, ele repassa a sobra para o
       proprio controle — o input de 46px virava 52px e o select, 68px. */
    align-content: start;
    display: grid;
    gap: var(--space-sm);
    margin-bottom: var(--space-lg);
}

.form-field label {
    color: var(--ink-soft);
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
}

/* Um unico tratamento para todo controle de texto/escolha. Antes so
   `input` era estilizado, e `select`/`textarea` caiam no visual nativo do
   navegador -- que num tema escuro aparece branco e quebra a tela inteira. */
.form-field input:not([type="checkbox"]):not([type="radio"]),
.form-field select,
.form-field textarea {
    background-color: var(--field-bg);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    color: var(--ink);
    min-height: 46px;
    /* Padding so na horizontal: a altura vem do `min-height`, que centra o
       texto sem depender de line-height. Padding vertical aqui empilha com
       o min-height e deixa o controle mais alto que o previsto. */
    padding: 0 var(--space-md);
    transition: border-color 140ms ease, box-shadow 140ms ease;
    width: 100%;
}

.form-field textarea {
    line-height: 1.5;
    min-height: 96px;
    padding: 11px 13px;
    resize: vertical;
}

/* Seta propria: `appearance: none` remove a do sistema, que nao acompanha
   o tema. SVG inline evita mais um arquivo estatico. */
.form-field select {
    appearance: none;
    background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='none' stroke='%23aeb9c6' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m4 6 4 4 4-4'/%3E%3C/svg%3E");
    background-position: right 13px center;
    background-repeat: no-repeat;
    cursor: pointer;
    padding-right: 38px;
}

.form-field input::placeholder,
.form-field textarea::placeholder {
    color: var(--ink-faint);
}

.form-field input:not([type="checkbox"]):focus,
.form-field select:focus,
.form-field textarea:focus {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px rgb(29 143 69 / 20%);
    outline: 0;
}

.form-field input:disabled,
.form-field select:disabled,
.form-field textarea:disabled {
    background: var(--surface);
    color: var(--ink-faint);
    cursor: not-allowed;
}

/* O icone do seletor de data e desenhado pelo navegador; sem isto ele sai
   preto sobre fundo escuro. */
.form-field input[type="date"]::-webkit-calendar-picker-indicator {
    cursor: pointer;
    filter: invert(72%) sepia(8%) saturate(400%) hue-rotate(175deg);
}

.password-control {
    position: relative;
}

.password-control input {
    padding-right: 48px;
}

.password-toggle {
    align-items: center;
    background: transparent;
    border: 0;
    border-radius: 6px;
    color: var(--ink-faint);
    display: inline-flex;
    height: 34px;
    justify-content: center;
    position: absolute;
    right: 6px;
    top: 6px;
    width: 34px;
}

.password-toggle:hover,
.password-toggle[aria-pressed="true"] {
    background: var(--surface-hover);
    color: var(--ink);
}

.password-toggle svg,
.icon-button svg,
.topbar-search svg,
.menu-link svg,
.button svg {
    height: 16px;
    width: 16px;
}

.login-feedback {
    width: 100%;
}

.alert {
    border-radius: var(--radius-sm);
    font-size: 0.82rem;
    line-height: 1.4;
    margin: 2px 0 16px;
    padding: 11px 12px;
}

.alert-error {
    background: var(--negative-soft);
    border: 1px solid rgb(255 107 118 / 30%);
    color: var(--negative-ink);
}

.button {
    align-items: center;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    display: inline-flex;
    font-size: var(--text-sm);
    font-weight: var(--weight-bold);
    gap: var(--space-sm);
    justify-content: center;
    min-height: 38px;
    padding: 0 var(--space-lg);
    transition: background-color var(--dur-2) var(--ease-standard),
        border-color var(--dur-2) var(--ease-standard),
        color var(--dur-2) var(--ease-standard),
        transform var(--dur-1) var(--ease-standard);
}

.button-primary {
    background: var(--accent);
    color: var(--accent-contrast);
}

.button-primary:hover {
    background: var(--accent-hover);
}

.button-primary:active {
    transform: translateY(1px);
}

.button-secondary {
    background: var(--surface-raised);
    border-color: var(--border-strong);
    color: var(--ink);
}

.button-secondary:hover {
    background: var(--surface-hover);
}

.button-full {
    width: 100%;
}

/* --- Indicador de espera -------------------------------------------------
   O giro vem de pseudo-elemento, e nao de markup no template.

   Enquanto ele dependia de o template escrever `.button-label` e
   `.button-loading` a mao, so 16 botoes do sistema o tinham — e tres deles
   esqueceram o icone dentro do span, entao mostravam texto parado no lugar
   de um indicador. Todo o resto, incluindo a emissao em lote na SEFAZ,
   ficava sem sinal nenhum. Com o pseudo-elemento, qualquer `.button` ganha
   o estado de graca, e um botao novo nasce coberto.

   O ROTULO PERMANECE visivel, ao contrario da troca de texto anterior: um
   botao que troca "Transmitir SEFAZ" por "Transmitindo..." muda de largura
   no meio do clique e empurra o que esta ao lado. */
.button.is-loading {
    cursor: wait;
    opacity: 0.82;
    /* `pointer-events: none` alem do `disabled` que o JS aplica: cobre o
       caso do <a class="button">, que nao tem atributo disabled. */
    pointer-events: none;
}

.button.is-loading::after {
    animation: spin var(--dur-spinner) linear infinite;
    border: 2px solid currentcolor;
    border-radius: var(--radius-pill);
    border-right-color: transparent;
    content: "";
    display: inline-block;
    flex: none;
    height: 14px;
    width: 14px;
}

/* Os `<span>` legados dos templates ainda nao migrados somem sozinhos: sem
   isto, um botao mostraria rotulo E texto de carregando ao mesmo tempo. */
.button-loading {
    display: none;
}

.app-shell {
    display: grid;
    grid-template-columns: 224px minmax(0, 1fr);
    min-height: 100dvh;
}

.sidebar {
    background: var(--sidebar);
    border-right: 1px solid var(--border);
    display: flex;
    flex-direction: column;
    gap: 30px;
    height: 100dvh;
    padding: 22px 14px;
    position: sticky;
    top: 0;
}

.sidebar .brand {
    display: flex;
    justify-content: center;
    padding: 0 8px;
}

.menu-list {
    display: grid;
    gap: 4px;
}

.menu-section {
    color: var(--ink-faint);
    font-size: 0.66rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    margin: 15px 8px 7px;
    text-transform: uppercase;
}

.menu-link {
    align-items: center;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    color: var(--ink-soft);
    display: flex;
    font-size: 0.84rem;
    gap: 11px;
    min-height: 40px;
    padding: 0 11px;
    transition: background-color 160ms ease, border-color 160ms ease, color 160ms ease;
}

.menu-link:hover {
    background: rgb(255 255 255 / 4%);
    color: var(--ink);
}

.menu-link.is-active {
    background: var(--accent-soft);
    border-color: rgb(29 143 69 / 45%);
    color: var(--accent-ink);
}

.menu-link svg {
    color: var(--ink-faint);
    flex: 0 0 auto;
}

.menu-link.is-active svg {
    color: var(--accent-ink);
}

.app-workspace {
    min-width: 0;
}

.topbar {
    align-items: center;
    background: rgb(15 17 21 / 94%);
    border-bottom: 1px solid var(--border);
    display: flex;
    gap: 20px;
    justify-content: space-between;
    min-height: 64px;
    padding: 10px clamp(20px, 3vw, 44px);
    position: sticky;
    top: 0;
    z-index: 5;
}

.topbar-search {
    align-items: center;
    background: var(--field-bg);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    color: var(--ink-faint);
    display: flex;
    gap: 9px;
    max-width: 460px;
    min-height: 38px;
    padding: 0 11px;
    width: 100%;
}

.topbar-search:focus-within {
    border-color: var(--border-strong);
}

.topbar-search input {
    background: transparent;
    border: 0;
    color: var(--ink);
    min-width: 0;
    outline: 0;
    width: 100%;
}

.topbar-search input::placeholder {
    color: var(--ink-faint);
}

.topbar-user {
    align-items: center;
    display: flex;
    gap: 10px;
}

.user-name {
    color: var(--ink-soft);
    font-size: 0.82rem;
    font-weight: 650;
    margin: 0;
}

.user-avatar {
    align-items: center;
    background: var(--accent);
    border-radius: 50%;
    color: #071b10;
    display: flex;
    font-size: 0.68rem;
    font-weight: 800;
    height: 30px;
    justify-content: center;
    width: 30px;
}

.icon-button {
    align-items: center;
    background: transparent;
    border: 0;
    border-radius: var(--radius-sm);
    color: var(--ink-faint);
    display: inline-flex;
    height: 34px;
    justify-content: center;
    width: 34px;
}

.icon-button:hover {
    background: var(--surface-hover);
    color: var(--ink);
}

.app-content {
    margin: 0 auto;
    max-width: 1240px;
    padding: clamp(30px, 4vw, 52px);
    width: 100%;
}

.page-heading {
    align-items: flex-end;
    display: flex;
    gap: 24px;
    justify-content: space-between;
    margin-bottom: 24px;
}

/* Mais de uma acao no cabecalho: lado a lado, e quebrando linha antes de
   empurrar o titulo. */
.page-heading-acoes {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
}

.page-heading h1 {
    font-size: 2rem;
    line-height: 1.15;
    margin-bottom: 8px;
}

.page-heading p:not(.eyebrow) {
    color: var(--ink-soft);
    line-height: 1.5;
    margin-bottom: 0;
}

.metric-grid {
    display: grid;
    gap: 14px;
    grid-template-columns: repeat(3, minmax(0, 1fr));
}

.metric-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    display: grid;
    gap: 9px;
    min-height: 132px;
    padding: 18px;
}

.metric-card-label {
    color: var(--ink-soft);
    font-size: 0.8rem;
    font-weight: 650;
}

.metric-card small {
    color: var(--ink-faint);
    font-size: 0.76rem;
    line-height: 1.35;
}

.metric-card strong {
    font-family: var(--font-mono);
    font-size: 1.65rem;
    font-variant-numeric: tabular-nums;
    line-height: 1;
}

.empty-state {
    align-items: center;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    display: flex;
    gap: 22px;
    justify-content: space-between;
    margin-top: 18px;
    padding: 24px;
}

.empty-state h2 {
    font-size: 1rem;
    margin-bottom: 6px;
}

.empty-state p {
    color: var(--ink-soft);
    font-size: 0.88rem;
    line-height: 1.45;
    margin-bottom: 0;
}

.nfe-panel {
    background: linear-gradient(145deg, rgb(30 38 49 / 90%), var(--surface));
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: inset 0 1px 0 rgb(255 255 255 / 3%), var(--shadow);
    padding: 22px;
}

/* `flex-wrap` obrigatorio desde que a barra de selecao passou a morar aqui:
   titulo + barra + contador nao cabem em 375px lado a lado, e sem a quebra
   eles empurravam a pagina inteira, criando 63px de transbordo horizontal
   numa tela que nao tinha nenhum. */
.panel-heading {
    align-items: flex-start;
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-md) var(--space-lg);
    justify-content: space-between;
    margin-bottom: var(--space-lg);
}

/* O bloco de titulo pode encolher; sem `min-width: 0` um titulo longo
   estabelece um piso de largura que a quebra de linha nao resolve. */
.panel-heading > div:first-child {
    flex: 1 1 220px;
    min-width: 0;
}

.panel-heading h2,
.action-panel h2 {
    font-size: 1rem;
    margin-bottom: 6px;
}

.panel-heading p,
.action-panel p,
.empty-copy {
    color: var(--ink-soft);
    font-size: 0.85rem;
    line-height: 1.5;
    margin-bottom: 0;
}

.data-table-wrap {
    overflow-x: auto;
}

.data-table {
    border-collapse: collapse;
    min-width: 680px;
    width: 100%;
}

.data-table th {
    color: var(--ink-faint);
    font-size: var(--text-2xs);
    font-weight: var(--weight-bold);
    letter-spacing: 0.09em;
    padding: 0 var(--space-md) var(--space-sm);
    text-align: left;
    text-transform: uppercase;
}

/* A altura da linha e o que decide quantos documentos cabem na tela, e o
   operador passa o dia nesta lista. `--leading-tight` faz mais trabalho aqui
   do que a reducao de corpo: a celula tinha altura de linha 1.5 herdada, que
   e medida de prosa, nao de dado tabular. */
.data-table td {
    border-top: 1px solid var(--border);
    color: var(--ink-soft);
    font-size: var(--text-sm);
    line-height: var(--leading-tight);
    padding: var(--space-sm) var(--space-md);
    vertical-align: middle;
}

.data-table td:first-child,
.data-table th:first-child {
    padding-left: 0;
}

.data-table td:last-child,
.data-table th:last-child {
    padding-right: 0;
}

.data-table small {
    color: var(--ink-faint);
    display: block;
    font-size: var(--text-xs);
    margin-top: var(--space-3xs);
}

/* Coluna numerica: digito de largura fixa e alinhamento a direita.
   Sem `tabular-nums` o "1" e mais estreito que o "8" na maioria das fontes,
   entao R$ 1.111,11 e R$ 8.888,88 ficam com larguras diferentes e a coluna
   deixa de poder ser lida na vertical — que e a unica razao de existir de
   uma coluna de valores. */
.data-table td.num,
.data-table th.num {
    font-variant-numeric: tabular-nums;
    text-align: right;
}

/* As duas listagens de documento fiscal sao as mais densas do sistema e as
   mais visitadas. Um degrau a mais de densidade so nelas. */
.data-table--compacta td {
    padding-bottom: var(--space-xs);
    padding-top: var(--space-xs);
}

.table-link {
    color: var(--ink);
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
}

.table-link:hover {
    color: var(--accent-ink);
}

.mono {
    font-family: var(--font-mono);
    font-size: 0.78rem;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.status-pill {
    white-space: nowrap;
    background: var(--neutro-bg);
    border: 1px solid var(--neutro-border);
    border-radius: var(--radius-pill);
    color: var(--ink-soft);
    display: inline-flex;
    font-size: var(--text-2xs);
    font-weight: var(--weight-semibold);
    letter-spacing: 0.02em;
    line-height: var(--leading-tight);
    padding: var(--space-3xs) var(--space-sm);
}

/* --- Familias semanticas de status ---------------------------------------
   Ate a feature 003 cada slug do model tinha o proprio bloco de regra, e um
   status novo em Python exigia CSS novo — foi assim que 25 seletores
   acabaram acoplados a nomes de constante do dominio.

   Agora ha CINCO familias, e todo status herda uma. A tag
   `{% templatetag openblock %} status_pill {% templatetag closeblock %}` emite a classe da familia, e o mapa
   slug -> familia vive em Python, ao lado das `choices`.

   OS SELETORES POR SLUG CONTINUAM AQUI, de proposito. Dez chamadas migraram
   para a tag; as que sobraram derivam o slug em tempo de render — o nivel de
   validade do certificado (`certificado-{{ nivel }}`) e os estados
   condicionais do robo de e-mail. Passa-los pela tag exigiria montar o slug
   no template com filtros encadeados, que e menos legivel do que a linha que
   substituiria. Como os slugs agora COMPARTILHAM a regra da familia em vez
   de terem bloco proprio, mante-los nao custa duplicacao nenhuma: sao nomes
   a mais no seletor, nao declaracoes a mais.

   MUDANCA DE SIGNIFICADO: `processando` e `transmitindo` sairam do amarelo
   (atencao) para o azul (em andamento). Trabalho correndo nao e alerta, e
   pinta-lo de amarelo fazia o operador procurar problema onde nao havia. */

.status-pill--positivo,
.status-concluido,
.status-aprovado,
.status-conferido,
.status-pronto_para_emissao,
.status-autorizado,
.status-averbado,
.status-importado,
.status-certificado-ok {
    background: var(--accent-soft);
    border-color: var(--accent-border);
    color: var(--accent-ink);
}

.status-pill--atencao,
.status-parcial,
.status-preparado_pendente,
.status-ja_existia,
.status-sem_anexo,
.status-certificado-atencao {
    background: var(--warning-soft);
    border-color: var(--warning-border);
    color: var(--warning);
}

.status-pill--negativo,
.status-erro,
.status-rejeitado,
.status-erro_transmissao,
.status-erro_averbacao,
.status-cancelado,
.status-falha,
.status-certificado-critico,
.status-certificado-vencido {
    background: var(--negative-soft);
    border-color: var(--negative-border);
    color: var(--negative-ink);
}

/* Em andamento: azul, e nao amarelo. */
.status-pill--processando,
.status-processando,
.status-transmitindo {
    background: var(--info-soft);
    border-color: var(--info-border);
    color: var(--info);
}

/* Neutro e o PADRAO: `.status-pill` sozinho ja pinta assim, entao um status
   desconhecido vindo do servidor renderiza sem quebrar o layout. */
.status-pill--neutro {
    background: var(--neutro-bg);
    border-color: var(--neutro-border);
    color: var(--ink-soft);
}

.nfe-form {
    display: grid;
    gap: 20px;
    max-width: 640px;
}

.cte-filtros {
    align-items: end;
    display: flex;
    flex-wrap: wrap;
    gap: 16px;
}

.cte-filtros .form-field {
    margin-bottom: 0;
    min-width: 200px;
}

/* Sem isso o botao (min-height: 40px) fica mais baixo que os inputs/selects
   ao lado (min-height: 46px) e a linha parece desalinhada mesmo com
   `align-items: end`. */
.cte-filtros .button {
    min-height: 46px;
}

.inline-upload-form input[type="file"] {
    height: 1px;
    opacity: 0;
    position: absolute;
    width: 1px;
}

.inline-upload-form label {
    cursor: pointer;
}

/* Seletor de arquivo do certificado. O `input[type=file]` nativo renderiza
   com o chrome claro do navegador e destoa do tema escuro -- some, e quem
   aparece e a `<label>` estilizada como botao. Mesma tecnica do
   `.inline-upload-form`, com o nome do arquivo escolhido ao lado. */
.certificado-arquivo {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    min-height: 46px;
}

/* `div.` e o `.form-field` no seletor nao sao decorativos: sem eles esta
   regra tem especificidade menor que a de
   `.form-field input:not([type=checkbox]):not([type=radio])` la em cima, que
   impoe `width: 100%` e `min-height: 46px`. O input continuaria invisivel
   (`opacity: 0` vence, porque a regra generica nao define opacidade) mas
   ocuparia 1265x46px ABSOLUTO por cima do painel -- capturando cliques de
   toda a area e criando barra de rolagem horizontal na pagina.
   `min-height: 0` e obrigatorio junto: `min-height` vence `height`. */
.form-field div.certificado-arquivo input[type="file"] {
    height: 1px;
    min-height: 0;
    opacity: 0;
    padding: 0;
    position: absolute;
    width: 1px;
}

/* `label` dentro de `.form-field` herdaria o estilo de rotulo de campo
   (0.82rem, cor apagada); aqui ela e um botao. */
.certificado-arquivo label.button {
    color: var(--ink);
    cursor: pointer;
    font-size: 0.84rem;
    font-weight: 700;
    margin: 0;
}

.certificado-arquivo-nome {
    color: var(--ink-faint);
    font-size: 0.8rem;
    /* Nome longo nao pode empurrar o botao para fora do painel. */
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.certificado-arquivo-nome.is-escolhido {
    color: var(--accent-ink);
}

.upload-dropzone {
    background: rgb(15 17 21 / 42%);
    border: 1px dashed var(--border-strong);
    border-radius: var(--radius-md);
    display: grid;
    min-height: 200px;
    padding: 0;
    text-align: center;
    transition: background-color .15s ease, border-color .15s ease;
}

.upload-dropzone-trigger {
    align-items: center;
    cursor: pointer;
    display: grid;
    gap: 6px;
    height: 100%;
    justify-items: center;
    padding: 28px;
    width: 100%;
}

.upload-dropzone-trigger > svg {
    color: var(--accent-ink);
    height: 30px;
    width: 30px;
}

.upload-dropzone-title {
    color: var(--ink);
    font-weight: 650;
}

.upload-dropzone input[type="file"] {
    height: 1px;
    opacity: 0;
    position: absolute;
    width: 1px;
}

.upload-dropzone:hover {
    background: rgb(255 255 255 / 4%);
    border-color: var(--ink-faint);
}

.upload-dropzone-trigger:active {
    background: rgb(255 255 255 / 6%);
}

.upload-dropzone:has(input[type="file"]:focus-visible) {
    border-color: var(--accent);
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.upload-dropzone.is-dragover {
    background: var(--accent-soft);
    border-color: var(--accent);
}

.upload-dropzone p,
.field-error {
    color: var(--ink-faint);
    font-size: var(--text-xs);
    line-height: var(--leading-normal);
    margin: 0;
}

/* O erro de campo era escrito ora assim, ora como `.alert.alert-error` —
   estilo de alerta de PAGINA emprestado para dentro de um campo, onde fica
   desproporcional e rouba a atencao do proprio campo que errou. Um nome so. */
.field-error {
    color: var(--negative-ink);
    margin-top: var(--space-2xs);
}

.import-errors {
    background: rgb(255 107 118 / 8%);
    border: 1px solid rgb(255 107 118 / 28%);
    border-radius: var(--radius-sm);
    color: var(--negative-ink);
    font-size: 0.84rem;
    line-height: 1.5;
    margin-top: 20px;
    padding: 14px;
}

.import-errors p {
    margin: 6px 0 0;
}

.detail-grid {
    display: grid;
    gap: 18px;
    grid-template-columns: minmax(0, 1.6fr) minmax(260px, 0.7fr);
}

.detail-list {
    display: grid;
    gap: var(--space-md) var(--space-xl);
    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
    margin: 0;
}

/* 3px entre rotulo e valor os deixava GRUDADOS: como o rotulo e maiusculo e
   pequeno e o valor e maior, sem respiro entre eles o par lia como uma
   palavra so de duas alturas. 6px separa sem soltar — o par continua lendo
   como uma unidade, e o `gap` de 12px entre pares e o dobro disso, que e o
   que mantem a hierarquia clara. */
.detail-list div {
    display: grid;
    gap: var(--space-xs);
    min-width: 0;
}

.detail-list dt {
    color: var(--ink-faint);
    font-size: var(--text-2xs);
    letter-spacing: 0.04em;
    line-height: var(--leading-tight);
    text-transform: uppercase;
}

.detail-list dd {
    color: var(--ink);
    font-size: var(--text-md);
    line-height: var(--leading-tight);
    margin: 0;
    overflow-wrap: break-word;
}

/* Histórico do CT-e: uma linha do tempo, não pares curtos de campo/valor --
   ver comentário em `cte/_cte_detalhe.html`. Uma coluna só, com separador
   sutil entre eventos em vez do `gap` largo do grid padrão. */
.detail-list--historico {
    grid-template-columns: 1fr;
}

.detail-list--historico div {
    border-bottom: 1px solid var(--border);
    padding-bottom: 10px;
}

.detail-list--historico div:last-child {
    border-bottom: 0;
    padding-bottom: 0;
}

.action-panel {
    align-content: start;
    display: grid;
    gap: 14px;
}

.action-panel h2,
.action-panel p {
    margin: 0;
}

.action-panel form {
    display: grid;
}

.button-danger {
    background: var(--negative-soft);
    border-color: var(--negative-border);
    color: var(--negative);
}

.button-danger:hover {
    background: rgb(255 107 118 / 18%);
}

.alert-success {
    background: var(--accent-soft);
    border: 1px solid var(--accent-border);
    color: var(--accent-ink);
}

.alert-warning {
    background: var(--warning-soft);
    border: 1px solid rgb(245 199 100 / 35%);
    color: var(--warning);
}

.alert-info,
.alert-debug {
    background: var(--info-soft);
    border: 1px solid rgb(88 166 255 / 35%);
    color: var(--info);
}

/* Painel operacional do NF-e/CT-e/Home: usa quase toda a largura da
   viewport, sem afetar o container padrao (1240px) das demais paginas do
   app. Escopado via `.nfe-dashboard`, o wrapper que a propria pagina
   declara. Preso a telas acima do breakpoint mobile para nao brigar em
   especificidade com o `padding` de 16px que a media query de 640px define
   para `.app-content`. */
@media (min-width: 641px) {
    .app-content:has(.nfe-dashboard),
    .app-content:has(.config-dashboard),
    .app-content:has(.dashboard) {
        max-width: none;
        padding-inline: clamp(24px, 3vw, 48px);
    }
}

/* Espacamento uniforme entre os blocos empilhados da pagina (cabecalho,
   filtros, paineis) -- sem isso cada painel depende de ter sua propria
   `margin-bottom`, e paineis sem essa margem ficam colados um no outro. Os
   dois filhos que ja tem `margin-bottom` proprio (`.page-heading`,
   `.nfe-intro-grid`) tem a margem zerada aqui para nao somar com o `gap`. */
.nfe-dashboard {
    display: flex;
    flex-direction: column;
    gap: 24px;
}

.nfe-dashboard > .page-heading,
.nfe-dashboard > .nfe-intro-grid {
    margin-bottom: 0;
}

/* Tela unica do dominio NF-e: upload dominante a esquerda (~65%),
   como-funciona a direita (~35%), lado a lado em telas largas. */
.nfe-intro-grid {
    align-items: stretch;
    display: grid;
    gap: 20px;
    grid-template-columns: minmax(0, 2fr) minmax(320px, 1fr);
    margin-bottom: 24px;
}

.file-types {
    display: grid;
    gap: 12px;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    margin-top: 14px;
}

.type-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    display: grid;
    gap: 4px;
    padding: 12px 14px;
}

.type-card strong {
    align-items: center;
    color: var(--ink);
    display: flex;
    font-size: 0.82rem;
    gap: 6px;
}

.type-card strong svg {
    height: 15px;
    width: 15px;
}

.type-card--recommended strong {
    color: var(--accent-ink);
}

.type-card span {
    color: var(--ink-soft);
    font-size: 0.78rem;
    line-height: 1.45;
}

.example-import {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    margin-top: 14px;
}

.example-import p {
    color: var(--ink-faint);
    font-size: 0.8rem;
    margin: 0;
}

/* Ponto neutro em cores ja existentes: reorganiza a apresentacao de
   rota/CT-e sem fabricar um sistema de cores de rota que ainda nao existe. */
.status-dot {
    background: currentColor;
    border-radius: 50%;
    display: inline-block;
    height: 6px;
    margin-right: 6px;
    width: 6px;
}

/* A listagem de NF-e tinha larguras fixas em porcentagem por coluna (NF-e
   15%, emitente e destino 16%, rota 12%...). Sairam quando as pilulas
   passaram a quebrar linha: numa tabela a porcentagem vence o conteudo, e a
   coluna de rota ficava presa em 12% -- a pilula caia em quatro linhas a
   1366px enquanto as colunas de nome sobravam. As colunas de texto agora se
   ajustam ao conteudo; so as de pilula tem largura propria (ver "Listagens
   sem barra de rolagem horizontal", no fim do arquivo). */

/* --- Explicacao em "i", em vez de painel ---------------------------------
   O bloco "Como funciona a automacao" era um painel inteiro ao lado do de
   conexao: metade da largura da tela gasta com texto que se le uma vez, e os
   Filtros — que se editam — empurrados para baixo da dobra.

   `<details>` e nao JS, pelo mesmo motivo do menu de acoes do CT-e: abre,
   fecha e navega por teclado sem uma linha de script. O painel e absoluto
   para nao empurrar o conteudo ao abrir, que e o que distingue um aviso de
   um acordeao. */
.info-pop {
    position: relative;
}

.info-pop > summary {
    cursor: pointer;
    list-style: none;
}

.info-pop > summary::-webkit-details-marker,
.info-pop > summary::marker {
    display: none;
}

.info-pop[open] > summary {
    background: var(--surface-hover);
    color: var(--ink);
}

.info-pop-painel {
    background: var(--surface-raised);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-md);
    box-shadow: 0 12px 32px rgb(6 8 11 / 55%);
    display: grid;
    gap: var(--space-sm);
    padding: var(--space-lg);
    position: absolute;
    right: 0;
    top: calc(100% + var(--space-2xs));
    width: min(360px, calc(100vw - var(--space-3xl)));
    z-index: var(--z-sticky);
}

.info-pop-painel h3 {
    color: var(--ink);
    font-size: var(--text-md);
    margin: 0;
}

.info-pop-painel p {
    color: var(--ink-soft);
    font-size: var(--text-xs);
    line-height: var(--leading-normal);
    margin: 0;
}

/* Numerada porque a ordem CARREGA informacao aqui: cada passo so acontece
   depois do anterior. Fora de uma sequencia real, numero e decoracao. */
.info-pop-passos {
    color: var(--ink-soft);
    display: grid;
    font-size: var(--text-xs);
    gap: var(--space-sm);
    line-height: var(--leading-normal);
    margin: 0;
    padding-left: var(--space-lg);
}

.info-pop-passos strong {
    color: var(--ink);
    display: block;
}

/* Linha da tabela clicavel: abre o popup de detalhes da nota. */
.data-table tbody tr[data-modal-url] {
    cursor: pointer;
}

.data-table tbody tr[data-modal-url]:hover {
    background: var(--surface-hover);
}

/* Popup de detalhes (dialog nativo) -- reutilizado por qualquer tela que
   declare `<dialog data-modal>` (NF-e, CT-e). Seletor por atributo, nao por
   ID: cada pagina só tem um desses por vez, mas o nome do ID nao devia
   amarrar o CSS a um dominio especifico. */
/* --- Tamanho do pop-up ---------------------------------------------------
   Ate a feature 003 TODO pop-up era travado em `min(820px, 94vw)` por 88vh —
   do formulario de uma pergunta ao detalhe fiscal de 688 linhas de template.
   Medido em 1440px, o detalhe de CT-e mostrava 34,7% do conteudo e exigia
   1143px de rolagem, com quase metade da tela vazia ao redor.

   O tamanho e proporcional a informacao que o pop-up carrega. `md` continua
   sendo o padrao, entao nada muda para quem nao pede outra coisa. */
dialog[data-modal],
dialog[data-form-dialog] {
    --dialogo-largura: 720px;
    --dialogo-altura: 88vh;

    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-3);
    color: var(--ink);
    max-height: var(--dialogo-altura);
    max-width: none;
    overflow: hidden;
    padding: 0;
    width: min(var(--dialogo-largura), 94vw);
}

dialog[data-tamanho="sm"] {
    --dialogo-largura: 520px;
}

dialog[data-tamanho="lg"] {
    --dialogo-largura: 1100px;
    --dialogo-altura: 92vh;
}

dialog[data-tamanho="full"] {
    --dialogo-largura: 1440px;
    --dialogo-altura: 94vh;
}

dialog[data-modal]::backdrop,
dialog[data-form-dialog]::backdrop {
    background: var(--backdrop);
}

.modal-shell {
    display: grid;
    /* Cabecalho e rodape sao LINHAS DE GRADE fora do rolador: e isso que os
       mantem visiveis enquanto so o corpo rola. A altura vem da custom
       property do pop-up, e nao de um `88vh` fixo, para acompanhar a
       variante de tamanho. */
    grid-template-rows: auto 1fr auto;
    max-height: var(--dialogo-altura, 88vh);
    min-height: 0;
}

/* Num pop-up largo, uma coluna unica de 1400px seria pior que o problema
   que ela resolve: a linha de leitura fica longa demais e o olho perde o
   comeco da proxima.

   O minimo de 480px foi MEDIDO, nao escolhido por gosto. No detalhe de CT-e
   a 1440px, a rolagem excedente por numero de colunas ficou:

     3 colunas (min 360px) -> 812px de rolagem, 45,2% visivel
     2 colunas (min 480px) -> 732px de rolagem, 47,8% visivel  <-- melhor
     1 coluna  (min 640px) -> 746px de rolagem, 47,3% visivel

   Tres colunas parecem aproveitar mais a largura e nao aproveitam: as secoes
   ficam estreitas demais, o conteudo quebra em mais linhas e a altura total
   cresce. Abaixo de ~1000px o `auto-fit` cai para uma coluna sozinho. */
dialog[data-tamanho="full"] .modal-body {
    align-content: start;
    grid-template-columns: repeat(auto-fit, minmax(480px, 1fr));
}

.modal-header {
    align-items: flex-start;
    border-bottom: 1px solid var(--border);
    display: flex;
    gap: 12px;
    justify-content: space-between;
    padding: var(--space-xl) var(--space-2xl);
}

.modal-header h2 {
    font-size: 1.15rem;
    margin: 0 0 4px;
}

.modal-header p {
    color: var(--ink-soft);
    font-size: 0.82rem;
    margin: 0;
}

.modal-body {
    display: grid;
    gap: 16px;
    min-height: 0;
    min-width: 0;
    overflow-x: hidden;
    overflow-y: auto;
    padding: var(--space-xl) var(--space-2xl);
}

.modal-section {
    background: rgb(255 255 255 / 2%);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    min-width: 0;
    padding: 16px 18px;
}

.modal-section-title {
    color: var(--ink-faint);
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    margin: 0 0 12px;
    text-transform: uppercase;
}

/* Mesmo título de seção, usado fora de um `.modal-section` (ex.: dentro de
   um `.nfe-panel` que já tem sua própria tabela acima) -- precisa de um
   respiro por cima que o `.modal-section-title` sozinho não tem. */
.modal-section-title--spaced {
    margin-top: 20px;
}

/* Form pequeno embutido numa célula de tabela (ex.: ativar/desativar uma
   cidade) -- não pode herdar o `display: grid` genérico de `.action-panel
   form`, senão o botão ocupa a célula inteira. */
.table-inline-form {
    display: inline-block;
    margin-top: 6px;
}

.modal-warning {
    background: var(--warning-soft);
    border: 1px solid rgb(245 199 100 / 35%);
    border-radius: var(--radius-sm);
    color: var(--warning);
    font-size: 0.82rem;
    min-width: 0;
    padding: 12px 14px;
}

.modal-warning ul {
    margin: 6px 0 0;
    padding-left: 18px;
}

/* Cada pendência de emissao ocupa uma linha propria: a lista costuma vir
   com o nome da tag do layout seguido de uma frase, e sem respiro entre os
   itens vira um paragrafo unico ilegivel. */
.modal-warning li + li {
    margin-top: 4px;
}

.modal-warning code {
    font-family: var(--font-mono);
    font-size: 0.76rem;
    opacity: 0.85;
}

/* Grade de campos editáveis dentro de uma seção do popup (ex.: revisão do
   CT-e) -- mesmo ritmo visual do `.detail-list` ao lado (mesmo `gap`,
   mesma lógica `auto-fill`), só que com inputs em vez de texto estático,
   para o popup ficar simétrico entre o modo leitura e o modo edição. */
.form-grid {
    display: grid;
    gap: 16px 20px;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 250px), 1fr));
    /* Formulario nao acompanha a largura do monitor: um campo de 1600px e
       tao ruim de ler quanto de preencher. */
    max-width: 1080px;
}

.form-grid .form-field {
    margin-bottom: 0;
}

.form-grid .form-field--full {
    grid-column: 1 / -1;
}

/* Campo que precisa de duas colunas sem ocupar a linha toda (ex.: nome de
   rua ao lado do numero). */
.form-grid .form-field--wide {
    grid-column: span 2;
}

@media (max-width: 640px) {
    .form-grid .form-field--wide {
        grid-column: 1 / -1;
    }
}

.modal-inline-action {
    margin-top: 14px;
}

/* Valor do frete cobrado à mão. Fica na seção "Valores", logo abaixo da
   tabela de componentes, porque é ali que o operador raciocina sobre preço
   -- o `form=` do input o mantém pertencendo ao formulário de revisão, que
   vive na seção "Carga". Largura contida: é um valor monetário, não um
   campo de texto, e esticá-lo até a borda sugeriria o contrário. */
.frete-manual {
    margin-top: 14px;
    max-width: 260px;
}

/* Alíquota do ICMS ajustada à mão. Mesmo raciocínio de `.frete-manual`:
   fica junto do valor na seção "Valores", contida porque é um percentual,
   não texto livre. */
.aliquota-manual {
    margin-top: 14px;
    max-width: 260px;
}

/* Consulta avulsa por chave: campo largo (44 dígitos) e botão ao lado, na
   mesma linha de base. `align-items: flex-end` alinha o botão pela BASE do
   campo, e não pelo topo -- senão ele flutua na altura do rótulo. */
.consulta-chave-linha {
    align-items: flex-end;
    display: flex;
    gap: 12px;
    max-width: 720px;
}

.consulta-chave-linha .form-field {
    flex: 1;
    /* Zera a margem herdada para que a base do input e a do botão
       coincidam — mesma correção do popup de importação de NF-e. */
    margin-bottom: 0;
}

.consulta-chave-linha input {
    font-family: var(--font-mono, ui-monospace, monospace);
    font-variant-numeric: tabular-nums;
}

@media (max-width: 640px) {
    .consulta-chave-linha {
        align-items: stretch;
        flex-direction: column;
    }
}

/* Separador entre as duas portas de entrada da importação (arquivo | chave).
   A linha atravessa o container e a palavra "ou" fica sobre o fundo do
   popup, escondendo o trecho central da régua. */
.importar-ou {
    align-items: center;
    display: flex;
    gap: 12px;
    margin: 18px 0;
}

.importar-ou::before,
.importar-ou::after {
    background: var(--border);
    content: "";
    flex: 1;
    height: 1px;
}

.importar-ou span {
    color: var(--text-muted);
    font-size: 0.85rem;
    text-transform: uppercase;
}

/* A linha (campo + botão) e a dica são irmãs, não aninhadas: a dica dentro
   da coluna do campo somava altura e o `align-items: flex-end` alinhava o
   botão pela base DELA, deixando-o solto abaixo do input. */
.importar-por-chave-linha {
    align-items: flex-end;
    display: flex;
    gap: 10px;
}

.importar-por-chave-linha .form-field {
    flex: 1;
    /* Zera a margem inferior herdada de `.form-field` para que a base do
       input e a do botão coincidam de verdade. */
    margin-bottom: 0;
}

.importar-por-chave .field-hint {
    margin-top: 6px;
}

/* A chave tem 44 dígitos: tabular-nums evita que o campo "dance" enquanto
   se digita, e o mono deixa conferir dígito a dígito contra o DANFE. */
.importar-por-chave input {
    font-family: var(--font-mono, ui-monospace, monospace);
    font-variant-numeric: tabular-nums;
}

@media (max-width: 640px) {
    .importar-por-chave-linha {
        align-items: stretch;
        flex-direction: column;
    }
}

/* Campo individual marcado incerto (Identificação, Valores, Carga e
   volumes): destaque contido numa única célula da grade. */
.detail-list > div.field-uncertain {
    background: var(--warning-soft);
    border-radius: var(--radius-sm);
    margin: -6px -8px;
    padding: 6px 8px;
}

.field-uncertain dt {
    color: var(--warning);
}

/* Seção inteira marcada incerta (Emitente/Destinatário/Endereço de entrega
   vindos de PDF): realce discreto no cartão, em vez de pintar cada valor de
   amarelo -- a lista de campos a conferir já está no aviso do topo. */
.modal-section:has(> .detail-list.field-uncertain) {
    border-color: rgb(245 199 100 / 45%);
}

.modal-footer {
    align-items: center;
    border-top: 1px solid var(--border);
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    justify-content: flex-end;
    padding: var(--space-lg) var(--space-2xl);
}

/* O menu "Mais ações" ancora à esquerda e o par secundária+principal à
   direita; `justify-content: space-between` no rodapé separa os dois. */
.acoes-extras {
    margin-right: auto;
    position: relative;
}

.acoes-extras > summary {
    /* `list-style: none` + `::-webkit-details-marker` derruba o triângulo
       nativo nos dois motores -- sem isso o <summary> estilizado como botão
       aparece com uma seta colada no texto. */
    cursor: pointer;
    list-style: none;
}

.acoes-extras > summary::-webkit-details-marker {
    display: none;
}

.acoes-extras-menu {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    bottom: calc(100% + 6px);
    box-shadow: var(--shadow-2);
    display: flex;
    flex-direction: column;
    left: 0;
    min-width: 232px;
    padding: 6px;
    position: absolute;
    /* Abre para CIMA: o rodapé fica no pé do popup, e um menu descendo
       sairia da área visível do <dialog>. */
    z-index: 20;
}

.acoes-extras-menu > button,
.acoes-extras-menu > a {
    align-items: center;
    background: none;
    border: 0;
    border-radius: var(--radius-sm);
    color: var(--text);
    cursor: pointer;
    display: flex;
    font: inherit;
    gap: 10px;
    padding: 9px 10px;
    text-align: left;
    text-decoration: none;
    white-space: nowrap;
}

.acoes-extras-menu > button:hover,
.acoes-extras-menu > a:hover,
.acoes-extras-menu > button:focus-visible,
.acoes-extras-menu > a:focus-visible {
    background: var(--surface-hover, rgb(255 255 255 / 6%));
}

.acoes-extras-menu svg {
    height: 16px;
    width: 16px;
}

/* Cancelar é destrutivo e raro: fica no fim, separado por uma linha, para
   não ser clicado por inércia junto das ações rotineiras acima. */
.acoes-extras-danger {
    border-top: 1px solid var(--border) !important;
    border-radius: 0 0 var(--radius-sm) var(--radius-sm) !important;
    color: var(--danger) !important;
    margin-top: 4px;
    padding-top: 11px !important;
}

.modal-footer-acoes {
    display: flex;
    gap: 10px;
}

.modal-footer-danger {
    margin-right: auto;
}

@keyframes spin {
    to {
        transform: rotate(360deg);
    }
}

@media (max-width: 960px) {
    .app-shell {
        grid-template-columns: 1fr;
    }

    .sidebar {
        gap: 14px;
        height: auto;
        max-width: 100vw;
        min-width: 0;
        padding: 14px 20px;
        position: static;
        width: 100%;
    }

    .sidebar .brand {
        padding: 0;
    }

    .menu-list {
        display: flex;
        gap: 8px;
        max-width: 100%;
        min-width: 0;
        overflow-x: auto;
        padding-bottom: 2px;
        scrollbar-width: thin;
        width: 100%;
    }

    .menu-section {
        display: none;
    }

    .menu-link {
        flex: 0 0 auto;
        white-space: nowrap;
    }

    /* Tablet/notebook menor: a coluna de importacao continua dominante, so
       menos folgada que no desktop grande. */
    .nfe-intro-grid {
        grid-template-columns: minmax(0, 1.5fr) minmax(280px, 1fr);
    }
}

@media (max-width: 640px) {
    .auth-layout {
        padding: 16px;
    }

    .login-card {
        border-radius: var(--radius-md);
        padding: 28px 22px;
    }

    .brand--login {
        margin-bottom: 26px;
    }

    .brand--login .brand-logo {
        height: 56px;
    }

    .topbar {
        flex-wrap: wrap;
        gap: 10px;
        padding: 10px 16px;
    }

    .topbar-search {
        flex-basis: 100%;
        max-width: none;
        order: 2;
    }

    .topbar-user {
        margin-left: auto;
    }

    .topbar-user>div {
        display: none;
    }

    .app-content {
        padding: 28px 16px;
    }

    .page-heading {
        align-items: flex-start;
        flex-direction: column;
        margin-bottom: 24px;
    }

    .metric-grid {
        grid-template-columns: 1fr;
    }

    .empty-state {
        align-items: flex-start;
        flex-direction: column;
    }

    .detail-grid {
        grid-template-columns: 1fr;
    }

    .nfe-intro-grid {
        grid-template-columns: 1fr;
    }

    .file-types {
        grid-template-columns: 1fr;
    }

    .upload-dropzone {
        min-height: 180px;
    }

    .detail-list {
        grid-template-columns: 1fr;
    }

    dialog[data-modal],
    dialog[data-form-dialog] {
        max-height: 94vh;
    }

    .modal-footer {
        flex-direction: column-reverse;
    }

    .modal-footer .button {
        width: 100%;
    }

    .config-grid {
        grid-template-columns: 1fr;
    }

    .panel-heading .switch {
        margin-top: 4px;
    }
}

/* Tela de Configuracoes: sub-navegacao de abas (hoje so "Email de NF-e",
   mas ja em formato de lista para as abas futuras). */
/* Rola dentro de si, nao no body. Com seis abas a tira passa de 375px e,
   sem `overflow-x`, quem rolava era a PAGINA -- deslocando cabecalho,
   paineis e tudo mais no celular. Regra geral do projeto: conteudo largo
   rola no proprio container. */
.subnav {
    border-bottom: 1px solid var(--border);
    display: flex;
    gap: 4px;
    margin-bottom: 24px;
    overflow-x: auto;
    /* A barra de rolagem embaixo de uma tira de abas parece defeito, e o
       gesto de arrastar ja e obvio no toque. */
    scrollbar-width: none;
}

.subnav::-webkit-scrollbar {
    display: none;
}

.subnav-link {
    border-bottom: 2px solid transparent;
    color: var(--ink-soft);
    /* Sem `flex: none` o flex encolhe as abas ate quebrar o rotulo em duas
       linhas em vez de deixar rolar. */
    flex: none;
    font-size: 0.86rem;
    font-weight: 650;
    margin-bottom: -1px;
    padding: 10px 4px;
    white-space: nowrap;
}

.subnav-link:hover {
    color: var(--ink);
}

.subnav-link.is-active {
    border-bottom-color: var(--accent);
    color: var(--accent-ink);
}

/* Texto de apoio abaixo de um campo, sem peso de erro.

   `.form-field small` era um segundo nome para a MESMA coisa, com corpo
   diferente (0.76 contra 0.8rem): a dica mudava de tamanho conforme a tela
   que o autor tinha copiado. Agora `.field-hint` e o unico, e o seletor de
   `small` cru apenas herda dele enquanto restar markup antigo. */
.field-hint,
.form-field small {
    color: var(--ink-faint);
    font-size: var(--text-xs);
    line-height: var(--leading-normal);
    margin: var(--space-xs) 0 0;
}

/* Formulario da tela de Configuracoes: um .nfe-panel por secao, com
   respiro entre eles -- sem isso os cartoes ficam colados um no outro. */
.config-form {
    display: grid;
    gap: var(--space-md);
}

/* Espacamento uniforme entre os blocos empilhados da tela, mesmo padrao ja
   usado por `.nfe-dashboard`. Antes era `> .nfe-panel { margin-top }`, que so
   alcanca filho DIRETO: em Certificado digital o segundo painel esta dentro
   de um `<form>`, ficava fora da regra e colava no primeiro. Com `gap`, o
   respiro nao depende de onde o painel esta aninhado.

   `.page-heading` e `.subnav` zeram a margem propria para nao somar com o
   `gap` e abrir um vao maior que o dos paineis. */
.config-dashboard {
    display: flex;
    flex-direction: column;
    gap: var(--space-md);
}

.config-dashboard > .page-heading,
.config-dashboard > .subnav {
    margin-bottom: 0;
}

/* Campos de conexao pareados (servidor+porta, usuario+senha, pasta+
   intervalo), como no restante dos formularios do app -- nao a grade larga
   de 6 colunas que o auto-fit produzia num container sem limite de largura.
   `align-items: start` evita que um campo mais alto (ex.: Senha, com dica
   abaixo) estique e desalinhe o campo vizinho na mesma linha. */
/* Responsiva, nao fixa em duas colunas: num monitor largo a grade de dois
   campos deixava metade da tela vazia e empilhava o formulario numa coluna
   longa de rolagem. `auto-fill` com piso de 260px mantem os campos legiveis
   e usa o espaco que existir. */
.config-grid {
    align-items: start;
    display: grid;
    gap: 18px 24px;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 260px), 1fr));
}

/* Campo que precisa de mais espaco que o vizinho (ex.: endereco, texto de
   observacao). */
.config-grid .form-field--wide {
    grid-column: span 2;
}

.config-grid .form-field--full {
    grid-column: 1 / -1;
}

@media (max-width: 640px) {
    .config-grid .form-field--wide {
        grid-column: 1 / -1;
    }
}

.config-subheading {
    color: var(--ink-faint);
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    margin: 20px 0 10px;
    text-transform: uppercase;
}

/* Barra de acoes grudada na base. `.config-actions` continua valendo como
   nome antigo enquanto as telas migram — sao a mesma coisa. */
.barra-acoes,
.config-actions {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-md);
    justify-content: flex-end;
    margin-top: var(--space-lg);
}

/* DOCK, e nao faixa sangrada. A versao anterior estendia a barra ate as
   bordas da area util com `margin-inline` negativo e so uma linha no topo:
   colada na base da janela e com canto vivo, ela lia como uma emenda da
   pagina — o unico elemento da tela sem raio nem contorno, no meio de uma
   interface inteira feita de cartoes.

   Agora ela e um cartao como os outros: contorno fechado, mesmo raio dos
   paineis e um respiro ate a base. A elevacao vem de sombra propria, mais
   forte que a dos paineis, porque ela FLUTUA sobre o conteudo que rola por
   baixo — sem isso o texto passava rente e encostava na borda. */
.barra-acoes {
    /* `color-mix` com fundo solido declarado antes: sem `backdrop-filter` a
       barra fica opaca, que perde o refinamento e nao a funcao. */
    background: var(--surface-raised);
    background: color-mix(in srgb, var(--surface-raised) 88%, transparent);
    backdrop-filter: blur(10px);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-lg);
    bottom: var(--space-md);
    box-shadow: 0 8px 24px rgb(6 8 11 / 45%), inset 0 1px 0 rgb(255 255 255 / 4%);
    margin-inline: 0;
    padding: var(--space-md) var(--space-lg);
    position: sticky;
    z-index: var(--z-sticky);
}

/* O dock encosta na base da janela quando a pagina termina. O respiro embaixo
   e do CONTAINER, e nao margem da barra: `margin-bottom` numa `position:
   sticky` desloca o ponto de parada e ela para antes da hora. */
.config-dashboard,
.nfe-dashboard {
    padding-bottom: var(--space-md);
}

.barra-acoes-dica {
    color: var(--ink-faint);
    font-size: var(--text-xs);
    margin-right: auto;
}

/* Painel de status do robo, na tela de Logs de e-mail. */
.log-status-grid {
    display: grid;
    gap: 18px 24px;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}

.log-status-item p {
    margin: 0;
}

.log-status-item .status-pill {
    margin-top: 2px;
}

.switch-group {
    display: grid;
    gap: 14px;
    margin-top: 14px;
}

/* Interruptor on/off em CSS puro -- sem JS, so um checkbox redesenhado. */
.switch {
    align-items: center;
    cursor: pointer;
    display: inline-flex;
    gap: 10px;
    width: fit-content;
}

.switch input {
    -webkit-appearance: none;
    appearance: none;
    background: var(--field-bg);
    border: 1px solid var(--border-strong);
    border-radius: 999px;
    cursor: pointer;
    flex: none;
    height: 22px;
    margin: 0;
    position: relative;
    transition: background-color 0.15s ease, border-color 0.15s ease;
    width: 40px;
}

.switch input::before {
    background: var(--ink-faint);
    border-radius: 50%;
    content: "";
    height: 16px;
    left: 2px;
    position: absolute;
    top: 2px;
    transition: transform 0.15s ease, background-color 0.15s ease;
    width: 16px;
}

.switch input:checked {
    background: var(--accent-soft);
    border-color: var(--accent);
}

.switch input:checked::before {
    background: var(--accent);
    transform: translateX(18px);
}

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

.switch-label {
    color: var(--ink);
    font-size: 0.86rem;
    font-weight: 600;
}

/* Resultado das mensagens processadas pelo robo de e-mail. */
/* ==========================================================================
   Movimento — o orcamento inteiro
   --------------------------------------------------------------------------
   Nove usos, nenhum a mais, todos com token de duracao e curva. A restricao
   nao e estetica: e o que separa "profissional" de "enfeitado" num console
   que o operador usa oito horas por dia. Animacao que ele ve mil vezes ao
   dia precisa ser curta o bastante para nao ser notada.

   Os nove: abertura de pop-up · fundo do pop-up · entrada de aviso · saida
   de aviso · realce de linha · acionamento de botao · painel ocupado ·
   selecao de atalho de filtro · indicador de espera.

   PROIBIDO: transicao entre paginas, entrada escalonada de elementos,
   animacao disparada por rolagem.

   Movimento de pop-up e de aviso mora nas secoes dos respectivos
   componentes, junto do que eles animam. Aqui ficam os transversais.
   ========================================================================== */

/* Realce de linha. Era instantaneo; a transicao curta evita o "pisca" ao
   passar o mouse rapido por uma lista de 25 linhas. */
.data-table tbody tr[data-modal-url] {
    transition: background-color var(--dur-1) var(--ease-standard);
}

/* Acionamento: o afundar de 1px era so do botao primario. Um secundario que
   nao afunda parece nao ter sido clicado — que e literalmente a reclamacao
   que esta feature existe para resolver. */
.button:active:not(:disabled):not(.is-loading) {
    transform: translateY(1px);
}

/* Painel ocupado: o esmaecimento diz "isto esta desatualizado" e a barra
   indeterminada diz "e tem alguem trabalhando nisso". Sozinho, o
   esmaecimento pareceria desabilitado. */
[data-relatorio-palco].is-remontando,
[data-dashboard-palco].is-remontando {
    position: relative;
}

[data-relatorio-palco].is-remontando::before,
[data-dashboard-palco].is-remontando::before {
    animation: barra-indeterminada 1.1s var(--ease-standard) infinite;
    background: linear-gradient(
        90deg,
        transparent,
        var(--accent) 45%,
        var(--accent-ink) 55%,
        transparent
    );
    border-radius: var(--radius-pill);
    content: "";
    height: 2px;
    left: 0;
    position: absolute;
    right: 0;
    top: 0;
    z-index: var(--z-sticky);
}

@keyframes barra-indeterminada {
    0% {
        transform: translateX(-60%) scaleX(0.4);
    }

    50% {
        transform: translateX(0) scaleX(0.7);
    }

    100% {
        transform: translateX(60%) scaleX(0.4);
    }
}

/* --------------------------------------------------------------------------
   A supressao. Cobre tudo acima e tudo que vier depois, porque casa `*` e
   porque todo movimento novo e declarado com token de duracao — nenhum
   escapa daqui.

   `animation-iteration-count: 1` alem da duracao: zerar so a duracao deixa
   uma animacao infinita rodando mil vezes por segundo em 0.01ms, o que
   custa CPU sem aparecer. O indicador de espera e o caso.

   O estado continua legivel sem movimento: botao ocupado tem `aria-busy` e
   opacidade; painel ocupado tem esmaecimento. O giro e reforco, nao a
   mensagem.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {

    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        scroll-behavior: auto !important;
        transition-duration: 0.01ms !important;
    }
}
/* ==========================================================================
   Formularios — camada compartilhada
   ========================================================================== */

/* Checkbox solto dentro de um `.form-field` vira interruptor, sem exigir
   que o template o embrulhe em `.switch`. Os formularios gerados por
   `{% for field in form %}` renderizam `{{ field }}` cru: sem esta regra o
   navegador desenha a caixa nativa gigante sobre o tema escuro. */
.form-field input[type="checkbox"] {
    appearance: none;
    background: var(--field-bg);
    border: 1px solid var(--border-strong);
    border-radius: 999px;
    cursor: pointer;
    flex: none;
    height: 24px;
    justify-self: start;
    margin: 0;
    position: relative;
    transition: background-color 140ms ease, border-color 140ms ease;
    width: 44px;
}

.form-field input[type="checkbox"]::before {
    background: var(--ink-faint);
    border-radius: 50%;
    content: "";
    height: 18px;
    left: 2px;
    position: absolute;
    top: 2px;
    transition: transform 140ms ease, background-color 140ms ease;
    width: 18px;
}

.form-field input[type="checkbox"]:checked {
    background: var(--accent-soft);
    border-color: var(--accent);
}

.form-field input[type="checkbox"]:checked::before {
    background: var(--accent);
    transform: translateX(20px);
}

.form-field input[type="checkbox"]:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* Campo booleano: o rotulo fica AO LADO do interruptor, nao acima — e como
   todo formulario de mercado apresenta um on/off, e evita a coluna de
   rotulo orfa que sobra quando o controle tem 44px de largura. */
.form-field--switch {
    align-items: center;
    /* `.form-field` usa `align-content: start`; aqui a linha precisa ficar
       centrada na caixa de 46px para o interruptor bater com o meio dos
       inputs vizinhos, que tem rotulo acima. */
    align-content: center;
    align-self: end;
    column-gap: 12px;
    grid-template-columns: auto minmax(0, 1fr);
    min-height: 46px;
}

.form-field--switch > label {
    grid-column: 2;
    grid-row: 1;
    margin: 0;
}

.form-field--switch > input[type="checkbox"] {
    grid-column: 1;
    grid-row: 1;
}

/* Dica e erro continuam ocupando a linha inteira abaixo do par. */
.form-field--switch > :not(label):not(input[type="checkbox"]) {
    grid-column: 1 / -1;
}

.form-field .alert {
    margin: 0;
}

/* Agrupador de campos dentro de um cartao. Substitui o `<h3>` solto que
   antes separava blocos ("Adicionar cidade") sem nenhuma hierarquia. */
/* Separador entre blocos. O primeiro bloco de um formulario nao leva linha
   — ele ja vem logo abaixo do cabecalho do cartao. */
.form-section {
    border-top: 1px solid var(--border);
    margin-top: 26px;
    padding-top: 22px;
}

form > .form-section:first-child {
    border-top: 0;
    margin-top: 0;
    padding-top: 0;
}

.form-section-title {
    align-items: baseline;
    color: var(--ink);
    display: flex;
    font-size: 0.92rem;
    font-weight: 700;
    gap: 10px;
    /* Respiro suficiente para o titulo nao encostar no primeiro rotulo da
       grade logo abaixo. */
    margin: 0 0 14px;
}

.form-section-desc {
    color: var(--ink-faint);
    font-size: 0.82rem;
    line-height: 1.5;
    margin: 0 0 16px;
}

/* Rodape de acoes do formulario: separado por uma linha, botao principal a
   direita como manda o padrao de formulario longo. */
.form-actions {
    align-items: center;
    border-top: 1px solid var(--border);
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    justify-content: flex-end;
    margin-top: 24px;
    padding-top: 20px;
}

.form-actions--start {
    justify-content: flex-start;
}

.form-actions .form-actions-hint {
    color: var(--ink-faint);
    font-size: 0.8rem;
    margin: 0 auto 0 0;
}

/* Botao compacto para celula de tabela. O `.button` padrao (40px) empurra a
   altura da linha e faz a tabela parecer um formulario. */
.button-sm {
    font-size: 0.78rem;
    min-height: 32px;
    padding: 0 11px;
}

.button-danger-ghost {
    background: transparent;
    border-color: rgb(255 107 118 / 35%);
    color: var(--negative-ink);
}

.button-danger-ghost:hover {
    background: var(--negative-soft);
}

/* Acoes de uma linha de tabela, lado a lado e alinhadas a direita. */
.table-actions {
    align-items: center;
    display: flex;
    gap: 8px;
    justify-content: flex-end;
}

.table-actions form {
    display: inline-flex;
    margin: 0;
}

/* ==========================================================================
   Mensagens da aplicacao
   ========================================================================== */

/* As variantes de cor por severidade ja existiam; o que faltava era a
   PILHA: sem regra nenhuma para `.message-stack`, dez pendencias de emissao
   viravam dez faixas soltas empurrando o titulo da pagina para fora da tela.
   Aqui a faixa e compacta, dispensavel, e o bloco rola quando a lista passa
   de meia duzia de itens. */
.message-stack {
    display: grid;
    gap: 8px;
    margin-bottom: 22px;
    max-height: 260px;
    overflow-y: auto;
    padding-right: 2px;
}

.message-stack .alert {
    align-items: flex-start;
    border-left-width: 3px;
    display: flex;
    gap: 10px;
    margin: 0;
    padding-right: 40px;
    position: relative;
}

.message-stack .alert::before {
    background: currentcolor;
    border-radius: 50%;
    content: "";
    flex: none;
    height: 7px;
    margin-top: 6px;
    opacity: 0.9;
    width: 7px;
}


.message-dismiss {
    background: transparent;
    border: 0;
    border-radius: 6px;
    color: inherit;
    line-height: 1;
    opacity: 0.55;
    padding: 4px 7px;
    position: absolute;
    right: 6px;
    top: 6px;
}

.message-dismiss:hover {
    background: rgb(255 255 255 / 10%);
    opacity: 1;
}

/* Resumo quando a operacao devolve muitas mensagens de uma vez (as
   pendencias de emissao do CT-e sao o caso tipico). Fechado por padrao, o
   operador abre se quiser a lista completa. */
/* Tom de atencao, nao neutro: o agrupamento so acontece quando uma acao
   devolve varias coisas a resolver. Um resumo cinza faria a recusa passar
   despercebida. */
.message-group {
    background: var(--warning-soft);
    border: 1px solid rgb(245 199 100 / 30%);
    border-radius: var(--radius-sm);
    margin-bottom: 22px;
}

.message-group > summary {
    color: var(--warning);
}

.message-group > summary svg {
    height: 15px;
    width: 15px;
}

.message-group > summary {
    align-items: center;
    cursor: pointer;
    display: flex;
    font-size: 0.84rem;
    font-weight: 650;
    gap: 8px;
    list-style: none;
    padding: 12px 14px;
}

.message-group > summary::-webkit-details-marker {
    display: none;
}

.message-group > summary::after {
    color: var(--ink-faint);
    content: "▾";
    margin-left: auto;
    transition: transform 140ms ease;
}

.message-group[open] > summary::after {
    transform: rotate(180deg);
}

.message-group .message-stack {
    margin: 0;
    padding: 0 12px 12px;
}

/* ==========================================================================
   Barra de filtros (listagens de NF-e e CT-e)
   ========================================================================== */

/* Flex, nao grid: a mesma barra serve telas com tres filtros e telas com
   um so. Com `grid-template-columns` fixo, o caso de um campo gerava
   trilhas vazias e jogava o botao para a linha de baixo. */
.filter-bar {
    align-items: end;
    display: flex;
    flex-wrap: wrap;
    gap: 14px 16px;
}

.filter-bar .form-field {
    flex: 1 1 190px;
}

/* O campo de busca e o principal: ganha o dobro do espaco livre. */
.filter-bar .form-field:first-child {
    flex: 2 1 260px;
}

.filter-bar .form-field {
    margin-bottom: 0;
}

.filter-bar .filter-actions {
    display: flex;
    gap: 8px;
}

/* Botao e campo na MESMA altura. O valor era 46px, de quando o campo media
   46 — depois que campo e botao foram alinhados em 38, esta regra passou a
   fazer o contrario do que o comentario dizia: deixava o botao mais ALTO
   que os controles ao lado. */
.filter-bar .button {
    min-height: 38px;
}

@media (max-width: 640px) {
    .filter-bar .form-field,
    .filter-bar .form-field:first-child {
        flex-basis: 100%;
    }

    .filter-bar .filter-actions {
        width: 100%;
    }

    .filter-bar .filter-actions .button {
        flex: 1;
    }
}

/* Chips de atalho por rota, acima da tabela: um clique filtra, sem abrir o
   select. */
.chip-row {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 16px;
}

.chip {
    background: var(--surface-raised);
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
    color: var(--ink-soft);
    display: inline-flex;
    align-items: center;
    font-size: var(--text-xs);
    font-weight: var(--weight-semibold);
    gap: var(--space-xs);
    min-height: 28px;
    padding: 0 var(--space-md);
    transition: background-color var(--dur-1) var(--ease-standard),
        border-color var(--dur-1) var(--ease-standard),
        color var(--dur-1) var(--ease-standard);
}

.chip:hover {
    background: var(--surface-hover);
    color: var(--ink);
}

.chip.is-active {
    background: var(--accent-soft);
    border-color: var(--accent);
    color: var(--accent-ink);
}


/* Cartao trazido para o primeiro plano (ex.: a regra da cidade que o
   operador acabou de abrir). Sem isto o formulario aparecia no fim de uma
   pagina longa sem nenhuma indicacao de que era o alvo do clique. */
.nfe-panel.is-highlighted {
    border-color: var(--accent);
    box-shadow: 0 0 0 1px rgb(29 143 69 / 22%), var(--shadow);
}

/* Linha da tabela correspondente ao item em edicao. */
.data-table tbody tr.is-selected {
    background: var(--accent-soft);
}

/* Só o fundo marca a linha em edicao. Uma barra lateral desenhada com
   `inset box-shadow` nao ocupa espaco e encostava no texto, e compensar com
   padding so nessa linha desalinhava a coluna em relacao as demais. */

/* Nota explicativa curta dentro de um cartao. Substitui os paineis inteiros
   de texto que ocupavam o topo das telas de cadastro empurrando a tabela e
   o formulario para fora da primeira dobra. */
.callout {
    background: rgb(88 166 255 / 7%);
    border: 1px solid rgb(88 166 255 / 22%);
    border-radius: var(--radius-sm);
    color: var(--ink-soft);
    display: flex;
    font-size: 0.82rem;
    gap: 10px;
    line-height: 1.55;
    margin-bottom: 20px;
    padding: 12px 14px;
}

.callout svg {
    color: var(--info);
    flex: none;
    height: 16px;
    margin-top: 2px;
    width: 16px;
}

.callout p {
    margin: 0;
}

.callout strong {
    color: var(--ink);
}

/* Formulario de uma unica linha dentro de uma celula de tabela (select +
   botao). Sem isto o `.form-field` empilhava o controle acima do botao e a
   linha da tabela dobrava de altura. */
/* O `<select>` de tomador nao divide mais a celula com um botao Salvar por
   linha: ele ocupa a coluna, com largura de leitura em vez de esticar ate a
   borda da tabela. */
.celula-tomador select {
    max-width: 260px;
    min-height: 38px;
    padding-block: 8px;
    width: 100%;
}

.row-form {
    align-items: center;
    display: flex;
    gap: 10px;
}

.row-form .form-field {
    margin-bottom: 0;
    min-width: 190px;
}

.row-form select {
    min-height: 38px;
    padding-block: 8px;
}

.callout--warning {
    background: var(--warning-soft);
    border-color: rgb(245 199 100 / 30%);
}

.callout--warning svg {
    color: var(--warning);
}

/* ==========================================================================
   Popup de formulario
   ========================================================================== */

/* Reaproveita a casca do popup de detalhes (`.modal-shell`), mas o corpo
   rola sozinho: formularios de cadastro passam da altura da tela em telas
   menores. */
dialog[data-form-dialog] .modal-body {
    overflow-y: auto;
}

dialog[data-form-dialog] .form-grid {
    max-width: none;
}

/* O `.form-actions` do formulario faz o papel do rodape do popup: fica
   colado embaixo, com a linha divisoria do proprio modal. */
dialog[data-form-dialog] .form-actions {
    border-top: 0;
    margin-top: 22px;
    padding-top: 0;
}

/* Botao de acao principal no cabecalho de um cartao (ex.: "+ Nova rota"). */
.panel-heading .button {
    flex: none;
}

/* Linha de tabela navegavel: o clique vale na linha inteira (ver
   `initializeRowLinks`), entao o cursor e o realce precisam dizer isso — sem
   eles so o texto do link parecia clicavel. */
.data-table--clicavel tbody tr {
    cursor: pointer;
    transition: background-color 120ms ease;
}

.data-table--clicavel tbody tr:hover {
    background: var(--surface-hover);
}

.data-table--clicavel tbody tr:hover .table-link {
    color: var(--accent-ink);
}

/* Grupo de botoes no cabecalho da pagina (voltar + acao principal). */
.page-heading-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
}

/* Campo que ocupa a linha inteira do `.detail-list` (ex.: observacoes). */
.detail-list-full {
    grid-column: 1 / -1;
}

/* ==========================================================================
   Correcoes de espacamento
   ========================================================================== */

/* `.mono` traz `white-space: nowrap` — util numa tabela, onde manter a
   coluna intacta importa. Dentro do `.detail-list` (grade de duas ou tres
   colunas) isso fazia a chave de acesso de 44 digitos estourar a celula e
   passar POR CIMA do valor vizinho. Aqui o valor quebra. */
.detail-list dd.mono,
.detail-list dd .mono {
    overflow-wrap: anywhere;
    white-space: normal;
}

/* Respiro entre a barra de filtro/busca e a tabela logo abaixo: sem isto o
   cabecalho da tabela encostava no campo de busca. */
.filter-bar + .data-table-wrap,
.filter-bar + .empty-state,
.chip-row + .data-table-wrap,
.chip-row + .empty-state {
    margin-top: 22px;
}


/* ==========================================================================
   Paginacao
   ========================================================================== */

.paginacao {
    align-items: center;
    border-top: 1px solid var(--border);
    display: flex;
    flex-wrap: wrap;
    gap: 12px 20px;
    justify-content: space-between;
    margin-top: 18px;
    padding-top: 16px;
}

.paginacao-resumo {
    color: var(--ink-faint);
    font-size: 0.8rem;
    margin: 0;
}

.paginacao-resumo strong {
    color: var(--ink);
}

.paginacao-paginas,
.paginacao-tamanho {
    align-items: center;
    display: flex;
    gap: 4px;
}

.paginacao-tamanho span {
    color: var(--ink-faint);
    font-size: 0.78rem;
    margin-right: 4px;
}

.paginacao-item {
    align-items: center;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    color: var(--ink-soft);
    display: inline-flex;
    font-size: 0.8rem;
    font-weight: 650;
    justify-content: center;
    min-height: 32px;
    min-width: 32px;
    padding: 0 8px;
    transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease;
}

.paginacao-item:hover {
    background: var(--surface-hover);
    color: var(--ink);
}

.paginacao-item.is-active {
    background: var(--accent-soft);
    border-color: var(--accent);
    color: var(--accent-ink);
}

/* Extremos da lista: o link continua no DOM (a URL e valida) mas nao
   convida ao clique. */
.paginacao-item.is-disabled {
    opacity: 0.35;
    pointer-events: none;
}

.paginacao-item svg {
    height: 15px;
    width: 15px;
}

.paginacao-reticencias {
    color: var(--ink-faint);
    padding: 0 2px;
}

/* Cabecalho de coluna ordenavel. */
.data-table th .ordenavel {
    align-items: center;
    color: inherit;
    display: inline-flex;
    gap: 5px;
}

.data-table th .ordenavel:hover {
    color: var(--ink);
}

.data-table th .ordenavel svg {
    height: 13px;
    opacity: 0.45;
    width: 13px;
}

.data-table th .ordenavel.is-active {
    color: var(--accent-ink);
}

.data-table th .ordenavel.is-active svg {
    opacity: 1;
}

/* ==========================================================================
   Relatorios — construtor + folha A4
   ========================================================================== */

/* A tela precisa de mais largura que o resto do app: sao duas colunas, e o
   documento A4 (210mm ~ 794px) tem largura fixa. Regra propria e nao um
   acrescimo ao seletor de `.nfe-dashboard`, que da `max-width: none`.
   Precisa ficar DENTRO da media query, senao perde para o `padding: 16px`
   que a media query de 640px aplica em `.app-content`. */
@media (min-width: 641px) {
    .app-content:has(.relatorio-dashboard) {
        max-width: 1520px;
        padding-inline: clamp(24px, 3vw, 48px);
    }
}

.relatorio-dashboard {
    display: flex;
    flex-direction: column;
    gap: 20px;
}

.relatorio-dashboard > .page-heading {
    margin-bottom: 0;
}

/* --- Barra de acoes ------------------------------------------------------- */

.relatorio-acoes {
    align-items: center;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    display: flex;
    flex-wrap: wrap;
    gap: 10px 14px;
    justify-content: space-between;
    padding: 12px 14px;
}

.relatorio-acoes-grupo {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.relatorio-acoes-grupo--fim {
    margin-left: auto;
}

.relatorio-acoes .button[disabled],
.page-heading-actions .button[disabled] {
    cursor: not-allowed;
    opacity: 0.5;
}

/* --- Layout de duas colunas ---------------------------------------------- */

.relatorio-layout {
    align-items: start;
    display: grid;
    gap: 20px;
    grid-template-columns: 330px minmax(0, 1fr);
}

@media (max-width: 960px) {
    .relatorio-layout {
        grid-template-columns: minmax(0, 1fr);
    }
}

/* --- Painel construtor ---------------------------------------------------- */

.relatorio-construtor {
    display: flex;
    flex-direction: column;
    gap: 10px;
    position: sticky;
    top: 16px;
}

@media (max-width: 960px) {
    .relatorio-construtor {
        position: static;
    }
}

.relatorio-aviso {
    align-items: center;
    background: var(--warning-soft);
    border: 1px solid var(--warning-border);
    border-radius: var(--radius-sm);
    color: var(--warning);
    display: flex;
    font-size: 0.8rem;
    gap: 8px;
    padding: 9px 11px;
}

.relatorio-aviso svg {
    flex: none;
    height: 15px;
    width: 15px;
}

.relatorio-bloco {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    overflow: hidden;
}

.relatorio-bloco > summary {
    align-items: center;
    color: var(--ink-faint);
    cursor: pointer;
    display: flex;
    font-size: 0.7rem;
    font-weight: 700;
    justify-content: space-between;
    letter-spacing: 0.08em;
    list-style: none;
    padding: 13px 14px;
    text-transform: uppercase;
    user-select: none;
}

/* Remove o triangulo nativo (Safari usa `::-webkit-details-marker`). */
.relatorio-bloco > summary::-webkit-details-marker {
    display: none;
}

.relatorio-bloco > summary:hover {
    color: var(--ink-soft);
}

.relatorio-bloco > summary:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}

.relatorio-bloco > summary svg {
    height: 15px;
    transition: transform 160ms ease;
    width: 15px;
}

.relatorio-bloco[open] > summary svg {
    transform: rotate(180deg);
}

.relatorio-bloco-corpo {
    border-top: 1px solid var(--border);
    display: grid;
    gap: 12px;
    /* `minmax(0, 1fr)` e nao `auto`: itens de grid tem `min-width: auto`, que
       os impede de encolher abaixo do proprio `min-content`. O texto longo das
       opcoes ("Baixada Santista - Rota 2 (Guaruja/Bertioga)") define esse
       minimo, a coluna estoura os 330px do painel e o `overflow: hidden` do
       bloco corta os campos. Com `minmax(0, ...)` a coluna nunca passa do
       container. */
    grid-template-columns: minmax(0, 1fr);
    padding: 14px;
}

.relatorio-bloco-corpo .form-field {
    margin-bottom: 0;
    /* `.form-field` tambem e grid: sem isto o select herda o mesmo piso de
       `min-content` e volta a estourar, agora um nivel abaixo. */
    grid-template-columns: minmax(0, 1fr);
    min-width: 0;
}

/* Texto longo de `<option>` nao pode ditar a largura do `<select>`. */
.relatorio-bloco-corpo select,
.relatorio-bloco-corpo input,
.relatorio-bloco-corpo textarea {
    max-width: 100%;
    min-width: 0;
}

.relatorio-periodo {
    display: grid;
    gap: 10px;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    min-width: 0;
}

.relatorio-switch {
    align-items: center;
    display: flex;
    gap: 9px;
}

.relatorio-bloco-acoes {
    display: grid;
    gap: 8px;
    margin-top: 2px;
}

/* --- Palco da previa ------------------------------------------------------ */

.relatorio-palco {
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    /* Rolagem horizontal em telas estreitas em vez de `transform: scale()`:
       `transform` altera o layout de impressao e a folha sairia errada no
       papel. */
    overflow: auto;
    padding: 20px;
}

.relatorio-documento {
    display: flex;
    justify-content: center;
}

/* A escala vem do JS (`ajustarEscala`), que mede o palco. `scale` preserva a
   proporcao do A4 e nao reflui o texto -- a folha continua com 210mm logicos,
   so e desenhada menor. Seguro porque a impressao tem rota propria, sem palco
   e sem transform. */
.relatorio-palco .folha-a4 {
    /* `flex: none` e obrigatorio: sem ele o container flex espreme a folha
       abaixo dos 210mm e o documento sai DISTORCIDO (texto refluido, colunas
       estreitas) em vez de apenas menor. A largura logica tem que ficar fixa
       para o `scale` ser a unica coisa que muda. */
    flex: none;
    transform: scale(var(--folha-escala, 1));
    transform-origin: top center;
}

.relatorio-palco.is-remontando {
    cursor: progress;
}

.relatorio-palco.is-remontando .relatorio-documento {
    opacity: 0.45;
    transition: opacity var(--dur-1) var(--ease-standard);
}

/* --- Folha A4 -------------------------------------------------------------
   DOCUMENTO, nao UI. Todas as cores sao variaveis locais: e assim que a folha
   continua branca num app inteiramente dark, sem precisar de tema claro. */

.folha-a4 {
    --doc-ink: #17212c;
    --doc-ink-soft: #55606d;
    --doc-border: #d8dee6;
    --doc-cabecalho: #1b2129;
    --doc-accent: #1d8f45;
    --doc-negativo: #b4232f;
    --doc-zebra: #f4f6f9;

    background: #fff;
    box-shadow: 0 10px 34px rgb(0 0 0 / 35%);
    color: var(--doc-ink);
    /* Fonte fixa e nao `var(--font-sans)`: Bahnschrift tem metricas proprias e
       a folha precisa renderizar igual na tela e no papel. */
    font-family: "Segoe UI", Arial, Helvetica, sans-serif;
    font-size: 10.5px;
    line-height: 1.45;
    min-height: 297mm;
    padding: 14mm 12mm;
    width: 210mm;
}

.folha-cabecalho {
    align-items: flex-start;
    background: var(--doc-cabecalho);
    color: #fff;
    display: flex;
    gap: 18px;
    justify-content: space-between;
    margin: -14mm -12mm 0;
    padding: 10mm 12mm 8mm;
}

.folha-logo-img {
    display: block;
    height: 40px;
    width: auto;
}

.folha-sublogo {
    color: #9fb0c2;
    font-size: 0.62rem;
    font-weight: 600;
    letter-spacing: 0.14em;
    margin: 3px 0 0;
    text-transform: uppercase;
}

.folha-identificacao {
    text-align: right;
}

.folha-identificacao h1 {
    font-size: 1.05rem;
    margin: 0;
}

.folha-identificacao p {
    color: #9fb0c2;
    font-size: 0.72rem;
    margin: 3px 0 0;
}

.folha-periodo {
    color: #fff !important;
    font-weight: 600;
}

.folha-regua {
    background: var(--doc-accent);
    height: 4px;
    margin: 0 -12mm 8mm;
}

.folha-secao {
    margin-bottom: 7mm;
}

.folha-secao-titulo {
    border-bottom: 1px solid var(--doc-border);
    color: var(--doc-ink-soft);
    font-size: 0.62rem;
    font-weight: 700;
    letter-spacing: 0.12em;
    margin: 0 0 3mm;
    padding-bottom: 2mm;
    text-transform: uppercase;
}

.folha-filtros {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 8px;
    list-style: none;
    margin: 0;
    padding: 0;
}

.folha-filtros li {
    background: var(--doc-zebra);
    border: 1px solid var(--doc-border);
    border-radius: 3px;
    display: inline-flex;
    gap: 5px;
    padding: 3px 7px;
}

.folha-filtros span {
    color: var(--doc-ink-soft);
}

.folha-kpis {
    display: grid;
    gap: 3mm;
    grid-template-columns: repeat(6, minmax(0, 1fr));
}

.folha-kpi {
    border: 1px solid var(--doc-border);
    border-radius: 4px;
    border-top: 3px solid var(--doc-ink-soft);
    padding: 3mm;
}

.folha-kpi span {
    color: var(--doc-ink-soft);
    display: block;
    font-size: 0.6rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    margin-bottom: 2px;
    text-transform: uppercase;
}

.folha-kpi strong {
    display: block;
    font-size: 0.95rem;
    font-variant-numeric: tabular-nums;
    line-height: 1.2;
    word-break: break-word;
}

.folha-kpi--positivo {
    border-top-color: var(--doc-accent);
}

.folha-kpi--positivo strong {
    color: var(--doc-accent);
}

.folha-kpi--atencao {
    border-top-color: var(--doc-negativo);
}

.folha-kpi--atencao strong {
    color: var(--doc-negativo);
}

.folha-resumo {
    display: grid;
    gap: 2mm;
    list-style: none;
    margin: 0;
    padding: 0;
}

.folha-resumo-item {
    border-left: 3px solid var(--doc-border);
    padding-left: 3mm;
}

.folha-resumo-item--positivo {
    border-left-color: var(--doc-accent);
}

.folha-resumo-item--atencao {
    border-left-color: var(--doc-negativo);
}

.folha-metricas {
    display: grid;
    gap: 3mm;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    margin: 0;
}

.folha-metricas dt {
    color: var(--doc-ink-soft);
    font-size: 0.6rem;
    letter-spacing: 0.05em;
    text-transform: uppercase;
}

.folha-metricas dd {
    font-size: 0.85rem;
    font-variant-numeric: tabular-nums;
    font-weight: 600;
    margin: 2px 0 0;
}

/* `table-layout: fixed` + larguras percentuais: sem isso uma tabela larga
   estoura a margem do A4 e o Chrome corta a direita SEM aviso. */
.folha-tabela {
    border-collapse: collapse;
    table-layout: fixed;
    width: 100%;
}

.folha-tabela th {
    background: var(--doc-cabecalho);
    color: #fff;
    font-size: 0.6rem;
    font-weight: 600;
    letter-spacing: 0.05em;
    padding: 2.4mm 1.6mm;
    text-align: left;
    text-transform: uppercase;
}

.folha-tabela td {
    border-bottom: 1px solid var(--doc-border);
    padding: 2mm 1.6mm;
    vertical-align: top;
    word-break: break-word;
}

.folha-tabela tbody tr:nth-child(even) {
    background: var(--doc-zebra);
}

.folha-tabela tfoot td {
    background: var(--doc-zebra);
    border-top: 2px solid var(--doc-cabecalho);
    font-variant-numeric: tabular-nums;
}

.folha-tabela .col-numero {
    font-variant-numeric: tabular-nums;
    text-align: right;
}

.folha-tabela .col-data { width: 9%; }
.folha-tabela .col-nf { width: 8%; }
.folha-tabela .col-cliente { width: 22%; }
.folha-tabela .col-cidade { width: 13%; }
.folha-tabela .col-rota { width: 13%; }
.folha-tabela .col-cte { width: 8%; }
.folha-tabela .col-valor { width: 10%; }
.folha-tabela .col-peso { width: 9%; }
.folha-tabela .col-status { width: 8%; }

.folha-vazio {
    border: 1px dashed var(--doc-border);
    border-radius: 4px;
    color: var(--doc-ink-soft);
    margin: 0;
    padding: 8mm;
    text-align: center;
}

.folha-rodape {
    border-top: 1px solid var(--doc-border);
    color: var(--doc-ink-soft);
    display: flex;
    font-size: 0.62rem;
    justify-content: space-between;
    margin-top: 6mm;
    padding-top: 2mm;
}

/* A paginacao e controle de tela, nao parte do documento. */
.folha-a4 .paginacao {
    border-top: 1px solid var(--doc-border);
    margin-top: 4mm;
    padding-top: 3mm;
}

/* --- Pagina de impressao -------------------------------------------------- */

.pagina-impressao {
    align-items: center;
    background: #2b333d;
    display: flex;
    flex-direction: column;
    gap: 16px;
    padding: 20px;
}

.impressao-barra {
    align-items: center;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    color: var(--ink-soft);
    display: flex;
    flex-wrap: wrap;
    font-size: 0.8rem;
    gap: 12px;
    max-width: 210mm;
    padding: 12px 14px;
}

/* --- Impressao ------------------------------------------------------------
   Escopado a folha e a pagina de impressao: regra global vazaria para as
   outras telas do app. */

@page {
    margin: 12mm 10mm 14mm;
    size: A4 portrait;
}

@media print {
    html,
    body,
    .pagina-impressao {
        background: #fff !important;
        display: block;
        padding: 0;
    }

    /* Cromo que nao pertence ao documento. */
    .sidebar,
    .topbar,
    .impressao-barra,
    .relatorio-construtor,
    .relatorio-acoes,
    .page-heading,
    .message-stack,
    .folha-a4 .paginacao {
        display: none !important;
    }

    .app-content,
    .relatorio-layout,
    .relatorio-palco,
    .relatorio-documento {
        background: none;
        border: 0;
        display: block;
        max-width: none;
        overflow: visible;
        padding: 0;
    }

    .folha-a4 {
        box-shadow: none;
        margin: 0;
        min-height: 0;
        padding: 0;
        width: auto;
    }

    /* Na tela o cabecalho "sangra" para fora do padding da folha com margem
       NEGATIVA (`margin: -14mm -12mm 0`), para tocar as bordas da folha
       branca. Na impressao o padding da folha ja vira 0 (`.folha-a4` acima)
       -- nao ha mais padding para sangrar -- mas se so as margens
       horizontais forem resetadas, a margem superior negativa sobrevive e
       empurra o cabecalho para CIMA do topo da pagina, cortando-o. Por isso o
       reset aqui e `margin: 0` completo, nao `margin-inline`. */
    .folha-cabecalho {
        margin: 0;
        padding: 6mm;
    }

    .folha-regua {
        margin-inline: 0;
    }

    /* Obrigatorio: sem isto o Chrome descarta TODO fundo e o cabecalho grafite
       da tabela e a zebra saem em branco. O alias -webkit- ainda e necessario. */
    .folha-a4,
    .folha-a4 * {
        -webkit-print-color-adjust: exact;
        print-color-adjust: exact;
    }

    /* Cabecalho repete em cada pagina; totais fecham no fim. */
    .folha-tabela thead {
        display: table-header-group;
    }

    .folha-tabela tfoot {
        display: table-footer-group;
    }

    .folha-tabela tr,
    .folha-kpi,
    .folha-resumo-item,
    .folha-secao--kpis,
    .folha-secao--resumo {
        break-inside: avoid;
    }
}

/* Link que age como botao desabilitado (exportar/imprimir sem registros).
   `<a>` nao aceita o atributo `disabled`, entao a trava e por CSS + o
   `aria-disabled` que o JS aplica. */
a.button.is-disabled {
    cursor: not-allowed;
    opacity: 0.5;
    pointer-events: none;
}

/* ==========================================================================
   Home — dashboard operacional
   ========================================================================== */

.dashboard {
    display: flex;
    flex-direction: column;
    gap: 18px;
}

.dashboard > .page-heading {
    margin-bottom: 0;
}

.dashboard-filtros {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

/* Grupo de rotas é o filtro PRIMÁRIO: chips maiores e com mais destaque que
   os demais controles, na própria primeira linha da barra -- não uma seção
   à parte embaixo dos cards. */
/* A barra de filtros da Home era TRES bandas soltas empilhadas sobre o fundo
   da pagina: chips de grupo, resumo do periodo, e a linha avancada. Sem
   nenhum limite visual entre elas e o resto, o olho lia tres blocos
   desconexos flutuando acima dos cartoes — que e a sensacao de "estranho"
   e "nao profissional" relatada.

   Contidas numa superficie unica, com hairline separando o resumo dos
   chips, elas passam a ler como UM painel de controle. E o mesmo argumento
   da barra lateral: numa torre de controle os comandos ficam agrupados num
   console, nao espalhados pela mesa. */
.dashboard-filtros {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    gap: 0;
    padding: var(--space-md) var(--space-lg);
}


/* O resumo do periodo e um RODAPE da barra, nao uma terceira banda: a
   hairline amarra os dois e o recuo negativo faz a linha atravessar a
   largura inteira da superficie. */
.dashboard-filtro-avancado {
    border-top: 1px solid var(--border);
    margin: var(--space-md) calc(var(--space-lg) * -1) 0;
    padding: var(--space-md) var(--space-lg) 0;
}

/* Segunda linha, densa: período + data + rota específica + limpar, tudo
   numa única faixa curta em vez de um bloco de `.form-field` empilhados. */
.filter-bar--compacto {
    align-items: center;
    gap: 10px;
}

.filter-bar--compacto .chip-row {
    margin-top: 0;
}

.filter-bar--compacto .form-field {
    flex: 0 1 150px;
}

.filter-bar--compacto #filtro-rota {
    min-width: 170px;
}

.filter-bar--compacto .form-field input,
.filter-bar--compacto .form-field select {
    min-height: 34px;
}

.filter-bar--compacto .filter-actions .button {
    min-height: 34px;
    padding: 0 10px;
}

@media (max-width: 640px) {
    .filter-bar--compacto .form-field {
        flex-basis: 100%;
    }
}

/* Painel de data/rota, fechado por padrão -- ver comentário em
   `core/_construtor.html`. `<details>` em vez de um toggle em JS: o
   navegador já dá o recolher e o teclado de graça (mesmo raciocínio do
   `<details>` de `relatorios/_construtor.html`). */
.dashboard-filtro-avancado > summary {
    align-items: center;
    cursor: pointer;
    display: flex;
    gap: var(--space-md);
    justify-content: space-between;
    list-style: none;
    padding: 0;
    user-select: none;
}

.dashboard-filtro-avancado > summary::-webkit-details-marker {
    display: none;
}

.dashboard-filtro-resumo {
    align-items: center;
    color: var(--ink-soft);
    display: inline-flex;
    font-size: 0.82rem;
    gap: 8px;
}

.dashboard-filtro-resumo svg {
    color: var(--ink-faint);
    flex: none;
    height: 15px;
    width: 15px;
}

.dashboard-filtro-resumo strong {
    color: var(--ink);
}

.dashboard-filtro-abrir {
    align-items: center;
    color: var(--ink-faint);
    display: inline-flex;
    flex: none;
    font-size: 0.76rem;
    font-weight: 650;
    gap: 5px;
    transition: color 140ms ease;
}

.dashboard-filtro-avancado > summary:hover .dashboard-filtro-abrir {
    color: var(--ink-soft);
}

.dashboard-filtro-abrir svg {
    height: 14px;
    transition: transform 160ms ease;
    width: 14px;
}

.dashboard-filtro-avancado[open] > summary .dashboard-filtro-abrir svg {
    transform: rotate(180deg);
}

.dashboard-filtro-avancado .filter-bar--compacto {
    margin-top: 12px;
}

/* Alerta acima da grade -- o aviso de "nenhuma NF-e automática" mora aqui,
   não dentro do card (texto longo dentro do card espremia o número, que é
   o dado que precisa saltar aos olhos). `margin-bottom` igual ao `gap` de
   `.dashboard`: o alerta vive DENTRO do palco (não é filho direto do
   flex), então não herda aquele espaçamento -- sem repetir o valor aqui,
   ficava colado na grade de cards logo abaixo, mais apertado que o resto
   do ritmo vertical da tela. */
.dashboard-alerta {
    align-items: center;
    display: flex;
    gap: 10px;
    margin: 0 0 18px;
}

.dashboard-alerta svg {
    flex: none;
    height: 18px;
    width: 18px;
}

/* --- Grade de indicadores ---------------------------------------------- */

/* Uma grade única, sem títulos de seção: a diferenciação NF-e/CT-e/Frete
   vem do ícone e da cor de cada card, não de um rótulo de bloco -- é isso
   que lê como um dashboard (referência do usuário), não como uma lista de
   três blocos empilhados. Em telas largas (a Home usa a largura cheia, ver
   `.app-content:has(.dashboard)`), `auto-fill` abre 5 colunas ou mais. */
.metric-grid--dashboard {
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}

.metric-card {
    border-top: 3px solid transparent;
    gap: 12px;
    padding: 18px 20px;
    transition: border-color 160ms ease, box-shadow 160ms ease, transform 160ms ease;
}

.metric-card:hover {
    box-shadow: 0 10px 26px rgb(0 0 0 / 22%);
    transform: translateY(-2px);
}

.metric-card-head {
    align-items: center;
    display: flex;
    justify-content: space-between;
}

.metric-card-icon {
    align-items: center;
    background: var(--surface-raised);
    border-radius: var(--radius-sm);
    color: var(--ink-faint);
    display: inline-flex;
    height: 32px;
    justify-content: center;
    width: 32px;
}

.metric-card-icon svg {
    height: 16px;
    width: 16px;
}

.metric-card strong {
    font-size: 2rem;
}

.metric-card--positivo {
    border-top-color: var(--accent);
}

.metric-card--positivo strong {
    color: var(--accent-ink);
}

.metric-card--positivo .metric-card-icon {
    background: var(--accent-soft);
    color: var(--accent-ink);
}

.metric-card--atencao {
    border-top-color: var(--warning);
}

.metric-card--atencao strong {
    color: var(--warning);
}

.metric-card--atencao .metric-card-icon {
    background: var(--warning-soft);
    color: var(--warning);
}

.metric-card--negativo {
    border-top-color: var(--negative);
}

.metric-card--negativo strong {
    color: var(--negative);
}

.metric-card--negativo .metric-card-icon {
    background: var(--negative-soft);
    color: var(--negative);
}

/* --- Estado ocupado (mesmo padrão do palco de relatórios) --------------- */

[data-dashboard-palco].is-remontando [data-dashboard-previa] {
    opacity: 0.55;
    transition: opacity 120ms ease;
}

/* ==========================================================================
   Avisos (toasts)
   --------------------------------------------------------------------------
   DOIS HOSTS, UMA API. `showModal()` promove o <dialog> ao TOP LAYER, uma
   camada acima de todo o documento e fora do alcance de `z-index`. Um aviso
   `position: fixed` no <body> fica ATRAS do pop-up por mais alto que seja o
   z-index — foi essa, e nao falta de z-index, a causa de toda acao feita
   dentro de um pop-up dar retorno invisivel ate a feature 003.

   O host de dentro do pop-up e `sticky`, e nao `fixed`: o
   `dialog[data-modal]` tem `overflow: hidden`, que recortaria um filho
   `fixed`. `sticky` no rodape da `.modal-shell` resolve sem mexer no
   overflow — e mexer no overflow quebraria a rolagem so-do-corpo.
   ========================================================================== */

.toast-host {
    display: grid;
    gap: var(--space-sm);
    justify-items: end;
    pointer-events: none;
}

/* Host da pagina */
body > .toast-host {
    bottom: var(--space-xl);
    position: fixed;
    right: var(--space-xl);
    width: min(380px, calc(100vw - var(--space-2xl) * 2));
    z-index: var(--z-toast);
}

/* Host de dentro do pop-up */
.modal-shell > .toast-host:not(:empty) {
    bottom: 0;
    justify-items: stretch;
    padding: 0 var(--space-2xl) var(--space-md);
    position: sticky;
}

.toast {
    align-items: flex-start;
    animation: toast-entra var(--dur-2) var(--ease-out);
    background: var(--surface-raised);
    border: 1px solid var(--border-strong);
    border-left: 3px solid var(--ink-faint);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-2);
    color: var(--ink);
    display: flex;
    font-size: var(--text-sm);
    gap: var(--space-md);
    line-height: var(--leading-normal);
    padding: var(--space-md) var(--space-lg);
    pointer-events: auto;
    transition: opacity var(--dur-2) var(--ease-out),
        transform var(--dur-2) var(--ease-out);
    width: 100%;
}

/* Entrada por `animation`, e NAO por remocao de classe num
   `requestAnimationFrame`.

   A versao com classe dependia de o navegador compor um quadro para o JS
   tirar o estado inicial. Numa aba em segundo plano — ou em qualquer
   contexto que nao esteja pintando — o rAF nao roda, a classe nunca sai e o
   aviso fica em `opacity: 0` para sempre: presente no DOM, invisivel para o
   operador. Uma `animation` roda na insercao do elemento e termina no
   estado final sozinha, sem depender de nada. */
@keyframes toast-entra {
    from {
        opacity: 0;
        transform: translateX(var(--space-md));
    }
}

.toast.is-saindo {
    opacity: 0;
    transform: translateY(calc(var(--space-2xs) * -1));
    transition: opacity var(--dur-1) var(--ease-in),
        transform var(--dur-1) var(--ease-in);
}

.toast--success {
    border-left-color: var(--accent);
}

.toast--error {
    border-left-color: var(--negative);
}

.toast--warning {
    border-left-color: var(--warning);
}

.toast--info,
.toast--debug {
    border-left-color: var(--info);
}

.toast-texto {
    flex: 1;
    min-width: 0;
    overflow-wrap: anywhere;
}

.toast-fechar {
    background: none;
    border: 0;
    border-radius: var(--radius-xs);
    color: var(--ink-faint);
    cursor: pointer;
    display: inline-flex;
    flex: none;
    font-size: var(--text-lg);
    line-height: 1;
    padding: 0 var(--space-2xs);
    transition: color var(--dur-1) var(--ease-standard);
}

.toast-fechar:hover {
    color: var(--ink);
}

/* ==========================================================================
   Pop-up: abertura e comportamento em tela estreita
   ========================================================================== */

/* Abertura em CSS PURO. Nenhuma classe alternada por JavaScript.

   Animar `<dialog>` sempre foi dificil porque ele alterna `display: none`,
   que nao transiciona. `@starting-style` define o estado de entrada de um
   elemento que acaba de chegar ao top layer, e `allow-discrete` deixa
   `display` e `overlay` participarem da transicao.

   A restricao constitucional pede HTML/CSS nativo antes de JavaScript, e
   aqui isso tambem protege os contratos travados de `tests/test_dialogos.py`:
   nada de handler novo, nada de `requestAnimationFrame`, nada que possa
   interferir em Esc ou no clique fora.

   Em navegador sem suporte o pop-up simplesmente aparece — que e exatamente
   o que `prefers-reduced-motion` produz de proposito. */
/* Os TRES estados que a animacao de pop-up exige. Faltar qualquer um deixa
   um defeito visivel:

     fechado  = o alvo da SAIDA. Sem ele o pop-up sai da camada superior
                ainda totalmente opaco e some seco 220ms depois — um
                fantasma desenhado no meio da pagina.
     [open]   = o repouso.
     starting = o ponto de partida da ENTRADA. */
dialog[data-modal],
dialog[data-form-dialog] {
    opacity: 0;
    transform: translateY(var(--space-sm)) scale(0.985);
    transition: opacity var(--dur-3) var(--ease-out),
        transform var(--dur-3) var(--ease-out),
        overlay var(--dur-3) allow-discrete,
        display var(--dur-3) allow-discrete;
}

dialog[data-modal][open],
dialog[data-form-dialog][open] {
    opacity: 1;
    transform: none;
}

@starting-style {

    dialog[data-modal][open],
    dialog[data-form-dialog][open] {
        opacity: 0;
        transform: translateY(var(--space-sm)) scale(0.985);
    }
}

dialog[data-modal]::backdrop,
dialog[data-form-dialog]::backdrop {
    transition: background-color var(--dur-3) var(--ease-out),
        overlay var(--dur-3) allow-discrete,
        display var(--dur-3) allow-discrete;
}

@starting-style {

    dialog[data-modal][open]::backdrop,
    dialog[data-form-dialog][open]::backdrop {
        background-color: transparent;
    }
}

/* --- Folha inferior em tela estreita -------------------------------------
   Num celular, um pop-up centralizado com margem dos dois lados desperdica
   a pouca largura que existe e deixa as acoes do rodape longe do polegar.
   Ancorado na base, ele se comporta como a folha que o operador ja conhece
   de qualquer app.

   Sem JavaScript: `<dialog>` se centraliza por margem, entao
   `margin: auto 0 0` basta para encostar embaixo.

   `dvh` e nao `vh`: com o teclado virtual aberto, `vh` continua contando a
   altura da janela SEM o teclado, e o rodape — onde ficam as acoes — sai da
   area visivel. */
@media (max-width: 640px) {

    dialog[data-modal],
    dialog[data-form-dialog] {
        --dialogo-altura: 92dvh;

        border-bottom: 0;
        border-radius: var(--radius-lg) var(--radius-lg) 0 0;
        margin: auto 0 0;
        /* `width` direto, e nao pela custom property: a regra base aplica
           `min(largura, 94vw)`, e a folha inferior quer a largura INTEIRA.
           Uma margem lateral de 3% de cada lado num aparelho de 375px e
           espaco que o conteudo precisa e nao ganha nada em troca. */
        width: 100vw;
    }

    /* Alca: sinaliza que o painel veio de baixo, sem custar altura util. */
    .modal-header::before {
        background: var(--border-strong);
        border-radius: var(--radius-pill);
        content: "";
        height: 4px;
        left: 50%;
        position: absolute;
        top: var(--space-sm);
        transform: translateX(-50%);
        width: 36px;
    }

    .modal-header {
        padding-top: var(--space-xl);
        position: relative;
    }

    /* Sobe em vez de escalar: escala num painel colado na base parece
       defeito de renderizacao, nao movimento intencional. Vale para a
       entrada E para a saida — o estado fechado tambem desce. */
    dialog[data-modal],
    dialog[data-form-dialog] {
        opacity: 1;
        transform: translateY(100%);
    }

    @starting-style {

        dialog[data-modal][open],
        dialog[data-form-dialog][open] {
            opacity: 1;
            transform: translateY(100%);
        }
    }
}

/* ==========================================================================
   Selecao em lote
   --------------------------------------------------------------------------
   Estas duas classes eram usadas em templates/cte/index.html e NAO TINHAM UMA
   LINHA DE CSS — a barra que comanda a emissao de documento fiscal, a acao
   mais irreversivel do sistema, renderizava como texto solto sobre o fundo.

   Ela gruda no topo da area de rolagem em vez de ficar no fluxo: a selecao
   vale para a pagina inteira e o operador precisa enxergar a contagem
   enquanto percorre as linhas, nao so quando esta no comeco da lista.
   ========================================================================== */

/* A barra lia como uma FAIXA DE ALERTA: caixa neutra, texto solto, botao
   grande. Ela nao esta avisando de nada — esta dizendo "voce tem N itens
   escolhidos, e aqui esta o que fazer com eles".

   Agora ela se veste de estado ativo: fundo em verde de sinalizacao muito
   diluido (a mesma cor que marca selecao no resto do sistema), barra de 3px
   na borda esquerda como marcador, e a contagem em numero tabular grande
   com o rotulo em caixa-alta pequena ao lado. O verde entra aqui pela Regra
   do Sinal Unico: e estado, nao enfeite. */
/* A barra fica no cabecalho do painel, colada a esquerda do contador. Como
   faixa de largura inteira ela dizia "3 selecionadas" ocupando 1150px — e
   uma faixa larga com pouco conteudo le como AVISO, nao como estado.

   Encolhida ao tamanho do proprio conteudo e posta ao lado do contador, ela
   entra no lugar onde o operador ja olha para saber quantos itens ha. */
.panel-heading-estado {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-sm);
    justify-content: flex-end;
}

.selecao-barra {
    align-items: center;
    background: color-mix(in srgb, var(--accent) 10%, var(--surface-raised));
    border: 1px solid var(--accent-border);
    border-radius: var(--radius-pill);
    display: flex;
    gap: var(--space-sm);
    padding: var(--space-2xs) var(--space-2xs) var(--space-2xs) var(--space-md);
}

.selecao-contagem {
    color: var(--ink);
    font-size: var(--text-xs);
    font-variant-numeric: tabular-nums;
    font-weight: var(--weight-semibold);
    white-space: nowrap;
}

.selecao-limpar {
    align-items: center;
    background: none;
    border: 0;
    border-radius: var(--radius-pill);
    color: var(--ink-faint);
    cursor: pointer;
    display: inline-flex;
    height: 24px;
    justify-content: center;
    padding: 0;
    transition: color var(--dur-1) var(--ease-standard),
        background-color var(--dur-1) var(--ease-standard);
    width: 24px;
}

.selecao-limpar:hover {
    background: var(--surface-hover);
    color: var(--ink);
}

.selecao-limpar svg {
    height: 14px;
    width: 14px;
}

/* `hidden` sozinho perde para `display: flex`. Sem isto a barra apareceria
   vazia mesmo sem nada selecionado. */
.selecao-barra[hidden] {
    display: none;
}

[data-selecao-contador] {
    color: var(--ink);
    font-size: var(--text-sm);
    font-variant-numeric: tabular-nums;
    font-weight: var(--weight-semibold);
}

/* Coluna estreita e sem padding a direita: a caixa fica colada no proprio
   alvo, e nao no meio de um vao que convida ao clique errado — clique errado
   aqui significa emitir a CT-e de outra pessoa. */
.col-selecao {
    padding-right: 0;
    width: 40px;
}

.col-selecao input[type="checkbox"] {
    /* Alvo maior que o desenho: 16px e o padrao do controle, mas a area
       clicavel confortavel comeca em 24. */
    cursor: pointer;
    height: 18px;
    margin: 0;
    width: 18px;
}

/* ==========================================================================
   Ajustes de densidade e contencao — segunda rodada da feature 003
   --------------------------------------------------------------------------
   Seis defeitos apontados na conferencia do operador. Todos sao do mesmo
   tipo: o CHROME da interface ocupando espaco que pertence ao CONTEUDO.
   ========================================================================== */

/* --- 1. Tabela dentro de pop-up nao carrega o minimo da listagem ---------
   `.data-table` tem `min-width: 680px` porque a listagem de CT-e tem dez
   colunas e comprimi-las torna a leitura impossivel. Mas as tabelas DENTRO
   de um pop-up sao pequenas — componentes de frete tem tres colunas — e num
   corpo de duas colunas cada coluna mede ~600px. O minimo herdado forcava
   uma barra de rolagem horizontal em cada bloco. */
dialog .data-table {
    min-width: 0;
}

/* --- 2. Corpo em duas colunas vale para `lg` tambem ----------------------
   Era exclusivo do `full`. O detalhe de NF-e (`lg`) tem a mesma natureza:
   blocos independentes que se leem em paralelo, nao um texto corrido. */
/* A largura minima da coluna e uma PROPRIEDADE DO CONTEUDO, nao do tamanho
   do pop-up. Medido nos dois detalhes, a 1354px:

     detalhe de CT-e   2 colunas (min 480px) -> 729px de rolagem, 47,9% visivel
                       3 colunas (min 360px) -> 812px, 45,2%
     detalhe de NF-e   4 colunas (min 300px) -> 533px de rolagem, 55,7% visivel
                       2 colunas (min 440px) -> 792px, 45,8%

   O inverso um do outro, e por um motivo claro: o CT-e tem blocos LARGOS
   (tabela de componentes, formulario de revisao) que espremidos quebram em
   mais linhas e crescem; o NF-e tem varios blocos PEQUENOS de duas colunas
   de rotulo/valor, que empacotam bem em faixas estreitas.

   Fingir que um numero serve para os dois seria escolher o pior dos dois
   mundos. `--colunas-min` deixa cada tela declarar o que mediu. */
dialog[data-tamanho="lg"] .modal-body,
dialog[data-tamanho="full"] .modal-body {
    align-content: start;
    grid-template-columns: repeat(auto-fit, minmax(var(--colunas-min, 480px), 1fr));
}

dialog[data-colunas="densa"] {
    --colunas-min: 300px;
}

/* Pop-up que traz o PROPRIO arranjo nao pode herdar a grade automatica do
   corpo. Foi o que quebrou o de importacao: ele e `lg`, o corpo virou grade
   de duas colunas, e o `.importar-corpo` — que ja se divide em duas —
   aterrissou dentro de UMA delas e subdividiu de novo. O resultado eram
   quatro faixas de ~245px, com o rotulo "Chave de acesso da NF-e" quebrando
   uma palavra por linha. */
dialog[data-colunas="unica"] {
    --colunas-min: 100%;
}

/* Bloco que precisa de mais largura que a coluna, sem precisar da linha
   inteira. "Arquivos anexados" mede 415px de conteudo minimo (nome de
   arquivo + tamanho + acao) numa coluna de 308: espremido, ele produzia a
   rolagem horizontal que esta rodada existe para eliminar. Duas colunas
   resolvem sem gastar a linha toda, que e o que uma tabela exige. */
.modal-body > .largura-dupla {
    grid-column: span 2;
}

/* Numa unica coluna (telas estreitas) o `span 2` viraria uma coluna
   fantasma. */
@media (max-width: 960px) {

    .modal-body > .largura-dupla {
        grid-column: auto;
    }
}

/* Bloco que perde sentido partido ao meio ocupa a linha inteira. */
dialog[data-tamanho="lg"] .modal-body > .largura-total,
dialog[data-tamanho="full"] .modal-body > .largura-total,
dialog[data-tamanho="lg"] .modal-body > .modal-warning,
dialog[data-tamanho="full"] .modal-body > .modal-warning,
dialog[data-tamanho="lg"] .modal-body > .alert,
dialog[data-tamanho="full"] .modal-body > .alert {
    grid-column: 1 / -1;
}

/* --- 3. A barra de "ocupado" nao pode escapar do painel ------------------
   A versao anterior animava `transform: translateX(±60%)` sobre uma barra
   da largura inteira do painel. Numa area de 1130px isso desloca 678px, e
   como o painel nao recorta, a barra era pintada ATE x=-411 — por cima da
   barra lateral, que termina em 224.

   Animar `background-position` de um gradiente resolve pela raiz: o
   elemento nao se move, entao nao ha o que escapar. */
[data-relatorio-palco].is-remontando::before,
[data-dashboard-palco].is-remontando::before {
    animation: barra-indeterminada 1.15s var(--ease-standard) infinite;
    background: linear-gradient(
        90deg,
        transparent 0%,
        var(--accent) 40%,
        var(--accent-ink) 50%,
        var(--accent) 60%,
        transparent 100%
    );
    background-size: 42% 100%;
    background-repeat: no-repeat;
    transform: none;
}

@keyframes barra-indeterminada {
    from {
        background-position: -45% 0;
    }

    to {
        background-position: 145% 0;
    }
}

/* --- 4. A barra de filtros e uma FERRAMENTA, nao um formulario -----------
   Ela consumia 194px e empurrava a primeira linha da lista para 59% da
   altura da tela: mais da metade do monitor gasta em controles, na tela
   onde o operador passa o dia lendo linhas.

   Vira uma faixa: sem cartao proprio, campos na altura dos botoes, e os
   atalhos de rota na mesma banda visual em vez de num bloco abaixo. */
/* MESMO IDIOMA DO DASHBOARD. La os filtros ficam contidos numa superficie
   com hairline separando os controles do resumo; aqui era uma faixa solta
   com so uma borda embaixo. Duas telas do mesmo sistema resolvendo o mesmo
   problema de dois jeitos ensinam ao operador que nao ha padrao.

   Um so idioma: superficie contida, controles em cima, atalhos embaixo,
   separados por hairline. */
.nfe-panel--filtros {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    box-shadow: none;
    /* Padding menor que o de um painel comum: a superficie existe para
       AGRUPAR os controles, nao para lhes dar respiro — eles ja tem o seu
       proprio. Cada 4px aqui vira 8px de conteudo empurrado para baixo da
       dobra, na tela onde o operador le linhas o dia inteiro. */
    padding: var(--space-sm) var(--space-md);
}

/* Os atalhos agora vem PRIMEIRO: a hairline separa por baixo, nao por cima. */
.nfe-panel--filtros .chip-row {
    border-bottom: 1px solid var(--border);
    margin: 0 calc(var(--space-md) * -1) var(--space-sm);
    padding: 0 var(--space-md) var(--space-sm);
}

/* --- Acoes do filtro com tamanho fixo -----------------------------------
   "Filtrar" e "Limpar" tinham larguras diferentes e o segundo so existia
   com filtro ativo. Ao filtrar, ele nascia e empurrava a linha inteira — o
   operador via a barra pular e achava que tinha mexido em outra coisa.

   Agora o slot existe sempre, ocupado ou vazio, e as duas acoes tem a mesma
   altura. `filtrar` mantem o rotulo porque e a acao principal; limpar vira
   quadrado de icone, que e o tamanho minimo honesto para um alvo de clique. */
.filter-actions {
    align-items: end;
    display: flex;
    gap: var(--space-sm);
}

.filter-acao {
    min-height: 38px;
}

.filter-acao--icone,
.filter-acao--vazio {
    flex: none;
    padding: 0;
    width: 38px;
}

.filter-acao--icone {
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.filter-acao--vazio {
    display: inline-block;
}

.nfe-panel--filtros .filter-bar {
    gap: var(--space-sm) var(--space-md);
}

.nfe-panel--filtros .form-field {
    margin-bottom: 0;
    gap: var(--space-2xs);
}

.nfe-panel--filtros .form-field label {
    font-size: var(--text-2xs);
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.nfe-panel--filtros .form-field input,
.nfe-panel--filtros .form-field select {
    min-height: 38px;
}

.nfe-panel--filtros .chip {
    font-size: var(--text-2xs);
    min-height: 26px;
    padding: 0 var(--space-sm);
}

/* --- 5. Pop-up de importacao: duas colunas, sem rolagem ------------------
   A area de arrastar arquivo e o campo de chave sao ALTERNATIVAS ("ou"),
   e alternativas se leem lado a lado. Empilhadas, o operador rolava para
   descobrir que existia uma segunda opcao. */
@media (min-width: 961px) {

    .importar-corpo {
        align-items: start;
        display: grid;
        gap: var(--space-2xl);
        grid-template-columns: 1fr 1fr;
    }

    /* O divisor "ou" vira vertical entre as duas colunas. */
    .importar-ou {
        display: none;
    }

    .importar-coluna-chave {
        border-left: 1px solid var(--border);
        padding-left: var(--space-2xl);
    }

    /* SIMETRIA das duas vias. Elas sao ALTERNATIVAS, e alternativa se le
       comparando: com alturas diferentes, a mais curta parecia a opcao
       secundaria mesmo sendo equivalente.

       As colunas esticam juntas e cada uma distribui o proprio conteudo: o
       dropzone cresce para ocupar o que sobra na esquerda, o aviso da SEFAZ
       encosta na base na direita. Antes a diferenca era coberta pela lista
       de tres passos embaixo, que empurrava as duas para a mesma linha de
       base sem elas estarem alinhadas de fato. */
    .importar-coluna-arquivo,
    .importar-coluna-chave {
        display: flex;
        flex-direction: column;
        height: 100%;
    }

    .importar-coluna-arquivo > .upload-dropzone {
        flex: 1;
    }

    .importar-coluna-chave > .callout {
        margin-top: auto;
    }
}

/* --- 6. Configuracoes: ritmo mais apertado ------------------------------
   Cinco paineis de 22px de padding, com grade de 18/24px e cabecalhos de
   20px de margem, somavam 1425px de rolagem numa tela de 900. Cada valor
   isolado parece razoavel; empilhados, viram uma pagina que nao acaba. */
.config-dashboard .nfe-panel {
    padding: var(--space-lg) var(--space-xl);
}

.config-dashboard .panel-heading {
    gap: var(--space-md);
    margin-bottom: var(--space-md);
}

.config-dashboard .config-grid {
    gap: var(--space-md) var(--space-lg);
}

.config-dashboard .form-field {
    margin-bottom: 0;
}

.config-dashboard .form-section-desc,
.config-dashboard .panel-heading p {
    margin-bottom: 0;
}

/* --- 4b. O caminho ate a primeira linha da lista -------------------------
   Medido: 486px de cromo antes da primeira linha de dados, numa tela de
   900px. Mais da metade do monitor gasta em titulo e controles, na tela
   onde o operador passa o dia LENDO LINHAS.

   Tres fontes, todas de valores que isolados parecem razoaveis:
     52px  padding superior do conteudo
     24px  respiro entre cada bloco da pagina
     60px  os atalhos de rota quebrando em DUAS linhas */
.app-content {
    padding: clamp(20px, 2.5vw, 32px) clamp(20px, 4vw, 44px);
}

.nfe-dashboard {
    gap: var(--space-lg);
}

.nfe-dashboard > .page-heading {
    margin-bottom: 0;
}

/* Os atalhos de rota QUEBRAM LINHA (feature 007). A feature 003 os tinha
   posto numa linha que rolava, para economizar os ~30px da segunda linha --
   mas a barra de rolagem escondia rotas fora da tela, e o operador concluía
   que elas não existiam. Mostrar todas vale a linha a mais. `.chip-row` já
   faz `flex-wrap: wrap`; aqui só se desfaz a rolagem. */
.chip-row--rotas {
    margin-top: 0;
}

/* --- 6b. Altura do campo alinhada a do botao ----------------------------
   O campo media 46px e o botao 38px: alem de custar 8px em cada um dos
   dezenas de campos de uma tela de configuracao, os dois nunca alinhavam
   quando ficavam lado a lado numa mesma linha — que e o caso da barra de
   filtros e das linhas de acao.

   38px continua confortavel para mouse. No toque a regra volta a 44px, que
   e o piso recomendado para o dedo. */
.form-field input:not([type="checkbox"]):not([type="radio"]),
.form-field select,
.form-field textarea {
    min-height: 38px;
}

@media (pointer: coarse) {

    .form-field input:not([type="checkbox"]):not([type="radio"]),
    .form-field select {
        min-height: 44px;
    }
}

/* Textarea nao herda o minimo do campo: ela tem `rows` proprio, e um
   minimo de 38px a faria menor que as duas linhas que declarou. */
.form-field textarea {
    min-height: 64px;
}

/* Paineis de configuracao empilhados: o respiro ENTRE eles pesa tanto
   quanto o de dentro. */
/* PAINEIS LADO A LADO. Empilhados numa coluna de 1240px, quatro paineis de
   configuracao viravam uma pagina que nao acaba — e nenhum deles precisa da
   largura toda: sao campos curtos e interruptores.

   A ordem nao e arbitraria, e por HIERARQUIA:
     linha inteira   o interruptor mestre ("esta ligado?") — decide se o
                     resto importa, entao vem primeiro e sozinho
     duas colunas    os blocos de configuracao, que se leem em paralelo
     linha inteira   o explicador e a barra de acoes, que fecham a tela

   `align-items: start` e obrigatorio: sem ele o painel mais curto do par
   estica ate a altura do vizinho e ganha um vao vazio embaixo. */
/* OPT-IN, e nao automatico para todo formulario de configuracao.

   Aplicar a grade a `.config-form` inteiro parecia economia e nao era: na
   tela da transportadora, cinco paineis de alturas muito diferentes ficaram
   irregulares (706 contra 456px lado a lado) e a barra de acoes, que e
   filha do form, virou ITEM DA GRADE e foi parar no meio da pagina, na
   coluna da direita.

   Duas colunas so valem onde o par foi MEDIDO e fecha equilibrado. Cada
   tela que quiser opta e mostra o numero. */
.config-form--duplo {
    align-items: start;
    display: grid;
    gap: var(--space-md);
    grid-template-columns: 1fr;
}

/* A barra de acoes atravessa a grade inteira e fica por ultimo: ela comanda
   o formulario todo, nao uma coluna. */
.config-form--duplo > .barra-acoes {
    grid-column: 1 / -1;
}

@media (min-width: 1100px) {

    /* 1.5fr / 1fr, e nao meio a meio. MEDIDO: em colunas iguais o painel de
       conexao media 669px contra 453 do de filtros, deixando 216px de vao
       vazio ao lado do menor. O de conexao tem seis campos que querem uma
       grade de tres colunas; espremido em metade da largura ele quebra em
       tres linhas e cresce.

       Dando a ele 60% da largura, os dois fecham em 471px — diferenca ZERO —
       e a pagina inteira cai de 1916 para 1718px. A proporcao nao e estetica:
       e o ponto onde os dois conteudos ficam do mesmo tamanho. */
    .config-form--duplo {
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    }

    /* SIMETRIA no par: os dois blocos lado a lado fecham na mesma altura, e
       nao no tamanho do proprio conteudo. Sem isto o mais curto termina
       antes e deixa um degrau visivel entre as duas colunas. */
    .config-form--duplo > .nfe-panel:not(.painel--largo) {
        align-self: stretch;
    }

    .config-form--duplo > .painel--largo {
        grid-column: 1 / -1;
    }
}

.config-dashboard .nfe-panel + .nfe-panel {
    margin-top: 0;
}

.config-dashboard .switch-group {
    gap: var(--space-sm);
}

/* --- Conteudo sem espacos nao pode alargar o pop-up ----------------------
   O titulo do detalhe de NF-e e a CHAVE DE ACESSO: 44 digitos sem um espaco
   sequer. Numa tela de 375px ela media 484px e, como item de grade nao
   encolhe abaixo do proprio min-content por padrao, empurrava a casca
   inteira para 576px — a casca rolava na horizontal, arrastando cabecalho e
   rodape junto.

   `min-width: 0` nos itens da grade permite o encolhimento; `anywhere`
   autoriza a quebra no meio da sequencia. `break-word` nao bastaria: ele so
   quebra onde ja haveria oportunidade, e aqui nao ha nenhuma. */
.modal-shell > * {
    min-width: 0;
}

.modal-header h2,
.modal-header .eyebrow {
    overflow-wrap: anywhere;
}

/* Mesmo motivo nos valores: chave, CNPJ e protocolo sao sequencias longas
   sem separador natural. */
.detail-list dd,
.detail-list dt {
    overflow-wrap: anywhere;
}

/* ==========================================================================
   Bipagem de NF-e
   --------------------------------------------------------------------------
   O campo e o alvo do leitor de codigo de barras, nao de digitacao: grande e
   com o foco bem visivel, porque o operador olha para o papel e para o
   leitor, e precisa conferir num relance que a tela esta pronta para bipar.
   ========================================================================== */

.bipagem-form {
    display: grid;
    gap: var(--space-sm);
    margin-bottom: var(--space-md);
}

.bipagem-rotulo {
    align-items: center;
    color: var(--ink-soft);
    display: inline-flex;
    font-weight: 600;
    gap: 8px;
}

.bipagem-linha {
    align-items: stretch;
    display: flex;
    gap: 10px;
}

/* Mono e tabular-nums pelo mesmo motivo do campo de chave da importacao:
   44 digitos que se conferem contra o DANFE, digito a digito. */
.bipagem-form input[data-bipagem-campo] {
    background-color: var(--field-bg);
    border: 2px solid var(--border-strong);
    border-radius: var(--radius-sm);
    color: var(--ink);
    flex: 1;
    font-family: var(--font-mono, ui-monospace, monospace);
    font-size: 1.25rem;
    font-variant-numeric: tabular-nums;
    min-height: 56px;
    min-width: 0;
    padding: 0 var(--space-md);
    transition: border-color 140ms ease, box-shadow 140ms ease;
}

.bipagem-form input[data-bipagem-campo]:focus {
    border-color: var(--accent);
    box-shadow: var(--focus-ring);
    outline: 0;
}

.bipagem-form input[data-bipagem-campo]::placeholder {
    color: var(--ink-faint);
    font-family: var(--font-sans);
    font-size: 1rem;
}

/* Buscando: o campo continua legivel (a chave lida fica visivel enquanto a
   SEFAZ responde), so perde a cor de acao. */
.bipagem-form input[data-bipagem-campo][aria-busy="true"] {
    border-color: var(--info-border);
    cursor: progress;
}

.bipagem-estado {
    color: var(--ink-faint);
    font-size: 0.875rem;
    margin: 0;
}

.bipagem-form:has(input[aria-busy="true"]) .bipagem-estado {
    color: var(--info);
}

.bipagem-totais {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    justify-content: flex-end;
}

.bipagem-tabela td {
    vertical-align: top;
}

.bipagem-emitente {
    color: var(--ink-soft);
    display: block;
}

.bipagem-chave {
    color: var(--ink-faint);
    display: block;
    font-size: 0.75rem;
    overflow-wrap: anywhere;
}

/* A mensagem e a resposta da bipada: nunca truncada, porque a parte que diz
   O QUE FAZER costuma estar no fim. */
.bipagem-mensagem {
    color: var(--ink-soft);
    max-width: 60ch;
}

.bipagem-sefaz {
    color: var(--ink-faint);
    display: block;
    font-size: 0.75rem;
    margin-top: 4px;
}

@media (max-width: 640px) {
    .bipagem-linha {
        flex-direction: column;
    }

    .bipagem-totais {
        justify-content: flex-start;
    }
}

/* ==========================================================================
   Listagens sem barra de rolagem horizontal
   --------------------------------------------------------------------------
   As listagens de NF-e, CT-e e bipagem rolavam na horizontal num monitor
   comum. Nenhuma coluna era larga de verdade: o que estourava era o que NAO
   PODIA QUEBRAR -- `.status-pill` e `.mono` trazem `white-space: nowrap`, e
   pilulas como "Baixada Santista — Rota 2 (Guarujá/Bertioga)" ou "Preparado
   (pendente de revisão)", a chave de 44 digitos e a data relativa ("4
   semanas, 1 dia atrás") exigiam cada uma a largura inteira numa linha so.

   A correcao vale SO dentro da tabela: fora dela (cabecalho de painel,
   detalhe do CT-e) a pilula continua numa linha, que e onde ela cabe.
   ========================================================================== */

/* Pilula de status, desfecho, CT-e e rota: ate duas linhas. O `max-width`
   em `ch` e o que decide a quebra -- sem ele o navegador so quebraria depois
   de esgotar a largura da tabela, que e justamente quando ja estourou.
   `balance` divide o texto em linhas de tamanho parecido, em vez de deixar
   uma palavra sozinha na segunda. */
.data-table td .status-pill {
    align-items: baseline;
    /* Em `em`, e nao em `ch`: `ch` e a largura do "0" e subestima letra
       larga e o espacamento entre letras. MEDIDO na fonte da pilula (11px):
       "Baixada Santista — Rota 2" ocupa 140px -- a maior primeira linha dos
       nomes de rota. 16em = 176px cabe o texto mais o ponto. */
    max-width: 16em;
    text-wrap: balance;
    white-space: normal;
    /* Em duas linhas o raio de pilula (999px) viraria semicirculo e comeria
       as pontas do texto. */
    border-radius: var(--radius-md);
}

/* O ponto acompanha a PRIMEIRA linha, e nao o meio da pilula. */
.data-table td .status-pill .status-dot {
    flex: none;
    position: relative;
    top: -1px;
}

/* Chave de acesso, CNPJ, datas e a linha "SEFAZ: ..." da bipagem: continuam
   em fonte mono, mas podem quebrar. `anywhere`, e nao `break-word`: a chave
   nao tem espaco nenhum, e `break-word` so quebra onde ja haveria
   oportunidade. Ficam de fora as colunas de valor (`.num`) e o "Nº 288052"
   do link: valor ou numero partido em duas linhas nao se le. */
.data-table td .mono:not(.table-link),
.data-table td.mono:not(.num) {
    overflow-wrap: anywhere;
    white-space: normal;
}

/* MEDIDO a 1366px (o monitor mais comum do escritorio): com tudo quebrando,
   as dez colunas da Central de CT-e ja estavam no minimo do conteudo e ainda
   somavam 1023px numa area de 999. O que sobrava era espacamento: 12px de
   cada lado em cada celula. Nas listagens compactas ele cai para 8px, que
   devolve ~70px sem partir mais nenhum texto. */
.data-table--compacta th,
.data-table--compacta td {
    padding-left: var(--space-sm);
    padding-right: var(--space-sm);
}

.data-table--compacta td:first-child,
.data-table--compacta th:first-child {
    padding-left: 0;
}

.data-table--compacta td:last-child,
.data-table--compacta th:last-child {
    padding-right: 0;
}

/* DUAS LINHAS, e nao tres. O teto da pilula nao basta: quem decide a largura
   e a tabela, que reparte o espaco entre dez colunas e entregava a rota com
   ~116px de texto -- a pilula caia em tres linhas mesmo a 1785px, com espaco
   sobrando nas colunas de nome.

   A largura da coluna so e imposta a partir de 1600px. MEDIDO a 1366px: as
   dez colunas da Central de CT-e ja estao no minimo do conteudo e fecham
   exatamente na area visivel; mais 90px nas duas colunas de pilula trariam
   a barra de rolagem de volta. Entre as duas perdas, a escolha e a pilula
   ganhar uma terceira linha, nunca a lista rolar de lado.

   Valores medidos: rota = 140px de texto + ponto + respiro da pilula + da
   celula = 192px; status = "Preparado (pendente" (113px) + o mesmo = 160px. */
@media (min-width: 1600px) {

    .data-table--compacta th.col-pilula-larga {
        width: 12rem;
    }

    .data-table--compacta th.col-pilula {
        width: 10rem;
    }
}

/* --- Histórico de vigências da regra de frete (feature 007) ---------------
   Abaixo do formulário, separado por uma linha: é consulta, não ação. */
.historico-regra {
    border-top: 1px solid var(--border);
    margin-top: var(--space-lg);
    padding-top: var(--space-md);
}

.historico-regra h3 {
    font-size: var(--text-sm);
    margin: 0 0 var(--space-sm);
}

.historico-regra-linha--substituida td,
.historico-regra-linha--encerrada td {
    color: var(--ink-faint);
}
