/* ===========================================================================
   ARGT Dashboards — shared lib (data + components)
   Locked copy verbatim from xyzagency/argt@main:
     - artifacts/argt-app/src/lib/dashboard-roles.ts
     - artifacts/argt-app/src/lib/role-overview-configs.ts
     - artifacts/argt-app/src/lib/ui-copy.ts
     - artifacts/argt-console/src/lib/ui-copy.ts
     - artifacts/argt-console/src/lib/launch/constants.ts
   Do not edit strings here. To change copy, file a proposal in
   docs/agents/ui-copy.proposals.md per the template.
   =========================================================================== */

/* ---------------------------------------------------------------------------
   ROLE IDENTITIES (from dashboard-roles.ts)
   --------------------------------------------------------------------------- */
const ROLE_ACCENTS = {
  amber:   { hex: "#C49800", bright: "#E0B638" },
  magenta: { hex: "#B5006A", bright: "#D8288C" }, // matches --pvt-gold/-bright
  teal:    { hex: "#007A8A", bright: "#10B5C7" },
  purple:  { hex: "#4C1285", bright: "#7A4FBF" },
};

const DASHBOARD_ROLES = {
  customer: {
    id: "customer", label: "Attendee", accent: "amber",
    eyebrow: "FESTIVAL ACCESS",
    title: "Your way in, and your weekend.",
    intro: "Your admission ticket and wristband, plus schedule, map, VIP, group orders, and travel for ARGT 2026 at Harbour Pointe in Fort Pierce.",
    nav: [
      { id: "overview", label: "Overview", icon: "home" },
      { id: "tickets", label: "Admission Ticket", icon: "ticket" },
      { id: "schedule", label: "Schedule", icon: "calendar" },
      { id: "map", label: "Venue Map", icon: "map" },
      { id: "vip", label: "VIP Add-ons", icon: "crown" },
      { id: "group", label: "Group Orders", icon: "users" },
      { id: "travel", label: "Travel", icon: "plane" },
    ],
  },
  fan: {
    id: "fan", label: "Fan", accent: "magenta",
    eyebrow: "FOR FANS",
    title: "Back the artists you want to see.",
    intro: "Discover artists, cast votes, save favorites, and follow the leaderboard. $10 per vote, first one free, and 60% goes directly to artist payouts.",
    nav: [
      { id: "overview", label: "Overview", icon: "home" },
      { id: "discover", label: "Discover", icon: "headphones" },
      { id: "votes", label: "My Votes", icon: "heart" },
      { id: "saved", label: "Saved", icon: "star" },
      { id: "leaderboard", label: "Leaderboard", icon: "chart" },
      { id: "share", label: "Share", icon: "share" },
      { id: "checkout", label: "Buy Votes", icon: "wallet" },
      { id: "rewards", label: "Rewards", icon: "trophy" },
    ],
  },
  creator: {
    id: "creator", label: "Creator", accent: "teal",
    eyebrow: "FOR CREATORS",
    title: "Your next stage is 10,000 ears away.",
    intro: "Submit your audition, build your profile, track votes received, and follow the BKK Street Team opportunities.",
    nav: [
      { id: "overview", label: "Overview", icon: "home" },
      { id: "status", label: "My Application", icon: "file" },
      { id: "profile", label: "Profile", icon: "person" },
      { id: "auditions", label: "Auditions", icon: "mic" },
      { id: "performance", label: "Performance", icon: "chart" },
      { id: "share", label: "Share Studio", icon: "share" },
      { id: "shows", label: "Shows", icon: "calendar" },
      { id: "opportunities", label: "Opportunities", icon: "briefcase" },
    ],
  },
  sponsor: {
    id: "sponsor", label: "Sponsor", accent: "amber",
    eyebrow: "BRAND PARTNERS",
    title: "Activate to 10,000 culture consumers.",
    intro: "Tier status, activation planning, asset uploads, payment, hospitality, and post-event recap, all in one place.",
    nav: [
      { id: "overview", label: "Overview", icon: "home" },
      { id: "intake", label: "Inquiry", icon: "file" },
      { id: "activation", label: "Activation", icon: "megaphone" },
      { id: "assets", label: "Assets", icon: "upload" },
      { id: "payment", label: "Payment", icon: "wallet" },
      { id: "deliverables", label: "Deliverables", icon: "trophy" },
      { id: "recap", label: "Recap", icon: "chart" },
      { id: "settings", label: "Settings", icon: "gear" },
    ],
  },
  vendor:  {
    id: "vendor",  label: "Vendor",  accent: "teal",
    eyebrow: "VENDOR VILLAGE",
    title: "Sell to a crowd that buys.",
    intro: "Application, booth, compliance, payment, logistics, and Vendor Village marketing inclusion.",
    nav: [
      { id: "overview", label: "Overview", icon: "home" },
      { id: "intake", label: "Application", icon: "file" },
      { id: "booth", label: "Booth", icon: "store" },
      { id: "compliance", label: "Compliance", icon: "shield" },
      { id: "payment", label: "Payment", icon: "wallet" },
      { id: "logistics", label: "Logistics", icon: "package" },
      { id: "settings", label: "Settings", icon: "gear" },
    ],
  },
  crew:    {
    id: "crew",    label: "Crew",    accent: "purple",
    eyebrow: "CREW",
    title: "Bring the festival to life.",
    intro: "Service category, availability, assignments, credentials, portfolio, hours logged, and call sheets.",
    nav: [
      { id: "overview", label: "Overview", icon: "home" },
      { id: "intake", label: "Apply", icon: "file" },
      { id: "availability", label: "Availability", icon: "calendar" },
      { id: "assignments", label: "Assignments", icon: "list" },
      { id: "credentials", label: "Credentials", icon: "id" },
      { id: "portfolio", label: "Portfolio", icon: "briefcase" },
      { id: "settings", label: "Settings", icon: "gear" },
    ],
  },
  volunteer: {
    id: "volunteer", label: "Volunteer", accent: "magenta",
    eyebrow: "VOLUNTEER",
    title: "Show up. Make it real.",
    intro: "Sign up for shifts, watch training, log hours, claim perks, and get your festival-day check-in QR.",
    nav: [
      { id: "overview", label: "Overview", icon: "home" },
      { id: "intake", label: "Interest", icon: "file" },
      { id: "shifts", label: "Shifts", icon: "calendar" },
      { id: "training", label: "Training", icon: "graduate" },
      { id: "check-in", label: "Check-In", icon: "qr" },
      { id: "settings", label: "Settings", icon: "gear" },
    ],
  },
  veterans: {
    id: "veterans", label: "Veterans", accent: "amber",
    eyebrow: "VETERAN WELLNESS ZONE",
    title: "Recognition starts here.",
    intro: "Group tickets, Wellness Zone pavilion, recognition moments, and resource kits for veteran organizations partnering with ARGT.",
    nav: [
      { id: "overview", label: "Overview", icon: "home" },
      { id: "intake", label: "Org Application", icon: "file" },
      { id: "group-tickets", label: "Group Tickets", icon: "ticket" },
      { id: "pavilion", label: "Pavilion", icon: "building" },
      { id: "recognition", label: "Recognition", icon: "shield" },
      { id: "resources", label: "Resources", icon: "list" },
      { id: "settings", label: "Settings", icon: "gear" },
    ],
  },
  "local-business": {
    id: "local-business", label: "Local Business", accent: "teal",
    eyebrow: "FORT PIERCE LOCAL",
    title: "Festival wristbands. Your customers.",
    intro: "Set up your wristband discount, upload your creative, generate your QR code, and watch scans and redemptions in real time during festival weekend.",
    nav: [
      { id: "overview", label: "Overview", icon: "home" },
      { id: "intake", label: "Business Application", icon: "file" },
      { id: "discount", label: "Discount Setup", icon: "wallet" },
      { id: "creative", label: "Creative", icon: "image" },
      { id: "qr", label: "QR Code", icon: "qr" },
      { id: "performance", label: "Performance", icon: "chart" },
      { id: "settings", label: "Settings", icon: "gear" },
    ],
  },
  press: {
    id: "press", label: "Press", accent: "purple",
    eyebrow: "PRESS & MEDIA",
    title: "Your credential, your assets, your access.",
    intro: "Request credentials, download approved press kit assets, request interview slots, and review story angles curated by the ARGT PR team.",
    nav: [
      { id: "overview", label: "Overview", icon: "home" },
      { id: "intake", label: "Credential Request", icon: "file" },
      { id: "assets", label: "Assets", icon: "image" },
      { id: "interviews", label: "Interviews", icon: "chat" },
      { id: "angles", label: "Story Angles", icon: "bulb" },
      { id: "settings", label: "Settings", icon: "gear" },
    ],
  },
};

/* ---------------------------------------------------------------------------
   ROLE OVERVIEW CONFIGS (verbatim from role-overview-configs.ts)
   --------------------------------------------------------------------------- */
