Optimised static comp with .astro

This commit is contained in:
Arunavo Ray
2025-07-09 01:01:37 +05:30
parent 9c17e5c240
commit 9301cc321c
7 changed files with 429 additions and 390 deletions

View File

@@ -0,0 +1,90 @@
---
import {
RefreshCw,
Building2,
FolderTree,
Activity,
Lock,
Heart,
} from 'lucide-react';
const features = [
{
title: "Automated Mirroring",
description: "Set it and forget it. Automatically sync your GitHub repositories to Gitea on a schedule.",
icon: RefreshCw,
gradient: "from-primary/10 to-accent/10",
iconColor: "text-primary"
},
{
title: "Bulk Operations",
description: "Mirror entire organizations or user accounts with a single configuration.",
icon: Building2,
gradient: "from-accent/10 to-accent-teal/10",
iconColor: "text-accent"
},
{
title: "Preserve Structure",
description: "Maintain your GitHub organization structure or customize how repos are organized.",
icon: FolderTree,
gradient: "from-accent-teal/10 to-primary/10",
iconColor: "text-accent-teal"
},
{
title: "Real-time Status",
description: "Monitor mirror progress with live updates and detailed activity logs.",
icon: Activity,
gradient: "from-accent-coral/10 to-primary/10",
iconColor: "text-accent-coral"
},
{
title: "Secure & Private",
description: "Self-hosted solution keeps your code on your infrastructure with full control.",
icon: Lock,
gradient: "from-accent-purple/10 to-primary/10",
iconColor: "text-accent-purple"
},
{
title: "Open Source",
description: "Free, transparent, and community-driven development. Contribute and customize.",
icon: Heart,
gradient: "from-primary/10 to-accent-purple/10",
iconColor: "text-primary"
}
];
---
<section id="features" class="py-16 sm:py-24 px-4 sm:px-6 lg:px-8">
<div class="max-w-7xl mx-auto">
<div class="text-center mb-12 sm:mb-16">
<h2 class="text-2xl sm:text-3xl md:text-4xl font-bold tracking-tight px-4">
Everything You Need for
<span class="text-gradient from-primary to-accent block sm:inline"> Reliable Backups</span>
</h2>
<p class="mt-4 text-base sm:text-lg text-muted-foreground max-w-2xl mx-auto px-4">
Powerful features designed to keep your code safe and accessible, no matter what happens.
</p>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6 lg:gap-8">
{features.map((feature) => {
const Icon = feature.icon;
return (
<div
class={`group relative p-6 sm:p-8 rounded-xl sm:rounded-2xl border bg-gradient-to-br ${feature.gradient} backdrop-blur-sm hover:shadow-lg hover:shadow-primary/10 transition-all duration-300 hover:-translate-y-1 hover:border-primary/30 overflow-hidden`}
>
<div class="absolute inset-0 bg-gradient-to-br from-transparent to-background/50 opacity-0 group-hover:opacity-100 transition-opacity duration-500" />
<div class="relative">
<div class={`inline-flex p-2.5 sm:p-3 rounded-lg bg-background/80 backdrop-blur-sm mb-3 sm:mb-4 ${feature.iconColor} shadow-sm`}>
<Icon className="w-5 h-5 sm:w-6 sm:h-6" />
</div>
<h3 class="text-lg sm:text-xl font-semibold mb-2">{feature.title}</h3>
<p class="text-sm sm:text-base text-muted-foreground">{feature.description}</p>
</div>
</div>
);
})}
</div>
</div>
</section>

View File

