|
1 | 1 | 'use client'; |
2 | 2 |
|
3 | | -import Image from 'next/image'; |
4 | 3 | import { useEffect, useState } from 'react'; |
5 | 4 |
|
6 | 5 | export default function ParallaxHero() { |
7 | 6 | const [scrollY, setScrollY] = useState(0); |
| 7 | + const [mousePos, setMousePos] = useState({ x: 0, y: 0 }); |
8 | 8 |
|
9 | 9 | useEffect(() => { |
10 | 10 | const handleScroll = () => { |
11 | 11 | setScrollY(window.scrollY); |
12 | 12 | }; |
13 | 13 |
|
| 14 | + const handleMouseMove = (e: MouseEvent) => { |
| 15 | + setMousePos({ |
| 16 | + x: (e.clientX / window.innerWidth - 0.5) * 20, |
| 17 | + y: (e.clientY / window.innerHeight - 0.5) * 20 |
| 18 | + }); |
| 19 | + }; |
| 20 | + |
14 | 21 | window.addEventListener('scroll', handleScroll, { passive: true }); |
15 | | - return () => window.removeEventListener('scroll', handleScroll); |
| 22 | + window.addEventListener('mousemove', handleMouseMove, { passive: true }); |
| 23 | + |
| 24 | + return () => { |
| 25 | + window.removeEventListener('scroll', handleScroll); |
| 26 | + window.removeEventListener('mousemove', handleMouseMove); |
| 27 | + }; |
16 | 28 | }, []); |
17 | 29 |
|
18 | 30 | return ( |
19 | | - <div |
20 | | - className="absolute inset-0 z-0" |
21 | | - style={{ |
22 | | - transform: `translateY(${scrollY * 0.3}px)`, |
23 | | - transition: 'transform 0.1s ease-out', |
24 | | - }} |
25 | | - > |
26 | | - <Image |
27 | | - src="/hero-placeholder.svg" |
28 | | - alt="AustroVis Hero" |
29 | | - fill |
30 | | - priority |
31 | | - className="object-cover opacity-5" |
32 | | - /> |
| 31 | + <div className="absolute inset-0 z-0 overflow-hidden"> |
| 32 | + {/* Austrian Alps - Background mountains */} |
| 33 | + <div |
| 34 | + className="absolute bottom-0 left-0 right-0 h-[60%]" |
| 35 | + style={{ |
| 36 | + transform: `translateY(${scrollY * 0.5}px) translateX(${mousePos.x * 0.5}px)`, |
| 37 | + }} |
| 38 | + > |
| 39 | + {/* Far mountain range */} |
| 40 | + <svg |
| 41 | + className="absolute bottom-0 w-full h-full opacity-[0.03]" |
| 42 | + viewBox="0 0 1200 600" |
| 43 | + preserveAspectRatio="none" |
| 44 | + > |
| 45 | + <path |
| 46 | + d="M0,600 L0,300 L200,200 L400,350 L600,150 L800,250 L1000,200 L1200,300 L1200,600 Z" |
| 47 | + fill="currentColor" |
| 48 | + /> |
| 49 | + </svg> |
| 50 | + </div> |
| 51 | + |
| 52 | + {/* Mid mountain range */} |
| 53 | + <div |
| 54 | + className="absolute bottom-0 left-0 right-0 h-[45%]" |
| 55 | + style={{ |
| 56 | + transform: `translateY(${scrollY * 0.35}px) translateX(${mousePos.x * 0.8}px)`, |
| 57 | + }} |
| 58 | + > |
| 59 | + <svg |
| 60 | + className="absolute bottom-0 w-full h-full opacity-[0.05]" |
| 61 | + viewBox="0 0 1200 450" |
| 62 | + preserveAspectRatio="none" |
| 63 | + > |
| 64 | + <path |
| 65 | + d="M0,450 L0,250 L150,180 L300,280 L450,120 L600,200 L750,140 L900,220 L1050,180 L1200,250 L1200,450 Z" |
| 66 | + fill="currentColor" |
| 67 | + /> |
| 68 | + </svg> |
| 69 | + </div> |
| 70 | + |
| 71 | + {/* Front mountain range */} |
| 72 | + <div |
| 73 | + className="absolute bottom-0 left-0 right-0 h-[30%]" |
| 74 | + style={{ |
| 75 | + transform: `translateY(${scrollY * 0.2}px) translateX(${mousePos.x * 1.2}px)`, |
| 76 | + }} |
| 77 | + > |
| 78 | + <svg |
| 79 | + className="absolute bottom-0 w-full h-full opacity-[0.08]" |
| 80 | + viewBox="0 0 1200 300" |
| 81 | + preserveAspectRatio="none" |
| 82 | + > |
| 83 | + <path |
| 84 | + d="M0,300 L0,180 L200,100 L400,200 L600,80 L800,160 L1000,120 L1200,180 L1200,300 Z" |
| 85 | + fill="currentColor" |
| 86 | + /> |
| 87 | + </svg> |
| 88 | + </div> |
| 89 | + |
| 90 | + {/* Data visualization elements - floating points */} |
| 91 | + <div |
| 92 | + className="absolute inset-0" |
| 93 | + style={{ |
| 94 | + transform: `translateY(${scrollY * 0.15}px) translateX(${mousePos.x * 1.5}px)`, |
| 95 | + }} |
| 96 | + > |
| 97 | + {/* Scatter plot points */} |
| 98 | + <div className="absolute top-[20%] left-[15%] w-1 h-1 bg-black/4 rounded-full" /> |
| 99 | + <div className="absolute top-[35%] left-[25%] w-1.5 h-1.5 bg-black/5 rounded-full" /> |
| 100 | + <div className="absolute top-[45%] left-[18%] w-1 h-1 bg-black/4 rounded-full" /> |
| 101 | + <div className="absolute top-[25%] right-[20%] w-1 h-1 bg-black/4 rounded-full" /> |
| 102 | + <div className="absolute top-[40%] right-[28%] w-1.5 h-1.5 bg-black/5 rounded-full" /> |
| 103 | + <div className="absolute top-[55%] right-[22%] w-1 h-1 bg-black/4 rounded-full" /> |
| 104 | + </div> |
| 105 | + |
| 106 | + {/* Graph lines - subtle */} |
| 107 | + <div |
| 108 | + className="absolute inset-0" |
| 109 | + style={{ |
| 110 | + transform: `translateY(${scrollY * 0.1}px) translateX(${-mousePos.x * 0.5}px)`, |
| 111 | + }} |
| 112 | + > |
| 113 | + <svg className="w-full h-full opacity-[0.02]" viewBox="0 0 1200 800"> |
| 114 | + <polyline |
| 115 | + points="100,400 200,350 300,380 400,320 500,340 600,300" |
| 116 | + fill="none" |
| 117 | + stroke="currentColor" |
| 118 | + strokeWidth="2" |
| 119 | + /> |
| 120 | + <polyline |
| 121 | + points="700,500 800,450 900,480 1000,440 1100,460" |
| 122 | + fill="none" |
| 123 | + stroke="currentColor" |
| 124 | + strokeWidth="2" |
| 125 | + /> |
| 126 | + </svg> |
| 127 | + </div> |
| 128 | + |
| 129 | + {/* Hidden chicken - very subtle, top right area */} |
| 130 | + <div |
| 131 | + className="absolute top-[15%] right-[12%] opacity-[0.015] hover:opacity-[0.15] transition-opacity duration-500 cursor-pointer" |
| 132 | + style={{ |
| 133 | + transform: `translateY(${scrollY * 0.25}px) translateX(${mousePos.x * 2}px) rotate(${mousePos.x * 0.5}deg)`, |
| 134 | + }} |
| 135 | + title="🐔 You found the Austrian chicken!" |
| 136 | + > |
| 137 | + <svg width="40" height="40" viewBox="0 0 100 100" fill="currentColor"> |
| 138 | + {/* Chicken body */} |
| 139 | + <ellipse cx="50" cy="60" rx="25" ry="20" /> |
| 140 | + {/* Chicken head */} |
| 141 | + <circle cx="45" cy="40" r="15" /> |
| 142 | + {/* Beak */} |
| 143 | + <path d="M35,38 L28,40 L35,42 Z" /> |
| 144 | + {/* Comb */} |
| 145 | + <path d="M42,28 Q45,22 48,28 Q51,22 54,28" fill="none" stroke="currentColor" strokeWidth="2" /> |
| 146 | + {/* Eye */} |
| 147 | + <circle cx="42" cy="38" r="2" /> |
| 148 | + {/* Legs */} |
| 149 | + <line x1="45" y1="80" x2="45" y2="90" stroke="currentColor" strokeWidth="2" /> |
| 150 | + <line x1="55" y1="80" x2="55" y2="90" stroke="currentColor" strokeWidth="2" /> |
| 151 | + {/* Feet */} |
| 152 | + <path d="M40,90 L45,90 L50,90" stroke="currentColor" strokeWidth="2" fill="none" /> |
| 153 | + <path d="M50,90 L55,90 L60,90" stroke="currentColor" strokeWidth="2" fill="none" /> |
| 154 | + </svg> |
| 155 | + </div> |
| 156 | + |
| 157 | + {/* Subtle data grid overlay */} |
| 158 | + <div |
| 159 | + className="absolute inset-0 opacity-[0.01]" |
| 160 | + style={{ |
| 161 | + transform: `translateY(${scrollY * 0.05}px)`, |
| 162 | + }} |
| 163 | + > |
| 164 | + <div className="w-full h-full" |
| 165 | + style={{ |
| 166 | + backgroundImage: 'linear-gradient(currentColor 1px, transparent 1px), linear-gradient(90deg, currentColor 1px, transparent 1px)', |
| 167 | + backgroundSize: '50px 50px' |
| 168 | + }} |
| 169 | + /> |
| 170 | + </div> |
33 | 171 | </div> |
34 | 172 | ); |
35 | 173 | } |
0 commit comments