diff --git a/src/assets/avatar.png b/src/assets/avatar.png new file mode 100644 index 0000000..e1a2e9d Binary files /dev/null and b/src/assets/avatar.png differ diff --git a/src/components/GlassCard.tsx b/src/components/GlassCard.tsx new file mode 100644 index 0000000..5793e55 --- /dev/null +++ b/src/components/GlassCard.tsx @@ -0,0 +1,38 @@ +import { useRef, useState, useCallback, ReactNode } from 'react'; +import { cn } from '@/lib/utils'; + +interface GlassCardProps { + children: ReactNode; + className?: string; + interactive?: boolean; +} + +export const GlassCard = ({ children, className, interactive = true }: GlassCardProps) => { + const cardRef = useRef(null); + const [glowPos, setGlowPos] = useState({ x: 0, y: 0 }); + const [isHovered, setIsHovered] = useState(false); + + const handleMouseMove = useCallback((e: React.MouseEvent) => { + if (!cardRef.current || !interactive) return; + const rect = cardRef.current.getBoundingClientRect(); + setGlowPos({ x: e.clientX - rect.left, y: e.clientY - rect.top }); + }, [interactive]); + + return ( +
setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + > + {interactive && isHovered && ( +
+ )} +
{children}
+
+ ); +}; diff --git a/src/hooks/useMousePosition.ts b/src/hooks/useMousePosition.ts new file mode 100644 index 0000000..86aaf0b --- /dev/null +++ b/src/hooks/useMousePosition.ts @@ -0,0 +1,22 @@ +import { useState, useEffect, useCallback, RefObject } from 'react'; + +export function useMousePosition(containerRef?: RefObject) { + const [position, setPosition] = useState({ x: 0, y: 0 }); + + const handleMouseMove = useCallback((e: MouseEvent) => { + if (containerRef?.current) { + const rect = containerRef.current.getBoundingClientRect(); + setPosition({ x: e.clientX - rect.left, y: e.clientY - rect.top }); + } else { + setPosition({ x: e.clientX, y: e.clientY }); + } + }, [containerRef]); + + useEffect(() => { + const target = containerRef?.current || window; + target.addEventListener('mousemove', handleMouseMove as EventListener); + return () => target.removeEventListener('mousemove', handleMouseMove as EventListener); + }, [handleMouseMove, containerRef]); + + return position; +} diff --git a/src/index.css b/src/index.css index 4844bbd..43278ea 100644 --- a/src/index.css +++ b/src/index.css @@ -2,95 +2,52 @@ @tailwind components; @tailwind utilities; -/* Definition of the design system. All colors, gradients, fonts, etc should be defined here. -All colors MUST be HSL. -*/ +@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=Space+Grotesk:wght@400;500;600;700&display=swap'); @layer base { :root { - --background: 0 0% 100%; - --foreground: 222.2 84% 4.9%; + --background: 220 15% 10%; + --foreground: 30 10% 90%; - --card: 0 0% 100%; - --card-foreground: 222.2 84% 4.9%; + --card: 220 12% 14%; + --card-foreground: 30 10% 90%; - --popover: 0 0% 100%; - --popover-foreground: 222.2 84% 4.9%; + --popover: 220 12% 14%; + --popover-foreground: 30 10% 90%; - --primary: 222.2 47.4% 11.2%; - --primary-foreground: 210 40% 98%; + --primary: 25 95% 55%; + --primary-foreground: 220 15% 8%; - --secondary: 210 40% 96.1%; - --secondary-foreground: 222.2 47.4% 11.2%; + --secondary: 220 12% 18%; + --secondary-foreground: 30 10% 85%; - --muted: 210 40% 96.1%; - --muted-foreground: 215.4 16.3% 46.9%; + --muted: 220 10% 20%; + --muted-foreground: 220 8% 55%; - --accent: 210 40% 96.1%; - --accent-foreground: 222.2 47.4% 11.2%; + --accent: 25 90% 50%; + --accent-foreground: 220 15% 8%; --destructive: 0 84.2% 60.2%; --destructive-foreground: 210 40% 98%; - --border: 214.3 31.8% 91.4%; - --input: 214.3 31.8% 91.4%; - --ring: 222.2 84% 4.9%; + --border: 220 10% 22%; + --input: 220 10% 22%; + --ring: 25 95% 55%; - --radius: 0.5rem; + --radius: 1rem; - --sidebar-background: 0 0% 98%; + --glass-bg: 220 12% 16%; + --glass-border: 220 10% 28%; + --glass-shine: 25 95% 55%; - --sidebar-foreground: 240 5.3% 26.1%; - - --sidebar-primary: 240 5.9% 10%; - - --sidebar-primary-foreground: 0 0% 98%; - - --sidebar-accent: 240 4.8% 95.9%; - - --sidebar-accent-foreground: 240 5.9% 10%; - - --sidebar-border: 220 13% 91%; - - --sidebar-ring: 217.2 91.2% 59.8%; - } - - .dark { - --background: 222.2 84% 4.9%; - --foreground: 210 40% 98%; - - --card: 222.2 84% 4.9%; - --card-foreground: 210 40% 98%; - - --popover: 222.2 84% 4.9%; - --popover-foreground: 210 40% 98%; - - --primary: 210 40% 98%; - --primary-foreground: 222.2 47.4% 11.2%; - - --secondary: 217.2 32.6% 17.5%; - --secondary-foreground: 210 40% 98%; - - --muted: 217.2 32.6% 17.5%; - --muted-foreground: 215 20.2% 65.1%; - - --accent: 217.2 32.6% 17.5%; - --accent-foreground: 210 40% 98%; - - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 210 40% 98%; - - --border: 217.2 32.6% 17.5%; - --input: 217.2 32.6% 17.5%; - --ring: 212.7 26.8% 83.9%; - --sidebar-background: 240 5.9% 10%; - --sidebar-foreground: 240 4.8% 95.9%; - --sidebar-primary: 224.3 76.3% 48%; - --sidebar-primary-foreground: 0 0% 100%; - --sidebar-accent: 240 3.7% 15.9%; - --sidebar-accent-foreground: 240 4.8% 95.9%; - --sidebar-border: 240 3.7% 15.9%; - --sidebar-ring: 217.2 91.2% 59.8%; + --sidebar-background: 220 15% 10%; + --sidebar-foreground: 30 10% 90%; + --sidebar-primary: 25 95% 55%; + --sidebar-primary-foreground: 220 15% 8%; + --sidebar-accent: 220 12% 18%; + --sidebar-accent-foreground: 30 10% 90%; + --sidebar-border: 220 10% 22%; + --sidebar-ring: 25 95% 55%; } } @@ -100,6 +57,85 @@ All colors MUST be HSL. } body { - @apply bg-background text-foreground; + @apply bg-background text-foreground font-body antialiased; + } +} + +@layer components { + .glass-card { + background: hsl(var(--glass-bg) / 0.6); + border: 1px solid hsl(var(--glass-border) / 0.4); + backdrop-filter: blur(20px) saturate(1.8); + -webkit-backdrop-filter: blur(20px) saturate(1.8); + box-shadow: + inset 0 1px 1px hsl(0 0% 100% / 0.05), + inset 0 -1px 1px hsl(0 0% 0% / 0.1), + 0 8px 32px hsl(0 0% 0% / 0.3), + 0 2px 8px hsl(0 0% 0% / 0.2); + position: relative; + overflow: hidden; + } + + .glass-card::before { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient( + 135deg, + hsl(0 0% 100% / 0.08) 0%, + transparent 40%, + transparent 60%, + hsl(0 0% 100% / 0.03) 100% + ); + pointer-events: none; + z-index: 1; + } + + .glass-card-hover { + transition: transform 0.3s cubic-bezier(0.23, 1, 0.32, 1), + box-shadow 0.3s cubic-bezier(0.23, 1, 0.32, 1), + border-color 0.3s ease; + } + + .glass-card-hover:hover { + transform: translateY(-2px); + border-color: hsl(var(--glass-shine) / 0.3); + box-shadow: + inset 0 1px 1px hsl(0 0% 100% / 0.08), + 0 12px 40px hsl(0 0% 0% / 0.4), + 0 4px 12px hsl(var(--primary) / 0.1); + } + + .mouse-glow { + position: absolute; + width: 300px; + height: 300px; + border-radius: 50%; + background: radial-gradient( + circle, + hsl(var(--primary) / 0.12) 0%, + transparent 70% + ); + pointer-events: none; + transform: translate(-50%, -50%); + z-index: 0; + transition: opacity 0.3s ease; + } + + .link-arrow { + @apply inline-block transition-transform duration-200; + } + + .link-arrow-parent:hover .link-arrow { + transform: translate(3px, -3px); + } +} + +@layer utilities { + .text-gradient { + background: linear-gradient(135deg, hsl(var(--primary)), hsl(30 100% 65%)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; } } diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx index 7130b54..4632c1a 100644 --- a/src/pages/Index.tsx +++ b/src/pages/Index.tsx @@ -1,11 +1,111 @@ -// Update this page (the content is just a fallback if you fail to update the page) +import { useRef } from 'react'; +import { GlassCard } from '@/components/GlassCard'; +import { useMousePosition } from '@/hooks/useMousePosition'; +import avatar from '@/assets/avatar.png'; +import { Home, MessageCircle, Rss, GitBranch, Search, ExternalLink } from 'lucide-react'; + +const links = [ + { icon: Home, label: 'Home', href: 'https://damaj.tech/', desc: 'Main page' }, + { icon: MessageCircle, label: 'Who Am I?', href: 'https://damaj.tech/site/whoami', desc: 'About me' }, + { icon: Rss, label: 'Blog', href: 'https://damaj.tech/site/blog', desc: 'My writings' }, +]; + +const services = [ + { icon: GitBranch, label: 'Git Server', href: 'https://git.damaj.tech/', desc: 'Self-hosted Forgejo' }, + { icon: Search, label: 'SearXNG', href: 'https://searxng.damaj.tech/', desc: 'Private search engine' }, +]; const Index = () => { + const containerRef = useRef(null); + const mouse = useMousePosition(containerRef); + return ( -
-
-

