/* ux_atlas.css, a camada de robustez do fork ATLAS.
 * ============================================================================
 *
 * Terceira e ULTIMA folha do painel, carregada depois de responsivo.css e de
 * identidade_atlas.css. Cada uma tem um trabalho que as outras nao fazem:
 *
 *     style.css            o desenho do painel
 *     responsivo.css       o mesmo desenho em cada tamanho de tela
 *     identidade_atlas.css a marca (cor, fonte, forma)
 *     ux_atlas.css         a garantia de que nada disso quebra com dado real   <-- esta
 *
 * A REGRA DESTA FOLHA: nenhuma linha aqui escolhe cor, hierarquia ou desenho.
 * Toda regra existe para impedir um defeito medido, e cada bloco cita qual.
 * Se o painel ja contem o conteudo, carregar este arquivo nao muda um pixel.
 *
 * A medida vem do auditor da skill ui-integridade:
 *     python3 ~/.claude/skills/ui-integridade/scripts/auditar_ui.py \
 *         --base http://localhost:8110 capex.html ... --telas 1440x900,1280x800,390x844
 *
 * NAO EDITE ESTE ARQUIVO PELO PAINEL: ele e fonte, e o regenerar_fork.command
 * o preserva. O que ele injeta nos HTML esta em aplicar_ux_atlas.py.
 */

/* ==========================================================================
   0. O cobre que e TEXTO
   ==========================================================================
   O cobre da marca (var(--k-a9713f), a cancela) da 4,10:1 sobre branco. Passa como
   elemento grafico, reprova como texto pequeno: o minimo WCAG AA para corpo
   e 4,5:1. Media na fonte: o subtitulo do cabecalho aparecia a 4,10:1 nas
   sete abas e em todas as tres larguras, 21 ocorrencias do mesmo defeito.

   A saida nao e trocar o cobre da marca, e reconhecer que cor de marca e cor
   de texto sao papeis diferentes. Este token tem matiz 28,3 e saturacao 46%
   IDENTICOS ao cobre; so a luminosidade cai de 45% para 41%. Lado a lado a
   diferenca nao se percebe, e ele passa nos dois fundos do painel.

       var(--k-a9713f)  marca   4,10:1 sobre branco   cancela, bordas, series
       var(--k-986539)  texto   4,93:1 sobre branco   rotulos pequenos
                        4,55:1 sobre var(--k-f4f6f8)

   Decisao do usuario, 2026-09-05. */
:root {
    --atlas-cobre-texto: var(--k-986539);

    /* Mesmo raciocinio para o grafite claro da rampa neutra. Como texto ele da
       4,37:1 e reprova; como linha de referencia, fundo de barra e borda ele
       passa (o limiar ali e 3:1) e continua sendo o var(--k-6e7a88) original.

           var(--k-6e7a88)  rampa   4,37:1 sobre branco   linhas, fundos, bordas
           var(--k-677280)  texto   4,89:1 sobre branco   notas, rodapes, rotulos
                            4,51:1 sobre var(--k-f4f6f8)

       Matiz 212 e saturacao 11% identicos; luminosidade 48% -> 45%. */
    --atlas-grafite-texto: var(--k-677280);
}

/* ==========================================================================
   0b. A SUPERFICIE ESCURA QUE NAO E' ESCURA O BASTANTE
   ==========================================================================
   O card de resumo de obras tem fundo linear-gradient(135deg, var(--k-1c2e40), var(--k-5b7a99))
   e texto branco. Contra a parada ESCURA o branco da 13,87:1; contra a CLARA,
   4,48:1, que reprova o minimo de 4,5 para texto pequeno. Por 0,02.

   E nao adianta mexer na opacidade: mesmo a 1,00 o branco para em 4,48. O
   gargalo e' a parada clara, nao o rotulo. Escurecendo-a 6 pontos de
   luminosidade, com matiz e saturacao IDENTICOS, o branco passa a 4,53:1 MESMO
   preservando o opacity: 0.85 que da hierarquia ao rotulo.

       var(--k-5b7a99)  ->  var(--k-506b86)     branco: 4,48 -> 5,33 : 1
                                com o opacity 0.85 do rotulo: 3,78 -> 4,53 : 1

   Mesma logica ja adotada para o cobre e o grafite: quando uma cor reprova NO
   PAPEL em que esta sendo usada, deriva-se para o papel, sem mexer na marca.
   Aqui o papel e' "fundo de texto pequeno".

   Para reverter, apague este bloco. */
