feat: enhance success animation with scale, random quotes, and persistent border

- Add scale transition (50% → 100%) to crispy badge animation
- Rotate through random encouraging quotes on completion
- Keep gradient border visible after completing a lesson
- Match completion badge gradient colors to glow effect
- Improve bounce animation with elastic bezier curve
This commit is contained in:
2026-01-14 03:13:44 +01:00
parent ab056c42c3
commit 9d786b5cf4
2 changed files with 83 additions and 54 deletions

View File

@@ -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();

View File

@@ -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;
}
}