const ROLE_OVERVIEW_CONFIGS = {
  sponsor: {
    roleSlug: "sponsor", roleLabel: "Sponsor", accent: "amber",
    eyebrow: "BRAND PARTNERS",
    preIntakeTitle: "Activate to 10,000 culture consumers.",
    preIntakeIntro: "Reach Treasure Coast culture-makers and Gen-Z fans face-to-face. Start with a 2-minute inquiry, our partnerships lead replies within 3 business days with tier options.",
    reviewTitle: "Application under review.",
    reviewIntro: "Our partnerships team is reviewing your inquiry. We respond within 3 business days with tier options, activation scope, and a kickoff call invitation.",
    acceptedTitle: "You're an ARGT 2026 sponsor.",
    acceptedIntro: "Your sponsor workspace is live. Plan your activation, upload assets, manage payment, track deliverables, and get your post-event recap, all in one place.",
    journeySteps: [
      { label: "Apply",            desc: "Submit the 3-section inquiry." },
      { label: "Tier locked",      desc: "ARGT lead confirms tier + budget." },
      { label: "Activation live",  desc: "Assets uploaded, payment cleared." },
      { label: "Recap delivered",  desc: "Post-event report within 14 days." },
    ],
    tiles: [
      { label: "Inquiry",      desc: "Edit your initial application.",   icon: "file" },
      { label: "Activation",   desc: "Plan zone, KPIs, hospitality.",    icon: "megaphone" },
      { label: "Assets",       desc: "Upload logos, creative, copy.",    icon: "upload" },
      { label: "Payment",      desc: "Tier invoice + Stripe checkout.",  icon: "wallet" },
      { label: "Deliverables", desc: "What ARGT owes you, tracked.",     icon: "trophy" },
      { label: "Recap",        desc: "Post-event report (Sep 30).",      icon: "chart" },
    ],
    extraCards: [
      { eyebrow: "HOSPITALITY",   title: "VIP allocation",   body: "Your tier includes a number of VIP passes — confirmed when ARGT locks your activation scope. Track allocations + plus-ones here." },
      { eyebrow: "CO-MARKETING",  title: "Materials inbox",  body: "Assets ARGT prepares for sponsor distribution land here: social posts, email blurbs, partner co-branded creative." },
    ],
    ctaPre: "Start inquiry",
    ctaReview: "View status",
    ctaAccepted: "Open activation",
  },
  vendor: {
    roleSlug: "vendor", roleLabel: "Vendor", accent: "teal",
    eyebrow: "VENDOR VILLAGE",
    preIntakeTitle: "Sell to a crowd that buys.",
    preIntakeIntro: "10,000 attendees. Three days. Real spending power. Apply for a Vendor Village booth, our vendor team reviews weekly and you'll hear back within 7 days.",
    reviewTitle: "Your booth application is in review.",
    reviewIntro: "Our vendor team reviews applications weekly. You'll hear back within 7 days with booth availability matched to your category preference.",
    acceptedTitle: "You're confirmed at the Vendor Village.",
    acceptedIntro: "Your booth workspace is live. Confirm your booth details, upload compliance docs, pay your booth fee, and lock in your day-of logistics.",
    journeySteps: [
      { label: "Apply",            desc: "Tell us about your brand + category." },
      { label: "Approved",         desc: "Vendor team confirms your booth." },
      { label: "Compliance done",  desc: "Docs + payment cleared." },
      { label: "Vendor weekend",   desc: "Sell, network, return next year." },
    ],
    tiles: [
      { label: "Application", desc: "Edit your vendor application.",    icon: "file" },
      { label: "Booth",       desc: "Location, size, power, water.",    icon: "store" },
      { label: "Compliance",  desc: "Insurance, permits, food handler.", icon: "shield" },
      { label: "Payment",     desc: "Booth fee via Stripe.",            icon: "wallet" },
      { label: "Logistics",   desc: "Load-in, setup, breakdown.",       icon: "package" },
    ],
    ctaPre: "Apply for a booth",
    ctaReview: "View status",
    ctaAccepted: "Confirm booth details",
  },
  crew: {
    roleSlug: "crew", roleLabel: "Crew", accent: "purple",
    eyebrow: "CREW",
    preIntakeTitle: "Bring the festival to life.",
    preIntakeIntro: "Professional production, lighting, sound, hospitality, security. Tell us your specialty, our production lead matches you to upcoming festival roles within 2 weeks.",
    reviewTitle: "Your crew application is in review.",
    reviewIntro: "Our production lead is reviewing your skills and availability. We match crew to festival roles as the schedule firms up, usually within 2 weeks.",
    acceptedTitle: "Welcome to the ARGT crew roster.",
    acceptedIntro: "Your crew workspace is live. Mark your festival-day availability, accept assignments, upload credentials, and keep your portfolio current.",
    journeySteps: [
      { label: "Apply",          desc: "Service category, portfolio, rates." },
      { label: "On the roster",  desc: "Production lead confirms fit." },
      { label: "Assignment",     desc: "Specific shifts offered + accepted." },
      { label: "Show weekend",   desc: "Call sheets, credentials, on-site." },
    ],
    tiles: [
      { label: "Apply",         desc: "Edit your crew application.",         icon: "briefcase" },
      { label: "Availability",  desc: "Mark your festival days.",            icon: "calendar" },
      { label: "Assignments",   desc: "Job offers from production.",         icon: "list" },
      { label: "Credentials",   desc: "Licenses + certifications.",          icon: "id" },
      { label: "Portfolio",     desc: "Your public reel + bio.",             icon: "image" },
    ],
    ctaPre: "Submit application",
    ctaReview: "View status",
    ctaAccepted: "Mark availability",
  },
  volunteer: {
    roleSlug: "volunteer", roleLabel: "Volunteer", accent: "magenta",
    eyebrow: "VOLUNTEER",
    preIntakeTitle: "Show up. Make it real.",
    preIntakeIntro: "Volunteers run the energy of ARGT. Help with check-in, hospitality, stage transitions, sustainability, and trade 2 shifts for a free festival weekend pass.",
    reviewTitle: "We're confirming your volunteer interest.",
    reviewIntro: "Once we confirm your interest form, shift sign-ups unlock in your dashboard. Usually takes a few days.",
    acceptedTitle: "Welcome to the ARGT volunteer team.",
    acceptedIntro: "Your volunteer workspace is live. Sign up for shifts, complete training, and grab your day-of check-in QR. 2 shifts = free festival pass.",
    journeySteps: [
      { label: "Interest",   desc: "T-shirt size, dietary, availability." },
      { label: "Confirmed",  desc: "Welcome email + shift sign-up unlocked." },
      { label: "Trained",    desc: "4 short videos, ~30 min total." },
      { label: "Check in",   desc: "QR scanned at the volunteer tent." },
    ],
    tiles: [
      { label: "Interest",  desc: "Edit your volunteer profile.",  icon: "heart" },
      { label: "Shifts",    desc: "Sign up for festival shifts.",  icon: "calendar" },
      { label: "Training",  desc: "4 required modules.",           icon: "graduate" },
      { label: "Check-In",  desc: "Your day-of festival QR.",      icon: "qr" },
    ],
    ctaPre: "Submit interest",
    ctaReview: "View status",
    ctaAccepted: "Sign up for shifts",
  },
  veterans: {
    roleSlug: "veterans", roleLabel: "Veterans", accent: "amber",
    eyebrow: "VETERAN WELLNESS ZONE",
    preIntakeTitle: "Recognition starts here.",
    preIntakeIntro: "Veteran organizations partner with ARGT for the Wellness Zone pavilion, group ticket allocations, and on-stage recognition. Submit your 501(c)(3), verified in 5 business days.",
    reviewTitle: "We're verifying your organization.",
    reviewIntro: "We're verifying your 501(c)(3) status and mission alignment, usually 5 business days. Once verified, pavilion + group ticket tools unlock.",
    acceptedTitle: "Verified, welcome to the Veteran Wellness Zone.",
    acceptedIntro: "Your Veterans workspace is live. Allocate group tickets to members, set your pavilion preferences, schedule recognition moments, and grab resource kits.",
    journeySteps: [
      { label: "Apply",            desc: "Submit your 501(c)(3) details." },
      { label: "Verified",         desc: "Org confirmed; tools unlock." },
      { label: "Pavilion locked",  desc: "Table, hospitality, recognition." },
      { label: "Festival weekend", desc: "On-site recognition moments." },
    ],
    tiles: [
      { label: "Org Application",  desc: "Edit your org details.",           icon: "file" },
      { label: "Group Tickets",    desc: "Allocate to members.",             icon: "users" },
      { label: "Pavilion",         desc: "Table, hospitality, signage.",     icon: "building" },
      { label: "Recognition",      desc: "On-stage moments.",                icon: "trophy" },
      { label: "Resources",        desc: "Downloadable kits.",               icon: "list" },
    ],
    ctaPre: "Submit org application",
    ctaReview: "View status",
    ctaAccepted: "Allocate tickets",
  },
  "local-business": {
    roleSlug: "local-business", roleLabel: "Local Business", accent: "teal",
    eyebrow: "FORT PIERCE LOCAL",
    preIntakeTitle: "Festival wristbands. Your customers.",
    preIntakeIntro: "Set up a wristband discount and ARGT attendees scan your QR, redeem your offer, and you get a wave of new customers during the busiest weekend of the year.",
    reviewTitle: "We're verifying your business.",
    reviewIntro: "We're confirming your Fort Pierce / Treasure Coast business, usually 3 business days. Once verified, discount setup + QR generation unlock.",
    acceptedTitle: "Verified, your storefront is connected.",
    acceptedIntro: "Your Local Business workspace is live. Set your wristband discount, upload your creative, generate your QR code, and watch live scans during festival weekend.",
    journeySteps: [
      { label: "Apply",            desc: "Submit business details + address." },
      { label: "Verified",         desc: "Fort Pierce / Treasure Coast confirmed." },
      { label: "QR live",          desc: "Discount set, creative uploaded." },
      { label: "Festival weekend", desc: "Live scans + redemptions." },
    ],
    tiles: [
      { label: "Business Application", desc: "Edit your business profile.",       icon: "store" },
      { label: "Discount Setup",       desc: "% off, dates, terms.",              icon: "wallet" },
      { label: "Creative",             desc: "Logo, hero image, copy.",           icon: "image" },
      { label: "QR Code",              desc: "Your festival scan code.",          icon: "qr" },
      { label: "Performance",          desc: "Live scans + redemptions.",         icon: "chart" },
    ],
    ctaPre: "Submit application",
    ctaReview: "View status",
    ctaAccepted: "Set up discount",
  },
  press: {
    roleSlug: "press", roleLabel: "Press", accent: "purple",
    eyebrow: "PRESS & MEDIA",
    preIntakeTitle: "Your credential, your assets, your access.",
    preIntakeIntro: "Photographers, videographers, writers, podcasters, request an ARGT 2026 credential. Approved press unlocks asset downloads, story angles, and interview slot requests.",
    reviewTitle: "Your credential request is under review.",
    reviewIntro: "Our PR team reviews coverage history to confirm fit. Expect a response within 5 business days. Approved credentials unlock the full asset library + interviews.",
    acceptedTitle: "Credential approved.",
    acceptedIntro: "Your Press workspace is live. Download the press kit, request interview slots, browse pitch-ready story angles, and manage your outlet info.",
    journeySteps: [
      { label: "Apply",            desc: "Outlet, role, past coverage URL." },
      { label: "Approved",         desc: "PR team confirms credential type." },
      { label: "Equipped",         desc: "Press kit + story angles downloaded." },
      { label: "Festival weekend", desc: "On-site Press Lounge access." },
    ],
    tiles: [
      { label: "Credential Request", desc: "Edit your press info.",                    icon: "file" },
      { label: "Assets",             desc: "Hi-res photos, EPK, boilerplate.",         icon: "image" },
      { label: "Interviews",         desc: "Request slots with founders + artists.",   icon: "chat" },
      { label: "Story Angles",       desc: "Curated pitch-ready angles.",              icon: "bulb" },
    ],
    ctaPre: "Request credential",
    ctaReview: "View status",
    ctaAccepted: "Open press kit",
  },
};

