/* ============================================================
   Habitar — Atmosferas Vivas (three.js)
   Um único motor WebGL para duas famílias de cena:

   1) CÉU  (chegada/vendas, atrás do conteúdo):
      estrelas com profundidade, cintilação e estrelas cadentes
      à noite; primeiras estrelas no entardecer; de dia, motas
      de pólen/brisa flutuando.

   2) ATMOSFERA DE AMBIENTE (por cima do conteúdo, sutil):
      cada cômodo tem sua vida — brasas que sobem, folhas que
      caem, ondas de eco, cristais, poeira dourada, vaga-lumes…
      Movimento 100% no shader (deriva + balanço + wrap), um
      draw call por cena.

   Imersão (técnicas de sites premiados): repulsão do cursor no
   shader, balanço em duas oitavas, falso DOF por profundidade,
   núcleo luminoso por preset, parallax de rolagem, câmera que
   respira sozinha e coreografia de entrada (uNasc).

   Regras: three importado dinamicamente na primeira cena;
   sem WebGL/módulos ou com prefers-reduced-motion, nada monta
   (fica o CSS de sempre). API: window.CeuVivo.montar(el) para o
   céu, window.CeuVivo.ambiente(el, preset) para atmosferas.
   ============================================================ */

const REDUZIDO = window.matchMedia("(prefers-reduced-motion: reduce)").matches;

let T = null;
let renderer = null, cena = null, camera = null;
let pontos = null, materialPts = null;
let cadente = null, proximaCadente = 6, cadentesAtivas = false;
let ceuAtivo = false;            /* cena atual é céu (deriva + cadentes) */
let canvas = null, alvo = null, conteudoAtual = null;
let ultimoPedido = null;         /* { el, tipo: "ceu" | nome-do-preset } */
let raf = 0, observadorTam = null;
let tempoTotal = 0, ultimoTique = 0;
let opacidadeAlvo = 1;
const mira = { x: 0, y: 0 };

/* interação de cursor no espaço do mundo (plano médio das partículas) */
const ponteiro = { x: 0, y: 0, forca: 0, ultimo: 0 };
let nasc = 0;          /* coreografia de entrada: 1 -> 0, partículas assentam */
let rolagem = 0;       /* parallax de rolagem suavizado */
let rolagemBase = 0;

/* intensidade do núcleo luminoso por preset (vaga-lume brilha, folha não) */
const NUCLEO = {
  brasas: 0.55, cristais: 0.5, folhas: 0, ondas: 0.2, sementes: 0.15,
  "poeira-de-sol": 0.35, poeira: 0.2, "poeira-pesada": 0.1,
  "poeira-dourada": 0.35, polen: 0.3, brisa: 0.1, vagalumes: 0.95
};

const PALETA_CEU = [0xF6E7C4, 0xEFDDB6, 0xD9C9A8, 0xE5D4AE];
const DOURADO = 0xF7C873;

