/* Virtues v2.0 — design tokens light / dark / system */
:root {
  --brand-green: #49b96e;
  --brand-yellow: #f1c84b;
  --brand-red: #e65f59;
  --brand-blue: #4b79d8;

  --radius-sm: 10px;
  --radius-md: 16px;
  --radius-lg: 24px;
  --radius-card: 14px;
  --space-section: clamp(90px, 10vw, 160px);
  --transition-fast: 160ms;
  --transition-default: 220ms;
  --font-sans: Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --container-max: 1280px;

  --color-accent: var(--brand-blue);
  --color-accent-hover: #3e68c2;
}

html,
[data-theme="dark"] {
  color-scheme: dark;
  --bg: #0b0c0e;
  --bg-secondary: #111317;
  --bg-tertiary: #17191e;
  --surface: rgba(255, 255, 255, 0.035);
  --surface-hover: rgba(255, 255, 255, 0.065);
  --surface-elevated: #17191d;
  --text-primary: #f5f6f7;
  --text-secondary: #a7abb3;
  --text-muted: #737780;
  --border: rgba(255, 255, 255, 0.09);
  --border-strong: rgba(255, 255, 255, 0.16);
  --line: rgba(255, 255, 255, 0.16);
  --line-active: rgba(255, 255, 255, 0.42);
  --header-bg: rgba(11, 12, 14, 0.78);
  --footer-bg: #08090a;
  --glow: rgba(75, 121, 216, 0.09);
  --shadow: 0 16px 50px rgba(0, 0, 0, 0.18);
  --btn-primary-bg: #f5f6f7;
  --btn-primary-fg: #141517;
  --grid-line: rgba(255, 255, 255, 0.035);
  --color-bg: var(--bg);
  --color-surface: var(--bg-secondary);
  --color-surface-elevated: var(--surface-elevated);
  --color-text: var(--text-primary);
  --color-text-muted: var(--text-secondary);
  --color-border: var(--border);
  --color-on-accent: var(--btn-primary-fg);
}

[data-theme="light"] {
  color-scheme: light;
  --bg: #f6f6f3;
  --bg-secondary: #ffffff;
  --bg-tertiary: #eeeeea;
  --surface: rgba(255, 255, 255, 0.72);
  --surface-hover: #ffffff;
  --surface-elevated: #ffffff;
  --text-primary: #141517;
  --text-secondary: #575b63;
  --text-muted: #858991;
  --border: rgba(20, 21, 23, 0.09);
  --border-strong: rgba(20, 21, 23, 0.16);
  --line: rgba(20, 21, 23, 0.16);
  --line-active: rgba(20, 21, 23, 0.4);
  --header-bg: rgba(246, 246, 243, 0.82);
  --footer-bg: #17181a;
  --glow: rgba(75, 121, 216, 0.055);
  --shadow: 0 16px 50px rgba(20, 20, 20, 0.04);
  --btn-primary-bg: #141517;
  --btn-primary-fg: #f5f6f7;
  --grid-line: rgba(20, 20, 20, 0.025);
  --color-bg: var(--bg);
  --color-surface: var(--bg-secondary);
  --color-surface-elevated: var(--surface-elevated);
  --color-text: var(--text-primary);
  --color-text-muted: var(--text-secondary);
  --color-border: var(--border);
  --color-on-accent: var(--btn-primary-fg);
}

html {
  /* scroll-behavior: smooth global foi removido -- ele quebra o refresh()
     do ScrollTrigger (GSAP), que depende de saltos de scroll instantaneos
     pra remedir a pagina; com smooth-scroll ligado, o salto vira uma
     animacao e o calculo de inicio/fim do pin do Hero fica corrompido ao
     redimensionar a janela no meio do scroll. So existiam 2 links de ancora
     no site inteiro dependendo disso (solucoes.php#id e index.php#produtos),
     que agora saltam instantaneo em vez de suave -- troca aceitavel pra
     nao quebrar a sequencia de scroll do Hero. O CTA do Hero ja tem seu
     proprio scroll suave via JS, nao depende desta regra. */
  scroll-behavior: auto;
  background: var(--bg);
  color: var(--text-primary);
  overflow-x: hidden;
  max-width: 100%;
}

body {
  overflow-x: hidden;
  max-width: 100%;
}

/* GSAP pin do Hero: o spacer herda innerWidth (inclui a barra vertical) e
   a pagina ganha ~15px de scroll horizontal. Corta na origem. */
.pin-spacer {
  max-width: 100% !important;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  .motion-safe,
  .eco-node,
  .eco-path,
  .fade-section {
    animation: none !important;
    transition: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
}

body {
  margin: 0;
  font-family: var(--font-sans);
  background: var(--bg);
  color: var(--text-primary);
  transition: background-color var(--transition-default) ease, color var(--transition-default) ease;
}

.v-container {
  width: 100%;
  max-width: var(--container-max);
  margin-inline: auto;
  padding-inline: 1rem;
}

@media (min-width: 1024px) {
  .v-container {
    padding-inline: 2rem;
  }
}

.v-section {
  padding-block: 4rem;
  border-top: 1px solid var(--color-border);
}

@media (min-width: 768px) {
  .v-section {
    padding-block: 5.5rem;
  }
}

/* Home: as seções pós-Hero não exibem a linha de delimitação superior
   (só a Home tem a classe home-page no <body> -- páginas internas seguem
   com a régua normal entre seções). A Home já abre direto na prova de
   escala sem borda; as demais seções acompanham o mesmo padrão continuo. */
body.home-page .v-section {
  border-top: none;
}

/* Home: o footer nao pode ter margem acima -- a luz do CTA final
   (cta-glow, gradiente radial ancorado no fim da secao) precisa tocar o
   footer sem gap. A margem vem do utilitario mt-8 do Tailwind no
   partial (compartilhado), entao a excecao e so nesta pagina. */
body.home-page .site-footer {
  margin-top: 0 !important;
}

.v-section-label {
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin-bottom: 0.75rem;
}

.v-section-title {
  font-size: clamp(1.75rem, 3vw, 2.5rem);
  font-weight: 700;
  line-height: 1.15;
  margin: 0 0 0.75rem;
}

.v-section-sub {
  color: var(--color-text-muted);
  font-size: 1.0625rem;
  line-height: 1.5;
  max-width: 40rem;
  margin: 0;
}

.v-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0.65rem 1.35rem;
  border-radius: 999px;
  font-size: 0.9375rem;
  font-weight: 500;
  text-decoration: none;
  transition: background var(--transition-fast) ease, border-color var(--transition-fast) ease, color var(--transition-fast) ease, box-shadow var(--transition-fast) ease;
}

.v-btn-primary {
  background: var(--btn-primary-bg);
  color: var(--btn-primary-fg);
  border: 1px solid transparent;
}

.v-btn-primary:hover {
  box-shadow: 0 0 0 1px var(--brand-blue);
}

.v-btn-secondary {
  background: transparent;
  color: var(--text-primary);
  border: 1px solid var(--border-strong);
}

.v-btn-secondary:hover {
  background: var(--surface-hover);
  border-color: var(--border-strong);
}

.v-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  padding: 1.25rem 1.35rem;
  box-shadow: var(--shadow);
  transition: border-color var(--transition-fast) ease, background var(--transition-fast) ease;
}

.v-card:hover {
  background: var(--surface-hover);
  border-color: var(--border-strong);
}

.v-card:focus-within {
  border-color: var(--brand-blue);
  outline: none;
}

/* Textura ambiente sutil de "hub central" atras da secao do ecossistema --
   mesma grade usada no palco do Hero (.hero-eco-stage::before), so que bem
   mais discreta (a secao aqui e conteudo denso, nao pode competir com a
   leitura). */
#solucoes {
  position: relative;
}

#solucoes::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image:
    linear-gradient(rgba(255, 255, 255, 0.025) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, 0.025) 1px, transparent 1px);
  background-size: 72px 72px;
  mask-image: radial-gradient(ellipse 65% 60% at 50% 40%, #000 0%, transparent 80%);
}

/* Entrada dos pillar-cards (#solucoes): card sobe, borda acende, centelha
   no titulo e plataformas revelam -- mesma linguagem do Hero/#escala.
   .is-pillar-staged e aplicado pelo JS antes do primeiro paint util. */
.pillar-card {
  position: relative;
}

#solucoes.is-pillar-staged .pillar-card:not(.is-lit) {
  opacity: 0;
  transform: translateY(16px);
}

#solucoes.is-pillar-staged .pillar-card:not(.is-lit) .plat-list,
#solucoes.is-pillar-staged .pillar-card:not(.is-lit) .pillar-extras {
  opacity: 0;
}

.pillar-edge {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 1.5px;
  background: linear-gradient(90deg, transparent 0%, var(--color-accent) 35%, var(--color-accent) 65%, transparent 100%);
  opacity: 0;
  transform: scaleX(0.28);
  transform-origin: center top;
  pointer-events: none;
}

.pillar-spark {
  position: absolute;
  left: 1.35rem;
  top: 0;
  width: 2px;
  height: 22px;
  background: linear-gradient(to bottom, transparent, var(--color-accent));
  border-radius: 999px;
  opacity: 0;
  pointer-events: none;
  transform-origin: top center;
  z-index: 1;
}

.pillar-spark::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: -2px;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--color-accent);
  box-shadow: 0 0 12px 2px var(--color-accent);
  transform: translate(-50%, 50%);
}

.pillar-card h3.is-igniting {
  text-shadow: 0 0 18px rgba(75, 121, 216, 0.7), 0 0 4px rgba(75, 121, 216, 0.5);
  transition: text-shadow 0.45s ease;
}

@media (prefers-reduced-motion: reduce) {
  .pillar-spark,
  .pillar-edge {
    display: none;
  }

  #solucoes.is-pillar-staged .pillar-card:not(.is-lit),
  #solucoes.is-pillar-staged .pillar-card:not(.is-lit) .plat-list,
  #solucoes.is-pillar-staged .pillar-card:not(.is-lit) .pillar-extras {
    opacity: 1;
    transform: none;
  }
}

/* Fade sections */
.fade-section {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 0.55s ease, transform 0.55s ease;
}

.fade-section.is-visible {
  opacity: 1;
  transform: none;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ============================================================
   SPLASH DE CARREGAMENTO -- visivel enquanto o JS ainda nao decidiu
   qual estado do Hero mostrar (sequencia animada ou fallback estatico)
   ============================================================ */
.app-splash {
  position: fixed;
  inset: 0;
  z-index: 9999;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--color-bg);
  /* "Iris": comeca cobrindo a tela toda (raio grande o suficiente pra
     cobrir qualquer diagonal de viewport) e fecha ate um ponto ao
     dispensar -- sem pulso, so o fechamento. O ponto e o centro da tela por
     padrao (--splash-ix/--splash-iy), mas o JS (ver hero-ecosystem.js,
     flyToCore) sobrescreve pra la coordenada exata do V do nucleo do
     ecossistema quando o usuario esta no topo da pagina -- a iris converge
     junto com o V voando pra la, nao pro centro. */
  clip-path: circle(150% at var(--splash-ix, 50%) var(--splash-iy, 50%));
  transition: clip-path 0.7s cubic-bezier(0.6, 0, 0.4, 1);
}

.app-splash.app-splash-done {
  clip-path: circle(0% at var(--splash-ix, 50%) var(--splash-iy, 50%));
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  .app-splash {
    transition: none;
  }
}

.app-splash-logo-wrap {
  position: relative;
  width: 72px;
  height: 72px;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Voo ate o nucleo: por padrao --fly-* fica nos valores neutros (sem
     mover/escalar nada), entao esta regra e inofensiva quando o JS nao
     define nada (dismiss padrao, fecha no centro). Quando flyToCore roda,
     ele so seta as custom properties -- a transicao abaixo cuida do
     movimento sozinha, sincronizada com o clip-path acima (mesma duracao/
     easing), entao V e iris chegam juntos no mesmo ponto. */
  transform: translate(var(--fly-x, 0px), var(--fly-y, 0px)) scale(var(--fly-scale, 1));
  transition: transform 0.7s cubic-bezier(0.6, 0, 0.4, 1);
}

@media (prefers-reduced-motion: reduce) {
  .app-splash-logo-wrap {
    transition: none;
  }
}

.app-splash-logo {
  position: relative;
  z-index: 1;
  width: 72px;
  height: 72px;
  object-fit: contain;
}

/* Particulas convergindo pro nucleo -- mesma linguagem visual do
   ecossistema no scroll-sequence (nos/pulsos), em miniatura, aqui so em
   CSS puro pra comecar a animar no primeiro paint, sem esperar o JS. */
.app-splash-particle {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 6px;
  height: 6px;
  margin: -3px;
  border-radius: 50%;
  background: var(--pcolor);
  opacity: 0;
  animation: app-splash-particle 1.8s ease-in infinite;
  animation-delay: var(--delay);
  will-change: transform, opacity;
}