/* ---------------------------------------------------------------------------
   SHELL COPY (from argt-app ui-copy.ts)
   --------------------------------------------------------------------------- */
const SHELL_COPY = {
  brandLabel: "ARGT",
  brandSublabelTemplate: "{{roleLabel}} dashboard",
  topbar: {
    notificationsLabel: "Notifications",
    notificationsEmpty: "You're all caught up.",
    accountMenu: {
      profile: "Profile", settings: "Settings",
      switchRole: "Switch role", addAnotherRole: "Add another role",
      logout: "Sign out",
    },
  },
};

/* ---------------------------------------------------------------------------
   CONTEXTUAL TOP-BAR DATA — per-role notifications + account-menu quick links.
   `nav` values must match a nav id in DASHBOARD_ROLES[role].nav so the bell /
   account menu can deep-link straight to the relevant sub-page.
   --------------------------------------------------------------------------- */
const ROLE_NOTIFICATIONS = {
  customer: [
    { icon: "ticket",   title: "Your GA wristbands are ready", body: "2 wristbands confirmed for ARGT 2026. Tap to view and add to wallet.", time: "2h", unread: true, nav: "tickets" },
    { icon: "calendar", title: "Set times just dropped",       body: "The full weekend schedule is live. Build your lineup now.",            time: "1d", unread: true, nav: "schedule" },
    { icon: "plane",    title: "Travel partner rates end soon", body: "Discounted Fort Pierce hotel block closes June 1.",                   time: "3d", unread: false, nav: "travel" },
  ],
  fan: [
    { icon: "heart",    title: "Maya Cole moved up to #2",     body: "An artist you backed is climbing the leaderboard. +128 votes today.", time: "30m", unread: true, nav: "votes" },
    { icon: "trophy",   title: "You unlocked a $15 ticket credit", body: "3 Backer Votes cast. Your reward is waiting in Rewards.",          time: "5h", unread: true, nav: "rewards" },
    { icon: "headphones", title: "12 new auditions to discover", body: "Fresh submissions in genres you follow just went live.",            time: "1d", unread: false, nav: "discover" },
  ],
  creator: [
    { icon: "chart",    title: "You crossed 1,000 votes",      body: "1,284 votes received and rising. See your 30-day performance.",       time: "1h", unread: true, nav: "performance" },
    { icon: "file",     title: "Round 1 decision window opens", body: "Judges review May 28 to Jun 4. Make sure your application is final.", time: "6h", unread: true, nav: "status" },
    { icon: "briefcase", title: "New BKK Street Team opportunity", body: "A paid showcase slot matches your profile. Review and apply.",        time: "2d", unread: false, nav: "opportunities" },
  ],
  sponsor: [
    { icon: "upload",   title: "Asset deadline in 5 days",     body: "Upload your final logo pack and booth render by June 2.",             time: "3h", unread: true, nav: "assets" },
    { icon: "wallet",   title: "Invoice ready for activation", body: "Your Gold tier activation invoice is available to review.",           time: "1d", unread: true, nav: "payment" },
    { icon: "megaphone", title: "Activation plan approved",    body: "The ARGT team signed off on your sampling activation.",               time: "2d", unread: false, nav: "activation" },
  ],
  vendor: [
    { icon: "shield",   title: "Permit needs one more doc",    body: "Upload your COI to finish compliance before move-in.",                time: "4h", unread: true, nav: "compliance" },
    { icon: "store",    title: "Booth assignment confirmed",   body: "You're in the Vendor Village main row, space V-14.",                  time: "1d", unread: true, nav: "booth" },
    { icon: "package",  title: "Load-in window scheduled",     body: "Your move-in slot is Friday 7:00 to 9:00 AM.",                        time: "3d", unread: false, nav: "logistics" },
  ],
  crew: [
    { icon: "list",     title: "New assignment: Main Stage",   body: "You're slotted for Saturday stage changeover. Review the call sheet.", time: "2h", unread: true, nav: "assignments" },
    { icon: "id",       title: "Your credential is ready",     body: "All-access crew pass approved. Pick up at the production office.",     time: "1d", unread: true, nav: "credentials" },
    { icon: "calendar", title: "Confirm your availability",    body: "Add your blackout dates so we can finalize shifts.",                  time: "2d", unread: false, nav: "availability" },
  ],
  volunteer: [
    { icon: "calendar", title: "2 shifts still open near you", body: "Gate greeter and info booth shifts match your interests.",            time: "1h", unread: true, nav: "shifts" },
    { icon: "graduate", title: "1 training module left",       body: "Finish Safety Basics to unlock festival-day check-in.",               time: "1d", unread: true, nav: "training" },
    { icon: "qr",       title: "Your check-in QR is live",     body: "Show this at the volunteer tent on festival day.",                    time: "3d", unread: false, nav: "check-in" },
  ],
  veterans: [
    { icon: "ticket",   title: "Group ticket block confirmed", body: "Your reserved block of 40 is held. Assign names anytime.",            time: "5h", unread: true, nav: "group-tickets" },
    { icon: "building", title: "Pavilion layout shared",       body: "Review your Wellness Zone footprint and load-in plan.",               time: "1d", unread: true, nav: "pavilion" },
    { icon: "shield",   title: "Recognition moment scheduled", body: "Your org will be recognized Saturday at 6:00 PM, Main Stage.",         time: "4d", unread: false, nav: "recognition" },
  ],
  "local-business": [
    { icon: "qr",       title: "Your discount QR is live",     body: "Print it or add to your storefront. Scans start counting at launch.", time: "2h", unread: true, nav: "qr" },
    { icon: "image",    title: "Creative approved",            body: "Your storefront tile is approved and live in the wristband guide.",   time: "1d", unread: true, nav: "creative" },
    { icon: "chart",    title: "First scans are in",           body: "12 wristband scans so far this weekend. See live performance.",        time: "3d", unread: false, nav: "performance" },
  ],
  press: [
    { icon: "id",       title: "Credential approved",          body: "Your press pass is confirmed. Pickup details inside.",                time: "3h", unread: true, nav: "intake" },
    { icon: "chat",     title: "Interview slot offered",       body: "A 15-minute artist interview is available Saturday afternoon.",        time: "1d", unread: true, nav: "interviews" },
    { icon: "bulb",     title: "New story angle curated",      body: "The PR team added an angle on the Clean Earth Initiative.",           time: "2d", unread: false, nav: "angles" },
  ],
};

/* Per-role contextual quick links shown in the account menu, above the
   universal Switch role / Exit / Sign out actions. */
const ROLE_ACCOUNT_LINKS = {
  customer:        [{ label: "My tickets", nav: "tickets", icon: "ticket" }, { label: "Travel", nav: "travel", icon: "plane" }],
  fan:             [{ label: "My votes", nav: "votes", icon: "heart" }, { label: "Rewards", nav: "rewards", icon: "trophy" }, { label: "Vote credits", nav: "checkout", icon: "wallet" }, { label: "Notifications", nav: "notifications", icon: "bell" }, { label: "My submissions", nav: "my-submissions", icon: "file" }, { label: "Setup checklist", nav: "onboarding", icon: "star" }],
  creator:         [{ label: "Edit profile", nav: "profile", icon: "person" }, { label: "My application", nav: "status", icon: "file" }, { label: "Notifications", nav: "notifications", icon: "bell" }, { label: "My submissions", nav: "my-submissions", icon: "file" }, { label: "Setup checklist", nav: "onboarding", icon: "star" }],
  sponsor:         [{ label: "Account settings", nav: "settings", icon: "gear" }, { label: "Payment", nav: "payment", icon: "wallet" }],
  vendor:          [{ label: "Account settings", nav: "settings", icon: "gear" }, { label: "Payment", nav: "payment", icon: "wallet" }],
  crew:            [{ label: "Account settings", nav: "settings", icon: "gear" }, { label: "Credentials", nav: "credentials", icon: "id" }],
  volunteer:       [{ label: "Account settings", nav: "settings", icon: "gear" }, { label: "Check-in QR", nav: "check-in", icon: "qr" }],
  veterans:        [{ label: "Account settings", nav: "settings", icon: "gear" }, { label: "Resources", nav: "resources", icon: "list" }],
  "local-business": [{ label: "Account settings", nav: "settings", icon: "gear" }, { label: "Your QR code", nav: "qr", icon: "qr" }],
  press:           [{ label: "Account settings", nav: "settings", icon: "gear" }, { label: "Credential", nav: "intake", icon: "file" }],
};

/* ---------------------------------------------------------------------------
   PERSONAL-ACCESS MODEL — the "you" layer that every account carries on top of
   any business role. Two personal contexts:
     • Fan       — voting / rewards (auto-granted to every account)
     • Attendee  — the real admission ticket + wristband you scan to get in
   The Attendee context becomes active the moment a ticket is HELD on the
   account, by any route (bought, group-assigned, gifted, comp). In production
   this reads entitlements; here it's a mock with a demo control.
   --------------------------------------------------------------------------- */
const PERSONAL_ROLE_IDS = ["fan", "customer"];      // customer == Attendee
const BUSINESS_ROLE_IDS = ["creator", "sponsor", "vendor", "crew", "volunteer", "veterans", "local-business", "press"];
const TICKET_SOURCE_COPY = {
  bought: { tag: "Weekend pass",      line: "You bought a weekend pass." },
  group:  { tag: "Group assignment",  line: "Assigned to you from a group order." },
  gift:   { tag: "Gifted pass",       line: "A friend sent you a pass." },
  comp:   { tag: "Complimentary",     line: "Complimentary pass on your account." },
};
window.PERSONAL_ROLE_IDS = PERSONAL_ROLE_IDS;
window.BUSINESS_ROLE_IDS = BUSINESS_ROLE_IDS;
window.TICKET_SOURCE_COPY = TICKET_SOURCE_COPY;
window.isBusinessRole = (roleId) => BUSINESS_ROLE_IDS.includes(roleId);

/* ---------------------------------------------------------------------------
   CONTEXTUAL SUPPORT — drives the left-rail Support drawer. Each role gets its
   own AI greeting, suggested questions, FAQ set, and request categories so help
   is about THAT workspace. A general set is the fallback.
   --------------------------------------------------------------------------- */
