/* * Intro pós-login sazonal "Aniversário" (56 anos da De Paula Contadores) — * variante de pidPlayLoginIntro (login-intro.js), portada da composição * autorada em "P.I.D. Logo Design Aniversário/Logo intro animation/export-56/ * pid-56-anos.html" (pasta de logos, fora do repositório — um componente * React/JSX, `pid-56.jsx`, extraído do bundle "Design Canvas" com o mesmo * processo ad-hoc de gzip+base64 por uuid já usado pra extrair a composição * do Halloween). Cenas originais Chegada/Confete/Texto/Serpentina * (1.1s/1.4s/1.6s/1.8s, total 5.9s): o personagem cai com os balões e * quica ao pousar, dá um pulo e joga confete pro alto, desliza pra esquerda * enquanto "Parabéns" + a logo da De Paula + "56 Anos" entram, e por fim * assopra uma língua-de-sogra que desenrola e recolhe enquanto o slogan * pousa. Só é chamada quando `pidIsAniversarioActive()` (seasonal-theme.js, * só 15/10); fora desse dia, `pidPlayLoginIntro()` (ou a variante Halloween) * continua sendo a usada. Reaproveita PID_INTRO_EASING/pidIntroAnimate/ * pidIntroClamp/pidIntroCornerTarget já globais de login-intro.js, mesma * página — só a coreografia é própria daqui. * * Diferenças em relação à composição original: * - A composição original foi autorada num canvas de vídeo 1920×1080 com o * ícone e o texto posicionados por coordenadas absolutas; aqui, seguindo o * mesmo padrão de `.login-intro__stage`/`.login-intro-h__stage`, os dois * viram uma única linha flexível (ícone + mensagem lado a lado) — o * "deslizar pra esquerda" do ícone (cena Texto) é reproduzido pela mesma * técnica de SHIFT_X já usada nas outras duas intros (o ícone se desloca * por `transform`, a mensagem só aparece no lugar fixo ao lado). * - O "zoom" de fundo do canvas original (respiro sutil de 1.05→1.0→1.02) e * os dois gradientes de vinheta foram descartados — floreios de canvas de * vídeo sem efeito perceptível numa janela de overlay pequena. * - A composição original termina com tudo esmaecendo junto (pensada como * vídeo solto); foi acrescentada uma cena final "Fly" (mesma ideia/ * matemática do `pidIntroCornerTarget`, já usada pelas outras duas intros) * pra o ícone voar até o canto real da sidebar antes de tudo desaparecer. * - "56 ANOS" é específico deste ano (56º aniversário) — ano que vem, * atualizar o número aqui e em `seasonal-theme.js`/`index.html` a partir * de uma composição nova (mesmo espírito de "marca temporária" do * `leiame.md` original). */ const PID_INTRO_A_CUES = { Chegada: 0, Confete: 1.1, Texto: 2.5, Serpentina: 4.1, Fly: 5.9 }; const PID_INTRO_A_TOTAL = 6.8; const PID_INTRO_A_ICON_SIZE = 260; // maior que os 200px das outras 2 intros — pedido explícito do usuário, "mais fácil sua visualização" const PID_INTRO_A_ICON_K = PID_INTRO_A_ICON_SIZE / 420; // coreografia original autorada pra um ícone de 420px const PID_INTRO_A_CONFETTI_COLORS = ["#C6A24A", "#2E86DE", "#F2A93B", "#E8446E", "#F2EDE3", "#7B5BA8"]; // mesmo gerador pseudo-aleatório determinístico do original (pid-56.jsx), // pra toda visita produzir a mesma explosão de confete. function pidIntroARnd(i, salt) { const x = Math.sin(i * 127.1 + salt * 311.7) * 43758.5453; return x - Math.floor(x); } function pidIntroABuildConfettiPieces() { return Array.from({ length: 58 }, (_, i) => { const a = (pidIntroARnd(i, 1) - 0.5) * 2.5 - Math.PI / 2; const speed = 300 + pidIntroARnd(i, 2) * 620; return { i, vx: Math.cos(a) * speed * (0.75 + pidIntroARnd(i, 7) * 0.6), vy: Math.sin(a) * speed, delay: pidIntroARnd(i, 3) * 0.24, size: 9 + pidIntroARnd(i, 4) * 15, long: pidIntroARnd(i, 8) > 0.5, spin: (pidIntroARnd(i, 5) - 0.5) * 1500, color: PID_INTRO_A_CONFETTI_COLORS[i % PID_INTRO_A_CONFETTI_COLORS.length], sway: (pidIntroARnd(i, 6) - 0.5) * 90, }; }); } function pidPlayLoginIntroAniversario(onDone) { const overlay = document.getElementById("login-intro-aniversario"); const icon = document.getElementById("login-intro-a-icon"); const iconWrap = document.getElementById("login-intro-a-iconwrap"); const stage = document.getElementById("login-intro-a-stage"); const message = document.getElementById("login-intro-a-message"); const flash = document.getElementById("login-intro-a-flash"); const confettiHost = document.getElementById("login-intro-a-confetti"); if (!overlay || !icon || !iconWrap || !stage || !message || !flash || !confettiHost) { onDone(); return; } const balloons = icon.querySelector(".login-intro-a__balloons"); const arm = icon.querySelector(".login-intro-a__arm"); const smile = document.getElementById("login-intro-a-smile"); const puckerOuter = document.getElementById("login-intro-a-pucker-outer"); const puckerInner = document.getElementById("login-intro-a-pucker-inner"); const hornGroup = document.getElementById("login-intro-a-horn"); const hornTube = document.getElementById("login-intro-a-horn-tube"); const hornDash = document.getElementById("login-intro-a-horn-dash"); const puff1 = document.getElementById("login-intro-a-puff1"); const puff2 = document.getElementById("login-intro-a-puff2"); const parabens = document.getElementById("login-intro-a-parabens"); const wordmarkImg = document.getElementById("login-intro-a-wordmark-img"); const rule = document.getElementById("login-intro-a-rule"); const num = document.getElementById("login-intro-a-num"); const numLabel = document.getElementById("login-intro-a-num-label"); const tagline = document.getElementById("login-intro-a-tagline"); // Peças de confete criadas uma vez (mesmo padrão de `pidApplyHalloweenCobwebs` // pro markup só existir em memória) e reaproveitadas se a intro tocar de novo. if (!confettiHost.dataset.piecesBuilt) { const pieces = pidIntroABuildConfettiPieces(); pieces.forEach((p) => { const el = document.createElement("div"); el.className = "login-intro-a__confetti-piece"; el.style.width = `${(p.size * PID_INTRO_A_ICON_K).toFixed(1)}px`; el.style.height = `${((p.long ? p.size * 0.42 : p.size) * PID_INTRO_A_ICON_K).toFixed(1)}px`; el.style.background = p.color; el.style.borderRadius = p.long ? "1px" : "50%"; el.style.opacity = "0"; confettiHost.appendChild(el); p.el = el; }); confettiHost._pidPieces = pieces; confettiHost.dataset.piecesBuilt = "1"; } const confettiPieces = confettiHost._pidPieces; const { easeOutCubic, easeInOutQuad, easeOutBack } = PID_INTRO_EASING; const easeOutExpo = (t) => (t >= 1 ? 1 : 1 - Math.pow(2, -10 * t)); const CUES = PID_INTRO_A_CUES; const K = PID_INTRO_A_ICON_K; // ── cena Chegada: o personagem cai e quica ao pousar ────────────────── const dropFn = pidIntroAnimate({ from: -260 * K, to: 0, start: CUES.Chegada + 0.05, end: CUES.Chegada + 0.95, ease: easeOutBack }); const appearFn = pidIntroAnimate({ start: CUES.Chegada + 0.05, end: CUES.Chegada + 0.5, ease: easeOutCubic }); // ── cena Confete: pulo + arremesso + explosão ───────────────────────── const hopFn = pidIntroAnimate({ start: CUES.Confete - 0.12, end: CUES.Confete + 0.12, ease: easeOutExpo }); const hopDownFn = pidIntroAnimate({ start: CUES.Confete + 0.2, end: CUES.Confete + 0.6, ease: easeInOutQuad }); const confettiT0 = CUES.Confete + 0.05; // ── cena Texto: ícone desliza, "Parabéns" + logo + "56 Anos" entram ── const shiftFn = pidIntroAnimate({ start: CUES.Texto - 0.2, end: CUES.Texto + 0.6, ease: easeOutCubic }); const B = CUES.Texto + 0.1; const parabensOFn = pidIntroAnimate({ start: B + 0.05, end: B + 0.45, ease: easeOutCubic }); const parabensYFn = pidIntroAnimate({ from: 28, to: 0, start: B + 0.05, end: B + 0.6, ease: easeOutBack }); const logoInFn = pidIntroAnimate({ start: B + 0.28, end: B + 0.85, ease: easeOutBack }); const ruleFn = pidIntroAnimate({ start: B + 0.45, end: B + 0.95, ease: easeInOutQuad }); const numFn = pidIntroAnimate({ start: B + 0.55, end: B + 1.15, ease: easeOutBack }); const tagFn = pidIntroAnimate({ start: B + 1.5, end: B + 2.0, ease: easeOutCubic }); // ── cena Serpentina: boca faz bico, língua-de-sogra desenrola e recolhe ─ const blowFn = pidIntroAnimate({ start: CUES.Serpentina + 0.05, end: CUES.Serpentina + 0.5, ease: easeOutExpo }); const recoilFn = pidIntroAnimate({ start: CUES.Serpentina + 0.65, end: CUES.Serpentina + 1.0, ease: easeInOutQuad }); // ── cena Fly (acrescentada): ícone voa até o canto real da sidebar ──── const flyFn = pidIntroAnimate({ start: CUES.Fly + 0.05, end: CUES.Fly + 0.55, ease: easeOutCubic }); const wordmarkOutFn = pidIntroAnimate({ from: 1, to: 0, start: CUES.Fly, end: CUES.Fly + 0.3, ease: easeOutCubic }); const stageFadeFn = pidIntroAnimate({ from: 1, to: 0, start: CUES.Fly + 0.5, end: CUES.Fly + 0.9, ease: easeOutCubic }); const SHIFT_X = -90; const ICON_SIZE = PID_INTRO_A_ICON_SIZE; const FINAL_ICON_SIZE = 40; const FINAL_SCALE = FINAL_ICON_SIZE / ICON_SIZE; const CORNER_MARGIN = 40; const loginCard = document.querySelector(".login-card"); if (loginCard) loginCard.classList.add("is-leaving"); overlay.hidden = false; void overlay.offsetWidth; overlay.classList.add("is-visible"); let start; function frame(now) { const T = Math.min((now - start) / 1000, PID_INTRO_A_TOTAL); const drop = dropFn(T); const appear = appearFn(T); const land = pidIntroClamp(1 - Math.abs(T - (CUES.Chegada + 0.92)) / 0.28, 0, 1); const squash = 1 - land * 0.1; const bob = Math.sin((T - CUES.Chegada) * 2.1) * 1.6; const swing = Math.sin((T - CUES.Chegada) * 1.5) * 3.4; const throwUp = pidIntroClamp(1 - Math.abs(T - (CUES.Confete + 0.1)) / 0.45, 0, 1); const hop = hopFn(T); const hopDown = hopDownFn(T); const lift = pidIntroClamp(hop - hopDown, 0, 1) * 38 * K; const blow = blowFn(T); const recoil = recoilFn(T); const horn = pidIntroClamp(blow - recoil, 0, 1); const hornLen = 30 * horn; const puff = pidIntroClamp(blow * 1.4 - recoil * 2, 0, 1); const blowing = pidIntroClamp(blow * 1.8 - recoil * 2.2, 0, 1); icon.style.opacity = String(appear); icon.style.transform = `translateY(${(drop - lift).toFixed(2)}px) scaleY(${squash.toFixed(3)})`; icon.style.filter = `drop-shadow(0 ${(26 * K).toFixed(1)}px ${(44 * K).toFixed(1)}px rgba(0,0,0,0.5))`; balloons.setAttribute("transform", `translate(${swing.toFixed(2)} ${(bob - throwUp * 16).toFixed(2)}) rotate(${(swing * 0.5).toFixed(2)} 53 34)`); arm.setAttribute("transform", `rotate(${(-throwUp * 22).toFixed(2)} 42 49)`); smile.style.opacity = String(1 - blowing); puckerOuter.style.opacity = String(blowing); puckerInner.style.opacity = String(blowing); const hornOpacity = pidIntroClamp(blow * 2.2, 0, 1) * (1 - recoil); hornGroup.style.opacity = String(hornOpacity); const hornD = `M25 57 q${(hornLen * 0.42).toFixed(2)} ${(-3 - horn * 2).toFixed(2)} ${hornLen.toFixed(2)} ${(-6 - horn * 5).toFixed(2)}`; hornTube.setAttribute("d", hornD); hornDash.setAttribute("d", hornD); hornTube.style.strokeWidth = String(4 - horn * 1.6); puff1.setAttribute("cx", String(25 + hornLen + 3)); puff1.setAttribute("cy", String(51 - horn * 5)); puff1.setAttribute("r", String(2.2 + puff * 2.4)); puff1.style.opacity = String(puff * 0.85); puff2.setAttribute("cx", String(25 + hornLen + 8)); puff2.setAttribute("cy", String(48 - horn * 5)); puff2.setAttribute("r", String(1.4 + puff * 1.8)); puff2.style.opacity = String(puff * 0.6); confettiPieces.forEach((p) => { const e = T - confettiT0 - p.delay; if (e <= 0) { p.el.style.opacity = "0"; return; } const x = (p.vx * e + Math.sin(e * 3.1 + p.i) * p.sway) * K; const y = (p.vy * e + 520 * e * e) * K; const fade = pidIntroClamp(1 - (e - 1.5) / 0.9, 0, 1); p.el.style.opacity = String(fade); p.el.style.transform = `translate(${x.toFixed(1)}px, ${y.toFixed(1)}px) rotate(${(p.spin * e).toFixed(1)}deg)`; }); const burstWin = pidIntroClamp(1 - Math.abs(T - (CUES.Confete + 0.1)) / 0.3, 0, 1); flash.style.opacity = String(burstWin * 0.1); const shift = shiftFn(T); const fly = flyFn(T); const lockupX = SHIFT_X * shift; const lockupScale = 1 - 0.26 * shift; const target = pidIntroCornerTarget(ICON_SIZE, FINAL_ICON_SIZE, CORNER_MARGIN); const flyX = lockupX + (target.x - lockupX) * fly; const flyY = target.y * fly; const flyScale = lockupScale + (FINAL_SCALE - lockupScale) * fly; iconWrap.style.transform = `translate(${flyX.toFixed(1)}px, ${flyY.toFixed(1)}px) scale(${flyScale.toFixed(3)})`; message.style.opacity = String(pidIntroClamp(shift * 1.5, 0, 1) * wordmarkOutFn(T)); const parabensY = parabensYFn(T); parabens.style.opacity = String(parabensOFn(T)); parabens.style.transform = `translateY(${parabensY.toFixed(1)}px)`; const logoIn = logoInFn(T); wordmarkImg.style.opacity = String(logoIn); wordmarkImg.style.transform = `translateY(${(-14 + (1 - logoIn) * 12).toFixed(1)}px) scale(${(0.9 + logoIn * 0.1).toFixed(3)})`; rule.style.transform = `scaleX(${ruleFn(T).toFixed(3)})`; const numVal = numFn(T); num.style.opacity = String(pidIntroClamp(numVal, 0, 1)); num.style.transform = `scale(${(0.7 + numVal * 0.3).toFixed(3)})`; numLabel.style.opacity = String(pidIntroClamp(numVal * 1.1 - 0.15, 0, 1)); const tag = tagFn(T); tagline.style.opacity = String(tag); tagline.style.transform = `translateY(${((1 - tag) * 12).toFixed(1)}px)`; stage.style.opacity = String(stageFadeFn(T)); if (T < PID_INTRO_A_TOTAL) { requestAnimationFrame(frame); } else { onDone(); } } setTimeout(() => { start = performance.now(); requestAnimationFrame(frame); }, PID_INTRO_FADE_MS); }