diff --git a/.prettierignore b/.prettierignore index d4d183d..ecf4ec9 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,3 +2,4 @@ LMS-Backend/.venv LMS-Frontend/app/.angular LMS-Frontend/app/node_modules LMS-Frontend/app/dist +*.md diff --git a/LICENSE b/LICENSE index a0ef1b3..83dd0a3 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Son Nguyen +Copyright (c) 2025 Son Nguyen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/script.js b/packages/script.js index ad336f5..fca21ce 100644 --- a/packages/script.js +++ b/packages/script.js @@ -7,7 +7,7 @@ // Smooth Scroll & Navigation // =================================== -document.addEventListener('DOMContentLoaded', function() { +document.addEventListener("DOMContentLoaded", function () { // Initialize all features initNavigation(); initScrollEffects(); @@ -22,91 +22,91 @@ document.addEventListener('DOMContentLoaded', function() { // Navigation functionality function initNavigation() { - const nav = document.querySelector('nav'); - const mobileMenuBtn = document.querySelector('.mobile-menu-btn'); - const navLinks = document.querySelector('.nav-links'); - + 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', () => { + window.addEventListener("scroll", () => { const currentScroll = window.pageYOffset; - + if (currentScroll > 50) { - nav.classList.add('scrolled'); + nav.classList.add("scrolled"); } else { - nav.classList.remove('scrolled'); + nav.classList.remove("scrolled"); } - + // Close mobile menu on scroll - if (navLinks && navLinks.classList.contains('active')) { - navLinks.classList.remove('active'); + if (navLinks && navLinks.classList.contains("active")) { + navLinks.classList.remove("active"); if (mobileMenuBtn) { - mobileMenuBtn.innerHTML = '☰'; + mobileMenuBtn.innerHTML = "☰"; } } - + lastScroll = currentScroll; }); - + // Mobile menu toggle if (mobileMenuBtn && navLinks) { - mobileMenuBtn.addEventListener('click', (e) => { + mobileMenuBtn.addEventListener("click", (e) => { e.stopPropagation(); - const isActive = navLinks.classList.toggle('active'); - mobileMenuBtn.innerHTML = isActive ? '✕' : '☰'; - + const isActive = navLinks.classList.toggle("active"); + mobileMenuBtn.innerHTML = isActive ? "✕" : "☰"; + // Prevent body scroll when menu is open if (isActive) { - document.body.style.overflow = 'hidden'; + document.body.style.overflow = "hidden"; } else { - document.body.style.overflow = ''; + document.body.style.overflow = ""; } }); } - + // Close mobile menu when clicking a link - const navLinkItems = document.querySelectorAll('.nav-links a'); - navLinkItems.forEach(link => { - link.addEventListener('click', () => { + const navLinkItems = document.querySelectorAll(".nav-links a"); + navLinkItems.forEach((link) => { + link.addEventListener("click", () => { if (navLinks) { - navLinks.classList.remove('active'); - document.body.style.overflow = ''; + navLinks.classList.remove("active"); + document.body.style.overflow = ""; } if (mobileMenuBtn) { - mobileMenuBtn.innerHTML = '☰'; + mobileMenuBtn.innerHTML = "☰"; } }); }); - + // Close mobile menu when clicking outside - document.addEventListener('click', (e) => { - if (navLinks && navLinks.classList.contains('active')) { + document.addEventListener("click", (e) => { + if (navLinks && navLinks.classList.contains("active")) { if (!nav.contains(e.target)) { - navLinks.classList.remove('active'); - document.body.style.overflow = ''; + navLinks.classList.remove("active"); + document.body.style.overflow = ""; if (mobileMenuBtn) { - mobileMenuBtn.innerHTML = '☰'; + mobileMenuBtn.innerHTML = "☰"; } } } }); - + // Highlight active section in navigation - const sections = document.querySelectorAll('section[id]'); - window.addEventListener('scroll', () => { - let current = ''; - sections.forEach(section => { + 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'); + current = section.getAttribute("id"); } }); - - navLinkItems.forEach(link => { - link.classList.remove('active'); - if (link.getAttribute('href').includes(current)) { - link.classList.add('active'); + + navLinkItems.forEach((link) => { + link.classList.remove("active"); + if (link.getAttribute("href").includes(current)) { + link.classList.add("active"); } }); }); @@ -117,18 +117,19 @@ function initNavigation() { // =================================== function initProgressBar() { - const progressBar = document.getElementById('progressBar'); - - window.addEventListener('scroll', () => { + 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; - + const scrollPercentage = + (scrollTop / (documentHeight - windowHeight)) * 100; + // Update progress bar width - progressBar.style.width = scrollPercentage + '%'; + progressBar.style.width = scrollPercentage + "%"; }); } @@ -138,41 +139,43 @@ function initProgressBar() { function initScrollEffects() { // Scroll to top button - const scrollTopBtn = document.querySelector('.scroll-top'); - - window.addEventListener('scroll', () => { + const scrollTopBtn = document.querySelector(".scroll-top"); + + window.addEventListener("scroll", () => { if (window.pageYOffset > 300) { - scrollTopBtn.classList.add('visible'); + scrollTopBtn.classList.add("visible"); } else { - scrollTopBtn.classList.remove('visible'); + scrollTopBtn.classList.remove("visible"); } }); - - scrollTopBtn.addEventListener('click', () => { + + scrollTopBtn.addEventListener("click", () => { window.scrollTo({ top: 0, - behavior: 'smooth' + behavior: "smooth", }); }); - + // Reveal animations on scroll const observerOptions = { threshold: 0.1, - rootMargin: '0px 0px -50px 0px' + rootMargin: "0px 0px -50px 0px", }; - + const observer = new IntersectionObserver((entries) => { - entries.forEach(entry => { + entries.forEach((entry) => { if (entry.isIntersecting) { - entry.target.classList.add('animate-in'); + entry.target.classList.add("animate-in"); observer.unobserve(entry.target); } }); }, observerOptions); - - document.querySelectorAll('.card, .stat-card, .timeline-item').forEach(el => { - observer.observe(el); - }); + + document + .querySelectorAll(".card, .stat-card, .timeline-item") + .forEach((el) => { + observer.observe(el); + }); } // =================================== @@ -180,30 +183,37 @@ function initScrollEffects() { // =================================== 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'); - + 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'); - }); - + document + .querySelectorAll(`[data-tab-group="${tabGroup}"] .tab-btn`) + .forEach((btn) => { + btn.classList.remove("active"); + }); + // Add active class to clicked button - button.classList.add('active'); - + 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'); - }); - + document + .querySelectorAll(`.tab-content[data-tab-group="${tabGroup}"]`) + .forEach((content) => { + content.classList.remove("active"); + }); + // Show target tab content - const targetContent = document.querySelector(`[data-tab="${targetTab}"][data-tab-group="${tabGroup}"].tab-content`); + const targetContent = document.querySelector( + `[data-tab="${targetTab}"][data-tab-group="${tabGroup}"].tab-content`, + ); if (targetContent) { - targetContent.classList.add('active'); + targetContent.classList.add("active"); } }); }); @@ -214,29 +224,29 @@ function initTabs() { // =================================== function initCopyButtons() { - document.querySelectorAll('.copy-btn').forEach(button => { - button.addEventListener('click', async () => { - const codeBlock = button.closest('.code-block'); - const code = codeBlock.querySelector('code').textContent; - + 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'; - + button.textContent = "✓ Copied!"; + button.style.background = "#10b981"; + setTimeout(() => { button.textContent = originalText; - button.style.background = ''; + button.style.background = ""; }, 2000); } catch (err) { - console.error('Failed to copy code:', err); - button.textContent = '✗ Error'; - button.style.background = '#ef4444'; - + console.error("Failed to copy code:", err); + button.textContent = "✗ Error"; + button.style.background = "#ef4444"; + setTimeout(() => { - button.textContent = 'Copy'; - button.style.background = ''; + button.textContent = "Copy"; + button.style.background = ""; }, 2000); } }); @@ -248,31 +258,31 @@ function initCopyButtons() { // =================================== function initMermaidDiagrams() { - if (typeof mermaid !== 'undefined') { + if (typeof mermaid !== "undefined") { mermaid.initialize({ startOnLoad: true, - theme: 'dark', + theme: "dark", themeVariables: { darkMode: true, - background: '#1e293b', - primaryColor: '#2563eb', - primaryTextColor: '#f1f5f9', - primaryBorderColor: '#334155', - lineColor: '#64748b', - secondaryColor: '#7c3aed', - tertiaryColor: '#06b6d4', - fontFamily: 'Inter, sans-serif' + background: "#1e293b", + primaryColor: "#2563eb", + primaryTextColor: "#f1f5f9", + primaryBorderColor: "#334155", + lineColor: "#64748b", + secondaryColor: "#7c3aed", + tertiaryColor: "#06b6d4", + fontFamily: "Inter, sans-serif", }, flowchart: { - curve: 'basis', - padding: 20 + curve: "basis", + padding: 20, }, sequence: { actorMargin: 50, width: 150, height: 65, - boxMargin: 10 - } + boxMargin: 10, + }, }); } } @@ -282,47 +292,49 @@ function initMermaidDiagrams() { // =================================== function initStats() { - const stats = document.querySelectorAll('.stat-number'); - + 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') || ''); - + + element.textContent = value + (element.getAttribute("data-suffix") || ""); + if (progress < 1) { requestAnimationFrame(step); } }; - + requestAnimationFrame(step); }; - + const observerOptions = { - threshold: 0.5 + threshold: 0.5, }; - + const observer = new IntersectionObserver((entries) => { - entries.forEach(entry => { + entries.forEach((entry) => { if (entry.isIntersecting) { const stat = entry.target; - const endValue = parseInt(stat.getAttribute('data-value') || stat.textContent); + 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, ''); + + 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); }); } @@ -333,17 +345,17 @@ function initStats() { function initAnimations() { // Add stagger animation delays - document.querySelectorAll('.cards-grid .card').forEach((card, index) => { + document.querySelectorAll(".cards-grid .card").forEach((card, index) => { card.style.animationDelay = `${index * 0.1}s`; }); - + // Parallax effect on hero - const hero = document.querySelector('.hero'); + const hero = document.querySelector(".hero"); if (hero) { - window.addEventListener('scroll', () => { + window.addEventListener("scroll", () => { const scrolled = window.pageYOffset; hero.style.transform = `translateY(${scrolled * 0.5}px)`; - hero.style.opacity = 1 - (scrolled / 600); + hero.style.opacity = 1 - scrolled / 600; }); } } @@ -353,17 +365,17 @@ function initAnimations() { // =================================== function initThemeToggle() { - const themeToggle = document.getElementById('theme-toggle'); + 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); + + 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); }); } @@ -372,23 +384,25 @@ function initThemeToggle() { // =================================== function initSearch() { - const searchInput = document.getElementById('search'); + const searchInput = document.getElementById("search"); if (!searchInput) return; - - const searchableElements = document.querySelectorAll('section, .card, h2, h3, p'); - - searchInput.addEventListener('input', (e) => { + + const searchableElements = document.querySelectorAll( + "section, .card, h2, h3, p", + ); + + searchInput.addEventListener("input", (e) => { const searchTerm = e.target.value.toLowerCase(); - - searchableElements.forEach(element => { + + 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 = ''; + 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'; + if (parent) parent.style.display = "none"; } }); }); @@ -401,24 +415,24 @@ function initSearch() { function openModal(modalId) { const modal = document.getElementById(modalId); if (modal) { - modal.style.display = 'flex'; - document.body.style.overflow = 'hidden'; + 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 = ''; + 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 = ''; +window.addEventListener("click", (e) => { + if (e.target.classList.contains("modal")) { + e.target.style.display = "none"; + document.body.style.overflow = ""; } }); @@ -427,23 +441,23 @@ window.addEventListener('click', (e) => { // =================================== 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'); + 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%)'; + tooltip.style.transform = "translateX(-50%)"; }); - - element.addEventListener('mouseleave', () => { - const tooltip = document.querySelector('.tooltip'); + + element.addEventListener("mouseleave", () => { + const tooltip = document.querySelector(".tooltip"); if (tooltip) tooltip.remove(); }); }); @@ -470,10 +484,10 @@ function hideLoading(element, originalContent) { async function fetchData(url) { try { const response = await fetch(url); - if (!response.ok) throw new Error('Network response was not ok'); + if (!response.ok) throw new Error("Network response was not ok"); return await response.json(); } catch (error) { - console.error('Fetch error:', error); + console.error("Fetch error:", error); return null; } } @@ -496,11 +510,11 @@ function debounce(func, wait) { function throttle(func, limit) { let inThrottle; - return function(...args) { + return function (...args) { if (!inThrottle) { func.apply(this, args); inThrottle = true; - setTimeout(() => inThrottle = false, limit); + setTimeout(() => (inThrottle = false), limit); } }; } @@ -516,7 +530,7 @@ window.LMSWiki = { hideLoading, fetchData, debounce, - throttle + throttle, }; // =================================== @@ -524,16 +538,27 @@ window.LMSWiki = { // =================================== let konamiCode = []; -const konamiSequence = ['ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowLeft', 'ArrowRight', 'b', 'a']; - -document.addEventListener('keydown', (e) => { +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'; + + if (konamiCode.join("") === konamiSequence.join("")) { + document.body.style.animation = "rainbow 2s linear infinite"; setTimeout(() => { - document.body.style.animation = ''; + document.body.style.animation = ""; }, 5000); } }); @@ -543,7 +568,7 @@ document.addEventListener('keydown', (e) => { // =================================== if (window.performance && window.performance.timing) { - window.addEventListener('load', () => { + window.addEventListener("load", () => { const perfData = window.performance.timing; const pageLoadTime = perfData.loadEventEnd - perfData.navigationStart; console.log(`⚡ Page loaded in ${pageLoadTime}ms`); @@ -551,6 +576,15 @@ if (window.performance && window.performance.timing) { } // 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;'); +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 index fb499e9..ff4992c 100644 --- a/packages/styles.css +++ b/packages/styles.css @@ -11,7 +11,7 @@ --success-color: #10b981; --warning-color: #f59e0b; --danger-color: #ef4444; - + /* Tech Stack Colors */ --angular-red: #dd0031; --django-green: #092e20; @@ -20,7 +20,7 @@ --docker-blue: #2496ed; --k8s-blue: #326ce5; --aws-orange: #ff9900; - + /* Neutral Colors */ --dark-bg: #0f172a; --darker-bg: #020617; @@ -29,7 +29,7 @@ --text-secondary: #cbd5e1; --text-muted: #94a3b8; --border-color: #334155; - + /* Spacing */ --spacing-xs: 0.5rem; --spacing-sm: 1rem; @@ -37,17 +37,18 @@ --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; - + --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); @@ -149,7 +150,12 @@ nav.scrolled { .progress-bar { height: 100%; - background: linear-gradient(90deg, var(--primary-color), var(--secondary-color), var(--accent-color)); + background: linear-gradient( + 90deg, + var(--primary-color), + var(--secondary-color), + var(--accent-color) + ); background-size: 200% 100%; width: 0%; transition: width 0.1s ease; @@ -158,9 +164,15 @@ nav.scrolled { } @keyframes gradient-shift { - 0% { background-position: 0% 50%; } - 50% { background-position: 100% 50%; } - 100% { background-position: 0% 50%; } + 0% { + background-position: 0% 50%; + } + 50% { + background-position: 100% 50%; + } + 100% { + background-position: 0% 50%; + } } .nav-container { @@ -190,7 +202,11 @@ nav.scrolled { .logo-icon { width: 40px; height: 40px; - background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); + background: linear-gradient( + 135deg, + var(--primary-color), + var(--secondary-color) + ); border-radius: var(--radius-md); display: flex; align-items: center; @@ -257,19 +273,30 @@ nav.scrolled { } .hero::before { - content: ''; + content: ""; position: absolute; top: -50%; left: -50%; width: 200%; height: 200%; - background: radial-gradient(circle, rgba(37, 99, 235, 0.1) 0%, transparent 70%); + 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; } + 0%, + 100% { + transform: scale(1); + opacity: 1; + } + 50% { + transform: scale(1.1); + opacity: 0.8; + } } .hero-content { @@ -287,15 +314,24 @@ nav.scrolled { } @keyframes float { - 0%, 100% { transform: translateY(0px); } - 50% { transform: translateY(-10px); } + 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)); + background: linear-gradient( + 135deg, + var(--text-primary), + var(--primary-color) + ); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; @@ -335,13 +371,34 @@ h1 { 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; } +.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; @@ -366,7 +423,11 @@ h1 { } .btn-primary { - background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); + background: linear-gradient( + 135deg, + var(--primary-color), + var(--secondary-color) + ); color: white; } @@ -439,13 +500,17 @@ section { } .card::before { - content: ''; + content: ""; position: absolute; top: 0; left: 0; right: 0; height: 3px; - background: linear-gradient(90deg, var(--primary-color), var(--secondary-color)); + background: linear-gradient( + 90deg, + var(--primary-color), + var(--secondary-color) + ); transform: scaleX(0); transition: transform 0.3s ease; } @@ -469,7 +534,11 @@ section { justify-content: center; font-size: 2rem; margin-bottom: var(--spacing-md); - background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); + background: linear-gradient( + 135deg, + var(--primary-color), + var(--secondary-color) + ); } .card-title { @@ -529,7 +598,7 @@ section { } .timeline::before { - content: ''; + content: ""; position: absolute; left: 50%; top: 0; @@ -600,7 +669,11 @@ section { .stat-number { font-size: 3rem; font-weight: 800; - background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); + background: linear-gradient( + 135deg, + var(--primary-color), + var(--secondary-color) + ); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; @@ -702,7 +775,11 @@ table { } thead { - background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); + background: linear-gradient( + 135deg, + var(--primary-color), + var(--secondary-color) + ); } th { @@ -738,7 +815,7 @@ tr:hover td { } .tab-btn { - padding: var(--spacing-sm) var(--spacing-md); + padding: var(--spacing-md) var(--spacing-lg); background: none; border: none; color: var(--text-secondary); @@ -767,8 +844,14 @@ tr:hover td { } @keyframes fadeIn { - from { opacity: 0; transform: translateY(10px); } - to { opacity: 1; transform: translateY(0); } + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } } /* =================================== @@ -858,7 +941,11 @@ footer { width: 50px; height: 50px; border-radius: 50%; - background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); + background: linear-gradient( + 135deg, + var(--primary-color), + var(--secondary-color) + ); color: white; border: none; cursor: pointer; @@ -898,7 +985,9 @@ footer { } @keyframes spin { - to { transform: rotate(360deg); } + to { + transform: rotate(360deg); + } } /* =================================== @@ -931,7 +1020,8 @@ footer { font-size: 0.875rem; } - td, th { + td, + th { padding: var(--spacing-sm); } @@ -953,33 +1043,47 @@ footer { 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.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); @@ -987,63 +1091,63 @@ footer { 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; } @@ -1053,16 +1157,16 @@ footer { .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); } @@ -1120,46 +1224,89 @@ footer { Utility Classes =================================== */ -.text-center { text-align: center; } -.text-left { text-align: left; } -.text-right { text-align: right; } +.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); } +.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); } +.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); } +.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); } +.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 { + nav, + .scroll-top, + .cta-buttons { display: none; } - + body { background: white; color: black; } - - .card, .code-block { + + .card, + .code-block { break-inside: avoid; } }