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

@@ -2,7 +2,6 @@ import ProductSidebar from '@/components/ProductSidebar';
import ProductTabs from '@/components/ProductTabs'; import ProductTabs from '@/components/ProductTabs';
import ProductTechnicalData from '@/components/ProductTechnicalData'; import ProductTechnicalData from '@/components/ProductTechnicalData';
import RelatedProducts from '@/components/RelatedProducts'; import RelatedProducts from '@/components/RelatedProducts';
import RequestQuoteForm from '@/components/RequestQuoteForm';
import { Badge, Container, Section } from '@/components/ui'; import { Badge, Container, Section } from '@/components/ui';
import { getDatasheetPath } from '@/lib/datasheets'; import { getDatasheetPath } from '@/lib/datasheets';
import { getAllProducts, getProductBySlug } from '@/lib/mdx'; import { getAllProducts, getProductBySlug } from '@/lib/mdx';
@@ -66,33 +65,33 @@ export async function generateMetadata({ params }: ProductPageProps): Promise<Me
const components = { const components = {
ProductTechnicalData, ProductTechnicalData,
ProductTabs, ProductTabs,
p: (props: any) => <p {...props} className="text-lg md:text-xl text-text-secondary leading-relaxed mb-6 font-medium block !mb-6" />, p: (props: any) => <p {...props} className="text-lg md:text-xl text-text-secondary leading-relaxed mb-8 font-medium" />,
h2: (props: any) => ( h2: (props: any) => (
<div className="relative mt-20 mb-10 block !mt-20 !mb-10"> <div className="relative mb-16">
<h2 {...props} className="text-4xl md:text-5xl font-black text-primary tracking-tighter uppercase mb-6 block !mb-6" /> <h2 {...props} className="text-4xl md:text-5xl font-black text-primary tracking-tighter uppercase mb-6" />
<div className="w-20 h-1.5 bg-accent rounded-full" /> <div className="w-20 h-1.5 bg-accent rounded-full" />
</div> </div>
), ),
h3: (props: any) => <h3 {...props} className="text-2xl md:text-3xl font-black text-primary mt-16 mb-6 tracking-tight uppercase block !mt-16 !mb-6" />, h3: (props: any) => <h3 {...props} className="text-2xl md:text-3xl font-black text-primary mb-10 tracking-tight uppercase" />,
ul: (props: any) => <ul {...props} className="list-none pl-0 mt-4 mb-8 space-y-4 block !mt-4 !mb-8" />, ul: (props: any) => <ul {...props} className="list-none pl-0 mb-10" />,
section: (props: any) => <div {...props} className="block" />, section: (props: any) => <div {...props} className="block" />,
li: (props: any) => ( li: (props: any) => (
<li className="flex items-start gap-4 group"> <li className="flex items-start gap-4 group mb-4 last:mb-0">
<div className="mt-2.5 w-2 h-2 rounded-full bg-accent flex-shrink-0 group-hover:scale-125 transition-transform" /> <div className="mt-2.5 w-2 h-2 rounded-full bg-accent flex-shrink-0 group-hover:scale-125 transition-transform" />
<span {...props} className="text-lg md:text-xl text-text-secondary leading-relaxed font-medium" /> <span {...props} className="text-lg md:text-xl text-text-secondary leading-relaxed font-medium" />
</li> </li>
), ),
strong: (props: any) => <strong {...props} className="font-black text-primary" />, strong: (props: any) => <strong {...props} className="font-black text-primary" />,
table: (props: any) => ( table: (props: any) => (
<div className="overflow-x-auto my-16 rounded-[32px] border border-neutral-dark/10 shadow-xl bg-white p-1"> <div className="overflow-x-auto my-20 rounded-[32px] border border-neutral-dark/10 shadow-xl bg-white p-1">
<table {...props} className="min-w-full divide-y divide-neutral-dark/10" /> <table {...props} className="min-w-full divide-y divide-neutral-dark/10" />
</div> </div>
), ),
th: (props: any) => <th {...props} className="px-8 py-6 bg-neutral-light/50 text-left text-[10px] font-black uppercase tracking-[0.25em] text-primary/60" />, th: (props: any) => <th {...props} className="px-8 py-6 bg-neutral-light/50 text-left text-[10px] font-black uppercase tracking-[0.25em] text-primary/60" />,
td: (props: any) => <td {...props} className="px-8 py-6 text-text-secondary border-t border-neutral-dark/5 text-lg md:text-xl font-medium" />, td: (props: any) => <td {...props} className="px-8 py-6 text-text-secondary border-t border-neutral-dark/5 text-lg md:text-xl font-medium" />,
hr: () => <hr className="my-20 border-t-2 border-neutral-dark/5" />, hr: () => <hr className="my-24 border-t-2 border-neutral-dark/5" />,
blockquote: (props: any) => ( blockquote: (props: any) => (
<div className="my-16 p-10 md:p-16 bg-primary-dark rounded-[40px] relative overflow-hidden group"> <div className="my-20 p-10 md:p-16 bg-primary-dark rounded-[40px] relative overflow-hidden group">
<div className="absolute top-0 right-0 w-64 h-64 bg-accent/10 rounded-full -translate-y-1/2 translate-x-1/2 blur-3xl group-hover:bg-accent/20 transition-colors duration-700" /> <div className="absolute top-0 right-0 w-64 h-64 bg-accent/10 rounded-full -translate-y-1/2 translate-x-1/2 blur-3xl group-hover:bg-accent/20 transition-colors duration-700" />
<div className="relative z-10 italic text-2xl md:text-4xl text-white/90 leading-relaxed font-black tracking-tight" {...props} /> <div className="relative z-10 italic text-2xl md:text-4xl text-white/90 leading-relaxed font-black tracking-tight" {...props} />
</div> </div>
@@ -201,6 +200,30 @@ export default async function ProductPage({ params }: ProductPageProps) {
const categoryKey = categorySlug.replace(/-cables$/, '').replace(/-([a-z])/g, (g) => g[1].toUpperCase()); const categoryKey = categorySlug.replace(/-cables$/, '').replace(/-([a-z])/g, (g) => g[1].toUpperCase());
const categoryTitle = t(`categories.${categoryKey}.title`); const categoryTitle = t(`categories.${categoryKey}.title`);
const sidebar = (
<ProductSidebar
productName={product.frontmatter.title}
productImage={product.frontmatter.images?.[0]}
datasheetPath={datasheetPath}
/>
);
const productComponents = {
...components,
ProductTabs: (props: any) => <ProductTabs {...props} sidebar={sidebar} />,
};
// Pre-process content to convert raw HTML tags to Markdown so they use our custom components
const processedContent = product.content
.replace(/<h2[^>]*>(.*?)<\/h2>/g, '\n## $1\n')
.replace(/<h3[^>]*>(.*?)<\/h3>/g, '\n### $1\n')
.replace(/<p[^>]*>(.*?)<\/p>/g, '\n$1\n')
.replace(/<ul[^>]*>(.*?)<\/ul>/gs, '\n$1\n')
.replace(/<li[^>]*>(.*?)<\/li>/g, '\n- $1\n')
.replace(/<strong[^>]*>(.*?)<\/strong>/g, '**$1**')
.replace(/<section[^>]*>/g, '')
.replace(/<\/section>/g, '');
return ( return (
<div className="flex flex-col min-h-screen bg-white relative"> <div className="flex flex-col min-h-screen bg-white relative">
{/* Product Hero */} {/* Product Hero */}
@@ -278,84 +301,37 @@ export default async function ProductPage({ params }: ProductPageProps) {
)} )}
<div className="relative"> <div className="relative">
<div className="space-y-20"> <div className="w-full">
{/* Sidebar on Mobile (Above Content) */} {/* Main Content Area */}
<div className="w-full lg:hidden"> <div className="max-w-none">
<div className="space-y-10"> <MDXRemote source={processedContent} components={productComponents} />
{/* 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>
<div className="w-full"> {/* Structured Data */}
{/* Main Content Area */} <script
<div className="max-w-none [&_h3]:mt-16 [&_h3]:mb-6 [&_p]:mb-6 [&_ul]:mt-4 [&_ul]:mb-8 [&_li]:mb-4"> type="application/ld+json"
<MDXRemote source={product.content} components={components} /> dangerouslySetInnerHTML={{
</div> __html: JSON.stringify({
'@context': 'https://schema.org',
{/* Structured Data */} '@type': 'Product',
<script name: product.frontmatter.title,
type="application/ld+json" description: product.frontmatter.description,
dangerouslySetInnerHTML={{ sku: product.frontmatter.sku,
__html: JSON.stringify({ image: product.frontmatter.images?.[0] ? `https://klz-cables.com${product.frontmatter.images[0]}` : undefined,
'@context': 'https://schema.org', brand: {
'@type': 'Product', '@type': 'Brand',
name: product.frontmatter.title, name: 'KLZ Cables',
description: product.frontmatter.description, },
sku: product.frontmatter.sku, offers: {
image: product.frontmatter.images?.[0] ? `https://klz-cables.com${product.frontmatter.images[0]}` : undefined, '@type': 'Offer',
brand: { availability: 'https://schema.org/InStock',
'@type': 'Brand', priceCurrency: 'EUR',
name: 'KLZ Cables', url: `https://klz-cables.com/${locale}/products/${slug.join('/')}`,
}, },
offers: { }),
'@type': 'Offer', }}
availability: 'https://schema.org/InStock', />
priceCurrency: 'EUR',
url: `https://klz-cables.com/${locale}/products/${slug.join('/')}`,
},
}),
}}
/>
</div>
</div> </div>
<ProductSidebar
productName={product.frontmatter.title}
productImage={product.frontmatter.images?.[0]}
datasheetPath={datasheetPath}
/>
</div> </div>
{/* Related Products Section */} {/* Related Products Section */}

View File

@@ -1,123 +1,97 @@
'use client'; 'use client';
import { useEffect, useRef } from 'react';
import Image from 'next/image'; import Image from 'next/image';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import RequestQuoteForm from '@/components/RequestQuoteForm'; import RequestQuoteForm from '@/components/RequestQuoteForm';
import Scribble from '@/components/Scribble';
import { cn } from '@/components/ui/utils';
interface ProductSidebarProps { interface ProductSidebarProps {
productName: string; productName: string;
productImage?: string; productImage?: string;
datasheetPath?: string | null; 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 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 ( return (
<div <div className={cn("flex flex-col gap-4 animate-slight-fade-in-from-bottom", className)}>
ref={sidebarRef} {/* Request Quote Form Card */}
className="hidden lg:block absolute left-full ml-12 top-0 w-[350px] xl:w-[400px] z-30 will-change-transform" <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">
<div className="space-y-6"> {/* Background Accent - Saturated Blue Glow */}
{/* Request Quote Form */} <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" />
<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"> {/* Product Thumbnail with Reflection */}
<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" /> {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">
{/* Product Thumbnail */} <div className="relative w-full h-full transition-transform duration-1000 ease-out group-hover:scale-105">
{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">
<Image <Image
src={productImage} src={productImage}
alt="" alt={productName}
fill fill
className="object-contain p-1" className="object-contain p-2 drop-shadow-[0_20px_30px_rgba(0,0,0,0.4)]"
/> />
</div> {/* Subtle Reflection Overlay */}
)} <div className="absolute inset-0 bg-gradient-to-tr from-white/20 via-transparent to-transparent opacity-30 pointer-events-none" />
<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>
</div> </div>
</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> </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> </div>
); );
} }

View File

@@ -3,13 +3,29 @@ import React from 'react';
interface ProductTabsProps { interface ProductTabsProps {
children: React.ReactNode; children: React.ReactNode;
technicalData?: React.ReactNode; technicalData?: React.ReactNode;
sidebar?: React.ReactNode;
} }
export default function ProductTabs({ children, technicalData }: ProductTabsProps) { export default function ProductTabs({ children, technicalData, sidebar }: ProductTabsProps) {
return ( return (
<div className="space-y-24"> <div className="space-y-24">
<div className="max-w-none"> <div className="flex flex-col lg:flex-row gap-12 items-start">
{children} <div className="flex-1 min-w-0">
{sidebar && (
<div className="lg:hidden mb-12">
{sidebar}
</div>
)}
<div className="max-w-none">
{children}
</div>
</div>
{sidebar && (
<div className="hidden lg:block sticky top-32 w-[350px] xl:w-[400px] flex-shrink-0">
{sidebar}
</div>
)}
</div> </div>
{technicalData && ( {technicalData && (

View File

@@ -31,19 +31,19 @@ export default function RequestQuoteForm({ productName }: RequestQuoteFormProps)
if (status === 'success') { if (status === 'success') {
return ( return (
<div className="bg-accent/5 border border-accent/20 text-primary-dark p-6 rounded-[32px] text-center animate-fade-in"> <div className="bg-accent/5 border border-accent/20 text-primary-dark p-4 rounded-xl text-center animate-fade-in !mt-0">
<div className="w-12 h-12 bg-accent rounded-full flex items-center justify-center mx-auto mb-4 shadow-lg shadow-accent/20"> <div className="w-10 h-10 bg-accent rounded-full flex items-center justify-center mx-auto mb-3 shadow-lg shadow-accent/20">
<svg className="w-6 h-6 text-primary-dark" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-5 h-5 text-primary-dark" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" />
</svg> </svg>
</div> </div>
<h3 className="text-xl font-extrabold mb-2 tracking-tight">{t('successTitle')}</h3> <h3 className="text-base font-extrabold mb-1 tracking-tight !mt-0">{t('successTitle')}</h3>
<p className="text-text-secondary text-sm leading-relaxed mb-6"> <p className="text-text-secondary text-xs leading-tight mb-4 !mt-0">
{t('successDesc', { productName })} {t('successDesc', { productName })}
</p> </p>
<button <button
onClick={() => setStatus('idle')} onClick={() => setStatus('idle')}
className="inline-flex items-center text-[10px] font-bold uppercase tracking-[0.2em] text-primary hover:text-accent transition-colors group" className="inline-flex items-center text-[9px] font-bold uppercase tracking-[0.2em] text-primary hover:text-accent transition-colors group"
> >
<span className="border-b-2 border-primary/10 group-hover:border-accent transition-colors pb-1"> <span className="border-b-2 border-primary/10 group-hover:border-accent transition-colors pb-1">
{t('sendAnother')} {t('sendAnother')}
@@ -54,9 +54,9 @@ export default function RequestQuoteForm({ productName }: RequestQuoteFormProps)
} }
return ( return (
<form onSubmit={handleSubmit} className="space-y-6"> <form onSubmit={handleSubmit} className="space-y-3 !mt-0">
<div className="space-y-4"> <div className="space-y-2 !mt-0">
<div className="space-y-1.5"> <div className="space-y-1 !mt-0">
<Input <Input
type="email" type="email"
id="email" id="email"
@@ -64,46 +64,48 @@ export default function RequestQuoteForm({ productName }: RequestQuoteFormProps)
value={email} value={email}
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
placeholder={t('email')} placeholder={t('email')}
className="h-9 text-xs !mt-0"
/> />
</div> </div>
<div className="space-y-1.5"> <div className="space-y-1 !mt-0">
<Textarea <Textarea
id="request" id="request"
required required
rows={4} rows={3}
value={request} value={request}
onChange={(e) => setRequest(e.target.value)} onChange={(e) => setRequest(e.target.value)}
placeholder={t('message')} placeholder={t('message')}
className="text-xs !mt-0"
/> />
</div> </div>
</div> </div>
<div className="space-y-4"> <div className="space-y-2 !mt-0">
<Button <Button
type="submit" type="submit"
disabled={status === 'submitting'} disabled={status === 'submitting'}
className="w-full py-3.5 rounded-xl flex items-center justify-center gap-2 group" className="w-full py-2 rounded-lg flex items-center justify-center gap-2 group !mt-0"
> >
{status === 'submitting' ? ( {status === 'submitting' ? (
<> <>
<svg className="animate-spin h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> <svg className="animate-spin h-3 w-3 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle> <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg> </svg>
<span className="text-sm">{t('submitting')}</span> <span className="text-xs">{t('submitting')}</span>
</> </>
) : ( ) : (
<> <>
<span className="text-sm">{t('submit')}</span> <span className="text-xs">{t('submit')}</span>
<svg className="w-4 h-4 transition-transform group-hover:translate-x-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-3 h-3 transition-transform group-hover:translate-x-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 8l4 4m0 0l-4 4m4-4H3" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 8l4 4m0 0l-4 4m4-4H3" />
</svg> </svg>
</> </>
)} )}
</Button> </Button>
<p className="text-[8px] text-center text-text-secondary/40 uppercase tracking-[0.15em] font-medium px-2"> <p className="text-[7px] text-center text-text-secondary/40 uppercase tracking-[0.15em] font-medium px-2 !mt-1 !mb-0">
{t('privacyNote')} {t('privacyNote')}
</p> </p>
</div> </div>

File diff suppressed because one or more lines are too long