{lang === 'ar' ? a.name_ar : a.name_en}
{lang === 'ar' ? a.bio_ar : a.bio_en}
"{lang === 'ar' ? a.quote_ar : a.quote_en}"
// app.jsx — storefront root: state, routing, fly-to-cart animation, Tweaks // NOTE: admin is now a separate file (Admin Dashboard.html) const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "palette": ["#F2EBDD", "#1C140F", "#8E2B3A", "#B08A3E"], "fontPair": "aref-fraunces", "gridCols": 3, "dark": false }/*EDITMODE-END*/; const PALETTES = { 'garnet-bone': { name:'Garnet', colors:['#F2EBDD','#1C140F','#8E2B3A','#B08A3E'] }, 'oudh-noir': { name:'Oudh Noir', colors:['#15100B','#F0E7D6','#C2453F','#C79A4C'] }, 'saffron-ivory': { name:'Saffron', colors:['#F4EEE2','#211913','#C06A2C','#7C6A45'] }, 'rose-attar': { name:'Rose Attar', colors:['#F3E7E2','#1E1411','#A23B53','#9C7A4A'] }, 'jade-parchment': { name:'Jade', colors:['#EFEBDD','#16190F','#3F5C46','#B08A3E'] }, }; const FONT_PAIRS = { 'aref-fraunces': { name: 'Aref Ruqaa × Fraunces', google: 'Aref+Ruqaa:wght@400;700|El+Messiri:wght@400;500;600;700|Fraunces:ital,opsz,wght@0,9..144,400;0,9..144,500;0,9..144,600;1,9..144,400;1,9..144,500|Hanken+Grotesk:wght@400;500;600;700', arHead: '"Aref Ruqaa", serif', arBody: '"El Messiri", system-ui, sans-serif', enHead: '"Fraunces", serif', enBody: '"Hanken Grotesk", system-ui, sans-serif', }, 'messiri-fraunces': { name: 'El Messiri × Fraunces', google: 'El+Messiri:wght@400;500;600;700|Fraunces:ital,opsz,wght@0,9..144,400;0,9..144,500;1,9..144,400|Hanken+Grotesk:wght@400;500;600;700', arHead: '"El Messiri", serif', arBody: '"El Messiri", system-ui, sans-serif', enHead: '"Fraunces", serif', enBody: '"Hanken Grotesk", system-ui, sans-serif', }, 'reem-marcellus': { name: 'Reem Kufi × Marcellus', google: 'Reem+Kufi:wght@400;500;600;700|Marcellus:wght@400|Hanken+Grotesk:wght@400;500;600', arHead: '"Reem Kufi", sans-serif', arBody: '"Reem Kufi", sans-serif', enHead: '"Marcellus", serif', enBody: '"Hanken Grotesk", sans-serif', }, }; function App() { const [t, setTweak] = useTweaks(TWEAK_DEFAULTS); const [route, _setRoute] = React.useState('home'); const [routeData, setRouteData] = React.useState(null); const [lang, setLang] = React.useState('ar'); const [cart, setCart] = React.useState([]); const [pdpProduct, setPdpProduct] = React.useState(null); const [searchOpen, setSearchOpen] = React.useState(false); const [lastOrder, setLastOrder] = React.useState(null); const cartBadgeRef = React.useRef(); const setRoute = (r, data) => { _setRoute(r); setRouteData(data || null); window.scrollTo({ top: 0, behavior: 'instant' }); }; const openPDP = (p) => { setPdpProduct(p); setRoute('pdp'); }; // ── flying bottle animation ─────────────────── const flyToCart = (sourceEl, color) => { if (!sourceEl || !cartBadgeRef.current) return; const srcRect = sourceEl.getBoundingClientRect(); const tgtRect = cartBadgeRef.current.getBoundingClientRect(); const fly = document.createElement('div'); fly.className = 'fly'; fly.style.background = `linear-gradient(170deg, ${color}, color-mix(in oklab, ${color} 55%, #000))`; fly.style.left = srcRect.left + srcRect.width / 2 - 20 + 'px'; fly.style.top = srcRect.top + srcRect.height / 2 - 25 + 'px'; document.body.appendChild(fly); requestAnimationFrame(() => { fly.style.transform = `translate(${tgtRect.left - srcRect.left - srcRect.width / 2 + 20 + tgtRect.width/2}px, ${tgtRect.top - srcRect.top - srcRect.height / 2 + 25}px) scale(0.2) rotate(360deg)`; fly.style.opacity = '0.3'; }); setTimeout(() => { fly.remove(); const badge = document.querySelector('[data-cart-badge]'); if (badge) { badge.classList.remove('cart-badge-shake'); void badge.offsetWidth; badge.classList.add('cart-badge-shake'); } }, 720); }; const addToCart = (product, sourceEl, size) => { const sz = size || product.sizes[1]; setCart(prev => { const idx = prev.findIndex(i => i.id === product.id && i.size === sz.ml); if (idx >= 0) { const next = [...prev]; next[idx] = { ...next[idx], qty: next[idx].qty + 1 }; return next; } return [...prev, { id: product.id, name_ar: product.name_ar, name_en: product.name_en, tone: product.tone, concentration: product.concentration, size: sz.ml, price: sz.price, qty: 1, }]; }); flyToCart(sourceEl, product.tone); }; // ── theme injection ──────────────────────────── const fontPair = FONT_PAIRS[t.fontPair] || FONT_PAIRS['messiri-cormorant']; React.useEffect(() => { document.documentElement.lang = lang; document.documentElement.dir = lang === 'ar' ? 'rtl' : 'ltr'; }, [lang]); React.useEffect(() => { const id = 'dyn-fonts'; let l = document.getElementById(id); if (!l) { l = document.createElement('link'); l.id = id; l.rel = 'stylesheet'; document.head.appendChild(l); } l.href = `https://fonts.googleapis.com/css2?family=${fontPair.google.replace(/\|/g, '&family=')}&family=JetBrains+Mono:wght@400;500&display=swap`; }, [t.fontPair]); const styleVars = { '--bg': t.palette[0], '--bg-2': `color-mix(in oklab, ${t.palette[0]} 88%, ${t.palette[1]})`, '--paper': `color-mix(in oklab, ${t.palette[0]} 96%, #fff)`, '--ink': t.palette[1], '--ink-2': `color-mix(in oklab, ${t.palette[1]} 75%, ${t.palette[0]})`, '--ink-3': `color-mix(in oklab, ${t.palette[1]} 50%, ${t.palette[0]})`, '--line': `color-mix(in oklab, ${t.palette[1]} 14%, transparent)`, '--line-2': `color-mix(in oklab, ${t.palette[1]} 7%, transparent)`, '--accent': t.palette[2], '--accent-2': t.palette[3], '--f-ar-head': fontPair.arHead, '--f-ar-body': fontPair.arBody, '--f-en-head': fontPair.enHead, '--f-en-body': fontPair.enBody, }; const cartCount = cart.reduce((s, i) => s + i.qty, 0); const minimalNav = route === 'checkout' || route === 'confirm'; return (
{t('استبرق هي بيت عطور مقرّه دبي، يستورد ويوزّع أرقى العطور المقطّرة يدوياً من بيوت العائلات الهندية. نعمل مع 12 عطّاراً عبر ميسور، آسام، كشمير، ولكنو — كل قطرة تحمل توقيع صانعها.', 'Istabraq is a Dubai-based perfumery, importing and decanting the finest hand-distilled fragrances from Indian family ateliers. We work with 12 attar masters across Mysore, Assam, Kashmir and Lucknow — every drop bears its maker\'s signature.')}
{t('خلف كل زجاجة من استبرق، عطّار يعمل بطرق توارثتها العائلات منذ قرون. هؤلاء بعضٌ منهم.', 'Behind every Istabraq bottle is an attar maker working by methods passed down through families for centuries. These are a few of them.')}
{lang === 'ar' ? a.bio_ar : a.bio_en}
"{lang === 'ar' ? a.quote_ar : a.quote_en}"