// shared.jsx — shared data, sponsor flow logic, crown SVG glyph
// All directions consume these.
const GALA_DATA = {
name: 'Elevate Noir Gala',
theme: 'Black Dream',
date: 'Saturday · October 17 · 2026',
dateShort: '17.10.2026',
venue: 'National Event Venue',
address: '1000 Finch Avenue W · Toronto',
city: 'Toronto, ON',
dress: 'Formal All Black Attire',
capacity: '250+ Professionals',
presenter: 'Party & Bash',
tagline: 'Where Culture, Capital & Community Connect',
instagram: '@elevatenoirgala',
url: 'elevatenoirgala.ca',
};
const TIERS = [
{ id: 'title', name: 'Title', price: 10000, avail: 1, color: 'gold',
perks: ['Headline naming rights', 'On-stage 5-min remarks', 'Premier table for 10', 'Red carpet co-brand', 'Full press inclusion'] },
{ id: 'gold', name: 'Gold', price: 5000, avail: 3, color: 'gold',
perks: ['Premium logo placement', 'Table for 8 · front section', 'Stage acknowledgement', 'Vendor marketplace booth', 'Press release inclusion'] },
{ id: 'silver', name: 'Silver', price: 2500, avail: 4, color: 'silver',
perks: ['Logo placement', 'Table for 6', 'Step-and-repeat brand', 'Social feature post'] },
{ id: 'community', name: 'Community', price: 500, avail: 8, color: 'mute',
perks: ['Program book mention', '2 tickets to gala', 'Community wall feature'] },
];
const TICKET_TYPES = [
{
id: 'gold',
name: 'Full Gala Experience',
badge: 'Gold Ticket',
price: 175, earlyPrice: 120,
desc: 'The full evening — every act, every detail, from arrival to last call.',
perks: [
'Welcome refreshments on arrival',
'Vendor marketplace hour',
'Designated networking sessions',
'Curated dinner & dessert from Black-owned GTA restaurants (open bar)',
'Inspirational & educational speakers',
'Spoken word performance',
'Dance performance',
'Fashion show · Black Canadian designers',
'Black professional panel',
'Musical performances',
'Luxury after-party with DJ',
],
},
{
id: 'silver',
name: 'The Culture Experience',
badge: 'Silver Ticket',
price: 100, earlyPrice: 75,
desc: 'For the cultural moments — runway, panel, performances, conversation.',
perks: [
'Refreshments',
'Fashion show',
'Black professional panel',
'Musical performances',
'Networking sessions',
],
},
{
id: 'bronze',
name: 'The After Party',
badge: 'Bronze Ticket',
price: 60, earlyPrice: 45,
desc: 'Close the night where the conversation keeps going.',
perks: [
'Luxury after-party access',
'Live DJ set · curated playlist',
'Cash bar',
],
},
];
const ACTIVATIONS = [
{ id: 'cocktail', name: 'Cocktail Hour', detail: "Brand the night's signature cocktail", price: 2500 },
{ id: 'photo', name: 'Photo Booth', detail: 'Branded backdrop · all guest photos', price: 1800 },
{ id: 'carpet', name: 'Red Carpet', detail: 'Step-and-repeat logo · arrival video', price: 3000 },
{ id: 'giftbag', name: 'Gift Bag', detail: 'Insert · branded tissue · main card', price: 1200 },
];
const EXPERIENCES = [
{ n: '01', name: 'Vendor Marketplace', blurb: 'A curated showcase of premium Black-owned brands.' },
{ n: '02', name: 'Curated Dining', blurb: "A journey through the GTA's finest Black-owned kitchens." },
{ n: '03', name: 'Expert Panels', blurb: 'Mental health, finance, and entrepreneurship — at the table.' },
{ n: '04', name: 'Fashion Show', blurb: 'Black Canadian designers on a runway built for them.' },
{ n: '05', name: 'Live Entertainment', blurb: 'DJ, spoken word, dancers, hosts. From dusk to last call.' },
{ n: '06', name: 'Silent Auction', blurb: 'Bid on exclusive experiences. Proceeds to the mission.' },
{ n: '07', name: 'Luxury After Party', blurb: 'Close the night where the conversation keeps going.' },
];
// Money formatter
const money = (n) => '$' + n.toLocaleString('en-US');
// Reusable sponsor flow hook — manages step + selection state
function useSponsorFlow(initialTier = 'gold') {
const [step, setStep] = React.useState(1);
const [tierId, setTierId] = React.useState(initialTier);
const [activations, setActivations] = React.useState([]);
const tier = TIERS.find(t => t.id === tierId);
const activationTotal = activations.reduce((sum, id) => sum + ACTIVATIONS.find(a => a.id === id).price, 0);
const total = tier.price + activationTotal;
const toggleActivation = (id) => {
setActivations(a => a.includes(id) ? a.filter(x => x !== id) : [...a, id]);
};
const next = () => setStep(s => Math.min(s + 1, 4));
const prev = () => setStep(s => Math.max(s - 1, 1));
const reset = () => { setStep(1); setActivations([]); };
return { step, setStep, tierId, setTierId, tier, activations, toggleActivation, total, activationTotal, next, prev, reset };
}
// ---- Crown logo (real artwork) ----
// PNG variants:
// assets/crown-light.png — cream + gold, for dark backgrounds
// assets/logo-light.png — crown + wordmark, cream + gold, for dark bg
// assets/crown.png — black + gold (original colors), for light bg
// assets/logo.png — original full logo (black + gold), for light bg
function LogoMark({ size = 48, variant = 'light', style }) {
const src = variant === 'light' ? 'assets/crown-light.png' : 'assets/crown.png';
// intrinsic aspect ≈ 702:538
return (
);
}
function LogoFull({ size = 96, variant = 'light', style }) {
const src = variant === 'light' ? 'assets/logo-light.png' : 'assets/logo.png';
return (
);
}
// ---- Crown glyph (simplified SVG) ----
// Kept as a fallback / decorative element for other directions.
function CrownGlyph({ size = 48, stroke = 1.2, style }) {
return (
);
}
// Tiny SVG mark used as iconographic divider
function DividerOrnament({ color = 'currentColor' }) {
return (
);
}
// Reusable ticket flow hook
function useTicketFlow(initial = 'gold') {
const [step, setStep] = React.useState(1);
const [ticketId, setTicketId] = React.useState(initial);
const [qty, setQty] = React.useState(2);
const [earlyBird, setEarlyBird] = React.useState(true); // assume early bird window
const ticket = TICKET_TYPES.find(t => t.id === ticketId);
const unitPrice = earlyBird ? ticket.earlyPrice : ticket.price;
const isTable = false; // tickets are individual now
const effectiveQty = qty;
const subtotal = unitPrice * effectiveQty;
const fees = Math.round(subtotal * 0.04);
const total = subtotal + fees;
const next = () => setStep(s => Math.min(s + 1, 4));
const prev = () => setStep(s => Math.max(s - 1, 1));
const reset = () => { setStep(1); setQty(2); };
return { step, setStep, ticketId, setTicketId, ticket, qty, setQty, isTable, effectiveQty, unitPrice, earlyBird, setEarlyBird, subtotal, fees, total, next, prev, reset };
}
Object.assign(window, { GALA_DATA, TIERS, TICKET_TYPES, ACTIVATIONS, EXPERIENCES, money, useSponsorFlow, useTicketFlow, LogoMark, LogoFull, CrownGlyph, DividerOrnament });