Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 150 additions & 0 deletions views/css/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
61 changes: 57 additions & 4 deletions views/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -880,8 +880,34 @@ <h2 class="tw-text-3xl max-md:tw-text-2xl tw-font-medium">
</div>
</div>

<!-- Skeleton Loader for Feature Cards -->
<div id="features-skeleton" class="tw-flex tw-flex-col tw-gap-10 tw-h-full tw-max-w-1/2 max-lg:tw-max-w-full tw-px-[10%] max-lg:tw-px-4 max-lg:tw-gap-3 max-lg:tw-w-full lg:tw-top-[20%] tw-place-items-center">
<div class="skeleton-feature-card tw-w-[450px] max-md:tw-w-full">
<div class="skeleton skeleton-feature-icon"></div>
<div class="skeleton-feature-content">
<div class="skeleton skeleton-feature-title"></div>
<div class="skeleton skeleton-feature-text"></div>
</div>
</div>
<div class="skeleton-feature-card tw-w-[450px] max-md:tw-w-full">
<div class="skeleton skeleton-feature-icon"></div>
<div class="skeleton-feature-content">
<div class="skeleton skeleton-feature-title"></div>
<div class="skeleton skeleton-feature-text"></div>
</div>
</div>
<div class="skeleton-feature-card tw-w-[450px] max-md:tw-w-full">
<div class="skeleton skeleton-feature-icon"></div>
<div class="skeleton-feature-content">
<div class="skeleton skeleton-feature-title"></div>
<div class="skeleton skeleton-feature-text"></div>
</div>
</div>
</div>

<div
class="tw-flex tw-flex-col tw-gap-10 tw-h-full tw-max-w-1/2 max-lg:tw-max-w-full tw-px-[10%] max-lg:tw-px-4 max-lg:tw-gap-3 max-lg:tw-w-full lg:tw-top-[20%] tw-place-items-center"
id="features-content"
class="tw-flex tw-flex-col tw-gap-10 tw-h-full tw-max-w-1/2 max-lg:tw-max-w-full tw-px-[10%] max-lg:tw-px-4 max-lg:tw-gap-3 max-lg:tw-w-full lg:tw-top-[20%] tw-place-items-center skeleton-hidden"
>
<div class="reveal-up tw-h-[240px] tw-w-[450px] max-md:tw-w-full">
<a
Expand Down Expand Up @@ -1070,7 +1096,7 @@ <h3 class="tw-text-2xl max-md:tw-text-xl">Video generator</h3>
</div>
</a>
</div>
</div>
</div> <!-- End of features-content -->
</div>
</section>

Expand Down Expand Up @@ -1831,8 +1857,35 @@ <h3 class="tw-text-4xl tw-font-medium max-md:tw-text-2xl tw-uppercase">
>
Regarding GSSOC FAQ BOT
</p>

<!-- Skeleton Loader for FAQs -->
<div id="faq-skeleton" class="tw-mt-5 tw-w-full tw-max-w-[850px] skeleton-container">
<div class="skeleton-faq-card">
<div class="skeleton-faq-header">
<div class="skeleton skeleton-faq-icon"></div>
<div class="skeleton skeleton-faq-title"></div>
</div>
<div class="skeleton skeleton-faq-content"></div>
</div>
<div class="skeleton-faq-card">
<div class="skeleton-faq-header">
<div class="skeleton skeleton-faq-icon"></div>
<div class="skeleton skeleton-faq-title"></div>
</div>
<div class="skeleton skeleton-faq-content"></div>
</div>
<div class="skeleton-faq-card">
<div class="skeleton-faq-header">
<div class="skeleton skeleton-faq-icon"></div>
<div class="skeleton skeleton-faq-title"></div>
</div>
<div class="skeleton skeleton-faq-content"></div>
</div>
</div>

<div
class="tw-mt-5 tw-flex tw-min-h-[300px] tw-w-full tw-max-w-[850px] tw-flex-col tw-gap-4"
id="faq-content"
class="tw-mt-5 tw-flex tw-min-h-[300px] tw-w-full tw-max-w-[850px] tw-flex-col tw-gap-4 skeleton-hidden"
>
<div class="faq tw-w-full">
<h4
Expand Down Expand Up @@ -2100,7 +2153,7 @@ <h3 class="tw-text-4xl tw-font-medium max-md:tw-text-2xl tw-uppercase">
</div>
</div>
<hr />
</div>
</div> <!-- End of faq-content -->
<div
class="purple-bg-grad max-md:tw-hidden reveal-up tw-absolute tw-bottom-14 tw-right-[20%] tw-h-[150px] tw-w-[150px] tw-rounded-full"
></div>
Expand Down
70 changes: 70 additions & 0 deletions views/scripts/components.js
Original file line number Diff line number Diff line change
@@ -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)
Expand Down Expand Up @@ -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();
Expand All @@ -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 = `<p style="color:red; text-align:center;">Error loading FAQs. Please try again later.</p>`;
Expand Down