This commit is contained in:
2026-03-29 10:26:38 -05:00
parent 05d4f6e78b
commit b1c99893a6
1628 changed files with 67782 additions and 60143 deletions

View File

@@ -1,189 +1,189 @@
'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>
)
}
'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>
)
}