feat: Add peeking Bongo Cat Easter Egg with micro-animations
All checks were successful
Build & Push Docker Image to Gitea Registry / build-and-push (push) Successful in 11s

This commit is contained in:
Kroonk
2026-05-21 17:41:28 +02:00
parent e9e589ddcc
commit 8946cbecf6
3 changed files with 224 additions and 2 deletions

64
app.js
View File

@@ -232,3 +232,67 @@ loadConfig()
dashboardSections.innerHTML = '<p class="empty-state">Dashboard-Konfiguration konnte nicht geladen werden.</p>'; dashboardSections.innerHTML = '<p class="empty-state">Dashboard-Konfiguration konnte nicht geladen werden.</p>';
onlineCount.textContent = '--'; onlineCount.textContent = '--';
}); });
// ==========================================================================
// Peeking Bongo Cat Easter Egg Logic
// ==========================================================================
(function initPeekingCat() {
const peekingCat = document.querySelector('#peekingCat');
const searchInput = document.querySelector('#serviceSearch');
if (!peekingCat) return;
let tapTimeout = null;
// Function to trigger heart popup on click
function createHeart(x, y) {
const heart = document.createElement('div');
heart.className = 'cat-heart';
heart.innerHTML = '❤️';
heart.style.left = `${x - 12}px`;
heart.style.top = `${y - 20}px`;
// Set random rotation angle in CSS custom variable
const randomRot = Math.random() * 40 - 20;
heart.style.setProperty('--rot', `${randomRot}deg`);
document.body.appendChild(heart);
// Cleanup heart after animation completes
setTimeout(() => heart.remove(), 800);
}
// Focus search box triggers peeking active state
if (searchInput) {
searchInput.addEventListener('focus', () => {
peekingCat.classList.add('is-active');
});
searchInput.addEventListener('blur', () => {
peekingCat.classList.remove('is-active');
peekingCat.classList.remove('is-tapping');
});
// Keyboard typing triggers tapping animation!
searchInput.addEventListener('input', () => {
peekingCat.classList.add('is-active');
peekingCat.classList.add('is-tapping');
if (tapTimeout) clearTimeout(tapTimeout);
tapTimeout = setTimeout(() => {
peekingCat.classList.remove('is-tapping');
}, 350); // Stop tapping 350ms after typing stops
});
}
// Click on cat triggers tapping + heart animation!
peekingCat.addEventListener('click', (e) => {
peekingCat.classList.add('is-tapping');
createHeart(e.clientX, e.clientY);
if (tapTimeout) clearTimeout(tapTimeout);
tapTimeout = setTimeout(() => {
peekingCat.classList.remove('is-tapping');
}, 800);
});
})();

View File

@@ -13,7 +13,7 @@
<link rel="apple-touch-icon" href="/icons/apple-touch-icon-v2.png"> <link rel="apple-touch-icon" href="/icons/apple-touch-icon-v2.png">
<link rel="icon" type="image/png" sizes="192x192" href="/icons/icon-192.png"> <link rel="icon" type="image/png" sizes="192x192" href="/icons/icon-192.png">
<link rel="icon" type="image/png" sizes="512x512" href="/icons/icon-512.png"> <link rel="icon" type="image/png" sizes="512x512" href="/icons/icon-512.png">
<link rel="stylesheet" href="style.css?v=18"> <link rel="stylesheet" href="style.css?v=19">
</head> </head>
<body> <body>
<header class="shell hero"> <header class="shell hero">
@@ -73,6 +73,50 @@
<span id="lastChecked">Status wird im Hintergrund geprueft.</span> <span id="lastChecked">Status wird im Hintergrund geprueft.</span>
</footer> </footer>
<script src="/app.js?v=4" defer></script> <!-- Peeking Bongo Cat Easter Egg -->
<div class="peeking-cat" id="peekingCat" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 100" width="120" height="100">
<!-- Left Ear -->
<path class="cat-ear cat-ear-left" d="M 25 50 L 10 15 L 45 35 Z" fill="#ffffff" stroke="#1e293b" stroke-width="3" stroke-linejoin="round" />
<path class="cat-ear cat-ear-left-inner" d="M 28 45 L 17 21 L 40 34 Z" fill="#fecdd3" />
<!-- Right Ear -->
<path class="cat-ear cat-ear-right" d="M 95 50 L 110 15 L 75 35 Z" fill="#ffffff" stroke="#1e293b" stroke-width="3" stroke-linejoin="round" />
<path class="cat-ear cat-ear-right-inner" d="M 92 45 L 103 21 L 80 34 Z" fill="#fecdd3" />
<!-- Face/Head -->
<path class="cat-head" d="M 15 70 C 15 25, 105 25, 105 70 L 15 70 Z" fill="#ffffff" stroke="#1e293b" stroke-width="3" stroke-linejoin="round" />
<!-- Cheeks -->
<ellipse class="cat-blush" cx="28" cy="58" rx="8" ry="5" fill="#fca5a5" opacity="0.6" />
<ellipse class="cat-blush" cx="92" cy="58" rx="8" ry="5" fill="#fca5a5" opacity="0.6" />
<!-- Eyes -->
<g class="cat-eye-group">
<ellipse class="cat-eye cat-eye-l" cx="42" cy="50" rx="4" ry="5" fill="#1e293b" />
<circle class="cat-eye-shine cat-eye-shine-l" cx="40" cy="48" r="1.2" fill="#ffffff" />
</g>
<g class="cat-eye-group">
<ellipse class="cat-eye cat-eye-r" cx="78" cy="50" rx="4" ry="5" fill="#1e293b" />
<circle class="cat-eye-shine cat-eye-shine-r" cx="76" cy="48" r="1.2" fill="#ffffff" />
</g>
<!-- Nose/Mouth -->
<path class="cat-mouth" d="M 57 54 Q 60 57 63 54 Q 66 57 69 54" fill="none" stroke="#1e293b" stroke-width="2.5" stroke-linecap="round" />
<!-- Whiskers -->
<line class="cat-whisker" x1="12" y1="52" x2="3" y2="50" stroke="#1e293b" stroke-width="2" stroke-linecap="round" />
<line class="cat-whisker" x1="11" y1="58" x2="2" y2="58" stroke="#1e293b" stroke-width="2" stroke-linecap="round" />
<line class="cat-whisker" x1="108" y1="52" x2="117" y2="50" stroke="#1e293b" stroke-width="2" stroke-linecap="round" />
<line class="cat-whisker" x1="109" y1="58" x2="118" y2="58" stroke="#1e293b" stroke-width="2" stroke-linecap="round" />
<!-- Paws -->
<path class="cat-paw cat-paw-left" d="M 30 70 C 30 58, 46 58, 46 70 Z" fill="#ffffff" stroke="#1e293b" stroke-width="3" stroke-linejoin="round" />
<path class="cat-paw cat-paw-right" d="M 74 70 C 74 58, 90 58, 90 70 Z" fill="#ffffff" stroke="#1e293b" stroke-width="3" stroke-linejoin="round" />
</svg>
</div>
<script src="/app.js?v=5" defer></script>
</body> </body>
</html> </html>

