/* ============================================================
   TRÂNSITO — a primeira dobra.

   A página não abre com um título. Abre com a leitura de onde a
   versão está no caminho da nuvem para a instalação do cliente.
   Essa assimetria (SaaS primeiro, self-hosted depois de um soak) é
   a regra que define o produto e já é invariante no domínio; aqui
   ela vira o instrumento que o visitante lê antes de qualquer coisa.

   Vocabulário emprestado do mundo do assunto — eixo de tempo,
   anotação de deploy, marcador de agora — e não do vocabulário
   genérico de changelog de SaaS.

   Tudo aqui usa o prefixo .transito / .trilha / .leitura. São nomes
   novos: esta folha entra por último na cascata e, por construção,
   não colide com app.css, controls.css nem polish.css.
   ============================================================ */

/* ---------- moldura ---------- */

.transito {
  /* Sem card. O herói assenta direto no paper — a moldura seria o
     primeiro passo na direção do genérico. */
  padding: clamp(2.5rem, 6vw, 4.5rem) 0 clamp(1.75rem, 3vw, 2.5rem);
  border-bottom: 1px solid var(--border);
  margin-bottom: 1.75rem;
}

/* ---------- estado (eyebrow) ---------- */

.transito__estado {
  display: flex; align-items: center; gap: .5rem;
  font-family: var(--font-mono);
  font-size: .6875rem; font-weight: 600;
  letter-spacing: .13em; text-transform: uppercase;
  color: var(--text-3);
  margin-bottom: clamp(1.25rem, 3vw, 1.75rem);
}
.transito__estado b { color: var(--text-1); font-weight: 600; }

/* O ponto de estado carrega a semântica da fase, não é enfeite. */
.transito__farol {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--accent);
  flex: none;
}
.farol--atencao { background: var(--warn); }
.farol--neutro  { background: var(--dot-muted); }

/* ---------- cabeça: versão + título ---------- */

.transito__cabeca {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: clamp(1.5rem, 4vw, 3.5rem);
  align-items: start;
  margin-bottom: clamp(2.25rem, 5vw, 3.25rem);
}

/* A versão é rótulo sobre valor, igual às leituras lá embaixo — é a maior
   leitura da página, não um outdoor. Isso também resolve o "v" solto: ele
   virou o rótulo, em vez de um glifo órfão colado no primeiro dígito. */
.transito__versao {
  display: block;
  text-decoration: none;
  color: var(--text-1);
}
.transito__versao .v {
  display: block;
  font-family: var(--font-mono);
  font-size: .625rem; font-weight: 600;
  letter-spacing: .13em; text-transform: uppercase;
  color: var(--text-3);
  margin-bottom: .45rem;
}
.transito__versao .n {
  display: block;
  font-family: var(--font-mono);
  font-weight: 600;
  font-size: clamp(2.5rem, 5vw, 3.75rem);
  line-height: .92;
  letter-spacing: -.045em;
  font-variant-numeric: tabular-nums;
  /* O mono reserva a largura cheia de cada glifo; o recuo ótico tira a folga
     à esquerda do primeiro dígito, para alinhar com o rótulo acima. */
  margin-left: -.05em;
}
.transito__versao:hover { color: var(--accent-texto); text-decoration: none; }

.transito__titulo {
  font-family: var(--font-sans);
  font-size: clamp(1.25rem, 2.2vw, 1.6rem);
  line-height: 1.28;
  font-weight: 590;
  letter-spacing: -.012em;
  max-width: 34ch;
  margin: 0 0 .625rem;
}
.transito__titulo a { color: inherit; text-decoration: none; }
.transito__titulo a:hover { color: var(--accent-texto); }

.transito__resumo {
  color: var(--text-2);
  font-size: .9375rem;
  line-height: 1.6;
  max-width: 56ch;
}

/* ============================================================
   A TRILHA
   Sólida da nuvem até hoje. Tracejada de hoje até a data prevista,
   porque aquilo ainda não aconteceu. Ticks de dia ao longo do eixo,
   losango no marcador de hoje como anotação de deploy.
   ============================================================ */

