Skip to main content

Implementation Plan

Prioritized Roadmap with Code Examples

Document Date: December 13, 2025


Phase 1: Foundation (Weeks 1-2)

Priority 1: Mobile-Responsive Navigation (CRITICAL)

Effort: 3-5 days | Impact: Very High | ROI: Very High

Tasks:

  1. Create mobile menu component
  2. Add hamburger icon transitions
  3. Implement slide-in animation
  4. Test on real devices
  5. Accessibility audit

Implementation: See /03-user-experience-recommendations.md Section 1.1

Acceptance Criteria:

  • Navigation works on all screen sizes (320px+)
  • Smooth open/close animations
  • Keyboard accessible (Tab, Enter, Escape)
  • Screen reader compatible
  • Touch-friendly tap targets (44px minimum)

Priority 2: Fix Critical Accessibility Issues (HIGH)

Effort: 1 week | Impact: Very High | ROI: Very High

Task 2.1: Add ARIA Labels

// Navigation improvements
<nav aria-label="Main navigation">
<button
aria-label="Open menu"
aria-expanded={menuOpen}
aria-controls="mobile-menu"
>
Menu
</button>
</nav>

// Modal improvements
<div
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
aria-describedby="modal-description"
>
<h2 id="modal-title">Modal Title</h2>
<p id="modal-description">Modal description</p>
</div>

// Form improvements
<form aria-label="Login form">
<Input
id="email"
label="Email Address"
aria-required="true"
aria-invalid={hasError}
aria-describedby={hasError ? "email-error" : undefined}
/>
{hasError && (
<span id="email-error" role="alert">
{error}
</span>
)}
</form>

Task 2.2: Keyboard Navigation

// Add keyboard handler utility
// /lib/keyboard.ts
export function handleKeyboardNav(event: React.KeyboardEvent) {
switch (event.key) {
case 'Escape':
// Close modals/dropdowns
break
case 'Tab':
// Trap focus in modals
break
case 'Enter':
case ' ':
// Activate buttons
break
}
}

// Modal with focus trap
import { useEffect, useRef } from 'react'

export function Modal({ isOpen, onClose, children }) {
const modalRef = useRef<HTMLDivElement>(null)
const previousFocus = useRef<HTMLElement | null>(null)

useEffect(() => {
if (isOpen) {
previousFocus.current = document.activeElement as HTMLElement
modalRef.current?.focus()
} else {
previousFocus.current?.focus()
}
}, [isOpen])

useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose()
if (e.key === 'Tab') {
// Implement focus trap
const focusableElements = modalRef.current?.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
)
// ... focus trap logic
}
}

if (isOpen) {
document.addEventListener('keydown', handleKeyDown)
}

return () => document.removeEventListener('keydown', handleKeyDown)
}, [isOpen, onClose])

// ... rest of component
}

Task 2.3: Color Contrast Fixes

// Before (may fail WCAG)
<span className="text-slate-400">Helper text</span>

// After (WCAG AA compliant)
<span className="text-slate-600">Helper text</span>

// Create contrast checker utility
// /lib/accessibility.ts
export function checkContrast(foreground: string, background: string): {
ratio: number
passes: { aa: boolean; aaa: boolean }
} {
// Implement WCAG contrast calculation
// Return ratio and compliance status
}

// Use in development
if (process.env.NODE_ENV === 'development') {
const result = checkContrast('#64748B', '#FFFFFF')
if (!result.passes.aa) {
console.warn('Color contrast fails WCAG AA')
}
}

Acceptance Criteria:

  • All interactive elements have proper ARIA labels
  • Full keyboard navigation support
  • Focus indicators visible on all elements
  • Color contrast ratio ≥ 4.5:1 for text
  • Lighthouse accessibility score ≥ 90

Priority 3: Standardize Loading States (HIGH)

Effort: 2-3 days | Impact: Medium | ROI: High

Implementation:

