diff --git a/src/app.js b/src/app.js index 67c2d5e..5d66d58 100644 --- a/src/app.js +++ b/src/app.js @@ -43,6 +43,7 @@ const elements = { showExpectedBtn: document.getElementById("show-expected-btn"), expectedOverlay: document.getElementById("expected-overlay"), previewWrapper: document.querySelector(".preview-wrapper"), + previewSection: document.querySelector(".preview-section"), prevBtn: document.getElementById("prev-btn"), nextBtn: document.getElementById("next-btn"), levelIndicator: document.getElementById("level-indicator"), @@ -346,6 +347,8 @@ function resetSuccessIndicators() { elements.taskInstruction.classList.remove("success-instruction"); elements.runBtn.classList.remove("success"); elements.previewWrapper?.classList.remove("matched"); + elements.previewWrapper?.classList.remove("completed-glow"); + elements.previewSection?.classList.remove("matched"); } function updateEditorForMode(mode) { @@ -455,6 +458,9 @@ function loadCurrentLesson() { badge.textContent = t("completed"); elements.lessonTitle.appendChild(badge); } + + // Show gradient border for completed lessons + elements.previewWrapper?.classList.add("completed-glow"); } else { elements.runBtn.querySelector("span").textContent = t("run"); @@ -627,9 +633,25 @@ function runCode() { elements.taskInstruction.classList.add("success-instruction"); // Show match animation (rotating gradient glow) + const crispyQuotes = [ + "Crispyyyyyy!", + "You did it!", + "Good job!", + "Nailed it!", + "Perfect!", + "Well done!", + "Awesome!", + "Nice work!" + ]; + const randomQuote = crispyQuotes[Math.floor(Math.random() * crispyQuotes.length)]; + elements.previewWrapper?.style.setProperty("--crispy-quote", `"${randomQuote}"`); elements.previewWrapper?.classList.add("matched"); + elements.previewSection?.classList.add("matched"); setTimeout(() => { elements.previewWrapper?.classList.remove("matched"); + elements.previewSection?.classList.remove("matched"); + // Keep the gradient border visible after animation + elements.previewWrapper?.classList.add("completed-glow"); }, 3500); updateNavigationButtons(); diff --git a/src/main.css b/src/main.css index 3d06168..e787cac 100644 --- a/src/main.css +++ b/src/main.css @@ -316,7 +316,7 @@ kbd { display: inline-block; margin-left: 0.5rem; padding: 0.15rem 0.5rem; - background: var(--success-color); + background: linear-gradient(135deg, #9b59b6, #e040fb, #00bcd4, #7c4dff); color: white; font-size: 0.7rem; font-weight: 600; @@ -525,6 +525,27 @@ kbd { display: flex; flex-direction: column; min-height: 0; + position: relative; +} + +/* Glow behind preview-wrapper when matched */ +.preview-section.matched::before { + content: ""; + position: absolute; + inset: var(--spacing-md); + border-radius: var(--border-radius-md); + background: conic-gradient( + from var(--border-angle), + #9b59b6, + #e040fb, + #00bcd4, + #7c4dff, + #9b59b6 + ); + filter: blur(30px); + opacity: 0; + animation: spin-glow 3s ease-out forwards; + pointer-events: none; } .preview-header { @@ -551,6 +572,7 @@ kbd { overflow: hidden; min-height: 0; border: 6px solid transparent; + z-index: 1; } .preview-frame { @@ -604,6 +626,21 @@ kbd { inherits: false; } +/* Persistent gradient border for completed lessons */ +.preview-wrapper.completed-glow { + border: 6px solid transparent; + background: + linear-gradient(var(--panel-bg), var(--panel-bg)) padding-box, + conic-gradient( + from 0deg, + #9b59b6, + #e040fb, + #00bcd4, + #7c4dff, + #9b59b6 + ) border-box; +} + .preview-wrapper.matched { --border-angle: 0deg; border: 6px solid transparent; @@ -621,64 +658,25 @@ kbd { overflow: visible; } -/* Colorful glow effect layer on the preview area */ -.preview-wrapper.matched::before { - content: ""; - position: absolute; - inset: -12px; - border-radius: calc(var(--border-radius-md) + 12px); - /* Multi-color gradient glow - works in all browsers */ - background: linear-gradient( - 135deg, - #9b59b6, - #e040fb, - #00bcd4, - #7c4dff, - #9b59b6 - ); - z-index: -1; - filter: blur(24px); - opacity: 0; - animation: glow-pulse 3s ease-out forwards; - pointer-events: none; -} -@keyframes glow-pulse { - 0% { - opacity: 0; - transform: scale(0.95); - } - 15% { - opacity: 0.8; - transform: scale(1); - } - 60% { - opacity: 0.6; - } - 100% { - opacity: 0; - transform: scale(1.02); - } -} - -/* Animated CRISPY badge (matches logo style) */ +/* Animated CRISPY badge with gradient colors */ .preview-wrapper.matched::after { - content: "Your CODE looks CRISPY!"; + content: var(--crispy-quote, "Crispyyyyyy!"); position: absolute; left: 50%; - top: 0; - transform: translateX(-50%) translateY(-100%); + top: 100%; + transform: translateX(-50%) scale(0.5); font-family: system-ui, -apple-system, sans-serif; font-size: 2rem; font-weight: 800; letter-spacing: 0.05em; color: white; - background: var(--primary-color); + background: linear-gradient(135deg, #9b59b6 0%, #e040fb 50%, #7c4dff 100%); padding: 0.5rem 1.25rem; border-radius: 8px; z-index: 10; pointer-events: none; - animation: crispy-fall 3s ease-in-out forwards; + animation: crispy-bounce 3s cubic-bezier(0.34, 1.56, 0.64, 1) forwards; opacity: 0; white-space: nowrap; } @@ -714,26 +712,35 @@ kbd { } } -@keyframes crispy-fall { +@keyframes crispy-bounce { 0% { - top: 0; - transform: translateX(-50%) translateY(-100%); + top: 100%; + transform: translateX(-50%) translateY(0) scale(0.5); opacity: 0; } - 15% { + 20% { + top: 45%; + transform: translateX(-50%) translateY(-50%) scale(1.1); opacity: 1; } - 50% { + 28% { + top: 52%; + transform: translateX(-50%) translateY(-50%) scale(0.95); + opacity: 1; + } + 35% { top: 50%; - transform: translateX(-50%) translateY(-50%); + transform: translateX(-50%) translateY(-50%) scale(1); opacity: 1; } - 85% { + 65% { + top: 50%; + transform: translateX(-50%) translateY(-50%) scale(1); opacity: 1; } 100% { top: 100%; - transform: translateX(-50%) translateY(0%); + transform: translateX(-50%) translateY(0) scale(0.5); opacity: 0; } }