@@ -1,94 +0,0 @@
import React from 'react';
import {
RefreshCw,
Building2,
FolderTree,
Activity,
Lock,
Heart,
} from 'lucide-react';
const features = [
{
title: "Automated Mirroring",
description: "Set it and forget it. Automatically sync your GitHub repositories to Gitea on a schedule.",
icon: RefreshCw,
gradient: "from-primary/10 to-accent/10",
iconColor: "text-primary"
},
{
title: "Bulk Operations",
description: "Mirror entire organizations or user accounts with a single configuration.",
icon: Building2,
gradient: "from-accent/10 to-accent-teal/10",
iconColor: "text-accent"
},
{
title: "Preserve Structure",
description: "Maintain your GitHub organization structure or customize how repos are organized.",
icon: FolderTree,
gradient: "from-accent-teal/10 to-primary/10",
iconColor: "text-accent-teal"
},
{
title: "Real-time Status",
description: "Monitor mirror progress with live updates and detailed activity logs.",
icon: Activity,
gradient: "from-accent-coral/10 to-primary/10",
iconColor: "text-accent-coral"
},
{
title: "Secure & Private",
description: "Self-hosted solution keeps your code on your infrastructure with full control.",
icon: Lock,
gradient: "from-accent-purple/10 to-primary/10",
iconColor: "text-accent-purple"
},
{
title: "Open Source",
description: "Free, transparent, and community-driven development. Contribute and customize.",
icon: Heart,
gradient: "from-primary/10 to-accent-purple/10",
iconColor: "text-primary"
}
];
export function Features() {
return (
<section id="features" className="py-16 sm:py-24 px-4 sm:px-6 lg:px-8">
<div className="max-w-7xl mx-auto">
<div className="text-center mb-12 sm:mb-16">
<h2 className="text-2xl sm:text-3xl md:text-4xl font-bold tracking-tight px-4">
Everything You Need for
<span className="text-gradient from-primary to-accent block sm:inline"> Reliable Backups</span>
</h2>
<p className="mt-4 text-base sm:text-lg text-muted-foreground max-w-2xl mx-auto px-4">
Powerful features designed to keep your code safe and accessible, no matter what happens.
</p>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6 lg:gap-8">
{features.map((feature, index) => {
const Icon = feature.icon;
return (
<div
key={index}
className={`group relative p-6 sm:p-8 rounded-xl sm:rounded-2xl border bg-gradient-to-br ${feature.gradient} backdrop-blur-sm hover:shadow-lg hover:shadow-primary/10 transition-all duration-300 hover:-translate-y-1 hover:border-primary/30 overflow-hidden`}
>
<div className="absolute inset-0 bg-gradient-to-br from-transparent to-background/50 opacity-0 group-hover:opacity-100 transition-opacity duration-500" />
<div className="relative">
<div className={`inline-flex p-2.5 sm:p-3 rounded-lg bg-background/80 backdrop-blur-sm mb-3 sm:mb-4 ${feature.iconColor} shadow-sm`}>
<Icon className="w-5 h-5 sm:w-6 sm:h-6" />
</div>
<h3 className="text-lg sm:text-xl font-semibold mb-2">{feature.title}</h3>
<p className="text-sm sm:text-base text-muted-foreground">{feature.description}</p>
</div>
</div>
);
})}
</div>
</div>
</section>
);
}

View File

