// 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)' }}> {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 }) => ; 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.'}
{TICKET_TYPES.map((t, i) => { const sel = f.ticketId === t.id; const displayPrice = f.earlyBird ? t.earlyPrice : t.price; return ( ); })}
} {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.qty}
{/* 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.

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 &&
Total
{money(f.total)}
← Back Continue →
} {f.step === 3 &&
Total
{money(f.total)}
← Back
}
); } // ───────────────────────────────────────────────────────────────────── // 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 (<> {mobile && setMenuOpen(false)} s={s} links={navLinks.map(([l, h]) => [l, h, false])} cta={} />} ); } // 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 (
setEmail(e.target.value)} placeholder="your@email.com" style={{ flex: 1, background: s.bg, border: `1px solid ${s.graySoft}`, borderRight: mobile ? `1px solid ${s.graySoft}` : 'none', color: s.cream, fontFamily: s.sans, fontSize: 13, padding: '14px 18px', outline: 'none' }} /> {status === 'error' &&

Something went wrong. Try again.

}
); } 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]) =>
{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.

); } // 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}

)}
Party & Bash
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.n}
ACT
{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 */}
FINALE
{e.n}
{/* 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()}.

) : (
setEmail(e.target.value)} placeholder="your@email.com" style={{ flex: 1, background: s.bg, border: `1px solid ${s.graySoft}`, borderRight: mobile ? `1px solid ${s.graySoft}` : 'none', color: s.cream, fontFamily: s.sans, fontSize: 13, padding: '14px 18px', outline: 'none' }} />
)} {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.

Early bird pricing live
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 */}
${t.earlyPrice}
${t.price}
EARLY BIRD
Save {money(t.price - t.earlyPrice)} · regular pricing {money(t.price)} when early bird closes.
{/* perks */}
    {t.perks.map((p, j) =>
  • {p}
  • )}
); })}
{/* 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 (
◇ PARTNERSHIP · 2026

Become a partner.

We're securing select brand partners. Spots are limited to maintain exclusivity, intentionality, and value.

{TIERS.map((t, i) =>
onSponsor(t.id)} style={{ background: s.bgSoft, border: `1px solid ${s.graySoft}`, padding: mobile ? '24px 22px' : '32px 28px', textAlign: 'left', height: '100%', display: 'flex', flexDirection: 'column', cursor: 'pointer', transition: 'border-color 0.2s' }} onMouseEnter={(e) => e.currentTarget.style.borderColor = s.gold} onMouseLeave={(e) => e.currentTarget.style.borderColor = s.graySoft}>
{String(i + 1).padStart(2, '0')}
{t.name} Sponsor
{money(t.price)}+
    {t.perks.slice(0, mobile ? 3 : 4).map((p, j) =>
  • {p}
  • )}
)}
OR
onSponsor('donation')} style={{ background: s.bgSoft, border: `1px solid ${s.graySoft}`, padding: mobile ? '20px 22px' : '24px 28px', display: 'flex', alignItems: 'center', gap: mobile ? 14 : 20, cursor: 'pointer', transition: 'border-color 0.2s' }} onMouseEnter={(e) => e.currentTarget.style.borderColor = s.gold} onMouseLeave={(e) => e.currentTarget.style.borderColor = s.graySoft}>
Community Donation
Not sponsoring? Still want to support the movement.
$50+
Hello@elevatenoirgala.com
); } // 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.
); } 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.

) : (
setEmail(e.target.value)} placeholder="Your email" style={{ flex: 1, padding: '13px 16px', fontSize: 13, fontFamily: s.sans, background: s.bg, color: s.cream, border: `1px solid ${s.graySoft}`, borderRight: mobile ? `1px solid ${s.graySoft}` : 'none', borderBottom: mobile ? 'none' : `1px solid ${s.graySoft}`, outline: 'none', letterSpacing: '0.02em' }} />
)} {status === 'error' && (

Something went wrong. Please try again.

)} {status !== 'done' && ( )}
); } Object.assign(window, { ObsidianModal, ObsidianTicketFlow, ObsidianNav, ObsidianTagline, ObsidianStats, ObsidianAbout, ObsidianExperience, ObsidianTicketTiers, ObsidianSponsorCTA, ObsidianFooter, CookieConsent, EmailPopup });