// os.jsx — ATELIER OS interactive layer: spotlight cursor, boot sequence, // command palette (⌘K), and the AI Scent Console. // ── spotlight cursor ───────────────────────────────────────── function SpotlightCursor() { React.useEffect(() => { if (window.matchMedia && window.matchMedia('(hover: none)').matches) return; const el = document.getElementById('os-spotlight'); if (!el) return; let raf = 0, x = 0, y = 0; const move = (e) => { x = e.clientX; y = e.clientY; if (!raf) raf = requestAnimationFrame(() => { el.style.setProperty('--mx', x + 'px'); el.style.setProperty('--my', y + 'px'); raf = 0; }); }; window.addEventListener('mousemove', move); return () => window.removeEventListener('mousemove', move); }, []); return
; } // ── boot sequence ──────────────────────────────────────────── function BootSequence({ lang }) { const [done, setDone] = React.useState(() => sessionStorage.getItem('istabraq_booted') === '1'); const [pct, setPct] = React.useState(0); const [line, setLine] = React.useState(''); React.useEffect(() => { if (done) return; const steps = [ 'initializing ISTABRAQ//OS', 'mounting scent index ………… ok', 'calibrating olfactory engine …', 'linking AI scent console …… ok', 'ready', ]; let i = 0, p = 0, alive = true; const tick = () => { if (!alive) return; p += 6 + Math.random() * 12; if (p > 100) p = 100; setPct(Math.floor(p)); setLine('> ' + steps[Math.min(steps.length - 1, Math.floor(p / 100 * steps.length))]); if (p >= 100) { setTimeout(() => { sessionStorage.setItem('istabraq_booted', '1'); setDone(true); }, 420); } else { setTimeout(tick, 160 + Math.random() * 120); } }; const t = setTimeout(tick, 200); return () => { alive = false; clearTimeout(t); }; }, []); if (done) return null; return (
= 100 ? 'done' : ''}>
{lang === 'ar' ? 'استبرق' : 'ISTABRAQ'}_
{line}
); } // ── command palette (⌘K / Ctrl+K) ──────────────────────────── function CommandPalette({ setRoute, onSearch, lang }) { const t = (ar, en) => lang === 'ar' ? ar : en; const [open, setOpen] = React.useState(false); const [q, setQ] = React.useState(''); const [idx, setIdx] = React.useState(0); const inputRef = React.useRef(); const items = React.useMemo(() => [ { label: t('الرئيسية', 'Home'), k: 'home', ico: , act: () => setRoute('home') }, { label: t('تصفّح العطور', 'Browse fragrances'), k: 'shop', ico: , act: () => setRoute('shop') }, { label: t('اسأل الذكاء الاصطناعي', 'Ask the AI console'), k: 'ai', ico: , act: () => { setRoute('home'); setTimeout(() => { const c = document.querySelector('.console input'); if (c) { c.scrollIntoView({ block: 'center', behavior: 'smooth' }); c.focus(); } }, 60); } }, { label: t('العطّارون', 'Attar masters'), k: 'attars', ico: , act: () => setRoute('attars') }, { label: t('قصتنا', 'Our story'), k: 'story', ico: , act: () => setRoute('story') }, { label: t('السلة', 'Cart'), k: 'cart', ico: , act: () => setRoute('cart') }, { label: t('بحث', 'Search'), k: 'search', ico: , act: () => onSearch && onSearch() }, ], [lang]); const filtered = q ? items.filter(i => i.label.includes(q) || i.k.includes(q.toLowerCase())) : items; React.useEffect(() => { const onKey = (e) => { if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') { e.preventDefault(); setOpen(o => !o); } else if (e.key === 'Escape') setOpen(false); }; window.addEventListener('keydown', onKey); return () => window.removeEventListener('keydown', onKey); }, []); React.useEffect(() => { if (open) { setQ(''); setIdx(0); setTimeout(() => inputRef.current?.focus(), 30); } }, [open]); if (!open) return null; const run = (it) => { setOpen(false); it.act(); }; const onKeyDown = (e) => { if (e.key === 'ArrowDown') { e.preventDefault(); setIdx(i => Math.min(filtered.length - 1, i + 1)); } else if (e.key === 'ArrowUp') { e.preventDefault(); setIdx(i => Math.max(0, i - 1)); } else if (e.key === 'Enter') { e.preventDefault(); if (filtered[idx]) run(filtered[idx]); } }; return (
setOpen(false)}>
e.stopPropagation()}>
{ setQ(e.target.value); setIdx(0); }} onKeyDown={onKeyDown} />
{filtered.map((it, i) => (
setIdx(i)} onClick={() => run(it)}> {it.ico} {it.label} {it.k}
))} {filtered.length === 0 &&
{t('لا نتائج', 'No matches')}
}
↑↓ {t('تنقّل', 'navigate')}↵ {t('تشغيل', 'run')}esc {t('إغلاق', 'close')}
); } // ── AI Scent Console ───────────────────────────────────────── function ScentConsole({ lang, openPDP }) { const t = (ar, en) => lang === 'ar' ? ar : en; const [q, setQ] = React.useState(''); const [busy, setBusy] = React.useState(false); const [reply, setReply] = React.useState(''); const [shown, setShown] = React.useState(''); const [picks, setPicks] = React.useState([]); const [engine, setEngine] = React.useState(''); const prompts = lang === 'ar' ? ['أمسية ماطرة في مكتبة خشبية', 'عطر جريء للسهرة', 'شيء نظيف ومنعش للنهار', 'دفء العنبر والفانيليا'] : ['a rainy evening in a cedar library', 'something bold for the night', 'clean & fresh for daytime', 'warm amber and vanilla']; // typewriter reveal for the reply React.useEffect(() => { if (!reply) { setShown(''); return; } let i = 0; setShown(''); const id = setInterval(() => { i += 2; setShown(reply.slice(0, i)); if (i >= reply.length) clearInterval(id); }, 16); return () => clearInterval(id); }, [reply]); const ask = async (text) => { const query = (text != null ? text : q).trim(); if (!query || busy) return; setQ(query); setBusy(true); setReply(''); setShown(''); setPicks([]); try { const res = await apiPost('ai', { q: query, lang }); setEngine(res.engine || ''); setPicks(Array.isArray(res.picks) ? res.picks : []); setReply(res.reply || t('لم أجد تطابقاً واضحاً — جرّب وصفاً آخر.', 'No clear match — try another description.')); } catch (e) { setReply(t('تعذّر الاتصال بمحرّك الذكاء.', 'Could not reach the AI engine.')); } setBusy(false); }; return (
{t('محرّك العطر · الذكاء الاصطناعي', 'Scent Engine · AI')} {engine ? engine.toUpperCase() : 'ISTABRAQ//OS v1.0'}
{t('// اكتشاف ذكي', '// intelligent discovery')}