@@ -0,0 +1,88 @@
---
import { Github, Book, MessageSquare, Bug } from 'lucide-react';
const links = [
{
title: "Source Code",
href: "https://github.com/RayLabsHQ/gitea-mirror",
icon: Github
},
{
title: "Documentation",
href: "https://github.com/RayLabsHQ/gitea-mirror/tree/main/docs",
icon: Book
},
{
title: "Discussions",
href: "https://github.com/RayLabsHQ/gitea-mirror/discussions",
icon: MessageSquare
},
{
title: "Report Issue",
href: "https://github.com/RayLabsHQ/gitea-mirror/issues",
icon: Bug
}
];
const currentYear = new Date().getFullYear();
---
<footer class="border-t py-8 sm:py-12 px-4 sm:px-6 lg:px-8">
<div class="max-w-7xl mx-auto">
<div class="flex flex-col items-center gap-6 sm:gap-8">
<!-- Logo and tagline -->
<div class="text-center">
<div class="flex items-center justify-center gap-2 mb-2">
<img
src="/logo-light.svg"
alt="Gitea Mirror"
class="w-6 h-6 sm:w-8 sm:h-8 dark:hidden"
/>
<img
src="/logo-dark.svg"
alt="Gitea Mirror"
class="w-6 h-6 sm:w-8 sm:h-8 hidden dark:block"
/>
<span class="font-semibold text-base sm:text-lg">Gitea Mirror</span>
</div>
<p class="text-xs sm:text-sm text-muted-foreground">
Keep your GitHub code safe and synced
</p>
</div>
<!-- Links -->
<nav class="grid grid-cols-2 sm:flex items-center justify-center gap-4 sm:gap-6 text-center">
{links.map((link) => {
const Icon = link.icon;
return (
<a
href={link.href}
target="_blank"
rel="noopener noreferrer"
class="flex items-center justify-center gap-2 text-xs sm:text-sm text-muted-foreground hover:text-foreground transition-colors py-2 sm:py-0"
>
<Icon className="w-3 h-3 sm:w-4 sm:h-4" />
<span>{link.title}</span>
</a>
);
})}
</nav>
<!-- Copyright -->
<div class="text-center text-xs sm:text-sm text-muted-foreground px-4">
<p>© {currentYear} Gitea Mirror. Open source under GPL-3.0 License.</p>
<p class="mt-1">
Made with dedication by the{' '}
<a
href="https://github.com/RayLabsHQ"
class="underline hover:text-foreground transition-colors"
target="_blank"
rel="noopener noreferrer"
>
RayLabs team
</a>
</p>
</div>
</div>
</div>
</footer>

View File

@@ -1,90 +0,0 @@
import React from 'react';
import { Github, Book, MessageSquare, Bug } from 'lucide-react';
export function Footer() {
const links = [
{
title: "Source Code",
href: "https://github.com/RayLabsHQ/gitea-mirror",
icon: Github
},
{
title: "Documentation",
href: "https://github.com/RayLabsHQ/gitea-mirror/tree/main/docs",
icon: Book
},
{
title: "Discussions",
href: "https://github.com/RayLabsHQ/gitea-mirror/discussions",
icon: MessageSquare
},
{
title: "Report Issue",
href: "https://github.com/RayLabsHQ/gitea-mirror/issues",
icon: Bug
}
];
return (
<footer className="border-t py-8 sm:py-12 px-4 sm:px-6 lg:px-8">
<div className="max-w-7xl mx-auto">
<div className="flex flex-col items-center gap-6 sm:gap-8">
{/* Logo and tagline */}
<div className="text-center">
<div className="flex items-center justify-center gap-2 mb-2">
<img
src="/logo-light.svg"
alt="Gitea Mirror"
className="w-6 h-6 sm:w-8 sm:h-8 dark:hidden"
/>
<img
src="/logo-dark.svg"
alt="Gitea Mirror"
className="w-6 h-6 sm:w-8 sm:h-8 hidden dark:block"
/>
<span className="font-semibold text-base sm:text-lg">Gitea Mirror</span>
</div>
<p className="text-xs sm:text-sm text-muted-foreground">
Keep your GitHub code safe and synced
</p>
</div>
{/* Links */}
<nav className="grid grid-cols-2 sm:flex items-center justify-center gap-4 sm:gap-6 text-center">
{links.map((link) => {
const Icon = link.icon;
return (
<a
key={link.title}
href={link.href}
target="_blank"
rel="noopener noreferrer"
className="flex items-center justify-center gap-2 text-xs sm:text-sm text-muted-foreground hover:text-foreground transition-colors py-2 sm:py-0"
>
<Icon className="w-3 h-3 sm:w-4 sm:h-4" />
<span>{link.title}</span>
</a>
);
})}
</nav>
{/* Copyright */}
<div className="text-center text-xs sm:text-sm text-muted-foreground px-4">
<p>© {new Date().getFullYear()} Gitea Mirror. Open source under GPL-3.0 License.</p>
<p className="mt-1">
Made with dedication by the{' '}
<a
href="https://github.com/RayLabsHQ"
className="underline hover:text-foreground transition-colors"
target="_blank"
rel="noopener noreferrer"
>
RayLabs team
</a>
</p>
</div>
</div>
</div>
</footer>
);
}

