// obsidian-sections.jsx — additional sections + ticket flow + modal shell
// for the full Obsidian landing prototype. Reuses obsidianStyles from
// direction-obsidian.jsx.
// ─────────────────────────────────────────────────────────────────────
// MODAL SHELL — common to ticket + sponsor flows on the full prototype
// ─────────────────────────────────────────────────────────────────────
function ObsidianModal({ open, onClose, children }) {
const { mobile, tablet } = useMobile();
React.useEffect(() => {
if (!open) return;
const onKey = (e) => {if (e.key === 'Escape') onClose();};
document.addEventListener('keydown', onKey);
document.body.style.overflow = 'hidden';
return () => {
document.removeEventListener('keydown', onKey);
document.body.style.overflow = '';
};
}, [open, onClose]);
if (!open) return null;
const s = obsidianStyles;
return (
e.stopPropagation()}
style={{
width: '100%', maxWidth: mobile ? '100%' : 1280, height: mobile ? '95vh' : '88vh', maxHeight: mobile ? 'none' : 880,
background: s.bg, border: `1px solid ${s.gold}40`,
position: 'relative', overflow: 'hidden',
boxShadow: '0 40px 120px rgba(0,0,0,0.6), 0 0 0 1px rgba(201,162,76,0.08)',
animation: 'slideUp 0.32s cubic-bezier(0.16,1,0.3,1)'
}}>
{e.currentTarget.style.borderColor = s.gold;e.currentTarget.style.color = s.gold;}}
onMouseLeave={(e) => {e.currentTarget.style.borderColor = s.graySoft;e.currentTarget.style.color = s.cream;}}>
×
{children}
);
}
// ─────────────────────────────────────────────────────────────────────
// OBSIDIAN TICKET FLOW
// ─────────────────────────────────────────────────────────────────────
function ObsidianTicketFlow({ onClose }) {
const s = obsidianStyles;
const f = useTicketFlow();
const { mobile, tablet } = useMobile();
const px = mobile ? 24 : 56;
const StepDot = ({ n, label }) =>
= n ? s.gold : s.graySoft}`,
background: f.step > n ? s.gold : 'transparent',
color: f.step > n ? s.bg : f.step === n ? s.gold : s.gray,
display: 'flex', alignItems: 'center', justifyContent: 'center',
fontFamily: s.mono, fontSize: 10, fontWeight: 500
}}>{f.step > n ? '✓' : n}
{!mobile &&
= n ? s.cream : s.gray
}}>{label} }
;
const Btn = ({ children, primary, onClick, disabled }) =>
{children} ;
return (
{/* header */}
RESERVATIONS · OCTOBER 17 · 2026
Reserve your seat.
{/* steps */}
{f.step === 1 &&
{/* Early bird toggle */}
{f.earlyBird ? '◇ EARLY BIRD ACTIVE · LIMITED TIME' : 'REGULAR PRICING'}
{f.earlyBird ? 'Early bird pricing automatically applied — reserve before doors release.' : 'Early bird window closed.'}
f.setEarlyBird((v) => !v)} style={{
background: 'transparent', color: f.earlyBird ? s.gold : s.gray,
border: `1px solid ${f.earlyBird ? s.gold : s.graySoft}`, padding: '8px 14px',
fontFamily: s.mono, fontSize: 10, letterSpacing: '0.18em', textTransform: 'uppercase',
cursor: 'pointer'
}}>{f.earlyBird ? 'Toggle off' : 'Toggle on'}
{TICKET_TYPES.map((t, i) => {
const sel = f.ticketId === t.id;
const displayPrice = f.earlyBird ? t.earlyPrice : t.price;
return (
f.setTicketId(t.id)} style={{
background: sel ? `linear-gradient(180deg, ${s.gold}18, ${s.gold}04)` : s.bgSoft,
border: `1px solid ${sel ? s.gold : s.graySoft}`,
padding: '24px 24px', textAlign: 'left', cursor: 'pointer',
fontFamily: s.sans, color: s.cream, display: 'flex', flexDirection: 'column',
transition: 'all 0.25s', position: 'relative', overflow: 'hidden'
}}>
{sel && ●
}
TIER {String(i + 1).padStart(2, '0')} · {t.badge.toUpperCase()}
{t.name}
{t.desc}
{/* price block */}
${displayPrice}
{f.earlyBird &&
${t.price}
}
{f.earlyBird ? `◇ EARLY BIRD · SAVE $${t.price - t.earlyPrice}` : 'PER TICKET'}
{t.perks.map((p, j) =>
— {p}
)}
);
})}
}
{f.step === 2 &&
HOW MANY?
How many tickets?
Bringing a group of 4+? Reach out and we'll arrange complimentary side-by-side seating and a welcome touch on arrival.
f.setQty((q) => Math.max(1, q - 1))}
style={{ width: 64, height: 64, background: 'transparent', border: `1px solid ${s.gold}`, color: s.gold, fontSize: 22, cursor: 'pointer', fontFamily: s.serif }}>−
{f.qty}
f.setQty((q) => Math.min(12, q + 1))}
style={{ width: 64, height: 64, background: 'transparent', border: `1px solid ${s.gold}`, color: s.gold, fontSize: 22, cursor: 'pointer', fontFamily: s.serif }}>+
{/* summary card */}
YOUR RESERVATION
{f.ticket.name}
{f.ticket.badge.toUpperCase()}
${f.unitPrice} × {f.effectiveQty} {f.earlyBird ? · early bird : null}
{money(f.subtotal)}
Service fees
{money(f.fees)}
Total
{money(f.total)}
{f.ticket.id === 'vip' ? 'The ultimate experience · reserved seating · VIP gift bag.' :
f.ticket.id === 'gold' ? 'Includes everything · refreshments → after-party · attendee gift bag.' :
f.ticket.id === 'silver' ? 'Cultural moments · runway · panel · performances · after party.' :
'After-party access · DJ set · live performances · cash bar.'}
}
{f.step === 3 &&
Complete your reservation.
A secure checkout window will open to complete your purchase via Ticket Tailor. Payment is processed securely via Stripe.
{
const w = 520, h = 720;
const left = (screen.width - w) / 2, top = (screen.height - h) / 2;
const checkoutUrl = TT_CHECKOUT + '?type=' + f.ticket.ttId + '&quantity=' + f.effectiveQty;
window.open(checkoutUrl, 'ElevateNoirCheckout', `width=${w},height=${h},left=${left},top=${top},menubar=no,toolbar=no,location=yes,status=no,scrollbars=yes`);
}} style={{
display: 'inline-flex', alignItems: 'center', gap: 10,
background: s.gold, color: s.bg, border: 'none',
padding: '16px 32px', fontSize: 12, letterSpacing: '0.18em', textTransform: 'uppercase',
fontWeight: 600, cursor: 'pointer', fontFamily: s.sans,
alignSelf: 'flex-start'
}}>
Proceed to secure checkout →
Secured by Stripe · Ticket Tailor
ORDER SUMMARY
{f.ticket.name}
{f.ticket.badge.toUpperCase()}
${f.unitPrice} × {f.effectiveQty} {f.earlyBird ? · early bird : null}
{money(f.subtotal)}
Service fees
{money(f.fees)}
Total
{money(f.total)}
{f.ticket.id === 'vip' ? 'The ultimate experience · reserved seating · VIP gift bag.' :
f.ticket.id === 'gold' ? 'Includes everything · refreshments → after-party · attendee gift bag.' :
f.ticket.id === 'silver' ? 'Cultural moments · runway · panel · performances · after party.' :
'After-party access · DJ set · live performances · cash bar.'}
}
{/* footer */}
{f.step < 3 &&
}
{f.step === 3 &&
}
);
}
// ─────────────────────────────────────────────────────────────────────
// FULL-PAGE OBSIDIAN LANDING SECTIONS
// ─────────────────────────────────────────────────────────────────────
// Smooth scroll-reveal hook
function useReveal() {
const ref = React.useRef(null);
const [seen, setSeen] = React.useState(false);
React.useEffect(() => {
if (!ref.current) return;
const io = new IntersectionObserver(([e]) => {
if (e.isIntersecting) {setSeen(true);io.disconnect();}
}, { threshold: 0.12 });
io.observe(ref.current);
return () => io.disconnect();
}, []);
return [ref, seen];
}
function Reveal({ children, delay = 0, style = {} }) {
const [ref, seen] = useReveal();
return (
{children}
);
}
// Nav (sticky, scroll-aware)
// Shared mobile menu overlay
function MobileMenuOverlay({ open, onClose, links, cta, s }) {
React.useEffect(() => {
if (open) document.body.style.overflow = 'hidden';
else document.body.style.overflow = '';
return () => { document.body.style.overflow = ''; };
}, [open]);
return (
✕
{links.map(([label, href, active]) => (
{label}
))}
{cta &&
{cta}
}
);
}
// Hamburger icon
function HamburgerIcon({ onClick, s }) {
return (
);
}
function ObsidianNav({ onTickets, onSponsor }) {
const s = obsidianStyles;
const { mobile, tablet } = useMobile();
const px = mobile ? 24 : 56;
const [scrolled, setScrolled] = React.useState(false);
const [menuOpen, setMenuOpen] = React.useState(false);
React.useEffect(() => {
const on = () => setScrolled(window.scrollY > 32);
window.addEventListener('scroll', on, { passive: true });
on();
return () => window.removeEventListener('scroll', on);
}, []);
const navLinks = [
['The Evening', '#about'],
['About', '#mission'],
['Experience', '#experience'],
['Partner', '#sponsors'],
['Contact', '#footer'],
];
return (<>
Elevate Noir Gala
{!mobile && }
{e.currentTarget.style.transform = 'translateY(-1px)';e.currentTarget.style.boxShadow = `0 8px 30px ${s.gold}40`;}}
onMouseLeave={(e) => {e.currentTarget.style.transform = 'none';e.currentTarget.style.boxShadow = 'none';}}>
Reserve
{mobile && setMenuOpen(true)} s={s} />}
{mobile && setMenuOpen(false)} s={s}
links={navLinks.map(([l, h]) => [l, h, false])}
cta={ { setMenuOpen(false); onTickets(); }} style={{
background: s.gold, color: s.bg, border: 'none', padding: '14px 36px',
letterSpacing: '0.18em', textTransform: 'uppercase', fontWeight: 500, cursor: 'pointer',
fontFamily: s.sans, fontSize: 13,
}}>Reserve Tickets }
/>}
>);
}
// Tagline + investment statement
function ObsidianTagline() {
const s = obsidianStyles;
const { mobile, tablet } = useMobile();
const px = mobile ? 24 : 56;
return (
◇ THE VISION
Where Culture, Capital
& Community Connect.
The Elevate Noir Gala is more than an event — it is a curated cultural experience that brings together influential professionals, creatives, entrepreneurs, and tastemakers for an unforgettable evening of knowledge, empowerment, connection, and visibility.
This is not a donation — it's a strategic investment in Black excellence.
);
}
// Stats / 250+ + by the numbers
// Email notify form with submission handling
function NotifyForm({ mobile }) {
const s = obsidianStyles;
const [email, setEmail] = React.useState('');
const [status, setStatus] = React.useState('idle'); // idle | sending | done | error
const handleSubmit = async (e) => {
e.preventDefault();
if (!email || status === 'sending') return;
setStatus('sending');
try {
const res = await fetch('subscribe.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email }),
});
const data = await res.json();
if (res.ok) {
setStatus('done');
setEmail('');
} else {
setStatus('error');
}
} catch {
setStatus('error');
}
};
if (status === 'done') {
return (
You're on the list. We'll be in touch.
);
}
return (
);
}
function ObsidianStats({ onTickets }) {
const s = obsidianStyles;
const { mobile, tablet } = useMobile();
const px = mobile ? 24 : 56;
return (
250+
Professionals & Entrepreneurs
A curated, high-intent audience of business owners, creatives, consultants, and career-driven individuals — the kind of room where conversations turn into companies.
{[
['07', 'Distinct experiences'],
['1', 'Night'],
['10+', 'Vendor tables']].
map(([n, l]) =>
)}
◇ DON'T MISS THIS NIGHT
Reserve your seat.
Tickets release in waves. Get the night-of program, the after-party address, and first access to early-bird seats.
Or reserve a seat now →
);
}
// Mission / Party & Bash about
function ObsidianAbout() {
const s = obsidianStyles;
const { mobile, tablet } = useMobile();
const px = mobile ? 24 : 56;
return (
◇ THE MISSION
To create a safe space for Black professionals to learn from experts in their field, experience the talents of creatives within Toronto, and foster connections that develop long-term economic growth within the Black community.
{[
{ h: 'The Gap', b: 'Black-owned businesses receive less funding and visibility. Safe, high-quality networking spaces remain limited.' },
{ h: 'The Solution', b: 'A premium environment where attendees connect and are noticed at a high level. A platform where emerging and established voices are amplified together.' },
{ h: 'The Impact', b: 'Meaningful networking that fosters real relationships and opportunity for long-term economic development within the community.' }].
map((p, i) =>
{String(i + 1).padStart(2, '0')} ━
{p.h}
{p.b}
)}
PRESENTED BY
Party & Bash
A luxury event planning & decor company creating immersive, intentional experiences across Toronto — from intimate celebrations to large-scale cultural moments.
As the creator of Elevate Noir, Party & Bash partners with Black-owned businesses, creatives, and entrepreneurs across the city to bring the vision to life — from décor to dining, fashion to media.
Visit partyandbash.ca →
);
}
// Act detail content — extended descriptions
const ACT_DETAILS = {
'01': 'Explore a curated marketplace of premium Black-owned brands from across the GTA. From fashion and beauty to tech and wellness — discover the entrepreneurs shaping Toronto\'s creative economy.',
'02': 'A culinary journey through the GTA\'s finest Black-owned restaurants. Taste of Toronto brings together chefs who are redefining the city\'s food scene, one plate at a time.',
'03': 'Engaging panel discussions on mental health, finance, and entrepreneurship. Hear from industry leaders and community voices at the intersection of knowledge and action.',
'04': 'A runway celebration of Black Canadian fashion designers. From emerging talent to established names — this is their stage, their moment, their art.',
'05': 'An evening of live entertainment featuring DJ sets, spoken word artists, dancers, hosts, and musical performances. From dusk to last call, every moment curated for the culture.',
'06': 'Bid on exclusive experiences and one-of-a-kind items. All proceeds fuel the Party & Bash initiative and future Elevate events.',
'07': 'The night doesn\'t end — it evolves. A curated soundtrack, continued conversation, and a room that doesn\'t want to leave. DJ-led, late-night, unforgettable.',
};
// Experience grid — full bleed
function ObsidianExperience() {
const s = obsidianStyles;
const { mobile, tablet } = useMobile();
const px = mobile ? 24 : 56;
const [selectedAct, setSelectedAct] = React.useState(null);
return (
◇ THE EVENING IN SEVEN ACTS
Elevate Gala Experience.
Every moment intentionally designed for connection, culture, and celebration.
{EXPERIENCES.slice(0, 6).map((e, i) =>
setSelectedAct(e)} style={{
background: s.bgSoft, border: `1px solid ${s.graySoft}`,
padding: mobile ? 28 : 36, height: '100%', display: 'flex', flexDirection: 'column',
transition: 'all 0.45s cubic-bezier(0.16,1,0.3,1)',
cursor: 'pointer', position: 'relative', overflow: 'hidden'
}}
onMouseEnter={(ev) => {ev.currentTarget.style.borderColor = `${obsidianStyles.gold}55`;ev.currentTarget.style.transform = 'translateY(-4px)';ev.currentTarget.style.boxShadow = '0 20px 60px rgba(0,0,0,0.4)';}}
onMouseLeave={(ev) => {ev.currentTarget.style.borderColor = obsidianStyles.graySoft;ev.currentTarget.style.transform = 'none';ev.currentTarget.style.boxShadow = 'none';}}>
{e.name}
{e.blurb}
)}
{/* Finale — full-width closing act */}
{EXPERIENCES[6] && (() => {
const e = EXPERIENCES[6];
return (
setSelectedAct(e)} style={{
position: 'relative', overflow: 'hidden', cursor: 'pointer',
background: s.bgSoft, border: `1px solid ${s.gold}33`,
minHeight: mobile ? 'auto' : 340, display: 'grid', gridTemplateColumns: mobile ? '1fr' : '1.2fr 1fr',
transition: 'all 0.45s cubic-bezier(0.16,1,0.3,1)'
}}
onMouseEnter={(ev) => {ev.currentTarget.style.borderColor = `${obsidianStyles.gold}88`;ev.currentTarget.style.boxShadow = '0 30px 80px rgba(0,0,0,0.5)';}}
onMouseLeave={(ev) => {ev.currentTarget.style.borderColor = `${obsidianStyles.gold}33`;ev.currentTarget.style.boxShadow = 'none';}}>
{/* video / atmosphere panel */}
{/* copy panel */}
ACT · CLOSING THE NIGHT
{e.name.split(' ').slice(0, -1).join(' ')} {e.name.split(' ').slice(-1)[0]}.
{e.blurb} A curated soundtrack, a continued conversation, and a room that doesn't want to leave.
11 PM — LATE
RSVP REQUIRED
);
})()}
{/* Act detail modal */}
setSelectedAct(null)}>
{selectedAct && setSelectedAct(null)} allActs={EXPERIENCES} onSelectAct={setSelectedAct} />}
);
}
function ActDetailPopup({ act, onClose, allActs, onSelectAct }) {
const s = obsidianStyles;
const { mobile } = useMobile();
const [email, setEmail] = React.useState('');
const [status, setStatus] = React.useState('idle');
const handleSubmit = async (e) => {
e.preventDefault();
if (!email || status === 'sending') return;
setStatus('sending');
try {
const res = await fetch('subscribe.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, list: 'attendee' }),
});
if (res.ok) { setStatus('done'); setEmail(''); }
else { setStatus('error'); }
} catch { setStatus('error'); }
};
const otherActs = allActs.filter(a => a.n !== act.n);
return (
◇ ACT {act.n} · THE EVENING IN SEVEN ACTS
{act.n}
{act.name}.
{act.blurb}
{ACT_DETAILS[act.n] || act.blurb}
{/* Email signup */}
◇ STAY UPDATED
Get notified about {act.name.toLowerCase()}.
Be the first to know when we announce performers, vendors, panelists, and more for this experience.
{status === 'done' ? (
You're on the list. We'll keep you posted on {act.name.toLowerCase()}.
) : (
)}
{status === 'error' &&
Something went wrong. Try again.
}
{/* Other acts */}
◇ MORE EXPERIENCES
{otherActs.map(e => (
onSelectAct(e)} style={{
color: s.cream, cursor: 'pointer',
background: s.bgSoft, border: `1px solid ${s.graySoft}`,
padding: mobile ? 20 : 28, display: 'block',
transition: 'border-color 0.3s'
}}>
{e.n}
{e.name}
{e.blurb}
))}
);
}
// Ticket tiers showcase — Gold / Silver / Bronze with early bird highlight
function ObsidianTicketTiers({ onTickets }) {
const s = obsidianStyles;
const { mobile, tablet } = useMobile();
const px = mobile ? 24 : 56;
const tierColors = {
vip: { ring: '#E0C080', glow: '#E0C08033', label: 'VIP' },
gold: { ring: s.gold, glow: `${s.gold}33`, label: 'GOLD' },
silver: { ring: '#B8B5AB', glow: '#B8B5AB22', label: 'SILVER' },
bronze: { ring: '#A66B3A', glow: '#A66B3A22', label: 'BRONZE' }
};
return (
◇ TICKETS · OCTOBER 17 · 2026
Choose how you arrive.
Limited release — save up to $55 per ticket
{TICKET_TYPES.map((t, i) => {
const c = tierColors[t.id];
const isFeatured = t.id === 'vip';
return (
{e.currentTarget.style.borderColor = c.ring;e.currentTarget.style.transform = 'translateY(-4px)';e.currentTarget.style.boxShadow = `0 20px 60px ${c.glow}`;}}
onMouseLeave={(e) => {e.currentTarget.style.borderColor = isFeatured ? `${s.gold}55` : s.graySoft;e.currentTarget.style.transform = 'none';e.currentTarget.style.boxShadow = 'none';}}>
{isFeatured &&
MOST POPULAR
}
TIER {String(i + 1).padStart(2, '0')}
{c.label}
{t.name}
{t.desc}
{/* price */}
Save {money(t.price - t.earlyPrice)} · regular pricing {money(t.price)} when early bird closes.
{/* perks */}
{t.perks.map((p, j) =>
— {p}
)}
{if (!isFeatured) {e.currentTarget.style.background = s.gold;e.currentTarget.style.color = s.bg;}}}
onMouseLeave={(e) => {if (!isFeatured) {e.currentTarget.style.background = 'transparent';e.currentTarget.style.color = s.gold;}}}>
Reserve this tier →
);
})}
{/* fine print */}
All tickets include arrival reception and program. Groups of 4+ in any tier — reach out for complimentary side-by-side seating.
tickets@elevatenoirgala.ca →
);
}
// Sponsor CTA — tiers preview + CTA to flow
function ObsidianSponsorCTA({ onSponsor }) {
const s = obsidianStyles;
const { mobile, tablet } = useMobile();
const px = mobile ? 24 : 56;
return (
);
}
// Footer
function ObsidianFooter() {
const s = obsidianStyles;
const { mobile, tablet } = useMobile();
const px = mobile ? 24 : 56;
return (
);
}
function CookieConsent() {
const s = obsidianStyles;
const [visible, setVisible] = React.useState(() => !localStorage.getItem('cookie_consent'));
if (!visible) return null;
const accept = () => { localStorage.setItem('cookie_consent', 'accepted'); setVisible(false); };
return (
We use cookies to improve your experience. By continuing, you agree to our{' '}
Privacy Policy .
Accept
);
}
function EmailPopup() {
const s = obsidianStyles;
const { mobile } = useMobile();
const [show, setShow] = React.useState(false);
const [email, setEmail] = React.useState('');
const [status, setStatus] = React.useState('idle'); // idle | sending | done | error
React.useEffect(() => {
if (localStorage.getItem('email_popup_dismissed')) return;
let scrollTime = 0;
let scrolling = false;
let timeout = null;
let interval = null;
const onScroll = () => {
if (!scrolling) {
scrolling = true;
}
clearTimeout(timeout);
timeout = setTimeout(() => { scrolling = false; }, 200);
};
interval = setInterval(() => {
if (scrolling) {
scrollTime += 100;
if (scrollTime >= 3000) {
setShow(true);
clearInterval(interval);
window.removeEventListener('scroll', onScroll);
}
}
}, 100);
window.addEventListener('scroll', onScroll, { passive: true });
return () => {
clearInterval(interval);
clearTimeout(timeout);
window.removeEventListener('scroll', onScroll);
};
}, []);
const dismiss = () => {
setShow(false);
localStorage.setItem('email_popup_dismissed', 'true');
};
const handleSubmit = async (e) => {
e.preventDefault();
if (!email || status === 'sending') return;
setStatus('sending');
try {
const res = await fetch('subscribe.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email }),
});
if (res.ok) {
setStatus('done');
localStorage.setItem('email_popup_dismissed', 'true');
} else {
setStatus('error');
}
} catch {
setStatus('error');
}
};
if (!show) return null;
return (
e.stopPropagation()} style={{
background: s.bgSoft, border: `1px solid ${s.graySoft}`,
padding: mobile ? '36px 24px 28px' : '48px 40px 36px', maxWidth: 440, width: '100%',
position: 'relative', textAlign: 'center',
borderRadius: mobile ? '16px 16px 0 0' : 0,
borderBottom: mobile ? 'none' : `1px solid ${s.graySoft}`
}}>
{/* Close button */}
×
{/* Crown logo */}
{/* Heading */}
Stay in the Know.
{/* Subtext */}
Get early access to tickets, lineup reveals, and exclusive updates.
{status === 'done' ? (
You're on the list. See you there.
) : (
)}
{status === 'error' && (
Something went wrong. Please try again.
)}
{status !== 'done' && (
No thanks
)}
);
}
Object.assign(window, { ObsidianModal, ObsidianTicketFlow, ObsidianNav, ObsidianTagline, ObsidianStats, ObsidianAbout, ObsidianExperience, ObsidianTicketTiers, ObsidianSponsorCTA, ObsidianFooter, CookieConsent, EmailPopup });