// cust-data.jsx — Customer Insights data + i18n

const CUST_T = {
  ru: {
    pageTitle: 'Customer Insights',
    pageSub: 'CRM · все гости с дедупликацией',
    sec1: 'Snapshot',
    sec2: 'Сегменты RFM',
    sec3: 'LTV & когорты',
    sec4: 'Acquisition & Retention',
    sec5: 'Топ-гости & At-risk',
    sec6: 'Демография',
    dedupOk: 'Гости дедуплицированы',
    dedupWarn: 'потенциальных дубликатов',
    dedupReview: 'проверить →',
    privacy: 'Privacy',
    snap: {
      total: 'Total guests', totalSub: 'all time, deduplicated',
      active: 'Active guests', activeSub: 'guests with stay in period',
      newG: 'New guests', newSub: 'first-time stays',
      repeat: 'Repeat guests', repeatSub: '2+ stays in lifetime',
      repeatRate: 'Repeat rate', repeatRateSub: '% guests with 2+ stays',
      ltv: 'Avg LTV', ltvSub: 'median:',
      bpg: 'Avg bookings / guest', bpgSub: 'lifetime average',
      tbv: 'Time between visits', tbvSub: 'median, repeat guests',
      conc: 'Concentration', concSub: '% revenue from top 20% guests',
      review: 'Avg review score', reviewSub: 'as loyalty proxy',
    },
    snapInfo: {
      total: { title: 'Total guests', body: 'Уникальные гости за все время после дедупликации. Один человек считается один раз, даже если бронировал несколько раз.' },
      active: { title: 'Active guests', body: 'Гости, у которых есть проживание или визит в выбранном периоде. Показывает размер активной клиентской базы сейчас.' },
      newG: { title: 'New guests', body: 'Гости, которые впервые приехали в выбранном периоде. Помогает оценить приток новой аудитории.' },
      repeat: { title: 'Repeat guests', body: 'Гости с двумя и более проживаниями за всю историю. Это основная база для retention и CRM-активаций.' },
      repeatRate: { title: 'Repeat rate', body: 'Доля гостей, у которых было 2+ проживаний. Чем выше показатель, тем меньше зависимость от постоянного платного acquisition.' },
      ltv: { title: 'Avg LTV', body: 'Средняя выручка на гостя за весь жизненный цикл. Median показывает типичного гостя без влияния high-spend выбросов.' },
      bpg: { title: 'Avg bookings / guest', body: 'Среднее количество бронирований на одного гостя за весь срок. Рост означает, что база возвращается чаще.' },
      tbv: { title: 'Time between visits', body: 'Медианное время между визитами у repeat-гостей. Снижение обычно значит, что гости возвращаются быстрее.' },
      conc: { title: 'Concentration', body: 'Доля выручки, которую дают top 20% гостей. Высокое значение показывает зависимость от небольшой группы high-value гостей.' },
      review: { title: 'Avg review score', body: 'Средняя оценка отзывов гостей. Используется как proxy лояльности и качества клиентского опыта.' },
    },
    rfm: {
      title: 'Сегменты по recency × frequency',
      x: 'Frequency · посещений', y: 'Recency · последний визит',
      xLabels: ['1', '2', '3', '4', '5+'],
      yLabels: ['<2 мес', '2–6 мес', '6–12 мес', '1–2 г', '2+ года'],
      summaryTitle: 'Обзор сегментов',
      cols: { seg: 'Сегмент', g: 'Гости', basePct: '% базы', avgLTV: 'Avg LTV', rev: 'Выручка', revPct: '% выручки' },
      migrationTitle: 'Миграция сегментов · 6 месяцев',
      migrationEmpty: 'Snapshot tracking required to enable migration view; will populate over time as historical snapshots accumulate.',
    },
    seg: {
      champions: 'Champions',
      loyal: 'Loyal',
      promising: 'Promising',
      atrisk: 'At Risk',
      lost: 'Lost',
      hibernating: 'Hibernating',
      newSeg: 'New',
    },
    ltv: {
      distTitle: 'Распределение LTV',
      distNote: 'Right-skewed distribution typical for hospitality — большинство гостей с одним визитом, и узкая группа high-LTV гостей вытягивает среднее.',
      meanLbl: 'mean',
      medianLbl: 'median',
      ratioTitle: 'LTV / CAC',
      ratioAvgLtv: 'Avg LTV', ratioAvgCac: 'Avg CAC', ratioRatio: 'Ratio',
      ratioHealthy: 'Healthy (>3× обычно здоровый показатель)',
      ratioUnhealthy: 'Below benchmark',
      ratioInterp: 'Marketing pays back many times over the guest lifetime.',
      ratioInterpAlert: 'Marketing eating into guest lifetime margin.',
      cohortTitle: 'Когортная retention · % гостей с повторным визитом',
      cohortInsight: 'Recent cohorts (last 6 months) retain stronger at month-3 — acquisition quality may be improving.',
      cohortInsightAlert: 'Recent cohorts retain weaker at month-3 — acquisition quality declining.',
      ltvEvoTitle: 'Рост LTV по когортам',
      ltvEvoCaption: 'Higher curves = cohort generates more lifetime value. Steeper curves = faster value accumulation.',
      ltvEvoEmpty: 'Недостаточно зрелых когорт. Минимум 6 месяцев истории требуется.',
    },
    acq: {
      flowTitle: 'Acquisition flow · 12 мес',
      flowNew: 'New', flowReturn: 'Returning',
      flowInsight: 'New & Returning растут — база здоровая.',
      flowInsightAlert: 'Returning растёт, New flat — возможен acquisition slowdown.',
      timingTitle: 'Когда гости возвращаются',
      timingCaption: 'Большинство возвращается в',
      timingCaption2: '— оптимальное окно для re-engagement.',
      sourceTitle: 'Качество источников · acquisition',
      sourceCols: { src: 'Источник', n: 'New гостей', ltv: 'Avg LTV', rep: 'Repeat rate', cross: 'Cross-sell' },
      churnTitle: 'Сигналы оттока',
      churn1: 'не возвращались 12+ месяцев',
      churn2: 'из них с LTV > €500',
      churn3: 'с last visit летом (high-season-only)',
    },
    top: {
      title: 'Топ-гости по LTV',
      cols: { name: 'Гость', first: 'First visit', last: 'Last visit', visits: 'Visits', spent: 'Total spent', last_b: 'Last booking' },
      atRiskTitle: 'At-risk · personal outreach recommended',
      atRiskCols: { name: 'Гость', spent: 'Total spent', visits: 'Visits', last: 'Last visit', months: 'Months since', status: 'Status' },
      reEngage: 'Re-engage',
      lostTitle: 'Recently moved to Lost',
      lostSub: 'Last chance for win-back outreach',
      birthTitle: 'Birthdays & anniversaries · next 30 days',
      sendGreeting: 'Send greeting',
      birthEmpty: 'Нет данных о датах. Подсказка — собирайте birthday at check-in для активации модуля.',
    },
    demo: {
      geoTitle: 'Откуда приезжают гости',
      compTitle: 'Состав гостей',
      compInsight: 'Couples доминируют — основной target продукта.',
      patternsTitle: 'Booking behavior · база',
      patterns: {
        leadTime: 'Avg lead time',
        channel: 'Preferred channel',
        alos: 'Avg ALOS',
        spa: 'Cross-sell SPA',
        fnb: 'Cross-sell F&B',
        bv: 'Avg booking value',
        bvCross: 'Avg booking value (incl. cross-sell)',
      },
      compareTitle: 'Repeat vs New · почему retention важна',
      compareCols: { metric: 'Метрика', n: 'New', r: 'Repeat', d: 'Δ' },
      compareSummary: 'Repeat-гости превосходят new-гостей по всем метрикам. 1 retained ≈ 1.7 new в выручке.',
    },
    legend: {
      champions: 'Champions · недавние, 3+ визитов, высокий spend',
      loyal: 'Loyal · 2+ визитов в последние 12 мес',
      promising: 'Promising · 1 недавний визит, потенциал',
      atrisk: 'At Risk · были активны, но не возвращаются',
      lost: 'Lost · 12+ месяцев без визита',
      hibernating: 'Hibernating · 6–12 мес, низкая активность',
    },
    help: {
      rfmMatrix: { title: 'RFM matrix', body: 'Для чего: быстро разложить базу по свежести визита и частоте возвращений. Как работает: каждая ячейка — recency × frequency, цвет показывает CRM-сегмент.' },
      segmentSummary: { title: 'Обзор сегментов', body: 'Для чего: понять размер и ценность каждого CRM-сегмента. Как работает: считает гостей, долю базы, Avg LTV, выручку и вклад в revenue.' },
      segmentMigration: { title: 'Миграция сегментов', body: 'Для чего: видеть, улучшается или стареет клиентская база. Как работает: показывает переходы гостей между RFM-сегментами за 6 месяцев.' },
      ltvDist: { title: 'Распределение LTV', body: 'Для чего: увидеть, насколько база зависит от high-value гостей. Как работает: группирует гостей по lifetime revenue и отмечает mean/median.' },
      ltvCac: { title: 'LTV / CAC', body: 'Для чего: проверить, окупается ли привлечение гостей. Как работает: средний LTV делится на средний CAC; benchmark выше 3× обычно здоровый.' },
      cohorts: { title: 'Когортная retention', body: 'Для чего: сравнить качество новых гостей по месяцам первого визита. Как работает: каждая строка — cohort, ячейки показывают долю гостей с повторным визитом на M1/M3/M6 и дальше.' },
      ltvEvolution: { title: 'Рост LTV по когортам', body: 'Для чего: понять, как быстро новые cohorts накапливают ценность. Как работает: линии показывают средний LTV cohort по месяцам жизни.' },
      acqFlow: { title: 'Acquisition flow', body: 'Для чего: увидеть баланс новых и возвращающихся гостей. Как работает: stacked area сравнивает new и returning по месяцам за 12 месяцев.' },
      repeatTiming: { title: 'Когда гости возвращаются', body: 'Для чего: выбрать окно для re-engagement кампаний. Как работает: группирует повторные визиты по времени после предыдущего визита.' },
      sourceQuality: { title: 'Качество источников', body: 'Для чего: понять, какие acquisition sources дают не просто гостей, а хорошую клиентскую базу. Как работает: сравнивает new guests, Avg LTV, repeat rate и cross-sell.' },
      churnSignals: { title: 'Сигналы оттока', body: 'Для чего: найти гостей, которых стоит возвращать персонально. Как работает: считает тех, кто давно не возвращался, включая high-LTV и seasonal-only группы.' },
      topGuests: { title: 'Топ-гости по LTV', body: 'Для чего: видеть самых ценных гостей для персонального сервиса и retention. Как работает: ранжирует гостей по total spent и показывает историю визитов.' },
      atRiskGuests: { title: 'At-risk guests', body: 'Для чего: приоритизировать win-back outreach. Как работает: показывает гостей с давним последним визитом, их spend, visits и уровень ценности.' },
      recentlyLost: { title: 'Recently moved to Lost', body: 'Для чего: поймать момент, пока гостя еще можно вернуть. Как работает: список гостей, недавно перешедших в Lost-сегмент.' },
      birthdays: { title: 'Birthdays & anniversaries', body: 'Для чего: запускать персональные CRM-касания. Как работает: показывает события на ближайшие 30 дней, если даты собраны при check-in.' },
      geo: { title: 'География гостей', body: 'Для чего: понять, откуда приезжает аудитория. Как работает: распределяет гостей по странам/регионам и показывает долю и количество.' },
      composition: { title: 'Состав гостей', body: 'Для чего: видеть основной тип аудитории продукта. Как работает: группирует stays по типам гостей: пары, семьи, друзья, solo, business.' },
      behaviorPatterns: { title: 'Booking behavior', body: 'Для чего: зафиксировать типичный профиль поведения базы. Как работает: собирает lead time, preferred channel, ALOS, cross-sell и booking value.' },
      repeatVsNew: { title: 'Repeat vs New', body: 'Для чего: показать ценность retention. Как работает: сравнивает new и repeat гостей по booking value, ALOS, cross-sell, отменам и review score.' },
    },
  },
  en: {
    pageTitle: 'Customer Insights',
    pageSub: 'CRM · deduplicated guest base',
    sec1: 'Snapshot',
    sec2: 'RFM Segments',
    sec3: 'LTV & Cohorts',
    sec4: 'Acquisition & Retention',
    sec5: 'Top guests & At-risk',
    sec6: 'Demographics',
    dedupOk: 'Guests deduplicated',
    dedupWarn: 'potential duplicates',
    dedupReview: 'review →',
    privacy: 'Privacy',
    snap: {
      total: 'Total guests', totalSub: 'all time, deduplicated',
      active: 'Active guests', activeSub: 'guests with stay in period',
      newG: 'New guests', newSub: 'first-time stays',
      repeat: 'Repeat guests', repeatSub: '2+ stays in lifetime',
      repeatRate: 'Repeat rate', repeatRateSub: '% guests with 2+ stays',
      ltv: 'Avg LTV', ltvSub: 'median:',
      bpg: 'Avg bookings / guest', bpgSub: 'lifetime average',
      tbv: 'Time between visits', tbvSub: 'median, repeat guests',
      conc: 'Concentration', concSub: '% revenue from top 20% guests',
      review: 'Avg review score', reviewSub: 'as loyalty proxy',
    },
    snapInfo: {
      total: { title: 'Total guests', body: 'Unique guests across all history after deduplication. One person is counted once, even if they booked several times.' },
      active: { title: 'Active guests', body: 'Guests with a stay or visit in the selected period. Shows the current active customer base.' },
      newG: { title: 'New guests', body: 'Guests whose first stay happened in the selected period. Useful for tracking new audience inflow.' },
      repeat: { title: 'Repeat guests', body: 'Guests with two or more lifetime stays. This is the core base for retention and CRM activation.' },
      repeatRate: { title: 'Repeat rate', body: 'Share of guests with 2+ lifetime stays. Higher repeat rate means less dependence on constant paid acquisition.' },
      ltv: { title: 'Avg LTV', body: 'Average revenue per guest over the full customer lifetime. Median shows a typical guest without high-spend outliers.' },
      bpg: { title: 'Avg bookings / guest', body: 'Average number of bookings per guest over their lifetime. Growth means the base is returning more often.' },
      tbv: { title: 'Time between visits', body: 'Median gap between visits for repeat guests. Lower value usually means guests are coming back faster.' },
      conc: { title: 'Concentration', body: 'Revenue share generated by the top 20% of guests. High concentration shows dependence on a smaller high-value group.' },
      review: { title: 'Avg review score', body: 'Average guest review score. Used as a loyalty and experience-quality proxy.' },
    },
    rfm: {
      title: 'Segments by recency × frequency',
      x: 'Frequency · visits', y: 'Recency · last visit',
      xLabels: ['1', '2', '3', '4', '5+'],
      yLabels: ['<2 mo', '2–6 mo', '6–12 mo', '1–2 y', '2+ y'],
      summaryTitle: 'Segment overview',
      cols: { seg: 'Segment', g: 'Guests', basePct: '% of base', avgLTV: 'Avg LTV', rev: 'Revenue', revPct: '% of revenue' },
      migrationTitle: 'Segment migration · last 6 months',
      migrationEmpty: 'Snapshot tracking required to enable migration view; will populate over time as historical snapshots accumulate.',
    },
    seg: { champions: 'Champions', loyal: 'Loyal', promising: 'Promising', atrisk: 'At Risk', lost: 'Lost', hibernating: 'Hibernating', newSeg: 'New' },
    ltv: {
      distTitle: 'Lifetime value distribution',
      distNote: 'Right-skewed distribution typical for hospitality — most guests are single-stay; a narrow high-LTV group pulls the mean up.',
      meanLbl: 'mean', medianLbl: 'median',
      ratioTitle: 'LTV / CAC',
      ratioAvgLtv: 'Avg LTV', ratioAvgCac: 'Avg CAC', ratioRatio: 'Ratio',
      ratioHealthy: 'Healthy (>3× is generally healthy)',
      ratioUnhealthy: 'Below benchmark',
      ratioInterp: 'Marketing pays back many times over the guest lifetime.',
      ratioInterpAlert: 'Marketing eating into guest lifetime margin.',
      cohortTitle: 'Cohort retention · % of cohort with repeat stay',
      cohortInsight: 'Recent cohorts (last 6 months) retain stronger at month-3 — acquisition quality may be improving.',
      cohortInsightAlert: 'Recent cohorts retain weaker at month-3 — acquisition quality declining.',
      ltvEvoTitle: 'Avg LTV growth by cohort',
      ltvEvoCaption: 'Higher curves = cohort generates more lifetime value. Steeper curves = faster value accumulation.',
      ltvEvoEmpty: 'Not enough mature cohorts yet. Minimum 6 months of history required.',
    },
    acq: {
      flowTitle: 'Acquisition flow · 12 mo',
      flowNew: 'New', flowReturn: 'Returning',
      flowInsight: 'New & Returning both growing — base health strong.',
      flowInsightAlert: 'Returning growing while New flat — possible acquisition slowdown.',
      timingTitle: 'When guests return',
      timingCaption: 'Most repeat guests return in',
      timingCaption2: '— optimal window for re-engagement.',
      sourceTitle: 'Acquisition channel quality',
      sourceCols: { src: 'Source', n: 'New guests', ltv: 'Avg LTV', rep: 'Repeat rate', cross: 'Cross-sell' },
      churnTitle: 'Churn signal',
      churn1: 'guests not returned in 12+ months',
      churn2: 'of them with LTV > €500',
      churn3: 'with last visit in summer (high-season-only)',
    },
    top: {
      title: 'Top guests by LTV',
      cols: { name: 'Guest', first: 'First visit', last: 'Last visit', visits: 'Visits', spent: 'Total spent', last_b: 'Last booking' },
      atRiskTitle: 'At-risk guests · personal outreach recommended',
      atRiskCols: { name: 'Guest', spent: 'Total spent', visits: 'Visits', last: 'Last visit', months: 'Months since', status: 'Status' },
      reEngage: 'Re-engage',
      lostTitle: 'Recently moved to Lost',
      lostSub: 'Last chance for win-back outreach',
      birthTitle: 'Birthdays & anniversaries · next 30 days',
      sendGreeting: 'Send greeting',
      birthEmpty: 'No event data captured. Hint — collect birthdays at check-in to enable this module.',
    },
    demo: {
      geoTitle: 'Where guests come from',
      compTitle: 'Guest composition',
      compInsight: 'Couples dominate — primary product target.',
      patternsTitle: 'Booking behavior · base average',
      patterns: {
        leadTime: 'Avg lead time',
        channel: 'Preferred channel',
        alos: 'Avg ALOS',
        spa: 'Cross-sell SPA',
        fnb: 'Cross-sell F&B',
        bv: 'Avg booking value',
        bvCross: 'Avg booking value (incl. cross-sell)',
      },
      compareTitle: 'Repeat vs New · why retention matters',
      compareCols: { metric: 'Metric', n: 'New', r: 'Repeat', d: 'Δ' },
      compareSummary: 'Repeat guests outperform new guests on every metric. 1 retained guest ≈ 1.7 new in revenue impact.',
    },
    legend: {
      champions: 'Champions · recent, 3+ stays, high spend',
      loyal: 'Loyal · 2+ stays in last 12 mo',
      promising: 'Promising · 1 recent stay, potential',
      atrisk: 'At Risk · was active, not returning',
      lost: 'Lost · 12+ months no visit',
      hibernating: 'Hibernating · 6–12 mo, low activity',
    },
    help: {
      rfmMatrix: { title: 'RFM matrix', body: 'Purpose: quickly segments the base by recency and return frequency. How it works: each cell is recency × frequency; color marks the CRM segment.' },
      segmentSummary: { title: 'Segment overview', body: 'Purpose: shows the size and value of each CRM segment. How it works: counts guests, base share, Avg LTV, revenue, and revenue contribution.' },
      segmentMigration: { title: 'Segment migration', body: 'Purpose: shows whether the guest base is improving or aging. How it works: maps guest movement between RFM segments over 6 months.' },
      ltvDist: { title: 'LTV distribution', body: 'Purpose: shows dependence on high-value guests. How it works: groups guests by lifetime revenue and marks mean and median.' },
      ltvCac: { title: 'LTV / CAC', body: 'Purpose: checks whether acquisition pays back. How it works: average LTV divided by average CAC; above 3× is generally healthy.' },
      cohorts: { title: 'Cohort retention', body: 'Purpose: compares quality of new guests by first-stay month. How it works: each row is a cohort; cells show repeat-stay share at M1/M3/M6 and later.' },
      ltvEvolution: { title: 'Cohort LTV growth', body: 'Purpose: shows how quickly new cohorts build value. How it works: lines plot average cohort LTV across lifecycle months.' },
      acqFlow: { title: 'Acquisition flow', body: 'Purpose: shows balance between new and returning guests. How it works: stacked area compares new and returning guests by month over 12 months.' },
      repeatTiming: { title: 'Repeat timing', body: 'Purpose: chooses the best re-engagement window. How it works: groups repeat visits by time since previous visit.' },
      sourceQuality: { title: 'Source quality', body: 'Purpose: shows which acquisition sources create valuable guests, not just volume. How it works: compares new guests, Avg LTV, repeat rate, and cross-sell.' },
      churnSignals: { title: 'Churn signals', body: 'Purpose: finds guests worth personal win-back. How it works: counts guests who have not returned, including high-LTV and seasonal-only groups.' },
      topGuests: { title: 'Top guests by LTV', body: 'Purpose: identifies the most valuable guests for personal service and retention. How it works: ranks guests by total spent and shows visit history.' },
      atRiskGuests: { title: 'At-risk guests', body: 'Purpose: prioritizes win-back outreach. How it works: lists guests with old last visits, spend, visits, and value status.' },
      recentlyLost: { title: 'Recently moved to Lost', body: 'Purpose: catches guests while they may still be recoverable. How it works: lists guests recently moved into the Lost segment.' },
      birthdays: { title: 'Birthdays & anniversaries', body: 'Purpose: powers personal CRM touches. How it works: shows events in the next 30 days when dates are captured at check-in.' },
      geo: { title: 'Guest geography', body: 'Purpose: shows where the audience comes from. How it works: splits guests by country/region with share and count.' },
      composition: { title: 'Guest composition', body: 'Purpose: shows the core audience type for the product. How it works: groups stays into couples, families, friends, solo, and business.' },
      behaviorPatterns: { title: 'Booking behavior', body: 'Purpose: summarizes the typical behavior profile of the base. How it works: collects lead time, preferred channel, ALOS, cross-sell, and booking value.' },
      repeatVsNew: { title: 'Repeat vs New', body: 'Purpose: demonstrates the value of retention. How it works: compares new and repeat guests on booking value, ALOS, cross-sell, cancellations, and review score.' },
    },
  },
};