View File

@@ -0,0 +1,245 @@
---
import { Image } from 'astro:assets';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import { Button } from './ui/button';
// Import all images
import dashboardDesktop from '../../public/assets/dashboard.png';
import dashboardMobile from '../../public/assets/dashboard_mobile.png';
import organisationDesktop from '../../public/assets/organisation.png';
import organisationMobile from '../../public/assets/organisation_mobile.png';
import repositoriesDesktop from '../../public/assets/repositories.png';
import repositoriesMobile from '../../public/assets/repositories_mobile.png';
import configurationDesktop from '../../public/assets/configuration.png';
import configurationMobile from '../../public/assets/configuration_mobile.png';
import activityDesktop from '../../public/assets/activity.png';
import activityMobile from '../../public/assets/activity_mobile.png';
const screenshots = [
{
title: "Dashboard Overview",
description: "Monitor all your mirrored repositories in one place",
desktop: dashboardDesktop,
mobile: dashboardMobile
},
{
title: "Organization Management",
description: "Easily manage and sync entire GitHub organizations",
desktop: organisationDesktop,
mobile: organisationMobile
},
{
title: "Repository Control",
description: "Fine-grained control over individual repository mirrors",
desktop: repositoriesDesktop,
mobile: repositoriesMobile
},
{
title: "Configuration",
description: "Simple and intuitive configuration interface",
desktop: configurationDesktop,
mobile: configurationMobile
},
{
title: "Activity Monitoring",
description: "Track sync progress and view detailed logs",
desktop: activityDesktop,
mobile: activityMobile
}
];
---
<section id="screenshots" class="py-16 sm:py-24 px-4 sm:px-6 lg:px-8 bg-gradient-to-b from-muted/30 via-primary/5 to-muted/30">
<div class="max-w-7xl mx-auto">
<div class="text-center mb-8 sm:mb-16">
<h2 class="text-2xl sm:text-3xl md:text-4xl font-bold tracking-tight">
See It In Action
</h2>
<p class="mt-4 text-base sm:text-lg text-muted-foreground max-w-2xl mx-auto px-4">
A clean, intuitive interface designed for efficiency and ease of use
</p>
</div>
<div class="relative max-w-5xl mx-auto">
<!-- Screenshot viewer -->
<div id="screenshot-container" class="relative group">
<div class="aspect-[9/16] sm:aspect-[16/10] overflow-hidden rounded-lg sm:rounded-2xl bg-card border shadow-lg">
{screenshots.map((screenshot, index) => (
<picture data-index={index} class={index === 0 ? 'block' : 'hidden'}>
<source media="(max-width: 640px)" srcset={screenshot.mobile.src} />
<Image
src={screenshot.desktop}
alt={screenshot.title}
class="w-full h-full object-cover object-top"
draggable={false}
loading={index === 0 ? 'eager' : 'lazy'}
/>
</picture>
))}
</div>
<!-- Navigation buttons -->
<Button
variant="outline"
size="icon"
className="screenshot-nav-prev absolute left-2 sm:left-4 top-1/2 -translate-y-1/2 opacity-0 sm:opacity-100 group-hover:opacity-100 transition-opacity hidden sm:flex"
aria-label="Previous screenshot"
>
<ChevronLeft className="h-4 w-4" />
</Button>
<Button
variant="outline"
size="icon"
className="screenshot-nav-next absolute right-2 sm:right-4 top-1/2 -translate-y-1/2 opacity-0 sm:opacity-100 group-hover:opacity-100 transition-opacity hidden sm:flex"
aria-label="Next screenshot"
>
<ChevronRight className="h-4 w-4" />
</Button>
</div>
<!-- Screenshot info -->
<div class="mt-6 sm:mt-8 text-center">
<h3 id="screenshot-title" class="text-lg sm:text-xl font-semibold">{screenshots[0].title}</h3>
<p id="screenshot-description" class="mt-2 text-sm sm:text-base text-muted-foreground">{screenshots[0].description}</p>
</div>
<!-- Dots indicator -->
<div class="mt-6 sm:mt-8 flex justify-center gap-2">
{screenshots.map((_, index) => (
<button
data-index={index}
class={`screenshot-dot transition-all duration-300 ${
index === 0
? 'w-8 h-2 bg-primary rounded-full'
: 'w-2 h-2 bg-muted-foreground/30 hover:bg-muted-foreground/50 rounded-full'
}`}
aria-label={`Go to screenshot ${index + 1}`}
/>
))}
</div>
<!-- Mobile swipe hint -->
<p class="mt-4 text-xs text-muted-foreground text-center sm:hidden">
Swipe left or right to navigate
</p>
</div>
<!-- Thumbnail grid -->
<div class="hidden lg:grid grid-cols-5 gap-4 mt-12 px-8">
{screenshots.map((screenshot, index) => (
<button
data-index={index}
class={`screenshot-thumb relative overflow-hidden rounded-lg transition-all duration-300 ${
index === 0
? 'ring-2 ring-primary shadow-lg scale-105'
: 'opacity-60 hover:opacity-100'
}`}
>
<Image
src={screenshot.desktop}
alt={screenshot.title}
class="w-full h-full object-cover"
loading="lazy"
/>
</button>
))}
</div>
</div>
</section>
<script define:vars={{ screenshots }}>
let currentIndex = 0;
let touchStart = 0;
let touchEnd = 0;
const minSwipeDistance = 50;
const container = document.getElementById('screenshot-container');
const pictures = container.querySelectorAll('picture');
const dots = document.querySelectorAll('.screenshot-dot');
const thumbs = document.querySelectorAll('.screenshot-thumb');
const titleEl = document.getElementById('screenshot-title');
const descriptionEl = document.getElementById('screenshot-description');
const prevBtn = container.querySelector('.screenshot-nav-prev');
const nextBtn = container.querySelector('.screenshot-nav-next');
function updateView(newIndex) {
// Hide current, show new
pictures[currentIndex].classList.add('hidden');
pictures[newIndex].classList.remove('hidden');
// Update dots
dots[currentIndex].classList.remove('w-8', 'h-2', 'bg-primary');
dots[currentIndex].classList.add('w-2', 'h-2', 'bg-muted-foreground/30');
dots[newIndex].classList.remove('w-2', 'h-2', 'bg-muted-foreground/30');
dots[newIndex].classList.add('w-8', 'h-2', 'bg-primary');
// Update thumbnails
if (thumbs.length > 0) {
thumbs[currentIndex].classList.remove('ring-2', 'ring-primary', 'shadow-lg', 'scale-105');
thumbs[currentIndex].classList.add('opacity-60');
thumbs[newIndex].classList.remove('opacity-60');
thumbs[newIndex].classList.add('ring-2', 'ring-primary', 'shadow-lg', 'scale-105');
}
// Update text
titleEl.textContent = screenshots[newIndex].title;
descriptionEl.textContent = screenshots[newIndex].description;
currentIndex = newIndex;
}
function goToPrevious() {
const newIndex = currentIndex === 0 ? screenshots.length - 1 : currentIndex - 1;
updateView(newIndex);
}
function goToNext() {
const newIndex = (currentIndex + 1) % screenshots.length;
updateView(newIndex);
}
// Touch handling
container.addEventListener('touchstart', (e) => {
touchEnd = 0;
touchStart = e.targetTouches[0].clientX;
});
container.addEventListener('touchmove', (e) => {
touchEnd = e.targetTouches[0].clientX;
});
container.addEventListener('touchend', () => {
if (!touchStart || !touchEnd) return;
const distance = touchStart - touchEnd;
const isLeftSwipe = distance > minSwipeDistance;
const isRightSwipe = distance < -minSwipeDistance;
if (isLeftSwipe && currentIndex < screenshots.length - 1) {
goToNext();
}
if (isRightSwipe && currentIndex > 0) {
goToPrevious();
}
});
// Button navigation
prevBtn?.addEventListener('click', goToPrevious);
nextBtn?.addEventListener('click', goToNext);
// Dot navigation
dots.forEach((dot, index) => {
dot.addEventListener('click', () => updateView(index));
});
// Thumbnail navigation
thumbs.forEach((thumb, index) => {
thumb.addEventListener('click', () => updateView(index));
});
// Keyboard navigation
window.addEventListener('keydown', (e) => {
if (e.key === 'ArrowLeft') goToPrevious();
if (e.key === 'ArrowRight') goToNext();
});
</script>