@keyframes app-splash-particle {
  0% { transform: translate(var(--dx), var(--dy)) scale(1); opacity: 0; }
  12% { opacity: 1; }
  70% { opacity: 0.85; }
  100% { transform: translate(0, 0) scale(0.35); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .app-splash-particle {
    animation: none;
    opacity: 0;
  }
}

html:has(body.is-loading),
body.is-loading {
  overflow: hidden !important;
}

/* Hero ecosystem scene */
.eco-story {
  position: relative;
  isolation: isolate;
  overflow: visible;
}

.hero-eco {
  position: relative;
  z-index: 1;
}

/* Estado seguro por padrao: se o JS (GSAP/ScrollTrigger) ainda nao
   carregou/rodou -- ou nunca roda -- a secao fica curta e tudo ja
   visivel, igual ao estado final de reduced-motion. So vira pin +
   560vh + elementos escondidos quando o JS confirma que a sequencia
   de scroll vai realmente rodar e adiciona .js-eco-ready no <html>
   (ver bootstrap em js/hero-ecosystem.js). Sem isso, um usuario que
   comeca a rolar antes do script terminar de carregar via um trecho
   enorme e vazio, preso por position:sticky nativo do navegador (que
   nao depende de JS) enquanto nada anima. */
.hero-eco-track {
  position: relative;
  height: auto;
}

.hero-eco-stage {
  position: static;
  height: auto;
  display: flex;
  flex-direction: column;
  justify-content: center;
  overflow: visible;
  padding: 5.5rem 0 2.5rem;
}

@media (min-width: 768px) {
  .js-eco-ready .hero-eco-track {
    height: 720vh;
  }

  .js-eco-ready .hero-eco-stage {
    position: sticky;
    top: 0;
    height: 100svh;
    overflow: hidden;
    padding-top: 80px;
  }
}

.hero-eco-stage::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image:
    linear-gradient(var(--grid-line, rgba(255, 255, 255, 0.035)) 1px, transparent 1px),
    linear-gradient(90deg, var(--grid-line, rgba(255, 255, 255, 0.035)) 1px, transparent 1px);
  background-size: 72px 72px;
  mask-image: radial-gradient(ellipse 70% 72% at 50% 48%, #000 0%, transparent 78%);
}

.hero-eco-stage::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0.032;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='80' height='80'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/></filter><rect width='80' height='80' filter='url(%23n)' opacity='0.55'/></svg>");
}

.hero-stage-glow {
  background: radial-gradient(circle at 50% 46%, var(--glow), transparent 42%);
}

.hero-layout {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: minmax(0, 0.4fr) minmax(0, 0.6fr);
  gap: 2rem 3vw;
  align-items: center;
  width: 100%;
  max-width: 1480px;
  margin: 0 auto;
  padding-inline: 1.25rem 1.5rem;
  min-height: 0;
  flex: 1;
}

.hero-layout.is-diagram-wide {
  grid-template-columns: minmax(0, 0fr) minmax(0, 1fr);
  column-gap: 0;
  row-gap: 0;
}

.hero-layout.is-diagram-wide .hero-copy {
  visibility: hidden;
  min-width: 0;
  width: 0;
  max-width: 0;
  padding: 0;
  margin: 0;
  overflow: hidden;
  opacity: 0;
  pointer-events: none;
}

.hero-copy {
  max-width: none;
  min-width: 0;
  overflow: hidden;
  filter: none !important;
  /* Wipe de saida direita->esquerda: --wipe e a posicao (0-100%, da
     esquerda) onde a mascara passa de opaca (visivel) pra transparente
     (escondida). Comeca alem da borda direita (nada escondido) e anima
     ate alem da esquerda (tudo escondido) -- ver buildDesktopSequence()
     em hero-ecosystem.js. Faixa de 10% suaviza a borda do wipe. */
  --wipe: 110%;
  -webkit-mask-image: linear-gradient(to right, black 0%, black calc(var(--wipe) - 10%), transparent var(--wipe));
  mask-image: linear-gradient(to right, black 0%, black calc(var(--wipe) - 10%), transparent var(--wipe));
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}

.hero-lead {
  margin-top: 1.5rem;
  max-width: 22.5rem;
  color: var(--color-text-muted);
  font-size: 1.0625rem;
  line-height: 1.55;
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1.15rem 1.5rem;
  margin-top: 1.85rem;
}

.hero-copy .v-btn-primary {
  min-height: 52px;
  padding-inline: 1.7rem;
  font-size: 1rem;
  font-weight: 600;
}

.hero-link {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.2rem 0;
  color: var(--text-muted);
  font-size: 0.9375rem;
  font-weight: 500;
  text-decoration: none;
  border: 0;
  background: none;
  transition: color var(--transition-fast) ease;
}

.hero-link::after {
  content: "→";
  font-size: 0.95em;
  line-height: 1;
}

.hero-link:hover {
  color: var(--text-primary);
}

.hero-proof {
  margin: 1.45rem 0 0;
  max-width: 28rem;
  font-size: 0.6875rem;
  line-height: 1.5;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-muted);
  opacity: 0.48;
}

.hero-title {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  margin: 0;
  font-weight: 600;
  line-height: 0.96;
  letter-spacing: -0.04em;
  font-size: clamp(56px, 3.6vw, 68px);
  background-image: linear-gradient(
    100deg,
    var(--brand-green) 0%,
    var(--brand-yellow) 28%,
    var(--brand-red) 58%,
    var(--brand-blue) 86%,
    var(--brand-green) 100%
  );
  background-size: 220% 100%;
  background-position: 0% 50%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
  animation: heroTitleFlow 14s ease-in-out infinite alternate;
}

@keyframes heroTitleFlow {
  from { background-position: 0% 50%; }
  to { background-position: 100% 50%; }
}

.hero-title .hero-kicker,
.hero-title .hero-line {
  white-space: nowrap;
}

@media (prefers-reduced-motion: reduce) {
  .hero-title {
    animation: none;
    background-position: 35% 50%;
  }
}

.hero-eco-stage .eco-flow {
  position: relative;
  z-index: 1;
  width: 100%;
  min-width: 0;
  margin: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
}

.hero-eco-stage .eco-flow-svg {
  display: block;
  width: 100%;
  height: auto;
  max-height: 68svh;
  overflow: visible;
}

.hero-eco-stage .eco-node,
.hero-eco-stage .eco-path,
.hero-eco-stage .eco-intel,
.hero-eco-stage .eco-ring {
  opacity: 1;
}

/* Legendas: ficam empilhadas absolutas (ver regra base .eco-scene-caption),
   entao no fallback so a ultima (o fechamento "Uma tecnologia. Todas as
   telas.") aparece -- igual ja fazemos pra reduced-motion -- em vez de
   deixar as 6 sobrepostas e ilegiveis. */
.hero-eco-stage .eco-scene-caption {
  display: none;
}

.hero-eco-stage .eco-scene-caption[data-scene="7"] {
  display: block;
  position: relative;
  opacity: 1;
}

@media (min-width: 768px) {
  .js-eco-ready .hero-eco-stage .eco-node,
  .js-eco-ready .hero-eco-stage .eco-path,
  .js-eco-ready .hero-eco-stage .eco-intel,
  .js-eco-ready .hero-eco-stage .eco-ring {
    opacity: 0;
  }

  .js-eco-ready .hero-eco-stage .eco-scene-caption {
    display: block;
    opacity: 0;
  }

  .js-eco-ready .hero-eco-stage .eco-scene-caption[data-scene="7"] {
    position: absolute;
  }

  /* Cor por cena do texto da legenda (sem o ponto final, que agora e o
     .eco-caption-dot flutuante controlado via JS) -- ciclo pela mesma
     paleta dos nos de origem do diagrama, pra ficar consistente com o
     resto do ecossistema. So no desktop com a sequencia de verdade rodando;
     no fallback (mobile/reduced-motion) o texto fica na cor neutra de
     sempre. */
  .js-eco-ready .eco-scene-caption[data-scene="1"] .cap-text,
  .js-eco-ready .eco-scene-caption[data-scene="5"] .cap-text {
    color: #4b79d8;
  }

  .js-eco-ready .eco-scene-caption[data-scene="2"] .cap-text,
  .js-eco-ready .eco-scene-caption[data-scene="6"] .cap-text {
    color: #49b96e;
  }

  .js-eco-ready .eco-scene-caption[data-scene="3"] .cap-text,
  .js-eco-ready .eco-scene-caption[data-scene="7"] .cap-text {
    color: #f1c84b;
  }

  .js-eco-ready .eco-scene-caption[data-scene="4"] .cap-text {
    color: #e65f59;
  }

  /* O ponto flutuante assume no desktop -- esconde o fallback estatico. */
  .js-eco-ready .eco-scene-caption[data-scene="7"] .cap-text::after {
    content: none;
  }
}

.hero-eco-stage .eco-core-halo {
  opacity: 0.28;
}

@media (min-width: 768px) and (max-width: 1023px) {
  .hero-title {
    font-size: clamp(44px, 5.4vw, 58px);
  }

  .hero-layout {
    grid-template-columns: minmax(0, 0.42fr) minmax(0, 0.58fr);
    gap: 1.25rem;
  }
}

.hero-dot {
  background: none;
  color: var(--brand-blue);
  -webkit-text-fill-color: var(--brand-blue);
}

#escala {
  position: relative;
  z-index: 1;
}

.escala-section {
  padding-top: 3rem;
  border-top: none;
}

@media (min-width: 768px) {
  .escala-section {
    padding-top: 3.75rem;
  }
}

#escala .metric-value {
  position: relative;
  z-index: 5;
  font-variant-numeric: tabular-nums;
  transition: opacity 0.45s ease, transform 0.45s ease;
}

.metric-value.is-pending {
  opacity: 0.28;
}

.metric-value.is-lit {
  opacity: 1;
  transform: translateY(0);
}

#escala .metric-value.is-igniting {
  text-shadow: 0 0 22px rgba(75, 121, 216, 0.85), 0 0 6px rgba(75, 121, 216, 0.6);
  transition: text-shadow 0.5s ease;
}

/* Trilho de luz que "pousa" em cada numero antes da contagem comecar --
   continuacao visual da centelha do colapso do Hero (ver .eco-spark em
   hero-ecosystem.php). Ancorado no wrapper .escala-metric (position:relative
   via #escala .metric-value nao serve aqui, o span fica fora do <p> pra nao
   ser apagado quando countUp() reescreve o textContent do numero). */
.escala-metric {
  position: relative;
}

.escala-spark {
  position: absolute;
  left: 50%;
  bottom: 100%;
  width: 2px;
  height: 46px;
  margin-bottom: 4px;
  background: linear-gradient(to bottom, transparent, var(--color-accent));
  border-radius: 999px;
  opacity: 0;
  pointer-events: none;
  transform-origin: bottom center;
}

.escala-spark::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: -2px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--color-accent);
  box-shadow: 0 0 14px 3px var(--color-accent);
  transform: translate(-50%, 50%);
}

@media (prefers-reduced-motion: reduce) {
  .escala-spark {
    display: none;
  }
}

.eco-flow.is-receding .eco-node-in,
.eco-flow.is-receding .eco-node-out,
.eco-flow.is-receding .eco-packet {
  opacity: var(--eco-outer, 1) !important;
}

/* Saida suave das bolinhas (cena avanca/volta no Hero): a classe e
   adicionada por js/hero-ecosystem.js; o VOO continua (o tween nao e
   morto) e so a opacidade esmaece. Precisa vencer o !important do
   .is-receding acima (0,4,0 > 0,3,0) e definir a propria transition
   (a base de 0.45s do .eco-packet tambem valeria, mas explicitamos). */
.eco-packet.is-fading {
  opacity: 0 !important;
  transition: opacity 0.4s ease-out;
}

.eco-flow.is-receding .eco-packet.is-fading {
  opacity: 0 !important;
}

.eco-flow.is-receding .eco-path {
  opacity: var(--eco-paths, 1) !important;
}

.eco-flow.is-receding .eco-core {
  opacity: 1 !important;
}

.hero-reveal {
  opacity: 1;
  transform: none;
}

.eco-flow {
  position: relative;
  margin: 0;
  width: 100%;
  background: radial-gradient(circle at 47% 42%, var(--glow), transparent 40%);
  overflow: visible;
}

.eco-flow-svg {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
}

.eco-scroll-captions {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 13%;
  z-index: 2;
  height: 3.8em;
  display: flex;
  justify-content: center;
  text-align: center;
  padding-inline: 1.5rem;
  pointer-events: none;
  overflow: hidden;
}

.eco-scene-caption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  margin: 0 auto;
  max-width: 26rem;
  color: var(--text-primary);
  font-size: clamp(1.25rem, 2.6vw, 1.75rem);
  font-weight: 500;
  line-height: 1.25;
  letter-spacing: -0.01em;
  opacity: 0;
}

.eco-scene-caption[data-scene="7"] {
  max-width: none;
  white-space: nowrap;
}

/* Fallback estatico do ponto final -- cobre mobile, reduced-motion e o
   estado sem JS/ScrollTrigger, onde so a legenda 7 aparece sozinha e o
   ponto flutuante animado (.eco-caption-dot) nunca entra em cena. Sera
   escondido especificamente quando o desktop assume o ponto de verdade
   (ver regra dentro do @media(min-width:768px) mais abaixo). */
.eco-scene-caption[data-scene="7"] .cap-text::after {
  content: ".";
  color: #F5C518;
}

/* Ponto flutuante compartilhado (ver setupCaptionDot em hero-ecosystem.js):
   um unico "." que se desloca de posicao entre as legendas em vez de
   desaparecer com uma e reaparecer com a outra -- cor fixa, sempre a
   mesma, so a posicao anima (medida em tempo real via getClientRects,
   nao em pixel fixo, pra se adaptar a qualquer largura de tela/fonte). */
.eco-caption-dot {
  position: absolute;
  left: 0;
  bottom: 0;
  font-size: clamp(1.45rem, 3vw, 2rem);
  font-weight: 500;
  line-height: 1.25;
  color: #F5C518;
  opacity: 0;
  pointer-events: none;
  will-change: transform;
}

.eco-core-label {
  fill: var(--text-primary);
  font-size: 9px;
  letter-spacing: 0.22em;
  opacity: 0;
  pointer-events: none;
  paint-order: stroke fill;
  stroke: rgba(11, 11, 12, 0.85);
  stroke-width: 3px;
}