.summary-card,
[class*="-card"][style*="linear-gradient"] {
    background-image: linear-gradient(135deg, var(--k-1c2e40), var(--k-506b86));
}

/* ==========================================================================
   1. O item de flex que se recusa a encolher
   ==========================================================================
   O defeito de contencao mais comum e o mais invisivel no codigo: item de flex
   e de grid tem min-width: auto, ou seja, ele NAO encolhe abaixo da largura do
   proprio conteudo. Um valor de 12 digitos dentro de um card flex nao quebra
   nem corta, empurra a caixa e vaza.

   Medido: span#total-investment ("R$ 5.305.651.670") ultrapassava 23 px o
   cartao no celular; aside#sidebar-extras cortava 5 px; a coluna do trafego
   cortava 15 px. Zerar o minimo devolve ao item a permissao de encolher, e a
   quebra de linha volta a funcionar sozinha. */
.card,
.panel,
[class*="-panel"],
[class*="-card"],
[class*="-section"],
.chart-container,
.filter-group,
.label-group,
.title-container,
.stat-box,
.summary-box {
    min-width: 0;
    min-height: 0;
}

.card > *,
.panel > *,
[class*="-panel"] > *,
[class*="-card"] > *,
.chart-container > *,
.title-container > *,
.filters-row > * {
    min-width: 0;
}

/* ==========================================================================
   2. A palavra que nao cabe
   ==========================================================================
   Codigo de rodovia, nome tecnico com parenteses, identificador de portico:
   sem espaco nao ha onde quebrar, e uma palavra so rompe a coluna inteira.
   break-word quebra apenas quando nao ha alternativa, entao texto normal
   continua quebrando nos espacos. */
h1, h2, h3, h4, h5, h6,
p, li, dd, dt, td, th, label, figcaption, summary,
.chart-title, .panel-header, .legend-item span {
    overflow-wrap: break-word;
    word-break: normal;
}

/* ==========================================================================
   2b. A TINTA QUE SAI DA CAIXA
   ==========================================================================
   Defeito visto a olho pelo usuario e que passou por TRES auditorias, porque
   as regras olhavam a caixa e o problema estava nas letras:

       #titmapa    { max-width: 206px; }
       #titmapa h3 { text-transform: uppercase; white-space: nowrap; }

   Tres coisas certas isoladamente e fatais juntas. A largura esta travada, a
   quebra esta proibida, e uppercase faz o texto renderizado ser mais largo do
   que o codigo aparenta ("Precipitacao - Pluviometria" no HTML vira
   "PRECIPITACAO - PLUVIOMETRIA" na tela). Sem para onde ir e com overflow
   visible, o texto simplesmente continua desenhando fora do cartao: 213 px de
   letra numa caixa de 180, saindo 20 px do bloco creme.

   getBoundingClientRect do h3 devolve 180 px e diz que esta tudo contido. A
   medida certa e a TINTA, com um Range sobre o conteudo. Foi o que a regra
   CONTENCAO_TINTA do auditor passou a fazer.

   nowrap em caixa de largura travada e bomba armada: funciona ate o dia em que
   alguem escreve um nome maior. Deixar quebrar resolve para qualquer texto
   futuro; alargar a caixa so adia.

   O !important e necessario e legitimo aqui: a origem usa seletor de ID
   (#titmapa h3), e ID so perde para ID ou para !important. Cravar o ID nesta
   folha resolveria um caso; a regra por classe resolve a familia toda. Uma
   camada de contencao precisa ser a ultima palavra sobre transbordo. */
