From 1e5cc006351fa7e66e3f3a1e678f2ad113c5387d Mon Sep 17 00:00:00 2001 From: Son Nguyen Date: Sun, 7 Dec 2025 21:08:45 -0500 Subject: [PATCH] Update: Enhance Terraform configurations with new outputs and variables --- index.html | 1070 +++++++++++++++++++++++++++++++++++++++ packages/script.js | 556 +++++++++++++++++++++ packages/styles.css | 1165 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 2791 insertions(+) create mode 100644 index.html create mode 100644 packages/script.js create mode 100644 packages/styles.css diff --git a/index.html b/index.html new file mode 100644 index 0000000..fae061b --- /dev/null +++ b/index.html @@ -0,0 +1,1070 @@ + + + + + + + + + + + + + + + + + + + + + Learning Management System - Wiki & Documentation + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + +
+
+ +

Learning Management System

+

+ A comprehensive full-stack e-learning platform built with the MAD Stack + (MongoDB, Angular, Django) featuring modern DevOps practices, cloud-native architecture, + and enterprise-grade security. +

+ +
+ + Angular 18 + + + Django 4.2 + + + MongoDB 5.0 + + + Redis 6 + + + Docker + + + Kubernetes + + + AWS + +
+ + +
+
+ + +
+
+

Project at a Glance

+

Enterprise-grade learning management solution with comprehensive features

+
+ +
+
+ 10 +
Core Features
+
+
+ 25+ +
API Endpoints
+
+
+ 3 +
Database Systems
+
+
+ 100% +
Containerized
+
+
+
+ + +
+
+

Core Features

+

Everything you need for a modern learning management system

+
+ +
+
+
πŸ‘₯
+

User Management

+

+ Complete user authentication and authorization with JWT tokens, role-based access control (RBAC), + and support for students, instructors, and administrators. +

+
+ +
+
πŸ“š
+

Course Management

+

+ Create, update, delete, and organize courses with categories, lessons, multimedia content, + and rich text descriptions. Full CRUD operations available. +

+
+ +
+
πŸ“
+

Quiz System

+

+ Interactive quizzes with multiple-choice questions, automatic grading, + progress tracking, and detailed analytics for instructors. +

+
+ +
+
πŸ“Š
+

Progress Tracking

+

+ Real-time progress monitoring for students, lesson completion tracking, + achievement milestones, and comprehensive reporting dashboards. +

+
+ +
+
⚑
+

Redis Caching

+

+ High-performance server-side caching with Redis for improved response times, + reduced database load, and enhanced scalability. +

+
+ +
+
πŸ”’
+

Security

+

+ Enterprise-grade security with encrypted passwords, secure token management, + CORS protection, and comprehensive API authentication. +

+
+ +
+
πŸ“±
+

Responsive Design

+

+ Fully responsive interface built with Bootstrap 5, optimized for desktop, tablet, + and mobile devices with PWA capabilities. +

+
+ +
+
πŸ“‘
+

RESTful API

+

+ Comprehensive REST API with Swagger/OpenAPI documentation, versioning support, + and extensive endpoint coverage for all features. +

+
+ +
+
πŸ””
+

Notifications

+

+ Real-time notification system for course updates, enrollment confirmations, + progress achievements, and important announcements. +

+
+ +
+
πŸš€
+

CI/CD Pipeline

+

+ Automated deployment with GitHub Actions and Jenkins, Docker containerization, + and Kubernetes orchestration for seamless updates. +

+
+
+
+ + +
+
+

System Architecture

+

Modern, scalable, and cloud-native architecture design

+
+ +
+

High-Level Architecture

+
+graph TB + subgraph "Client Layer" + A[Web Browser] + B[Mobile Browser] + end + + subgraph "Frontend - Angular SPA" + C[Angular Components] + D[Angular Services] + E[HTTP Interceptors] + F[State Management] + end + + subgraph "API Gateway" + G[NGINX Reverse Proxy] + end + + subgraph "Backend - Django REST API" + H[Django REST Framework] + I[Authentication Layer] + J[Business Logic] + K[Serializers & ViewSets] + end + + subgraph "Caching Layer" + L[Redis Cache] + end + + subgraph "Data Persistence" + M[(MongoDB - Course Data)] + N[(SQLite - Auth Data)] + end + + A --> C + B --> C + C --> D + D --> E + E --> G + G --> H + H --> I + I --> J + J --> K + K --> L + K --> M + I --> N + + style C fill:#dd0031 + style H fill:#092e20 + style M fill:#47a248 + style L fill:#dc382d +
+
+ +
+

Authentication Flow

+
+sequenceDiagram + participant User + participant Angular + participant Django + participant SQLite + participant Redis + + User->>Angular: Enter Credentials + Angular->>Django: POST /api/auth/login/ + Django->>SQLite: Verify Credentials + + alt Valid Credentials + SQLite-->>Django: User Valid + Django->>Django: Generate JWT Token + Django->>Redis: Cache Token + Django-->>Angular: {token, user} + Angular->>Angular: Store in localStorage + Angular-->>User: Redirect to Dashboard + else Invalid Credentials + SQLite-->>Django: Invalid + Django-->>Angular: 401 Unauthorized + Angular-->>User: Show Error + end +
+
+ +
+

Database Schema

