/* global window */
// Data: client types, services with real tier×commitment pricing, FAQ
// Source: official GoGorilla pricing sheet (provided by client 2026-04-24)

const CLIENT_TYPES = [
  {
    id: 'founder',
    subBrand: 'Founders',
    heading: 'For Founders',
    desc: "I'm a founder or business leader",
    cardTip: "For founders and business leaders who want managed growth on fixed, transparent monthly pricing with built-in profit sharing.",
    icon: 'assets/icons/client-founders.webp',
    logo: 'assets/logos/gogorilla-founders.webp',
    perks: ['Accountability discount', 'Flexible 3-month commits', 'Free growth audit'],
    quickBullets: [], // ICP ticks removed per Alexander (Loom 14, 00:12), 2026-06-02
    valueStrip: [
      { icon: '✦', title: 'Transparent pricing', desc: 'Fixed monthly retainers. No surprise invoices.' },
      { icon: '◆', title: 'Accountability discount', desc: 'Bundle 2 or more services and save up to 10%.' },
      { icon: '◇', title: 'No-lock pilots', desc: 'Try us on a 3-month commit before scaling up.' },
      { icon: '✧', title: 'Capital-efficient', desc: 'Built-in profit sharing aligns us with your runway.' },
    ],
    intents: [
      {
        id: 'founder-standard',
        title: 'I prefer standard pricing',
        desc: 'Transparent, fixed pricing with built-in Algorithmic Team Profit Sharing. A fully accountable team focused on your success, without complex pricing models.',
        icon: 'assets/icons/transparency.webp',
        info: 'Most predictable. Best for teams that want clear monthly costs.',
      },
      {
        id: 'founder-tailored',
        title: 'I need a tailored solution',
        desc: 'For founders who need a custom approach, want standalone services, or are unsure where to start. We prepare a proposal based on your specific needs.',
        icon: 'assets/icons/partnership.webp',
        info: 'Bespoke scope. Best for unique or complex situations.',
      },
    ],
  },
  {
    id: 'investor',
    subBrand: 'Investors',
    heading: 'For Investors',
    desc: "I'm an angel, venture capital, or private equity investor",
    cardTip: "For angel, venture capital, and private equity investors who want marketing diligence on deals, portfolio discounts, and recurring referral commission.",
    icon: 'assets/icons/client-ventures.webp',
    logo: 'assets/logos/gogorilla-ventures.webp',
    perks: ['Portfolio-wide rates', 'Dedicated portfolio CSM', 'Diligence-ready reporting'],
    quickBullets: [], // ICP ticks removed per Alexander (Loom 14, 00:12), 2026-06-02
    valueStrip: [
      { icon: '✦', title: 'Free DD assessments', desc: 'Repeatable marketing diligence for every deal.' },
      { icon: '◆', title: 'Portfolio discounts', desc: 'Discounted rates across the companies you back.' },
      { icon: '◇', title: '10% recurring commission', desc: 'On every portfolio company you refer.' },
      { icon: '✧', title: 'Diligence-ready data', desc: 'Reporting that drops straight into your diligence notes.' },
    ],
    intents: [
      {
        id: 'investor-considering',
        title: "I'm considering an investment",
        desc: 'Get a complimentary marketing assessment to strengthen your due diligence.',
        icon: 'assets/icons/calculator.webp',
        info: 'Pre-investment diligence. Free, no obligation.',
        // 2026-06-08 (Loom 30 4:21): 15% investor discount applies on any
        // services selected here, the same as the other service-led intents.
        discount: 0.15,
      },
      {
        id: 'investor-portfolio',
        title: 'I want to grow my portfolio company',
        desc: 'Get a tailored proposal to accelerate growth for one portfolio company.',
        icon: 'assets/icons/seo.webp',
        info: 'One portfolio company at a time, with a tailored proposal.',
        tip: 'You engage one portfolio company at a time. Build a tailored proposal for this company now, then repeat the process for your other companies from your admin panel.',
        // 2026-06-08 (Loom 30 4:21): 15% investor discount on services.
        discount: 0.15,
      },
      {
        id: 'investor-partnership',
        title: "I'm interested in an investment partnership",
        desc: 'Access our investor network, portfolio benefits, and optional commission.',
        icon: 'assets/icons/partnership.webp',
        info: 'Long-term capital partnership with shared upside.',
        waitlist: true,
      },
      {
        // 2026-06-08 (Loom 30 3:01-4:05): a fourth, lowest-commitment intent for
        // investors who are not sure about a company or an investment yet. They
        // pick the services they may be interested in and book a call.
        id: 'investor-general',
        title: 'I have a general enquiry',
        desc: 'Not sure yet? Explore the services you might be interested in, then book a call to talk it through.',
        icon: 'assets/icons/transparency.webp',
        info: 'No commitment. Pick what interests you and we will take it from there.',
        discount: 0.15,
      },
    ],
  },
  {
    id: 'agency',
    subBrand: 'Agencies',
    heading: 'For Agencies',
    desc: "I'm an agency, freelancer, or software company",
    cardTip: "For agencies, freelancers, and software companies. Use our services to grow your own agency, or resell them to your clients white-label at up to 40% wholesale pricing, shipping the work as your own.",
    icon: 'assets/icons/client-agencies.webp',
    logo: 'assets/logos/gogorilla-agencies.webp',
    perks: ['40% white-label discount', 'Unbranded deliverables', 'SLA reporting'],
    quickBullets: [], // ICP ticks removed per Alexander (Loom 14, 00:12), 2026-06-02
    // Dynamic bullets shown after intent is selected (updates the strip in real-time)
    quickBulletsByIntent: {
      'agency-own': [
        'Agency pricing',
        'Dedicated CSM',
        'GorillaPerks™ benefits',
      ],
      'agency-whitelabel': [
        'Wholesale pricing',
        'Anonymous delivery',
        'GorillaPerks™ benefits',
      ],
    },
    valueStrip: [
      { icon: '✦', title: 'White-label ready', desc: 'Unbranded deliverables you ship as your own.' },
      { icon: '◆', title: '40% partner pricing', desc: 'Best margin tier in the industry. SLA-backed.' },
      { icon: '◇', title: 'Dedicated agency CSM', desc: 'A single point of contact across all clients.' },
      { icon: '✧', title: 'Reseller perks', desc: 'GorillaPerks for Resellers + 10% recurring referral commission, for the duration of the minimum commitment.' },
    ],
    discount: 0.4,
    intents: [
      {
        id: 'agency-own',
        title: 'Growing my own agency with GoGorilla.com',
        flTitle: 'Growing my freelance business with GoGorilla.com',
        flDesc: 'Use GoGorilla.com\'s services to deliver for your own clients across strategy, ops, and content. Your 15% partner discount unlocks at your first active referral or white-label client and rises as you grow.',
        desc: "Use GoGorilla.com's services internally to grow your own agency, across strategy, ops, and content. The 15% partner discount applies once you have at least one active referral or white-label client.",
        icon: 'assets/icons/agency.webp',
        discount: 0.15,
        info: '15% partner discount on every service. Standard turnaround, GoGorilla-branded reporting.',
      },
      {
        id: 'agency-whitelabel',
        title: 'White-labelling GoGorilla.com services to my clients',
        flTitle: 'White-labelling GoGorilla.com services to my clients',
        flDesc: 'Resell GoGorilla.com as your own fulfilment. Your brand goes on every deliverable whilst we work anonymously behind the scenes, so you keep the client relationship.',
        desc: 'Resell GoGorilla.com as your own fulfilment. Your brand goes on every deliverable whilst we work anonymously behind the scenes, so you keep the client relationship.',
        icon: 'assets/icons/affiliate.webp',
        discount: 0.4,
        info: 'You can start with a single client and add more whenever you are ready. It includes the 40% white-label discount, unbranded deliverables, SLA reporting, and a dedicated agency client success manager.',
      },
      {
        id: 'agency-referral',
        title: 'Referring a client to GoGorilla.com',
        flTitle: 'Referring a client to GoGorilla.com',
        flDesc: 'Refer a business to GoGorilla.com and earn 10% recurring commission for the duration of their minimum commitment, and they get 10% off their plan. You can be both a white-label partner and a strategic referral partner.',
        desc: 'Refer a business to GoGorilla.com and earn 10% recurring commission for the duration of their minimum commitment, and they get 10% off their plan. You can be both a white-label partner and a strategic referral partner.',
        icon: 'assets/icons/affiliate.webp',
      },
    ],
  },
];

const TIERS = [
  { id: 'starter',    name: 'Starter',    blurb: 'Essentials only' },
  { id: 'grow',       name: 'Grow',       blurb: 'Proven playbook' },
  { id: 'scale',      name: 'Scale',      blurb: 'Full managed pod' },
  { id: 'enterprise', name: 'Enterprise', blurb: 'Custom scope', isEnterprise: true },
];

// ── Tier feature pill chips ──────────────────────────────────────────────
// Short (≤4 words) feature labels rendered as pill chips under the tier
// description on each service card. Distilled from Airtable Services bullets
// (base appRgD9IZC7YHxw1d, table Services, "Title (from Options Ref)").
//
// Lookup: TIER_BADGES[serviceId][tierId] -> array of strings.
// Falls back to empty array (no chips rendered) when missing.
const TIER_BADGES = {
  sales: {
    starter:    ['Email and LinkedIn outreach with managed responses', '4-touch email cadence', '750 prospects/mo', 'Meetings booked into your calendar'],
    grow:       ['Email, SMS, call, and WhatsApp outreach with managed responses', 'Cohort-personalised 6-touch cadence', 'Human cold calling on every lead', '1,000 prospects/mo'],
    scale:      ['Email, SMS, call, and WhatsApp outreach with managed responses, plus Meta ad warm-up', '1:1 per-prospect 10-touch cadence', 'Multi-threaded cold calling', '1,250 prospects/mo'],
    enterprise: ['Bespoke multi-channel programme', 'Closing support available', 'Dedicated senior team', 'Higher lead volume'],
  },
  'paid-ads': {
    starter:    ['1 search channel', 'Fully managed campaigns', 'Weekly reporting and optimisation', 'Direct team collaboration in Slack'],
    grow:       ['1 social channel', 'Fully managed campaigns', 'Unlimited ad creatives', 'Weekly reporting and optimisation'],
    scale:      ['2 or more channels', 'Fully managed with daily pacing', 'Unlimited ad creatives', 'Full-funnel optimisation'],
    enterprise: ['3 or more channels', 'Fully managed at enterprise scale', 'Custom strategy and reporting', 'Dedicated senior team'],
  },
  email: {
    starter:    ['Fully managed email marketing', 'Domain authentication and verification', 'Basic design and segmentation', 'Deliverability monitoring and list hygiene'],
    grow:       ['Fully managed email marketing', 'Intermediate design and segmentation', 'A/B testing programme', 'Deliverability monitoring and list hygiene'],
    scale:      ['Fully managed email and SMS', 'Advanced design and segmentation', 'Dedicated IP setup and warm-up', 'Deliverability monitoring and list hygiene'],
    enterprise: ['Fully managed at enterprise scale', 'Custom HTML and animation', 'Granular and predictive segmentation', 'Bespoke integrations'],
  },
  smm: {
    starter:    ['Up to 8 creative assets a month', 'Fully managed social channels', 'Standard graphics and videos', 'Direct team collaboration in Slack'],
    grow:       ['Up to 12 creative assets a month', 'Fully managed social channels', 'Custom graphics and videos', 'Community engagement support'],
    scale:      ['Up to 16 creative assets a month', 'Fully managed social channels', 'Advanced graphics and videos', 'Community engagement support'],
    enterprise: ['Custom creative volume', 'Fully managed at enterprise scale', 'Premium graphics and videos', 'Dedicated senior team'],
  },
  content: {
    starter:    ['Up to 6 creator-led videos a month', '2 revision rounds', 'Multiple ad copy variations', 'Full ownership of final assets'],
    grow:       ['Up to 10 creator-led videos a month', '3 revision rounds', 'Advanced post-production editing', 'Full ownership of final assets'],
    scale:      ['Up to 15 creator videos and 5 studio assets', '4 revision rounds', 'Storyboard and script development', 'Full ownership of final assets'],
    enterprise: ['Custom strategy', 'Premium production', 'Dedicated team'],
  },
  motion: {
    starter:    ['1 product hero video with 4 cut-downs', 'Photoreal product rendering', 'AI soundtrack and voice', '3 revision rounds'],
    grow:       ['2 hero videos with 8 cut-downs', 'Custom 360° AI avatar', 'Branded environments', '5 revision rounds'],
    scale:      ['3 hero videos and a long-form explainer', '4K-upgradeable rendering', '2 custom AI avatars', 'Unlimited revisions'],
    enterprise: ['Premium designer', 'Custom production', 'Dedicated team'],
  },
  whitelabel: {
    starter:    ['Self-serve catalogue', 'Unbranded reports', 'Pay-as-you-go'],
    grow:       ['White-label portal', 'Workspace sync', 'Multi-channel comms'],
    scale:      ['Senior account manager', 'Accountability discounts', 'Priority onboarding'],
    enterprise: ['Dedicated delivery team', 'Co-branded proposals', 'Custom SLAs'],
  },
  fundraising: {
    starter:    ['Pitch deck', '3-year model', 'Founders Pro included'],
    grow:       ['Premium deck', '50 investors', 'Grant support'],
    scale:      ['5-year model', '150+ investors', 'Founders Pro+ included'],
    enterprise: ['Custom scope', 'Dedicated strategist'],
  },
  'founders-portal': {
    starter:    ['100 UK investors', 'Profiles without contact details', 'AI matching', 'Personal dashboard'],
    grow:       ['7,500+ investors', 'Enriched contacts with email and LinkedIn', 'Verified badge', 'Priority placement'],
    scale:      ['Strategy call', 'Readiness assessment', 'Curated investor list'],
  },
  'investor-portal': {
    starter:    ['Due diligence access', 'Portfolio discounts', 'Partner Network'],
    grow:       ['Deal flow access', 'Warm intros', 'Portfolio discounts'],
    scale:      ['Buy-side sourcing', 'Deal origination', 'Mandate management'],
  },
};
window.TIER_BADGES = TIER_BADGES;

