279 lines
14 KiB
JavaScript
279 lines
14 KiB
JavaScript
/*
|
||
* 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);
|
||
}
|