diff options
| author | hrbrmstr <bob@rud.is> | 2025-12-31 11:30:26 -0500 |
|---|---|---|
| committer | hrbrmstr <bob@rud.is> | 2025-12-31 11:30:26 -0500 |
| commit | b70adecfaab54d3ac747a7aa2f96b1169babcdd4 (patch) | |
| tree | b3820494454f2c080f2d285538b9dbf118421695 | |
| parent | ee879420d132b9195a0de7ab62749aba55ae780f (diff) | |
add: dropped
| -rw-r--r-- | 2025/2025-12-25-dropped-animation.html | 190 |
1 files changed, 190 insertions, 0 deletions
diff --git a/2025/2025-12-25-dropped-animation.html b/2025/2025-12-25-dropped-animation.html new file mode 100644 index 0000000..53a7f36 --- /dev/null +++ b/2025/2025-12-25-dropped-animation.html @@ -0,0 +1,190 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <title>2025 — Dropped</title> + <link rel="preconnect" href="https://fonts.googleapis.com" /> + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> + <link href="https://fonts.googleapis.com/css2?family=Londrina+Outline&display=swap" rel="stylesheet" /> + <style> + * { + margin: 0; + padding: 0; + box-sizing: border-box; + } + + body { + background: #0a0a0a; + min-height: 100vh; + overflow: hidden; + font-family: "Londrina Outline", system-ui, sans-serif; + } + + .container { + position: relative; + width: 100%; + height: 100vh; + } + + .year { + position: absolute; + top: 10%; + left: 50%; + transform: translateX(-50%); + display: flex; + gap: 0.5vw; + } + + .digit { + font-size: 22vw; + font-weight: 400; + color: #fff; + line-height: 1; + position: relative; + } + + .year.dropping { + animation: drop 1.4s cubic-bezier(0.55, 0, 1, 0.45) forwards; + } + + @keyframes drop { + 0% { + top: 10%; + } + 100% { + top: calc(100vh - 24vw); + } + } + + .shard { + position: absolute; + font-size: 22vw; + font-weight: 400; + color: #fff; + line-height: 1; + font-family: "Londrina Outline", system-ui, sans-serif; + clip-path: polygon(var(--clip)); + animation: shard-fall var(--duration) cubic-bezier(0.25, 0, 0.5, 1) forwards; + animation-delay: var(--delay); + opacity: 1; + } + + @keyframes shard-fall { + 0% { + transform: translate(0, 0) rotate(0deg); + opacity: 1; + } + 100% { + transform: translate(var(--tx), var(--ty)) rotate(var(--rot)); + opacity: 0; + } + } + + .restart { + position: fixed; + bottom: 30px; + left: 50%; + transform: translateX(-50%); + background: transparent; + border: 1px solid #333; + color: #666; + padding: 10px 24px; + font-size: 14px; + cursor: pointer; + transition: all 0.2s; + opacity: 0; + z-index: 50; + } + + .restart.visible { + opacity: 1; + } + + .restart:hover { + border-color: #fff; + color: #fff; + } + </style> + </head> + <body> + <div class="container"> + <div class="year" id="year"> + <span class="digit" data-char="2">2</span> + <span class="digit" data-char="0">0</span> + <span class="digit" data-char="2">2</span> + <span class="digit" data-char="5">5</span> + </div> + </div> + <button class="restart" id="restart">drop again</button> + + <script> + const year = document.getElementById("year"); + const restartBtn = document.getElementById("restart"); + const container = document.querySelector(".container"); + + const shardConfigs = [ + { clip: "0% 0%, 50% 0%, 30% 50%, 0% 40%", tx: -120, ty: 180, rot: -25 }, + { clip: "50% 0%, 100% 0%, 100% 35%, 60% 50%", tx: 80, ty: 200, rot: 35 }, + { clip: "0% 40%, 30% 50%, 40% 100%, 0% 100%", tx: -90, ty: 250, rot: -40 }, + { clip: "30% 50%, 60% 50%, 70% 100%, 40% 100%", tx: 20, ty: 300, rot: 10 }, + { clip: "60% 50%, 100% 35%, 100% 100%, 70% 100%", tx: 100, ty: 220, rot: 45 }, + ]; + + function createShards(digit, index) { + const rect = digit.getBoundingClientRect(); + const char = digit.dataset.char; + + shardConfigs.forEach((config, i) => { + const shard = document.createElement("span"); + shard.className = "shard"; + shard.textContent = char; + shard.style.left = rect.left + "px"; + shard.style.top = rect.top + "px"; + shard.style.setProperty("--clip", config.clip); + shard.style.setProperty("--tx", config.tx + (Math.random() - 0.5) * 60 + "px"); + shard.style.setProperty("--ty", config.ty + Math.random() * 100 + "px"); + shard.style.setProperty("--rot", config.rot + (Math.random() - 0.5) * 30 + "deg"); + shard.style.setProperty("--duration", 1.2 + Math.random() * 0.6 + "s"); + shard.style.setProperty("--delay", i * 0.02 + index * 0.05 + "s"); + container.appendChild(shard); + }); + } + + function shatter() { + const digits = document.querySelectorAll(".digit"); + digits.forEach((digit, index) => { + createShards(digit, index); + }); + + year.style.opacity = "0"; + + setTimeout(() => { + restartBtn.classList.add("visible"); + }, 2000); + } + + function startAnimation() { + document.querySelectorAll(".shard").forEach((s) => s.remove()); + year.style.opacity = "1"; + year.classList.remove("dropping", "shattered"); + restartBtn.classList.remove("visible"); + + void year.offsetWidth; + + year.classList.add("dropping"); + + year.addEventListener("animationend", function handler(e) { + if (e.animationName === "drop") { + year.removeEventListener("animationend", handler); + shatter(); + } + }); + } + + restartBtn.addEventListener("click", startAnimation); + + setTimeout(startAnimation, 500); + </script> + </body> +</html> |