/* ---------- presets de atmosfera (cores escuras o bastante p/ fundo claro) ---------- */
const ATMOS = {
  /* sala-dos-alarmes: brasas mornas subindo devagar */
  brasas:          { n: 80, cores: [0xD97B4F, 0xE8A24E, 0xC1653F], tam: [1.2, 3.0], vel: [1.5, 7],   amp: [6, 2],   freq: 0.5,  cint: [0.35, 0.5],  alfa: 0.65 },
  /* sala-dos-espelhos: cristais e poeira de luz cintilando */
  cristais:        { n: 65, cores: [0x7E93AA, 0x9DB0C4, 0xC7A75C], tam: [1.0, 2.6], vel: [2, 1.2],   amp: [8, 6],   freq: 0.4,  cint: [0.15, 0.8],  alfa: 0.6 },
  /* sala-das-perdas: folhas descendo em balanço */
  folhas:          { n: 40, cores: [0xA8825A, 0x8F7350, 0xB59A6A], tam: [2.5, 4.5], vel: [-3, -6],   amp: [14, 3],  freq: 0.7,  cint: [0.5, 0.25],  alfa: 0.7 },
  /* sala-dos-ecos: partículas pulsando em ondas que atravessam a tela */
  ondas:           { n: 75, cores: [0x8E7BA8, 0xA593BB, 0xBFA9CE], tam: [1.2, 2.6], vel: [4, 0.6],   amp: [6, 8],   freq: 0.5,  cint: [0.4, 0.4],   alfa: 0.6, onda: 1 },
  /* sala-das-escolhas: sementes levadas pelo vento */
  sementes:        { n: 50, cores: [0x6F8A6B, 0x8CA283, 0xC7B27E], tam: [1.4, 2.8], vel: [7, 1.5],   amp: [10, 7],  freq: 0.6,  cint: [0.4, 0.35],  alfa: 0.6 },
  /* sala-das-possibilidades: poeira de sol dourada */
  "poeira-de-sol": { n: 75, cores: [0xD9A441, 0xC98E3C, 0xE0B565], tam: [1.0, 2.4], vel: [1.5, 2.2], amp: [9, 7],   freq: 0.35, cint: [0.3, 0.55],  alfa: 0.6 },
  /* quarto-do-descanso: poeira lenta na luz do abajur */
  poeira:          { n: 45, cores: [0xB9986F, 0xA9895F],           tam: [0.9, 2.0], vel: [1, 0.8],   amp: [7, 5],   freq: 0.3,  cint: [0.35, 0.35], alfa: 0.5 },
  /* sala-dos-pesos: poeira densa assentando */
  "poeira-pesada": { n: 40, cores: [0x8F7B66, 0x7C6A57],           tam: [1.2, 2.6], vel: [0.8, -2.5],amp: [5, 3],   freq: 0.35, cint: [0.4, 0.3],   alfa: 0.55 },
  /* biblioteca-dos-encontros: poeira dourada de biblioteca */
  "poeira-dourada":{ n: 60, cores: [0xC79A55, 0xB78740, 0xD9B06A], tam: [1.0, 2.3], vel: [2, 1.2],   amp: [8, 6],   freq: 0.4,  cint: [0.3, 0.5],   alfa: 0.6 },
  /* fora dos cômodos, por hora do dia */
  polen:           { n: 55, cores: [0xC9A84C, 0xA9B36B, 0xD9BC6A], tam: [1.0, 2.4], vel: [3, 3.5],   amp: [10, 6],  freq: 0.5,  cint: [0.35, 0.45], alfa: 0.55 },
  brisa:           { n: 45, cores: [0xA79B84, 0xBFB39A],           tam: [0.9, 2.0], vel: [8, 1],     amp: [8, 6],   freq: 0.6,  cint: [0.35, 0.35], alfa: 0.5 },
  vagalumes:       { n: 26, cores: [0xD98E2B, 0xE8A24E, 0xC9862F], tam: [2.0, 3.4], vel: [2.5, 1.6], amp: [16, 12], freq: 0.35, cint: [0.06, 0.94], alfa: 0.95 }
};

function faixaAtual() {
  const c = document.documentElement.classList;
  if (c.contains("ceu-noite")) return "noite";
  if (c.contains("ceu-entardecer")) return "entardecer";
  if (c.contains("ceu-manha")) return "manha";
  return "tarde";
}

