mirror of
https://github.com/th30d4y/OpenLearnX.git
synced 2026-05-26 11:25:49 +00:00
docker
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
README.md
|
||||
|
||||
# Node modules
|
||||
node_modules
|
||||
frontend/node_modules
|
||||
frontend/.next
|
||||
|
||||
# Python
|
||||
__pycache__
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
venv
|
||||
.pytest_cache
|
||||
|
||||
# Environment files
|
||||
.env*
|
||||
!.env.example
|
||||
|
||||
# IDE
|
||||
.vscode
|
||||
.idea
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
logs
|
||||
backend/openlearnx.log
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Docker
|
||||
Dockerfile*
|
||||
docker-compose*.yml
|
||||
.dockerignore
|
||||
|
||||
# Temporary
|
||||
tmp/
|
||||
temp/
|
||||
cache/
|
||||
backend/cache/
|
||||
|
||||
# Build artifacts
|
||||
dist/
|
||||
build/
|
||||
out/
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
# Multi-stage Dockerfile for OpenLearnX Platform
|
||||
FROM node:18-alpine AS frontend-builder
|
||||
|
||||
# Build Frontend
|
||||
WORKDIR /app/frontend
|
||||
COPY frontend/package.json frontend/pnpm-lock.yaml* ./
|
||||
RUN corepack enable pnpm && pnpm install --frozen-lockfile
|
||||
|
||||
COPY frontend/ ./
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
RUN pnpm run build
|
||||
|
||||
# Main Python image with everything
|
||||
FROM python:3.10-slim
|
||||
|
||||
# Set environment variables
|
||||
ENV PYTHONDONTWRITEBYTECODE=1
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
ENV TF_CPP_MIN_LOG_LEVEL=2
|
||||
ENV NODE_ENV=production
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
# Install system dependencies (Python + Node.js)
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
gcc \
|
||||
g++ \
|
||||
libpq-dev \
|
||||
curl \
|
||||
wget \
|
||||
ca-certificates \
|
||||
gnupg \
|
||||
nginx \
|
||||
supervisor \
|
||||
&& curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
|
||||
&& apt-get install -y nodejs \
|
||||
&& npm install -g pnpm \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Set work directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy and install Python dependencies
|
||||
COPY backend/requirements.txt ./backend/
|
||||
RUN pip install --no-cache-dir --upgrade pip \
|
||||
&& pip install --no-cache-dir -r backend/requirements.txt
|
||||
|
||||
# Copy backend code
|
||||
COPY backend/ ./backend/
|
||||
|
||||
# Copy built frontend from previous stage
|
||||
COPY --from=frontend-builder /app/frontend/.next/standalone ./frontend/
|
||||
COPY --from=frontend-builder /app/frontend/.next/static ./frontend/.next/static
|
||||
COPY --from=frontend-builder /app/frontend/public ./frontend/public
|
||||
|
||||
# Create necessary directories
|
||||
RUN mkdir -p /var/log/supervisor \
|
||||
&& mkdir -p /app/logs \
|
||||
&& mkdir -p /app/uploads
|
||||
|
||||
# Configure Nginx
|
||||
COPY <<EOF /etc/nginx/sites-available/openlearnx
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
# Frontend
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:3000;
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
}
|
||||
|
||||
# Backend API
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:5000/;
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
}
|
||||
|
||||
# Health check
|
||||
location /health {
|
||||
access_log off;
|
||||
return 200 "healthy\\n";
|
||||
add_header Content-Type text/plain;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
RUN ln -s /etc/nginx/sites-available/openlearnx /etc/nginx/sites-enabled/ \
|
||||
&& rm /etc/nginx/sites-enabled/default
|
||||
|
||||
# Configure Supervisor
|
||||
COPY <<EOF /etc/supervisor/conf.d/openlearnx.conf
|
||||
[supervisord]
|
||||
nodaemon=true
|
||||
user=root
|
||||
logfile=/var/log/supervisor/supervisord.log
|
||||
pidfile=/var/run/supervisord.pid
|
||||
|
||||
[program:nginx]
|
||||
command=nginx -g "daemon off;"
|
||||
autostart=true
|
||||
autorestart=true
|
||||
stderr_logfile=/var/log/supervisor/nginx.err.log
|
||||
stdout_logfile=/var/log/supervisor/nginx.out.log
|
||||
|
||||
[program:backend]
|
||||
command=gunicorn --bind 127.0.0.1:5000 --workers 4 --timeout 120 main:app
|
||||
directory=/app/backend
|
||||
autostart=true
|
||||
autorestart=true
|
||||
stderr_logfile=/var/log/supervisor/backend.err.log
|
||||
stdout_logfile=/var/log/supervisor/backend.out.log
|
||||
environment=PYTHONPATH="/app/backend"
|
||||
|
||||
[program:frontend]
|
||||
command=node server.js
|
||||
directory=/app/frontend
|
||||
autostart=true
|
||||
autorestart=true
|
||||
stderr_logfile=/var/log/supervisor/frontend.err.log
|
||||
stdout_logfile=/var/log/supervisor/frontend.out.log
|
||||
environment=PORT="3000",HOSTNAME="127.0.0.1"
|
||||
EOF
|
||||
|
||||
# Create non-root user
|
||||
RUN adduser --disabled-password --gecos '' appuser \
|
||||
&& chown -R appuser:appuser /app \
|
||||
&& chown -R appuser:appuser /var/log/supervisor
|
||||
|
||||
# Health check script
|
||||
COPY <<EOF /app/healthcheck.sh
|
||||
#!/bin/bash
|
||||
curl -f http://127.0.0.1/health && \
|
||||
curl -f http://127.0.0.1:3000/ && \
|
||||
curl -f http://127.0.0.1:5000/api/health
|
||||
EOF
|
||||
|
||||
RUN chmod +x /app/healthcheck.sh
|
||||
|
||||
# Switch to non-root user for app files
|
||||
USER appuser
|
||||
|
||||
# Expose port
|
||||
EXPOSE 80
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
|
||||
CMD /app/healthcheck.sh || exit 1
|
||||
|
||||
# Switch back to root for supervisor
|
||||
USER root
|
||||
|
||||
# Start all services with supervisor
|
||||
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/openlearnx.conf"]
|
||||
@@ -0,0 +1,43 @@
|
||||
FROM python:3.10-slim
|
||||
|
||||
# Set environment variables
|
||||
ENV PYTHONDONTWRITEBYTECODE=1
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
ENV TF_CPP_MIN_LOG_LEVEL=2
|
||||
|
||||
# Set work directory
|
||||
WORKDIR /app
|
||||
|
||||
# Install system dependencies
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
gcc \
|
||||
g++ \
|
||||
libpq-dev \
|
||||
curl \
|
||||
wget \
|
||||
ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy requirements and install Python dependencies
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir --upgrade pip \
|
||||
&& pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Copy application code
|
||||
COPY . .
|
||||
|
||||
# Create non-root user
|
||||
RUN adduser --disabled-password --gecos '' appuser \
|
||||
&& chown -R appuser:appuser /app
|
||||
USER appuser
|
||||
|
||||
# Expose port
|
||||
EXPOSE 5000
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
||||
CMD curl -f http://localhost:5000/api/health || exit 1
|
||||
|
||||
# Run with Gunicorn for production
|
||||
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "4", "--timeout", "120", "main:app"]
|
||||
@@ -0,0 +1,128 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
print_status() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
print_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
|
||||
print_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
|
||||
print_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
|
||||
echo "🚀 Deploying OpenLearnX Single Container Platform"
|
||||
echo "================================================="
|
||||
|
||||
# Check prerequisites
|
||||
print_status "Checking prerequisites..."
|
||||
command -v docker >/dev/null 2>&1 || { print_error "Docker not found. Please install Docker."; exit 1; }
|
||||
command -v docker-compose >/dev/null 2>&1 || { print_error "docker-compose not found. Please install docker-compose."; exit 1; }
|
||||
|
||||
# Check if Docker is running
|
||||
if ! docker info >/dev/null 2>&1; then
|
||||
print_error "Docker is not running. Please start Docker first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_success "Prerequisites check passed!"
|
||||
|
||||
# Create necessary directories
|
||||
print_status "Creating necessary directories..."
|
||||
mkdir -p backend/uploads backend/logs
|
||||
|
||||
# Stop existing containers
|
||||
print_status "Stopping existing containers..."
|
||||
docker-compose down -v 2>/dev/null || true
|
||||
|
||||
# Clean up old images
|
||||
print_status "Cleaning up old images..."
|
||||
docker system prune -f
|
||||
|
||||
# Build the single container
|
||||
print_status "Building OpenLearnX single container..."
|
||||
docker-compose build --no-cache openlearnx
|
||||
|
||||
# Start all services
|
||||
print_status "Starting all services..."
|
||||
docker-compose up -d
|
||||
|
||||
# Wait for services to be ready
|
||||
print_status "Waiting for services to start..."
|
||||
sleep 60
|
||||
|
||||
# Health checks
|
||||
print_status "Performing health checks..."
|
||||
|
||||
check_service() {
|
||||
local service=$1
|
||||
local url=$2
|
||||
local max_attempts=30
|
||||
local attempt=1
|
||||
|
||||
while [ $attempt -le $max_attempts ]; do
|
||||
if curl -f "$url" >/dev/null 2>&1; then
|
||||
print_success "$service is healthy!"
|
||||
return 0
|
||||
fi
|
||||
print_status "Waiting for $service... (attempt $attempt/$max_attempts)"
|
||||
sleep 5
|
||||
((attempt++))
|
||||
done
|
||||
|
||||
print_warning "$service health check failed after $max_attempts attempts"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Check all services
|
||||
check_service "Main Application" "http://localhost/health"
|
||||
check_service "Frontend" "http://localhost/"
|
||||
check_service "Backend API" "http://localhost/api/health"
|
||||
check_service "Database" "http://localhost:5432" || print_warning "Database connection check skipped"
|
||||
|
||||
# Display service status
|
||||
print_status "Service Status:"
|
||||
docker-compose ps
|
||||
|
||||
# Display logs for debugging if needed
|
||||
print_status "Recent logs:"
|
||||
docker-compose logs --tail=10 openlearnx
|
||||
|
||||
# Display URLs and information
|
||||
echo ""
|
||||
echo "🎉 OpenLearnX Platform Deployed Successfully!"
|
||||
echo "============================================="
|
||||
echo "🌐 Main Application: http://localhost"
|
||||
echo "🔧 Backend API: http://localhost/api/"
|
||||
echo "📱 Frontend: http://localhost/"
|
||||
echo "🗄️ Database: postgresql://postgres:openlearnx123@localhost:5432/openlearnx"
|
||||
echo "📊 Redis: redis://localhost:6379"
|
||||
echo ""
|
||||
echo "📋 Useful Commands:"
|
||||
echo " - View logs: docker-compose logs -f openlearnx"
|
||||
echo " - View all logs: docker-compose logs -f"
|
||||
echo " - Stop services: docker-compose down"
|
||||
echo " - Restart: docker-compose restart openlearnx"
|
||||
echo " - Enter container: docker-compose exec openlearnx bash"
|
||||
echo " - View processes: docker-compose exec openlearnx supervisorctl status"
|
||||
echo ""
|
||||
echo "🔍 Monitoring:"
|
||||
echo " - Application logs: docker-compose exec openlearnx tail -f /var/log/supervisor/backend.out.log"
|
||||
echo " - Frontend logs: docker-compose exec openlearnx tail -f /var/log/supervisor/frontend.out.log"
|
||||
echo " - Nginx logs: docker-compose exec openlearnx tail -f /var/log/supervisor/nginx.out.log"
|
||||
echo ""
|
||||
|
||||
# Optional: Open browser
|
||||
if command -v xdg-open &> /dev/null; then
|
||||
print_status "Opening application in browser..."
|
||||
xdg-open http://localhost
|
||||
elif command -v open &> /dev/null; then
|
||||
print_status "Opening application in browser..."
|
||||
open http://localhost
|
||||
fi
|
||||
|
||||
print_success "Single container deployment completed! 🐳🚀"
|
||||
print_status "Your OpenLearnX platform is running in a single Docker container with all services!"
|
||||
@@ -0,0 +1,69 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
# Main OpenLearnX Application
|
||||
openlearnx:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "80:80"
|
||||
environment:
|
||||
- DATABASE_URL=postgresql://postgres:openlearnx123@postgres:5432/openlearnx
|
||||
- REDIS_URL=redis://redis:6379
|
||||
- FLASK_ENV=production
|
||||
- NODE_ENV=production
|
||||
- TF_CPP_MIN_LOG_LEVEL=2
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_started
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- openlearnx-network
|
||||
volumes:
|
||||
- app_uploads:/app/uploads
|
||||
- app_logs:/app/logs
|
||||
|
||||
# PostgreSQL Database
|
||||
postgres:
|
||||
image: postgres:15-alpine
|
||||
environment:
|
||||
POSTGRES_DB: openlearnx
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: openlearnx123
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- openlearnx-network
|
||||
|
||||
# Redis Cache
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
command: redis-server --appendonly yes
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- openlearnx-network
|
||||
|
||||
networks:
|
||||
openlearnx-network:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
redis_data:
|
||||
app_uploads:
|
||||
app_logs:
|
||||
+694
-178
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { Play, Lock, Shield, AlertTriangle, Users, Trophy, Clock } from 'lucide-react'
|
||||
import { Play, Lock, Shield, AlertTriangle, Users, Trophy, Clock, Code, Zap, Sparkles, Star, Brain, CheckCircle, XCircle } from 'lucide-react'
|
||||
|
||||
type UserRole = 'selector' | 'host' | 'participant'
|
||||
type ExamStatus = 'waiting' | 'active' | 'completed'
|
||||
@@ -139,7 +139,6 @@ export default function CodingExamPlatform() {
|
||||
exam_details: data.exam_details || {}
|
||||
}))
|
||||
|
||||
// ✅ ENHANCED SUCCESS MESSAGE WITH REDIRECT INFO
|
||||
alert(`✅ Exam Created Successfully!
|
||||
|
||||
📝 Exam Code: ${participantCode}
|
||||
@@ -251,6 +250,7 @@ Redirecting to exam interface...`)
|
||||
}
|
||||
|
||||
const submitSolution = async () => {
|
||||
setIsExecuting(true)
|
||||
try {
|
||||
const response = await fetch('http://127.0.0.1:5000/api/exam/submit-solution', {
|
||||
method: 'POST',
|
||||
@@ -265,6 +265,8 @@ Redirecting to exam interface...`)
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Failed to submit solution')
|
||||
} finally {
|
||||
setIsExecuting(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,108 +286,315 @@ Redirecting to exam interface...`)
|
||||
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
|
||||
}
|
||||
|
||||
// Role Selection Screen
|
||||
// Role Selection Screen with Enhanced Animations
|
||||
if (userRole === 'selector') {
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-blue-900 to-purple-900 flex items-center justify-center">
|
||||
<div className="bg-white rounded-lg shadow-xl p-8 max-w-md w-full">
|
||||
<h1 className="text-2xl font-bold text-center mb-8">OpenLearnX Coding Exam</h1>
|
||||
<div className="min-h-screen bg-gradient-to-br from-blue-900 via-purple-900 to-indigo-900 flex items-center justify-center relative overflow-hidden animate-fade-in">
|
||||
{/* Animated Background Elements */}
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
<div className="absolute top-1/4 left-1/4 w-32 h-32 bg-white rounded-full animate-float"></div>
|
||||
<div className="absolute top-3/4 right-1/4 w-24 h-24 bg-white rounded-full animate-float animate-delay-500"></div>
|
||||
<div className="absolute top-1/2 right-1/3 w-16 h-16 bg-white rounded-full animate-float animate-delay-1000"></div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<button
|
||||
onClick={() => setUserRole('host')}
|
||||
className="w-full bg-blue-600 hover:bg-blue-700 text-white py-3 px-4 rounded-lg flex items-center justify-center space-x-2"
|
||||
>
|
||||
<Users className="h-5 w-5" />
|
||||
<span>Host an Exam</span>
|
||||
</button>
|
||||
{/* Floating particles */}
|
||||
<div className="absolute top-1/6 left-1/6 w-2 h-2 bg-white rounded-full animate-pulse opacity-60"></div>
|
||||
<div className="absolute top-2/3 left-3/4 w-1 h-1 bg-white rounded-full animate-pulse animate-delay-300 opacity-40"></div>
|
||||
<div className="absolute top-1/3 right-1/2 w-1.5 h-1.5 bg-white rounded-full animate-pulse animate-delay-700 opacity-50"></div>
|
||||
</div>
|
||||
|
||||
{/* Floating sparkles */}
|
||||
<div className="absolute top-1/4 right-1/3 animate-float animate-delay-200">
|
||||
<Sparkles className="w-6 h-6 text-white opacity-60 animate-pulse" />
|
||||
</div>
|
||||
<div className="absolute bottom-1/4 left-1/4 animate-float animate-delay-800">
|
||||
<Star className="w-4 h-4 text-white opacity-50 animate-spin-slow" />
|
||||
</div>
|
||||
|
||||
<div className="bg-white/95 backdrop-blur-sm rounded-2xl shadow-2xl p-10 max-w-lg w-full transform animate-scale-in hover:scale-105 transition-all duration-500 relative overflow-hidden group">
|
||||
{/* Card shine effect */}
|
||||
<div className="absolute inset-0 -translate-x-full group-hover:translate-x-full bg-gradient-to-r from-transparent via-white/20 to-transparent transition-transform duration-1000"></div>
|
||||
|
||||
<div className="relative z-10">
|
||||
<div className="text-center mb-10">
|
||||
<div className="flex justify-center mb-4 animate-bounce">
|
||||
<Code className="h-16 w-16 text-blue-600 animate-pulse" />
|
||||
</div>
|
||||
<h1 className="text-3xl font-bold text-gray-800 mb-3 animate-slide-down">
|
||||
OpenLearnX Coding Exam
|
||||
</h1>
|
||||
<p className="text-gray-600 animate-fade-in animate-delay-300">
|
||||
Choose your role to get started
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => setUserRole('participant')}
|
||||
className="w-full bg-green-600 hover:bg-green-700 text-white py-3 px-4 rounded-lg flex items-center justify-center space-x-2"
|
||||
>
|
||||
<Play className="h-5 w-5" />
|
||||
<span>Join an Exam</span>
|
||||
</button>
|
||||
<div className="space-y-6">
|
||||
<button
|
||||
onClick={() => setUserRole('host')}
|
||||
className="w-full bg-gradient-to-r from-blue-600 to-blue-700 hover:from-blue-700 hover:to-blue-800 text-white py-4 px-6 rounded-xl flex items-center justify-center space-x-3 transform transition-all duration-300 hover:scale-105 hover:shadow-2xl active:scale-95 animate-slide-up group relative overflow-hidden"
|
||||
style={{ animationDelay: '0.1s' }}
|
||||
>
|
||||
{/* Button background animation */}
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-blue-500 to-purple-600 opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||
|
||||
<div className="relative z-10 flex items-center space-x-3">
|
||||
<Users className="h-6 w-6 transition-transform duration-300 group-hover:rotate-12 group-hover:scale-110" />
|
||||
<span className="text-lg font-semibold group-hover:tracking-wider transition-all duration-300">Host an Exam</span>
|
||||
</div>
|
||||
|
||||
{/* Floating particles on hover */}
|
||||
<div className="absolute -top-1 -right-1 opacity-0 group-hover:opacity-100 group-hover:animate-ping transition-all duration-300">
|
||||
<div className="w-3 h-3 bg-white rounded-full"></div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => setUserRole('participant')}
|
||||
className="w-full bg-gradient-to-r from-green-600 to-green-700 hover:from-green-700 hover:to-green-800 text-white py-4 px-6 rounded-xl flex items-center justify-center space-x-3 transform transition-all duration-300 hover:scale-105 hover:shadow-2xl active:scale-95 animate-slide-up group relative overflow-hidden"
|
||||
style={{ animationDelay: '0.2s' }}
|
||||
>
|
||||
{/* Button background animation */}
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-green-500 to-teal-600 opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||
|
||||
<div className="relative z-10 flex items-center space-x-3">
|
||||
<Play className="h-6 w-6 transition-transform duration-300 group-hover:translate-x-1 group-hover:scale-110" />
|
||||
<span className="text-lg font-semibold group-hover:tracking-wider transition-all duration-300">Join an Exam</span>
|
||||
</div>
|
||||
|
||||
{/* Floating particles on hover */}
|
||||
<div className="absolute -top-1 -right-1 opacity-0 group-hover:opacity-100 group-hover:animate-ping transition-all duration-300">
|
||||
<div className="w-3 h-3 bg-white rounded-full"></div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Animated footer */}
|
||||
<div className="mt-8 text-center animate-fade-in animate-delay-500">
|
||||
<p className="text-sm text-gray-500 hover:text-gray-700 transition-colors duration-300">
|
||||
Secure • Real-time • Professional
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Host Setup Screen
|
||||
// Host Setup Screen with Enhanced UI
|
||||
if (userRole === 'host' && !examId) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-blue-900 to-purple-900 flex items-center justify-center">
|
||||
<div className="bg-white rounded-lg shadow-xl p-8 max-w-md w-full">
|
||||
<h1 className="text-2xl font-bold text-center mb-8">Host Coding Exam</h1>
|
||||
<div className="min-h-screen bg-gradient-to-br from-blue-900 via-indigo-900 to-purple-900 flex items-center justify-center relative overflow-hidden animate-fade-in">
|
||||
{/* Enhanced background animations */}
|
||||
<div className="absolute inset-0 opacity-5">
|
||||
<div className="absolute top-0 left-0 w-96 h-96 bg-white rounded-full mix-blend-overlay animate-blob"></div>
|
||||
<div className="absolute top-0 right-0 w-96 h-96 bg-white rounded-full mix-blend-overlay animate-blob animation-delay-2000"></div>
|
||||
<div className="absolute -bottom-8 left-20 w-96 h-96 bg-white rounded-full mix-blend-overlay animate-blob animation-delay-4000"></div>
|
||||
</div>
|
||||
|
||||
{/* Floating icons */}
|
||||
<div className="absolute top-1/5 right-1/5 animate-float animate-delay-1000">
|
||||
<Brain className="w-8 h-8 text-white opacity-30 animate-pulse" />
|
||||
</div>
|
||||
<div className="absolute bottom-1/5 left-1/5 animate-float animate-delay-2000">
|
||||
<Zap className="w-6 h-6 text-white opacity-20 animate-bounce" />
|
||||
</div>
|
||||
|
||||
<div className="bg-white/95 backdrop-blur-lg rounded-3xl shadow-2xl p-12 max-w-xl w-full transform animate-scale-in hover:scale-105 transition-all duration-500 relative overflow-hidden group">
|
||||
{/* Enhanced shine effect */}
|
||||
<div className="absolute inset-0 -translate-x-full group-hover:translate-x-full bg-gradient-to-r from-transparent via-blue-200/30 to-transparent transition-transform duration-1000"></div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Enter your name"
|
||||
value={participantName}
|
||||
onChange={(e) => setParticipantName(e.target.value)}
|
||||
className="w-full p-3 border border-gray-300 rounded-lg"
|
||||
/>
|
||||
<div className="relative z-10">
|
||||
<div className="text-center mb-8">
|
||||
<div className="flex justify-center mb-6 relative">
|
||||
<div className="p-4 bg-blue-100 rounded-full animate-bounce">
|
||||
<Users className="h-12 w-12 text-blue-600 animate-pulse" />
|
||||
</div>
|
||||
{/* Floating particles around icon */}
|
||||
<div className="absolute -top-2 -right-2 w-3 h-3 bg-blue-400 rounded-full animate-ping"></div>
|
||||
<div className="absolute -bottom-2 -left-2 w-2 h-2 bg-blue-300 rounded-full animate-ping animation-delay-500"></div>
|
||||
</div>
|
||||
<h1 className="text-4xl font-bold text-gray-800 mb-4 animate-slide-down">
|
||||
Host Coding Exam
|
||||
</h1>
|
||||
<p className="text-gray-600 text-lg animate-fade-in animate-delay-300">
|
||||
Create a secure coding environment for your participants
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={createExam}
|
||||
disabled={!participantName}
|
||||
className="w-full bg-blue-600 hover:bg-blue-700 disabled:bg-gray-400 text-white py-3 px-4 rounded-lg"
|
||||
>
|
||||
Create Exam
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Debug Info */}
|
||||
<div className="mt-4 p-3 bg-gray-50 rounded text-xs text-gray-600">
|
||||
<p>Will create with host_name: "{participantName}"</p>
|
||||
<p>✅ Will display exam_code (6 chars), not exam_id</p>
|
||||
<p>🔄 After creation → redirect to /coding/host/[examCode]</p>
|
||||
<div className="space-y-6">
|
||||
<div className="relative animate-slide-up" style={{ animationDelay: '0.1s' }}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Enter your name"
|
||||
value={participantName}
|
||||
onChange={(e) => setParticipantName(e.target.value)}
|
||||
className="w-full p-4 border-2 border-gray-200 rounded-xl text-lg transition-all duration-300 focus:ring-4 focus:ring-blue-200 focus:border-blue-500 hover:border-blue-300 bg-gray-50 hover:bg-white focus:bg-white group"
|
||||
/>
|
||||
{/* Input decoration */}
|
||||
<div className="absolute right-4 top-1/2 transform -translate-y-1/2 opacity-0 group-focus-within:opacity-100 transition-opacity duration-300">
|
||||
<CheckCircle className="w-5 h-5 text-green-500 animate-pulse" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={createExam}
|
||||
disabled={!participantName}
|
||||
className="w-full bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700 disabled:from-gray-400 disabled:to-gray-500 text-white py-4 px-6 rounded-xl text-lg font-semibold transform transition-all duration-300 hover:scale-105 hover:shadow-2xl active:scale-95 disabled:hover:scale-100 animate-slide-up group relative overflow-hidden"
|
||||
style={{ animationDelay: '0.2s' }}
|
||||
>
|
||||
{/* Button animation background */}
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-purple-600 to-pink-600 opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||
|
||||
<div className="relative z-10 flex items-center justify-center space-x-3">
|
||||
<span className="group-hover:tracking-wider transition-all duration-300">Create Exam</span>
|
||||
<Sparkles className="w-5 h-5 group-hover:animate-spin transition-transform duration-300" />
|
||||
</div>
|
||||
|
||||
{/* Ripple effect */}
|
||||
<div className="absolute inset-0 opacity-0 group-hover:opacity-20 group-hover:animate-ping transition-all duration-300 bg-white rounded-xl"></div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Enhanced Debug Info */}
|
||||
<div className="mt-8 p-6 bg-gradient-to-r from-gray-50 to-blue-50 rounded-xl text-sm text-gray-600 animate-fade-in border border-gray-200 hover:border-blue-300 transition-colors duration-300" style={{ animationDelay: '0.3s' }}>
|
||||
<div className="flex items-center space-x-2 mb-3">
|
||||
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
|
||||
<span className="font-semibold">System Status</span>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<p className="flex items-center space-x-2">
|
||||
<CheckCircle className="w-4 h-4 text-green-500" />
|
||||
<span>Will create with host_name: "{participantName}"</span>
|
||||
</p>
|
||||
<p className="flex items-center space-x-2">
|
||||
<CheckCircle className="w-4 h-4 text-green-500" />
|
||||
<span>Will display exam_code (6 chars), not exam_id</span>
|
||||
</p>
|
||||
<p className="flex items-center space-x-2">
|
||||
<Zap className="w-4 h-4 text-blue-500 animate-pulse" />
|
||||
<span>After creation → redirect to /coding/host/[examCode]</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Join Exam Screen
|
||||
// Join Exam Screen with Enhanced Animations
|
||||
if (userRole === 'participant' && !examInfo) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-green-900 to-blue-900 flex items-center justify-center">
|
||||
<div className="bg-white rounded-lg shadow-xl p-8 max-w-md w-full">
|
||||
<h1 className="text-2xl font-bold text-center mb-8">Join Coding Exam</h1>
|
||||
<div className="min-h-screen bg-gradient-to-br from-green-900 via-emerald-900 to-blue-900 flex items-center justify-center relative overflow-hidden animate-fade-in">
|
||||
{/* Enhanced background effects */}
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
<div className="absolute top-1/4 left-1/4 w-40 h-40 bg-white rounded-full animate-float hover:scale-150 transition-transform duration-500"></div>
|
||||
<div className="absolute top-3/4 right-1/4 w-32 h-32 bg-white rounded-full animate-float animate-delay-500 hover:scale-125 transition-transform duration-500"></div>
|
||||
<div className="absolute top-1/2 right-1/3 w-24 h-24 bg-white rounded-full animate-float animate-delay-1000 hover:scale-200 transition-transform duration-500"></div>
|
||||
</div>
|
||||
|
||||
{/* Animated particles */}
|
||||
<div className="absolute inset-0 pointer-events-none">
|
||||
<div className="absolute top-1/6 left-1/6 w-3 h-3 bg-white rounded-full animate-pulse opacity-60 shadow-lg"></div>
|
||||
<div className="absolute top-2/3 left-3/4 w-2 h-2 bg-white rounded-full animate-pulse animate-delay-300 opacity-40"></div>
|
||||
<div className="absolute top-1/2 left-1/5 w-2.5 h-2.5 bg-white rounded-full animate-pulse animate-delay-700 opacity-50"></div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white/95 backdrop-blur-lg rounded-3xl shadow-2xl p-12 max-w-xl w-full transform animate-scale-in hover:scale-105 transition-all duration-500 relative overflow-hidden group">
|
||||
{/* Enhanced card effects */}
|
||||
<div className="absolute inset-0 -translate-x-full group-hover:translate-x-full bg-gradient-to-r from-transparent via-green-200/30 to-transparent transition-transform duration-1000"></div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Enter exam code (e.g., 3BPIBZ)"
|
||||
value={examId}
|
||||
onChange={(e) => setExamId(e.target.value.toUpperCase())}
|
||||
className="w-full p-3 border border-gray-300 rounded-lg text-center font-mono text-lg tracking-widest uppercase"
|
||||
maxLength={6}
|
||||
/>
|
||||
<div className="relative z-10">
|
||||
<div className="text-center mb-10">
|
||||
<div className="flex justify-center mb-6 relative">
|
||||
<div className="p-4 bg-green-100 rounded-full animate-bounce">
|
||||
<Play className="h-12 w-12 text-green-600 animate-pulse" />
|
||||
</div>
|
||||
{/* Animated ring around icon */}
|
||||
<div className="absolute inset-0 border-4 border-green-300 rounded-full animate-ping opacity-30"></div>
|
||||
<div className="absolute inset-2 border-2 border-green-400 rounded-full animate-ping opacity-40 animation-delay-500"></div>
|
||||
</div>
|
||||
<h1 className="text-4xl font-bold text-gray-800 mb-4 animate-slide-down">
|
||||
Join Coding Exam
|
||||
</h1>
|
||||
<p className="text-gray-600 text-lg animate-fade-in animate-delay-300">
|
||||
Enter the exam code to participate in the coding challenge
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Enter your name"
|
||||
value={participantName}
|
||||
onChange={(e) => setParticipantName(e.target.value)}
|
||||
className="w-full p-3 border border-gray-300 rounded-lg"
|
||||
/>
|
||||
|
||||
<button
|
||||
onClick={joinExam}
|
||||
disabled={!examId || !participantName}
|
||||
className="w-full bg-green-600 hover:bg-green-700 disabled:bg-gray-400 text-white py-3 px-4 rounded-lg"
|
||||
>
|
||||
Join Exam
|
||||
</button>
|
||||
|
||||
{/* Debug Info */}
|
||||
<div className="text-xs text-gray-500 p-3 bg-gray-50 rounded">
|
||||
<p>Will send: exam_code="{examId}" student_name="{participantName}"</p>
|
||||
<p>✅ After join → redirect to /coding/exam</p>
|
||||
<div className="space-y-6">
|
||||
<div className="relative animate-slide-up" style={{ animationDelay: '0.1s' }}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Enter exam code (e.g., 3BPIBZ)"
|
||||
value={examId}
|
||||
onChange={(e) => setExamId(e.target.value.toUpperCase())}
|
||||
className="w-full p-4 border-2 border-gray-200 rounded-xl text-center font-mono text-2xl tracking-widest uppercase transition-all duration-300 focus:ring-4 focus:ring-green-200 focus:border-green-500 hover:border-green-300 bg-gray-50 hover:bg-white focus:bg-white relative group"
|
||||
maxLength={6}
|
||||
/>
|
||||
{/* Input decorations */}
|
||||
<div className="absolute inset-x-0 bottom-0 h-1 bg-gradient-to-r from-green-400 to-blue-400 transform scale-x-0 group-focus-within:scale-x-100 transition-transform duration-300 rounded-full"></div>
|
||||
{examId.length === 6 && (
|
||||
<div className="absolute right-4 top-1/2 transform -translate-y-1/2 animate-bounce">
|
||||
<CheckCircle className="w-6 h-6 text-green-500 animate-pulse" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="relative animate-slide-up" style={{ animationDelay: '0.2s' }}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Enter your name"
|
||||
value={participantName}
|
||||
onChange={(e) => setParticipantName(e.target.value)}
|
||||
className="w-full p-4 border-2 border-gray-200 rounded-xl text-lg transition-all duration-300 focus:ring-4 focus:ring-green-200 focus:border-green-500 hover:border-green-300 bg-gray-50 hover:bg-white focus:bg-white group"
|
||||
/>
|
||||
{/* Name validation indicator */}
|
||||
{participantName.length > 2 && (
|
||||
<div className="absolute right-4 top-1/2 transform -translate-y-1/2 animate-bounce">
|
||||
<CheckCircle className="w-5 h-5 text-green-500 animate-pulse" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={joinExam}
|
||||
disabled={!examId || !participantName}
|
||||
className="w-full bg-gradient-to-r from-green-600 to-emerald-600 hover:from-green-700 hover:to-emerald-700 disabled:from-gray-400 disabled:to-gray-500 text-white py-4 px-6 rounded-xl text-lg font-semibold transform transition-all duration-300 hover:scale-105 hover:shadow-2xl active:scale-95 disabled:hover:scale-100 animate-slide-up group relative overflow-hidden"
|
||||
style={{ animationDelay: '0.3s' }}
|
||||
>
|
||||
{/* Enhanced button animations */}
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-blue-600 to-purple-600 opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||
|
||||
<div className="relative z-10 flex items-center justify-center space-x-3">
|
||||
<span className="group-hover:tracking-wider transition-all duration-300">Join Exam</span>
|
||||
<div className="flex space-x-1">
|
||||
<div className="w-1 h-1 bg-white rounded-full animate-bounce"></div>
|
||||
<div className="w-1 h-1 bg-white rounded-full animate-bounce animation-delay-200"></div>
|
||||
<div className="w-1 h-1 bg-white rounded-full animate-bounce animation-delay-400"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Button ripple effect */}
|
||||
<div className="absolute inset-0 opacity-0 group-hover:opacity-20 group-hover:animate-ping transition-all duration-300 bg-white rounded-xl"></div>
|
||||
</button>
|
||||
|
||||
{/* Enhanced Debug Info */}
|
||||
<div className="text-sm text-gray-500 p-6 bg-gradient-to-r from-gray-50 to-green-50 rounded-xl animate-fade-in border border-gray-200 hover:border-green-300 transition-colors duration-300" style={{ animationDelay: '0.4s' }}>
|
||||
<div className="flex items-center space-x-2 mb-3">
|
||||
<div className="w-2 h-2 bg-blue-500 rounded-full animate-pulse"></div>
|
||||
<span className="font-semibold">Connection Status</span>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<p className="flex items-center space-x-2">
|
||||
<CheckCircle className="w-4 h-4 text-green-500" />
|
||||
<span>Will send: exam_code="{examId}" student_name="{participantName}"</span>
|
||||
</p>
|
||||
<p className="flex items-center space-x-2">
|
||||
<Zap className="w-4 h-4 text-blue-500 animate-pulse" />
|
||||
<span>After join → redirect to /coding/exam</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -393,139 +602,446 @@ Redirecting to exam interface...`)
|
||||
)
|
||||
}
|
||||
|
||||
// System Requirements Check
|
||||
// Enhanced System Requirements Check
|
||||
if (!systemChecked) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-900 text-white flex items-center justify-center">
|
||||
<div className="bg-gray-800 rounded-lg p-8 max-w-lg w-full">
|
||||
<h1 className="text-2xl font-bold mb-6">System Requirements Check</h1>
|
||||
<div className="min-h-screen bg-gradient-to-br from-gray-900 via-red-900 to-black text-white flex items-center justify-center relative overflow-hidden animate-fade-in">
|
||||
{/* Animated warning elements */}
|
||||
<div className="absolute inset-0 opacity-5">
|
||||
<div className="absolute top-1/4 left-1/4 w-32 h-32 bg-red-500 rounded-full animate-pulse"></div>
|
||||
<div className="absolute top-3/4 right-1/4 w-24 h-24 bg-yellow-500 rounded-full animate-pulse animate-delay-500"></div>
|
||||
<div className="absolute top-1/2 right-1/3 w-16 h-16 bg-orange-500 rounded-full animate-pulse animate-delay-1000"></div>
|
||||
</div>
|
||||
|
||||
{/* Floating warning icons */}
|
||||
<div className="absolute top-1/5 right-1/5 animate-float animate-delay-1000">
|
||||
<AlertTriangle className="w-8 h-8 text-red-400 opacity-60 animate-pulse" />
|
||||
</div>
|
||||
<div className="absolute bottom-1/5 left-1/5 animate-float animate-delay-2000">
|
||||
<Shield className="w-6 h-6 text-yellow-400 opacity-40 animate-bounce" />
|
||||
</div>
|
||||
|
||||
<div className="bg-gray-800/95 backdrop-blur-lg rounded-3xl p-12 max-w-2xl w-full transform animate-scale-in hover:scale-105 transition-all duration-500 border border-red-500/30 relative overflow-hidden group">
|
||||
{/* Security-themed background */}
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-red-900/20 to-yellow-900/20 opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
|
||||
|
||||
<div className="space-y-4 mb-6">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Shield className="h-5 w-5 text-green-400" />
|
||||
<span>Fullscreen mode support</span>
|
||||
<div className="relative z-10">
|
||||
<div className="text-center mb-8">
|
||||
<div className="flex justify-center mb-6 relative">
|
||||
<div className="p-4 bg-red-900/50 rounded-full animate-pulse">
|
||||
<Shield className="h-16 w-16 text-red-400 animate-bounce" />
|
||||
</div>
|
||||
{/* Security rings */}
|
||||
<div className="absolute inset-0 border-2 border-red-500 rounded-full animate-ping opacity-30"></div>
|
||||
<div className="absolute inset-2 border border-yellow-500 rounded-full animate-ping opacity-40 animation-delay-500"></div>
|
||||
</div>
|
||||
<h1 className="text-4xl font-bold mb-6 animate-slide-down">
|
||||
System Requirements Check
|
||||
</h1>
|
||||
<p className="text-xl text-gray-300 animate-fade-in animate-delay-300">
|
||||
Preparing secure exam environment
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Lock className="h-5 w-5 text-yellow-400" />
|
||||
<span>Copy/paste will be disabled</span>
|
||||
|
||||
<div className="space-y-6 mb-10">
|
||||
<div className="flex items-center space-x-4 p-4 bg-gray-700/50 rounded-xl animate-slide-up hover:bg-gray-600/50 transition-colors duration-300" style={{ animationDelay: '0.1s' }}>
|
||||
<Shield className="h-8 w-8 text-green-400 animate-pulse" />
|
||||
<div className="flex-1">
|
||||
<span className="text-lg font-medium">Fullscreen mode support</span>
|
||||
<p className="text-sm text-gray-400">Required for secure examination</p>
|
||||
</div>
|
||||
<CheckCircle className="h-6 w-6 text-green-400 animate-bounce" />
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-4 p-4 bg-gray-700/50 rounded-xl animate-slide-up hover:bg-gray-600/50 transition-colors duration-300" style={{ animationDelay: '0.2s' }}>
|
||||
<Lock className="h-8 w-8 text-yellow-400 animate-bounce" />
|
||||
<div className="flex-1">
|
||||
<span className="text-lg font-medium">Copy/paste will be disabled</span>
|
||||
<p className="text-sm text-gray-400">Prevents unauthorized assistance</p>
|
||||
</div>
|
||||
<XCircle className="h-6 w-6 text-yellow-400 animate-pulse" />
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-4 p-4 bg-gray-700/50 rounded-xl animate-slide-up hover:bg-gray-600/50 transition-colors duration-300" style={{ animationDelay: '0.3s' }}>
|
||||
<AlertTriangle className="h-8 w-8 text-red-400 animate-pulse" />
|
||||
<div className="flex-1">
|
||||
<span className="text-lg font-medium">Virtual environments will be detected</span>
|
||||
<p className="text-sm text-gray-400">Ensures exam integrity</p>
|
||||
</div>
|
||||
<Shield className="h-6 w-6 text-red-400 animate-bounce" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<AlertTriangle className="h-5 w-5 text-red-400" />
|
||||
<span>Virtual environments will be detected</span>
|
||||
|
||||
<button
|
||||
onClick={acceptSystemRequirements}
|
||||
className="w-full bg-gradient-to-r from-red-600 to-red-700 hover:from-red-700 hover:to-red-800 text-white py-4 px-6 rounded-xl text-lg font-semibold transform transition-all duration-300 hover:scale-105 hover:shadow-2xl active:scale-95 animate-slide-up group relative overflow-hidden"
|
||||
style={{ animationDelay: '0.4s' }}
|
||||
>
|
||||
{/* Button warning effect */}
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-orange-600 to-yellow-600 opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||
|
||||
<div className="relative z-10 flex items-center justify-center space-x-3">
|
||||
<Lock className="h-6 w-6 group-hover:animate-bounce transition-transform duration-300" />
|
||||
<span className="group-hover:tracking-wider transition-all duration-300">Accept & Enter Secure Mode</span>
|
||||
<Shield className="h-6 w-6 group-hover:animate-pulse transition-transform duration-300" />
|
||||
</div>
|
||||
|
||||
{/* Warning pulse effect */}
|
||||
<div className="absolute inset-0 opacity-0 group-hover:opacity-20 group-hover:animate-ping transition-all duration-300 bg-white rounded-xl"></div>
|
||||
</button>
|
||||
|
||||
{/* Security notice */}
|
||||
<div className="mt-6 p-4 bg-yellow-900/30 border border-yellow-500/50 rounded-xl animate-fade-in animate-delay-500">
|
||||
<div className="flex items-center space-x-2 mb-2">
|
||||
<AlertTriangle className="w-5 h-5 text-yellow-400 animate-pulse" />
|
||||
<span className="font-semibold text-yellow-300">Security Notice</span>
|
||||
</div>
|
||||
<p className="text-sm text-yellow-200">
|
||||
This exam uses advanced security measures. Browser restrictions will be enforced during the examination period.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={acceptSystemRequirements}
|
||||
className="w-full bg-red-600 hover:bg-red-700 text-white py-3 px-4 rounded-lg"
|
||||
>
|
||||
Accept & Enter Secure Mode
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Main Exam Interface
|
||||
// Enhanced Main Exam Interface
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-900 text-white">
|
||||
{/* Security Status Bar */}
|
||||
<div className="bg-red-900 text-white p-2 flex items-center justify-between">
|
||||
<div className="flex items-center space-x-4">
|
||||
<Shield className="h-4 w-4" />
|
||||
<span className="text-sm font-medium">SECURE MODE ACTIVE</span>
|
||||
<Lock className="h-4 w-4" />
|
||||
<span className="text-sm">Copy/Paste Disabled</span>
|
||||
<div className="min-h-screen bg-gradient-to-br from-gray-900 via-slate-900 to-black text-white animate-fade-in relative overflow-hidden">
|
||||
{/* Animated background elements */}
|
||||
<div className="absolute inset-0 opacity-5">
|
||||
<div className="absolute top-0 left-0 w-96 h-96 bg-blue-500 rounded-full mix-blend-overlay animate-blob"></div>
|
||||
<div className="absolute top-0 right-0 w-96 h-96 bg-purple-500 rounded-full mix-blend-overlay animate-blob animation-delay-2000"></div>
|
||||
<div className="absolute -bottom-8 left-20 w-96 h-96 bg-green-500 rounded-full mix-blend-overlay animate-blob animation-delay-4000"></div>
|
||||
</div>
|
||||
|
||||
{/* Enhanced Security Status Bar */}
|
||||
<div className="bg-gradient-to-r from-red-900 via-red-800 to-red-900 text-white p-4 flex items-center justify-between animate-slide-down shadow-2xl relative overflow-hidden">
|
||||
{/* Security bar background animation */}
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-red-600/20 to-transparent animate-pulse"></div>
|
||||
|
||||
<div className="flex items-center space-x-6 relative z-10">
|
||||
<div className="flex items-center space-x-2 px-3 py-1 bg-red-800/50 rounded-full">
|
||||
<Shield className="h-5 w-5 animate-pulse" />
|
||||
<span className="text-sm font-bold tracking-wider">SECURE MODE ACTIVE</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2 px-3 py-1 bg-red-700/50 rounded-full">
|
||||
<Lock className="h-5 w-5 animate-bounce" />
|
||||
<span className="text-sm font-medium">Copy/Paste Disabled</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2 px-3 py-1 bg-red-600/50 rounded-full">
|
||||
<AlertTriangle className="h-5 w-5 animate-pulse" />
|
||||
<span className="text-sm font-medium">VM Detection Active</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="flex items-center space-x-6 relative z-10">
|
||||
{timeRemaining > 0 && (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Clock className="h-4 w-4" />
|
||||
<span className="font-mono">{formatTime(timeRemaining)}</span>
|
||||
<div className="flex items-center space-x-3 px-4 py-2 bg-gradient-to-r from-red-700 to-red-600 rounded-full shadow-lg">
|
||||
<Clock className="h-5 w-5 animate-pulse" />
|
||||
<span className="font-mono text-lg font-bold tracking-wider">{formatTime(timeRemaining)}</span>
|
||||
{timeRemaining < 300 && ( // Last 5 minutes warning
|
||||
<div className="w-2 h-2 bg-yellow-400 rounded-full animate-ping"></div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<span>Exam: {examId}</span>
|
||||
|
||||
<div className="px-4 py-2 bg-gradient-to-r from-gray-700 to-gray-600 rounded-full shadow-lg">
|
||||
<span className="font-mono tracking-widest text-lg font-bold">Exam: {examId}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex h-screen">
|
||||
{/* Main Coding Area */}
|
||||
<div className="flex-1 p-6">
|
||||
{/* Problem Description */}
|
||||
<div className="bg-gray-800 rounded-lg p-6 mb-6">
|
||||
<h2 className="text-xl font-bold mb-4">Problem: String Capitalizer</h2>
|
||||
<p className="mb-4">Write a function that converts a string to uppercase.</p>
|
||||
<div className="bg-gray-900 p-4 rounded">
|
||||
<code>
|
||||
{/* Enhanced Main Coding Area */}
|
||||
<div className="flex-1 p-8 animate-slide-right relative">
|
||||
{/* Problem Description with Enhanced Styling */}
|
||||
<div className="bg-gradient-to-br from-gray-800 via-gray-800 to-gray-700 rounded-2xl p-8 mb-8 transform transition-all duration-300 hover:shadow-2xl hover:shadow-blue-500/20 hover:scale-105 relative overflow-hidden group">
|
||||
{/* Card background animation */}
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-blue-900/20 to-purple-900/20 opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
|
||||
|
||||
<div className="relative z-10">
|
||||
<div className="flex items-center space-x-4 mb-6">
|
||||
<div className="p-3 bg-blue-600/20 rounded-xl animate-pulse">
|
||||
<Code className="h-8 w-8 text-blue-400" />
|
||||
</div>
|
||||
<h2 className="text-3xl font-bold animate-slide-up">Problem: String Capitalizer</h2>
|
||||
<div className="flex-1 h-1 bg-gradient-to-r from-blue-500 to-purple-500 rounded-full animate-pulse"></div>
|
||||
</div>
|
||||
|
||||
<p className="mb-6 text-lg text-gray-300 animate-slide-up" style={{ animationDelay: '0.1s' }}>
|
||||
Write a function that converts a string to uppercase.
|
||||
</p>
|
||||
|
||||
<div className="bg-black/50 p-6 rounded-xl transform transition-all duration-300 hover:bg-black/60 animate-slide-up border border-gray-600 hover:border-blue-500/50" style={{ animationDelay: '0.2s' }}>
|
||||
<pre className="text-green-400 font-mono text-lg">
|
||||
{`def capitalize_string(text):
|
||||
# Your code here
|
||||
pass
|
||||
|
||||
# Test: capitalize_string("hello") should return "HELLO"`}
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Code Editor */}
|
||||
<div className="bg-gray-800 rounded-lg p-4">
|
||||
<h3 className="text-lg font-bold mb-4">Code Editor</h3>
|
||||
<textarea
|
||||
value={code}
|
||||
onChange={(e) => setCode(e.target.value)}
|
||||
placeholder="def capitalize_string(text):\n # Your code here\n pass"
|
||||
className="w-full h-64 bg-gray-900 text-green-400 font-mono p-4 rounded border border-gray-600 resize-none"
|
||||
style={{ userSelect: 'none', WebkitUserSelect: 'none' }}
|
||||
/>
|
||||
|
||||
<div className="flex space-x-4 mt-4">
|
||||
<button
|
||||
onClick={submitSolution}
|
||||
className="bg-green-600 hover:bg-green-700 px-6 py-2 rounded flex items-center space-x-2"
|
||||
>
|
||||
<Play className="h-4 w-4" />
|
||||
<span>Submit Solution</span>
|
||||
</button>
|
||||
|
||||
{userRole === 'host' && examInfo?.status === 'waiting' && (
|
||||
<button
|
||||
onClick={startExam}
|
||||
className="bg-blue-600 hover:bg-blue-700 px-6 py-2 rounded"
|
||||
>
|
||||
Start Exam
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{/* Hover shine effect */}
|
||||
<div className="absolute inset-0 -translate-x-full group-hover:translate-x-full bg-gradient-to-r from-transparent via-white/5 to-transparent transition-transform duration-1000"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Leaderboard Sidebar */}
|
||||
<div className="w-80 bg-gray-800 p-6">
|
||||
<div className="flex items-center space-x-2 mb-4">
|
||||
<Trophy className="h-6 w-6 text-yellow-400" />
|
||||
<h3 className="text-xl font-bold">Leaderboard</h3>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
{leaderboard.map((participant, index) => (
|
||||
<div key={index} className={`p-3 rounded ${index === 0 ? 'bg-yellow-900' : index === 1 ? 'bg-gray-700' : index === 2 ? 'bg-orange-900' : 'bg-gray-700'}`}>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="font-medium">
|
||||
{index + 1}. {participant.name}
|
||||
</span>
|
||||
<span className="font-bold">{participant.score}%</span>
|
||||
{/* Enhanced Code Editor */}
|
||||
<div className="bg-gradient-to-br from-gray-800 via-gray-800 to-gray-700 rounded-2xl p-8 transform transition-all duration-300 hover:shadow-2xl hover:shadow-purple-500/20 relative overflow-hidden group">
|
||||
{/* Editor background animation */}
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-purple-900/20 to-green-900/20 opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
|
||||
|
||||
<div className="relative z-10">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="p-3 bg-purple-600/20 rounded-xl animate-pulse">
|
||||
<Zap className="h-8 w-8 text-purple-400" />
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold animate-slide-up">Code Editor</h3>
|
||||
</div>
|
||||
|
||||
{/* Editor status indicators */}
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="flex items-center space-x-2 px-3 py-1 bg-green-900/30 rounded-full">
|
||||
<div className="w-2 h-2 bg-green-400 rounded-full animate-pulse"></div>
|
||||
<span className="text-sm text-green-300">Ready</span>
|
||||
</div>
|
||||
<div className="text-sm text-gray-400 font-mono">
|
||||
Lines: {code.split('\n').length} | Chars: {code.length}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="relative">
|
||||
<textarea
|
||||
value={code}
|
||||
onChange={(e) => setCode(e.target.value)}
|
||||
placeholder="def capitalize_string(text):\n # Your code here\n pass"
|
||||
className="w-full h-80 bg-black/70 text-green-400 font-mono p-6 rounded-xl border-2 border-gray-600 resize-none transition-all duration-300 focus:border-green-500 focus:ring-4 focus:ring-green-500/20 hover:border-gray-500 animate-slide-up backdrop-blur-sm"
|
||||
style={{
|
||||
userSelect: 'none',
|
||||
WebkitUserSelect: 'none',
|
||||
animationDelay: '0.1s',
|
||||
lineHeight: '1.6',
|
||||
fontSize: '16px'
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Editor enhancements */}
|
||||
<div className="absolute top-4 right-4 flex space-x-2">
|
||||
{code.length > 0 && (
|
||||
<div className="px-2 py-1 bg-blue-900/50 rounded text-xs text-blue-300 animate-fade-in">
|
||||
Modified
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Line numbers overlay */}
|
||||
<div className="absolute left-2 top-6 text-gray-500 font-mono text-sm select-none pointer-events-none">
|
||||
{Array.from({ length: code.split('\n').length }, (_, i) => (
|
||||
<div key={i} className="h-6 leading-6">
|
||||
{i + 1}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between mt-6">
|
||||
<div className="flex space-x-4">
|
||||
<button
|
||||
onClick={submitSolution}
|
||||
disabled={isExecuting}
|
||||
className="bg-gradient-to-r from-green-600 to-emerald-600 hover:from-green-700 hover:to-emerald-700 disabled:from-gray-600 disabled:to-gray-700 px-8 py-3 rounded-xl flex items-center space-x-3 transform transition-all duration-300 hover:scale-105 hover:shadow-2xl active:scale-95 animate-slide-up group relative overflow-hidden"
|
||||
style={{ animationDelay: '0.2s' }}
|
||||
>
|
||||
{/* Button background animation */}
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-blue-600 to-purple-600 opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||
|
||||
<div className="relative z-10 flex items-center space-x-3">
|
||||
{isExecuting ? (
|
||||
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin"></div>
|
||||
) : (
|
||||
<Play className="h-5 w-5 transition-transform duration-300 group-hover:translate-x-1" />
|
||||
)}
|
||||
<span className="text-lg font-semibold">
|
||||
{isExecuting ? 'Submitting...' : 'Submit Solution'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Sparkle effect */}
|
||||
<div className="absolute -top-1 -right-1 opacity-0 group-hover:opacity-100 group-hover:animate-ping transition-all duration-300">
|
||||
<Sparkles className="w-4 h-4 text-white" />
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{userRole === 'host' && examInfo?.status === 'waiting' && (
|
||||
<button
|
||||
onClick={startExam}
|
||||
className="bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700 px-8 py-3 rounded-xl text-lg font-semibold transform transition-all duration-300 hover:scale-105 hover:shadow-2xl active:scale-95 animate-slide-up group relative overflow-hidden"
|
||||
style={{ animationDelay: '0.3s' }}
|
||||
>
|
||||
{/* Button animation */}
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-purple-600 to-pink-600 opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||
|
||||
<div className="relative z-10 flex items-center space-x-2">
|
||||
<span>Start Exam</span>
|
||||
<Zap className="h-5 w-5 group-hover:animate-bounce" />
|
||||
</div>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Code statistics */}
|
||||
<div className="flex items-center space-x-4 text-sm text-gray-400">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-2 h-2 bg-blue-400 rounded-full animate-pulse"></div>
|
||||
<span>Python 3.9</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<CheckCircle className="w-4 h-4 text-green-400" />
|
||||
<span>Syntax OK</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Editor shine effect */}
|
||||
<div className="absolute inset-0 -translate-x-full group-hover:translate-x-full bg-gradient-to-r from-transparent via-white/5 to-transparent transition-transform duration-1000"></div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={fetchLeaderboard}
|
||||
className="w-full mt-4 bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded text-sm"
|
||||
>
|
||||
Refresh Leaderboard
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Enhanced Leaderboard Sidebar */}
|
||||
<div className="w-96 bg-gradient-to-b from-gray-800 via-gray-800 to-gray-700 p-8 animate-slide-left relative overflow-hidden">
|
||||
{/* Sidebar background animation */}
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-yellow-900/10 to-orange-900/10 opacity-50"></div>
|
||||
|
||||
<div className="relative z-10">
|
||||
<div className="flex items-center space-x-4 mb-8 animate-slide-down">
|
||||
<div className="p-3 bg-yellow-600/20 rounded-xl animate-bounce">
|
||||
<Trophy className="h-8 w-8 text-yellow-400 animate-pulse" />
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold">Leaderboard</h3>
|
||||
<div className="flex-1 h-1 bg-gradient-to-r from-yellow-500 to-orange-500 rounded-full animate-pulse"></div>
|
||||
</div>
|
||||
|
||||
{/* Leaderboard stats */}
|
||||
<div className="mb-6 p-4 bg-black/30 rounded-xl border border-gray-600">
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<span className="text-sm text-gray-400">Total Participants</span>
|
||||
<span className="font-bold text-blue-400">{leaderboard.length}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-400">Completed</span>
|
||||
<span className="font-bold text-green-400">
|
||||
{leaderboard.filter(p => p.completed).length}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3 max-h-96 overflow-y-auto custom-scrollbar">
|
||||
{leaderboard.length > 0 ? leaderboard.map((participant, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`p-4 rounded-xl transform transition-all duration-300 hover:scale-105 hover:shadow-lg animate-slide-up relative overflow-hidden group cursor-pointer ${
|
||||
index === 0 ? 'bg-gradient-to-r from-yellow-900/50 to-orange-900/50 border border-yellow-500/30' :
|
||||
index === 1 ? 'bg-gradient-to-r from-gray-700/50 to-gray-600/50 border border-gray-400/30' :
|
||||
index === 2 ? 'bg-gradient-to-r from-orange-900/50 to-red-900/50 border border-orange-500/30' :
|
||||
'bg-gradient-to-r from-gray-700/50 to-gray-600/50 border border-gray-500/30'
|
||||
}`}
|
||||
style={{ animationDelay: `${0.1 * index}s` }}
|
||||
>
|
||||
{/* Rank indicator */}
|
||||
<div className="absolute top-2 left-2">
|
||||
{index < 3 ? (
|
||||
<div className={`w-6 h-6 rounded-full flex items-center justify-center text-xs font-bold ${
|
||||
index === 0 ? 'bg-yellow-500 text-black' :
|
||||
index === 1 ? 'bg-gray-400 text-black' :
|
||||
'bg-orange-500 text-white'
|
||||
}`}>
|
||||
{index + 1}
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-6 h-6 rounded-full bg-gray-600 flex items-center justify-center text-xs font-bold text-white">
|
||||
{index + 1}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Participant info */}
|
||||
<div className="ml-8">
|
||||
<div className="flex justify-between items-start mb-2">
|
||||
<span className="font-semibold text-lg group-hover:text-white transition-colors duration-300">
|
||||
{participant.name}
|
||||
</span>
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="font-bold text-xl animate-pulse">
|
||||
{participant.score}%
|
||||
</span>
|
||||
{participant.completed && (
|
||||
<CheckCircle className="w-5 h-5 text-green-400 animate-bounce" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Progress bar */}
|
||||
<div className="w-full bg-gray-600 rounded-full h-2 mb-2">
|
||||
<div
|
||||
className={`h-2 rounded-full transition-all duration-1000 ${
|
||||
index === 0 ? 'bg-gradient-to-r from-yellow-400 to-orange-400' :
|
||||
index === 1 ? 'bg-gradient-to-r from-gray-300 to-gray-400' :
|
||||
index === 2 ? 'bg-gradient-to-r from-orange-400 to-red-400' :
|
||||
'bg-gradient-to-r from-blue-400 to-purple-400'
|
||||
}`}
|
||||
style={{ width: `${participant.score}%` }}
|
||||
></div>
|
||||
</div>
|
||||
|
||||
{/* Submission time */}
|
||||
{participant.submitted_at && (
|
||||
<div className="text-xs text-gray-400">
|
||||
Submitted: {new Date(participant.submitted_at).toLocaleTimeString()}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Hover effect */}
|
||||
<div className="absolute inset-0 -translate-x-full group-hover:translate-x-full bg-gradient-to-r from-transparent via-white/5 to-transparent transition-transform duration-700"></div>
|
||||
</div>
|
||||
)) : (
|
||||
<div className="text-center py-8 text-gray-400 animate-pulse">
|
||||
<Users className="h-12 w-12 mx-auto mb-3 opacity-50" />
|
||||
<p>No participants yet</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={fetchLeaderboard}
|
||||
className="w-full mt-6 bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 px-6 py-3 rounded-xl text-lg font-semibold transform transition-all duration-300 hover:scale-105 hover:shadow-2xl active:scale-95 animate-slide-up group relative overflow-hidden"
|
||||
style={{ animationDelay: '0.4s' }}
|
||||
>
|
||||
{/* Button background animation */}
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-purple-600 to-pink-600 opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||
|
||||
<div className="relative z-10 flex items-center justify-center space-x-2">
|
||||
<span className="group-hover:tracking-wider transition-all duration-300">Refresh Leaderboard</span>
|
||||
<div className="w-2 h-2 bg-white rounded-full animate-bounce group-hover:animate-ping"></div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Floating action button for help */}
|
||||
<div className="fixed bottom-8 right-8 animate-bounce">
|
||||
<button className="bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700 p-4 rounded-full shadow-2xl transform transition-all duration-300 hover:scale-110 group">
|
||||
<AlertTriangle className="h-6 w-6 text-white group-hover:animate-spin" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useEffect, useState } from "react"
|
||||
import { useRouter, useParams } from "next/navigation"
|
||||
import { Loader2, Play, Clock, BookOpen, ChevronDown, ChevronRight, User, Users, Star } from "lucide-react"
|
||||
import { Loader2, Play, Clock, BookOpen, ChevronDown, ChevronRight, User, Users, Star, Award, TrendingUp, CheckCircle, ArrowRight } from "lucide-react"
|
||||
import { toast } from "react-hot-toast"
|
||||
import api from "@/lib/api"
|
||||
import { useAuth } from "@/context/auth-context"
|
||||
@@ -62,7 +62,7 @@ export default function CoursePage() {
|
||||
const [expandedModules, setExpandedModules] = useState<{ [moduleId: string]: boolean }>({})
|
||||
const [completed, setCompleted] = useState(false)
|
||||
|
||||
// ✅ Certificate Modal State
|
||||
// Certificate Modal State
|
||||
const [showCertificateModal, setShowCertificateModal] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -83,13 +83,11 @@ export default function CoursePage() {
|
||||
try {
|
||||
console.log('🔍 Starting to fetch course data for:', courseId)
|
||||
|
||||
// Fetch course details
|
||||
const courseResponse = await api.get<Course>(`/api/courses/${courseId}?t=${Date.now()}`)
|
||||
const courseData = courseResponse.data
|
||||
console.log('✅ Course data loaded:', courseData)
|
||||
setCourse(courseData)
|
||||
|
||||
// ✅ FIXED: Better module fetching with multiple endpoint attempts
|
||||
await fetchModulesAndLessons(courseId)
|
||||
|
||||
} catch (err: any) {
|
||||
@@ -107,11 +105,9 @@ export default function CoursePage() {
|
||||
try {
|
||||
console.log('🔍 Fetching modules for course:', courseId)
|
||||
|
||||
// Try multiple endpoints for modules
|
||||
let modulesData = null
|
||||
let modulesResponse = null
|
||||
|
||||
// Try admin endpoint first (most likely to work based on previous conversation)
|
||||
try {
|
||||
modulesResponse = await fetch(`http://127.0.0.1:5000/api/admin/courses/${courseId}/modules`, {
|
||||
headers: {
|
||||
@@ -128,7 +124,6 @@ export default function CoursePage() {
|
||||
console.log('⚠️ Admin endpoint failed, trying public endpoint')
|
||||
}
|
||||
|
||||
// If admin endpoint failed, try public endpoint
|
||||
if (!modulesData || !modulesResponse?.ok) {
|
||||
try {
|
||||
modulesResponse = await fetch(`http://127.0.0.1:5000/api/courses/${courseId}/modules`, {
|
||||
@@ -149,7 +144,6 @@ export default function CoursePage() {
|
||||
if (modulesData) {
|
||||
let modulesList: Module[] = []
|
||||
|
||||
// Handle different response formats
|
||||
if (modulesData.success && modulesData.modules && Array.isArray(modulesData.modules)) {
|
||||
modulesList = modulesData.modules
|
||||
} else if (modulesData.modules && Array.isArray(modulesData.modules)) {
|
||||
@@ -160,13 +154,11 @@ export default function CoursePage() {
|
||||
modulesList = modulesData.data
|
||||
}
|
||||
|
||||
// Sort modules by order
|
||||
modulesList = modulesList.sort((a, b) => a.order - b.order)
|
||||
|
||||
console.log('🔍 Processed modules list:', modulesList)
|
||||
setModules(modulesList)
|
||||
|
||||
// Fetch lessons for all modules
|
||||
if (modulesList.length > 0) {
|
||||
await fetchLessonsForAllModules(modulesList)
|
||||
}
|
||||
@@ -193,7 +185,6 @@ export default function CoursePage() {
|
||||
try {
|
||||
console.log('🔍 Fetching lessons for module:', module.id)
|
||||
|
||||
// Try admin endpoint first
|
||||
let lessonsResponse = await fetch(`http://127.0.0.1:5000/api/admin/modules/${module.id}/lessons`, {
|
||||
headers: {
|
||||
'Authorization': 'Bearer admin-secret-key',
|
||||
@@ -201,7 +192,6 @@ export default function CoursePage() {
|
||||
}
|
||||
})
|
||||
|
||||
// If admin fails, try public endpoint
|
||||
if (!lessonsResponse.ok) {
|
||||
lessonsResponse = await fetch(`http://127.0.0.1:5000/api/modules/${module.id}/lessons`, {
|
||||
headers: {
|
||||
@@ -225,11 +215,9 @@ export default function CoursePage() {
|
||||
lessonsList = lessonData.data
|
||||
}
|
||||
|
||||
// Sort lessons by order
|
||||
lessonsList = lessonsList.sort((a, b) => a.order - b.order)
|
||||
lessonsData[module.id] = lessonsList
|
||||
|
||||
// Auto-expand first module with lessons
|
||||
if (!selectedModuleId && lessonsList.length > 0) {
|
||||
expandedState[module.id] = true
|
||||
}
|
||||
@@ -246,7 +234,6 @@ export default function CoursePage() {
|
||||
setLessons(lessonsData)
|
||||
setExpandedModules(expandedState)
|
||||
|
||||
// Auto-select first lesson if available
|
||||
if (!selectedModuleId && modulesList.length > 0) {
|
||||
const firstModule = modulesList[0]
|
||||
const firstModuleLessons = lessonsData[firstModule.id] || []
|
||||
@@ -258,7 +245,6 @@ export default function CoursePage() {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper: embed URL
|
||||
function getEmbedUrl(url?: string): string | undefined {
|
||||
if (!url) return undefined
|
||||
const regExp = /(?:youtu\.be\/|youtube\.com\/(?:watch\?v=|embed\/))([^#&?]{11})/
|
||||
@@ -279,7 +265,6 @@ export default function CoursePage() {
|
||||
const selectLesson = (moduleId: string, lessonId: string) => {
|
||||
setSelectedModuleId(moduleId)
|
||||
setSelectedLessonId(lessonId)
|
||||
// Auto-expand the module when lesson is selected
|
||||
setExpandedModules(prev => ({
|
||||
...prev,
|
||||
[moduleId]: true
|
||||
@@ -324,10 +309,9 @@ export default function CoursePage() {
|
||||
return allLessons.length > 0 && allLessons[allLessons.length - 1].id === selectedLessonId
|
||||
}
|
||||
|
||||
// ✅ Updated markComplete function to show certificate modal
|
||||
const markComplete = () => {
|
||||
setCompleted(true)
|
||||
setShowCertificateModal(true) // Show certificate modal instead of just toast
|
||||
setShowCertificateModal(true)
|
||||
}
|
||||
|
||||
const getTotalLessons = () => {
|
||||
@@ -338,10 +322,24 @@ export default function CoursePage() {
|
||||
|
||||
if (authLoading || loading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<Loader2 className="h-12 w-12 animate-spin text-indigo-600 mx-auto mb-4" />
|
||||
<p className="text-lg text-gray-700">Loading course...</p>
|
||||
<div className="min-h-screen bg-gradient-to-br from-purple-900 via-blue-900 to-indigo-900 flex items-center justify-center relative overflow-hidden">
|
||||
{/* Animated background elements */}
|
||||
<div className="absolute inset-0">
|
||||
<div className="absolute top-1/4 left-1/4 w-96 h-96 bg-purple-500 rounded-full mix-blend-multiply filter blur-xl opacity-20 animate-pulse"></div>
|
||||
<div className="absolute top-1/3 right-1/4 w-96 h-96 bg-yellow-500 rounded-full mix-blend-multiply filter blur-xl opacity-20 animate-pulse animation-delay-1000"></div>
|
||||
<div className="absolute bottom-1/4 left-1/3 w-96 h-96 bg-pink-500 rounded-full mix-blend-multiply filter blur-xl opacity-20 animate-pulse animation-delay-2000"></div>
|
||||
</div>
|
||||
<div className="text-center z-10">
|
||||
<div className="relative">
|
||||
<Loader2 className="h-16 w-16 animate-spin text-white mx-auto mb-6 drop-shadow-lg" />
|
||||
<div className="absolute inset-0 h-16 w-16 border-4 border-transparent border-t-purple-400 rounded-full animate-ping mx-auto"></div>
|
||||
</div>
|
||||
<p className="text-xl text-white font-semibold tracking-wide animate-pulse">Loading your learning journey...</p>
|
||||
<div className="mt-4 flex justify-center space-x-1">
|
||||
<div className="w-2 h-2 bg-white rounded-full animate-bounce"></div>
|
||||
<div className="w-2 h-2 bg-white rounded-full animate-bounce animation-delay-200"></div>
|
||||
<div className="w-2 h-2 bg-white rounded-full animate-bounce animation-delay-400"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -349,14 +347,17 @@ export default function CoursePage() {
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
||||
<div className="text-center max-w-md mx-auto px-4">
|
||||
<div className="bg-red-50 border border-red-200 rounded-lg p-6">
|
||||
<h2 className="text-lg font-semibold text-red-800 mb-2">Error Loading Course</h2>
|
||||
<p className="text-red-600 mb-4">{error}</p>
|
||||
<div className="min-h-screen bg-gradient-to-br from-red-900 via-pink-900 to-purple-900 flex items-center justify-center p-4">
|
||||
<div className="text-center max-w-md mx-auto px-6">
|
||||
<div className="bg-white/10 backdrop-blur-lg border border-red-300/30 rounded-3xl p-10 shadow-2xl animate-bounce">
|
||||
<div className="w-20 h-20 bg-red-500 rounded-full flex items-center justify-center mx-auto mb-6 animate-pulse">
|
||||
<span className="text-3xl">⚠️</span>
|
||||
</div>
|
||||
<h2 className="text-2xl font-bold text-white mb-4">Oops! Something went wrong</h2>
|
||||
<p className="text-red-200 mb-8 leading-relaxed">{error}</p>
|
||||
<button
|
||||
onClick={fetchCourseData}
|
||||
className="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700 transition-colors"
|
||||
className="px-8 py-4 bg-gradient-to-r from-red-500 to-pink-500 text-white rounded-2xl hover:from-red-600 hover:to-pink-600 shadow-lg transition-all duration-300 transform hover:scale-105 font-semibold text-lg"
|
||||
>
|
||||
Try Again
|
||||
</button>
|
||||
@@ -368,382 +369,429 @@ export default function CoursePage() {
|
||||
|
||||
if (!course) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<h2 className="text-xl font-semibold text-gray-800 mb-2">Course Not Found</h2>
|
||||
<p className="text-gray-600">The course you're looking for doesn't exist.</p>
|
||||
<div className="min-h-screen bg-gradient-to-br from-gray-800 to-gray-900 flex items-center justify-center p-4">
|
||||
<div className="text-center max-w-sm bg-white/10 backdrop-blur-lg rounded-3xl shadow-2xl p-10 animate-fadeIn">
|
||||
<div className="w-24 h-24 bg-gray-500 rounded-full flex items-center justify-center mx-auto mb-6 animate-bounce">
|
||||
<span className="text-4xl">🔍</span>
|
||||
</div>
|
||||
<h2 className="text-3xl font-bold text-white mb-4">Course Not Found</h2>
|
||||
<p className="text-gray-300 leading-relaxed">The course you're looking for doesn't exist or may have been removed.</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<div className="min-h-screen bg-gradient-to-br from-indigo-50 via-purple-50 to-pink-50">
|
||||
{/* Animated Background Elements */}
|
||||
<div className="fixed inset-0 overflow-hidden pointer-events-none">
|
||||
<div className="absolute -top-40 -right-40 w-96 h-96 bg-purple-300 rounded-full mix-blend-multiply filter blur-xl opacity-30 animate-float"></div>
|
||||
<div className="absolute -bottom-40 -left-40 w-96 h-96 bg-yellow-300 rounded-full mix-blend-multiply filter blur-xl opacity-30 animate-float animation-delay-2000"></div>
|
||||
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-96 h-96 bg-pink-300 rounded-full mix-blend-multiply filter blur-xl opacity-20 animate-pulse"></div>
|
||||
</div>
|
||||
|
||||
{/* Header */}
|
||||
<header className="bg-white shadow-sm border-b">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex items-center justify-between h-16">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="w-10 h-10 bg-indigo-600 rounded-lg flex items-center justify-center">
|
||||
<span className="text-white font-bold text-lg">OL</span>
|
||||
<header className="bg-white/80 backdrop-blur-lg shadow-xl border-b border-purple-200 sticky top-0 z-50">
|
||||
<div className="w-full px-6 sm:px-10 lg:px-16 xl:px-20">
|
||||
<div className="flex items-center justify-between h-20">
|
||||
<div className="flex items-center space-x-6 animate-slideInLeft">
|
||||
<div className="w-14 h-14 bg-gradient-to-br from-purple-600 to-indigo-600 rounded-2xl flex items-center justify-center shadow-lg transform hover:scale-110 transition-transform duration-300">
|
||||
<span className="text-white font-extrabold text-2xl">OL</span>
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-xl font-bold text-gray-900">{course.title}</h1>
|
||||
<p className="text-sm text-gray-600">by {course.mentor}</p>
|
||||
<h1 className="text-2xl font-extrabold bg-gradient-to-r from-purple-600 to-indigo-600 bg-clip-text text-transparent tracking-tight">{course.title}</h1>
|
||||
<p className="text-sm text-purple-700 font-semibold tracking-wide">by {course.mentor}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="hidden md:flex items-center space-x-6 text-sm">
|
||||
<div className="flex items-center text-gray-600">
|
||||
<BookOpen className="w-4 h-4 mr-2" />
|
||||
<span>{modules.length} modules</span>
|
||||
<div className="hidden md:flex items-center space-x-8 text-sm text-purple-700 animate-slideInRight">
|
||||
<div className="flex items-center space-x-2 bg-purple-100 px-4 py-2 rounded-full">
|
||||
<BookOpen className="w-5 h-5 text-purple-600" />
|
||||
<span className="font-semibold">{modules.length} modules</span>
|
||||
</div>
|
||||
<div className="flex items-center text-gray-600">
|
||||
<Play className="w-4 h-4 mr-2" />
|
||||
<span>{getTotalLessons()} lessons</span>
|
||||
<div className="flex items-center space-x-2 bg-indigo-100 px-4 py-2 rounded-full">
|
||||
<Play className="w-5 h-5 text-indigo-600" />
|
||||
<span className="font-semibold">{getTotalLessons()} lessons</span>
|
||||
</div>
|
||||
<div className="flex items-center text-gray-600">
|
||||
<Users className="w-4 h-4 mr-2" />
|
||||
<span>{course.students} students</span>
|
||||
<div className="flex items-center space-x-2 bg-pink-100 px-4 py-2 rounded-full">
|
||||
<Users className="w-5 h-5 text-pink-600" />
|
||||
<span className="font-semibold">{course.students.toLocaleString()} students</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<div className="lg:grid lg:grid-cols-12 lg:gap-8">
|
||||
|
||||
{/* Sidebar */}
|
||||
<aside className="lg:col-span-4 xl:col-span-3">
|
||||
<div className="bg-white rounded-xl shadow-sm border p-6 sticky top-8">
|
||||
<h2 className="text-lg font-semibold text-gray-900 mb-4">Course Content</h2>
|
||||
|
||||
{/* Debug Info - Enhanced */}
|
||||
<div className="mb-6 p-4 bg-blue-50 border border-blue-200 rounded-lg">
|
||||
<h3 className="text-sm font-semibold text-blue-800 mb-2">🔍 Debug Info:</h3>
|
||||
<div className="text-xs space-y-1 text-blue-700">
|
||||
<p><strong>Course ID:</strong> {courseId}</p>
|
||||
<p><strong>Modules Loaded:</strong> {modules.length}</p>
|
||||
<p><strong>Total Lessons:</strong> {getTotalLessons()}</p>
|
||||
<p><strong>Modules Loading:</strong> {modulesLoading ? 'Yes' : 'No'}</p>
|
||||
<p><strong>Selected Module:</strong> {selectedModuleId || 'None'}</p>
|
||||
<p><strong>Selected Lesson:</strong> {currentLesson?.title || 'None'}</p>
|
||||
<p><strong>Expanded Modules:</strong> {Object.keys(expandedModules).length}</p>
|
||||
</div>
|
||||
{modules.length > 0 && (
|
||||
<details className="mt-2">
|
||||
<summary className="text-xs cursor-pointer text-blue-600">Show Raw Data</summary>
|
||||
<pre className="text-xs mt-2 p-2 bg-gray-100 rounded overflow-auto max-h-32">
|
||||
{JSON.stringify({ modules, lessons }, null, 2)}
|
||||
</pre>
|
||||
</details>
|
||||
)}
|
||||
<main className="w-full px-6 sm:px-10 lg:px-16 xl:px-20 py-12 grid grid-cols-1 lg:grid-cols-5 gap-12 relative z-10">
|
||||
|
||||
{/* Sidebar - Now takes up 2 columns on large screens */}
|
||||
<aside className="lg:col-span-2 animate-slideInLeft">
|
||||
<div className="bg-white/80 backdrop-blur-lg rounded-3xl shadow-2xl border border-purple-200 p-10 sticky top-28">
|
||||
<div className="flex items-center justify-between mb-8">
|
||||
<h2 className="text-2xl font-extrabold bg-gradient-to-r from-purple-600 to-indigo-600 bg-clip-text text-transparent">Course Content</h2>
|
||||
<div className="w-8 h-8 bg-gradient-to-r from-purple-500 to-indigo-500 rounded-full flex items-center justify-center">
|
||||
<BookOpen className="w-4 h-4 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Loading State */}
|
||||
{modulesLoading && (
|
||||
<div className="text-center py-8">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-indigo-600 mx-auto mb-2" />
|
||||
<p className="text-sm text-gray-600">Loading modules...</p>
|
||||
</div>
|
||||
)}
|
||||
{/* Enhanced Progress Bar */}
|
||||
<div className="mb-8 p-4 bg-gradient-to-r from-purple-50 to-indigo-50 rounded-2xl border border-purple-200">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<span className="text-sm font-semibold text-purple-700">Progress</span>
|
||||
<span className="text-sm font-bold text-indigo-600">25%</span>
|
||||
</div>
|
||||
<div className="w-full bg-gray-200 rounded-full h-3 overflow-hidden">
|
||||
<div className="bg-gradient-to-r from-purple-500 to-indigo-500 h-3 rounded-full transition-all duration-1000 ease-out animate-pulse" style={{width: '25%'}}></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* No Modules State */}
|
||||
{!modulesLoading && modules.length === 0 && (
|
||||
<div className="text-center py-8">
|
||||
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
|
||||
<h3 className="text-sm font-semibold text-yellow-800 mb-2">No Modules Found</h3>
|
||||
<p className="text-xs text-yellow-700 mb-3">
|
||||
This could mean:
|
||||
</p>
|
||||
<ul className="text-xs text-yellow-700 text-left space-y-1">
|
||||
<li>• No modules created for this course yet</li>
|
||||
<li>• API endpoint issues</li>
|
||||
<li>• Course ID mismatch</li>
|
||||
</ul>
|
||||
<button
|
||||
onClick={() => fetchModulesAndLessons(courseId)}
|
||||
className="mt-3 px-3 py-1 bg-yellow-600 text-white text-xs rounded hover:bg-yellow-700"
|
||||
>
|
||||
Retry Loading Modules
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Modules List */}
|
||||
{!modulesLoading && modules.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
{modules.map((module, index) => (
|
||||
<div key={module.id} className="border border-gray-200 rounded-lg overflow-hidden">
|
||||
{/* Module Header */}
|
||||
<button
|
||||
onClick={() => toggleModule(module.id)}
|
||||
className={`w-full px-4 py-3 text-left hover:bg-gray-50 flex items-center justify-between transition-colors ${
|
||||
selectedModuleId === module.id ? 'bg-indigo-50 border-indigo-200' : 'bg-white'
|
||||
}`}
|
||||
>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="flex-shrink-0 w-6 h-6 bg-indigo-100 text-indigo-800 rounded-full flex items-center justify-center text-xs font-semibold">
|
||||
{index + 1}
|
||||
</span>
|
||||
<h3 className="font-medium text-sm text-gray-900 truncate">{module.title}</h3>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 mt-1 ml-8">
|
||||
{lessons[module.id]?.length || 0} lessons
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex-shrink-0 ml-2">
|
||||
{expandedModules[module.id] ? (
|
||||
<ChevronDown className="w-4 h-4 text-gray-400" />
|
||||
) : (
|
||||
<ChevronRight className="w-4 h-4 text-gray-400" />
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{/* Lessons */}
|
||||
{expandedModules[module.id] && (
|
||||
<div className="bg-gray-50 border-t border-gray-200">
|
||||
{lessons[module.id] && lessons[module.id].length > 0 ? (
|
||||
lessons[module.id].map((lesson, lessonIndex) => (
|
||||
<button
|
||||
key={lesson.id}
|
||||
onClick={() => selectLesson(module.id, lesson.id)}
|
||||
className={`w-full px-6 py-3 text-left hover:bg-gray-100 transition-colors border-l-4 ${
|
||||
selectedLessonId === lesson.id
|
||||
? 'border-indigo-500 bg-indigo-50 text-indigo-900'
|
||||
: 'border-transparent text-gray-700'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className={`flex-shrink-0 w-5 h-5 rounded-full flex items-center justify-center text-xs ${
|
||||
selectedLessonId === lesson.id
|
||||
? 'bg-indigo-500 text-white'
|
||||
: 'bg-gray-300 text-gray-600'
|
||||
}`}>
|
||||
<Play className="w-2.5 h-2.5" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="font-medium text-sm truncate">{lesson.title}</p>
|
||||
{lesson.duration && (
|
||||
<p className={`text-xs flex items-center mt-1 ${
|
||||
selectedLessonId === lesson.id ? 'text-indigo-600' : 'text-gray-500'
|
||||
}`}>
|
||||
<Clock className="w-3 h-3 mr-1" />
|
||||
{lesson.duration}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
))
|
||||
) : (
|
||||
<div className="px-6 py-4 text-center">
|
||||
<p className="text-xs text-gray-500">No lessons in this module</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{/* Debug Info - Enhanced */}
|
||||
<div className="mb-8 p-5 bg-gradient-to-r from-blue-50 to-cyan-50 border border-blue-200 rounded-2xl animate-fadeIn">
|
||||
<h3 className="text-sm font-bold text-blue-800 mb-4 flex items-center">
|
||||
<TrendingUp className="w-4 h-4 mr-2" />
|
||||
🔍 Debug Info:
|
||||
</h3>
|
||||
<div className="text-xs space-y-2 text-blue-700">
|
||||
<p><strong>Course ID:</strong> {courseId}</p>
|
||||
<p><strong>Modules Loaded:</strong> {modules.length}</p>
|
||||
<p><strong>Total Lessons:</strong> {getTotalLessons()}</p>
|
||||
<p><strong>Modules Loading:</strong> {modulesLoading ? 'Yes' : 'No'}</p>
|
||||
<p><strong>Selected Module:</strong> {selectedModuleId || 'None'}</p>
|
||||
<p><strong>Selected Lesson:</strong> {currentLesson?.title || 'None'}</p>
|
||||
<p><strong>Expanded Modules:</strong> {Object.keys(expandedModules).length}</p>
|
||||
</div>
|
||||
{modules.length > 0 && (
|
||||
<details className="mt-4 border-t border-blue-200 pt-4">
|
||||
<summary className="text-xs cursor-pointer text-blue-600 font-semibold hover:text-blue-800 transition-colors">Show Raw Data</summary>
|
||||
<pre className="mt-3 text-xs p-4 bg-white rounded-xl shadow max-h-40 overflow-auto">
|
||||
{JSON.stringify({ modules, lessons }, null, 2)}
|
||||
</pre>
|
||||
</details>
|
||||
)}
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="mt-8 lg:mt-0 lg:col-span-8 xl:col-span-9">
|
||||
<div className="bg-white rounded-xl shadow-sm border overflow-hidden">
|
||||
{currentLesson ? (
|
||||
<>
|
||||
{/* Video Player */}
|
||||
{(currentLesson.embed_url || currentLesson.video_url) && (
|
||||
<div className="aspect-video bg-black">
|
||||
<iframe
|
||||
src={getEmbedUrl(currentLesson.embed_url || currentLesson.video_url)}
|
||||
title={currentLesson.title}
|
||||
allowFullScreen
|
||||
className="w-full h-full"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{/* Loading State */}
|
||||
{modulesLoading && (
|
||||
<div className="text-center py-10 animate-pulse">
|
||||
<Loader2 className="h-12 w-12 animate-spin text-purple-500 mx-auto mb-4" />
|
||||
<p className="text-lg text-purple-700 font-semibold">Loading modules...</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Lesson Content */}
|
||||
<div className="p-8">
|
||||
{/* Lesson Header */}
|
||||
<div className="mb-6">
|
||||
<div className="flex items-center text-sm text-gray-500 mb-2">
|
||||
<User className="w-4 h-4 mr-1" />
|
||||
<span>by {course.mentor}</span>
|
||||
<span className="mx-2">•</span>
|
||||
<span className="bg-indigo-100 text-indigo-800 px-2 py-1 rounded-full text-xs font-medium">
|
||||
{course.difficulty}
|
||||
</span>
|
||||
</div>
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-2">{currentLesson.title}</h1>
|
||||
{currentLesson.duration && (
|
||||
<div className="flex items-center text-sm text-gray-600">
|
||||
<Clock className="w-4 h-4 mr-1" />
|
||||
<span>{currentLesson.duration}</span>
|
||||
{/* No Modules State */}
|
||||
{!modulesLoading && modules.length === 0 && (
|
||||
<div className="text-center py-8 animate-bounce">
|
||||
<div className="bg-gradient-to-r from-yellow-50 to-orange-50 border border-yellow-300 rounded-2xl p-6 text-yellow-800">
|
||||
<div className="w-16 h-16 bg-yellow-500 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<span className="text-2xl">📚</span>
|
||||
</div>
|
||||
<h3 className="text-lg font-bold mb-3">No Modules Found</h3>
|
||||
<p className="text-sm mb-4 leading-relaxed">
|
||||
This could mean:<br />
|
||||
• No modules created yet<br />
|
||||
• API endpoint issues<br />
|
||||
• Course ID mismatch
|
||||
</p>
|
||||
<button
|
||||
onClick={() => fetchModulesAndLessons(courseId)}
|
||||
className="px-6 py-3 bg-gradient-to-r from-yellow-500 to-orange-500 rounded-2xl text-white font-bold hover:from-yellow-600 hover:to-orange-600 transition-all duration-300 transform hover:scale-105 shadow-lg"
|
||||
>
|
||||
Retry Loading Modules
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Modules List */}
|
||||
{!modulesLoading && modules.length > 0 && (
|
||||
<div className="space-y-4">
|
||||
{modules.map((module, index) => (
|
||||
<div key={module.id} className="border border-purple-200 rounded-2xl overflow-hidden shadow-lg bg-white/60 backdrop-blur-sm hover:shadow-xl transition-all duration-300 animate-fadeInUp" style={{animationDelay: `${index * 100}ms`}}>
|
||||
{/* Module Header */}
|
||||
<button
|
||||
onClick={() => toggleModule(module.id)}
|
||||
className={`w-full px-6 py-5 text-left hover:bg-gradient-to-r hover:from-purple-50 hover:to-indigo-50 flex items-center justify-between transition-all duration-300 ${
|
||||
selectedModuleId === module.id ? 'bg-gradient-to-r from-purple-100 to-indigo-100 border-purple-300' : 'bg-white/80'
|
||||
}`}
|
||||
>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center space-x-4">
|
||||
<span className="flex-shrink-0 w-10 h-10 bg-gradient-to-r from-purple-500 to-indigo-500 text-white rounded-full flex items-center justify-center font-bold text-sm shadow-lg transform hover:scale-110 transition-transform duration-300">
|
||||
{index + 1}
|
||||
</span>
|
||||
<h3 className="font-bold text-purple-900 truncate text-lg">{module.title}</h3>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Lesson Description */}
|
||||
{currentLesson.description && (
|
||||
<div className="prose max-w-none mb-8">
|
||||
<h2 className="text-xl font-semibold text-gray-900 mb-4">About this lesson</h2>
|
||||
<div className="text-gray-700 leading-relaxed whitespace-pre-line bg-gray-50 p-4 rounded-lg">
|
||||
{currentLesson.description}
|
||||
<p className="text-sm text-purple-600 mt-2 ml-14 flex items-center">
|
||||
<CheckCircle className="w-4 h-4 mr-2" />
|
||||
{(lessons[module.id]?.length ?? 0) + (lessons[module.id]?.length === 1 ? ' lesson' : ' lessons')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex-shrink-0 ml-4">
|
||||
<div className={`transform transition-transform duration-300 ${expandedModules[module.id] ? 'rotate-180' : ''}`}>
|
||||
{expandedModules[module.id] ? (
|
||||
<ChevronDown className="w-6 h-6 text-purple-500" />
|
||||
) : (
|
||||
<ChevronRight className="w-6 h-6 text-purple-400" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{/* Lesson Content */}
|
||||
{currentLesson.content && (
|
||||
<div className="prose max-w-none mb-8">
|
||||
<h2 className="text-xl font-semibold text-gray-900 mb-4">Lesson Content</h2>
|
||||
<div className="text-gray-700 leading-relaxed whitespace-pre-line bg-gray-50 p-4 rounded-lg">
|
||||
{currentLesson.content}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Navigation */}
|
||||
<div className="flex justify-between items-center pt-8 border-t border-gray-200">
|
||||
<button
|
||||
onClick={() => navigateLesson('prev')}
|
||||
disabled={isFirstLesson()}
|
||||
className="px-6 py-3 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 disabled:opacity-50 disabled:cursor-not-allowed transition-colors font-medium"
|
||||
>
|
||||
← Previous Lesson
|
||||
</button>
|
||||
|
||||
{!isLastLesson() ? (
|
||||
<button
|
||||
onClick={() => navigateLesson('next')}
|
||||
className="px-6 py-3 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors font-medium"
|
||||
>
|
||||
Next Lesson →
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={markComplete}
|
||||
disabled={completed}
|
||||
className={`px-6 py-3 rounded-lg font-medium transition-colors ${
|
||||
completed
|
||||
? "bg-green-600 text-white cursor-not-allowed"
|
||||
: "bg-purple-600 text-white hover:bg-purple-700"
|
||||
}`}
|
||||
>
|
||||
{completed ? "✓ Course Completed" : "Mark as Complete"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* ✅ Updated Completion Message */}
|
||||
{completed && !showCertificateModal && (
|
||||
<div className="mt-8 bg-green-50 border border-green-200 rounded-lg p-6 text-center">
|
||||
<div className="text-green-700">
|
||||
<div className="text-4xl mb-2">🎉</div>
|
||||
<h3 className="text-xl font-bold mb-2">Congratulations!</h3>
|
||||
<p className="mb-4">You have successfully completed this course!</p>
|
||||
<button
|
||||
onClick={() => setShowCertificateModal(true)}
|
||||
className="px-6 py-3 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors font-medium"
|
||||
>
|
||||
Get Your Certificate 🏆
|
||||
</button>
|
||||
</div>
|
||||
{/* Lessons */}
|
||||
{expandedModules[module.id] && (
|
||||
<div className="bg-gradient-to-r from-purple-50 to-indigo-50 border-t border-purple-200 animate-slideDown">
|
||||
{lessons[module.id] && lessons[module.id].length > 0 ? (
|
||||
lessons[module.id].map((lesson, lessonIndex) => (
|
||||
<button
|
||||
key={lesson.id}
|
||||
onClick={() => selectLesson(module.id, lesson.id)}
|
||||
className={`w-full px-8 py-4 text-left hover:bg-gradient-to-r hover:from-purple-100 hover:to-indigo-100 transition-all duration-300 border-l-4 group ${
|
||||
selectedLessonId === lesson.id
|
||||
? 'border-purple-500 bg-gradient-to-r from-purple-100 to-indigo-100 text-purple-900 font-bold shadow-inner'
|
||||
: 'border-transparent text-purple-700 hover:border-purple-300'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className={`flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center text-xs transition-all duration-300 group-hover:scale-110 ${
|
||||
selectedLessonId === lesson.id
|
||||
? 'bg-gradient-to-r from-purple-500 to-indigo-500 text-white shadow-lg'
|
||||
: 'bg-purple-200 text-purple-700 group-hover:bg-purple-300'
|
||||
}`}>
|
||||
<Play className="w-4 h-4" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="truncate font-semibold">{lesson.title}</p>
|
||||
{lesson.duration && (
|
||||
<p className={`text-xs flex items-center mt-1 ${
|
||||
selectedLessonId === lesson.id ? 'text-purple-700 font-semibold' : 'text-purple-500'
|
||||
}`}>
|
||||
<Clock className="w-4 h-4 mr-2" />
|
||||
{lesson.duration}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<ArrowRight className={`w-4 h-4 transition-all duration-300 ${
|
||||
selectedLessonId === lesson.id ? 'text-purple-600 transform scale-110' : 'text-transparent group-hover:text-purple-400'
|
||||
}`} />
|
||||
</div>
|
||||
</button>
|
||||
))
|
||||
) : (
|
||||
<p className="px-8 py-6 text-purple-600 text-sm italic text-center">No lessons in this module</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
/* Course Overview */
|
||||
<div className="p-8 text-center">
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<h1 className="text-4xl font-bold text-gray-900 mb-4">{course.title}</h1>
|
||||
|
||||
<div className="flex items-center justify-center space-x-4 mb-6 text-sm">
|
||||
<div className="flex items-center text-gray-600">
|
||||
<User className="w-4 h-4 mr-1" />
|
||||
<span>by {course.mentor}</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
{/* Main Content - Now takes up 3 columns on large screens for full width */}
|
||||
<section className="lg:col-span-3 animate-slideInRight">
|
||||
<div className="bg-white/80 backdrop-blur-lg rounded-3xl shadow-2xl border border-purple-200 overflow-hidden">
|
||||
{currentLesson ? (
|
||||
<>
|
||||
{/* Video Player */}
|
||||
{(currentLesson.embed_url || currentLesson.video_url) && (
|
||||
<div className="aspect-video bg-black rounded-t-3xl overflow-hidden relative group">
|
||||
<iframe
|
||||
src={getEmbedUrl(currentLesson.embed_url || currentLesson.video_url)}
|
||||
title={currentLesson.title}
|
||||
allowFullScreen
|
||||
className="w-full h-full"
|
||||
loading="lazy"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/20 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Lesson Content */}
|
||||
<div className="p-16">
|
||||
{/* Lesson Header */}
|
||||
<div className="mb-12 animate-fadeInUp">
|
||||
<div className="flex items-center text-purple-600 space-x-4 mb-6">
|
||||
<div className="flex items-center space-x-2 bg-purple-100 px-6 py-3 rounded-full">
|
||||
<User className="w-6 h-6" />
|
||||
<span className="font-bold text-lg">{course.mentor}</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<Star className="w-4 h-4 text-yellow-400 mr-1" />
|
||||
<span className="text-gray-600">4.8</span>
|
||||
</div>
|
||||
<span className="bg-indigo-100 text-indigo-800 px-3 py-1 rounded-full text-sm font-medium">
|
||||
<span className="text-purple-300">•</span>
|
||||
<span className="bg-gradient-to-r from-purple-500 to-indigo-500 text-white px-6 py-3 rounded-full text-lg font-bold uppercase tracking-widest shadow-lg">
|
||||
{course.difficulty}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p className="text-lg text-gray-700 mb-8 leading-relaxed">{course.description}</p>
|
||||
|
||||
{/* Course Stats */}
|
||||
<div className="grid grid-cols-3 gap-8 mb-8">
|
||||
<div className="text-center">
|
||||
<div className="text-3xl font-bold text-indigo-600 mb-1">{modules.length}</div>
|
||||
<div className="text-sm text-gray-600">Modules</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-3xl font-bold text-indigo-600 mb-1">{getTotalLessons()}</div>
|
||||
<div className="text-sm text-gray-600">Lessons</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-3xl font-bold text-indigo-600 mb-1">{course.students}</div>
|
||||
<div className="text-sm text-gray-600">Students</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Course Intro Video */}
|
||||
{(course.embed_url || course.video_url) && (
|
||||
<div className="aspect-video rounded-xl overflow-hidden mb-8 shadow-lg bg-black">
|
||||
<iframe
|
||||
src={getEmbedUrl(course.embed_url || course.video_url)}
|
||||
title={course.title}
|
||||
allowFullScreen
|
||||
className="w-full h-full"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{getTotalLessons() > 0 ? (
|
||||
<div>
|
||||
<p className="text-gray-600 mb-6">
|
||||
Ready to start learning? Select a lesson from the course content to begin your journey.
|
||||
</p>
|
||||
<button
|
||||
onClick={() => {
|
||||
const firstModule = modules[0]
|
||||
const firstLessons = lessons[firstModule?.id] || []
|
||||
if (firstLessons.length > 0) {
|
||||
selectLesson(firstModule.id, firstLessons[0].id)
|
||||
}
|
||||
}}
|
||||
className="px-8 py-4 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 font-semibold text-lg transition-colors"
|
||||
>
|
||||
Start Learning
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-6">
|
||||
<h3 className="text-lg font-semibold text-yellow-800 mb-2">Coming Soon</h3>
|
||||
<p className="text-yellow-700">Lessons are being prepared for this course. Check back soon!</p>
|
||||
<h1 className="text-6xl font-extrabold bg-gradient-to-r from-purple-600 to-indigo-600 bg-clip-text text-transparent leading-tight mb-6 drop-shadow-sm">{currentLesson.title}</h1>
|
||||
{currentLesson.duration && (
|
||||
<div className="flex items-center text-purple-600 space-x-3 text-xl font-semibold">
|
||||
<div className="flex items-center space-x-2 bg-purple-100 px-6 py-3 rounded-full">
|
||||
<Clock className="w-6 h-6" />
|
||||
<span>{currentLesson.duration}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ✅ Certificate Modal */}
|
||||
{/* Lesson Description */}
|
||||
{currentLesson.description && (
|
||||
<section className="mb-16 animate-fadeInUp animation-delay-200">
|
||||
<h2 className="text-4xl font-bold bg-gradient-to-r from-purple-600 to-indigo-600 bg-clip-text text-transparent mb-8 border-b-2 border-purple-200 pb-4">
|
||||
About this lesson
|
||||
</h2>
|
||||
<article className="bg-gradient-to-r from-purple-50 to-indigo-50 rounded-3xl p-10 text-purple-900 prose max-w-none shadow-inner border border-purple-200 text-lg leading-relaxed">
|
||||
{currentLesson.description}
|
||||
</article>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Lesson Content */}
|
||||
{currentLesson.content && (
|
||||
<section className="mb-16 animate-fadeInUp animation-delay-400">
|
||||
<h2 className="text-4xl font-bold bg-gradient-to-r from-purple-600 to-indigo-600 bg-clip-text text-transparent mb-8 border-b-2 border-purple-200 pb-4">
|
||||
Lesson Content
|
||||
</h2>
|
||||
<article className="bg-gradient-to-r from-purple-50 to-indigo-50 rounded-3xl p-10 text-purple-900 prose max-w-none whitespace-pre-line shadow-inner border border-purple-200 text-lg leading-relaxed">
|
||||
{currentLesson.content}
|
||||
</article>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Navigation */}
|
||||
<div className="flex justify-between items-center pt-12 border-t-2 border-purple-200 animate-fadeInUp animation-delay-600">
|
||||
<button
|
||||
onClick={() => navigateLesson('prev')}
|
||||
disabled={isFirstLesson()}
|
||||
className="px-12 py-5 bg-gradient-to-r from-gray-100 to-gray-200 text-gray-700 rounded-3xl hover:from-gray-200 hover:to-gray-300 disabled:opacity-50 disabled:cursor-not-allowed font-bold transition-all duration-300 transform hover:scale-105 shadow-lg text-xl"
|
||||
>
|
||||
← Previous Lesson
|
||||
</button>
|
||||
|
||||
{!isLastLesson() ? (
|
||||
<button
|
||||
onClick={() => navigateLesson('next')}
|
||||
className="px-12 py-5 bg-gradient-to-r from-purple-600 to-indigo-600 text-white rounded-3xl hover:from-purple-700 hover:to-indigo-700 font-bold transition-all duration-300 transform hover:scale-105 shadow-xl text-xl"
|
||||
>
|
||||
Next Lesson →
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={markComplete}
|
||||
disabled={completed}
|
||||
className={`px-12 py-5 rounded-3xl font-bold transition-all duration-300 transform hover:scale-105 shadow-xl text-xl ${
|
||||
completed
|
||||
? "bg-gradient-to-r from-green-500 to-emerald-500 text-white cursor-not-allowed shadow-inner"
|
||||
: "bg-gradient-to-r from-purple-600 to-pink-600 text-white hover:from-purple-700 hover:to-pink-700"
|
||||
}`}
|
||||
>
|
||||
{completed ? "✓ Course Completed" : "Mark as Complete"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Completion Message */}
|
||||
{completed && !showCertificateModal && (
|
||||
<div className="mt-16 bg-gradient-to-r from-green-50 to-emerald-50 border-2 border-green-300 rounded-3xl p-12 text-center shadow-2xl animate-bounce">
|
||||
<div className="text-green-700">
|
||||
<div className="text-8xl mb-8 animate-pulse">🎉</div>
|
||||
<h3 className="text-4xl font-extrabold mb-6 bg-gradient-to-r from-green-600 to-emerald-600 bg-clip-text text-transparent">Congratulations!</h3>
|
||||
<p className="mb-10 text-green-800 font-semibold text-2xl">
|
||||
You have successfully completed this course!
|
||||
</p>
|
||||
<button
|
||||
onClick={() => setShowCertificateModal(true)}
|
||||
className="px-16 py-6 bg-gradient-to-r from-green-600 to-emerald-600 text-white rounded-3xl hover:from-green-700 hover:to-emerald-700 transition-all duration-300 transform hover:scale-105 font-bold text-xl shadow-xl"
|
||||
>
|
||||
Get Your Certificate 🏆
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
/* Course Overview */
|
||||
<div className="p-20 text-center max-w-5xl mx-auto text-purple-900 animate-fadeIn">
|
||||
<h1 className="text-7xl font-extrabold mb-10 bg-gradient-to-r from-purple-600 via-pink-600 to-indigo-600 bg-clip-text text-transparent drop-shadow-lg">{course.title}</h1>
|
||||
|
||||
<div className="flex flex-wrap justify-center gap-8 mb-16 text-purple-700 font-bold text-xl">
|
||||
<div className="flex items-center space-x-4 bg-purple-100 px-8 py-4 rounded-full shadow-lg transform hover:scale-105 transition-transform duration-300">
|
||||
<User className="w-8 h-8" />
|
||||
<span>by {course.mentor}</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-4 bg-yellow-100 px-8 py-4 rounded-full shadow-lg transform hover:scale-105 transition-transform duration-300">
|
||||
<Star className="w-8 h-8 text-yellow-500" />
|
||||
<span>4.8 Rating</span>
|
||||
</div>
|
||||
<span className="bg-gradient-to-r from-purple-500 to-indigo-500 text-white px-8 py-4 rounded-full text-xl uppercase font-extrabold tracking-widest shadow-lg transform hover:scale-105 transition-transform duration-300">
|
||||
{course.difficulty}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p className="text-3xl max-w-5xl mx-auto mb-16 leading-relaxed tracking-wide text-purple-800">{course.description}</p>
|
||||
|
||||
<div className="grid grid-cols-3 gap-16 mb-16">
|
||||
<div className="text-center transform hover:scale-110 transition-transform duration-300">
|
||||
<div className="w-32 h-32 bg-gradient-to-r from-purple-500 to-indigo-500 rounded-full flex items-center justify-center mx-auto mb-6 shadow-xl">
|
||||
<span className="text-5xl font-extrabold text-white">{modules.length}</span>
|
||||
</div>
|
||||
<div className="uppercase text-purple-700 font-bold tracking-wide text-xl">Modules</div>
|
||||
</div>
|
||||
<div className="text-center transform hover:scale-110 transition-transform duration-300">
|
||||
<div className="w-32 h-32 bg-gradient-to-r from-pink-500 to-purple-500 rounded-full flex items-center justify-center mx-auto mb-6 shadow-xl">
|
||||
<span className="text-5xl font-extrabold text-white">{getTotalLessons()}</span>
|
||||
</div>
|
||||
<div className="uppercase text-purple-700 font-bold tracking-wide text-xl">Lessons</div>
|
||||
</div>
|
||||
<div className="text-center transform hover:scale-110 transition-transform duration-300">
|
||||
<div className="w-32 h-32 bg-gradient-to-r from-indigo-500 to-blue-500 rounded-full flex items-center justify-center mx-auto mb-6 shadow-xl">
|
||||
<span className="text-5xl font-extrabold text-white">{course.students.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="uppercase text-purple-700 font-bold tracking-wide text-xl">Students</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{(course.embed_url || course.video_url) && (
|
||||
<div className="aspect-video rounded-3xl overflow-hidden shadow-2xl mx-auto max-w-6xl bg-black border-4 border-purple-600 mb-16 transform hover:scale-105 transition-transform duration-500">
|
||||
<iframe
|
||||
src={getEmbedUrl(course.embed_url || course.video_url)}
|
||||
title={course.title}
|
||||
allowFullScreen
|
||||
className="w-full h-full"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{getTotalLessons() > 0 ? (
|
||||
<button
|
||||
onClick={() => {
|
||||
const firstModule = modules[0]
|
||||
const firstLessons = lessons[firstModule?.id] || []
|
||||
if (firstLessons.length > 0) {
|
||||
selectLesson(firstModule.id, firstLessons[0].id)
|
||||
}
|
||||
}}
|
||||
className="mt-12 px-20 py-8 bg-gradient-to-r from-purple-600 via-pink-600 to-indigo-600 text-white rounded-3xl hover:from-purple-700 hover:via-pink-700 hover:to-indigo-700 font-extrabold text-3xl shadow-2xl transition-all duration-300 transform hover:scale-110 hover:shadow-purple-500/25"
|
||||
>
|
||||
🚀 Start Learning Journey
|
||||
</button>
|
||||
) : (
|
||||
<div className="bg-gradient-to-r from-yellow-50 to-orange-50 border-2 border-yellow-300 rounded-3xl p-12 text-yellow-800 text-2xl font-bold max-w-lg mx-auto shadow-xl">
|
||||
<div className="w-24 h-24 bg-yellow-500 rounded-full flex items-center justify-center mx-auto mb-8">
|
||||
<span className="text-4xl">🚧</span>
|
||||
</div>
|
||||
<h3 className="text-3xl mb-6">Coming Soon</h3>
|
||||
<p className="font-normal text-yellow-700 text-xl">
|
||||
Amazing lessons are being crafted for this course. Check back soon!
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
{/* Certificate Modal */}
|
||||
{showCertificateModal && course && (
|
||||
<CertificateModal
|
||||
isOpen={showCertificateModal}
|
||||
@@ -752,9 +800,48 @@ export default function CoursePage() {
|
||||
courseMentor={course.mentor}
|
||||
courseId={course.id}
|
||||
userId={user?.uid || firebaseUser?.uid || 'anonymous'}
|
||||
walletId={user?.wallet || firebaseUser?.uid || 'no-wallet'} // Adjust based on your user structure
|
||||
walletId={user?.wallet || firebaseUser?.uid || 'no-wallet'}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Custom CSS for animations */}
|
||||
<style jsx>{`
|
||||
@keyframes float {
|
||||
0%, 100% { transform: translateY(0px); }
|
||||
50% { transform: translateY(-20px); }
|
||||
}
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
@keyframes fadeInUp {
|
||||
from { opacity: 0; transform: translateY(30px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
@keyframes slideInLeft {
|
||||
from { opacity: 0; transform: translateX(-50px); }
|
||||
to { opacity: 1; transform: translateX(0); }
|
||||
}
|
||||
@keyframes slideInRight {
|
||||
from { opacity: 0; transform: translateX(50px); }
|
||||
to { opacity: 1; transform: translateX(0); }
|
||||
}
|
||||
@keyframes slideDown {
|
||||
from { opacity: 0; max-height: 0; }
|
||||
to { opacity: 1; max-height: 500px; }
|
||||
}
|
||||
.animate-float { animation: float 6s ease-in-out infinite; }
|
||||
.animate-fadeIn { animation: fadeIn 1s ease-out; }
|
||||
.animate-fadeInUp { animation: fadeInUp 0.8s ease-out; }
|
||||
.animate-slideInLeft { animation: slideInLeft 0.8s ease-out; }
|
||||
.animate-slideInRight { animation: slideInRight 0.8s ease-out; }
|
||||
.animate-slideDown { animation: slideDown 0.3s ease-out; }
|
||||
.animation-delay-200 { animation-delay: 0.2s; }
|
||||
.animation-delay-400 { animation-delay: 0.4s; }
|
||||
.animation-delay-600 { animation-delay: 0.6s; }
|
||||
.animation-delay-1000 { animation-delay: 1s; }
|
||||
.animation-delay-2000 { animation-delay: 2s; }
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
+382
-49
@@ -1,92 +1,425 @@
|
||||
import Link from "next/link"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Brain, Zap, LinkIcon, BookOpen, Code, Lightbulb } from "lucide-react"
|
||||
import { Brain, Zap, LinkIcon, BookOpen, Code, Lightbulb, Star, Sparkles, Rocket } from "lucide-react"
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="flex flex-col min-h-[calc(100vh-64px)]">
|
||||
<section className="relative w-full py-12 md:py-24 lg:py-32 bg-gradient-to-r from-primary-blue to-primary-purple text-white">
|
||||
<div className="container px-4 md:px-6 text-center">
|
||||
<div className="flex flex-col min-h-[calc(100vh-64px)] overflow-hidden">
|
||||
{/* Hero Section with Enhanced Animations */}
|
||||
<section className="relative w-full py-12 md:py-24 lg:py-32 bg-gradient-to-br from-primary-blue via-purple-600 to-primary-purple text-white animate-fade-in overflow-hidden cursor-crosshair">
|
||||
{/* Enhanced Animated geometric shapes with more visual appeal */}
|
||||
<div className="absolute inset-0 opacity-5">
|
||||
<div className="absolute top-0 left-0 w-96 h-96 bg-white rounded-full mix-blend-overlay animate-blob"></div>
|
||||
<div className="absolute top-0 right-0 w-96 h-96 bg-white rounded-full mix-blend-overlay animate-blob animation-delay-2000"></div>
|
||||
<div className="absolute -bottom-8 left-20 w-96 h-96 bg-white rounded-full mix-blend-overlay animate-blob animation-delay-4000"></div>
|
||||
|
||||
{/* New attractive geometric shapes */}
|
||||
<div className="absolute top-1/3 left-1/2 w-64 h-64 bg-gradient-to-r from-pink-300 to-purple-300 rounded-full mix-blend-overlay animate-pulse opacity-30"></div>
|
||||
<div className="absolute bottom-1/4 right-1/3 w-48 h-48 bg-gradient-to-l from-yellow-300 to-orange-300 rounded-full mix-blend-overlay animate-bounce opacity-20"></div>
|
||||
<div className="absolute top-3/4 left-1/4 w-80 h-80 bg-gradient-to-br from-blue-300 to-cyan-300 rounded-full mix-blend-overlay animate-float opacity-25"></div>
|
||||
</div>
|
||||
|
||||
{/* Enhanced animated background elements with more interactions and visual effects */}
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
<div className="absolute top-1/4 left-1/4 w-32 h-32 bg-white rounded-full animate-float hover:animate-bounce cursor-pointer transition-all duration-500 hover:scale-150 hover:opacity-30 hover:rotate-45 hover:bg-gradient-to-r hover:from-pink-300 hover:to-yellow-300"></div>
|
||||
<div className="absolute top-3/4 right-1/4 w-24 h-24 bg-white rounded-full animate-float animate-delay-500 hover:animate-wiggle cursor-pointer transition-all duration-500 hover:scale-125 hover:opacity-40 hover:rotate-90 hover:bg-gradient-to-l hover:from-blue-300 hover:to-green-300"></div>
|
||||
<div className="absolute top-1/2 right-1/3 w-16 h-16 bg-white rounded-full animate-float animate-delay-1000 hover:animate-pulse cursor-pointer transition-all duration-500 hover:scale-200 hover:opacity-20 hover:rotate-180"></div>
|
||||
<div className="absolute top-1/3 left-1/2 w-20 h-20 bg-white rounded-full animate-rotate-slow hover:animate-wiggle cursor-pointer transition-all duration-500 hover:scale-150 hover:animate-spin-slow"></div>
|
||||
<div className="absolute bottom-1/4 left-1/3 w-28 h-28 bg-white rounded-full animate-pulse-subtle hover:animate-bounce cursor-pointer transition-all duration-500 hover:scale-125 hover:shadow-2xl hover:shadow-white/50"></div>
|
||||
|
||||
{/* Enhanced triangular shapes with gradient effects */}
|
||||
<div className="absolute top-1/5 right-1/5 w-0 h-0 border-l-[20px] border-l-transparent border-r-[20px] border-r-transparent border-b-[35px] border-b-white animate-spin-slow hover:animate-bounce hover:border-b-yellow-300 cursor-pointer transition-all duration-700"></div>
|
||||
<div className="absolute bottom-1/3 right-2/3 w-0 h-0 border-l-[15px] border-l-transparent border-r-[15px] border-r-transparent border-b-[25px] border-b-white animate-ping hover:animate-wiggle hover:border-b-pink-300 cursor-pointer transition-all duration-700"></div>
|
||||
|
||||
{/* New visually attractive hexagon shapes */}
|
||||
<div className="absolute top-1/6 left-3/4 w-12 h-12 bg-gradient-to-r from-purple-300 to-pink-300 transform rotate-45 animate-spin-slow hover:animate-bounce cursor-pointer transition-all duration-700 opacity-60"></div>
|
||||
<div className="absolute bottom-1/5 left-1/5 w-8 h-8 bg-gradient-to-l from-cyan-300 to-blue-300 transform rotate-12 animate-pulse hover:animate-wiggle cursor-pointer transition-all duration-700 opacity-70"></div>
|
||||
|
||||
{/* Animated diamond shapes */}
|
||||
<div className="absolute top-2/3 left-2/3 w-6 h-6 bg-gradient-to-br from-yellow-300 to-orange-300 transform rotate-45 animate-bounce hover:animate-spin cursor-pointer transition-all duration-500 opacity-50"></div>
|
||||
<div className="absolute top-1/5 left-1/6 w-10 h-10 bg-gradient-to-tr from-green-300 to-teal-300 transform rotate-45 animate-float hover:animate-pulse cursor-pointer transition-all duration-600 opacity-40"></div>
|
||||
|
||||
{/* Glowing orbs with pulsing effect */}
|
||||
<div className="absolute top-1/2 left-1/6 w-20 h-20 bg-gradient-radial from-pink-400/50 to-transparent rounded-full animate-pulse hover:animate-ping cursor-pointer transition-all duration-500"></div>
|
||||
<div className="absolute bottom-1/3 right-1/5 w-16 h-16 bg-gradient-radial from-cyan-400/40 to-transparent rounded-full animate-bounce hover:animate-pulse cursor-pointer transition-all duration-600"></div>
|
||||
|
||||
{/* Morphing blob shapes */}
|
||||
<div className="absolute top-3/5 right-3/5 w-24 h-16 bg-gradient-to-r from-purple-300/30 to-pink-300/30 rounded-full animate-morph hover:animate-wobble cursor-pointer transition-all duration-700"></div>
|
||||
<div className="absolute bottom-2/5 left-3/5 w-18 h-24 bg-gradient-to-l from-blue-300/25 to-cyan-300/25 rounded-full animate-morph-reverse hover:animate-wobble cursor-pointer transition-all duration-800"></div>
|
||||
</div>
|
||||
|
||||
{/* Enhanced interactive particles with trail effects and new visual elements */}
|
||||
<div className="absolute inset-0 pointer-events-none">
|
||||
<div className="absolute top-1/6 left-1/6 w-2 h-2 bg-white rounded-full animate-pulse opacity-60 shadow-lg shadow-white/50"></div>
|
||||
<div className="absolute top-2/3 left-3/4 w-1 h-1 bg-white rounded-full animate-pulse animate-delay-300 opacity-40 shadow-md shadow-white/30"></div>
|
||||
<div className="absolute top-1/2 left-1/5 w-1.5 h-1.5 bg-white rounded-full animate-pulse animate-delay-700 opacity-50 shadow-lg shadow-white/40"></div>
|
||||
<div className="absolute top-3/4 right-1/6 w-1 h-1 bg-white rounded-full animate-bounce opacity-70 shadow-sm shadow-white/60"></div>
|
||||
<div className="absolute top-1/3 right-1/2 w-2 h-2 bg-white rounded-full animate-ping opacity-30 shadow-xl shadow-white/20"></div>
|
||||
|
||||
{/* Enhanced floating sparkles with different sizes and animations */}
|
||||
<div className="absolute top-1/4 right-1/3 animate-float animate-delay-200">
|
||||
<Sparkles className="w-4 h-4 text-white opacity-60 animate-pulse" />
|
||||
</div>
|
||||
<div className="absolute bottom-1/4 left-1/4 animate-float animate-delay-800">
|
||||
<Star className="w-3 h-3 text-white opacity-50 animate-spin-slow" />
|
||||
</div>
|
||||
<div className="absolute top-1/5 left-2/5 animate-float animate-delay-1200">
|
||||
<Sparkles className="w-2 h-2 text-yellow-300 opacity-70 animate-ping" />
|
||||
</div>
|
||||
<div className="absolute bottom-1/5 right-2/5 animate-float animate-delay-1600">
|
||||
<Star className="w-5 h-5 text-pink-300 opacity-40 animate-pulse" />
|
||||
</div>
|
||||
|
||||
{/* Constellation-like connected dots */}
|
||||
<div className="absolute top-1/3 left-1/4 w-1 h-1 bg-white rounded-full opacity-60"></div>
|
||||
<div className="absolute top-1/3 left-1/3 w-1 h-1 bg-white rounded-full opacity-60"></div>
|
||||
<div className="absolute top-2/5 left-1/4 w-1 h-1 bg-white rounded-full opacity-60"></div>
|
||||
{/* Connecting lines for constellation effect */}
|
||||
<div className="absolute top-1/3 left-1/4 w-16 h-px bg-gradient-to-r from-white/30 to-transparent"></div>
|
||||
<div className="absolute top-1/3 left-1/4 w-px h-8 bg-gradient-to-b from-white/30 to-transparent"></div>
|
||||
|
||||
{/* Floating geometric shapes as particles */}
|
||||
<div className="absolute top-1/8 right-1/8 w-3 h-3 border border-white/40 rotate-45 animate-spin-slow opacity-50"></div>
|
||||
<div className="absolute bottom-1/8 left-1/8 w-2 h-2 border border-white/30 rounded-full animate-pulse opacity-60"></div>
|
||||
<div className="absolute top-5/6 right-5/6 w-4 h-1 bg-white/30 animate-float opacity-40"></div>
|
||||
|
||||
{/* Ripple effects */}
|
||||
<div className="absolute top-1/2 left-1/2 w-32 h-32 border border-white/10 rounded-full animate-ping opacity-20"></div>
|
||||
<div className="absolute top-1/4 right-1/4 w-24 h-24 border border-white/15 rounded-full animate-pulse opacity-25"></div>
|
||||
<div className="absolute bottom-1/4 left-1/4 w-40 h-40 border border-white/8 rounded-full animate-ping animate-delay-1000 opacity-15"></div>
|
||||
</div>
|
||||
|
||||
<div className="container px-4 md:px-6 text-center relative z-10">
|
||||
<div className="max-w-3xl mx-auto space-y-4">
|
||||
<h1 className="text-4xl font-bold tracking-tighter sm:text-5xl md:text-6xl">
|
||||
OpenLearnX – Decentralized Adaptive Learning
|
||||
{/* Enhanced title with typing effect simulation */}
|
||||
<h1 className="text-4xl font-bold tracking-tighter sm:text-5xl md:text-6xl animate-slide-in-up text-shimmer hover:animate-glow cursor-default transition-all duration-700 hover:scale-105 hover:rotate-1 hover:tracking-widest relative">
|
||||
<span className="inline-block hover:animate-bounce hover:text-yellow-300 transition-all duration-300">O</span>
|
||||
<span className="inline-block hover:animate-bounce hover:text-pink-300 transition-all duration-300 animation-delay-100">p</span>
|
||||
<span className="inline-block hover:animate-bounce hover:text-blue-300 transition-all duration-300 animation-delay-200">e</span>
|
||||
<span className="inline-block hover:animate-bounce hover:text-green-300 transition-all duration-300 animation-delay-300">n</span>
|
||||
<span className="inline-block hover:animate-bounce hover:text-purple-300 transition-all duration-300 animation-delay-400">L</span>
|
||||
<span className="inline-block hover:animate-bounce hover:text-red-300 transition-all duration-300 animation-delay-500">e</span>
|
||||
<span className="inline-block hover:animate-bounce hover:text-indigo-300 transition-all duration-300 animation-delay-600">a</span>
|
||||
<span className="inline-block hover:animate-bounce hover:text-orange-300 transition-all duration-300 animation-delay-700">r</span>
|
||||
<span className="inline-block hover:animate-bounce hover:text-teal-300 transition-all duration-300 animation-delay-800">n</span>
|
||||
<span className="inline-block hover:animate-bounce hover:text-cyan-300 transition-all duration-300 animation-delay-900">X</span>
|
||||
<span className="ml-4 text-2xl">–</span>
|
||||
<span className="text-3xl sm:text-4xl md:text-5xl ml-2">Decentralized Adaptive Learning</span>
|
||||
|
||||
{/* Animated underline */}
|
||||
<div className="absolute -bottom-2 left-0 right-0 h-1 bg-gradient-to-r from-transparent via-white to-transparent animate-pulse opacity-60"></div>
|
||||
</h1>
|
||||
<p className="text-lg md:text-xl">
|
||||
|
||||
<p className="text-lg md:text-xl animate-slide-in-up animate-delay-200 opacity-90 hover:opacity-100 transition-all duration-300 cursor-text hover:scale-105 hover:text-yellow-100 relative">
|
||||
Unlock your potential with AI-powered adaptive learning, coding practice, and blockchain-secured
|
||||
credentials.
|
||||
|
||||
{/* Floating accent */}
|
||||
<div className="absolute -top-2 -right-2 animate-bounce animate-delay-1000">
|
||||
<Rocket className="w-5 h-5 opacity-70 animate-spin-slow" />
|
||||
</div>
|
||||
</p>
|
||||
<div className="flex flex-col gap-2 sm:flex-row justify-center">
|
||||
|
||||
<div className="flex flex-col gap-2 sm:flex-row justify-center animate-slide-in-up animate-delay-300">
|
||||
<Link href="/courses">
|
||||
<Button className="bg-white text-primary-purple hover:bg-gray-100 px-8 py-3 text-lg font-semibold rounded-full shadow-lg">
|
||||
Start Learning
|
||||
<Button className="bg-white text-primary-purple hover:bg-gray-100 px-8 py-3 text-lg font-semibold rounded-full shadow-lg btn-animated hover-lift hover-glow transform transition-all duration-300 group cursor-pointer hover:shadow-2xl hover:animate-bounce-in hover:scale-110 hover:rotate-3 hover:bg-gradient-to-r hover:from-white hover:to-gray-100 relative overflow-hidden">
|
||||
{/* Button shine effect */}
|
||||
<div className="absolute inset-0 -translate-x-full group-hover:translate-x-full bg-gradient-to-r from-transparent via-white/20 to-transparent transition-transform duration-700"></div>
|
||||
|
||||
<span className="group-hover:translate-x-1 transition-transform duration-200 group-hover:font-bold relative z-10">Start Learning</span>
|
||||
<Zap className="ml-2 h-5 w-5 group-hover:rotate-12 group-hover:animate-pulse group-hover:text-yellow-500 transition-all duration-300 relative z-10" />
|
||||
|
||||
{/* Floating particles on hover */}
|
||||
<div className="absolute -top-1 -right-1 opacity-0 group-hover:opacity-100 group-hover:animate-ping transition-all duration-300">
|
||||
<div className="w-2 h-2 bg-primary-purple rounded-full"></div>
|
||||
</div>
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Enhanced animated gradient overlay with dynamic effects */}
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/5 to-transparent animate-gradient opacity-30 hover:opacity-50 transition-opacity duration-500"></div>
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-transparent via-transparent to-black/10 animate-pulse"></div>
|
||||
|
||||
{/* Enhanced animated wave effect at bottom with more visual appeal */}
|
||||
<div className="absolute bottom-0 left-0 right-0 h-20 bg-gradient-to-t from-gray-50 dark:from-gray-900 via-transparent to-transparent opacity-80"></div>
|
||||
|
||||
{/* Additional floating elements for visual richness */}
|
||||
<div className="absolute inset-0 opacity-5">
|
||||
{/* Animated grid pattern */}
|
||||
<div className="absolute top-0 left-0 w-full h-full bg-grid-pattern animate-grid-float"></div>
|
||||
|
||||
{/* Flowing lines */}
|
||||
<div className="absolute top-1/4 left-0 w-full h-px bg-gradient-to-r from-transparent via-white/20 to-transparent animate-flow-right"></div>
|
||||
<div className="absolute top-3/4 left-0 w-full h-px bg-gradient-to-r from-transparent via-white/15 to-transparent animate-flow-left"></div>
|
||||
<div className="absolute top-1/2 left-0 w-full h-px bg-gradient-to-r from-transparent via-white/10 to-transparent animate-flow-right animate-delay-1000"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="w-full py-12 md:py-24 lg:py-32 bg-gray-50 dark:bg-gray-900">
|
||||
<div className="container px-4 md:px-6">
|
||||
<div className="grid gap-8 md:grid-cols-2 lg:grid-cols-3">
|
||||
<Card className="bg-white shadow-md rounded-lg p-6 dark:bg-gray-800 dark:text-gray-100">
|
||||
<CardHeader className="flex flex-row items-center space-x-4 pb-2">
|
||||
<BookOpen className="h-8 w-8 text-primary-blue" />
|
||||
<CardTitle className="text-xl font-semibold">Interactive Courses</CardTitle>
|
||||
{/* Features Section with Enhanced Size and Better Space Utilization */}
|
||||
<section className="w-full py-16 md:py-28 lg:py-36 bg-gray-50 dark:bg-gray-900 animate-fade-in animate-delay-500 relative overflow-hidden">
|
||||
{/* Enhanced background pattern animation with more visual elements */}
|
||||
<div className="absolute inset-0 opacity-5">
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-primary-blue/10 via-transparent to-primary-purple/10 animate-gradient-slow"></div>
|
||||
|
||||
{/* Additional floating background elements */}
|
||||
<div className="absolute top-1/4 left-1/4 w-64 h-64 bg-gradient-radial from-purple-200/20 to-transparent rounded-full animate-float-slow"></div>
|
||||
<div className="absolute bottom-1/4 right-1/4 w-48 h-48 bg-gradient-radial from-blue-200/15 to-transparent rounded-full animate-float-slow animate-delay-2000"></div>
|
||||
<div className="absolute top-3/4 left-3/4 w-32 h-32 bg-gradient-radial from-pink-200/25 to-transparent rounded-full animate-pulse-subtle"></div>
|
||||
|
||||
{/* Geometric pattern overlay */}
|
||||
<div className="absolute inset-0 bg-hex-pattern opacity-10 animate-pattern-shift"></div>
|
||||
</div>
|
||||
|
||||
{/* Wider container for better space utilization */}
|
||||
<div className="max-w-8xl mx-auto px-6 md:px-8 lg:px-12 relative z-10">
|
||||
<div className="text-center mb-16 animate-slide-in-up animate-delay-700">
|
||||
<h2 className="text-4xl font-bold tracking-tight sm:text-5xl md:text-6xl mb-6 text-gradient hover:animate-wiggle cursor-default transition-all duration-500 hover:scale-105 relative">
|
||||
Powerful Features
|
||||
|
||||
{/* Animated accent lines */}
|
||||
<div className="absolute -top-4 left-1/2 transform -translate-x-1/2 w-16 h-1 bg-gradient-to-r from-primary-blue to-primary-purple animate-pulse"></div>
|
||||
<div className="absolute -bottom-4 left-1/2 transform -translate-x-1/2 w-24 h-1 bg-gradient-to-r from-primary-purple to-primary-blue animate-pulse animate-delay-500"></div>
|
||||
</h2>
|
||||
<p className="text-xl text-muted-foreground max-w-3xl mx-auto hover:text-foreground transition-colors duration-300 cursor-text hover:scale-105 animate-fade-in animate-delay-1000">
|
||||
Discover the tools and technologies that make OpenLearnX the ultimate learning platform
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Enhanced grid with larger cards and better spacing */}
|
||||
<div className="grid gap-10 md:gap-12 lg:gap-14 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-3 2xl:grid-cols-3 stagger-container">
|
||||
|
||||
{/* Interactive Courses Card - Enhanced Size */}
|
||||
<Card className="bg-white shadow-xl rounded-2xl p-8 md:p-10 dark:bg-gray-800 dark:text-gray-100 card-interactive hover-lift group transform transition-all duration-500 hover:shadow-2xl border-0 hover:border-2 hover:border-primary/30 cursor-pointer hover:rotate-1 hover:-translate-y-4 animate-slide-in-left animate-delay-300 relative overflow-hidden min-h-[320px] md:min-h-[360px]">
|
||||
{/* Card background gradient on hover */}
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-primary-blue/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500 rounded-2xl"></div>
|
||||
|
||||
<CardHeader className="flex flex-col items-center text-center space-y-6 pb-6 relative z-10">
|
||||
<div className="p-4 bg-primary-blue/10 rounded-2xl group-hover:bg-primary-blue/20 transition-all duration-500 group-hover:rotate-12 group-hover:scale-110 cursor-pointer group-hover:shadow-2xl group-hover:shadow-primary-blue/30">
|
||||
<BookOpen className="h-12 w-12 md:h-14 md:w-14 text-primary-blue group-hover:scale-125 group-hover:animate-pulse transition-all duration-500" />
|
||||
|
||||
{/* Floating sparkle effect */}
|
||||
<div className="absolute -top-2 -right-2 opacity-0 group-hover:opacity-100 group-hover:animate-ping transition-all duration-300">
|
||||
<Sparkles className="w-4 h-4 text-primary-blue" />
|
||||
</div>
|
||||
</div>
|
||||
<CardTitle className="text-2xl md:text-3xl font-bold group-hover:text-primary-blue transition-all duration-500 group-hover:tracking-wide cursor-pointer group-hover:translate-y-1">
|
||||
Interactive Courses
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="text-gray-600 dark:text-gray-300">
|
||||
Engage with rich multimedia content, track your progress, and master new subjects at your own pace.
|
||||
<CardContent className="text-gray-600 dark:text-gray-300 group-hover:text-gray-900 dark:group-hover:text-gray-100 transition-all duration-300 relative z-10 group-hover:translate-y-2 text-center text-lg leading-relaxed">
|
||||
Engage with rich multimedia content, track your progress, and master new subjects at your own pace with our comprehensive learning platform.
|
||||
</CardContent>
|
||||
|
||||
{/* Hover shine effect */}
|
||||
<div className="absolute inset-0 -translate-x-full group-hover:translate-x-full bg-gradient-to-r from-transparent via-white/10 to-transparent transition-transform duration-1000 rounded-2xl"></div>
|
||||
</Card>
|
||||
<Card className="bg-white shadow-md rounded-lg p-6 dark:bg-gray-800 dark:text-gray-100">
|
||||
<CardHeader className="flex flex-row items-center space-x-4 pb-2">
|
||||
<Code className="h-8 w-8 text-primary-purple" />
|
||||
<CardTitle className="text-xl font-semibold">LeetCode-Style Coding Practice</CardTitle>
|
||||
|
||||
{/* Coding Practice Card - Enhanced Size */}
|
||||
<Card className="bg-white shadow-xl rounded-2xl p-8 md:p-10 dark:bg-gray-800 dark:text-gray-100 card-interactive hover-lift group transform transition-all duration-500 hover:shadow-2xl border-0 hover:border-2 hover:border-primary/30 cursor-pointer hover:-rotate-1 hover:-translate-y-4 animate-slide-in-up animate-delay-500 relative overflow-hidden min-h-[320px] md:min-h-[360px]">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-primary-purple/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500 rounded-2xl"></div>
|
||||
|
||||
<CardHeader className="flex flex-col items-center text-center space-y-6 pb-6 relative z-10">
|
||||
<div className="p-4 bg-primary-purple/10 rounded-2xl group-hover:bg-primary-purple/20 transition-all duration-500 group-hover:-rotate-12 group-hover:scale-110 cursor-pointer group-hover:shadow-2xl group-hover:shadow-primary-purple/30 relative">
|
||||
<Code className="h-12 w-12 md:h-14 md:w-14 text-primary-purple group-hover:scale-125 group-hover:animate-bounce transition-all duration-500" />
|
||||
|
||||
{/* Code brackets animation */}
|
||||
<div className="absolute -left-3 top-1/2 transform -translate-y-1/2 opacity-0 group-hover:opacity-100 group-hover:animate-pulse transition-all duration-300 text-primary-purple font-bold text-lg">{'<'}</div>
|
||||
<div className="absolute -right-3 top-1/2 transform -translate-y-1/2 opacity-0 group-hover:opacity-100 group-hover:animate-pulse transition-all duration-300 text-primary-purple font-bold text-lg">{'>'}</div>
|
||||
</div>
|
||||
<CardTitle className="text-2xl md:text-3xl font-bold group-hover:text-primary-purple transition-all duration-500 group-hover:tracking-wide cursor-pointer group-hover:translate-y-1">
|
||||
LeetCode-Style Coding Practice
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="text-gray-600 dark:text-gray-300">
|
||||
Sharpen your coding skills with interactive problems, instant feedback, and a built-in code editor.
|
||||
<CardContent className="text-gray-600 dark:text-gray-300 group-hover:text-gray-900 dark:group-hover:text-gray-100 transition-all duration-300 relative z-10 group-hover:translate-y-2 text-center text-lg leading-relaxed">
|
||||
Sharpen your coding skills with interactive problems, instant feedback, and a built-in code editor designed for optimal learning.
|
||||
</CardContent>
|
||||
|
||||
<div className="absolute inset-0 -translate-x-full group-hover:translate-x-full bg-gradient-to-r from-transparent via-white/10 to-transparent transition-transform duration-1000 rounded-2xl"></div>
|
||||
</Card>
|
||||
<Card className="bg-white shadow-md rounded-lg p-6 dark:bg-gray-800 dark:text-gray-100">
|
||||
<CardHeader className="flex flex-row items-center space-x-4 pb-2">
|
||||
<Lightbulb className="h-8 w-8 text-primary-blue" />
|
||||
<CardTitle className="text-xl font-semibold">Advanced Quiz Platform</CardTitle>
|
||||
|
||||
{/* Advanced Quiz Platform Card - Enhanced Size */}
|
||||
<Card className="bg-white shadow-xl rounded-2xl p-8 md:p-10 dark:bg-gray-800 dark:text-gray-100 card-interactive hover-lift group transform transition-all duration-500 hover:shadow-2xl border-0 hover:border-2 hover:border-primary/30 cursor-pointer hover:rotate-1 hover:-translate-y-4 animate-slide-in-right animate-delay-700 relative overflow-hidden min-h-[320px] md:min-h-[360px]">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-yellow-500/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500 rounded-2xl"></div>
|
||||
|
||||
<CardHeader className="flex flex-col items-center text-center space-y-6 pb-6 relative z-10">
|
||||
<div className="p-4 bg-primary-blue/10 rounded-2xl group-hover:bg-yellow-500/20 transition-all duration-500 group-hover:rotate-45 group-hover:scale-110 cursor-pointer group-hover:shadow-2xl group-hover:shadow-yellow-500/30 relative">
|
||||
<Lightbulb className="h-12 w-12 md:h-14 md:w-14 text-primary-blue group-hover:scale-125 group-hover:animate-pulse-subtle transition-all duration-500 group-hover:text-yellow-500" />
|
||||
|
||||
{/* Lightbulb glow effect */}
|
||||
<div className="absolute inset-0 bg-yellow-400 rounded-2xl opacity-0 group-hover:opacity-20 group-hover:animate-ping transition-all duration-500"></div>
|
||||
</div>
|
||||
<CardTitle className="text-2xl md:text-3xl font-bold group-hover:text-yellow-600 transition-all duration-500 group-hover:tracking-wide cursor-pointer group-hover:translate-y-1">
|
||||
Advanced Quiz Platform
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="text-gray-600 dark:text-gray-300">
|
||||
Test your knowledge with timed multiple-choice quizzes, detailed explanations, and performance tracking.
|
||||
<CardContent className="text-gray-600 dark:text-gray-300 group-hover:text-gray-900 dark:group-hover:text-gray-100 transition-all duration-300 relative z-10 group-hover:translate-y-2 text-center text-lg leading-relaxed">
|
||||
Test your knowledge with timed multiple-choice quizzes, detailed explanations, and comprehensive performance tracking analytics.
|
||||
</CardContent>
|
||||
|
||||
<div className="absolute inset-0 -translate-x-full group-hover:translate-x-full bg-gradient-to-r from-transparent via-yellow-300/10 to-transparent transition-transform duration-1000 rounded-2xl"></div>
|
||||
</Card>
|
||||
<Card className="bg-white shadow-md rounded-lg p-6 dark:bg-gray-800 dark:text-gray-100">
|
||||
<CardHeader className="flex flex-row items-center space-x-4 pb-2">
|
||||
<Brain className="h-8 w-8 text-primary-purple" />
|
||||
<CardTitle className="text-xl font-semibold">AI-powered Adaptive Learning</CardTitle>
|
||||
|
||||
{/* AI-powered Learning Card - Enhanced Size */}
|
||||
<Card className="bg-white shadow-xl rounded-2xl p-8 md:p-10 dark:bg-gray-800 dark:text-gray-100 card-interactive hover-lift group transform transition-all duration-500 hover:shadow-2xl border-0 hover:border-2 hover:border-primary/30 cursor-pointer hover:-rotate-1 hover:-translate-y-4 animate-slide-in-left animate-delay-900 relative overflow-hidden min-h-[320px] md:min-h-[360px]">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-pink-500/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500 rounded-2xl"></div>
|
||||
|
||||
<CardHeader className="flex flex-col items-center text-center space-y-6 pb-6 relative z-10">
|
||||
<div className="p-4 bg-primary-purple/10 rounded-2xl group-hover:bg-pink-500/20 transition-all duration-500 group-hover:rotate-12 group-hover:scale-110 cursor-pointer group-hover:shadow-2xl group-hover:shadow-pink-500/30 relative">
|
||||
<Brain className="h-12 w-12 md:h-14 md:w-14 text-primary-purple group-hover:scale-125 group-hover:animate-glow transition-all duration-500 group-hover:text-pink-500" />
|
||||
|
||||
{/* Neural network visualization */}
|
||||
<div className="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity duration-500">
|
||||
<div className="absolute top-1 left-1 w-2 h-2 bg-pink-400 rounded-full animate-ping animation-delay-100"></div>
|
||||
<div className="absolute bottom-1 right-1 w-2 h-2 bg-pink-400 rounded-full animate-ping animation-delay-300"></div>
|
||||
<div className="absolute top-1/2 right-1 w-2 h-2 bg-pink-400 rounded-full animate-ping animation-delay-500"></div>
|
||||
</div>
|
||||
</div>
|
||||
<CardTitle className="text-2xl md:text-3xl font-bold group-hover:text-pink-600 transition-all duration-500 group-hover:tracking-wide cursor-pointer group-hover:translate-y-1">
|
||||
AI-powered Adaptive Learning
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="text-gray-600 dark:text-gray-300">
|
||||
Our platform intelligently adjusts content and question difficulty based on your performance.
|
||||
<CardContent className="text-gray-600 dark:text-gray-300 group-hover:text-gray-900 dark:group-hover:text-gray-100 transition-all duration-300 relative z-10 group-hover:translate-y-2 text-center text-lg leading-relaxed">
|
||||
Our platform intelligently adjusts content and question difficulty based on your performance, ensuring optimal learning outcomes.
|
||||
</CardContent>
|
||||
|
||||
<div className="absolute inset-0 -translate-x-full group-hover:translate-x-full bg-gradient-to-r from-transparent via-pink-300/10 to-transparent transition-transform duration-1000 rounded-2xl"></div>
|
||||
</Card>
|
||||
<Card className="bg-white shadow-md rounded-lg p-6 dark:bg-gray-800 dark:text-gray-100">
|
||||
<CardHeader className="flex flex-row items-center space-x-4 pb-2">
|
||||
<LinkIcon className="h-8 w-8 text-primary-blue" />
|
||||
<CardTitle className="text-xl font-semibold">Blockchain-secured Credentials</CardTitle>
|
||||
|
||||
{/* Blockchain Credentials Card - Enhanced Size */}
|
||||
<Card className="bg-white shadow-xl rounded-2xl p-8 md:p-10 dark:bg-gray-800 dark:text-gray-100 card-interactive hover-lift group transform transition-all duration-500 hover:shadow-2xl border-0 hover:border-2 hover:border-primary/30 cursor-pointer hover:rotate-1 hover:-translate-y-4 animate-slide-in-up animate-delay-1100 relative overflow-hidden min-h-[320px] md:min-h-[360px]">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-green-500/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500 rounded-2xl"></div>
|
||||
|
||||
<CardHeader className="flex flex-col items-center text-center space-y-6 pb-6 relative z-10">
|
||||
<div className="p-4 bg-primary-blue/10 rounded-2xl group-hover:bg-green-500/20 transition-all duration-500 group-hover:-rotate-45 group-hover:scale-110 cursor-pointer group-hover:shadow-2xl group-hover:shadow-green-500/30 relative">
|
||||
<LinkIcon className="h-12 w-12 md:h-14 md:w-14 text-primary-blue group-hover:scale-125 group-hover:rotate-180 transition-all duration-700 group-hover:text-green-500" />
|
||||
|
||||
{/* Chain link animation */}
|
||||
<div className="absolute -top-3 -left-3 opacity-0 group-hover:opacity-100 group-hover:animate-bounce transition-all duration-300">
|
||||
<div className="w-3 h-3 border-2 border-green-500 rounded-full"></div>
|
||||
</div>
|
||||
<div className="absolute -bottom-3 -right-3 opacity-0 group-hover:opacity-100 group-hover:animate-bounce animation-delay-200 transition-all duration-300">
|
||||
<div className="w-3 h-3 border-2 border-green-500 rounded-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
<CardTitle className="text-2xl md:text-3xl font-bold group-hover:text-green-600 transition-all duration-500 group-hover:tracking-wide cursor-pointer group-hover:translate-y-1">
|
||||
Blockchain-secured Credentials
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="text-gray-600 dark:text-gray-300">
|
||||
Your achievements are secured on the blockchain, providing verifiable and tamper-proof records.
|
||||
<CardContent className="text-gray-600 dark:text-gray-300 group-hover:text-gray-900 dark:group-hover:text-gray-100 transition-all duration-300 relative z-10 group-hover:translate-y-2 text-center text-lg leading-relaxed">
|
||||
Your achievements are secured on the blockchain, providing verifiable and tamper-proof records that employers trust.
|
||||
</CardContent>
|
||||
|
||||
<div className="absolute inset-0 -translate-x-full group-hover:translate-x-full bg-gradient-to-r from-transparent via-green-300/10 to-transparent transition-transform duration-1000 rounded-2xl"></div>
|
||||
</Card>
|
||||
<Card className="bg-white shadow-md rounded-lg p-6 dark:bg-gray-800 dark:text-gray-100">
|
||||
<CardHeader className="flex flex-row items-center space-x-4 pb-2">
|
||||
<Zap className="h-8 w-8 text-primary-purple" />
|
||||
<CardTitle className="text-xl font-semibold">Personalized Dashboard</CardTitle>
|
||||
|
||||
{/* Personalized Dashboard Card - Enhanced Size - Spans full width on smaller screens */}
|
||||
<Card className="bg-white shadow-xl rounded-2xl p-8 md:p-10 dark:bg-gray-800 dark:text-gray-100 card-interactive hover-lift group transform transition-all duration-500 hover:shadow-2xl border-0 hover:border-2 hover:border-primary/30 cursor-pointer hover:-rotate-1 hover:-translate-y-4 animate-slide-in-right animate-delay-1300 relative overflow-hidden min-h-[320px] md:min-h-[360px] md:col-span-2 lg:col-span-1">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-yellow-300/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500 rounded-2xl"></div>
|
||||
|
||||
<CardHeader className="flex flex-col items-center text-center space-y-6 pb-6 relative z-10">
|
||||
<div className="p-4 bg-primary-purple/10 rounded-2xl group-hover:bg-yellow-300/20 transition-all duration-500 group-hover:rotate-180 group-hover:scale-110 cursor-pointer group-hover:shadow-2xl group-hover:shadow-yellow-300/30 relative">
|
||||
<Zap className="h-12 w-12 md:h-14 md:w-14 text-primary-purple group-hover:scale-125 group-hover:animate-glow transition-all duration-500 group-hover:text-yellow-500" />
|
||||
|
||||
{/* Electric effect */}
|
||||
<div className="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
||||
<div className="absolute top-0 left-1/2 w-px h-3 bg-yellow-400 animate-pulse"></div>
|
||||
<div className="absolute bottom-0 left-1/2 w-px h-3 bg-yellow-400 animate-pulse animation-delay-200"></div>
|
||||
<div className="absolute left-0 top-1/2 w-3 h-px bg-yellow-400 animate-pulse animation-delay-400"></div>
|
||||
<div className="absolute right-0 top-1/2 w-3 h-px bg-yellow-400 animate-pulse animation-delay-600"></div>
|
||||
</div>
|
||||
</div>
|
||||
<CardTitle className="text-2xl md:text-3xl font-bold group-hover:text-yellow-600 transition-all duration-500 group-hover:tracking-wide cursor-pointer group-hover:translate-y-1">
|
||||
Personalized Dashboard
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="text-gray-600 dark:text-gray-300">
|
||||
Track your progress, identify strengths and weaknesses, and visualize your learning journey.
|
||||
<CardContent className="text-gray-600 dark:text-gray-300 group-hover:text-gray-900 dark:group-hover:text-gray-100 transition-all duration-300 relative z-10 group-hover:translate-y-2 text-center text-lg leading-relaxed">
|
||||
Track your progress, identify strengths and weaknesses, and visualize your learning journey with intuitive analytics.
|
||||
</CardContent>
|
||||
|
||||
<div className="absolute inset-0 -translate-x-full group-hover:translate-x-full bg-gradient-to-r from-transparent via-yellow-300/10 to-transparent transition-transform duration-1000 rounded-2xl"></div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Enhanced Call-to-Action Section with more dynamic effects */}
|
||||
<section className="w-full py-16 bg-gradient-to-br from-primary-blue/5 via-purple-500/5 to-primary-purple/5 animate-fade-in animate-delay-1000 hover:from-primary-blue/10 hover:via-purple-500/10 hover:to-primary-purple/10 transition-all duration-700 cursor-default relative overflow-hidden">
|
||||
{/* Enhanced animated background pattern with more visual elements */}
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
<div className="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-transparent via-primary-blue to-transparent animate-pulse"></div>
|
||||
<div className="absolute bottom-0 right-0 w-full h-1 bg-gradient-to-l from-transparent via-primary-purple to-transparent animate-pulse animate-delay-500"></div>
|
||||
|
||||
{/* Additional decorative elements */}
|
||||
<div className="absolute top-1/2 left-1/4 w-8 h-8 bg-gradient-to-r from-primary-blue/20 to-transparent rounded-full animate-ping"></div>
|
||||
<div className="absolute top-1/3 right-1/4 w-6 h-6 bg-gradient-to-l from-primary-purple/20 to-transparent rounded-full animate-pulse animate-delay-700"></div>
|
||||
<div className="absolute bottom-1/3 left-1/3 w-4 h-4 bg-gradient-to-br from-purple-400/20 to-transparent rounded-full animate-bounce"></div>
|
||||
</div>
|
||||
|
||||
<div className="container px-4 md:px-6 text-center relative z-10">
|
||||
<div className="max-w-2xl mx-auto space-y-6 animate-slide-in-up animate-delay-300">
|
||||
<h3 className="text-2xl font-bold tracking-tight sm:text-3xl hover:animate-pulse cursor-default transition-all duration-500 hover:scale-105 hover:text-primary relative">
|
||||
Ready to Transform Your Learning Journey?
|
||||
|
||||
{/* Floating question mark */}
|
||||
<div className="absolute -top-2 -right-6 animate-bounce animate-delay-1000 opacity-60">
|
||||
<div className="w-6 h-6 rounded-full bg-primary text-white flex items-center justify-center text-sm font-bold">?</div>
|
||||
</div>
|
||||
</h3>
|
||||
|
||||
<p className="text-lg text-muted-foreground hover:text-foreground transition-all duration-300 cursor-text hover:scale-105 animate-fade-in animate-delay-500">
|
||||
Join thousands of learners who are already experiencing the future of education
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center animate-slide-in-up animate-delay-700">
|
||||
<Link href="/courses">
|
||||
<Button className="btn-animated hover-lift hover-glow px-8 py-3 text-lg font-semibold cursor-pointer hover:shadow-2xl hover:animate-wiggle transform transition-all duration-500 hover:scale-110 hover:rotate-1 relative overflow-hidden group">
|
||||
{/* Ripple effect */}
|
||||
<div className="absolute inset-0 bg-white opacity-0 group-hover:opacity-20 group-hover:animate-ping transition-all duration-300 rounded-lg"></div>
|
||||
|
||||
<span className="group-hover:tracking-wider transition-all duration-300 relative z-10">Explore Courses</span>
|
||||
|
||||
{/* Trailing sparkles */}
|
||||
<div className="absolute -right-1 top-1/2 transform -translate-y-1/2 opacity-0 group-hover:opacity-100 group-hover:animate-bounce transition-all duration-300">
|
||||
<Sparkles className="w-4 h-4" />
|
||||
</div>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
<Link href="/coding">
|
||||
<Button variant="outline" className="btn-animated hover-lift px-8 py-3 text-lg font-semibold border-2 hover:border-primary cursor-pointer hover:shadow-xl hover:animate-bounce transform transition-all duration-500 hover:scale-110 hover:-rotate-1 hover:bg-primary hover:text-primary-foreground relative overflow-hidden group">
|
||||
{/* Slide-in background */}
|
||||
<div className="absolute inset-0 bg-primary transform -translate-x-full group-hover:translate-x-0 transition-transform duration-500"></div>
|
||||
|
||||
<span className="group-hover:tracking-wider transition-all duration-300 relative z-10">Try Coding Practice</span>
|
||||
|
||||
{/* Code symbol animation */}
|
||||
<div className="absolute -left-1 top-1/2 transform -translate-y-1/2 opacity-0 group-hover:opacity-100 group-hover:animate-pulse transition-all duration-300 text-xs font-bold relative z-10">
|
||||
{'</>'}
|
||||
</div>
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Enhanced floating action elements */}
|
||||
<div className="absolute top-1/4 left-1/4 animate-float animate-delay-2000 opacity-20">
|
||||
<div className="w-8 h-8 border-2 border-primary rotate-45"></div>
|
||||
</div>
|
||||
<div className="absolute bottom-1/4 right-1/4 animate-float animate-delay-3000 opacity-20">
|
||||
<div className="w-6 h-6 bg-primary-purple rounded-full"></div>
|
||||
</div>
|
||||
<div className="absolute top-1/2 right-1/6 animate-pulse opacity-15">
|
||||
<div className="w-12 h-12 border border-primary-blue rounded-full"></div>
|
||||
</div>
|
||||
<div className="absolute bottom-1/6 left-1/6 animate-bounce opacity-25">
|
||||
<div className="w-4 h-4 bg-gradient-to-r from-purple-400 to-pink-400 transform rotate-45"></div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,19 +5,19 @@ import { cva, type VariantProps } from "class-variance-authority"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-all duration-200 ease-in-out focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 transform hover:scale-105 active:scale-95",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary/90 hover:shadow-lg hover:shadow-primary/25",
|
||||
destructive:
|
||||
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||
"bg-destructive text-destructive-foreground hover:bg-destructive/90 hover:shadow-lg hover:shadow-destructive/25",
|
||||
outline:
|
||||
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
||||
"border border-input bg-background hover:bg-accent hover:text-accent-foreground hover:border-accent hover:shadow-md",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80 hover:shadow-md hover:shadow-secondary/25",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground hover:shadow-sm",
|
||||
link: "text-primary underline-offset-4 hover:underline hover:text-primary/80",
|
||||
},
|
||||
size: {
|
||||
default: "h-10 px-4 py-2",
|
||||
|
||||
@@ -41,7 +41,7 @@ NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
|
||||
const NavigationMenuItem = NavigationMenuPrimitive.Item
|
||||
|
||||
const navigationMenuTriggerStyle = cva(
|
||||
"group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50"
|
||||
"group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-all duration-300 ease-in-out transform hover:bg-accent hover:text-accent-foreground hover:scale-105 hover:shadow-md focus:bg-accent focus:text-accent-foreground focus:outline-none focus:ring-2 focus:ring-accent focus:ring-offset-2 active:scale-95 disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[active]:shadow-sm data-[state=open]:bg-accent/50 data-[state=open]:scale-105 data-[state=open]:shadow-lg"
|
||||
)
|
||||
|
||||
const NavigationMenuTrigger = React.forwardRef<
|
||||
@@ -55,7 +55,7 @@ const NavigationMenuTrigger = React.forwardRef<
|
||||
>
|
||||
{children}{" "}
|
||||
<ChevronDown
|
||||
className="relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180"
|
||||
className="relative top-[1px] ml-1 h-3 w-3 transition-all duration-300 ease-in-out group-data-[state=open]:rotate-180 group-hover:text-accent-foreground"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</NavigationMenuPrimitive.Trigger>
|
||||
@@ -69,7 +69,7 @@ const NavigationMenuContent = React.forwardRef<
|
||||
<NavigationMenuPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ",
|
||||
"left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 data-[motion^=from-]:duration-300 data-[motion^=to-]:duration-200 md:absolute md:w-auto backdrop-blur-sm",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -86,7 +86,7 @@ const NavigationMenuViewport = React.forwardRef<
|
||||
<div className={cn("absolute left-0 top-full flex justify-center")}>
|
||||
<NavigationMenuPrimitive.Viewport
|
||||
className={cn(
|
||||
"origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
|
||||
"origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 data-[state=open]:fade-in data-[state=closed]:fade-out data-[state=open]:duration-300 data-[state=closed]:duration-200 data-[state=open]:ease-out data-[state=closed]:ease-in md:w-[var(--radix-navigation-menu-viewport-width)] backdrop-blur-md",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
@@ -94,8 +94,7 @@ const NavigationMenuViewport = React.forwardRef<
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
NavigationMenuViewport.displayName =
|
||||
NavigationMenuPrimitive.Viewport.displayName
|
||||
NavigationMenuViewport.displayName = NavigationMenuPrimitive.Viewport.displayName
|
||||
|
||||
const NavigationMenuIndicator = React.forwardRef<
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
|
||||
@@ -104,16 +103,15 @@ const NavigationMenuIndicator = React.forwardRef<
|
||||
<NavigationMenuPrimitive.Indicator
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
|
||||
"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in data-[state=visible]:slide-in-from-top-1 data-[state=hidden]:slide-out-to-top-1 data-[state=visible]:duration-200 data-[state=hidden]:duration-150",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
|
||||
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md transition-all duration-200 data-[state=visible]:shadow-lg" />
|
||||
</NavigationMenuPrimitive.Indicator>
|
||||
))
|
||||
NavigationMenuIndicator.displayName =
|
||||
NavigationMenuPrimitive.Indicator.displayName
|
||||
NavigationMenuIndicator.displayName = NavigationMenuPrimitive.Indicator.displayName
|
||||
|
||||
export {
|
||||
navigationMenuTriggerStyle,
|
||||
|
||||
Reference in New Issue
Block a user