Add student delight features: celebrations, sounds, animations, and background patterns

- Confetti + pop sound on lesson/assignment completion
- Done stamp animation (awaitable, shows below toast)
- Welcome toast on first login only (not back-navigation)
- Web Audio ding/buzz during spelling practice and flashcards
- Fanfare on all-day completion
- Score counter animate-up on practice/test results
- 3D flashcard flip animation (rotateY via CSS keyframes)
- Tab slide transition (Today/Courses/Spelling, directional)
- Staggered card entrance + time-of-day greeting on home screen
- Emoji wobble on kid card hover
- Word card green/red flash on correct/wrong spelling answer
- Combo streak celebration at milestones (3, 5, 7, 10 in a row)
- Random cheer messages for generic celebrations
- Button bounce on :active (scale + translateY)
- Progress bar smooth transition (0.5s ease-out)
- Background pattern picker: dots, stripes, grid, stars, bubbles
- bg_pattern persisted per-student via PATCH /api/students/:id/bg-pattern
- Theme switching preserves bg pattern class

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 4.6 committed 2026-08-04 13:15:53 -04:00
1 parent 6f73acd03e
commit 61f9288aa5
5 files changed
+523 -13

No files matched your search

+193 -4
View File
@@ -55,6 +55,18 @@ body {
.error { color: var(--bad); margin: 0.5rem 0; }
/* ---------- kid picker ---------- */
@keyframes card-enter {
from { opacity: 0; transform: translateY(26px) scale(0.93); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes emoji-wobble {
0% { transform: rotate(0deg) scale(1); }
20% { transform: rotate(-15deg) scale(1.18); }
50% { transform: rotate(13deg) scale(1.14); }
75% { transform: rotate(-7deg) scale(1.07); }
100% { transform: rotate(0deg) scale(1); }
}
.kid-cards {
display: flex; flex-wrap: wrap; gap: 1rem; justify-content: center;
margin-bottom: 2.5rem;
@@ -68,10 +80,16 @@ body {
font-weight: 700;
cursor: pointer;
box-shadow: 0 4px 12px rgba(45, 49, 66, 0.08);
transition: transform 0.1s, border-color 0.1s;
transition: transform 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease;
min-width: 140px;
animation: card-enter 0.42s ease backwards;
}
.kid-card:hover { transform: translateY(-3px) scale(1.03); border-color: var(--accent); }
.kid-card:hover {
transform: translateY(-7px) scale(1.07);
border-color: var(--accent);
box-shadow: 0 12px 30px rgba(45, 49, 66, 0.18);
}
.kid-card:hover .avatar { animation: emoji-wobble 0.48s ease; }
.kid-card .avatar { display: block; font-size: 3rem; margin-bottom: 0.5rem; }
/* ---------- kid home ---------- */
@@ -111,7 +129,7 @@ body {
.progress-track {
height: 12px; background: #e8e2d0; border-radius: 6px; overflow: hidden; margin-bottom: 1.5rem;
}
.progress-fill { height: 100%; width: 0; background: var(--accent); transition: width 0.3s; }
.progress-fill { height: 100%; width: 0; background: var(--accent); transition: width 0.5s ease-out; }
.progress-fill.test-fill { background: var(--gold); }
.word-card {
@@ -140,8 +158,9 @@ body {
background: var(--accent); color: #fff; border: none;
border-radius: 12px; padding: 0.75rem 2.5rem; cursor: pointer;
box-shadow: 0 4px 0 var(--accent-dark);
transition: transform 0.12s;
}
.check-btn:active { transform: translateY(3px); box-shadow: 0 1px 0 var(--accent-dark); }
.check-btn:active { transform: scale(0.94) translateY(2px); box-shadow: 0 1px 0 var(--accent-dark); }
/* letter-diff feedback */
.feedback { min-height: 1rem; margin-bottom: 1rem; font-size: 1.2rem; }
@@ -276,6 +295,16 @@ table.results tr:nth-child(even) td { background: #faf7ee; }
}
.nav-pill.active { background: var(--accent); color: #fff; border-color: var(--accent); }
.tab-panel { text-align: left; }
@keyframes tab-slide-right {
from { transform: translateX(56px); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@keyframes tab-slide-left {
from { transform: translateX(-56px); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
.tab-panel.sliding-right { animation: tab-slide-right 0.26s ease; }
.tab-panel.sliding-left { animation: tab-slide-left 0.26s ease; }
/* ---------- today list / item rows (shared by agenda + course pages) ---------- */
.today-row {
@@ -441,6 +470,28 @@ table.results tr:nth-child(even) td { background: #faf7ee; }
}
.grade-btn.again { background: var(--bad); }
.grade-btn.got-it { background: var(--good); }
@keyframes card-flash-correct {
0% { background: var(--card); }
30% { background: color-mix(in srgb, var(--good) 25%, var(--card)); }
100% { background: var(--card); }
}
@keyframes card-flash-wrong {
0% { background: var(--card); }
30% { background: color-mix(in srgb, var(--bad) 20%, var(--card)); }
100% { background: var(--card); }
}
.word-card.flash-correct { animation: card-flash-correct 0.55s ease; }
.word-card.flash-wrong { animation: card-flash-wrong 0.45s ease; }
@keyframes flash-flip-out {
from { transform: perspective(800px) rotateY(0deg); }
to { transform: perspective(800px) rotateY(90deg); }
}
@keyframes flash-flip-in {
from { transform: perspective(800px) rotateY(-90deg); }
to { transform: perspective(800px) rotateY(0deg); }
}
#flash-card.flip-out { animation: flash-flip-out 0.18s ease-in forwards; }
#flash-card.flip-in { animation: flash-flip-in 0.18s ease-out forwards; }
/* ---------- admin console ---------- */
.admin-header {
@@ -800,6 +851,144 @@ body.theme-indigo { --accent: #5b6abf; --accent-dark: #4a57a0; --bg: #f4f5ff; }
.icon-choice:hover { transform: scale(1.2); }
.icon-choice.active { border-color: var(--accent); background: var(--bg); }
/* ---------- background patterns ---------- */
body.bg-dots {
background-image: radial-gradient(circle, color-mix(in srgb, var(--accent) 38%, transparent) 2.5px, transparent 2.5px);
background-size: 26px 26px;
}
body.bg-stripes {
background-image: repeating-linear-gradient(
45deg, transparent, transparent 14px,
color-mix(in srgb, var(--accent) 18%, transparent) 14px,
color-mix(in srgb, var(--accent) 18%, transparent) 28px
);
}
body.bg-grid {
background-image:
linear-gradient(color-mix(in srgb, var(--accent) 16%, transparent) 1px, transparent 1px),
linear-gradient(90deg, color-mix(in srgb, var(--accent) 16%, transparent) 1px, transparent 1px);
background-size: 32px 32px;
}
body.bg-stars {
background-image:
radial-gradient(circle, color-mix(in srgb, var(--accent) 55%, transparent) 1.5px, transparent 1.5px),
radial-gradient(circle, color-mix(in srgb, var(--accent) 30%, transparent) 1px, transparent 1px),
radial-gradient(circle, color-mix(in srgb, var(--accent) 45%, transparent) 2px, transparent 2px);
background-size: 48px 48px, 28px 28px, 68px 68px;
background-position: 5px 8px, 18px 32px, 46px 18px;
}
body.bg-bubbles {
background-image: radial-gradient(circle, color-mix(in srgb, var(--accent) 18%, transparent) 18px, transparent 18px);
background-size: 64px 64px;
}
/* ---------- bg pattern picker ---------- */
.bg-pattern-panel {
display: flex; gap: 0.5rem; flex-wrap: wrap;
padding: 0.6rem 0.75rem; background: var(--card);
border-radius: var(--radius); box-shadow: 0 4px 16px rgba(0,0,0,0.12);
margin-bottom: 0.75rem;
}
.bg-pattern-btn {
width: 46px; height: 46px; border-radius: 8px;
border: 2.5px solid transparent; cursor: pointer;
background-color: var(--bg); flex-shrink: 0;
transition: border-color 0.15s, transform 0.15s;
}
.bg-pattern-btn:hover { transform: scale(1.12); }
.bg-pattern-btn.active { border-color: var(--accent); }
.bg-pattern-btn[data-pattern="dots"] {
background-image: radial-gradient(circle, color-mix(in srgb, var(--accent) 50%, transparent) 2.5px, transparent 2.5px);
background-size: 9px 9px;
}
.bg-pattern-btn[data-pattern="stripes"] {
background-image: repeating-linear-gradient(
45deg, transparent, transparent 5px,
color-mix(in srgb, var(--accent) 30%, transparent) 5px,
color-mix(in srgb, var(--accent) 30%, transparent) 10px
);
}
.bg-pattern-btn[data-pattern="grid"] {
background-image:
linear-gradient(color-mix(in srgb, var(--accent) 25%, transparent) 1px, transparent 1px),
linear-gradient(90deg, color-mix(in srgb, var(--accent) 25%, transparent) 1px, transparent 1px);
background-size: 10px 10px;
}
.bg-pattern-btn[data-pattern="stars"] {
background-image:
radial-gradient(circle, color-mix(in srgb, var(--accent) 60%, transparent) 1.5px, transparent 1.5px),
radial-gradient(circle, color-mix(in srgb, var(--accent) 35%, transparent) 1px, transparent 1px);
background-size: 14px 14px, 8px 8px;
background-position: 2px 3px, 6px 10px;
}
.bg-pattern-btn[data-pattern="bubbles"] {
background-image: radial-gradient(circle, color-mix(in srgb, var(--accent) 25%, transparent) 8px, transparent 8px);
background-size: 22px 22px;
}
/* ---------- welcome toast ---------- */
.welcome-toast {
position: fixed; top: 50%; left: 50%;
transform: translate(-50%, -50%);
background: var(--card); border-radius: var(--radius);
padding: 2.2rem 3.5rem; text-align: center;
box-shadow: 0 16px 56px rgba(0,0,0,0.22); z-index: 1000;
pointer-events: none;
animation: welcome-in 0.38s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}
.welcome-emoji { display: block; font-size: 4.5rem; margin-bottom: 0.4rem; }
.welcome-name { font-size: 2.2rem; font-weight: 800; }
@keyframes welcome-in {
from { opacity: 0; transform: translate(-50%, -50%) scale(0.7); }
to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}
@keyframes welcome-out {
to { opacity: 0; transform: translate(-50%, -54%) scale(0.88); }
}
.welcome-toast-out { animation: welcome-out 0.4s ease forwards; }
/* ---------- done stamp ---------- */
.done-stamp-wrap {
position: fixed; inset: 0;
display: flex; align-items: flex-end; justify-content: center;
padding-bottom: 10vh;
z-index: 1000; pointer-events: none;
background: rgba(0,0,0,0.06);
}
.done-stamp {
background: var(--card); border-radius: 50%;
width: 170px; height: 170px;
display: flex; align-items: center; justify-content: center;
font-size: 5.5rem;
box-shadow: 0 10px 48px rgba(0,0,0,0.22);
animation: stamp-in 0.38s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}
@keyframes stamp-in {
from { transform: scale(0) rotate(-18deg); opacity: 0; }
to { transform: scale(1) rotate(0deg); opacity: 1; }
}
@keyframes stamp-out {
to { transform: scale(0.75) rotate(6deg); opacity: 0; }
}
.done-stamp-out { animation: stamp-out 0.35s ease forwards; }
/* ---------- mini celebration (confetti + toast) ---------- */
@keyframes confetti-fall {
to { transform: translateY(105vh) rotate(540deg); opacity: 0; }
}
.mini-celebration-toast {
position: fixed; top: 45%; left: 50%;
transform: translate(-50%, -50%);
background: var(--card); border-radius: var(--radius);
padding: 1.6rem 3.2rem; font-size: 2.4rem; font-weight: 800;
text-align: center; white-space: nowrap;
box-shadow: 0 12px 48px rgba(0,0,0,0.28); z-index: 1000;
animation: pop-in 0.25s ease; pointer-events: none;
}
@keyframes mini-toast-out {
to { opacity: 0; transform: translate(-50%, -65%) scale(0.88); }
}
/* ---------- celebration overlay ---------- */
#celebration-overlay {
position: fixed; inset: 0; z-index: 200;