/* ---------- base ---------- */
function construirBase() {
  renderer = new T.WebGLRenderer({ alpha: true, antialias: false, powerPreference: "low-power" });
  renderer.setClearColor(0x000000, 0);
  renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
  canvas = renderer.domElement;
  canvas.className = "ceu-canvas";
  canvas.setAttribute("aria-hidden", "true");

  cena = new T.Scene();
  camera = new T.PerspectiveCamera(55, 1, 1, 420);

  window.addEventListener("pointermove", (e) => {
    mira.x = (e.clientX / window.innerWidth - 0.5) * 2;
    mira.y = -(e.clientY / window.innerHeight - 0.5) * 2;
    /* projeta o cursor no plano médio das partículas (z ~ -110) */
    if (canvas && canvas.isConnected) {
      const r = canvas.getBoundingClientRect();
      if (r.width > 0 && r.height > 0) {
        const nx = ((e.clientX - r.left) / r.width) * 2 - 1;
        const ny = -(((e.clientY - r.top) / r.height) * 2 - 1);
        const meiaAltura = Math.tan((camera.fov * Math.PI) / 360) * 110;
        ponteiro.x = nx * meiaAltura * camera.aspect;
        ponteiro.y = ny * meiaAltura;
        ponteiro.ultimo = performance.now();
      }
    }
  }, { passive: true });

  document.addEventListener("visibilitychange", () => {
    if (document.hidden) pausar();
    else if (canvas && canvas.isConnected) retomar();
  });
}

function limparPontos() {
  if (pontos) {
    cena.remove(pontos);
    pontos.geometry.dispose();
    materialPts.dispose();
    pontos = null;
    materialPts = null;
  }
  if (cadente) {
    cena.remove(cadente.linha);
    cadente.linha.geometry.dispose();
    cadente.linha.material.dispose();
    cadente = null;
  }
}

/* ---------- cena 1: céu estrelado ---------- */
function povoarCeu(faixa) {
  limparPontos();
  const noite = faixa === "noite";
  const n = noite ? 640 : 150;
  opacidadeAlvo = noite ? 1 : 0.55;
  cadentesAtivas = noite;
  ceuAtivo = true;

  const pos = new Float32Array(n * 3);
  const cor = new Float32Array(n * 3);
  const tam = new Float32Array(n);
  const fase = new Float32Array(n);
  const vel = new Float32Array(n);
  const corTmp = new T.Color();

  for (let i = 0; i < n; i++) {
    pos[i * 3]     = (Math.random() - 0.5) * 520;
    pos[i * 3 + 1] = noite ? -30 + Math.random() * 150 : 25 + Math.random() * 95;
    pos[i * 3 + 2] = -40 - Math.random() * 200;

    const dourada = Math.random() < 0.07;
    corTmp.setHex(dourada ? DOURADO : PALETA_CEU[(Math.random() * PALETA_CEU.length) | 0]);
    corTmp.multiplyScalar(0.82 + Math.random() * 0.18);
    cor[i * 3] = corTmp.r; cor[i * 3 + 1] = corTmp.g; cor[i * 3 + 2] = corTmp.b;

    tam[i] = (dourada ? 1.9 : 1.05) + Math.pow(Math.random(), 2) * 2.4;
    fase[i] = Math.random() * Math.PI * 2;
    vel[i] = 0.35 + Math.random() * 1.35;
  }

  const geo = new T.BufferGeometry();
  geo.setAttribute("position", new T.BufferAttribute(pos, 3));
  geo.setAttribute("aCor", new T.BufferAttribute(cor, 3));
  geo.setAttribute("aTamanho", new T.BufferAttribute(tam, 1));
  geo.setAttribute("aFase", new T.BufferAttribute(fase, 1));
  geo.setAttribute("aVel", new T.BufferAttribute(vel, 1));

  materialPts = new T.ShaderMaterial({
    transparent: true, depthWrite: false, blending: T.AdditiveBlending,
    uniforms: {
      uTempo: { value: 0 },
      uRatio: { value: Math.min(window.devicePixelRatio || 1, 2) },
      uGeral: { value: 0 },
      uPonteiro: { value: new T.Vector3(0, 0, 0) }
    },
    vertexShader: [
      "attribute vec3 aCor;",
      "attribute float aTamanho;",
      "attribute float aFase;",
      "attribute float aVel;",
      "uniform float uTempo;",
      "uniform float uRatio;",
      "uniform vec3 uPonteiro;",
      "varying vec3 vCor;",
      "varying float vBrilho;",
      "void main() {",
      "  vec3 p = position;",
      "  vec2 dif = p.xy - uPonteiro.xy;",
      "  float dist = length(dif) + 0.001;",
      "  p.xy += (dif / dist) * smoothstep(46.0, 0.0, dist) * uPonteiro.z * 7.0;",
      "  vec4 mv = modelViewMatrix * vec4(p, 1.0);",
      "  gl_Position = projectionMatrix * mv;",
      "  gl_PointSize = aTamanho * uRatio * (170.0 / -mv.z);",
      "  vBrilho = 0.62 + 0.38 * sin(uTempo * aVel + aFase);",
      "  vCor = aCor;",
      "}"
    ].join("\n"),
    fragmentShader: [
      "uniform float uGeral;",
      "varying vec3 vCor;",
      "varying float vBrilho;",
      "void main() {",
      "  float d = distance(gl_PointCoord, vec2(0.5));",
      "  float halo = smoothstep(0.5, 0.08, d);",
      "  float nucleo = smoothstep(0.16, 0.0, d);",
      "  float alfa = (halo * vBrilho + nucleo * 0.85) * uGeral;",
      "  gl_FragColor = vec4(vCor + nucleo * 0.25, alfa);",
      "}"
    ].join("\n")
  });

  pontos = new T.Points(geo, materialPts);
  cena.add(pontos);
}

