// 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 = ; break;
case '/program': page = ; break;
case '/speakers': page = ; break;
case '/riyadh': page = ; break;
case '/partner': page = ; break;
case '/resources': page = ; break;
case '/faq': page = ; break;
case '/contact': page = ; break;
default: page = ;
}
return (
<>
{page}
>
);
}
ReactDOM.createRoot(document.getElementById('root')).render();