wip
This commit is contained in:
@@ -276,77 +276,79 @@ export default async function ProductPage({ params }: ProductPageProps) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col lg:flex-row relative items-start">
|
||||
{/* Sidebar on Mobile (Above Content) */}
|
||||
<div className="w-full lg:hidden mb-20">
|
||||
<div className="space-y-10">
|
||||
{/* Request Quote Form */}
|
||||
<div className="bg-white rounded-[40px] shadow-[0_32px_64px_-12px_rgba(0,0,0,0.08)] border border-neutral-dark/5 overflow-hidden">
|
||||
<div className="bg-primary-dark p-8 md:p-10 text-white relative overflow-hidden">
|
||||
<div className="absolute top-0 right-0 w-48 h-48 bg-accent/10 rounded-full -translate-y-1/2 translate-x-1/2 blur-3xl" />
|
||||
<h3 className="text-2xl md:text-3xl font-black mb-3 relative z-10 tracking-tight uppercase">{t('requestQuote')}</h3>
|
||||
<p className="text-white/50 text-base relative z-10 leading-relaxed font-medium">{t('requestQuoteDesc')}</p>
|
||||
</div>
|
||||
<div className="p-8 md:p-10">
|
||||
<RequestQuoteForm productName={product.frontmatter.title} />
|
||||
<div className="relative">
|
||||
<div className="space-y-20">
|
||||
{/* Sidebar on Mobile (Above Content) */}
|
||||
<div className="w-full lg:hidden">
|
||||
<div className="space-y-10">
|
||||
{/* Request Quote Form */}
|
||||
<div className="bg-white rounded-[40px] shadow-[0_32px_64px_-12px_rgba(0,0,0,0.08)] border border-neutral-dark/5 overflow-hidden">
|
||||
<div className="bg-primary-dark p-8 md:p-10 text-white relative overflow-hidden">
|
||||
<div className="absolute top-0 right-0 w-48 h-48 bg-accent/10 rounded-full -translate-y-1/2 translate-x-1/2 blur-3xl" />
|
||||
<h3 className="text-2xl md:text-3xl font-black mb-3 relative z-10 tracking-tight uppercase">{t('requestQuote')}</h3>
|
||||
<p className="text-white/50 text-base relative z-10 leading-relaxed font-medium">{t('requestQuoteDesc')}</p>
|
||||
</div>
|
||||
<div className="p-8 md:p-10">
|
||||
<RequestQuoteForm productName={product.frontmatter.title} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Datasheet Download */}
|
||||
{datasheetPath && (
|
||||
<a
|
||||
href={datasheetPath}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="block bg-neutral-light/30 rounded-[40px] border border-neutral-dark/5 overflow-hidden group hover:bg-white hover:shadow-2xl transition-all duration-700"
|
||||
>
|
||||
<div className="p-8 md:p-10 flex items-center gap-8">
|
||||
<div className="w-20 h-20 rounded-3xl bg-white shadow-sm flex items-center justify-center flex-shrink-0 group-hover:bg-accent group-hover:text-white transition-all duration-700 text-accent">
|
||||
<svg className="w-10 h-10" 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-xl font-black text-primary mb-2 uppercase tracking-tight">{t('downloadDatasheet')}</h3>
|
||||
<p className="text-text-secondary text-sm font-medium leading-relaxed">{t('downloadDatasheetDesc')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full">
|
||||
{/* Main Content Area */}
|
||||
<div className="max-w-none">
|
||||
<MDXRemote source={product.content} components={components} />
|
||||
</div>
|
||||
|
||||
{/* Datasheet Download */}
|
||||
{datasheetPath && (
|
||||
<a
|
||||
href={datasheetPath}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="block bg-neutral-light/30 rounded-[40px] border border-neutral-dark/5 overflow-hidden group hover:bg-white hover:shadow-2xl transition-all duration-700"
|
||||
>
|
||||
<div className="p-8 md:p-10 flex items-center gap-8">
|
||||
<div className="w-20 h-20 rounded-3xl bg-white shadow-sm flex items-center justify-center flex-shrink-0 group-hover:bg-accent group-hover:text-white transition-all duration-700 text-accent">
|
||||
<svg className="w-10 h-10" 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-xl font-black text-primary mb-2 uppercase tracking-tight">{t('downloadDatasheet')}</h3>
|
||||
<p className="text-text-secondary text-sm font-medium leading-relaxed">{t('downloadDatasheetDesc')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
)}
|
||||
{/* Structured Data */}
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: JSON.stringify({
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'Product',
|
||||
name: product.frontmatter.title,
|
||||
description: product.frontmatter.description,
|
||||
sku: product.frontmatter.sku,
|
||||
image: product.frontmatter.images?.[0] ? `https://klz-cables.com${product.frontmatter.images[0]}` : undefined,
|
||||
brand: {
|
||||
'@type': 'Brand',
|
||||
name: 'KLZ Cables',
|
||||
},
|
||||
offers: {
|
||||
'@type': 'Offer',
|
||||
availability: 'https://schema.org/InStock',
|
||||
priceCurrency: 'EUR',
|
||||
url: `https://klz-cables.com/${locale}/products/${slug.join('/')}`,
|
||||
},
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 w-full">
|
||||
{/* Main Content Area */}
|
||||
<div className="max-w-none">
|
||||
<MDXRemote source={product.content} components={components} />
|
||||
</div>
|
||||
|
||||
{/* Structured Data */}
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: JSON.stringify({
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'Product',
|
||||
name: product.frontmatter.title,
|
||||
description: product.frontmatter.description,
|
||||
sku: product.frontmatter.sku,
|
||||
image: product.frontmatter.images?.[0] ? `https://klz-cables.com${product.frontmatter.images[0]}` : undefined,
|
||||
brand: {
|
||||
'@type': 'Brand',
|
||||
name: 'KLZ Cables',
|
||||
},
|
||||
offers: {
|
||||
'@type': 'Offer',
|
||||
availability: 'https://schema.org/InStock',
|
||||
priceCurrency: 'EUR',
|
||||
url: `https://klz-cables.com/${locale}/products/${slug.join('/')}`,
|
||||
},
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ProductSidebar
|
||||
productName={product.frontmatter.title}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import Image from 'next/image';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import RequestQuoteForm from '@/components/RequestQuoteForm';
|
||||
@@ -13,30 +13,62 @@ interface ProductSidebarProps {
|
||||
|
||||
export default function ProductSidebar({ productName, productImage, datasheetPath }: ProductSidebarProps) {
|
||||
const t = useTranslations('Products');
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const sidebarRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
// Show sidebar after scrolling down 400px (approx height of hero)
|
||||
if (window.scrollY > 400) {
|
||||
setIsVisible(true);
|
||||
} else {
|
||||
setIsVisible(false);
|
||||
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)`;
|
||||
};
|
||||
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
// Check initial scroll position
|
||||
handleScroll();
|
||||
let rafId: number;
|
||||
const onScroll = () => {
|
||||
rafId = requestAnimationFrame(handleScroll);
|
||||
};
|
||||
|
||||
window.addEventListener('scroll', onScroll, { passive: true });
|
||||
window.addEventListener('resize', handleScroll);
|
||||
|
||||
return () => window.removeEventListener('scroll', handleScroll);
|
||||
// Initial call
|
||||
handleScroll();
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('scroll', onScroll);
|
||||
window.removeEventListener('resize', handleScroll);
|
||||
cancelAnimationFrame(rafId);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`hidden lg:block fixed top-32 right-8 z-50 w-[350px] xl:w-[400px] transition-all duration-700 transform ${
|
||||
isVisible ? 'translate-x-0 opacity-100' : 'translate-x-[120%] opacity-0'
|
||||
}`}
|
||||
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 */}
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user