/* ---------- cena 2: atmosfera de ambiente ---------- */
function povoarAtmos(nome) {
  limparPontos();
  const cfg = ATMOS[nome];
  if (!cfg) return;
  opacidadeAlvo = 1;
  cadentesAtivas = false;
  ceuAtivo = false;

  const n = cfg.n;
  const pos = new Float32Array(n * 3);
  const cor = new Float32Array(n * 3);
  const tam = new Float32Array(n);
  const fase = new Float32Array(n);
  const vel = new Float32Array(n);
  const corTmp = new T.Color();

  for (let i = 0; i < n; i++) {
    pos[i * 3]     = -110 + Math.random() * 220;
    pos[i * 3 + 1] = -70 + Math.random() * 140;
    pos[i * 3 + 2] = -70 - Math.random() * 80;

    corTmp.setHex(cfg.cores[(Math.random() * cfg.cores.length) | 0]);
    corTmp.multiplyScalar(0.88 + Math.random() * 0.12);
    cor[i * 3] = corTmp.r; cor[i * 3 + 1] = corTmp.g; cor[i * 3 + 2] = corTmp.b;

    tam[i] = cfg.tam[0] + Math.random() * (cfg.tam[1] - cfg.tam[0]);
    fase[i] = Math.random() * Math.PI * 2;
    vel[i] = 0.6 + Math.random() * 0.8;
  }

  const geo = new T.BufferGeometry();
  geo.setAttribute("position", new T.BufferAttribute(pos, 3));
  geo.setAttribute("aCor", new T.BufferAttribute(cor, 3));
  geo.setAttribute("aTamanho", new T.BufferAttribute(tam, 1));
  geo.setAttribute("aFase", new T.BufferAttribute(fase, 1));
  geo.setAttribute("aVel", new T.BufferAttribute(vel, 1));

  materialPts = new T.ShaderMaterial({
    transparent: true, depthWrite: false, blending: T.NormalBlending,
    uniforms: {
      uTempo: { value: 0 },
      uRatio: { value: Math.min(window.devicePixelRatio || 1, 2) },
      uGeral: { value: 0 },
      uAlfa:  { value: cfg.alfa },
      uVelG:  { value: new T.Vector2(cfg.vel[0], cfg.vel[1]) },
      uAmp:   { value: new T.Vector2(cfg.amp[0], cfg.amp[1]) },
      uFreq:  { value: cfg.freq },
      uCint:  { value: new T.Vector2(cfg.cint[0], cfg.cint[1]) },
      uOnda:  { value: cfg.onda ? 1 : 0 },
      uCaixa: { value: new T.Vector4(-115, 230, -75, 150) },
      uPonteiro: { value: new T.Vector3(0, 0, 0) },
      uNasc:  { value: 1 },
      uScroll: { value: 0 },
      uNucleo: { value: NUCLEO[nome] || 0 }
    },
    vertexShader: [
      "attribute vec3 aCor;",
      "attribute float aTamanho;",
      "attribute float aFase;",
      "attribute float aVel;",
      "uniform float uTempo;",
      "uniform float uRatio;",
      "uniform vec2 uVelG;",
      "uniform vec2 uAmp;",
      "uniform float uFreq;",
      "uniform vec4 uCaixa;",
      "uniform vec3 uPonteiro;",
      "uniform float uNasc;",
      "uniform float uScroll;",
      "varying vec3 vCor;",
      "varying float vFase;",
      "varying float vVel;",
      "varying float vX;",
      "varying float vProf;",
      "void main() {",
      "  vec3 p = position;",
      "  float t = uTempo;",
      "  p.x += uVelG.x * t * aVel",
      "       + sin(t * uFreq * aVel + aFase) * uAmp.x",
      "       + sin(t * uFreq * 2.7 * aVel + aFase * 3.1) * uAmp.x * 0.35;",
      "  p.y += uVelG.y * t * aVel",
      "       + cos(t * uFreq * 0.8 * aVel + aFase * 1.3) * uAmp.y",
      "       + cos(t * uFreq * 2.3 * aVel + aFase * 2.2) * uAmp.y * 0.3;",
      "  p.x = uCaixa.x + mod(p.x - uCaixa.x, uCaixa.y);",
      "  p.y = uCaixa.z + mod(p.y - uCaixa.z, uCaixa.w);",
      "  float prof = clamp((-position.z - 70.0) / 80.0, 0.0, 1.0);",
      "  p.y -= uScroll * mix(0.85, 0.3, prof);",
      "  p.xy = mix(p.xy, p.xy * 0.82, uNasc);",
      "  vec2 dif = p.xy - uPonteiro.xy;",
      "  float dist = length(dif) + 0.001;",
      "  p.xy += (dif / dist) * smoothstep(34.0, 0.0, dist) * uPonteiro.z * 13.0;",
      "  vX = p.x;",
      "  vProf = prof;",
      "  vec4 mv = modelViewMatrix * vec4(p, 1.0);",
      "  gl_Position = projectionMatrix * mv;",
      "  gl_PointSize = aTamanho * uRatio * (140.0 / -mv.z);",
      "  vCor = aCor;",
      "  vFase = aFase;",
      "  vVel = aVel;",
      "}"
    ].join("\n"),
    fragmentShader: [
      "uniform float uTempo;",
      "uniform float uGeral;",
      "uniform float uAlfa;",
      "uniform vec2 uCint;",
      "uniform float uOnda;",
      "uniform float uNucleo;",
      "varying vec3 vCor;",
      "varying float vFase;",
      "varying float vVel;",
      "varying float vX;",
      "varying float vProf;",
      "void main() {",
      "  float d = distance(gl_PointCoord, vec2(0.5));",
      "  float borda = mix(0.30, 0.12, vProf);",
      "  float disco = smoothstep(0.5, borda, d);",
      "  float nucleo = smoothstep(0.16, 0.0, d) * uNucleo;",
      "  float cint = uCint.x + uCint.y * (0.5 + 0.5 * sin(uTempo * (0.6 + vVel) + vFase));",
      "  float onda = 0.35 + 0.65 * (0.5 + 0.5 * sin(uTempo * 1.6 - vX * 0.12));",
      "  float brilho = mix(cint, onda, uOnda);",
      "  float alfaProf = mix(0.6, 1.0, vProf);",
      "  gl_FragColor = vec4(vCor + nucleo * 0.6, (disco * brilho + nucleo) * uGeral * uAlfa * alfaProf);",
      "}"
    ].join("\n")
  });

  pontos = new T.Points(geo, materialPts);
  cena.add(pontos);
  nasc = 1;
  rolagem = 0;
  rolagemBase = window.scrollY || 0;
}

