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:
22
src/app.js
22
src/app.js
@@ -43,6 +43,7 @@ const elements = {
|
|||||||
showExpectedBtn: document.getElementById("show-expected-btn"),
|
showExpectedBtn: document.getElementById("show-expected-btn"),
|
||||||
expectedOverlay: document.getElementById("expected-overlay"),
|
expectedOverlay: document.getElementById("expected-overlay"),
|
||||||
previewWrapper: document.querySelector(".preview-wrapper"),
|
previewWrapper: document.querySelector(".preview-wrapper"),
|
||||||
|
previewSection: document.querySelector(".preview-section"),
|
||||||
prevBtn: document.getElementById("prev-btn"),
|
prevBtn: document.getElementById("prev-btn"),
|
||||||
nextBtn: document.getElementById("next-btn"),
|
nextBtn: document.getElementById("next-btn"),
|
||||||
levelIndicator: document.getElementById("level-indicator"),
|
levelIndicator: document.getElementById("level-indicator"),
|
||||||
@@ -346,6 +347,8 @@ function resetSuccessIndicators() {
|
|||||||
elements.taskInstruction.classList.remove("success-instruction");
|
elements.taskInstruction.classList.remove("success-instruction");
|
||||||
elements.runBtn.classList.remove("success");
|
elements.runBtn.classList.remove("success");
|
||||||
elements.previewWrapper?.classList.remove("matched");
|
elements.previewWrapper?.classList.remove("matched");
|
||||||
|
elements.previewWrapper?.classList.remove("completed-glow");
|
||||||
|
elements.previewSection?.classList.remove("matched");
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateEditorForMode(mode) {
|
function updateEditorForMode(mode) {
|
||||||
@@ -455,6 +458,9 @@ function loadCurrentLesson() {
|
|||||||
badge.textContent = t("completed");
|
badge.textContent = t("completed");
|
||||||
elements.lessonTitle.appendChild(badge);
|
elements.lessonTitle.appendChild(badge);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show gradient border for completed lessons
|
||||||
|
elements.previewWrapper?.classList.add("completed-glow");
|
||||||
} else {
|
} else {
|
||||||
elements.runBtn.querySelector("span").textContent = t("run");
|
elements.runBtn.querySelector("span").textContent = t("run");
|
||||||
|
|
||||||
@@ -627,9 +633,25 @@ function runCode() {
|
|||||||
elements.taskInstruction.classList.add("success-instruction");
|
elements.taskInstruction.classList.add("success-instruction");
|
||||||
|
|
||||||
// Show match animation (rotating gradient glow)
|
// 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.previewWrapper?.classList.add("matched");
|
||||||
|
elements.previewSection?.classList.add("matched");
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
elements.previewWrapper?.classList.remove("matched");
|
elements.previewWrapper?.classList.remove("matched");
|
||||||
|
elements.previewSection?.classList.remove("matched");
|
||||||
|
// Keep the gradient border visible after animation
|
||||||
|
elements.previewWrapper?.classList.add("completed-glow");
|
||||||
}, 3500);
|
}, 3500);
|
||||||
|
|
||||||
updateNavigationButtons();
|
updateNavigationButtons();
|
||||||
|
|||||||
115
src/main.css
115
src/main.css
@@ -316,7 +316,7 @@ kbd {
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-left: 0.5rem;
|
margin-left: 0.5rem;
|
||||||
padding: 0.15rem 0.5rem;
|
padding: 0.15rem 0.5rem;
|
||||||
background: var(--success-color);
|
background: linear-gradient(135deg, #9b59b6, #e040fb, #00bcd4, #7c4dff);
|
||||||
color: white;
|
color: white;
|
||||||
font-size: 0.7rem;
|
font-size: 0.7rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@@ -525,6 +525,27 @@ kbd {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-height: 0;
|
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 {
|
.preview-header {
|
||||||
@@ -551,6 +572,7 @@ kbd {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
border: 6px solid transparent;
|
border: 6px solid transparent;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-frame {
|
.preview-frame {
|
||||||
@@ -604,6 +626,21 @@ kbd {
|
|||||||
inherits: false;
|
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 {
|
.preview-wrapper.matched {
|
||||||
--border-angle: 0deg;
|
--border-angle: 0deg;
|
||||||
border: 6px solid transparent;
|
border: 6px solid transparent;
|
||||||
@@ -621,64 +658,25 @@ kbd {
|
|||||||
overflow: visible;
|
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 {
|
/* Animated CRISPY badge with gradient colors */
|
||||||
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) */
|
|
||||||
.preview-wrapper.matched::after {
|
.preview-wrapper.matched::after {
|
||||||
content: "Your CODE looks CRISPY!";
|
content: var(--crispy-quote, "Crispyyyyyy!");
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
top: 0;
|
top: 100%;
|
||||||
transform: translateX(-50%) translateY(-100%);
|
transform: translateX(-50%) scale(0.5);
|
||||||
font-family: system-ui, -apple-system, sans-serif;
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.05em;
|
||||||
color: white;
|
color: white;
|
||||||
background: var(--primary-color);
|
background: linear-gradient(135deg, #9b59b6 0%, #e040fb 50%, #7c4dff 100%);
|
||||||
padding: 0.5rem 1.25rem;
|
padding: 0.5rem 1.25rem;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
pointer-events: none;
|
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;
|
opacity: 0;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
@@ -714,26 +712,35 @@ kbd {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes crispy-fall {
|
@keyframes crispy-bounce {
|
||||||
0% {
|
0% {
|
||||||
top: 0;
|
top: 100%;
|
||||||
transform: translateX(-50%) translateY(-100%);
|
transform: translateX(-50%) translateY(0) scale(0.5);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
15% {
|
20% {
|
||||||
|
top: 45%;
|
||||||
|
transform: translateX(-50%) translateY(-50%) scale(1.1);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
50% {
|
28% {
|
||||||
|
top: 52%;
|
||||||
|
transform: translateX(-50%) translateY(-50%) scale(0.95);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
35% {
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translateX(-50%) translateY(-50%);
|
transform: translateX(-50%) translateY(-50%) scale(1);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
85% {
|
65% {
|
||||||
|
top: 50%;
|
||||||
|
transform: translateX(-50%) translateY(-50%) scale(1);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
top: 100%;
|
top: 100%;
|
||||||
transform: translateX(-50%) translateY(0%);
|
transform: translateX(-50%) translateY(0) scale(0.5);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user