Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
171 changes: 122 additions & 49 deletions client/src/components/HeroSection.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,97 @@
import { useEffect, useRef, useState } from "react";
import { ArrowDown, ArrowUpRight, Check } from "lucide-react";
import { CSSProperties, useEffect, useRef, useState } from "react";
import { ArrowDown, ArrowUpRight, Check, Cog } from "lucide-react";
import { trackCTAClick } from "@/lib/analytics";

const backgrounds = ["geometry", "matrix", "gears"] as const;
type Background = (typeof backgrounds)[number];

const pickBackground = (): Background => {
const requested = new URLSearchParams(location.search).get("background");
if (backgrounds.includes(requested as Background)) return requested as Background;

try {
const previous = sessionStorage.getItem("primebuild-background");
const choices = backgrounds.filter((item) => item !== previous);
const selected = choices[Math.floor(Math.random() * choices.length)];
sessionStorage.setItem("primebuild-background", selected);
return selected;
} catch {
return backgrounds[Math.floor(Math.random() * backgrounds.length)];
}
};

const HeroBackground = ({ variant }: { variant: Background }) => {
if (variant === "matrix") {
return (
<div className="matrix-field absolute inset-0" aria-hidden="true">
<div className="matrix-reticle"><span /><span /><span /></div>
<div className="matrix-panel matrix-panel-a"><b>PRIME // CORE</b><span>BUILD STATUS: READY</span><span>THERMAL LINK: STABLE</span></div>
<div className="matrix-panel matrix-panel-b"><b>SYS.01</b><span>LATENCY 01.6MS</span><span>FRAME SYNC ACTIVE</span></div>
<div className="matrix-bracket matrix-bracket-left" />
<div className="matrix-bracket matrix-bracket-right" />
{Array.from({ length: 18 }, (_, index) => (
<span
key={index}
className="matrix-column"
style={{
left: `${1 + index * 5.7}%`,
animationDuration: `${8 + (index % 5) * 1.3}s`,
animationDelay: `${-index * 0.73}s`,
}}
>
{index % 3 === 0 ? "01001101011010010100110101101001" : "10110010100101100110100100110110"}
</span>
))}
</div>
);
}

if (variant === "gears") {
const gears = [
["5%", "10%", "17rem", "gear-clockwise"],
["23%", "56%", "11rem", "gear-counter"],
["56%", "5%", "23rem", "gear-counter"],
["71%", "59%", "15rem", "gear-clockwise"],
["87%", "27%", "9rem", "gear-counter"],
["45%", "73%", "7rem", "gear-clockwise"],
];
return (
<div className="gear-field absolute inset-0" aria-hidden="true">
<div className="hero-glow" />
<div className="gear-belt gear-belt-a" />
<div className="gear-belt gear-belt-b" />
<div className="gear-blueprint gear-blueprint-a">DRIVE TRAIN / 01</div>
<div className="gear-blueprint gear-blueprint-b">TORQUE SYNC / ACTIVE</div>
{gears.map(([left, top, size, animation], index) => (
<div
key={`${left}-${top}`}
className={`hero-gear ${animation}`}
style={{ left, top, width: size, height: size, animationDelay: `${-index * 1.7}s` } as CSSProperties}
>
<Cog className="gear-icon" strokeWidth={0.8} />
<span className="gear-inner" />
<span className="gear-hub" />
</div>
))}
</div>
);
}

return (
<div className="hero-geometry absolute inset-0" aria-hidden="true">
<div className="hero-glow" />
<div className="hero-ring hero-ring-one" />
<div className="hero-ring hero-ring-two" />
<div className="hero-prism" />
<div className="hero-scan" />
</div>
);
};