/* ---------- estrela cadente (só no céu noturno) ---------- */
function lancarCadente() {
  const pos = new Float32Array(6);
  const geo = new T.BufferGeometry();
  geo.setAttribute("position", new T.BufferAttribute(pos, 3));
  const mat = new T.LineBasicMaterial({
    color: DOURADO, transparent: true, opacity: 0, blending: T.AdditiveBlending, depthWrite: false
  });
  const linha = new T.Line(geo, mat);
  const dir = new T.Vector3(Math.random() < 0.5 ? -1 : 1, -(0.35 + Math.random() * 0.3), 0).normalize();
  cadente = {
    linha, dir,
    cabeca: new T.Vector3((Math.random() - 0.5) * 140, 45 + Math.random() * 45, -90),
    comprimento: 10 + Math.random() * 8,
    velocidade: 95 + Math.random() * 45,
    vida: 0,
    duracao: 0.8 + Math.random() * 0.4
  };
  cena.add(linha);
}

function atualizarCadente(dt) {
  if (!cadente) {
    if (cadentesAtivas && tempoTotal > proximaCadente) lancarCadente();
    return;
  }
  cadente.vida += dt;
  const c = cadente;
  if (c.vida >= c.duracao) {
    cena.remove(c.linha);
    c.linha.geometry.dispose();
    c.linha.material.dispose();
    cadente = null;
    proximaCadente = tempoTotal + 14 + Math.random() * 24;
    return;
  }
  c.cabeca.addScaledVector(c.dir, c.velocidade * dt);
  const p = c.linha.geometry.attributes.position.array;
  p[0] = c.cabeca.x; p[1] = c.cabeca.y; p[2] = c.cabeca.z;
  p[3] = c.cabeca.x - c.dir.x * c.comprimento;
  p[4] = c.cabeca.y - c.dir.y * c.comprimento;
  p[5] = c.cabeca.z;
  c.linha.geometry.attributes.position.needsUpdate = true;
  c.linha.material.opacity = Math.sin(Math.PI * (c.vida / c.duracao)) * 0.85;
}

