feat: configurator overhaul — full i18n, AI brief generation, redesigned UI
Configurator:
- Full i18n: industries, timelines, AI types, field labels, error messages,
complete screen — all translated to French
- Real AI brief generation via OpenRouter (DeepSeek V3.2) with fallback template
- Fixed email notification bug (was sending to client instead of admin)
- Added wizard reset capability ("Start Over" button on success screen)
- Redesigned section shell with refined card, accent line, step indicators
- All step components use translation keys instead of hardcoded strings
Hero:
- Tighter spacing to keep CTAs above the fold
- Reduced bottom gradient height
Footer:
- Removed GitHub link
- Legal name in copyright
- "American-founded" location text
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,20 +6,20 @@ import { ShieldCheck } from 'lucide-react';
|
||||
import { revealVariants, staggerContainer, viewportOnce } from '@/lib/animations';
|
||||
import WizardContainer from '@/components/configurator/WizardContainer';
|
||||
|
||||
// ─── Step indicator dot ───────────────────────────────────────────────────────
|
||||
// ─── Step indicator ──────────────────────────────────────────────────────────
|
||||
|
||||
interface StepDotProps {
|
||||
index: number;
|
||||
label: string;
|
||||
}
|
||||
|
||||
function StepDot({ index, label }: StepDotProps) {
|
||||
function StepIndicator({ index, label, isLast }: { index: number; label: string; isLast: boolean }) {
|
||||
return (
|
||||
<motion.div variants={revealVariants} className="flex items-center gap-3">
|
||||
<div className="w-6 h-6 rounded-full bg-primary/10 flex items-center justify-center flex-shrink-0">
|
||||
<span className="text-xs font-semibold text-primary-dark leading-none">{index}</span>
|
||||
<motion.div variants={revealVariants} className="flex items-start gap-4">
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="w-7 h-7 rounded-full bg-primary/10 border border-primary/20 flex items-center justify-center flex-shrink-0">
|
||||
<span className="text-xs font-semibold text-primary-dark leading-none">{index}</span>
|
||||
</div>
|
||||
{!isLast && (
|
||||
<div className="w-px h-5 bg-primary/15 mt-1" aria-hidden="true" />
|
||||
)}
|
||||
</div>
|
||||
<span className="text-sm text-outline">{label}</span>
|
||||
<span className="text-sm text-outline pt-1">{label}</span>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
@@ -36,11 +36,20 @@ export default function Configurator() {
|
||||
];
|
||||
|
||||
return (
|
||||
<section id="configure" className="bg-surface-low py-20">
|
||||
<div className="container mx-auto px-6">
|
||||
<div className="grid grid-cols-1 gap-12 lg:grid-cols-12">
|
||||
<section id="configure" className="relative bg-surface py-24 overflow-hidden">
|
||||
{/* Subtle diagonal accent line */}
|
||||
<div
|
||||
className="absolute top-0 left-0 right-0 h-px pointer-events-none"
|
||||
style={{
|
||||
background: 'linear-gradient(90deg, transparent 10%, rgba(91,164,217,0.15) 50%, transparent 90%)',
|
||||
}}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
||||
{/* ── Left: Sticky context panel ─────────────────────────────── */}
|
||||
<div className="relative z-10 container mx-auto px-6">
|
||||
<div className="grid grid-cols-1 gap-12 lg:grid-cols-12 lg:gap-16 items-start">
|
||||
|
||||
{/* ── Left: Context panel ──────────────────────────────────────── */}
|
||||
<div className="lg:col-span-5">
|
||||
<div className="lg:sticky lg:top-24">
|
||||
<motion.div
|
||||
@@ -50,7 +59,6 @@ export default function Configurator() {
|
||||
viewport={viewportOnce}
|
||||
className="flex flex-col gap-6"
|
||||
>
|
||||
{/* Eyebrow */}
|
||||
<motion.span
|
||||
variants={revealVariants}
|
||||
className="label-md text-primary"
|
||||
@@ -58,7 +66,6 @@ export default function Configurator() {
|
||||
{t('eyebrow')}
|
||||
</motion.span>
|
||||
|
||||
{/* H2 */}
|
||||
<motion.h2
|
||||
variants={revealVariants}
|
||||
className="font-serif text-4xl font-semibold tracking-headline text-on-surface leading-tight md:text-5xl"
|
||||
@@ -66,7 +73,6 @@ export default function Configurator() {
|
||||
{t('title')}
|
||||
</motion.h2>
|
||||
|
||||
{/* Description */}
|
||||
<motion.p
|
||||
variants={revealVariants}
|
||||
className="text-base text-outline leading-relaxed max-w-sm"
|
||||
@@ -74,50 +80,79 @@ export default function Configurator() {
|
||||
{t('description')}
|
||||
</motion.p>
|
||||
|
||||
{/* Divider */}
|
||||
<motion.div
|
||||
variants={revealVariants}
|
||||
className="w-12 h-px bg-outline-variant/40"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
||||
{/* Step indicators */}
|
||||
<motion.div
|
||||
variants={revealVariants}
|
||||
className="flex flex-col gap-3 pt-2"
|
||||
className="flex flex-col gap-1"
|
||||
>
|
||||
<p className="text-xs font-semibold uppercase tracking-label text-outline/70">
|
||||
<p className="text-xs font-semibold uppercase tracking-label text-outline/60 mb-3">
|
||||
{t('howItWorks')}
|
||||
</p>
|
||||
{/* Vertical accent line + steps */}
|
||||
<div className="flex gap-4">
|
||||
<div className="flex-shrink-0 w-px bg-primary/20 ml-3 rounded-full" aria-hidden="true" />
|
||||
<div className="flex flex-col gap-2.5 flex-1">
|
||||
{steps.map((step, i) => (
|
||||
<StepDot key={i} index={i + 1} label={step} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{steps.map((step, i) => (
|
||||
<StepIndicator
|
||||
key={i}
|
||||
index={i + 1}
|
||||
label={step}
|
||||
isLast={i === steps.length - 1}
|
||||
/>
|
||||
))}
|
||||
</motion.div>
|
||||
|
||||
{/* Trust signal */}
|
||||
<motion.div
|
||||
variants={revealVariants}
|
||||
className="pt-1 flex items-center gap-2"
|
||||
className="flex items-center gap-2 pt-2"
|
||||
>
|
||||
<ShieldCheck size={14} strokeWidth={1.75} className="text-primary flex-shrink-0" aria-hidden="true" />
|
||||
<ShieldCheck
|
||||
size={14}
|
||||
strokeWidth={1.75}
|
||||
className="text-primary flex-shrink-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<p className="text-xs text-outline">{t('noCommitment')}</p>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ── Right: Wizard ───────────────────────────────────────────── */}
|
||||
{/* ── Right: Wizard card ────────────────────────────────────────── */}
|
||||
<div className="lg:col-span-7">
|
||||
<div className="relative rounded-2xl bg-surface-high shadow-subtle p-6 sm:p-8 overflow-hidden">
|
||||
{/* Radial gradient glow — top-left warmth */}
|
||||
<div
|
||||
className="pointer-events-none absolute -top-16 -left-16 w-72 h-72 rounded-full"
|
||||
style={{
|
||||
background: 'radial-gradient(circle, rgba(91,164,217,0.07) 0%, transparent 70%)',
|
||||
}}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<WizardContainer />
|
||||
</div>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 24 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={viewportOnce}
|
||||
transition={{ duration: 0.7, ease: [0.16, 1, 0.3, 1], delay: 0.1 }}
|
||||
className="relative"
|
||||
>
|
||||
<div className="relative rounded-2xl bg-surface-high shadow-[0_20px_50px_rgba(25,28,29,0.08)] p-6 sm:p-8 overflow-hidden border border-outline-variant/20">
|
||||
{/* Top-edge accent line */}
|
||||
<div
|
||||
className="absolute top-0 left-6 right-6 h-[2px] rounded-full pointer-events-none"
|
||||
style={{
|
||||
background: 'linear-gradient(90deg, #006494, #5BA4D9, transparent)',
|
||||
}}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
||||
{/* Soft radial glow */}
|
||||
<div
|
||||
className="pointer-events-none absolute -top-20 -left-20 w-80 h-80 rounded-full"
|
||||
style={{
|
||||
background: 'radial-gradient(circle, rgba(91,164,217,0.05) 0%, transparent 70%)',
|
||||
}}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
||||
<WizardContainer />
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user