From 896f0eb5f4724434dbd7b634995bcc6f181cc9d7 Mon Sep 17 00:00:00 2001 From: Matt Date: Mon, 6 Apr 2026 14:45:17 -0400 Subject: [PATCH] feat: create Discovery section component with voice panel Standalone landing page section with warm copy, CTA, and expandable inline voice conversation panel. Shows StepComplete on brief completion. Only renders if voice support is detected. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/components/sections/Discovery.tsx | 162 ++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 src/components/sections/Discovery.tsx diff --git a/src/components/sections/Discovery.tsx b/src/components/sections/Discovery.tsx new file mode 100644 index 0000000..11de656 --- /dev/null +++ b/src/components/sections/Discovery.tsx @@ -0,0 +1,162 @@ +'use client'; + +import { useState, useEffect, useRef } from 'react'; +import { useLocale, useTranslations } from 'next-intl'; +import { motion, AnimatePresence } from 'framer-motion'; +import { MessageCircle } from 'lucide-react'; +import { revealVariants, staggerContainer, viewportOnce } from '@/lib/animations'; +import VoiceAgentProvider from '@/components/configurator/VoiceAgentProvider'; +import VoiceAgent from '@/components/configurator/VoiceAgent'; +import StepComplete from '@/components/configurator/StepComplete'; +import type { WizardFormData } from '@/components/configurator/WizardContainer'; + +export default function Discovery() { + const t = useTranslations('discovery'); + const locale = useLocale(); + const [isOpen, setIsOpen] = useState(false); + const [completed, setCompleted] = useState<{ brief: string; formData: WizardFormData } | null>(null); + const panelRef = useRef(null); + const [voiceSupported, setVoiceSupported] = useState(false); + + // Check if voice is available (same logic as old ModeToggle) + useEffect(() => { + async function check() { + if (typeof WebSocket === 'undefined') return; + if (!navigator.mediaDevices?.getUserMedia) return; + try { + const res = await fetch('/api/gemini-token'); + const data = (await res.json()) as { success: boolean }; + if (data.success) setVoiceSupported(true); + } catch { + // silent — section stays hidden + } + } + void check(); + }, []); + + const handleOpen = () => { + setIsOpen(true); + // Scroll to panel after it renders + requestAnimationFrame(() => { + panelRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' }); + }); + }; + + const handleComplete = (brief: string, formData: WizardFormData) => { + setCompleted({ brief, formData }); + }; + + const handleReset = () => { + setCompleted(null); + setIsOpen(false); + }; + + if (!voiceSupported) return null; + + return ( +
+ {/* Top accent line */} +
+ ); +}