// onboarding-data.jsx
// Atlas onboarding flow, discovery questionnaire across 7 sections.
// Section 0 (Contact) is added on top of the user's 6-section spec to
// capture gym name, website + person filling out the form.
//
// Each step has:
//   id            unique key
//   sectionId     which section it belongs to
//   eyebrow       small caps label shown above the title
//   title         the question
//   helper        one-line guidance
//   kind          'text' | 'longtext' | 'cards' | 'chips' | 'roster'
//   optional      true → "Skip" button, advance allowed without answer
//   note          appears as a small hint under input area
//
// Field-driven kinds:
//   text      → fields[] of {key, label, placeholder}
//   longtext  → field {key, placeholder, rows}
//   cards     → fieldKey + options[] of {value, label, detail}
//   chips     → fieldKey + options[] (strings); multi-select; allowOther
//   roster    → fieldKey (array of {name, role}); add/remove rows

// Common gym staff roles, used as the dropdown for the team roster.
// Users can also type a custom role.
const ATLAS_ROLES = [
  'Owner / Founder',
  'General Manager',
  'Head Coach',
  'Coach',
  'Personal Trainer',
  'Success Coach',
  'Front Desk',
  'Membership Sales',
  'Marketing',
  'Operations / Admin',
  'Nutrition Coach',
];

const ATLAS_SECTIONS = [
  { id: 'contact',    label: 'Contact',                 short: 'Contact' },
  { id: 'business',   label: '1. Business Overview',   short: 'Business' },
  { id: 'staff',      label: '2. Staff & Roles',       short: 'Staff' },
  { id: 'journey',    label: '3. Client Journey',      short: 'Journey' },
  { id: 'mgmt',       label: '4. Accountability',      short: 'Accountability' },
  { id: 'reporting',  label: '5. Reporting',           short: 'Reporting' },
  { id: 'priorities', label: '6. Build Priorities',    short: 'Priorities' },
];