+
+erDiagram + USER ||--o{ COURSE : instructs + USER ||--o{ ENROLLMENT : enrolls + USER ||--o{ PROGRESS : tracks + USER ||--o{ NOTIFICATION : receives + + CATEGORY ||--o{ COURSE : categorizes + + COURSE ||--o{ LESSON : contains + COURSE ||--o{ ENROLLMENT : has + + LESSON ||--o{ QUIZ : includes + LESSON ||--o{ PROGRESS : tracks + + QUIZ ||--o{ QUESTION : contains + QUESTION ||--o{ CHOICE : has + + USER { + ObjectId id PK + string username UK + string email UK + boolean is_instructor + boolean is_student + string bio + string profile_picture + } + + COURSE { + ObjectId id PK + string title + string description + ObjectId instructor_id FK + ObjectId category_id FK + decimal price + boolean published + datetime created_at + } + + LESSON { + ObjectId id PK + string title + ObjectId course_id FK + string content + string video_url + datetime created_at + } + + ENROLLMENT { + ObjectId id PK + ObjectId student_id FK + ObjectId course_id FK + datetime enrolled_at + } + + PROGRESS { + ObjectId id PK + ObjectId student_id FK + ObjectId lesson_id FK + boolean completed + datetime completed_at + } +
+
+
+ + +
+
+

Technology Stack

+

Built with modern, production-ready technologies

+
+ +
+
+ + + + +
+
+ +
+
+
+

Angular 18

+

+ Modern TypeScript-based framework with reactive forms, routing, HTTP client, + and comprehensive component architecture. +

+
+
+

Bootstrap 5

+

+ Responsive CSS framework for mobile-first design with utility classes + and pre-built components. +

+
+
+

RxJS

+

+ Reactive programming library for handling asynchronous data streams + and event-based operations. +

+
+
+
+ +
+
+
+

Django 4.2

+

+ High-level Python web framework with ORM, authentication, admin interface, + and comprehensive security features. +

+
+
+

Django REST Framework

+

+ Powerful toolkit for building Web APIs with serializers, viewsets, + authentication, and browsable API interface. +

+
+
+

Python 3.12

+

+ Latest Python version with improved performance, better error messages, + and enhanced type hinting capabilities. +

+
+
+
+ +
+
+
+

MongoDB 5.0

+

+ NoSQL document database for storing courses, lessons, enrollments, + and user data with flexible schema design. +

+
+
+

Redis 6

+

+ In-memory data store for caching, session management, and real-time + data processing with sub-millisecond latency. +

+
+
+

SQLite 3

+

+ Lightweight database for Django authentication and user management + with zero-configuration setup. +

+
+
+
+ +
+
+
+

Docker

+

+ Container platform for packaging applications with all dependencies + in isolated, portable environments. +

+
+
+

Kubernetes

+

+ Container orchestration platform for automated deployment, scaling, + and management of containerized applications. +

+
+
+

Jenkins

+

+ Automation server for CI/CD pipelines with build, test, and deployment + automation capabilities. +

+
+
+

GitHub Actions

+

+ Native CI/CD workflows integrated with GitHub for automated testing + and deployment on every push. +

+
+
+

AWS

+

+ Cloud infrastructure with ECS Fargate, RDS, DocumentDB, ElastiCache, + and comprehensive monitoring services. +

+
+
+

Terraform

+

+ Infrastructure as Code tool for provisioning and managing cloud + resources with declarative configuration. +

+
+
+
+
+ + +
+
+

Deployment Options

+

Multiple deployment strategies for different needs and scales

+
+ +
+
+
🐳
+

Docker Compose

+

+ Best for: Local development and testing +

+ Quick setup with docker-compose.yml including frontend, backend, MongoDB, + and Redis services. Perfect for development environments. +

+ Setup time: 5-10 minutes +

+ + Learn More + +
+ +
+
☸️
+

Kubernetes

+

+ Best for: Self-hosted production environments +

+ Complete Kubernetes manifests for deploying to any K8s cluster with auto-scaling, + service discovery, and health monitoring. +

+ Setup time: 30-60 minutes +

+ + View Guide + +
+ +
+
☁️
+

AWS with Terraform

+

+ Best for: Enterprise production deployments +

+ Full Infrastructure as Code with Terraform modules for VPC, ECS, RDS, + ElastiCache, CloudFront, and comprehensive AWS services. +

+ Cost: ~$1,400/month +

+ + Deploy Now + +
+ +
+
β–²
+

Vercel + Render

+

+ Best for: Quick deployments and MVPs +

+ Simple managed deployment with Vercel for frontend and Render for backend. + Includes MongoDB Atlas and Redis Cloud. +

+ Cost: ~$25/month +

+ + Live Demo + +
+
+ +
+

Deployment Architecture (AWS)

+
+graph TB + subgraph "Cloud Infrastructure" + subgraph "Frontend Hosting - Vercel" + A[Vercel CDN] + B[Static Assets] + end + + subgraph "Backend Hosting - AWS ECS" + C[Django Application] + D[Auto-scaling] + end + + subgraph "Database Services" + E[(MongoDB Atlas)] + F[(Redis Cloud)] + end + end + + subgraph "CI/CD Pipeline" + G[GitHub] + H[GitHub Actions] + I[Docker Build] + end + + A --> B + C --> D + E --> F + + G --> H + H --> I + I --> C + + B --> C + D --> E + D --> F + + style A fill:#000000 + style C fill:#ff9900 + style E fill:#47a248 + style F fill:#dc382d +
+
+
+ + +
+
+

API Documentation

+

Comprehensive RESTful API with Swagger documentation

+
+ +
+
+
πŸ“–
+

Swagger UI

+

+ Interactive API documentation with try-it-out functionality. Test all endpoints + directly from your browser with authentication support. +

+ + Open Swagger UI + +
+ +
+
πŸ“„
+

ReDoc

+

+ Beautiful API documentation with detailed endpoint descriptions, request/response + examples, and schema definitions. +

+ + Open ReDoc + +
+ +
+
πŸ“‹
+

OpenAPI Spec

+

+ Download the complete OpenAPI 3.0 specification for code generation, + testing, and integration with your tools. +

+ + Download YAML + +
+
+ +

Key API Endpoints

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
EndpointMethodDescriptionAuth Required
/api/auth/login/POSTUser login with credentialsNo
/api/auth/registration/POSTRegister new user accountNo
/api/courses/GETList all available coursesYes
/api/courses/POSTCreate new courseYes (Instructor)
/api/courses/{id}/GETGet course detailsYes
/api/lessons/GETList all lessonsYes
/api/enrollments/POSTEnroll in a courseYes (Student)
/api/progress/GETGet learning progressYes
/api/quizzes/{id}/GETGet quiz questionsYes
/api/users/GETList all usersYes (Admin)
+
+
+ + +
+
+

Getting Started

+

Quick setup guide for local development

+
+ +
+
+
+
+

1. Clone Repository

+
+
+ bash + +
+
git clone https://github.com/hoangsonww/Learning-Management-System-Fullstack.git
+cd Learning-Management-System-Fullstack
+
+
+
+ +
+
+
+

2. Setup Backend (Django)

+
+
+ bash + +
+
cd LMS-Backend
+python -m venv .venv
+source .venv/bin/activate  # On Windows: .venv\Scripts\activate
+pip install -r requirements.txt
+python manage.py migrate
+python manage.py createsuperuser
+python manage.py runserver
+
+

Backend will run on http://localhost:8000

+
+
+ +
+
+
+

3. Setup Frontend (Angular)

+
+
+ bash + +
+
cd LMS-Frontend/app
+npm install
+ng serve
+
+

Frontend will run on http://localhost:4200

+
+
+ +
+
+
+

4. Configure Databases

+

Ensure MongoDB and Redis are running:

+
+
+ bash + +
+
# Using Docker
+docker run -d -p 27017:27017 --name mongodb mongo:5.0
+docker run -d -p 6379:6379 --name redis redis:6
+
+# Or use MongoDB Atlas and Redis Cloud for production
+
+
+
+ +
+
+
+

5. Access the Application

+
+
+

Frontend

+

http://localhost:4200

+
+
+

Backend API

+

http://localhost:8000

+
+
+

Admin Panel

+

http://localhost:8000/admin

+
+ +
+
+
+
+ +
+

Or use Docker Compose for instant setup:

+
+
+ bash + +
+
docker-compose up --build
+
+

All services will start automatically with proper networking

+
+
+ + +
+
+

Screenshots & UI

+

Preview of the application interface

+
+ +
+
+

Home Page

+ Home Page +
+
+

Course Management

+ Courses +
+
+

Lesson View

+ Lessons +
+
+

Progress Tracking

+ Progress +
+
+

API Documentation

+ Swagger +
+
+

User Management

+ Users +
+
+
+ + +
+
+

Documentation & Resources

+

Additional guides and references

+
+ +
+
+
πŸ“˜
+

Architecture Guide

+

+ Comprehensive architecture documentation with detailed diagrams, + component interactions, and design decisions. +

+ + Read Guide + +
+ +
+
πŸš€
+

Deployment Guide

+

+ Step-by-step deployment instructions for Docker, Kubernetes, AWS, + and managed services with cost estimates. +

+ + View Deployment + +
+ +
+
πŸ“
+

README

+

+ Complete project overview, installation instructions, features list, + and troubleshooting guide. +

+ + Read README + +
+ +
+
πŸ’»
+

GitHub Repository

+

+ Source code, issues, pull requests, releases, and contributions. + Star the repo and contribute! +

+ + View Repository + +
+
+
+ + + + + + + + + + + diff --git a/packages/script.js b/packages/script.js new file mode 100644 index 0000000..ad336f5 --- /dev/null +++ b/packages/script.js @@ -0,0 +1,556 @@ +/** + * Learning Management System Wiki + * Interactive Features & Animations + */ + +// =================================== +// Smooth Scroll & Navigation +// =================================== + +document.addEventListener('DOMContentLoaded', function() { + // Initialize all features + initNavigation(); + initScrollEffects(); + initProgressBar(); + initTabs(); + initCopyButtons(); + initMermaidDiagrams(); + initAnimations(); + initStats(); + initThemeToggle(); +}); + +// Navigation functionality +function initNavigation() { + const nav = document.querySelector('nav'); + const mobileMenuBtn = document.querySelector('.mobile-menu-btn'); + const navLinks = document.querySelector('.nav-links'); + + // Scroll effect on navigation + let lastScroll = 0; + window.addEventListener('scroll', () => { + const currentScroll = window.pageYOffset; + + if (currentScroll > 50) { + nav.classList.add('scrolled'); + } else { + nav.classList.remove('scrolled'); + } + + // Close mobile menu on scroll + if (navLinks && navLinks.classList.contains('active')) { + navLinks.classList.remove('active'); + if (mobileMenuBtn) { + mobileMenuBtn.innerHTML = '☰'; + } + } + + lastScroll = currentScroll; + }); + + // Mobile menu toggle + if (mobileMenuBtn && navLinks) { + mobileMenuBtn.addEventListener('click', (e) => { + e.stopPropagation(); + const isActive = navLinks.classList.toggle('active'); + mobileMenuBtn.innerHTML = isActive ? 'βœ•' : '☰'; + + // Prevent body scroll when menu is open + if (isActive) { + document.body.style.overflow = 'hidden'; + } else { + document.body.style.overflow = ''; + } + }); + } + + // Close mobile menu when clicking a link + const navLinkItems = document.querySelectorAll('.nav-links a'); + navLinkItems.forEach(link => { + link.addEventListener('click', () => { + if (navLinks) { + navLinks.classList.remove('active'); + document.body.style.overflow = ''; + } + if (mobileMenuBtn) { + mobileMenuBtn.innerHTML = '☰'; + } + }); + }); + + // Close mobile menu when clicking outside + document.addEventListener('click', (e) => { + if (navLinks && navLinks.classList.contains('active')) { + if (!nav.contains(e.target)) { + navLinks.classList.remove('active'); + document.body.style.overflow = ''; + if (mobileMenuBtn) { + mobileMenuBtn.innerHTML = '☰'; + } + } + } + }); + + // Highlight active section in navigation + const sections = document.querySelectorAll('section[id]'); + window.addEventListener('scroll', () => { + let current = ''; + sections.forEach(section => { + const sectionTop = section.offsetTop; + const sectionHeight = section.clientHeight; + if (pageYOffset >= sectionTop - 200) { + current = section.getAttribute('id'); + } + }); + + navLinkItems.forEach(link => { + link.classList.remove('active'); + if (link.getAttribute('href').includes(current)) { + link.classList.add('active'); + } + }); + }); +} + +// =================================== +// Scroll Progress Bar +// =================================== + +function initProgressBar() { + const progressBar = document.getElementById('progressBar'); + + window.addEventListener('scroll', () => { + const windowHeight = window.innerHeight; + const documentHeight = document.documentElement.scrollHeight; + const scrollTop = window.pageYOffset || document.documentElement.scrollTop; + + // Calculate scroll percentage + const scrollPercentage = (scrollTop / (documentHeight - windowHeight)) * 100; + + // Update progress bar width + progressBar.style.width = scrollPercentage + '%'; + }); +} + +// =================================== +// Scroll Effects +// =================================== + +function initScrollEffects() { + // Scroll to top button + const scrollTopBtn = document.querySelector('.scroll-top'); + + window.addEventListener('scroll', () => { + if (window.pageYOffset > 300) { + scrollTopBtn.classList.add('visible'); + } else { + scrollTopBtn.classList.remove('visible'); + } + }); + + scrollTopBtn.addEventListener('click', () => { + window.scrollTo({ + top: 0, + behavior: 'smooth' + }); + }); + + // Reveal animations on scroll + const observerOptions = { + threshold: 0.1, + rootMargin: '0px 0px -50px 0px' + }; + + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + entry.target.classList.add('animate-in'); + observer.unobserve(entry.target); + } + }); + }, observerOptions); + + document.querySelectorAll('.card, .stat-card, .timeline-item').forEach(el => { + observer.observe(el); + }); +} + +// =================================== +// Tabs Functionality +// =================================== + +function initTabs() { + const tabButtons = document.querySelectorAll('.tab-btn'); + + tabButtons.forEach(button => { + button.addEventListener('click', () => { + const tabGroup = button.closest('.tabs').getAttribute('data-tab-group') || 'default'; + const targetTab = button.getAttribute('data-tab'); + + // Remove active class from all buttons in this group + document.querySelectorAll(`[data-tab-group="${tabGroup}"] .tab-btn`).forEach(btn => { + btn.classList.remove('active'); + }); + + // Add active class to clicked button + button.classList.add('active'); + + // Hide all tab contents in this group + document.querySelectorAll(`[data-tab-group="${tabGroup}"] .tab-content`).forEach(content => { + content.classList.remove('active'); + }); + + // Show target tab content + const targetContent = document.querySelector(`[data-tab="${targetTab}"][data-tab-group="${tabGroup}"].tab-content`); + if (targetContent) { + targetContent.classList.add('active'); + } + }); + }); +} + +// =================================== +// Copy Code Functionality +// =================================== + +function initCopyButtons() { + document.querySelectorAll('.copy-btn').forEach(button => { + button.addEventListener('click', async () => { + const codeBlock = button.closest('.code-block'); + const code = codeBlock.querySelector('code').textContent; + + try { + await navigator.clipboard.writeText(code); + const originalText = button.textContent; + button.textContent = 'βœ“ Copied!'; + button.style.background = '#10b981'; + + setTimeout(() => { + button.textContent = originalText; + button.style.background = ''; + }, 2000); + } catch (err) { + console.error('Failed to copy code:', err); + button.textContent = 'βœ— Error'; + button.style.background = '#ef4444'; + + setTimeout(() => { + button.textContent = 'Copy'; + button.style.background = ''; + }, 2000); + } + }); + }); +} + +// =================================== +// Mermaid Diagrams +// =================================== + +function initMermaidDiagrams() { + if (typeof mermaid !== 'undefined') { + mermaid.initialize({ + startOnLoad: true, + theme: 'dark', + themeVariables: { + darkMode: true, + background: '#1e293b', + primaryColor: '#2563eb', + primaryTextColor: '#f1f5f9', + primaryBorderColor: '#334155', + lineColor: '#64748b', + secondaryColor: '#7c3aed', + tertiaryColor: '#06b6d4', + fontFamily: 'Inter, sans-serif' + }, + flowchart: { + curve: 'basis', + padding: 20 + }, + sequence: { + actorMargin: 50, + width: 150, + height: 65, + boxMargin: 10 + } + }); + } +} + +// =================================== +// Animated Statistics +// =================================== + +function initStats() { + const stats = document.querySelectorAll('.stat-number'); + + const animateValue = (element, start, end, duration) => { + const startTime = performance.now(); + const step = (currentTime) => { + const elapsed = currentTime - startTime; + const progress = Math.min(elapsed / duration, 1); + + // Easing function + const easeOutQuad = progress * (2 - progress); + const value = Math.floor(start + (end - start) * easeOutQuad); + + element.textContent = value + (element.getAttribute('data-suffix') || ''); + + if (progress < 1) { + requestAnimationFrame(step); + } + }; + + requestAnimationFrame(step); + }; + + const observerOptions = { + threshold: 0.5 + }; + + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + const stat = entry.target; + const endValue = parseInt(stat.getAttribute('data-value') || stat.textContent); + animateValue(stat, 0, endValue, 2000); + observer.unobserve(stat); + } + }); + }, observerOptions); + + stats.forEach(stat => { + const value = stat.textContent.replace(/\D/g, ''); + stat.setAttribute('data-value', value); + stat.textContent = '0' + stat.textContent.replace(/\d/g, ''); + observer.observe(stat); + }); +} + +// =================================== +// Animation Classes +// =================================== + +function initAnimations() { + // Add stagger animation delays + document.querySelectorAll('.cards-grid .card').forEach((card, index) => { + card.style.animationDelay = `${index * 0.1}s`; + }); + + // Parallax effect on hero + const hero = document.querySelector('.hero'); + if (hero) { + window.addEventListener('scroll', () => { + const scrolled = window.pageYOffset; + hero.style.transform = `translateY(${scrolled * 0.5}px)`; + hero.style.opacity = 1 - (scrolled / 600); + }); + } +} + +// =================================== +// Theme Toggle (Optional) +// =================================== + +function initThemeToggle() { + const themeToggle = document.getElementById('theme-toggle'); + if (!themeToggle) return; + + const currentTheme = localStorage.getItem('theme') || 'dark'; + document.documentElement.setAttribute('data-theme', currentTheme); + + themeToggle.addEventListener('click', () => { + const theme = document.documentElement.getAttribute('data-theme'); + const newTheme = theme === 'dark' ? 'light' : 'dark'; + document.documentElement.setAttribute('data-theme', newTheme); + localStorage.setItem('theme', newTheme); + }); +} + +// =================================== +// Search Functionality +// =================================== + +function initSearch() { + const searchInput = document.getElementById('search'); + if (!searchInput) return; + + const searchableElements = document.querySelectorAll('section, .card, h2, h3, p'); + + searchInput.addEventListener('input', (e) => { + const searchTerm = e.target.value.toLowerCase(); + + searchableElements.forEach(element => { + const text = element.textContent.toLowerCase(); + const parent = element.closest('.card, section'); + + if (text.includes(searchTerm) || searchTerm === '') { + if (parent) parent.style.display = ''; + element.style.display = ''; + } else { + if (parent) parent.style.display = 'none'; + } + }); + }); +} + +// =================================== +// Modal Functionality +// =================================== + +function openModal(modalId) { + const modal = document.getElementById(modalId); + if (modal) { + modal.style.display = 'flex'; + document.body.style.overflow = 'hidden'; + } +} + +function closeModal(modalId) { + const modal = document.getElementById(modalId); + if (modal) { + modal.style.display = 'none'; + document.body.style.overflow = ''; + } +} + +// Close modal when clicking outside +window.addEventListener('click', (e) => { + if (e.target.classList.contains('modal')) { + e.target.style.display = 'none'; + document.body.style.overflow = ''; + } +}); + +// =================================== +// Tooltip Functionality +// =================================== + +function initTooltips() { + const tooltips = document.querySelectorAll('[data-tooltip]'); + + tooltips.forEach(element => { + element.addEventListener('mouseenter', (e) => { + const tooltip = document.createElement('div'); + tooltip.className = 'tooltip'; + tooltip.textContent = element.getAttribute('data-tooltip'); + document.body.appendChild(tooltip); + + const rect = element.getBoundingClientRect(); + tooltip.style.left = `${rect.left + rect.width / 2}px`; + tooltip.style.top = `${rect.top - 40}px`; + tooltip.style.transform = 'translateX(-50%)'; + }); + + element.addEventListener('mouseleave', () => { + const tooltip = document.querySelector('.tooltip'); + if (tooltip) tooltip.remove(); + }); + }); +} + +// =================================== +// Loading State +// =================================== + +function showLoading(element) { + element.innerHTML = ''; + element.disabled = true; +} + +function hideLoading(element, originalContent) { + element.innerHTML = originalContent; + element.disabled = false; +} + +// =================================== +// API Request Helper +// =================================== + +async function fetchData(url) { + try { + const response = await fetch(url); + if (!response.ok) throw new Error('Network response was not ok'); + return await response.json(); + } catch (error) { + console.error('Fetch error:', error); + return null; + } +} + +// =================================== +// Utility Functions +// =================================== + +function debounce(func, wait) { + let timeout; + return function executedFunction(...args) { + const later = () => { + clearTimeout(timeout); + func(...args); + }; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; +} + +function throttle(func, limit) { + let inThrottle; + return function(...args) { + if (!inThrottle) { + func.apply(this, args); + inThrottle = true; + setTimeout(() => inThrottle = false, limit); + } + }; +} + +// =================================== +// Export functions for external use +// =================================== + +window.LMSWiki = { + openModal, + closeModal, + showLoading, + hideLoading, + fetchData, + debounce, + throttle +}; + +// =================================== +// Easter Egg +// =================================== + +let konamiCode = []; +const konamiSequence = ['ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowLeft', 'ArrowRight', 'b', 'a']; + +document.addEventListener('keydown', (e) => { + konamiCode.push(e.key); + konamiCode = konamiCode.slice(-10); + + if (konamiCode.join('') === konamiSequence.join('')) { + document.body.style.animation = 'rainbow 2s linear infinite'; + setTimeout(() => { + document.body.style.animation = ''; + }, 5000); + } +}); + +// =================================== +// Performance Monitoring +// =================================== + +if (window.performance && window.performance.timing) { + window.addEventListener('load', () => { + const perfData = window.performance.timing; + const pageLoadTime = perfData.loadEventEnd - perfData.navigationStart; + console.log(`⚑ Page loaded in ${pageLoadTime}ms`); + }); +} + +// Console welcome message +console.log('%cπŸš€ Learning Management System Wiki', 'font-size: 20px; font-weight: bold; color: #2563eb;'); +console.log('%cBuilt with ❀️ using Angular, Django, MongoDB & Redis', 'font-size: 14px; color: #7c3aed;'); +console.log('%cGitHub: https://github.com/hoangsonww/Learning-Management-System-Fullstack', 'font-size: 12px; color: #06b6d4;'); diff --git a/packages/styles.css b/packages/styles.css new file mode 100644 index 0000000..fb499e9 --- /dev/null +++ b/packages/styles.css @@ -0,0 +1,1165 @@ +/* =================================== + Learning Management System Wiki + Professional Styling + =================================== */ + +:root { + /* Color Palette */ + --primary-color: #2563eb; + --secondary-color: #7c3aed; + --accent-color: #06b6d4; + --success-color: #10b981; + --warning-color: #f59e0b; + --danger-color: #ef4444; + + /* Tech Stack Colors */ + --angular-red: #dd0031; + --django-green: #092e20; + --mongodb-green: #47a248; + --redis-red: #dc382d; + --docker-blue: #2496ed; + --k8s-blue: #326ce5; + --aws-orange: #ff9900; + + /* Neutral Colors */ + --dark-bg: #0f172a; + --darker-bg: #020617; + --card-bg: #1e293b; + --text-primary: #f1f5f9; + --text-secondary: #cbd5e1; + --text-muted: #94a3b8; + --border-color: #334155; + + /* Spacing */ + --spacing-xs: 0.5rem; + --spacing-sm: 1rem; + --spacing-md: 1.5rem; + --spacing-lg: 2rem; + --spacing-xl: 3rem; + --spacing-2xl: 4rem; + + /* Typography */ + --font-primary: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + --font-code: 'Fira Code', 'Monaco', 'Courier New', monospace; + + /* Border Radius */ + --radius-sm: 0.375rem; + --radius-md: 0.5rem; + --radius-lg: 0.75rem; + --radius-xl: 1rem; + + /* Shadows */ + --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1); + --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1); + --shadow-glow: 0 0 20px rgba(37, 99, 235, 0.3); +} + +/* =================================== + Base Styles + =================================== */ + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; +} + +body { + font-family: var(--font-primary); + background: linear-gradient(135deg, var(--darker-bg) 0%, var(--dark-bg) 100%); + color: var(--text-primary); + line-height: 1.6; + overflow-x: hidden; +} + +/* Override default link colors to white */ +a { + color: var(--text-primary); + text-decoration: none; +} + +a:hover { + color: var(--primary-color); +} + +a:visited { + color: var(--text-primary); +} + +/* Links in text content */ +p a, +li a:not(.nav-links a):not(.footer-links a):not(.card-link):not(.btn) { + color: var(--text-primary); + text-decoration: underline; + text-decoration-color: var(--primary-color); + text-underline-offset: 3px; + transition: all 0.3s ease; +} + +p a:hover, +li a:not(.nav-links a):not(.footer-links a):not(.card-link):not(.btn):hover { + color: var(--primary-color); + text-decoration-color: var(--secondary-color); +} + +/* Code links */ +code a { + color: var(--accent-color); + text-decoration: underline; +} + +/* =================================== + Navigation + =================================== */ + +nav { + position: fixed; + top: 0; + left: 0; + right: 0; + background: rgba(15, 23, 42, 0.95); + backdrop-filter: blur(10px); + border-bottom: 1px solid var(--border-color); + padding: var(--spacing-sm) 0; + z-index: 1000; + transition: all 0.3s ease; +} + +nav.scrolled { + box-shadow: var(--shadow-lg); +} + +/* Scroll Progress Bar */ +.progress-container { + position: fixed; + top: 70px; + left: 0; + width: 100%; + height: 4px; + background: rgba(51, 65, 85, 0.3); + z-index: 999; + overflow: hidden; +} + +.progress-bar { + height: 100%; + background: linear-gradient(90deg, var(--primary-color), var(--secondary-color), var(--accent-color)); + background-size: 200% 100%; + width: 0%; + transition: width 0.1s ease; + animation: gradient-shift 3s ease infinite; + box-shadow: 0 0 10px rgba(37, 99, 235, 0.5); +} + +@keyframes gradient-shift { + 0% { background-position: 0% 50%; } + 50% { background-position: 100% 50%; } + 100% { background-position: 0% 50%; } +} + +.nav-container { + max-width: 1400px; + margin: 0 auto; + padding: 0 var(--spacing-md); + display: flex; + justify-content: space-between; + align-items: center; +} + +.logo { + display: flex; + align-items: center; + gap: var(--spacing-sm); + font-size: 1.5rem; + font-weight: 700; + color: var(--text-primary); + text-decoration: none; + transition: transform 0.3s ease; +} + +.logo:hover { + transform: scale(1.05); +} + +.logo-icon { + width: 40px; + height: 40px; + background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); + border-radius: var(--radius-md); + display: flex; + align-items: center; + justify-content: center; + font-size: 1.5rem; +} + +.nav-links { + display: flex; + gap: var(--spacing-md); + list-style: none; +} + +.nav-links a { + color: var(--text-secondary); + text-decoration: none; + padding: var(--spacing-xs) var(--spacing-sm); + border-radius: var(--radius-sm); + transition: all 0.3s ease; + font-weight: 500; +} + +.nav-links a:hover { + color: var(--text-primary); + background: var(--card-bg); +} + +.nav-links a.active { + color: var(--primary-color); + background: rgba(37, 99, 235, 0.1); +} + +/* Mobile Menu */ +.mobile-menu-btn { + display: none; + background: none; + border: none; + color: var(--text-primary); + font-size: 1.75rem; + cursor: pointer; + padding: var(--spacing-sm); + z-index: 1001; + transition: transform 0.3s ease; +} + +.mobile-menu-btn:hover { + color: var(--primary-color); + transform: scale(1.1); +} + +.mobile-menu-btn:active { + transform: scale(0.95); +} + +/* =================================== + Hero Section + =================================== */ + +.hero { + padding: calc(84px + var(--spacing-2xl)) var(--spacing-md) var(--spacing-2xl); + text-align: center; + position: relative; + overflow: hidden; +} + +.hero::before { + content: ''; + position: absolute; + top: -50%; + left: -50%; + width: 200%; + height: 200%; + background: radial-gradient(circle, rgba(37, 99, 235, 0.1) 0%, transparent 70%); + animation: pulse 15s ease-in-out infinite; +} + +@keyframes pulse { + 0%, 100% { transform: scale(1); opacity: 1; } + 50% { transform: scale(1.1); opacity: 0.8; } +} + +.hero-content { + max-width: 1200px; + margin: 0 auto; + position: relative; + z-index: 1; +} + +.hero-logo { + width: 120px; + height: 120px; + margin: 0 auto var(--spacing-md); + animation: float 3s ease-in-out infinite; +} + +@keyframes float { + 0%, 100% { transform: translateY(0px); } + 50% { transform: translateY(-10px); } +} + +h1 { + font-size: clamp(2rem, 5vw, 3.5rem); + font-weight: 800; + margin-bottom: var(--spacing-md); + background: linear-gradient(135deg, var(--text-primary), var(--primary-color)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.hero-subtitle { + font-size: clamp(1rem, 2vw, 1.5rem); + color: var(--text-secondary); + margin-bottom: var(--spacing-lg); + max-width: 800px; + margin-left: auto; + margin-right: auto; +} + +.tech-badges { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: var(--spacing-sm); + margin-top: var(--spacing-lg); +} + +.badge { + display: inline-flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 1rem; + border-radius: 9999px; + font-weight: 600; + font-size: 0.875rem; + transition: all 0.3s ease; + border: 2px solid transparent; +} + +.badge:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-lg); +} + +.badge-angular { background: var(--angular-red); color: white; } +.badge-django { background: var(--django-green); color: white; } +.badge-mongodb { background: var(--mongodb-green); color: white; } +.badge-redis { background: var(--redis-red); color: white; } +.badge-docker { background: var(--docker-blue); color: white; } +.badge-k8s { background: var(--k8s-blue); color: white; } +.badge-aws { background: var(--aws-orange); color: white; } + +.cta-buttons { + display: flex; + gap: var(--spacing-md); + justify-content: center; + margin-top: var(--spacing-xl); + flex-wrap: wrap; +} + +.btn { + padding: 0.875rem 2rem; + border-radius: var(--radius-md); + font-weight: 600; + text-decoration: none; + transition: all 0.3s ease; + display: inline-flex; + align-items: center; + gap: 0.5rem; + cursor: pointer; + border: 2px solid transparent; + font-size: 1rem; +} + +.btn-primary { + background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); + color: white; +} + +.btn-primary:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-glow); +} + +.btn-secondary { + background: transparent; + color: var(--text-primary); + border-color: var(--border-color); +} + +.btn-secondary:hover { + background: var(--card-bg); + border-color: var(--primary-color); +} + +/* =================================== + Sections + =================================== */ + +section { + padding: var(--spacing-2xl) var(--spacing-md); + max-width: 1400px; + margin: 0 auto; +} + +.section-header { + text-align: center; + margin-bottom: var(--spacing-xl); +} + +.section-title { + font-size: clamp(2rem, 4vw, 2.5rem); + font-weight: 700; + margin-bottom: var(--spacing-sm); + color: var(--text-primary); +} + +.section-subtitle { + font-size: 1.125rem; + color: var(--text-secondary); + max-width: 700px; + margin: 0 auto; +} + +/* =================================== + Cards + =================================== */ + +.cards-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr)); + gap: var(--spacing-lg); + margin-top: var(--spacing-xl); +} + +.card { + background: var(--card-bg); + border: 1px solid var(--border-color); + border-radius: var(--radius-lg); + padding: var(--spacing-lg); + transition: all 0.3s ease; + position: relative; + overflow: hidden; + max-width: 100%; + box-sizing: border-box; +} + +.card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 3px; + background: linear-gradient(90deg, var(--primary-color), var(--secondary-color)); + transform: scaleX(0); + transition: transform 0.3s ease; +} + +.card:hover { + transform: translateY(-5px); + box-shadow: var(--shadow-xl); + border-color: var(--primary-color); +} + +.card:hover::before { + transform: scaleX(1); +} + +.card-icon { + width: 60px; + height: 60px; + border-radius: var(--radius-md); + display: flex; + align-items: center; + justify-content: center; + font-size: 2rem; + margin-bottom: var(--spacing-md); + background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); +} + +.card-title { + font-size: 1.5rem; + font-weight: 600; + margin-bottom: var(--spacing-sm); + color: var(--text-primary); +} + +.card-description { + color: var(--text-secondary); + line-height: 1.6; +} + +.card-link { + display: inline-flex; + align-items: center; + gap: 0.5rem; + color: var(--primary-color); + text-decoration: none; + margin-top: var(--spacing-md); + font-weight: 600; + transition: all 0.3s ease; +} + +.card-link:hover { + gap: 0.75rem; + color: var(--secondary-color); +} + +/* =================================== + Architecture Diagram + =================================== */ + +.diagram-container { + background: var(--card-bg); + border: 1px solid var(--border-color); + border-radius: var(--radius-lg); + padding: var(--spacing-lg); + margin: var(--spacing-xl) 0; + overflow-x: auto; +} + +.diagram-container .mermaid { + display: flex; + justify-content: center; + min-height: 400px; +} + +/* =================================== + Timeline + =================================== */ + +.timeline { + position: relative; + padding: var(--spacing-lg) 0; +} + +.timeline::before { + content: ''; + position: absolute; + left: 50%; + top: 0; + bottom: 0; + width: 2px; + background: var(--border-color); +} + +.timeline-item { + position: relative; + margin-bottom: var(--spacing-xl); + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--spacing-lg); +} + +.timeline-content { + background: var(--card-bg); + padding: var(--spacing-lg); + border-radius: var(--radius-lg); + border: 1px solid var(--border-color); + position: relative; + overflow: hidden; + max-width: 100%; +} + +.timeline-item:nth-child(even) .timeline-content { + grid-column: 2; +} + +.timeline-marker { + position: absolute; + left: 50%; + transform: translateX(-50%); + width: 20px; + height: 20px; + border-radius: 50%; + background: var(--primary-color); + border: 4px solid var(--dark-bg); + z-index: 1; +} + +/* =================================== + Stats Section + =================================== */ + +.stats-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr)); + gap: var(--spacing-lg); + margin-top: var(--spacing-xl); +} + +.stat-card { + text-align: center; + padding: var(--spacing-lg); + background: var(--card-bg); + border: 1px solid var(--border-color); + border-radius: var(--radius-lg); + transition: all 0.3s ease; +} + +.stat-card:hover { + transform: translateY(-5px); + border-color: var(--primary-color); +} + +.stat-number { + font-size: 3rem; + font-weight: 800; + background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + display: block; +} + +.stat-label { + color: var(--text-secondary); + font-weight: 600; + margin-top: var(--spacing-sm); +} + +/* =================================== + Code Blocks + =================================== */ + +.code-block { + background: #1a1b26; + border: 1px solid var(--border-color); + border-radius: var(--radius-lg); + padding: 0; + margin: var(--spacing-md) 0; + position: relative; + width: 100%; + max-width: 100%; + box-sizing: border-box; + overflow: hidden; +} + +.code-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: var(--spacing-sm) var(--spacing-md); + border-bottom: 1px solid var(--border-color); + background: rgba(0, 0, 0, 0.3); + position: sticky; + top: 0; + z-index: 10; +} + +.code-language { + color: var(--text-secondary); + font-size: 0.875rem; + font-weight: 600; +} + +.copy-btn { + background: var(--primary-color); + color: white; + border: none; + padding: 0.5rem 1rem; + border-radius: var(--radius-sm); + cursor: pointer; + font-size: 0.875rem; + transition: all 0.3s ease; + flex-shrink: 0; +} + +.copy-btn:hover { + background: var(--secondary-color); +} + +pre { + margin: 0; + padding: var(--spacing-md); + font-family: var(--font-code); + font-size: 0.875rem; + line-height: 1.5; + white-space: pre; + overflow-x: auto; + overflow-y: hidden; + max-width: 100%; + box-sizing: border-box; + -webkit-overflow-scrolling: touch; +} + +code { + font-family: var(--font-code); + color: var(--accent-color); + white-space: pre; +} + +/* =================================== + Tables + =================================== */ + +.table-container { + overflow-x: auto; + border-radius: var(--radius-lg); + border: 1px solid var(--border-color); + margin: var(--spacing-lg) 0; +} + +table { + width: 100%; + border-collapse: collapse; + background: var(--card-bg); +} + +thead { + background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); +} + +th { + padding: var(--spacing-md); + text-align: left; + font-weight: 600; + color: white; +} + +td { + padding: var(--spacing-md); + border-bottom: 1px solid var(--border-color); + color: var(--text-secondary); +} + +tr:hover td { + background: rgba(37, 99, 235, 0.05); +} + +/* =================================== + Tabs + =================================== */ + +.tabs { + border-bottom: 2px solid var(--border-color); + margin-bottom: var(--spacing-lg); +} + +.tab-buttons { + display: flex; + gap: var(--spacing-sm); + flex-wrap: wrap; +} + +.tab-btn { + padding: var(--spacing-sm) var(--spacing-md); + background: none; + border: none; + color: var(--text-secondary); + font-weight: 600; + cursor: pointer; + border-bottom: 3px solid transparent; + transition: all 0.3s ease; +} + +.tab-btn:hover { + color: var(--text-primary); +} + +.tab-btn.active { + color: var(--primary-color); + border-bottom-color: var(--primary-color); +} + +.tab-content { + display: none; + animation: fadeIn 0.3s ease; +} + +.tab-content.active { + display: block; +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(10px); } + to { opacity: 1; transform: translateY(0); } +} + +/* =================================== + Footer + =================================== */ + +footer { + background: var(--card-bg); + border-top: 1px solid var(--border-color); + padding: var(--spacing-2xl) var(--spacing-md) var(--spacing-lg); + margin-top: var(--spacing-2xl); +} + +.footer-content { + max-width: 1400px; + margin: 0 auto; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(min(100%, 250px), 1fr)); + gap: var(--spacing-xl); +} + +.footer-section h3 { + font-size: 1.25rem; + margin-bottom: var(--spacing-md); + color: var(--text-primary); +} + +.footer-links { + list-style: none; + display: flex; + flex-direction: column; + gap: var(--spacing-sm); +} + +.footer-links a { + color: var(--text-secondary); + text-decoration: none; + transition: color 0.3s ease; +} + +.footer-links a:hover { + color: var(--primary-color); +} + +.social-links { + display: flex; + gap: var(--spacing-md); + margin-top: var(--spacing-md); +} + +.social-link { + width: 40px; + height: 40px; + border-radius: 50%; + background: var(--dark-bg); + display: flex; + align-items: center; + justify-content: center; + color: var(--text-primary); + text-decoration: none; + transition: all 0.3s ease; +} + +.social-link:hover { + background: var(--primary-color); + color: white; + transform: translateY(-3px); +} + +.footer-bottom { + max-width: 1400px; + margin: var(--spacing-xl) auto 0; + padding-top: var(--spacing-lg); + border-top: 1px solid var(--border-color); + text-align: center; + color: var(--text-muted); +} + +/* =================================== + Scroll to Top Button + =================================== */ + +.scroll-top { + position: fixed; + bottom: 2rem; + right: 2rem; + width: 50px; + height: 50px; + border-radius: 50%; + background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); + color: white; + border: none; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.5rem; + box-shadow: var(--shadow-xl); + opacity: 0; + visibility: hidden; + transition: all 0.3s ease; + z-index: 999; +} + +.scroll-top.visible { + opacity: 1; + visibility: visible; +} + +.scroll-top:hover { + transform: translateY(-5px); + box-shadow: var(--shadow-glow); +} + +/* =================================== + Loading Animation + =================================== */ + +.loading { + display: inline-block; + width: 20px; + height: 20px; + border: 3px solid var(--border-color); + border-radius: 50%; + border-top-color: var(--primary-color); + animation: spin 1s ease-in-out infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +/* =================================== + Responsive Design + =================================== */ + +@media (max-width: 968px) { + body { + overflow-x: hidden; + } + + section { + padding: var(--spacing-xl) var(--spacing-sm); + } + + .card { + padding: var(--spacing-md); + } + + .cards-grid { + gap: var(--spacing-md); + } + + .diagram-container { + padding: var(--spacing-md); + overflow-x: auto; + } + + .table-container { + font-size: 0.875rem; + } + + td, th { + padding: var(--spacing-sm); + } + + .nav-links { + position: fixed; + top: 74px; + left: 0; + right: 0; + background: rgba(15, 23, 42, 0.98); + backdrop-filter: blur(10px); + flex-direction: column; + padding: var(--spacing-lg) var(--spacing-md); + border-bottom: 1px solid var(--border-color); + box-shadow: var(--shadow-xl); + transform: translateX(100%); + opacity: 0; + visibility: hidden; + transition: all 0.4s ease; + max-height: calc(100vh - 74px); + overflow-y: auto; + } + + .nav-links.active { + transform: translateX(0); + opacity: 1; + visibility: visible; + } + + .nav-links li { + margin: var(--spacing-xs) 0; + opacity: 0; + transform: translateX(20px); + transition: all 0.3s ease; + } + + .nav-links.active li { + opacity: 1; + transform: translateX(0); + } + + .nav-links.active li:nth-child(1) { transition-delay: 0.1s; } + .nav-links.active li:nth-child(2) { transition-delay: 0.15s; } + .nav-links.active li:nth-child(3) { transition-delay: 0.2s; } + .nav-links.active li:nth-child(4) { transition-delay: 0.25s; } + .nav-links.active li:nth-child(5) { transition-delay: 0.3s; } + .nav-links.active li:nth-child(6) { transition-delay: 0.35s; } + .nav-links.active li:nth-child(7) { transition-delay: 0.4s; } + + .nav-links a { + display: block; + padding: var(--spacing-md); + border-radius: var(--radius-md); + font-size: 1.1rem; + text-align: center; + } + + .mobile-menu-btn { + display: block; + } + + .logo { + font-size: 1.25rem; + } + + .logo-icon { + width: 35px; + height: 35px; + font-size: 1.25rem; + } + + .timeline::before { + left: 0; + } + + .timeline-item { + grid-template-columns: 1fr; + } + + .timeline-item:nth-child(even) .timeline-content { + grid-column: 1; + } + + .timeline-marker { + left: 0; + transform: translateX(-50%); + } + + .cta-buttons { + flex-direction: column; + } + + .btn { + width: 100%; + justify-content: center; + } + + .cards-grid { + grid-template-columns: 1fr; + } + + .stats-grid { + grid-template-columns: repeat(2, 1fr); + } + + .hero { + padding: calc(84px + var(--spacing-xl)) var(--spacing-md) var(--spacing-xl); + } + + h1 { + font-size: 2rem; + } + + .section-title { + font-size: 1.75rem; + } +} + +@media (max-width: 480px) { + .stats-grid { + grid-template-columns: 1fr; + } + + .tech-badges { + gap: var(--spacing-xs); + } + + .badge { + font-size: 0.75rem; + padding: 0.4rem 0.8rem; + } + + .nav-container { + padding: 0 var(--spacing-sm); + } + + section { + padding: var(--spacing-lg) var(--spacing-sm); + } + + .card { + padding: var(--spacing-sm); + } + + .card-icon { + width: 50px; + height: 50px; + font-size: 1.5rem; + } + + .card-title { + font-size: 1.25rem; + } + + .diagram-container { + padding: var(--spacing-sm); + } + + .code-block { + padding: var(--spacing-sm); + font-size: 0.8rem; + } + + pre { + font-size: 0.75rem; + } + + code { + font-size: 0.75rem; + } + + .footer-content { + grid-template-columns: 1fr; + gap: var(--spacing-lg); + } + + .scroll-top { + bottom: 1rem; + right: 1rem; + width: 45px; + height: 45px; + font-size: 1.25rem; + } +} + +/* =================================== + Utility Classes + =================================== */ + +.text-center { text-align: center; } +.text-left { text-align: left; } +.text-right { text-align: right; } + +.mt-sm { margin-top: var(--spacing-sm); } +.mt-md { margin-top: var(--spacing-md); } +.mt-lg { margin-top: var(--spacing-lg); } +.mt-xl { margin-top: var(--spacing-xl); } + +.mb-sm { margin-bottom: var(--spacing-sm); } +.mb-md { margin-bottom: var(--spacing-md); } +.mb-lg { margin-bottom: var(--spacing-lg); } +.mb-xl { margin-bottom: var(--spacing-xl); } + +.text-primary { color: var(--primary-color); } +.text-secondary { color: var(--text-secondary); } +.text-muted { color: var(--text-muted); } + +.d-flex { display: flex; } +.flex-column { flex-direction: column; } +.align-center { align-items: center; } +.justify-center { justify-content: center; } +.gap-sm { gap: var(--spacing-sm); } +.gap-md { gap: var(--spacing-md); } + +/* =================================== + Print Styles + =================================== */ + +@media print { + nav, .scroll-top, .cta-buttons { + display: none; + } + + body { + background: white; + color: black; + } + + .card, .code-block { + break-inside: avoid; + } +}