.eco-core-label-bg {
  fill: rgba(11, 11, 12, 0.72);
  stroke: rgba(255, 255, 255, 0.08);
  stroke-width: 0.75;
  pointer-events: none;
}

@media (max-width: 767px) {
  .hero-eco-track {
    height: auto;
  }

  .hero-eco-stage {
    position: static;
    height: auto;
    min-height: 0;
    overflow: visible;
    padding: 5.5rem 0 2.5rem;
  }

  .hero-layout {
    grid-template-columns: 1fr;
    gap: 2rem;
    flex: none;
  }

  .hero-copy {
    max-width: none;
    overflow: visible;
    /* Sem wipe no mobile: a mascara do desktop nao tem sentido aqui (o
       texto sai da tela rolando) e o JS tambem nao toca --wipe abaixo de
       768px. Sem mask, o texto do hero permanece intacto. */
    -webkit-mask-image: none;
    mask-image: none;
  }

  .hero-title {
    font-size: clamp(34px, 9.4vw, 44px);
  }

  .hero-title .hero-kicker,
  .hero-title .hero-line {
    white-space: normal;
  }

  .hero-eco-stage .eco-flow-svg {
    max-height: none;
  }

  .eco-scroll-captions {
    position: relative;
    bottom: auto;
    height: auto;
    margin-top: 1.25rem;
    padding-inline: 1.25rem;
  }

  .eco-scene-caption {
    position: absolute;
    height: 0;
    overflow: hidden;
    opacity: 0;
  }

  .eco-scene-caption.is-in {
    position: relative;
    height: auto;
    overflow: visible;
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .hero-eco-track {
    height: auto;
  }

  .hero-eco-stage {
    position: static;
    height: auto;
    overflow: visible;
    padding-block: 5.5rem 3rem;
  }

  .eco-scroll-captions {
    position: relative;
    bottom: auto;
    height: auto;
  }

  .eco-scene-caption {
    display: none;
  }

  .eco-scene-caption[data-scene="7"] {
    display: block;
    position: relative;
    opacity: 1;
  }
}

.eco-node,
.eco-path,
.eco-packet,
.eco-screen,
.eco-wave,
.eco-core {
  transition: opacity 0.45s ease;
}

.eco-glass {
  transition: stroke 0.4s ease, filter 0.4s ease;
}

.eco-layer-bg {
  pointer-events: none;
}

.eco-path {
  fill: none;
  opacity: 0.58;
  transition: opacity 0.45s ease, stroke 0.4s ease, stroke-width 0.4s ease, filter 0.4s ease;
}

.eco-node-in {
  opacity: 0.95;
}

.eco-device-lg {
  opacity: 0.62;
}

.eco-device-md {
  opacity: 0.42;
}

.eco-port {
  pointer-events: none;
}

.eco-label {
  fill: var(--text-secondary);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-anchor: middle;
  font-family: var(--font-sans);
  text-transform: uppercase;
}

.eco-label-end {
  text-anchor: end;
}

.eco-label-layer {
  text-anchor: middle;
  letter-spacing: 0.22em;
  fill: var(--text-muted);
  font-size: 9.5px;
}

.eco-intel-brace {
  fill: none;
  stroke: var(--line);
  stroke-width: 1;
  pointer-events: none;
}

.eco-core-disk {
  filter: drop-shadow(0 0 28px rgba(75, 121, 216, 0.22));
}

.eco-core-mark {
  pointer-events: none;
  opacity: 0;
}

.eco-screen {
  opacity: 0.05;
}

.eco-wave {
  fill: none;
}

.eco-node {
  cursor: pointer;
  outline: none;
}

.eco-flow.is-solo .eco-node:not(.is-on),
.eco-flow.is-solo .eco-intel .eco-node:not(.is-on) {
  opacity: 0.22 !important;
}

.eco-flow.is-solo .eco-path:not(.is-on) {
  opacity: 0.14 !important;
}

/* No hover (is-solo), nos/paths que ainda nao apareceram na sequencia de
   scroll (marcados .is-pending, sincronizado em hero-ecosystem.js) devem
   continuar escondidos -- sem isso, o dimming acima (opacity 0.22/0.14
   com !important) sobrescrevia o opacity:0 deles e revelava por um
   instante dispositivos que a cena ainda nao tinha mostrado. Precisa vir
   DEPOIS das regras acima: especificidade empata, quem ganha e a ordem. */
.eco-flow.is-solo .eco-node.is-pending,
.eco-flow.is-solo .eco-path.is-pending {
  opacity: 0 !important;
}

.eco-flow.is-solo .eco-packet {
  opacity: 0.16 !important;
}

.eco-flow.is-solo .eco-packet-feed {
  opacity: 1 !important;
}

.eco-flow.is-solo .eco-node-in.is-feeding {
  opacity: 1 !important;
}

.eco-flow.is-solo .eco-node.is-on,
.eco-flow.is-solo .eco-path.is-on,
.eco-flow.is-solo .eco-core {
  opacity: 1 !important;
}

.eco-node.is-on .eco-glass {
  stroke: var(--line-active);
}

.eco-node.is-on .eco-screen {
  opacity: 0.9;
}

.eco-node.is-on .eco-wave {
  opacity: 0.9;
}

.eco-path.is-on {
  opacity: 0.95;
  stroke-width: 1.6;
  filter: url(#ecoSoftGlow);
}

.eco-packet {
  filter: url(#ecoSoftGlow);
}

.eco-tooltip {
  position: absolute;
  z-index: 4;
  min-width: 148px;
  max-width: calc(100% - 16px);
  box-sizing: border-box;
  padding: 0.55rem 0.8rem 0.6rem;
  border-radius: 12px;
  border: 1px solid var(--border-strong);
  background: var(--bg-secondary);
  color: var(--text-primary);
  box-shadow: var(--shadow);
  pointer-events: none;
}

.eco-tooltip-title {
  margin: 0;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--text-primary);
}

.eco-tooltip-meta {
  margin: 0.12rem 0 0;
  font-size: 0.6875rem;
  letter-spacing: 0.04em;
  color: var(--color-text-muted);
}

.eco-tooltip-meta.is-platforms {
  margin-top: 0.5rem;
  letter-spacing: 0;
}

.eco-tip-plat-list {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.5rem 0.85rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

.eco-tooltip-meta.is-platforms[data-count="4"] .eco-tip-plat-list {
  grid-template-columns: 1fr 1fr;
}

.eco-tip-plat {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  min-width: 0;
}

.eco-tip-mark {
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  flex-shrink: 0;
}

.eco-tip-mark svg {
  width: 22px;
  height: 22px;
  color: var(--plat-color);
}

.eco-tip-plat-name {
  font-size: 0.6875rem;
  line-height: 1.15;
  color: var(--color-text-muted);
  white-space: nowrap;
}

.eco-flow.is-static .eco-node-in {
  opacity: 0.95 !important;
}

.eco-flow.is-static .eco-device-lg {
  opacity: 0.62 !important;
}

.eco-flow.is-static .eco-device-md {
  opacity: 0.4 !important;
}

.eco-flow.is-static .eco-path {
  opacity: 0.58 !important;
}

.eco-flow.is-static .eco-sat {
  opacity: 0.32 !important;
}

.eco-flow.is-static .eco-screen {
  opacity: 0.1 !important;
}

.eco-flow.is-static .eco-wave {
  opacity: 0.22 !important;
}

.eco-flow.is-static .eco-core-mark {
  opacity: 1 !important;
}

.hero-reveal.is-in {
  opacity: 1;
  transform: none;
}

@media (max-width: 767px) {
  .hero-title {
    max-width: none;
    line-height: 0.98;
  }

  .eco-hide-sm {
    display: none;
  }

  .eco-intel {
    display: none;
  }
}

/* ---- #solucoes: indice editorial + campo do ecossistema ----
   Substitui os 5 cards. A esquerda as 4 areas como indice (sem card
   nenhum); a direita todas as plataformas/produtos convivendo num campo
   so. O vinculo entre os dois lados e CSS puro (:has(), ver adiante) --
   sem JS, entao o estado parado ja e o estado completo. */
/* align-items:stretch (padrao) de proposito: o campo precisa acompanhar a
   altura do indice, senao as marcas se espremem no terco de cima e sobra
   um vazio grande embaixo. */
.eco-grid {
  display: grid;
  gap: 3rem;
}

@media (min-width: 900px) {
  .eco-grid {
    grid-template-columns: minmax(0, 0.82fr) minmax(0, 1.18fr);
    gap: 4vw;
  }
}

.eco-areas {
  display: flex;
  flex-direction: column;
  gap: 1.9rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.eco-area {
  position: relative;
  padding-left: 1rem;
  transition: opacity 0.35s ease;
}

/* Regua fina a esquerda: e o unico "enfeite" da area, e so acende na
   ativa -- substitui a borda de card que existia antes. */
.eco-area::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.25rem;
  bottom: 0.25rem;
  width: 1px;
  background: var(--color-border);
  transition: background 0.35s ease;
}

.eco-area-name {
  font-size: 1.0625rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--text-primary);
  margin: 0;
}

.eco-area-desc {
  margin: 0.4rem 0 0;
  font-size: 0.9375rem;
  line-height: 1.55;
  color: var(--color-text-muted);
}

.eco-area-link {
  display: inline-block;
  margin-top: 0.6rem;
  font-size: 0.875rem;
  color: var(--color-accent);
}

.eco-area-link:hover {
  text-decoration: underline;
}

/* Marcas por area (mobile) -- escondidas no desktop, onde o campo
   espalhado (.eco-field) e a versao real. Ver .eco-area-marks abaixo de
   900px e virtues_eco_area_marks() em platform-list.php. */
.eco-area-marks {
  display: none;
}

.eco-field {
  position: relative;
  min-height: 26rem;
  margin: 0;
  padding: 0;
  list-style: none;
  overflow: hidden;
}

.eco-mark {
  position: absolute;
  left: var(--fx);
  top: var(--fy);
  transform: translate(-50%, -50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
  width: max-content;
  max-width: 6.5rem;
  text-align: center;
  /* Repouso: marca legivel mas dessaturada -- a cor real de cada
     plataforma so entra quando a area dela esta em foco, entao a cor
     vira informacao ("isto pertence a esta area"), nao decoracao. */
  color: var(--color-text-muted);
  opacity: 0.62;
  transition: opacity 0.35s ease, color 0.35s ease, transform 0.35s ease;
}

.eco-mark-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
}

.eco-mark-icon svg {
  width: 100%;
  height: 100%;
}

/* Produtos com logo em duas versoes (LiveCore) emitem as DUAS imagens no
   HTML e o CSS escolhe qual mostrar -- sem estas regras as duas
   aparecem lado a lado (bug visto no primeiro render). Mesmo padrao ja
   usado em .plat-mark. */
.eco-mark-icon .plat-img--theme-light {
  display: none;
}

[data-theme="light"] .eco-mark-icon .plat-img--theme-dark {
  display: none;
}

[data-theme="light"] .eco-mark-icon .plat-img--theme-light {
  display: block;
}

/* O ajuste de brilho e por tema: no escuro os PNGs dessaturados somem no
   fundo e precisam clarear; no claro acontece o oposto (o logo glossy do
   APPRADIO some no branco), entao escurecem. Os SVGs em currentColor nao
   precisam disso -- ja seguem o token de cor. */
.eco-mark-icon img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  filter: grayscale(1) brightness(1.45);
  transition: filter 0.35s ease;
}

[data-theme="light"] .eco-mark-icon img {
  filter: grayscale(1) brightness(0.55) contrast(1.1);
}

.eco-mark-name {
  font-size: 0.625rem;
  letter-spacing: 0.04em;
  line-height: 1.25;
  color: var(--color-text-muted);
}

/* Marca de produto sem logo (Virtues AI / Virtues Ads): a sigla faz o
   papel do icone. */
.eco-mark .plat-fallback {
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.04em;
}

/* Estado aceso: cor real da marca, sem dessaturacao. Vale tanto pro
   hover direto na marca quanto pro vinculo com a area (abaixo). */
.eco-mark:hover,
.eco-mark:focus-within {
  opacity: 1;
  color: var(--mark-color);
  transform: translate(-50%, -50%) scale(1.06);
}

.eco-mark:hover .eco-mark-icon img,
.eco-mark:focus-within .eco-mark-icon img {
  filter: none;
}

.eco-mark:hover .eco-mark-name,
.eco-mark:focus-within .eco-mark-name {
  color: var(--text-primary);
}

/* Vinculo indice <-> campo, 100% CSS via :has(). Passar o mouse numa
   area acende as marcas dela e apaga as outras; passar numa marca acende
   a area correspondente. Navegador sem :has() simplesmente nao destaca
   nada -- nada quebra, so nao ha o realce. */
.eco-grid:has(.eco-area:hover) .eco-mark,
.eco-grid:has(.eco-mark:hover) .eco-area {
  opacity: 0.3;
}

.eco-grid:has(.eco-area[data-area="apps"]:hover) .eco-mark[data-area="apps"],
.eco-grid:has(.eco-area[data-area="streaming"]:hover) .eco-mark[data-area="streaming"],
.eco-grid:has(.eco-area[data-area="dados"]:hover) .eco-mark[data-area="dados"],
.eco-grid:has(.eco-area[data-area="ia"]:hover) .eco-mark[data-area="ia"] {
  opacity: 1;
  color: var(--mark-color);
}

.eco-grid:has(.eco-area[data-area="apps"]:hover) .eco-mark[data-area="apps"] img,
.eco-grid:has(.eco-area[data-area="streaming"]:hover) .eco-mark[data-area="streaming"] img,
.eco-grid:has(.eco-area[data-area="dados"]:hover) .eco-mark[data-area="dados"] img,
.eco-grid:has(.eco-area[data-area="ia"]:hover) .eco-mark[data-area="ia"] img {
  filter: none;
}

.eco-grid:has(.eco-mark[data-area="apps"]:hover) .eco-area[data-area="apps"],
.eco-grid:has(.eco-mark[data-area="streaming"]:hover) .eco-area[data-area="streaming"],
.eco-grid:has(.eco-mark[data-area="dados"]:hover) .eco-area[data-area="dados"],
.eco-grid:has(.eco-mark[data-area="ia"]:hover) .eco-area[data-area="ia"] {
  opacity: 1;
}

.eco-grid:has(.eco-mark[data-area="apps"]:hover) .eco-area[data-area="apps"]::before,
.eco-grid:has(.eco-mark[data-area="streaming"]:hover) .eco-area[data-area="streaming"]::before,
.eco-grid:has(.eco-mark[data-area="dados"]:hover) .eco-area[data-area="dados"]::before,
.eco-grid:has(.eco-mark[data-area="ia"]:hover) .eco-area[data-area="ia"]::before,
.eco-area:hover::before {
  background: var(--color-accent);
}

/* Ciclo ambiente (ver js/eco-field.js): uma area acende sozinha de cada
   vez, pra secao nao ficar inerte enquanto ninguem passa o mouse. Os
   estados abaixo espelham exatamente os de hover -- e quando o ponteiro
   entra na secao o JS tira .is-cycling, entao o hover assume sem nenhuma
   regra de desempate entre os dois. */
.eco-grid.is-cycling .eco-area:not(.is-active),
.eco-grid.is-cycling .eco-mark:not(.is-active) {
  opacity: 0.3;
}

.eco-grid.is-cycling .eco-mark.is-active {
  opacity: 1;
  color: var(--mark-color);
}

.eco-grid.is-cycling .eco-mark.is-active img {
  filter: none;
}

.eco-grid.is-cycling .eco-area.is-active::before {
  background: var(--color-accent);
}

/* Abaixo de 900px o campo posicionado por porcentagem ficaria apertado
   demais (14 marcas num palco estreito). Vira uma grade simples, na
   ordem do documento -- mesma informacao, sem posicionamento livre. */
@media (max-width: 899px) {
  /* O campo espalhado exige espaco livre pra funcionar como composicao;
     apertado numa coluna estreita virava so uma parede de icones solta,
     sem ligacao visual com o texto de cada area. Sai de cena no mobile;
     .eco-area-marks (dentro de cada area, abaixo) assume o lugar. */
  .eco-field {
    display: none;
  }

  /* Area vira alvo de toque: tocar acende ela e apaga as outras -- o
     mesmo efeito que o ciclo automático (ver eco-field.js) ja faz
     sozinho, so que agora sob controle do usuario. */
  .eco-area {
    cursor: pointer;
    padding: 0.15rem 0 0.15rem 1rem;
  }

  .eco-area-marks {
    display: flex;
    flex-wrap: wrap;
    gap: 0.55rem 1rem;
    margin: 0.9rem 0 0;
    padding: 0;
    list-style: none;
  }

  .eco-area-mark {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    color: var(--color-text-muted);
    transition: color 0.35s ease;
  }

  .eco-area-mark-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 1.375rem;
    height: 1.375rem;
    flex: none;
    color: inherit;
  }

  .eco-area-mark-icon svg {
    width: 100%;
    height: 100%;
  }

  .eco-area-mark-icon img {
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
    filter: grayscale(1) brightness(1.45);
    transition: filter 0.35s ease;
  }

  [data-theme="light"] .eco-area-mark-icon img {
    filter: grayscale(1) brightness(0.55) contrast(1.1);
  }

  .eco-area-mark-name {
    font-size: 0.75rem;
    letter-spacing: 0.02em;
  }

  /* Ativa (tocada ou no ciclo automatico): marca ganha a cor real da
     plataforma, igual ao estado aceso do campo espalhado no desktop. */
  .eco-area.is-active .eco-area-mark {
    color: var(--mark-color);
  }

  .eco-area.is-active .eco-area-mark-icon img {
    filter: none;
  }
}