// /components/ui/LoadingState.tsx
export function LoadingState({
type = 'spinner',
message = 'Loading...',
}: {
type?: 'spinner' | 'skeleton' | 'dots'
message?: string
}) {
if (type === 'skeleton') {
return <SkeletonLoader />
}

if (type === 'dots') {
return (
<div className="flex items-center justify-center gap-2">
<div className="w-2 h-2 bg-stem-blue-500 rounded-full animate-bounce" />
<div className="w-2 h-2 bg-stem-blue-500 rounded-full animate-bounce [animation-delay:0.1s]" />
<div className="w-2 h-2 bg-stem-blue-500 rounded-full animate-bounce [animation-delay:0.2s]" />
</div>
)
}

return (
<div className="flex flex-col items-center justify-center p-8">
<svg
className="animate-spin h-12 w-12 text-stem-blue-500"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
/>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
<p className="mt-4 text-lg font-semibold text-slate-700">{message}</p>
</div>
)
}

// Usage in pages
export default function StudentDashboard() {
const { data, isLoading, error } = useQuery({
queryKey: ['assignments'],
queryFn: fetchAssignments,
})

if (isLoading) return <LoadingState type="skeleton" />
if (error) return <ErrorState />

return <div>{/* ... */}</div>
}

Acceptance Criteria:

  • Consistent loading patterns across all pages
  • Skeleton loaders for content-heavy pages
  • Spinner for quick operations
  • Loading text indicates what's loading
  • Min display time to avoid flicker

Phase 2: Enhancement (Weeks 3-5)

Priority 4: Data Visualization with Highcharts (MEDIUM)

Effort: 2-3 weeks | Impact: Very High | ROI: Medium

Task 4.1: Install and Configure Highcharts

npm install highcharts highcharts-react-official
npm install --save-dev @types/highcharts
// /lib/highcharts-theme.ts
import Highcharts from 'highcharts'

// STEMBlock.ai custom theme
export const stemBlockTheme: Highcharts.Options = {
colors: [
'#2E7CF6', // stem-blue-500
'#8B5CF6', // stem-purple-500
'#10B981', // stem-green-500
'#F59E0B', // stem-orange-500
'#14B8A6', // stem-teal-500
'#EF4444', // stem-red-500
],
chart: {
backgroundColor: 'transparent',
style: {
fontFamily: 'Inter, system-ui, sans-serif',
},
},
title: {
style: {
fontFamily: 'Poppins, system-ui, sans-serif',
fontWeight: '700',
fontSize: '20px',
color: '#1E293B',
},
},
xAxis: {
gridLineColor: '#E2E8F0',
labels: {
style: {
color: '#64748B',
fontSize: '12px',
},
},
lineColor: '#CBD5E1',
},
yAxis: {
gridLineColor: '#E2E8F0',
labels: {
style: {
color: '#64748B',
fontSize: '12px',
},
},
},
legend: {
itemStyle: {
color: '#475569',
fontSize: '14px',
fontWeight: '500',
},
},
plotOptions: {
series: {
borderRadius: 4,
dataLabels: {
style: {
fontSize: '12px',
fontWeight: '600',
textOutline: 'none',
},
},
},
},
tooltip: {
backgroundColor: '#FFFFFF',
borderColor: '#CBD5E1',
borderRadius: 8,
shadow: true,
style: {
color: '#1E293B',
fontSize: '14px',
},
},
}

// Apply theme globally
Highcharts.setOptions(stemBlockTheme)

Task 4.2: Create Chart Components

// /components/charts/ScoreTrendChart.tsx
import Highcharts from 'highcharts'
import HighchartsReact from 'highcharts-react-official'

interface ScoreTrendChartProps {
data: Array<{ date: string; score: number; maxScore: number }>
}