const HeroSection = () => {
const sectionRef = useRef<HTMLElement>(null);
const [isVisible, setIsVisible] = useState(true);
const [background] = useState(pickBackground);

useEffect(() => {
const section = sectionRef.current;
Expand All @@ -15,60 +102,46 @@ const HeroSection = () => {
}, []);

return (
<section ref={sectionRef} id="home" className={`relative isolate flex min-h-[100svh] items-center overflow-hidden bg-[#050505] pt-24 ${isVisible ? "" : "motion-idle"}`}>
<div className="hero-geometry absolute inset-0" aria-hidden="true">
<div className="hero-glow" />
<div className="hero-ring hero-ring-one" />
<div className="hero-ring hero-ring-two" />
<div className="hero-prism" />
<div className="hero-scan" />
</div>
<section ref={sectionRef} id="home" className={`relative isolate flex min-h-[100svh] items-center overflow-hidden bg-[#050505] pt-24 ${isVisible ? "" : "motion-idle"}`}>
<HeroBackground variant={background} />

<div className="relative z-10 mx-auto w-full max-w-7xl px-5 py-20 text-center sm:px-8">
<p className="hero-enter mx-auto mb-7 flex max-w-full w-fit items-center justify-center gap-3 rounded-full border border-white/10 bg-black/30 px-4 py-2 text-center text-[0.65rem] font-semibold uppercase tracking-[0.18em] text-neutral-300 backdrop-blur-xl sm:text-[0.68rem] sm:tracking-[0.22em]">
<span className="h-1.5 w-1.5 shrink-0 rounded-full bg-primary shadow-[0_0_12px_hsl(var(--primary))]" />
Montegrotto Terme · 35036 (PD)
</p>
<div className="relative z-10 mx-auto w-full max-w-7xl px-5 py-20 text-center sm:px-8">
<p className="hero-enter mx-auto mb-7 flex max-w-full w-fit items-center justify-center gap-3 rounded-full border border-white/10 bg-black/30 px-4 py-2 text-center text-[0.65rem] font-semibold uppercase tracking-[0.18em] text-neutral-300 backdrop-blur-xl sm:text-[0.68rem] sm:tracking-[0.22em]">
<span className="h-1.5 w-1.5 shrink-0 rounded-full bg-primary shadow-[0_0_12px_hsl(var(--primary))]" />
Montegrotto Terme · 35036 (PD)
</p>

<h1 className="mx-auto font-montserrat font-semibold uppercase leading-[0.77] tracking-[-0.075em]" aria-label="Prime Build — PC gaming su misura">
<span className="brand-enter-left block text-[clamp(4.2rem,15vw,12.5rem)] text-white">
Prime
</span>
<span className="brand-enter-right block text-[clamp(4.2rem,15vw,12.5rem)] text-primary">
Build
</span>
</h1>
<h1 className="mx-auto font-montserrat font-semibold uppercase leading-[0.77] tracking-[-0.075em]" aria-label="Prime Build — PC gaming su misura">
<span className="brand-enter-left block text-[clamp(4.2rem,15vw,12.5rem)] text-white">Prime</span>
<span className="brand-enter-right block text-[clamp(4.2rem,15vw,12.5rem)] text-primary">Build</span>
</h1>

<div className="mx-auto mt-10 max-w-2xl">
<p className="text-base leading-relaxed text-neutral-300 sm:text-lg md:text-xl">
PC gaming progettati intorno a te. Consulenza reale, assemblaggio preciso e prestazioni ottimizzate senza sprechi.
</p>
<div className="mt-8 flex flex-col justify-center gap-3 sm:flex-row">
<a
href="#contact"
onClick={() => trackCTAClick("Configura il tuo PC", "hero")}
className="button-primary group"
>
Configura il tuo PC
<ArrowUpRight className="h-4 w-4 transition-transform group-hover:-translate-y-0.5 group-hover:translate-x-0.5" aria-hidden="true" />
</a>
<a href="#services" className="button-secondary">Scopri Prime Build</a>
<div className="mx-auto mt-10 max-w-2xl">
<p className="text-base leading-relaxed text-neutral-300 sm:text-lg md:text-xl">
PC gaming progettati intorno a te. Consulenza reale, assemblaggio preciso e prestazioni ottimizzate senza sprechi.
</p>
<div className="mt-8 flex flex-col justify-center gap-3 sm:flex-row">
<a href="#contact" onClick={() => trackCTAClick("Configura il tuo PC", "hero")} className="button-primary group">
Configura il tuo PC
<ArrowUpRight className="h-4 w-4 transition-transform group-hover:-translate-y-0.5 group-hover:translate-x-0.5" aria-hidden="true" />
</a>
<a href="#services" className="button-secondary">Scopri Prime Build</a>
</div>
</div>
</div>

<div className="hero-enter hero-enter-delay mx-auto mt-10 flex max-w-3xl flex-wrap justify-center gap-x-7 gap-y-3 text-xs text-neutral-400 sm:text-sm">
{["Preventivo gratuito", "Componenti selezionati", "Supporto post-vendita"].map((item) => (
<span key={item} className="flex items-center gap-2">
<Check className="h-3.5 w-3.5 text-primary" aria-hidden="true" /> {item}
</span>
))}
<div className="hero-enter hero-enter-delay mx-auto mt-10 flex max-w-3xl flex-wrap justify-center gap-x-7 gap-y-3 text-xs text-neutral-400 sm:text-sm">
{["Preventivo gratuito", "Componenti selezionati", "Supporto post-vendita"].map((item) => (
<span key={item} className="flex items-center gap-2">
<Check className="h-3.5 w-3.5 text-primary" aria-hidden="true" /> {item}
</span>
))}
</div>
</div>
</div>

<a href="#services" aria-label="Vai ai servizi" className="absolute bottom-5 left-1/2 z-10 hidden -translate-x-1/2 items-center gap-2 text-[0.65rem] uppercase tracking-[0.25em] text-neutral-500 transition hover:text-white sm:flex">
Esplora <ArrowDown className="h-4 w-4 animate-bounce" aria-hidden="true" />
</a>
</section>
<a href="#services" aria-label="Vai ai servizi" className="absolute bottom-5 left-1/2 z-10 hidden -translate-x-1/2 items-center gap-2 text-[0.65rem] uppercase tracking-[0.25em] text-neutral-500 transition hover:text-white sm:flex">
Esplora <ArrowDown className="h-4 w-4 animate-bounce" aria-hidden="true" />
</a>
</section>
);
};

Expand Down
153 changes: 153 additions & 0 deletions client/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@
html.motion-preview .hero-ring-two { animation-duration: 17s !important; animation-iteration-count: infinite !important; }
html.motion-preview .hero-prism { animation-duration: 10s !important; animation-iteration-count: infinite !important; }
html.motion-preview .hero-scan { animation-duration: 2.4s !important; animation-iteration-count: 1 !important; }
html.motion-preview .matrix-column { animation-duration: 12s !important; animation-iteration-count: infinite !important; }
html.motion-preview .gear-clockwise { animation-duration: 28s !important; animation-iteration-count: infinite !important; }
html.motion-preview .gear-counter { animation-duration: 34s !important; animation-iteration-count: infinite !important; }
html.motion-preview .hero-enter { animation-duration: .6s !important; }
html.motion-preview .brand-enter-left,
html.motion-preview .brand-enter-right { animation-duration: .85s !important; }
Expand Down Expand Up @@ -120,6 +123,150 @@
background: linear-gradient(315deg, hsl(var(--primary) / .55), rgba(255,255,255,.16));
transform: rotate(-12deg);
}
.matrix-field {
overflow: hidden;
background:
linear-gradient(rgba(255,102,0,.035) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,102,0,.035) 1px, transparent 1px),
radial-gradient(circle at 50% 48%, #120b07, #050505 68%);
background-size: 64px 64px, 64px 64px, auto;
mask-image: linear-gradient(to bottom, #000 84%, transparent);
}
.matrix-column {
position: absolute;
top: -75%;
color: hsl(var(--primary) / .48);
font: 600 12px/1.55 ui-monospace, SFMono-Regular, Menlo, monospace;
letter-spacing: .12em;
text-orientation: upright;
text-shadow: 0 0 10px hsl(var(--primary) / .55);
writing-mode: vertical-rl;
animation-name: matrix-rain;
animation-timing-function: linear;
animation-iteration-count: infinite;
will-change: transform;
}
.matrix-column:nth-child(3n) { color: rgba(255,255,255,.36); font-size: 10px; }
.matrix-column:nth-child(even) { opacity: .56; }
.matrix-column:nth-child(5n) { font-size: 14px; opacity: .72; }
.matrix-reticle {
position: absolute;
left: 50%;
top: 50%;
width: min(54vw, 42rem);
aspect-ratio: 1;
translate: -50% -50%;
border: 1px solid hsl(var(--primary) / .15);
border-radius: 50%;
box-shadow: inset 0 0 70px hsl(var(--primary) / .04);
}
.matrix-reticle::before,
.matrix-reticle::after,
.matrix-reticle span {
content: "";
position: absolute;
border: 1px solid hsl(var(--primary) / .1);
border-radius: 50%;
}
.matrix-reticle::before { inset: 11%; border-style: dashed; }
.matrix-reticle::after { inset: 28%; }
.matrix-reticle span:nth-child(1) { inset: 4% 49%; border-radius: 0; }
.matrix-reticle span:nth-child(2) { inset: 49% 4%; border-radius: 0; }
.matrix-reticle span:nth-child(3) { inset: 38%; background: hsl(var(--primary) / .035); }
.matrix-panel {
position: absolute;
z-index: 1;
display: grid;
gap: .3rem;
min-width: 12rem;
padding: .75rem 1rem;
border: 1px solid hsl(var(--primary) / .16);
background: rgba(0,0,0,.28);
color: hsl(var(--primary) / .5);
font: 500 9px/1.35 ui-monospace, SFMono-Regular, Menlo, monospace;
letter-spacing: .13em;
}
.matrix-panel b { color: hsl(var(--primary) / .82); font-size: 10px; }
.matrix-panel-a { left: 5%; bottom: 15%; }
.matrix-panel-b { right: 5%; top: 16%; text-align: right; }
.matrix-bracket {
position: absolute;
top: 24%;
bottom: 22%;
width: 4rem;
border-block: 1px solid hsl(var(--primary) / .2);
}
.matrix-bracket-left { left: 12%; border-left: 1px solid hsl(var(--primary) / .2); }
.matrix-bracket-right { right: 12%; border-right: 1px solid hsl(var(--primary) / .2); }

.gear-field {
overflow: hidden;
background:
linear-gradient(rgba(255,255,255,.025) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,.025) 1px, transparent 1px),
radial-gradient(circle at 50% 45%, #17100b, #050505 70%);
background-size: 72px 72px, 72px 72px, auto;
mask-image: linear-gradient(to bottom, #000 84%, transparent);
}
.hero-gear {
position: absolute;
color: hsl(var(--primary) / .28);
will-change: transform;
}
.hero-gear::before {
content: "";
position: absolute;
inset: 7%;
border: 1px dashed hsl(var(--primary) / .22);
border-radius: 50%;
}
.gear-icon { width: 100%; height: 100%; }
.gear-inner {
position: absolute;
inset: 31%;
border: 1px solid hsl(var(--primary) / .36);
border-radius: 50%;
box-shadow: inset 0 0 18px hsl(var(--primary) / .08);
}
.gear-hub {
position: absolute;
left: 50%;
top: 50%;
width: 8%;
aspect-ratio: 1;
translate: -50% -50%;
border-radius: 50%;
background: hsl(var(--primary) / .7);
box-shadow: 0 0 14px hsl(var(--primary) / .36);
}
.gear-belt {
position: absolute;
height: 12rem;
border-block: 1px dashed hsl(var(--primary) / .15);
transform-origin: left center;
}
.gear-belt-a { left: 14%; top: 28%; width: 54%; transform: rotate(14deg); }
.gear-belt-b { left: 52%; top: 55%; width: 38%; transform: rotate(-18deg); }
.gear-blueprint {
position: absolute;
padding-left: 2rem;
color: hsl(var(--primary) / .38);
font: 600 9px/1 ui-monospace, SFMono-Regular, Menlo, monospace;
letter-spacing: .2em;
}
.gear-blueprint::before { content: ""; position: absolute; left: 0; top: 50%; width: 1.4rem; border-top: 1px solid hsl(var(--primary) / .35); }
.gear-blueprint-a { left: 8%; bottom: 13%; }
.gear-blueprint-b { right: 7%; top: 17%; }
.gear-clockwise { animation: gear-clockwise 28s linear infinite; }
.gear-counter { animation: gear-counter 34s linear infinite; }

@media (max-width: 640px) {
.matrix-panel, .matrix-bracket, .gear-blueprint, .gear-belt { display: none; }
.matrix-reticle { width: 145vw; opacity: .55; }
.matrix-column:nth-child(even) { display: none; }
.hero-gear { opacity: .72; }
}

.hero-glow {
position: absolute;
inset: 18% 22%;
Expand Down Expand Up @@ -229,6 +376,12 @@ html.motion-paused *::after,
animation-play-state: paused !important;
}

@keyframes matrix-rain {
from { transform: translate3d(0, -10%, 0); }
to { transform: translate3d(0, 190vh, 0); }
}
@keyframes gear-clockwise { to { transform: rotate(360deg); } }
@keyframes gear-counter { to { transform: rotate(-360deg); } }
@keyframes enter-up { from { opacity: 0; transform: translateY(12px); } }
@keyframes brand-left { from { transform: translateX(-18vw) scale(.88); letter-spacing: .02em; } }
@keyframes brand-right { from { transform: translateX(18vw) scale(.88); letter-spacing: .02em; } }
Expand Down
Loading