.ov h3, .ov h4, .ov p,
.info h3, .info h4,
.legend h3, .legend h4,
.leaflet-control h3, .leaflet-control h4,
[class*="overlay"] h3, [class*="overlay"] h4 {
    white-space: normal !important;
    overflow-wrap: break-word;
}

/* ==========================================================================
   2c. O CARTAO SOBRE CONTEUDO OCUPA O MINIMO
   ==========================================================================
   Um cartao num layout comum e' neutro: o espaco que ele ocupa e' dele. Um
   cartao SOBRE um mapa, uma foto ou um grafico e' diferente: cada pixel dele
   TAPA conteudo. Ali a largura deixa de ser uma escolha de composicao e vira
   uma troca, e a regra se inverte, ele deve pedir o minimo.

   O #titmapa tinha max-width: 206px porque o titulo, com white-space: nowrap,
   exigia uma linha inteira. Corrigido o nowrap (bloco 2b), o titulo quebra e
   quem passa a definir a largura sao as linhas de trecho, que sao mais curtas.
   Manter os 206 px seria tapar mapa a toa.

   width: fit-content faz o cartao medir o que ele tem; o max-width segue como
   TETO, para um texto longo nao esticar o cartao sobre o mapa inteiro. E o
   min-width impede que ele fique estreito demais e comece a quebrar tudo. */
.ov,
.leaflet-control-container .info,
[class*="overlay"] {
    width: fit-content;
    min-inline-size: 150px;
}

/* ==========================================================================
   2d. O SEGMENTADOR LARGO USA O TRATAMENTO DO ELEMENTO LARGO
   ==========================================================================
   O painel ja tem DOIS tratamentos de "selecionado", e eles nao sao
   intercambiaveis, porque peso visual e' area, nao cor:

       botao "Ano"      47 px de largura   fundo midnight cheio
       aba ativa        98 px              fundo transparente + sublinhado 3 px

   O segmentador da chuva tem 239 px e usava o primeiro. Cinco vezes a area do
   botao que o inspirou, o mesmo preto solido: deixa de ser acento e vira um
   bloco pesado no meio de uma area de dados claros.

   Aqui ele passa a usar o tratamento que o proprio sistema reservou ao elemento
   largo. Nada de cor nova: e' o vocabulario que ja existe, aplicado na medida
   certa. O midnight continua marcando o ativo, agora como sublinhado, e o texto
   ganha contraste em vez de perder (10,4:1 sobre a superficie clara, contra os
   15,7:1 do branco sobre midnight; ambos passam AAA com folga).

   Para reverter, apague este bloco. */
.calbtn {
    background: var(--atlas-superficie, var(--k-f4f6f8));
    color: var(--atlas-grafite, var(--k-3e4c5e));
    border: 1px solid var(--border-color);
    border-bottom-width: 3px;
    border-bottom-color: transparent;
    transition: none;
}

.calbtn.on {
    background: var(--k-ffffff);
    color: var(--atlas-midnight, var(--k-0f1e2c));
    border-bottom-color: var(--atlas-midnight, var(--k-0f1e2c));
    font-weight: 800;
}

/* ==========================================================================
   3. O numero que muda de largura
   ==========================================================================
   Valores monetarios mudam de largura conforme o dado, e a caixa danca a cada
   troca de filtro. Com algarismos de largura fixa a coluna para de se mexer e
   o alinhamento passa a valer para qualquer valor. */
td, th,
.total-value, .cell-valor, .cell-area,
[class*="valor"], [class*="value"], [class*="total"],
#total-investment {
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
}

/* ==========================================================================
   4. A palavra pendurada
   ==========================================================================
   Medido: "Reforco estrutural de Obra de Arte Especial (TB-36 para TB-45)"
   deixava "TB-45)" sozinho numa linha ocupando 11% da largura, na tabela e na
   legenda do mapa de obras. balance equilibra as linhas de um titulo; pretty
   olha so o fim do paragrafo, mais barato. Onde nao houver suporte a regra e
   ignorada e o texto quebra como antes. */
h1, h2, h3, h4, h5, h6,
.chart-title, .panel-header, caption, legend {
    text-wrap: balance;
}