export function ScoreTrendChart({ data }: ScoreTrendChartProps) {
const options: Highcharts.Options = {
chart: {
type: 'line',
height: 300,
},
title: {
text: 'Score Trends Over Time',
},
xAxis: {
categories: data.map(d => new Date(d.date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })),
title: {
text: 'Assignment Date',
},
},
yAxis: {
title: {
text: 'Score (%)',
},
min: 0,
max: 100,
},
series: [
{
name: 'Your Scores',
type: 'line',
data: data.map(d => Math.round((d.score / d.maxScore) * 100)),
marker: {
radius: 6,
symbol: 'circle',
},
},
{
name: 'Target (80%)',
type: 'line',
data: Array(data.length).fill(80),
dashStyle: 'Dash',
marker: {
enabled: false,
},
enableMouseTracking: false,
},
],
credits: {
enabled: false,
},
}

return (
<div className="bg-white p-6 rounded-xl border-2 border-slate-200">
<HighchartsReact highcharts={Highcharts} options={options} />
</div>
)
}
// /components/charts/SkillsRadarChart.tsx
export function SkillsRadarChart({ skills }: { skills: Record<string, number> }) {
const options: Highcharts.Options = {
chart: {
polar: true,
type: 'area',
height: 400,
},
title: {
text: 'Skills Assessment',
},
xAxis: {
categories: Object.keys(skills),
tickmarkPlacement: 'on',
lineWidth: 0,
},
yAxis: {
gridLineInterpolation: 'polygon',
lineWidth: 0,
min: 0,
max: 100,
},
series: [
{
name: 'Your Skills',
type: 'area',
data: Object.values(skills),
fillOpacity: 0.3,
},
],
credits: {
enabled: false,
},
}

return (
<div className="bg-white p-6 rounded-xl border-2 border-slate-200">
<HighchartsReact highcharts={Highcharts} options={options} />
</div>
)
}

Task 4.3: Update Student Dashboard

// /app/student/dashboard/page.tsx - Add charts
export default function StudentDashboard() {
// ... existing code ...

// Add after "Recent Scores" section
return (
<main>
{/* ... existing sections ... */}

{/* Performance Analytics */}
{submissions.length >= 3 && (
<section className="mb-8">
<h2 className="text-2xl font-display font-bold text-slate-900 mb-6">
Your Performance Analytics 📊
</h2>

<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
<ScoreTrendChart
data={submissions.map(s => ({
date: s.submittedAt,
score: s.evaluation?.overallScore || 0,
maxScore: s.assignment.maxScore,
}))}
/>

<SkillsRadarChart
skills={{
'Problem Solving': 85,
'Creativity': 92,
'Technical Skills': 78,
'Documentation': 88,
'Collaboration': 90,
}}
/>
</div>
</section>
)}

{/* ... rest of dashboard ... */}
</main>
)
}

Acceptance Criteria:

  • Highcharts theme matches STEMBlock.ai design
  • Charts are responsive (mobile-friendly)
  • Tooltips show detailed information
  • Charts accessible (ARIA labels, keyboard nav)
  • Print-friendly versions
  • Export functionality (PNG, PDF)

Priority 5: Micro-interactions & Animations (MEDIUM)

Effort: 1 week | Impact: High | ROI: Medium

Task 5.1: Install Framer Motion

npm install framer-motion

Task 5.2: Page Transitions

// /app/template.tsx
'use client'

import { motion } from 'framer-motion'

export default function Template({ children }: { children: React.ReactNode }) {
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3, ease: 'easeInOut' }}
>
{children}
</motion.div>
)
}

Task 5.3: Stagger Animations

// /components/AnimatedGrid.tsx
import { motion } from 'framer-motion'

const container = {
hidden: { opacity: 0 },
show: {
opacity: 1,
transition: {
staggerChildren: 0.1,
},
},
}

const item = {
hidden: { opacity: 0, y: 20 },
show: { opacity: 1, y: 0 },
}

export function AnimatedGrid({ children }: { children: React.ReactNode[] }) {
return (
<motion.div
variants={container}
initial="hidden"
animate="show"
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"
>
{React.Children.map(children, (child, index) => (
<motion.div key={index} variants={item}>
{child}
</motion.div>
))}
</motion.div>
)
}

