/* Topbar logo animations. The SVG is INLINE in base.html (not an : that runs in the browser's secure static mode, no scripts, so its timing could only ever be a fixed CSS loop). logo_anim.js adds one v-* class at a time, waits out its duration, removes it — so every keyframe set here runs ONCE and lands back on the resting pose. Y axis only on the eyes (never X); each antenna pivots where it leaves the head. */ /* Dark mode: the antennas (stem + ball) are the only ink that sits OUTSIDE the white head, on the dark page — the #1b2330 brand ink makes them vanish there. Lift just those to a light tone (CSS wins over the inline stroke/fill). Never touch the eyes/wink: they are dark ON the white head, and must stay dark. */ :root[data-theme="dark"] .lg-ant-l path, :root[data-theme="dark"] .lg-ant-r path { stroke: #e8eaed; } :root[data-theme="dark"] .lg-ball { fill: #e8eaed; } .lg-ant-l { transform-box: fill-box; transform-origin: 100% 100%; } .lg-ant-r { transform-box: fill-box; transform-origin: 0% 100%; } .lg-eye, .lg-wink { transform-box: fill-box; transform-origin: center; } .lg-wink { opacity: 0; } /* the wink curve only shows while an eye is shut */ /* whole-mark and head-only groups, for the body moves */ .lg-all { transform-box: fill-box; transform-origin: center bottom; } .lg-hg { transform-box: fill-box; transform-origin: 27px 44.2px; } /* the HEAD centre: the antennas now live inside this group, whose fill-box grew upward, so `center` would pivot above the head */ /* ===== the antennas never go still ===== A permanent, tiny breath on the PARENT group of each antenna. It has to be the parent: a move (lg-ant-*) animates `transform` too, and two animations on one element fight — nested, the two rotations simply compose. Both pivot on the same point (where the antenna leaves the head), and their periods share no factor (7.3s / 11.9s), so the pair never falls into a metronome beat. */ .lg-sway-l { transform-box: fill-box; transform-origin: 100% 100%; animation: lgSwayL 7.3s ease-in-out infinite; } .lg-sway-r { transform-box: fill-box; transform-origin: 0% 100%; animation: lgSwayR 11.9s ease-in-out infinite; } @keyframes lgSwayL { 0%, 100% { transform: rotate(-5deg) } 50% { transform: rotate(5deg) } } @keyframes lgSwayR { 0%, 100% { transform: rotate(4deg) } 50% { transform: rotate(-4deg) } } /* ================= 1. WINK — the signature (1.1s) ================= */ .v-wink .lg-eye-r { animation: lgWinkEye 1.1s ease-in-out 1 both; } .v-wink .lg-wink { animation: lgWinkCurve 1.1s ease-in-out 1 both; } .v-wink .lg-ant-r { animation: lgWinkAnt 1.1s ease-in-out 1 both; } @keyframes lgWinkEye { 0% { transform: scaleY(1); opacity: 1 } 9% { transform: scaleY(.12); opacity: 0 } /* snaps shut, 100ms */ 64% { transform: scaleY(.12); opacity: 0 } /* held 600ms as the curve */ 77% { transform: scaleY(1.25); opacity: 1 } /* springs past rest */ 86% { transform: scaleY(.8); opacity: 1 } /* bounces under it */ 100% { transform: scaleY(1); opacity: 1 } /* settles */ } @keyframes lgWinkCurve { 0%, 8% { opacity: 0 } 9%, 64% { opacity: 1 } 70%, 100% { opacity: 0 } } @keyframes lgWinkAnt { 0% { transform: rotate(0) } 20% { transform: rotate(-6deg) } 60% { transform: rotate(3deg) } 100% { transform: rotate(0) } } /* ================= 2. LOOK — follows someone across the room (2.6s) ================= */ .v-look .lg-eye-l, .v-look .lg-eye-r { animation: lgLookEye 2.6s ease-in-out 1 both; } .v-look .lg-ant-l { animation: lgLookAntL 2.6s ease-in-out 1 both; } .v-look .lg-ant-r { animation: lgLookAntR 2.6s ease-in-out 1 both; } @keyframes lgLookEye { 0%, 8% { transform: translateX(0) } 24%, 38% { transform: translateX(3.2px) } 56%, 72% { transform: translateX(-3.2px) } 92%, 100% { transform: translateX(0) } } @keyframes lgLookAntL { 0%, 20% { transform: rotate(0) } 40% { transform: rotate(-5deg) } 70% { transform: rotate(2deg) } 100% { transform: rotate(0) } } @keyframes lgLookAntR { 0%, 30% { transform: rotate(0) } 55% { transform: rotate(5deg) } 80% { transform: rotate(-2deg) } 100% { transform: rotate(0) } } /* ================= 3. DOUBLE BLINK — both eyes, twice (0.9s) ================= */ .v-blink .lg-eye-l, .v-blink .lg-eye-r { animation: lgBlinkEye .9s ease-in-out 1 both; } .v-blink .lg-ant-l { animation: lgBlinkAnt .9s ease-in-out 1 both; } @keyframes lgBlinkEye { 0% { transform: scaleY(1) } 12% { transform: scaleY(.08) } 24% { transform: scaleY(1) } 40% { transform: scaleY(.08) } 55% { transform: scaleY(1.15) } 72%, 100% { transform: scaleY(1) } } @keyframes lgBlinkAnt { 0% { transform: rotate(0) } 30% { transform: rotate(-4deg) } 60% { transform: rotate(3deg) } 100% { transform: rotate(0) } } /* ================= 5. SLEEPY — lids droop, then a startle (2.4s) ================= */ .v-sleepy .lg-eye-l, .v-sleepy .lg-eye-r { animation: lgSleepEye 2.4s ease-in 1 both; } .v-sleepy .lg-ant-l { animation: lgSleepAntL 2.4s ease-in-out 1 both; } .v-sleepy .lg-ant-r { animation: lgSleepAntR 2.4s ease-in-out 1 both; } @keyframes lgSleepEye { 0% { transform: scaleY(1) } 45% { transform: scaleY(.45) } /* the lids slide down… */ 62% { transform: scaleY(.1) } 68% { transform: scaleY(1.3) } /* …startle! */ 76% { transform: scaleY(.9) } 86%, 100% { transform: scaleY(1) } } @keyframes lgSleepAntL { 0% { transform: rotate(0) } 55% { transform: rotate(9deg) } 68% { transform: rotate(-6deg) } 100% { transform: rotate(0) } } @keyframes lgSleepAntR { 0% { transform: rotate(0) } 55% { transform: rotate(-9deg) } 68% { transform: rotate(6deg) } 100% { transform: rotate(0) } } /* ================= 6. EXCITED — antennas buzz, eyes go wide (1.8s) ================= */ .v-excited .lg-ant-l { animation: lgExAntL 1.8s ease-in-out 1 both; } .v-excited .lg-ant-r { animation: lgExAntR 1.8s ease-in-out 1 both; } .v-excited .lg-eye-l, .v-excited .lg-eye-r { animation: lgExEye 1.8s ease-in-out 1 both; } @keyframes lgExAntL { 0% { transform: rotate(0) } 8% { transform: rotate(-7deg) } 16% { transform: rotate(7deg) } 24% { transform: rotate(-6deg) } 32% { transform: rotate(6deg) } 40% { transform: rotate(-5deg) } 48% { transform: rotate(5deg) } 56% { transform: rotate(-4deg) } 64% { transform: rotate(4deg) } 72% { transform: rotate(-2deg) } 80% { transform: rotate(2deg) } 90%, 100% { transform: rotate(0) } } @keyframes lgExAntR { 0% { transform: rotate(0) } 8% { transform: rotate(7deg) } 16% { transform: rotate(-7deg) } 24% { transform: rotate(6deg) } 32% { transform: rotate(-6deg) } 40% { transform: rotate(5deg) } 48% { transform: rotate(-5deg) } 56% { transform: rotate(4deg) } 64% { transform: rotate(-4deg) } 72% { transform: rotate(2deg) } 80% { transform: rotate(-2deg) } 90%, 100% { transform: rotate(0) } } @keyframes lgExEye { 0% { transform: scale(1) } 18% { transform: scale(1.28) } 70% { transform: scale(1.28) } 100% { transform: scale(1) } } /* ================= 9. BOOT — wakes up, heavy lids first (2.2s) ================= */ /* eyes start shut, crack open, droop back (too heavy), second try, then a wide stretch past rest. The right eye wakes a beat after the left (the asymmetry canon), and the antennas rise from their sleep droop as the eyes open. */ .v-boot .lg-eye-l { animation: lgWakeL 2.2s ease-in-out 1 both; } .v-boot .lg-eye-r { animation: lgWakeR 2.2s ease-in-out 1 both; } .v-boot .lg-ant-l { animation: lgWakeAntL 2.2s ease-in-out 1 both; } .v-boot .lg-ant-r { animation: lgWakeAntR 2.2s ease-in-out 1 both; } @keyframes lgWakeL { 0% { transform: scaleY(.08) } /* asleep */ 22% { transform: scaleY(.45) } /* first crack */ 38% { transform: scaleY(.16) } /* lids too heavy, droops back */ 58% { transform: scaleY(.55) } /* second try */ 74% { transform: scaleY(1.25) } /* wide awake stretch, past rest */ 86% { transform: scaleY(.92) } 100% { transform: scaleY(1) } } @keyframes lgWakeR { 0%, 12% { transform: scaleY(.08) } /* still asleep a beat longer */ 32% { transform: scaleY(.4) } 46% { transform: scaleY(.14) } 64% { transform: scaleY(.5) } 80% { transform: scaleY(1.2) } 90% { transform: scaleY(.88) } 100% { transform: scaleY(1) } } @keyframes lgWakeAntL { 0%, 30% { transform: rotate(-12deg) } /* drooped in sleep */ 70% { transform: rotate(4deg) } /* rises with the eyes */ 85% { transform: rotate(-2deg) } 100% { transform: rotate(0) } } @keyframes lgWakeAntR { 0%, 40% { transform: rotate(10deg) } /* the smaller one lags here too */ 76% { transform: rotate(-3deg) } 90% { transform: rotate(1.5deg) } 100% { transform: rotate(0) } } @media (prefers-reduced-motion: reduce) { .topbar-logo * { animation: none !important; } } /* ================= 10. SQUINT — goes cross-eyed a moment (2s) ================= */ .v-squint .lg-eye-l { animation: lgSquintL 2s ease-in-out 1 both; } .v-squint .lg-eye-r { animation: lgSquintR 2s ease-in-out 1 both; } @keyframes lgSquintL { 0%, 8% { transform: translateX(0) } 20%, 72% { transform: translateX(4.5px) } /* both eyes dive toward the nose */ 88%, 100% { transform: translateX(0) } } @keyframes lgSquintR { 0%, 8% { transform: translateX(0) } 20%, 72% { transform: translateX(-4.5px) } 88%, 100% { transform: translateX(0) } } /* ================= 11. GLASSES — puts its glasses on (2.8s) ================= */ /* the .lg-glasses group sits in the SVG, invisible at rest; the move drops it onto the eyes with a tiny overshoot, wears it a moment, lifts it away */ .lg-glasses { transform-box: fill-box; transform-origin: center; opacity: 0; } .v-glasses .lg-glasses { animation: lgGlasses 2.8s ease-in-out 1 both; } @keyframes lgGlasses { 0% { opacity: 0; transform: translateY(-28px) } 10% { opacity: 1; transform: translateY(2.5px) } /* lands, slight overshoot */ 16% { transform: translateY(0) } 72% { opacity: 1; transform: translateY(0) } /* worn ~1.6s */ 86%, 100% { opacity: 0; transform: translateY(-28px) } /* lifted off */ } /* ================= 12. PENDULE — the whole mark swings and settles (2.6s) ================= */ .v-pendule .lg-all { animation: lgPend 2.6s ease-in-out 1 both; } @keyframes lgPend { 0%{transform:rotate(0)} 18%{transform:rotate(-6deg)} 42%{transform:rotate(5deg)} 64%{transform:rotate(-3deg)} 82%{transform:rotate(1.5deg)} 100%{transform:rotate(0)} } /* ================= 13. TWIST — head one way, eyes the other (1.8s) ================= */ .v-twist .lg-hg { animation: lgTwistH 1.8s ease-in-out 1 both; } .v-twist .lg-eye-l, .v-twist .lg-eye-r { animation: lgTwistE 1.8s ease-in-out 1 both; } @keyframes lgTwistH { 0%,100%{transform:rotate(0)} 16%{transform:rotate(-8deg)} 40%{transform:rotate(8deg)} 64%{transform:rotate(-5deg)} 84%{transform:rotate(2deg)} } @keyframes lgTwistE { 0%,100%{transform:translateX(0)} 16%{transform:translateX(2.4px)} 40%{transform:translateX(-2.4px)} 64%{transform:translateX(1.5px)} 84%{transform:translateX(-.6px)} } /* ================= 14. SHIMMY — a lateral head vibration burst (1.3s) ================= */ .v-shimmy .lg-hg { animation: lgShimmy 1.3s linear 1 both; } @keyframes lgShimmy { 0%,100%{transform:translateX(0)} 8%,24%,40%,56%,72%{transform:translateX(-2.2px)} 16%,32%,48%,64%,84%{transform:translateX(2.2px)} } /* ================= 15. PIROUETTE — a full 360, bold (1.6s) ================= */ .v-pirouette .lg-all { transform-origin: center; animation: lgPir 1.6s cubic-bezier(.5,.1,.2,1) 1 both; } @keyframes lgPir { 0%{transform:rotate(0)} 12%{transform:rotate(-8deg)} 88%,100%{transform:rotate(360deg)} } /* ================= 16. BOING — the antennas spring, one after the other (1.7s) ================= */ .v-boing .lg-ant-l { animation: lgBoingL 1.7s ease-out 1 both; } .v-boing .lg-ant-r { animation: lgBoingR 1.7s ease-out 1 both; } @keyframes lgBoingL { 0%,100%{transform:scaleY(1)} 12%{transform:scaleY(.68)} 26%{transform:scaleY(1.18)} 40%{transform:scaleY(.9)} 54%{transform:scaleY(1.05)} 70%{transform:scaleY(1)} } @keyframes lgBoingR { 0%,10%{transform:scaleY(1)} 22%{transform:scaleY(.68)} 36%{transform:scaleY(1.18)} 50%{transform:scaleY(.9)} 64%{transform:scaleY(1.05)} 80%,100%{transform:scaleY(1)} } /* ================= 17. COUCOU-TÊTE — sinks out of frame, pops back (2.6s) ================= */ .v-coucoutete .lg-all { animation: lgPeek 2.6s ease-in-out 1 both; } @keyframes lgPeek { 0%,6%{transform:translateY(0)} 28%,55%{transform:translateY(15px)} 70%{transform:translateY(-3px)} 82%{transform:translateY(1px)} 100%{transform:translateY(0)} } /* ================= 18. CURIEUX — the head shifts over to see better (2.4s) ================= */ .v-curieux .lg-hg { animation: lgCurH 2.4s ease-in-out 1 both; } .v-curieux .lg-eye-l, .v-curieux .lg-eye-r { animation: lgCurE 2.4s ease-in-out 1 both; } @keyframes lgCurH { 0%,100%{transform:translateX(0)} 18%,70%{transform:translateX(5px)} } @keyframes lgCurE { 0%,100%{transform:translateX(0)} 18%,70%{transform:translateX(2.4px)} }