View File

@@ -1,200 +0,0 @@
import React, { useState, useRef, useEffect } from 'react';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import { Button } from './ui/button';
const screenshots = [
{
title: "Dashboard Overview",
description: "Monitor all your mirrored repositories in one place",
desktop: "/assets/dashboard.png",
mobile: "/assets/dashboard_mobile.png"
},
{
title: "Organization Management",
description: "Easily manage and sync entire GitHub organizations",
desktop: "/assets/organisation.png",
mobile: "/assets/organisation_mobile.png"
},
{
title: "Repository Control",
description: "Fine-grained control over individual repository mirrors",
desktop: "/assets/repositories.png",
mobile: "/assets/repositories_mobile.png"
},
{
title: "Configuration",
description: "Simple and intuitive configuration interface",
desktop: "/assets/configuration.png",
mobile: "/assets/configuration_mobile.png"
},
{
title: "Activity Monitoring",
description: "Track sync progress and view detailed logs",
desktop: "/assets/activity.png",
mobile: "/assets/activity_mobile.png"
}
];
export function Screenshots() {
const [currentIndex, setCurrentIndex] = useState(0);
const [touchStart, setTouchStart] = useState(0);
const [touchEnd, setTouchEnd] = useState(0);
const containerRef = useRef<HTMLDivElement>(null);
const minSwipeDistance = 50;
const onTouchStart = (e: React.TouchEvent) => {
setTouchEnd(0);
setTouchStart(e.targetTouches[0].clientX);
};
const onTouchMove = (e: React.TouchEvent) => {
setTouchEnd(e.targetTouches[0].clientX);
};
const onTouchEnd = () => {
if (!touchStart || !touchEnd) return;
const distance = touchStart - touchEnd;
const isLeftSwipe = distance > minSwipeDistance;
const isRightSwipe = distance < -minSwipeDistance;
if (isLeftSwipe && currentIndex < screenshots.length - 1) {
setCurrentIndex(currentIndex + 1);
}
if (isRightSwipe && currentIndex > 0) {
setCurrentIndex(currentIndex - 1);
}
};
const goToPrevious = () => {
setCurrentIndex((prevIndex) =>
prevIndex === 0 ? screenshots.length - 1 : prevIndex - 1
);
};
const goToNext = () => {
setCurrentIndex((prevIndex) =>
(prevIndex + 1) % screenshots.length
);
};
// Keyboard navigation
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'ArrowLeft') goToPrevious();
if (e.key === 'ArrowRight') goToNext();
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [currentIndex]);
const current = screenshots[currentIndex];
return (
<section id="screenshots" className="py-16 sm:py-24 px-4 sm:px-6 lg:px-8 bg-gradient-to-b from-muted/30 via-primary/5 to-muted/30">
<div className="max-w-7xl mx-auto">
<div className="text-center mb-8 sm:mb-16">
<h2 className="text-2xl sm:text-3xl md:text-4xl font-bold tracking-tight">
See It In Action
</h2>
<p className="mt-4 text-base sm:text-lg text-muted-foreground max-w-2xl mx-auto px-4">
A clean, intuitive interface designed for efficiency and ease of use
</p>
</div>
<div className="relative max-w-5xl mx-auto">
{/* Screenshot viewer */}
<div
ref={containerRef}
className="relative group"
onTouchStart={onTouchStart}
onTouchMove={onTouchMove}
onTouchEnd={onTouchEnd}
>
<div className="aspect-[9/16] sm:aspect-[16/10] overflow-hidden rounded-lg sm:rounded-2xl bg-card border shadow-lg">
<picture>
<source media="(max-width: 640px)" srcSet={current.mobile} />
<img
src={current.desktop}
alt={current.title}
className="w-full h-full object-cover object-top"
draggable={false}
/>
</picture>
</div>
{/* Navigation buttons - hidden on mobile, visible on desktop */}
<Button
variant="outline"
size="icon"
className="absolute left-2 sm:left-4 top-1/2 -translate-y-1/2 opacity-0 sm:opacity-100 group-hover:opacity-100 transition-opacity hidden sm:flex"
onClick={goToPrevious}
aria-label="Previous screenshot"
>
<ChevronLeft className="h-4 w-4" />
</Button>
<Button
variant="outline"
size="icon"
className="absolute right-2 sm:right-4 top-1/2 -translate-y-1/2 opacity-0 sm:opacity-100 group-hover:opacity-100 transition-opacity hidden sm:flex"
onClick={goToNext}
aria-label="Next screenshot"
>
<ChevronRight className="h-4 w-4" />
</Button>
</div>
{/* Screenshot info */}
<div className="mt-6 sm:mt-8 text-center">
<h3 className="text-lg sm:text-xl font-semibold">{current.title}</h3>
<p className="mt-2 text-sm sm:text-base text-muted-foreground">{current.description}</p>
</div>
{/* Dots indicator */}
<div className="mt-6 sm:mt-8 flex justify-center gap-2">
{screenshots.map((_, index) => (
<button
key={index}
className={`transition-all duration-300 ${
index === currentIndex
? 'w-8 h-2 bg-primary rounded-full'
: 'w-2 h-2 bg-muted-foreground/30 hover:bg-muted-foreground/50 rounded-full'
}`}
onClick={() => setCurrentIndex(index)}
aria-label={`Go to screenshot ${index + 1}`}
/>
))}
</div>
{/* Mobile swipe hint */}
<p className="mt-4 text-xs text-muted-foreground text-center sm:hidden">
Swipe left or right to navigate
</p>
</div>
{/* Thumbnail grid - visible on larger screens */}
<div className="hidden lg:grid grid-cols-5 gap-4 mt-12 px-8">
{screenshots.map((screenshot, index) => (
<button
key={index}
onClick={() => setCurrentIndex(index)}
className={`relative overflow-hidden rounded-lg transition-all duration-300 ${
index === currentIndex
? 'ring-2 ring-primary shadow-lg scale-105'
: 'opacity-60 hover:opacity-100'
}`}
>
<img
src={screenshot.desktop}
alt={screenshot.title}
className="w-full h-full object-cover"
/>
</button>
))}
</div>
</div>
</section>
);
}

View File

@@ -2,11 +2,11 @@
import '../styles/global.css';
import { Header } from '../components/Header';
import { Hero } from '../components/Hero';
import { Features } from '../components/Features';
import { Screenshots } from '../components/Screenshots';
import Features from '../components/Features.astro';
import Screenshots from '../components/Screenshots.astro';
import { Installation } from '../components/Installation';
import { CTA } from '../components/CTA';
import { Footer } from '../components/Footer';
import Footer from '../components/Footer.astro';
const siteUrl = 'https://gitea-mirror.com';
const title = 'Gitea Mirror - Automated GitHub to Gitea Repository Mirroring & Backup';
@@ -118,13 +118,13 @@ const structuredData = {
<main>
<Hero client:load />
<Features client:load />
<Screenshots client:load />
<Features />
<Screenshots />
<Installation client:load />
<CTA client:load />
</main>
<Footer client:load />
<Footer />
<style>
/* Smooth scrolling */