.plat-list {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 0.55rem 0.4rem;
  margin-top: 1rem;
}

.plat-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.28rem;
  min-width: 3.9rem;
  flex: 0 0 auto;
}

.plat-mark {
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  flex-shrink: 0;
}

.plat-mark svg {
  width: 22px;
  height: 22px;
  color: var(--plat-color);
}

.plat-name {
  font-size: 0.625rem;
  line-height: 1.2;
  color: var(--color-text-muted);
  text-align: center;
  white-space: nowrap;
}

/* Cards do ecossistema (#solucoes): icones maiores e layout unificado
   (plataforma ou produto = icone em cima + label embaixo). */
.pillar-card .plat-list {
  gap: 0.75rem 0.6rem;
  margin-top: 1.15rem;
}

.pillar-card .plat-item {
  min-width: 4.85rem;
  gap: 0.4rem;
}

.pillar-card .plat-mark {
  width: 44px;
  height: 44px;
}

.pillar-card .plat-mark svg {
  width: 30px;
  height: 30px;
}

.pillar-card .plat-mark img {
  width: 34px;
  height: 34px;
  object-fit: contain;
}

.plat-mark .plat-img--theme-light {
  display: none;
}

[data-theme="light"] .plat-mark .plat-img--theme-dark {
  display: none;
}

[data-theme="light"] .plat-mark .plat-img--theme-light {
  display: block;
}

.pillar-card .plat-fallback {
  display: grid;
  place-items: center;
  width: 100%;
  height: 100%;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  color: var(--color-text-muted);
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid var(--border);
  border-radius: 10px;
}

.pillar-card .plat-name {
  font-size: 0.6875rem;
  line-height: 1.25;
  max-width: 5.75rem;
  white-space: normal;
}

[data-theme="light"] .pillar-card .plat-fallback {
  background: rgba(0, 0, 0, 0.04);
}

/* Pillar reveal */
.pillar-card .pillar-products {
  max-height: 0;
  opacity: 0;
  overflow: hidden;
  transition: max-height 0.35s ease, opacity 0.25s ease, margin 0.25s ease;
  margin-top: 0;
}

.pillar-card:hover .pillar-products,
.pillar-card:focus-within .pillar-products {
  max-height: 8rem;
  opacity: 1;
  margin-top: 0.85rem;
}

/* Platform tiles */
.platform-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.65rem;
  min-height: 104px;
  padding: 1rem 0.75rem;
  border-radius: var(--radius-card);
  border: 1px solid var(--color-border);
  background: var(--color-bg);
  text-align: center;
  transition: border-color 0.2s ease, transform 0.2s ease;
}

.platform-tile:hover {
  border-color: var(--brand-blue);
  transform: translateY(-2px);
}

.platform-tile svg {
  width: 28px;
  height: 28px;
  color: var(--color-accent);
}

.platform-tile span {
  font-size: 0.8125rem;
  color: var(--color-text-muted);
}

.metric-value {
  font-variant-numeric: tabular-nums;
}

.product-card {
  position: relative;
  overflow: hidden;
  min-height: 280px;
  display: flex;
  flex-direction: column;
  padding: 0;
}

.product-stage {
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
}

.product-support-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.75rem;
}

@media (min-width: 1024px) {
  .product-support-grid {
    grid-template-columns: repeat(6, minmax(0, 1fr));
    gap: 0.85rem;
  }

  .product-support-grid .product-card--support {
    grid-column: span 2;
  }

  .product-support-grid .product-card--support:nth-child(4),
  .product-support-grid .product-card--support:nth-child(5) {
    grid-column: span 3;
  }
}

.product-card--support {
  min-height: 0;
}

.product-card--support .product-card-visual {
  height: auto;
  aspect-ratio: 16 / 9;
  min-height: 0;
}

.product-card--support .product-card-logo {
  width: min(78%, 168px);
  height: 52px;
}

.product-card--support .product-card-img {
  max-height: 52px;
}

.product-card--support .product-card-mark {
  font-size: 1.35rem;
}

.product-card--support .product-card-body {
  padding: 0.9rem 1rem 1rem;
}

.product-support-shot {
  position: relative;
  z-index: 1;
  display: block;
  width: 100%;
  height: 100%;
}

.product-support-shot img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.product-card--support.has-shot .product-card-visual::after {
  background: linear-gradient(to top, rgba(0, 0, 0, 0.18), transparent 55%);
}

/* Entrada dos product-cards (#produtos): mesma linguagem dos pillars --
   card sobe, borda do accent do produto acende, centelha no titulo. */
#produtos.is-product-staged .product-card:not(.is-lit) {
  opacity: 0;
  transform: translateY(18px);
}

.product-edge {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 1.5px;
  background: linear-gradient(90deg, transparent 0%, var(--product-accent, var(--color-accent)) 35%, var(--product-accent, var(--color-accent)) 65%, transparent 100%);
  opacity: 0;
  transform: scaleX(0.28);
  transform-origin: center top;
  pointer-events: none;
  z-index: 2;
}

.product-spark {
  position: absolute;
  left: 1.35rem;
  top: 0;
  width: 2px;
  height: 22px;
  background: linear-gradient(to bottom, transparent, var(--product-accent, var(--color-accent)));
  border-radius: 999px;
  opacity: 0;
  pointer-events: none;
  transform-origin: top center;
  z-index: 2;
}

.product-spark::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: -2px;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--product-accent, var(--color-accent));
  box-shadow: 0 0 12px 2px var(--product-accent, var(--color-accent));
  transform: translate(-50%, 50%);
}