// 2026-06-13 (review): best-effort professional-language check for the custom
// role name and description fields (PT and FT). Client-side only and
// conservative, light leetspeak normalisation then word-boundary matching so
// legitimate words (Scunthorpe, analyst, cocktail) pass. Server-side review
// remains the real gate, this just stops obvious abuse at the form.
window.ggTextOk = function (raw) {
  if (!raw) return true;
  var t = ' ' + String(raw).toLowerCase()
    .replace(/0/g, 'o').replace(/[@4]/g, 'a').replace(/3/g, 'e')
    .replace(/[1!]/g, 'i').replace(/[5$]/g, 's')
    .replace(/[^a-z]+/g, ' ') + ' ';
  var slurs = /\b(nigg\w*|fagg\w*|kike\w*|pakis?\b|chink\w*|spics?\b|wetback\w*|raghead\w*|trann\w*|retard\w*|spastic\w*|coons?\b|golliwog\w*)/;
  var profanity = /\b(fuck\w*|shite?\b|shitt\w*|shits\b|cunt\w*|bitch\w*|bastard\w*|wank\w*|twat\w*|pricks?\b|sluts?\b|whore\w*|dickhead\w*|bollock\w*|arsehole\w*|asshole\w*|bullshit\w*)/;
  var unsafe = /\b(rape\b|rapist\w*|nazis?\b|hitler\w*|terroris\w*|porn\w*|onlyfans\w*|paedo\w*|pedo\w*|jihad\w*)/;
  var exclusion = /\b(whites?|blacks?|asians?|males?|females?|men|women|gays?|straights?|christians?|muslims?|jews?|hindus?|sikhs?|catholics?|protestants?|locals?|foreigners?|immigrants?|disabled)\s+only\b|\bno\s+(blacks?|whites?|asians?|gays?|jews?|muslims?|christians?|hindus?|sikhs?|women|men|males?|females?|disabled|foreigners?|immigrants?)\b/;
  if (slurs.test(t) || profanity.test(t) || unsafe.test(t) || exclusion.test(t)) return false;
  // Censored spellings (f*ck, sh*t) lose their vowel to the star, so when a
  // censor character is present also test the consonant skeleton. Gated on
  // the censor character so words like Count or Shot can never false-match.
  if (/[*#%_]/.test(String(raw))) {
    var t2 = ' ' + String(raw).toLowerCase().replace(/[*#%_]/g, '').replace(/[aeiou]/g, '').replace(/[^a-z]+/g, ' ') + ' ';
    if (/\b(fck\w*|sht|cnts?|btch\w*|wnkr?|slts?|whrs?|ngg\w*|fggts?)\b/.test(t2)) return false;
  }
  return true;
};

const COMMITMENTS = [
  { id: '3',  months: 3,  label: '3 months',  save: 0  },
  { id: '6',  months: 6,  label: '6 months',  save: 20 },
  { id: '12', months: 12, label: '12 months', save: 40 },
];

// Pricing helper: look up exact £/mo from matrix using THIS service's own minimum
// commitment (commitMonths), or 'Custom' for Enterprise/one-time.
// Returns { value: Number|null, label: String, custom: Boolean, oneTime: Boolean }
function priceFor(service, tierId, overrideCommitId) {
  if (!service || !tierId) return { value: 0, label: ',', custom: false };
  if (tierId === 'enterprise') return { value: 0, label: 'Bespoke Pricing', custom: true };
  if (service.oneTime) {
    const val = service.prices?.[tierId];
    if (val == null) return { value: 0, label: 'Custom', custom: true };
    return { value: val, label: window.fmt(val), custom: false, oneTime: true };
  }
  if (service.freeStarter && tierId === 'starter') return { value: 0, label: 'Free', custom: false };
  // Founders Pro (founders-portal 'grow') becomes free to the monthly total when
  // the founder has at least one OTHER GoGorilla service selected. Founders Pro+
  // ('scale') is unaffected. window.__ggSelectedIds is published every render by
  // <App> so this lookup stays in sync with the live selection set.
  if (service.id === 'founders-portal' && tierId === 'grow') {
    const _ggSel = window.__ggSelectedIds;
    const _ggOthers = Array.isArray(_ggSel) && _ggSel.some(sid => sid && sid !== 'founders-portal');
    if (_ggOthers) return { value: 0, label: 'Free', custom: false, free: true };
  }
  // Use override if the service supports a price at that commit; else fall back to the service default.
  const tierPrices = service.prices?.[tierId];
  let commitId;
  if (tierPrices && typeof tierPrices === 'object' && overrideCommitId != null && tierPrices[String(overrideCommitId)] != null) {
    commitId = String(overrideCommitId);
  } else {
    commitId = '12';
  }
  // Some services have a flat-number price per tier (no commit matrix), return that directly.
  if (tierPrices != null && typeof tierPrices !== 'object') {
    return { value: tierPrices, label: window.fmt(tierPrices), custom: false };
  }
  const val = tierPrices?.[commitId];
  if (val == null) return { value: 0, label: 'Custom', custom: true };
  return { value: val, label: window.fmt(val), custom: false, commitId };
}

// Returns the array of available commit options for a service, or null if the
// service has only a single (flat) price per tier and no commit choice applies.
function commitsFor(service) {
  if (!service || service.oneTime) return null;
  // Inspect any tier price entry, if it's an object keyed by commit ids, expose those.
  const tiers = service.prices ? Object.values(service.prices) : [];
  const matrix = tiers.find(t => t && typeof t === 'object');
  if (!matrix) return null;
  const ids = Object.keys(matrix);
  return window.COMMITMENTS.filter(c => ids.includes(c.id));
}

// Human-readable commitment label for a service. Returns null if the service
// doesn't have a recurring commitment (one-time deliverables).
function commitLabelFor(service, overrideCommitId) {
  if (!service || service.oneTime) return null;
  // Prefer the user's override when it's a valid option for the service
  const opts = window.commitsFor ? window.commitsFor(service) : null;
  let m = 12;
  if (overrideCommitId != null && opts && opts.some(o => o.id === String(overrideCommitId))) {
    const opt = opts.find(o => o.id === String(overrideCommitId));
    if (opt) m = opt.months;
  }
  return `${m}-month minimum commitment`;
}

const SERVICES = [
  {
    id: 'sales',
    pricingUrl: 'https://www.gogorilla.com/pricing#plans',
    name: 'Sales & Demand Generation',
    desc: 'Managed outbound sales with human cold calling, booking qualified appointments directly into your calendar.',
    category: 'Growth',
    icon: 'assets/icons/algorithmic.webp',
    commitMonths: 6,
    waitlistTiers: [],
    tierFeatures: {
      starter:    ['Email and LinkedIn outreach', '150 contacts/week', 'Shared CRM'],
      grow:       ['Email and call outreach', '400 contacts/week', 'Dedicated CRM workspace', 'Weekly strategy call'],
      scale:      ['Email and call outreach', 'Advanced signals', '1,000+ contacts/week', 'Full pipeline ops', 'Multi-threaded outbound'],
      enterprise: ['All signals', 'Senior SDRs', 'Dedicated strategist', 'Local presence'],
    },
    prices: {
      starter: { '3': 1995, '6': 1395, '12':  995 },
      grow:    { '3': 3295, '6': 2295, '12': 1795 },
      scale:   { '3': 3995, '6': 2995, '12': 2495 },
    },
    // One-time setup fees per tier. Sourced from Airtable Services table.
    setupFees: {
      starter: 495,
      grow:    995,
      scale:   1995,
    },
    tierDesc: {
      starter: 'B2B founders looking for managed email outreach. We book qualified meetings to your calendar',
      grow: 'B2B founders looking for managed multitouch outbound. Our in-house human cold calling is sequenced in',
      scale: 'B2B sales leaders looking for managed outbound. We layer in web-intent, paid ad warm-up & multithreading',
      enterprise: 'B2B sales leaders scaling enterprise outbound with senior SDRs, local presence & a dedicated strategist',
    },
    incentives: [
      { type: 'built-in', label: 'Algorithmic Team Profit Sharing' },
      // 2026-06-10 (Loom 33 0:00-0:20): PRT removed for SDG (effectively part
      // of the model here) and Outcome-Linked is BUILT IN, the cost per
      // meeting already ties part of our fee to delivered outcomes.
      { type: 'built-in', label: 'Outcome-Linked Success Bonus' },
    ],
    addons: [
      // ── Outbound acceleration ──────────────────────────────────────────
      { id: 'byol',             group: 'Outbound acceleration', name: 'Bring Your Own List',                  desc: '15% off your retainer when you supply a verified prospect list.', tip: 'Supply your own verified list and we take 15% off your monthly retainer. We still enrich the data, write the copy, and run the full cadence for you.', priceLabel: '-15% off retainer', price: 0, unit: 'discount' },
      { id: 'domains',          group: 'Outbound acceleration', name: 'Additional Email Domains & Accounts',    desc: 'Extra sending infrastructure for higher-volume campaigns.', tip: 'More sending domains and mailboxes so you can raise outbound volume whilst protecting deliverability and staying within safe per-inbox limits.',  priceLabel: 'From £195', price: 195, unit: '/account' },
      // 2026-05-30: 'Supplementary Dialling' and 'Dedicated SDR' add-ons removed (no longer offered).
      { id: 'native-crm',       group: 'Outbound acceleration', name: 'Native CRM Sync',                     desc: 'Two-way sync with any major CRM, including HubSpot, Salesforce, and Pipedrive.', tip: 'By default you get access to our CRM at no cost, or you can export your leads as an Airtable sheet or CSV and upload them yourself. This add-on adds automated two-way sync with your own CRM, kept in step in real time using Make.com or Zapier, so there are no manual exports and no lag on contact or activity data. Works with HubSpot, Salesforce, Pipedrive, and others.', priceLabel: 'From £145', price: 145, unit: '/mo' },
      { id: 'overseas-calling', group: 'Outbound acceleration', name: 'Overseas Cold Calling',                desc: 'Extend calling into North America or APAC time zones beyond UK hours.', priceLabel: 'From £325', price: 325, unit: '/mo', setupFee: 195, tip: 'Each region you select adds its own monthly rate, with Rest of World quoted at onboarding. We call from local mobile numbers for each country, so your prospects see a native dialling code, and we schedule calling windows that overlap sensibly with each market, typically around four hours of East Coast cover for North America, without graveyard shifts for the team.' },
      // ── Paid Advertising Layer ───────────────────────────────────────────────
      { id: 'ads-lite',         group: 'Paid Advertising Layer',     name: 'Paid Advertising Integration (Lite)',    desc: 'A light-touch paid layer running alongside your outbound.', tip: 'A starter paid-media layer that supports outbound with retargeting and a few core campaigns. Management fee only, your ad spend is paid directly to the platforms.', priceLabel: 'From £195', price: 195, unit: '/mo' },
      { id: 'ads-full',         group: 'Paid Advertising Layer',     name: 'Paid Advertising Integration (Full)',    desc: 'Full-funnel paid media managed alongside your outbound.', tip: 'Full paid-media management across your funnel, integrated with outbound. Available on the Enterprise plan. Management fee only, ad spend is paid directly to the platforms.',                 priceLabel: 'See Paid Advertising plans', custom: true },
      // ── Custom List Building (renamed from Premium Sourcing, Loom 32) ───────
      { id: 'premium-sourcing', group: 'Custom List Building', name: 'Custom List Building', standalone: true, desc: 'A custom prospect list built from our 300+ premium database providers and our own custom-built tools. Contacts are screened against the TPS and CTPS registers and the data is GDPR compliant.', tip: 'We build you a custom list of GDPR compliant, fully enriched prospects from the same 300+ premium database providers and our own custom-built tools behind your standard leads, ready for your cadence. Mobile numbers and direct dials are included where available.\nIf you have already subscribed to a plan above, the cost includes calling and any additional domains as part of your existing plan. Purchased on its own, you get access to the enriched leads list.', priceLabel: '£4.50', price: 4.5, unit: '/prospect', oneTime: true, recurringOption: { price: 4, savePct: 11 }, minQty: 56, maxQty: 20000, qtyPresets: [100, 250, 500, 1000], onboardingStatus: 'limited' },
      // ── Signals & Buying Intent ──────────────────────────────────────────────
      { id: 'gorilla-signals',  group: 'Signals & Buying Intent',    name: 'Web Intent & Visitor De-anonymisation', desc: 'We track which companies are landing on your site or actively researching your category, then match each anonymous visitor back to the company at IP level and score how strongly they are showing intent. The warmest accounts drop straight into your outbound, so your team reaches a buyer while they are still comparing options rather than weeks later.\nThis covers your whole monthly lead volume, so its price scales with it. It starts at £295 covering up to 1,250 prospects a month and rises as you add more leads in the [[Monthly lead volume]] box above. Includes a 14-day trial before billing.', tip: 'We de-anonymise website visitors at IP level, score their intent, and enrol the strongest signals into outbound automatically, so you reach buyers whilst they are still looking. Web Intent works across every prospect we process, so the price scales with your monthly lead volume. It starts at £295 covering up to 1,250 prospects a month and rises as you add more leads, which you set in the Monthly lead volume box above.', priceLabel: 'From £295 /mo', price: 295, unit: '/mo', subGroup: 'sdg-signals' },
      { id: 'hiring-signals',   group: 'Signals & Buying Intent',    name: 'Hiring & Job-Change Signals',         desc: 'We watch for hiring, promotions, and senior role changes across your target accounts in real time and fire your sequence the moment one lands. Covers 51+ sources including LinkedIn, Indeed, and Glassdoor. Reaching a leader in their first weeks at a new company tends to land far better, because they are actively reviewing suppliers and looking to make their mark, so a well-timed message often beats months of cold follow-up.', tip: 'Adds real-time hiring and job-change data to your cadence, so prospects are contacted exactly when a trigger event makes them more likely to buy.', priceLabel: 'From £145 /mo', price: 145, unit: '/mo', subGroup: 'sdg-signals' },
      { id: 'financial-signals', group: 'Signals & Buying Intent',    name: 'Financial & Funding Signals',          desc: 'We fire your outbound the day a company announces funding, closes an acquisition, or makes a major expansion public, so you reach decision-makers while fresh budget is sitting unspent and they are actively buying. Covers Crunchbase, Y Combinator, Product Hunt, and live press feeds, so a newly funded account can be in your sequence within hours of the news.', tip: 'We track funding rounds, acquisitions, and other financial triggers from sources like PitchBook and Crunchbase, then surface the companies with fresh budget so your outbound lands whilst they are actively investing.', priceLabel: 'From £695 /mo', price: 695, unit: '/mo', subGroup: 'sdg-signals' },
      { id: 'tech-signals',    group: 'Signals & Buying Intent',    name: 'Tech-Stack Change Signals', desc: 'We monitor the tools and platforms a company runs and flag the moment they adopt, drop, or switch one, which is often the clearest sign your offer has just become relevant and there is budget behind it. Powered by BuiltWith, Majestic, and Semrush, and ideal for SaaS-to-SaaS switch plays. Need a signal we do not track yet? Tell us and we will build a custom tool to watch for it.', tip: 'We monitor the tools and platforms a company runs and flag the moment they adopt, drop, or switch one, which is often the clearest sign your offer has just become relevant and there is budget behind it. Powered by BuiltWith, Majestic, and Semrush, and ideal for SaaS-to-SaaS switch plays. Need a signal we do not track yet? Tell us and we will build a custom tool to watch for it.', priceLabel: 'From £145 /mo', price: 145, unit: '/mo', subGroup: 'sdg-signals' },
      { id: 'custom-signals',  group: 'Signals & Buying Intent',    name: 'Custom Signals', desc: 'Tell us the trigger you want and we build a custom tool to watch for it, then feed matching companies into your outbound. Almost any signal, on a 3-month minimum commitment.', tip: 'Tell us the buying signal you want to act on and we build a custom tool to track it, then feed the matching companies straight into your outbound. We can build almost any signal, on a 3-month minimum commitment. There may be additional costs for some custom tools, and more again for extremely complex, multi-step AI builds.', priceLabel: '£995 /mo', price: 995, unit: '/mo', subGroup: 'sdg-signals' },
      // ── Physical Touch & Field Coverage ─────────────────────────────────────
      { id: 'physical-mail-handwritten', group: 'Physical Touch & Field Coverage', name: 'Handwritten Letters', desc: 'Individually handwritten letters on our branded paper, sent from our UK base. UK only.', tip: 'Each letter is written by hand on our branded paper and posted from our UK fulfilment base, a high-impact touch for your best prospects. Branded paper is included. Minimum order 100 letters. Currently UK addresses only.', priceLabel: 'From £15', price: 15, unit: '/letter', minQty: 100, oneTime: true, recurringOption: { price: 13.5, savePct: 10 } },
      { id: 'physical-mail-printed', group: 'Physical Touch & Field Coverage', name: 'Printed Letters', desc: 'Professionally printed letters sent by second-class post from our UK base. UK only.', tip: 'We print and send your letters by second-class post from our UK fulfilment base to cut through crowded inboxes for high-value prospects. Minimum order 500 letters, since printed mail works best at volume. Currently UK addresses only.', priceLabel: 'From £2.75', price: 2.75, unit: '/letter', minQty: 500, oneTime: true, recurringOption: { price: 2.48, savePct: 10 } },
      { id: 'field-sales',      group: 'Physical Touch & Field Coverage', name: 'In-Person Field Sales & Event Coverage',          desc: 'In-person reps for events, visits, and appointment confirmation, on a day rate plus a cost per sale. UK only.', tip: 'We provide in-person coverage for trade shows, store visits, and door-to-door or appointment confirmation, drawn from our UK university partnership network. Currently UK addresses only.', priceTip: 'Charged at £245 per day per person plus a cost per sale agreed up front, with a minimum of 10 days. Associated transport costs are excluded and quoted separately.', priceLabel: 'From £245', price: 245, unit: '/day', perUnit: true, minQty: 10, oneTime: true, recurringOption: { price: 220, savePct: 10 } },
    ],
  },
  {
    id: 'paid-ads',
    pricingUrl: 'https://www.gogorilla.com/pricing/paid-advertising#plans',
    name: 'Paid Advertising',
    desc: 'Acquire new customers at scale with fully managed ad campaigns on channels including Google, Meta, TikTok, Pinterest, and more.',
    category: 'Growth',
    icon: 'assets/icons/paid-ads.webp',
    badge: 'popular',
    commitMonths: 3,
    waitlistTiers: [],
    tierFeatures: {
      starter: ['1 channel', 'Up to £10k/mo ad spend', 'Monthly reporting'],
      grow:    ['1 channel', 'Up to £50k/mo ad spend', 'Weekly reporting', 'Creative refresh'],
      scale:   ['2 or more channels', 'Unlimited managed spend', 'Daily pacing', 'Dedicated media buyer'],
    },
    prices: {
      starter: { '3':  795, '6':  595, '12':  495 },
      grow:    { '3': 1495, '6': 1095, '12':  895 },
      // 2026-06-11 (Loom 38 item 4, confirmed by Nicole): Scale bundles the
      // Starter search channel and the Grow social channel, so it prices a
      // touch below the two combined (2,290 / 1,690 / 1,390), not above.
      scale:   { '3': 1995, '6': 1495, '12': 1195 },
    },
    setupFees: {
      starter: 245,
      grow:    345,
      scale:   445,
    },
    tierDesc: {
      starter: 'DTC & B2B founders looking to acquire new customers through fully managed paid search advertising',
      grow: 'DTC & B2B founders looking to acquire new customers through fully managed paid social media advertising',
      scale: 'DTC & B2B founders looking to scale through fully managed multi-channel paid advertising with full-funnel optimisation',
      enterprise: 'DTC & B2B founders looking to scale through fully managed enterprise-scale paid with bespoke teams & category specialists',
    },
    incentives: [
      { type: 'built-in', label: 'Algorithmic Team Profit Sharing' },
      { type: 'optional', label: 'Performance Recognition Tips' },
      { type: 'add-on',  label: 'Outcome-Linked Success Bonus' },
    ],
    addons: [
      // ── Channel expansion ────────────────────────────────────────────
      { id: 'meta-retarget',     group: 'Channel expansion', name: 'Meta Retargeting',                             desc: 'Retargeting campaigns across Facebook and Instagram.', tip: 'We build and manage retargeting across Facebook and Instagram to bring warm audiences back. Management fee only, your ad spend goes directly to Meta.',                                             price: 195,   unit: '/mo' },
      { id: 'google-display',    group: 'Channel expansion', name: 'Google Display Ads',                           desc: 'Programmatic display across the Google Display Network.', tip: 'We run programmatic display for reach and retargeting across the Google Display Network. Management fee only, ad spend is paid directly to Google.',                                         priceLabel: 'From £95',  price: 95,   unit: '/mo' },
      { id: 'google-shop',       group: 'Channel expansion', name: 'Google Shopping Ads',                          desc: 'Merchant Center feed management and Shopping campaigns.', tip: 'We manage your Merchant Center feed and run Shopping campaigns to put your products in front of high-intent searchers. Management fee only, ad spend paid directly to Google.',                                         priceLabel: 'From £95',  price: 95,   unit: '/mo' },
      { id: 'linkedin-retarget', group: 'Channel expansion', name: 'LinkedIn Ads Retargeting (Lite)',                     desc: 'A light-touch LinkedIn retargeting layer to re-engage warm audiences, not a full managed LinkedIn ads programme.', tip: 'We run light-touch LinkedIn retargeting and sponsored content to re-engage professional audiences who already know you. It is a starter retargeting layer rather than a full managed LinkedIn ads programme, which keeps the price low. Management fee only, your ad spend goes directly to LinkedIn.',                                     price: 195,   unit: '/mo' },
      // ── B2B audience data for paid campaigns ─────────────────────────
      { id: 'premium-sourcing-pa', group: 'B2B audience data for paid campaigns', name: 'Custom List Building', standalone: true, desc: 'A custom, GDPR-validated B2B prospect list for paid custom audiences.', tip: 'We build you a custom list of GDPR-validated, fully enriched B2B prospects so you can upload them to Meta, Google, or LinkedIn as matched custom audiences and lookalikes, putting your ads in front of the exact companies and roles you want.\nPurchased on its own, you get access to the enriched leads list.', priceLabel: '£4.50', price: 4.5, unit: '/prospect', oneTime: true, recurringOption: { price: 4, savePct: 11 }, minQty: 56, maxQty: 20000, qtyPresets: [100, 250, 500, 1000] },
      // ── Conversion infrastructure and reporting ───────────────────────
      { id: 'lp-build',          group: 'Conversion infrastructure and reporting', name: 'Landing Page Build',                   desc: 'A custom, conversion-focused landing page, built and tested.', tip: 'A one-off build of a conversion-focused landing page for your campaign, designed, built, and QA tested before launch.',                                                         priceLabel: 'From £905', price: 905, unit: 'one-time', oneTime: true },  // 2026-06-12 (review): repriced, AI-assisted builds
      { id: 'landing',           group: 'Conversion infrastructure and reporting', name: 'Existing Landing Page Optimisation',           desc: 'Ongoing management and A/B testing of your existing landing pages.', tip: 'Continuous management of your existing landing pages with structured A/B testing, so conversion rates keep improving over time rather than staying static.',                                                      priceLabel: 'From £145',   price: 145,  unit: '/mo' },
      { id: 'cro-prog',          group: 'Conversion infrastructure and reporting', name: 'Conversion Rate Optimisation Programme',         desc: 'Systematic testing and funnel analysis to lower your cost per lead.', tip: 'An ongoing programme of landing-page tests, copy experiments, and funnel analysis, aimed at steadily lowering your cost per lead.',                             priceLabel: 'From £145',   price: 145,  unit: '/mo' },
      { id: 'call-track',        group: 'Conversion infrastructure and reporting', name: 'Google Call Tracking Setup',                     desc: 'Call-level attribution with dynamic number insertion.', tip: 'We set up call tracking with dynamic number insertion and analytics integration, so phone enquiries are attributed to the right campaign and keyword.',                                        price: 95,   unit: 'one-time', oneTime: true },
      { id: 'dw-setup',          group: 'Conversion infrastructure and reporting', name: 'Data Warehouse Integration',                     desc: 'Wire your ad platforms, CRM, and analytics into BigQuery or Snowflake.', tip: 'A one-off setup connecting your ad platforms, CRM, and analytics into BigQuery or Snowflake, giving you a single source of truth for reporting.',                                                     priceLabel: 'From £495',   price: 495,  unit: 'one-time', oneTime: true },
      { id: 'zapier',            group: 'Conversion infrastructure and reporting', name: 'Zapier Automation Management',                   desc: 'Build and maintain automations across your paid-funnel tools.', tip: 'We build and maintain Zapier automations that connect the tools in your paid funnel, removing manual handoffs between platforms. For example, pushing new lead lists into your CRM or email marketing software where there is no native integration.',                                                        priceLabel: 'From £95',    price: 95,   unit: '/mo' },
    ],
  },
  {
    id: 'email',
    pricingUrl: 'https://www.gogorilla.com/pricing/email-marketing#plans',
    name: 'Email Marketing',
    desc: 'Nurture leads and maximise customer lifetime value with fully managed email campaigns and automated sequences that convert.',
    category: 'Growth',
    icon: 'assets/icons/email-marketing.webp',
    commitMonths: 3,
    // Tier-level waiting list: grow + scale are at full capacity. They render
    // as "Waiting list" instead of a price, can still be added to a quote
    // (capturing demand in Airtable), but contribute £0 to the calculator.
    waitlistTiers: [],
    tierFeatures: {
      starter: ['2 broadcasts/mo', 'Basic flows (welcome, abandoned)', 'Klaviyo or Mailchimp'],
      grow:    ['8 broadcasts/mo', 'Full lifecycle (6 flows)', 'A/B testing', 'Deliverability audits'],
      scale:   ['Weekly broadcasts', 'Advanced segmentation', 'RFM + predictive', 'Dedicated IP management'],
    },
    prices: {
      starter: { '3': 1295, '6':  595, '12':  495 },
      grow:    { '3': 1495, '6': 1095, '12':  895 },
      scale:   { '3': 2495, '6': 1895, '12': 1495 },
    },
    setupFees: {
      starter: 245,
      grow:    345,
      scale:   995,
    },
    tierDesc: {
      starter: 'DTC & B2B founders looking to nurture leads & boost retention through fully managed email marketing',
      grow: 'DTC & B2B founders looking to drive repeat revenue through fully managed email marketing with segmentation',
      scale: 'DTC & B2B founders looking to scale through fully managed high-volume email marketing with advanced segmentation',
      enterprise: 'DTC & B2B founders looking to scale through fully managed enterprise email programmes across multiple brands, regions, & channels',
    },
    incentives: [
      { type: 'built-in', label: 'Algorithmic Team Profit Sharing' },
      { type: 'optional', label: 'Performance Recognition Tips' },
      { type: 'add-on',  label: 'Outcome-Linked Success Bonus' },
    ],
    addons: [
      // ── Campaign enhancements ─────────────────────────────────────────────
      { id: 'platform-migration', group: 'Campaign enhancements', name: 'Email Marketing on Any Platform',       desc: 'Full management on Klaviyo, Mailchimp, HubSpot, Braze, or your own.', tip: 'We run your email programme on whatever platform you already use, so there is no forced migration. Setup and ongoing management are included.',        price: 75,  unit: '/platform', oneTime: true },
      { id: 'deliverability-audit', group: 'Campaign enhancements', name: 'Deliverability Audit and Domain Warmup', desc: 'Inbox-placement audit, blacklist review, and domain warmup.', tip: 'A full audit of why your emails land where they do, plus blacklist checks and a structured warmup for each sending domain to lift inbox placement. It also sets up a dedicated sending subdomain, which is not part of the standard plans, to protect your main domain reputation.', price: 245, unit: 'one-time', oneTime: true },
      { id: 'landing',            group: 'Campaign enhancements', name: 'Landing Page Design and Optimisation',  desc: 'Conversion-tuned landing pages for your major broadcasts.', tip: 'We design and manage conversion-tuned landing pages for your key email campaigns, then refine them month to month to lift results.',                        priceLabel: 'From £145', price: 145, unit: '/mo' },
      // ── Audience and automation ───────────────────────────────────────────
      // 2026-06-12 (Loom 44 6:49): B2B List Enrichment removed, it served
      // existing subscribers rather than new prospects.
      { id: 'adv-segmentation',   group: 'Audience and automation', name: 'Advanced Segmentation and Personalisation Build', desc: 'Custom RFM, behavioural, and predictive segments, built once.', tip: 'A one-off project to build custom RFM, behavioural, and predictive segments, so your campaigns can be properly targeted and personalised.',   priceLabel: 'From £475', price: 475, unit: 'one-time', oneTime: true },
      { id: 'zapier',             group: 'Audience and automation', name: 'Zapier Automation Management',        desc: 'Build and maintain automations between your ESP, CRM, and stack.', tip: 'We build and maintain Zapier automations connecting your email platform, CRM, and product tools, removing manual data handoffs. For example, syncing new subscribers into your CRM or pushing purchase events back into your email platform where there is no native integration.',                priceLabel: 'From £95',  price: 95,  unit: '/mo' },
      // ── Data warehouse integration ────────────────────────────────────────
      { id: 'dw-setup',           group: 'Data warehouse integration', name: 'Data Warehouse Integration',       desc: 'Connect your email platform into your data warehouse.', tip: 'A one-off setup linking your email platform into your existing data warehouse for unified, cross-channel reporting.',  priceLabel: 'See Paid Advertising plans', price: 595, unit: 'one-time', oneTime: true,
        tip: 'This covers the initial integration of your email marketing platforms, including your existing data warehouse, custom API connections, and data centralisation. Because this work usually spans multiple marketing channels, we price and deliver it through our Paid Advertising pricing plans. If you already have a Paid Advertising plan with us, data warehouse integration is already included at no extra cost.' },
      { id: 'dw-ongoing',         group: 'Data warehouse integration', name: 'Data Warehouse Maintenance',       desc: 'Monthly pipeline monitoring, model tuning, and cost control.', tip: 'Ongoing upkeep of your data warehouse pipelines, including monitoring, tuning, and cost optimisation. Billed monthly.',                                  price: 95,  unit: '/mo' },
      // ── Strategic support ─────────────────────────────────────────────────
      { id: 'additional-reports', group: 'Strategic support', name: 'Additional Weekly Reports and Calls', desc: 'An extra weekly performance report and strategy call.', tip: 'Adds one more performance report and strategy call each week on top of your plan, for teams that want a closer feedback loop.', priceLabel: 'From £75/wk', price: 75, unit: '/wk' },
    ],
  },
  {
    id: 'smm',
    pricingUrl: 'https://www.gogorilla.com/pricing/smm#plans',
    name: 'Social Media Management',
    desc: 'We create and execute tailored social media strategies to grow and nurture your online communities across all major channels.',
    category: 'Creative',
    icon: 'assets/icons/smm.webp',
    commitMonths: 6,
    waitlistTiers: [],
    tierFeatures: {
      starter: ['2 channels', '8 posts/mo', 'Community monitoring'],
      grow:    ['4 channels', '20 posts/mo', 'DM management', 'Monthly trend report'],
      scale:   ['All channels', '60+ posts/mo', '24/7 community mgmt', 'Influencer coordination'],
    },
    prices: {
      starter: { '3': 1295, '6':  995, '12':  795 },
      grow:    { '3': 2495, '6': 1995, '12': 1495 },
      scale:   { '3': 5495, '6': 4295, '12': 3595 },
    },
    setupFees: {
      starter: 245,
      grow:    345,
      scale:   995,
    },
    tierDesc: {
      starter: 'DTC & B2B founders looking to grow brand visibility through fully managed social media on up to 2 channels',
      grow: 'DTC & B2B founders looking to grow audiences through fully managed multi-channel social media with custom creative',
      scale: 'DTC & B2B founders looking to scale through fully managed high-output, multi-channel social media with premium creative',
      enterprise: 'DTC & B2B founders looking to scale through fully managed enterprise social across multiple brands, regions, & platforms',
    },
    incentives: [
      { type: 'built-in', label: 'Algorithmic Team Profit Sharing' },
      { type: 'optional', label: 'Performance Recognition Tips' },
      { type: 'add-on',  label: 'Outcome-Linked Success Bonus' },
    ],
    addons: [
      // 2026-06-12 (decision confirmed): per-platform management add-ons
      // answer the just-manage-my-LinkedIn scenario, the tiers stay
      // creatives-led. Mirrors Follower & Community Growth's pattern, the
      // £395 anchor mirrors it too pending Alexander's glance.
      { id: 'linkedin-mgmt',     name: 'LinkedIn Management', desc: 'Human-led LinkedIn posting, community management, and replies.', tip: 'A real person runs your LinkedIn day to day, scheduling and publishing your posts, replying to comments and messages, welcoming new followers, and flagging anything that needs your attention. Creative assets come from your plan or your own library.',       priceLabel: 'From £395',    price: 395,   unit: '/mo',       priceNote: 'Under £100 a week', standalone: true },
      { id: 'community',         name: 'Follower & Community Growth', badge: 'popular', badgeTip: 'Our most popular add-on in Social Media Management. It is real, manual follower growth and community management that builds both brand awareness and a genuine following, which is why most clients add it.', desc: 'Human-led Instagram follower growth, community management, and DM replies.', tip: 'A real person grows your Instagram following and runs your community day to day, replying to comments and DMs, and welcoming new followers. It is all done manually, around one to two hours of hands-on work a day, which works out to less than £15 an hour.\nBased on the latest platform guidelines, we follow a safe, paced volume that starts slower and builds up to protect the account. It is used both to build brand awareness and to grow your following, and follow-back rates typically range from 20% to 40%, depending on how strong and well-targeted your content is.',       priceLabel: 'From £495',    price: 495,   unit: '/mo',       priceNote: 'Under £115 a week', standalone: true },
      { id: 'post-boost',        standalone: true, name: 'Paid Advertising Post Promotion',      desc: 'Boost your best organic posts to a wider audience.', tip: 'We put paid budget behind your strongest organic posts to extend their reach. Management fee only, your ad spend is paid directly to the platform.',                    price: 295,                 unit: '/mo' },
      { id: 'insta-story',       name: 'Instagram Story Design & Posting',              desc: 'On-brand Instagram Stories designed in packs of 10, ready to publish.', tip: 'We recommend running day-to-day Stories in-house for speed and authenticity. When you want a polished, on-brand set, we design them in packs of 10 ready to publish. Choose how many packs you need.', unitTip: 'One pack is 10 designed, on-brand Instagram Stories, ready to publish.',           priceLabel: 'From £145',    price: 145,    unit: '/pack', perUnit: true, minQty: 1, oneTime: true, recurringOption: { savePct: 11 } },
      { id: 'custom-highlights', name: 'Custom Highlights Design',             desc: 'Branded Highlight cover icons for your Instagram profile.', tip: 'Up to 5 custom-designed Highlight covers in your brand style, with revisions included, priced on a one-day rate of a Graphic Designer.',   priceLabel: 'From £295',    price: 295,   unit: 'one-time', oneTime: true },  // 2026-06-12 (Loom 44 7:56): GD day-rate basis
      // 2026-06-12 (review): Custom LinkedIn Social Posts removed.
      { id: 'extra-lang',        name: 'Additional Language or Market',       desc: 'Full social strategy and content in another language or market.', tip: 'We deliver a complete social presence in an additional language or for a new region, properly adapted to local culture rather than just translated.', priceLabel: 'From £395', price: 395, unit: '/mo per market' },
      { id: 'showcase',          name: 'Custom Showcase Page Design',          desc: 'A branded LinkedIn or Meta showcase or business page build.', tip: 'We design and set up a LinkedIn Showcase page or a Meta business page in your brand style, ready to publish.',            price: 295,                 unit: 'one-time', oneTime: true },
    ],
  },
  {
    id: 'content',
    pricingUrl: 'https://www.gogorilla.com/pricing/content-creation#plans',
    name: 'Content Creation',
    desc: 'We work with vetted top-tier creators and direct internal shoots to create full-funnel content optimised for paid or organic media.',
    category: 'Creative',
    icon: 'assets/icons/content.webp',
    fixedDuration: true,
    tierDuration: { starter: '1 month', grow: '1 to 2 months', scale: '2 to 6 months', enterprise: 'Custom, typically 3 to 12 months' },
    waitlistTiers: ['starter', 'grow', 'scale', 'enterprise'],
    tierFeatures: {
      starter: ['6 long-form pieces', 'Ghostwriting support', 'Hub + spokes structure'],
      grow:    ['18 pieces', 'Case studies + newsletters', 'Editorial calendar', 'Quarterly retainers'],
      scale:   ['Newsroom model', 'Podcast + video included', 'Dedicated editor-in-chief', 'Annual content strategy'],
    },
    prices: { starter: 1495, grow: 2495, scale: 5995 },
    setupFees: {
      starter: 895,
      grow:    1295,
      scale:   1795,
    },
    tierDesc: {
      starter: 'DTC & B2B founders looking to kickstart brand awareness through fully managed creator-led video content',
      grow: 'DTC & B2B founders looking to scale customer acquisition through fully managed high-volume, creator-led video content',
      scale: 'DTC & B2B founders looking to scale through fully managed multi-channel content with creator-led & studio-produced video',
      enterprise: 'DTC & B2B founders looking to scale through fully managed enterprise content production across brands, regions, & formats',
    },
    incentives: [
      { type: 'built-in', label: 'Algorithmic Team Profit Sharing' },
      { type: 'optional', label: 'Performance Recognition Tips' },
      { type: 'add-on',  label: 'Outcome-Linked Success Bonus' },
    ],
    addons: [
      // ── Additional Video Content ─────────────────────────────────────────
      { id: 'content-video-creator', group: 'Additional Video Content', name: 'Additional Creator-Led Videos',   desc: 'Extra creator-produced videos beyond your plan allowance.', tip: 'Additional creator-led video pieces on top of your plan, filmed by vetted creators in an authentic, social-first style. Priced per video.',                                           price: 195,  unit: '/video', oneTime: true, recurringOption: { price: 175, savePct: 10 },     onboardingStatus: 'full' },
      { id: 'content-video-studio',  group: 'Additional Video Content', name: 'Additional In-House Studio Assets', desc: 'Extra studio-produced assets on top of your plan.', tip: 'Additional polished assets produced in our studio, suited to brand and product content where higher production value matters. Priced per asset.',                                       priceLabel: 'From £995', price: 995, unit: '/asset', unitTip: 'One asset is a single finished studio-produced deliverable (for example, one edited photo, one short-form video, or one product render).', oneTime: true, recurringOption: { price: 895, savePct: 10 }, onboardingStatus: 'full' },
      // ── Content Rights & Access (rendered as sub-box chip group) ─────────
      { id: 'content-raw-footage',   group: 'Content Rights & Access',  name: 'Raw Footage Access',              desc: 'All unedited raw footage from your campaign, per production.', tip: 'We hand over the complete unedited footage from your shoot, so you can re-edit or repurpose it yourself. Priced per production.',                                  price: 495,  unit: 'one-time', oneTime: true,                onboardingStatus: 'full' },
      { id: 'content-rights-perp',   group: 'Content Rights & Access',  name: 'Perpetual',                          desc: 'Use the content on any channel, with no time limit.', tip: 'A perpetual licence to use the content across owned, paid, and earned channels with no expiry. The most flexible rights option.',   price: 595,  unit: 'one-time', oneTime: true,                subGroup: 'usage-rights',   onboardingStatus: 'full' },
      { id: 'content-rights-12m',    group: 'Content Rights & Access',  name: '12 Months',                          desc: 'A 12-month licence for paid and owned channel use.', tip: 'A rolling 12-month licence covering paid media placements and owned channel use. Renew or upgrade to perpetual later.',                               price: 345,  unit: '/mo',                                   subGroup: 'usage-rights',   onboardingStatus: 'full' },
      { id: 'content-rights-6m',     group: 'Content Rights & Access',  name: '6 Months',                           desc: 'A 6-month licence for paid and owned channel use.', tip: 'A rolling 6-month licence covering paid media placements and owned channel use. Best for short campaigns.',                                price: 195,  unit: '/mo',                                   subGroup: 'usage-rights',   onboardingStatus: 'full' },
      // ── Production Services ──────────────────────────────────────────────
      { id: 'content-rush',          group: 'Production Services',      name: 'Expedited Production: Rush Fee',  desc: 'Priority scheduling and faster turnaround for urgent campaigns.', tip: 'We move your production to the front of the queue and compress the timeline, for launches and time-sensitive campaigns.',                            priceLabel: 'From £995',   price: 995,  unit: 'one-time', oneTime: true, onboardingStatus: 'full' },
      { id: 'content-talent-emrg',   group: 'Production Services',      name: 'Emerging Talent',                 desc: 'Sourcing and on-set management of emerging creators.', tip: 'We source, contract, and manage up-and-coming creators and influencers for your shoot, a cost-effective way to add on-screen talent.',                     priceLabel: 'From £795',   price: 795,  unit: 'one-time', oneTime: true, subGroup: 'talent-casting', onboardingStatus: 'full' },
      { id: 'content-talent-pro',    group: 'Production Services',      name: 'Professional Talent',             desc: 'Sourcing and management of professional actors and presenters.', tip: 'We source and manage experienced actors, presenters, or on-screen talent, and handle contracting and on-set direction.',                       priceLabel: 'From £1,995', price: 1995, unit: 'one-time', oneTime: true, subGroup: 'talent-casting', onboardingStatus: 'full' },
      { id: 'content-shipment',      group: 'Production Services',      name: 'Product Shipment Coordination',   desc: 'We coordinate product delivery to creators or shoot locations.', tip: 'End-to-end handling of getting your product to creators, studios, or shoot locations on time, including tracking and logistics.',                    price: 295,  unit: '/campaign', unitTip: 'One campaign is a single coordinated shipment run, getting your product to the creators or shoot locations for one batch of content.', oneTime: true,                 onboardingStatus: 'full' },
      { id: 'content-talent-celeb',  group: 'Production Services',      name: 'Celebrity Talent',                desc: 'Sourcing and management of celebrity talent.', tip: 'We source celebrity talent and handle bespoke negotiation, scheduling, usage rights, and on-set support. Priced on request.',   priceLabel: 'Bespoke Pricing', custom: true,              subGroup: 'talent-casting', onboardingStatus: 'full' },
      // ── Maintenance & Optimisation ───────────────────────────────────────
      { id: 'content-maintain-std',  group: 'Maintenance & Optimisation', name: 'Standard Maintenance',          desc: 'Monthly refresh, minor copy updates, and asset repurposing.', tip: 'A monthly service that keeps your content current with light updates and repurposing of evergreen assets. Billed monthly.',                          price: 895,  unit: '/mo',                                   onboardingStatus: 'full', subGroup: 'content-maintenance' },
      { id: 'content-maintain-adv',  group: 'Maintenance & Optimisation', name: 'Advanced Maintenance',          desc: 'Monthly overhaul with A/B testing and content reworking.', tip: 'A deeper monthly service including A/B testing, performance analysis, and reworking of underperforming content. Billed monthly.',     price: 1495, unit: '/mo',                                   onboardingStatus: 'full', subGroup: 'content-maintenance' },
      { id: 'content-perf-opt',      group: 'Maintenance & Optimisation', name: 'Performance Optimisation Service', desc: 'Data-driven tuning of existing content to lift results.', tip: 'We analyse how your content performs and optimise it for better reach, engagement, and conversion. Billed monthly.',          priceLabel: 'From £295', price: 295, unit: '/mo',             onboardingStatus: 'full' },
      // ── Platform & Distribution ──────────────────────────────────────────
      { id: 'content-multilang',     group: 'Platform & Distribution',  name: 'Multi-Language Localisation',     desc: 'Translation and cultural adaptation, per video per language.', tip: 'We translate and culturally adapt your video content for new markets, a proper localisation rather than just subtitles. Priced per video per language.',                    priceLabel: 'From £195', price: 195, unit: '/video per language', onboardingStatus: 'full' },
      { id: 'content-platform-exp',  group: 'Platform & Distribution',  name: 'Platform Expansion',              desc: 'Setup and ongoing management for one more content platform.', tip: 'We set up and run distribution on an additional social or content platform beyond those in your plan. Billed monthly.',                price: 295,  unit: '/mo per channel',                       onboardingStatus: 'full' },
    ],
  },
  {
    id: 'motion',
    pricingUrl: 'https://www.gogorilla.com/pricing/3d-animation#plans',
    name: 'Photorealistic 3D Animation',
    desc: 'We combine photorealistic 3D animation and powerful storytelling to produce high-performing content optimised for paid advertising.',
    category: 'Creative',
    icon: 'assets/icons/motion.webp',
    oneTime: true,
    // 2026-06-11 (Nicole): production durations mirror the live pricing page.
    tierDuration: { starter: '1 to 2 weeks', grow: '2 to 3 weeks', scale: '3 to 4 weeks', enterprise: 'Custom' },
    waitlistTiers: ['starter', 'grow', 'scale', 'enterprise'],
    tierFeatures: {
      starter: ['15-30s animation', 'Template-based scenes', 'HD delivery'],
      grow:    ['60-90s animation', 'Custom 3D scenes', '4K delivery', '2 revision rounds'],
      scale:   ['Hero brand film', 'Full custom production', 'Broadcast-grade', 'Unlimited revisions'],
    },
    prices: { starter: 795,  grow: 1995, scale: 4995  },
    setupFees: {
      starter: 195,
      grow:    395,
      scale:   795,
    },
    tierDesc: {
      starter: 'DTC and B2B founders launching quickly with AI-first photoreal product ads for multiplatform testing',
      grow: 'DTC and B2B founders scaling brand-led photoreal ads with custom avatars, bespoke sound, and localisation',
      scale: 'DTC and B2B founders running high-stakes campaigns needing AI-first scale with professional finishing',
      enterprise: 'Established brands needing always-on AI-first 3D animation at broadcast fidelity across multiple campaigns',
    },
    incentives: [
      { type: 'built-in', label: 'Algorithmic Team Profit Sharing' },
      { type: 'optional', label: 'Performance Recognition Tips' },
      { type: 'add-on',  label: 'Outcome-Linked Success Bonus' },
    ],
    addons: [
      // ── Additional 3D Animation Content (exclusive chip sub-group) ──

      // ── Render & Delivery Upgrades ──
      // Render Quality Upgrade (exclusive chip sub-group) — QHD → 4K → 8K
      { id: 'm-render-qhd',    name: 'QHD',                              desc: 'QHD (1440p) render for high-quality digital and social.', tip: 'Upgrades your render to QHD (1440p) for crisp digital and social placements. Priced per video.',                        price: 195,  unit: '/video', oneTime: true, group: 'Render & Delivery Upgrades',    subGroup: '3d-render-quality', onboardingStatus: 'full' },
      { id: 'm-render-4k',     name: '4K',                               desc: '4K UHD render for broadcast, streaming, and premium digital.', tip: 'Upgrades your render to 4K UHD, suited to broadcast, streaming, and premium digital placements. Priced per video.',                   price: 395,  unit: '/video', oneTime: true, group: 'Render & Delivery Upgrades',    subGroup: '3d-render-quality', onboardingStatus: 'full' },
      { id: 'm-render-8k',     name: '8K',                               desc: '8K render for cinema and large-format displays.', tip: 'Upgrades your render to 8K for cinema, large-format displays, and ultra-premium campaigns. Priced per video.',                 price: 995,  unit: '/video', oneTime: true, group: 'Render & Delivery Upgrades',    subGroup: '3d-render-quality', onboardingStatus: 'full' },
      // Standalones
      { id: 'm-aspect-ratios', name: 'Additional Aspect Ratios',         desc: 'Extra aspect ratios for multi-platform delivery.', tip: 'Additional renders in other aspect ratios such as portrait, square, or ultrawide, so the animation fits every placement. Priced per ratio.',           price: 95,   unit: '/ratio', group: 'Render & Delivery Upgrades',    onboardingStatus: 'full' },
      { id: 'm-codec',         name: 'Advanced Codec Delivery',          desc: 'Delivery in broadcast codecs like ProRes or DNxHD.', tip: 'We deliver your animation in professional codecs such as ProRes or DNxHD for post-production and broadcast workflows. Priced per video.',                  price: 145,  unit: '/video', oneTime: true, group: 'Render & Delivery Upgrades',    onboardingStatus: 'full' },

      // ── Advanced VFX & Simulations ──
      { id: 'm-custom-vfx',    name: 'Custom VFX',                       desc: 'Bespoke visual effects tailored to your campaign.', tip: 'Custom effects built for your concept, from particles to energy and destruction. Scoped per project, priced from £495 per video.',          price: 495,  unit: '/video', oneTime: true, priceLabel: 'From £495',               group: 'Visual Effects', onboardingStatus: 'full', subGroup: '3d-vfx' },
      { id: 'm-fluid-dyn',     name: 'Fluid Dynamics',                   desc: 'Realistic liquid, smoke, and fire simulations.', tip: 'Adds high-end liquid, smoke, and fire simulation, suited to luxury and high-impact campaigns. Priced per video.',                  price: 345,  unit: '/video', oneTime: true, group: 'Visual Effects',   onboardingStatus: 'full', subGroup: '3d-vfx' },
      { id: 'm-particles',     name: 'Particle Systems',                  desc: 'Dynamic particle effects like dust, sparks, and glitter.', tip: 'Adds dynamic particle animation such as dust, sparks, glitter, and atmospheric effects. Priced per video.',                        price: 245,  unit: '/video', oneTime: true, group: 'Visual Effects',   onboardingStatus: 'full', subGroup: '3d-vfx' },
      { id: 'm-physics-anim',  name: 'Physics-Based Animations',         desc: 'Animation driven by real-world physics.', tip: 'Adds physically accurate motion, including rigid body, soft body, and constraint simulations, for believable movement. Priced per video.',         price: 245,  unit: '/video', oneTime: true, group: 'Visual Effects',   onboardingStatus: 'full', subGroup: '3d-vfx' },

      // ── Voice-Over & Sound Design ──
      // Voice-Over (exclusive chip sub-group)
      { id: 'm-vo-pro',        name: 'Professional Voice-Over',          desc: 'Studio-recorded professional voice-over in your language.', tip: 'Professional voice-over talent recorded in a studio, in your preferred language and style. Priced per video.',                 price: 145,  unit: '/video', oneTime: true, group: 'Voice-Over & Sound Design',    subGroup: '3d-voice-over',     onboardingStatus: 'full' },
      { id: 'm-vo-celeb',      name: 'Celebrity Voice-Over',             desc: 'Celebrity voice-over talent for your animation.', tip: 'Celebrity voice-over, priced on request based on the talent tier and usage rights you need.',               priceLabel: 'Bespoke Pricing',             group: 'Voice-Over & Sound Design',    subGroup: '3d-voice-over',     onboardingStatus: 'full' },
      // Standalones
      { id: 'm-music-comp',    name: 'Custom Music Composition',         desc: 'Original music composed for your animation.', tip: 'Bespoke music composed and produced specifically for your animation, rather than a stock track.', priceTip: 'Priced from £245 per video.',                                price: 245,  unit: '/video', oneTime: true, priceLabel: 'From £245',               group: 'Voice-Over & Sound Design', onboardingStatus: 'full' },
      { id: 'm-sound-design',  name: 'Advanced Sound Design',            desc: 'Full bespoke sound design, effects, and ambience.', tip: 'Complete sound design including effects, ambience, and stem delivery, for a polished, immersive result. Priced per video.',                           price: 145,  unit: '/video', oneTime: true, group: 'Voice-Over & Sound Design',    onboardingStatus: 'full' },

      // ── Maintenance & Iteration ──
      // Maintenance Plan (exclusive chip sub-group)
      { id: 'm-maint-std',     name: 'Standard Maintenance',             desc: 'One animation refresh a month, five-day turnaround.', tip: 'One refresh or iteration of your animation each month with a five-day turnaround. Billed monthly.',                               price: 695,  unit: '/mo',   group: 'Maintenance & Iteration',      subGroup: '3d-maintenance',    onboardingStatus: 'full' },
      { id: 'm-maint-adv',     name: 'Advanced Maintenance',             desc: 'Two refreshes a month with priority turnaround.', tip: 'Two animation refreshes each month with priority turnaround and a dedicated producer. Billed monthly.',                price: 1295, unit: '/mo',   group: 'Maintenance & Iteration',      subGroup: '3d-maintenance',    onboardingStatus: 'full' },
      // Standalone
      { id: 'm-perf-opt',      name: 'Performance Optimisation Service', desc: 'Ongoing tuning of your 3D assets for better results.', tip: 'We analyse and optimise your 3D animation assets for conversion and engagement over time.', priceTip: 'Priced from £395 a month.',        price: 395,  unit: '/mo',   priceLabel: 'From £395',               group: 'Maintenance & Iteration', onboardingStatus: 'full' },

      // ── Asset Licensing & Ownership ──
      // Extended Usage Rights (exclusive chip sub-group) — names and descs match Content Creation
      { id: 'm-eur-perpetual', name: 'Perpetual',                        desc: 'Use the animation on any channel, with no time limit.', tip: 'A perpetual licence to use your animation across owned, paid, and earned channels with no expiry. One-off fee.',   price: 795,  unit: 'one-time', oneTime: true, group: 'Asset Licensing & Ownership', subGroup: '3d-usage-rights', onboardingStatus: 'full' },
      { id: 'm-eur-6mo',       name: '6 Months',                         desc: 'A 6-month licence for paid and owned channel use.', tip: 'A rolling 6-month licence covering paid media and owned channel use. Billed monthly, best for short campaigns.',                               price: 295,  unit: '/mo',                   group: 'Asset Licensing & Ownership', subGroup: '3d-usage-rights', onboardingStatus: 'full' },
      { id: 'm-eur-12mo',      name: '12 Months',                        desc: 'A 12-month licence for paid and owned channel use.', tip: 'A rolling 12-month licence covering paid media and owned channel use. Billed monthly.',                              price: 495,  unit: '/mo',                   group: 'Asset Licensing & Ownership', subGroup: '3d-usage-rights', onboardingStatus: 'full' },
      // Standalones
      { id: 'm-asset-library', name: 'AI Brand Asset Library',           desc: 'A reusable 3D brand asset library in your visual identity.', tip: 'A custom library of 3D brand assets built in your visual identity, reusable across all future campaigns.', priceTip: 'Priced from £2,995.',  price: 2995, priceLabel: 'From £2,995', group: 'Asset Licensing & Ownership', onboardingStatus: 'full' },

      // ── Platform & Distribution ──
      // Multi-Language Localisation (exclusive chip sub-group) — chip names: Full, Text, Voice-Over
      { id: 'm-loc-full',      name: 'Full Localisation',                desc: 'Translation, voice-over, and on-screen text in one package.', tip: 'A complete localisation package combining translated on-screen text and localised voice-over. Priced per language.',                    price: 295,  unit: '/language', oneTime: true, group: 'Platform & Distribution', subGroup: '3d-localisation', onboardingStatus: 'full' },
      { id: 'm-loc-text',      name: 'Text Localisation',                desc: 'On-screen text and subtitles translated and integrated.', tip: 'We translate and rebuild your on-screen text and subtitles in the target language, integrated into the animation. Priced per language.',                          price: 95,   unit: '/language', oneTime: true, group: 'Platform & Distribution', subGroup: '3d-localisation', onboardingStatus: 'full' },
      { id: 'm-loc-vo',        name: 'Voice-Over',                       desc: 'Localised voice-over recorded in your target language.', tip: 'We record and mix a localised voice-over into your animation in the target language. Priced per language.',                 price: 195,  unit: '/language', oneTime: true, group: 'Platform & Distribution', subGroup: '3d-localisation', onboardingStatus: 'full' },
      // Standalones
      { id: 'm-ctv',           name: 'Connected TV (CTV) Optimisation',  desc: 'Delivery optimised for connected TV and OTT platforms.', tip: 'We adapt and optimise your animation for connected TV and OTT streaming placements. Billed monthly.',                    price: 495,  unit: '/mo', group: 'Platform & Distribution', onboardingStatus: 'full' },
      { id: 'm-platform-exp',  name: 'Platform Expansion',               desc: 'A distribution package for one more platform or channel.', tip: 'Distribution setup and management for an additional platform such as Pinterest or TikTok. Billed monthly.',               price: 295,  unit: '/mo', group: 'Platform & Distribution', onboardingStatus: 'full' },
    ],
  },
  // Dedicated Resources is split into two independent services so visitors can
  // pick BOTH Part-Time and Full-Time at the same time on the Talent step.
  // Each renders as its own card and tracks its own selection in state.
  {
    id: 'dedicated-pt',
    name: 'Dedicated Resources · Part-Time',
    desc: 'Access pre-vetted specialists part-time to increase capacity and fill skill gaps, with flexible scheduling and no long-term commitment or hiring overhead.',
    category: 'Talent',
    icon: 'assets/icons/perks.webp',
    oneTime: false,
    discountExcluded: true, // Staffing cost — excluded from multi-service bundle discount
    // Single-tier service, the in-card PT/FT toggle inside <DedicatedFlow /> is
    // suppressed automatically when only one tier is available.
    tiers: [
      { id: 'parttime', name: 'Part Time', blurb: 'Pre-vetted specialists from our talent pool, ready to start on a flexible day rate.' },
    ],
    tierFeatures: {
      parttime: ['Flexible day rates', 'Pre-vetted talent pool', 'Scale up or down month-to-month'],
    },
    // No `prices` keyed entry -> priceFor() returns { custom: true } at the
    // service level; per-role day rates handle the actual numbers.
    prices: {},
    roles: {
      // Each PT role declares its own explicit day-rate ladder (1 / 5 / 10 / custom-11+).
      // 2026-06-12 (Loom 41 1:36): role list matched 1:1 to Full-Time, the same
      // eight roles in the same order. SEO Specialist, Appointment Setter, and
      // Sales Representative retired. SDR, Lead Generation Expert, Account
      // Manager, and Executive Assistant added with rough day rates pending
      // Alexander's review ("fill it in with some rough day rates").
      parttime: [
        { id: 'pt-ai',           name: 'AI Implementation & Training Specialist', priceLabel: 'From £590/day', price: 590, unit: '/day', badge: 'recommended',
          dayRates: { d1: 590, d5: 530, d10: 520, custom: 500 } },
        { id: 'pt-fsm',          name: 'Full-Stack Marketer',      priceLabel: 'From £325/day', price: 325, unit: '/day', badge: 'popular',
          dayRates: { d1: 325, d5: 290, d10: 285, custom: 275 } },
        { id: 'pt-2d',           name: 'Graphic Designer',         priceLabel: 'From £325/day', price: 325, unit: '/day',
          dayRates: { d1: 325, d5: 290, d10: 285, custom: 275 } },
        { id: 'pt-video',        name: 'Video Editor', waitlist: true,             priceLabel: 'From £355/day', price: 355, unit: '/day',
          dayRates: { d1: 355, d5: 320, d10: 310, custom: 300 } },
        { id: 'pt-sdr',          name: 'Sales Development Representative', priceLabel: 'From £295/day', price: 295, unit: '/day',
          dayRates: { d1: 295, d5: 265, d10: 260, custom: 250 } },
        { id: 'pt-leadgen',      name: 'Lead Generation Expert',   priceLabel: 'From £265/day', price: 265, unit: '/day',
          dayRates: { d1: 265, d5: 240, d10: 235, custom: 225 } },
        { id: 'pt-am',           name: 'Account Manager',          priceLabel: 'From £345/day', price: 345, unit: '/day',
          dayRates: { d1: 345, d5: 310, d10: 305, custom: 295 } },
        { id: 'pt-exec',         name: 'Executive Assistant',      priceLabel: 'From £245/day', price: 245, unit: '/day',
          dayRates: { d1: 245, d5: 220, d10: 215, custom: 210 } },
      ],
    },
    // Part-Time has no addons, those (visa, buyout, IT equipment, etc.) only
    // make sense for embedded Full-Time hires, where they're shown.
    addons: [],
  },
  {
    id: 'dedicated-ft',
    name: 'Dedicated Resources · Full-Time',
    nameTip: 'As a general rule, the majority of these roles are hired from our talent network, but in most cases not as part of our existing team.',
    desc: 'Hire dedicated specialists working exclusively for you, with a talent buyout option available and a 90-day performance guarantee on every placement.',
    category: 'Talent',
    icon: 'assets/icons/perks.webp',
    oneTime: false,
    discountExcluded: true, // Staffing cost — excluded from multi-service bundle discount
    tiers: [
      { id: 'fulltime', name: 'Full Time', blurb: 'Full-Time roles are sourced through our comprehensive recruitment process.', badge: 'popular' },
    ],
    tierFeatures: {
      fulltime: ['Dedicated monthly subscription', 'Comprehensive recruitment', 'Long-term placement'],
    },
    prices: {},
    roles: {
      fulltime: [
        // 2026-06-10 (Loom 34): one fixed seniority ladder for every role,
        // £7.50 junior / £8.50 mid / £12 senior at the Philippines baseline,
        // scaled by location. Final price is confirmed post-shortlist.
        { id: 'ft-ai',           name: 'AI Implementation & Training Specialist', priceLabel: 'From £7.50/hour', hourlyFrom: 7.5, custom: true, badge: 'recommended' },
        { id: 'ft-mkt-ops',      name: 'Full-Stack Marketer',                 priceLabel: 'From £7.50/hour', hourlyFrom: 7.5, custom: true, badge: 'popular' },
        { id: 'ft-graphic',      name: 'Graphic Designer',                    priceLabel: 'From £7.50/hour', hourlyFrom: 7.5, custom: true },
        { id: 'ft-video',        name: 'Video Editor', waitlist: true,                        priceLabel: 'From £7.50/hour', hourlyFrom: 7.5, custom: true },
        { id: 'ft-sdr',          name: 'Sales Development Representative', priceLabel: 'From £7.50/hour', hourlyFrom: 7.5, custom: true },
        { id: 'ft-leadgen',      name: 'Lead Generation Expert',              priceLabel: 'From £7.50/hour', hourlyFrom: 7.5, custom: true },
        { id: 'ft-am',           name: 'Account Manager',                     priceLabel: 'From £7.50/hour', hourlyFrom: 7.5, custom: true },
        { id: 'ft-exec',         name: 'Executive Assistant',                 priceLabel: 'From £7.50/hour', hourlyFrom: 7.5, custom: true },
      ],
    },
    addons: [
      { id: 'it-equip',     group: 'Standard add-ons', name: 'IT Equipment Provisioning',       desc: 'Company-issued laptops and device management through our trusted third-party providers.', tip: 'All specialists use their own laptop as standard, so most clients never need this. If you would like company-issued hardware, we arrange the laptop, delivery, and ongoing device management through our trusted third-party providers. It runs for as long as the placement does. It is priced per specialist, and we confirm the exact number for your team on your scoping call.', priceTip: 'From £55 a month per specialist depending on the spec you choose.', priceLabel: 'From £55', price: 55, unit: '/mo per specialist' },
      { id: 'visa',         group: 'Enterprise support', name: 'Visa & Work Permit Sponsorship',  desc: 'Full support for UK visa and work permit sponsorship.', tip: 'We manage UK visa and work permit sponsorship for specialist hires end to end, including the application process and compliance guidance.', priceLabel: 'Bespoke Pricing', custom: true },
      { id: 'benefits',     group: 'Enterprise support', name: 'Enhanced Benefits Package',       desc: 'Premium health, dental, and vision cover to attract top talent.', tip: 'A premium benefits package, including top-tier private health, dental, and vision insurance, to help you attract and retain the best specialists.', priceLabel: 'From £295', price: 295, unit: '/mo' },
      { id: 'coworking',    group: 'Enterprise support', name: 'Office Space Access',      desc: 'A physical office or local co-working space for your specialist, arranged on request and charged per head per month.', tip: 'If you would like your specialist to work from a physical office rather than remotely, we source desk or office space, or arrange a local co-working membership, in the UK, South Africa, or the Philippines.', priceTip: 'Indicative cost is around £1,000 per head a month in the UK, £450 in South Africa, and £300 in the Philippines, confirmed on request based on location.', priceLabel: 'From £300', price: 300, unit: '/mo' },
    ],
  },
  {
    id: 'whitelabel',
    name: 'White Label Services',
    desc: 'We deliver the client work under your brand whilst you keep the relationship, so you grow without hiring.',
    category: 'Agency',
    icon: 'assets/icons/agency.webp',
    freeStarter: true,
    commitMonths: 3,
    // Tier-level waiting list: these tier ids render as "Waiting list" instead
    // of a price, can still be added to a quote (so we capture demand in
    // Airtable), but contribute £0 to the calculator total.
    // 2026-05-22: Airtable has only Grow (£95 Fulfilment + Client Management)
    // and Enterprise (Contact for Pricing) as active WL tiers. Starter (free
    // reseller catalogue) and the legacy Scale tier (£255, archived April
    // 2026) are hidden from the calculator entirely via the per-service
    // `tiers` override below. Enterprise stays visible but flagged waitlist.
    tiers: [
      { id: 'grow',       name: 'Grow',       blurb: 'For growing agencies who want GoGorilla.com to manage all day-to-day client communication under their brand' },
      { id: 'enterprise', name: 'Enterprise', blurb: 'For large agencies needing a dedicated elite team, custom reporting, and global support.', isEnterprise: true },
    ],
    waitlistTiers: [],
    tierFeatures: {
      starter: ['Reseller fulfilment', 'Unbranded deliverables', 'Self-serve portal'],
      grow:    ['Fulfilment + client mgmt', 'White-label reporting', 'Dedicated success mgr'],
      scale:   ['Full white-label agency', 'Co-branded proposals', 'Priority SLAs'],
    },
    prices: {
      starter: { '3': 0,   '6': 0,   '12': 0   },
      grow:    { '3': 95,  '6': 95,  '12': 95  },
      scale:   { '3': 255, '6': 255, '12': 255 },
    },
    addons: [],
  },
  {
    id: 'founders-portal',
    pricingUrl: 'https://www.gogorilla.com/pricing/founders-portal#plans',
    name: 'Founders Portal',
    desc: 'Get matched with relevant investors and run your raise with deal-flow tracking and fundraising tools.',
    category: 'Capital',
    icon: 'assets/icons/algorithmic.webp',
    freeStarter: true,
    commitMonths: 3,
    tiers: [
      { id: 'starter', name: 'Founders Free' },
      { id: 'grow',    name: 'Founders Pro' },
      { id: 'scale',   name: 'Founders Pro+', badge: 'featured' },
    ],
    waitlistTiers: [],
    tierFeatures: {
      starter: ['Up to 50 investors from database', 'Profiles without contact details', 'Standard profile', 'Basic search & filters'],
      grow:    ['7,500+ verified investors', 'Enriched contacts with email and LinkedIn', 'Pitch deck & video upload', 'Advanced search & analytics'],
      scale:   ['Everything in Pro', 'White-glove intros', 'Dedicated capital associate', 'Referral rewards'],
    },
    prices: {
      starter: { '3': 0,   '6': 0,   '12': 0   },
      grow:    { '3': 245, '6': 195, '12': 95  },
      scale:   { '3': 495, '6': 495, '12': 495 },
    },
    addons: [],
  },
  {
    id: 'fundraising',
    pricingUrl: 'https://www.gogorilla.com/pricing/fundraising-support#plans',
    name: 'Fundraising Support',
    desc: 'Investor-ready materials, targeted investor research, and pitch coaching to help you close your round.',
    category: 'Capital',
    icon: 'assets/icons/partnership.webp',
    oneTime: true,
    waitlistTiers: [],
    tierFeatures: {
      starter: ['Pitch deck + one-pager', 'Investor research', 'Email intro templates'],
      grow:    ['Full data room', 'Financial model', 'Pitch coaching (3 sessions)', 'Investor outreach list'],
      scale:   ['End-to-end campaign', 'Live investor meetings', 'Roadshow coordination', 'Negotiation support'],
    },
    prices: { starter: 3495, grow: 6995, scale: 14995 },
    tierDesc: {
      starter: 'Pre-seed startups preparing their first raise who need professional materials and self-service outreach.',
      grow: 'Seed-stage startups raising £250k-£2M who need full support across materials, research, and strategy.',
      scale: 'Series A startups raising £2M+ who need premium materials, deep investor research, and hands-on strategy.',
      enterprise: 'Later-stage or complex raises that need a fully bespoke, hands-on fundraising programme.',
    },
    addons: [
      // ── Fundraising Materials ──
      { id: 'deck-refresh',group:'Fundraising Materials',            name: 'Pitch Deck Refresh',               desc: 'A full rewrite and redesign of your existing deck for your next round.', tip: 'We rework the narrative, structure, and visuals of your current deck so it is round-ready, rather than building a new deck from scratch.',        price: 2495, unit: 'one-time', oneTime: true, discountable: true },
      { id: 'model-ext',  group: 'Fundraising Materials',            name: 'Extended Financial Model (5-7 Years)', desc: 'A long-horizon model with cohort tracking and unit economics built up.', tip: 'A deeper five to seven year model with cohort tracking and bottom-up unit economics, built fresh or rebuilt from your existing model, suited to Series A and later raises.',     price: 2995, unit: 'one-time', oneTime: true, discountable: true },
      { id: 'investor-up',group: 'Fundraising Materials',            name: 'Investor Update Template',         desc: 'A branded monthly or quarterly update template, with your first one drafted.', tip: 'A reusable investor-update format plus a first drafted edition, so your ongoing updates stay consistent and quick to send.',    price: 495, unit: 'one-time', oneTime: true, discountable: true },
      // ── Investor Outreach & Research ──
      { id: 'research50', group: 'Investor Outreach & Research',     name: 'Extended Investor Research (+50)', desc: 'Another 50 fit-matched investors, each with thesis and contact details.', tip: 'Adds 50 investors on top of your plan\'s research, matched to your stage and sector, each with verified contacts and a note on why they fit.',      price: 2995, unit: 'one-time', oneTime: true, discountable: true },
      { id: 'crm-setup',  group: 'Investor Outreach & Research',     name: 'Investor CRM Setup',               desc: 'A ready-to-use investor pipeline in HubSpot or Affinity.', tip: 'We build your outreach pipeline with stages, templates, and reporting in HubSpot or Affinity, so you can track every investor conversation from day one.', price:  995, unit: 'one-time', oneTime: true, discountable: true },
      { id: 'li-optim',   group: 'Investor Outreach & Research',     name: 'LinkedIn Profile Optimisation',    desc: 'Founder and executive profile rewrites tuned for investor discovery.', tip: 'We rewrite your founder and leadership LinkedIn profiles so they signal credibility and traction when investors look you up.',  price:  495, unit: 'one-time', oneTime: true, discountable: true },
      // ── Pitch Coaching & Preparation ──
      { id: 'coach',      group: 'Pitch Coaching & Preparation',     name: 'Additional Pitch Coaching Session', desc: 'A one-to-one rehearsal with live feedback from a former investor.', tip: 'A 60-minute session where a former investor hears your pitch and gives direct, practical feedback on your delivery and story.',          price:  995, unit: '/session',  oneTime: true, discountable: true },
      { id: 'meet-prep',  group: 'Pitch Coaching & Preparation',     name: 'Investor Meeting Preparation',     desc: 'A pre-meeting brief, a question-and-answer drill, and a debrief afterwards.', tip: 'We prepare you for a specific meeting with a tailored brief, rehearse the likely questions, then debrief to sharpen your follow-up.',         price:  495, unit: '/meeting', oneTime: true, discountable: true },
      // ── Grants & Non-Dilutive Funding ──
      { id: 'grant-app',  group: 'Grants & Non-Dilutive Funding',    name: 'Additional Grant Application',     desc: 'A full grant application, written, submitted, and followed up.', tip: 'We handle one complete grant application end to end, from drafting to submission and follow-up, so you can pursue non-dilutive funding without the admin.',              price: 2995, unit: '/application', oneTime: true, discountable: true },
      // ── Crowdfunding ──
      { id: 'partner-intros', group: 'Grants & Non-Dilutive Funding', name: 'Partner Introductions', desc: 'Warm introductions to our vetted funding and research partners.', tip: 'Free, warm introductions to our trusted partner network, covering R&D tax credit specialists, SEIS and EIS compliance, innovation loans including Innovate UK routes, customer research partners, and mock investor panels. Introductions are free, with any partner fees agreed directly with them.', price: 0, priceLabel: 'Free', free: true },
      { id: 'crowd-mkt',  group: 'Crowdfunding',                     name: 'Crowdfunding Marketing Support',   desc: 'Pre-launch audience building, campaign creative, and paid promotion.', tip: 'A full marketing push for your crowdfunding raise, covering pre-launch audience build, campaign assets, and paid promotion to drive pledges.',   price: 8995, unit: 'one-time', oneTime: true, discountable: true },
      { id: 'crowd-strat',group: 'Crowdfunding',                     name: 'Crowdfunding Campaign Strategy',   desc: 'Goal setting, reward tiers, narrative, and a launch plan.', tip: 'We shape your crowdfunding campaign, setting the target, designing reward tiers, and planning the launch sequence to build early momentum.',          price: 1995, unit: 'one-time', oneTime: true, discountable: true },
      // ── Strategic Advisory ──
      { id: 'termsheet',  group: 'Strategic Advisory',               name: 'Term Sheet Review',                desc: 'A line-by-line review flagging risks and founder-friendly alternatives.', tip: 'We go through your term sheet clause by clause, flag anything unusual or risky, and suggest founder-friendly alternatives to take back to investors.',price:  995, unit: '/term sheet', oneTime: true, discountable: true },
      { id: 'captable',   group: 'Strategic Advisory',               name: 'Cap Table Modelling',              desc: 'Dilution, option pool, and round-by-round scenarios modelled out.', tip: 'We model how your cap table evolves across rounds, including dilution and option-pool impact, so you can see the effect of different deal terms.',            price: 1495, unit: 'one-time',  oneTime: true, discountable: true },
      { id: 'dataroom',   group: 'Strategic Advisory',               name: 'Data Room Audit',                  desc: 'A review of your data room\'s structure, gaps, and narrative before diligence.', tip: 'We audit your data room before investors see it, flagging missing documents and weak spots so diligence runs smoothly.',    price:  995, unit: 'one-time',  oneTime: true, discountable: true },
      // ── Market Analysis ──
      { id: 'market-size',group: 'Market Analysis',                  name: 'Market Sizing Analysis',           desc: 'A defensible TAM, SAM, and SOM, built bottom-up and top-down.', tip: 'We size your market both bottom-up and top-down using credible sources, giving you figures that stand up to investor scrutiny.',       price: 2495, unit: 'one-time', oneTime: true, discountable: true },
      { id: 'competitor', group: 'Market Analysis',                  name: 'Competitive Analysis',             desc: 'A positioning map plus a feature and pricing teardown of your category.', tip: 'We map your competitors and break down their features and pricing, so you can position clearly and answer the "why you" question.', price: 2495, unit: 'one-time', oneTime: true, discountable: true },
      // ── Ongoing Support ──
      { id: 'fundraise-ret', group: 'Ongoing Support',               name: 'Monthly Fundraising Support',      desc: 'A named capital associate managing your raise month to month.', tip: 'A dedicated associate who runs your raise day to day, from outreach and scheduling to materials and investor follow-up. Billed monthly.',     price: 1995, unit: '/mo', discountable: true },
      { id: 'partner-onb',   group: 'Ongoing Support',               name: 'Partner Onboarding Package',       desc: 'White-glove onboarding across the GoGorilla.com partner network.', tip: 'We set you up across the GoGorilla.com partner network, handling introductions and account setup so you can start using every benefit quickly.',         price:  495, unit: 'one-time', oneTime: true, discountable: true },
    ],
  },
  {
    id: 'investor-portal',
    pricingUrl: 'https://www.gogorilla.com/pricing/investor-portal',
    name: 'Investor Portal',
    desc: 'Curated deal flow, due-diligence support, and partner benefits to help you back stronger companies.',
    category: 'Capital',
    icon: 'assets/icons/algorithmic.webp',
    freeStarter: true,
    commitMonths: 3,
    tiers: [
      { id: 'starter', name: 'Network Free' },
      { id: 'grow',    name: 'Partner Pro' },
      { id: 'scale',   name: 'Partner Pro+', badge: 'featured' },
    ],
    // 2026-06-08 (Loom 30 2:39-3:01): Investor Portal is not live yet. All
    // tiers render on the waiting list (amber, no price, £0 to the total)
    // and every add-on below is flagged onboardingStatus 'full' so the whole
    // service reads as a waiting list, matching Content Creation.
    waitlistTiers: ['starter', 'grow', 'scale'],
    // 2026-06-08 (Loom 30 30.6): card descriptions ("Who is it for"), taken
    // from the pricing page, so the cards read like Content Creation and 3D
    // Animation.
    tierDesc: {
      starter: 'For angels, VCs, and private equity investors who want global deal flow plus partnership benefits for their portfolio companies',
      grow:    'For active investors who want weekly thesis-matched deal flow, AI matching, co-investment access, and partnership benefits',
      scale:   'Concierge buy-side deal origination for private equity firms, family offices, corporate acquirers, and search funds with a defined global acquisition thesis',
    },
    tierFeatures: {
      starter: ['Unlimited due diligence assessments (£2,500-£5,000 each)', 'Discounts on GoGorilla.com services for portfolio companies (confirmed at proposal)', 'Access Trusted Partner Network (SEIS/EIS, legal, M&A, fractional execs)', 'Curated founder directory (predominantly UK & US-based)', 'Monthly general-interest deal flow digest', 'Optional reciprocal listing in Founders Portal'],
      grow:    ['Weekly thesis-matched deal flow digest', 'Smart Deal Matching (AI-powered by thesis, sector, stage, cheque size)', 'Exclusive Co-investment Network Access', 'Advanced search and filtering of founder directory', 'Personal opportunity tracker', 'Priority reciprocal listing in Founders Portal', 'Quarterly investor community calls'],
      scale:   ['Acquisition thesis calibration session', 'Dedicated buy-side deal origination', 'Vendor-advisor relationships and mandate access', 'Curated opportunity briefs with thesis match', 'Warm introductions and meeting facilitation', 'Live 12-month mandate with continuous sourcing', 'Named mandate lead with quarterly reviews', 'Partner firm introductions (legal, tax, banking)', 'Success-fee engagement, no upfront cost'],
    },
    prices: {
      starter: { '3': 0,    '6': 0,    '12': 0    },
      grow:    { '3': 195,  '6': 145,  '12': 95   },
      scale:   { '3': null, '6': null, '12': null },
    },
    addons: [
      { id: 'investor-dinner', name: 'Investor Dinner Hosting',         desc: 'A hosted investor dinner, organised and run end to end.', tip: 'We plan and host a private investor dinner on your behalf, from venue and guest list to running the evening, so you build relationships in the right setting.', priceTip: 'Priced from £4,995 per event.', price: 4995, priceLabel: 'From £4,995', unit: '/event', oneTime: true, onboardingStatus: 'full' },
      { id: 'sector-research',  name: 'Bespoke Sector Research',          desc: 'An expert shortlist of 10 to 15 thesis-matched investors.', tip: 'We research your target sector and build a shortlist of 10 to 15 investors matched to your thesis, with the context you need to approach them.', priceTip: 'Priced from £2,995 per project.', price: 2995, priceLabel: 'From £2,995', unit: '/project', unitTip: 'One project is a single bespoke research brief on one defined sector or thesis, delivered as one shortlist of 10 to 15 matched investors.', oneTime: true, onboardingStatus: 'full' },
      { id: 'portfolio-pod',    name: 'Portfolio Marketing Pod',          desc: 'A dedicated marketing pod for one portfolio company, billed monthly.', tip: 'A dedicated marketing team for one of your portfolio companies. Starts with a 60 to 90 minute strategy call and a written assessment.', priceTip: 'Priced from £2,495 a month per company.', price: 2495, priceLabel: 'From £2,495', unit: '/mo', onboardingStatus: 'full' },
      { id: 'premium-dd',       name: 'Premium Due Diligence Report',     desc: 'A premium marketing due-diligence report on a target.', tip: 'A detailed marketing due-diligence report on a potential investment, covering positioning, channels, and growth potential, to strengthen your diligence.', priceTip: 'Priced from £995 per report.', price: 995, priceLabel: 'From £995', unit: '/report', oneTime: true, onboardingStatus: 'full' },
    ],
  },
];

const FAQS = [
  { q: 'How does the instant quote work?',
    a: "Pick your client type, tier each service to the level you need, and the summary updates live. The number you see is the monthly price you'd pay based on your commitment, no hidden setup fees, no retainers, no surprises." },
  { q: "What if I'm an agency or reseller?",
    a: 'Pick "Agency / Reseller" as your client type and a 40% white-label discount applies automatically across every service. You can also apply any partner code on top.' },
  { q: 'Can I change service tiers mid-contract?',
    a: "Yes. Upgrade at any time by topping up the difference. Downgrades take effect at the start of the next billing period." },
  { q: "What happens after I pick 'I'm ready to sign up'?",
    a: "You'll be handed to Stripe Checkout with exactly the scope you built here, same price, same services. Onboarding kicks off within 48 hours with your GorillaAudit® blueprint." },
  { q: 'Do you work with companies outside the UK?',
    a: 'Yes. Pricing shown is GBP but we invoice in USD, EUR, or AUD on request. Our pods cover UK, US, and APAC timezones with tax-compliant entities across those regions.' },
];


// ── ADD-ON AVAILABILITY MATRIX ─────────────────────────────────────────────
// Defines which add-ons are available per (service, tier) and what price
// to show for each commitment period.
//
// Value per tier key:
//   false    → Not Available, hide this add-on entirely for this tier
//   'custom' → Show with "Bespoke Pricing" label
//   'inc'    → Show as Included in plan (free, treated as £0 in totals)
//   true     → Show at the default price defined in the service's addons array
//   <number> → Fixed price for all commitment periods (overrides default)
//   { '3': n, '6': n, '12': n } → Per-commitment price override
//              (omit a key to fall back to the nearest provided value)
//
// Services not listed here pass through all add-ons unchanged (no filtering).
const ADDON_MATRIX = {
  // Values sourced from Airtable base appRgD9IZC7YHxw1d (Services table).
  // Each entry is per-tier; the value is:
  //   false        → Not Available (hidden)
  //   true         → Available at the addon's default price
  //   <number>     → Fixed price override (all commitment periods)
  //   { '3':n, '6':n, '12':n } → Per-commitment price
  //   'custom'     → Custom / Contact us
  //   'inc'        → Included free in the plan

  // ── Paid Advertising ───────────────────────────────────────────────────────
  // 2026-06-08 (Loom 31 00:59): Paid Advertising Integration (Lite) is £195/mo,
  // and shown as Included (free) on the Scale and Enterprise plans. Only this
  // add-on is overridden here; every other SDG add-on passes through unchanged.
  'sales': {
    'ads-lite': { starter: 195, grow: 195, scale: 'inc', enterprise: 'inc' },
  },
  'paid-ads': {
    //                              Starter                              Grow                                  Scale                               Enterprise
    'meta-retarget':    { starter: 195,                              grow: 195,                              scale: 195,                              enterprise: 'custom'                         },
    'google-display':   { starter: { '3': 165, '6': 135, '12':  95 }, grow: false,                              scale: { '3': 165, '6': 135, '12':  95 }, enterprise: 'custom'                         },
    'google-shop':      { starter: { '3': 165, '6': 135, '12':  95 }, grow: false,                              scale: { '3': 165, '6': 135, '12':  95 }, enterprise: 'custom'                         },
    'linkedin-retarget':{ starter: 195,                              grow: 195,                              scale: 195,                              enterprise: 'custom'                         },
    'lp-build':         { starter: 905,                                grow: 905,                                scale: 905,                                enterprise: 'custom'                         },  // 2026-06-12 (review): flat £905, AI-assisted builds
    'landing':          { starter: { '3': 245, '6': 205, '12': 145 }, grow: { '3': 245, '6': 205, '12': 145 }, scale: { '3': 245, '6': 205, '12': 145 }, enterprise: 'custom'                         },
    'cro-prog':         { starter: { '3': 245, '6': 205, '12': 145 }, grow: { '3': 675, '6': 555, '12': 395 }, scale: { '3': 995, '6': 835, '12': 595 }, enterprise: 'custom'                         },
    'call-track':       { starter: { '3': 165, '6': 135, '12':  95 }, grow: false,                              scale: { '3': 165, '6': 135, '12':  95 }, enterprise: 'inc'                            },
    'dw-setup':         { starter: false,                              grow: false,                              scale: 495,                                enterprise: 595                              },
    'dw-ongoing':       { starter: false,                              grow: false,                              scale: 95,                                 enterprise: false                            },
    'zapier':           { starter: 95,                               grow: 95,                               scale: 95,                               enterprise: 95 },  // 2026-06-12 (Loom 44 7:07): flat £95/mo
  },

  // ── Social Media Management ────────────────────────────────────────────────
  'smm': {
    //                           Starter                              Grow                                    Scale                               Enterprise
    'community':  { starter: false,                              grow: { '3': 695, '6': 595, '12': 495 }, scale: { '3': 695, '6': 595, '12': 495 }, enterprise: { '3': 695, '6': 595, '12': 495 } },  // 2026-06-18 (Loom 48): anchored at £495
    'linkedin-mgmt': { starter: false,                            grow: { '3': 595, '6': 495, '12': 395 }, scale: { '3': 595, '6': 495, '12': 395 }, enterprise: { '3': 595, '6': 495, '12': 395 } },  // mirrors community
    'post-boost': { starter: { '3': 495, '6': 415, '12': 295 }, grow: { '3': 495, '6': 415, '12': 295 }, scale: { '3': 495, '6': 415, '12': 295 }, enterprise: { '3': 495, '6': 415, '12': 295 } },
    'showcase':   { starter: false,                              grow: false,                              scale: false,                             enterprise: 295                                },
    'dw-setup':   { starter: false,                              grow: false,                              scale: false,                             enterprise: true                               },
    // New addons, Airtable sourced
    'additional-reports': { starter: { '3': 135, '6': 105, '12':  75 }, grow: { '3': 135, '6': 105, '12':  75 }, scale: { '3': 135, '6': 105, '12':  75 }, enterprise: false                                   },
    'insta-story':        { starter: { '3':  85, '6':  65, '12':  45 }, grow: { '3': 115, '6':  95, '12':  65 }, scale: { '3': 145, '6': 115, '12':  85 }, enterprise: { '3': 235, '6': 195, '12': 135 }        },
    'custom-highlights':  { starter: { '3': 495, '6': 395, '12': 295 }, grow: { '3': 495, '6': 395, '12': 295 }, scale: { '3': 495, '6': 395, '12': 295 }, enterprise: { '3': 495, '6': 395, '12': 295 } },  // 2026-06-12 (review): ladder anchored at the £295 12-month RRP
    'extra-lang':         { starter: false,                              grow: 395,                                scale: 395,                               enterprise: 395                                      },
    'extra-channel':      { starter: false,                              grow: { '3': 295, '6': 245, '12': 195 }, scale: { '3': 295, '6': 245, '12': 195 }, enterprise: { '3': 295, '6': 245, '12': 195 }        },
    'influencer':         { starter: false,                              grow: { '3': 995, '6': 845, '12': 795 }, scale: { '3': 995, '6': 845, '12': 795 }, enterprise: { '3': 995, '6': 845, '12': 795 }        },
  },

  // ── Email Marketing ────────────────────────────────────────────────────────
  'email': {
    //                                Starter                              Grow                                  Scale                                 Enterprise
    'platform-migration': { starter: false,                              grow: false,                              scale: { '3': 135, '6': 105, '12':  75 }, enterprise: { '3': 135, '6': 105, '12':  75 } },
    'sms':                { starter: false,                              grow: false,                              scale: { '3': 595, '6': 505, '12': 355 }, enterprise: { '3': 595, '6': 505, '12': 355 } },
    'deliverability-audit': { starter: 245,                             grow: 195,                                scale: 145,                               enterprise: 95                                  },
    'campaign-pack':      { starter: true,                              grow: true,                               scale: true,                              enterprise: true                                },
    'landing':            { starter: { '3': 245, '6': 205, '12': 145 }, grow: { '3': 245, '6': 205, '12': 145 }, scale: { '3': 245, '6': 205, '12': 145 }, enterprise: 'custom'                             },
    'adv-segmentation':   { starter: false,                             grow: false,                              scale: 475,                               enterprise: 475                                 },
    'zapier':             { starter: 95,                               grow: 95,                               scale: 95,                               enterprise: 95 },  // 2026-06-12 (Loom 44 7:07): flat £95/mo
    'dw-setup':           { starter: false,                              grow: false,                              scale: false,                             enterprise: true                                },
    'dw-ongoing':         { starter: false,                              grow: false,                              scale: false,                             enterprise: false                               },
    'additional-reports': { starter: false,                              grow: false,                              scale: false,                             enterprise: false                               },
  },

  // ── Content Creation ─────────────────────────────────────────────────────
  'content': {
    //                                 Starter  Grow    Scale   Enterprise
    'content-raw-footage':  { starter: 495,     grow: 495,     scale: 495,     enterprise: 'inc' },
    'content-rights-perp':  { starter: 595,     grow: 595,     scale: 595,     enterprise: 'inc' },
    'content-rights-12m':   { starter: 345,     grow: 345,     scale: 345,     enterprise: 'inc' },
    'content-rights-6m':    { starter: 195,     grow: 195,     scale: 195,     enterprise: 'inc' },
  },

  // ── Photorealistic 3D Animation ────────────────────────────────────────────
  // No per-tier add-on restrictions. An entry here (even empty) keeps the add-ons
  // disclosure visible on every tier, including Enterprise. Add-ons keep their
  // normal list prices across all tiers.
  motion: {},

  // ── Sales & Demand Generation ──────────────────────────────────────────────
  'sales': {
    //                          Starter     Grow        Scale       Enterprise
    // 2026-06-11 (Loom 38 item 6): on Enterprise only Full reads as included,
    // with custom scope, and Lite reads Included in Full (greyed, no sidebar
    // line). Neither switches to Free.
    'ads-lite':         { starter: true,     grow: true,     scale: 'inc',    enterprise: 'inc-in-full'    },
    'native-crm':       { starter: true,     grow: true,     scale: true,     enterprise: 'inc'            },
    'ads-full':         { starter: false,     grow: false,     scale: false,    enterprise: 'inc'    },
    'tech-signals':     { starter: { '3': 225, '6': 145, '12': 145 }, grow: { '3': 225, '6': 145, '12': 145 }, scale: 'inc', enterprise: 'inc' },
    'hiring-signals':   { starter: true, grow: true, scale: 'inc', enterprise: 'inc' },
    'financial-signals':{ starter: true, grow: true, scale: 'inc', enterprise: 'inc' },
    'gorilla-signals':  { starter: true, grow: true, scale: true, enterprise: 'inc' },
    'custom-signals':   { starter: false, grow: false, scale: true, enterprise: 'inc' },
  },
};

// Internal: resolve a single tier slot to { available, price?, custom?, included? }.
function _resolveAddonTierEntry(entry, commitId) {
  if (entry === false || entry === undefined) return { available: false };
  if (entry === 'custom') return { available: true, custom: true };
  if (entry === 'inc')    return { available: true, included: true };
  // 2026-06-11 (Loom 38 item 6): two Enterprise-flavoured included states.
  // 'inc-custom'  reads "Included (custom scope)" on the card and sidebar.
  // 'inc-in-full' greys the card as "Included in Full" with no sidebar line,
  // for a Lite add-on whose Full sibling already covers it. Neither is Free.
  if (entry === 'inc-custom')  return { available: true, included: true, includedLabel: 'Included (custom scope)' };
  if (entry === 'inc-in-full') return { available: true, includedInFull: true };
  if (entry === true)     return { available: true };
  if (typeof entry === 'number') return { available: true, price: entry, committed: false };
  if (typeof entry === 'object') {
    const cid = String(commitId || '12');
    const price = entry[cid] != null ? entry[cid]
                : entry['12'] != null ? entry['12']
                : entry['6']  != null ? entry['6']
                : entry['3'];
    return price != null ? { available: true, price, committed: true } : { available: true };
  }
  return { available: true };
}

// Returns a filtered + price-adjusted copy of service.addons for the
// given (tierId, commitId) context.
//   • Add-ons marked Not Available for this tier are removed.
//   • Prices are updated to the correct value for the commitment period.
//   • Included add-ons get free:true + included:true (£0, non-selectable).
//   • Custom add-ons get custom:true.
// Services without an ADDON_MATRIX entry are returned unchanged.
// 2026-06-18 (Loom): the commitment discount applies to add-ons too, not just
// the plans. Add-on prices (matrix flat numbers and base definitions) are the
// 12-month rate, the -40% anchor. Shorter commitments pay more, 6 months at
// -20% and 3 months at the full base, so we scale flat (non-commit-keyed)
// prices up. Commit-keyed matrix entries already carry their own ladder, so
// they are left alone (committed flag). Free / included / custom and the two
// specially-computed add-ons (CLB, Overseas Calling) are never scaled here.
function _addonCommitScaleEligible(addon, price) {
  return typeof price === 'number' && price > 0 && addon
    && !addon.free && !addon.included && !addon.includedInFull && !addon.custom
    && addon.id !== 'premium-sourcing' && addon.id !== 'overseas-calling';
}
function _scaleFlatPrice(addon, price, commitId) {
  if (!_addonCommitScaleEligible(addon, price)) return price;
  const cid = String(commitId || '12');
  const factor = cid === '3' ? (1 / 0.6) : cid === '6' ? (0.8 / 0.6) : 1;
  if (factor === 1) return price;
  const v = price * factor;
  const r5 = (window.ggRound5 || ((x) => Math.round(x / 5) * 5));
  return price >= 10 ? r5(v) : Math.round(v * 100) / 100;
}
function _scaleFlatAddon(addon, commitId) {
  const p = _scaleFlatPrice(addon, addon.price, commitId);
  return p === addon.price ? addon : { ...addon, price: p };
}
function getAddonsForContext(service, tierId, commitId, channels, leads) {
  if (!service || !Array.isArray(service.addons)) return [];
  const svcMatrix = ADDON_MATRIX[service.id];
  if (!svcMatrix) return service.addons.map(a => _scaleFlatAddon(a, commitId)); // no matrix → passthrough (commit-scaled)

  const result = [];
  for (const addon of service.addons) {
    const tierMap = svcMatrix[addon.id];
    if (tierMap === undefined) {
      result.push(_scaleFlatAddon(addon, commitId)); // addon not in matrix → commit-scaled
      continue;
    }
    // 2026-06-11: standalone add-ons in a tier-less context (no plan chosen)
    // price at their base data.jsx rate instead of vanishing because the
    // matrix has no row for an empty tier.
    if (!tierId && addon.standalone) {
      result.push(addon);
      continue;
    }
    const r = _resolveAddonTierEntry(tierMap[tierId], commitId);
    if (!r.available) continue;                    // Not Available → skip

    // 2026-06-25 (Nicole / Loom 59): Web Intent is volume-priced. It de-anonymises and
    // enriches the client's monthly lead volume, so it tracks the lead-volume box and its
    // price scales from a floor as that volume rises. Defaults to the floor when no volume
    // is set yet. Starting rate, easy to tune: floor + £100 per 1,000 prospects above 1,250.
    if (addon.id === 'gorilla-signals' && !r.included && !r.custom) {
      const _lv = Math.max(0, Number(leads) || 0);
      const _BASE = 295, _PER_K = 100, _ANCHOR = 1250;
      const _r5 = (window.ggRound5 || ((x) => Math.round(x / 5) * 5));
      const _price = _BASE + _r5(_PER_K * Math.max(0, _lv - _ANCHOR) / 1000);
      result.push({ ...addon, price: _price, priceLabel: 'From £' + _BASE + ' /mo', volumePriced: true });
      continue;
    }

    if (r.custom) {
      result.push({ ...addon, custom: true, price: 0, priceLabel: null });
    } else if (r.includedInFull) {
      result.push({ ...addon, includedInFull: true, free: true, price: 0, priceLabel: null });
    } else if (r.included) {
      result.push({ ...addon, free: true, price: 0, priceLabel: null, included: true, ...(r.includedLabel ? { includedLabel: r.includedLabel } : {}) });
    } else if (r.price != null) {
      result.push({ ...addon, price: r.committed ? r.price : _scaleFlatPrice(addon, r.price, commitId), priceLabel: null });
    } else {
      result.push(_scaleFlatAddon(addon, commitId)); // true → default price, commit-scaled
    }
  }
  // 2026-06-08 (Loom 31 03:47): on Paid Advertising, when the matching ad
  // channel is selected, that channel's retargeting add-on is already covered,
  // so it shows as Included (free, £0 to the total) rather than an extra charge.
  if (service.id === 'paid-ads' && Array.isArray(channels) && channels.length) {
    const _chPair = { 'meta-retarget': 'meta', 'linkedin-retarget': 'linkedin' };
    return result.map(a => (_chPair[a.id] && channels.includes(_chPair[a.id]))
      ? { ...a, free: true, price: 0, priceLabel: null, included: true }
      : a);
  }
  return result;
}

Object.assign(window, { CLIENT_TYPES, TIERS, COMMITMENTS, SERVICES, FAQS, priceFor, commitLabelFor, commitsFor, ADDON_MATRIX, getAddonsForContext });

// ── 2026-06-11: Best-paired-with. One curated companion per add-on, shown in
// the add-on tooltip with a clickable name (scrolls to the companion card).
// Pairs are service-local so the target always exists on the same page. ──
window.ADDON_LOOKUP = {};
SERVICES.forEach(svc => {
  if (Array.isArray(svc.addons)) {
    window.ADDON_LOOKUP[svc.id] = {};
    svc.addons.forEach(a => { window.ADDON_LOOKUP[svc.id][a.id] = a.name; });
  }
});
window.ADDON_PAIRS = {
  sales: { 'native-crm': 'premium-sourcing', 'overseas-calling': 'premium-sourcing', 'ads-lite': 'premium-sourcing', 'ads-full': 'premium-sourcing', 'premium-sourcing': 'overseas-calling', 'physical-mail-handwritten': 'premium-sourcing', 'physical-mail-printed': 'premium-sourcing', 'field-sales': 'physical-mail-handwritten' },
  'paid-ads': { 'meta-retarget': 'google-display', 'google-display': 'meta-retarget', 'google-shop': 'google-display', 'linkedin-retarget': 'premium-sourcing-pa', 'premium-sourcing-pa': 'linkedin-retarget', 'lp-build': 'cro-prog', landing: 'cro-prog', 'cro-prog': 'landing', 'call-track': 'dw-setup', 'dw-setup': 'zapier', zapier: 'dw-setup' },
  email: { 'platform-migration': 'deliverability-audit', 'deliverability-audit': 'platform-migration', landing: 'adv-segmentation', 'adv-segmentation': 'landing', zapier: 'dw-setup', 'dw-setup': 'dw-ongoing', 'dw-ongoing': 'dw-setup', 'additional-reports': 'dw-setup' },
  smm: { 'linkedin-mgmt': 'community', community: 'insta-story', 'post-boost': 'community', 'insta-story': 'custom-highlights', 'custom-highlights': 'insta-story', showcase: 'custom-highlights', 'extra-lang': 'community' },
  content: { 'content-video-creator': 'content-perf-opt', 'content-video-studio': 'content-rush', 'content-raw-footage': 'content-video-creator', 'content-rush': 'content-shipment', 'content-shipment': 'content-rush', 'content-perf-opt': 'content-platform-exp', 'content-multilang': 'content-platform-exp', 'content-platform-exp': 'content-multilang' },
  motion: { 'm-aspect-ratios': 'm-platform-exp', 'm-codec': 'm-ctv', 'm-music-comp': 'm-sound-design', 'm-sound-design': 'm-music-comp', 'm-perf-opt': 'm-ctv', 'm-asset-library': 'm-perf-opt', 'm-ctv': 'm-codec', 'm-platform-exp': 'm-aspect-ratios' },
  'dedicated-ft': { 'it-equip': 'benefits', visa: 'benefits', benefits: 'it-equip', coworking: 'it-equip' },
  fundraising: { 'deck-refresh': 'coach', 'model-ext': 'captable', 'investor-up': 'crm-setup', research50: 'crm-setup', 'crm-setup': 'investor-up', 'li-optim': 'meet-prep', coach: 'meet-prep', 'meet-prep': 'coach', 'grant-app': 'model-ext', 'partner-intros': 'meet-prep', 'crowd-mkt': 'crowd-strat', 'crowd-strat': 'crowd-mkt', termsheet: 'captable', captable: 'termsheet', dataroom: 'termsheet', 'market-size': 'competitor', competitor: 'market-size', 'fundraise-ret': 'crm-setup' },
  'investor-portal': { 'investor-dinner': 'sector-research', 'sector-research': 'premium-dd', 'portfolio-pod': 'sector-research', 'premium-dd': 'sector-research' },
};
// Lowest plan a matrix-gated add-on becomes available on, for the
// Best-paired-with note when the companion is locked at the current tier.
window.addonMinTier = function (svcId, addonId) {
  const svcM = ADDON_MATRIX[svcId];
  const tierMap = svcM && svcM[addonId];
  if (!tierMap) return null;
  const order = ['starter', 'grow', 'scale', 'enterprise'];
  const names = { starter: 'Starter', grow: 'Grow', scale: 'Scale', enterprise: 'Enterprise' };
  for (const t of order) {
    const r = _resolveAddonTierEntry(tierMap[t], '12');
    if (r.available) return names[t];
  }
  return null;
};

// #82: per-tier add-on discount shown on the Fundraising Support pricing page.
// Starter 20% off, Grow 25%, Scale 30% (Enterprise treated as Scale). Returns a
// price multiplier. Used for both the add-on card display and the cart total.
function fundraisingAddonMult(tierId) {
  switch (tierId) {
    case 'starter': return 0.80;
    case 'grow':    return 0.75;
    case 'scale':   return 0.70;
    case 'enterprise': return 0.70;
    default: return 0.75;
  }
}
window.fundraisingAddonMult = fundraisingAddonMult;

// ── Multi-page step model ──
// The pricing flow is composed dynamically from a CATALOG of step definitions
// keyed by `id`. The visible flow depends on the chosen client type:
//   • Agency  → client → WHITE-LABEL (route-setting) → growth → creative → talent → checkout
//                (whitelabel is its OWN step for agencies and is hidden from talent;
//                 fundraising is hidden, agencies aren't raising capital)
//   • Founder → client → growth → creative → talent → fundraising → checkout
//   • Investor→ client → growth → creative → talent → fundraising → checkout
//                (portfolio-wide pricing/discount logic to come later)
//   • No client type chosen → same as founder (default browse view)
const STEP_CATALOG = {
  client:      { id: 'client',      label: 'Client type',         icon: 'assets/icons/step-client.webp',      serviceIds: [] },
  qualifier:   { id: 'qualifier',   label: 'Tell us about you',   icon: 'assets/icons/step-client.webp',      serviceIds: [], isQualifier: true },
  whitelabel:  { id: 'whitelabel',  label: 'White-label plan',    icon: 'assets/icons/step-talent.webp',      serviceIds: ['whitelabel'] },
  growth:      { id: 'growth',      label: 'Growth services',     icon: 'assets/icons/step-growth.webp',      serviceIds: ['sales', 'paid-ads', 'email'] },
  creative:    { id: 'creative',    label: 'Creative services',   icon: 'assets/icons/step-creative.webp',    serviceIds: ['smm', 'content', 'motion'] },
  talent:      { id: 'talent',      label: 'Talent solutions',    icon: 'assets/icons/step-talent.webp',      serviceIds: ['dedicated-ft', 'dedicated-pt', 'whitelabel'] },
  fundraising: { id: 'fundraising', label: 'Capital solutions',   icon: 'assets/icons/step-fundraising.webp', serviceIds: ['founders-portal', 'fundraising'] },
  checkout:    { id: 'checkout',    label: 'Next steps',          icon: 'assets/icons/step-checkout.webp',    serviceIds: [] },
};

// Returns the ordered step definitions for a given client type. Each step is a
// CLONE so we can override `serviceIds` (e.g. removing `whitelabel` from talent
// for agencies, where it lives as its own step) without mutating the catalog.
function stepsForClient(clientTypeId, intentId) {
  if (clientTypeId === 'agency') {
    // Sprint 1: qualifier inserts right after client. White-label gets its own
    // dedicated step IMMEDIATELY after qualifier, it's the route-setting
    // choice that determines discount tier and pathway. Talent step keeps
    // `dedicated` only (whitelabel moves out).
    //
    // 2026-06-18 (Loom 49): white-label agencies can now also access the Talent
    // step. On 'agency-whitelabel' talent is sold at the standard rate and the
    // partner earns a 10% recurring commission (see getAgencyMultiplier), not a
    // wholesale margin. Agencies on 'agency-own' (internal, 15%) keep talent at
    // the partner rate as before.
    const isWhitelabel = intentId === 'agency-whitelabel';
    // Sprint 5: 'Tell us about you' (qualifier) step has been removed.
    // Recommendation / default-tier / addon-rec functions all handle empty
    // qualifier objects already, so dropping the step is safe.
    const isAgencyOwn = intentId === 'agency-own';
    // 2026-05-26: removed the dedicated white-label plan step. The 40%
    // white-label discount is already triggered by intentId ===
    // 'agency-whitelabel' (see getAgencyMultiplier), so making the user
    // pick a WL tier on a dedicated page was redundant. Agencies now go
    // straight from client-type → growth services.
    const baseFlow = [
      { ...STEP_CATALOG.client },
      ...(isAgencyOwn
        ? [{ ...STEP_CATALOG.growth, serviceIds: ['sales'] }]
        : [{ ...STEP_CATALOG.growth }, { ...STEP_CATALOG.creative }]),
    ];
    // 2026-06-18 (Loom 49): talent now shown to white-label agencies too.
    baseFlow.push({ ...STEP_CATALOG.talent, serviceIds: ['dedicated-ft', 'dedicated-pt'] });
    baseFlow.push({ ...STEP_CATALOG.checkout });
    return baseFlow;
  }
  // Founder / Investor / no-client-yet: hide the whitelabel service entirely.
  if (clientTypeId === 'investor') {
    // Investment-partnership intent is about the Investor Portal / Partner Pro
    // relationship, not buying marketing services, so route it straight to the
    // Investor Portal pricing rather than the full services builder.
    if (intentId === 'investor-partnership') {
      return [
        { ...STEP_CATALOG.client },
        { ...STEP_CATALOG.fundraising, label: 'Investor Portal', serviceIds: ['investor-portal'] },
        { ...STEP_CATALOG.checkout },
      ];
    }
    // 2026-06-08 (Loom 30 1:33-2:25): "I'm considering an investment" now
    // behaves like the other investor intents and goes through service
    // selection, rather than skipping straight to the call. (The old
    // Batch 7 skip-to-checkout was removed per Alexander.)
    return [
      { ...STEP_CATALOG.client },
      { ...STEP_CATALOG.growth },
      { ...STEP_CATALOG.creative },
      { ...STEP_CATALOG.talent, serviceIds: ['dedicated-ft', 'dedicated-pt'] },
      { ...STEP_CATALOG.fundraising, label: 'Investor Network', serviceIds: ['investor-portal'] },
      { ...STEP_CATALOG.checkout },
    ];
  }
  // Default (founder / no-client-yet): insert qualifier between client and growth.
  // For "no client yet" we still surface the qualifier step in the breadcrumb
  // so the layout looks coherent before a client is picked.
  if (!clientTypeId) {
    return [
      { ...STEP_CATALOG.client },
      { ...STEP_CATALOG.growth },
      { ...STEP_CATALOG.creative },
      { ...STEP_CATALOG.talent, serviceIds: ['dedicated-ft', 'dedicated-pt'] },
      { ...STEP_CATALOG.fundraising },
      { ...STEP_CATALOG.checkout },
    ];
  }
  // §8, Founder intent skip rules. `intent === 'portal'` (Sign up to Founders
  // Portal) skips Growth / Creative / Talent, jump straight to Capital where
  // the portal signup lives. `intent === 'explore'` (anonymous pricing browse)
  // additionally skips Capital, only About-you and Schedule-a-call remain.
  if (clientTypeId === 'founder' && intentId === 'portal') {
    return [
      { ...STEP_CATALOG.client },
      { ...STEP_CATALOG.fundraising },
      { ...STEP_CATALOG.checkout },
    ];
  }
  if (clientTypeId === 'founder' && intentId === 'explore') {
    return [
      { ...STEP_CATALOG.client },
      { ...STEP_CATALOG.checkout },
    ];
  }
  return [
    { ...STEP_CATALOG.client },
    { ...STEP_CATALOG.growth },
    { ...STEP_CATALOG.creative },
    { ...STEP_CATALOG.talent, serviceIds: ['dedicated-ft', 'dedicated-pt'] },
    { ...STEP_CATALOG.fundraising },
    { ...STEP_CATALOG.checkout },
  ];
}

// ── Breadcrumb label + tooltip overrides (founders-step-breadcrumb §9/§10) ─
// Map step id → display label per persona + intent. The base label comes from
// STEP_CATALOG; this function overrides it when persona/intent semantics call
// for different copy.
function getStepLabel(stepId, clientTypeId, intentId, fallbackLabel) {
  if (stepId === 'client') {
    if (clientTypeId === 'investor') return 'About you';
    if (clientTypeId === 'agency')   return (typeof window !== 'undefined' && window.isFreelancerMode && window.isFreelancerMode()) ? 'About your business' : 'About your agency';
    if (clientTypeId === 'founder')  return 'About you';
    return 'About you';
  }
  if (stepId === 'fundraising') {
    if (clientTypeId === 'founder' && intentId === 'portal') return 'Founders Portal';
    if (clientTypeId === 'investor') return 'Investors Portal';
  }
  if (stepId === 'checkout') {
    // 2026-06-03: post-call mode (?booked=1, the Cal.com redirect). The user has
    // already booked, so the final step sends their selections to the team
    // rather than scheduling a call.
    // 2026-06-04: post-call. Investor due-diligence sends the areas to assess
    // plus their details, not a service selection, so it gets its own label.
    try {
      if (new URLSearchParams(window.location.search).get('booked') === '1') {
        return intentId === 'investor-considering' ? 'Send your details' : 'Send your selections';
      }
    } catch (e) {}
    if (clientTypeId === 'founder' && intentId === 'explore') return 'View pricing';
    return 'Schedule a call';
  }
  return fallbackLabel || (window.STEP_CATALOG?.[stepId]?.label) || '';
}
window.getStepLabel = getStepLabel;

const STEP_TOOLTIPS_BASE = {
  client:      'Tell us about you so we can tailor pricing, tiers, and add-ons to your stage and ideal customer.',
  whitelabel:  'Pick your reseller route. It sets your discount tier and how we deliver for your clients.',
  growth:      'Fully managed sales and marketing across Sales & Demand Generation, Paid Advertising, and Email Marketing.',
  creative:    'Social, content, and 3D animation. Your owned channel and brand engine.',
  talent:      'Embed dedicated specialists from our team, marketers, creatives, ops, and engineering.',
  fundraising: 'Investor-ready materials, investor research, and access to our Founders Portal.',
  checkout:    'Book a call with our team to walk through your tailored proposal.',
};
function getStepTooltip(stepId, clientTypeId, intentId) {
  // 2026-06-08 (Loom 29 05:40-05:54): full rewrite so each step tooltip is
  // tailored to the path (client type) and the chosen intent. Falls back to
  // STEP_TOOLTIPS_BASE for any combination not covered here.
  const isAgency   = clientTypeId === 'agency';
  const isInvestor = clientTypeId === 'investor';
  const isFounder  = clientTypeId === 'founder';
  const isWL  = intentId === 'agency-whitelabel';
  const isOwn = intentId === 'agency-own';

  switch (stepId) {
    case 'client':
      if (isInvestor) return 'Tell us about your investment focus, typical cheque size, and what brings you here, so we can tailor the next step.';
      if (isAgency)   return 'Tell us about your agency, your model, your size, and what you sell to clients, so we can set the right discount and delivery.';
      return 'Tell us about your business, your stage, and your ideal customer, so we can tailor pricing, tiers, and add-ons.';

    case 'qualifier':
      return 'A few quick questions about your business so we can tailor what you see next.';

    case 'whitelabel':
      return 'Pick your reseller route. It sets your discount tier and how we deliver for your clients.';

    case 'growth':
      if (isWL)       return 'Pick the growth services you will resell, across Sales and Demand Generation, Paid Advertising, and Email Marketing. Prices shown are your wholesale cost.';
      if (isOwn)      return 'Sales and Demand Generation to grow your own agency, at your 15% partner rate.';
      if (isInvestor) return 'Fully managed Sales and Demand Generation, Paid Advertising, and Email Marketing for your portfolio company.';
      return 'Fully managed growth across Sales and Demand Generation, Paid Advertising, and Email Marketing.';

    case 'creative':
      if (isWL)       return 'Pick the creative services you will resell, across Social Media Management, Content Creation, and 3D Animation. Prices shown are your wholesale cost.';
      if (isInvestor) return 'Social, content, and 3D animation for your portfolio company, the owned channel and brand engine.';
      return 'Social, content, and 3D animation, your owned channel and brand engine.';

    case 'talent':
      if (isOwn)      return 'Embed dedicated specialists in your own agency, at GorillaPerks partner rates.';
      if (isInvestor) return 'Embed dedicated specialists in your portfolio company, across marketing, creative, ops, and engineering.';
      return 'Embed dedicated specialists from our team, across marketing, creative, ops, and engineering.';

    case 'fundraising':
      if (isInvestor) return 'Investor Portal, your route to curated deal flow, due-diligence support, and partner benefits. It is on a waiting list for now, so adding it reserves your place.';
      if (isFounder && intentId === 'portal') return 'Founders Portal, get matched to 7,500+ UK investors with AI thesis-matching.';
      return 'Capital solutions, investor-ready materials, investor research, and access to our Founders Portal.';

    case 'checkout':
      if (isInvestor && intentId === 'investor-considering') return 'Book a call so we can scope the marketing due diligence for the company you are assessing.';
      if (isInvestor && intentId === 'investor-general')     return 'Book a call to talk through the services you are interested in, with no commitment needed.';
      if (isInvestor && intentId === 'investor-partnership') return 'Join the Investor Portal waiting list and book a call to talk through the partnership.';
      if (isInvestor) return 'Review the proposal for your portfolio company, then book a call when you are ready.';
      if (isWL)       return 'Review your wholesale plan, then book a call to get your clients onboarded.';
      if (isOwn)      return (typeof window !== 'undefined' && window.isFreelancerMode && window.isFreelancerMode()) ? 'Review your plan, then book a call to get started.' : 'Review your agency plan, then book a call to get started.';
      if (isFounder && intentId === 'explore') return 'View anonymised pricing, no signup required.';
      return 'Book a call with our team to walk through your tailored proposal.';

    default:
      return STEP_TOOLTIPS_BASE[stepId] || '';
  }
}
window.getStepTooltip = getStepTooltip;
window.STEP_TOOLTIPS_BASE = STEP_TOOLTIPS_BASE;

// Back-compat: a sane default array for code paths that haven't been threaded
// through `stepsForClient` yet (and for the initial render before client-type
// is known). Mirrors the founder/investor/no-client flow.
const BUILD_STEPS = stepsForClient(null);
window.BUILD_STEPS = BUILD_STEPS;
window.STEP_CATALOG = STEP_CATALOG;
window.stepsForClient = stepsForClient;

// Per-service tier override: services can declare their own `tiers` array
// (different ids/names, e.g. Dedicated Resources uses Part Time / Full Time).
// Falls back to the global TIERS when not specified.
function tiersFor(service) {
  return (service && Array.isArray(service.tiers) && service.tiers.length) ? service.tiers : window.TIERS;
}
function findTier(service, tierId) {
  return tiersFor(service).find(t => t.id === tierId) || null;
}
Object.assign(window, { tiersFor, findTier });

// Returns the multiplier to apply to discountable line items based on client type + chosen intent.
// Default: 1 (no discount). Agencies: 0.85 own / 0.6 white-label. No discount until an intent is chosen.
//
// Sprint 2, talent-on-white-label rule (handover §5 / agency spec §7.1.1):
// Talent services (dedicated-pt, dedicated-ft) under WHITE-LABEL intent use a
// 0.85 multiplier instead of 0.60. We protect GoGorilla's headcount margins
// and still pass a sensible 15% margin for the agency. Pass serviceId as the
// 2nd arg to apply the override; without serviceId, the legacy intent rate
// continues to apply (used for blanket UI displays like the discount alert).
const TALENT_SERVICE_IDS = new Set(['dedicated-pt', 'dedicated-ft']);
function getAgencyMultiplier(state, serviceId) {
  // 2026-06-08 (Loom 30 4:21): also applies the investor intent discount (15%),
  // so the function now covers agency and investor priced lines. Name kept for
  // back-compat with its many call sites.
  if (!state || (state.clientTypeId !== 'agency' && state.clientTypeId !== 'investor')) return 1;
  // 2026-07-01 (Option A): in referring mode there is no reselling and no
  // white-label discount. The referred client pays the standard rate, and the
  // freelancer earns 10% recurring commission on that, so prices show at RRP.
  if (state.referMode) return 1;
  const ct = window.CLIENT_TYPES.find(c => c.id === state.clientTypeId);
  const intent = ct?.intents?.find(i => i.id === state.intentId);
  const d = intent?.discount ?? 0;
  // 2026-06-18 (Loom 49): talent under white-label is sold at the standard rate.
  // The partner earns a 10% recurring commission on placements rather than a
  // wholesale margin, so no discount multiplier is applied to talent here.
  if (serviceId && state.intentId === 'agency-whitelabel' && TALENT_SERVICE_IDS.has(serviceId)) {
    // 2026-06-18 (Loom): part-time dedicated takes the standard 40% reseller
    // discount like the other resold services. Full-time dedicated stays at the
    // standard rate, where the partner earns a 10% recurring commission instead
    // of a wholesale margin (see the full-time card commission badge).
    if (serviceId === 'dedicated-pt') return Math.max(0, 1 - d);
    return 1;
  }
  return Math.max(0, 1 - d);
}
window.getAgencyMultiplier = getAgencyMultiplier;

/* 2026-05-31 (#120): Agency wholesale pricing - per-region wholesale for
   location-variable items. Core retainers are pod-delivered and stay on the
   single agency multiplier (fallbackMultiplier) until per-region service rates
   are populated from the pricing source (#83). On the white-label path only the
   Overseas Cold Calling add-on is region-variable today (Talent is not offered
   on white-label), so its confirmed per-region rates live here. RoW = by quote
   (null). MarginRow consumes this via the helpers below. */
window.WHOLESALE = {
  regions: ['uk', 'europe', 'north-america', 'apac', 'row'],
  regionLabels: { uk: 'UK', europe: 'Europe', 'north-america': 'North America', apac: 'APAC', row: 'Rest of World' },
  defaultRegion: 'uk',
  services: {},
  addons: {
    'overseas-calling': { uk: 295, europe: 325, 'north-america': 375, apac: 345, row: null },
  },
  fallbackMultiplier: 0.6,
};

window.wholesaleRegion = function (region) {
  var W = window.WHOLESALE;
  if (region && W.regions.indexOf(region) !== -1) return region;
  return W.defaultRegion;
};

window.wholesaleService = function (serviceId, tierId, region, rrp, mult) {
  var W = window.WHOLESALE;
  var r = window.wholesaleRegion(region);
  var svc = W.services[serviceId];
  var cell = (svc && svc[tierId]) ? svc[tierId][r] : undefined;
  if (typeof cell === 'number') return { value: cell, byQuote: false, explicit: true };
  if (cell === null) return { value: null, byQuote: true, explicit: true };
  var m = (typeof mult === 'number') ? mult : W.fallbackMultiplier;
  return { value: Math.round(rrp * m), byQuote: false, explicit: false };
};

window.wholesaleAddon = function (addonId, region, price, mult) {
  var W = window.WHOLESALE;
  var r = window.wholesaleRegion(region);
  var cfg = W.addons[addonId];
  if (cfg && Object.prototype.hasOwnProperty.call(cfg, r)) {
    var v = cfg[r];
    if (v === null) return { value: null, byQuote: true, explicit: true };
    if (typeof v === 'number') return { value: v, byQuote: false, explicit: true };
  }
  var m = (typeof mult === 'number') ? mult : W.fallbackMultiplier;
  if (typeof price !== 'number') return { value: null, byQuote: false, explicit: false };
  return { value: Math.round(price * m), byQuote: false, explicit: false };
};

/* 2026-05-31 (#120): map Overseas Cold Calling destination countries (from the
   add-on's own country picker) to a wholesale region band, so the per-region
   wholesale reflects where the calling actually happens. Countries without a
   confirmed band fall to 'row' (by quote). */
window.OVERSEAS_REGION_MAP = {
  US: 'north-america', CA: 'north-america',
  IE: 'europe', DE: 'europe', FR: 'europe', ES: 'europe', IT: 'europe', NL: 'europe', SE: 'europe', DK: 'europe', CH: 'europe',
  AU: 'apac', NZ: 'apac', SG: 'apac', HK: 'apac', JP: 'apac', IN: 'apac',
  AE: 'row', BR: 'row', MX: 'row',
};
// Given selected destination country codes, return the region to price calling
// at: the costliest priced band among them; 'row' (by quote) if any selected
// market is unpriced. { region, byQuote, regions:[...], hasSelection }.
window.regionForCountries = function (codes) {
  var list = Array.isArray(codes) ? codes : [];
  if (!list.length) return { region: null, byQuote: false, regions: [], hasSelection: false };
  var map = window.OVERSEAS_REGION_MAP || {};
  var bands = list.map(function (c) { return map[c] || 'row'; });
  var uniq = bands.filter(function (b, i) { return bands.indexOf(b) === i; });
  if (uniq.indexOf('row') !== -1) return { region: 'row', byQuote: true, regions: uniq, hasSelection: true };
  var order = ['north-america', 'apac', 'europe', 'uk']; // costliest first
  var pick = order.filter(function (b) { return uniq.indexOf(b) !== -1; })[0] || 'uk';
  return { region: pick, byQuote: false, regions: uniq, hasSelection: true };
};

// Given selected region-band codes (the calling regions the agency picked
// directly), return the region to price calling at: costliest priced band;
// 'row' (by quote) if Rest of World is selected. { region, byQuote, regions,
// hasSelection }.
// 2026-06-12 (decision confirmed): summed wholesale across the selected
// region bands, mirroring the per-region retail. Rest of World stays by
// quote, excluded from the numeric sum and flagged for callers to surface.
// 2026-06-12 (review): premium price endings. Computed unit prices round to
// the nearest figure ending in 5 (so partner discounts and commit maths keep
// landing on premium-looking numbers). Values under £20 pass through
// untouched, protecting per-lead pence rates. Effective discount percentages
// flex slightly as a result, by design.
window.ggRound5 = function (v) {
  if (typeof v !== 'number' || !isFinite(v) || Math.abs(v) < 20) return v;
  return Math.round((v - 5) / 10) * 10 + 5;
};

window.commitScaleFactor = function (commitId) {
  var cid = String(commitId || '12');
  return cid === '3' ? (1 / 0.6) : cid === '6' ? (0.8 / 0.6) : 1;
};
window.callingWholesaleSum = function (regions, commitId) {
  var W = window.WHOLESALE;
  var card = (W && W.addons && W.addons['overseas-calling']) || {};
  var cr = window.callingRegion ? window.callingRegion(regions) : { regions: [], hasSelection: false };
  if (!cr.hasSelection) return { value: null, hasSelection: false, byQuoteOnly: false, anyQuote: false, quotes: [], regions: [] };
  var sum = 0, nums = 0, quotes = [];
  cr.regions.forEach(function (rc) {
    var v = card[rc];
    if (typeof v === 'number') { sum += v; nums += 1; } else { quotes.push(rc); }
  });
  var _csf = window.commitScaleFactor ? window.commitScaleFactor(commitId) : 1;
  var _g5 = window.ggRound5 || function (x) { return Math.round(x / 5) * 5; };
  return { value: nums ? _g5(sum * _csf) : null, hasSelection: true, byQuoteOnly: nums === 0, anyQuote: quotes.length > 0, quotes: quotes, regions: cr.regions };
};

window.callingRegion = function (regions) {
  var W = window.WHOLESALE;
  var valid = (W && W.regions) ? W.regions : ['uk', 'europe', 'north-america', 'apac', 'row'];
  // Only count recognised region-band codes. Guards against stale selections
  // that still hold individual country codes from before the region picker.
  var list = (Array.isArray(regions) ? regions.filter(Boolean) : []).filter(function (r) { return valid.indexOf(r) !== -1; });
  if (!list.length) return { region: null, byQuote: false, regions: [], hasSelection: false };
  if (list.indexOf('row') !== -1) return { region: 'row', byQuote: true, regions: list, hasSelection: true };
  var order = ['north-america', 'apac', 'europe', 'uk'];
  var pick = order.filter(function (b) { return list.indexOf(b) !== -1; })[0] || list[0];
  return { region: pick, byQuote: false, regions: list, hasSelection: true };
};
window.TALENT_SERVICE_IDS = TALENT_SERVICE_IDS;

// ── SPRINT 1: QUALIFIER QUESTIONS, INDUSTRIES, COHORT SCORING ──────────
// Q1, Q4 + conditional Q1.5 per founder spec §3 / handover §3 sprint 1.
// All three client types share the same qualifier, with conditional rendering
// of Q1.5 (S&DG tier preference) when Q1 indicates B2B + cold-outbound fit.
// ── Founders Step 1, 28-panel conditional question tree ────────────────
// Source of truth: founders-step-1-conditional-tree.md.
// Each question declares a `visibleIf(state)` predicate that the
// QualifierSection consults on every render to decide which panels mount.
// Some panels are multi-select chip groups (Q6) with an optional `exclusive`
// chip-value that clears all other selections when picked (and is itself
// cleared when any non-exclusive chip is picked).
//
// All questions are founders-only, for investor/agency personas the parent
// flow should render its own Step 1 (out of scope for this build).
const QUALIFIER_QUESTIONS = [
  {
    id: 'q1',
    label: 'What does your business do?',
    sub: 'Shapes which channels and services we recommend across your plan.',
    cols: 2,
    foundersOnly: true,
    options: [
      { id: 'dtc',         label: 'DTC / e-commerce',       desc: 'Physical or digital products sold to consumers' },
      { id: 'saas',        label: 'B2B SaaS',               desc: 'Software, platform, technology product' },
      { id: 'bservices',   label: 'B2B services',           desc: 'Agency, consultancy, recruitment, professional services' },
      { id: 'bcservices',  label: 'B2C services',           desc: 'Local, coaching, courses, healthcare, fitness, hospitality' },
      { id: 'both',        label: 'Both B2B and B2C',       desc: 'Hybrid revenue across both segments' },
      { id: 'mktplace',    label: 'Marketplace or platform', desc: 'Two-sided platform connecting supply and demand' },
    ],
    visibleIf: () => true,
  },
  // ── DTC follow-up chain ──
  {
    id: 'q1a', branch: 'dtc',
    label: 'What kind of products do you sell?',
    sub: 'Sharpens the mix of paid ads, social, content, and email we recommend across your plan.',
    cols: 2, foundersOnly: true,
    options: [
      { id: 'apparel',        label: 'Apparel & fashion' },
      { id: 'beauty',         label: 'Beauty & cosmetics' },
      { id: 'food',           label: 'Food & supplements' },
      { id: 'sportswellness', label: 'Sports & wellness' },
      { id: 'lifestyle',      label: 'Home & lifestyle' },
      { id: 'electronics',    label: 'Electronics & tech' },
      { id: 'jewellery',      label: 'Jewellery & accessories' },
      { id: 'other',          label: 'Other / multiple categories', desc: 'We tailor at kick-off' },
    ],
    visibleIf: (s) => s.q1 === 'dtc',
  },
  {
    id: 'q1aDtcAov',
    label: "What's your typical Average Order Value?",
    cols: 2, foundersOnly: true,
    options: [
      { id: 'sub50',    label: 'Under £50' },
      { id: '50-100',   label: '£50 to £100' },
      { id: '100-250',  label: '£100 to £250' },
      { id: '250-500',  label: '£250 to £500' },
      { id: '500-750',  label: '£500 to £750' },
      { id: '750plus',  label: '£750+',        desc: 'Recalibrated at kick-off' },
    ],
    visibleIf: (s) => s.q1 === 'dtc' && !!s.q1a,
  },
  {
    id: 'q1aDtcMargin',
    label: "What's your gross margin per order?",
    info: 'Revenue minus your direct production or fulfilment costs, as a percentage of revenue. Example: if you sell at £100 and it costs £40 to produce, your gross margin is 60%.',
    cols: 2, foundersOnly: true,
    options: [
      { id: 'sub20',   label: 'Under 20%' },
      { id: '20-40',   label: '20-40%' },
      { id: '40-60',   label: '40-60%' },
      { id: '60-80',   label: '60-80%' },
      { id: '80plus',  label: '80%+' },
    ],
    visibleIf: (s) => s.q1 === 'dtc' && !!s.q1aDtcAov,
  },
  {
    id: 'q1aDtcRepeat',
    label: 'How often do customers buy again?',
    cols: 2, foundersOnly: true,
    options: [
      { id: 'onetime',      label: 'One-time only' },
      { id: 'biannual',     label: 'Twice a year' },
      { id: 'quarterly',    label: 'Quarterly' },
      { id: 'monthly',      label: 'Monthly' },
      { id: 'subscription', label: 'Subscription' },
    ],
    visibleIf: (s) => s.q1 === 'dtc' && !!s.q1aDtcMargin,
  },
  // ── SaaS follow-up chain ──
  {
    id: 'q1a', branch: 'saas',
    label: "What's your sales motion?",
    sub: 'Tells us whether to lead with outbound or inbound across your plan.',
    cols: 3, foundersOnly: true,
    options: [
      { id: 'sales-led',   label: 'Sales-led',  desc: 'Outbound + AE-driven' },
      { id: 'product-led', label: 'Product-led', desc: 'Self-serve sign-up + free trial' },
      { id: 'hybrid',      label: 'Hybrid',      desc: 'PLG with sales-assisted close' },
    ],
    visibleIf: (s) => s.q1 === 'saas',
  },
  {
    id: 'saasCycle',
    label: 'How long is your typical sales cycle?',
    info: 'Shapes whether we lead with velocity outbound (short cycles) or ABM-style nurture (longer cycles).',
    cols: 2, foundersOnly: true,
    options: [
      { id: 'sub30',   label: 'Under 30 days',  desc: 'Velocity outbound, fast iteration' },
      { id: '30-90',   label: '30-90 days',     desc: 'Standard B2B SaaS cycle' },
      { id: '90-180',  label: '90-180 days',    desc: 'Mid-market evaluation cycle' },
      { id: '180plus', label: '180+ days',      desc: 'Enterprise / ABM territory' },
    ],
    visibleIf: (s) => s.q1 === 'saas' && !!s.q1a,
  },
  // ── B2B services follow-up chain ──
  {
    id: 'q1a', branch: 'bservices',
    label: 'How do you typically charge?',
    cols: 3, foundersOnly: true,
    options: [
      { id: 'project',  label: 'Project-based', desc: 'One-off or hourly engagements' },
      { id: 'retainer', label: 'Retainer',      desc: 'Recurring monthly fees or AUM-based' },
      { id: 'mixed',    label: 'Mixed',         desc: 'Both project and retainer, or hourly plus retainer' },
    ],
    visibleIf: (s) => s.q1 === 'bservices',
  },
  {
    id: 'bservicesCat',
    label: 'What kind of B2B service do you provide?',
    cols: 2, foundersOnly: true,
    options: [
      { id: 'legal',      label: 'Legal / accounting / compliance', desc: 'High-intent search + ABM' },
      { id: 'consulting', label: 'Consulting / advisory',           desc: 'LinkedIn + thought leadership' },
      { id: 'agency',     label: 'Creative / marketing agency',     desc: 'Portfolio + social + outbound' },
      { id: 'tech',       label: 'Tech services / IT',              desc: 'LinkedIn + Google + content' },
      { id: 'financial',  label: 'Financial / wealth services',     desc: 'Trust-led nurture + ABM' },
      { id: 'other',      label: 'Other / multiple categories',     desc: 'We tailor at kick-off' },
    ],
    visibleIf: (s) => s.q1 === 'bservices' && !!s.q1a,
  },
  {
    id: 'bservicesCycle',
    label: 'How long is your typical sales cycle?',
    cols: 3, foundersOnly: true,
    options: [
      { id: 'sub-30d', label: 'Under 30 days', desc: 'Fast-cycle, transactional engagements' },
      { id: '30-90d',  label: '30 to 90 days', desc: 'Standard professional-services cycle' },
      { id: '90dplus', label: '90+ days',      desc: 'Long-form trust-led nurture' },
    ],
    visibleIf: (s) => s.q1 === 'bservices' && !!s.bservicesCat,
  },
  // ── B2C services follow-up chain ──
  {
    id: 'q1a', branch: 'bcservices',
    label: 'Where do customers find you?',
    cols: 2, foundersOnly: true,
    options: [
      { id: 'local',         label: 'Local (city or area)' },
      { id: 'regional',      label: 'Regional' },
      { id: 'national',      label: 'National' },
      { id: 'international', label: 'International' },
    ],
    visibleIf: (s) => s.q1 === 'bcservices',
  },
  {
    id: 'bcservicesCat',
    label: 'What kind of consumer service do you provide?',
    cols: 2, foundersOnly: true,
    options: [
      { id: 'home',         label: 'Home services',          desc: 'Plumbers, electricians, cleaners, trades' },
      { id: 'wellness',     label: 'Wellness / fitness',     desc: 'Gyms, salons, therapists, coaches' },
      { id: 'education',    label: 'Education / tutoring',   desc: 'Schools, tutors, training providers' },
      { id: 'hospitality',  label: 'Hospitality / events',   desc: 'Restaurants, venues, event planners' },
      { id: 'professional', label: 'Professional services',  desc: 'Photography, consulting, freelance' },
      { id: 'other',        label: 'Other / multiple',       desc: 'We tailor at kick-off' },
    ],
    visibleIf: (s) => s.q1 === 'bcservices' && !!s.q1a,
  },
  // ── Both follow-up ──
  {
    id: 'q1a', branch: 'both',
    label: 'Where does most of your annual recurring revenue (ARR) come from?',
    cols: 3, foundersOnly: true,
    options: [
      { id: 'mostly-b2b', label: 'Mostly B2B' },
      { id: 'mostly-b2c', label: 'Mostly B2C' },
      { id: 'split',      label: 'Roughly split' },
    ],
    visibleIf: (s) => s.q1 === 'both',
  },
  // ── Marketplace follow-up ──
  {
    id: 'q1a', branch: 'mktplace',
    label: 'Which side do you need to grow?',
    cols: 3, foundersOnly: true,
    options: [
      { id: 'supply',     label: 'Supply-side', desc: 'Acquire sellers' },
      { id: 'demand',     label: 'Demand-side', desc: 'Acquire buyers' },
      { id: 'both-sides', label: 'Both sides',  desc: 'Blended plan' },
    ],
    visibleIf: (s) => s.q1 === 'mktplace',
  },
  // ── Common chain, q1b, q1c, q2, q2b, q3, trading, funding, q4, q5, q5b, urgency, q6 ──
  {
    id: 'q1b',
    label: 'Who do you primarily sell to?',
    sub: 'Shapes channel choice, copy, and which tier we recommend.',
    cols: 2, foundersOnly: true,
    options: [
      { id: 'midmarket',   label: 'Mid-market & enterprise',    desc: 'Companies with 250+ staff or significant ACV' },
      { id: 'smb',         label: 'SMB / small businesses',     desc: 'Under 250 staff, owner-operated or small teams' },
      { id: 'consumer',    label: 'Consumers (D2C)',            desc: 'End consumers buying for personal use' },
      { id: 'mixed',       label: 'Mixed audiences',            desc: 'Multiple segments across business and consumer' },
      { id: 'government',  label: 'Government / public sector', desc: 'Procurement-driven, RFP-led sales' },
    ],
    visibleIf: (s) => window.isQ1aChainComplete && window.isQ1aChainComplete(s),
  },
  {
    id: 'q1c',
    label: 'How specialised is your buyer profile?',
    cols: 2, foundersOnly: true,
    options: [
      { id: 'broad',       label: 'Broad, anyone with a budget could buy',  desc: 'Cross-industry, horizontal, or volume-driven' },
      { id: 'focused',     label: 'Focused, a clear vertical or function',  desc: 'e.g. CFOs at SaaS companies, marketing teams at DTC brands' },
      { id: 'niche',       label: 'Niche, small set of specific buyers',    desc: 'e.g. heads of clinical trials at biotechs, port operators' },
      { id: 'ultra-niche', label: 'Ultra-niche, under 500 prospects globally', desc: 'e.g. CIOs at top-100 hedge funds, CTOs at top-50 cybersecurity' },
      { id: 'unsure',      label: 'Still figuring this out', desc: 'Pre-revenue or pre-PMF; pick a slot above when you have one.' },
    ],
    visibleIf: (s) => !!s.q1b,
  },
  {
    id: 'q2',
    label: "What's your typical ACV (annual contract value)?",
    sub: 'Drives default tier and add-on recommendations.',
    cols: 2, foundersOnly: true,
    options: [
      { id: 'free',      label: 'Free / freemium' },
      { id: 'sub1k',     label: 'Under £1,000' },
      { id: '1k-5k',     label: '£1,000 to £5,000' },
      { id: '5k-10k',    label: '£5,000 to £10,000' },
      { id: '10k-100k',  label: '£10,000 to £100,000' },
      { id: '100kplus',  label: '£100,000+' },
    ],
    visibleIf: (s) => !!s.q1c && window.needsQ2 && window.needsQ2(s),
  },
  {
    id: 'q2b',
    label: "What's the average lifetime value of each customer?",
    info: 'Roughly: Average Order Value x number of repeat purchases x how many years they stay. If unsure, estimate. We will refine at proposal stage.',
    cols: 2, foundersOnly: true,
    options: [
      { id: 'sub500',   label: 'Under £500' },
      { id: '500-2k',   label: '£500 to £2,000' },
      { id: '2k-10k',   label: '£2,000 to £10,000' },
      { id: '10k-50k',  label: '£10,000 to £50,000' },
      { id: '50kplus',  label: '£50,000+' },
      { id: 'unknown',  label: "I'm not sure" },
    ],
    visibleIf: (s) => (window.needsQ2 && window.needsQ2(s) ? !!s.q2 : !!s.q1c),
  },
  {
    id: 'q3',
    label: "What's your current annual recurring revenue (ARR)?",
    sub: 'Drives the default tier across every service we recommend.',
    cols: 2, foundersOnly: true,
    options: [
      { id: 'prerevenue', label: 'Pre-revenue or under £100k' },
      { id: '100k-1m',    label: '£100k to £1M' },
      { id: '1m-5m',      label: '£1M to £5M' },
      { id: '5m-25m',     label: '£5M to £25M' },
      { id: '25mplus',    label: '£25M+' },
      { id: 'noanswer',   label: "I'd rather not say" },
    ],
    visibleIf: (s) => !!s.q2b,
  },
  {
    id: 'tradingLength',
    label: 'How long has your business been operating?',
    info: 'Affects our recommended commitment length, particularly for Paid Advertising and Sales & Demand Generation.',
    cols: 2, foundersOnly: true,
    options: [
      { id: 'under6m', label: 'Less than 6 months' },
      { id: '6-12m',   label: '6 to 12 months' },
      { id: '1-3y',    label: '1 to 3 years' },
      { id: '3-7y',    label: '3 to 7 years' },
      { id: '7yplus',  label: '7+ years' },
    ],
    visibleIf: (s) => !!s.q3,
  },
  {
    id: 'fundBacked',
    label: 'Are you investor-backed?',
    info: 'Helps us frame the proposal around your funding stage and pull relevant case studies.',
    cols: 3, foundersOnly: true,
    options: [
      { id: 'yes',     label: 'Yes',                        desc: 'We have raised from angels, VCs, or other investors' },
      { id: 'not-yet', label: 'Not yet, planning to raise', desc: 'Pre-raise, actively preparing or in conversations' },
      { id: 'no',      label: 'No, bootstrapped or self-funded', desc: 'No external investors and not planning to raise right now' },
    ],
    visibleIf: (s) => !!s.tradingLength,
  },
  {
    id: 'fundComplete',
    label: 'Have you completed this raise?',
    info: 'Tells us whether you are still raising, so we can line up the right capital support.',
    cols: 2, foundersOnly: true,
    options: [
      { id: 'yes', label: 'Yes, the round has closed', desc: 'The raise is done and the funds are in' },
      { id: 'no',  label: 'No, we are still raising',   desc: 'Mid-raise and looking to bring in more investors' },
    ],
    visibleIf: (s) => s.fundBacked === 'yes',
  },
  {
    id: 'fundRaised',
    label: 'How much have you raised?',
    cols: 2, foundersOnly: true,
    options: [
      { id: 'sub100k',   label: 'Under £100k',  desc: 'Pre-seed or friends-and-family' },
      { id: '100k-500k', label: '£100k to £500k', desc: 'Angel round or small seed' },
      { id: '500k-2m',   label: '£500k to £2m',  desc: 'Seed territory' },
      { id: '2m-10m',    label: '£2m to £10m',   desc: 'Series A or large seed' },
      { id: '10mplus',   label: '£10m+',         desc: 'Series B and beyond' },
    ],
    visibleIf: (s) => s.fundBacked === 'yes',
  },
  {
    id: 'fundInvestorCount',
    label: 'How many investors are on your cap table?',
    cols: 2, foundersOnly: true,
    options: [
      { id: '1-5',     label: '1 to 5' },
      { id: '6-15',    label: '6 to 15' },
      { id: '16-50',   label: '16 to 50' },
      { id: '50plus',  label: '50+' },
    ],
    visibleIf: (s) => s.fundBacked === 'yes' && !!s.fundRaised,
  },
  {
    id: 'q4',
    label: "What's your monthly budget for our team's work?",
    info: 'This sets your default tier across every service. Excludes ad spend, which goes directly to Meta and Google. You can adjust per service in the next step.',
    cols: 2, foundersOnly: true,
    options: [
      { id: 'sub500',     label: 'Under £500 / mo' },
      { id: '500-2.5k',   label: '£500 to £2,500 / mo' },
      { id: '2.5k-7.5k',  label: '£2,500 to £7,500 / mo' },
      { id: '7.5k-20k',   label: '£7,500 to £20,000 / mo' },
      { id: '20k-50k',    label: '£20,000 to £50,000 / mo' },
      { id: '50kplus',    label: '£50,000+ or custom' },
      { id: 'notsure',    label: "I'm not sure yet" },
    ],
    visibleIf: (s) => (s.fundBacked && s.fundBacked !== 'yes') || (s.fundBacked === 'yes' && !!s.fundInvestorCount),
  },
  {
    id: 'q5',
    label: "What's your current outbound setup?",
    cols: 2, foundersOnly: true,
    options: [
      { id: 'none',         label: 'No outbound yet' },
      { id: 'adhoc',        label: 'Founder / team doing it ad-hoc' },
      { id: 'team-small',   label: '1-3 SDRs in-house' },
      { id: 'team-large',   label: '4 to 10 SDRs in-house' },
      { id: 'team-xlarge',  label: '10+ SDRs in-house' },
    ],
    visibleIf: (s) => !!s.q4 && window.isB2BPath && window.isB2BPath(s),
  },
  {
    id: 'q5b',
    label: 'Do your buyers leave visible buying signals?',
    info: 'Buying signals are public events that suggest a prospect is in-market: funding announcements, hiring spikes, tech-stack changes, and similar.',
    cols: 2, foundersOnly: true,
    options: [
      { id: 'yes',     label: "Yes, clear signals when they're buying", desc: 'Funding rounds, hiring waves, tech-stack changes' },
      { id: 'some',    label: 'Some, occasional indicators',            desc: "A few signals fire (hiring spikes maybe) but most buyers don't telegraph intent" },
      { id: 'no',      label: "No, buyers don't telegraph intent online", desc: 'Legacy industries, manufacturing, traditional retail' },
      { id: 'unknown', label: 'Not sure' },
    ],
    visibleIf: (s) => !!s.q5,
  },
  {
    id: 'urgency',
    label: 'When would you want to be live, if you decide to progress?',
    info: 'Affects your recommended start pace and commitment length. Longer commitments unlock the largest discounts.',
    cols: 2, foundersOnly: true,
    options: [
      { id: 'now',         label: 'This week',        desc: 'We move fast. We can onboard you this week.' },
      { id: '30d',         label: 'Within 30 days',   desc: 'Active buying window' },
      { id: '90d',         label: 'Within 3 months',  desc: 'Planning ahead, not blocked' },
      { id: 'q3plus',      label: 'Within 6 months',  desc: 'Planning ahead, broader research window' },
      { id: 'exploring',   label: 'Just exploring',   desc: 'No timeline pressure yet' },
    ],
    visibleIf: (s) => (window.isB2BPath && window.isB2BPath(s) ? !!s.q5b : !!s.q4),
  },
  {
    id: 'priorActivities',
    label: 'Which of these are you already doing today?',
    info: 'Multi-select. Tells us how much groundwork is already in place so we do not recommend things you are already covered for.',
    cols: 2, foundersOnly: true,
    multi: true,
    exclusive: 'none',
    options: [
      { id: 'sales',        label: 'Cold sales / outbound' },
      { id: 'paid',         label: 'Paid advertising' },
      { id: 'email',        label: 'Email marketing' },
      { id: 'content',      label: 'Content creation' },
      { id: 'smm',          label: 'Social media management' },
      { id: '3d',           label: '3D animation / video production' },
      { id: 'talent',       label: 'Hiring specialists or contractors' },
      { id: 'fundraising',  label: 'Fundraising or planning an exit' },
      { id: 'portal-active', label: 'Already on Founders Portal' },
      { id: 'none',         label: 'Starting fresh - none of the above' },
    ],
    visibleIf: (s) => !!s.urgency,
  },
];
window.QUALIFIER_QUESTIONS = QUALIFIER_QUESTIONS;

// ── Agency qualifier questions ───────────────────────────────────────────────
// Three-scenario flow: Resell (A), Grow own agency (B), Strategic Partner (C).
// All questions use visibleIf to route based on aq_scenario selection.
// No foundersOnly flag — these are exclusively for the agency persona.
// Text-input questions (client name, website, brand assets) are deferred to
// the proposal generator feature and are not included here.
// 2026-05-25: Added -- Batch 10. Updated -- Batch 12. Updated -- Batch 13.
const AGENCY_QUALIFIER_QUESTIONS = [

  // ── Q1: Routing ────────────────────────────────────────────────────────────
  {
    id: 'aq_scenario',
    label: 'How do you want to work with GoGorilla.com?',
    sub: 'Your answer determines which questions we show you next.',
    cols: 2,
    options: [
      { id: 'resell',  label: 'Resell to my clients',  desc: 'White-label our services under your brand: wholesale pricing and margin support.' },
      { id: 'grow',    label: 'Grow my own agency',    desc: 'Use GoGorilla.com to scale your agency: new clients, better clients, more capacity.' },
    ],
    visibleIf: () => false,  // Batch 2 (Loom): removed. The agency intent cards already capture resell vs grow, and aq_scenario is auto-derived from intentId, so this question only duplicated the intent picker.
  },

  // ── Scenario A: Reseller / White-Label ─────────────────────────────────────
  {
    id: 'aq_returning',
    label: 'Have you white-labelled with us before?',
    sub: 'Returning partners skip straight to onboarding questions.',
    cols: 2,
    options: [
      { id: 'returning',  label: 'Yes, returning partner' },
      { id: 'first_time', label: 'No, first time' },
    ],
    visibleIf: (s) => s.aq_scenario === 'resell',
  },
  {
    id: 'aq_type_a',
    label: 'Are you a team or a freelancer?',
    cols: 2,
    options: [
      { id: 'team',       label: 'Team / agency' },
      { id: 'freelancer', label: 'Freelancer / solo' },
    ],
    visibleIf: (s) => s.aq_scenario === 'resell' && s.aq_returning === 'first_time',
  },
  {
    id: 'aq_cat_a',
    label: 'What kind of agency are you?',
    cols: 2,
    options: [
      { id: 'full_service', label: 'Full-service' },
      { id: 'performance',  label: 'Performance / growth' },
      { id: 'creative',     label: 'Creative' },
      { id: 'specialist',   label: 'Specialist (PR, SEO, etc.)' },
      { id: 'ops',          label: 'Operations / outsourcing' },
      { id: 'other',        label: 'Other' },
    ],
    visibleIf: (s) => s.aq_scenario === 'resell' && s.aq_returning === 'first_time' && !!s.aq_type_a,
  },
  {
    id: 'aq_revenue_a',
    label: "What’s your agency’s monthly revenue?",
    sub: 'Confidential. Helps us recommend the right plan.',
    cols: 2,
    options: [
      { id: 'sub5k',    label: 'Under £5k' },
      { id: '5k_20k',   label: '£5k – £20k' },
      { id: '20k_50k',  label: '£20k – £50k' },
      { id: '50k_100k', label: '£50k – £100k' },
      { id: '100kplus', label: '£100k+' },
    ],
    visibleIf: (s) => s.aq_scenario === 'resell' && s.aq_returning === 'first_time' && !!s.aq_cat_a,
  },
  {
    id: 'aq_clients_a',
    label: 'How many active clients do you have today?',
    cols: 2,
    options: [
      { id: '1_3',   label: '1 – 3' },
      { id: '4_10',  label: '4 – 10' },
      { id: '11_25', label: '11 – 25' },
      { id: '25plus', label: '25+' },
    ],
    visibleIf: (s) => s.aq_scenario === 'resell' && s.aq_returning === 'first_time' && !!s.aq_revenue_a,
  },
  {
    id: 'aq_retainer_a',
    label: "What’s your typical client retainer or project value?",
    cols: 2,
    options: [
      { id: 'sub500',    label: 'Under £500/mo' },
      { id: '500_1500',  label: '£500 – £1,500/mo' },
      { id: '1500_5000', label: '£1,500 – £5,000/mo' },
      { id: '5000plus',  label: '£5,000+/mo' },
    ],
    visibleIf: (s) => s.aq_scenario === 'resell' && s.aq_returning === 'first_time' && !!s.aq_clients_a,
  },
  // 2026-05-30: 'Is this the first client...' (aq_first_client_a) removed. Redundant:
  // step 1 already asks if the agency has a client ready, and 'No' cannot proceed.
  // aq_client_status_a now chains off aq_retainer_a / returning instead.
  {
    id: 'aq_client_status_a',
    label: 'Is this for a prospective or existing client?',
    cols: 2,
    options: [
      { id: 'prospect', label: 'A prospect I am pitching' },
      { id: 'existing', label: 'An existing client moving across' },
    ],
    visibleIf: (s) => s.aq_scenario === 'resell' && (s.aq_returning === 'returning' || !!s.aq_retainer_a),
  },
  {
    id: 'aq_goal_a',
    label: 'What is your primary goal for partnering with us?',
    cols: 2,
    options: [
      { id: 'revenue',  label: 'Generate more revenue' },
      { id: 'fill_gap', label: 'Fill a service gap' },
      { id: 'scale',    label: 'Scale without hiring' },
      { id: 'wl_arm',   label: 'Add a white-label arm' },
      { id: 'other',    label: 'Other' },
    ],
    visibleIf: (s) => s.aq_scenario === 'resell' && !!s.aq_client_status_a,
  },
  {
    id: 'aq_start_a',
    label: 'When are you looking to get started?',
    cols: 2,
    options: [
      { id: 'now',       label: 'This week',       desc: 'We move fast. Onboarding starts straight away.' },
      { id: '30d',       label: 'Within 30 days',  desc: 'Active buying window' },
      { id: '90d',       label: 'Within 3 months', desc: 'Planning ahead' },
      { id: 'exploring', label: 'Just exploring',  desc: 'No timeline pressure yet' },
    ],
    visibleIf: (s) => s.aq_scenario === 'resell' && !!s.aq_goal_a,
  },

  {
    id: 'aq_proposal_a',
    label: 'Is your client ready to start, or do they need a proposal first?',
    cols: 2,
    options: [
      { id: 'ready',    label: 'Ready to start',  desc: 'Happy to proceed once the pricing is agreed.' },
      { id: 'proposal', label: 'They need a proposal', desc: 'We prepare a branded proposal for you to send to them.' },
    ],
    visibleIf: (s) => s.aq_scenario === 'resell' && !!s.aq_start_a,
  },

  // ── Scenario B: Own Growth ──────────────────────────────────────────────────
  {
    id: 'aq_type_b',
    label: 'Are you a team or a freelancer?',
    cols: 2,
    options: [
      { id: 'team',       label: 'Team / agency' },
      { id: 'freelancer', label: 'Freelancer / solo' },
    ],
    visibleIf: (s) => s.aq_scenario === 'grow',
  },
  {
    id: 'aq_cat_b',
    label: 'What kind of agency are you?',
    cols: 2,
    options: [
      { id: 'full_service', label: 'Full-service' },
      { id: 'performance',  label: 'Performance / growth' },
      { id: 'creative',     label: 'Creative' },
      { id: 'specialist',   label: 'Specialist (PR, SEO, etc.)' },
      { id: 'ops',          label: 'Operations / outsourcing' },
      { id: 'other',        label: 'Other' },
    ],
    visibleIf: (s) => s.aq_scenario === 'grow' && !!s.aq_type_b,
  },
  {
    id: 'aq_services_offer',
    label: 'Which services do you currently offer clients?',
    sub: 'Select all that apply.',
    cols: 2,
    multi: true,
    exclusive: 'none',
    options: [
      { id: 'paid_ads',  label: 'Paid Advertising',    desc: 'Meta, Google, TikTok, etc.' },
      { id: 'outbound',  label: 'Outbound & SDG',       desc: 'Cold email, cold calling, SDRs' },
      { id: 'email_crm', label: 'Email & CRM',          desc: 'Campaigns, automation, flows' },
      { id: 'content',   label: 'Content & Creative',   desc: 'Video, copy, design, social' },
      { id: 'web_tech',  label: 'Web & Technology',     desc: 'Dev, SEO, analytics, platforms' },
      { id: 'strategy',  label: 'Strategy & Advisory',  desc: 'Consulting, go-to-market, ops' },
      { id: 'none',      label: 'None yet',             desc: 'Just getting started' },
    ],
    visibleIf: (s) => s.aq_scenario === 'grow' && !!s.aq_cat_b,
  },
  {
    id: 'aq_client_types_b',
    label: 'What kinds of clients do you primarily serve?',
    sub: 'Select all that apply.',
    cols: 2,
    multi: true,
    exclusive: 'mixed',
    options: [
      { id: 'ecom_dtc',     label: 'E-commerce / DTC' },
      { id: 'b2b_saas',     label: 'B2B SaaS' },
      { id: 'b2b_services', label: 'B2B Services' },
      { id: 'prof_svcs',    label: 'Professional Services' },
      { id: 'local_smb',    label: 'Local / SMB' },
      { id: 'enterprise',   label: 'Enterprise' },
      { id: 'mixed',        label: 'Mixed verticals' },
      { id: 'other',        label: 'Other' },
    ],
    visibleIf: (s) => s.aq_scenario === 'grow' && Array.isArray(s.aq_services_offer) && s.aq_services_offer.length > 0,
  },
  {
    id: 'aq_team_size_b',
    label: 'How many people work at your agency?',
    cols: 2,
    options: [
      { id: 'solo',   label: 'Just me' },
      { id: '2_5',    label: '2 – 5' },
      { id: '6_15',   label: '6 – 15' },
      { id: '16_50',  label: '16 – 50' },
      { id: '50plus', label: '50+' },
    ],
    visibleIf: (s) => s.aq_scenario === 'grow' && Array.isArray(s.aq_client_types_b) && s.aq_client_types_b.length > 0,
  },
  {
    id: 'aq_revenue_b',
    label: "What’s your agency’s monthly revenue?",
    sub: 'Confidential. Helps us recommend the right plan.',
    cols: 2,
    options: [
      { id: 'sub5k',    label: 'Under £5k' },
      { id: '5k_20k',   label: '£5k – £20k' },
      { id: '20k_50k',  label: '£20k – £50k' },
      { id: '50k_100k', label: '£50k – £100k' },
      { id: '100kplus', label: '£100k+' },
    ],
    visibleIf: (s) => s.aq_scenario === 'grow' && !!s.aq_team_size_b,
  },
  {
    id: 'aq_winning_b',
    label: 'How are you currently winning new clients?',
    sub: 'Select all that apply. Deep sub-questions deferred to discovery call.',
    cols: 2,
    multi: true,
    exclusive: 'none',
    options: [
      { id: 'inbound',   label: 'Inbound / referrals' },
      { id: 'paid_ads',  label: 'Paid ads' },
      { id: 'outbound',  label: 'Cold outbound' },
      { id: 'events',    label: 'Events / networking' },
      { id: 'partners',  label: 'Partnerships' },
      { id: 'none',      label: 'Not consistently' },
    ],
    visibleIf: (s) => s.aq_scenario === 'grow' && !!s.aq_revenue_b,
  },
  {
    id: 'aq_challenges_b',
    label: 'What are the biggest challenges you are solving right now?',
    sub: 'Select all that apply.',
    cols: 2,
    multi: true,
    exclusive: 'none',
    options: [
      { id: 'leads',     label: 'Lead generation' },
      { id: 'hiring',    label: 'Hiring / capacity' },
      { id: 'margins',   label: 'Profit margins' },
      { id: 'retention', label: 'Client retention' },
      { id: 'scale',     label: 'Scalability' },
    ],
    visibleIf: (s) => s.aq_scenario === 'grow' && Array.isArray(s.aq_winning_b) && s.aq_winning_b.length > 0,
  },
  {
    id: 'aq_hiring_b',
    label: 'Are you currently hiring?',
    cols: 2,
    options: [
      { id: 'yes', label: 'Yes' },
      { id: 'no',  label: 'No' },
    ],
    visibleIf: (s) => s.aq_scenario === 'grow' && Array.isArray(s.aq_challenges_b) && s.aq_challenges_b.length > 0,
  },
  {
    id: 'aq_hiring_roles_b',
    label: 'Which roles are you hiring for?',
    sub: 'Select all that apply.',
    cols: 2,
    multi: true,
    exclusive: 'none',
    options: [
      { id: 'sales_bdr',  label: 'Sales / BDR' },
      { id: 'creative',   label: 'Creative & design' },
      { id: 'paid_media', label: 'Paid media' },
      { id: 'delivery',   label: 'Delivery & ops' },
      { id: 'other',      label: 'Other' },
    ],
    visibleIf: (s) => s.aq_scenario === 'grow' && s.aq_hiring_b === 'yes',
  },
  {
    id: 'aq_goal_b',
    label: 'What is your primary goal for partnering with us?',
    cols: 2,
    options: [
      { id: 'revenue',  label: 'Generate more revenue' },
      { id: 'fill_gap', label: 'Fill a service gap' },
      { id: 'scale',    label: 'Scale without hiring' },
      { id: 'margins',  label: 'Improve margins' },
      { id: 'other',    label: 'Other' },
    ],
    visibleIf: (s) => s.aq_scenario === 'grow' && !!s.aq_hiring_b,
  },
  {
    id: 'aq_start_b',
    label: 'When are you looking to get started?',
    cols: 2,
    options: [
      { id: 'now',       label: 'This week',       desc: 'We move fast. Onboarding starts straight away.' },
      { id: '30d',       label: 'Within 30 days',  desc: 'Active buying window' },
      { id: '90d',       label: 'Within 3 months', desc: 'Planning ahead' },
      { id: 'exploring', label: 'Just exploring',  desc: 'No timeline pressure yet' },
    ],
    visibleIf: (s) => s.aq_scenario === 'grow' && !!s.aq_goal_b,
  },
];
window.AGENCY_QUALIFIER_QUESTIONS = AGENCY_QUALIFIER_QUESTIONS;

// ── Attribution: "How did you hear about us?" (Batch 20, Nicole) ───────────
// One multi-select on the last qualifier step of the Founders + Agencies paths
// (Investors to follow), with conditional follow-ups. Marketing attribution:
// which channel(s) originated and touched the lead. The follow-ups are shared
// by both banks and keyed on the same `heardVia` array so the data lands under
// one set of fields.
const HEARD_VIA_OPTIONS = [
  { id: 'referral', label: 'A referral or recommendation' },
  { id: 'youtube',  label: 'A YouTube video' },
  { id: 'outreach', label: 'You reached out to me' },
  { id: 'ad',       label: 'An online ad' },
  { id: 'social',   label: 'A social media post' },
  { id: 'search',   label: 'A web search' },
  { id: 'event',    label: 'A podcast, webinar, or event' },
  { id: 'other',    label: 'Something else' },
];
const _heardHas = (s, id) => Array.isArray(s && s.heardVia) && s.heardVia.includes(id);
// 2026-06-25 (Nicole): attribution is "answered" when the main question has a
// selection AND any conditional follow-up that needs a choice is filled. Free-text
// follow-ups (name, YouTube source, other) stay optional. Used to gate the plan card
// so it never appears over an unanswered "How did you hear about us?" question.
const _attributionComplete = (s) => {
  const hv = s && s.heardVia;
  if (!(Array.isArray(hv) && hv.length > 0)) return false;
  if (hv.includes('referral') && !s.heardReferralWho) return false;
  if (hv.includes('outreach') && !(Array.isArray(s.heardOutreachHow) && s.heardOutreachHow.length > 0)) return false;
  if (hv.includes('ad') && !s.heardAdPlatform) return false;
  return true;
};
const ATTRIBUTION_FOLLOWUPS = [
  {
    id: 'heardReferralWho', label: 'Who referred you?', cols: 2,
    options: [
      { id: 'client',  label: 'A client or customer' },
      { id: 'partner', label: 'A business partner' },
      { id: 'friend',  label: 'A friend or colleague' },
      { id: 'other',   label: 'Someone else' },
    ],
    visibleIf: (s) => _heardHas(s, 'referral'),
  },
  {
    id: 'heardReferralName', text: true, label: 'Who was it?',
    sub: 'Optional. A name helps us thank them and credit the referral.',
    placeholder: 'Their name',
    visibleIf: (s) => _heardHas(s, 'referral'),
  },
  {
    id: 'heardYoutube', text: true,
    label: 'Which YouTube channel or video, if you remember?',
    sub: 'Optional.',
    placeholder: 'Channel or video name',
    visibleIf: (s) => _heardHas(s, 'youtube'),
  },
  {
    id: 'heardOutreachHow', label: 'How did we first reach out to you?',
    sub: 'Select all that apply.', cols: 2, multi: true,
    options: [
      { id: 'call',     label: 'A phone call' },
      { id: 'email',    label: 'An email' },
      { id: 'linkedin', label: 'A LinkedIn message' },
    ],
    visibleIf: (s) => _heardHas(s, 'outreach'),
  },
  {
    id: 'heardAdPlatform', label: 'Which platform was the ad on?', cols: 2,
    options: [
      { id: 'facebook',  label: 'Facebook' },
      { id: 'instagram', label: 'Instagram' },
      { id: 'linkedin',  label: 'LinkedIn' },
      { id: 'google',    label: 'Google' },
      { id: 'youtube',   label: 'YouTube' },
      { id: 'other',     label: 'Other' },
    ],
    visibleIf: (s) => _heardHas(s, 'ad'),
  },
  {
    id: 'heardOther', text: true, label: 'Where did you hear about us?',
    sub: 'Optional.',
    placeholder: 'Tell us where',
    visibleIf: (s) => _heardHas(s, 'other'),
  },
];
const _heardViaQuestion = (visibleIf) => ({
  id: 'heardVia', label: 'How did you hear about us?',
  sub: 'Select all that apply.', cols: 2, multi: true,
  options: HEARD_VIA_OPTIONS, visibleIf,
});
QUALIFIER_QUESTIONS.push(_heardViaQuestion((s) => _founderCoreComplete(s)), ...ATTRIBUTION_FOLLOWUPS);
AGENCY_QUALIFIER_QUESTIONS.push(_heardViaQuestion((s) => _agencyCoreComplete(s)), ...ATTRIBUTION_FOLLOWUPS);

// Returns true when the agency qualifier is considered complete for the
// selected scenario. Used by the Schedule a Call step to reveal the plan
// summary card (same gate as window.qualifierComplete for founders).
// Checks the final question of each scenario path — if answered, the user
// has worked through the whole flow.
function agencyQualifierComplete(qualifier, intentId) {
  if (!qualifier) return false;
  const s = qualifier.aq_scenario
    || (intentId === 'agency-whitelabel' ? 'resell'
        : intentId === 'agency-own' ? 'grow' : null);
  if (s === 'resell')  return !!qualifier.aq_start_a && !!qualifier.aq_proposal_a && _attributionComplete(qualifier);
  if (s === 'grow')    return !!qualifier.aq_start_b && _attributionComplete(qualifier);
  return false;
}
window.agencyQualifierComplete = agencyQualifierComplete;

// Industry list for white-label agency Q0 (capture client industry so we can
// pre-fill Q1 ICP). Curated to common verticals; "other" is the safe escape.
const INDUSTRIES = [
  { id: 'saas',         label: 'SaaS / Tech' },
  { id: 'enterprise-sw',label: 'Enterprise software' },
  { id: 'ecommerce',    label: 'E-commerce / DTC' },
  { id: 'professional', label: 'Professional services' },
  { id: 'marketplace',  label: 'Marketplace / Platform' },
  { id: 'healthcare',   label: 'Healthcare' },
  { id: 'financial',    label: 'Financial services' },
  { id: 'education',    label: 'Education' },
  { id: 'media',        label: 'Media / Publishing' },
  { id: 'manufacturing',label: 'Manufacturing / Industrial' },
  { id: 'real-estate',  label: 'Real estate / PropTech' },
  { id: 'other',        label: 'Other' },
];
window.INDUSTRIES = INDUSTRIES;

// Industry → Q1 ICP pre-fill map (sprint 1, agency white-label only).
const INDUSTRY_TO_Q1_ICP = {
  saas:          'b2b-smb',
  'enterprise-sw':'b2b-mid',
  ecommerce:     'dtc',
  professional:  'b2b-smb',
  marketplace:   'marketplace',
  healthcare:    'b2b-mid',
  financial:     'b2b-mid',
  education:     'b2b-smb',
  media:         'b2b-smb',
  manufacturing: 'b2b-mid',
  'real-estate': 'b2b-smb',
  other:         null,
};
window.INDUSTRY_TO_Q1_ICP = INDUSTRY_TO_Q1_ICP;

// Compute warmth score 0-100 from qualifier answers.
// Per founder spec §4 + handover sprint 1.
function computeWarmth(qualifier) {
  // Cohort banner hidden in current UX. Return a coarse score so downstream
  // CRM tagging stays meaningful but no UI depends on it. Unknown values → 0.
  if (!qualifier) return 0;
  let w = 0;
  w += ({ 'saas': 25, 'bservices': 22, 'mktplace': 20, 'both': 18, 'dtc': 16, 'bcservices': 12 }[qualifier.q1] || 0);
  w += ({ '7yplus': 30, '3-7y': 26, '1-3y': 22, '6-12m': 14, 'under6m': 8 }[qualifier.tradingLength] || 0);
  w += ({ 'yes': 25, 'not-yet': 16, 'no': 12 }[qualifier.fundBacked] || 0);
  return Math.min(100, w);
}
window.computeWarmth = computeWarmth;

// Cohort tag A/B/C/D from warmth score. D = bypass to book-a-call.
function computeCohort(warmth) {
  if (warmth >= 75) return 'A';
  if (warmth >= 50) return 'B';
  if (warmth >= 25) return 'C';
  return 'D';
}
window.computeCohort = computeCohort;

// True only if every required qualifier question has been answered. Q1.5 is
// only required when its conditional dependency is met (q1 ∈ B2B set).
// ── Helpers for the 28-panel founders Step 1 tree ────────────────────────
function isQ1aChainComplete(s) {
  if (!s || !s.q1 || !s.q1a) return false;
  switch (s.q1) {
    case 'dtc':       return !!s.q1aDtcAov && !!s.q1aDtcMargin && !!s.q1aDtcRepeat;
    case 'saas':      return !!s.saasCycle;
    case 'bservices': return !!s.bservicesCat && !!s.bservicesCycle;
    case 'bcservices': return !!s.bcservicesCat;
    case 'both':
    case 'mktplace':  return true; // q1a is the only follow-up
    default:          return true;
  }
}
window.isQ1aChainComplete = isQ1aChainComplete;

function needsQ2(s) {
  if (!s) return false;
  const b2b = ['saas', 'bservices', 'mktplace'];
  if (b2b.includes(s.q1)) return true;
  if (s.q1 === 'both' && s.q1a !== 'mostly-b2c') return true;
  return false;
}
window.needsQ2 = needsQ2;

function isB2BPath(s) {
  if (!s) return false;
  return ['saas', 'bservices', 'both', 'mktplace'].includes(s.q1);
}
window.isB2BPath = isB2BPath;

// Counts non-null state in the canonical order list after `level`.
// `priorActivities` is counted as "answered" when its array has at least one entry.
function countDownstreamAnswers(level, s) {
  if (!s) return 0;
  const order = ['q1','q1a','q1b','q1c','q2','q2b','q3','q4','q5','q5b','priorActivities'];
  const idx = order.indexOf(level);
  if (idx < 0) return 0;
  let n = 0;
  for (let i = idx + 1; i < order.length; i++) {
    const k = order[i];
    if (k === 'priorActivities') { if (Array.isArray(s.priorActivities) && s.priorActivities.length > 0) n++; continue; }
    if (s[k] !== null && s[k] !== undefined) n++;
  }
  return n;
}
window.countDownstreamAnswers = countDownstreamAnswers;

// 2026-06-26 (Nicole): the founder core qualifier completion, everything except
// the attribution question. Gates BOTH the box (via isReadyToAdvance) and the
// visibility of "How did you hear about us?", so attribution is genuinely the
// last question and answering it always reveals the plan/checkout box.
function _founderCoreComplete(s) {
  if (!s) return false;
  if (!s.q1) return false;
  if (!isQ1aChainComplete(s)) return false;
  if (!s.q1b || !s.q1c) return false;
  if (needsQ2(s) && !s.q2) return false;
  if (!s.q2b || !s.q3) return false;
  if (!s.tradingLength || !s.fundBacked) return false;
  if (s.fundBacked === 'yes' && (!s.fundRaised || !s.fundInvestorCount)) return false;
  if (!s.q4) return false;
  if (isB2BPath(s) && (!s.q5 || !s.q5b)) return false;
  if (!s.urgency) return false;
  if (!Array.isArray(s.priorActivities) || s.priorActivities.length === 0) return false;
  // Q6 cascade gates per founders-q6-prior-activities-spec.md §13.
  // 'Skip' values count as answered (recording explicit decline).
  const prior = s.priorActivities;
  if (prior.includes('sales')) {
    if (!Array.isArray(s.priorOutboundChannels) || s.priorOutboundChannels.length === 0) return false;
    if (s.priorSalesCycle == null) return false;
  }
  if (prior.includes('paid')) {
    if (!Array.isArray(s.priorPaidPlatforms) || s.priorPaidPlatforms.length === 0) return false;
    if (s.priorPaidBudget == null) return false;
    if (s.priorPaidRoas == null) return false;
  }
  if (prior.includes('email')) {
    if (s.emailSubscriberCount == null) return false;
    if (s.emailCampaignsPerMonth == null) return false;
    if (s.priorEmailPlatform == null) return false;
  }
  if (prior.includes('smm')) {
    if (s.priorSocialFrequency == null) return false;
    if (!Array.isArray(s.priorSocialPlatforms) || s.priorSocialPlatforms.length === 0) return false;
  }
  if (prior.includes('talent')) {
    if (s.hiringTime == null) return false;
  }
  if (prior.includes('fundraising')) {
    if (s.fundraisingStage == null) return false;
    if (s.fundraisingRaiseSize == null) return false;
    if (!Array.isArray(s.fundraisingInvestorTypes) || s.fundraisingInvestorTypes.length === 0) return false;
    const needsTargetVal = ['seriesA', 'late-exit'].includes(s.fundraisingStage);
    if (needsTargetVal && s.fundraisingTargetValuation == null) return false;
    // fundraisingFlags is optional, no gate
  }
  return true;
}
window._founderCoreComplete = _founderCoreComplete;
function _agencyCoreComplete(s) {
  if (!s) return false;
  if (s.aq_scenario === 'resell') return !!s.aq_start_a && !!s.aq_proposal_a;
  if (s.aq_scenario === 'grow')   return !!s.aq_start_b;
  // scenario not yet derived: infer from whichever start field is answered
  if (s.aq_start_b) return true;
  if (s.aq_start_a) return !!s.aq_proposal_a;
  return false;
}
window._agencyCoreComplete = _agencyCoreComplete;

function isReadyToAdvance(s, clientTypeId) {
  if (!s) return false;
  // Founders-only flow, for other personas, treat as ready (their Step 1 lives elsewhere).
  const founderLike = !clientTypeId || clientTypeId === 'founder';
  if (!founderLike) return true;
  if (!_founderCoreComplete(s)) return false;
  // 2026-06-25 (Nicole): attribution must be answered before the qualifier counts as
  // complete, so the plan card no longer appears over the "How did you hear about us?" question.
  if (!_attributionComplete(s)) return false;
  return true;
}
window.isReadyToAdvance = isReadyToAdvance;
const qualifierComplete = isReadyToAdvance;
window.qualifierComplete = qualifierComplete;

// getStepGates, parent step router reads this off the answers.
function getStepGates(qualifier) {
  const q = qualifier || {};
  const ib = q.fundBacked;
  const od = q.tradingLength;
  const step4 = (od === 'under6m' && ib === 'no') ? 'render-with-soft-skip' : 'render';
  const step5 = ib === 'no' ? 'render-collapsed' : 'render';
  let step6CTA = 'Book a call with our team';
  if (ib === 'not-yet') step6CTA = "Book a call, we'll also cover raise-prep";
  else if (ib === 'no') step6CTA = "Book a call, we'll focus on payback-first plays";
  return { step2: 'render', step3: 'render', step4, step5, step6CTA };
}
window.getStepGates = getStepGates;

// ── STEP 1 → STEP 2+ RECOMMENDATION ENGINE V2 ─────────────────────────
// Rewired in v2 to read the 28-panel founders-step-1 qualifier shape
// (q1='dtc'|'saas'|'bservices'|'bcservices'|'both'|'mktplace';
//  q2=ACV bucket; q3=ARR bucket; q4=monthly budget; q5=outbound team;
//  q1c=ICP focus; q2b=LTV bucket; tradingLength; fundBacked; etc.).
//
// This block is the SINGLE source of truth for how Step 1 answers drive
// Step 2+ (service auto-recommendation, tier defaults, add-on visibility,
// channel ranking, multi-service discount, cohort kickoff, commit length).
//
// IMPORTANT: the visible "Recommended" badge on tier cards is currently
// hard-pinned to Grow per task #131. The tier-rec functions below are
// advisory only, they expose context-aware recommendations that future
// UI work can opt into without touching the badge logic.

// Service-id mapping. Spec §17 uses `sdg` and `3d`; the app uses `sales`
// and `motion`. Both forms are accepted on input; canonical output uses
// the app's internal ids so downstream selection code keeps working.
const _SERVICE_ID_ALIASES = { sdg: 'sales', '3d': 'motion' };
function _canonicalServiceId(s) { return _SERVICE_ID_ALIASES[s] || s; }
window._canonicalServiceId = _canonicalServiceId;

// Default tier per service from Step 1 inputs. Reads ACV (q2), ARR (q3),
// budget (q4), and outbound team (q5). For Sales (sdg) specifically, the
// SaaS sales-motion (q1a) gives an extra signal, sales-led + long cycle
// pushes Scale, product-led keeps it lean.
function defaultTierForService(serviceId, qualifier, intent) {
  // DISABLED, qualifier moved to Step 6; show full breadth. Default to Grow. (task #249)
  return 'grow';
  // eslint-disable-next-line no-unreachable
  if (!qualifier) return 'grow';
  const sid = _canonicalServiceId(serviceId);
  const q = qualifier;

  // S&DG (sales): SaaS sales motion gives a strong tier signal
  if (sid === 'sales' && q.q1 === 'saas' && q.q1a) {
    if (q.q1a === 'sales-led' && (q.saasCycle === '90-180' || q.saasCycle === '180plus')) return 'scale';
    if (q.q1a === 'product-led') return 'starter';
    if (q.q1a === 'hybrid') return 'grow';
  }

  // Established outbound team → bump tier on Sales
  if (sid === 'sales' && (q.q5 === 'team-large' || q.q5 === 'team-xlarge')) {
    return q.q3 === '25mplus' ? 'enterprise' : 'scale';
  }

  // ACV (B2B) drives default tier
  if (q.q2 === '100kplus') return 'scale';
  if (q.q2 === '10k-100k') return 'grow';
  if (q.q2 === 'free' || q.q2 === 'sub1k') return 'starter';

  // Monthly budget drives default tier
  if (q.q4 === 'sub500' || q.q4 === '500-2.5k') return 'starter';
  if (q.q4 === '20k-50k' || q.q4 === '50kplus') return 'scale';

  // ARR drives default tier
  if (q.q3 === '25mplus') return 'scale';
  if (q.q3 === '1m-5m' || q.q3 === '5m-25m') return 'grow';

  return 'grow';
}
window.defaultTierForService = defaultTierForService;

// Recommendation engine, returns { strength: 'strong' | 'subtle', reason }
// or null. Reads NEW qualifier keys. Gated on qualifierComplete so the
// "we recommend" copy only fires when we have enough signal to back it.
function computeServiceRecommendation(serviceId, qualifier, intent, clientTypeId) {
  // DISABLED, qualifier moved to Step 6; no qualifier-driven tier recommendations. (task #249)
  return null;
  // eslint-disable-next-line no-unreachable
  if (!qualifier || !window.qualifierComplete?.(qualifier)) return null;
  const sid = _canonicalServiceId(serviceId);
  const q = qualifier;
  const isB2BPath = window.isB2BPath ? window.isB2BPath(q) : ['saas','bservices','both','mktplace'].includes(q.q1);
  const isDTC = q.q1 === 'dtc';
  const isB2CServices = q.q1 === 'bcservices';
  const dealBig = ['10k-100k','100kplus'].includes(q.q2);
  const arrBig = ['1m-5m','5m-25m','25mplus'].includes(q.q3);
  const teamEstablished = ['team-small','team-large','team-xlarge'].includes(q.q5);

  switch (sid) {
    case 'sales': // Sales & Demand Generation (spec id: sdg)
      if (isB2BPath && (dealBig || arrBig)) {
        return { strength: 'strong', reason: 'B2B with mid-to-large deal sizes, outbound delivers fastest payback at your stage.' };
      }
      if (isB2BPath) return { strength: 'subtle', reason: 'Worth considering, B2B outbound is a reliable demand floor.' };
      return null;
    case 'paid-ads':
      if (isDTC) return { strength: 'strong', reason: 'DTC scales fastest through paid social + search at your stage.' };
      if (isB2CServices) return { strength: 'strong', reason: 'Local & B2C services rely on paid search and social to fill the pipeline.' };
      if (isB2BPath && dealBig) return { strength: 'subtle', reason: 'Targeted paid layered on outbound shortens the cycle.' };
      return null;
    case 'email':
      if (isDTC) return { strength: 'strong', reason: 'Repeat-purchase economics make email your highest-leverage owned channel.' };
      if (q.q1 === 'mktplace') return { strength: 'strong', reason: 'Lifecycle email is the cheapest reactivation lever for two-sided marketplaces.' };
      if (isB2BPath && q.q3 !== 'prerevenue') return { strength: 'subtle', reason: 'Lifecycle email lifts pipeline conversion 15-25%.' };
      return null;
    case 'smm':
      if (isDTC && ['apparel','beauty','sportswellness','lifestyle','jewellery'].includes(q.q1a)) {
        return { strength: 'strong', reason: 'Visual product categories compound through organic social.' };
      }
      if (isB2CServices && ['wellness','hospitality'].includes(q.bcservicesCat)) {
        return { strength: 'strong', reason: 'Local & lifestyle service buyers discover through social.' };
      }
      if (isDTC) return { strength: 'subtle', reason: 'Worth a measured starter cadence.' };
      return null;
    case 'content':
      if (q.q1 === 'saas' && (q.saasCycle === '90-180' || q.saasCycle === '180plus')) {
        return { strength: 'strong', reason: 'Long-cycle B2B SaaS, content lowers CAC and nurtures the buying committee.' };
      }
      if (q.q1 === 'bservices' && q.bservicesCat === 'consulting') {
        return { strength: 'strong', reason: 'Consulting buyers convert through thought leadership.' };
      }
      if (arrBig) return { strength: 'subtle', reason: 'Content compounds, invest now to lower CAC over 6-12 months.' };
      return null;
    case 'motion': // 3D / Video Production (spec id: 3d)
      if (isDTC && q.q1a === 'lifestyle' && ['250-500','500plus'].includes(q.q1aDtcAov)) {
        return { strength: 'strong', reason: 'High-AOV lifestyle products convert best with motion + 3D creative.' };
      }
      if (isDTC) return { strength: 'subtle', reason: 'Motion creative outperforms static on Meta + TikTok.' };
      return null;
    case 'dedicated-pt':
    case 'dedicated-ft':
      if (intent === 'agency-whitelabel') {
        return { strength: 'subtle', reason: 'Embedded talent is the cleanest white-label fulfilment.' };
      }
      if (arrBig) return { strength: 'subtle', reason: 'At your scale, dedicated headcount is more efficient than bursty engagements.' };
      return null;
    case 'whitelabel':
      if (intent === 'agency-whitelabel') return { strength: 'strong', reason: 'Required to unlock white-label fulfilment + 40% partner pricing.' };
      return null;
    case 'fundraising':
      if (clientTypeId === 'founder' && (q.fundBacked === 'not-yet' || q.fundBacked === 'yes')) {
        return { strength: 'subtle', reason: 'Aligns with where you are in the raise cycle.' };
      }
      return null;
    case 'investor-portal':
    case 'founders-portal':
      if ((sid === 'investor-portal' && clientTypeId === 'investor') ||
          (sid === 'founders-portal' && clientTypeId === 'founder')) {
        return { strength: 'subtle', reason: 'Foundational, a sensible default for your client type.' };
      }
      return null;
    default:
      return null;
  }
}
window.computeServiceRecommendation = computeServiceRecommendation;

// Channel ranking, reads NEW q1 enum. Sales motion comes from SaaS q1a
// (sales-led/product-led/hybrid) instead of the legacy q15 enum.
function rankedChannelsFor(serviceId, tierId, qualifier) {
  const base = window.channelsForTier ? window.channelsForTier(serviceId, tierId) : [];
  // DISABLED, qualifier moved to Step 6; show base channel order. (task #249)
  return base;
  // eslint-disable-next-line no-unreachable
  if (!qualifier || !qualifier.q1) return base;
  const sid = _canonicalServiceId(serviceId);
  if (sid === 'paid-ads') {
    const order = (() => {
      if (['saas','bservices','mktplace'].includes(qualifier.q1) ||
          (qualifier.q1 === 'both' && qualifier.q1a !== 'mostly-b2c')) {
        return ['google', 'linkedin', 'microsoft', 'meta', 'tiktok', 'pinterest'];
      }
      if (qualifier.q1 === 'dtc') {
        return ['meta', 'tiktok', 'google', 'pinterest', 'linkedin', 'microsoft'];
      }
      if (qualifier.q1 === 'bcservices') {
        return ['google', 'meta', 'tiktok', 'pinterest', 'linkedin', 'microsoft'];
      }
      return base;
    })();
    const allowed = new Set(base);
    return order.filter(c => allowed.has(c)).concat(base.filter(c => !order.includes(c)));
  }
  if (sid === 'sales') {
    const order = (() => {
      if (qualifier.q1 === 'saas' && qualifier.q1a === 'product-led') return ['email'];
      if (qualifier.q1 === 'saas' && qualifier.q1a === 'hybrid') return ['email', 'linkedin'];
      if (qualifier.q1 === 'saas' && qualifier.q1a === 'sales-led') return ['email', 'linkedin', 'phone'];
      return ['email', 'linkedin'];
    })();
    const allowed = new Set(base);
    return order.filter(c => allowed.has(c)).concat(base.filter(c => !order.includes(c)));
  }
  return base;
}
window.rankedChannelsFor = rankedChannelsFor;

// ── Multi-service discount tracker (5-45% cap) ─────────────────────
// Kept on the existing 1:0% / 2:5% / 3:15% / 4:25% / 5:35% / 6+:45%
// schedule rather than the spec's softer 1/2/3/5/10 table, the user has
// the more-generous schedule live and we don't want to silently reduce it.
const MULTI_SERVICE_DISCOUNT_PCT = { 1: 0, 2: 0.05, 3: 0.10, 4: 0.10, 5: 0.10, 6: 0.10 };
window.MULTI_SERVICE_DISCOUNT_PCT = MULTI_SERVICE_DISCOUNT_PCT;
function multiServiceDiscountPct(count) {
  if (!count || count < 1) return 0;
  if (count >= 3) return 0.10;
  if (count === 2) return 0.05;
  return 0;
}
window.multiServiceDiscountPct = multiServiceDiscountPct;
function combinedDiscountCap(state) {
  // 2026-06-03 (Loom 18 7:06): combined-discount ceiling for direct, founder,
  // investor and agency clients. Returns the MAX total discount off rrp, capping
  // the EXTRA stacked savings (pay upfront + bundle + code + BYOL) at 50% of the
  // standard rate, a margin safety net that only binds on the very deepest stacks.
  // 2026-06-15 (Alexander): a partner code LIFTS the ceiling for its own quote,
  // e.g. AGENCY55 raises the cap to its target so the code lands at the full rate.
  let cap = 0.50;
  const _pc = state && state.promoApplied;
  if (_pc && typeof _pc.topUpAgencyTo === 'number') cap = Math.max(cap, _pc.topUpAgencyTo / 100);
  return cap;
}
window.combinedDiscountCap = combinedDiscountCap;

// Add-on recommendation overrides for top services (Sales, Paid Ads, Email,
// SMM, Content). Returns a Set of addon ids that render as "recommended" on
// top of any badge already declared in data. Anchored in the v2 qualifier
// shape (q1 enum, q1a sub-followup, q2 ACV, q3 ARR, q5 outbound team, q5b
// signals, priorActivities chips).
function recommendedAddonIds(serviceId, qualifier, intentId) {
  const out = new Set();
  // DISABLED, qualifier moved to Step 6; no qualifier-driven add-on badges. (task #249)
  return out;
  // eslint-disable-next-line no-unreachable
  if (!qualifier) return out;
  const sid = _canonicalServiceId(serviceId);
  const q = qualifier;
  const isB2BPath = window.isB2BPath ? window.isB2BPath(q) : ['saas','bservices','both','mktplace'].includes(q.q1);
  const isDTC = q.q1 === 'dtc';
  const dealBig = ['10k-100k','100kplus'].includes(q.q2);
  const arrEstablished = ['1m-5m','5m-25m','25mplus'].includes(q.q3);
  const prior = Array.isArray(q.priorActivities) ? q.priorActivities : [];

  if (sid === 'sales') {
    // SaaS sales motion drives add-on bundle
    if (q.q1 === 'saas' && q.q1a === 'product-led') out.add('byol');
    if (q.q1 === 'saas' && q.q1a === 'sales-led') { out.add('overseas-calling'); }
    if (dealBig) out.add('premium-sourcing');
    // Spec §19: GorillaSignals on SDG is recommended when q5b === 'yes' (clear signals)
    if (q.q5b === 'yes') out.add('hiring-signals');
    // Per-lead quality: deal-size drives standard vs premium
    // Single lead tier now: always recommend leads-standard (was Standard vs Premium split)
    if (dealBig || ['free','sub1k','1k-5k'].includes(q.q2)) out.add('leads-standard');
  } else if (sid === 'paid-ads') {
    if (isB2BPath) out.add('dm-list');
    if (isDTC) { out.add('meta-retarget'); out.add('google-shop'); }
    // CRO + landing recommended when budget is healthy
    if (q.q4 === '7.5k-20k' || q.q4 === '20k-50k' || q.q4 === '50kplus') { out.add('cro-prog'); out.add('landing'); }
    if (dealBig && isB2BPath) out.add('call-track');
  } else if (sid === 'email') {
    // SMS adds value for DTC + marketplace lifecycle programmes
    if (isDTC || q.q1 === 'mktplace') out.add('sms');
    // Established programmes benefit from custom reporting; bigger budgets warrant vCMO
    if (prior.includes('email')) out.add('additional-reports');
  } else if (sid === 'smm') {
    // UGC sourcing for DTC / B2C only (mirrors isSMMAddonHiddenForICP)
    if (isDTC || q.q1 === 'bcservices') out.add('smm-ugc-rights');
  } else if (sid === 'content') {
    // Long-cycle SaaS or consulting benefits from thought-leadership packages
    if ((q.q1 === 'saas' && (q.saasCycle === '90-180' || q.saasCycle === '180plus')) ||
        (q.q1 === 'bservices' && q.bservicesCat === 'consulting')) {
      out.add('thought-leadership');
    }
  } else if (sid === 'dedicated-ft') {
    // §3.8 / §10, dedicated-ft addons: surface a Recommended badge on any
    // addon scoring ≥ 5 via the Step 1 boost rules.
    if (typeof window.getDrFtAddonScore === 'function') {
      const score = window.getDrFtAddonScore(window.__lastBuildPageState || { qualifier, selections: {} });
      Object.entries(score).forEach(([aid, s]) => { if (s >= 5) out.add(aid); });
    }
  }
  return out;
}
window.recommendedAddonIds = recommendedAddonIds;

// ── Step 1 → Step 2+ HELPERS (spec §17-20) ────────────────────────
//
// All helpers below are PURE and ADVISORY. They do not auto-mutate state.
// Step 2 panels can opt into them at render time. Today the only consumer
// that does is the recommendation badge engine above (computeServiceRec).
// Future work can wire these into SET_CLIENT / SET_QUALIFIER reducers to
// pre-select services / tiers / add-ons; that's intentionally NOT done
// here so the "always Grow recommended" rule (task #131) stays in force.

// §17, service auto-recommendation. Returns array of canonical service ids
// (e.g. 'sales','paid-ads','email','content','smm','motion') that Step 2
// would pre-select for this founder profile.
function getGapRecommendations(qualifier) {
  if (!qualifier || !qualifier.q1) return [];
  const q = qualifier;
  switch (q.q1) {
    case 'dtc': {
      // Lifestyle promoted to home at £250+ AOV (high-ticket furniture/decor)
      const cat = (q.q1a === 'lifestyle' && ['250-500','500plus'].includes(q.q1aDtcAov)) ? 'home' : q.q1a;
      const visualCats = ['apparel','beauty','lifestyle','sportswellness','jewellery','home'];
      const gaps = ['paid-ads', 'email', 'content'];
      if (visualCats.includes(cat)) gaps.splice(2, 0, 'smm'); // splice SMM in before content
      // 3D rec for high-ticket lifestyle/home
      if (cat === 'home' && ['250-500','500plus'].includes(q.q1aDtcAov)) gaps.push('motion');
      return gaps;
    }
    case 'bcservices': {
      const map = {
        home:        ['paid-ads', 'smm', 'email'],
        wellness:    ['paid-ads', 'smm', 'email', 'content'],
        education:   ['paid-ads', 'email', 'content'],
        hospitality: ['paid-ads', 'smm', 'content', 'email'],
        professional:['paid-ads', 'email', 'content'],
      };
      return map[q.bcservicesCat] || ['paid-ads', 'email', 'smm'];
    }
    case 'saas': {
      if (q.q1a === 'product-led') return ['paid-ads', 'email', 'content'];
      if (q.q1a === 'hybrid') return ['sales', 'paid-ads', 'email'];
      if (q.q1a === 'sales-led') {
        if (q.saasCycle === '180plus') return ['sales', 'paid-ads', 'email', 'content'];
        if (q.saasCycle === '90-180' || ['1m-5m','5m-25m','25mplus'].includes(q.q3)) {
          return ['sales', 'paid-ads', 'email'];
        }
        return ['sales', 'paid-ads'];
      }
      return ['sales', 'paid-ads'];
    }
    case 'bservices': {
      if (q.bservicesCat === 'consulting') return ['sales', 'email', 'content'];
      if (q.bservicesCat === 'agency') return ['sales', 'paid-ads', 'email', 'content'];
      return ['sales', 'paid-ads', 'email'];
    }
    case 'mktplace': {
      if (q.q1a === 'supply') return ['sales', 'paid-ads', 'content', 'smm'];
      if (q.q1a === 'demand') return ['paid-ads', 'email', 'content', 'smm'];
      return ['paid-ads', 'email', 'content', 'smm'];
    }
    case 'both':
      // Blended default, refine when dual-track engine ships
      return ['paid-ads', 'email', 'content'];
    default:
      return [];
  }
}
window.getGapRecommendations = getGapRecommendations;

// §18, context-aware tier recommendation (advisory). Reads q2 (ACV),
// q3 (ARR), q4 (budget), q5 (outbound), q1c (ICP focus). Returns one of
// 'starter' | 'grow' | 'scale' | 'enterprise'. (We use 'grow' as the
// canonical mid-tier id rather than the spec's 'growth' to match data.)
function getRecommendedTierFromQualifier(state, serviceId) {
  const q = (state && state.qualifier) || state || {};
  let rec = 'grow';

  // 1. Sub-economic deal sizes → starter
  if (q.q2 === 'free' || q.q2 === 'sub1k') rec = 'starter';
  // 2. Low budget → starter
  else if (q.q4 === 'sub500' || q.q4 === '500-2.5k') rec = 'starter';
  // 3. Established outbound team → scale or enterprise
  else if (q.q5 === 'team-large' || q.q5 === 'team-xlarge') {
    rec = q.q3 === '25mplus' ? 'enterprise' : 'scale';
  }
  // 4. Small team but real budget → scale
  else if (q.q5 === 'team-small' && !['sub500','500-2.5k'].includes(q.q4)) rec = 'scale';
  // 5. Big-ticket ACV → scale
  else if (q.q2 === '100kplus') rec = 'scale';
  // 6. Big budget → scale
  else if (q.q4 === '20k-50k' || q.q4 === '50kplus') rec = 'scale';
  // 7. Mid-ticket ACV / mid revenue → grow
  else if (q.q2 === '10k-100k' || q.q3 === '1m-5m') rec = 'grow';

  // ── Caps applied last ──
  // Pre-revenue can't sustain Scale
  if (q.q3 === 'prerevenue' && rec === 'scale') rec = 'grow';
  // Niche/ultra-niche TAM cap
  if ((q.q1c === 'niche' || q.q1c === 'ultra-niche') && (rec === 'scale' || rec === 'enterprise')) {
    rec = 'grow';
  }
  return rec;
}
window.getRecommendedTierFromQualifier = getRecommendedTierFromQualifier;

// Soft warning: selected starter tier might not be enough volume for stage.
// Returns true when the user picked starter but their ARR + budget warrant Scale.
function starterNotEnoughVolume(state, currentTier) {
  if (currentTier !== 'starter') return false;
  const q = (state && state.qualifier) || state || {};
  const arrBig = q.q3 === '5m-25m' || q.q3 === '25mplus';
  const budgetBig = q.q4 === '20k-50k' || q.q4 === '50kplus';
  return arrBig && budgetBig;
}
window.starterNotEnoughVolume = starterNotEnoughVolume;

// §19, ICP gates on SMM add-ons
//   smm-ig-stories  hidden for pure-B2B paths (SaaS, consulting)
//   smm-ugc-rights  hidden outside DTC + B2C services
function isSMMAddonHiddenForICP(addonId, qualifier) {
  // DISABLED, qualifier moved to Step 6; show full breadth of add-ons. (task #249)
  return false;
  // eslint-disable-next-line no-unreachable
  if (!qualifier || !qualifier.q1) return false;
  const q = qualifier;
  if (addonId === 'smm-ig-stories') {
    if (q.q1 === 'saas') return true;
    if (q.q1 === 'bservices' && q.bservicesCat === 'consulting') return true;
  }
  if (addonId === 'smm-ugc-rights') {
    if (q.q1 !== 'dtc' && q.q1 !== 'bcservices') return true;
  }
  return false;
}
window.isSMMAddonHiddenForICP = isSMMAddonHiddenForICP;

// §19, Paid Ads daily spend pre-fill, driven by monthly budget and whether
// the founder has run paid before (priorActivities). Returns daily £ amount.
function paidRecommendedDailySpend(qualifier) {
  if (!qualifier) return 25;
  const q = qualifier;
  const prior = Array.isArray(q.priorActivities) ? q.priorActivities : [];
  const hasPaidHistory = prior.includes('paid');
  const budgetMap = {
    'sub500': 10,
    '500-2.5k': 25,
    '2.5k-7.5k': 75,
    '7.5k-20k': 200,
    '20k-50k': 500,
    '50kplus': 1000,
    'notsure': 50,
  };
  let base = budgetMap[q.q4] || 50;
  // Conservative bump down if no paid history and pre-rev / bootstrapped
  if (!hasPaidHistory && q.q3 === 'prerevenue' && q.fundBacked === 'no') base = Math.max(10, Math.round(base * 0.6));
  // Aggressive bump up if funded + established + prior paid
  if (hasPaidHistory && q.fundBacked === 'yes' && (q.q3 === '5m-25m' || q.q3 === '25mplus')) base = Math.round(base * 1.5);
  return base;
}
window.paidRecommendedDailySpend = paidRecommendedDailySpend;

// §20, Next cohort kickoff date label from urgency. Returns
// e.g. 'Mon 27 May' formatted in London time.
function getNextCohortKickoffLabel(urgency) {
  const bufferDays = ({ 'now': 0, '30d': 7, '90d': 14, 'q3plus': 14, 'exploring': 14 })[urgency] ?? 14;
  const now = new Date();
  const target = new Date(now.getTime() + bufferDays * 24 * 60 * 60 * 1000);
  // Snap to next Monday
  const dow = target.getDay();
  const daysToMonday = dow === 0 ? 1 : (dow === 1 ? 0 : 8 - dow);
  target.setDate(target.getDate() + daysToMonday);
  const days   = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
  const months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
  return `${days[target.getDay()]} ${target.getDate()} ${months[target.getMonth()]}`;
}
window.getNextCohortKickoffLabel = getNextCohortKickoffLabel;

// §20, Recommended commit length per service. tradingLength sets the floor,
// urgency='now' shortens by one step (let new clients test before locking in).
function getRecommendedCommitLength(tradingLength, urgency, serviceId) {
  const sid = _canonicalServiceId(serviceId);
  const ladder = ['monthly', '3', '6', '12'];
  let idx = 1; // default 3-month
  if (tradingLength === 'under6m') idx = 0;
  else if (tradingLength === '6-12m') idx = 1;
  else if (tradingLength === '1-3y') idx = 2;
  else if (tradingLength === '3-7y' || tradingLength === '7yplus') idx = 3;
  // Performance-sensitive services prefer monthly when trading is short
  if ((sid === 'paid-ads' || sid === 'email') && tradingLength === 'under6m') idx = 0;
  // Urgency=now shortens by one step
  if (urgency === 'now' && idx > 0) idx -= 1;
  return ladder[idx];
}
window.getRecommendedCommitLength = getRecommendedCommitLength;

// §5.8, Future-work stub. When wired in later, this would hide trading /
// fund / urgency questions for solo + pre-revenue founders. NOT currently
// active; reveal logic above ignores this.
function shouldShowAdvancedStep1Questions(state) {
  const q = (state && state.qualifier) || state || {};
  if (!q) return true;
  if (q.q5 === 'none' && q.q3 === 'prerevenue') return false;
  return true;
}
window.shouldShowAdvancedStep1Questions = shouldShowAdvancedStep1Questions;

// ── Talent FT (Dedicated Resources), Step 1 → Step 4 defaults ─────
// Based on founders-talent-solutions-conditional-tree.md §5/§6/§8/§13.
// All helpers are pure and read off the v2 qualifier shape; they degrade
// safely to empty/false when qualifier is incomplete.

// Map of spec role slugs → the existing ft-* role ids used in the SERVICES
// catalog's dedicated-ft.roles.fulltime list. Roles in the spec that have
// no exact in-app counterpart (paid-ads-media-buyer, customer-service-rep,
// appointment-setter, 2d-designer) are remapped to the closest existing
// role or dropped silently. Keep this map narrow, if you add a role to
// SERVICES, also add it here.
const DR_FT_SLUG_TO_ID = {
  'marketing-ops-coordinator': 'ft-mkt-ops',
  'graphic-designer':          'ft-graphic',
  'copywriter':                'ft-mkt-ops', // role removed 2026-06-05 (Loom 24); copy covered by Full-Stack Marketer
  'video-editor':              'ft-video',
  'sdr':                       'ft-sdr',
  'lead-generation-expert':    'ft-leadgen',
  'customer-success-manager':  'ft-am',  // role removed 2026-06-05 (Loom 24); nearest is Account Manager
  'account-manager':           'ft-am',
  'fullstack-developer':       null,     // role removed 2026-06-05 (Loom 24)
  'ai-automation':             'ft-ai',
  'data-analyst':              null,         // role removed 2026-06-03
  'devops-engineer':           null,         // role removed 2026-06-03
  'uiux-designer':             'ft-graphic', // merged into Graphic Designer 2026-06-05 (Loom 24)
  '3d-designer':               'ft-graphic', // merged into Graphic Designer 2026-06-05 (Loom 24)
  'video-motion-designer':     'ft-video',   // merged into Video Editor 2026-06-05 (Loom 24)
  'executive-assistant':       'ft-exec',
  'virtual-assistant':         'ft-exec', // VA removed 2026-06-05 (Loom 24); admin covered by Executive Assistant
  // Remaps for slugs without an exact match
  'appointment-setter':        'ft-sdr',     // appointment setting ~= outbound SDR
  '2d-designer':               'ft-graphic', // 2D ~= graphic designer
  'paid-ads-media-buyer':      null,         // no app-side equivalent; skip
  'customer-service-rep':      null,         // no app-side equivalent; skip
};
window.DR_FT_SLUG_TO_ID = DR_FT_SLUG_TO_ID;

function _drftMap(slugs) {
  const seen = new Set();
  return slugs
    .map(s => DR_FT_SLUG_TO_ID[s])
    .filter(id => id != null && !seen.has(id) && seen.add(id));
}

// §5, default 4-ish-role pre-selection per Step 1 path. Returns the
// canonical ft-* ids (not the spec slugs) so the existing service card's
// role grid can read them directly.
function getDrFtDefaultRoles(state) {
  const q = (state && state.qualifier) || state || {};
  if (!q.q1) return [];
  const isPreRev = q.q3 === 'prerevenue';

  switch (q.q1) {
    case 'saas': {
      if (q.q1a === 'product-led') {
        return _drftMap(['fullstack-developer','data-analyst','uiux-designer','customer-success-manager','sdr','executive-assistant']);
      }
      // sales-led / hybrid / unset
      return _drftMap(['marketing-ops-coordinator','copywriter','sdr','customer-service-rep']);
    }
    case 'dtc': {
      // DTC: electronics + luxury get a creative-heavy roster (3D/UI-UX);
      // every other DTC sub gets ops + creative + paid-media-buyer.
      const isElectronicsOrLuxury = q.q1a === 'electronics' || q.q1a === 'jewellery';
      if (isElectronicsOrLuxury) {
        return _drftMap(['graphic-designer','3d-designer','uiux-designer','paid-ads-media-buyer']);
      }
      const base = ['marketing-ops-coordinator','graphic-designer','video-editor','paid-ads-media-buyer'];
      // Pre-rev DTC drops customer-service-rep per v33.110-SVC-T06 (already a no-op
      // here because that slug isn't in base, but matches spec rationale).
      return _drftMap(base);
    }
    case 'bservices':
      return _drftMap(['marketing-ops-coordinator','account-manager','copywriter','executive-assistant']);
    case 'bcservices':
      return _drftMap(['virtual-assistant','customer-service-rep','appointment-setter','2d-designer']);
    case 'mktplace': {
      if (q.q1a === 'supply') {
        return _drftMap(['ai-automation','account-manager','copywriter','data-analyst']);
      }
      return _drftMap(['marketing-ops-coordinator','ai-automation','paid-ads-media-buyer','account-manager']);
    }
    case 'both':
    default:
      return _drftMap(['marketing-ops-coordinator','graphic-designer','copywriter','virtual-assistant']);
  }
}
window.getDrFtDefaultRoles = getDrFtDefaultRoles;

// §8, Buyout option should pre-check when the user is funded Series A+
// AND has picked an SDR or CSM role. Returns true iff:
//   • role id is ft-sdr or ft-csm, AND
//   • fundBacked === 'yes', AND
//   • fundRaised indicates Series A or beyond (500k-2m / 2m-10m / 10mplus).
function shouldDefaultDrFtBuyout(state, ftRoleId) {
  const q = (state && state.qualifier) || state || {};
  if (ftRoleId !== 'ft-sdr' && ftRoleId !== 'ft-csm') return false;
  if (q.fundBacked !== 'yes') return false;
  const seriesAplus = ['500k-2m', '2m-10m', '10mplus'];
  return seriesAplus.includes(q.fundRaised);
}
window.shouldDefaultDrFtBuyout = shouldDefaultDrFtBuyout;

// §6, Talent FT multi-role discount within the dedicated-ft service.
// Stacks with the global multi-service ladder; final retainer capped at 50%
// floor at the plan-summary aggregator. Setup fees excluded.
// Exposed for future wiring, not yet applied to subtotals (FT roles are
// 'Custom' pricing right now so the discount is a no-op until pricing
// flips numeric).
const DR_FT_MULTI_ROLE_DISCOUNT = { 1: 0, 2: 0.03, 3: 0.05, 4: 0.08 };
window.DR_FT_MULTI_ROLE_DISCOUNT = DR_FT_MULTI_ROLE_DISCOUNT;
function drFtMultiRoleDiscountPct(state) {
  const sel = state?.selections?.['dedicated-ft'];
  const n = Array.isArray(sel?.roles) ? sel.roles.length : 0;
  if (n <= 1) return 0;
  if (n >= 4) return DR_FT_MULTI_ROLE_DISCOUNT[4];
  return DR_FT_MULTI_ROLE_DISCOUNT[n] || 0;
}
window.drFtMultiRoleDiscountPct = drFtMultiRoleDiscountPct;

// §13, Soft "too early for FT" warning. Surfaces a yellow banner inside
// the dedicated-ft card when the founder is pre-revenue with sub-£1k ACV.
// No hard gate, just nudges them toward Part-Time or skipping.
function isDrFtTooEarlyWarning(state) {
  const q = (state && state.qualifier) || state || {};
  return q.q3 === 'prerevenue' && (q.q2 === 'free' || q.q2 === 'sub1k');
}
window.isDrFtTooEarlyWarning = isDrFtTooEarlyWarning;

// ── Talent PT (Dedicated Resources Part-Time), Step 1 defaults ─────
// Based on founders-talent-dedicated-resources-full-spec.md §4.6. Maps the
// spec's PT role slugs to the existing app pt-* role ids in the dedicated-pt
// catalog. Slugs in the spec without an exact match remap to the closest
// existing role or drop silently.
const DR_PT_SLUG_TO_ID = {
  'virtual-assistant-pt':     'pt-fsm',
  'copywriter-pt':            'pt-fsm',
  'video-editor-pt':          'pt-video',
  'motion-graphic-designer':  'pt-video',   // merged into Video Editor 2026-06-05 (Loom 24)
  'web-developer-pt':         'pt-fsm',
  'automation-ai-agent-pt':   'pt-ai',
  'general-marketer-pt':      'pt-fsm',
  'seo-specialist-pt':        'pt-fsm',    // SEO retired from PT 2026-06-12 (Loom 41), FSM is the closest cover
  'appointment-setter-pt':    'pt-sdr',    // retired 2026-06-12 (Loom 41), SDR covers appointment setting
  'sales-rep-closer-pt':      'pt-sdr',    // retired 2026-06-12 (Loom 41), SDR is the closest sales cover
  '2d-designer':              'pt-2d',
  '3d-designer-pt':           'pt-2d',     // merged into Graphic Designer 2026-06-05 (Loom 24)
};
window.DR_PT_SLUG_TO_ID = DR_PT_SLUG_TO_ID;
function _drptMap(slugs) {
  const seen = new Set();
  return slugs.map(s => DR_PT_SLUG_TO_ID[s]).filter(id => id != null && !seen.has(id) && seen.add(id));
}

// §4.6, default PT role mix per Step 1 path. Returns canonical pt-* ids.
function getDrPtDefaultRoles(state) {
  const q = (state && state.qualifier) || state || {};
  if (!q.q1) return [];

  switch (q.q1) {
    case 'dtc': {
      if (q.q1a === 'electronics' || q.q1a === 'jewellery') {
        return _drptMap(['3d-designer-pt', '2d-designer', 'web-developer-pt']);
      }
      return _drptMap(['2d-designer', 'motion-graphic-designer', 'automation-ai-agent-pt', 'copywriter-pt']);
    }
    case 'saas': {
      if (q.q1a === 'product-led') {
        return _drptMap(['web-developer-pt', 'general-marketer-pt', 'copywriter-pt']);
      }
      // sales-led / hybrid / unset
      return _drptMap(['appointment-setter-pt', 'sales-rep-closer-pt', 'copywriter-pt']);
    }
    case 'bservices': {
      if (q.q1b === 'midmarket') {
        // 'enterprise' bucket per spec, closest in our q1b is 'midmarket'
        return _drptMap(['seo-specialist-pt', 'sales-rep-closer-pt', 'copywriter-pt']);
      }
      if (q.bservicesCat === 'agency') {
        return _drptMap(['web-developer-pt', 'general-marketer-pt', '2d-designer']);
      }
      return _drptMap(['general-marketer-pt', 'web-developer-pt', 'copywriter-pt']);
    }
    case 'bcservices':
      return _drptMap(['virtual-assistant-pt', 'appointment-setter-pt', '2d-designer']);
    case 'mktplace':
      return _drptMap(['general-marketer-pt', 'web-developer-pt', 'automation-ai-agent-pt']);
    case 'both':
    default: {
      if (q.q3 === 'prerevenue') {
        return _drptMap(['virtual-assistant-pt', 'copywriter-pt', '2d-designer']);
      }
      return _drptMap(['general-marketer-pt', 'copywriter-pt', '2d-designer']);
    }
  }
}
window.getDrPtDefaultRoles = getDrPtDefaultRoles;

// §3.2, Commit-length auto-recommendation for FT. Short trading length OR
// urgency=now defaults the commit to 3-mo; otherwise 12-mo (best rate).
function getDrFtRecommendedCommit(state) {
  const q = (state && state.qualifier) || state || {};
  if (q.tradingLength === 'under6m' || q.urgency === 'now') return '3';
  if (q.tradingLength === '6-12m') return '6';
  return '12';
}
window.getDrFtRecommendedCommit = getDrFtRecommendedCommit;

// §3.8 / §10, Add-on render-order score for the dedicated-ft addons.
// Returns a map of addonId → score. The service card sorts addons by score
// descending (ties by declaration order) and surfaces a Recommended ribbon
// on any addon with score ≥ 5.
function getDrFtAddonScore(state) {
  const q = (state && state.qualifier) || state || {};
  const sel = state?.selections?.['dedicated-ft'];
  const scoreMap = {
    'visa':      0,
    'buyout':    0,
    'uk-direct': 0,
    'it-equip':  0,
    'benefits':  0,
    'coworking': 0,
    'pro-dev':   0,
  };
  // UK Direct Hire for B2B enterprise audiences
  if (q.q1 === 'bservices' && q.q1b === 'midmarket') scoreMap['uk-direct'] += 10;
  // IT Equipment for engineering-heavy SaaS
  if (q.q1 === 'saas' && q.q1a === 'product-led') scoreMap['it-equip'] += 8;
  // Benefits + Coworking for mature revenue
  if (q.q3 === '25mplus') {
    scoreMap['benefits']  += 7;
    scoreMap['coworking'] += 6;
  }
  // Pro Dev for mid-stage growth
  if (q.q3 === '1m-5m' || q.q3 === '5m-25m') scoreMap['pro-dev'] += 5;
  // Visa for international roles (any UK-located role)
  if (Array.isArray(sel?.roles)) {
    const ukRoles = sel.roles.some(rid => sel.roleConfigs?.[rid]?.location === 'uk');
    if (ukRoles) scoreMap['visa'] += 5;
  }
  // Buyout surfaces high if any selected role qualifies
  if (Array.isArray(sel?.roles) && window.shouldDefaultDrFtBuyout) {
    const hasQualifier = sel.roles.some(rid => window.shouldDefaultDrFtBuyout(state, rid));
    if (hasQualifier) scoreMap['buyout'] += 6;
  }
  return scoreMap;
}
window.getDrFtAddonScore = getDrFtAddonScore;

function isDrFtAddonRecommended(state, addonId) {
  const score = getDrFtAddonScore(state);
  return (score[addonId] || 0) >= 5;
}
window.isDrFtAddonRecommended = isDrFtAddonRecommended;

// §3.6 + §11, Talent FT capacity. Returns { status, fill, caption } based on
// global capacity (always 'Open' for v1 since ops capacity sync isn't wired)
// plus per-state shifts: urgency=now → Limited, more roles selected → fill
// climbs. Status drives the badge color (Open=green, Limited=amber, Closed=red).
const TALENT_CAPACITY_CAPTIONS = {
  open:    'We currently have capacity to onboard new clients for this service. We recommend scheduling a call soon to secure your place with our specialist team.',
  limited: 'Capacity is filling fast for this service. We can still take new clients this month, and we prioritise existing clients as slots tighten, so adding it now reserves your place.',
  closed:  'This service is at full capacity. Adding it reserves your place on the waiting list, and we email you as soon as a spot opens. We prioritise existing clients for waiting list onboarding.',
};
window.TALENT_CAPACITY_CAPTIONS = TALENT_CAPACITY_CAPTIONS;
function getDrFtTalentCapacity(state) {
  const q = (state && state.qualifier) || state || {};
  const sel = state?.selections?.['dedicated-ft'];
  const rolesCount = Array.isArray(sel?.roles) ? sel.roles.length : 0;
  // Base fill climbs as user selects roles (more roles = more capacity needed).
  const baseFill = Math.min(95, 40 + rolesCount * 8);
  // 'now' urgency proxy for the spec's hiringTime='within-14-days'.
  if (q.urgency === 'now') {
    return { status: 'limited', fill: Math.max(70, baseFill), caption: TALENT_CAPACITY_CAPTIONS.limited };
  }
  return { status: 'open', fill: baseFill, caption: TALENT_CAPACITY_CAPTIONS.open };
}
window.getDrFtTalentCapacity = getDrFtTalentCapacity;

// §3.7 + §12, Talent FT forecast. Returns { gorilla, inhouse, savings } for
// the side-by-side 12-month comparison. The Gorilla side uses the role's
// hourlyFrom × 160 hours/month × 12 (matches the "from £X/hour" headline
// shown on each role tile). The in-house side uses UK loaded cost = base
// salary midpoint × 1.4 loading factor.
//
// We don't have a full DR_FT_FEE_MATRIX populated yet, so the forecast is
// indicative based on the role's published `hourlyFrom` Philippines floor;
// when actual fee numbers ship, swap to drFtMonthlyForRole().
const DR_FT_UK_BASE_SALARY = {
  'ft-mkt-ops':  { junior: 28000, mid: 38000, senior: 52000 },
  'ft-graphic':  { junior: 26000, mid: 36000, senior: 48000 },
  'ft-copy':     { junior: 28000, mid: 38000, senior: 52000 },
  'ft-video':    { junior: 28000, mid: 40000, senior: 55000 },
  'ft-sdr':      { junior: 32000, mid: 42000, senior: 58000 },
  'ft-leadgen':  { junior: 32000, mid: 42000, senior: 58000 },
  'ft-csm':      { junior: 35000, mid: 48000, senior: 65000 },
  'ft-am':       { junior: 35000, mid: 48000, senior: 65000 },
  'ft-fullstack':{ junior: 42000, mid: 60000, senior: 85000 },
  'ft-ai':       { junior: 45000, mid: 65000, senior: 90000 },
  'ft-uiux':     { junior: 38000, mid: 55000, senior: 75000 },
  'ft-3d':       { junior: 38000, mid: 52000, senior: 70000 },
  'ft-vmotion':  { junior: 32000, mid: 48000, senior: 65000 },
  'ft-exec':     { junior: 26000, mid: 35000, senior: 48000 },
  'ft-va':       { junior: 22000, mid: 30000, senior: 42000 },
};
window.DR_FT_UK_BASE_SALARY = DR_FT_UK_BASE_SALARY;

function drFtInhouseAnnual(roleId, seniority) {
  const salaries = DR_FT_UK_BASE_SALARY[roleId];
  if (!salaries) return 0;
  const base = salaries[seniority || 'mid'] || salaries.mid || 0;
  // UK loaded cost = base × 1.4 (NI 13.8%, pension 5%, benefits, equipment,
  // training, recruitment fee, admin overhead). Conservative single multiplier.
  return Math.round(base * 1.4);
}
window.drFtInhouseAnnual = drFtInhouseAnnual;

function drFtForecast(state) {
  const sel = state?.selections?.['dedicated-ft'];
  if (!sel || !Array.isArray(sel.roles) || sel.roles.length === 0) {
    return { gorilla: { rows: [], total: 0 }, inhouse: { rows: [], total: 0 }, savings: 0 };
  }
  const svc = (window.SERVICES || []).find(s => s.id === 'dedicated-ft');
  const tierRoles = svc?.roles?.fulltime || [];
  // 2026-06-10: flat 3-month minimum commitment - no commit discounts.
  const commitMult = 1.0;
  const HOURS_PER_MONTH = 160; // 8h/day × 20 working days
  const gorillaRows = [];
  const inhouseRows = [];
  let gorillaTotal = 0;
  let inhouseTotal = 0;
  for (const rid of sel.roles) {
    const def = tierRoles.find(r => r.id === rid);
    if (!def) continue;
    const cfg = sel.roleConfigs?.[rid] || {};
    const seniority = cfg.seniority || 'mid';
    const baseHourly = def.hourlyFrom || 8;
    // Seniority multiplier on hourly: junior 0.85, mid 1.0, senior 1.35
    const senMult = ({ junior: 0.85, mid: 1.0, senior: 1.35 })[seniority] || 1.0;
    // Location multiplier: philippines 1.0, south-africa 1.3, uk 2.4
    const locMult = ({ philippines: 1.0, 'south-africa': 1.3, uk: 2.4 })[cfg.location || 'philippines'] || 1.0;
    const monthly = Math.round(baseHourly * senMult * locMult * HOURS_PER_MONTH * commitMult);
    const annual = monthly * 12;
    gorillaRows.push({ roleId: rid, name: def.name, monthly, annual });
    gorillaTotal += annual;
    const ihAnnual = drFtInhouseAnnual(rid, seniority);
    inhouseRows.push({ roleId: rid, name: def.name, annual: ihAnnual });
    inhouseTotal += ihAnnual;
  }
  return {
    gorilla: { rows: gorillaRows, total: gorillaTotal },
    inhouse: { rows: inhouseRows, total: inhouseTotal },
    savings: Math.max(0, inhouseTotal - gorillaTotal),
  };
}
window.drFtForecast = drFtForecast;

// ── Step 6 (Ready to launch) helpers per founders-step-6 spec §4.1, §5 ─────
// Summarises 2-3 Step 1 highlights for the plan-summary eyebrow.
// Examples:
//   DTC → "£175 AOV · 50% margin · monthly repeat"
//   SaaS sales-led → "Sales-led · 30-90 day cycle · £5M ARR"
function _aovLabel(v) {
  return ({ sub50:'£<50 AOV','50-100':'£50-100 AOV','100-250':'£100-250 AOV','250-500':'£250-500 AOV','500-750':'£500-750 AOV','750plus':'£750+ AOV','500plus':'£500+ AOV' })[v] || '';
}
function _marginLabel(v) {
  return ({ sub20:'<20% margin','20-40':'20-40% margin','40-60':'40-60% margin','60-80':'60-80% margin','80plus':'80%+ margin' })[v] || '';
}
function _repeatLabel(v) {
  return ({ onetime:'one-off', biannual:'twice-a-year', quarterly:'quarterly repeat', monthly:'monthly repeat', subscription:'subscription' })[v] || '';
}
function _saasCycleLabel(v) {
  return ({ 'sub30':'<30 day cycle','30-90':'30-90 day cycle','90-180':'90-180 day cycle','180plus':'180+ day cycle' })[v] || '';
}
function _arrLabel(v) {
  return ({ prerevenue:'pre-revenue','100k-1m':'£100k, 1M ARR','1m-5m':'£1-5M ARR','5m-25m':'£5-25M ARR','25mplus':'£25M+ ARR' })[v] || '';
}
function summarizeForEyebrow(state) {
  const q = (state && state.qualifier) || state || {};
  const parts = [];
  switch (q.q1) {
    case 'dtc': {
      if (q.q1aDtcAov)    parts.push(_aovLabel(q.q1aDtcAov));
      if (q.q1aDtcMargin) parts.push(_marginLabel(q.q1aDtcMargin));
      if (q.q1aDtcRepeat) parts.push(_repeatLabel(q.q1aDtcRepeat));
      break;
    }
    case 'saas': {
      if (q.q1a === 'sales-led')   parts.push('Sales-led');
      else if (q.q1a === 'product-led') parts.push('Product-led');
      else if (q.q1a === 'hybrid') parts.push('Hybrid');
      if (q.saasCycle) parts.push(_saasCycleLabel(q.saasCycle));
      if (q.q3) parts.push(_arrLabel(q.q3));
      break;
    }
    case 'bservices': {
      if (q.bservicesCat) parts.push(q.bservicesCat.charAt(0).toUpperCase() + q.bservicesCat.slice(1));
      if (q.q1a) parts.push(q.q1a.charAt(0).toUpperCase() + q.q1a.slice(1));
      if (q.bservicesCycle) parts.push(q.bservicesCycle.replace('-d','d'));
      break;
    }
    case 'bcservices': {
      if (q.q1a) parts.push(q.q1a.charAt(0).toUpperCase() + q.q1a.slice(1));
      if (q.bcservicesCat) parts.push(q.bcservicesCat.charAt(0).toUpperCase() + q.bcservicesCat.slice(1));
      break;
    }
    case 'both': {
      parts.push('Mixed B2B / B2C');
      if (q.q1a === 'mostly-b2b') parts.push('mostly B2B');
      else if (q.q1a === 'mostly-b2c') parts.push('mostly B2C');
      else if (q.q1a === 'split') parts.push('roughly split');
      if (q.q3) parts.push(_arrLabel(q.q3));
      break;
    }
    case 'mktplace': {
      if (q.q1a === 'supply') parts.push('Supply-side marketplace');
      else if (q.q1a === 'demand') parts.push('Demand-side marketplace');
      else parts.push('Marketplace');
      if (q.q3) parts.push(_arrLabel(q.q3));
      break;
    }
    default:
      if (q.q3) parts.push(_arrLabel(q.q3));
  }
  return parts.filter(Boolean).slice(0, 3).join(' · ');
}
window.summarizeForEyebrow = summarizeForEyebrow;

// Plan title per client type (§4.1)
function getPlanTitle(clientTypeId, intentId) {
  if (clientTypeId === 'investor') {
    // 2026-06-08 (Loom 30): the final-step plan box reflects the chosen
    // investor intent rather than always saying "portfolio plan".
    if (intentId === 'investor-considering') return 'Investment assessment plan';
    if (intentId === 'investor-general')     return 'Investor enquiry';
    if (intentId === 'investor-partnership') return 'Investor Portal plan';
    return 'Investor portfolio plan';
  }
  if (clientTypeId === 'agency') return 'Agency partner plan';
  return 'Founders growth plan';
}
window.getPlanTitle = getPlanTitle;

// 2026-07-01 (Batch 1): freelancer wording transform. In freelancer mode the
// reused agency engine still says "your agency" in long-form overlay copy and
// qualifier labels. flAgencyStr rewrites those phrases to "your business";
// flAgencyDeep walks a plain data object (no JSX) applying the same. Both are
// no-ops on strings that do not contain the agency phrasing, so they are safe
// to apply broadly. Only used when window.isFreelancerMode() is true.
function flAgencyStr(str) {
  if (typeof str !== 'string') return str;
  return str
    .replace(/your agency’s/g, 'your business’s')
    .replace(/your agency's/g, "your business's")
    .replace(/Your agency’s/g, 'Your business’s')
    .replace(/Your agency's/g, "Your business's")
    .replace(/your agency/g, 'your business')
    .replace(/Your agency/g, 'Your business');
}
window.flAgencyStr = flAgencyStr;
function flAgencyDeep(v) {
  if (typeof v === 'string') return flAgencyStr(v);
  if (Array.isArray(v)) return v.map(flAgencyDeep);
  if (v && typeof v === 'object') {
    if (v.$$typeof) return v; // React element, leave untouched
    var o = {};
    for (var k in v) { if (Object.prototype.hasOwnProperty.call(v, k)) o[k] = flAgencyDeep(v[k]); }
    return o;
  }
  return v;
}
window.flAgencyDeep = flAgencyDeep;

// §5.6, Submit button copy per call preference.
function getStep6SubmitCopy(callPref) {
  switch (callPref) {
    case 'email-first':      return 'Email me the details →';
    case 'already-had':      return 'Send the proposal over →';
    case 'contract-payment': return 'Request my payment link →';
    case 'schedule':
    default:                 return 'Continue to scheduling →';
  }
}
window.getStep6SubmitCopy = getStep6SubmitCopy;

// §5.5, Next-step hint per call preference.
function getStep6NextHint(callPref) {
  switch (callPref) {
    case 'email-first': return 'Next: we email you the details and follow up';
    case 'already-had': return null; // hidden, early-exit caption replaces it
    case 'schedule':
    default:            return 'Next: schedule a call after you submit';
  }
}
window.getStep6NextHint = getStep6NextHint;

// §5.7, Trust line per call preference.
function getStep6TrustLine(callPref, clientTypeId, intentId) {
  if (callPref === 'email-first') {
    return 'No commitment. We email you the details and follow up, and you can book a call any time.';
  }
  if (callPref === 'already-had') {
    return 'We will send your proposal within one business day. No further steps are needed from you.';
  }
  if (callPref === 'contract-payment') {
    return 'We will send your payment link within one to two hours so you can start straight away.';
  }
  // 2026-06-08 (Loom 30): schedule trust line tailored per investor intent.
  if (clientTypeId === 'investor') {
    if (intentId === 'investor-considering') return 'Free, no obligation. We assess the target\'s marketing and share diligence-ready notes.';
    if (intentId === 'investor-general')     return 'No commitment. Book a call to talk through whatever interests you.';
    if (intentId === 'investor-partnership') return 'No commitment. We will email you as soon as your Investor Portal place opens.';
  }
  return 'No commitment, cancel any time. We email your proposal within one business day, then you choose a time to review it with us.';
}
window.getStep6TrustLine = getStep6TrustLine;

// Sprint 3, S&DG meeting-volume estimates per tier × deal-size band.
// Conservative defaults; flagged as "confirm against active client data" in
// the handover Open Questions §6 #4. Used by costPerMeeting() helper below.
const SDG_MEETINGS_PER_MONTH = {
  starter: { 'lt-100': 8, '100-1k': 5, '1k-10k': 4, '10k-100k': 3, 'gt-100k': 2 },
  grow:    { 'lt-100':16, '100-1k':12, '1k-10k': 9, '10k-100k': 6, 'gt-100k': 4 },
  scale:   { 'lt-100':30, '100-1k':22, '1k-10k':16, '10k-100k':11, 'gt-100k': 8 },
};
window.SDG_MEETINGS_PER_MONTH = SDG_MEETINGS_PER_MONTH;

// Lead-volume defaults shipped with each tier (for cost-per-meeting math).
// Increase ratio: more leads → more meetings, with reasonable conversion.
const SDG_LEADS_PER_MONTH = { starter: 600, grow: 1600, scale: 4000 };
window.SDG_LEADS_PER_MONTH = SDG_LEADS_PER_MONTH;

// Returns the all-in cost-per-meeting for S&DG given tier + commit + qualifier
// answers. Includes the monthly retainer (after agency multiplier) + per-lead
// pricing × leads volume. Returns null if any input is missing.
// Sprint 4, GorillaPerks: bundled benefits unlocked on every paid plan.
// Universal perks apply to all plans; client-type perks layer on top.
const GORILLA_PERKS = {
  universal: [
    { id: 'gp-shield',  icon: '◆', label: 'GorillaShield warranty',     desc: 'If we miss the agreed quality bar, the next month is free.' },
    { id: 'gp-review',  icon: '✦', label: 'Monthly strategy review',     desc: 'A 30-min review with your CSM each month, every month.' },
    { id: 'gp-tools',   icon: '◇', label: 'Stack tools included',         desc: 'No extra licence fees for the SaaS we run on top, Apollo, Clay, SendSpark, Smartlead, etc.' },
  ],
  founder: [
    { id: 'gp-fp',      icon: '✧', label: 'Founders Portal access',       desc: 'Free read-only access to our investor matching tool.' },
    { id: 'gp-audit',   icon: '◆', label: 'Free baseline growth audit',   desc: 'Pre-engagement audit of your channels, copy, funnel.' },
  ],
  agency: [
    { id: 'gp-csm',     icon: '✧', label: 'Dedicated agency CSM',         desc: 'A single point of contact across all your client engagements.' },
    { id: 'gp-reseller',icon: '◇', label: 'Reseller perks',               desc: '10% recurring referral commission for the duration of the client\'s minimum commitment. Your referred client also receives 10% off their plan for the same period.' },
    { id: 'gp-wlbrand', icon: '◆', label: 'White-label branding',         desc: 'Unbranded deliverables you ship as your own.' },
  ],
  investor: [
    { id: 'gp-pcsm',    icon: '✧', label: 'Portfolio CSM',                desc: 'Dedicated CSM coordinating across every portfolio company we onboard.' },
    { id: 'gp-fund',    icon: '◇', label: 'Fund-wide pricing',            desc: 'Discounted rates on the portfolio companies we onboard, confirmed at proposal.' },
    { id: 'gp-comms',   icon: '◆', label: '10% recurring commission',     desc: 'On every portfolio company that ends up signing with us.' },
  ],
};
window.GORILLA_PERKS = GORILLA_PERKS;

// Returns the merged perks list for a client type, universal + client-specific.
function gorillaPerksFor(clientTypeId) {
  const universal = GORILLA_PERKS.universal || [];
  const specific = GORILLA_PERKS[clientTypeId] || [];
  return [...universal, ...specific];
}
window.gorillaPerksFor = gorillaPerksFor;

// ── Tier comparison matrices ─────────────────────────────────────────────
// Powers the "Compare all tiers" modal launched from inside each service
// card. Per-service object with a subtitle + a rows[] array. Row types:
//
//   { type: 'header', label: 'CHANNELS' }      , section heading
//   { type: 'price', tiers: { starter, grow, scale, enterprise } }
//   { type: 'check', name: 'Email outbound',
//     tiers: { starter: 'yes'|'no'|'addon', ... } } , ✓/− matrix row
//   { type: 'text',  tiers: { starter, grow, scale, enterprise } }
//                                             , free-text per-tier rows
//   { type: 'best',  tiers: { starter, grow, scale, enterprise } }
//                                             , emphasis "Best for" row
//
// Data distilled from Airtable Services bullets (base appRgD9IZC7YHxw1d).
const TIER_COMPARISON = {
  sales: {
    subtitle: 'Same SDR pod, more channels and richer signals as you scale. Switch tiers any time after the initial term, no penalty, no renegotiation.',
    rows: [
      { type: 'header', label: 'Channels' },
      { type: 'check', name: 'Email outreach',
        tiers: { starter: 'yes', grow: 'yes', scale: 'yes', enterprise: 'yes' } },
      { type: 'check', name: 'LinkedIn outreach',
        tiers: { starter: 'addon', grow: 'addon', scale: 'addon', enterprise: 'addon' } },
      { type: 'check', name: 'Phone (cold calling)',
        tiers: { starter: 'no', grow: 'yes', scale: 'yes', enterprise: 'yes' } },
      { type: 'check', name: 'WhatsApp follow-up',
        tiers: { starter: 'no', grow: 'yes', scale: 'yes', enterprise: 'yes' } },
      { type: 'check', name: 'Instagram outreach',
        tiers: { starter: 'no', grow: 'yes', scale: 'yes', enterprise: 'yes' } },
      { type: 'check', name: 'Meta ad warm-up',
        tiers: { starter: 'no', grow: 'no', scale: 'yes', enterprise: 'yes' } },
      { type: 'check', name: 'Google Ads warm-up',
        tiers: { starter: 'no', grow: 'no', scale: 'no', enterprise: 'yes' } },

      { type: 'header', label: 'Volume' },
      { type: 'text', tiers: {
          starter: '750 prospects/mo',
          grow: '1,000 prospects/mo',
          scale: '1,250 prospects/mo',
          enterprise: 'Unlimited verified prospects',
      } },
      { type: 'text', tiers: {
          starter: 'Standard databases · premium niche sources available as add-ons',
          grow: 'Standard databases · premium niche sources available as add-ons',
          scale: 'Standard databases · premium niche sources available as add-ons',
          enterprise: 'Bespoke + niche & gated sources included',
      } },

      { type: 'header', label: 'Buyer-intent signals' },
      { type: 'text', tiers: {
          starter: 'Persona and intent-matched targeting',
          grow: 'Deeper intent layering',
          scale: 'Advanced intent scoring and web-intent tracking',
          enterprise: 'Predictive modelling and custom triggers',
      } },

      { type: 'header', label: 'Cadence' },
      { type: 'text', tiers: {
          starter: 'Persona-personalised 4-touch email cadence',
          grow: 'Cohort-personalised 6-touch multi-channel cadence',
          scale: '1:1 per-prospect 10-touch multi-channel cadence',
          enterprise: 'Custom cadence + buying-committee orchestration',
      } },

      { type: 'header', label: 'Meetings booked' },
      { type: 'text', tiers: {
          starter: 'Into your calendar via email',
          grow: 'Into your calendar via email, LinkedIn, and phone',
          scale: 'Into your calendar via email, LinkedIn, and phone',
          enterprise: 'Into your calendar via email, LinkedIn, and phone',
      } },

      { type: 'header', label: 'Reporting and optimisation' },
      { type: 'text', tiers: {
          starter: 'Weekly reporting and campaign optimisation',
          grow: 'Weekly reporting and GorillaLearn optimisation',
          scale: 'Weekly reporting, monthly strategic review, and GorillaLearn optimisation',
          enterprise: 'Detailed reporting, audits, and strategic reviews',
      } },

      { type: 'header', label: 'Best for' },
      { type: 'best', tiers: {
          starter: 'Founders testing outbound, sub-£1k deal sizes, or local/regional businesses.',
          grow: '£1k-£10k deals, mid-market SaaS or B2B services with proven product-market fit.',
          scale: '£10k+ deals, mid-market/enterprise with 4+ SDRs, multi-thread outbound.',
          enterprise: '£25M+ revenue, multi-product portfolios, dedicated programme manager.',
      } },
    ],
  },

  email: {
    subtitle: 'Same managed email programme, with more channels, depth, and senior specialists as you scale.',
    rows: [
      { type: 'header', label: 'Channels' },
      { type: 'text', tiers: {
          starter: 'Email',
          grow: 'Email',
          scale: 'Email and SMS',
          enterprise: 'Email and SMS',
      } },
      { type: 'header', label: 'Segmentation' },
      { type: 'text', tiers: {
          starter: 'Basic',
          grow: 'Intermediate',
          scale: 'Advanced',
          enterprise: 'Granular and predictive',
      } },
      { type: 'header', label: 'Email design' },
      { type: 'text', tiers: {
          starter: 'Basic',
          grow: 'Intermediate',
          scale: 'Advanced',
          enterprise: 'Custom HTML and animation',
      } },
      { type: 'header', label: 'Integrations' },
      { type: 'text', tiers: {
          starter: 'Up to 10',
          grow: 'Up to 100',
          scale: 'Up to 300',
          enterprise: 'Up to 350+',
      } },
      { type: 'header', label: 'A/B testing' },
      { type: 'check', name: 'Structured A/B testing programme',
        tiers: { starter: 'no', grow: 'yes', scale: 'yes', enterprise: 'yes' } },
      { type: 'header', label: 'Deliverability' },
      { type: 'check', name: 'Dedicated IP setup and warm-up',
        tiers: { starter: 'no', grow: 'no', scale: 'yes', enterprise: 'yes' } },
      { type: 'header', label: 'Security and privacy' },
      { type: 'text', tiers: {
          starter: 'Standard',
          grow: 'Standard',
          scale: 'Standard',
          enterprise: 'Advanced',
      } },
    ],
  },

  'paid-ads': {
    // 2026-06-12 (review): deliverable-led rows, no specialist counts or
    // trademarked process names, channel counts current with Loom 38.
    subtitle: 'Same managed paid programme, more channels and creative output as you scale.',
    rows: [
      { type: 'header', label: 'Channels' },
      { type: 'text', tiers: {
          starter: '1 search channel',
          grow: '1 social channel',
          scale: '2 or more channels',
          enterprise: '3 or more channels',
      } },
      { type: 'header', label: 'Creatives' },
      { type: 'check', name: 'Unlimited graphic + video creatives',
        tiers: { starter: 'no', grow: 'yes', scale: 'yes', enterprise: 'yes' } },
      { type: 'header', label: 'Campaign management' },
      { type: 'text', tiers: {
          starter: 'Fully managed campaigns with weekly optimisation',
          grow: 'Fully managed campaigns with weekly optimisation',
          scale: 'Daily pacing and full-funnel optimisation',
          enterprise: 'Enterprise-scale managed spend with custom strategy',
      } },
      { type: 'header', label: 'Collaboration' },
      { type: 'check', name: 'Direct team collaboration in Slack',
        tiers: { starter: 'yes', grow: 'yes', scale: 'yes', enterprise: 'yes' } },
    ],
  },

  smm: {
    // 2026-06-12 (review): creative deliverables lead, no specialist counts
    // or trademarked process names.
    subtitle: 'Same managed social programme, more creative volume and depth as you scale.',
    rows: [
      { type: 'header', label: 'Creative assets' },
      { type: 'text', tiers: {
          starter: 'Up to 8 a month',
          grow: 'Up to 12 a month',
          scale: 'Up to 16 a month',
          enterprise: 'Custom volume',
      } },
      { type: 'header', label: 'Graphics & video' },
      { type: 'text', tiers: {
          starter: 'Standard',
          grow: 'Custom',
          scale: 'Advanced',
          enterprise: 'Premium',
      } },
      { type: 'header', label: 'Community engagement' },
      { type: 'check', name: 'Community management',
        tiers: { starter: 'no', grow: 'yes', scale: 'yes', enterprise: 'yes' } },
    ],
  },
  'founders-portal': {
    subtitle: 'The same founder fundraising toolkit, with more investor access and hands-on support as you move up. Upgrade any time.',
    rows: [
      { type: 'header', label: 'Investor database' },
      { type: 'text', tiers: {
          starter: 'Up to 50 investors from our database',
          grow: '7,500+ verified investors',
          scale: '7,500+ verified investors',
      } },
      { type: 'header', label: 'Profile and visibility' },
      { type: 'check', name: 'Verified badge with priority placement',
        tiers: { starter: 'no', grow: 'yes', scale: 'yes' } },
      { type: 'check', name: 'Pitch deck and video upload',
        tiers: { starter: 'no', grow: 'yes', scale: 'yes' } },
      { type: 'header', label: 'Search and analytics' },
      { type: 'text', tiers: {
          starter: 'Basic search and filters',
          grow: 'Advanced search and analytics',
          scale: 'Advanced search and analytics',
      } },
      { type: 'header', label: 'Hands-on support' },
      { type: 'check', name: 'White-glove investor introductions',
        tiers: { starter: 'no', grow: 'no', scale: 'yes' } },
      { type: 'check', name: 'Dedicated capital associate',
        tiers: { starter: 'no', grow: 'no', scale: 'yes' } },
      { type: 'check', name: 'Referral rewards',
        tiers: { starter: 'no', grow: 'no', scale: 'yes' } },
      { type: 'header', label: 'Best for' },
      { type: 'best', tiers: {
          starter: 'Founders exploring the investor database and getting set up.',
          grow: 'Founders actively raising who want verified investors and richer tools.',
          scale: 'Founders who want white-glove introductions and a dedicated associate.',
      } },
    ],
  },
  'investor-portal': {
    subtitle: 'The same investor network, with richer deal flow and hands-on origination as you move up.',
    rows: [
      { type: 'header', label: 'Deal flow' },
      { type: 'text', tiers: {
          starter: 'Monthly general-interest deal flow digest',
          grow: 'Weekly thesis-matched deal flow digest',
          scale: 'Dedicated buy-side deal origination',
      } },
      { type: 'header', label: 'Matching and network' },
      { type: 'check', name: 'Smart Deal Matching, AI-powered by thesis, sector, stage and cheque size',
        tiers: { starter: 'no', grow: 'yes', scale: 'yes' } },
      { type: 'check', name: 'Exclusive co-investment network access',
        tiers: { starter: 'no', grow: 'yes', scale: 'yes' } },
      { type: 'header', label: 'Founder directory' },
      { type: 'text', tiers: {
          starter: 'Curated directory, predominantly UK and US',
          grow: 'Advanced search and filtering',
          scale: 'Curated opportunity briefs with thesis match',
      } },
      { type: 'header', label: 'Engagement and support' },
      { type: 'text', tiers: {
          starter: 'Trusted Partner Network access',
          grow: 'Personal opportunity tracker and quarterly community calls',
          scale: 'Named mandate lead with quarterly reviews',
      } },
      { type: 'header', label: 'Mandate' },
      { type: 'check', name: 'Live 12-month mandate with continuous sourcing',
        tiers: { starter: 'no', grow: 'no', scale: 'yes' } },
      { type: 'check', name: 'Success-fee engagement, no upfront cost',
        tiers: { starter: 'no', grow: 'no', scale: 'yes' } },
      { type: 'header', label: 'Best for' },
      { type: 'best', tiers: {
          starter: 'Investors who want curated deal flow and partner benefits at no cost.',
          grow: 'Active investors who want thesis-matched deal flow and smart matching.',
          scale: 'Investors who want dedicated buy-side origination and a live mandate.',
      } },
    ],
  },
  fundraising: {
    subtitle: 'One-time fundraising support packages. Pick the level of help you need to get investor-ready and close your round.',
    rows: [
      { type: 'header', label: 'What you get' },
      { type: 'text', tiers: {
          starter: 'Pitch deck and one-pager',
          grow: 'Full data room',
          scale: 'End-to-end campaign',
          enterprise: 'Fully bespoke programme',
      } },
      { type: 'text', tiers: {
          starter: 'Investor research',
          grow: 'Financial model',
          scale: 'Live investor meetings',
      } },
      { type: 'text', tiers: {
          starter: 'Email intro templates',
          grow: 'Pitch coaching, 3 sessions',
          scale: 'Roadshow coordination',
      } },
      { type: 'text', tiers: {
          grow: 'Investor outreach list',
          scale: 'Negotiation support',
      } },
      { type: 'header', label: 'Best for' },
      { type: 'best', tiers: {
          starter: 'Pre-seed startups preparing their first raise who need professional materials and self-service outreach.',
          grow: 'Seed-stage startups raising £250k to £2M who need full support across materials, research, and strategy.',
          scale: 'Series A startups raising £2M+ who need premium materials, deep investor research, and hands-on strategy.',
          enterprise: 'Later-stage or complex raises that need a fully bespoke, hands-on fundraising programme.',
      } },
    ],
  },
  content: {
    subtitle: 'The same managed content engine, with more volume, formats, and senior editorial as you scale.',
    rows: [
      { type: 'header', label: 'Duration' },
      { type: 'text', tiers: { starter: '1 month', grow: '1 to 2 months', scale: '2 to 6 months', enterprise: 'Custom, typically 3 to 12 months' } },
      { type: 'header', label: 'Videos' },
      { type: 'text', tiers: { starter: 'Up to 6 creator-led videos a month', grow: 'Up to 10 creator-led videos a month', scale: 'Up to 15 creator videos and 5 studio assets a month', enterprise: 'Custom volume across brands and regions' } },
      { type: 'header', label: 'Production' },
      { type: 'text', tiers: { starter: 'Standard post-production editing', grow: 'Advanced post-production editing', scale: 'Premium editing with storyboard and script development', enterprise: 'Premium production with on-location shoots' } },
      { type: 'header', label: 'Revisions' },
      { type: 'text', tiers: { starter: '2 rounds', grow: '3 rounds', scale: '4 rounds', enterprise: 'Custom' } },
      { type: 'header', label: 'Ownership' },
      { type: 'check', name: 'Full ownership of final assets',
        tiers: { starter: 'yes', grow: 'yes', scale: 'yes', enterprise: 'yes' } },
      { type: 'header', label: 'Best for' },
      { type: 'best', tiers: {
          starter: 'DTC and B2B founders kickstarting brand awareness through fully managed, creator-led content.',
          grow: 'DTC and B2B founders scaling customer acquisition through high-volume, creator-led content.',
          scale: 'DTC and B2B founders scaling through fully managed, multi-channel content with creator-led and studio-produced video.',
          enterprise: 'DTC and B2B founders scaling enterprise content production across brands, regions, and formats.',
      } },
    ],
  },
  motion: {
    subtitle: 'The same photorealistic 3D studio, with longer runtimes, full custom production, and broadcast finishing as you scale.',
    rows: [
      { type: 'header', label: 'Videos' },
      { type: 'text', tiers: { starter: '1 hero video with 4 cut-downs', grow: '2 hero videos with 8 cut-downs', scale: '3 hero videos, a long-form explainer, and 12 cut-downs', enterprise: 'Unlimited 3D animation per engagement' } },
      { type: 'header', label: 'Avatars and scenes' },
      { type: 'text', tiers: { starter: 'Photoreal product rendering', grow: '1 custom AI avatar with branded environments', scale: '2 custom AI avatars with expanded motion', enterprise: 'Unlimited custom AI avatars across campaigns' } },
      { type: 'header', label: 'Delivery' },
      { type: 'text', tiers: { starter: 'FHD, multiple aspect ratios', grow: 'FHD with QHD upgrade', scale: 'QHD with 4K upgrade', enterprise: '4K to 8K rendering' } },
      { type: 'header', label: 'Revisions' },
      { type: 'text', tiers: { starter: '3 rounds', grow: '5 rounds', scale: 'Unlimited', enterprise: 'Unlimited across all languages' } },
      { type: 'header', label: 'Best for' },
      { type: 'best', tiers: {
          starter: 'DTC and B2B founders launching quickly with AI-first photoreal product ads for multiplatform testing.',
          grow: 'DTC and B2B founders scaling brand-led photoreal ads with custom avatars, bespoke sound, and localisation.',
          scale: 'DTC and B2B founders running high-stakes campaigns that need AI-first scale with professional finishing.',
          enterprise: 'Established brands needing always-on AI-first 3D animation at broadcast fidelity across multiple campaigns.',
      } },
    ],
  },
};
window.TIER_COMPARISON = TIER_COMPARISON;

// ── Monthly Lead Volume widget (SDG) ─────────────────────────────────────────
// Single source: Standard databases. Each tier includes 750 leads/mo
// free. Additional leads beyond 750 are charged at a flat £4/lead.
// Slider capped at 5,000.
// 2026-05-22: lowered included tier amount 1000 -> 750.
// 2026-05-22: flat £4/lead (was £2.50 with 10% bulk discount).
const LEAD_PRICE_PER_UNIT = 4;
const LEAD_BULK_DISCOUNT = 0;
// 2026-05-26: included leads now mirror the tier feature pills shown on each
// SDG tier card. Starter 750, Grow 1,000, Scale 1,250 — these are the
// "prospects/mo" numbers users see when picking a tier.
const LEAD_INCLUDED_BY_TIER = { starter: 750, grow: 1000, scale: 1250, enterprise: 1250 };
const LEAD_INCLUDED_DEFAULT = 750;
const LEAD_VOLUME_MAX = 20000;
function leadIncludedForTier(tierId) {
  if (tierId && Object.prototype.hasOwnProperty.call(LEAD_INCLUDED_BY_TIER, tierId)) {
    return LEAD_INCLUDED_BY_TIER[tierId];
  }
  return LEAD_INCLUDED_DEFAULT;
}
function computeAdditionalLeadCost(monthlyLeads, tierId) {
  const included = leadIncludedForTier(tierId);
  const additional = Math.max(0,
    Math.min(LEAD_VOLUME_MAX, Number(monthlyLeads) || 0) - included
  );
  // 2026-06-02 (#6): Starter extra-lead rate is GBP3/lead (Alexander Loom 14 #6, agreed; reverted CALC-02 back to 3).
  const _leadRate = (tierId === 'starter') ? 3 : LEAD_PRICE_PER_UNIT;
  if (additional === 0) return { additional: 0, cost: 0, included };
  const cost = Math.round(additional * _leadRate * (1 - LEAD_BULK_DISCOUNT));
  return { additional, cost, included };
}
// Back-compat: LEAD_INCLUDED_PER_TIER / LEAD_VOLUME_MIN exported for any
// caller still referring to the old constants. Both resolve to the Starter
// floor (750), which keeps existing math correct when no tier is picked yet.
const LEAD_INCLUDED_PER_TIER = LEAD_INCLUDED_DEFAULT;
const LEAD_VOLUME_MIN = LEAD_INCLUDED_DEFAULT;
window.LEAD_PRICE_PER_UNIT = LEAD_PRICE_PER_UNIT;
window.LEAD_BULK_DISCOUNT = LEAD_BULK_DISCOUNT;
window.LEAD_INCLUDED_BY_TIER = LEAD_INCLUDED_BY_TIER;
window.LEAD_INCLUDED_PER_TIER = LEAD_INCLUDED_PER_TIER;
window.LEAD_VOLUME_MIN = LEAD_VOLUME_MIN;
window.LEAD_VOLUME_MAX = LEAD_VOLUME_MAX;
window.leadIncludedForTier = leadIncludedForTier;
window.computeAdditionalLeadCost = computeAdditionalLeadCost;