{t('صِف شعورك — نختار عطرك.', 'Describe a feeling — we conjure the scent.')}

{t('اكتب مزاجاً أو ذكرى أو مناسبة، ويقترح المحرّك العطور الأقرب من مجموعتنا.', 'Type a mood, a memory, or an occasion and the engine suggests the closest fragrances from our collection.')}

setQ(e.target.value)} onKeyDown={e => e.key === 'Enter' && ask()} />
{prompts.map((p, i) => )}
{(busy || reply) && (
{busy && !reply &&
{t('يحلّل وصفك ويطابق النوتات…', 'analysing your description, matching notes…')}
} {reply && ( <>
{t('استجابة المحرّك', 'engine response')}
{shown}{shown.length < reply.length && }
{picks.length > 0 && (
{picks.map(p => { const fam = (window.SCENT_FAMILIES || []).find(f => f.id === p.family); return (
openPDP && openPDP(p)}>
{lang === 'ar' ? p.name_ar : p.name_en}
{fam ? (lang === 'ar' ? fam.ar : fam.en) : ''} · {p.concentration || ''}
{p.price}
); })}
)} )}
)}
); } window.SpotlightCursor = SpotlightCursor; window.BootSequence = BootSequence; window.CommandPalette = CommandPalette; window.ScentConsole = ScentConsole;