.product-card h3.is-igniting {
  text-shadow: 0 0 18px color-mix(in srgb, var(--product-accent, #4b79d8) 70%, transparent),
    0 0 4px color-mix(in srgb, var(--product-accent, #4b79d8) 50%, transparent);
  transition: text-shadow 0.45s ease;
}

.product-card.is-lit .product-card-visual {
  box-shadow: inset 0 -40px 60px -40px color-mix(in srgb, var(--product-accent, transparent) 18%, transparent);
}

@media (prefers-reduced-motion: reduce) {
  .product-spark,
  .product-edge {
    display: none;
  }

  #produtos.is-product-staged .product-card:not(.is-lit) {
    opacity: 1;
    transform: none;
  }
}

.product-card-visual {
  height: 132px;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  background: linear-gradient(
    145deg,
    color-mix(in srgb, var(--product-accent, var(--color-accent)) 30%, var(--color-surface)),
    color-mix(in srgb, var(--product-accent-2, var(--product-accent, var(--color-accent))) 14%, var(--color-bg))
  );
}

[data-theme="light"] .product-card-visual {
  background: linear-gradient(
    145deg,
    color-mix(in srgb, var(--product-accent, var(--color-accent)) 18%, #ffffff),
    color-mix(in srgb, var(--product-accent-2, var(--product-accent, var(--color-accent))) 10%, var(--color-surface))
  );
}

/* APPRADIO.PRO: fundo neutro (preto/branco/cinza) -- a logo e glossy e
   nao combina com tint colorido por cima. */
.product-card[data-product="appradio"] .product-card-visual {
  background: linear-gradient(
    145deg,
    color-mix(in srgb, #ffffff 11%, var(--bg-secondary)),
    color-mix(in srgb, #ffffff 5%, var(--bg))
  );
}

[data-theme="light"] .product-card[data-product="appradio"] .product-card-visual {
  background: linear-gradient(145deg, #e8e8e8, #f4f4f4);
}

.product-card[data-product="appradio"] .product-card-visual::after {
  background:
    radial-gradient(ellipse at 72% 18%, rgba(255, 255, 255, 0.1), transparent 58%),
    radial-gradient(ellipse at 18% 88%, rgba(255, 255, 255, 0.05), transparent 52%);
}

[data-theme="light"] .product-card[data-product="appradio"] .product-card-visual::after {
  background:
    radial-gradient(ellipse at 72% 18%, rgba(0, 0, 0, 0.05), transparent 58%),
    radial-gradient(ellipse at 18% 88%, rgba(0, 0, 0, 0.03), transparent 52%);
}

.product-card[data-product="appradio"].is-lit .product-card-visual {
  box-shadow: inset 0 -40px 60px -40px rgba(255, 255, 255, 0.06);
}

[data-theme="light"] .product-card[data-product="appradio"].is-lit .product-card-visual {
  box-shadow: inset 0 -40px 60px -40px rgba(0, 0, 0, 0.04);
}

.product-card-visual::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(ellipse at 72% 18%, color-mix(in srgb, var(--product-accent, #fff) 22%, transparent), transparent 58%),
    radial-gradient(ellipse at 18% 88%, color-mix(in srgb, var(--product-accent-2, var(--product-accent, #fff)) 14%, transparent), transparent 52%);
  pointer-events: none;
}

.product-card-logo {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  width: min(88%, 240px);
  height: 76px;
}

.product-card-img {
  display: block;
  max-width: 100%;
  max-height: 76px;
  width: auto;
  height: auto;
  object-fit: contain;
}

.product-card-img--theme-light {
  display: none;
}

[data-theme="light"] .product-card-img--theme-dark {
  display: none;
}

[data-theme="light"] .product-card-img--theme-light {
  display: block;
}

.product-card-mark {
  position: relative;
  z-index: 1;
  font-size: 1.75rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--product-accent);
}

.product-card-body {
  padding: 1.25rem 1.35rem 1.4rem;
  flex: 1;
  display: flex;
  flex-direction: column;
}

/* Produto em destaque ("capa de revista"): o unico card do grid com print
   real (AppRadio -- mobile, smart TV e site, ver assets/produtos/index/),
   entao ele sai do padrao uniforme e vira a peca grande no topo, provando
   "multiplataforma" com imagem de verdade em vez de logo. No mobile os 3
   prints empilham normais (sem espaco pra colagem com sobreposicao); a
   colagem em camadas (TV ao fundo, site e mobile por cima, girados) so
   entra a partir de 860px, onde o card tambem vira 2 colunas. */
.product-card--featured {
  grid-column: 1 / -1;
}

.product-featured-tag {
  display: inline-block;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--product-accent, var(--color-accent));
}

.product-featured-surfaces {
  margin: 0.85rem 0 0;
  font-size: 0.8125rem;
  color: var(--color-text-muted);
}

.product-shot {
  display: block;
  overflow: hidden;
  border-radius: 0.85rem;
  border: 1px solid var(--color-border);
  box-shadow: 0 12px 30px -14px rgba(0, 0, 0, 0.45);
  background: var(--bg-tertiary);
  transition: transform 0.35s cubic-bezier(0.22, 1, 0.36, 1), box-shadow 0.35s ease;
}

/* Passar o mouse aumenta a foto um pouco pra melhor visualizacao --
   pedido do dono do site. So em dispositivo com mouse de verdade
   (hover:hover), senao um tap no touch deixaria a foto "presa" maior
   sem hover real pra desfazer (mesmo cuidado do toque em .eco-area, ver
   eco-field.js). z-index sobe junto pra a foto que cresce nunca ficar
   por baixo de uma vizinha na colagem. */
@media (hover: hover) and (pointer: fine) {
  .product-shot:hover {
    transform: scale(1.045);
    z-index: 6;
    box-shadow: 0 22px 48px -14px rgba(0, 0, 0, 0.6);
  }
}

.product-shot img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.product-shot--tv {
  aspect-ratio: 16 / 9;
}

.product-shot--web {
  aspect-ratio: 16 / 10;
}

.product-shot--mobile {
  aspect-ratio: 9 / 18;
  max-width: 9.5rem;
  margin: 0 auto;
}

.product-card-visual--featured {
  position: relative;
  height: auto;
  padding: 1.1rem 1.1rem 0.35rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

@media (min-width: 860px) {
  .product-card--featured {
    flex-direction: row;
    align-items: stretch;
  }

  .product-card-visual--featured {
    width: 56%;
    flex: none;
    min-height: 24rem;
    padding: 0;
    display: block;
  }

  /* Cada foto vira uma caixa absoluta definida so por inset/largura/altura
     (nunca aspect-ratio aqui) -- com um retrato de verdade (a do mobile e
     bem mais alta que larga) misturar aspect-ratio com %largura fazia a
     altura calculada estourar bem além do container e o corte ficava
     errado. object-fit:cover no <img> resolve o enquadramento sozinho,
     seja qual for a caixa. */
  .product-shot {
    position: absolute;
    margin: 0;
    max-width: none;
  }

  .product-shot--tv {
    top: 6%;
    left: 4%;
    right: 30%;
    bottom: 8%;
  }

  .product-shot--web {
    top: 2%;
    right: 2%;
    width: 38%;
    height: 44%;
    transform: rotate(2.5deg);
    z-index: 2;
  }

  /* Proporcao de telefone de verdade (9:19.5, nao 24%/58% -- isso dava uma
     caixa larga/baixa que "parecia tablet", bug reportado pelo dono do
     site). Largura fixa em rem (nao %) + aspect-ratio: a altura sai
     sozinha da proporcao, sem o estouro que %largura + aspect-ratio dava
     antes (ver comentario acima sobre nao misturar os dois em %). */
  .product-shot--mobile {
    bottom: 4%;
    left: 44%;
    width: 8.5rem;
    aspect-ratio: 9 / 19.5;
    transform: rotate(-5deg);
    z-index: 3;
    box-shadow: 0 20px 40px -12px rgba(0, 0, 0, 0.55);
  }

  .product-card--featured .product-card-body {
    flex: 1;
    justify-content: center;
    padding: 2rem 2.25rem 2rem 1.5rem;
  }
}

/* Hover das fotos giradas (colagem >=860px): precisa manter a propria
   rotacao no transform do :hover (senao o scale sozinho zera o giro) --
   por isso fica num media query proprio em vez de dentro do bloco acima,
   ja combinado com hover:hover pro mesmo motivo do :hover generico la em
   cima (nao travar giro+aumento num tap sem hover de verdade). */
@media (min-width: 860px) and (hover: hover) and (pointer: fine) {
  .product-shot--web:hover {
    transform: rotate(2.5deg) scale(1.08);
  }

  .product-shot--mobile:hover {
    transform: rotate(-5deg) scale(1.08);
  }
}

/* ---- Virtues Labs v4: "laboratorio vivo" ----
   Nada de scroll-sequence/pin/scrub aqui de proposito (feedback do dono do
   site: mais uma secao presa ao scroll cansaria, o Hero ja e a experiencia
   scroll-driven forte da pagina). A secao entra no fluxo normal com um
   fade-in escalonado disparado uma unica vez por IntersectionObserver (ver
   setupLabsSequence em hero-ecosystem.js) e, depois disso, fica viva
   sozinha via microanimacoes ambientais em CSS puro (nao dependem de
   scroll nem de interacao) -- ver bloco "microanimacoes ambientais" mais
   abaixo. Linguagem deliberadamente diferente do Hero: sem radial, sem
   particulas, sem rede conectada. */
.labs-heading {
  font-size: clamp(48px, 5vw, 76px);
  line-height: 0.98;
  letter-spacing: -0.04em;
  font-weight: 700;
  margin: 0 0 0.75rem;
}

/* Estado inicial oculto SO existe quando o JS ja confirmou que vai animar
   (classe .js-labs-ready no <html>, mesmo padrao do Hero) -- sem JS, ou
   com reduced-motion (que nunca chega a marcar essa classe), a secao fica
   com a opacidade/posicao final do navegador desde o primeiro paint: a
   composicao estatica E o fallback, igual as rodadas anteriores desta
   secao. So depois disso o JS marca #labs.is-in (IntersectionObserver, uma
   vez) pra revelar de fato.
   .labs-object e #labsCore precisam de translate(-50%,-50%) PERMANENTE
   pra centralizar (posicionamento via --ox/--oy e via left:50%;top:50%) --
   por isso a entrada deles soma um deslocamento Y ao inves de substituir o
   transform inteiro (o que apagaria a centralizacao). Os demais elementos
   nao tem transform proprio, entao translateY/none simples basta. */
.js-labs-ready #labsEyebrow,
.js-labs-ready #labs-heading,
.js-labs-ready #labsSub,
.js-labs-ready #labsResearchLabel,
.js-labs-ready #labsProductLabel,
.js-labs-ready #labsStack {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.6s ease, transform 0.6s ease;
}

.js-labs-ready .labs-object {
  opacity: 0;
  transform: translate(-50%, calc(-50% + 14px));
  transition: opacity 0.6s ease, transform 0.6s ease, color 0.5s ease;
}

.js-labs-ready #labsCore {
  opacity: 0;
  transform: translate(-50%, calc(-50% + 14px));
  transition: opacity 0.6s ease, transform 0.6s ease;
}

#labs.is-in #labsEyebrow,
#labs.is-in #labs-heading,
#labs.is-in #labsSub,
#labs.is-in #labsResearchLabel,
#labs.is-in #labsProductLabel,
#labs.is-in #labsStack {
  opacity: 1;
  transform: none;
}

#labs.is-in .labs-object,
#labs.is-in #labsCore {
  opacity: 1;
  transform: translate(-50%, -50%);
}

/* "R&D Compiler": campo de experimentos com objetos visuais reais (desenho
   tecnico simples, nao icones grandes) em vez de tags de texto. Uma linha
   divisoria central (scanline) separa bruto/em-avaliacao (esquerda) de
   polido/producao (direita). Cores com funcao: amarelo = em teste, verde =
   validado, vermelho = descartado (nunca atravessa), azul =
   engenharia/producao (ja atravessou). */
.labs-lab {
  position: relative;
  height: 460px;
  /* .labs-pulse-line varre ate left:110% de proposito (o rastro precisa
     sumir alem da borda, nao parar exatamente nela) -- sem isso, a pagina
     inteira ganha uma scrollbar horizontal real em larguras onde este
     container fica largo (a partir de ~1000px), mesmo com o pulso ja
     opacity:0 nesse trecho do ciclo (bug reportado pelo dono do site).
     Contido aqui, nao no body, pra nao arriscar quebrar o pin/sticky do
     Hero em algum navegador com overflow-x:hidden global. */
  overflow: hidden;
}

.labs-connect-svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
  overflow: visible;
}

.labs-connect {
  stroke: var(--color-border);
  stroke-width: 0.3;
  fill: none;
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  transition: stroke-dashoffset 1.1s ease;
}

/* Objetos do lado esquerdo (ainda em avaliacao) tambem ganham linha ate o
   nucleo -- antes so o lado direito (ja em producao) tinha, o que fazia os
   3 objetos da esquerda parecerem soltos/sem relacao nenhuma com o resto
   da composicao (feedback do dono do site). Mesma tecnica de "desenhar"
   (stroke-dashoffset 1->0) das linhas solidas, so que na cor do proprio
   status e mais fina/fraca -- comunica "ainda ligado ao nucleo, mas nao
   consolidado" sem virar um padrao tracejado de verdade (que quebraria a
   mesma tecnica de reveal usada nas linhas de producao). */
.labs-connect--testing {
  stroke: var(--brand-yellow);
  stroke-width: 0.2;
  stroke-opacity: 0.55;
}

.labs-connect--rejected {
  stroke: var(--brand-red);
  stroke-width: 0.2;
  stroke-opacity: 0.35;
}

/* Lado direito (producao): traco solido em azul de engenharia, mais presente
   que as linhas de rascunho da esquerda -- comunica "ja em ordem" sem virar
   brilho. */
.labs-connect--prod {
  stroke: var(--brand-blue);
  stroke-width: 0.32;
  stroke-opacity: 0.65;
}

#labs.is-in .labs-connect {
  stroke-dashoffset: 0;
}

/* Ordem de revelacao: hemisferio esquerdo (rascunhos) desenha primeiro,
   hemisferio direito (producao) em seguida -- o campo "flui" da pesquisa
   pra producao na entrada. */
.labs-connect:nth-child(1) { transition-delay: 0s; }
.labs-connect:nth-child(2) { transition-delay: 0.05s; }
.labs-connect:nth-child(3) { transition-delay: 0.1s; }
.labs-connect:nth-child(4) { transition-delay: 0.15s; }
.labs-connect:nth-child(5) { transition-delay: 0.2s; }
.labs-connect:nth-child(6) { transition-delay: 0.25s; }
.labs-connect:nth-child(7) { transition-delay: 0.3s; }
.labs-connect:nth-child(8) { transition-delay: 0.55s; }
.labs-connect:nth-child(9) { transition-delay: 0.6s; }
.labs-connect:nth-child(10) { transition-delay: 0.65s; }
.labs-connect:nth-child(11) { transition-delay: 0.7s; }
.labs-connect:nth-child(12) { transition-delay: 0.75s; }

.labs-scanline {
  position: absolute;
  left: 50%;
  top: 6%;
  bottom: 6%;
  width: 1px;
  background: linear-gradient(180deg, transparent, var(--color-border) 15%, var(--color-border) 85%, transparent);
  z-index: 0;
}

/* Pulso curto atravessando a composicao a cada poucos segundos -- "o
   laboratorio respirando". Puramente decorativo, looping, independente de
   scroll/interacao; passa rapido e some, deixando um intervalo de silencio
   bem maior que o proprio pulso pra nao virar poluicao visual. */
.labs-pulse-line {
  position: absolute;
  top: 50%;
  left: -12%;
  width: 14%;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--brand-blue), transparent);
  opacity: 0;
  z-index: 0;
  animation: labs-pulse-sweep 8s ease-in-out infinite;
}

@keyframes labs-pulse-sweep {
  0% { left: -12%; opacity: 0; }
  4% { opacity: 0.6; }
  22% { opacity: 0.6; }
  30% { left: 110%; opacity: 0; }
  100% { left: 110%; opacity: 0; }
}

.labs-object {
  position: absolute;
  left: var(--ox);
  top: var(--oy);
  transform: translate(-50%, -50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  z-index: 1;
  color: var(--color-text-muted);
}

.labs-object-icon {
  width: 52px;
  height: auto;
  overflow: visible;
  transition: transform 0.3s ease;
}

.labs-object-icon--dashed {
  stroke-dasharray: 3 3;
}

.labs-object-icon,
.labs-object-icon * {
  stroke-width: 1.2;
}

.labs-object-label {
  font-size: 0.625rem;
  letter-spacing: 0.05em;
  color: var(--color-text-muted);
  white-space: nowrap;
}

/* Microdetalhe qualitativo por objeto -- nunca numero/estatistica --
   revelado so no hover, pra nao poluir a composicao em repouso. */
.labs-object-detail {
  font-size: 0.5625rem;
  letter-spacing: 0.02em;
  color: var(--color-text-muted);
  white-space: nowrap;
  max-height: 0;
  opacity: 0;
  overflow: hidden;
  transition: opacity 0.25s ease, max-height 0.25s ease;
}

.labs-object:hover .labs-object-icon,
.labs-object:focus-within .labs-object-icon {
  transform: scale(1.08);
}

.labs-object:hover,
.labs-object:focus-within {
  color: var(--text-primary);
}

.labs-object:hover .labs-object-detail,
.labs-object:focus-within .labs-object-detail {
  opacity: 1;
  max-height: 1.5rem;
}

.labs-object[data-status="testing"] {
  color: var(--brand-yellow);
}

.labs-object[data-status="validated"] {
  color: var(--brand-green);
}

.labs-object[data-status="rejected"] {
  color: var(--brand-red);
  opacity: 0.55;
}

.labs-object.is-in[data-status="rejected"] {
  opacity: 0.55;
}

.labs-object[data-status="engineering"] {
  color: var(--brand-blue);
}

.labs-lab-core {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: 2;
  text-align: center;
  animation: labs-core-breathe 4s ease-in-out infinite;
  animation-play-state: paused;
}

#labs.is-in .labs-lab-core {
  animation-play-state: running;
}

@keyframes labs-core-breathe {
  0%, 100% { transform: translate(-50%, -50%) scale(1); }
  50% { transform: translate(-50%, -50%) scale(1.02); }
}

.labs-core-word {
  font-size: clamp(2.25rem, 5.5vw, 4rem);
  font-weight: 700;
  line-height: 0.96;
  letter-spacing: -0.02em;
  color: var(--text-primary);
  margin: 0;
  white-space: nowrap;
}

.labs-lab-zone {
  position: absolute;
  bottom: -0.25rem;
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin: 0;
  z-index: 1;
}

.labs-lab-zone--left {
  left: 0;
}

.labs-lab-zone--right {
  right: 0;
}

.labs-tagline {
  margin: 2.5rem 0 0;
  text-align: center;
  font-size: 1.0625rem;
  font-weight: 500;
  letter-spacing: -0.01em;
  color: var(--text-primary);
}

/* ---- Microanimacoes ambientais (permanentes, sutis, sem scroll) ----
   So comecam a rodar depois que a secao ja revelou (.is-in), pra nao
   competir com o fade-in escalonado. Antes cada uma tinha sua propria
   duracao solta (1.1s, 1.8s, 2.4s, 6s, 8s) -- nenhuma relacao entre elas,
   entao nunca parecia um "organismo" pulsando junto, so eventos soltos
   acontecendo em momentos aleatorios (feedback do dono do site: a
   animacao parecia "fora de sintonia" com o resto). Agora todas giram em
   torno do MESMO ciclo base de 4s (BASE) -- o nucleo respira em 4s, o
   audio em BASE/2, o cursor em BASE/4, os pacotes de dados percorrem a
   linha em exatamente 1 BASE -- e o pulso que atravessa a composicao
   continua em 8s (2x BASE). Como tudo comeca junto no mesmo instante
   (#labs.is-in), os ciclos ficam sempre em fase uns com os outros. */

/* Cursor piscando no wireframe da UI. */
.labs-cursor {
  animation: labs-cursor-blink 1s steps(1, end) infinite;
  animation-play-state: paused;
}

#labs.is-in .labs-cursor {
  animation-play-state: running;
}

@keyframes labs-cursor-blink {
  0%, 49% { opacity: 1; }
  50%, 100% { opacity: 0; }
}

/* Waveform do audio engine: cada barra pulsa em altura com um delay
   proprio, criando a sensacao de sinal ativo. */
.labs-icon-audio rect {
  transform-box: fill-box;
  transform-origin: center;
  animation: labs-waveform 2s ease-in-out infinite;
  animation-play-state: paused;
}

#labs.is-in .labs-icon-audio rect {
  animation-play-state: running;
}

.labs-icon-audio rect:nth-child(1) { animation-delay: 0s; }
.labs-icon-audio rect:nth-child(2) { animation-delay: 0.15s; }
.labs-icon-audio rect:nth-child(3) { animation-delay: 0.3s; }
.labs-icon-audio rect:nth-child(4) { animation-delay: 0.45s; }
.labs-icon-audio rect:nth-child(5) { animation-delay: 0.3s; }
.labs-icon-audio rect:nth-child(6) { animation-delay: 0.15s; }
.labs-icon-audio rect:nth-child(7) { animation-delay: 0s; }

@keyframes labs-waveform {
  0%, 100% { transform: scaleY(1); }
  50% { transform: scaleY(0.55); }
}

/* Pontos do data pipeline: em vez de pulsar parados no lugar (o que nao
   lia como "pipeline" nenhum -- feedback do dono do site: "linha reta com
   bolinhas, o que isso queria dizer?"), agora percorrem a linha de
   ponta a ponta ate o "no" de destino, como pacotes de verdade fluindo,
   um atras do outro. */
.labs-data-dot {
  animation: labs-data-flow 4s linear infinite;
  animation-play-state: paused;
}

#labs.is-in .labs-data-dot {
  animation-play-state: running;
}

.labs-data-dot:nth-child(4) { animation-delay: -1.33s; }
.labs-data-dot:nth-child(5) { animation-delay: -2.67s; }

@keyframes labs-data-flow {
  0% { transform: translateX(0); opacity: 0; }
  8% { opacity: 1; }
  88% { opacity: 1; }
  96%, 100% { transform: translateX(58px); opacity: 0; }
}

@keyframes labs-data-pulse {
  0%, 100% { opacity: 0.5; transform: scale(1); }
  50% { opacity: 1; transform: scale(1.3); }
}

@media (prefers-reduced-motion: reduce) {
  .labs-pulse-line,
  .labs-lab-core,
  .labs-cursor,
  .labs-icon-audio rect,
  .labs-data-dot {
    animation: none;
  }

  /* .labs-connect comeca oculta (stroke-dashoffset:1) e so o JS desenha ao
     confirmar que vai animar (#labs.is-in) -- sob reduced-motion essa
     classe nunca chega (setupLabsSequence retorna cedo), entao sem isto as
     linhas ficariam invisiveis pra sempre. Mostra o traco ja desenhado. */
  .labs-connect {
    stroke-dashoffset: 0;
    transition: none;
  }
}

/* ---- Lado esquerdo (pesquisa): "vivo como rascunho" ----
   Icones com traco tracejado do hemisferio esquerdo ganham um percurso
   lento do tracejado (stroke-dashoffset), como se o desenho ainda estivesse
   sendo rabiscado -- diferencia do lado direito (que fica estavel, com as
   microanimacoes de producao). Ciclo em 2x BASE pra ser mais lento e
   discreto que o pulso do nucleo. */
.labs-object--left .labs-object-icon--dashed {
  stroke-dashoffset: 0;
  animation: labs-draft-sketch 8s linear infinite;
  animation-play-state: paused;
}

#labs.is-in .labs-object--left .labs-object-icon--dashed {
  animation-play-state: running;
}