const SUPPORT_CONTEXT = {
  customer: {
    greeting: "Hi! I'm the ARGT helper. Ask me about your tickets, wristband, schedule, parking, VIP, or group orders.",
    chips: ["Where is my wristband?", "Transfer a ticket", "Parking and directions", "Refund policy"],
    faqs: [
      { q: "When does my wristband arrive?", a: "Wristbands ship to your address about a week before the festival, around Sep 11. Track yours under Admission Ticket. If it hasn't arrived by Sep 14, open a request and we'll overnight a replacement." },
      { q: "How do I transfer or gift a ticket?", a: "Go to Admission Ticket, then Manage, then Transfer. Enter the recipient's email and they get their own QR. Transfers are free up to 24 hours before doors." },
      { q: "What is the refund policy?", a: "Tickets are refundable up to 30 days before the festival, minus the processing fee. After that they're transferable but not refundable." },
      { q: "Where do I park and how do I get in?", a: "Harbour Pointe is in Fort Pierce. Pre-buy parking under Travel for the closest lots, rideshare drop-off is at Gate B, and doors open at 1 PM." },
      { q: "How do I add VIP?", a: "Add VIP under VIP Add-ons. It includes the elevated viewing deck, premium restrooms, and a dedicated bar, available until it sells out." },
    ],
    categories: ["Tickets and wristband", "Schedule", "VIP and upgrades", "Group orders", "Travel and parking", "Something else"],
  },
  fan: {
    greeting: "Hey! I can help with voting, vote credits, rewards, and the leaderboard.",
    chips: ["How do votes work?", "Where are my credits?", "How do I earn rewards?", "What is Clean Earth?"],
    faqs: [
      { q: "How does voting work?", a: "Each Backer Vote is $10 and supports an artist's run at ARGT. Cast votes from Discover or any artist page. Your first vote is free." },
      { q: "Where are my vote credits?", a: "Your credit balance is on the Overview and in Buy Votes. Credits never expire during the active phase." },
      { q: "How do I earn ticket rewards?", a: "Every vote after your free one earns $5 off your festival ticket, up to $50. Track it under Rewards." },
      { q: "What is the Clean Earth Initiative?", a: "10% of every vote goes to the Clean Earth Initiative. Your personal share is shown on your Overview." },
    ],
    categories: ["Voting and credits", "Rewards", "Payments", "Account", "Something else"],
  },
  creator: {
    greeting: "Hi! I can help with your application, profile, performance, and BKK Street Team opportunities.",
    chips: ["When is my decision?", "Edit my profile", "How are votes counted?", "Apply to an opportunity"],
    faqs: [
      { q: "When will I hear back on my application?", a: "Round 1 decisions land between May 28 and Jun 4. You'll get a notification and your My Application status will update automatically." },
      { q: "How do I update my profile?", a: "Open Profile to edit your bio, photos, reel, and links. It goes live the moment you're accepted." },
      { q: "How are votes counted?", a: "Fan votes accrue on your public profile and feed the leaderboard. See the breakdown under Performance." },
      { q: "How do I apply to an opportunity?", a: "Open Opportunities and tap Apply on any matching slot. The ARGT team reviews and responds in your notifications." },
    ],
    categories: ["Application", "Profile", "Performance", "Opportunities", "Payments", "Something else"],
  },
  sponsor: {
    greeting: "Hi! I can help with your activation, asset specs, invoicing, hospitality passes, and recap.",
    chips: ["Asset deadlines and specs", "When is my invoice due?", "Hospitality passes", "Edit my activation"],
    faqs: [
      { q: "What are the asset deadlines and specs?", a: "Final logos and booth renders are due Jun 2. Specs and templates are under Assets. We'll flag anything that needs a higher-res version." },
      { q: "When is my invoice due and how do I pay?", a: "Your activation invoice is under Payment with terms. Pay by card or ACH, due net 30 from issue." },
      { q: "How do my hospitality passes work?", a: "Hospitality passes are part of your tier and are separate from a personal admission ticket. Assign them under Deliverables. To attend yourself, grab your own ticket in the Attendee dashboard." },
      { q: "Can I still change my activation plan?", a: "Yes, edit your plan under Activation until it's locked by the ARGT team. Big changes may affect your footprint." },
    ],
    categories: ["Activation", "Assets", "Payment and invoicing", "Hospitality", "Recap", "Something else"],
  },
  vendor: {
    greeting: "Hi! I can help with your booth, permits and compliance, load-in, and payment.",
    chips: ["My booth assignment", "What permits do I need?", "Load-in window", "Payment status"],
    faqs: [
      { q: "Where is my booth and when do I find out?", a: "Your space is confirmed under Booth once assignments lock. You'll get a notification with your row and number." },
      { q: "What compliance documents do I need?", a: "Upload your COI and any required permits under Compliance. Food vendors also need a health permit on file before move-in." },
      { q: "What is my load-in window?", a: "Move-in slots are scheduled under Logistics. Arrive within your window so we can stage the village smoothly." },
      { q: "How do I pay my vendor fee?", a: "Your fee and balance are under Payment. Pay by card or ACH to confirm your space." },
    ],
    categories: ["Booth", "Compliance and permits", "Logistics", "Payment", "Something else"],
  },
  crew: {
    greeting: "Hi! I can help with your assignments, credentials, call sheets, and hours.",
    chips: ["My next assignment", "Where is my credential?", "Update availability", "Log my hours"],
    faqs: [
      { q: "Where do I see my assignments?", a: "Your shifts and call sheets are under Assignments. Each one lists the stage, time, and lead." },
      { q: "How do I get my credential?", a: "Once approved, your crew pass is under Credentials. Pick it up at the production office on arrival." },
      { q: "How do I set my availability?", a: "Add your available dates and any blackouts under Availability so we can finalize your shifts." },
      { q: "How are my hours tracked and paid?", a: "Logged hours appear under your profile and feed payroll. Flag any discrepancy with a request." },
    ],
    categories: ["Assignments", "Credentials", "Availability", "Hours and pay", "Something else"],
  },
  volunteer: {
    greeting: "Hi! I can help with shifts, training, festival-day check-in, and perks.",
    chips: ["Open shifts near me", "Finish my training", "Where is my check-in QR?", "What perks do I get?"],
    faqs: [
      { q: "How do I sign up for shifts?", a: "Browse and claim shifts under Shifts. You can swap or drop up to 48 hours before with no penalty." },
      { q: "What training do I need?", a: "Required modules are under Training. Finish them to unlock festival-day check-in." },
      { q: "Where is my check-in QR?", a: "Your check-in QR is under Check-In. Show it at the volunteer tent on festival day." },
      { q: "What do volunteers get?", a: "Work the required shifts and you earn festival entry plus volunteer perks. Details are on your Overview." },
    ],
    categories: ["Shifts", "Training", "Check-in", "Perks", "Something else"],
  },
  veterans: {
    greeting: "Hi! I can help with your group tickets, the Wellness Zone pavilion, recognition, and resources.",
    chips: ["Manage group tickets", "Pavilion setup", "Recognition moment", "Resource kits"],
    faqs: [
      { q: "How do I assign my group tickets?", a: "Your reserved block is under Group Tickets. Assign names by email and each recipient gets a personal QR." },
      { q: "What is the pavilion setup?", a: "Your Wellness Zone footprint and load-in plan are under Pavilion. Reach out for layout changes." },
      { q: "When is our recognition moment?", a: "Recognition moments are scheduled under Recognition, typically on the Main Stage Saturday evening." },
      { q: "Where are the resource kits?", a: "Downloadable resource kits and signage are under Resources." },
    ],
    categories: ["Group tickets", "Pavilion", "Recognition", "Resources", "Something else"],
  },
  "local-business": {
    greeting: "Hi! I can help with your wristband discount, QR code, creative, and scan performance.",
    chips: ["Set up my discount", "Where is my QR code?", "Upload creative", "How do scans work?"],
    faqs: [
      { q: "How do I set up my discount?", a: "Define your offer, dates, and terms under Discount Setup. It goes live in the wristband guide once approved." },
      { q: "Where is my QR code?", a: "Your discount QR is under QR Code. Print it or display it at your storefront. Scans start counting at launch." },
      { q: "How do I upload my creative?", a: "Add your logo, hero image, and copy under Creative. We'll review and place it in the wristband guide." },
      { q: "How do I track redemptions?", a: "Live scans and redemptions show under Performance during festival weekend." },
    ],
    categories: ["Discount setup", "QR code", "Creative", "Performance", "Something else"],
  },
  press: {
    greeting: "Hi! I can help with your credential, press assets, interview slots, and story angles.",
    chips: ["Credential status", "Download press assets", "Request an interview", "Embargo and usage"],
    faqs: [
      { q: "What is my credential status?", a: "Your credential request and approval are under Credential Request. Pickup details arrive by notification once approved." },
      { q: "Where are the press assets?", a: "Approved logos, photos, and the press kit are under Assets, cleared for editorial use." },
      { q: "How do I request an interview?", a: "Open Interviews to see available slots and request one. The PR team confirms in your notifications." },
      { q: "What are the embargo and usage rules?", a: "Usage terms are noted on each asset. Embargoed materials are labeled with their lift date." },
    ],
    categories: ["Credential", "Assets", "Interviews", "Story angles", "Something else"],
  },
};
const SUPPORT_GENERAL = {
  greeting: "Hi! I'm the ARGT helper. Ask me anything about your account, and I can open a request to a human if you need one.",
  chips: ["Festival dates and venue", "Update my account", "Contact a human"],
  faqs: [
    { q: "When and where is ARGT 2026?", a: "ARGT 2026 is Sep 18 to 20 at Harbour Pointe in Fort Pierce." },
    { q: "How do I update my account?", a: "Open the account menu in the top right to manage your profile, personal access, and settings." },
  ],
  categories: ["Account", "Billing", "Technical", "Something else"],
};
window.argtSupportContext = (roleId) => SUPPORT_CONTEXT[roleId] || SUPPORT_GENERAL;
/* Demo support requests shown in the Requests tab. */
window.SUPPORT_REQUESTS_SEED = [
  { id: "REQ-4821", subject: "Question about my account", status: "Resolved", when: "Apr 22", last: "Thanks, that's all sorted." },
];