const ATLAS_STEPS = [
  // ── Welcome ──────────────────────────────────────────
  {
    id: 'welcome',
    sectionId: 'contact',
    eyebrow: 'Welcome',
    title: 'Welcome to Atlas.',
    helper: 'A short discovery so we can personalize Atlas to your gym.',
    kind: 'welcome',
  },
  // ── Contact ──────────────────────────────────────────
  {
    id: 'contact',
    sectionId: 'contact',
    eyebrow: 'Contact, Your gym',
    title: 'Tell us who we\u2019re working with.',
    kind: 'text',
    fields: [
      { key: 'gymName',     label: 'Gym name',           placeholder: 'e.g. Iron District Athletic Club', autoFocus: true },
      { key: 'website',     label: 'Website',            placeholder: 'irondistrict.com', optional: true },
      { key: 'contactEmail',label: 'Your email',         placeholder: 'you@gym.com', type: 'email' },
    ],
  },

  // ── 1. Business Overview ─────────────────────────────
  {
    id: 'memberCount',
    sectionId: 'business',
    eyebrow: 'Business, Members',
    title: 'How many active members do you have?',
    helper: 'Rough is fine. Pick a band. We\u2019ll calibrate from there.',
    kind: 'cards',
    fieldKey: 'memberCount',
    options: [
      { value: '<50',      label: 'Under 50',       detail: 'Studio / specialty' },
      { value: '<100',     label: 'Under 100',      detail: 'Boutique gym' },
      { value: '<200',     label: 'Under 200',      detail: 'Small independent' },
      { value: '200-500',  label: '200-500',        detail: 'Independent gym' },
      { value: '500-1k',   label: '500-1,000',      detail: 'Mid-size box' },
      { value: '5k+',      label: '5,000 +',        detail: 'Enterprise volume' },
      { value: 'unknown',  label: 'Don\u2019t know yet', detail: 'We\u2019ll help you pull this' },
    ],
  },
  {
    id: 'tiers',
    sectionId: 'business',
    eyebrow: 'Business, Memberships',
    title: 'What are your membership tiers, and what does each include?',
    helper: 'List each tier with a short description. One per line is fine.',
    kind: 'longtext',
    field: { key: 'tiers', placeholder: 'e.g.\nFoundations: group classes, 8/mo\nUnlimited: all classes + open gym\nPersonal: 4x 1:1 + unlimited group', rows: 6 },
  },
  {
    id: 'software',
    sectionId: 'business',
    eyebrow: 'Business, Stack',
    title: 'What software do you currently use?',
    helper: 'CRM, coaching, scheduling, billing. Pick everything in play.',
    kind: 'chips',
    fieldKey: 'software',
    multi: true,
    allowOther: true,
    options: [
      'Mindbody', 'Zen Planner', 'Wodify', 'Glofox',
      'Mariana Tek', 'ClubReady', 'ABC Fitness',
      'Trainerize', 'TrueCoach', 'Hubspot',
      'Stripe', 'Square', 'GoHighLevel',
      'Google Sheets / Excel', 'Pen & paper',
    ],
  },
  {
    id: 'metrics',
    sectionId: 'business',
    eyebrow: 'Business, Numbers',
    title: 'Do you have a defined ARPM, length-of-engagement, or LTV?',
    helper: 'Average Revenue Per Member, Length of Engagement, Lifetime Value.',
    kind: 'cards',
    fieldKey: 'metricsDefined',
    options: [
      { value: 'all',   label: 'Yes, all three',  detail: 'Tracked and trusted' },
      { value: 'some',  label: 'Some of them',     detail: 'One or two' },
      { value: 'none',  label: 'None defined',     detail: 'No formal numbers yet' },
      { value: 'unsure',label: 'Not sure',         detail: 'Need help defining' },
    ],
  },
  {
    id: 'successMonth',
    sectionId: 'business',
    eyebrow: 'Business, Success',
    title: 'What does a "successful month" look like right now?',
    helper: 'Revenue, member growth, retention. Whatever you measure success by.',
    kind: 'longtext',
    field: { key: 'successMonth', placeholder: 'e.g. 15 new members, < 3% churn, $X revenue, no staff fires...', rows: 4 },
  },

  // ── 2. Staff & Roles ─────────────────────────────────
  {
    id: 'roster',
    sectionId: 'staff',
    eyebrow: 'Staff, Your team',
    title: 'List every staff role and the person in it.',
    helper: 'Add as many as you need. Coach, front desk, success coach, owner.',
    kind: 'roster',
    fieldKey: 'roster',
  },
  {
    id: 'checkinsOwner',
    sectionId: 'staff',
    eyebrow: 'Staff, Check-ins',
    title: 'Who owns client check-ins and follow-ups?',
    helper: 'Pick from your team above. The people on the hook for making sure these happen.',
    kind: 'rosterPick',
    fieldKey: 'checkinsOwner',
    sourceKey: 'roster',
    multi: true,
  },
  {
    id: 'onboardingOwner',
    sectionId: 'staff',
    eyebrow: 'Staff, Onboarding',
    title: 'Who handles new member onboarding?',
    helper: 'First touchpoints, intake, first-month milestones.',
    kind: 'rosterPick',
    fieldKey: 'onboardingOwner',
    sourceKey: 'roster',
    multi: true,
  },
  {
    id: 'escalationOwner',
    sectionId: 'staff',
    eyebrow: 'Staff, Escalations',
    title: 'Where do escalations go when a task isn\u2019t completed?',
    helper: 'Usually the owner. Pick whoever should be the final backstop.',
    kind: 'rosterPick',
    fieldKey: 'escalationOwner',
    sourceKey: 'roster',
    multi: true,
  },
  {
    id: 'staffSharing',
    sectionId: 'staff',
    eyebrow: 'Staff, Coverage',
    title: 'Are any staff members part-time or shared across roles?',
    helper: 'Helps us model who actually has bandwidth for what.',
    kind: 'cards',
    fieldKey: 'staffSharing',
    options: [
      { value: 'yes-many',   label: 'Yes, many people wear multiple hats', detail: 'Common in small operations' },
      { value: 'yes-some',   label: 'Yes, a few do',                       detail: '1-2 people stretched' },
      { value: 'no',         label: 'No, everyone has one role',           detail: 'Clean role split' },
      { value: 'unsure',     label: 'Not sure',                             detail: 'We can sort it' },
    ],
  },

  // ── 3. Client Journey ────────────────────────────────
  {
    id: 'journey90',
    sectionId: 'journey',
    eyebrow: 'Journey, First 90 days',
    title: 'Walk us through a member\u2019s first 90 days.',
    helper: 'Free-write whatever stages matter. Use the prompts below as a guide, skip any that don\u2019t apply.',
    kind: 'longtext',
    field: {
      key: 'journey90',
      placeholder: 'Day 1: e.g. Welcome email + intro call booked\nWeek 1: e.g. Foundations class, intake form\nMonth 1: e.g. Coach check-in, attendance review\nMonth 3: e.g. Goal review, testimonial ask',
      rows: 8,
    },
  },
  {
    id: 'inconsistent',
    sectionId: 'journey',
    eyebrow: 'Journey, Gaps',
    title: 'What touchpoints SHOULD be happening but currently aren\u2019t consistent?',
    helper: 'Pick everything that slips. Add anything else under "Other".',
    kind: 'chips',
    fieldKey: 'inconsistent',
    multi: true,
    allowOther: true,
    options: [
      'Birthday messages',
      'Membership anniversary',
      'Weekly / monthly check-ins',
      'Goal reviews',
      'Re-engagement after no-show',
      'Win-back after cancel',
      'Referral asks',
      'Testimonial / review asks',
      'Milestone celebrations',
      'Welcome / onboarding follow-up',
    ],
    optional: true,
  },
  {
    id: 'dropoff',
    sectionId: 'journey',
    eyebrow: 'Journey, Drop-off',
    title: 'Where do members tend to disengage or cancel?',
    helper: 'Pick all that apply. Best guess if you don\u2019t have hard data.',
    kind: 'chips',
    fieldKey: 'dropoff',
    multi: true,
    options: [
      'First 30 days',
      'First 3 months',
      '3-6 months',
      '6-12 months',
      'After 1 year',
      'Don\u2019t know',
    ],
  },
  {
    id: 'milestones',
    sectionId: 'journey',
    eyebrow: 'Journey, Milestones',
    title: 'Do you track member milestones?',
    helper: 'Pick everything you currently track. We\u2019ll ask "how" next.',
    kind: 'chips',
    fieldKey: 'milestones',
    multi: true,
    allowOther: true,
    options: [
      'First workout', 'Weight-loss goals', 'PRs', 'Anniversaries',
      'Birthdays', 'Attendance streaks', 'Referrals',
      'We don\u2019t track these',
    ],
  },
  {
    id: 'milestonesHow',
    sectionId: 'journey',
    eyebrow: 'Journey, Milestones',
    title: 'How do you track them today?',
    helper: 'Software, spreadsheet, memory, sticky notes. Be honest.',
    kind: 'longtext',
    field: { key: 'milestonesHow', placeholder: 'e.g. Mindbody notes, a Google sheet, in our heads', rows: 3 },
    optional: true,
  },

  // ── 4. Employee Management & Accountability ──────────
  {
    id: 'tasksDropping',
    sectionId: 'mgmt',
    eyebrow: 'Accountability, Cracks',
    title: 'Which recurring staff tasks fall through most often?',
    helper: 'Pick everything that frustrates you.',
    kind: 'chips',
    fieldKey: 'tasksDropping',
    multi: true,
    allowOther: true,
    options: [
      'Member check-ins', 'Follow-ups', 'Birthday / anniversary messages',
      'At-risk outreach', 'Renewals & retention',
      'Cancellation handling', 'Onboarding steps',
      'Staff scheduling', 'Cleaning / facility', 'Reporting',
    ],
  },
  {
    id: 'accountabilityHow',
    sectionId: 'mgmt',
    eyebrow: 'Accountability, System',
    title: 'How do you currently hold staff accountable for client-facing tasks?',
    helper: 'Pick whichever is closest to reality.',
    kind: 'cards',
    fieldKey: 'accountabilityHow',
    options: [
      { value: 'software',  label: 'Software with task tracking',     detail: 'CRM / task tool / dashboards' },
      { value: 'manual',    label: 'Manual checks & meetings',        detail: 'I review work weekly' },
      { value: 'honor',     label: 'Honor system',                    detail: 'I trust they\u2019re getting done' },
      { value: 'none',      label: 'No real system',                  detail: 'This is the problem' },
    ],
  },
  {
    id: 'escalationWindow',
    sectionId: 'mgmt',
    eyebrow: 'Accountability, Escalation',
    title: 'How long should a task sit before it flags to you?',
    helper: 'Default escalation window. You can fine-tune per task later.',
    kind: 'cards',
    fieldKey: 'escalationWindow',
    options: [
      { value: '24h',    label: '24 hours',  detail: 'Tight ship' },
      { value: '48h',    label: '48 hours',  detail: 'Standard' },
      { value: '72h',    label: '72 hours',  detail: 'Loose' },
      { value: '1w',     label: '1 week',    detail: 'Slow-moving items' },
      { value: 'varies', label: 'Varies by task', detail: 'We\u2019ll set it per task' },
    ],
  },
  {
    id: 'sops',
    sectionId: 'mgmt',
    eyebrow: 'Accountability, SOPs',
    title: 'Do you have SOPs documented?',
    helper: 'Standard Operating Procedures for staff tasks.',
    kind: 'cards',
    fieldKey: 'sops',
    options: [
      { value: 'all',    label: 'Yes, all documented',  detail: 'We have a playbook' },
      { value: 'some',   label: 'Some are',              detail: 'Partial coverage' },
      { value: 'none',   label: 'None yet',              detail: 'Need to be built' },
      { value: 'help',   label: 'Need help building them', detail: 'Atlas can help' },
    ],
  },
  {
    id: 'comms',
    sectionId: 'mgmt',
    eyebrow: 'Accountability, Comms',
    title: 'How does your team communicate day-to-day?',
    helper: 'Pick everything in active use.',
    kind: 'chips',
    fieldKey: 'comms',
    multi: true,
    allowOther: true,
    options: [
      'Slack', 'Microsoft Teams', 'iMessage / SMS group',
      'WhatsApp', 'Email', 'In-person', 'Phone calls',
    ],
  },

  // ── 5. Reporting & Decision-Making ───────────────────
  {
    id: 'weeklyNumbers',
    sectionId: 'reporting',
    eyebrow: 'Reporting, Weekly',
    title: 'What numbers do you look at every week?',
    helper: 'The metrics you actually open the dashboard for.',
    kind: 'chips',
    fieldKey: 'weeklyNumbers',
    multi: true,
    allowOther: true,
    options: [
      'Revenue', 'New members', 'Cancellations / churn',
      'Attendance', 'Retention', 'Lead conversion',
      'NPS / satisfaction', 'Coach performance',
      'Class utilization', 'Cash flow',
    ],
  },
  {
    id: 'cadence',
    sectionId: 'reporting',
    eyebrow: 'Reporting, Cadence',
    title: 'What\u2019s your reporting cadence?',
    helper: 'How often do you actually pull and review reports?',
    kind: 'cards',
    fieldKey: 'cadence',
    options: [
      { value: 'daily',   label: 'Daily',     detail: 'Every morning' },
      { value: 'weekly',  label: 'Weekly',    detail: 'Standing review' },
      { value: 'monthly', label: 'Monthly',   detail: 'End-of-month wrap' },
      { value: 'adhoc',   label: 'Ad-hoc',    detail: 'When something feels off' },
      { value: 'none',    label: 'No cadence',detail: 'We don\u2019t have one yet' },
    ],
  },
  {
    id: 'reportsPulled',
    sectionId: 'reporting',
    eyebrow: 'Reporting, What you pull',
    title: 'Which reports do you currently pull?',
    helper: 'Quick list. We\u2019ll match these to Atlas dashboards.',
    kind: 'longtext',
    field: { key: 'reportsPulled', placeholder: 'e.g. monthly revenue by tier, churn list, lead funnel...', rows: 4 },
    optional: true,
  },
  {
    id: 'wishMetrics',
    sectionId: 'reporting',
    eyebrow: 'Reporting, Wishlist',
    title: 'Which metrics do you wish you had visibility into?',
    helper: 'Things you\u2019ve always wanted to see but couldn\u2019t.',
    kind: 'chips',
    fieldKey: 'wishMetrics',
    multi: true,
    allowOther: true,
    options: [
      'Utilization by membership tier', 'ROI by coach',
      'At-risk score per member', 'LTV by tier',
      'Lead → member conversion by source', 'Attendance trends',
      'Coach 1:1 effectiveness', 'Referral velocity',
    ],
  },

  // ── 6. Build Priorities ──────────────────────────────
  {
    id: 'oneProblem',
    sectionId: 'priorities',
    eyebrow: 'Priorities, The one thing',
    title: 'If Atlas could only solve ONE problem right now, what would it be?',
    helper: 'Pick the closest. We\u2019ll dig deeper next.',
    kind: 'cards',
    fieldKey: 'oneProblem',
    options: [
      { value: 'retention',     label: 'Member retention',       detail: 'Keep more of who we have' },
      { value: 'accountability',label: 'Staff accountability',   detail: 'Make sure tasks actually happen' },
      { value: 'visibility',    label: 'Visibility into numbers',detail: 'Know what\u2019s working' },
      { value: 'onboarding',    label: 'Onboarding consistency', detail: 'Better first 90 days' },
      { value: 'comms',         label: 'Member communication',   detail: 'Right message, right time' },
      { value: 'other',         label: 'Something else',         detail: 'We\u2019ll ask in the next step' },
    ],
  },
  {
    id: 'painful',
    sectionId: 'priorities',
    eyebrow: 'Priorities, What\u2019s broken',
    title: 'What\u2019s broken or painful right now that you want fixed first?',
    kind: 'longtext',
    field: { key: 'painful', placeholder: 'The thing that keeps you up. The fire you\u2019re always putting out.', rows: 6 },
  },
];