@keyframes labs-draft-sketch {
  from { stroke-dashoffset: 0; }
  to { stroke-dashoffset: -24; }
}

@media (prefers-reduced-motion: reduce) {
  .labs-object--left .labs-object-icon--dashed {
    animation: none;
  }
}

@media (max-width: 900px) {
  /* Versao mobile do campo Labs: o palco absoluto do desktop (posicoes
     em --ox/--oy + SVG de conexoes) e desenhado pra respirar largo e fica
     ilegivel empilhado. Aqui o container vira uma GRADE de duas colunas --
     pesquisa a esquerda, producao a direita, scanline central continua
     marcando a divisoria -- com os MESMOS 12 objetos, tracos (tracejado
     vs. solido) e cores de status. A narrativa sobrevive: bruto a
     esquerda, ordenado a direita, e a entrada reproduz o fluxo do desktop
     (pesquisa revela primeiro, producao em seguida). Conexoes SVG e a
     linha de pulso ficam de fora: sao desenho de palco largo. */
  .labs-lab {
    height: auto;
    min-height: 0;
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: 1.25rem;
    row-gap: 0.1rem;
    align-items: center;
    padding-block: 1rem 0.25rem;
  }

  .labs-connect-svg,
  .labs-pulse-line {
    display: none;
  }

  .labs-scanline {
    display: block; /* volta: no mobile ela e a propria divisoria entre as colunas */
  }

  .labs-object {
    position: static;
    transform: none;
    display: grid;
    grid-template-columns: auto 1fr;
    grid-template-areas:
      "icon label"
      "icon detail";
    column-gap: 0.55rem;
    row-gap: 0.05rem;
    align-items: start;
    padding-block: 0.3rem;
  }

  /* Microdetalhes voltam no mobile, mas de forma ESTATICA (sempre
     visiveis) -- no touch nao existe hover, entao esconde-los significava
     que o conteudo nunca apareceria. A mini-grid acima empilha
     label+detail como bloco de texto, com o icone centrado nas duas
     linhas -- sem virar terceira coluna na horizontal (bug da versao
     anterior: flex-row empurrava o detail pro lado do label). */
  .labs-object-icon {
    grid-area: icon;
    align-self: center;
  }

  .labs-object-label {
    grid-area: label;
  }

  .labs-object--left .labs-object-detail,
  .labs-object--right .labs-object-detail {
    grid-area: detail;
    display: block;
    max-height: none;
    opacity: 1;
    overflow: visible;
    font-size: 0.5938rem;
    line-height: 1.3;
    white-space: normal;
  }

  /* Icones nas bordas externas, texto apontando pra divisoria central:
     espelha a composicao dos dois hemisferios do desktop. */
  .labs-object--left {
    grid-column: 1;
    text-align: left;
  }

  .labs-object--right {
    grid-column: 2;
    grid-template-columns: 1fr auto;
    grid-template-areas:
      "label icon"
      "detail icon";
    text-align: right;
  }

  /* Linhas explicitas: o auto-placement de grid so anda pra frente, entao
     sem isso a coluna direita comecaria ABAIXO da esquerda (apos os 7
     objetos da esquerda). Os nth-of-type contam as divs do container (a
     esquerda sao as divs 1-7, a direita 8-12). As linhas da direita
     (1,2,4,5,7) espelham as alturas do desktop (14/32/50/68/86%). */
  .labs-object--left:nth-of-type(1) { grid-row: 1; }
  .labs-object--left:nth-of-type(2) { grid-row: 2; }
  .labs-object--left:nth-of-type(3) { grid-row: 3; }
  .labs-object--left:nth-of-type(4) { grid-row: 4; }
  .labs-object--left:nth-of-type(5) { grid-row: 5; }
  .labs-object--left:nth-of-type(6) { grid-row: 6; }
  .labs-object--left:nth-of-type(7) { grid-row: 7; }
  .labs-object--right:nth-of-type(8) { grid-row: 1; }
  .labs-object--right:nth-of-type(9) { grid-row: 2; }
  .labs-object--right:nth-of-type(10) { grid-row: 4; }
  .labs-object--right:nth-of-type(11) { grid-row: 5; }
  .labs-object--right:nth-of-type(12) { grid-row: 7; }

  .labs-object-icon {
    width: 38px;
    flex: none;
  }

  .labs-object-label {
    font-size: 0.6875rem;
    white-space: normal;
    line-height: 1.25;
  }

  /* Entrada escalonada por linha (pesquisa em cascata, producao em
     seguida -- espelha o fluxo esquerda->direita do desktop). Delays por
     PROPRIEDADE: opacity/transform levam o stagger, color fica em 0s pra
     troca de cor do toggle "API ?" responder na hora. */
  #labs.is-in .labs-object--left:nth-of-type(1) { transition-delay: 0s, 0s, 0s; }
  #labs.is-in .labs-object--left:nth-of-type(2) { transition-delay: 0.05s, 0.05s, 0s; }
  #labs.is-in .labs-object--left:nth-of-type(3) { transition-delay: 0.1s, 0.1s, 0s; }
  #labs.is-in .labs-object--left:nth-of-type(4) { transition-delay: 0.15s, 0.15s, 0s; }
  #labs.is-in .labs-object--left:nth-of-type(5) { transition-delay: 0.2s, 0.2s, 0s; }
  #labs.is-in .labs-object--left:nth-of-type(6) { transition-delay: 0.25s, 0.25s, 0s; }
  #labs.is-in .labs-object--left:nth-of-type(7) { transition-delay: 0.3s, 0.3s, 0s; }
  #labs.is-in .labs-object--right:nth-of-type(8) { transition-delay: 0.48s, 0.48s, 0s; }
  #labs.is-in .labs-object--right:nth-of-type(9) { transition-delay: 0.53s, 0.53s, 0s; }
  #labs.is-in .labs-object--right:nth-of-type(10) { transition-delay: 0.58s, 0.58s, 0s; }
  #labs.is-in .labs-object--right:nth-of-type(11) { transition-delay: 0.63s, 0.63s, 0s; }
  #labs.is-in .labs-object--right:nth-of-type(12) { transition-delay: 0.68s, 0.68s, 0s; }

  .labs-lab-core {
    /* static, NAO relative -- so static neutraliza left/top:50% da regra
       base (o truque de centralizacao absoluta do desktop). Ja foi bug
       real: relative deixa left/top:50% ativos, deslocando o elemento
       longe do lugar (visto direto no screenshot: titulo sobreposto ao
       rotulo "Producao"). O ::before abaixo nao precisa de nada disso,
       um pseudo-elemento display:block funciona em fluxo normal sem
       ancestral posicionado. */
    position: static;
    transform: none;
    grid-column: 1 / -1;
    justify-self: center;
    /* Mais respiro antes do titulo (pedido do dono do site) -- o topo
       tambem reserva espaco pro conector::before abaixo. */
    padding-block: 3.5rem 1.75rem;
    /* Breathing pulse usa translate(-50%,-50%) + scale, pensado pro
       nucleo centralizado via position:absolute do desktop -- no layout
       estatico do mobile isso deslocaria o elemento. Desligado aqui de
       proposito, nao e um bug do keyframe. */
    animation: none;
  }

  /* Conector: a scanline (divisoria entre as colunas) termina no fim dos
     objetos, mas o titulo agora fica mais abaixo -- sem isso os dois
     pareceriam desconectados. Esta linha curta retoma exatamente onde a
     scanline parou e "entrega" o sinal no titulo, com o mesmo pulso azul
     percorrendo ela (delay pra sugerir continuidade, nao repeticao). */
  .labs-lab-core::before {
    content: "";
    display: block;
    width: 1px;
    height: 2.25rem;
    margin: 0 auto;
    background:
      linear-gradient(180deg, transparent, var(--brand-blue) 30%, var(--brand-blue) 55%, transparent 85%) no-repeat,
      linear-gradient(180deg, var(--color-border), transparent);
    background-size: 1px 55%, 1px 100%;
    background-position: 0 -60%, 0 0;
    /* Mesmo ciclo de 6s da scanline principal (comeca junto, no mesmo
       #labs.is-in) -- as janelas de percurso das duas sao escalonadas
       dentro desse ciclo pra este trecho retomar bem quando o trecho de
       cima termina, como um bastao passado adiante, sem precisar de
       animation-delay pra sincronizar. */
    animation: labs-core-connector-sheen-mobile 6s ease-in-out infinite;
    animation-play-state: paused;
  }

  #labs.is-in .labs-lab-core::before {
    animation-play-state: running;
  }

  #labs.is-in #labsCore {
    transform: none;
  }

  /* Estado oculto pre-reveal (.js-labs-ready) e o reveal (.is-in) usam
     translate(-50%,-50%) por causa do posicionamento absoluto do desktop;
     no fluxo estatico da grade isso deslocaria tudo -- usa deslocamento Y
     simples. Mesma especificidade dos originais, declarados DEPOIS: vencem. */
  .js-labs-ready .labs-object,
  .js-labs-ready #labsCore {
    transform: translateY(10px);
  }

  #labs.is-in .labs-object {
    transform: none;
  }

  /* O nucleo volta a RESPIRAR no mobile: o ciclo do desktop (translate
     -50%,-50% + scale) deslocaria o elemento no layout estatico da grade,
     entao aqui o ciclo e reescrito com scale puro -- mesmo ritmo de 4s,
     mesma sensacao de organismo vivo, sem o translate. */
  .labs-lab-core {
    animation: labs-core-breathe-mobile 4s ease-in-out infinite;
    animation-play-state: paused;
  }

  #labs.is-in .labs-lab-core {
    animation-play-state: running;
  }

  /* A scanline (divisoria central) ganha vida propria no mobile: brilho
     azul descendo lentamente, como o pulso que atravessa a composicao no
     desktop -- so que vertical e presa a divisoria, onde faz sentido na
     grade estreita. */
  .labs-scanline {
    width: 1px;
    background:
      linear-gradient(180deg, transparent, var(--brand-blue) 22%, var(--brand-blue) 30%, transparent 52%) no-repeat,
      linear-gradient(180deg, transparent, var(--color-border) 15%, var(--color-border) 85%, transparent);
    background-size: 1px 34%, 100% 100%;
    animation: labs-scanline-sheen-mobile 6s ease-in-out infinite;
    animation-play-state: paused;
  }

  #labs.is-in .labs-scanline {
    animation-play-state: running;
  }

  /* Rotulos dos hemisferios: cada um sob a propria coluna, na mesma linha. */
  .labs-lab-zone {
    position: static;
    display: block;
    text-align: center;
    margin-top: 0.75rem;
  }

  #labsResearchLabel {
    grid-column: 1;
  }

  #labsProductLabel {
    grid-column: 2;
  }
}