Welcome to Your Blank App

-

Start building your amazing project here!

+
+ {/* Global mouse glow */} +
+ +
+ {/* Avatar + Intro */} + +
+ Mohamad's avatar +
+

+ 👋 Hey There! +

+

+ My name is Mohamad +

+

+ Welcome to my space. Feel free to look around. +

+
+
+
+ + {/* Website Content */} +
+

+ Website Content +

+
+ {links.map((link) => ( + + +
+ + +
+

{link.label}

+

{link.desc}

+
+
+ ))} +
+
+ + {/* Self-hosted Services */} +
+

+ Self-hosted Services +

+
+ {services.map((svc) => ( + + +
+ + +
+

{svc.label}

+

{svc.desc}

+
+
+ ))} +
+
+ + {/* Footer */} +

+ damaj.tech +

); diff --git a/tailwind.config.ts b/tailwind.config.ts index a1edb69..1fd7cf0 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -13,7 +13,17 @@ export default { }, }, extend: { + fontFamily: { + heading: ['"Space Grotesk"', 'sans-serif'], + body: ['"Space Grotesk"', 'sans-serif'], + mono: ['"JetBrains Mono"', 'monospace'], + }, colors: { + glass: { + bg: "hsl(var(--glass-bg) / )", + border: "hsl(var(--glass-border) / )", + shine: "hsl(var(--glass-shine) / )", + }, border: "hsl(var(--border))", input: "hsl(var(--input))", ring: "hsl(var(--ring))",