This commit is contained in:
2026-01-19 19:09:32 +01:00
parent e8b6b13a3b
commit 5c9b2e3f5a
5 changed files with 173 additions and 205 deletions

View File

@@ -1,123 +1,97 @@
'use client';
import { useEffect, useRef } from 'react';
import Image from 'next/image';
import { useTranslations } from 'next-intl';
import RequestQuoteForm from '@/components/RequestQuoteForm';
import Scribble from '@/components/Scribble';
import { cn } from '@/components/ui/utils';
interface ProductSidebarProps {
productName: string;
productImage?: string;
datasheetPath?: string | null;
className?: string;
}
export default function ProductSidebar({ productName, productImage, datasheetPath }: ProductSidebarProps) {
export default function ProductSidebar({ productName, productImage, datasheetPath, className }: ProductSidebarProps) {
const t = useTranslations('Products');
const sidebarRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const handleScroll = () => {
if (!sidebarRef.current) return;
const sidebar = sidebarRef.current;
const container = sidebar.parentElement;
if (!container) return;
const containerRect = container.getBoundingClientRect();
const sidebarHeight = sidebar.offsetHeight;
// Offset from top of viewport when sticky
const stickyOffset = 128; // 8rem = top-32
let translateY = 0;
// If the top of the container has scrolled past our sticky offset
if (containerRect.top < stickyOffset) {
translateY = stickyOffset - containerRect.top;
}
// Don't let it go past the bottom of the container
const maxTranslateY = containerRect.height - sidebarHeight;
if (translateY > maxTranslateY) {
translateY = maxTranslateY;
}
// Ensure translateY is never negative
if (translateY < 0) translateY = 0;
sidebar.style.transform = `translateY(${translateY}px)`;
};
let rafId: number;
const onScroll = () => {
rafId = requestAnimationFrame(handleScroll);
};
window.addEventListener('scroll', onScroll, { passive: true });
window.addEventListener('resize', handleScroll);
// Initial call
handleScroll();
return () => {
window.removeEventListener('scroll', onScroll);
window.removeEventListener('resize', handleScroll);
cancelAnimationFrame(rafId);
};
}, []);
return (
<div
ref={sidebarRef}
className="hidden lg:block absolute left-full ml-12 top-0 w-[350px] xl:w-[400px] z-30 will-change-transform"
>
<div className="space-y-6">
{/* Request Quote Form */}
<div className="bg-white rounded-[32px] shadow-2xl border border-neutral-dark/5 overflow-hidden">
<div className="bg-primary-dark p-6 text-white relative overflow-hidden">
<div className="absolute top-0 right-0 w-32 h-32 bg-accent/10 rounded-full -translate-y-1/2 translate-x-1/2 blur-2xl" />
{/* Product Thumbnail */}
{productImage && (
<div className="relative w-full aspect-[21/9] mb-4 rounded-2xl overflow-hidden bg-white/5 backdrop-blur-xl p-4 border border-white/10 z-10">
<div className={cn("flex flex-col gap-4 animate-slight-fade-in-from-bottom", className)}>
{/* Request Quote Form Card */}
<div className="bg-white rounded-3xl border border-neutral-medium shadow-sm transition-all duration-500 hover:shadow-2xl hover:-translate-y-1 overflow-hidden group/card">
<div className="bg-primary p-6 text-white relative overflow-hidden">
{/* Background Accent - Saturated Blue Glow */}
<div className="absolute top-0 right-0 w-40 h-40 bg-saturated/30 rounded-full -translate-y-1/2 translate-x-1/2 blur-[80px] pointer-events-none" />
{/* Product Thumbnail with Reflection */}
{productImage && (
<div className="relative w-full aspect-[16/10] mb-6 rounded-2xl overflow-hidden bg-white/5 backdrop-blur-md p-4 border border-white/10 z-10 group">
<div className="relative w-full h-full transition-transform duration-1000 ease-out group-hover:scale-105">
<Image
src={productImage}
alt=""
alt={productName}
fill
className="object-contain p-1"
className="object-contain p-2 drop-shadow-[0_20px_30px_rgba(0,0,0,0.4)]"
/>
</div>
)}
<h3 className="text-xl font-black mb-2 relative z-10 tracking-tight uppercase">{t('requestQuote')}</h3>
<p className="text-white/50 text-sm relative z-10 leading-relaxed font-medium">{t('requestQuoteDesc')}</p>
</div>
<div className="p-6">
<RequestQuoteForm productName={productName} />
</div>
</div>
{/* Datasheet Download */}
{datasheetPath && (
<a
href={datasheetPath}
target="_blank"
rel="noopener noreferrer"
className="block bg-white/90 backdrop-blur-md rounded-[32px] border border-neutral-dark/5 overflow-hidden group hover:bg-white hover:shadow-xl transition-all duration-500 shadow-lg"
>
<div className="p-6 flex items-center gap-6">
<div className="w-14 h-14 rounded-2xl bg-neutral-light shadow-sm flex items-center justify-center flex-shrink-0 group-hover:bg-accent group-hover:text-white transition-all duration-500 text-accent">
<svg className="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
</div>
<div className="flex-1">
<h3 className="text-lg font-black text-primary mb-1 uppercase tracking-tight">{t('downloadDatasheet')}</h3>
<p className="text-text-secondary text-xs font-medium leading-relaxed">{t('downloadDatasheetDesc')}</p>
{/* Subtle Reflection Overlay */}
<div className="absolute inset-0 bg-gradient-to-tr from-white/20 via-transparent to-transparent opacity-30 pointer-events-none" />
</div>
</div>
</a>
)}
)}
<div className="relative z-10">
<div className="inline-block relative mb-2">
<h3 className="text-xl font-heading font-black m-0 tracking-tighter uppercase leading-none">
{t('requestQuote')}
</h3>
<Scribble
variant="underline"
className="w-full h-3 -bottom-3 left-0 text-accent/80"
color="var(--color-accent)"
/>
</div>
<p className="text-white/60 text-xs m-0 mt-2 leading-relaxed font-medium max-w-[90%]">
{t('requestQuoteDesc')}
</p>
</div>
</div>
<div className="p-6 bg-neutral-light/50">
<RequestQuoteForm productName={productName} />
</div>
</div>
{/* Datasheet Download */}
{datasheetPath && (
<a
href={datasheetPath}
target="_blank"
rel="noopener noreferrer"
className="block bg-white rounded-2xl border border-neutral-medium overflow-hidden group transition-all duration-500 hover:shadow-xl hover:border-saturated/30 hover:-translate-y-0.5"
>
<div className="p-4 flex items-center gap-4">
<div className="w-12 h-12 rounded-xl bg-neutral-medium/20 flex items-center justify-center flex-shrink-0 group-hover:bg-saturated group-hover:text-white transition-all duration-500 text-saturated border border-transparent group-hover:border-white/20">
<svg className="w-6 h-6 transition-transform duration-500 group-hover:scale-110" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
</div>
<div className="flex-1 min-w-0">
<h3 className="text-sm font-heading font-black text-neutral-dark m-0 uppercase tracking-tighter leading-tight group-hover:text-saturated transition-colors duration-300">
{t('downloadDatasheet')}
</h3>
<p className="text-text-secondary text-[10px] m-0 mt-0.5 font-semibold leading-tight truncate uppercase tracking-widest opacity-60">
{t('downloadDatasheetDesc')}
</p>
</div>
<div className="text-neutral-dark/20 group-hover:text-saturated transition-all duration-500 transform group-hover:translate-x-1">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M9 5l7 7-7 7" />
</svg>
</div>
</div>
</a>
)}
</div>
);
}