Greenlens/greenlns-landing/components/Hero.tsx

190 lines
6.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client'
import { useEffect, useRef, useState } from 'react'
import { useLang } from '@/context/LangContext'
function useReveal() {
useEffect(() => {
const els = document.querySelectorAll('.reveal, .reveal-fade')
const obs = new IntersectionObserver(
(entries) => entries.forEach(e => { if (e.isIntersecting) e.target.classList.add('active') }),
{ threshold: 0.12 }
)
els.forEach(el => obs.observe(el))
return () => obs.disconnect()
}, [])
}
export default function Hero() {
useReveal()
const bgRef = useRef<HTMLDivElement>(null)
const { t } = useLang()
const [segChoice, setSegChoice] = useState<0 | 1 | null>(null)
useEffect(() => {
const handle = () => {
if (bgRef.current) {
const y = window.scrollY * 0.3
bgRef.current.style.transform = `scale(1.08) translateY(${y}px)`
}
}
window.addEventListener('scroll', handle, { passive: true })
setTimeout(() => bgRef.current?.classList.add('loaded'), 100)
return () => window.removeEventListener('scroll', handle)
}, [])
const handleSeg = (choice: 0 | 1) => {
setSegChoice(choice)
const target = choice === 0 ? '#features' : '#brownleaf'
const el = document.querySelector(target)
if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' })
}
return (
<section className="hero" id="hero" aria-label="Hero">
{/* Background */}
<div
ref={bgRef}
className="hero-bg-image"
style={{ backgroundImage: 'url(/hero-plant.png)' }}
aria-hidden="true"
/>
<div className="hero-bg-overlay" aria-hidden="true" />
<div className="container">
{/* Content */}
<div className="hero-content">
<div className="hero-eyebrow reveal">
<span className="hero-eyebrow-dot" />
<span className="hero-eyebrow-text">{t.hero.eyebrow}</span>
</div>
<h1 className="reveal delay-1">
{t.hero.h1a}<br />{t.hero.h1b}<br />
<em>{t.hero.h1em}</em>
</h1>
<p className="hero-desc reveal delay-2">
{t.hero.desc}
</p>
<div className="hero-actions reveal delay-3">
<a href="#cta" className="btn-primary" id="hero-cta-primary">
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<path d="M11 20A7 7 0 0 1 9.8 6.1C15.5 5 17 4.48 19 2c1 2 2 4.18 2 8 0 5.5-4.78 10-10 10z" />
<path d="M2 21c0-3 1.85-5.36 5.08-6C9.5 14.52 12 13 13 12" />
</svg>
&nbsp;{t.hero.primary}
</a>
<a href="#features" className="btn-outline" id="hero-cta-secondary">
{t.hero.secondary}
</a>
</div>
{/* Segmentation widget */}
<div className="hero-seg reveal delay-4" role="group" aria-label={t.hero.segTitle}>
<p className="hero-seg-title">{t.hero.segTitle}</p>
<div className="hero-seg-options">
<button
className={`hero-seg-btn${segChoice === 0 ? ' hero-seg-btn--active' : ''}`}
onClick={() => handleSeg(0)}
aria-pressed={segChoice === 0}
>
<span className="hero-seg-radio" aria-hidden="true" />
{t.hero.segOpt1}
</button>
<button
className={`hero-seg-btn${segChoice === 1 ? ' hero-seg-btn--active' : ''}`}
onClick={() => handleSeg(1)}
aria-pressed={segChoice === 1}
>
<span className="hero-seg-radio" aria-hidden="true" />
{t.hero.segOpt2}
</button>
</div>
</div>
</div>
{/* Video 16:9 */}
<div className="hero-visual reveal-fade delay-2">
<div className="hero-video-card hero-video-16-9">
<video autoPlay loop muted playsInline aria-label="GreenLens App Demo">
<source src="/GreenLensHype.mp4" type="video/mp4" />
</video>
<div className="hero-video-card-overlay" />
<div className="hero-video-badge">
<span className="hero-video-badge-dot" />
{t.hero.badge}
</div>
</div>
</div>
</div>
<style jsx>{`
.hero-seg {
margin-top: 2rem;
background: rgba(244,241,232,0.06);
border: 1px solid rgba(244,241,232,0.12);
border-radius: 16px;
padding: 1.2rem 1.5rem;
max-width: 460px;
}
.hero-seg-title {
font-size: 0.72rem;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
color: rgba(244,241,232,0.55);
margin-bottom: 0.8rem;
font-family: var(--body);
}
.hero-seg-options {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.hero-seg-btn {
display: flex;
align-items: center;
gap: 0.75rem;
background: transparent;
border: 1.5px solid rgba(244,241,232,0.15);
border-radius: 10px;
padding: 0.65rem 1rem;
color: rgba(244,241,232,0.75);
font-size: 0.82rem;
font-weight: 500;
text-align: left;
cursor: pointer;
transition: background 0.2s, border-color 0.2s, color 0.2s;
font-family: var(--body);
}
.hero-seg-btn:hover {
background: rgba(244,241,232,0.08);
border-color: rgba(244,241,232,0.3);
color: rgba(244,241,232,0.95);
}
.hero-seg-btn--active {
background: rgba(86,160,116,0.15);
border-color: rgba(86,160,116,0.5);
color: #fff;
}
.hero-seg-radio {
width: 14px;
height: 14px;
min-width: 14px;
border-radius: 50%;
border: 2px solid rgba(244,241,232,0.35);
display: inline-block;
transition: border-color 0.2s, background 0.2s;
}
.hero-seg-btn--active .hero-seg-radio {
border-color: var(--green-light);
background: var(--green-light);
box-shadow: 0 0 0 3px rgba(86,160,116,0.2);
}
`}</style>
</section>
)
}