/* ---------- laço ---------- */
function quadro(agora) {
  raf = requestAnimationFrame(quadro);
  if (!canvas.isConnected || !materialPts) { pausar(); return; }

  const dt = Math.min((agora - ultimoTique) / 1000 || 0, 0.05);
  ultimoTique = agora;
  tempoTotal += dt;

  const u = materialPts.uniforms;
  u.uTempo.value = tempoTotal;
  u.uGeral.value += (opacidadeAlvo - u.uGeral.value) * Math.min(dt * 1.4, 1);

  /* cursor: a força decai suave quando o ponteiro para */
  const alvoForca = (performance.now() - ponteiro.ultimo < 1500) ? 1 : 0;
  ponteiro.forca += (alvoForca - ponteiro.forca) * 0.05;
  if (u.uPonteiro) u.uPonteiro.value.set(ponteiro.x, ponteiro.y, ponteiro.forca);

  /* atmosferas: nascimento assenta e a rolagem cria camadas de parallax */
  if (u.uNasc) {
    nasc += (0 - nasc) * Math.min(dt * 1.2, 1);
    u.uNasc.value = nasc;
    const alvoRol = Math.max(-18, Math.min(18, ((window.scrollY || 0) - rolagemBase) * 0.05));
    rolagem += (alvoRol - rolagem) * 0.06;
    u.uScroll.value = rolagem;
  }

  /* câmera respira sozinha (vida também sem mouse, ex. celular) */
  const par = ceuAtivo ? 1 : 0.45; /* parallax mais contido nas atmosferas */
  const idX = Math.sin(tempoTotal * 0.11) * (ceuAtivo ? 1.4 : 0.8);
  const idY = Math.cos(tempoTotal * 0.09) * (ceuAtivo ? 0.8 : 0.5);
  camera.position.x += ((mira.x * 2.4 * par + idX) - camera.position.x) * 0.03;
  camera.position.y += ((mira.y * 1.3 * par + idY) - camera.position.y) * 0.03;

  if (ceuAtivo) {
    pontos.rotation.z = tempoTotal * 0.004;
    atualizarCadente(dt);
  }
  renderer.render(cena, camera);
}