p, li, dd,
.legend-item span,
.col-tipo-sticky,
#stat, #titmapa p {
    text-wrap: pretty;
}

/* ==========================================================================
   5. A tabela que empurra a pagina
   ==========================================================================
   Rolagem horizontal na PAGINA e sempre defeito; dentro do envelope da tabela
   e esperada. O painel ja tem os envelopes, aqui eles so ficam garantidos. */
.drilldown-table-wrap,
.table-wrap,
[class*="table-wrap"],
[class*="-table-container"],
.portico-table-section {
    overflow-x: auto;
    max-inline-size: 100%;
    -webkit-overflow-scrolling: touch;
}

/* rede de seguranca: se algo escapar, o corte fica no documento em vez de
   virar barra de rolagem horizontal. Nao esconde nada do auditor, que mede
   scrollWidth e continua acusando. */
img, svg, video, canvas, iframe {
    max-inline-size: 100%;
}

/* MAS NAO DENTRO DE QUEM GERENCIA O PROPRIO LAYOUT.
   Esta excecao nao e' um detalhe: sem ela a regra acima APAGA o mapa.

   O Leaflet dimensiona o canvas de vetores MAIOR que o container de proposito
   (2196 px de atributo width para um mapa de 1098) e o desloca com transform,
   para arrastar o mapa sem redesenhar tudo. Um max-inline-size: 100% comprime
   esse canvas para caber, e o desenho inteiro e' esmagado: medido, o canvas
   de 2196 px foi renderizado com 150 px, e o usuario relatou "o mapa sumiu".

   Vale para qualquer componente que calcula as proprias dimensoes: mapa,
   biblioteca de grafico com canvas dimensionado por devicePixelRatio, viewer de
   PDF. A camada de robustez protege o LAYOUT DO DOCUMENTO; onde um componente
   e' dono do seu proprio, ela sai da frente.

   Licao: uma regra defensiva escrita para "todo mundo" precisa saber de quem
   ela NAO e' dona. */
.leaflet-container :is(canvas, svg, img),
.leaflet-pane :is(canvas, svg, img),
[class*="chartjs"] canvas,
[data-layout-proprio] :is(canvas, svg, img) {
    max-inline-size: none;
}

/* ==========================================================================
   6. O celular
   ==========================================================================
   48 dos 70 achados da primeira medicao estavam so em 390 px. A causa maior
   (o cabecalho travado em 80 px) foi corrigida na origem, em
   identidade_atlas.css. O que resta aqui sao os arremates. */