Task 5.4: Success Animations

// /components/SuccessConfetti.tsx
import { motion } from 'framer-motion'

export function SuccessConfetti() {
return (
<div className="fixed inset-0 pointer-events-none overflow-hidden">
{Array.from({ length: 50 }).map((_, i) => (
<motion.div
key={i}
className="absolute w-2 h-2 rounded-full"
style={{
left: `${Math.random() * 100}%`,
top: '-10px',
backgroundColor: ['#2E7CF6', '#8B5CF6', '#10B981', '#F59E0B'][
Math.floor(Math.random() * 4)
],
}}
animate={{
y: [0, window.innerHeight + 100],
x: [0, (Math.random() - 0.5) * 200],
rotate: [0, Math.random() * 360],
opacity: [1, 0],
}}
transition={{
duration: 2 + Math.random() * 2,
delay: Math.random() * 0.5,
ease: 'easeOut',
}}
/>
))}
</div>
)
}

Acceptance Criteria:

  • Smooth page transitions
  • Card hover animations
  • Button press feedback
  • Success celebrations (confetti, etc.)
  • No animation jank (60fps)
  • Respects prefers-reduced-motion

Phase 3: Optimization (Weeks 6-8)

Priority 6: Performance Optimization (MEDIUM)

Effort: 1-2 weeks | Impact: Medium | ROI: High

Task 6.1: Image Optimization

// Use Next.js Image component everywhere
import Image from 'next/image'

<Image
src="/hero-image.png"
alt="Description"
width={1200}
height={600}
priority // For above-fold images
placeholder="blur"
blurDataURL="data:image/..." // Generated at build time
/>

Task 6.2: Code Splitting

// Dynamic imports for heavy components
import dynamic from 'next/dynamic'

const HeavyChart = dynamic(
() => import('@/components/charts/ComplexChart'),
{
loading: () => <LoadingState type="skeleton" />,
ssr: false, // Don't render on server if not needed
}
)

Task 6.3: React Query Optimization

// /lib/query-client.ts
import { QueryClient } from '@tanstack/react-query'

export const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5 minutes
cacheTime: 10 * 60 * 1000, // 10 minutes
refetchOnWindowFocus: false,
retry: 1,
},
},
})

// Prefetch data
await queryClient.prefetchQuery({
queryKey: ['assignments'],
queryFn: fetchAssignments,
})

Acceptance Criteria:

  • Lighthouse Performance score ≥ 90
  • First Contentful Paint < 1.5s
  • Time to Interactive < 3.5s
  • Cumulative Layout Shift < 0.1
  • Bundle size < 250KB (initial load)

Implementation Schedule

Week 1-2: Foundation

  • Day 1-3: Mobile navigation
  • Day 4-5: ARIA labels and keyboard nav
  • Day 6-7: Color contrast fixes
  • Day 8-10: Loading states standardization

Week 3-4: Data Visualization

  • Day 1-2: Highcharts setup and theme
  • Day 3-5: Create chart components
  • Day 6-8: Integrate into dashboards
  • Day 9-10: Testing and refinement

Week 5: Micro-interactions

  • Day 1-2: Framer Motion setup
  • Day 3-4: Page transitions
  • Day 5-7: Component animations
  • Day 8-10: Polish and testing

Week 6-8: Optimization

  • Week 6: Performance optimization
  • Week 7: Accessibility final audit
  • Week 8: Testing, bug fixes, documentation

Success Metrics

Track these KPIs weekly:

MetricBaselineTargetCurrent
Mobile Usability60%95%-
Lighthouse Accessibility7595+-
Lighthouse Performance8090+-
User Task Completion75%90%-
Time on Task--30%-
Mobile Bounce Rate--25%-

Document Version: 1.0 Last Updated: December 13, 2025