// Pre-compute step indices grouped by section
const ATLAS_SECTION_INDICES = ATLAS_SECTIONS.map((sec) => ({
  ...sec,
  stepIndices: ATLAS_STEPS.map((s, i) => s.sectionId === sec.id ? i : -1).filter((i) => i >= 0),
}));

// ─────────────────────────────────────────────────────────────
// useOnboarding, small state hook each variation uses.
// Holds answers, current step index, completion flag.
// ─────────────────────────────────────────────────────────────
function emptyAnswers() {
  const base = {};
  for (const s of ATLAS_STEPS) {
    if (s.kind === 'text') {
      for (const f of s.fields) base[f.key] = '';
    } else if (s.kind === 'longtext') {
      base[s.field.key] = '';
    } else if (s.kind === 'cards') {
      base[s.fieldKey] = '';
    } else if (s.kind === 'chips') {
      base[s.fieldKey] = [];
      base[s.fieldKey + '_other'] = '';
    } else if (s.kind === 'roster') {
      base[s.fieldKey] = [{ name: '', role: '' }];
    } else if (s.kind === 'rosterPick') {
      base[s.fieldKey] = [];
    } else if (s.kind === 'stages') {
      base[s.fieldKey] = {};
      for (const st of s.stages) base[s.fieldKey][st.key] = '';
    }
  }
  return base;
}