/* Ciclos do mobile (so aplicados dentro do max-width: 900px): versoes
   sem translate do breathing do nucleo e do brilho da scanline. */
@keyframes labs-core-breathe-mobile {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.02); }
}

/* Matematica do bug reportado (print do dono do site): com
   background-size 1px 34%, a posicao 140% ainda deixa ~8% do brilho
   dentro da area visivel (a formula de background-position so sai de
   vista de verdade acima de ~151%) -- por isso ele parecia "travar"
   aceso no fim da linha em vez de sumir. -60%/160% garantem folga real
   pra fora dos dois lados antes de cada pausa. */
@keyframes labs-scanline-sheen-mobile {
  0%, 8% { background-position: 0 -60%, 0 0; }
  58%, 100% { background-position: 0 160%, 0 0; }
}

/* Percurso do conector (::before do nucleo): janela de 54%-70% do MESMO
   ciclo de 6s da scanline principal -- comeca a percorrer bem quando o
   trecho de cima acaba de sumir (58%), como se o sinal fosse repassado
   adiante em vez de reiniciar do zero. Mesma folga -60%/160% pra nunca
   ficar "travado" visivel na pausa (background-size aqui e 1px 55%, a
   folga de seguranca calculada do mesmo jeito que a scanline principal). */
@keyframes labs-core-connector-sheen-mobile {
  0%, 54% { background-position: 0 -60%, 0 0; }
  70%, 100% { background-position: 0 160%, 0 0; }
}

@media (prefers-reduced-motion: reduce) {
  .labs-lab-core::before {
    animation: none;
    /* Sem o pulso, a linha de conexao fica estatica e sempre visivel --
       ainda cumpre o papel de ligar visualmente a scanline ao titulo. */
    background: linear-gradient(180deg, var(--color-border), var(--brand-blue));
  }

  .labs-lab-core,
  .labs-scanline {
    animation: none;
  }
}

@media (max-width: 400px) {
  .labs-object-icon {
    width: 32px;
  }

  .labs-object-label {
    font-size: 0.625rem;
  }
}

.bar-track {
  height: 8px;
  border-radius: 999px;
  background: var(--surface);
  overflow: hidden;
}

.bar-fill {
  height: 100%;
  border-radius: 999px;
  background: var(--brand-blue);
}

.page-hero {
  padding-block: 3.5rem 1.5rem;
}

@media (min-width: 768px) {
  .page-hero {
    padding-block: 4.5rem 2rem;
  }
}

.v-field {
  width: 100%;
  min-height: 48px;
  padding: 0.75rem 1rem;
  border-radius: 12px;
  border: 1px solid var(--color-border);
  background: var(--color-bg);
  color: var(--color-text);
  font-size: 0.9375rem;
  font-family: inherit;
}

.v-field:focus {
  outline: none;
  border-color: var(--color-accent);
}

.v-field::placeholder {
  color: var(--color-text-muted);
}

.v-label {
  display: block;
  font-size: 0.8125rem;
  color: var(--color-text-muted);
  margin-bottom: 0.4rem;
}

.field-error {
  min-height: 1.1rem;
  font-size: 0.75rem;
  color: #e05a57;
  margin-top: 0.25rem;
}

.phone-mock {
  width: 92px;
  height: 168px;
  border-radius: 16px;
  border: 2px solid var(--border-strong);
  background: var(--bg-tertiary);
  position: relative;
  box-shadow: var(--shadow);
}

.phone-mock::before {
  content: "";
  position: absolute;
  top: 8px;
  left: 50%;
  transform: translateX(-50%);
  width: 28px;
  height: 4px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.28);
}

.toast {
  opacity: 0;
  transform: translateY(-12px);
  pointer-events: none;
  transition: opacity 0.25s ease, transform 0.25s ease;
}

.toast.show {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

.faq-answer {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.3s ease;
}

.faq-answer.is-open {
  max-height: 24rem;
}

/* Header / nav / theme */
.site-header {
  position: fixed;
  top: 0;
  inset-inline: 0;
  z-index: 1000;
  background: transparent;
  border-bottom: 1px solid transparent;
  transition: background-color var(--transition-default) ease, border-color var(--transition-default) ease, backdrop-filter var(--transition-fast) ease, transform var(--transition-default) ease;
}

.site-header.is-scrolled {
  background: var(--header-bg);
  border-bottom-color: var(--border);
  backdrop-filter: blur(18px);
}

/* Header recolhido (só desktop, ver media abaixo): durante a
   sequencia de scroll do Hero a barra some INTEIRA, deslizando acima da
   viewport -- nao e um encolhimento. Fora da sequencia (antes de comecar,
   depois do fim do trilho, ou em outras paginas) a barra permanece normal
   (fixa, so com fundo quando rolada). Volta quando o mouse se aproxima do
   topo (histerese em js/theme.js pra nao piscar), quando a pagina volta ao
   topo ou quando algo dentro dela recebe foco via teclado. No mobile nao ha
   proximidade de mouse: a barra continua como sempre foi (fixa, so com
   fundo quando rolada) -- .is-compact e neutro la (ver media abaixo). */
.site-header .v-container {
  transition: padding var(--transition-default) ease;
}

.site-header.is-compact {
  transform: translateY(-102%);
}

.site-header.is-compact:focus-within {
  transform: none;
}

@media (max-width: 1023px) {
  /* Mobile/tablet: sem recolhimento -- a barra fica como sempre foi
     (fixa, so com fundo quando rolada). .is-compact e aplicado pelo JS
     mas nao causa efeito aqui: o translate volta a none e nao ha
     encolhimento nenhum de logo/nav/acoes. */
  .site-header.is-compact {
    transform: none;
  }
}

.site-logo-header {
  height: 2rem;
  width: auto;
}

@media (min-width: 768px) {
  .site-logo-header { height: 2.25rem; }
}

[data-theme="dark"] .site-logo-header {
  filter: brightness(0) invert(1);
}

[data-theme="light"] .site-logo-header {
  filter: none;
}

.nav-link {
  font-size: 15px;
  color: var(--text-secondary);
  text-decoration: none;
  transition: color var(--transition-fast) ease;
}

@media (min-width: 1024px) {
  .nav-link { font-size: 16px; }
}

.nav-link:hover,
.nav-link.is-active {
  color: var(--text-primary);
}

.nav-link.is-active {
  font-weight: 500;
  border-bottom: 1px solid var(--brand-blue);
  padding-bottom: 2px;
}

.nav-link-sm { font-size: 14px; }

.nav-toggle {
  color: var(--text-primary);
}

.mobile-menu {
  border-top: 1px solid var(--border);
  padding: 0.5rem 1rem 1rem;
}

.mobile-menu-link {
  display: block;
  padding: 0.75rem 0;
  color: var(--text-secondary);
  text-decoration: none;
}

.mobile-menu-link:hover { color: var(--text-primary); }

.mobile-menu-extra {
  padding-top: 0.75rem;
  margin-top: 0.5rem;
  border-top: 1px solid var(--border);
}

.theme-switch {
  position: relative;
}

.theme-switch-btn {
  width: 36px;
  height: 36px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--text-secondary);
  cursor: pointer;
}

.theme-switch-btn:hover,
.theme-switch-btn[aria-expanded="true"] {
  color: var(--text-primary);
  background: var(--surface);
}

.theme-icon {
  width: 18px;
  height: 18px;
}

[data-theme="dark"] .theme-icon-sun { display: none; }
[data-theme="light"] .theme-icon-moon { display: none; }

.theme-menu {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  min-width: 168px;
  padding: 0.55rem 0.4rem;
  border-radius: 12px;
  border: 1px solid var(--border);
  background: var(--bg-secondary);
  box-shadow: var(--shadow);
  z-index: 20;
  transform-origin: top right;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: translateY(-8px) scale(0.94);
  transition:
    opacity 0.22s ease,
    transform 0.26s cubic-bezier(0.22, 1, 0.36, 1),
    visibility 0s linear 0.22s;
}

.theme-menu.is-open {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: translateY(0) scale(1);
  transition:
    opacity 0.22s ease,
    transform 0.26s cubic-bezier(0.22, 1, 0.36, 1),
    visibility 0s linear 0s;
}

.theme-menu .theme-menu-label,
.theme-menu [role="option"] {
  opacity: 0;
  transform: translateY(-4px);
  transition:
    opacity 0.2s ease,
    transform 0.22s cubic-bezier(0.22, 1, 0.36, 1),
    background var(--transition-fast) ease,
    color var(--transition-fast) ease;
}

.theme-menu.is-open .theme-menu-label {
  opacity: 1;
  transform: none;
  transition-delay: 0.04s;
}

.theme-menu.is-open [role="option"]:nth-child(2) {
  opacity: 1;
  transform: none;
  transition-delay: 0.07s;
}

.theme-menu.is-open [role="option"]:nth-child(3) {
  opacity: 1;
  transform: none;
  transition-delay: 0.11s;
}

.theme-menu.is-open [role="option"]:nth-child(4) {
  opacity: 1;
  transform: none;
  transition-delay: 0.15s;
}

@media (prefers-reduced-motion: reduce) {
  .theme-menu,
  .theme-menu .theme-menu-label,
  .theme-menu [role="option"] {
    transition: none;
    transform: none;
  }

  .theme-menu:not(.is-open) {
    opacity: 0;
  }

  .theme-menu.is-open .theme-menu-label,
  .theme-menu.is-open [role="option"] {
    opacity: 1;
    transition-delay: 0s;
  }
}

.theme-menu-label {
  margin: 0 0.55rem 0.35rem;
  font-size: 0.6875rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.theme-menu [role="option"] {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  padding: 0.45rem 0.55rem;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--text-secondary);
  font-size: 0.875rem;
  text-align: left;
  cursor: pointer;
}

.theme-menu [role="option"]::before {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: 999px;
  border: 1px solid var(--border-strong);
}

