mirror of
https://github.com/RayLabsHQ/gitea-mirror.git
synced 2025-12-06 11:36:44 +03:00
Updated website deisgn
This commit is contained in:
92
www/CLAUDE.md
Normal file
92
www/CLAUDE.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
This is the marketing website for Gitea Mirror, built with Astro and Tailwind CSS v4. It serves as a landing page to showcase the Gitea Mirror application's features and provide getting started information.
|
||||
|
||||
**Note**: This is NOT the main Gitea Mirror application. The actual application is located in the parent directory (`../`).
|
||||
|
||||
## Essential Commands
|
||||
|
||||
```bash
|
||||
bun install # Install dependencies
|
||||
bun run dev # Start development server (port 4321)
|
||||
bun run build # Build for production
|
||||
bun run preview # Preview production build
|
||||
```
|
||||
|
||||
## Architecture & Key Concepts
|
||||
|
||||
### Technology Stack
|
||||
- **Framework**: Astro (v5.0.5) - Static site generator with React integration
|
||||
- **UI**: React (v19.0.0) + Tailwind CSS v4
|
||||
- **Runtime**: Bun
|
||||
- **Styling**: Tailwind CSS v4 with Vite plugin
|
||||
|
||||
### Project Structure
|
||||
- `/src/pages/` - Astro pages (single `index.astro` page)
|
||||
- `/src/components/` - React components for UI sections
|
||||
- `Hero.tsx` - Landing hero section
|
||||
- `Features.tsx` - Feature showcase
|
||||
- `GettingStarted.tsx` - Installation and setup guide
|
||||
- `Screenshots.tsx` - Product screenshots gallery
|
||||
- `Footer.tsx` - Page footer
|
||||
- `/src/layouts/` - Layout wrapper components
|
||||
- `/public/assets/` - Static assets (shared with main project)
|
||||
- `/public/favicon.svg` - Site favicon
|
||||
|
||||
### Key Implementation Details
|
||||
|
||||
1. **Single Page Application**: The entire website is a single page (`index.astro`) composed of React components.
|
||||
|
||||
2. **Responsive Design**: All components use Tailwind CSS for responsive layouts with mobile-first approach.
|
||||
|
||||
3. **Asset Sharing**: Screenshots and images are shared with the main Gitea Mirror project (located in `/public/assets/`).
|
||||
|
||||
4. **Component Pattern**: Each major section is a separate React component with TypeScript interfaces for props.
|
||||
|
||||
### Development Guidelines
|
||||
|
||||
**When updating content:**
|
||||
- Hero section copy is in `Hero.tsx`
|
||||
- Features are defined in `Features.tsx` as an array
|
||||
- Getting started steps are in `GettingStarted.tsx`
|
||||
- Screenshots are referenced from `/public/assets/`
|
||||
|
||||
**When adding new sections:**
|
||||
1. Create a new component in `/src/components/`
|
||||
2. Import and add it to `index.astro`
|
||||
3. Follow the existing pattern of full-width sections with container constraints
|
||||
|
||||
**Styling conventions:**
|
||||
- Use Tailwind CSS v4 classes exclusively
|
||||
- Follow the existing color scheme (zinc/neutral grays, blue accents)
|
||||
- Maintain consistent spacing using Tailwind's spacing scale
|
||||
- Keep mobile responsiveness in mind
|
||||
|
||||
### Common Tasks
|
||||
|
||||
**Updating screenshots:**
|
||||
- Screenshots should match those in the main application
|
||||
- Place new screenshots in `/public/assets/`
|
||||
- Update the `Screenshots.tsx` component to reference new images
|
||||
|
||||
**Modifying feature list:**
|
||||
- Edit the `features` array in `Features.tsx`
|
||||
- Each feature needs: icon, title, and description
|
||||
- Icons come from `lucide-react`
|
||||
|
||||
**Changing getting started steps:**
|
||||
- Edit the content in `GettingStarted.tsx`
|
||||
- Docker and direct installation tabs are separate sections
|
||||
- Code blocks use `<pre>` and `<code>` tags with Tailwind styling
|
||||
|
||||
## Relationship to Main Project
|
||||
|
||||
This website showcases the Gitea Mirror application located in the parent directory. When making updates:
|
||||
- Ensure feature descriptions match actual capabilities
|
||||
- Keep version numbers and requirements synchronized
|
||||
- Use the same screenshots as the main application's documentation
|
||||
- Maintain consistent branding and messaging
|
||||
@@ -1,19 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<defs>
|
||||
<linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#f97316;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#ea580c;stop-opacity:1" />
|
||||
<linearGradient id="gitea-mirror-gradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#3b82f6;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#8b5cf6;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<\!-- Background circle -->
|
||||
<circle cx="16" cy="16" r="15" fill="url(#grad)" />
|
||||
|
||||
<\!-- Mirror/sync icon -->
|
||||
<rect width="32" height="32" rx="6" fill="url(#gitea-mirror-gradient)"/>
|
||||
<g fill="white" transform="translate(16, 16)">
|
||||
<\!-- First arrow (top) -->
|
||||
<path d="M -8 -2 L -3 -2 L -3 -5 L 2 0 L -3 5 L -3 2 L -8 2 C -8 2 -10 2 -10 0 C -10 -2 -8 -2 -8 -2 Z" />
|
||||
<\!-- Second arrow (bottom) -->
|
||||
<path d="M 8 2 L 3 2 L 3 5 L -2 0 L 3 -5 L 3 -2 L 8 -2 C 8 -2 10 -2 10 0 C 10 2 8 2 8 2 Z" opacity="0.8" />
|
||||
<path d="M 8 2 L 3 2 L 3 5 L -2 0 L 3 -5 L 3 -2 L 8 -2 C 8 -2 10 -2 10 0 C 10 2 8 2 8 2 Z" opacity="0.9" />
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 781 B After Width: | Height: | Size: 686 B |
8
www/public/robots.txt
Normal file
8
www/public/robots.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
# Robots.txt for Gitea Mirror
|
||||
User-agent: *
|
||||
Allow: /
|
||||
Sitemap: https://gitea-mirror.com/sitemap.xml
|
||||
|
||||
# Crawl-delay for responsible crawling
|
||||
User-agent: *
|
||||
Crawl-delay: 1
|
||||
9
www/public/sitemap.xml
Normal file
9
www/public/sitemap.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://gitea-mirror.com/</loc>
|
||||
<lastmod>2025-01-08</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
</urlset>
|
||||
69
www/src/components/CTA.tsx
Normal file
69
www/src/components/CTA.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
import React from 'react';
|
||||
import { Button } from './ui/button';
|
||||
import { ArrowRight, Star, GitFork, Users } from 'lucide-react';
|
||||
|
||||
export function CTA() {
|
||||
return (
|
||||
<section className="py-16 sm:py-24 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<div className="relative overflow-hidden rounded-2xl sm:rounded-3xl bg-gradient-to-r from-blue-600 to-purple-600 p-6 sm:p-8 md:p-12 text-center">
|
||||
{/* Background pattern */}
|
||||
<div className="absolute inset-0 bg-grid-white/10 [mask-image:linear-gradient(0deg,transparent,rgba(255,255,255,0.5))]" />
|
||||
|
||||
<div className="relative">
|
||||
<h2 className="text-2xl sm:text-3xl md:text-4xl font-bold text-white mb-4">
|
||||
Start Protecting Your Code Today
|
||||
</h2>
|
||||
<p className="text-base sm:text-lg text-white/90 mb-6 sm:mb-8 max-w-2xl mx-auto px-4">
|
||||
Join developers who trust Gitea Mirror to keep their repositories safe and accessible.
|
||||
Free, open source, and ready to deploy.
|
||||
</p>
|
||||
|
||||
{/* Stats */}
|
||||
<div className="flex flex-wrap items-center justify-center gap-4 sm:gap-6 md:gap-8 mb-6 sm:mb-8 text-white/80 text-sm sm:text-base">
|
||||
<div className="flex items-center gap-2">
|
||||
<Star className="w-4 h-4 sm:w-5 sm:h-5" />
|
||||
<span className="font-semibold">500+ Stars</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<GitFork className="w-4 h-4 sm:w-5 sm:h-5" />
|
||||
<span className="font-semibold">50+ Forks</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Users className="w-4 h-4 sm:w-5 sm:h-5" />
|
||||
<span className="font-semibold">Active Community</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row items-center justify-center gap-3 sm:gap-4">
|
||||
<Button size="lg" variant="secondary" className="group w-full sm:w-auto min-h-[48px]" asChild>
|
||||
<a href="https://github.com/RayLabsHQ/gitea-mirror" target="_blank" rel="noopener noreferrer">
|
||||
Get Started Now
|
||||
<ArrowRight className="ml-2 h-4 w-4 transition-transform group-hover:translate-x-1" />
|
||||
</a>
|
||||
</Button>
|
||||
<Button size="lg" variant="ghost" className="text-white hover:bg-white/20 w-full sm:w-auto min-h-[48px]" asChild>
|
||||
<a href="https://github.com/RayLabsHQ/gitea-mirror/discussions" target="_blank" rel="noopener noreferrer">
|
||||
Join Community
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Open source note */}
|
||||
<div className="mt-8 sm:mt-12 text-center">
|
||||
<p className="text-xs sm:text-sm text-muted-foreground">
|
||||
Gitea Mirror is licensed under GPL-3.0.
|
||||
<a href="https://github.com/RayLabsHQ/gitea-mirror/blob/main/LICENSE"
|
||||
className="ml-1 underline hover:text-foreground transition-colors"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
View License
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
127
www/src/components/Features.tsx
Normal file
127
www/src/components/Features.tsx
Normal file
@@ -0,0 +1,127 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
RefreshCw,
|
||||
Building2,
|
||||
FolderTree,
|
||||
Activity,
|
||||
Lock,
|
||||
Heart,
|
||||
Zap,
|
||||
Shield,
|
||||
GitBranch
|
||||
} 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-blue-500 to-cyan-500"
|
||||
},
|
||||
{
|
||||
title: "Bulk Operations",
|
||||
description: "Mirror entire organizations or user accounts with a single configuration.",
|
||||
icon: Building2,
|
||||
gradient: "from-purple-500 to-pink-500"
|
||||
},
|
||||
{
|
||||
title: "Preserve Structure",
|
||||
description: "Maintain your GitHub organization structure or customize how repos are organized.",
|
||||
icon: FolderTree,
|
||||
gradient: "from-green-500 to-emerald-500"
|
||||
},
|
||||
{
|
||||
title: "Real-time Status",
|
||||
description: "Monitor mirror progress with live updates and detailed activity logs.",
|
||||
icon: Activity,
|
||||
gradient: "from-orange-500 to-red-500"
|
||||
},
|
||||
{
|
||||
title: "Secure & Private",
|
||||
description: "Self-hosted solution keeps your code on your infrastructure with full control.",
|
||||
icon: Lock,
|
||||
gradient: "from-indigo-500 to-purple-500"
|
||||
},
|
||||
{
|
||||
title: "Open Source",
|
||||
description: "Free, transparent, and community-driven development. Contribute and customize.",
|
||||
icon: Heart,
|
||||
gradient: "from-pink-500 to-rose-500"
|
||||
}
|
||||
];
|
||||
|
||||
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="bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent 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-card hover:shadow-xl transition-all duration-300 hover:-translate-y-1"
|
||||
>
|
||||
<div className="absolute inset-0 bg-gradient-to-r opacity-0 group-hover:opacity-5 rounded-xl sm:rounded-2xl transition-opacity duration-300"
|
||||
style={{
|
||||
backgroundImage: `linear-gradient(to right, var(--tw-gradient-stops))`,
|
||||
'--tw-gradient-from': feature.gradient.split(' ')[1],
|
||||
'--tw-gradient-to': feature.gradient.split(' ')[3],
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className={`inline-flex p-2.5 sm:p-3 rounded-lg bg-gradient-to-r ${feature.gradient} mb-3 sm:mb-4`}>
|
||||
<Icon className="w-5 h-5 sm:w-6 sm:h-6 text-white" />
|
||||
</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>
|
||||
|
||||
{/* Additional feature highlights */}
|
||||
<div className="mt-12 sm:mt-16 grid grid-cols-1 sm:grid-cols-3 gap-4 sm:gap-6 lg:gap-8 p-6 sm:p-8 rounded-xl sm:rounded-2xl bg-muted/30">
|
||||
<div className="flex items-center gap-3 sm:gap-4">
|
||||
<div className="p-2 rounded-lg bg-blue-500/10 flex-shrink-0">
|
||||
<Zap className="w-4 h-4 sm:w-5 sm:h-5 text-blue-600 dark:text-blue-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-semibold text-sm sm:text-base">Lightning Fast</p>
|
||||
<p className="text-xs sm:text-sm text-muted-foreground">Optimized for speed</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 sm:gap-4">
|
||||
<div className="p-2 rounded-lg bg-green-500/10 flex-shrink-0">
|
||||
<Shield className="w-4 h-4 sm:w-5 sm:h-5 text-green-600 dark:text-green-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-semibold text-sm sm:text-base">Enterprise Ready</p>
|
||||
<p className="text-xs sm:text-sm text-muted-foreground">Scale with confidence</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 sm:gap-4">
|
||||
<div className="p-2 rounded-lg bg-purple-500/10 flex-shrink-0">
|
||||
<GitBranch className="w-4 h-4 sm:w-5 sm:h-5 text-purple-600 dark:text-purple-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-semibold text-sm sm:text-base">Full Git Support</p>
|
||||
<p className="text-xs sm:text-sm text-muted-foreground">All features preserved</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
81
www/src/components/Footer.tsx
Normal file
81
www/src/components/Footer.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
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="/assets/logo-no-bg.png" alt="Gitea Mirror" className="w-6 h-6 sm:w-8 sm:h-8" />
|
||||
<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>
|
||||
);
|
||||
}
|
||||
118
www/src/components/Header.tsx
Normal file
118
www/src/components/Header.tsx
Normal file
@@ -0,0 +1,118 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { ThemeToggle } from './ThemeToggle';
|
||||
import { Github, Menu, X } from 'lucide-react';
|
||||
import { Button } from './ui/button';
|
||||
|
||||
export function Header() {
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
const [isScrolled, setIsScrolled] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
setIsScrolled(window.scrollY > 10);
|
||||
};
|
||||
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
return () => window.removeEventListener('scroll', handleScroll);
|
||||
}, []);
|
||||
|
||||
const navLinks = [
|
||||
{ href: '#features', label: 'Features' },
|
||||
{ href: '#screenshots', label: 'Screenshots' },
|
||||
{ href: '#installation', label: 'Installation' }
|
||||
];
|
||||
|
||||
const handleNavClick = () => {
|
||||
setIsMenuOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<header className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${
|
||||
isScrolled ? 'backdrop-blur-lg bg-background/80 border-b shadow-sm' : 'bg-background/50'
|
||||
}`}>
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex items-center justify-between h-16">
|
||||
{/* Logo */}
|
||||
<a href="#" className="flex items-center gap-2 group">
|
||||
<img
|
||||
src="/assets/logo-no-bg.png"
|
||||
alt="Gitea Mirror"
|
||||
className="w-8 h-8 transition-transform group-hover:scale-110"
|
||||
/>
|
||||
<span className="font-semibold text-lg hidden sm:block">Gitea Mirror</span>
|
||||
</a>
|
||||
|
||||
{/* Desktop Navigation */}
|
||||
<nav className="hidden md:flex items-center gap-8">
|
||||
{navLinks.map((link) => (
|
||||
<a
|
||||
key={link.href}
|
||||
href={link.href}
|
||||
className="text-sm font-medium text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
{link.label}
|
||||
</a>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
{/* Desktop Actions */}
|
||||
<div className="hidden md:flex items-center gap-4">
|
||||
<ThemeToggle />
|
||||
<Button variant="outline" size="sm" asChild>
|
||||
<a href="https://github.com/RayLabsHQ/gitea-mirror" target="_blank" rel="noopener noreferrer">
|
||||
<Github className="w-4 h-4 mr-2" />
|
||||
Star on GitHub
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Mobile Actions */}
|
||||
<div className="flex md:hidden items-center gap-2">
|
||||
<ThemeToggle />
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setIsMenuOpen(!isMenuOpen)}
|
||||
aria-label="Toggle menu"
|
||||
className="w-10 h-10"
|
||||
>
|
||||
{isMenuOpen ? (
|
||||
<X className="h-5 w-5" />
|
||||
) : (
|
||||
<Menu className="h-5 w-5" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu */}
|
||||
<div className={`md:hidden transition-all duration-300 ease-in-out ${
|
||||
isMenuOpen ? 'max-h-96 opacity-100' : 'max-h-0 opacity-0 overflow-hidden'
|
||||
}`}>
|
||||
<div className="bg-background/95 backdrop-blur-lg border-b">
|
||||
<nav className="px-4 py-4 space-y-3">
|
||||
{navLinks.map((link) => (
|
||||
<a
|
||||
key={link.href}
|
||||
href={link.href}
|
||||
onClick={handleNavClick}
|
||||
className="block py-2 text-base font-medium text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
{link.label}
|
||||
</a>
|
||||
))}
|
||||
<div className="pt-3 border-t">
|
||||
<Button variant="outline" size="sm" className="w-full" asChild>
|
||||
<a href="https://github.com/RayLabsHQ/gitea-mirror" target="_blank" rel="noopener noreferrer">
|
||||
<Github className="w-4 h-4 mr-2" />
|
||||
Star on GitHub
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
75
www/src/components/Hero.tsx
Normal file
75
www/src/components/Hero.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import React from 'react';
|
||||
import { Button } from './ui/button';
|
||||
import { ArrowRight, Github, Shield, RefreshCw } from 'lucide-react';
|
||||
|
||||
export function Hero() {
|
||||
return (
|
||||
<section className="relative min-h-[100vh] pt-20 pb-10 flex items-center justify-center px-4 sm:px-6 lg:px-8 overflow-hidden">
|
||||
{/* Background gradients */}
|
||||
<div className="absolute inset-0 -z-10">
|
||||
<div className="absolute top-20 -left-4 w-48 h-48 sm:w-72 sm:h-72 bg-purple-300 rounded-full mix-blend-multiply filter blur-xl opacity-20 animate-blob dark:opacity-10"></div>
|
||||
<div className="absolute top-20 -right-4 w-48 h-48 sm:w-72 sm:h-72 bg-yellow-300 rounded-full mix-blend-multiply filter blur-xl opacity-20 animate-blob animation-delay-2000 dark:opacity-10"></div>
|
||||
<div className="absolute -bottom-8 left-10 w-48 h-48 sm:w-72 sm:h-72 bg-pink-300 rounded-full mix-blend-multiply filter blur-xl opacity-20 animate-blob animation-delay-4000 dark:opacity-10"></div>
|
||||
</div>
|
||||
|
||||
<div className="max-w-7xl mx-auto text-center w-full">
|
||||
<div className="mb-6 sm:mb-8 flex justify-center">
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-blue-600 to-purple-600 rounded-full blur-lg opacity-75 animate-pulse"></div>
|
||||
<img
|
||||
src="/assets/logo-no-bg.png"
|
||||
alt="Gitea Mirror Logo"
|
||||
className="relative w-20 h-20 sm:w-24 sm:h-24 md:w-32 md:h-32"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1 className="text-3xl xs:text-4xl sm:text-5xl md:text-6xl lg:text-7xl font-bold tracking-tight leading-tight">
|
||||
<span className="bg-gradient-to-r from-gray-900 to-gray-600 dark:from-white dark:to-gray-400 bg-clip-text text-transparent">
|
||||
Keep Your Code
|
||||
</span>
|
||||
<br />
|
||||
<span className="bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
|
||||
Safe & Synced
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
<p className="mt-4 sm:mt-6 text-base sm:text-lg md:text-xl text-muted-foreground max-w-3xl mx-auto px-4">
|
||||
Automatically mirror your GitHub repositories to self-hosted Gitea.
|
||||
Never lose access to your code with continuous backup and synchronization.
|
||||
</p>
|
||||
|
||||
<div className="mt-6 sm:mt-8 flex flex-wrap items-center justify-center gap-3 text-xs sm:text-sm text-muted-foreground px-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Shield className="w-3 h-3 sm:w-4 sm:h-4" />
|
||||
<span>Self-Hosted</span>
|
||||
</div>
|
||||
<span className="text-gray-300 dark:text-gray-700 hidden xs:inline">•</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<RefreshCw className="w-3 h-3 sm:w-4 sm:h-4" />
|
||||
<span>Auto-Sync</span>
|
||||
</div>
|
||||
<span className="text-gray-300 dark:text-gray-700 hidden xs:inline">•</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<Github className="w-3 h-3 sm:w-4 sm:h-4" />
|
||||
<span>Open Source</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 sm:mt-10 flex flex-col sm:flex-row items-center justify-center gap-3 sm:gap-4 px-4">
|
||||
<Button size="lg" className="group w-full sm:w-auto min-h-[48px] text-base" asChild>
|
||||
<a href="https://github.com/RayLabsHQ/gitea-mirror" target="_blank" rel="noopener noreferrer">
|
||||
Get Started
|
||||
<ArrowRight className="ml-2 h-4 w-4 transition-transform group-hover:translate-x-1" />
|
||||
</a>
|
||||
</Button>
|
||||
<Button size="lg" variant="outline" className="w-full sm:w-auto min-h-[48px] text-base" asChild>
|
||||
<a href="#features">
|
||||
View Features
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
165
www/src/components/Installation.tsx
Normal file
165
www/src/components/Installation.tsx
Normal file
@@ -0,0 +1,165 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Button } from './ui/button';
|
||||
import { Copy, Check, Terminal, Container, Cloud } from 'lucide-react';
|
||||
|
||||
type InstallMethod = 'docker' | 'manual' | 'proxmox';
|
||||
|
||||
export function Installation() {
|
||||
const [activeMethod, setActiveMethod] = useState<InstallMethod>('docker');
|
||||
const [copiedCommand, setCopiedCommand] = useState<string | null>(null);
|
||||
|
||||
const copyToClipboard = async (text: string, commandId: string) => {
|
||||
await navigator.clipboard.writeText(text);
|
||||
setCopiedCommand(commandId);
|
||||
setTimeout(() => setCopiedCommand(null), 2000);
|
||||
};
|
||||
|
||||
const installMethods = {
|
||||
docker: {
|
||||
icon: Container,
|
||||
title: "Docker",
|
||||
description: "Recommended for most users",
|
||||
steps: [
|
||||
{
|
||||
title: "Clone the repository",
|
||||
command: "git clone https://github.com/RayLabsHQ/gitea-mirror.git\ncd gitea-mirror",
|
||||
id: "docker-clone"
|
||||
},
|
||||
{
|
||||
title: "Start with Docker Compose",
|
||||
command: "docker compose -f docker-compose.alt.yml up -d",
|
||||
id: "docker-start"
|
||||
},
|
||||
{
|
||||
title: "Access the application",
|
||||
command: "# Open http://localhost:4321 in your browser",
|
||||
id: "docker-access"
|
||||
}
|
||||
]
|
||||
},
|
||||
manual: {
|
||||
icon: Terminal,
|
||||
title: "Manual",
|
||||
description: "For development or custom setups",
|
||||
steps: [
|
||||
{
|
||||
title: "Install Bun runtime",
|
||||
command: "curl -fsSL https://bun.sh/install | bash",
|
||||
id: "manual-bun"
|
||||
},
|
||||
{
|
||||
title: "Clone and setup",
|
||||
command: "git clone https://github.com/RayLabsHQ/gitea-mirror.git\ncd gitea-mirror\nbun run setup",
|
||||
id: "manual-setup"
|
||||
},
|
||||
{
|
||||
title: "Start the application",
|
||||
command: "bun run dev",
|
||||
id: "manual-start"
|
||||
}
|
||||
]
|
||||
},
|
||||
proxmox: {
|
||||
icon: Cloud,
|
||||
title: "Proxmox LXC",
|
||||
description: "One-click install for Proxmox VE",
|
||||
steps: [
|
||||
{
|
||||
title: "Run the installation script",
|
||||
command: 'bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/gitea-mirror.sh)"',
|
||||
id: "proxmox-install"
|
||||
},
|
||||
{
|
||||
title: "Follow the prompts",
|
||||
command: "# The script will guide you through the setup",
|
||||
id: "proxmox-follow"
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section id="installation" className="py-16 sm:py-24 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-4xl 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">
|
||||
Get Started in Minutes
|
||||
</h2>
|
||||
<p className="mt-4 text-base sm:text-lg text-muted-foreground">
|
||||
Choose your preferred installation method
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Installation method tabs */}
|
||||
<div className="flex flex-col sm:flex-row flex-wrap justify-center gap-3 sm:gap-4 mb-8 sm:mb-12">
|
||||
{(Object.entries(installMethods) as [InstallMethod, typeof installMethods[InstallMethod]][]).map(([method, config]) => {
|
||||
const Icon = config.icon;
|
||||
return (
|
||||
<button
|
||||
key={method}
|
||||
onClick={() => setActiveMethod(method)}
|
||||
className={`flex items-center gap-3 px-4 sm:px-6 py-3 rounded-lg border transition-all min-h-[60px] ${
|
||||
activeMethod === method
|
||||
? 'bg-primary text-primary-foreground border-primary'
|
||||
: 'bg-card hover:bg-muted border-border'
|
||||
}`}
|
||||
>
|
||||
<Icon className="w-5 h-5 flex-shrink-0" />
|
||||
<div className="text-left">
|
||||
<p className="font-semibold text-sm sm:text-base">{config.title}</p>
|
||||
<p className={`text-xs ${activeMethod === method ? 'text-primary-foreground/80' : 'text-muted-foreground'}`}>
|
||||
{config.description}
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Installation steps */}
|
||||
<div className="space-y-4 sm:space-y-6">
|
||||
{installMethods[activeMethod].steps.map((step, index) => (
|
||||
<div key={step.id} className="relative">
|
||||
<div className="flex items-start gap-3 sm:gap-4">
|
||||
<div className="flex-shrink-0 w-8 h-8 rounded-full bg-primary/10 flex items-center justify-center">
|
||||
<span className="text-sm font-semibold text-primary">{index + 1}</span>
|
||||
</div>
|
||||
<div className="flex-grow min-w-0">
|
||||
<h3 className="font-semibold mb-2 text-sm sm:text-base">{step.title}</h3>
|
||||
<div className="relative group">
|
||||
<pre className="bg-muted/50 rounded-lg p-3 sm:p-4 pr-12 overflow-x-auto text-xs sm:text-sm">
|
||||
<code className="break-all sm:break-normal">{step.command}</code>
|
||||
</pre>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="absolute top-1 right-1 sm:top-2 sm:right-2 w-8 h-8 sm:w-9 sm:h-9 opacity-100 sm:opacity-0 sm:group-hover:opacity-100 transition-opacity"
|
||||
onClick={() => copyToClipboard(step.command, step.id)}
|
||||
>
|
||||
{copiedCommand === step.id ? (
|
||||
<Check className="h-3 w-3 sm:h-4 sm:w-4 text-green-600" />
|
||||
) : (
|
||||
<Copy className="h-3 w-3 sm:h-4 sm:w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{index < installMethods[activeMethod].steps.length - 1 && (
|
||||
<div className="absolute left-4 top-10 bottom-0 w-[1px] bg-border -z-10" />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Additional info */}
|
||||
<div className="mt-8 sm:mt-12 p-4 sm:p-6 rounded-lg bg-muted/30 border">
|
||||
<p className="text-xs sm:text-sm text-muted-foreground">
|
||||
<strong className="text-foreground">First user becomes admin.</strong> After installation,
|
||||
create your account and configure GitHub and Gitea connections through the web interface.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
200
www/src/components/Screenshots.tsx
Normal file
200
www/src/components/Screenshots.tsx
Normal file
@@ -0,0 +1,200 @@
|
||||
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-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-[16/10] overflow-hidden rounded-lg sm:rounded-2xl bg-gradient-to-br from-gray-900 to-gray-800 dark:from-gray-800 dark:to-gray-900 shadow-2xl">
|
||||
<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>
|
||||
);
|
||||
}
|
||||
40
www/src/components/ThemeToggle.tsx
Normal file
40
www/src/components/ThemeToggle.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Moon, Sun } from 'lucide-react';
|
||||
import { Button } from './ui/button';
|
||||
|
||||
export function ThemeToggle() {
|
||||
const [theme, setTheme] = useState<'light' | 'dark'>('light');
|
||||
|
||||
useEffect(() => {
|
||||
// Check for saved theme preference or default to light
|
||||
const savedTheme = localStorage.getItem('theme') as 'light' | 'dark' | null;
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
const initialTheme = savedTheme || (prefersDark ? 'dark' : 'light');
|
||||
|
||||
setTheme(initialTheme);
|
||||
document.documentElement.classList.toggle('dark', initialTheme === 'dark');
|
||||
}, []);
|
||||
|
||||
const toggleTheme = () => {
|
||||
const newTheme = theme === 'light' ? 'dark' : 'light';
|
||||
setTheme(newTheme);
|
||||
localStorage.setItem('theme', newTheme);
|
||||
document.documentElement.classList.toggle('dark', newTheme === 'dark');
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={toggleTheme}
|
||||
className="w-10 h-10 rounded-full"
|
||||
aria-label={`Switch to ${theme === 'light' ? 'dark' : 'light'} mode`}
|
||||
>
|
||||
{theme === 'light' ? (
|
||||
<Moon className="h-5 w-5 transition-all" />
|
||||
) : (
|
||||
<Sun className="h-5 w-5 transition-all" />
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
@@ -1,343 +1,166 @@
|
||||
---
|
||||
import '../styles/global.css';
|
||||
import { Button } from '../components/ui/button';
|
||||
import { Header } from '../components/Header';
|
||||
import { Hero } from '../components/Hero';
|
||||
import { Features } from '../components/Features';
|
||||
import { Screenshots } from '../components/Screenshots';
|
||||
import { Installation } from '../components/Installation';
|
||||
import { CTA } from '../components/CTA';
|
||||
import { Footer } from '../components/Footer';
|
||||
|
||||
const features = [
|
||||
{
|
||||
title: "Automated Mirroring",
|
||||
description: "Set it and forget it. Automatically sync your GitHub repositories to Gitea on a schedule.",
|
||||
icon: "🔄"
|
||||
},
|
||||
{
|
||||
title: "Bulk Operations",
|
||||
description: "Mirror entire organizations or user accounts with a single configuration.",
|
||||
icon: "📦"
|
||||
},
|
||||
{
|
||||
title: "Preserve Structure",
|
||||
description: "Maintain your GitHub organization structure or customize how repos are organized.",
|
||||
icon: "🏗️"
|
||||
},
|
||||
{
|
||||
title: "Real-time Status",
|
||||
description: "Monitor mirror progress with live updates and detailed activity logs.",
|
||||
icon: "📊"
|
||||
},
|
||||
{
|
||||
title: "Secure & Private",
|
||||
description: "Self-hosted solution keeps your code on your infrastructure.",
|
||||
icon: "🔒"
|
||||
},
|
||||
{
|
||||
title: "Open Source",
|
||||
description: "Free, transparent, and community-driven development.",
|
||||
icon: "💚"
|
||||
}
|
||||
];
|
||||
const siteUrl = 'https://gitea-mirror.com';
|
||||
const title = 'Gitea Mirror - Automated GitHub to Gitea Repository Mirroring & Backup';
|
||||
const description = 'Automatically mirror and backup your GitHub repositories to self-hosted Gitea. Keep your code safe with scheduled syncing, bulk operations, and real-time monitoring. Free and open source.';
|
||||
const keywords = 'github backup, gitea mirror, repository sync, github to gitea, git mirror, code backup, self-hosted git, repository migration, github mirror tool, gitea sync, automated backup, github repository backup, git repository mirror, self-hosted backup solution';
|
||||
|
||||
const steps = [
|
||||
{ number: "1", title: "Install", description: "Deploy with Docker or run directly with Bun" },
|
||||
{ number: "2", title: "Connect", description: "Add your GitHub and Gitea credentials" },
|
||||
{ number: "3", title: "Configure", description: "Select repositories or organizations to mirror" },
|
||||
{ number: "4", title: "Relax", description: "Let Gitea Mirror handle the synchronization" }
|
||||
];
|
||||
|
||||
const screenshots = [
|
||||
{ src: "/assets/dashboard.png", alt: "Dashboard view", mobile: "/assets/dashboard_mobile.png" },
|
||||
{ src: "/assets/repositories.png", alt: "Repository management", mobile: "/assets/repositories_mobile.png" },
|
||||
{ src: "/assets/configuration.png", alt: "Configuration interface", mobile: "/assets/configuration_mobile.png" },
|
||||
{ src: "/assets/activity.png", alt: "Activity monitoring", mobile: "/assets/activity_mobile.png" }
|
||||
];
|
||||
// Structured data for SEO
|
||||
const structuredData = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "SoftwareApplication",
|
||||
"name": "Gitea Mirror",
|
||||
"applicationCategory": "DeveloperApplication",
|
||||
"operatingSystem": "Linux, macOS, Windows",
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"price": "0",
|
||||
"priceCurrency": "USD"
|
||||
},
|
||||
"description": description,
|
||||
"url": siteUrl,
|
||||
"author": {
|
||||
"@type": "Organization",
|
||||
"name": "RayLabs",
|
||||
"url": "https://github.com/RayLabsHQ"
|
||||
},
|
||||
"softwareVersion": "2.22.0",
|
||||
"screenshot": [
|
||||
`${siteUrl}/assets/dashboard.png`,
|
||||
`${siteUrl}/assets/repositories.png`,
|
||||
`${siteUrl}/assets/organisation.png`
|
||||
],
|
||||
"featureList": [
|
||||
"Automated repository mirroring",
|
||||
"Bulk organization sync",
|
||||
"Real-time monitoring",
|
||||
"Self-hosted solution",
|
||||
"Open source"
|
||||
],
|
||||
"softwareRequirements": "Docker or Bun runtime"
|
||||
};
|
||||
---
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="icon" type="image/png" href="/assets/logo.png" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
|
||||
<!-- SEO Meta Tags -->
|
||||
<title>Gitea Mirror - Automated GitHub to Gitea Repository Backup & Sync</title>
|
||||
<meta name="description" content="Automatically mirror and backup your GitHub repositories to self-hosted Gitea. Keep your code safe with scheduled syncing, bulk operations, and real-time monitoring." />
|
||||
<meta name="keywords" content="github backup, gitea mirror, repository sync, github to gitea, git mirror, code backup, self-hosted git, repository migration" />
|
||||
<meta name="robots" content="index, follow" />
|
||||
<!-- Primary Meta Tags -->
|
||||
<title>{title}</title>
|
||||
<meta name="title" content={title} />
|
||||
<meta name="description" content={description} />
|
||||
<meta name="keywords" content={keywords} />
|
||||
<meta name="robots" content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1" />
|
||||
<meta name="author" content="RayLabs" />
|
||||
|
||||
<!-- Open Graph -->
|
||||
<meta property="og:title" content="Gitea Mirror - Automated GitHub to Gitea Repository Backup" />
|
||||
<meta property="og:description" content="Keep your GitHub repositories safely backed up to your self-hosted Gitea instance with automated mirroring." />
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:image" content="/assets/dashboard.png" />
|
||||
<meta property="og:url" content={siteUrl} />
|
||||
<meta property="og:title" content={title} />
|
||||
<meta property="og:description" content={description} />
|
||||
<meta property="og:image" content={`${siteUrl}/assets/dashboard.png`} />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta property="og:site_name" content="Gitea Mirror" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
|
||||
<!-- Twitter Card -->
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="Gitea Mirror - GitHub to Gitea Backup Solution" />
|
||||
<meta name="twitter:description" content="Automated repository mirroring from GitHub to self-hosted Gitea instances." />
|
||||
<meta name="twitter:image" content="/assets/dashboard.png" />
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content={siteUrl} />
|
||||
<meta property="twitter:title" content={title} />
|
||||
<meta property="twitter:description" content={description} />
|
||||
<meta property="twitter:image" content={`${siteUrl}/assets/dashboard.png`} />
|
||||
<meta name="twitter:creator" content="@RayLabsHQ" />
|
||||
|
||||
<!-- Canonical URL -->
|
||||
<link rel="canonical" href="https://gitea-mirror.com" />
|
||||
<link rel="canonical" href={siteUrl} />
|
||||
|
||||
<!-- Additional Meta Tags -->
|
||||
<meta name="theme-color" content="#3b82f6" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||
<meta name="apple-mobile-web-app-title" content="Gitea Mirror" />
|
||||
|
||||
<!-- Structured Data -->
|
||||
<script type="application/ld+json" set:html={JSON.stringify(structuredData)} />
|
||||
|
||||
<!-- Preconnect to external domains -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="dns-prefetch" href="https://github.com" />
|
||||
|
||||
<!-- Theme detection script (prevent flash) -->
|
||||
<script is:inline>
|
||||
const theme = localStorage.getItem('theme') ||
|
||||
(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
|
||||
document.documentElement.classList.toggle('dark', theme === 'dark');
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body class="min-h-screen bg-background text-foreground antialiased">
|
||||
<!-- Hero Section -->
|
||||
<section class="relative overflow-hidden px-6 py-24 sm:py-32 lg:px-8">
|
||||
<div class="absolute inset-0 -z-10 bg-gradient-to-br from-primary/5 via-transparent to-transparent"></div>
|
||||
<div class="animate-float absolute -top-20 -right-20 h-72 w-72 rounded-full bg-primary/10 blur-3xl"></div>
|
||||
<div class="animate-float-delayed absolute -bottom-20 -left-20 h-72 w-72 rounded-full bg-primary/10 blur-3xl"></div>
|
||||
<Header client:load />
|
||||
|
||||
<div class="mx-auto max-w-7xl text-center">
|
||||
<div class="animate-fade-in">
|
||||
<h1 class="text-4xl font-bold tracking-tight sm:text-6xl bg-gradient-to-r from-foreground to-foreground/70 bg-clip-text">
|
||||
Keep Your GitHub Code
|
||||
<span class="block text-primary">Safe & Synced</span>
|
||||
</h1>
|
||||
<p class="mt-6 text-lg leading-8 text-muted-foreground max-w-2xl mx-auto">
|
||||
Automatically mirror your GitHub repositories to self-hosted Gitea.
|
||||
Never worry about losing access to your code with continuous backup and synchronization.
|
||||
</p>
|
||||
<div class="mt-10 flex items-center justify-center gap-4 flex-wrap">
|
||||
<Button size="lg" className="animate-fade-in-up" asChild>
|
||||
<a href="https://github.com/yourusername/gitea-mirror" target="_blank" rel="noopener noreferrer">
|
||||
Get Started Free
|
||||
<svg className="ml-2 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</a>
|
||||
</Button>
|
||||
<Button size="lg" variant="outline" className="animate-fade-in-up animation-delay-200" asChild>
|
||||
<a href="https://github.com/yourusername/gitea-mirror" target="_blank" rel="noopener noreferrer">
|
||||
View on GitHub
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<main>
|
||||
<Hero client:load />
|
||||
<Features client:load />
|
||||
<Screenshots client:load />
|
||||
<Installation client:load />
|
||||
<CTA client:load />
|
||||
</main>
|
||||
|
||||
<!-- Problem/Solution Section -->
|
||||
<section class="px-6 py-24 sm:py-32 lg:px-8 bg-muted/30">
|
||||
<div class="mx-auto max-w-7xl">
|
||||
<div class="mx-auto max-w-2xl text-center">
|
||||
<h2 class="text-3xl font-bold tracking-tight sm:text-4xl">Why You Need Repository Mirroring</h2>
|
||||
<p class="mt-6 text-lg leading-8 text-muted-foreground">
|
||||
GitHub is great, but what happens if you lose access? Service outages, account issues, or policy changes
|
||||
can lock you out of your own code. Gitea Mirror ensures you always have a backup on infrastructure you control.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Features Grid -->
|
||||
<section class="px-6 py-24 sm:py-32 lg:px-8">
|
||||
<div class="mx-auto max-w-7xl">
|
||||
<div class="mx-auto max-w-2xl text-center mb-16">
|
||||
<h2 class="text-3xl font-bold tracking-tight sm:text-4xl">Everything You Need for Reliable Backups</h2>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{features.map((feature, index) => (
|
||||
<div
|
||||
class="group relative rounded-2xl border bg-card p-8 hover:shadow-lg transition-all duration-300 hover:-translate-y-1 animate-fade-in-up"
|
||||
style={`animation-delay: ${index * 100}ms`}
|
||||
>
|
||||
<div class="text-4xl mb-4">{feature.icon}</div>
|
||||
<h3 class="text-xl font-semibold mb-2">{feature.title}</h3>
|
||||
<p class="text-muted-foreground">{feature.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Screenshots Section -->
|
||||
<section class="px-6 py-24 sm:py-32 lg:px-8 bg-muted/30">
|
||||
<div class="mx-auto max-w-7xl">
|
||||
<div class="mx-auto max-w-2xl text-center mb-16">
|
||||
<h2 class="text-3xl font-bold tracking-tight sm:text-4xl">See It In Action</h2>
|
||||
<p class="mt-6 text-lg leading-8 text-muted-foreground">
|
||||
A clean, intuitive interface for managing your repository mirrors
|
||||
</p>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<div class="flex overflow-x-auto gap-6 pb-4 snap-x snap-mandatory scrollbar-hide">
|
||||
{screenshots.map((screenshot, index) => (
|
||||
<div
|
||||
class="flex-none w-full sm:w-[48%] lg:w-[32%] snap-center animate-fade-in-up"
|
||||
style={`animation-delay: ${index * 150}ms`}
|
||||
>
|
||||
<picture>
|
||||
<source media="(max-width: 640px)" srcset={screenshot.mobile} />
|
||||
<img
|
||||
src={screenshot.src}
|
||||
alt={screenshot.alt}
|
||||
class="rounded-lg shadow-2xl w-full"
|
||||
loading="lazy"
|
||||
/>
|
||||
</picture>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- How It Works -->
|
||||
<section class="px-6 py-24 sm:py-32 lg:px-8">
|
||||
<div class="mx-auto max-w-7xl">
|
||||
<div class="mx-auto max-w-2xl text-center mb-16">
|
||||
<h2 class="text-3xl font-bold tracking-tight sm:text-4xl">Get Started in Minutes</h2>
|
||||
</div>
|
||||
<div class="mx-auto max-w-3xl">
|
||||
<div class="space-y-8">
|
||||
{steps.map((step, index) => (
|
||||
<div
|
||||
class="flex gap-4 animate-fade-in-left"
|
||||
style={`animation-delay: ${index * 200}ms`}
|
||||
>
|
||||
<div class="flex-none">
|
||||
<div class="flex h-12 w-12 items-center justify-center rounded-full bg-primary text-primary-foreground font-bold text-lg">
|
||||
{step.number}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-auto">
|
||||
<h3 class="text-xl font-semibold mb-1">{step.title}</h3>
|
||||
<p class="text-muted-foreground">{step.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Open Source Section -->
|
||||
<section class="px-6 py-24 sm:py-32 lg:px-8 bg-muted/30">
|
||||
<div class="mx-auto max-w-7xl text-center">
|
||||
<div class="mx-auto max-w-2xl">
|
||||
<h2 class="text-3xl font-bold tracking-tight sm:text-4xl mb-6">Free & Open Source</h2>
|
||||
<p class="text-lg leading-8 text-muted-foreground mb-8">
|
||||
Gitea Mirror is completely free and open source. Self-host with confidence knowing you have
|
||||
full control over your infrastructure and data.
|
||||
</p>
|
||||
<div class="rounded-lg border bg-card/50 p-6 mb-8">
|
||||
<p class="text-sm text-muted-foreground">
|
||||
<span class="font-semibold text-foreground">Coming Soon:</span> Support development with an optional
|
||||
supporter tier ($20/year) for priority features and dedicated support.
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center justify-center gap-4 flex-wrap">
|
||||
<Button size="lg" asChild>
|
||||
<a href="https://github.com/yourusername/gitea-mirror" target="_blank" rel="noopener noreferrer">
|
||||
View Source Code
|
||||
<svg className="ml-2 h-4 w-4" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
|
||||
</svg>
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="border-t">
|
||||
<div class="mx-auto max-w-7xl px-6 py-12 lg:px-8">
|
||||
<div class="flex flex-col items-center justify-between gap-4 sm:flex-row">
|
||||
<p class="text-sm text-muted-foreground">
|
||||
© 2025 Gitea Mirror. Open source under MIT License.
|
||||
</p>
|
||||
<div class="flex gap-6">
|
||||
<a href="https://github.com/yourusername/gitea-mirror" class="text-sm text-muted-foreground hover:text-foreground transition-colors">
|
||||
GitHub
|
||||
</a>
|
||||
<a href="https://github.com/yourusername/gitea-mirror/wiki" class="text-sm text-muted-foreground hover:text-foreground transition-colors">
|
||||
Documentation
|
||||
</a>
|
||||
<a href="https://github.com/yourusername/gitea-mirror/issues" class="text-sm text-muted-foreground hover:text-foreground transition-colors">
|
||||
Support
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<Footer client:load />
|
||||
|
||||
<style>
|
||||
/* Smooth scroll for screenshot carousel */
|
||||
.scrollbar-hide {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
.scrollbar-hide::-webkit-scrollbar {
|
||||
display: none;
|
||||
/* Smooth scrolling */
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
/* CSS Animations */
|
||||
@keyframes fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
/* Blob animation */
|
||||
@keyframes blob {
|
||||
0% {
|
||||
transform: translate(0px, 0px) scale(1);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
33% {
|
||||
transform: translate(30px, -50px) scale(1.1);
|
||||
}
|
||||
66% {
|
||||
transform: translate(-20px, 20px) scale(0.9);
|
||||
}
|
||||
100% {
|
||||
transform: translate(0px, 0px) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fade-in-up {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
.animate-blob {
|
||||
animation: blob 7s infinite;
|
||||
}
|
||||
|
||||
@keyframes fade-in-left {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(-20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
.animation-delay-2000 {
|
||||
animation-delay: 2s;
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
.animation-delay-4000 {
|
||||
animation-delay: 4s;
|
||||
}
|
||||
|
||||
.animate-fade-in {
|
||||
animation: fade-in 0.6s ease-out forwards;
|
||||
}
|
||||
|
||||
.animate-fade-in-up {
|
||||
opacity: 0;
|
||||
animation: fade-in-up 0.6s ease-out forwards;
|
||||
}
|
||||
|
||||
.animate-fade-in-left {
|
||||
opacity: 0;
|
||||
animation: fade-in-left 0.6s ease-out forwards;
|
||||
}
|
||||
|
||||
.animate-float {
|
||||
animation: float 6s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.animate-float-delayed {
|
||||
animation: float 6s ease-in-out 3s infinite;
|
||||
}
|
||||
|
||||
.animation-delay-200 {
|
||||
animation-delay: 200ms;
|
||||
/* Grid background pattern */
|
||||
.bg-grid-white\/10 {
|
||||
background-image: linear-gradient(to right, rgba(255, 255, 255, 0.1) 1px, transparent 1px),
|
||||
linear-gradient(to bottom, rgba(255, 255, 255, 0.1) 1px, transparent 1px);
|
||||
background-size: 20px 20px;
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
---
|
||||
title: 'Markdown + Tailwind'
|
||||
layout: ../layouts/main.astro
|
||||
---
|
||||
|
||||
<div class="grid place-items-center h-screen content-center">
|
||||
<div class="py-2 px-4 bg-purple-500 text-white font-semibold rounded-lg shadow-md">
|
||||
Tailwind classes also work in Markdown!
|
||||
</div>
|
||||
<a
|
||||
href="/"
|
||||
class="p-4 underline hover:text-purple-500 transition-colors ease-in-out duration-200"
|
||||
>
|
||||
Go home
|
||||
</a>
|
||||
</div>
|
||||
@@ -2,6 +2,7 @@
|
||||
@import "tw-animate-css";
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
@custom-media --xs (width >= 475px);
|
||||
|
||||
@theme inline {
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
@@ -42,72 +43,72 @@
|
||||
}
|
||||
|
||||
:root {
|
||||
--radius: 0.625rem;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
--radius: 0.5rem;
|
||||
--background: oklch(0.99 0 0);
|
||||
--foreground: oklch(0.15 0 0);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.145 0 0);
|
||||
--card-foreground: oklch(0.15 0 0);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.145 0 0);
|
||||
--primary: oklch(0.205 0 0);
|
||||
--primary-foreground: oklch(0.985 0 0);
|
||||
--secondary: oklch(0.97 0 0);
|
||||
--secondary-foreground: oklch(0.205 0 0);
|
||||
--muted: oklch(0.97 0 0);
|
||||
--muted-foreground: oklch(0.556 0 0);
|
||||
--accent: oklch(0.97 0 0);
|
||||
--accent-foreground: oklch(0.205 0 0);
|
||||
--popover-foreground: oklch(0.15 0 0);
|
||||
--primary: oklch(0.5 0.2 250);
|
||||
--primary-foreground: oklch(0.98 0 0);
|
||||
--secondary: oklch(0.96 0 0);
|
||||
--secondary-foreground: oklch(0.15 0 0);
|
||||
--muted: oklch(0.96 0 0);
|
||||
--muted-foreground: oklch(0.45 0 0);
|
||||
--accent: oklch(0.96 0 0);
|
||||
--accent-foreground: oklch(0.15 0 0);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.922 0 0);
|
||||
--input: oklch(0.922 0 0);
|
||||
--ring: oklch(0.708 0 0);
|
||||
--border: oklch(0.9 0 0);
|
||||
--input: oklch(0.9 0 0);
|
||||
--ring: oklch(0.5 0.2 250);
|
||||
--chart-1: oklch(0.646 0.222 41.116);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
--sidebar-foreground: oklch(0.145 0 0);
|
||||
--sidebar-primary: oklch(0.205 0 0);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.97 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||
--sidebar-border: oklch(0.922 0 0);
|
||||
--sidebar-ring: oklch(0.708 0 0);
|
||||
--sidebar: oklch(0.98 0 0);
|
||||
--sidebar-foreground: oklch(0.15 0 0);
|
||||
--sidebar-primary: oklch(0.5 0.2 250);
|
||||
--sidebar-primary-foreground: oklch(0.98 0 0);
|
||||
--sidebar-accent: oklch(0.96 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.15 0 0);
|
||||
--sidebar-border: oklch(0.9 0 0);
|
||||
--sidebar-ring: oklch(0.5 0.2 250);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.145 0 0);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--card: oklch(0.205 0 0);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.205 0 0);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(0.922 0 0);
|
||||
--primary-foreground: oklch(0.205 0 0);
|
||||
--secondary: oklch(0.269 0 0);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.269 0 0);
|
||||
--muted-foreground: oklch(0.708 0 0);
|
||||
--accent: oklch(0.269 0 0);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--background: oklch(0.1 0 0);
|
||||
--foreground: oklch(0.95 0 0);
|
||||
--card: oklch(0.15 0 0);
|
||||
--card-foreground: oklch(0.95 0 0);
|
||||
--popover: oklch(0.15 0 0);
|
||||
--popover-foreground: oklch(0.95 0 0);
|
||||
--primary: oklch(0.6 0.2 250);
|
||||
--primary-foreground: oklch(0.1 0 0);
|
||||
--secondary: oklch(0.2 0 0);
|
||||
--secondary-foreground: oklch(0.95 0 0);
|
||||
--muted: oklch(0.25 0 0);
|
||||
--muted-foreground: oklch(0.65 0 0);
|
||||
--accent: oklch(0.25 0 0);
|
||||
--accent-foreground: oklch(0.95 0 0);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.556 0 0);
|
||||
--border: oklch(0.3 0 0);
|
||||
--input: oklch(0.3 0 0);
|
||||
--ring: oklch(0.6 0.2 250);
|
||||
--chart-1: oklch(0.488 0.243 264.376);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
--chart-4: oklch(0.627 0.265 303.9);
|
||||
--chart-5: oklch(0.645 0.246 16.439);
|
||||
--sidebar: oklch(0.205 0 0);
|
||||
--sidebar-foreground: oklch(0.985 0 0);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.269 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.556 0 0);
|
||||
--sidebar: oklch(0.15 0 0);
|
||||
--sidebar-foreground: oklch(0.95 0 0);
|
||||
--sidebar-primary: oklch(0.6 0.2 250);
|
||||
--sidebar-primary-foreground: oklch(0.95 0 0);
|
||||
--sidebar-accent: oklch(0.25 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.95 0 0);
|
||||
--sidebar-border: oklch(0.3 0 0);
|
||||
--sidebar-ring: oklch(0.6 0.2 250);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
|
||||
Reference in New Issue
Block a user