.trilha {
  position: relative;
  /* Recuo lateral igual ao raio dos marcos, para que o círculo da
     ponta não seja cortado pela borda do container. */
  padding: 0 8px;
  /* A caixa das datas já reserva a altura do qualificador ("previsto") logo
     abaixo da data; somar a margem cheia aqui abriria um vão morto. */
  margin-bottom: clamp(1.5rem, 3vw, 2rem);
}

.trilha__pista {
  position: relative;
  height: 3px;
  background: var(--border);
  /* Pontas retas: a trilha é um eixo, não uma barra de progresso. */
}

/* Ticks de dia — estrutura que codifica os 14 dias reais de soak.
   Se não houvesse contagem de dias, não haveria tick. */
.trilha__dias {
  position: absolute; left: 0; right: 0; top: -7px; height: 7px;
  background-image: linear-gradient(90deg, var(--dot-muted) 0 1px, transparent 1px);
  background-size: var(--passo-dia, 100%) 100%;
  background-repeat: repeat-x;
  pointer-events: none;
}

/* Trecho cumprido: da nuvem até hoje. */
.trilha__feito {
  position: absolute; left: 0; top: 0; height: 100%;
  width: var(--pct, 0%);
  background: var(--accent);
  transform-origin: left center;
}

/* Trecho previsto: de hoje até a data estimada. Tracejado porque é
   promessa, não fato. */
.trilha__previsto {
  position: absolute; top: 0; height: 100%;
  left: var(--pct, 0%); right: 0;
  background-image: linear-gradient(90deg, var(--trilha-fantasma) 0 6px, transparent 6px 12px);
  background-size: 12px 100%;
}

/* Marcos das pontas. */
.trilha__marco {
  position: absolute; top: 50%;
  width: 13px; height: 13px; border-radius: 50%;
  transform: translate(-50%, -50%);
  background: var(--bg);
  border: 3px solid var(--trilha-fantasma);
  box-sizing: border-box;
}
.trilha__marco--nuvem { left: 0; background: var(--accent); border-color: var(--accent); }
.trilha__marco--destino { left: 100%; }
.trilha[data-fase="liberado"] .trilha__marco--destino {
  background: var(--accent); border-color: var(--accent);
}

/* Marcador de hoje: losango, como anotação de deploy num gráfico. */
.trilha__hoje {
  position: absolute; top: 50%; left: var(--pct, 0%);
  width: 13px; height: 13px;
  transform: translate(-50%, -50%) rotate(45deg);
  background: var(--accent);
  box-shadow: 0 0 0 4px var(--bg);
}
.trilha__hoje::after {
  content: ""; position: absolute; inset: -3px;
  border: 1.5px solid var(--accent);
  opacity: 0;
}

/* ---------- rótulos e datas da trilha ---------- */

.trilha__rotulos, .trilha__datas {
  position: relative; height: 1rem;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}
.trilha__rotulos { margin-bottom: .625rem; }
/* Os filhos são todos absolutos, então a caixa não cresce sozinha: a altura
   tem de cobrir a linha da data mais o <em> ("previsto") logo abaixo, senão o
   texto invade o bloco seguinte. */
.trilha__datas  { margin-top: .625rem; height: 2.375rem; }

.trilha__rotulos span, .trilha__datas span {
  position: absolute;
  white-space: nowrap;
}
/* As pontas ancoram na borda em vez de centralizar, senão a primeira sai pela
   esquerda do container e a última pela direita. Só o rótulo do meio segue a
   posição do marcador — e ela já está em --pct, herdada de .trilha, o que
   evita ter de injetar uma variável por rótulo. */
.trilha__rotulos span[data-anc="ini"], .trilha__datas span[data-anc="ini"] { left: 0; }
.trilha__rotulos span[data-anc="fim"], .trilha__datas span[data-anc="fim"] { left: 100%; transform: translateX(-100%); }
.trilha__rotulos span[data-anc="meio"], .trilha__datas span[data-anc="meio"] { left: var(--pct, 0%); transform: translateX(-50%); }

.trilha__rotulos span {
  font-size: .625rem; font-weight: 600;
  letter-spacing: .13em; text-transform: uppercase;
  color: var(--text-3);
}
.trilha__rotulos span.on { color: var(--accent-texto); }

