Story 5.7 - Celebration Animations Wireframe
Story: BAT-11 - Slot Assignment & Progressive Publishing
Purpose
This wireframe documents celebration animations, visual feedback, and micro-interactions throughout the slot assignment workflow. Enhances user experience with delightful, purposeful animations that provide clear feedback, celebrate achievements, and guide user attention.
Design Principles
- Purposeful: Every animation serves a functional purpose (feedback, guidance, or celebration)
- Subtle: Animations enhance without distracting from core workflow
- Performant: 60fps minimum, GPU-accelerated where possible
- Respectful: Honor
prefers-reduced-motionsetting - Contextual: Celebration intensity matches achievement significance
- Accessible: Animations complement (not replace) text announcements
Animation Categories
1. Micro-Interactions
- Button hover/press states
- Input focus
- Card elevation on hover
- Toggle switches
- Checkbox/radio animations
2. State Transitions
- Loading spinners
- Progress bar fills
- Status badge changes
- Speaker card state changes (unassigned β assigned)
- Session cell state changes (empty β filled)
3. Navigation Animations
- Modal entrance/exit
- Drawer slide-in/out
- Tab transitions
- Accordion expand/collapse
- Tooltip fade-in
4. Feedback Animations
- Success checkmarks
- Error shake
- Warning pulse
- Info bounce
- Validation feedback
5. Celebration Animations
- Milestone notifications (25%, 50%, 75%)
- Confetti on 100% completion
- Success banner entrance
- Achievement badges
- Completion fireworks (optional)
1. Micro-Interactions
Button States
/* Button hover animation */
.button {
position: relative;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
transform: translateY(0);
}
.button::before {
content: "";
position: absolute;
inset: 0;
background: rgba(255, 255, 255, 0.1);
opacity: 0;
transition: opacity 0.2s ease;
}
.button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.button:hover::before {
opacity: 1;
}
.button:active {
transform: translateY(0);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
/* Ripple effect on click */
@keyframes ripple {
0% {
transform: scale(0);
opacity: 0.5;
}
100% {
transform: scale(4);
opacity: 0;
}
}
.button-ripple {
position: absolute;
border-radius: 50%;
background: rgba(255, 255, 255, 0.6);
width: 20px;
height: 20px;
animation: ripple 0.6s ease-out;
pointer-events: none;
}
Visual Representation:
Button States:
Default:
βββββββββββββββββββ
β Assign Speaker β β No elevation
βββββββββββββββββββ
Hover (0.2s transition):
βββββββββββββββββββ
β Assign Speaker β β Lift 2px, shadow appears
βββββββββββββββββββ
β transform: translateY(-2px)
Active/Click (0.1s):
βββββββββββββββββββ
β Assign Speaker β β Press down, ripple expands from click point
β β β
β β± β² β β Ripple animation
β β β β
βββββββββββββββββββ
After Click (0.2s):
Returns to default state, ripple fades out
Speaker Card Hover
/* Speaker card hover animation */
.speaker-card {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
border: 2px solid transparent;
}
.speaker-card:hover {
transform: translateY(-4px);
border-color: #2196f3;
box-shadow: 0 8px 16px rgba(33, 150, 243, 0.2);
}
.speaker-card:hover .speaker-avatar {
transform: scale(1.05);
}
.speaker-card:hover .assign-button {
animation: pulse-button 1s ease-in-out infinite;
}
@keyframes pulse-button {
0%, 100% {
box-shadow: 0 0 0 0 rgba(33, 150, 243, 0.7);
}
50% {
box-shadow: 0 0 0 6px rgba(33, 150, 243, 0);
}
}
Visual Representation:
Speaker Card Hover Animation (0.3s):
Before Hover:
βββββββββββββββββββββββββββββββββββββββ
β πΆ Dr. Sarah Miller β β Border: transparent
β Topic: AI in Architecture β No shadow
β [Assign] β
βββββββββββββββββββββββββββββββββββββββ
During Hover:
βββββββββββββββββββββββββββββββββββββββ β Border: blue
β πΆ Dr. Sarah Miller β Lifts 4px
β (avatar scales 1.05) β Blue shadow
β Topic: AI in Architecture β
β [Assign] β Pulsing blue ring β
βββββββββββββββββββββββββββββββββββββββ
β²
β² Shadow extends 16px below
Input Focus Animation
/* Input focus animation */
.input-field {
border: 2px solid #e0e0e0;
transition: all 0.2s ease;
position: relative;
}
.input-field::after {
content: "";
position: absolute;
bottom: 0;
left: 50%;
width: 0;
height: 2px;
background: #2196f3;
transition: all 0.3s ease;
}
.input-field:focus {
border-color: #2196f3;
box-shadow: 0 0 0 4px rgba(33, 150, 243, 0.1);
}
.input-field:focus::after {
left: 0;
width: 100%;
}
Visual Representation:
Input Focus Animation:
Default:
βββββββββββββββββββββββββββββββββββββββ
β Search speakers... β β Gray border
βββββββββββββββββββββββββββββββββββββββ
Focus (0.2s):
βββββββββββββββββββββββββββββββββββββββ
β Search speakers...β β β Blue border
βββββββββββββββββββββββββββββββββββββββ Focus ring (0-4px spread)
ββββββββββββββββββ Blue underline expands from center
(0.3s expand animation)
2. State Transition Animations
Progress Bar Fill
/* Progress bar animation */
@keyframes progress-fill {
from {
width: var(--from-width);
}
to {
width: var(--to-width);
}
}
.progress-bar-fill {
height: 100%;
background: linear-gradient(90deg, #4caf50 0%, #66bb6a 100%);
border-radius: inherit;
transition: width 0.6s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
}
/* Shimmer effect on progress */
.progress-bar-fill::after {
content: "";
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(
90deg,
transparent,
rgba(255, 255, 255, 0.3),
transparent
);
animation: shimmer 1.5s ease-in-out;
}
@keyframes shimmer {
0% {
left: -100%;
}
100% {
left: 100%;
}
}
Visual Representation:
Progress Bar Animation (3 of 8 β 4 of 8):
Initial State (37%):
Progress: 3 of 8 (37%)
βββββββββββββββββββββββ
βββ37%βββ
Animating (0.6s smooth):
Progress: 4 of 8 (50%)
βββββββββββββββββββββββ
ββββ50%ββββ
β
ββ Shimmer effect sweeps across during animation
Final State:
Progress: 4 of 8 (50%)
ββββββββββββββββββββββ
ββββ50%ββββ
Speaker Card State Change (Unassigned β Assigned)
/* State change animation */
@keyframes card-state-change {
0% {
opacity: 1;
transform: scale(1);
}
50% {
opacity: 0.7;
transform: scale(0.98);
}
100% {
opacity: 1;
transform: scale(1);
}
}
.speaker-card.state-changing {
animation: card-state-change 0.4s ease-in-out;
}
/* Border color transition */
.speaker-card {
border: 2px solid #ff9800; /* Orange - unassigned */
transition: border-color 0.5s ease, background-color 0.5s ease;
}
.speaker-card.assigned {
border-color: #4caf50; /* Green - assigned */
background: linear-gradient(135deg, #e8f5e9 0%, #f1f8e9 100%);
}
/* Icon change animation */
@keyframes icon-change {
0% {
content: "πΆ";
transform: rotate(0deg) scale(1);
}
50% {
transform: rotate(180deg) scale(0);
}
100% {
content: "β";
transform: rotate(360deg) scale(1);
}
}
.speaker-card-icon {
display: inline-block;
transition: all 0.4s ease;
}
.speaker-card.assigned .speaker-card-icon::before {
animation: icon-change 0.6s ease-in-out;
}
Visual Representation:
Speaker Card State Change (0.6s total):
Frame 1 (0s - Unassigned):
βββββββββββββββββββββββββββββββββββββββ
β πΆ Dr. Sarah Miller β β Orange border
β Topic: AI in Architecture β Light background
β Status: Not assigned β
β [Assign] β
βββββββββββββββββββββββββββββββββββββββ
Frame 2 (0.2s - Transition):
βββββββββββββββββββββββββββββββββββββββ
β β² Dr. Sarah Miller β β Border color interpolating
β (icon rotating, scaling down) β Background fading to green
β Topic: AI in Architecture β Slight scale pulse (0.98)
β Status: Assigning... β
β β
βββββββββββββββββββββββββββββββββββββββ
Frame 3 (0.4s - Icon Change):
βββββββββββββββββββββββββββββββββββββββ
β β» Dr. Sarah Miller β β Checkmark rotating in
β (new icon scaling up) β Green border
β Topic: AI in Architecture β
β Assigned: Workshop 2, 14:00 β
β [Edit] [Details] β
βββββββββββββββββββββββββββββββββββββββ
Frame 4 (0.6s - Final):
βββββββββββββββββββββββββββββββββββββββ
β β Dr. Sarah Miller β β Fully green
β Topic: AI in Architecture β Light green gradient bg
β Assigned: Workshop 2, 14:00-15:30 β Match score shown
β Match: π’ 85% β
β [Edit] [Details] β
βββββββββββββββββββββββββββββββββββββββ
Session Cell Drop Zone Highlight
/* Drop zone highlight animation */
.drop-zone {
border: 2px dashed #e0e0e0;
transition: all 0.2s ease;
position: relative;
}
.drop-zone.drag-over {
border-color: #4caf50;
border-style: solid;
background: linear-gradient(135deg, rgba(76, 175, 80, 0.05) 0%, rgba(129, 199, 132, 0.1) 100%);
animation: pulse-drop-zone 1s ease-in-out infinite;
}
@keyframes pulse-drop-zone {
0%, 100% {
box-shadow: 0 0 0 0 rgba(76, 175, 80, 0.4);
}
50% {
box-shadow: 0 0 0 8px rgba(76, 175, 80, 0);
}
}
/* Match score badge slide-in */
.match-score-badge {
position: absolute;
top: 8px;
right: 8px;
transform: translateX(100px);
opacity: 0;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.drop-zone.drag-over .match-score-badge {
transform: translateX(0);
opacity: 1;
}
Visual Representation:
Drop Zone Highlight (during drag):
Default State:
βββββββββββββββββββββββββββββββββββββββ
β 14:00 - 15:30 β β Dashed gray border
β Workshop 2 β White background
β Room B β
β β Not assigned β
βββββββββββββββββββββββββββββββββββββββ
Drag Over (0.2s transition):
βββββββββββββββββββββββββββββββββββββββ β Solid green border
β 14:00 - 15:30 π’ 85% β Pulsing green shadow
β Workshop 2 β β Light green gradient bg
β Room B badge slides β
β β Drop Dr. Miller here β
βββββββββββββββββββββββββββββββββββββββ
β²
β² Pulsing shadow (0-8px)
Drop Confirmed (0.3s):
Flash green, then transition to assigned state
3. Navigation Animations
Modal Entrance/Exit
/* Modal overlay fade */
@keyframes modal-overlay-fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes modal-overlay-fade-out {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
.modal-overlay {
background: rgba(0, 0, 0, 0.5);
animation: modal-overlay-fade-in 0.2s ease;
}
.modal-overlay.closing {
animation: modal-overlay-fade-out 0.2s ease;
}
/* Modal dialog entrance */
@keyframes modal-dialog-entrance {
from {
opacity: 0;
transform: scale(0.9) translateY(-20px);
}
to {
opacity: 1;
transform: scale(1) translateY(0);
}
}
@keyframes modal-dialog-exit {
from {
opacity: 1;
transform: scale(1) translateY(0);
}
to {
opacity: 0;
transform: scale(0.9) translateY(-20px);
}
}
.modal-dialog {
animation: modal-dialog-entrance 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.modal-dialog.closing {
animation: modal-dialog-exit 0.2s cubic-bezier(0.4, 0, 1, 1);
}
/* Mobile bottom sheet */
@keyframes bottom-sheet-slide-up {
from {
transform: translateY(100%);
}
to {
transform: translateY(0);
}
}
@keyframes bottom-sheet-slide-down {
from {
transform: translateY(0);
}
to {
transform: translateY(100%);
}
}
.bottom-sheet {
animation: bottom-sheet-slide-up 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.bottom-sheet.closing {
animation: bottom-sheet-slide-down 0.2s cubic-bezier(0.4, 0, 1, 1);
}
Visual Representation:
Modal Entrance (0.3s):
Frame 1 (0s):
[Background: normal view]
Frame 2 (0.1s):
[Background: darkening]
βββββββββββββββββββββββββββββββββββββββ
β β β Modal scaling up (0.9 β 1.0)
β βββββββββββββββββββββββββββββ β Sliding up (opacity 0 β 1)
β β Auto-Assign Sessions β β
β β β β
β βββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββ
Frame 3 (0.3s):
[Background: dark overlay 50%]
βββββββββββββββββββββββββββββββββββββββ
β βββββββββββββββββββββββββββββ β β Modal fully visible
β β Auto-Assign Sessions β β Scale 1.0, opacity 1
β β Choose strategy... β β
β β [Options] β β
β β [Preview] [Cancel] β β
β βββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββ
Drawer Slide-In/Out
/* Drawer slide animation */
@keyframes drawer-slide-in {
from {
transform: translateX(100%);
}
to {
transform: translateX(0);
}
}
@keyframes drawer-slide-out {
from {
transform: translateX(0);
}
to {
transform: translateX(100%);
}
}
.drawer {
position: fixed;
top: 0;
right: 0;
height: 100vh;
width: 400px;
background: white;
box-shadow: -4px 0 16px rgba(0, 0, 0, 0.2);
animation: drawer-slide-in 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.drawer.closing {
animation: drawer-slide-out 0.25s cubic-bezier(0.4, 0, 1, 1);
}
/* Drawer overlay */
.drawer-overlay {
animation: modal-overlay-fade-in 0.2s ease;
}
.drawer-overlay.closing {
animation: modal-overlay-fade-out 0.25s ease;
}
Visual Representation:
Drawer Slide-In (Speaker Preferences):
Frame 1 (0s):
βββββββββββββββββββββββββββββββββββββββ
β Main Content β
β [View Preferences] β User clicks β
β β
β β
βββββββββββββββββββββββββββββββββββββββ
Frame 2 (0.15s):
ββββββββββββββββββββββββββββββββββββββββββββ
β Main Content ββPref
β (darkening slightly) ββDr.
β ββ
β ββ[X]
ββββββββββββββββββββββββββββββββββββββββββββ
β
Drawer sliding in from right
Frame 3 (0.3s):
ββββββββββββββββββββββββββββββββββββββββββββ
β Main Content ββPreferences β
β (dark overlay 30%) ββDr. S. Millerβ
β ββ β
β ββTime: βοΈ PM β
β ββA/V: π€π½οΈ β
β ββ[Close] β
ββββββββββββββββββββββββββββββββββββββββββββ
400px drawer
Accordion Expand/Collapse
/* Accordion animation */
.accordion-content {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.accordion-content.expanded {
max-height: 2000px; /* Larger than content */
}
.accordion-icon {
transition: transform 0.3s ease;
}
.accordion.expanded .accordion-icon {
transform: rotate(180deg);
}
/* Content fade-in during expansion */
@keyframes accordion-content-fade {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.accordion-content.expanded > * {
animation: accordion-content-fade 0.2s ease 0.1s both;
}
.accordion-content.expanded > *:nth-child(2) {
animation-delay: 0.15s;
}
.accordion-content.expanded > *:nth-child(3) {
animation-delay: 0.2s;
}
Visual Representation:
Accordion Expand Animation:
Collapsed:
βββββββββββββββββββββββββββββββββββββββ
β Speakers (5 unassigned) [βΌ] β β Click to expand
βββββββββββββββββββββββββββββββββββββββ
Expanding (0.3s):
βββββββββββββββββββββββββββββββββββββββ
β Speakers (5 unassigned) [β²] β β Icon rotating
βββββββββββββββββββββββββββββββββββββββ€ (0Β° β 180Β° over 0.3s)
β βββββββββββββββββββββββββββββββββββ β
β β Dr. Sarah Miller β β β Content fading in
β βββββββββββββββββββββββββββββββββββ β Staggered (0.1s, 0.15s, 0.2s)
β βββββββββββββββββββββββββββββββββββ β
β β Prof. John Chen β β
β βββββββββββββββββββββββββββββββββββ β
β ... (more speakers appearing) β
βββββββββββββββββββββββββββββββββββββββ
Expanded:
βββββββββββββββββββββββββββββββββββββββ
β Speakers (5 unassigned) [β²] β
βββββββββββββββββββββββββββββββββββββββ€
β βββββββββββββββββββββββββββββββββββ β
β β Dr. Sarah Miller β β
β β [Assign] [Preferences] β β
β βββββββββββββββββββββββββββββββββββ β
β βββββββββββββββββββββββββββββββββββ β
β β Prof. John Chen β β
β β [Assign] [Preferences] β β
β βββββββββββββββββββββββββββββββββββ β
β ... (all speakers visible) β
βββββββββββββββββββββββββββββββββββββββ
4. Feedback Animations
Success Checkmark
/* Success checkmark animation */
@keyframes checkmark-circle {
0% {
stroke-dashoffset: 166;
opacity: 0;
transform: scale(0);
}
50% {
stroke-dashoffset: 166;
opacity: 1;
transform: scale(1.1);
}
100% {
stroke-dashoffset: 0;
opacity: 1;
transform: scale(1);
}
}
@keyframes checkmark-check {
0% {
stroke-dashoffset: 48;
opacity: 0;
}
50% {
stroke-dashoffset: 48;
opacity: 0;
}
100% {
stroke-dashoffset: 0;
opacity: 1;
}
}
.checkmark-circle {
stroke-dasharray: 166;
stroke-dashoffset: 166;
stroke: #4caf50;
fill: none;
stroke-width: 4;
animation: checkmark-circle 0.6s cubic-bezier(0.65, 0, 0.45, 1) forwards;
}
.checkmark-check {
stroke-dasharray: 48;
stroke-dashoffset: 48;
stroke: #4caf50;
stroke-width: 4;
animation: checkmark-check 0.6s 0.3s cubic-bezier(0.65, 0, 0.45, 1) forwards;
}
Visual Representation:
Success Checkmark Animation (0.9s total):
Frame 1 (0s):
β β Empty, scaled to 0
Frame 2 (0.3s):
β― β Circle drawing (scale 1.1)
β Stroke drawing clockwise
Frame 3 (0.6s):
β¦Ώ β Circle complete, checkmark starts
β± β Check drawing from bottom-left
Frame 4 (0.9s):
β β Checkmark complete
β Scale settles to 1.0
Final:
βββββββββββββββββββββββββββββββββββββββ
β β β
β Assignment Confirmed! β
β Dr. Miller β Workshop 2 β
βββββββββββββββββββββββββββββββββββββββ
Error Shake
/* Error shake animation */
@keyframes shake {
0%, 100% {
transform: translateX(0);
}
10%, 30%, 50%, 70%, 90% {
transform: translateX(-10px);
}
20%, 40%, 60%, 80% {
transform: translateX(10px);
}
}
.error-shake {
animation: shake 0.5s ease-in-out;
}
/* Error icon bounce */
@keyframes error-bounce {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.1);
}
}
.error-icon {
animation: error-bounce 0.5s ease-in-out 3;
}
Visual Representation:
Error Shake Animation (0.5s):
User attempts invalid assignment:
Frame 1 (0s):
βββββββββββββββββββββββββββββββββββββββ
β Dr. Sarah Miller β
β [Assign] β Click β
βββββββββββββββββββββββββββββββββββββββ
Frame 2 (0.1s):
βββββββββββββββββββββββββββββββββββββββ
β Dr. Sarah Miller β β Shake left 10px
βββββββββββββββββββββββββββββββββββββββ
Frame 3 (0.2s):
βββββββββββββββββββββββββββββββββββββββ
β Dr. Sarah Miller β β Shake right 10px
βββββββββββββββββββββββββββββββββββββββ
Frame 4 (0.3s-0.5s):
Continues shaking with decreasing amplitude
Final (0.5s):
βββββββββββββββββββββββββββββββββββββββ
β β οΈ Dr. Sarah Miller β β Error icon bouncing
β Cannot assign: No available slots β
β [View Conflicts] β
βββββββββββββββββββββββββββββββββββββββ
Warning Pulse
/* Warning pulse animation */
@keyframes warning-pulse {
0% {
box-shadow: 0 0 0 0 rgba(255, 152, 0, 0.7);
}
50% {
box-shadow: 0 0 0 10px rgba(255, 152, 0, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(255, 152, 0, 0);
}
}
.warning-badge {
animation: warning-pulse 1.5s ease-in-out infinite;
}
/* Warning icon glow */
@keyframes warning-glow {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.6;
}
}
.warning-icon {
animation: warning-glow 2s ease-in-out infinite;
}
Visual Representation:
Warning Pulse (continuous):
Frame 1 (0s):
βββββββββββββββββββββββββββββββββββββββ
β β οΈ 3 sessions have low match β β No shadow
β [Review] [Ignore] β
βββββββββββββββββββββββββββββββββββββββ
Frame 2 (0.5s):
βββββββββββββββββββββββββββββββββββββββ
β β οΈ 3 sessions have low match β β Orange shadow expanding
β [Review] [Ignore] β (0 β 10px)
βββββββββββββββββββββββββββββββββββββββ
β²β²β²β²β²β²β²β²β²
Frame 3 (1.5s):
βββββββββββββββββββββββββββββββββββββββ
β β οΈ 3 sessions have low match β β Shadow faded
β [Review] [Ignore] β
βββββββββββββββββββββββββββββββββββββββ
Repeats continuously, icon also pulsing opacity
5. Celebration Animations
Milestone Notifications (25%, 50%, 75%)
/* Milestone toast slide-in */
@keyframes milestone-slide-in {
from {
transform: translateY(-100px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
@keyframes milestone-slide-out {
from {
transform: translateY(0);
opacity: 1;
}
to {
transform: translateY(-100px);
opacity: 0;
}
}
.milestone-toast {
position: fixed;
top: 80px;
left: 50%;
transform: translateX(-50%);
animation: milestone-slide-in 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
.milestone-toast.hiding {
animation: milestone-slide-out 0.3s cubic-bezier(0.4, 0, 1, 1);
}
/* Milestone icon bounce */
@keyframes milestone-icon-bounce {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-10px);
}
}
.milestone-icon {
animation: milestone-icon-bounce 0.6s ease-in-out 3;
}
/* Milestone sparkle */
@keyframes sparkle {
0% {
opacity: 0;
transform: scale(0) rotate(0deg);
}
50% {
opacity: 1;
}
100% {
opacity: 0;
transform: scale(1.5) rotate(180deg);
}
}
.milestone-sparkle {
position: absolute;
animation: sparkle 1s ease-out forwards;
}
Visual Representation:
Milestone Toast (25% - First Quarter):
Frame 1 (0s):
[Toast above viewport, invisible]
Frame 2 (0.2s):
βββββββββββββββββββββββββββββββββββββββ
β π― Good progress! β β Sliding down from top
β 2 of 8 sessions assigned β Icon bouncing
βββββββββββββββββββββββββββββββββββββββ
β translateY(-100px β 0)
Frame 3 (0.4s):
βββββββββββββββββββββββββββββββββββββββ
β π― Good progress! β¨ β β Fully visible
β 2 of 8 sessions assigned β¨ β Sparkles appearing
β 6 more to go! β around edges
βββββββββββββββββββββββββββββββββββββββ
Stays visible for 3 seconds, then slides up
Milestone Variations:
25% (2 of 8):
βββββββββββββββββββββββββββββββββββββββ
β π― Good progress! β
β 2 of 8 sessions assigned β
β 6 more to go! β
βββββββββββββββββββββββββββββββββββββββ
50% (4 of 8):
βββββββββββββββββββββββββββββββββββββββ
β π₯ Halfway there! β
β 4 of 8 sessions assigned β
β Avg match: 85% (excellent!) β
βββββββββββββββββββββββββββββββββββββββ
75% (6 of 8):
βββββββββββββββββββββββββββββββββββββββ
β π Almost done! β
β 6 of 8 sessions assigned β
β Only 2 sessions left! β
βββββββββββββββββββββββββββββββββββββββ
Confetti Celebration (100% Complete)
// Confetti particle system
interface ConfettiParticle {
x: number;
y: number;
vx: number;
vy: number;
rotation: number;
rotationSpeed: number;
color: string;
size: number;
gravity: number;
}
function createConfetti(count: number = 100): ConfettiParticle[] {
const colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A', '#98D8C8', '#F7DC6F'];
const particles: ConfettiParticle[] = [];
for (let i = 0; i < count; i++) {
particles.push({
x: Math.random() * window.innerWidth,
y: -20,
vx: (Math.random() - 0.5) * 4,
vy: Math.random() * 3 + 2,
rotation: Math.random() * 360,
rotationSpeed: (Math.random() - 0.5) * 10,
color: colors[Math.floor(Math.random() * colors.length)],
size: Math.random() * 10 + 5,
gravity: 0.3 + Math.random() * 0.2,
});
}
return particles;
}
function animateConfetti(particles: ConfettiParticle[], ctx: CanvasRenderingContext2D) {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
particles.forEach((p) => {
// Update position
p.x += p.vx;
p.y += p.vy;
p.vy += p.gravity;
p.rotation += p.rotationSpeed;
// Draw particle
ctx.save();
ctx.translate(p.x, p.y);
ctx.rotate((p.rotation * Math.PI) / 180);
ctx.fillStyle = p.color;
ctx.fillRect(-p.size / 2, -p.size / 2, p.size, p.size);
ctx.restore();
});
// Continue animation if particles are visible
if (particles.some((p) => p.y < ctx.canvas.height)) {
requestAnimationFrame(() => animateConfetti(particles, ctx));
}
}
/* Confetti canvas overlay */
.confetti-canvas {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
pointer-events: none;
z-index: 9999;
}
/* Success banner entrance (after confetti starts) */
@keyframes success-banner-entrance {
from {
opacity: 0;
transform: translateY(50px) scale(0.9);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
.success-banner {
animation: success-banner-entrance 0.6s cubic-bezier(0.4, 0, 0.2, 1) 0.5s both;
}
/* Success banner glow pulse */
@keyframes success-glow {
0%, 100% {
box-shadow: 0 4px 20px rgba(76, 175, 80, 0.3);
}
50% {
box-shadow: 0 8px 40px rgba(76, 175, 80, 0.5);
}
}
.success-banner {
animation: success-banner-entrance 0.6s cubic-bezier(0.4, 0, 0.2, 1) 0.5s both,
success-glow 2s ease-in-out 1.1s infinite;
}
Visual Representation:
Confetti Celebration (100% Complete):
Frame 1 (0s - Assignment completes):
Progress: 8 of 8 (100%)
ββββββββββββββββββββββββββ
Frame 2 (0.1s - Confetti starts):
βββββββββββββββββββββββββββββββββββββββ
β βͺ β« βͺ β β Confetti particles
β β« βͺ β appearing at top
β βͺ β« β Multiple colors
β β Falling, rotating
β β
β β
βββββββββββββββββββββββββββββββββββββββ
Frame 3 (0.5s - Success banner appears):
βββββββββββββββββββββββββββββββββββββββ
β βͺ β« βͺ β« βͺ β
β β« βͺ β« βͺ β β Confetti falling
β βͺ β« βͺ β« β throughout screen
β β« β
β βββββββββββββββββββββββββββββββββββ β
β β π SUCCESS! π β β β Banner sliding up
β β All Session Timings Assigned! β β from below
β βββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββ
Frame 4 (1s - Full celebration):
βββββββββββββββββββββββββββββββββββββββ
β β« βͺ β« βͺ β
β βͺ β« βͺ β« βͺ β« β β Confetti spread
β β« βͺ β« βͺ β« βͺ β across screen
β βͺ β« βͺ β« βͺ β
β ββββββββββββββββββββββββββββββββββ β
β β π π SUCCESS! π π β β β Banner glowing
β β All Session Timings Assigned! β β Pulsing green shadow
β β β β
β β 8 of 8 sessions (100%) β β
β β Average match: 82% (High!) β β
β β β β
β β [View Publishing Options β] β β β CTA button pulsing
β ββββββββββββββββββββββββββββββββββ β
β β« βͺ β« βͺ β« βͺ β
βββββββββββββββββββββββββββββββββββββββ
Frame 5 (3s - Confetti settling):
βββββββββββββββββββββββββββββββββββββββ
β β
β β
β ββββββββββββββββββββββββββββββββββ β
β β π SUCCESS! β β β Banner remains
β β All Session Timings Assigned! β β Gentle glow pulse
β β 8 of 8 (100%) β Avg: 82% β β
β β [View Publishing Options β] β β
β ββββββββββββββββββββββββββββββββββ β
β βͺ β« β β Few particles
β βͺ β still settling
βββββββββββββββββββββββββββββββββββββββ
Achievement Badges (Optional)
/* Achievement badge pop-in */
@keyframes badge-pop-in {
0% {
opacity: 0;
transform: scale(0) rotate(-180deg);
}
50% {
transform: scale(1.2) rotate(10deg);
}
100% {
opacity: 1;
transform: scale(1) rotate(0deg);
}
}
.achievement-badge {
animation: badge-pop-in 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
/* Badge shine effect */
@keyframes badge-shine {
0% {
left: -100%;
}
100% {
left: 100%;
}
}
.achievement-badge::after {
content: "";
position: absolute;
top: 0;
left: -100%;
width: 50%;
height: 100%;
background: linear-gradient(
90deg,
transparent,
rgba(255, 255, 255, 0.6),
transparent
);
animation: badge-shine 2s ease-in-out 1;
}
Visual Representation:
Achievement Badge Animation:
Trigger: User completes all 8 sessions with 90%+ average match
Frame 1 (0s):
[Badge invisible, scaled to 0]
Frame 2 (0.3s):
β± β²
β β β β Badge rotating in, scaling up
β² β± (0 β 1.2 scale, -180Β° β 10Β° rotation)
Frame 3 (0.6s):
βββββββ
β β β β Badge settled (scale 1.0, rotation 0Β°)
βMatchβ Shine sweeping across
βMasterβ
βββββββ
Final Display:
βββββββββββββββββββββββββββββββββββββββ
β π Achievement Unlocked! β
β β
β βββββββ β
β β β β Match Master β
β β 90%+β Achieved 90%+ average β
β βββββββ match score β
β β
β [Share] [Close] β
βββββββββββββββββββββββββββββββββββββββ
Achievements List:
- First Assignment: Assign your first session (appears at 1/8)
- Quarter Complete: 25% progress (appears at 2/8)
- Halfway Hero: 50% progress (appears at 4/8)
- Almost There: 75% progress (appears at 6/8)
- Perfect Match: 100% sessions with 90%+ avg score (appears at 8/8)
- Speed Demon: Complete all assignments in under 5 minutes
- Preference Master: All sessions match speaker time preferences
6. Loading States
Skeleton Loaders
/* Skeleton shimmer effect */
@keyframes skeleton-shimmer {
0% {
background-position: -1000px 0;
}
100% {
background-position: 1000px 0;
}
}
.skeleton {
background: linear-gradient(
90deg,
#f0f0f0 25%,
#e0e0e0 50%,
#f0f0f0 75%
);
background-size: 1000px 100%;
animation: skeleton-shimmer 2s infinite linear;
border-radius: 4px;
}
.skeleton-speaker-card {
width: 100%;
height: 120px;
margin-bottom: 12px;
}
.skeleton-session-cell {
width: 100%;
height: 80px;
}
Visual Representation:
Skeleton Loaders (while data loading):
Speaker Pool Loading:
βββββββββββββββββββββββββββββββββββββββ
β SPEAKERS β
βββββββββββββββββββββββββββββββββββββββ€
β βββββββββββββββββββββββββββββββββββ β
β βββββββββββββββββββββββββββββββββββ β β Shimmer effect
β βββββββββββββββββββββββββββββββββββ β moving leftβright
β βββββββββββββββββββββββββββββββββββ β
β βββββββββββββββββββββββββββββββββββ β
β βββββββββββββββββββββββββββββββββββ β
β βββββββββββββββββββββββββββββββββββ β
β βββββββββββββββββββββββββββββββββββ β
β ... (more skeletons) β
βββββββββββββββββββββββββββββββββββββββ
Session Grid Loading:
βββββββββββββββββββββββββββββββββββββββ
β SESSION TIMELINE β
βββββββββββββββββββββββββββββββββββββββ€
β ββββ βββββ βββββ βββββ β β Shimmer blocks
β ββββ βββββ βββββ βββββ β representing
β βββββ βββββ β session cells
β βββββ βββββ β
β ββββ βββββ β
βββββββββββββββββββββββββββββββββββββββ
Spinner Animations
/* Circular spinner */
@keyframes spinner-rotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
@keyframes spinner-dash {
0% {
stroke-dasharray: 1, 150;
stroke-dashoffset: 0;
}
50% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -35;
}
100% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -124;
}
}
.spinner {
animation: spinner-rotate 2s linear infinite;
}
.spinner-path {
stroke: #2196f3;
stroke-linecap: round;
animation: spinner-dash 1.5s ease-in-out infinite;
}
/* Dot pulse loader */
@keyframes dot-pulse {
0%, 100% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.5);
opacity: 0.5;
}
}
.dot-loader span {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
background: #2196f3;
margin: 0 4px;
}
.dot-loader span:nth-child(1) {
animation: dot-pulse 1s ease-in-out infinite;
}
.dot-loader span:nth-child(2) {
animation: dot-pulse 1s ease-in-out 0.2s infinite;
}
.dot-loader span:nth-child(3) {
animation: dot-pulse 1s ease-in-out 0.4s infinite;
}
Visual Representation:
Loading Spinners:
Circular Spinner:
β±ββ² β Rotating circle
β β Dash animating around edge
β²ββ± (0Β° β 360Β° continuous)
Auto-Assigning...
Dot Pulse Loader:
β β β β β β β β β β β β β β β
(dots pulsing sequentially, 0.2s delay)
Processing assignments...
7. Reduced Motion Alternatives
/* Respect prefers-reduced-motion */
@media (prefers-reduced-motion: reduce) {
/* Disable all animations */
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
/* Replace confetti with simple checkmark */
.confetti-canvas {
display: none;
}
.success-banner-reduced-motion {
display: block; /* Hidden by default, shown when reduced motion */
}
.success-banner-reduced-motion::before {
content: "βββ ";
font-size: 32px;
color: #4caf50;
}
/* Replace progress bar animation with instant change */
.progress-bar-fill {
transition: none;
}
/* Replace modal slide with fade only */
.modal-dialog {
animation: none;
opacity: 1;
}
.modal-overlay {
animation: none;
opacity: 1;
}
/* Replace shake with border flash */
@keyframes border-flash-reduced {
0%, 100% {
border-color: #f44336;
}
50% {
border-color: #ff5252;
}
}
.error-shake {
animation: border-flash-reduced 0.3s ease 2;
}
}
Performance Optimization
GPU Acceleration
/* Use transform and opacity for animations (GPU-accelerated) */
.animated-element {
will-change: transform, opacity; /* Hint to browser */
transform: translateZ(0); /* Force GPU layer */
}
/* Avoid animating expensive properties */
/* β
Good - GPU accelerated */
.good-animation {
transition: transform 0.3s ease, opacity 0.3s ease;
}
/* β Bad - CPU intensive */
.bad-animation {
transition: width 0.3s ease, height 0.3s ease, top 0.3s ease, left 0.3s ease;
}
Animation Batching
// Batch DOM updates with requestAnimationFrame
function updateProgress(newProgress: number) {
requestAnimationFrame(() => {
// Update progress bar
progressBar.style.width = `${newProgress}%`;
progressText.textContent = `${assignedCount} of ${totalCount} (${newProgress}%)`;
// Update speaker cards
updateSpeakerCardStates();
// Update session cells
updateSessionCellStates();
});
}
Lazy Animation Loading
// Only load confetti library when needed
async function celebrate() {
if (!window.confetti) {
const { default: confetti } = await import('canvas-confetti');
window.confetti = confetti;
}
window.confetti({
particleCount: 100,
spread: 70,
origin: { y: 0.6 },
});
}
Testing
Animation Testing Checklist
Visual Testing:
Functional Testing:
Accessibility Testing:
Automated Tests
// Test animation completion
test('should complete progress bar animation', async ({ page }) => {
await page.goto('/organizer/events/batbern-2025/slot-assignment');
// Perform assignment
await performAssignment(page, 'Dr. Sarah Miller', 'Workshop 2');
// Wait for animation to complete (0.6s)
await page.waitForTimeout(700);
// Verify progress bar updated
const progressText = await page.locator('.progress-text').textContent();
expect(progressText).toContain('4 of 8');
const progressWidth = await page.locator('.progress-bar-fill').evaluate(
(el) => getComputedStyle(el).width
);
expect(progressWidth).toBe('50%'); // 4/8 = 50%
});
// Test confetti triggers
test('should show confetti on 100% completion', async ({ page }) => {
await page.goto('/organizer/events/batbern-2025/slot-assignment');
// Assign all 8 sessions
await assignAllSessions(page);
// Verify confetti canvas visible
const confettiCanvas = await page.locator('.confetti-canvas');
await expect(confettiCanvas).toBeVisible();
// Wait for confetti to complete (3s)
await page.waitForTimeout(3500);
// Verify success banner visible
const successBanner = await page.locator('.success-banner');
await expect(successBanner).toBeVisible();
});
// Test reduced motion
test('should respect prefers-reduced-motion', async ({ page, context }) => {
// Emulate reduced motion preference
await context.emulateMedia({ reducedMotion: 'reduce' });
await page.goto('/organizer/events/batbern-2025/slot-assignment');
// Perform assignment
await performAssignment(page, 'Dr. Sarah Miller', 'Workshop 2');
// Verify no confetti
const confettiCanvas = await page.locator('.confetti-canvas');
await expect(confettiCanvas).toBeHidden();
// Verify instant progress update (no animation)
const progressWidth = await page.locator('.progress-bar-fill').evaluate(
(el) => getComputedStyle(el).transitionDuration
);
expect(progressWidth).toBe('0.01ms'); // Near-instant
});
Implementation Priority
Must-Have (MVP):
- Progress bar fill animation
- Speaker card state change (unassigned β assigned)
- Success toast notifications
- Basic loading spinners
Should-Have (Post-MVP):
- Milestone notifications (25%, 50%, 75%)
- Confetti celebration (100%)
- Hover/focus micro-interactions
- Modal entrance/exit animations
Nice-to-Have (Polish):
- Achievement badges
- Particle effects
- Advanced confetti patterns
- Celebration sounds (optional)
Summary
This wireframe documents comprehensive celebration animations and visual feedback for the slot assignment workflow:
Key Animation Categories:
- Micro-Interactions: Button hovers, card elevations, input focus
- State Transitions: Progress bars, status changes, loading states
- Navigation: Modal/drawer entrance/exit, accordion expand
- Feedback: Success checkmarks, error shakes, warning pulses
- Celebrations: Milestone toasts, confetti, achievement badges
Performance:
- 60fps minimum for all animations
- GPU-accelerated transforms and opacity
- Lazy loading for heavy libraries (confetti)
- Reduced motion alternatives for accessibility
Testing:
- Automated Playwright tests for all major animations
- Visual regression testing
- Performance profiling
- Accessibility compliance
Next Steps:
- Implement core animations (progress bar, state transitions)
- Add celebration animations (milestones, confetti)
- Test across devices and browsers
- Verify reduced motion alternatives
- Performance test on low-end devices