@media (max-width: 768px) {

    /* a faixa de abas rola por toque com ponto de parada, em vez de sair
       cortada pela direita. O responsivo.css ja rolava; o snap e o respiro do
       fim da lista sao o que faltava para a ultima aba nao ficar colada na
       borda e parecer cortada. */
    .main-nav {
        scroll-snap-type: x proximity;
        scroll-padding-inline: var(--pad-lateral, 12px);
        padding-inline-end: var(--pad-lateral, 12px);
        overscroll-behavior-x: contain;
    }

    .main-nav > * {
        flex: 0 0 auto;
        scroll-snap-align: start;
    }

    /* o valor grande do cabecalho de obras vazava 23 px do cartao: aqui ele
       encolhe com a caixa em vez de empurra-la */
    #total-investment,
    .total-value {
        display: block;
        min-width: 0;
        overflow-wrap: break-word;
    }

    /* rodape do calendario de chuva: "seco" e "chuvoso (mm/dia)" ficavam por
       cima dos rotulos dos meses quando a grade encolhia */
    #calFootL,
    #calFootR {
        position: static;
        white-space: nowrap;
    }

    #yearGrid {
        min-width: 0;
        overflow-x: auto;
    }

    /* paineis empilhados nao herdam largura de coluna: sem isto o conteudo
       define a largura e a caixa e cortada */
    .trafego-panel,
    #sidebar-extras,
    .chart-and-table-row > * {
        min-width: 0;
        max-inline-size: 100%;
    }

    /* grafico e tabela lado a lado nao cabem em 390 px: a nota de rodape da
       tabela acabava sobre a legenda do grafico. Empilhados, cada um tem a
       largura toda e a colisao deixa de existir. */
    .chart-and-table-row {
        flex-direction: column;
    }

    /* ----------------------------------------------------------------------
       A LARGURA MINIMA LEGIVEL
       ----------------------------------------------------------------------
       Perseguir um a um os contêineres que nao empilham e trabalho infinito:
       .receita-panel-bottom e flex row com TRES filhos e, em 390 px, cada um
       ficava com 97 px. Dentro deles, os titulos dos donuts caiam para caixas
       de 46 px, e "LIQUIDA TARIFARIA (APOS PIS/COFINS/ISS)" saia 41 px do
       cartao. Nenhum ajuste de tipografia salva 46 px.

       A defesa que resolve a CLASSE inverte a logica: em vez de o pai decidir
       empilhar, o filho declara a largura minima em que ainda e legivel, e o
       wrap cuida do resto. Vale para qualquer numero de filhos e qualquer
       largura de tela, inclusive as que ninguem testou.

       260 px e o piso: abaixo disso um grafico com titulo, legenda e eixo
       deixa de comunicar. */
    [class*="-panel"],
    [class*="-row"],
    .chart-and-table-row,
    .filters-row {
        flex-wrap: wrap;
    }

    .chart-container,
    [class*="-section"] {
        flex: 1 1 260px;
        min-inline-size: min(260px, 100%);
    }

    /* ----------------------------------------------------------------------
       A LINHA DE PAINEIS QUE NAO EMPILHA
       ----------------------------------------------------------------------
       O responsivo.css ja empilha o painel no celular, mas ele mira CLASSES
       (.trafego-layout, #content-area), e as linhas internas de paineis vivem
       em <div> SEM CLASSE. Elas escapam da regra e continuam lado a lado: no
       trafego, quatro paineis com 67 a 83 px cada, titulos cortados em 15, 40 e
       5 px. Em obras, o card lateral com 79 px de largura, onde nenhuma
       tipografia salva "INVESTIMENTO EM OBRAS DE MELHORIA".

       Mirar pelo FILHO resolve sem depender de o pai ter nome. :has() e a
       ferramenta certa: onde nao houver suporte a regra e ignorada e o layout
       fica como esta hoje, sem quebrar nada. */
    div:has(> .trafego-panel),
    div:has(> .chart-container + .chart-container) {
        flex-direction: column;
        flex-wrap: wrap;
    }

    .trafego-panel,
    .summary-card {
        inline-size: 100%;
    }

    /* O card de resumo e flex ROW: rotulo e valor lado a lado. Em 338 px o
       rotulo fica com 79 px, e "INVESTIMENTO EM OBRAS DE MELHORIA" em 79 px
       nao tem quebra boa possivel, nenhuma regra de tipografia salva. O que
       resolve e empilhar: o rotulo ganha a largura toda e cabe em duas linhas
       equilibradas. Aqui o alvo e o PROPRIO card, nao o pai dele. */
    .summary-card {
        flex-direction: column;
        align-items: flex-start;
        gap: 2px;
    }

    /* legenda de grafico: dois rotulos lado a lado numa caixa de 97 px vazavam
       16 px para cada lado. Deixa-los quebrar e o suficiente. */
    .chart-container div:has(> span) {
        flex-wrap: wrap;
        justify-content: center;
        min-width: 0;
    }

    /* a nota de rodape da tabela de porticos tem max-width: 300px em style
       inline, maior que a coluna no celular, e passava por cima do vizinho.
       min() preserva a intencao (300 px de medida de linha) e so entra em acao
       quando 300 px nao cabem: forcar 100% resolveria a colisao e criaria um
       vao morto de 85% no bloco largo. */
    .portico-table-section div[style*="max-width"] {
        max-inline-size: min(300px, 100%) !important;
    }
}

