diff --git a/attached_assets/generated_images/arch_linux_i3_terminal_screenshot.png b/attached_assets/generated_images/arch_linux_i3_terminal_screenshot.png new file mode 100644 index 0000000..fa94326 Binary files /dev/null and b/attached_assets/generated_images/arch_linux_i3_terminal_screenshot.png differ diff --git a/attached_assets/generated_images/elementary_os_mac-like_screenshot.png b/attached_assets/generated_images/elementary_os_mac-like_screenshot.png new file mode 100644 index 0000000..622a2b3 Binary files /dev/null and b/attached_assets/generated_images/elementary_os_mac-like_screenshot.png differ diff --git a/attached_assets/generated_images/fedora_kde_plasma_screenshot.png b/attached_assets/generated_images/fedora_kde_plasma_screenshot.png new file mode 100644 index 0000000..9de5764 Binary files /dev/null and b/attached_assets/generated_images/fedora_kde_plasma_screenshot.png differ diff --git a/attached_assets/generated_images/linux_mint_cinnamon_screenshot.png b/attached_assets/generated_images/linux_mint_cinnamon_screenshot.png new file mode 100644 index 0000000..05354d6 Binary files /dev/null and b/attached_assets/generated_images/linux_mint_cinnamon_screenshot.png differ diff --git a/attached_assets/generated_images/pop_os_developer_screenshot.png b/attached_assets/generated_images/pop_os_developer_screenshot.png new file mode 100644 index 0000000..8f74242 Binary files /dev/null and b/attached_assets/generated_images/pop_os_developer_screenshot.png differ diff --git a/attached_assets/generated_images/ubuntu_gnome_desktop_screenshot.png b/attached_assets/generated_images/ubuntu_gnome_desktop_screenshot.png new file mode 100644 index 0000000..b0ab5ea Binary files /dev/null and b/attached_assets/generated_images/ubuntu_gnome_desktop_screenshot.png differ diff --git a/client/index.html b/client/index.html index 5ac9c7f..599027f 100644 --- a/client/index.html +++ b/client/index.html @@ -3,6 +3,10 @@ + + + + DistroMatch - Find Your Perfect Linux Distro diff --git a/client/src/App.tsx b/client/src/App.tsx index b4c5b9e..f86c957 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -4,13 +4,12 @@ import { QueryClientProvider } from "@tanstack/react-query"; import { Toaster } from "@/components/ui/toaster"; import { TooltipProvider } from "@/components/ui/tooltip"; import NotFound from "@/pages/not-found"; +import Home from "@/pages/home"; function Router() { return ( - {/* Add pages below */} - {/* */} - {/* Fallback to 404 */} + ); diff --git a/client/src/components/ActionButtons.tsx b/client/src/components/ActionButtons.tsx new file mode 100644 index 0000000..394c1f0 --- /dev/null +++ b/client/src/components/ActionButtons.tsx @@ -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 ( +
+ + + {showReset && onReset && ( + + )} + + +
+ ); +} diff --git a/client/src/components/CardStack.tsx b/client/src/components/CardStack.tsx new file mode 100644 index 0000000..f18992e --- /dev/null +++ b/client/src/components/CardStack.tsx @@ -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([]); + const [dislikedCards, setDislikedCards] = useState([]); + + 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 ; + } + + return ( +
+
+ + {visibleCards.map((card, index) => { + const isTop = index === 0; + const scale = 1 - index * 0.05; + const offsetY = index * 12; + const zIndex = visibleCards.length - index; + + return ( + + ); + })} + +
+ + + handleSwipe('right')} + onDislike={() => handleSwipe('left')} + onReset={handleReset} + showReset={currentIndex > 0} + disabled={isFinished} + /> + + +
+ {currentIndex + 1} / {cards.length} +
+
+ ); +} diff --git a/client/src/components/DynamicBackground.tsx b/client/src/components/DynamicBackground.tsx new file mode 100644 index 0000000..c97951f --- /dev/null +++ b/client/src/components/DynamicBackground.tsx @@ -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 ( + +
+ + ); +} diff --git a/client/src/components/EndOfDeck.tsx b/client/src/components/EndOfDeck.tsx new file mode 100644 index 0000000..6ac7724 --- /dev/null +++ b/client/src/components/EndOfDeck.tsx @@ -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 ( + + + + + +
+

+ Öneriler Hazır! +

+

+ {likedCards.length > 0 + ? `${likedCards.length} dağıtım beğendin` + : 'Hiçbir dağıtım beğenmedin'} +

+
+ + {likedCards.length > 0 && ( +
+ {likedCards.map((card) => ( + + {card.distroName} +
+

{card.distroName}

+

{card.desktopEnvironment}

+
+
+ ))} +
+ )} + +
+ + {likedCards.length > 0 && ( + + )} +
+
+ ); +} diff --git a/client/src/components/Header.tsx b/client/src/components/Header.tsx new file mode 100644 index 0000000..573e22b --- /dev/null +++ b/client/src/components/Header.tsx @@ -0,0 +1,22 @@ +import { motion } from 'framer-motion'; +import { Terminal } from 'lucide-react'; + +export default function Header() { + return ( + +
+
+ +
+

+ DistroMatch +

+
+
+ ); +} diff --git a/client/src/components/SwipeCard.tsx b/client/src/components/SwipeCard.tsx new file mode 100644 index 0000000..3a862ab --- /dev/null +++ b/client/src/components/SwipeCard.tsx @@ -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 ( + 0 ? 500 : -500, + opacity: 0, + rotate: x.get() > 0 ? 20 : -20, + transition: { duration: 0.3 }, + }} + > +
+
+ {`${card.distroName} + + {isTop && ( + <> + +
+ +
+
+ + +
+ +
+
+ + )} +
+ +
+

+ {card.wittyTitle} +

+
+ + {card.distroName} + + + {card.desktopEnvironment} + +
+ {card.tags.slice(0, 3).map((tag) => ( + + {tag} + + ))} +
+
+
+
+
+ ); +} diff --git a/client/src/components/examples/ActionButtonsExample.tsx b/client/src/components/examples/ActionButtonsExample.tsx new file mode 100644 index 0000000..c07e207 --- /dev/null +++ b/client/src/components/examples/ActionButtonsExample.tsx @@ -0,0 +1,14 @@ +import ActionButtons from '../ActionButtons'; + +export default function ActionButtonsExample() { + return ( +
+ console.log('Liked!')} + onDislike={() => console.log('Disliked!')} + onReset={() => console.log('Reset!')} + showReset={true} + /> +
+ ); +} diff --git a/client/src/components/examples/CardStackExample.tsx b/client/src/components/examples/CardStackExample.tsx new file mode 100644 index 0000000..ea652f2 --- /dev/null +++ b/client/src/components/examples/CardStackExample.tsx @@ -0,0 +1,13 @@ +import CardStack from '../CardStack'; +import { distroCards } from '@/data/distros'; + +export default function CardStackExample() { + return ( +
+ console.log('Colors:', colors)} + /> +
+ ); +} diff --git a/client/src/components/examples/EndOfDeckExample.tsx b/client/src/components/examples/EndOfDeckExample.tsx new file mode 100644 index 0000000..87b7f90 --- /dev/null +++ b/client/src/components/examples/EndOfDeckExample.tsx @@ -0,0 +1,15 @@ +import EndOfDeck from '../EndOfDeck'; +import { distroCards } from '@/data/distros'; + +export default function EndOfDeckExample() { + const likedCards = distroCards.slice(0, 3); + + return ( +
+ console.log('Reset!')} + /> +
+ ); +} diff --git a/client/src/components/examples/HeaderExample.tsx b/client/src/components/examples/HeaderExample.tsx new file mode 100644 index 0000000..87947a1 --- /dev/null +++ b/client/src/components/examples/HeaderExample.tsx @@ -0,0 +1,9 @@ +import Header from '../Header'; + +export default function HeaderExample() { + return ( +
+
+
+ ); +} diff --git a/client/src/components/examples/SwipeCardExample.tsx b/client/src/components/examples/SwipeCardExample.tsx new file mode 100644 index 0000000..de89c0d --- /dev/null +++ b/client/src/components/examples/SwipeCardExample.tsx @@ -0,0 +1,21 @@ +import SwipeCard from '../SwipeCard'; +import { distroCards } from '@/data/distros'; + +export default function SwipeCardExample() { + const card = distroCards[0]; + + return ( +
+
+ console.log('Swiped:', dir)} + scale={1} + zIndex={10} + offsetY={0} + /> +
+
+ ); +} diff --git a/client/src/data/distros.ts b/client/src/data/distros.ts new file mode 100644 index 0000000..a41cd48 --- /dev/null +++ b/client/src/data/distros.ts @@ -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'], + }, +]; diff --git a/client/src/index.css b/client/src/index.css index c8a4f35..a1f0801 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -43,9 +43,9 @@ --chart-3: 245 85% 55%; --chart-4: 310 70% 48%; --chart-5: 230 80% 52%; - --font-sans: Open Sans, sans-serif; + --font-sans: Inter, sans-serif; --font-serif: Georgia, serif; - --font-mono: Menlo, monospace; + --font-mono: 'Space Grotesk', monospace; --radius: .5rem; --shadow-2xs: 0px 2px 0px 0px hsl(0 0% 0% / 0.00); --shadow-xs: 0px 2px 0px 0px hsl(0 0% 0% / 0.00); diff --git a/client/src/pages/home.tsx b/client/src/pages/home.tsx new file mode 100644 index 0000000..d7565f1 --- /dev/null +++ b/client/src/pages/home.tsx @@ -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( + distroCards[0]?.colorPalette || ['#6B46C1', '#4F46E5', '#2563EB'] + ); + + useEffect(() => { + document.documentElement.classList.add('dark'); + }, []); + + return ( +
+ +
+ +
+ +
+
+ ); +}