const ROLE_SEARCH_PLACEHOLDERS = {
  customer: "Search tickets, schedule, travel…",
  fan: "Search artists, shows, votes…",
  creator: "Search performances, applications, shows…",
  sponsor: "Search activations, deliverables, recap…",
  vendor: "Search booth setup, compliance, payment…",
  crew: "Search assignments, shifts, credentials…",
  volunteer: "Search shifts, training, check-in…",
  veterans: "Search group tickets, pavilion, resources…",
  "local-business": "Search discount, creative, performance…",
  press: "Search assets, interviews, angles…",
};
const FAN_TOPBAR_COPY = {
  searchPlaceholder: "Search artists, shows, votes…",
  voteCreditsLabel: "Vote credits",
  voteCreditsBuyMore: "Buy more",
  voteCreditsEmpty: "Get your first credits to vote.",
};
const COMMON_EMPTY_STATES = {
  noVotes:        { title: "No votes yet",            body: "Start voting on auditions to support the artists you want at ARGT 2026.", cta: { label: "Discover auditions" } },
  noSaved:        { title: "Nothing saved yet",       body: "Save artists, shows, and moments to come back to them.", cta: { label: "Browse artists" } },
  noTickets:      { title: "No tickets yet",          body: "When tickets go on sale, your purchases will show up here.", cta: { label: "Get ticket updates" } },
  noApplications: { title: "No applications submitted", body: "Apply to perform at ARGT 2026. Submissions stay editable until you submit.", cta: { label: "Start an application" } },
  noShows:        { title: "No shows scheduled",      body: "Once your festival application is approved and slotted, your set details show up here." },
  noNotifications:{ title: "You're all caught up",    body: "New activity on your account will appear here." },
};

/* ---------------------------------------------------------------------------
   ADMIN CONSOLE COPY (argt-console ui-copy.ts)
   --------------------------------------------------------------------------- */
const ADMIN_SIDEBAR = [
  { items: [{ id: "home", label: "Home", href: "/home" }] },
  { label: "Festival", items: [
    { id: "festival", label: "Festival settings", href: "/festival" },
    { id: "events", label: "Events", href: "/events" },
    { id: "venues", label: "Venues", href: "/venues" },
    { id: "auditions", label: "Auditions", href: "/auditions" },
    { id: "opportunities", label: "Opportunities", href: "/opportunities" },
    { id: "opportunity-applications", label: "Opportunity applications", href: "/opportunity-applications" },
    { id: "role-intakes", label: "Role intakes", href: "/role-intakes" },
    { id: "sponsors", label: "Sponsors", href: "/sponsors" },
    { id: "products", label: "Products", href: "/products" },
  ]},
  { label: "Launch", items: [
    { id: "launch-engines", label: "Launch engines", href: "/launch/engines" },
    { id: "launch-tasks", label: "Tasks", href: "/launch/tasks" },
    { id: "launch-checklists", label: "Checklists", href: "/launch/checklists" },
    { id: "launch-comms", label: "Comms calendar", href: "/launch/comms-calendar" },
    { id: "launch-files", label: "Files", href: "/launch/files" },
    { id: "launch-incidents", label: "Incidents", href: "/launch/incidents" },
    { id: "launch-budget", label: "Budget", href: "/launch/budget" },
    { id: "launch-team", label: "Team", href: "/launch/team" },
  ]},
  { label: "Users + community", items: [
    { id: "users", label: "Users", href: "/users" },
    { id: "members", label: "Members", href: "/members" },
    { id: "creators", label: "Creators", href: "/creators" },
    { id: "creator-types", label: "Creator types", href: "/creator-types" },
    { id: "genres", label: "Genres", href: "/genres" },
    { id: "fraud", label: "Fraud review", href: "/fraud" },
    { id: "account-deletions", label: "Account deletions", href: "/account-deletions" },
  ]},
  { label: "Voting + gamification", items: [
    { id: "votes", label: "Votes", href: "/votes" },
    { id: "voting-phases", label: "Voting phases", href: "/voting-phases" },
    { id: "vote-credits-admin", label: "Vote credits", href: "/vote-credits-admin" },
    { id: "vote-credit-purchases", label: "Vote credit purchases", href: "/vote-credit-purchases" },
    { id: "bulk-grant-credits", label: "Bulk grant credits", href: "/bulk-grant-credits" },
    { id: "gamification-badges", label: "Badges", href: "/gamification-badges" },
    { id: "gamification-events", label: "Gamification events", href: "/gamification-events" },
  ]},
  { label: "Campaigns + referrals", items: [
    { id: "campaigns", label: "Campaigns", href: "/campaigns" },
    { id: "announcements", label: "Announcements", href: "/announcements" },
    { id: "promo-codes", label: "Promo codes", href: "/promo-codes" },
    { id: "referrals", label: "Referrals", href: "/referrals" },
    { id: "referral-codes", label: "Referral codes", href: "/referral-codes" },
  ]},
  { label: "Operations", items: [
    { id: "analytics", label: "Analytics", href: "/analytics" },
    { id: "dashboard-user-data", label: "Dashboard user data", href: "/dashboard-user-data" },
    { id: "stripe-charges", label: "Stripe charges", href: "/stripe-charges" },
    { id: "email-log", label: "Email log", href: "/email-log" },
    { id: "email-preview", label: "Email templates", href: "/email-preview" },
    { id: "alert-outbox", label: "Alert outbox", href: "/alert-outbox" },
    { id: "ai-logs", label: "AI logs", href: "/ai-logs" },
    { id: "media", label: "Media library", href: "/media" },
  ]},
  { label: "Settings", items: [
    { id: "settings", label: "Settings", href: "/settings" },
  ]},
];

const ADMIN_TOPBAR = {
  searchPlaceholder: "Search users, artists, applications, tasks…",
  notificationsLabel: "Notifications",
  notificationsEmpty: "Nothing to review.",
  messagesLabel: "Messages",
  messagesEmpty: "Your inbox is clear.",
  accountMenu: { profile: "Profile", settings: "Settings", impersonate: "End impersonation", logout: "Sign out" },
};

const STATUS_BADGES = {
  pending:     { label: "Pending",      tone: "warning" },
  underReview: { label: "Under review", tone: "info" },
  approved:    { label: "Approved",     tone: "success" },
  rejected:    { label: "Rejected",     tone: "destructive" },
  withdrawn:   { label: "Withdrawn",    tone: "neutral" },
  paid:        { label: "Paid",         tone: "success" },
  refunded:    { label: "Refunded",     tone: "neutral" },
  disputed:    { label: "Disputed",     tone: "warning" },
  failed:      { label: "Failed",       tone: "destructive" },
  draft:       { label: "Draft",        tone: "neutral" },
  published:   { label: "Published",    tone: "success" },
  archived:    { label: "Archived",     tone: "neutral" },
};

/* ---------------------------------------------------------------------------
   LAUNCH CC CONSTANTS (from artifacts/argt-console/src/lib/launch/constants.ts)
   --------------------------------------------------------------------------- */
const HARDCODED_TEAM = [
  { id: 1, name: "Kam",    role: "Festival Director",   color: "#C49800" },
  { id: 2, name: "Jordan", role: "Marketing Lead",      color: "#B5006A" },
  { id: 3, name: "Taylor", role: "Sponsor Coordinator", color: "#007A8A" },
  { id: 4, name: "Morgan", role: "Production Manager",  color: "#4C1285" },
  { id: 5, name: "Casey",  role: "Artist Relations",    color: "#00D9B8" },
];
const FESTIVAL_DATE = new Date("2026-09-18T18:00:00-04:00");
const VENUE_NAME = "Harbour Pointe";
const VENUE_CITY = "Fort Pierce";
function computeCurrentWeek() {
  const msLeft = FESTIVAL_DATE.getTime() - Date.now();
  return Math.max(0, Math.ceil(msLeft / (86_400_000 * 7)));
}

/* ===========================================================================
   COMPONENTS
   =========================================================================== */