/* ==========================================================================
   9. O CARD QUE ESTICA VAZIO
   ==========================================================================
   Medido no OPEX, em 1440 e em 1280: a densidade varia 62% em torno da media,
   com cinco celulas saturadas e duas quase vazias de dezesseis. A causa e
   estrutural, nao de conteudo.

   .opex-layout e flex row de altura travada em 100vh, e as duas colunas
   esticam. A da direita e um grafico, que usa a altura toda. A da esquerda e
   uma tabela de CINCO linhas: ela ocupa o terco de cima e deixa cerca de 500 px
   de cartao branco vazio embaixo. O CAPEX tem a mesma estrutura com oito
   linhas e nao acusa, o que confirma que o problema e a caixa nao acompanhar o
   conteudo.

   align-self: flex-start faz a coluna medir o que ela tem. O vazio nao
   desaparece (ele e da pagina), mas deixa de estar DENTRO de um cartao, que e
   o que se le como area morta. max-block-size garante que, se a tabela crescer,
   o cartao para na altura da tela e rola por dentro, em vez de estourar.

   Esta e a unica regra deste arquivo que muda DESENHO, e nao so robustez.
   Para reverter, apague o bloco. */
.opex-layout > .opex-panel-top {
    align-self: flex-start;
    max-block-size: 100%;
}

/* ==========================================================================
   6b. A ABA QUE "SUMIU"
   ==========================================================================
   Entre 769 e 1023 px as sete abas somam 642 px numa barra que chega a 475 px.
   A barra tem overflow-x: auto, entao tecnicamente ela ROLA. Mas sem nenhuma
   pista visual de que ha mais conteudo a direita, "rola" e' indistinguivel de
   "sumiu": o usuario relatou que a aba HDM PAVIMENTO tinha desaparecido.

   Rolagem sem affordance nao e' acesso, e' esconderijo.

   Nesta faixa as abas cabem folgadas em duas linhas, entao quebrar e' melhor que
   rolar: o usuario VE todas as opcoes, que e' a funcao de uma barra de
   navegacao. Abaixo de 768 px o painel ja e' documento rolavel e a faixa por
   toque com snap continua sendo a escolha certa, porque ali a altura e' cara.

   Esta faixa nunca tinha sido auditada: a matriz era 1440, 1280 e 390, e o
   defeito morava no buraco entre 768 e 1280. Ver o protocolo no SKILL.md. */
@media (min-width: 769px) and (max-width: 1023px) {
    .main-nav {
        flex-wrap: wrap;
        overflow-x: visible;
        row-gap: 2px;
    }

    .main-nav > * {
        flex: 0 0 auto;
    }
}

/* ==========================================================================
   6c. A FAIXA DO MEIO (769 a 1024 px)
   ==========================================================================
   O painel foi desenhado para 1280+ e para celular. Entre um e outro fica a
   faixa do laptop pequeno e do tablet deitado, que ninguem projetou e ninguem
   auditava: a matriz era 1440, 1280 e 390.

   Auditada pela primeira vez, ela sozinha tinha 4 achados ALTOS que nao
   existiam em nenhuma das outras larguras: rotulos de mes do calendario
   sobrepostos e vazando, o rodape da rampa por cima da grade, o botao de
   download sobre o vizinho, e um painel de trafego cortado.

   A causa e' uma so: as defesas de empilhamento estavam presas a
   max-width: 768px, o ponto onde o painel vira documento rolavel. Contencao,
   porem, nao e' assunto de celular: e' assunto de "o conteudo nao cabe", e isso
   comeca bem antes dos 768 px. Aqui elas valem ate 1024. */