function pausar() {
  if (raf) { cancelAnimationFrame(raf); raf = 0; }
}
function retomar() {
  if (!raf && renderer && pontos) {
    ultimoTique = performance.now();
    raf = requestAnimationFrame(quadro);
  }
}

function dimensionar() {
  if (!alvo || !renderer) return;
  const w = alvo.clientWidth || 1, h = alvo.clientHeight || 1;
  renderer.setSize(w, h, false);
  camera.aspect = w / h;
  camera.updateProjectionMatrix();
}

function vigiarTamanho() {
  if (observadorTam) observadorTam.disconnect();
  observadorTam = new ResizeObserver(dimensionar);
  observadorTam.observe(alvo);
  dimensionar();
}

function desligar() {
  pausar();
  if (canvas) canvas.remove();
  conteudoAtual = null;
  document.documentElement.classList.remove("ceu-3d");
}

/* núcleo compartilhado das duas cenas */
async function apresentar(el, conteudo, sobre) {
  if (!el || REDUZIDO) return;
  try {
    if (!T) T = await import("./vendor/three.module.min.js");
    if (!renderer) construirBase();
    if (conteudoAtual !== conteudo) {
      if (conteudo.indexOf("ceu-") === 0) povoarCeu(conteudo.slice(4));
      else povoarAtmos(conteudo);
      conteudoAtual = conteudo;
    }
  } catch (e) { return; }
  alvo = el;
  canvas.classList.toggle("ceu-canvas--sobre", !!sobre);
  if (canvas.parentElement !== alvo) alvo.prepend(canvas);
  /* guarda anti-reentrada: classList.add re-serializa o atributo e
     acordaria o MutationObserver de novo — só mexe se precisa */
  if (!document.documentElement.classList.contains("ceu-3d")) {
    document.documentElement.classList.add("ceu-3d");
  }
  vigiarTamanho();
  retomar();
}

function conteudoDoCeu() {
  const faixa = faixaAtual();
  return (faixa === "noite" || faixa === "entardecer")
    ? "ceu-" + faixa
    : (faixa === "manha" ? "polen" : "brisa");
}

/* céu da chegada: estrelas à noite/entardecer, motas de dia */
function montar(el) {
  if (el) ultimoPedido = { el: el, tipo: "ceu" };
  const pedido = ultimoPedido;
  if (!pedido || pedido.tipo !== "ceu") return;
  apresentar(pedido.el, conteudoDoCeu(), false);
}

/* atmosfera de um ambiente (por cima do conteúdo, bem sutil) */
function ambiente(el, preset) {
  if (!el || !ATMOS[preset]) return;
  ultimoPedido = { el: el, tipo: preset };
  apresentar(el, preset, true);
}

/* o céu da chegada reage sozinho quando a faixa do dia muda —
   só remonta quando o conteúdo desejado realmente mudou (anti-loop) */
new MutationObserver(() => {
  if (!ultimoPedido || ultimoPedido.tipo !== "ceu" || !ultimoPedido.el.isConnected) return;
  if (conteudoDoCeu() !== conteudoAtual) montar();
}).observe(document.documentElement, { attributes: true, attributeFilter: ["class"] });

window.CeuVivo = { montar, ambiente };

/* página de vendas monta sozinha; o app entrega alvos via handshake */
const automatico = document.querySelector("[data-ceu]");
if (automatico) montar(automatico);
if (window.__ceuAlvo) montar(window.__ceuAlvo);
if (window.__ceuAmb) ambiente(window.__ceuAmb.el, window.__ceuAmb.preset);