function useOnboarding(initial = {}) {
  const [answers, setAnswers] = React.useState({ ...emptyAnswers(), ...initial });
  const [stepIdx, setStepIdx] = React.useState(0);
  const [done, setDone] = React.useState(false);
  const total = ATLAS_STEPS.length;
  const step = ATLAS_STEPS[stepIdx];

  const setField = React.useCallback((key, value) => {
    setAnswers((a) => ({ ...a, [key]: value }));
  }, []);

  const toggleMulti = React.useCallback((key, value) => {
    setAnswers((a) => {
      const cur = a[key] || [];
      return {
        ...a,
        [key]: cur.includes(value) ? cur.filter((v) => v !== value) : [...cur, value],
      };
    });
  }, []);

  const setRosterRow = React.useCallback((key, idx, patch) => {
    setAnswers((a) => {
      const cur = (a[key] || []).slice();
      cur[idx] = { ...cur[idx], ...patch };
      // Auto-grow: if this is the last row and it now has both name + role,
      // append a fresh empty row so the user doesn't have to click "Add another".
      const isLast = idx === cur.length - 1;
      const row = cur[idx];
      const hasName = (row.name || '').trim().length > 0;
      const hasRole = (row.role || '').trim().length > 0;
      if (isLast && hasName && hasRole) {
        cur.push({ name: '', role: '' });
      }
      return { ...a, [key]: cur };
    });
  }, []);

  const addRosterRow = React.useCallback((key) => {
    setAnswers((a) => ({ ...a, [key]: [...(a[key] || []), { name: '', role: '' }] }));
  }, []);

  const removeRosterRow = React.useCallback((key, idx) => {
    setAnswers((a) => {
      const cur = (a[key] || []).slice();
      cur.splice(idx, 1);
      return { ...a, [key]: cur.length ? cur : [{ name: '', role: '' }] };
    });
  }, []);

  const canAdvance = React.useMemo(() => {
    if (!step) return false;
    if (step.kind === 'welcome') return true;
    if (step.optional) return true;
    if (step.kind === 'text') {
      // require all non-optional fields
      return step.fields.every((f) => f.optional || (answers[f.key] || '').trim().length > 0);
    }
    if (step.kind === 'longtext') return (answers[step.field.key] || '').trim().length > 0;
    if (step.kind === 'cards') return !!answers[step.fieldKey];
    if (step.kind === 'chips') return (answers[step.fieldKey] || []).length > 0;
    if (step.kind === 'roster') {
      const rows = answers[step.fieldKey] || [];
      return rows.some((r) => (r.name || '').trim() && (r.role || '').trim());
    }
    if (step.kind === 'rosterPick') {
      return (answers[step.fieldKey] || []).length > 0;
    }
    if (step.kind === 'stages') {
      const v = answers[step.fieldKey] || {};
      return step.stages.some((st) => (v[st.key] || '').trim().length > 0);
    }
    return true;
  }, [step, answers]);

  const next = React.useCallback(() => {
    if (!canAdvance) return;
    if (stepIdx >= total - 1) { setDone(true); return; }
    setStepIdx((i) => Math.min(total - 1, i + 1));
  }, [canAdvance, stepIdx, total]);

  const prev = React.useCallback(() => {
    if (done) { setDone(false); return; }
    setStepIdx((i) => Math.max(0, i - 1));
  }, [done]);

  const reset = React.useCallback(() => {
    setStepIdx(0); setDone(false);
    setAnswers(emptyAnswers());
  }, []);

  const sectionOf = React.useCallback((idx) => {
    const id = ATLAS_STEPS[idx]?.sectionId;
    return ATLAS_SECTIONS.find((s) => s.id === id);
  }, []);

  // Submit to Supabase when the user finishes.
  const [submitState, setSubmitState] = React.useState('idle');
  const submittedRef = React.useRef(false);
  React.useEffect(() => {
    if (!done) {
      submittedRef.current = false;
      setSubmitState('idle');
      return;
    }
    if (submittedRef.current) return;
    submittedRef.current = true;
    setSubmitState('sending');
    const SUPABASE_URL = 'https://hbqyjydxpasoogfgnoie.supabase.co';
    const SUPABASE_KEY = 'sb_publishable_zyCuiIpEwa7ahiOz4hfR7g__HZmzEJh';
    const client = new URLSearchParams(window.location.search).get('c') || null;
    fetch(SUPABASE_URL + '/rest/v1/responses', {
      method: 'POST',
      headers: {
        'apikey': SUPABASE_KEY,
        'Authorization': 'Bearer ' + SUPABASE_KEY,
        'Content-Type': 'application/json',
        'Prefer': 'return=minimal',
      },
      body: JSON.stringify({ client: client, answers: answers }),
    }).then((r) => {
      if (r.ok) { setSubmitState('sent'); }
      else { setSubmitState('error'); submittedRef.current = false;
        r.text().then((t) => console.error('Atlas submit failed:', r.status, t)); }
    }).catch((err) => {
      setSubmitState('error'); submittedRef.current = false;
      console.error('Atlas submit error:', err);
    });
  }, [done, answers]);

  return {
    answers, setField, toggleMulti,
    setRosterRow, addRosterRow, removeRosterRow,
    stepIdx, step, total, done,
    next, prev, reset, canAdvance,
    setStepIdx, sectionOf,
    submitState,
  };
}

