// 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 ( Elevate Noir Gala ); } function LogoFull({ size = 96, variant = 'light', style }) { const src = variant === 'light' ? 'assets/logo-light.png' : 'assets/logo.png'; return ( Elevate Noir Gala ); } // ---- Crown glyph (simplified SVG) ---- // Kept as a fallback / decorative element for other directions. function CrownGlyph({ size = 48, stroke = 1.2, style }) { return ( {/* base band */} {/* center peak */} {/* side peaks */} {/* base flourish */} {/* small adornments */} ); } // 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 });