diff --git a/views/css/index.css b/views/css/index.css index f2f7233..dbad429 100644 --- a/views/css/index.css +++ b/views/css/index.css @@ -584,3 +584,153 @@ hr{ cursor: pointer; border-radius: 99%; } + +/* ======================================== + SKELETON LOADER STYLES + ======================================== */ + +/* Skeleton shimmer animation */ +@keyframes skeleton-shimmer { + 0% { + background-position: -468px 0; + } + 100% { + background-position: 468px 0; + } +} + +/* Base skeleton class */ +.skeleton { + animation: skeleton-shimmer 1.5s ease-in-out infinite; + background: linear-gradient( + 90deg, + #f0f0f0 0%, + #f8f8f8 20%, + #f0f0f0 40%, + #f0f0f0 100% + ); + background-size: 468px 100%; + border-radius: 8px; +} + +/* Dark mode skeleton */ +.tw-dark .skeleton { + background: linear-gradient( + 90deg, + #1a1a1a 0%, + #2a2a2a 20%, + #1a1a1a 40%, + #1a1a1a 100% + ); + background-size: 468px 100%; +} + +/* Skeleton container */ +.skeleton-container { + display: flex; + flex-direction: column; + gap: 1rem; + padding: 1rem 0; +} + +/* FAQ Skeleton Card */ +.skeleton-faq-card { + display: flex; + flex-direction: column; + gap: 0.75rem; + padding: 1.25rem; + border: 1px solid #e5e7eb; + border-radius: 12px; + background: white; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); +} + +.tw-dark .skeleton-faq-card { + background: #17181b; + border-color: #2a2a2a; +} + +.skeleton-faq-header { + display: flex; + align-items: center; + gap: 0.75rem; +} + +.skeleton-faq-icon { + width: 24px; + height: 24px; + border-radius: 4px; +} + +.skeleton-faq-title { + flex: 1; + height: 24px; + border-radius: 6px; +} + +.skeleton-faq-content { + height: 60px; + border-radius: 6px; + margin-top: 0.5rem; +} + +/* Feature Card Skeleton */ +.skeleton-feature-card { + display: flex; + gap: 2rem; + padding: 2rem; + border-radius: 12px; + height: 240px; + border: 1px solid #e5e7eb; +} + +.tw-dark .skeleton-feature-card { + border-color: #2a2a2a; +} + +.skeleton-feature-icon { + width: 48px; + height: 48px; + border-radius: 8px; + flex-shrink: 0; +} + +.skeleton-feature-content { + display: flex; + flex-direction: column; + gap: 1rem; + flex: 1; +} + +.skeleton-feature-title { + width: 60%; + height: 32px; + border-radius: 6px; +} + +.skeleton-feature-text { + width: 100%; + height: 80px; + border-radius: 6px; +} + +/* Hide skeleton when content is loaded */ +.skeleton-hidden { + display: none; +} + +/* Fade in animation for loaded content */ +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.content-loaded { + animation: fadeIn 0.5s ease-out; +} diff --git a/views/index.html b/views/index.html index 043fe0c..b7bf628 100644 --- a/views/index.html +++ b/views/index.html @@ -880,8 +880,34 @@

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

- + @@ -1831,8 +1857,35 @@

> Regarding GSSOC FAQ BOT

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


- +
diff --git a/views/scripts/components.js b/views/scripts/components.js index 343858f..34905f4 100644 --- a/views/scripts/components.js +++ b/views/scripts/components.js @@ -1,3 +1,64 @@ +// ======================================== +// SKELETON LOADER UTILITIES +// ======================================== + +/** + * Shows skeleton loader and hides content + * @param {string} skeletonId - ID of the skeleton loader element + * @param {string} contentId - ID of the content element + */ +function showSkeleton(skeletonId, contentId) { + const skeleton = document.getElementById(skeletonId); + const content = document.getElementById(contentId); + + if (skeleton) { + skeleton.style.display = 'flex'; + } + if (content) { + content.classList.add('skeleton-hidden'); + } +} + +/** + * Hides skeleton loader and shows content with fade-in animation + * @param {string} skeletonId - ID of the skeleton loader element + * @param {string} contentId - ID of the content element + */ +function hideSkeleton(skeletonId, contentId) { + const skeleton = document.getElementById(skeletonId); + const content = document.getElementById(contentId); + + if (skeleton) { + skeleton.style.display = 'none'; + } + if (content) { + content.classList.remove('skeleton-hidden'); + content.classList.add('content-loaded'); + } +} + +/** + * Simulates a delay (useful for demonstrating skeleton loaders) + * @param {number} ms - Milliseconds to delay + * @returns {Promise} + */ +function delay(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +// Initialize skeleton loaders when DOM is ready +document.addEventListener('DOMContentLoaded', function() { + // Show skeletons initially + showSkeleton('faq-skeleton', 'faq-content'); + showSkeleton('features-skeleton', 'features-content'); + + // Hide feature skeletons after a short delay (simulating page load) + // Features are static content, so they load quickly + setTimeout(() => { + hideSkeleton('features-skeleton', 'features-content'); + }, 800); +}); + class Dropdown { constructor(selector, onChange) { this.dropdown = document.querySelector(selector) @@ -240,6 +301,11 @@ fetch('/faqs.json') } renderFAQs(faqs.slice(0, 5)); // Load initial FAQs + + // Hide skeleton loader after FAQ data is loaded + setTimeout(() => { + hideSkeleton('faq-skeleton', 'faq-content'); + }, 600); // Small delay for smooth transition searchInput.addEventListener('input', () => { const q = searchInput.value.toLowerCase(); @@ -252,6 +318,10 @@ fetch('/faqs.json') }) .catch(err => { console.error('FAQ loading failed:', err); + + // Hide skeleton and show error + hideSkeleton('faq-skeleton', 'faq-content'); + const container = document.getElementById('faq-section'); if (container) { container.innerHTML = `

Error loading FAQs. Please try again later.

`;