// app.jsx — root: routing + theme application. // Tweaks panel was a designer-only tool in the prototype; production freezes // the default Aurora Night palette + Bricolage typography + regular density. const THEME = { palette: ['#0A0820', '#F4F1FF', '#DDD3FF'], // bg / ink / signal density: 'regular', hero: 'text', speakerStyle: 'initial', fonts: { display: '"Bricolage Grotesque"', mono: '"Geist Mono"', serif: '"Newsreader"', }, }; function applyTheme(t) { const [bg, ink, signal] = t.palette; const dark = !isLightHex(bg); const r = document.documentElement.style; r.setProperty('--bg', bg); r.setProperty('--ink', ink); r.setProperty('--paper', dark ? mixToward(bg, ink, 0.07) : mixToward(bg, '#fff', 0.35)); r.setProperty('--signal', signal); r.setProperty('--signal-fg', isLightHex(signal) ? '#0A0820' : '#FFFFFF'); r.setProperty('--rule', hexA(ink, 0.18)); r.setProperty('--muted', hexA(ink, dark ? 0.62 : 0.58)); r.setProperty('--soft', hexA(ink, dark ? 0.05 : 0.06)); r.setProperty('--foot-bg', dark ? mixToward(bg, '#000', 0.5) : ink); r.setProperty('--foot-fg', dark ? ink : mixToward(bg, '#fff', 0.35)); r.setProperty('--atmos', dark ? `radial-gradient(ellipse 80% 60% at 20% 0%, ${hexA(signal, 0.10)}, transparent 60%), radial-gradient(ellipse 70% 50% at 90% 100%, ${hexA(ink, 0.04)}, transparent 60%)` : 'none'); r.setProperty('--font-display', `${t.fonts.display}, system-ui, sans-serif`); r.setProperty('--font-sans', `${t.fonts.display}, system-ui, sans-serif`); r.setProperty('--font-mono', `${t.fonts.mono}, ui-monospace, monospace`); r.setProperty('--font-serif', `${t.fonts.serif}, Georgia, serif`); document.body.dataset.density = t.density; document.body.dataset.dark = dark ? '1' : '0'; } function isLightHex(hex) { const h = String(hex).replace('#', ''); const x = h.length === 3 ? h.replace(/./g, (c) => c + c) : h.padEnd(6, '0'); const n = parseInt(x.slice(0, 6), 16); if (Number.isNaN(n)) return true; const r = (n >> 16) & 255, g = (n >> 8) & 255, b = n & 255; return (r * 299 + g * 587 + b * 114) > 148000; } function hexA(hex, a) { const h = String(hex).replace('#', ''); const x = h.length === 3 ? h.replace(/./g, (c) => c + c) : h; const n = parseInt(x, 16); const r = (n >> 16) & 255, g = (n >> 8) & 255, b = n & 255; return `rgba(${r}, ${g}, ${b}, ${a})`; } function mixToward(a, b, t) { const ha = a.replace('#', ''), hb = b.replace('#', ''); const pa = parseInt(ha.length === 3 ? ha.replace(/./g, (c) => c + c) : ha, 16); const pb = parseInt(hb.length === 3 ? hb.replace(/./g, (c) => c + c) : hb, 16); const ar = (pa >> 16) & 255, ag = (pa >> 8) & 255, ab = pa & 255; const br = (pb >> 16) & 255, bg = (pb >> 8) & 255, bb = pb & 255; const r = Math.round(ar + (br - ar) * t); const g = Math.round(ag + (bg - ag) * t); const b2 = Math.round(ab + (bb - ab) * t); return '#' + [r, g, b2].map((v) => v.toString(16).padStart(2, '0')).join(''); } function useHashRoute() { const get = () => (location.hash.replace(/^#/, '') || '/'); const [path, setPath] = React.useState(get); React.useEffect(() => { const on = () => { setPath(get()); window.scrollTo({ top: 0, behavior: 'instant' }); }; window.addEventListener('hashchange', on); return () => window.removeEventListener('hashchange', on); }, []); return path; } // Hash routes are a single URL to a crawler, so every route otherwise inherits // the homepage /description. Swap them per route, point the canonical at // the matching static landing page, and fire a GA4 page_view on each change. const ORIGIN = 'https://futurelabsummit.com'; const ROUTE_META = { '/': ['Future Labs Summit 2027 · Riyadh · 19—22 Mar · Deep-Tech & Lab Automation', 'The CES of deep-tech science. Four days in Riyadh where lab automation, research AI, robotic surgery, longevity therapeutics and decentralised science step onto a public floor.', '/'], '/about': ['About the Future Labs Summit · Riyadh 2027', 'A four-day, public-facing gathering for the new discovery stack — AI, automation, decentralised science and augmented reality. Riyadh, 19—22 March 2027.', '/about.html'], '/program': ['Programme — 20 Main Stage & Child Stage Sessions · FLS 2027', 'Twenty named sessions across the main stage and child stages — keynotes, fireside chats, panels, live demos and workshop cohorts. Hosts and guests announced 30 days out.', '/programme.html'], '/speakers': ['Speakers & Roster · Future Labs Summit 2027', 'Who is in the room at Future Labs Summit 2027. Names file 30 days out — a fairer game for early-stage founders, first-time speakers and students.', '/speakers.html'], '/riyadh': ['Why Riyadh — Venue & Travel · Future Labs Summit 2027', 'Future Labs Summit 2027 runs at the King Abdullah Financial District, Riyadh — the centre of the largest deployment of capital into AI, biotech and advanced research.', '/riyadh.html'], '/partner': ['Partner & Exhibit · Future Labs Summit 2027', 'Exhibit, sponsor or host a partner track. 7,500 research buyers, founders, funders and operators across 40,000m² of floor. Riyadh, 19—22 March 2027.', '/partner.html'], '/resources': ['Resources — Press Kit & Programme PDFs · FLS 2027', 'Press kits, programme PDFs, partner prospectuses and the Edition 01 working whitepaper. Freely downloadable, no email wall.', '/resources.html'], '/faq': ['Frequently Asked Questions · Future Labs Summit 2027', 'Dates, passes, visas, accessibility and press accreditation for Future Labs Summit 2027 in Riyadh, 19—22 March 2027.', '/faq.html'], '/contact': ['Contact · Future Labs Summit 2027', 'Contact the convening team. No bots, no ticket queue — every email lands in a small monitored inbox. Average reply time 36 hours.', '/contact.html'], }; function setMeta(selector, attr, value) { const el = document.head.querySelector(selector); if (el) el.setAttribute(attr, value); } function useRouteMeta(path) { React.useEffect(() => { const [title, desc, file] = ROUTE_META[path] || ROUTE_META['/']; const canonical = ORIGIN + file; document.title = title; setMeta('meta[name="description"]', 'content', desc); setMeta('meta[property="og:title"]', 'content', title); setMeta('meta[property="og:description"]', 'content', desc); setMeta('meta[property="og:url"]', 'content', canonical); setMeta('meta[name="twitter:title"]', 'content', title); setMeta('meta[name="twitter:description"]', 'content', desc); setMeta('link[rel="canonical"]', 'href', canonical); if (typeof window.gtag === 'function') { window.gtag('event', 'page_view', { page_title: title, page_location: location.href, page_path: file, }); } }, [path]); } function App() { const t = THEME; const path = useHashRoute(); React.useEffect(() => { applyTheme(t); }, []); React.useEffect(() => { if (!location.hash) location.hash = '#/'; }, []); useRouteMeta(path); let page; switch (path) { case '/about': page = <About t={t} />; break; case '/program': page = <Program t={t} />; break; case '/speakers': page = <SpeakersPage t={t} />; break; case '/riyadh': page = <Riyadh t={t} />; break; case '/partner': page = <Partner t={t} />; break; case '/resources': page = <Resources t={t} />; break; case '/faq': page = <Faq t={t} />; break; case '/contact': page = <Contact t={t} />; break; default: page = <Home t={t} />; } return ( <> <StarField /> <StatusBar /> <Nav path={path} /> <main key={path}>{page}</main> <Footer /> </> ); } ReactDOM.createRoot(document.getElementById('root')).render(<App />);