/* Inline SVG icon set — small, no dependency on Phosphor */
const ICON_PATHS = {
  home: <path d="M3 11l9-8 9 8v9a2 2 0 0 1-2 2h-4v-6h-6v6H5a2 2 0 0 1-2-2v-9z" stroke="currentColor" strokeWidth="1.6" fill="none" strokeLinejoin="round"/>,
  person: <><circle cx="12" cy="8" r="4" stroke="currentColor" strokeWidth="1.6" fill="none"/><path d="M4 21c0-4 4-6 8-6s8 2 8 6" stroke="currentColor" strokeWidth="1.6" fill="none"/></>,
  bell: <path d="M6 9a6 6 0 1 1 12 0c0 5 2 6 2 6H4s2-1 2-6zm3 10a3 3 0 0 0 6 0" stroke="currentColor" strokeWidth="1.6" fill="none" strokeLinejoin="round"/>,
  search: <><circle cx="11" cy="11" r="6" stroke="currentColor" strokeWidth="1.6" fill="none"/><path d="m20 20-4-4" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"/></>,
  bag: <path d="M5 8h14l-1 12a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 8zm3 0a4 4 0 1 1 8 0" stroke="currentColor" strokeWidth="1.6" fill="none" strokeLinejoin="round"/>,
  heart: <path d="M12 21s-7-4.35-9-9a5 5 0 0 1 9-4 5 5 0 0 1 9 4c-2 4.65-9 9-9 9z" stroke="currentColor" strokeWidth="1.6" fill="none" strokeLinejoin="round"/>,
  mic: <><rect x="9" y="3" width="6" height="12" rx="3" stroke="currentColor" strokeWidth="1.6" fill="none"/><path d="M5 11a7 7 0 0 0 14 0M12 18v3" stroke="currentColor" strokeWidth="1.6" fill="none" strokeLinecap="round"/></>,
  logout: <><path d="M9 4H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h3" stroke="currentColor" strokeWidth="1.6" fill="none" strokeLinejoin="round"/><path d="M16 17l5-5-5-5M21 12H10" stroke="currentColor" strokeWidth="1.6" fill="none" strokeLinecap="round" strokeLinejoin="round"/></>,
  calendar: <><rect x="4" y="6" width="16" height="15" rx="2" stroke="currentColor" strokeWidth="1.6" fill="none"/><path d="M4 11h16M8 3v4M16 3v4" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"/></>,
  ticket: <path d="M3 9a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v2a2 2 0 0 0 0 4v2a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-2a2 2 0 0 0 0-4V9zm5-1v12" stroke="currentColor" strokeWidth="1.6" fill="none" strokeLinejoin="round"/>,
  map: <path d="M3 6l6-2 6 2 6-2v14l-6 2-6-2-6 2V6zM9 4v14m6-12v14" stroke="currentColor" strokeWidth="1.6" fill="none" strokeLinejoin="round"/>,
  crown: <path d="M3 8l3 9h12l3-9-5 3-4-6-4 6-5-3z" stroke="currentColor" strokeWidth="1.6" fill="none" strokeLinejoin="round"/>,
  users: <><circle cx="9" cy="9" r="3" stroke="currentColor" strokeWidth="1.6" fill="none"/><circle cx="17" cy="10" r="2.5" stroke="currentColor" strokeWidth="1.6" fill="none"/><path d="M3 20c0-3 3-5 6-5s6 2 6 5M14 19c0-2 2-3.5 4-3.5s4 1.5 4 3.5" stroke="currentColor" strokeWidth="1.6" fill="none"/></>,
  plane: <path d="M21 12l-9 9-1-7-7-1 9-9 2 4 6 4z" stroke="currentColor" strokeWidth="1.6" fill="none" strokeLinejoin="round"/>,
  file: <><path d="M14 3H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z" stroke="currentColor" strokeWidth="1.6" fill="none"/><path d="M14 3v6h6M8 13h8M8 17h6" stroke="currentColor" strokeWidth="1.6" fill="none"/></>,
  chart: <path d="M4 20V8m6 12V4m6 16v-9m6 9V12" stroke="currentColor" strokeWidth="1.6" fill="none" strokeLinecap="round"/>,
  briefcase: <><rect x="3" y="7" width="18" height="14" rx="2" stroke="currentColor" strokeWidth="1.6" fill="none"/><path d="M9 7V5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2" stroke="currentColor" strokeWidth="1.6" fill="none"/></>,
  headphones: <path d="M3 14v-2a9 9 0 0 1 18 0v2M3 14v3a2 2 0 0 0 2 2h2v-7H5a2 2 0 0 0-2 2zm18 0v3a2 2 0 0 1-2 2h-2v-7h2a2 2 0 0 1 2 2z" stroke="currentColor" strokeWidth="1.6" fill="none" strokeLinejoin="round"/>,
  star: <path d="M12 3l3 6 6 1-4.5 4 1 6L12 17l-5.5 3 1-6L3 10l6-1z" stroke="currentColor" strokeWidth="1.6" fill="none" strokeLinejoin="round"/>,
  share: <><circle cx="18" cy="5" r="3" stroke="currentColor" strokeWidth="1.6" fill="none"/><circle cx="6" cy="12" r="3" stroke="currentColor" strokeWidth="1.6" fill="none"/><circle cx="18" cy="19" r="3" stroke="currentColor" strokeWidth="1.6" fill="none"/><path d="M8.5 10.5l7-4M8.5 13.5l7 4" stroke="currentColor" strokeWidth="1.6"/></>,
  wallet: <path d="M3 8a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v2h-4a2 2 0 0 0 0 4h4v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8zm14 3h2" stroke="currentColor" strokeWidth="1.6" fill="none" strokeLinejoin="round"/>,
  megaphone: <path d="M3 11v2a2 2 0 0 0 2 2h1l3 5h2l-2-5h4l6 3V6l-6 3H6a2 2 0 0 0-2 2zM18 9v6" stroke="currentColor" strokeWidth="1.6" fill="none" strokeLinejoin="round"/>,
  upload: <path d="M12 4v12m0-12l-4 4m4-4l4 4M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2" stroke="currentColor" strokeWidth="1.6" fill="none" strokeLinecap="round" strokeLinejoin="round"/>,
  shield: <path d="M12 3l8 3v6c0 4-3 7-8 9-5-2-8-5-8-9V6l8-3z" stroke="currentColor" strokeWidth="1.6" fill="none" strokeLinejoin="round"/>,
  qr: <><rect x="3" y="3" width="7" height="7" stroke="currentColor" strokeWidth="1.6" fill="none"/><rect x="14" y="3" width="7" height="7" stroke="currentColor" strokeWidth="1.6" fill="none"/><rect x="3" y="14" width="7" height="7" stroke="currentColor" strokeWidth="1.6" fill="none"/><rect x="16" y="16" width="5" height="5" stroke="currentColor" strokeWidth="1.6" fill="none"/></>,
  gear: <><circle cx="12" cy="12" r="3" stroke="currentColor" strokeWidth="1.6" fill="none"/><path d="M12 3v2m0 14v2m9-9h-2M5 12H3m14.5-6.5l-1.5 1.5M7.5 16.5L6 18m12 0l-1.5-1.5M7.5 7.5L6 6" stroke="currentColor" strokeWidth="1.6"/></>,
  store: <path d="M3 8l1-4h16l1 4M3 8h18M3 8v12a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1V8M9 21v-6h6v6" stroke="currentColor" strokeWidth="1.6" fill="none" strokeLinejoin="round"/>,
  package: <path d="M3 7l9-4 9 4m-18 0l9 4 9-4M3 7v10l9 4 9-4V7M12 11v10" stroke="currentColor" strokeWidth="1.6" fill="none" strokeLinejoin="round"/>,
  id: <><rect x="3" y="5" width="18" height="14" rx="2" stroke="currentColor" strokeWidth="1.6" fill="none"/><circle cx="9" cy="11" r="2" stroke="currentColor" strokeWidth="1.6" fill="none"/><path d="M14 9h4M14 13h3M6 17h6" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"/></>,
  trophy: <path d="M7 4h10v4a5 5 0 0 1-10 0V4zM7 6H4a2 2 0 0 0 2 4h1m10-4h3a2 2 0 0 1-2 4h-1M10 14h4m-3 0v4m-3 0h6" stroke="currentColor" strokeWidth="1.6" fill="none" strokeLinejoin="round"/>,
  building: <path d="M3 21h18M5 21V5l7-2v18M12 21V8h7v13M9 8h.01M9 11h.01M9 14h.01M9 17h.01M15 11h.01M15 14h.01M15 17h.01" stroke="currentColor" strokeWidth="1.6" fill="none" strokeLinecap="round" strokeLinejoin="round"/>,
  image: <><rect x="3" y="4" width="18" height="16" rx="2" stroke="currentColor" strokeWidth="1.6" fill="none"/><circle cx="9" cy="9" r="2" stroke="currentColor" strokeWidth="1.6" fill="none"/><path d="M21 16l-5-5L7 20" stroke="currentColor" strokeWidth="1.6" fill="none" strokeLinejoin="round"/></>,
  list: <path d="M8 6h13M8 12h13M8 18h13M4 6h.01M4 12h.01M4 18h.01" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"/>,
  graduate: <path d="M22 10L12 5 2 10l10 5 10-5zM6 12v5a6 6 0 0 0 12 0v-5M22 10v6" stroke="currentColor" strokeWidth="1.6" fill="none" strokeLinejoin="round"/>,
  chat: <path d="M21 12a8 8 0 0 1-11.5 7.2L4 21l1.8-5.5A8 8 0 1 1 21 12z" stroke="currentColor" strokeWidth="1.6" fill="none" strokeLinejoin="round"/>,
  bulb: <path d="M9 18h6m-5 3h4M12 3a6 6 0 0 0-4 10.5c.7.7 1 1.5 1 2.5v1h6v-1c0-1 .3-1.8 1-2.5A6 6 0 0 0 12 3z" stroke="currentColor" strokeWidth="1.6" fill="none" strokeLinejoin="round"/>,
};
const Icon = ({ name, size = 22 }) => (
  <svg width={size} height={size} viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">{ICON_PATHS[name] || ICON_PATHS.home}</svg>
);

/* Atom components */
const Pill = ({ children, active, dark, dot, lg, onClick }) => (
  <span
    onClick={onClick}
    className={"pill" + (active ? " active" : "") + (dark ? " dark" : "") + (lg ? " pill-lg" : "")}
  >
    {dot && <span className="dot" />}
    {children}
  </span>
);

const Badge = ({ kind, children }) => (
  <span className={"badge " + (kind || "pending")}>{children}</span>
);

const Journey = ({ steps, currentIndex }) => (
  <div className="journey">
    {steps.map((s, i) => (
      <div key={i} className={"step " + (i < currentIndex ? "done" : i === currentIndex ? "current" : "")}>
        <div className="idx">{String(i + 1).padStart(2, "0")}</div>
        <div className="label">{s.label}</div>
        <div className="desc">{s.desc}</div>
      </div>
    ))}
  </div>
);

const StatTriad = ({ cells }) => (
  <div className="stat-triad">
    {cells.map((c, i) => (
      <div key={i} className="stat-cell">
        <div className="num">{c.num}</div>
        <div className="lab">{c.lab}</div>
      </div>
    ))}
  </div>
);

const Tile = ({ glyph, icon, name, desc }) => (
  <div className="tile">
    <div className="ico">{icon ? <Icon name={icon} size={22} /> : glyph}</div>
    <div className="name">{name}</div>
    <div className="desc">{desc}</div>
    <div className="arr">→</div>
  </div>
);

/* Render an icon-name as <Icon> if known, else fall back to whatever was passed (text/Unicode/React element). */
const renderIco = (val, size = 20) => {
  if (typeof val === "string" && ICON_PATHS && Object.prototype.hasOwnProperty.call(ICON_PATHS, val)) {
    return <Icon name={val} size={size} />;
  }
  return val;
};

const Kpi = ({ label, num, delta, deltaDir, glyph, onClick }) => (
  <div className={"kpi" + (onClick ? " kpi--link" : "")} onClick={onClick} role={onClick ? "button" : undefined} tabIndex={onClick ? 0 : undefined}
       onKeyDown={onClick ? (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onClick(e); } } : undefined}>
    {glyph && <div className="glyph">{renderIco(glyph, 16)}</div>}
    <div className="label">{label}</div>
    <div className="num">{num}</div>
    {delta && <div className={"delta " + (deltaDir || "up")}>{delta}</div>}
    {onClick && <span className="kpi-go" aria-hidden="true">↗</span>}
  </div>
);

const ListRow = ({ title, sub, right }) => (
  <div className="list-row">
    <div className="avatar"></div>
    <div className="main-text">
      <div className="t1">{title}</div>
      <div className="t2">{sub}</div>
    </div>
    {right && <div className="right-text">{right}</div>}
  </div>
);

