mirror of
https://github.com/yusufipk/distromatch.git
synced 2026-09-11 10:46:07 +00:00
Add a visually engaging Linux distribution discovery app
Implement a Tinder-style card swiping interface for discovering Linux distributions, featuring landscape screenshots, physics-based animations, and dynamic backgrounds. Replit-Commit-Author: Agent Replit-Commit-Session-Id: e522d728-b28a-437f-b576-83935afe7ea0 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 214eff6b-5420-4bc2-8d15-c6c2ebb822ab Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/55e426a8-5a2b-421b-8bd4-30481ae0063b/e522d728-b28a-437f-b576-83935afe7ea0/3ouALIb Replit-Helium-Checkpoint-Created: true
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 1.0 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 970 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.0 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 711 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
@@ -3,6 +3,10 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
|
||||||
|
<meta name="description" content="DistroMatch - Find your perfect Linux distribution with a Tinder-style swipe experience. Discover desktop environments that match your personality." />
|
||||||
|
<meta property="og:title" content="DistroMatch - Find Your Perfect Linux Distro" />
|
||||||
|
<meta property="og:description" content="Swipe through beautiful Linux desktops and find the one that matches your style." />
|
||||||
|
<title>DistroMatch - Find Your Perfect Linux Distro</title>
|
||||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
|||||||
+2
-3
@@ -4,13 +4,12 @@ import { QueryClientProvider } from "@tanstack/react-query";
|
|||||||
import { Toaster } from "@/components/ui/toaster";
|
import { Toaster } from "@/components/ui/toaster";
|
||||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||||
import NotFound from "@/pages/not-found";
|
import NotFound from "@/pages/not-found";
|
||||||
|
import Home from "@/pages/home";
|
||||||
|
|
||||||
function Router() {
|
function Router() {
|
||||||
return (
|
return (
|
||||||
<Switch>
|
<Switch>
|
||||||
{/* Add pages below */}
|
<Route path="/" component={Home} />
|
||||||
{/* <Route path="/" component={Home}/> */}
|
|
||||||
{/* Fallback to 404 */}
|
|
||||||
<Route component={NotFound} />
|
<Route component={NotFound} />
|
||||||
</Switch>
|
</Switch>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import { Heart, X, RotateCcw } from 'lucide-react';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
|
||||||
|
interface ActionButtonsProps {
|
||||||
|
onLike: () => void;
|
||||||
|
onDislike: () => void;
|
||||||
|
onReset?: () => void;
|
||||||
|
showReset?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ActionButtons({
|
||||||
|
onLike,
|
||||||
|
onDislike,
|
||||||
|
onReset,
|
||||||
|
showReset = false,
|
||||||
|
disabled = false,
|
||||||
|
}: ActionButtonsProps) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center gap-6">
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
variant="outline"
|
||||||
|
className="h-16 w-16 rounded-full border-2 border-red-500/50 bg-transparent transition-all duration-200 hover:scale-110 hover:border-red-500 hover:bg-red-500/10"
|
||||||
|
onClick={onDislike}
|
||||||
|
disabled={disabled}
|
||||||
|
data-testid="button-dislike"
|
||||||
|
>
|
||||||
|
<X className="h-8 w-8 text-red-500" />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{showReset && onReset && (
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
variant="outline"
|
||||||
|
className="h-12 w-12 rounded-full border-2 border-yellow-500/50 bg-transparent transition-all duration-200 hover:scale-110 hover:border-yellow-500 hover:bg-yellow-500/10"
|
||||||
|
onClick={onReset}
|
||||||
|
data-testid="button-reset"
|
||||||
|
>
|
||||||
|
<RotateCcw className="h-5 w-5 text-yellow-500" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
variant="outline"
|
||||||
|
className="h-16 w-16 rounded-full border-2 border-green-500/50 bg-transparent transition-all duration-200 hover:scale-110 hover:border-green-500 hover:bg-green-500/10"
|
||||||
|
onClick={onLike}
|
||||||
|
disabled={disabled}
|
||||||
|
data-testid="button-like"
|
||||||
|
>
|
||||||
|
<Heart className="h-8 w-8 text-green-500" fill="currentColor" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
import { useState, useCallback } from 'react';
|
||||||
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
|
import SwipeCard from './SwipeCard';
|
||||||
|
import ActionButtons from './ActionButtons';
|
||||||
|
import EndOfDeck from './EndOfDeck';
|
||||||
|
import type { DistroCard } from '@/data/distros';
|
||||||
|
|
||||||
|
interface CardStackProps {
|
||||||
|
cards: DistroCard[];
|
||||||
|
onBackgroundChange?: (colors: string[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function CardStack({ cards, onBackgroundChange }: CardStackProps) {
|
||||||
|
const [currentIndex, setCurrentIndex] = useState(0);
|
||||||
|
const [likedCards, setLikedCards] = useState<DistroCard[]>([]);
|
||||||
|
const [dislikedCards, setDislikedCards] = useState<DistroCard[]>([]);
|
||||||
|
|
||||||
|
const visibleCards = cards.slice(currentIndex, currentIndex + 3);
|
||||||
|
const isFinished = currentIndex >= cards.length;
|
||||||
|
|
||||||
|
const handleSwipe = useCallback(
|
||||||
|
(direction: 'left' | 'right') => {
|
||||||
|
const currentCard = cards[currentIndex];
|
||||||
|
if (!currentCard) return;
|
||||||
|
|
||||||
|
if (direction === 'right') {
|
||||||
|
setLikedCards((prev) => [...prev, currentCard]);
|
||||||
|
console.log('Liked:', currentCard.distroName);
|
||||||
|
} else {
|
||||||
|
setDislikedCards((prev) => [...prev, currentCard]);
|
||||||
|
console.log('Disliked:', currentCard.distroName);
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextIndex = currentIndex + 1;
|
||||||
|
setCurrentIndex(nextIndex);
|
||||||
|
|
||||||
|
const nextCard = cards[nextIndex];
|
||||||
|
if (nextCard && onBackgroundChange) {
|
||||||
|
onBackgroundChange(nextCard.colorPalette);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[currentIndex, cards, onBackgroundChange]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleReset = useCallback(() => {
|
||||||
|
setCurrentIndex(0);
|
||||||
|
setLikedCards([]);
|
||||||
|
setDislikedCards([]);
|
||||||
|
if (cards[0] && onBackgroundChange) {
|
||||||
|
onBackgroundChange(cards[0].colorPalette);
|
||||||
|
}
|
||||||
|
}, [cards, onBackgroundChange]);
|
||||||
|
|
||||||
|
if (isFinished) {
|
||||||
|
return <EndOfDeck likedCards={likedCards} onReset={handleReset} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center gap-8">
|
||||||
|
<div className="relative flex h-[550px] w-[900px] max-w-[90vw] items-center justify-center">
|
||||||
|
<AnimatePresence>
|
||||||
|
{visibleCards.map((card, index) => {
|
||||||
|
const isTop = index === 0;
|
||||||
|
const scale = 1 - index * 0.05;
|
||||||
|
const offsetY = index * 12;
|
||||||
|
const zIndex = visibleCards.length - index;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SwipeCard
|
||||||
|
key={card.id}
|
||||||
|
card={card}
|
||||||
|
isTop={isTop}
|
||||||
|
onSwipe={handleSwipe}
|
||||||
|
scale={scale}
|
||||||
|
zIndex={zIndex}
|
||||||
|
offsetY={offsetY}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</AnimatePresence>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ delay: 0.3 }}
|
||||||
|
>
|
||||||
|
<ActionButtons
|
||||||
|
onLike={() => handleSwipe('right')}
|
||||||
|
onDislike={() => handleSwipe('left')}
|
||||||
|
onReset={handleReset}
|
||||||
|
showReset={currentIndex > 0}
|
||||||
|
disabled={isFinished}
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<div className="text-center text-sm text-white/60">
|
||||||
|
{currentIndex + 1} / {cards.length}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { motion } from 'framer-motion';
|
||||||
|
|
||||||
|
interface DynamicBackgroundProps {
|
||||||
|
colors: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DynamicBackground({ colors }: DynamicBackgroundProps) {
|
||||||
|
const gradientColors = colors.length >= 2
|
||||||
|
? colors
|
||||||
|
: ['#6B46C1', '#4F46E5', '#2563EB'];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
className="fixed inset-0 -z-10"
|
||||||
|
animate={{
|
||||||
|
background: `radial-gradient(ellipse at center, ${gradientColors[0]}30 0%, ${gradientColors[1]}20 50%, ${gradientColors[2] || gradientColors[0]}10 100%)`,
|
||||||
|
}}
|
||||||
|
transition={{ duration: 0.5, ease: 'easeInOut' }}
|
||||||
|
>
|
||||||
|
<div className="absolute inset-0 bg-background/80 backdrop-blur-3xl" />
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { PartyPopper, RotateCcw, Download } from 'lucide-react';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import type { DistroCard } from '@/data/distros';
|
||||||
|
|
||||||
|
interface EndOfDeckProps {
|
||||||
|
likedCards: DistroCard[];
|
||||||
|
onReset: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function EndOfDeck({ likedCards, onReset }: EndOfDeckProps) {
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
className="flex flex-col items-center gap-8 text-center"
|
||||||
|
initial={{ opacity: 0, scale: 0.8 }}
|
||||||
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
|
transition={{ type: 'spring', stiffness: 200, damping: 20 }}
|
||||||
|
>
|
||||||
|
<motion.div
|
||||||
|
animate={{
|
||||||
|
rotate: [0, -10, 10, -10, 10, 0],
|
||||||
|
scale: [1, 1.1, 1],
|
||||||
|
}}
|
||||||
|
transition={{ duration: 0.6, delay: 0.2 }}
|
||||||
|
>
|
||||||
|
<PartyPopper className="h-24 w-24 text-yellow-400" />
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2 className="font-mono text-3xl font-bold text-white" data-testid="text-end-title">
|
||||||
|
Öneriler Hazır!
|
||||||
|
</h2>
|
||||||
|
<p className="mt-2 text-lg text-white/70">
|
||||||
|
{likedCards.length > 0
|
||||||
|
? `${likedCards.length} dağıtım beğendin`
|
||||||
|
: 'Hiçbir dağıtım beğenmedin'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{likedCards.length > 0 && (
|
||||||
|
<div className="flex flex-wrap justify-center gap-4">
|
||||||
|
{likedCards.map((card) => (
|
||||||
|
<motion.div
|
||||||
|
key={card.id}
|
||||||
|
className="overflow-hidden rounded-lg bg-white/10 backdrop-blur-sm"
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
data-testid={`result-card-${card.id}`}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={card.screenshot}
|
||||||
|
alt={card.distroName}
|
||||||
|
className="h-24 w-40 object-cover"
|
||||||
|
/>
|
||||||
|
<div className="p-3">
|
||||||
|
<p className="font-semibold text-white">{card.distroName}</p>
|
||||||
|
<p className="text-xs text-white/60">{card.desktopEnvironment}</p>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex gap-4">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="gap-2 border-white/20 bg-white/10 text-white hover:bg-white/20"
|
||||||
|
onClick={onReset}
|
||||||
|
data-testid="button-restart"
|
||||||
|
>
|
||||||
|
<RotateCcw className="h-4 w-4" />
|
||||||
|
Tekrar Başla
|
||||||
|
</Button>
|
||||||
|
{likedCards.length > 0 && (
|
||||||
|
<Button
|
||||||
|
className="gap-2 bg-primary text-primary-foreground"
|
||||||
|
data-testid="button-download"
|
||||||
|
>
|
||||||
|
<Download className="h-4 w-4" />
|
||||||
|
İndir
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { Terminal } from 'lucide-react';
|
||||||
|
|
||||||
|
export default function Header() {
|
||||||
|
return (
|
||||||
|
<motion.header
|
||||||
|
className="fixed left-0 right-0 top-0 z-50 flex items-center justify-center p-6"
|
||||||
|
initial={{ opacity: 0, y: -20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ delay: 0.1 }}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="rounded-lg bg-primary p-2">
|
||||||
|
<Terminal className="h-6 w-6 text-primary-foreground" />
|
||||||
|
</div>
|
||||||
|
<h1 className="font-mono text-2xl font-bold text-white" data-testid="text-logo">
|
||||||
|
DistroMatch
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</motion.header>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
import { motion, useMotionValue, useTransform, PanInfo } from 'framer-motion';
|
||||||
|
import { Heart, X } from 'lucide-react';
|
||||||
|
import type { DistroCard } from '@/data/distros';
|
||||||
|
|
||||||
|
interface SwipeCardProps {
|
||||||
|
card: DistroCard;
|
||||||
|
isTop: boolean;
|
||||||
|
onSwipe: (direction: 'left' | 'right') => void;
|
||||||
|
scale?: number;
|
||||||
|
zIndex: number;
|
||||||
|
offsetY?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SwipeCard({
|
||||||
|
card,
|
||||||
|
isTop,
|
||||||
|
onSwipe,
|
||||||
|
scale = 1,
|
||||||
|
zIndex,
|
||||||
|
offsetY = 0,
|
||||||
|
}: SwipeCardProps) {
|
||||||
|
const x = useMotionValue(0);
|
||||||
|
const rotate = useTransform(x, [-300, 0, 300], [-15, 0, 15]);
|
||||||
|
const likeOpacity = useTransform(x, [0, 150], [0, 1]);
|
||||||
|
const nopeOpacity = useTransform(x, [-150, 0], [1, 0]);
|
||||||
|
|
||||||
|
const handleDragEnd = (_: MouseEvent | TouchEvent | PointerEvent, info: PanInfo) => {
|
||||||
|
const threshold = 150;
|
||||||
|
if (info.offset.x > threshold) {
|
||||||
|
onSwipe('right');
|
||||||
|
} else if (info.offset.x < -threshold) {
|
||||||
|
onSwipe('left');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
className="absolute cursor-grab active:cursor-grabbing"
|
||||||
|
style={{
|
||||||
|
x: isTop ? x : 0,
|
||||||
|
rotate: isTop ? rotate : 0,
|
||||||
|
scale,
|
||||||
|
zIndex,
|
||||||
|
y: offsetY,
|
||||||
|
}}
|
||||||
|
drag={isTop ? 'x' : false}
|
||||||
|
dragConstraints={{ left: 0, right: 0 }}
|
||||||
|
dragElastic={0.9}
|
||||||
|
onDragEnd={isTop ? handleDragEnd : undefined}
|
||||||
|
animate={{
|
||||||
|
scale,
|
||||||
|
y: offsetY,
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
type: 'spring',
|
||||||
|
stiffness: 300,
|
||||||
|
damping: 20,
|
||||||
|
}}
|
||||||
|
exit={{
|
||||||
|
x: x.get() > 0 ? 500 : -500,
|
||||||
|
opacity: 0,
|
||||||
|
rotate: x.get() > 0 ? 20 : -20,
|
||||||
|
transition: { duration: 0.3 },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="relative w-[900px] max-w-[90vw] overflow-hidden rounded-2xl bg-black/90 shadow-2xl"
|
||||||
|
data-testid={`card-distro-${card.id}`}
|
||||||
|
>
|
||||||
|
<div className="relative aspect-video">
|
||||||
|
<img
|
||||||
|
src={card.screenshot}
|
||||||
|
alt={`${card.distroName} desktop`}
|
||||||
|
className="h-full w-full object-cover"
|
||||||
|
draggable={false}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{isTop && (
|
||||||
|
<>
|
||||||
|
<motion.div
|
||||||
|
className="absolute inset-0 flex items-center justify-center bg-green-500/30"
|
||||||
|
style={{ opacity: likeOpacity }}
|
||||||
|
>
|
||||||
|
<div className="rounded-full border-4 border-white bg-green-500 p-6">
|
||||||
|
<Heart className="h-16 w-16 text-white" fill="white" />
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
className="absolute inset-0 flex items-center justify-center bg-red-500/30"
|
||||||
|
style={{ opacity: nopeOpacity }}
|
||||||
|
>
|
||||||
|
<div className="rounded-full border-4 border-white bg-red-500 p-6">
|
||||||
|
<X className="h-16 w-16 text-white" />
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-black/80 px-8 py-6 backdrop-blur-md">
|
||||||
|
<h2
|
||||||
|
className="font-mono text-2xl font-bold text-white"
|
||||||
|
data-testid={`text-title-${card.id}`}
|
||||||
|
>
|
||||||
|
{card.wittyTitle}
|
||||||
|
</h2>
|
||||||
|
<div className="mt-2 flex flex-wrap items-center gap-3">
|
||||||
|
<span className="text-lg font-semibold text-white/90">
|
||||||
|
{card.distroName}
|
||||||
|
</span>
|
||||||
|
<span className="text-sm text-white/60">
|
||||||
|
{card.desktopEnvironment}
|
||||||
|
</span>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
{card.tags.slice(0, 3).map((tag) => (
|
||||||
|
<span
|
||||||
|
key={tag}
|
||||||
|
className="rounded-full bg-white/10 px-3 py-1 text-xs text-white/70"
|
||||||
|
>
|
||||||
|
{tag}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import ActionButtons from '../ActionButtons';
|
||||||
|
|
||||||
|
export default function ActionButtonsExample() {
|
||||||
|
return (
|
||||||
|
<div className="flex min-h-[200px] items-center justify-center bg-gradient-to-br from-gray-900 to-gray-800 p-8">
|
||||||
|
<ActionButtons
|
||||||
|
onLike={() => console.log('Liked!')}
|
||||||
|
onDislike={() => console.log('Disliked!')}
|
||||||
|
onReset={() => console.log('Reset!')}
|
||||||
|
showReset={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import CardStack from '../CardStack';
|
||||||
|
import { distroCards } from '@/data/distros';
|
||||||
|
|
||||||
|
export default function CardStackExample() {
|
||||||
|
return (
|
||||||
|
<div className="flex min-h-[750px] items-center justify-center bg-gradient-to-br from-purple-900/50 to-blue-900/50 p-8">
|
||||||
|
<CardStack
|
||||||
|
cards={distroCards}
|
||||||
|
onBackgroundChange={(colors) => console.log('Colors:', colors)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import EndOfDeck from '../EndOfDeck';
|
||||||
|
import { distroCards } from '@/data/distros';
|
||||||
|
|
||||||
|
export default function EndOfDeckExample() {
|
||||||
|
const likedCards = distroCards.slice(0, 3);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex min-h-[500px] items-center justify-center bg-gradient-to-br from-purple-900/50 to-blue-900/50 p-8">
|
||||||
|
<EndOfDeck
|
||||||
|
likedCards={likedCards}
|
||||||
|
onReset={() => console.log('Reset!')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import Header from '../Header';
|
||||||
|
|
||||||
|
export default function HeaderExample() {
|
||||||
|
return (
|
||||||
|
<div className="relative min-h-[100px] bg-gradient-to-br from-purple-900/50 to-blue-900/50">
|
||||||
|
<Header />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import SwipeCard from '../SwipeCard';
|
||||||
|
import { distroCards } from '@/data/distros';
|
||||||
|
|
||||||
|
export default function SwipeCardExample() {
|
||||||
|
const card = distroCards[0];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex min-h-[600px] items-center justify-center bg-gradient-to-br from-purple-900/50 to-blue-900/50 p-8">
|
||||||
|
<div className="relative">
|
||||||
|
<SwipeCard
|
||||||
|
card={card}
|
||||||
|
isTop={true}
|
||||||
|
onSwipe={(dir) => console.log('Swiped:', dir)}
|
||||||
|
scale={1}
|
||||||
|
zIndex={10}
|
||||||
|
offsetY={0}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import ubuntuScreenshot from '@assets/generated_images/ubuntu_gnome_desktop_screenshot.png';
|
||||||
|
import archScreenshot from '@assets/generated_images/arch_linux_i3_terminal_screenshot.png';
|
||||||
|
import elementaryScreenshot from '@assets/generated_images/elementary_os_mac-like_screenshot.png';
|
||||||
|
import fedoraScreenshot from '@assets/generated_images/fedora_kde_plasma_screenshot.png';
|
||||||
|
import mintScreenshot from '@assets/generated_images/linux_mint_cinnamon_screenshot.png';
|
||||||
|
import popScreenshot from '@assets/generated_images/pop_os_developer_screenshot.png';
|
||||||
|
|
||||||
|
export interface DistroCard {
|
||||||
|
id: string;
|
||||||
|
distroName: string;
|
||||||
|
screenshot: string;
|
||||||
|
wittyTitle: string;
|
||||||
|
desktopEnvironment: string;
|
||||||
|
tags: string[];
|
||||||
|
colorPalette: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const distroCards: DistroCard[] = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
distroName: 'Ubuntu',
|
||||||
|
screenshot: ubuntuScreenshot,
|
||||||
|
wittyTitle: 'Sadece çalışsın yeter',
|
||||||
|
desktopEnvironment: 'GNOME',
|
||||||
|
tags: ['beginner-friendly', 'stable', 'popular'],
|
||||||
|
colorPalette: ['#E95420', '#77216F', '#5E2750'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
distroName: 'Arch Linux',
|
||||||
|
screenshot: archScreenshot,
|
||||||
|
wittyTitle: 'Terminalden çıkmam',
|
||||||
|
desktopEnvironment: 'i3wm',
|
||||||
|
tags: ['advanced', 'minimal', 'customizable'],
|
||||||
|
colorPalette: ['#1793D1', '#333333', '#0D47A1'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
distroName: 'elementary OS',
|
||||||
|
screenshot: elementaryScreenshot,
|
||||||
|
wittyTitle: 'Mac gibi olsun',
|
||||||
|
desktopEnvironment: 'Pantheon',
|
||||||
|
tags: ['beautiful', 'simple', 'curated'],
|
||||||
|
colorPalette: ['#64BAFF', '#3689E6', '#0D52BF'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
distroName: 'Fedora',
|
||||||
|
screenshot: fedoraScreenshot,
|
||||||
|
wittyTitle: 'Her zaman güncel',
|
||||||
|
desktopEnvironment: 'KDE Plasma',
|
||||||
|
tags: ['cutting-edge', 'developer', 'reliable'],
|
||||||
|
colorPalette: ['#51A2DA', '#294172', '#3C6EB4'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '5',
|
||||||
|
distroName: 'Linux Mint',
|
||||||
|
screenshot: mintScreenshot,
|
||||||
|
wittyTitle: 'Windows gibi ama özgür',
|
||||||
|
desktopEnvironment: 'Cinnamon',
|
||||||
|
tags: ['familiar', 'stable', 'beginner-friendly'],
|
||||||
|
colorPalette: ['#87CF3E', '#5FAD56', '#3E8E41'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '6',
|
||||||
|
distroName: 'Pop!_OS',
|
||||||
|
screenshot: popScreenshot,
|
||||||
|
wittyTitle: 'Özelleştirme canavarı',
|
||||||
|
desktopEnvironment: 'COSMIC',
|
||||||
|
tags: ['gaming', 'developer', 'modern'],
|
||||||
|
colorPalette: ['#FAA41A', '#48B9C7', '#574F4A'],
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -43,9 +43,9 @@
|
|||||||
--chart-3: 245 85% 55%;
|
--chart-3: 245 85% 55%;
|
||||||
--chart-4: 310 70% 48%;
|
--chart-4: 310 70% 48%;
|
||||||
--chart-5: 230 80% 52%;
|
--chart-5: 230 80% 52%;
|
||||||
--font-sans: Open Sans, sans-serif;
|
--font-sans: Inter, sans-serif;
|
||||||
--font-serif: Georgia, serif;
|
--font-serif: Georgia, serif;
|
||||||
--font-mono: Menlo, monospace;
|
--font-mono: 'Space Grotesk', monospace;
|
||||||
--radius: .5rem;
|
--radius: .5rem;
|
||||||
--shadow-2xs: 0px 2px 0px 0px hsl(0 0% 0% / 0.00);
|
--shadow-2xs: 0px 2px 0px 0px hsl(0 0% 0% / 0.00);
|
||||||
--shadow-xs: 0px 2px 0px 0px hsl(0 0% 0% / 0.00);
|
--shadow-xs: 0px 2px 0px 0px hsl(0 0% 0% / 0.00);
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import Header from '@/components/Header';
|
||||||
|
import CardStack from '@/components/CardStack';
|
||||||
|
import DynamicBackground from '@/components/DynamicBackground';
|
||||||
|
import { distroCards } from '@/data/distros';
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
const [backgroundColors, setBackgroundColors] = useState<string[]>(
|
||||||
|
distroCards[0]?.colorPalette || ['#6B46C1', '#4F46E5', '#2563EB']
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.documentElement.classList.add('dark');
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative min-h-screen w-full overflow-hidden">
|
||||||
|
<DynamicBackground colors={backgroundColors} />
|
||||||
|
<Header />
|
||||||
|
|
||||||
|
<main className="flex min-h-screen items-center justify-center pt-20">
|
||||||
|
<CardStack
|
||||||
|
cards={distroCards}
|
||||||
|
onBackgroundChange={setBackgroundColors}
|
||||||
|
/>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user