// Segment colors (consistent everywhere)
const SEG_COLORS = {
  champions:  '#10B981',
  loyal:      '#06B6D4',
  promising:  '#6366F1',
  atrisk:     '#F59E0B',
  lost:       '#94A3B8',
  hibernating:'#A78BFA',
  newSeg:     '#38BDF8',
  empty:      'transparent',
};

// Avatar palette — deterministic by hash
const AVATAR_COLORS = [
  '#6366F1', '#10B981', '#F59E0B', '#EC4899', '#06B6D4',
  '#8B5CF6', '#F97316', '#14B8A6', '#EF4444', '#3B82F6',
];

function avatarColor(name) {
  let h = 0;
  for (let i = 0; i < name.length; i++) h = (h * 31 + name.charCodeAt(i)) | 0;
  return AVATAR_COLORS[Math.abs(h) % AVATAR_COLORS.length];
}
function initialsOf(name) {
  return name.split(/\s+/).slice(0, 2).map(p => p[0] || '').join('').toUpperCase();
}
function maskName(name) {
  const parts = name.split(/\s+/);
  if (parts.length === 1) return parts[0][0].toUpperCase() + '.';
  return parts[0][0].toUpperCase() + '. ' + parts[parts.length - 1][0].toUpperCase() + '.';
}

