/* Teias de aranha decorativas de canto, tema sazonal Halloween — só no
canto superior-esquerdo e no inferior-direito (pedido explícito do
usuário, mesmos 2 cantos marcados nos prints que ele mandou), só no login
(`index.html`) e na tela Principal (`portal.html`) — nenhuma outra
página. Puramente decorativa (`pointer-events:none`, `z-index:-1` pra
ficar atrás de todo o conteúdo real, exceto a própria aranha — ver
"Clicar mata a aranha" abaixo), escondida por padrão (`hidden` no HTML) e
só exibida via `pidApplyHalloweenCobwebs()` (seasonal-theme.js) quando
`pidIsHalloweenActive()` — ver `docs/temas-sazonais/temas-sazonais.md`.
Desenho/cores validados no protótipo (artifact "Cantos Assombrados")
antes de aplicar aqui; o desenho da teia em si (fios/anéis) foi refeito
numa rodada seguinte a partir de uma referência visual do usuário, pra
parecer uma teia real (espiral levemente arqueada, mais fios, um fio
"perdido" se estendendo bem mais longe que o resto) em vez de um desenho
geométrico cru.
No login, ancorada em `.login-page` (`position:absolute`, ela já cobre
`min-height:100vh` e ganhou `isolation:isolate` em `login.css` — sem
isolar, o próprio fundo de `.login-page` pintava por cima da teia,
escondendo-a por completo). Na tela Principal, ancorada em
`.main-content` (mesma ideia, `isolation:isolate` em `layout.css`) — não
no viewport, porque a sidebar já é sólida e cobriria a teia do canto
esquerdo de qualquer jeito; assim a teia nunca fica escondida atrás dela.
A teia da Principal também é um pouco menor que a do 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 as duas foram aumentadas numa rodada
seguinte, "ficaram muito pequenas". */
.pw-corners {
position: absolute;
inset: 0;
pointer-events: none;
overflow: hidden;
z-index: -1;
}
.pw-corner {
position: absolute;
pointer-events: none;
}
.pw-corner--tl {
top: 0;
left: 0;
}
.pw-corner--br {
bottom: 0;
right: 0;
}
/* O canto inferior-direito reaproveita o MESMO desenho/markup do
superior-esquerdo (a teia é autorada assumindo o canto no (0,0) do seu
próprio viewBox), só espelhando a TEIA em si (`.pw-strands`) em torno do
centro do viewBox — não a aranha nem a caveira. Espelhar o `
` inteiro (como era antes) virava a aranha de
cabeça pra baixo e fazia ela "cair" pra cima ao morrer (bug relatado pelo
usuário): `scale(-1,-1)` em qualquer ancestral inverte literalmente tudo
dentro, inclusive a direção "pra baixo" das animações de queda/subida.
Por isso a aranha e a caveira têm sua própria posição "espelhada" abaixo
(variáveis `--rx`/`--ry`/`--rr`/`--ox`/`--oy` e `--sx`/`--sy`,
recalculadas pra esse canto), sem nenhum `scale`/`rotate` adicional nelas
— assim ficam sempre "em pé" e caem/sobem na direção real da tela nos
dois cantos. `transform-box:view-box` garante que o centro (100,100) do
espelhamento é sempre o centro do viewBox autorado, não a caixa
delimitadora visual dos fios (que dispararia pro lado errado por causa
do fio "perdido", bem mais comprido que o resto). */
.pw-corner--br .pw-strands {
transform-box: view-box;
transform-origin: 100px 100px;
transform: scale(-1, -1);
}
/* Na Principal, a topbar (`position:sticky`, `z-index:20`, fundo opaco —
layout.css) pintava por cima da metade de cima da teia do canto
superior-esquerdo, escondendo a aranha; empurra a teia pra começar
logo abaixo dela (`var(--topbar-height)`, mesmo token que a topbar já
usa). Não se aplica ao canto inferior-direito (nada cobre o rodapé) nem
ao login (sem topbar). */
.pw-corners--portal .pw-corner--tl {
top: var(--topbar-height);
}
.pw-corners--login .pw-corner {
width: clamp(220px, 22vw, 320px);
}
.pw-corners--portal .pw-corner {
width: clamp(160px, 15vw, 230px);
}
.pw-corner svg {
display: block;
width: 100%;
height: auto;
overflow: visible;
}
.pw-strands {
fill: none;
stroke: rgba(242, 237, 227, 0.24);
stroke-width: 0.7;
stroke-linecap: round;
animation: pwShimmer 6.5s ease-in-out infinite;
}
.pw-strand-stray {
stroke: rgba(242, 237, 227, 0.16);
}
.pw-ring {
stroke: rgba(242, 237, 227, 0.16);
stroke-width: 0.6;
}
.pw-legs {
stroke: #17111d;
stroke-width: 1.6;
stroke-linecap: round;
fill: none;
transform-box: fill-box;
transform-origin: center;
animation: pwLegsWiggle 0.85s ease-in-out infinite;
}
.pw-abdomen,
.pw-head {
fill: #17111d;
}
.pw-eye {
fill: #9e3241;
}
.pw-spider {
/* Posição/ângulo de descanso (--r*) e do "passeio" pra fora (--o*) — o
canto inferior-direito redefine essas 4 variáveis abaixo com os valores
espelhados (200-x, 200-y, +180deg), em vez de espelhar a aranha via
`transform` (ver comentário grande em `.pw-corner--br .pw-strands`). */
--rx: 85px;
--ry: 113px;
--rr: 51deg;
--ox: 138px;
--oy: 178px;
transform: translate(var(--rx), var(--ry)) rotate(var(--rr));
animation: pwWalk 8s ease-in-out infinite;
/* Corpo/pernas são quase a mesma cor do fundo escuro padrão (#17111d vs
--bg-canvas #121017) — sem esse brilho claro a aranha praticamente some,
sobrando só o olho vermelho visível. Removido no tema claro abaixo
(lá o contraste já é natural: aranha escura sobre fundo claro). */
filter: drop-shadow(0 0 2px rgba(255, 255, 255, 0.6)) drop-shadow(0 0 5px rgba(255, 255, 255, 0.3));
/* Única parte "viva" com `pointer-events:auto` — o resto da teia
(`.pw-corners`/`.pw-corner`) é `pointer-events:none`, decorativo. */
pointer-events: auto;
cursor: pointer;
}
.pw-corner--br .pw-spider {
--rx: 115px;
--ry: 87px;
--rr: 231deg;
--ox: 62px;
--oy: 22px;
}
/* Clicar mata a aranha (pedido explícito do usuário) — some caindo/girando,
pernas encolhem, e uma caveirinha sobe da teia (ver `.pw-skull` abaixo);
a teia fica vazia pelo resto desta visita à página (só volta com um F5
de verdade — `pidApplyHalloweenCobwebs()`, seasonal-theme.js, reconstrói
tudo do zero a cada carregamento). `forwards` mantém o estado final
(invisível) depois que a animação termina, sem precisar de mais JS. Os
deslocamentos em `calc()` são sempre "queda pra baixo" (`--ry` + valor
positivo) em relação ao próprio ponto de descanso do canto, então caem
do jeito certo nos dois cantos sem precisar de uma segunda animação. */
.pw-spider.is-dying {
animation: pwSpiderDie 950ms ease-in forwards;
pointer-events: none;
}
/* Aranha já morta numa visita anterior (persistido em `localStorage`, ver
`pidGetDeadSpiders()`/`pidApplyHalloweenCobwebs()` em seasonal-theme.js)
— nasce direto no estado final, sem tocar `pwSpiderDie`/`pwWalk` de novo;
a caveira nem chega a aparecer (já subiu e sumiu na visita em que a
aranha morreu, não faz sentido reprisar isso a cada carregamento). Só
volta com o atalho Tab+B. */
.pw-spider.is-dead {
animation: none;
opacity: 0;
pointer-events: none;
}
.pw-spider.is-dying .pw-legs {
animation: pwLegsDie 450ms ease-out forwards;
}
.pw-skull {
--sx: 85px;
--sy: 105px;
opacity: 0;
transform: translate(var(--sx), var(--sy)) scale(0.4);
transform-box: fill-box;
transform-origin: center;
pointer-events: none;
}
.pw-corner--br .pw-skull {
--sx: 115px;
--sy: 95px;
}
.pw-skull circle {
fill: #f2ede3;
}
.pw-skull-socket {
fill: #17111d;
}
.pw-skull path {
stroke: #17111d;
stroke-width: 0.9;
fill: none;
stroke-linecap: round;
stroke-linejoin: round;
}
.pw-skull.is-rising {
animation: pwSkullRise 1300ms ease-out forwards;
}
/* Tema claro: a teia (cor creme, pensada pra contrastar com fundo escuro)
ficaria invisível sobre o fundo branco do tema claro — vira escura. A
aranha já é escura por natureza (#17111d), então já contrasta sozinha
contra um fundo claro; o brilho branco de cima (pensado pro fundo escuro)
só seria ruído aqui, por isso desligado. */
:root[data-theme="light"] .pw-strands {
stroke: rgba(20, 16, 28, 0.32);
}
:root[data-theme="light"] .pw-strand-stray,
:root[data-theme="light"] .pw-ring {
stroke: rgba(20, 16, 28, 0.2);
}
:root[data-theme="light"] .pw-spider {
filter: none;
}
@keyframes pwShimmer {
0%, 100% {
opacity: 0.85;
}
50% {
opacity: 1;
}
}
@keyframes pwLegsWiggle {
0%, 100% {
transform: rotate(0deg) scaleY(1);
}
50% {
transform: rotate(1.4deg) scaleY(0.95);
}
}
@keyframes pwWalk {
0%, 14% {
transform: translate(var(--rx), var(--ry)) rotate(var(--rr));
}
32%, 55% {
transform: translate(var(--ox), var(--oy)) rotate(var(--rr));
}
74%, 100% {
transform: translate(var(--rx), var(--ry)) rotate(var(--rr));
}
}
@keyframes pwSpiderDie {
0% {
transform: translate(var(--rx), var(--ry)) rotate(var(--rr));
opacity: 1;
}
45% {
transform: translate(calc(var(--rx) + 8px), calc(var(--ry) + 52px)) rotate(calc(var(--rr) + 99deg));
opacity: 1;
}
100% {
transform: translate(calc(var(--rx) + 13px), calc(var(--ry) + 117px)) rotate(calc(var(--rr) + 209deg));
opacity: 0;
}
}
@keyframes pwLegsDie {
to {
transform: rotate(22deg) scaleY(0.72);
}
}
@keyframes pwSkullRise {
0% {
opacity: 0;
transform: translate(var(--sx), var(--sy)) scale(0.35);
}
20% {
opacity: 1;
transform: translate(var(--sx), calc(var(--sy) - 15px)) scale(1.15);
}
32% {
transform: translate(var(--sx), calc(var(--sy) - 11px)) scale(1);
}
78% {
opacity: 1;
transform: translate(var(--sx), calc(var(--sy) - 37px)) scale(1);
}
100% {
opacity: 0;
transform: translate(var(--sx), calc(var(--sy) - 47px)) scale(0.9);
}
}