114
style.css
View File

@@ -820,3 +820,117 @@ main {
animation-iteration-count: 1 !important; animation-iteration-count: 1 !important;
} }
} }
/* ==========================================================================
Peeking Bongo Cat Easter Egg
========================================================================== */
.peeking-cat {
position: fixed;
bottom: -45px;
left: 24px;
width: 120px;
height: 100px;
z-index: 1000;
cursor: pointer;
transition: bottom 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275), transform 0.3s ease;
user-select: none;
pointer-events: auto;
}
.peeking-cat:hover {
bottom: -15px;
transform: scale(1.06);
}
.peeking-cat.is-active {
bottom: -15px;
}
/* Blinking eyes animation */
@keyframes cat-blink {
0%, 90%, 100% {
transform: scaleY(1);
}
95% {
transform: scaleY(0.1);
}
}
.cat-eye-group {
transform-origin: 42px 50px;
animation: cat-blink 4s infinite;
}
.cat-eye-group:nth-child(2) {
transform-origin: 78px 50px;
}
/* Ear wiggle animation on hover */
@keyframes ear-wiggle-l {
0%, 100% { transform: rotate(0deg); }
50% { transform: rotate(-6deg); }
}
@keyframes ear-wiggle-r {
0%, 100% { transform: rotate(0deg); }
50% { transform: rotate(6deg); }
}
.peeking-cat:hover .cat-ear-left,
.peeking-cat:hover .cat-ear-left-inner {
animation: ear-wiggle-l 0.4s ease-in-out infinite alternate;
transform-origin: 35px 42px;
}
.peeking-cat:hover .cat-ear-right,
.peeking-cat:hover .cat-ear-right-inner {
animation: ear-wiggle-r 0.4s ease-in-out infinite alternate;
transform-origin: 85px 42px;
}
/* Paw tapping animation (Bongo Cat typing style) */
@keyframes paw-tap-left {
0%, 100% { transform: translateY(0) rotate(0); }
50% { transform: translateY(-10px) rotate(-3deg); }
}
@keyframes paw-tap-right {
0%, 100% { transform: translateY(0) rotate(0); }
50% { transform: translateY(-10px) rotate(3deg); }
}
.peeking-cat.is-tapping .cat-paw-left {
animation: paw-tap-left 0.14s ease-in-out infinite alternate;
transform-origin: 38px 70px;
}
.peeking-cat.is-tapping .cat-paw-right {
animation: paw-tap-right 0.14s ease-in-out infinite alternate-reverse;
transform-origin: 82px 70px;
}
/* Floating Hearts on click */
.cat-heart {
position: fixed;
pointer-events: none;
z-index: 10001;
font-size: 20px;
animation: heart-float 0.8s cubic-bezier(0.1, 0.8, 0.3, 1) forwards;
}
@keyframes heart-float {
0% {
transform: translateY(0) scale(0.6) rotate(0deg);
opacity: 1;
}
100% {
transform: translateY(-80px) scale(1.4) rotate(var(--rot, 15deg));
opacity: 0;
}
}
/* Responsive adjustment for mobile screen */
@media (max-width: 620px) {
.peeking-cat {
width: 90px;
height: 75px;
bottom: -35px;
left: 12px;
}
.peeking-cat:hover,
.peeking-cat.is-active {
bottom: -10px;
}
}