const CUST = {
  // ===== Section 1 — Snapshot =====
  snapshot: {
    default: {
      total:     { value: 380, delta: 18, fmt: 'int', deltaFmt: 'abs' },
      active:    { value: 142, delta: 12, fmt: 'int', deltaFmt: 'abs' },
      newG:      { value: 71,  delta: 9,  fmt: 'int', deltaFmt: 'abs' },
      repeat:    { value: 71,  delta: 6,  fmt: 'int', deltaFmt: 'abs' },
      repeatRate:{ value: 28,  delta: 1.2, fmt: 'pct', deltaFmt: 'pp' },
      ltv:       { value: 620, median: 280, delta: 40, fmt: 'eur', deltaFmt: 'eur' },
      bpg:       { value: 1.6, delta: 0.1, fmt: 'dec1', deltaFmt: 'abs1' },
      tbv:       { value: 8.4, delta: -0.4, fmt: 'mo', deltaFmt: 'mo', invert: true },
      conc:      { value: 48,  delta: 0,   fmt: 'pct', deltaFmt: 'pp' },
      review:    { value: 9.1, delta: 0.1, fmt: 'dec1', deltaFmt: 'abs1' },
    },
    alert: {
      total:     { value: 372, delta: 4 },
      active:    { value: 124, delta: -8 },
      newG:      { value: 58,  delta: -12 },
      repeat:    { value: 66,  delta: -4 },
      repeatRate:{ value: 22,  delta: -5.6, alert: true },
      ltv:       { value: 540, median: 240, delta: -80, alert: true },
      bpg:       { value: 1.4, delta: -0.2 },
      tbv:       { value: 9.8, delta: 0.8, invert: true },
      conc:      { value: 58,  delta: 8.0,  alert: true },
      review:    { value: 8.7, delta: -0.4 },
    },
  },

  // ===== Section 2 — RFM matrix (rows=recency, cols=frequency) =====
  // segment classification per cell
  rfmMatrix: [
    // recency <2mo
    [ { n: 38, seg: 'newSeg' }, { n: 14, seg: 'promising' }, { n: 8,  seg: 'loyal' }, { n: 4,  seg: 'champions' }, { n: 6,  seg: 'champions' } ],
    // recency 2-6mo
    [ { n: 22, seg: 'promising' }, { n: 18, seg: 'loyal' }, { n: 12, seg: 'loyal' }, { n: 6,  seg: 'champions' }, { n: 2,  seg: 'champions' } ],
    // recency 6-12mo
    [ { n: 24, seg: 'atrisk' }, { n: 16, seg: 'atrisk' }, { n: 8,  seg: 'hibernating' }, { n: 4,  seg: 'hibernating' }, { n: 1,  seg: 'hibernating' } ],
    // recency 1-2y
    [ { n: 32, seg: 'lost' }, { n: 12, seg: 'lost' }, { n: 6,  seg: 'lost' }, { n: 2,  seg: 'lost' }, { n: 0,  seg: 'empty' } ],
    // recency 2+y
    [ { n: 48, seg: 'lost' }, { n: 8,  seg: 'lost' }, { n: 2,  seg: 'lost' }, { n: 0,  seg: 'empty' }, { n: 0,  seg: 'empty' } ],
  ],

  segments: [
    { id: 'champions',  guests: 42,  basePct: 11, ltv: 1840, rev: 77280,  revPct: 32 },
    { id: 'loyal',      guests: 56,  basePct: 15, ltv: 920,  rev: 51520,  revPct: 21 },
    { id: 'promising',  guests: 74,  basePct: 19, ltv: 380,  rev: 28120,  revPct: 12 },
    { id: 'atrisk',     guests: 48,  basePct: 13, ltv: 620,  rev: 29760,  revPct: 12 },
    { id: 'hibernating',guests: 32,  basePct:  8, ltv: 410,  rev: 13120,  revPct:  5 },
    { id: 'lost',       guests: 128, basePct: 34, ltv: 340,  rev: 43520,  revPct: 18 },
  ],

  // segment migration (sankey flows)
  migration: [
    { from: 'promising', to: 'loyal',     n: 18 },
    { from: 'loyal',     to: 'champions', n: 12 },
    { from: 'newSeg',    to: 'promising', n: 28 },
    { from: 'loyal',     to: 'atrisk',    n: 8  },
    { from: 'atrisk',    to: 'lost',      n: 14 },
    { from: 'champions', to: 'loyal',     n: 4  },
    { from: 'atrisk',    to: 'champions', n: 2  },
    { from: 'hibernating', to: 'lost',    n: 6  },
  ],

  // ===== Section 3 — LTV & Cohorts =====
  ltvDist: [
    { lo: 0,    hi: 250,  n: 168, pct: 44 },
    { lo: 250,  hi: 500,  n: 92,  pct: 24 },
    { lo: 500,  hi: 1000, n: 64,  pct: 17 },
    { lo: 1000, hi: 2000, n: 38,  pct: 10 },
    { lo: 2000, hi: 9999, n: 18,  pct:  5 },
  ],

  ltvCac: {
    default: { ltv: 620, cac: 68, ratio: 9.1, healthy: true },
    alert:   { ltv: 540, cac: 142, ratio: 3.8, healthy: true, marginal: true },
  },

  // cohort heatmap — rows = first-stay month
  cohorts: [
    // m0 always 100, future months = null
    { c: 'Apr 2024', size: 22, m: [100, 18, 14, 9,  18, 24, 28, 36] },
    { c: 'May 2024', size: 28, m: [100, 22, 18, 12, 24, 32, 38, 42] },
    { c: 'Jun 2024', size: 24, m: [100, 16, 14, 12, 22, 30, 34, 40] },
    { c: 'Jul 2024', size: 32, m: [100, 14, 12, 8,  20, 28, 32, 38] },
    { c: 'Aug 2024', size: 26, m: [100, 18, 14, 10, 22, 28, 34, null] },
    { c: 'Sep 2024', size: 20, m: [100, 22, 18, 14, 26, 32, 38, null] },
    { c: 'Oct 2024', size: 18, m: [100, 26, 22, 18, 30, 36, null, null] },
    { c: 'Nov 2024', size: 16, m: [100, 28, 24, 20, 32, null, null, null] },
    { c: 'Dec 2024', size: 22, m: [100, 32, 28, 22, null, null, null, null] },
    { c: 'Jan 2025', size: 18, m: [100, 36, 32, 28, null, null, null, null] },
    { c: 'Feb 2025', size: 24, m: [100, 38, 34, null, null, null, null, null] },
    { c: 'Mar 2025', size: 24, m: [100, 42, 38, null, null, null, null, null] },
  ],
  cohortCols: ['M0', 'M1', 'M3', 'M6', 'M12', 'M18', 'M24', 'M36'],

  // LTV evolution by cohort
  cohortLtv: [
    { c: 'Sep 2024', color: '#10B981', m: [180, 220, 280, 340, 420, 510, 620, null] },
    { c: 'Oct 2024', color: '#06B6D4', m: [200, 240, 310, 380, 460, 540, null, null] },
    { c: 'Nov 2024', color: '#6366F1', m: [220, 270, 330, 410, 490, null, null, null] },
    { c: 'Dec 2024', color: '#8B5CF6', m: [240, 290, 360, 430, null, null, null, null] },
    { c: 'Jan 2025', color: '#EC4899', m: [260, 320, 390, null, null, null, null, null] },
    { c: 'Feb 2025', color: '#F59E0B', m: [280, 340, null, null, null, null, null, null] },
  ],

  // ===== Section 4 — Acquisition & Retention =====
  acqFlow: [
    { m: 'May', n: 8,  r: 4 },  { m: 'Jun', n: 12, r: 6 },
    { m: 'Jul', n: 14, r: 8 },  { m: 'Aug', n: 10, r: 10 },
    { m: 'Sep', n: 8,  r: 12 }, { m: 'Oct', n: 6,  r: 14 },
    { m: 'Nov', n: 6,  r: 12 }, { m: 'Dec', n: 12, r: 16 },
    { m: 'Jan', n: 10, r: 14 }, { m: 'Feb', n: 12, r: 16 },
    { m: 'Mar', n: 14, r: 18 }, { m: 'Apr', n: 16, r: 22 },
  ],

  repeatTiming: [
    { lbl: '0–1 mo',    n: 8 },
    { lbl: '1–3 mo',    n: 18 },
    { lbl: '3–6 mo',    n: 28, peak: true },
    { lbl: '6–12 mo',   n: 14 },
    { lbl: '12–18 mo',  n: 8 },
    { lbl: '18+ mo',    n: 4 },
  ],

  acqSources: [
    { src: 'Direct',         n: 24, ltv: 720, repeat: 38, cross: 56, hot: false },
    { src: 'Word of mouth',  n: 12, ltv: 920, repeat: 58, cross: 64, hot: true },
    { src: 'Booking.com',    n: 22, ltv: 380, repeat: 18, cross: 42, hot: false },
    { src: 'Instagram',      n: 18, ltv: 540, repeat: 32, cross: 48, hot: false },
    { src: 'Google Ads',     n: 14, ltv: 410, repeat: 22, cross: 38, hot: false },
    { src: 'Expedia',        n:  8, ltv: 340, repeat: 14, cross: 32, hot: false },
    { src: 'Organic search', n: 16, ltv: 680, repeat: 42, cross: 58, hot: true },
  ],

  churn: [
    { n: 92, kind: 'all',     keyTok: 'churn1' },
    { n: 14, kind: 'highVal', keyTok: 'churn2' },
    { n:  8, kind: 'summer',  keyTok: 'churn3' },
  ],

  // ===== Section 5 — Top guests / At-risk =====
  topGuests: [
    { name: 'Иванов Александр',  first: 'Mar 2022', last: 'Apr 2026', visits: 7, spent: 4820, lastB: '#2 Forest Premium · 3 нт' },
    { name: 'Петрова Мария',      first: 'Jun 2022', last: 'Mar 2026', visits: 6, spent: 4120, lastB: '#1 Bay View · 2 нт' },
    { name: 'Smith Robert',       first: 'Aug 2022', last: 'Apr 2026', visits: 5, spent: 3840, lastB: '#3 Spa Suite · 4 нт' },
    { name: 'Козлов Дмитрий',     first: 'Sep 2022', last: 'Feb 2026', visits: 5, spent: 3520, lastB: '#2 Forest Premium · 2 нт' },
    { name: 'Wagner Anna',        first: 'Nov 2022', last: 'Mar 2026', visits: 5, spent: 3280, lastB: '#5 Lake House · 3 нт' },
    { name: 'Соколова Елена',     first: 'Dec 2022', last: 'Apr 2026', visits: 4, spent: 2940, lastB: '#1 Bay View · 2 нт' },
    { name: 'Müller Klaus',       first: 'Jan 2023', last: 'Jan 2026', visits: 4, spent: 2680, lastB: '#3 Spa Suite · 3 нт' },
    { name: 'Новикова Анна',      first: 'Feb 2023', last: 'Mar 2026', visits: 4, spent: 2480, lastB: '#2 Forest Premium · 2 нт' },
    { name: 'Brown Michael',      first: 'Apr 2023', last: 'Feb 2026', visits: 3, spent: 2240, lastB: '#5 Lake House · 4 нт' },
    { name: 'Лебедев Сергей',     first: 'May 2023', last: 'Mar 2026', visits: 3, spent: 2080, lastB: '#1 Bay View · 2 нт' },
    { name: 'Jansen Eva',         first: 'Jul 2023', last: 'Apr 2026', visits: 3, spent: 1920, lastB: '#3 Spa Suite · 2 нт' },
    { name: 'Морозова Ольга',     first: 'Aug 2023', last: 'Mar 2026', visits: 3, spent: 1840, lastB: '#2 Forest Premium · 2 нт' },
    { name: 'Garcia Carlos',      first: 'Oct 2023', last: 'Feb 2026', visits: 3, spent: 1720, lastB: '#5 Lake House · 3 нт' },
    { name: 'Волков Игорь',       first: 'Nov 2023', last: 'Apr 2026', visits: 2, spent: 1640, lastB: '#1 Bay View · 4 нт' },
    { name: 'Schmidt Hans',       first: 'Jan 2024', last: 'Mar 2026', visits: 2, spent: 1480, lastB: '#3 Spa Suite · 2 нт' },
  ],

  atRisk: [
    { name: 'Иванова Светлана',   spent: 3240, visits: 4, last: 'Sep 2024', months: 19, status: 'high-value' },
    { name: 'Berg Henrik',         spent: 2840, visits: 4, last: 'Nov 2024', months: 17, status: 'high-value' },
    { name: 'Петров Николай',      spent: 2480, visits: 3, last: 'Dec 2024', months: 16, status: 'high-value' },
    { name: 'Romano Giulia',       spent: 1920, visits: 3, last: 'Feb 2025', months: 14, status: 'high-value' },
    { name: 'Семёнова Ирина',      spent: 1680, visits: 3, last: 'Mar 2025', months: 13, status: 'high-value' },
    { name: 'Olsen Karl',          spent: 1480, visits: 2, last: 'Apr 2025', months: 12, status: 'mid-value' },
    { name: 'Захарова Юлия',       spent: 1240, visits: 2, last: 'May 2025', months: 11, status: 'mid-value' },
    { name: 'Hoffmann Dieter',     spent: 1080, visits: 2, last: 'Jun 2025', months: 10, status: 'mid-value' },
    { name: 'Кузнецов Андрей',     spent:  920, visits: 2, last: 'Jul 2025', months:  9, status: 'mid-value' },
    { name: 'Lopez Maria',         spent:  840, visits: 2, last: 'Aug 2025', months:  8, status: 'mid-value' },
    { name: 'Орлова Татьяна',      spent:  720, visits: 1, last: 'Sep 2025', months:  7, status: 'low-value' },
    { name: 'Klein Sabine',        spent:  640, visits: 1, last: 'Oct 2025', months:  6, status: 'low-value' },
  ],

  recentlyLost: [
    { name: 'Бабицкая Анна',  spent: 1240, last: 'Apr 2025', daysAgo: 24 },
    { name: 'Weber Stefan',    spent:  980, last: 'May 2025', daysAgo: 18 },
    { name: 'Григорьев Олег',  spent:  840, last: 'Mar 2025', daysAgo: 12 },
    { name: 'Lindberg Eva',    spent:  720, last: 'Apr 2025', daysAgo:  8 },
    { name: 'Тарасова Ольга',  spent:  640, last: 'May 2025', daysAgo:  4 },
  ],

  birthdays: [
    { name: 'Иванов Александр', kind: 'birthday',    date: '14 May', spent: 4820 },
    { name: 'Петрова Мария',     kind: 'anniversary', date: '18 May', spent: 4120 },
    { name: 'Smith Robert',      kind: 'birthday',    date: '21 May', spent: 3840 },
    { name: 'Wagner Anna',       kind: 'anniversary', date: '24 May', spent: 3280 },
    { name: 'Müller Klaus',      kind: 'birthday',    date: '28 May', spent: 2680 },
    { name: 'Соколова Елена',    kind: 'anniversary', date:  '2 Jun', spent: 2940 },
  ],

  // ===== Section 6 — Demographics =====
  geo: [
    { country: 'Латвия',     pct: 48, n: 182, regions: ['Рига 38%', 'Юрмала 12%', 'Лиепая 8%'] },
    { country: 'Эстония',    pct: 18, n: 68 },
    { country: 'Литва',      pct: 12, n: 46 },
    { country: 'Германия',   pct:  8, n: 30 },
    { country: 'Россия',     pct:  6, n: 22 },
    { country: 'Финляндия',  pct:  4, n: 14 },
    { country: 'Other',      pct:  4, n: 18 },
  ],

  composition: [
    { id: 'couples',  pct: 62, n: 88, color: '#6366F1' },
    { id: 'families', pct: 18, n: 26, color: '#10B981' },
    { id: 'friends',  pct:  9, n: 12, color: '#F59E0B' },
    { id: 'solo',     pct:  7, n: 10, color: '#EC4899' },
    { id: 'business', pct:  4, n:  6, color: '#06B6D4' },
  ],
  compositionLabels: {
    ru: { couples: 'Пары', families: 'Семьи', friends: 'Друзья', solo: 'Соло', business: 'Деловые' },
    en: { couples: 'Couples', families: 'Families', friends: 'Friends', solo: 'Solo', business: 'Business' },
  },

  patterns: [
    { k: 'leadTime', v: '14 дней (med)' },
    { k: 'channel',  v: 'Direct 54%' },
    { k: 'alos',     v: '1.8 нт' },
    { k: 'spa',      v: '64% stays' },
    { k: 'fnb',      v: '42% stays' },
    { k: 'bv',       v: '€280' },
    { k: 'bvCross',  v: '€395' },
  ],

  compare: [
    { metric: 'Avg booking value',     n: 280, r: 420, isPct: false, isMoney: true },
    { metric: 'ALOS, нт',              n: 1.6, r: 2.4, isPct: false },
    { metric: 'Cross-sell SPA',        n: 48,  r: 78,  isPct: true },
    { metric: 'Lead time, дней',       n: 12,  r: 28,  isPct: false, invert: true },
    { metric: 'Cancellation rate',     n: 18,  r: 6,   isPct: true,  invert: true },
    { metric: 'Review score',          n: 8.6, r: 9.4, isPct: false },
  ],
};

window.CUST = CUST;
window.CUST_T = CUST_T;
window.SEG_COLORS = SEG_COLORS;
window.AVATAR_COLORS = AVATAR_COLORS;
window.avatarColor = avatarColor;
window.initialsOf = initialsOf;
window.maskName = maskName;