@media (max-width: 1024px) {
    [class*="-panel"],
    [class*="-row"],
    .chart-and-table-row,
    .filters-row {
        flex-wrap: wrap;
    }

    .chart-container,
    [class*="-section"] {
        flex: 1 1 260px;
        min-inline-size: min(260px, 100%);
    }

    .trafego-panel,
    #sidebar-extras,
    .summary-card,
    .chart-and-table-row > * {
        min-width: 0;
        max-inline-size: 100%;
    }

    div:has(> .trafego-panel) {
        flex-direction: column;
    }

    /* o calendario da chuva: quando a grade encolhe, os rotulos de mes ficam
       mais altos que a linha e passam por cima da linha de baixo. Dar a eles a
       altura da linha resolve sem mexer na grade. */
    #yearGrid {
        overflow-x: auto;
        align-items: stretch;
    }

    #yearGrid .ylabel {
        display: flex;
        align-items: center;
        min-block-size: 100%;
        line-height: 1.1;
        white-space: nowrap;
    }

    /* o rodape da rampa ("seco" / "chuvoso") empilha em vez de disputar a linha */
    .calfoot {
        flex-wrap: wrap;
        row-gap: 3px;
    }

    /* A BARRA DE ACOES QUE ESTOURA.
       .header-actions tem 421 px e os tres botoes somam 442 (156 + 131 + 131 mais
       24 de gap), com flex-wrap: nowrap. Eles saiam ate x=1033 numa viewport de
       1024, um por cima do outro.

       O flex: 0 0 auto dos botoes esta CERTO, botao nao deve encolher e virar
       rotulo ilegivel. O que falta e' o pai permitir a quebra: com wrap, o
       terceiro botao desce uma linha e todos continuam clicaveis e legiveis. */
    [class*="-actions"],
    [class*="-toolbar"],
    [class*="header-actions"] {
        flex-wrap: wrap;
        justify-content: flex-end;
        row-gap: 6px;
    }

    /* botoes de acao param de disputar espaco com o texto ao lado */
    [id*="download"],
    [class*="btn"] {
        flex: 0 0 auto;
        white-space: nowrap;
    }
}

/* ==========================================================================
   7. O layout que pula depois de pronto
   ==========================================================================
   CLS medido em trafego.html: 0,202, o dobro do limite de 0,1. A causa e o
   grafico que nasce sem altura e empurra tudo para baixo quando o Chart.js
   termina de desenhar. Reservar a altura antes elimina o salto sem mudar o
   desenho final.

   min-height, e nao height: se o painel ja define uma altura maior, ela vence. */
.chart-container,
canvas[id*="chart"],
canvas[id*="Chart"] {
    min-height: 180px;
}

.leaflet-container,
#map, #trafego-map, #receita-map {
    min-height: 180px;
}

/* ==========================================================================
   8. Quem pediu menos movimento
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}

/* ── CONTRASTE: rótulo sobre cartão escuro não pode ser transparente ──────────
   O h4 do .summary-card de obras nasceu com opacity .85 sobre um gradiente
   midnight, e o número ao lado com opacidade plena: a hierarquia estava sendo
   feita com transparência, que é barata quando o fundo é claro e cara quando
   não é. Medido pelo auditor com a paleta NBIM aplicada: 3,85:1, abaixo do
   mínimo AA de 4,5:1 para 11px bold.

   A hierarquia continua existindo, só que por peso, corpo e entreletra, que já
   estão declarados ali e não custam contraste. A regra é da camada de UX, e não
   da paleta, de propósito: ela vale para qualquer paleta que venha depois, e o
   defeito reapareceria em toda paleta de cartão escuro. */
.summary-card h4 {
    opacity: 1;
}

/* ── TELA ÚNICA: nada de scroll vertical, e nada de conteúdo cortado ──────────
   A regra de desenho da família é que a aba CABE na tela: o valor de um painel
   está na comparação simultânea, e o que fica abaixo da dobra não é comparado.
   O contrário disso, porém, não é esconder o excesso: cortado é pior que
   rolado, porque o dado some sem avisar.

   Medido por `_testes/conferir_tela_unica.py` nas sete abas em três telas: a
   única página que escondia conteúdo era a receita, 11 px do gráfico anual. A
   causa é a barra de rolagem HORIZONTAL do wrapper (o gráfico rola de lado
   quando há muitos anos): ela ocupa altura, e o filho pedia 100% da caixa como
   se ela não existisse. Reservar a calha e descontar a altura resolve nos dois
   sentidos, com e sem barra visível. */
#chart-scroll-wrapper {
    scrollbar-gutter: stable;
}

#chart-inner-container {
    height: calc(100% - 7px);
}