.theme-menu [role="option"].is-on {
  color: var(--text-primary);
}

.theme-menu [role="option"].is-on::before {
  border-color: var(--brand-blue);
  background: var(--brand-blue);
}

.theme-menu [role="option"]:hover {
  background: var(--surface);
}

.site-footer {
  background: var(--footer-bg);
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  color: #a7abb3;
}

.site-footer h3 {
  margin: 0 0 1rem;
  font-size: 14px;
  font-weight: 500;
  color: #f5f6f7;
}

.site-footer ul { font-size: 14px; }

.site-footer a {
  color: #a7abb3;
  text-decoration: none;
  transition: color var(--transition-fast) ease;
}

.site-footer a:hover { color: #f5f6f7; }

.site-footer-bottom {
  margin-top: 2.5rem;
  padding-top: 1.5rem;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

@media (min-width: 640px) {
  .site-footer-bottom {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
  }
}

.site-footer-bottom p { font-size: 12px; margin: 0; }
.site-footer-bottom strong { color: rgba(245, 246, 247, 0.8); font-weight: 600; }

/* Copyright + seletor de tema no rodape: agrupa os dois (o par continua
   junto quando a linha quebra no mobile) e integra o .theme-switch a
   paleta fixa do footer (a base usa var(--text-secondary)/var(--surface),
   que nao combinam com o fundo escuro do rodape). */
.site-footer-meta {
  display: inline-flex;
  align-items: center;
  gap: 0.75rem;
}

.site-footer .theme-switch-btn {
  color: #a7abb3;
}

.site-footer .theme-switch-btn:hover,
.site-footer .theme-switch-btn[aria-expanded="true"] {
  color: #f5f6f7;
  background: rgba(255, 255, 255, 0.08);
}

.site-footer .theme-menu {
  background: #1d1e21;
}

.site-footer .theme-menu [role="option"]:hover {
  background: rgba(255, 255, 255, 0.08);
}

/* Footer fica no fim da pagina: o menu abre PRA CIMA, senao nasce fora
   da viewport. */
.theme-menu-up {
  top: auto;
  bottom: calc(100% + 8px);
  transform-origin: bottom right;
  transform: translateY(8px) scale(0.94);
}

.theme-menu-up.is-open {
  transform: translateY(0) scale(1);
}

@media (max-width: 639px) {
  .site-footer .theme-menu-up {
    transform-origin: bottom left;
  }
}

@media (max-width: 639px) {
  .site-footer .theme-menu {
    right: auto;
    left: 0;
  }
}

.site-logo-footer {
  height: 1.5rem;
  width: auto;
  filter: brightness(0) invert(1);
  opacity: 0.9;
}

@media (min-width: 768px) {
  .site-logo-footer { height: 1.75rem; }
}

[data-theme="dark"] .eco-paths-in,
[data-theme="dark"] .eco-paths-out {
  stroke: rgba(255, 255, 255, 0.16);
}

[data-theme="dark"] .eco-path.is-on {
  stroke: rgba(255, 255, 255, 0.5);
}

[data-theme="light"] .eco-paths-in,
[data-theme="light"] .eco-paths-out {
  stroke: rgba(20, 21, 23, 0.15);
}

[data-theme="light"] .eco-path.is-on {
  stroke: rgba(20, 21, 23, 0.48);
}

[data-theme="light"] .eco-core-halo {
  opacity: 0.2;
}

[data-theme="light"] .eco-core-disk {
  fill: #fff;
  stroke: rgba(20, 21, 23, 0.18);
  filter: drop-shadow(0 10px 24px rgba(20, 20, 20, 0.08));
}

/* .eco-core-label usa var(--text-primary) pro texto (ja se adapta ao
   tema sozinho), mas o contorno (stroke, usado pra legibilidade contra
   o fundo) e o fundo do pill (.eco-core-label-bg) eram cores fixas
   pensadas so pro tema escuro -- texto escuro sobre pill escuro no
   tema claro, quase ilegivel. */
[data-theme="light"] .eco-core-label {
  stroke: rgba(255, 255, 255, 0.85);
}

[data-theme="light"] .eco-core-label-bg {
  fill: rgba(255, 255, 255, 0.92);
  stroke: rgba(20, 21, 23, 0.14);
}

[data-theme="light"] .eco-ring {
  stroke: rgba(20, 21, 23, 0.2);
}

[data-theme="light"] .eco-glass {
  fill: #ffffff;
  stroke: rgba(20, 21, 23, 0.5);
  filter: drop-shadow(0 8px 18px rgba(20, 20, 20, 0.08));
}

[data-theme="light"] .hero-eco-stage::after {
  opacity: 0.02;
  mix-blend-mode: multiply;
}

[data-theme="light"] .eco-orbit,
[data-theme="light"] .eco-layer-bg circle {
  stroke: rgba(20, 21, 23, 0.08);
}

[data-theme="light"] main .text-white {
  color: var(--text-primary) !important;
}

[data-theme="light"] main .border-white\/10,
[data-theme="light"] main .border-white\/15 {
  border-color: var(--border) !important;
}

/* Ícones Apple (iOS / Watch / CarPlay) são brancos no dark; no light ficam pretos.
   !important vence o --plat-color inline do PHP/JS. */
[data-theme="light"] .plat-item[data-plat="ios"],
[data-theme="light"] .plat-item[data-plat="watchos"],
[data-theme="light"] .plat-item[data-plat="carplay"],
[data-theme="light"] .eco-tip-plat[data-plat="ios"] .eco-tip-mark,
[data-theme="light"] .eco-tip-plat[data-plat="watchos"] .eco-tip-mark,
[data-theme="light"] .eco-tip-plat[data-plat="carplay"] .eco-tip-mark {
  --plat-color: #1d1d1f !important;
}

/* Campo do ecossistema (.eco-mark): o hover acende color = --mark-color,
   e iOS/Watch tem --mark-color BRANCO (#F5F5F7, inline no <li>) -- no
   tema claro o "acender" ficava invisivel. Os SVGs usam fill:currentColor,
   entao basta escurecer a variavel. data-plat e emitido pelo PHP no
   <li> (partials/platform-list.php). !important vence o style inline. */
[data-theme="light"] .eco-mark[data-plat="ios"],
[data-theme="light"] .eco-mark[data-plat="watchos"] {
  --mark-color: #1d1d1f !important;
}

.metric-value {
  color: var(--text-primary);
}

.cta-glow {
  background: radial-gradient(ellipse 60% 80% at 50% 100%, var(--glow), transparent 60%);
}

/* ---- Fechamento do CTA final: colofao ----
   Ultima passada do que a pagina provou, comprimida a nomes. E o unico
   bloco puro-indice da Home (todas as outras secoes sao explicacao +
   visual), entao a densidade e o proprio contraste -- nao ha decoracao
   nenhuma aqui de proposito.
   Historico, pra nao repetir: antes esta secao foi (1) titulo
   centralizado + dois botoes num glow, o final mais generico possivel, e
   (2) copy a esquerda + nucleo grande a direita, que era layout
   disfarcado de ideia -- o V nao fazia nada pelo visitante no momento da
   decisao, era peca decorativa, e nenhum ajuste de tamanho consertava. */
.cta-recap-title {
  font-size: clamp(2rem, 3.6vw, 3.25rem);
  line-height: 1.04;
  letter-spacing: -0.03em;
  font-weight: 700;
  max-width: 30rem;
  margin: 0.75rem 0 0;
}

/* Ficha tecnica: rotulo curto a esquerda, lista corrida a direita. O
   filete entre as linhas e a unica coisa que estrutura o bloco. */
.cta-recap-list {
  margin: 3rem 0 0;
  border-top: 1px solid var(--color-border);
}

.cta-recap-row {
  display: grid;
  gap: 0.35rem;
  padding: 1.15rem 0;
  border-bottom: 1px solid var(--color-border);
}

@media (min-width: 700px) {
  .cta-recap-row {
    grid-template-columns: 9rem minmax(0, 1fr);
    gap: 2rem;
    align-items: baseline;
  }
}

.cta-recap-row dt {
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.cta-recap-row dd {
  margin: 0;
  font-size: 1rem;
  line-height: 1.65;
  color: var(--text-primary);
}

/* CTA final: botões centralizados com a assinatura (o V) no meio dos
   dois -- a marca literally entre as duas portas de entrada. Os botões
   recebem flex igual, entao ficam com a MESMA largura (a do maior) e o
   par fecha simetrico em volta do V. */
.cta-final-actions {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 1.25rem;
  margin-top: 2.5rem;
}

/* Largura igual entre os dois botoes: cada par usa o maior (o
   "especialista") como referencia -- igualado por script no index.php
   (mede o mais largo e fixa essa largura nos dois, inclusive no
   resize). De proposito NAO e flex:1: os botoes devem ficar do tamanho
   do maior, sem esticar ate as bordas do container. */

/* Assinatura: o V no tamanho de quem assina o fim de um documento, nao
   de quem ocupa metade da tela. No CTA final ela senta NO MEIO dos dois
   botões (ver .cta-final-actions); a converge dos sinais (js/
   signal-field.js) alimenta --cta-converge, entao ela acende conforme os
   sinais chegam. */
.cta-signature {
  position: relative;
  isolation: isolate;
  display: inline-grid;
  place-items: center;
  width: 6rem;
  height: 6rem;
  border-radius: 50%;
  border: 1px solid var(--color-border);
  flex: none;
  --cta-mx: 0px;
  --cta-my: 0px;
  --cta-hover: 0;
  opacity: calc(0.5 + var(--cta-converge, 0) * 0.5 + var(--cta-hover, 0) * 0.14);
  box-shadow: var(--cta-mx) var(--cta-my)
    calc(var(--cta-converge, 0) * 4rem + var(--cta-hover, 0) * 2.6rem)
    color-mix(in srgb, var(--color-accent) calc(var(--cta-converge, 0) * 30% + var(--cta-hover, 0) * 32%), transparent);
  transition: opacity 0.5s ease;
}

/* Brilho com as 4 cores da marca (as mesmas do splash/particulas, ver
   BRAND_COLORS em signal-field.js) girando devagar atras do V -- pedido
   do dono do site: um nucleo "brilhoso", nao so o halo azul unico que ja
   existia pra convergencia. Fica por baixo (z-index -1 dentro do
   isolation:isolate do pai) e desfocado, entao nunca vira um disco de
   cor solida, so um brilho ambiente. Intensifica junto com
   --cta-converge, igual ao box-shadow acima. */
.cta-signature::before {
  content: "";
  position: absolute;
  inset: -0.4rem;
  z-index: -1;
  border-radius: 50%;
  background: conic-gradient(
    from 0deg,
    var(--brand-blue),
    var(--brand-green),
    var(--brand-yellow),
    var(--brand-red),
    var(--brand-blue)
  );
  filter: blur(0.75rem);
  opacity: calc(0.3 + var(--cta-converge, 0) * 0.45 + var(--cta-hover, 0) * 0.4);
  margin-left: var(--cta-mx);
  margin-top: var(--cta-my);
  animation: cta-signature-spin 14s linear infinite;
}

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

.cta-signature img {
  width: 3rem;
  height: 3rem;
  object-fit: contain;
}

@media (prefers-reduced-motion: reduce) {
  .cta-signature {
    transition: none;
  }

  .cta-signature::before {
    animation: none;
    transition: none;
  }
}

.device-cell {
  color: var(--text-primary);
}

.field-error {
  color: var(--brand-red);
}

/* ---- Campo de sinais: fundo ambiente persistente pos-Hero ----
   Protótipo aprovado antes de entrar aqui (canvas 2D isolado, testado com
   controles ao vivo): https://claude.ai/code/artifact/7c369a7d-10f2-48da-b939-efeb4904c27f
   Ver setup completo em js/signal-field.js. Nunca cobre o Hero -- o script
   liga a opacidade so depois que #signalFieldStart (logo apos o Hero)
   cruza o topo da viewport, checado a cada frame (nao um callback de
   IntersectionObserver -- um scrollTo/link ancora rapido demais pode
   pular o cruzamento sem disparar callback nenhum, ja visto no
   prototipo). pointer-events:none pra nunca bloquear cliques no
   conteudo real.
   z-index:0 (NAO negativo): o canvas e o PRIMEIRO elemento depois do
   Hero no DOM, entao com z-index 0 ele ja fica atras de todas as secoes
   seguintes por ordem natural (elemento mais tarde no DOM vence empate
   de z-index) -- sem precisar tocar no z-index de mais nada no site. Um
   z-index negativo aqui e uma pegadinha classica: um position:fixed com
   z-index<0 pode ficar escondido atras do proprio background do body
   (confirmado: com -1 o campo inteiro sumia, invisivel mesmo com
   opacity:1 confirmado via JS). O header (.site-header) tem
   z-index:1000, entao continua por cima do campo normalmente. */
#signalField {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
  opacity: 0;
  transition: opacity 1.4s ease;
  pointer-events: none;
}

body.signal-field-active #signalField {
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  #signalField {
    display: none;
  }
}

/* 404 / 403 */
.error-status {
  width: 100%;
  min-height: calc(100vh - 72px - 200px);
  padding-block: 4.5rem 5rem;
  text-align: center;
  box-sizing: border-box;
}

.error-status-code {
  display: block;
  width: 100%;
  font-size: clamp(4.5rem, 12vw, 7.5rem);
  font-weight: 800;
  line-height: 0.9;
  letter-spacing: -0.06em;
  color: var(--color-text);
  opacity: 0.14;
  margin: 0 0 1rem;
  user-select: none;
}

.error-status .v-section-sub {
  margin-inline: auto;
}