/* Icon rail — uses role nav data; renders pill rail visual */
const IconRail = ({ items, activeId, withBadgeOn = "bell" }) => (
  <div className="icon-rail">
    {items.map((it) => {
      const iconName = it.icon || "home";
      return (
        <div key={it.id} className={"rail-item" + (it.id === activeId ? " active" : "")} title={it.label}>
          <Icon name={iconName} />
          {it.id === "bell" || iconName === withBadgeOn ? <span className="badge-dot"/> : null}
        </div>
      );
    })}
    <div className="rail-divider"></div>
    <div className="rail-item"><Icon name="logout" /></div>
    <div className="rail-avatar"></div>
  </div>
);

/* Top bar — search pill + notifications + account.
   The brand block doubles as a ROLE SWITCHER: tap to see all 10 user
   dashboards + Admin + Launcher. Helps move across surfaces quickly. */
const TopBar = ({ placeholder, roleLabel, currentRoleId, navItems, onNavigate, heldTicket }) => {
  const [menu, setMenu] = React.useState(null); // null | "brand" | "notif" | "account"
  const open = menu === "brand";
  const baseNotifs = (window.argtAggregatedNotifs ? window.argtAggregatedNotifs(currentRoleId) : (window.ROLE_NOTIFICATIONS[currentRoleId] || []));
  const [readMap, setReadMap] = React.useState({});
  const navIds = (navItems || []).map((n) => n.id);
  const unreadCount = baseNotifs.filter((n, i) => n.unread && !readMap[i]).length;

  // Close any open menu on outside click
  React.useEffect(() => {
    if (!menu) return;
    const onDoc = (e) => {
      if (!e.target.closest(".topbar-menu")) setMenu(null);
    };
    const onKey = (e) => { if (e.key === "Escape") setMenu(null); };
    document.addEventListener("mousedown", onDoc);
    document.addEventListener("keydown", onKey);
    return () => { document.removeEventListener("mousedown", onDoc); document.removeEventListener("keydown", onKey); };
  }, [menu]);

  // Deep-link to a sub-page if it exists for this role, then close the menu.
  const go = (navId) => {
    setMenu(null);
    if (navId && onNavigate && navIds.includes(navId)) onNavigate(navId);
    else if (navId && onNavigate) onNavigate("overview");
  };
  const markAllRead = () => {
    const m = {};
    baseNotifs.forEach((_, i) => { m[i] = true; });
    setReadMap(m);
  };
  const signOut = () => { try { localStorage.removeItem("argt-mock-user"); } catch (e) {} window.location.href = "../login.html"; };

  const userRoles = ["customer", "fan", "creator", "sponsor", "vendor", "crew", "volunteer", "veterans", "local-business", "press"];
  const accentHex = (window.ROLE_ACCENTS[(window.DASHBOARD_ROLES[currentRoleId] || {}).accent] || {}).hex || "var(--pvt-gold)";
  const accountLinks = window.ROLE_ACCOUNT_LINKS[currentRoleId] || [{ label: "Account settings", nav: "settings", icon: "gear" }];

  return (
  <div className="shell-topbar">
    <div className="brand brand-switcher topbar-menu" style={{ position: "relative", cursor: "pointer", userSelect: "none" }} onClick={() => setMenu(open ? null : "brand")}>
      <span className="wm">ARGT</span>
      <span className="sub">{roleLabel ? `${roleLabel} dashboard` : "Dashboard"}</span>
      <span style={{ marginLeft: 4, color: "var(--pvt-text-dim)", fontSize: 11, transform: open ? "rotate(180deg)" : "none", transition: "transform 160ms" }}>▾</span>
      {open && (
        <div style={{
          position: "absolute", top: "calc(100% + 8px)", left: 0,
          background: "var(--pvt-surface-raised)",
          border: "1px solid var(--pvt-border-light)",
          borderRadius: "var(--r-lg)",
          padding: 8,
          minWidth: 280,
          boxShadow: "0 24px 60px rgba(0,0,0,0.5)",
          zIndex: 100,
          maxHeight: 480,
          overflowY: "auto",
        }}>
          <window.HatSwitcherMenu currentRoleId={currentRoleId} onAddRole={() => { setMenu(null); window.dispatchEvent(new Event("argt:add-role")); }} />
        </div>
      )}
    </div>
    <div className="top-search" onClick={() => window.dispatchEvent(new Event("argt:command-palette"))} style={{ cursor: "text" }}>
      <div className="pill-search-ico"><Icon name="search" size={14}/></div>
      <input placeholder={placeholder} readOnly style={{ cursor: "text" }} onFocus={() => window.dispatchEvent(new Event("argt:command-palette"))} />
      <span style={{ color: "var(--pvt-text-dim)", fontSize: 11, padding: "0 6px", border: "1px solid var(--pvt-border-light)", borderRadius: 6 }}>⌘K</span>
    </div>
    <div className="actions">
      {/* Notifications */}
      <div className="topbar-menu" style={{ position: "relative" }}>
        <div className="btn-icon" style={{ cursor: "pointer" }} onClick={() => setMenu(menu === "notif" ? null : "notif")} title="Notifications" role="button" aria-label="Notifications">
          <Icon name="bell" size={20}/>
          {unreadCount > 0 && <span className="badge-dot"/>}
        </div>
        {menu === "notif" && (
          <div style={{
            position: "absolute", top: "calc(100% + 10px)", right: 0,
            background: "var(--pvt-surface-raised)", border: "1px solid var(--pvt-border-light)",
            borderRadius: "var(--r-lg)", width: 340, maxWidth: "calc(100vw - 32px)",
            boxShadow: "0 24px 60px rgba(0,0,0,0.5)", zIndex: 100, overflow: "hidden",
          }}>
            <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "14px 16px 10px" }}>
              <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                <span style={{ fontFamily: "var(--pvt-font-display)", fontSize: 14, color: "var(--pvt-text)" }}>Notifications</span>
                {unreadCount > 0 && <span style={{ fontSize: 10, fontWeight: 800, letterSpacing: "0.04em", padding: "2px 7px", borderRadius: 999, background: accentHex, color: "#0c0a18" }}>{unreadCount} new</span>}
              </div>
              {unreadCount > 0 && <button onClick={(e) => { e.stopPropagation(); markAllRead(); }} style={{ background: "none", border: "none", color: "var(--pvt-text-dim)", fontSize: 11, cursor: "pointer", padding: 0 }}>Mark all read</button>}
            </div>
            <div style={{ maxHeight: 380, overflowY: "auto" }}>
              {baseNotifs.length === 0 ? (
                <div style={{ padding: "28px 16px", textAlign: "center", color: "var(--pvt-text-dim)", fontSize: 13 }}>You're all caught up.</div>
              ) : baseNotifs.map((n, i) => {
                const isUnread = n.unread && !readMap[i];
                const ac = n.accentHex || accentHex;
                const handle = () => { if (n.cross) { window.location.href = n.href + (n.nav ? "#" + n.nav : ""); } else { go(n.nav); } };
                return (
                  <div key={i} onClick={handle}
                       style={{ display: "flex", gap: 12, padding: "12px 16px", cursor: "pointer", borderTop: "1px solid var(--pvt-border-light)", background: isUnread ? "var(--pvt-surface-mid)" : "transparent", transition: "background 120ms" }}
                       onMouseEnter={(e) => e.currentTarget.style.background = "var(--pvt-surface-mid)"}
                       onMouseLeave={(e) => e.currentTarget.style.background = isUnread ? "var(--pvt-surface-mid)" : "transparent"}>
                    <span style={{ width: 34, height: 34, flexShrink: 0, borderRadius: 10, display: "grid", placeItems: "center", background: `color-mix(in srgb, ${ac} 18%, transparent)`, color: ac }}>
                      <Icon name={n.icon} size={17}/>
                    </span>
                    <div style={{ minWidth: 0, flex: 1 }}>
                      <div style={{ display: "flex", alignItems: "baseline", gap: 6 }}>
                        <span style={{ fontSize: 13, fontWeight: 700, color: "var(--pvt-text)", lineHeight: 1.3, flex: 1 }}>{n.title}</span>
                        <span style={{ fontSize: 11, color: "var(--pvt-text-dim)", flexShrink: 0 }}>{n.time}</span>
                      </div>
                      <p style={{ margin: "3px 0 0", fontSize: 12, color: "var(--pvt-muted)", lineHeight: 1.4 }}>{n.body}</p>
                      {n.cross && <span style={{ display: "inline-block", marginTop: 5, fontSize: 10, fontWeight: 800, textTransform: "uppercase", letterSpacing: "0.05em", color: ac }}>{n.roleLabel} ↗</span>}
                    </div>
                    {isUnread && <span style={{ width: 7, height: 7, borderRadius: "50%", background: ac, flexShrink: 0, marginTop: 6 }}/>}
                  </div>
                );
              })}
            </div>
            <div onClick={() => { setMenu(null); window.dispatchEvent(new Event("argt:activity")); }} style={{ padding: "11px 16px", textAlign: "center", fontSize: 12, fontWeight: 700, color: "var(--pvt-text-dim)", cursor: "pointer", borderTop: "1px solid var(--pvt-border-light)" }}
                 onMouseEnter={(e) => e.currentTarget.style.color = "var(--pvt-text)"}
                 onMouseLeave={(e) => e.currentTarget.style.color = "var(--pvt-text-dim)"}>
              View all activity
            </div>
          </div>
        )}
      </div>

      {/* Account */}
      <div className="topbar-menu" style={{ position: "relative" }}>
        <div className="btn-icon primary" style={{ cursor: "pointer" }} onClick={() => setMenu(menu === "account" ? null : "account")} title="Account" role="button" aria-label="Account">
          <Icon name="person" size={20}/>
        </div>
        {menu === "account" && (
          <div style={{
            position: "absolute", top: "calc(100% + 10px)", right: 0,
            background: "var(--pvt-surface-raised)", border: "1px solid var(--pvt-border-light)",
            borderRadius: "var(--r-lg)", width: 280, maxWidth: "calc(100vw - 32px)",
            boxShadow: "0 24px 60px rgba(0,0,0,0.5)", zIndex: 100, padding: 8,
          }}>
            <div style={{ display: "flex", alignItems: "center", gap: 12, padding: "8px 10px 12px" }}>
              <span style={{ width: 40, height: 40, borderRadius: "50%", flexShrink: 0, display: "grid", placeItems: "center", fontFamily: "var(--pvt-font-display)", fontSize: 15, fontWeight: 700, color: "#0c0a18", background: `linear-gradient(135deg, ${accentHex}, var(--accent-purple))` }}>DU</span>
              <div style={{ minWidth: 0 }}>
                <div style={{ fontFamily: "var(--pvt-font-display)", fontSize: 14, color: "var(--pvt-text)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>Demo User</div>
                <div style={{ fontSize: 11, color: "var(--pvt-text-dim)" }}>{roleLabel} account</div>
              </div>
            </div>
            <div style={{ height: 1, background: "var(--pvt-border-light)", margin: "0 2px 8px" }}/>
            {/* Personal access — the "you" layer present on every account */}
            <div style={{ padding: "0 10px 4px", fontSize: 10, color: "var(--pvt-text-dim)", textTransform: "uppercase", letterSpacing: "0.12em", fontWeight: 700 }}>Your personal access</div>
            {currentRoleId !== "customer" && (
              <a href="attendee.html"
                 style={{ display: "flex", alignItems: "center", gap: 12, padding: "9px 10px", borderRadius: "var(--r-md)", textDecoration: "none", color: "var(--pvt-muted)" }}
                 onMouseEnter={(e) => e.currentTarget.style.background = "var(--pvt-surface-mid)"}
                 onMouseLeave={(e) => e.currentTarget.style.background = "transparent"}>
                <span style={{ color: "var(--pvt-text-dim)", display: "grid", placeItems: "center" }}><Icon name="ticket" size={16}/></span>
                <span style={{ flex: 1, minWidth: 0 }}>
                  <span style={{ fontSize: 13, display: "block" }}>Admission ticket &amp; wristband</span>
                  <span style={{ fontSize: 11, color: "var(--pvt-text-dim)" }}>{heldTicket && heldTicket !== "none" ? (window.TICKET_SOURCE_COPY[heldTicket] || {}).tag + " · Attendee" : "Not attending yet · get a ticket"}</span>
                </span>
              </a>
            )}
            {currentRoleId !== "fan" && (
              <a href="fan.html"
                 style={{ display: "flex", alignItems: "center", gap: 12, padding: "9px 10px", borderRadius: "var(--r-md)", textDecoration: "none", color: "var(--pvt-muted)" }}
                 onMouseEnter={(e) => e.currentTarget.style.background = "var(--pvt-surface-mid)"}
                 onMouseLeave={(e) => e.currentTarget.style.background = "transparent"}>
                <span style={{ color: "var(--pvt-text-dim)", display: "grid", placeItems: "center" }}><Icon name="heart" size={16}/></span>
                <span style={{ fontSize: 13, flex: 1 }}>My votes &amp; rewards</span>
                <span style={{ fontSize: 11, color: "var(--pvt-text-dim)" }}>Fan</span>
              </a>
            )}
            <div style={{ height: 1, background: "var(--pvt-border-light)", margin: "8px 2px" }}/>
            <div style={{ padding: "0 10px 4px", fontSize: 10, color: "var(--pvt-text-dim)", textTransform: "uppercase", letterSpacing: "0.12em", fontWeight: 700 }}>{roleLabel} workspace</div>
            {accountLinks.map((l) => (
              <div key={l.label} onClick={() => go(l.nav)}
                   style={{ display: "flex", alignItems: "center", gap: 12, padding: "9px 10px", borderRadius: "var(--r-md)", cursor: "pointer", color: "var(--pvt-muted)" }}
                   onMouseEnter={(e) => e.currentTarget.style.background = "var(--pvt-surface-mid)"}
                   onMouseLeave={(e) => e.currentTarget.style.background = "transparent"}>
                <span style={{ color: "var(--pvt-text-dim)", display: "grid", placeItems: "center" }}><Icon name={l.icon} size={16}/></span>
                <span style={{ fontSize: 13, flex: 1 }}>{l.label}</span>
              </div>
            ))}
            <div style={{ height: 1, background: "var(--pvt-border-light)", margin: "8px 2px" }}/>
            <a href="index.html"
               style={{ display: "flex", alignItems: "center", gap: 12, padding: "9px 10px", borderRadius: "var(--r-md)", textDecoration: "none", color: "var(--pvt-muted)" }}
               onMouseEnter={(e) => e.currentTarget.style.background = "var(--pvt-surface-mid)"}
               onMouseLeave={(e) => e.currentTarget.style.background = "transparent"}>
              <span style={{ color: "var(--pvt-text-dim)", display: "grid", placeItems: "center" }}><Icon name="users" size={16}/></span>
              <span style={{ fontSize: 13, flex: 1 }}>Switch role</span>
            </a>
            <a href="../homepage.html"
               style={{ display: "flex", alignItems: "center", gap: 12, padding: "9px 10px", borderRadius: "var(--r-md)", textDecoration: "none", color: "var(--pvt-muted)" }}
               onMouseEnter={(e) => e.currentTarget.style.background = "var(--pvt-surface-mid)"}
               onMouseLeave={(e) => e.currentTarget.style.background = "transparent"}>
              <span style={{ color: "var(--pvt-text-dim)", display: "grid", placeItems: "center" }}><Icon name="home" size={16}/></span>
              <span style={{ fontSize: 13, flex: 1 }}>Exit to main site</span>
            </a>
            <div onClick={signOut}
                 style={{ display: "flex", alignItems: "center", gap: 12, padding: "9px 10px", borderRadius: "var(--r-md)", cursor: "pointer", color: "#F25BA7" }}
                 onMouseEnter={(e) => e.currentTarget.style.background = "color-mix(in srgb, #F25BA7 12%, transparent)"}
                 onMouseLeave={(e) => e.currentTarget.style.background = "transparent"}>
              <span style={{ display: "grid", placeItems: "center" }}><Icon name="logout" size={16}/></span>
              <span style={{ fontSize: 13, flex: 1, fontWeight: 700 }}>Sign out</span>
            </div>
          </div>
        )}
      </div>
    </div>
  </div>
  );
};