// Tiny confetti emitter, pure DOM, scoped to a container.
function fireConfetti(container, accent = '#14b8a6') {
  if (!container) return;
  const colors = [accent, '#ffffff', '#0a0a0b', accent];
  const N = 70;
  for (let i = 0; i < N; i++) {
    const el = document.createElement('div');
    const size = 6 + Math.random() * 8;
    el.style.cssText = `
      position:absolute;left:50%;top:40%;width:${size}px;height:${size * 0.4}px;
      background:${colors[i % colors.length]};border-radius:1px;
      pointer-events:none;z-index:50;opacity:1;
      transform:translate(-50%,-50%) rotate(${Math.random() * 360}deg);
    `;
    container.appendChild(el);
    const dx = (Math.random() - 0.5) * 360;
    const dy = -120 - Math.random() * 220;
    const rot = (Math.random() - 0.5) * 720;
    const dur = 1100 + Math.random() * 900;
    el.animate(
      [
        { transform: `translate(-50%,-50%) rotate(0deg)`, opacity: 1 },
        { transform: `translate(calc(-50% + ${dx}px), calc(-50% + ${dy}px)) rotate(${rot}deg)`, offset: 0.4, opacity: 1 },
        { transform: `translate(calc(-50% + ${dx * 1.3}px), calc(-50% + ${dy + 320}px)) rotate(${rot * 1.2}deg)`, opacity: 0 },
      ],
      { duration: dur, easing: 'cubic-bezier(.2,.7,.2,1)', fill: 'forwards' }
    );
    setTimeout(() => el.remove(), dur + 100);
  }
}

Object.assign(window, {
  ATLAS_STEPS, ATLAS_SECTIONS, ATLAS_SECTION_INDICES, ATLAS_ROLES,
  useOnboarding, fireConfetti,
});
