35 KiB
Temas Sazonais
Criado em 2026-09-15, seguindo o mesmo padrão das demais aplicações sem pacote Python dedicado (ver
CLAUDE.mdna raiz, "Documentação dividida por aplicação") — como o Halloween foi só o primeiro de possivelmente vários temas sazonais, faz mais sentido documentar isso como uma "aplicação" própria desde já do que deixar acumulando dentro doCLAUDE.mdraiz. Este arquivo não é auto-carregado pelo Claude Code (não há pacote Python — o mecanismo é só frontend,static/js/static/css/templates) — leia manualmente ao mexer nisto. VerCLAUDE.mdna raiz para arquitetura geral/transversal do Portal.
Troca sazonal da marca P.I.D. (logo, ícone da sidebar/login, favicon, intro de abertura) por uma variante temática durante uma janela de datas — hoje dois: Halloween (mascote "vampiro", 16 a 31/10) e Aniversário (56 anos da De Paula Contadores, mascote com balões, só 15/10). O mecanismo (pidIsHalloweenActive()/pidIsAniversarioActive()/seasonal-theme.js) foi pensado pra ser genérico o bastante pra outro tema sazonal futuro reaproveitar o mesmo esqueleto sem reescrever nada — as duas janelas atuais nunca coincidem por desenho (15 vs 16-31/10), não por checagem explícita de conflito.
Status atual
Confirmado e programado — revisão visual concluída pelo usuário. PID_HALLOWEEN_PREVIEW_FORCE/PID_ANIVERSARIO_PREVIEW_FORCE = false em static/js/seasonal-theme.js (deixados de propósito nas constantes, documentando a decisão, em vez de removidos): daqui pra frente só a data decide se cada tema está ativo, sem nenhuma ação manual — nem pra ligar, nem pra desligar depois. Nada mais precisa mudar de código pra isso valer.
O Halloween só começa em 16/10 (antes: outubro inteiro) desde que o tema de Aniversário foi criado — 15/10 é o aniversário da empresa e passou a ter tema próprio, então os dois janelas foram desenhadas pra nunca disputar o mesmo dia.
"56 ANOS" é específico deste ano — a pílula do lockup horizontal, o texto "56" da intro e os 3 arquivos static/img/pid-aniversario-*.svg precisam ser atualizados manualmente todo ano (novo número, nova pasta pid-marca-<N> de referência) — ver leiame.md original: "é uma marca temporária". Isso não é automatizado de propósito (não vale a complexidade pra um ajuste anual de um texto).
Origem dos assets (Halloween)
Mascote "vampiro" (a mesma marca P.I.D. fantasiada de Drácula — capa com gola erguida e forro vinho, lápis de olho, dois dentinhos) desenhada em C:\Users\Depaula\Documents\Logos\P.I.D. Logo Design Halloween\pid-marca-halloween\ (SVGs de referência + leiame.md, fora do repositório, mesmo papel de pid-marca/ pra marca "P.I.D." normal — ver "Logos em static/img/" em docs/identidade-visual/identidade-visual.md) e a intro de abertura em ...\P.I.D. Logo Design Halloween\Logo intro animation Halloween\export-halloween\pid-intro-halloween.html (mesmo formato de bundle "Design Canvas" do pid-intro-escuro.html original — ver CLAUDE.md raiz, "Animação de intro do login" → "Origem do arquivo"; a composição (PidHalloween, cenas Rise/Scare/Settle/Wordmark, 1,3s/1,5s/1,4s/1,8s) foi extraída do bundle com o mesmo processo ad-hoc (gzip+base64 por uuid) e portada fielmente pra vanilla JS/CSS). Cópias dos SVGs de referência (não usadas diretamente pela UI, que injeta o markup inline — ver abaixo) vivem em static/img/pid-halloween-favicon.svg/pid-halloween-icone-escuro.svg/pid-halloween-logo-horizontal-escuro.svg.
static/js/seasonal-theme.js
Incluído em toda página (mesmo padrão de confirm-modal.js, logo depois dele) — concentra tudo que é compartilhado entre login e sidebar:
pidIsHalloweenActive()/pidIsAniversarioActive()— a checagem de data de cada tema (mais o respectivo preview force e o opt-out do usuário, ver "Opt-out do usuário" abaixo — checado antes de tudo, prioridade máxima).pidApplySeasonalFavicon(iconFile)— troca<link rel="icon">depid-icone-escuro.svgpro arquivo passado (pid-halloween-icone-escuro.svgoupid-aniversario-icone-escuro.svg) via substring nohref(funciona com qualquer prefixo deSTATIC_URL). Não usa o favicon dedicado de cada tema (pid-halloween-favicon.svg/pid-aniversario-favicon.svg) — são variantes simplificadas (sem capa/gola ou sem balões, cores diferentes) que destoam do resto da marca sazonal na aba do navegador (bug relatado pelo usuário na revisão visual do Halloween, aplicado por precedente ao Aniversário); mesmo padrão do P.I.D. normal, cujo favicon também usapid-icone-escuro.svgem vez dopid-favicon.svgdedicado.pidApplyVampireIcon(svg)/pidApplyVampireFull(svg)— substituem oviewBox+innerHTMLde um<svg>existente (ícone 64×64 → vampiro 76×80; assinatura horizontal 300×80 → 320×96) pelo markup do vampiro, com dois grupos de rosto sempre presentes (.pid-vamp-face--friendly/--scare, cruzando opacidade) dentro de um<g class="pid-vamp-icon-rig">— mesma ideia de "dois estados sempre no DOM, alternando opacidade" já usada pelo crossfade da sidebar (sidebar__logo--full/--icon).pidTriggerVampireScare(root)— dispara a animação de susto (.is-scaring, verbase.css) em todo.pid-vamp-icon-rigdentro deroot.pidApplyAniversarioIcon(svg)/pidApplyAniversarioFull(svg)— substituem só oinnerHTML(viewBox não muda, ao contrário do vampiro) pelo markup com balões; preservam os dois.pid-icon-eyedo ícone normal (a cara não muda nesse tema), então a piscada ao clicar continua funcionando sem nenhum código de interação dedicado.
Troca do ícone/assinatura (sidebar + login)
Como o markup do ícone P.I.D. normal já é idêntico (copiado inline) nas 13 páginas-shell + login, a troca é feita em runtime, não editando cada template — sidebar-brand.js/login-logo-blink.js chamam pidApplyVampireIcon/pidApplyVampireFull/pidApplyAniversarioIcon/pidApplyAniversarioFull/pidApplySeasonalFavicon no próprio DOMContentLoaded, escolhendo o tema (pidIsHalloweenActive() checado primeiro, pidIsAniversarioActive() senão — nunca os dois juntos, mas Halloween ganha prioridade de código por clareza). Isso também é o que torna o "retorno" ao P.I.D. normal automático fora de qualquer janela: o innerHTML original nunca é tocado no arquivo, só sobrescrito em memória a cada carregamento de página enquanto alguma janela estiver ativa.
Susto ao clicar (substitui a piscada de olhos)
Fora do Halloween, clicar na logo pisca os olhos (.pid-icon-eye/is-blinking, comportamento original, intocado). Durante o Halloween, o mesmo clique dispara pidTriggerVampireScare() — .pid-vamp-icon-rig.is-scaring (base.css, logo depois do bloco de piscada) toca 4 animações de 900ms em paralelo: pidVampFaceOut/pidVampFaceIn (crossfade rosto amigável → raivoso e de volta), pidVampCapeBurst (a capa "estufa" com scale+rotate) e pidVampShake (o ícone inteiro treme e ganha um brilho vermelho via filter:drop-shadow) — tudo CSS puro (sem requestAnimationFrame), disparado só pela troca de classe, mesmo padrão da piscada original. Na sidebar, a navegação pra portal.html é adiada PID_HALLOWEEN_SCARE_MS - 100 (800ms, contra 260ms da piscada normal) pra dar tempo de ver o susto antes de trocar de página; no login não há navegação, é só o toque decorativo.
Intro de abertura pós-login (static/css/login-intro-halloween.css + static/js/login-intro-halloween.js)
Variante completa de login-intro.css/.js, com seu próprio overlay (#login-intro-halloween, mesma estrutura de #login-intro em index.html, hidden por padrão) — reaproveita as funções de easing/animate/clamp/corner-target já globais de login-intro.js (PID_INTRO_EASING, pidIntroAnimate, pidIntroClamp, pidIntroCornerTarget) em vez de duplicá-las, só a coreografia é própria. auth.js escolhe qual intro chamar no sucesso do login (pidPlayLoginIntroHalloween vs pidPlayLoginIntro) com base em pidIsHalloweenActive().
- Cenas (
PID_INTRO_H_CUES/PID_INTRO_H_TOTAL—Rise:0, Scare:1.3, Settle:2.8, Wordmark:4.2, Fly:6.0, total6.9):Rise(a figura surge do escuro com a capa erguida cobrindo o rosto, só os olhos semicerrados espiando —squint),Scare(a capa se abre numa investida —lunge/push/tremida —, revelando o rosto raivoso com sobrancelhas e presas, mais um flash vermelho de tela inteira via#login-intro-h-flash),Settle(a capa volta a fechar, o rosto cruza pro amigável de sempre),Wordmark(a figura desliza, "P.I.D." aparece na fonte "Creepster" com régua dourado→vinho e a tagline). As primeiras 4 cenas (Rise/Scare/Settle/Wordmark, somando 6,0s) são a composição original praticamente 1:1 — mesmas fórmulas/janelas de tempo depid-intro-halloween.html, sem reproporcionar nada (ao contrário da intro normal, que teve que comprimir as cenas — aqui não foi pedido acelerar). Fly(~0,9s, acrescentada, não existe no arquivo original): a composição-fonte foi pensada como vídeo solto (tudo esmaece junto no final); pra se integrar ao fluxo do Portal (a mesma razão documentada noCLAUDE.mdraiz sobre o "voo" da intro normal), foi adicionada uma cena final equivalente à cena "Portal" da intro normal — a wordmark esmaece, o ícone encolhe e voa até o canto real da janela (mesmopidIntroCornerTarget(), reaproveitado) e o stage inteiro esmaece por cima do fim do voo.- Reescala de tamanho: a composição original foi autorada pra um ícone de 460px (uso como hero de vídeo); aqui o ícone roda a 200px (
PID_INTRO_H_ICON_K = 200/460), então todo deslocamento em "px" (a subida inicial, a tremida, o desvio vertical da investida, o blur do brilho) é multiplicado por esse fator — as transformações internas do SVG (a capa abrindo, o ângulo da aba) continuam em unidades do próprioviewBox(76×80, igual ao das duas variantes vampiro emstatic/img/) e não precisam de reescala. - Tema claro: mesmo raciocínio da intro normal — fundo acompanha
--bg-canvas, texto do wordmark vira escuro (#3a1620) pra continuar legível; o ícone não precisa de override (roxo/vinho com contraste de sobra nos dois fundos).
Teias de aranha de canto (static/css/halloween-cobweb.css)
Decoração de canto pedida pelo usuário depois de revisar um protótipo isolado publicado como Artifact ("Cantos Assombrados", fora do repositório — comparava 2 vs 4 cantos e aranha andando vs parada antes de aplicar de verdade). Só em 2 cantos (superior-esquerdo/inferior-direito, os mesmos marcados por ele num print da tela real) e só em duas páginas: index.html (login) e portal.html (tela Principal) — nenhum outro shell. Cada teia é um quarto de círculo (8 fios retos + 5 anéis em espiral levemente arqueados — <path class="pw-ring"> com curvas Q em vez de polilinhas retas, pra não parecer um desenho geométrico cru — mais 1 fio "perdido" bem mais comprido que os outros, se estendendo pra fora do canto; todas as coordenadas calculadas por trigonometria, não à mão) com uma aranha (<g class="pw-spider">, corpo+cabeça+8 pernas) que anda pra fora ao longo de um dos fios e volta (@keyframes pwWalk, 8s, com pausas nas pontas) enquanto as pernas mexem continuamente (pwLegsWiggle) — tudo CSS puro, sem requestAnimationFrame. Desenho da teia refeito numa rodada seguinte a partir de uma referência visual que o usuário anexou (queria "algo menos bruto", mais profissional, "sem parecer que só foi colado um PNG"): a versão original tinha só 6 fios retos + 3 anéis poligonais (linhas retas entre pontos, sem curva nenhuma) — a v2 acima é mais densa perto do canto e mais esparsa longe dele, com as curvas arqueadas simulando a espiral de captura de uma teia real.
- Markup só existe em memória: igual ao ícone vampiro, o HTML da teia não fica hardcoded no template —
PID_COBWEB_SVG(string,seasonal-theme.js) é injetado dentro de um container já presente ehidden(#pw-cobwebs-login/#pw-cobwebs-portal) porpidApplyHalloweenCobwebs(containerId), chamada porlogin-logo-blink.js/sidebar-brand.jsno mesmoif (halloween)que já troca o ícone — fora de outubro, o container continua vazio ehidden. - Ancoragem diferente em cada página: no login, a teia é filha direta de
.login-page(ela já cobremin-height:100vh); na Principal, filha de.main-content(não o viewport, porque a sidebar já é sólida e cobriria o canto esquerdo de qualquer jeito) — ancorar ali garante que a teia nunca fica escondida atrás dela, com ou sem a sidebar colapsada. As duas usam a mesma regra base.pw-corners{position:absolute;inset:0}. - Tamanho: menor na Principal que no login (pedido explícito do usuário, "evitando cobrir muito as informações que constam na página" — a tela Principal tem cards/widgets perto das bordas, o login só tem o card centralizado), mas aumentado em duas rodadas seguintes ("ficaram muito pequenas", nas duas telas):
clamp(220px,22vw,320px)no login vsclamp(160px,15vw,230px)na Principal (valores atuais). z-index:-1(nãopointer-eventssozinho) garante que a teia fica atrás do conteúdo real, não só sem receber clique.- Bug real — a teia do login não aparecia de jeito nenhum:
z-index:-1só funciona como esperado dentro de um contexto de empilhamento isolado; sem isso, um elemento negativo escapa pro contexto raiz da página e qualquer irmão estático com fundo opaco pinta por cima dele (é exatamente o caso de.login-page, que tem fundo cobrindo a tela inteira) — a teia ficava sempre atrás desse fundo, nunca visível. Na Principal o mesmo problema existia, só que passava despercebido porque.main-contentnão tem fundo opaco cobrindo tudo (a teia só aparecia nos vãos sem conteúdo por cima, "sumindo" só parcialmente/nos cantos exatos). Corrigido comisolation:isolateem.login-page(login.css) e em.main-content(layout.css, junto doposition:relativejá existente) — isola um contexto de empilhamento próprio pra cada uma, e a teia (filha direta,z-index:-1) passa a ficar corretamente atrás do fundo e na frente do conteúdo real nas duas. - Canto superior-esquerdo da Principal escondido atrás da topbar: a
.topbar(position:sticky,z-index:20, fundo opaco —layout.css) cobria a metade de cima da teia desse canto..pw-corners--portal .pw-corner--tlganhoutop: var(--topbar-height)(mesmo token que a topbar já usa) — só esse canto, o inferior-direito e os dois do login não têm nada cobrindo por cima e continuam emtop:0/bottom:0. - Contraste por tema: a aranha (
#17111d) é quase a mesma cor do fundo escuro padrão (--bg-canvas:#121017) e sumia, sobrando só o olho vermelho visível —.pw-spiderganhoufilter:drop-shadow(...)branco (dois drop-shadows, um mais fechado e um mais espalhado) só pra dar contraste no tema escuro. No tema claro é o oposto: a teia (creme, pensada pro fundo escuro) sumiria no fundo branco —:root[data-theme="light"] .pw-strands/.pw-strand-stray/.pw-ringviram um cinza-chumbo escuro semitransparente, e o filtro branco da aranha é desligado (filter:none, já que aranha escura sobre fundo claro já contrasta sozinha e o brilho branco só seria ruído ali). - Clicar mata a aranha (pedido explícito do usuário): a aranha (única parte da teia com
pointer-events:auto, o resto continuapointer-events:none, decorativo) tem um listener{once:true}(pidApplyHalloweenCobwebs()) que, ao clique, adiciona.is-dying(pwSpiderDie, 950ms — cai girando, opacity até 0,animation-fill-mode:forwardsmantém o estado final) e.is-risingnum<g class="pw-skull">irmão (pwSkullRise, 1300ms — sobe, aparece e some, como a "alma saindo do corpo" enquanto o corpo cai); as pernas ganham sua própriapwLegsDie(encolhem/giram) via.pw-spider.is-dying .pw-legs, sobrescrevendo opwLegsWigglecontínuo por especificidade. - O óbito é persistido (pedido explícito do usuário, numa rodada seguinte — "guardar que a aranha foi morta na memória... até executar o comando tab+b que reseta a aranha"): cada aranha tem um id próprio (
${containerId}__tl/__br— 4 ids possíveis, 2 cantos × 2 páginas) guardado num array JSON emlocalStorage(pid_seasonal_dead_spiders,pidMarkSpiderDead()/pidGetDeadSpiders()), mesmo padrão de armazenamento das outras preferências sazonais (nunca migrado pro banco). No carregamento seguinte,pidApplyHalloweenCobwebs()checa essa lista e, pra aranha já morta, aplica.is-deaddireto (animation:none;opacity:0, sem tocarpwSpiderDie/a caveira de novo — não faz sentido reprisar a queda a cada F5) em vez de ligar o clique. - Atalho Tab+B reseta todas as aranhas (
pidResetDeadSpiders(), limpa olocalStoragee recarrega a página): segurar Tab (sempreventDefault, a navegação por foco continua normal) e apertar B enquanto ele está pressionado. Listener global (seasonal-theme.js, incluído em toda página), só tem efeito de verdade nas duas páginas com teia e só quando há alguma aranha morta salva. - Bug real — aranha caindo pra cima e caveira de cabeça pra baixo no canto inferior-direito: o canto
--broriginalmente era só o mesmo desenho do--tlcomtransform:scale(-1,-1)no<div class="pw-corner">inteiro (jeito mais simples de reaproveitar o mesmo markup pro canto oposto). Só quescale(-1,-1)num ancestral vira tudo de cabeça pra baixo, inclusive a "aranha"/"caveira" (que têm uma orientação certa — pernas, olhos, queixo) e a direção das animações de queda/subida (pwSpiderDie/pwSkullRise, que "sobem" em vez de "descer" depois de espelhadas). Corrigido separando as duas coisas: só a teia em si (.pw-strands, sem opinião de "lado certo pra cima") continua sendo espelhada, agora comtransform-box:view-box; transform-origin:100px 100px(o centro do viewBox autorado, não a caixa delimitadora dos fios — importante porque o fio "perdido" mais comprido deslocaria esse centro se fossefill-box); a aranha e a caveira passaram a usar variáveis CSS (--rx/--ry/--rr/--ox/--oyna aranha,--sx/--syna caveira) pra descanso/passeio/posição, redefinidas com os valores espelhados (200-x,200-y,+180deg) só pra.pw-corner--br, sem nenhumscale/rotateextra nelas — ficam sempre em pé, e as@keyframesde queda/subida (agora usandocalc(var(--r*) + valor)) sempre significam "pra baixo"/"pra cima" de verdade na tela, nos dois cantos.
Tema "Aniversário" (56 anos da De Paula Contadores, só 15/10)
Segundo tema sazonal, criado numa rodada seguinte à do Halloween — mesmo personagem P.I.D., "o acento é o número" (ver leiame.md original abaixo): ganha três balões (azul/âmbar/rosa) segurados por um braço saindo da lateral direita do corpo. A cara de repouso não muda (sem susto como o vampiro) — mas o clique na logo ganhou uma animação própria numa rodada seguinte (ver "Clique de festa" abaixo), e o tema também ganhou um bolo com velinhas na tela Principal (ver "Bolo de aniversário" abaixo).
Origem dos assets
Desenhado em C:\Users\Depaula\Documents\Logos\P.I.D. Logo Design Aniversário\pid-marca-56\ (SVGs de referência + leiame.md, fora do repositório, mesmo papel de pid-marca-halloween/ pro Halloween) e a intro de abertura em ...\P.I.D. Logo Design Aniversário\Logo intro animation\export-56\pid-56-anos.html (mesmo formato de bundle "Design Canvas", desta vez um componente React/JSX — pid-56.jsx — em vez de JS puro; extraído do bundle com o mesmo processo ad-hoc de gzip+base64 por uuid já usado no Halloween/intro normal e portado fielmente pra vanilla JS/CSS em login-intro-aniversario.js). Cópias dos SVGs de referência (não usadas diretamente pela UI, que injeta o markup inline) vivem em static/img/pid-aniversario-favicon.svg/pid-aniversario-icone-escuro.svg/pid-aniversario-logo-horizontal-escuro.svg; a logo da De Paula Contadores usada na intro (imagem <img>, não SVG) vive em static/img/pid-aniversario-depaula-wordmark.png.
Exceção deliberada à separação logo cursivo vs "P.I.D." (ver "Logos em static/img/" no CLAUDE.md raiz): a intro de abertura deste tema mostra a logo cursiva oficial da De Paula Contadores (não a marca "P.I.D.") ao lado do texto "Parabéns" — normalmente essa logo é reservada só a documentos/PDFs gerados pela aplicação, nunca à UI do Portal. Aqui a exceção é intencional: a composição celebra o aniversário da empresa, não do sistema, e o próprio usuário forneceu essa imagem como parte do design de referência (não é uma migração de logo decidida unilateralmente).
"56 ANOS" é específico deste ano — ver "Status atual" no topo deste arquivo.
Ícone/assinatura (sidebar + login)
pidApplyAniversarioIcon(svg)/pidApplyAniversarioFull(svg) (seasonal-theme.js) substituem só o innerHTML — ao contrário do vampiro, a caixa (viewBox) não muda (64×64/300×80, iguais ao P.I.D. normal), já que os balões cabem na mesma composição sem precisar de mais espaço. Os dois .pid-icon-eye do ícone normal são preservados dentro do markup novo, então a piscada ao clicar (sidebar-brand.js/login-logo-blink.js) continua funcionando — inclusive junto com o clique de festa (ver abaixo), não em vez dele.
Estrutura em dois <g> aninhados — <g class="pid-aniv-hop"> (recebe o "pulo" via CSS, sem nenhum transform de atributo XML) por fora, <g class="pid-aniv-icon-rig" transform="..."> (o posicionamento estático dentro da caixa) por dentro — pelo mesmo motivo já documentado pra .pid-icon-eye/.pid-vamp-icon-rig: um transform de CSS aplicado ao MESMO elemento que já tem um transform de atributo substitui o atributo inteiro, perdendo a posição.
Clique de festa (pulo + confete + língua-de-sogra), pedido numa rodada seguinte
Pedido explícito do usuário depois de ver o tema pela primeira vez ("aplique a mesma animação do mascote de pular com os confetes e a língua de sogra ao clicar no botão da logo"). Ao contrário do susto do vampiro (que substitui a piscada), o clique de festa convive com ela — a cara de repouso do mascote de aniversário não muda, então a piscada de olhos continua fazendo sentido; o clique dispara os dois efeitos juntos.
- Markup:
pidAniversarioIconRigMarkup()(seasonal-theme.js) ganhou, além dos balões já existentes, um braço/balões agrupados (.pid-aniv-balloons/.pid-aniv-arm, antes soltos), um grupo de confete escondido (.pid-aniv-confetti, 8 formas pequenas perto do braço) e, dentro do corpo, um sorriso/bico-de-pato que se alternam (.pid-aniv-smile/.pid-aniv-pucker-outer/-inner) mais uma língua-de-sogra (.pid-aniv-horn, um tubo + traço pontilhado + 2 "bolhas" de sopro) — todos escondidos por padrão (opacity:0/scaleX(0)), revelados só durante o clique. - Trigger:
pidTriggerAniversarioParty(root)(mesmo padrão depidTriggerVampireScare— remove e reaplica.is-partyingem todo.pid-aniv-hopdentro deroot, permitindo cliques repetidos re-tocarem do zero). Chamado porsidebar-brand.js/login-logo-blink.jsquandopidIsAniversarioActive(), sempre antes de também disparar a piscada normal. - Animação (
base.css,PID_ANIVERSARIO_PARTY_MS = 650mspra tudo, mesmo truque do susto do vampiro de escalonar o efeito só via percentuais internos de cada@keyframes): o personagem pula (pidAnivHop, translateY+squash), o braço joga os balões pra cima (pidAnivArmThrow, rotate), os balões balançam mais forte (pidAnivBalloonSwing), as 8 peças de confete explodem em direções fixas (pidAnivConfettiPiece, uma por:nth-childcom--tx/--typróprios e um pequeno atraso escalonado) e a boca faz bico enquanto a língua-de-sogra desenrola e recolhe (pidAnivPuckerIn/pidAnivSmileOut/pidAnivHornUnroll). - Pivôs de transform:
.pid-aniv-hop/.pid-aniv-balloons/.pid-aniv-armusamtransform-box:fill-box; transform-origin:center bottom(pivô aproximado na base do próprio desenho, suficiente pra um ícone pequeno — não vale a complexidade de calcular o pivô exato emview-box);.pid-aniv-hornusatransform-origin:left center(a ponta esquerda da própria caixa delimitadora é exatamente onde o tubo nasce, perto da boca —scaleXa partir dali "desenrola" corretamente sem precisar de coordenada manual). - Bug real — a língua-de-sogra saía do lugar errado e não chegava a sair do corpo: ela tinha nascido DENTRO do grupo do corpo (
<g transform="translate(2,18) scale(0.72)">), com as coordenadas da ponta esquerda do sorriso (M25 47, o começo do traço da boca, não o centro dela) — resultado: o tubo atravessava o rosto na diagonal e terminava ainda dentro do disquete, sem nunca aparecer pra fora. Corrigida movendo o grupo pra FORA do corpo (irmão dele, depois dele no DOM pra continuar desenhando por cima) e recalculando o ponto de origem no espaço do rig: o bico da boca está em(32, 48.5)no espaço local do corpo, que vira(2 + 32×0,72, 18 + 48,5×0,72) = (25, 53)no espaço do rig. Daí o tubo segueq12.6 -5 30 -11(as mesmas proporções da composição original, onde o efeito também cruza o corpo de propósito) e termina 11 unidades além da borda direita do disquete, em espaço livre.
Intro de abertura pós-login (static/css/login-intro-aniversario.css + static/js/login-intro-aniversario.js)
Variante de login-intro.css/.js, com seu próprio overlay (#login-intro-aniversario em index.html) — reaproveita PID_INTRO_EASING/pidIntroAnimate/pidIntroClamp/pidIntroCornerTarget já globais de login-intro.js. auth.js escolhe a intro certa (Halloween > Aniversário > normal, nessa ordem de prioridade de código — nunca duas ativas ao mesmo tempo de verdade).
- Cenas originais (
PID_INTRO_A_CUES—Chegada:0, Confete:1.1, Texto:2.5, Serpentina:4.1, somando 5,9s, extraídas depid-56.jsx):Chegada(o personagem cai com os balões e quica ao pousar),Confete(dá um pulo, joga o braço com os balões pra cima e arremessa uma explosão de confete — 58 peças com física determinística, mesma semente pseudo-aleatória do original pra toda visita produzir a mesma explosão),Texto(o ícone desliza pra esquerda enquanto "Parabéns" + a logo da De Paula + "56 Anos" entram),Serpentina(a boca faz bico e uma língua-de-sogra desenrola da boca, depois recolhe totalmente, enquanto o slogan pousa e a cena esmaece). - Diferenças em relação à composição original: (1) a composição original foi autorada num canvas de vídeo 1920×1080 com elementos posicionados por coordenadas absolutas — aqui virou uma única linha flexível (ícone + mensagem lado a lado), com o "deslizar pra esquerda" reproduzido pela mesma técnica de
SHIFT_Xjá usada nas outras duas intros (o ícone se desloca portransform, a mensagem só aparece fixa ao lado); (2) o "zoom" de respiro do fundo (1.05→1.0→1.02) e as vinhetas radiais do canvas original foram descartados — floreios de vídeo sem efeito perceptível numa janela de overlay pequena; (3)Fly(~0,9s, acrescentada, não existe no arquivo original, mesma ideia já usada nas outras duas intros) — a composição original termina com tudo esmaecendo junto (pensada como vídeo solto); aqui o ícone encolhe e voa até o canto real da sidebar antes do stage inteiro esmaecer. - Reescala de tamanho: a composição original foi autorada pra um ícone de 420px (
Partycomponent,size=420default); aqui o ícone roda aPID_INTRO_A_ICON_SIZE(260px — maior que os 200px das outras duas intros, pedido explícito do usuário numa rodada seguinte, "aumente um pouco o tamanho do mascote... para que fique mais fácil sua visualização"), comPID_INTRO_A_ICON_K = PID_INTRO_A_ICON_SIZE / 420calculado a partir dessa constante (não fixo) — só os deslocamentos aplicados via CSStransformem px reais no elemento<svg>(a quedadrop, o "lift" do pulo, o blur da sombra) são multiplicados por esse fator; as transformações internas em unidades doviewBox(balanço dos balões, ângulo do braço, comprimento da língua-de-sogra) escalam junto automaticamente, e a física do confete (px reais fora do SVG) é multiplicada pelo mesmo fator. - Tema claro: mesmo raciocínio das outras duas intros — fundo acompanha
--bg-canvas, texto claro (pensado pra contrastar com fundo escuro) vira escuro; o ícone não precisa de override (roxo/dourado com contraste de sobra nos dois fundos).
Bolo de aniversário (static/css/aniversario-decor.css), pedido numa rodada seguinte
Pedido explícito do usuário pra "deixar o tema mais completo". A mesma rodada também trouxe bandeirinhas de festa nos cantos (login + Principal, nos mesmos 2 cantos das teias do Halloween), removidas a pedido do usuário logo depois de vê-las na tela — não tentar ressuscitá-las sem pedido explícito. O bolo ficou.
- Bolo de aniversário (
pid-aniv-cake,pid-cake-*): só na Principal,position:fixedno canto inferior-direito da janela (não da página — continua visível mesmo com scroll, ao contrário das teias de aranha, que sãoposition:absolutedentro do conteúdo). Duas camadas com gotejamento de cobertura (pid-cake-drip1/drip2, um path ondulado) e 3 velas com chama tremulando continuamente (pidCakeFlicker, atrasos diferentes por vela pra não tremerem em sincronia). Cada gotejamento usa a cor da camada DE CIMA (creme sobre o dourado, dourado sobre o roxo) — pintá-lo da cor da própria camada o deixaria invisível, que foi como nasceu.- Iluminação das velas (pedida numa rodada seguinte), em duas camadas: um
drop-shadowduplo no próprio.pid-cake-flame(o brilho colado na chama, que pulsa junto compidCakeFlicker) e o.pid-cake-halo— uma elipse com<radialGradient>âmbar, posicionada depois das camadas do bolo e antes das velas, pra a luz lavar o topo do bolo sem desbotar as chamas. Os dois pulsam em ciclos de durações diferentes (1,4s e 2,6s) de propósito, pra não parecerem sincronizados. Ofilterestá também na regra base de.pid-cake-flame, não só nos keyframes, pra o brilho sobreviver enquantopidCakeFlameOuttoca; e o halo tem seu própriopidCakeHaloOut, senão sobraria uma mancha quente boiando no canto depois das velas apagadas. No tema claro a intensidade cai pela metade (--halo-max) — brilho quente sobre fundo claro lê como névoa suja, não como luz. - Clicar apaga as velinhas (pedido explícito do usuário):
pidApplyAniversarioCake(containerId)liga o clique num<button>(não numa<div>, pra ganhar foco por teclado/aria-labelde graça) — adiciona.is-blowing(tocapidCakeFlameOut/pidCakeSmokeRiseuma vez, a fumaça sobe e some enquanto a chama encolhe) e, depois dePID_CAKE_BLOW_MS(1400ms), troca pra.is-blown(estado final em repouso, semanimation— mesma ideia de.pw-spider.is-dead). - O estado apagado NÃO é persistido, de propósito — as velinhas reacendem a cada carregamento da página, e o atalho Tab+B continua sendo só da aranha do Halloween. A persistência chegou a existir aqui (
pid_aniversario_cake_blownnolocalStorage, copiada depidMarkSpiderDead) e foi removida depois que o usuário relatou como bug: "não aparece o fogo das velinhas para funcionar a animação de apagar elas ao clickar". O que acontecia: quem clicasse no bolo uma vez nunca mais via as velas acesas (nem o listener de clique era ligado, já que o bolo nascia direto em.is-blown), sem nenhuma pista disso na tela, e só voltava com um atalho de teclado obscuro. A aranha persiste porque o usuário pediu isso explicitamente; aqui o pedido foi só "ao clicar nele as velas se apagam" — a persistência era invenção minha por analogia. Lição pra decoração futura: só persistir estado de easter egg quando pedido, senão o efeito desaparece de vez no primeiro uso. - Bug real — o bolo nasceu ocupando a tela inteira, fora do canto: a
<div>emportal.htmltinha só oid(#pid-aniv-cake), sem a classe.pid-aniv-cakeque carrega TODO o dimensionamento/posicionamento (position:fixed,right/bottom,width:clamp(...)) — sem ela a div virou um bloco normal do fluxo, com a largura inteira do conteúdo, e o<svg>(width:100%) esticou junto. As classes.is-blowing/.is-blowntambém não teriam efeito, pelo mesmo motivo. Ao adicionar um container decorativo novo, conferir que ele leva classe E id — o id é só o gancho do JS, quem estiliza é a classe (mesmo padrão dos containers de teia, que já nasceram com as duas). - Bug real — as chamas flutuavam acima das velas: nasciam alguns pontos acima do topo de cada vela, soltas no ar; agora começam exatamente no
ydo<rect>da vela correspondente. A fumaça também precisou detransform-box:fill-box— sem isso, oscaleda subida cresce a partir da origem do viewBox e joga o traço pra longe do pavio. - Outros dois ajustes visuais da mesma rodada: as chamas nasciam alguns pontos acima do topo das velas (flutuando soltas) — agora começam exatamente no
ydo<rect>de cada vela; e o gotejamento de cobertura de cada camada estava pintado da mesma cor da própria camada (portanto invisível) — agora cada gotejamento usa a cor da camada de cima (creme escorrendo sobre o dourado, dourado sobre o roxo).
- Iluminação das velas (pedida numa rodada seguinte), em duas camadas: um
Opt-out do usuário ("Temas sazonais", menu da conta)
Pedido explícito do usuário — "essa medida serve para caso o usuário tenha alguma preferência com relação aos temas, ou no caso do halloween, se tiver aracnofobia". Botão-toggle (.seasonal-theme-toggle, components.css) no dropdown da conta (#account-dropdown, duplicado nos 13 shells — mesmo padrão de duplicação de sidebar__brand já documentado no CLAUDE.md raiz, "Ordem de <script>"), logo abaixo de "Tema de cores", com dois estados "Ativo"/"Inativo" (aria-pressed, cor de destaque quando ativo). Wiring em account.js, junto do bloco de theme-swatches — não em seasonal-theme.js, que só guarda a preferência.
- Armazenamento:
pid_seasonal_theme_opt_outnolocalStorage("1"= desligado), mesmo padrão depid_theme/pid_color_theme(theme.js) — preferência de navegador, nunca migrada pro banco. - Prioridade máxima:
pidIsHalloweenActive()/pidIsAniversarioActive()checampidSeasonalThemesOptedOut()antes de qualquer outra coisa, inclusive o respectivo preview force — se o usuário desligou, fica desligado mesmo com o preview forçado ligado. O toggle vale pros dois temas de uma vez (não há opt-out por tema). - Recarrega a página ao alternar: o tema sazonal (ícone, teias, favicon) já foi aplicado via substituição de
innerHTMLno carregamento da página atual — não dá pra desfazer isso in-place de forma confiável, então o clique já salva a preferência e chamawindow.location.reload(). Importante sobretudo pra quem desligou por aracnofobia: a aranha precisa sumir na hora, não só na próxima navegação.