/* HeroPanel — concave + punch-through. Title/intro/cta are role-state copy. */
const HeroPanel = ({ eyebrow, title, intro, cta, ctaNav, statCells }) => {
  const navTo = (nav) => { if (nav) window.dispatchEvent(new CustomEvent("argt:navigate", { detail: { nav } })); };
  return (
    <div className="hero-panel">
      <span className="eyebrow">{eyebrow}</span>
      <h2 className="title">{title}</h2>
      <p className="intro">{intro}</p>
      {cta && <button className="cta" onClick={() => navTo(ctaNav)}>{cta} <span style={{ marginLeft: 6 }}>↗</span></button>}
      {statCells && <div className="corner-stats"><StatTriad cells={statCells} /></div>}
      <button className="punch" aria-label="Primary action" onClick={() => navTo(ctaNav)}>→</button>
    </div>
  );
};

/* SideCard — used widely */
const SideCard = ({ icon, title, action, children }) => (
  <div className="side-card">
    <div className="card-head">
      <div className="ic">{renderIco(icon || "star", 14)}</div>
      <h4>{title}</h4>
      {action && <span className="head-action">{action}</span>}
    </div>
    {children}
  </div>
);

/* ROLE OVERVIEW SURFACE — composes the whole RoleOverviewPage.tsx-equivalent layout
   given a config + state + role identity. Used by all 8 of the generic-renderer roles
   (sponsor through press + customer). */
const RoleOverviewSurface = ({ roleId, state, sideCards, kpiCards }) => {
  const config = ROLE_OVERVIEW_CONFIGS[roleId];
  const role = DASHBOARD_ROLES[roleId];
  const accent = ROLE_ACCENTS[role?.accent || "magenta"];
  const placeholder = ROLE_SEARCH_PLACEHOLDERS[roleId] || "Search…";

  const stateMap = {
    "pre-intake":  { title: config.preIntakeTitle, intro: config.preIntakeIntro, cta: config.ctaPre, currentIdx: 0 },
    "under-review":{ title: config.reviewTitle,    intro: config.reviewIntro,    cta: config.ctaReview, currentIdx: 1 },
    "accepted":    { title: config.acceptedTitle,  intro: config.acceptedIntro,  cta: config.ctaAccepted, currentIdx: 2 },
  };
  const s = stateMap[state] || stateMap.accepted;

  // Build sidebar nav from role (or a generic 6-item fallback)
  const navItems = (role && role.nav) || [
    { id: "overview", label: "Overview", icon: "home" },
    { id: "intake", label: "Application", icon: "file" },
    { id: "calendar", label: "Schedule", icon: "calendar" },
    { id: "bag", label: "Items", icon: "bag" },
    { id: "heart", label: "Saved", icon: "heart" },
  ];

  return (
    <div className="shell" style={{
      "--role-accent": accent.hex,
      "--role-accent-bright": accent.bright,
    }}>
      <div className="shell-inner">
        <TopBar placeholder={placeholder} roleLabel={config.roleLabel} currentRoleId={roleId} />
        <div style={{ alignSelf: "start" }}>
          <IconRail items={navItems} activeId="overview" />
        </div>
        <div className="shell-body">
          <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
            {(sideCards || []).map((sc, i) => (
              <SideCard key={i} icon={sc.icon} title={sc.title} action={sc.action}>
                <div className="stat-rows">
                  {sc.rows.map((r, j) => (
                    <div key={j} className="stat-row">
                      <span className="lab">{r.lab}</span>
                      <span className="num">{r.num}</span>
                    </div>
                  ))}
                </div>
              </SideCard>
            ))}
          </div>
          <div style={{ display: "flex", flexDirection: "column", gap: 22 }}>
            <HeroPanel
              eyebrow={config.eyebrow}
              title={s.title}
              intro={s.intro}
              cta={s.cta}
              statCells={kpiCards}
            />
            <Journey steps={config.journeySteps} currentIndex={s.currentIdx} />
            <div className="tile-grid">
              {config.tiles.map((t, i) => (
                <Tile key={i} glyph={t.glyph} name={t.label} desc={t.desc} />
              ))}
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

/* ===========================================================================
   EXPORTS
   =========================================================================== */
Object.assign(window, {
  // data
  ROLE_ACCENTS, DASHBOARD_ROLES, ROLE_OVERVIEW_CONFIGS,
  SHELL_COPY, ROLE_SEARCH_PLACEHOLDERS, FAN_TOPBAR_COPY, COMMON_EMPTY_STATES,
  ADMIN_SIDEBAR, ADMIN_TOPBAR, STATUS_BADGES,
  HARDCODED_TEAM, FESTIVAL_DATE, VENUE_NAME, VENUE_CITY, computeCurrentWeek,
  // components
  Icon, Pill, Badge, Journey, StatTriad, Tile, Kpi, ListRow,
  IconRail, TopBar, HeroPanel, SideCard, RoleOverviewSurface,
});