.trilha__datas span {
  font-size: .75rem;
  color: var(--text-2);
}
.trilha__datas span.on { color: var(--text-1); font-weight: 600; }
/* Qualificador da data, não rótulo de campo: em caixa alta ele competiria com
   NUVEM/SELF-HOSTED e a dobra já carrega versalete demais. */
.trilha__datas em {
  display: block; font-style: normal;
  font-size: .6875rem; letter-spacing: 0;
  color: var(--text-3); margin-top: .1rem;
}
.trilha__datas span[data-anc="fim"] em { text-align: right; }

/* ---------- fases ---------- */

/* Já liberada: o caminho inteiro aconteceu. Nada de tracejado. */
.trilha[data-fase="liberado"] .trilha__previsto { display: none; }
.trilha[data-fase="liberado"] .trilha__feito { width: 100%; }
.trilha[data-fase="liberado"] .trilha__hoje { display: none; }

/* Sem destino, o eixo tem de terminar em nada — não numa borda reta, que
   leria como fim de percurso. A máscara esvai a ponta direita. Vale para as
   duas fases sem data: "somente nuvem" (nunca vai replicar) e "sem previsão"
   (vai, mas ainda não se sabe quando). */
.trilha[data-fase="somente-nuvem"] .trilha__marco--destino,
.trilha[data-fase="somente-nuvem"] .trilha__hoje,
.trilha[data-fase="somente-nuvem"] .trilha__dias,
.trilha[data-fase="somente-nuvem"] .trilha__previsto { display: none; }
.trilha[data-fase="somente-nuvem"] .trilha__pista { background: none; }
.trilha[data-fase="somente-nuvem"] .trilha__feito {
  -webkit-mask-image: linear-gradient(90deg, #000 62%, transparent 100%);
  mask-image: linear-gradient(90deg, #000 62%, transparent 100%);
}

/* Anunciada mas ainda não publicada: o percurso não começou. A pista inteira
   é fantasma e o marco da origem fica vazado, porque nem a nuvem recebeu. */
.trilha[data-fase="anunciado"] .trilha__feito,
.trilha[data-fase="anunciado"] .trilha__hoje,
.trilha[data-fase="anunciado"] .trilha__dias { display: none; }
.trilha[data-fase="anunciado"] .trilha__marco--nuvem {
  background: var(--bg); border-color: var(--trilha-fantasma);
}

.trilha[data-fase="sem-previsao"] .trilha__marco--destino,
.trilha[data-fase="sem-previsao"] .trilha__dias { display: none; }
.trilha[data-fase="sem-previsao"] .trilha__pista { background: none; }
.trilha[data-fase="sem-previsao"] .trilha__previsto {
  -webkit-mask-image: linear-gradient(90deg, #000 55%, transparent 100%);
  mask-image: linear-gradient(90deg, #000 55%, transparent 100%);
}

/* Previsão vencida ou soak cumprido sem liberação: o trecho cumprido vai até o
   fim, mas o destino continua vazio — a versão não chegou. O âmbar aqui tem
   significado (algo passou do prazo), não é decoração. */
.trilha[data-fase="vencido"] .trilha__feito { background: var(--warn); }
.trilha[data-fase="vencido"] .trilha__previsto { display: none; }
.trilha[data-fase="vencido"] .trilha__hoje { display: none; }
.trilha[data-fase="vencido"] .trilha__dias { display: none; }
.trilha[data-fase="vencido"] .trilha__marco--nuvem { background: var(--warn); border-color: var(--warn); }
.trilha[data-fase="vencido"] .trilha__marco--destino { border-color: var(--warn); }

/* A legenda da trilha. Existe porque quem chega frio precisa saber o que
   "soak" quer dizer para ler o gráfico acima — é legenda, não política
   solta no topo da página. */
.transito__politica {
  font-size: .8125rem;
  line-height: 1.55;
  color: var(--text-3);
  max-width: 58ch;
  margin-bottom: clamp(1.75rem, 4vw, 2.5rem);
}
.transito__politica b { color: var(--text-2); font-weight: 590; }
.transito__maislink {
  font: inherit; color: var(--text-2); padding: 0;
  text-decoration: underline; text-underline-offset: 2px;
  text-decoration-color: var(--border);
}
.transito__maislink:hover { color: var(--accent-texto); text-decoration-color: currentColor; }

/* ============================================================
   LEITURAS — a fileira de números.
   ============================================================ */

.leitura {
  /* nowrap de propósito: as réguas verticais são border-right, e numa linha
     que quebra a última coluna de cada linha fica com um traço solto no ar.
     Em vez de tentar detectar a quebra (o CSS não consegue), a fileira só
     existe enquanto cabe inteira — abaixo de 60rem ela vira grade sem régua. */
  display: flex;
  flex-wrap: nowrap;
  gap: 0;
  margin: 0;
  border-top: 1px solid var(--border);
}
.leitura__item {
  display: flex; flex-direction: column; gap: .3rem;
  padding: 1rem clamp(1.25rem, 3vw, 2.25rem) 0 0;
  margin-right: clamp(1.25rem, 3vw, 2.25rem);
  border-right: 1px solid var(--border-soft);
  min-width: 0;
}
.leitura__item:last-child { border-right: 0; margin-right: 0; padding-right: 0; }

.leitura dt {
  font-family: var(--font-mono);
  font-size: .625rem; font-weight: 600;
  letter-spacing: .13em; text-transform: uppercase;
  color: var(--text-3);
  white-space: nowrap;
}
.leitura dd {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 1.625rem; font-weight: 600;
  line-height: 1; letter-spacing: -.03em;
  font-variant-numeric: tabular-nums;
  color: var(--text-1);
}
.leitura dd.is-alerta { color: var(--warn); }
.leitura dd.is-grave  { color: var(--danger); }
.leitura dd.is-zero   { color: var(--text-3); font-weight: 400; }

/* Nota de apoio sob um número (ex.: "3 releases atrás"). */
.leitura__nota {
  font-family: var(--font-mono);
  font-size: .6875rem; color: var(--text-2);
  margin-top: .1rem;
}
.leitura__link {
  font: inherit; color: var(--accent-texto);
  text-decoration: underline; text-underline-offset: 2px;
  padding: 0;
}
.leitura__link:hover { color: var(--text-1); }

/* ---------- a instalação, como leitura e não como formulário ---------- */

.leitura__item--inst { flex: 0 1 auto; }

/* O <select> se dimensiona pela opção mais larga da lista — inclusive pelos
   rótulos dos <optgroup>. Isso deixava "v4.9.0" com um vão de vários caracteres
   até o chevron, quebrando o alinhamento da fileira de leituras. A medida
   invisível abaixo carrega o texto realmente selecionado e dá ao container a
   largura certa; o select ocupa esse container por inteiro. */
.instsel { position: relative; display: inline-block; }
.instsel__medida {
  display: block;
  visibility: hidden;
  white-space: pre;
  font-family: var(--font-mono);
  font-size: 1.625rem; font-weight: 600;
  line-height: 1.05; letter-spacing: -.03em;
  padding-right: 1.05rem;
}
.instsel--vazio .instsel__medida { font-size: 1.0625rem; }
.instsel select:not([multiple]):not([size]) {
  position: absolute; inset: 0; width: 100%;
}

/* A especificidade aqui não é gratuita: controls.css normaliza todo
   select com `select:not([multiple]):not([size])`, que vale (0,2,1) e
   injeta caixa, chevron próprio e padding-inline-end. Sem igualar isso,
   o controle sai com dois chevrons e recuo fantasma. */
.instsel select:not([multiple]):not([size]) {
  /* O <select> continua sendo o controle real — teclado, leitor de tela e o
     seletor nativo do celular vêm de graça. O que muda é que ele deixa de
     parecer campo de formulário e passa a parecer a leitura que está do lado. */
  appearance: none; -webkit-appearance: none;
  background: none; border: 0; border-radius: 0;
  border-bottom: 2px solid var(--border);
  padding: 0 1.05rem 0 0; margin: 0;
  font-family: var(--font-mono);
  font-size: 1.625rem; font-weight: 600;
  line-height: 1.05; letter-spacing: -.03em;
  color: var(--text-1);
  cursor: pointer;
  min-block-size: 0;
  text-overflow: clip;
}
/* controls.css casa o hover com `:is(input…, select, textarea):hover:not(:disabled):not([readonly])`,
   que vale (0,5,1). Igualar o número de pseudo-classes é o preço de sobrescrever. */
.instsel select:not([multiple]):not([size]):hover:not(:disabled):not([readonly]) { border-bottom-color: var(--accent); }
.instsel select:focus-visible { outline: none; }
/* O sistema de foco do controls.css é em accent; um anel azul só aqui
   pareceria estado de erro de outro produto. */
.instsel:has(select:focus-visible) {
  outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 2px;
}

/* Vazio: menor e em accent. Não é um valor, é um convite. */
.instsel--vazio select:not([multiple]):not([size]) { color: var(--accent-texto); font-size: 1.0625rem; }

.instsel::after {
  content: ""; position: absolute; right: 2px; top: .34em;
  width: 6px; height: 6px;
  border-right: 2px solid var(--text-2); border-bottom: 2px solid var(--text-2);
  transform: rotate(45deg);
  pointer-events: none;
}
.instsel select option {
  /* A lista aberta é a mesma coisa que o valor fechado; trocar de família no
     meio da interação parece outro controle. */
  font-family: var(--font-mono);
  font-size: 1rem; color: var(--text-1); background: var(--bg-raised);
}

/* ============================================================
   BARRA DE FILTROS

   Antes eram três fileiras empilhadas de pill contornada — segmentado,
   componentes, impacto — sem hierarquia entre elas. Aqui os três eixos
   compartilham um tratamento só e se distinguem por rótulo e separador,
   que é o que a diferença entre eles realmente é.

   Nada de contorno: o estado ligado é peso de texto mais sublinhado em
   accent, como aba de painel. Contorno em tudo é o que produzia a sopa.
   ============================================================ */

.barra {
  display: flex; flex-wrap: wrap;
  align-items: baseline;
  gap: .875rem 2.75rem;
  padding-bottom: 1rem;
}
.barra__grupo {
  display: flex; align-items: baseline; gap: .75rem;
  flex-wrap: wrap;
  min-width: 0;
}
/* Sem régua vertical entre os eixos: quando um grupo quebra para a linha de
   baixo, o separador vai junto e sobra um traço solto abrindo a linha. Como
   o CSS não enxerga a quebra, a separação é feita por espaço — o dobro do
   respiro interno — e pelo rótulo em versalete que abre cada grupo. */
.barra__rot {
  font-family: var(--font-mono);
  font-size: .5625rem; font-weight: 600;
  letter-spacing: .13em; text-transform: uppercase;
  color: var(--text-3);
  flex: none;
}
.barra__opts { display: flex; flex-wrap: wrap; gap: .25rem .875rem; min-width: 0; }

.opt {
  font-size: .875rem;
  color: var(--text-2);
  padding: .125rem 0;
  border-bottom: 2px solid transparent;
  white-space: nowrap;
  transition: color var(--t-fast), border-color var(--t-fast);
}
.opt:hover { color: var(--text-1); border-bottom-color: var(--border); }
.opt[aria-pressed="true"] {
  color: var(--text-1); font-weight: 590;
  border-bottom-color: var(--accent);
}
.opt__n {
  font-family: var(--font-mono);
  font-size: .6875rem;
  font-variant-numeric: tabular-nums;
  color: var(--text-3);
  margin-left: .3rem;
}
.opt[aria-pressed="true"] .opt__n { color: var(--accent-texto); }
/* Cor só quando o filtro está ligado: um vermelho permanente em "Breaking"
   grita para quem nem pediu para ver breaking. */
.opt--grave[aria-pressed="true"]  { color: var(--danger); border-bottom-color: var(--danger); }
.opt--alerta[aria-pressed="true"] { color: var(--warn);   border-bottom-color: var(--warn); }

.barra__meta {
  display: flex; flex-wrap: wrap; gap: .375rem 1rem;
  font-family: var(--font-mono);
  font-size: .6875rem;
  font-variant-numeric: tabular-nums;
  color: var(--text-3);
  padding-bottom: 1.5rem;
  border-bottom: 1px solid var(--border);
  margin-bottom: 2rem;
}
.barra__limpar {
  font: inherit; color: var(--accent-texto);
  text-decoration: underline; text-underline-offset: 2px; padding: 0;
}
.barra__limpar:hover { color: var(--text-1); }

.barra__aviso {
  font-size: .8125rem; color: var(--text-2);
  margin: -1rem 0 2rem;
  padding-left: .75rem;
  border-left: 2px solid var(--accent);
}

@media (max-width: 48rem) {
  .barra { gap: 0; }
  .barra__grupo { flex: 1 1 100%; padding: .875rem 0; }
  .barra__grupo + .barra__grupo { border-top: 1px solid var(--border-soft); }
}

/* ============================================================
   PENDÊNCIA — só aparece quando há o que aplicar.
   Nunca ocupa espaço para dizer que não há nada.
   ============================================================ */

.pend {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: .625rem 1.25rem;
  margin-top: 1.5rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--border);
}
.pend__rotulo {
  font-family: var(--font-mono);
  font-size: .625rem; font-weight: 600;
  letter-spacing: .13em; text-transform: uppercase;
  color: var(--text-3);
  flex: none;
}
.pend__itens { display: flex; flex-wrap: wrap; gap: .375rem 1rem; flex: 1 1 auto; min-width: 0; }
.pend__item {
  font-family: var(--font-mono);
  font-size: .8125rem;
  font-variant-numeric: tabular-nums;
  color: var(--text-2);
}
.pend__item.is-alerta { color: var(--warn); }
.pend__item.is-grave  { color: var(--danger); font-weight: 600; }
.pend__btn { flex: none; }

@media (max-width: 40rem) {
  .pend { align-items: flex-start; flex-direction: column; gap: .75rem; }
}

/* ============================================================
   TRILHA MINI — o mesmo componente, pequeno, em cada entrada do feed.
   Um elemento de assinatura usado grande uma vez e pequeno muitas é o
   que faz virar sistema em vez de enfeite.
   ============================================================ */

.disp { margin-bottom: 1rem; }
.disp__estado {
  display: flex; align-items: center; gap: .5rem;
  font-family: var(--font-mono);
  font-size: .625rem; font-weight: 600;
  letter-spacing: .13em; text-transform: uppercase;
  color: var(--text-3);
  margin-bottom: .75rem;
}
.disp__estado b { color: var(--text-2); font-weight: 600; }

.trilha--mini { padding: 0 5px; margin: 0 0 .875rem; max-width: 22rem; }
.trilha--mini .trilha__pista { height: 2px; }
.trilha--mini .trilha__dias { display: none; }
.trilha--mini .trilha__marco { width: 8px; height: 8px; border-width: 2px; }
.trilha--mini .trilha__hoje { width: 8px; height: 8px; box-shadow: 0 0 0 2px var(--bg); }
.trilha--mini .trilha__rotulos { display: none; }
.trilha--mini .trilha__datas { margin-top: .5rem; }
.trilha--mini .trilha__datas span { font-size: .6875rem; }
.trilha--mini .trilha__datas em { display: inline; margin-left: .35rem; }
.trilha--mini .trilha__previsto {
  background-image: linear-gradient(90deg, var(--trilha-fantasma) 0 4px, transparent 4px 8px);
  background-size: 8px 100%;
}

/* ============================================================
   MOVIMENTO
   Uma sequência no load, não efeitos espalhados. A trilha desenha da
   esquerda para a direita porque é isso que a versão faz.
   ============================================================ */

@media (prefers-reduced-motion: no-preference) {
  @keyframes t-surgir {
    from { opacity: 0; transform: translateY(9px); }
    to   { opacity: 1; transform: none; }
  }
  @keyframes t-desenhar {
    from { transform: scaleX(0); }
    to   { transform: scaleX(1); }
  }
  @keyframes t-aparecer { from { opacity: 0; } to { opacity: 1; } }
  @keyframes t-halo {
    0%   { opacity: .55; transform: scale(.9); }
    70%  { opacity: 0;   transform: scale(2.6); }
    100% { opacity: 0;   transform: scale(2.6); }
  }

  /* .t-estatico é posto pelo JS a partir do segundo render. A tela é remontada
     inteira a cada filtro e a cada tecla na busca; sem essa trava o herói
     re-encenaria a entrada a cada interação. */
  .transito:not(.t-estatico) .transito__estado  { animation: t-surgir .5s .04s both cubic-bezier(.22,1,.36,1); }
  .transito:not(.t-estatico) .transito__versao  { animation: t-surgir .55s .1s both cubic-bezier(.22,1,.36,1); }
  .transito:not(.t-estatico) .transito__texto   { animation: t-surgir .55s .17s both cubic-bezier(.22,1,.36,1); }
  .transito:not(.t-estatico) .trilha            { animation: t-aparecer .4s .3s both; }
  .transito:not(.t-estatico) .leitura           { animation: t-surgir .5s .5s both cubic-bezier(.22,1,.36,1); }
  .transito:not(.t-estatico) .trilha__feito     { animation: t-desenhar .75s .34s both cubic-bezier(.22,1,.36,1); }
  .transito:not(.t-estatico) .trilha__hoje      { animation: t-aparecer .3s .95s both; }

  /* O halo continua depois que a entrada termina: ele marca "isto está
     acontecendo agora", e isso não deixa de ser verdade no segundo render. */
  .transito .trilha__hoje::after { animation: t-halo 2.8s 1.5s infinite; }
  .transito.t-estatico .trilha__hoje::after { animation-delay: 0s; }

  /* O feed nunca encena: só o herói. */
  .trilha--mini .trilha__feito,
  .trilha--mini .trilha__hoje,
  .trilha--mini .trilha__hoje::after { animation: none; }
}

/* ============================================================
   RESPONSIVO
   ============================================================ */

@media (max-width: 60rem) {
  .transito__cabeca { grid-template-columns: 1fr; gap: 1rem; }
  .transito__titulo { max-width: none; }

  /* Grade de duas colunas, sem régua vertical. */
  .leitura { display: grid; grid-template-columns: 1fr 1fr; }
  .leitura__item {
    border-right: 0; margin-right: 0; padding: .875rem 0;
    border-bottom: 1px solid var(--border-soft);
  }
  .leitura__item:nth-child(odd) { padding-right: 1rem; }
  /* A instalação é entrada, não medição, e sobraria sozinha numa linha par. */
  .leitura__item--inst { grid-column: 1 / -1; }
  .leitura__item:last-child { border-bottom: 0; padding-bottom: 0; }

  /* "HOJE" e sua data colidem com "SELF-HOSTED" antes de a trilha estreitar
     o bastante. O losango continua marcando a posição e a data de hoje já
     está no eyebrow, então some só o rótulo. */
  .trilha__rotulos span[data-anc="meio"],
  .trilha__datas span[data-anc="meio"] { display: none; }
}

@media (max-width: 40rem) {
  .transito { padding-top: 1.75rem; }
  .transito__versao .n { font-size: 3.25rem; }
  .transito__titulo { font-size: 1.1875rem; }

  .leitura dd { font-size: 1.375rem; }
  /* Precisa repetir a blindagem contra controls.css, senão a regra de fora da
     media query (0,3,1) vence esta. E a medida invisível tem de encolher
     junto, senão o controle mantém a largura do tamanho grande. */
  .instsel select:not([multiple]):not([size]) { font-size: 1.375rem; }
  .instsel__medida { font-size: 1.375rem; }
  .instsel--vazio select:not([multiple]):not([size]),
  .instsel--vazio .instsel__medida { font-size: 1rem; }
  .trilha--mini { max-width: none; }
}

/* ============================================================
   ACABAMENTO — classes que o markup emitia sem nenhuma regra.

   Uma auditoria cruzando os class="" do JS contra os seletores das
   folhas encontrou nove nomes órfãos. O sintoma visível era o atalho
   "/" da busca flutuando fora do campo e dois ícones que nunca
   desenhavam nada. Os demais eram no-ops silenciosos, mas markup que
   promete um gancho de estilo e não entrega é dívida esperando para
   confundir a próxima pessoa.

   Os ícones usam mask-image com data: URI e background-color
   currentColor — assim herdam a cor do texto e seguem o tema sem uma
   segunda cópia do SVG. A CSP permite (img-src 'self' data:).
   ============================================================ */

.icone {
  display: inline-block;
  width: 1em; height: 1em;
  background-color: currentColor;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center;  mask-position: center;
  -webkit-mask-size: contain;     mask-size: contain;
  flex: none;
}
.icone--busca {
  -webkit-mask-image: var(--ic-busca); mask-image: var(--ic-busca);
}
.icone--tema {
  -webkit-mask-image: var(--ic-tema); mask-image: var(--ic-tema);
}
:root {
  --ic-busca: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'%3E%3Ccircle cx='10.5' cy='10.5' r='6.5'/%3E%3Cpath d='M15.5 15.5 20 20'/%3E%3C/svg%3E");
  /* Círculo meio preenchido: a metáfora de contraste, que é o que o
     botão faz — alternar entre automático, claro e escuro. */
  --ic-tema: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2'%3E%3Ccircle cx='12' cy='12' r='8.5'/%3E%3Cpath d='M12 3.5a8.5 8.5 0 0 1 0 17z' fill='%23000' stroke='none'/%3E%3C/svg%3E");
}

/* O atalho "/" pertence ao campo, não ao lado dele. */
.search { display: flex; align-items: center; }
.search input.entrada { padding-right: 2rem; }
.search__atalho {
  position: absolute; right: .5rem; top: 50%;
  transform: translateY(-50%);
  font-family: var(--font-mono);
  font-size: .625rem; line-height: 1;
  color: var(--text-3);
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  padding: .1875rem .3125rem;
  pointer-events: none;
}
/* Enquanto se digita, o atalho já não serve para nada e só polui. */
.search input.entrada:not(:placeholder-shown) ~ .search__atalho,
.search input.entrada:focus ~ .search__atalho { opacity: 0; }

/* `.controle` e `.entrada` são a convenção de nome que o markup usa para
   campos; controls.css estiliza os elementos por tipo e nunca chegou a
   ancorar nesses nomes. Ficam como ganchos declarados, sem inventar
   aparência nova por cima do que já funciona. */
.controle { position: relative; display: inline-flex; align-items: center; min-width: 0; }
/* Sem `flex` aqui: app.css:@media(max-width:899px) dá `.search { flex:1 1 auto }`
   para o campo ocupar a sobra do header no celular, e como esta folha carrega
   depois com a mesma especificidade, qualquer flex declarado aqui venceria a
   media query e desfaria aquele ajuste sem deixar rastro. */
.controle--busca { min-width: 0; }
.entrada { min-width: 0; }

.btn__rotulo { display: inline-block; }
.estado-erro__corpo { display: flow-root; }

/* ---------- badges de impacto na entrada ----------
   Eram pill contornada com fundo tingido, três de uma vez logo abaixo da
   trilha — o mesmo excesso de contorno que fazia a barra de filtros virar
   sopa. Viram marcador quadrado mais rótulo em versalete mono, na mesma
   voz do eyebrow. A cor continua carregando a semântica; o que sai é a
   moldura, que não carregava nada. */
.improw { gap: .25rem 1rem; margin-top: .875rem; }
.imp {
  display: inline-flex; align-items: center; gap: .4rem;
  font-family: var(--font-mono);
  font-size: .625rem; font-weight: 600;
  letter-spacing: .13em; text-transform: uppercase;
  padding: 0; border: 0; border-radius: 0; background: none;
  line-height: 1.1;
}
.imp::before {
  content: ""; width: 6px; height: 6px; flex: none;
  background: currentColor;
}
.imp.brk, .imp.sec { color: var(--danger); background: none; }
.imp.act { color: var(--warn); background: none; }
.imp.mig { color: var(--text-3); background: none; }
/* Inline dentro de um título de ação: ali o marcador atrapalharia a leitura
   da frase, então some e sobra só a palavra colorida. */
.imp.inline { margin-right: .5rem; }
.imp.inline::before { display: none; }

/* O título da entrada virou <h2> (antes era um <a> solto, o que deixava o feed
   sem nenhum heading). O h2 é só o contêiner semântico: toda a aparência
   continua no <a>.etitle, que já existia. */
.etitle-h { margin: 0; min-width: 0; font-weight: inherit; }
