From d96c70be052a1df112859d2c4fa0ab25e94a847e Mon Sep 17 00:00:00 2001 From: Michael Czechowski Date: Tue, 13 Jan 2026 21:13:54 +0100 Subject: [PATCH] feat: replace completion animation with Gemini-style rotating gradient glow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove bouncing "CRISPY" text animation (dvd-bounce) - Add rotating conic-gradient border with glow effect - Colors: purple (#9b59b6), magenta (#e040fb), cyan (#00bcd4), violet (#7c4dff) - Animation plays once (2.5s) then fades out so students can continue - Update success colors from green to purple theme: - success-color: #9b6dd4 - success-color-dark: #7c4dff - success-color-light: #c9b8e8 - Uses CSS @property for animating conic-gradient angle - Two layers: blurred glow + crisp border using mask-composite 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- src/app.js | 4 +- src/main.css | 124 +++++++++++++++++++++++++++++++++++---------------- 2 files changed, 88 insertions(+), 40 deletions(-) diff --git a/src/app.js b/src/app.js index 3e60225..a3c505d 100644 --- a/src/app.js +++ b/src/app.js @@ -626,11 +626,11 @@ function runCode() { elements.nextBtn.classList.add("success"); elements.taskInstruction.classList.add("success-instruction"); - // Show match animation (DVD-style bouncing) + // Show match animation (rotating gradient glow) elements.previewWrapper?.classList.add("matched"); setTimeout(() => { elements.previewWrapper?.classList.remove("matched"); - }, 10000); + }, 3000); updateNavigationButtons(); updateProgressDisplay(); diff --git a/src/main.css b/src/main.css index 2dacc7d..b76833e 100644 --- a/src/main.css +++ b/src/main.css @@ -28,9 +28,9 @@ /* Status colors */ --info-color: #7a93fe; - --success-color: #58b890; - --success-color-dark: #3d8d6a; - --success-color-light: #a3e6c8; + --success-color: #9b6dd4; + --success-color-dark: #7c4dff; + --success-color-light: #c9b8e8; --error-color: #cb6e75; --danger-color: #dc3545; @@ -38,7 +38,7 @@ --primary-bg-light: rgba(94, 75, 139, 0.05); --primary-bg-medium: rgba(94, 75, 139, 0.1); --primary-bg-instruction: rgba(125, 92, 203, 0.9); - --success-bg-light: rgba(88, 184, 144, 0.15); + --success-bg-light: rgba(155, 109, 212, 0.15); --modal-bg: rgba(0, 0, 0, 0.5); /* Typography */ @@ -596,50 +596,98 @@ kbd { border-radius: var(--border-radius-sm); } -/* Success Match Animation */ +/* Success Match Animation - Gemini-style rotating gradient glow */ +@property --gradient-angle { + syntax: ""; + initial-value: 0deg; + inherits: false; +} + .preview-wrapper.matched { - box-shadow: 0 0 0 3px var(--success-color); + --glow-size: 3px; + --glow-spread: 12px; } -.preview-wrapper.matched::after { - content: "CRISPY ٩( ◕‿◕ )۶"; +/* Glow layer (blurred background) */ +.preview-wrapper.matched::before { + content: ""; position: absolute; - background: var(--success-color-dark); - color: white; - padding: var(--spacing-sm) var(--spacing-lg); - border-radius: var(--border-radius-lg); - font-weight: bold; - font-size: 1.3rem; - animation: dvd-bounce 8s ease-in-out infinite; - z-index: 10; - white-space: nowrap; + inset: calc(var(--glow-size) * -1); + border-radius: calc(var(--border-radius-md) + var(--glow-size)); + background: conic-gradient( + from var(--gradient-angle, 0deg), + #9b59b6, + #e040fb, + #00bcd4, + #7c4dff, + #9b59b6 + ); + z-index: -1; + filter: blur(var(--glow-spread)); + opacity: 0; + animation: glow-fade 2.5s ease-out forwards; } -@keyframes dvd-bounce { +/* Border layer (crisp rotating gradient) */ +.preview-wrapper.matched::after { + content: ""; + position: absolute; + inset: calc(var(--glow-size) * -1); + border-radius: calc(var(--border-radius-md) + var(--glow-size)); + background: conic-gradient( + from var(--gradient-angle, 0deg), + #9b59b6, + #e040fb, + #00bcd4, + #7c4dff, + #9b59b6 + ); + z-index: -1; + padding: var(--glow-size); + -webkit-mask: + linear-gradient(#fff 0 0) content-box, + linear-gradient(#fff 0 0); + -webkit-mask-composite: xor; + mask-composite: exclude; + opacity: 0; + animation: border-rotate 2.5s ease-out forwards; +} + +@keyframes border-rotate { 0% { - top: 15%; - left: 15%; - transform: translate(-50%, -50%) scale(1); + --gradient-angle: 0deg; + opacity: 1; } - 25% { - top: 75%; - left: 85%; - transform: translate(-50%, -50%) scale(1.1); - } - 50% { - top: 25%; - left: 75%; - transform: translate(-50%, -50%) scale(0.95); - } - 75% { - top: 70%; - left: 20%; - transform: translate(-50%, -50%) scale(1.05); + 70% { + opacity: 1; } 100% { - top: 15%; - left: 15%; - transform: translate(-50%, -50%) scale(1); + --gradient-angle: -360deg; + opacity: 0; + } +} + +@keyframes glow-fade { + 0% { + --gradient-angle: 0deg; + opacity: 0; + filter: blur(var(--glow-spread)); + } + 15% { + opacity: 0.7; + filter: blur(calc(var(--glow-spread) * 1.5)); + } + 50% { + opacity: 0.5; + filter: blur(var(--glow-spread)); + } + 70% { + opacity: 0.3; + } + 100% { + --gradient-angle: -360deg; + opacity: 0; + filter: blur(calc(var(--glow-spread) * 0.5)); } }