// Keynara Hero — bold typographic hero + working inline chat agent + property card stream const { useState: useS, useEffect: useE, useRef: useR, useMemo: useM } = React; // Mock city dataset const MOCK_LISTINGS = [ { id: 1, city: 'Wien', zip: '1120', type: 'Kauf', rooms: 3, area: 110, price: 498000, rate: null, match: 96, tag: 'Top Match', note: 'Ruhig, Balkon West' }, { id: 2, city: 'Wien', zip: '1090', type: 'Kauf', rooms: 3, area: 95, price: 475000, rate: null, match: 91, tag: 'Gute Lage', note: 'U4/U6, renoviert' }, { id: 3, city: 'Wien', zip: '1030', type: 'Kauf', rooms: 3, area: 102, price: 489000, rate: null, match: 88, tag: 'Wertstabil', note: 'Altbau, hohe Decken' }, { id: 4, city: 'Berlin', zip: '10117', type: 'Miete', rooms: 2, area: 62, price: 1490, rate: 'mtl.', match: 94, tag: 'Neu', note: 'Mitte, ab Mai' }, { id: 5, city: 'Berlin', zip: '10115', type: 'Miete', rooms: 2, area: 58, price: 1390, rate: 'mtl.', match: 90, tag: 'Beliebt', note: 'Rosenthaler Platz' }, { id: 6, city: 'Berlin', zip: '10178', type: 'Miete', rooms: 2, area: 65, price: 1550, rate: 'mtl.', match: 87, tag: 'Direktlage', note: 'Hackescher Markt' }, { id: 7, city: 'Milano', zip: '20121', type: 'Investment', rooms: 3, area: 85, price: 620000, rate: null, match: 93, tag: '4.3% Rendite', note: 'Brera, vermietet' }, { id: 8, city: 'Milano', zip: '20154', type: 'Investment', rooms: 2, area: 62, price: 420000, rate: null, match: 89, tag: '4.1% Rendite', note: 'Isola, Nähe Tram' }, { id: 9, city: 'Milano', zip: '20145', type: 'Investment', rooms: 3, area: 90, price: 580000, rate: null, match: 85, tag: '3.9% Rendite', note: 'Sempione, Park' }, ]; function detectScenario(text) { const s = text.toLowerCase(); if (/milano|mail|invest|rendite|yield|milan/.test(s)) return 'milan'; if (/berlin|miete|rent/.test(s)) return 'berlin'; return 'vienna'; } function scenarioListings(key) { if (key === 'berlin') return MOCK_LISTINGS.slice(3, 6); if (key === 'milan') return MOCK_LISTINGS.slice(6, 9); return MOCK_LISTINGS.slice(0, 3); } function Hero({ t, lang, onFocus }) { // Chat state const [stage, setStage] = useS('idle'); // idle | typing-user | agent-asking | typing-user-2 | agent-thinking | results const [value, setValue] = useS(''); const [messages, setMessages] = useS([]); const [listings, setListings] = useS([]); const [activeSuggest, setActiveSuggest] = useS(0); const bodyRef = useR(null); useE(() => { if (bodyRef.current) bodyRef.current.scrollTop = bodyRef.current.scrollHeight; }, [messages, stage]); // Idle demo: rotate suggestions useE(() => { if (stage !== 'idle') return; const id = setInterval(() => setActiveSuggest(i => (i + 1) % t.hero.suggestions.length), 2800); return () => clearInterval(id); }, [stage, t.hero.suggestions.length]); function submit(customText) { const text = (customText ?? value).trim(); if (!text) return; setValue(''); const scenario = detectScenario(text); setMessages([{ role: 'user', text }]); setStage('agent-asking'); setTimeout(() => { const ask = lang === 'de' ? 'Verstanden. Kauf oder Miete? In welcher Stadt und mit welchem Budget?' : 'Got it. Buy or rent? Which city and what budget?'; setMessages(m => [...m, { role: 'agent', text: ask }]); }, 900); setTimeout(() => { const reply = lang === 'de' ? 'Budget steht, Lage passt — zeig mir die Treffer.' : 'Budget set, location fits — show me the matches.'; setMessages(m => [...m, { role: 'user', text: reply }]); }, 2400); setTimeout(() => { setStage('agent-thinking'); }, 3000); setTimeout(() => { const intro = lang === 'de' ? 'Hier sind drei Treffer, nach Relevanz gewichtet:' : 'Here are three matches, weighted by relevance:'; setMessages(m => [...m, { role: 'agent', text: intro, withResults: true }]); setListings(scenarioListings(scenario)); setStage('results'); }, 4200); } function reset() { setMessages([]); setListings([]); setValue(''); setStage('idle'); } return (
{/* Background grid */}
{/* LEFT: statement */}
{t.hero.eyebrow}

{t.hero.title_1}
{t.hero.title_2}
{t.hero.title_3}

{t.hero.sub}

{/* RIGHT: chat agent */}
); } function Stat({ v, l }) { return (
{v}
{l}
); } function BgGrid() { return ( <>
); } function ChatAgent({ t, lang, stage, setStage, messages, setMessages, listings, value, setValue, submit, reset, activeSuggest, bodyRef }) { const inputRef = useR(null); return (
{/* Header */}
Keynara Agent
{t.hero.live}
{stage !== 'idle' && ( )}
{/* Chat body */}
{stage === 'idle' && } {messages.map((m, i) => ( {m.withResults && } ))} {(stage === 'agent-asking' || stage === 'agent-thinking') && }
{/* Composer */}
{stage === 'idle' && (
{t.hero.suggestions.map((s, i) => ( ))}
)}
{ e.preventDefault(); submit(); }} style={{ display: 'flex', alignItems: 'center', gap: 8, background: 'var(--fog)', borderRadius: 16, padding: 6, border: '1px solid transparent', transition: 'border-color 180ms', }} onFocus={(e) => e.currentTarget.style.borderColor = 'var(--mint)'} onBlur={(e) => e.currentTarget.style.borderColor = 'transparent'} > setValue(e.target.value)} placeholder={t.hero.chat_placeholder} style={{ flex: 1, padding: '12px 14px', background: 'transparent', border: 'none', outline: 'none', fontFamily: 'inherit', fontSize: 15, color: 'var(--navy)', }} />
); } function AgentWelcome({ t, lang }) { return (
); } function Message({ role, text, children }) { const isUser = role === 'user'; return (
{text}
{children &&
{children}
}
); } function TypingBubble({ label }) { return (
{[0, 1, 2].map(i => ( ))}
{label}
); } function ResultsList({ listings, lang }) { return (
{listings.map((l, i) => ( ))}
); } function ResultCard({ l, idx, lang }) { const priceStr = l.rate ? `${l.price.toLocaleString(lang === 'de' ? 'de-DE' : 'en-US')} € / ${lang === 'de' ? 'Monat' : 'month'}` : `${l.price.toLocaleString(lang === 'de' ? 'de-DE' : 'en-US')} €`; return (
{l.match}% Match {l.tag}
{l.zip} {l.city} · {l.rooms} {lang === 'de' ? 'Zi' : 'rms'} · {l.area} m²
{l.note}
{priceStr}
); } function PlaceholderImage({ type }) { // Subtle striped svg placeholder colored by brand const colors = { Kauf: ['#1A2B3C', '#4A6278'], Miete: ['#4A6278', '#00C9A7'], Investment: ['#00C9A7', '#1A2B3C'], }[type] || ['#1A2B3C', '#4A6278']; return (
); } Object.assign(window, { Hero });