.birthday-container { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; pointer-events: none; z-index: 9999; overflow: hidden; contain: strict; } .birthday-garland { position: absolute; top: -1vh; left: 0; width: 100vw; z-index: 55; pointer-events: none; /* optional: a little drop shadow for depth */ filter: drop-shadow(0 5px 8px rgba(0,0,0,0.5)); } .birthday-garland img { width: 100%; height: auto; object-fit: cover; } .birthday-cake { position: absolute; bottom: 2vh; left: 50vw; transform: translateX(-50%); font-size: 8rem; z-index: 50; filter: drop-shadow(0 0 10px rgba(255,255,255,0.4)); pointer-events: auto; } .birthday-cake img { height: 15vh; width: auto; object-fit: contain; max-height: 150px; } .birthday-symbol { position: absolute; bottom: -10vh; /* balloons rise from bottom */ animation: birthday-rise linear infinite; font-size: 3rem; opacity: 0.95; z-index: 40; pointer-events: none; /* Container itself should not block clicks */ } .birthday-inner { pointer-events: auto; /* Allow hover over the actual item */ cursor: crosshair; display: inline-block; } .birthday-symbol img { width: 6vh; height: auto; max-width: 60px; object-fit: contain; } .birthday-confetti { position: absolute; top: -5vh; width: 10px; height: 10px; opacity: 0.9; animation: birthday-confetti-fall linear infinite; z-index: 30; /* Mix of circles and squares by using CSS variables or random in JS. For simplicity, we make all slightly rounded rectangles */ border-radius: 2px; } @keyframes birthday-rise { 0% { transform: translateY(10vh) rotate(var(--start-rot, 0deg)); opacity: 0; } 10% { opacity: 1; } 90% { opacity: 1; } 100% { transform: translateY(-110vh) rotate(calc(var(--start-rot, 0deg) * -1)); opacity: 0; } } @keyframes birthday-confetti-fall { 0% { transform: translateY(-5vh) rotateX(0deg) rotateY(0deg) rotateZ(0deg); opacity: 0; } 5% { opacity: 1; } 90% { opacity: 1; } 100% { transform: translateY(105vh) rotateX(720deg) rotateY(360deg) rotateZ(180deg); opacity: 0; } } @keyframes birthday-sway { 0% { transform: rotate(-8deg) translateX(-5%); } 100% { transform: rotate(8deg) translateX(5%); } } @keyframes birthday-pop { 0% { transform: scale(1); opacity: 1; filter: brightness(1); } 30% { transform: scale(1.3); opacity: 1; filter: brightness(1.5); } 100% { transform: scale(0); opacity: 0; filter: brightness(2); } } .birthday-burst-confetti { position: absolute; pointer-events: none; z-index: 1000; will-change: transform, opacity; animation: birthday-burst-fall 1.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards; } .birthday-burst-confetti.circle { border-radius: 50%; } .birthday-burst-confetti.triangle { width: 0; height: 0; background-color: transparent !important; border-left: 5px solid transparent; border-right: 5px solid transparent; border-bottom: 10px solid var(--shape-color, #ff0000); } @keyframes birthday-burst-fall { 0% { transform: translate(0, 0) rotate3d(var(--rx), var(--ry), var(--rz), 0deg); opacity: 1; } 30% { transform: translate(var(--burst-x), var(--burst-y)) rotate3d(var(--rx), var(--ry), var(--rz), calc(var(--rot-dir) * 0.3)); opacity: 1; } 100% { transform: translate(var(--burst-x), calc(var(--burst-y) + 150px)) rotate3d(var(--rx), var(--ry), var(--rz), var(--rot-dir)); opacity: 0; } }