// widgets.jsx — Friends SUP electronic gift certificates wizard
// Step 1 (route) -> Step 2 (recipient) -> Step 3 (payment)
// All three steps share the same 1240×880 chrome.

const RES = window.__resources;

const ROUTES = [
  {
    id: 'universal',
    name: 'Универсальный',
    subtitle: 'Любой маршрут на выбор',
    photo: RES.certArt,
    price: 4000,
    featured: true
  },
  {
    id: 'city',
    name: 'Городской',
    subtitle: 'Исторический центр · Новая Голландия · Каменный остров · Закат на Лахте',
    photo: RES.certArt,
    price: 3000
  },
  {
    id: 'city_light',
    name: 'Городской LIGHT',
    subtitle: 'Каменный остров · Закат на Лахте',
    photo: RES.certArt,
    price: 2500
  },
  {
    id: 'country',
    name: 'Загородный',
    subtitle: 'Каньон · Вуокса',
    photo: RES.certArt,
    price: 4000
  }
];

const fmtRub = (n) => {
  if (n == null || isNaN(n)) return '0 ₽';
  return new Intl.NumberFormat('ru-RU').format(Math.round(n)) + ' ₽';
};

function FriendsLogo({ size = 22 }) {
  return (
    <a
      href="https://friends-sup.ru/"
      target="_blank"
      rel="noopener noreferrer"
      style={{ display: 'inline-block', lineHeight: 0 }}
      title="friends-sup.ru">
      <img
        src={RES.logoOrange}
        alt="Friends SUP"
        style={{
          height: size * 1.1,
          width: 'auto',
          display: 'block',
          objectFit: 'contain'
        }} />
    </a>);
}

function HeroCard({ amount, accent = '#0B5D3B', routeName, routeSubtitle, persons = 1, photo = RES.supPhoto }) {
  return (
    <div style={{
      position: 'relative', width: '100%', aspectRatio: '4 / 5',
      borderRadius: 18, background: '#FAF7EF',
      boxShadow: '0 30px 60px -20px rgba(10,46,31,.28), 0 4px 12px rgba(0,0,0,.06)',
      overflow: 'hidden', color: '#0A2E1F', fontFamily: 'inherit',
      display: 'flex', flexDirection: 'column'
    }}>
      <div style={{ position: 'absolute', inset: 0, boxShadow: 'inset 0 0 0 1px rgba(10,46,31,.06)', borderRadius: 18, pointerEvents: 'none', zIndex: 3 }} />

      <div style={{ height: '66.666%', position: 'relative', overflow: 'hidden', background: '#1d2a26' }}>
        <img src={photo} alt="" style={{
          position: 'absolute', inset: 0, width: '100%', height: '100%',
          objectFit: 'cover', objectPosition: 'center 65%', display: 'block'
        }} />
        <div style={{
          position: 'absolute', inset: 0,
          background: "linear-gradient(rgba(0, 0, 0, 0) 60%, rgba(0, 0, 0, 0.2) 100%) center bottom / cover"
        }} />
      </div>

      <div style={{
        height: '33.333%', padding: '12px 18px 12px',
        display: 'flex', flexDirection: 'column', justifyContent: 'space-between',
        background: '#fff'
      }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 14, flex: 1, minHeight: 0 }}>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{
              fontSize: 'clamp(11px, 1.7cqw, 14px)', color: '#5a6660',
              letterSpacing: '.18em', textTransform: 'uppercase', fontWeight: 500,
              marginBottom: 6
            }}>Подарочный сертификат</div>
            <div style={{
              fontFamily: '"RF Dewi Extended", "NT Somic", sans-serif',
              fontSize: 'clamp(24px, 4.4cqw, 36px)', fontWeight: 800,
              letterSpacing: '-.01em', lineHeight: 1.05, color: '#0A2E1F',
              whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis'
            }}>{routeName || 'Универсальный'}</div>
            {routeSubtitle && (
              <div style={{
                fontSize: 'clamp(13px, 1.85cqw, 16px)', color: '#5a6660',
                lineHeight: 1.4, marginTop: 6, display: '-webkit-box',
                WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden'
              }}>{routeSubtitle}</div>
            )}
            <div style={{
              fontSize: 'clamp(13px, 1.85cqw, 16px)', color: '#0A2E1F',
              fontWeight: 600, marginTop: 8, fontVariantNumeric: 'tabular-nums'
            }}>{
              persons === 1 ? 'На одного человека'
              : persons < 5 ? 'На ' + persons + ' человека'
              : 'На ' + persons + ' человек'
            }</div>
          </div>

          <a href="#" onClick={(e) => e.preventDefault()} style={{
            width: 'clamp(78px, 13cqw, 108px)', aspectRatio: '1 / 1',
            background: '#fff', borderRadius: 8, padding: 4,
            flexShrink: 0, alignSelf: 'center', transform: 'translateY(-4px)',
            display: 'block', boxShadow: '0 0 0 1px rgba(10,46,31,.08)'
          }} title="taplink">
            <img src={RES.qr} alt="QR" style={{ width: '100%', height: '100%', display: 'block' }} />
          </a>
        </div>

        <div style={{
          display: 'flex', alignItems: 'center', justifyContent: 'space-between',
          fontSize: 'clamp(12px, 1.65cqw, 14px)', color: '#5a6660',
          paddingTop: 10, borderTop: '1px solid rgba(10,46,31,.08)', gap: 10
        }}>
          <div>
            <span style={{ opacity: .55, textTransform: 'uppercase', letterSpacing: '.14em', fontSize: 'clamp(11px, 1.45cqw, 12px)' }}>№ </span>
            <span style={{ fontVariantNumeric: 'tabular-nums', color: '#0A2E1F', fontWeight: 500 }}>FS · 2026 · XXXXXX</span>
          </div>
          <div>
            <span style={{ opacity: .55, textTransform: 'uppercase', letterSpacing: '.14em', fontSize: 'clamp(11px, 1.45cqw, 12px)' }}>Действует до </span>
            <span style={{ color: '#0A2E1F', fontVariantNumeric: 'tabular-nums', fontWeight: 500 }}>01.09.2026</span>
          </div>
        </div>
      </div>
    </div>);
}

function CardSwitcher({ amountKey, children }) {
  const [key, setKey] = React.useState(amountKey);
  const [opacity, setOpacity] = React.useState(1);
  React.useEffect(() => {
    if (amountKey === key) return;
    setOpacity(0);
    const t = setTimeout(() => { setKey(amountKey); setOpacity(1); }, 130);
    return () => clearTimeout(t);
  }, [amountKey, key]);
  return <div style={{ opacity, transition: 'opacity .15s' }}>{children}</div>;
}

// ─── Shared chrome ─────────────────────────────────────────────────
function StepShell({ stepNumber, children }) {
  return (
    <div style={{
      position: 'relative', width: '100%', height: '100%',
      background: '#fff', borderRadius: 18, overflow: 'hidden',
      boxShadow: '0 1px 0 rgba(0,0,0,.04), 0 30px 80px -30px rgba(0,0,0,.18)',
      fontFamily: '"NT Somic", system-ui, -apple-system, sans-serif',
      color: '#0A2E1F', display: 'flex', flexDirection: 'column'
    }}>
      <header style={{
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        padding: '22px 32px', borderBottom: '1px solid rgba(10,46,31,.06)',
        flexShrink: 0
      }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 18 }}>
          <FriendsLogo size={22} />
          <div style={{ width: 1, height: 22, background: 'rgba(10,46,31,.12)' }} />
          <span style={{ fontSize: 14, color: '#3a4a44' }}>Электронные подарочные сертификаты</span>
        </div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 12, fontSize: 12, color: '#5a6660' }}>
          {[
            { n: stepNumber > 1 ? '✓' : '1', label: 'Сертификат', done: stepNumber > 1, active: stepNumber === 1 },
            { n: stepNumber > 2 ? '✓' : '2', label: 'Получатель', done: stepNumber > 2, active: stepNumber === 2 },
            { n: '3', label: 'Оплата', done: false, active: stepNumber === 3 }
          ].map((s, i) => (
            <React.Fragment key={i}>
              {i > 0 && <span style={{ width: 14, height: 1, background: 'rgba(10,46,31,.16)' }} />}
              <span style={{
                display: 'flex', alignItems: 'center', gap: 6,
                color: s.active ? '#0A2E1F' : '#5a6660',
                fontWeight: s.active ? 500 : 400,
                opacity: !s.active && !s.done ? .5 : 1
              }}>
                <span style={{
                  width: 18, height: 18, borderRadius: '50%',
                  background: s.active ? '#DD3310' : s.done ? '#0A2E1F' : 'rgba(10,46,31,.12)',
                  color: s.active || s.done ? '#fff' : '#0A2E1F',
                  display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
                  fontSize: 10, fontWeight: 600
                }}>{s.n}</span>
                {s.label}
              </span>
            </React.Fragment>
          ))}
        </div>
      </header>

      <div style={{ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column' }}>
        {children}
      </div>

      <footer style={{
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        padding: '14px 32px', borderTop: '1px solid rgba(10,46,31,.06)', flexShrink: 0
      }}>
        <a href="https://friends-sup.ru/" target="_blank" rel="noopener noreferrer" style={{
          fontSize: 13, color: '#5a6660', letterSpacing: '.04em', textDecoration: 'none'
        }}>friends-sup.ru</a>
        <a href="https://t.me/friendssuphelp" target="_blank" rel="noopener noreferrer" style={{
          display: 'flex', alignItems: 'center', gap: 10, textDecoration: 'none'
        }}>
          <div style={{
            width: 30, height: 30, borderRadius: '50%',
            background: '#0A2E1F', display: 'flex', alignItems: 'center', justifyContent: 'center',
            color: '#fff', fontSize: 14
          }}>?</div>
          <span style={{ fontSize: 12, fontWeight: 600, letterSpacing: '.16em', color: '#0A2E1F' }}>ПОДДЕРЖКА</span>
        </a>
        <a href="#" onClick={(e) => e.preventDefault()} style={{ fontSize: 13, color: '#5a6660', textDecoration: 'none' }}>Правила</a>
      </footer>
    </div>);
}

function TwoCol({ left, right }) {
  return (
    <div style={{
      flex: 1, display: 'grid', gridTemplateColumns: '0.85fr 1fr',
      gap: 0, background: '#F4F1EA', minHeight: 0
    }}>
      <div style={{
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        padding: '20px 14px 20px 28px'
      }}>
        <div style={{ width: '100%', maxWidth: 440, containerType: 'inline-size' }}>
          {left}
        </div>
      </div>
      <div style={{
        padding: '24px 28px 22px 16px',
        display: 'flex', flexDirection: 'column', minHeight: 0
      }}>
        {right}
      </div>
    </div>);
}

// ─── STEP 1: Route + persons ─────────────────────────────────────────
function Step1Widget({
  initialRouteId = null,
  initialPersons = 1,
  accent = '#5c615f',
  onContinue
}) {
  const [routeId, setRouteId] = React.useState(initialRouteId);
  const [persons, setPersons] = React.useState(initialPersons);

  const selectedRoute = routeId ? ROUTES.find((r) => r.id === routeId) : null;
  const previewRoute = selectedRoute || ROUTES[0];   // карточка-визуал даже до выбора
  const total = selectedRoute ? selectedRoute.price * persons : 0;
  const canContinue = !!selectedRoute;

  return (
    <StepShell stepNumber={1}>
      <TwoCol
        left={
          <CardSwitcher amountKey={`step1-${routeId || 'none'}-${persons}`}>
            <HeroCard amount={previewRoute.price} accent={accent}
              routeName={selectedRoute ? previewRoute.name : 'Выберите маршрут'}
              routeSubtitle={selectedRoute ? previewRoute.subtitle : ''}
              persons={persons} photo={previewRoute.photo} />
          </CardSwitcher>
        }
        right={
          <>
            <div style={{
              display: 'flex', alignItems: 'center', gap: 10,
              fontSize: 11, letterSpacing: '.18em', textTransform: 'uppercase',
              color: '#5a6660', fontWeight: 500, marginBottom: 14
            }}>
              <span style={{ width: 22, height: 22, borderRadius: '50%', background: '#0A2E1F', color: '#fff', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontSize: 11, fontWeight: 600 }}>1</span>
              Шаг 1 из 3 · Маршрут
            </div>

            <div style={{
              fontFamily: '"RF Dewi Extended", "NT Somic", sans-serif', fontSize: 24, fontWeight: 500,
              letterSpacing: '-.01em', color: '#0A2E1F', marginBottom: 4
            }}>Какой сертификат дарите?</div>
            <div style={{ fontSize: 13, color: '#5a6660', marginBottom: 16 }}>
              Выберите тип сертификата — друг сам решит, на какой маршрут пойти
            </div>

            <div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginBottom: 26 }}>
              {ROUTES.map((r) => {
                const active = routeId === r.id;
                return (
                  <button key={r.id} onClick={() => setRouteId(r.id)} style={{
                    appearance: 'none', border: 'none', cursor: 'pointer',
                    background: active ? '#DD3310' : '#fff',
                    color: active ? '#fff' : '#0A2E1F',
                    borderRadius: 12, padding: '14px 16px',
                    display: 'flex', alignItems: 'center', justifyContent: 'space-between',
                    gap: 12, textAlign: 'left', fontFamily: 'inherit',
                    boxShadow: active ? '0 8px 22px -10px rgba(221,51,16,.55)' : '0 1px 0 rgba(0,0,0,.04)',
                    transition: 'all .15s'
                  }}>
                    <div style={{ minWidth: 0, flex: 1 }}>
                      <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4 }}>
                        <span style={{ fontSize: 15, fontWeight: 500 }}>{r.name}</span>
                        {r.featured && (
                          <span style={{
                            fontSize: 9, fontWeight: 600, letterSpacing: '.14em', textTransform: 'uppercase',
                            padding: '2px 6px', borderRadius: 4,
                            background: active ? '#E9F7B0' : 'rgba(10,46,31,.08)', color: '#0A2E1F'
                          }}>хит</span>
                        )}
                      </div>
                      <div style={{
                        fontSize: 12, lineHeight: 1.4,
                        color: active ? 'rgba(255,255,255,.72)' : '#5a6660'
                      }}>{r.subtitle}</div>
                    </div>
                    <div style={{
                      fontFamily: '"RF Dewi Extended", "NT Somic", sans-serif',
                      fontSize: 16, fontWeight: 600, fontVariantNumeric: 'tabular-nums',
                      flexShrink: 0, textAlign: 'right'
                    }}>
                      <div>{fmtRub(r.price)}</div>
                    </div>
                  </button>);
              })}
            </div>

            <div style={{
              display: 'flex', alignItems: 'center', gap: 10,
              fontSize: 11, letterSpacing: '.18em', textTransform: 'uppercase',
              color: '#5a6660', fontWeight: 500, marginBottom: 12
            }}>На сколько человек</div>
            <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginBottom: 18 }}>
              {[1, 2, 3, 4, 5, 6].map((n) => {
                const active = persons === n;
                return (
                  <button key={n} onClick={() => setPersons(n)} style={{
                    appearance: 'none', border: 'none', cursor: 'pointer',
                    minWidth: 56, padding: '10px 14px',
                    background: active ? '#DD3310' : 'rgba(10,46,31,.06)',
                    color: active ? '#fff' : '#0A2E1F',
                    borderRadius: 999, fontSize: 13, fontWeight: 500,
                    fontFamily: 'inherit', transition: 'all .15s'
                  }}>
                    {n + ' ' + (n === 1 ? 'человек' : n < 5 ? 'человека' : 'человек')}
                  </button>);
              })}
            </div>

            <div style={{ flex: 1, minHeight: 16 }} />

            <div style={{
              background: '#fff', borderRadius: 14, padding: '14px 18px',
              display: 'flex', alignItems: 'center', justifyContent: 'space-between',
              gap: 16, marginBottom: 14, boxShadow: '0 1px 0 rgba(0,0,0,.04)'
            }}>
              <div>
                <div style={{ fontSize: 11, color: '#5a6660', textTransform: 'uppercase', letterSpacing: '.14em' }}>К оплате</div>
                <div style={{ fontSize: 12, color: '#5a6660', marginTop: 4, fontVariantNumeric: 'tabular-nums' }}>
                  {selectedRoute
                    ? `${fmtRub(selectedRoute.price)} × ${persons} ${persons === 1 ? 'человек' : persons < 5 ? 'человека' : 'человек'}`
                    : 'Выберите маршрут, чтобы увидеть сумму'}
                </div>
              </div>
              <div style={{
                fontFamily: '"RF Dewi Extended", "NT Somic", sans-serif', fontSize: 26, fontWeight: 500,
                letterSpacing: '-.01em', color: '#0A2E1F', fontVariantNumeric: 'tabular-nums'
              }}>{fmtRub(total)}</div>
            </div>

            <button
              onClick={() => canContinue && onContinue && onContinue({ routeId, persons })}
              disabled={!canContinue}
              style={{
                width: '100%', padding: '18px 24px',
                background: '#0A2E1F', border: 'none', borderRadius: 14,
                cursor: canContinue ? 'pointer' : 'not-allowed', color: '#fff',
                opacity: canContinue ? 1 : .5,
                fontSize: 16, fontWeight: 500,
                display: 'flex', alignItems: 'center', justifyContent: 'space-between',
                boxShadow: '0 14px 30px -10px rgba(10,46,31,.5)',
                fontFamily: 'inherit', transition: 'transform .12s'
              }}
              onMouseDown={(e) => canContinue && (e.currentTarget.style.transform = 'scale(.99)')}
              onMouseUp={(e) => e.currentTarget.style.transform = 'scale(1)'}
              onMouseLeave={(e) => e.currentTarget.style.transform = 'scale(1)'}>
              <span>Оформить</span>
              <span style={{
                width: 30, height: 30, borderRadius: '50%',
                background: '#DD3310', color: '#fff',
                display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
                fontSize: 15, fontWeight: 500
              }}>→</span>
            </button>
          </>
        } />
    </StepShell>);
}

// ─── STEP 2: Recipient ─────────────────────────────────────────────
function Step2RecipientWidget({
  routeId = 'universal',
  persons = 1,
  initialRecipient = 'friend',
  accent = '#5c615f',
  initialData = {},
  onBack,
  onContinue
}) {
  const [recipient, setRecipient] = React.useState(initialData.recipient || initialRecipient);
  const [name, setName] = React.useState(initialData.name || '');
  const [phone, setPhone] = React.useState(initialData.phone || '');
  const [email, setEmail] = React.useState(initialData.email || '');
  const [wish, setWish] = React.useState(initialData.wish || '');
  const [whenMode, setWhenMode] = React.useState(initialData.whenMode || 'now');
  const [date, setDate] = React.useState(initialData.date || '2026-05-08');
  const [time, setTime] = React.useState(initialData.time || '11:00');

  const route = ROUTES.find((r) => r.id === routeId) || ROUTES[0];
  const total = route.price * persons;

  const canContinue =
    name.trim().length > 0 &&
    email.trim().length > 0;

  const handleContinue = () => {
    if (!canContinue) return;
    const deliveryMethod = `Email · ${email}`;
    const whenLabel = recipient === 'self'
      ? 'Сейчас'
      : whenMode === 'now' ? 'Сейчас' : `${formatDate(date)}, ${time}`;
    onContinue && onContinue({
      recipient, name, phone, email, wish,
      whenMode, date, time,
      deliveryMethod, whenLabel
    });
  };

  return (
    <StepShell stepNumber={2}>
      <TwoCol
        left={
          <CardSwitcher amountKey={`step2-${routeId}-${persons}`}>
            <HeroCard amount={route.price} accent={accent} routeName={route.name}
              routeSubtitle={route.subtitle} persons={persons} photo={route.photo} />
          </CardSwitcher>
        }
        right={
          <>
            <div style={{
              display: 'flex', alignItems: 'center', gap: 10,
              fontSize: 11, letterSpacing: '.18em', textTransform: 'uppercase',
              color: '#5a6660', fontWeight: 500, marginBottom: 12
            }}>
              <button onClick={() => onBack && onBack()} style={{
                appearance: 'none', border: 'none', background: 'transparent',
                cursor: 'pointer', padding: 0,
                fontSize: 11, color: '#5a6660', letterSpacing: '.18em',
                textTransform: 'uppercase', fontFamily: 'inherit',
                display: 'inline-flex', alignItems: 'center', gap: 6
              }}>← Назад</button>
              <span style={{ opacity: .4 }}>·</span>
              <span style={{ display: 'inline-flex', alignItems: 'center', gap: 8 }}>
                <span style={{ width: 22, height: 22, borderRadius: '50%', background: '#DD3310', color: '#fff', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontSize: 11, fontWeight: 600 }}>2</span>
                Шаг 2 из 3 · Получатель
              </span>
            </div>

            <div style={{
              fontFamily: '"RF Dewi Extended", "NT Somic", sans-serif', fontSize: 22, fontWeight: 500,
              letterSpacing: '-.01em', color: '#0A2E1F', marginBottom: 14
            }}>Кому подарок?</div>

            <div style={{
              display: 'inline-flex', padding: 4, gap: 4,
              background: 'rgba(10,46,31,.06)', borderRadius: 999,
              marginBottom: 16, alignSelf: 'flex-start'
            }}>
              <ToggleBtn active={recipient === 'self'} onClick={() => setRecipient('self')}>Себе</ToggleBtn>
              <ToggleBtn active={recipient === 'friend'} onClick={() => setRecipient('friend')}>Другу</ToggleBtn>
            </div>

            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 12, marginBottom: recipient === 'friend' ? 10 : 14 }}>
              <Field
                label={recipient === 'self' ? 'Имя*' : 'Имя получателя*'}
                value={name} onChange={setName}
                placeholder={recipient === 'self' ? 'Ваше имя' : 'Например, Аня'} />
              <Field label="Телефон" value={phone}
                onChange={(v) => setPhone(formatRussianPhone(v))}
                placeholder="+7 (___) ___-__-__" prefix="🇷🇺" type="tel" />
              <Field
                label={recipient === 'self' ? 'Email*' : 'Email получателя*'}
                value={email} onChange={setEmail}
                placeholder="name@mail.ru" />
            </div>


            {recipient === 'friend' ? (
              <>
                <div style={{ marginBottom: 14 }}>
                  <div style={{ fontSize: 12, color: '#5a6660', marginBottom: 6 }}>
                    Пожелание <span style={{ color: '#9aa39e' }}>· напечатаем на открытке</span>
                  </div>
                  <textarea
                    value={wish}
                    onChange={(e) => setWish(e.target.value)}
                    rows={2} maxLength={200}
                    placeholder="Например: «С днём рождения! Поплывём на закат вместе.»"
                    style={{
                      width: '100%', boxSizing: 'border-box',
                      background: '#fff', border: 'none', borderRadius: 12,
                      padding: '12px 14px',
                      fontSize: 13, lineHeight: 1.5,
                      color: '#0A2E1F', fontFamily: 'inherit',
                      resize: 'none', outline: 'none',
                      boxShadow: '0 1px 0 rgba(0,0,0,.04)'
                    }} />
                  <div style={{
                    marginTop: 4, fontSize: 11, color: '#9aa39e',
                    display: 'flex', justifyContent: 'flex-end',
                    fontVariantNumeric: 'tabular-nums'
                  }}>{wish.length} / 200</div>
                </div>

                <div style={{ fontSize: 12, color: '#5a6660', marginBottom: 8 }}>Когда отправить</div>
                <div style={{ display: 'flex', gap: 8, marginBottom: whenMode === 'pick' ? 8 : 0 }}>
                  <PillBtn active={whenMode === 'now'} onClick={() => setWhenMode('now')}>
                    <span style={{ fontSize: 13, fontWeight: 500 }}>Сейчас</span>
                    <span style={{ fontSize: 11, opacity: .7, marginTop: 1 }}>сразу после оплаты</span>
                  </PillBtn>
                  <PillBtn active={whenMode === 'pick'} onClick={() => setWhenMode('pick')}>
                    <span style={{ fontSize: 13, fontWeight: 500 }}>К дате</span>
                    <span style={{ fontSize: 11, opacity: .7, marginTop: 1 }}>{whenMode === 'pick' ? `${formatDate(date)}, ${time}` : 'выбрать день и время'}</span>
                  </PillBtn>
                </div>
                {whenMode === 'pick' && (
                  <>
                    <div style={{
                      background: '#fff', borderRadius: 12, padding: '10px 14px',
                      boxShadow: '0 1px 0 rgba(0,0,0,.04)',
                      display: 'flex', alignItems: 'center', gap: 14
                    }}>
                      <DateTimeRow icon="📅" label="Дата">
                        <input type="date" value={date} min={new Date().toISOString().slice(0, 10)}
                          onChange={(e) => setDate(e.target.value)} style={dtInputStyle} />
                      </DateTimeRow>
                      <div style={{ width: 1, alignSelf: 'stretch', background: 'rgba(10,46,31,.08)' }} />
                      <DateTimeRow icon="⏱" label="Время">
                        <input type="time" value={time}
                          onChange={(e) => setTime(e.target.value)}
                          style={{ ...dtInputStyle, fontVariantNumeric: 'tabular-nums' }} />
                      </DateTimeRow>
                    </div>
                    <div style={{ fontSize: 11, color: '#9aa39e', marginTop: 6, paddingLeft: 4 }}>
                      Время московское (UTC+3)
                    </div>
                  </>
                )}
              </>
            ) : (
              <>
                <div style={{ fontSize: 12, color: '#5a6660', marginBottom: 8 }}>Куда отправить сертификат</div>
                <div style={{ display: 'flex', marginBottom: 14 }}>
                  <ChannelBtn active icon="✉" label="Email" sub={email || 'на указанный email'} />
                </div>
                <div style={{
                  fontSize: 11, color: '#5a6660', lineHeight: 1.5,
                  padding: '10px 14px', background: 'rgba(10,46,31,.04)', borderRadius: 10
                }}>
                  Отправим сертификат сразу после оплаты — обычно занимает меньше минуты.
                </div>
              </>
            )}

            <div style={{ flex: 1, minHeight: 8 }} />

            <TotalBar route={route} persons={persons} total={total} />

            <button
              onClick={handleContinue}
              disabled={!canContinue}
              style={{
                width: '100%', appearance: 'none', border: 'none',
                cursor: canContinue ? 'pointer' : 'not-allowed',
                background: '#0A2E1F', color: '#fff',
                opacity: canContinue ? 1 : .5,
                borderRadius: 14, padding: '16px 22px',
                display: 'flex', alignItems: 'center', justifyContent: 'space-between',
                gap: 12, fontFamily: 'inherit',
                boxShadow: '0 14px 28px -10px rgba(10,46,31,.5)'
              }}>
              <span style={{ fontSize: 15, fontWeight: 500 }}>Далее · оплата</span>
              <span style={{
                width: 30, height: 30, borderRadius: '50%',
                background: '#DD3310', display: 'inline-flex',
                alignItems: 'center', justifyContent: 'center', fontSize: 15, color: '#fff'
              }}>→</span>
            </button>
          </>
        } />
    </StepShell>);
}

// ─── STEP 3: Payment ───────────────────────────────────────────────
function Step3PaymentWidget({
  routeId = 'universal',
  persons = 1,
  recipientName = '',
  deliveryMethod = '',
  whenLabel = 'Сейчас',
  initialPromo = '',
  initialPromoApplied = false,
  initialPayerName = '',
  initialPayerEmail = '',
  accent = '#5c615f',
  onBack,
  onPay
}) {
  const [payerName, setPayerName] = React.useState(initialPayerName);
  const [payerEmail, setPayerEmail] = React.useState(initialPayerEmail);
  const [promo, setPromo] = React.useState(initialPromo);
  const [promoApplied, setPromoApplied] = React.useState(initialPromoApplied);
  const [promoPercent, setPromoPercent] = React.useState(initialPromoApplied ? 10 : 0);
  const [promoError, setPromoError] = React.useState('');
  const [promoChecking, setPromoChecking] = React.useState(false);
  const [submitting, setSubmitting] = React.useState(false);

  const route = ROUTES.find((r) => r.id === routeId) || ROUTES[0];
  const subtotal = route.price * persons;
  const discount = promoApplied ? Math.round(subtotal * promoPercent / 100) : 0;
  const total = subtotal - discount;

  const canPay = payerName.trim().length > 0 && payerEmail.trim().length > 0 && !submitting;

  const checkPromo = async () => {
    if (!promo.trim()) return;
    setPromoChecking(true);
    setPromoError('');
    try {
      const res = await fetch('/api/promo/check', {
        method: 'POST', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ code: promo })
      });
      const data = await res.json();
      if (data.valid) {
        setPromoApplied(true);
        setPromoPercent(data.discountPercent);
        setPromoError('');
      } else {
        setPromoApplied(false);
        setPromoPercent(0);
        setPromoError(data.error || 'Промокод не найден');
      }
    } catch (err) {
      setPromoApplied(false);
      setPromoError('Не удалось проверить промокод');
    } finally {
      setPromoChecking(false);
    }
  };

  const handlePay = async () => {
    if (!canPay) return;
    setSubmitting(true);
    try {
      await (onPay && onPay({ payerName, payerEmail, promo, promoApplied, total, discount }));
    } finally {
      setSubmitting(false);
    }
  };

  return (
    <StepShell stepNumber={3}>
      <TwoCol
        left={
          <CardSwitcher amountKey={`step3-${routeId}-${persons}`}>
            <HeroCard amount={route.price} accent={accent} routeName={route.name}
              routeSubtitle={route.subtitle} persons={persons} photo={route.photo} />
          </CardSwitcher>
        }
        right={
          <>
            <div style={{
              display: 'flex', alignItems: 'center', gap: 10,
              fontSize: 11, letterSpacing: '.18em', textTransform: 'uppercase',
              color: '#5a6660', fontWeight: 500, marginBottom: 12
            }}>
              <button onClick={() => onBack && onBack()} style={{
                appearance: 'none', border: 'none', background: 'transparent',
                cursor: 'pointer', padding: 0,
                fontSize: 11, color: '#5a6660', letterSpacing: '.18em',
                textTransform: 'uppercase', fontFamily: 'inherit',
                display: 'inline-flex', alignItems: 'center', gap: 6
              }}>← Назад</button>
              <span style={{ opacity: .4 }}>·</span>
              <span style={{ display: 'inline-flex', alignItems: 'center', gap: 8 }}>
                <span style={{ width: 22, height: 22, borderRadius: '50%', background: '#DD3310', color: '#fff', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontSize: 11, fontWeight: 600 }}>3</span>
                Шаг 3 из 3 · Оплата
              </span>
            </div>

            <div style={{
              fontFamily: '"RF Dewi Extended", "NT Somic", sans-serif', fontSize: 22, fontWeight: 500,
              letterSpacing: '-.01em', color: '#0A2E1F', marginBottom: 4
            }}>Ваши контактные данные</div>
            <div style={{ fontSize: 12, color: '#5a6660', marginBottom: 14 }}>
              Пришлём электронный чек на этот email
            </div>

            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12, marginBottom: 14 }}>
              <Field label="Имя*" value={payerName} onChange={setPayerName} placeholder="Ваше имя" />
              <Field label="Email*" value={payerEmail} onChange={setPayerEmail} placeholder="name@mail.ru" />
            </div>

            <div style={{ fontSize: 12, color: '#5a6660', marginBottom: 6 }}>Промокод</div>
            {promoApplied ? (
              <div style={{
                background: 'linear-gradient(90deg, #0B5D3B 0%, #0A2E1F 100%)', color: '#fff',
                borderRadius: 12, padding: '12px 14px',
                display: 'flex', alignItems: 'center', gap: 12, marginBottom: 14,
                boxShadow: '0 8px 22px -10px rgba(11,93,59,.5)'
              }}>
                <span style={{
                  width: 26, height: 26, borderRadius: '50%',
                  background: 'rgba(255,255,255,.18)', display: 'inline-flex',
                  alignItems: 'center', justifyContent: 'center', fontSize: 13
                }}>✓</span>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontSize: 13, fontWeight: 500, letterSpacing: '.04em' }}>
                    {promo.toUpperCase()} · −{promoPercent}%
                  </div>
                  <div style={{ fontSize: 11, opacity: .8, marginTop: 1 }}>
                    Скидка применена · −{fmtRub(discount)}
                  </div>
                </div>
                <button onClick={() => { setPromoApplied(false); setPromoPercent(0); setPromo(''); setPromoError(''); }} style={{
                  appearance: 'none', border: 'none', cursor: 'pointer',
                  background: 'transparent', color: 'rgba(255,255,255,.7)',
                  fontSize: 11, fontFamily: 'inherit',
                  textDecoration: 'underline', textUnderlineOffset: 2
                }}>Убрать</button>
              </div>
            ) : (
              <div style={{ marginBottom: 14 }}>
                <div style={{
                  background: '#fff', borderRadius: 12, padding: '10px 12px 10px 14px',
                  boxShadow: promoError ? '0 0 0 1.5px #DD3310' : '0 1px 0 rgba(0,0,0,.04)',
                  display: 'flex', alignItems: 'center', gap: 10
                }}>
                  <span style={{ fontSize: 14 }}>🏷</span>
                  <input
                    value={promo}
                    onChange={(e) => { setPromo(e.target.value); setPromoApplied(false); setPromoError(''); }}
                    onKeyDown={(e) => { if (e.key === 'Enter') checkPromo(); }}
                    placeholder="Введите промокод"
                    style={{
                      flex: 1, border: 'none', outline: 'none', background: 'transparent',
                      fontFamily: 'inherit', fontSize: 13, color: '#0A2E1F',
                      letterSpacing: '.04em', textTransform: 'uppercase'
                    }} />
                  <button
                    onClick={checkPromo}
                    disabled={!promo || promoChecking}
                    style={{
                      appearance: 'none', border: 'none', cursor: (promo && !promoChecking) ? 'pointer' : 'default',
                      background: 'rgba(10,46,31,.06)', color: '#0A2E1F',
                      padding: '7px 12px', borderRadius: 999,
                      fontSize: 11, fontWeight: 500,
                      fontFamily: 'inherit', opacity: (promo && !promoChecking) ? 1 : .5
                    }}>{promoChecking ? '…' : 'Применить'}</button>
                </div>
                {promoError && (
                  <div style={{ fontSize: 11, color: '#DD3310', marginTop: 6, paddingLeft: 4 }}>
                    {promoError}
                  </div>
                )}
              </div>
            )}

            <div style={{ flex: 1, minHeight: 8 }} />

            <div style={{
              background: '#fff', borderRadius: 12, padding: '10px 16px',
              boxShadow: '0 1px 0 rgba(0,0,0,.04)', marginBottom: 8
            }}>
              <RecapRow label="Имя" value={recipientName || '—'} />
              <RecapRow label="Способ отправки" value={deliveryMethod || '—'} />
              <RecapRow label="Время отправки" value={whenLabel} />
            </div>

            <TotalBar route={route} persons={persons} total={total} discount={discount} />

            <button
              onClick={handlePay}
              disabled={!canPay}
              style={{
                width: '100%', appearance: 'none', border: 'none',
                cursor: canPay ? 'pointer' : 'not-allowed',
                background: '#DD3310', color: '#fff',
                opacity: canPay ? 1 : .6,
                borderRadius: 14, padding: '16px 22px',
                display: 'flex', alignItems: 'center', justifyContent: 'space-between',
                gap: 12, fontFamily: 'inherit',
                boxShadow: '0 14px 28px -10px rgba(221,51,16,.6)',
                marginBottom: 8
              }}>
              <span style={{ fontSize: 15, fontWeight: 500 }}>{submitting ? 'Создаём заказ…' : `Оплатить ${fmtRub(total)}`}</span>
              <span style={{
                width: 30, height: 30, borderRadius: '50%',
                background: 'rgba(255,255,255,.18)', display: 'inline-flex',
                alignItems: 'center', justifyContent: 'center', fontSize: 15
              }}>→</span>
            </button>

            <div style={{
              fontSize: 10, color: '#5a6660', textAlign: 'center', lineHeight: 1.5
            }}>
              Нажимая «Оплатить», вы соглашаетесь с&nbsp;
              <a href="https://friends-sup.ru/oferta" target="_blank" rel="noopener noreferrer" style={{
                color: '#5a6660', textDecoration: 'none',
                borderBottom: '1px dotted rgba(90,102,96,.5)'
              }}>офертой</a>
              &nbsp;и&nbsp;
              <a href="https://friends-sup.ru/privacy" target="_blank" rel="noopener noreferrer" style={{
                color: '#5a6660', textDecoration: 'none',
                borderBottom: '1px dotted rgba(90,102,96,.5)'
              }}>политикой обработки персональных данных</a>
            </div>
          </>
        } />
    </StepShell>);
}

// ─── Sub-components ────────────────────────────────────────────────
function ToggleBtn({ active, onClick, children }) {
  return (
    <button onClick={onClick} style={{
      appearance: 'none', border: 'none', cursor: 'pointer',
      padding: '8px 22px', borderRadius: 999,
      background: active ? '#fff' : 'transparent',
      color: '#0A2E1F',
      fontSize: 13, fontWeight: active ? 500 : 400,
      fontFamily: 'inherit',
      boxShadow: active ? '0 2px 6px -2px rgba(10,46,31,.18)' : 'none',
      transition: 'all .18s'
    }}>{children}</button>);
}

function PillBtn({ active, onClick, children }) {
  return (
    <button onClick={onClick} style={{
      flex: 1, appearance: 'none', border: 'none', cursor: 'pointer',
      background: active ? '#0A2E1F' : '#fff',
      color: active ? '#fff' : '#0A2E1F',
      borderRadius: 12, padding: '11px 16px',
      display: 'flex', flexDirection: 'column', alignItems: 'flex-start',
      textAlign: 'left',
      boxShadow: active ? '0 8px 22px -10px rgba(10,46,31,.5)' : '0 1px 0 rgba(0,0,0,.04)',
      fontFamily: 'inherit', transition: 'all .15s'
    }}>{children}</button>);
}

function ChannelBtn({ active, onClick, icon, label, sub }) {
  return (
    <button onClick={onClick} style={{
      flex: 1, appearance: 'none', cursor: 'pointer',
      border: active ? '1.5px solid #DD3310' : '1.5px solid transparent',
      background: '#fff', borderRadius: 12, padding: '11px 14px',
      display: 'flex', alignItems: 'center', gap: 12,
      textAlign: 'left', fontFamily: 'inherit',
      boxShadow: '0 1px 0 rgba(0,0,0,.04)', transition: 'all .15s'
    }}>
      <span style={{
        width: 30, height: 30, borderRadius: 8,
        background: active ? 'rgba(221,51,16,.1)' : 'rgba(10,46,31,.06)',
        display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
        fontSize: 13, color: active ? '#DD3310' : '#0A2E1F'
      }}>{icon}</span>
      <div style={{ minWidth: 0, flex: 1 }}>
        <div style={{ fontSize: 13, fontWeight: 500, color: '#0A2E1F' }}>{label}</div>
        <div style={{
          fontSize: 11, color: '#5a6660', marginTop: 1,
          whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis'
        }}>{sub}</div>
      </div>
      <span style={{
        width: 16, height: 16, borderRadius: '50%',
        border: active ? 'none' : '1.5px solid rgba(10,46,31,.18)',
        background: active ? '#DD3310' : 'transparent',
        display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
        color: '#fff', fontSize: 9
      }}>{active ? '✓' : ''}</span>
    </button>);
}

function Field({ label, value, onChange, placeholder, prefix, status, type = 'text', onFocus }) {
  const [focused, setFocused] = React.useState(false);
  const ringColor = status === 'error' ? '#DD3310' : status === 'ok' ? '#0B5D3B' : focused ? '#0A2E1F' : null;
  return (
    <div>
      <label style={{ display: 'block', fontSize: 12, color: '#5a6660', marginBottom: 5 }}>{label}</label>
      <div style={{
        background: '#fff', borderRadius: 10,
        boxShadow: ringColor ? `0 0 0 1.5px ${ringColor}` : '0 1px 0 rgba(0,0,0,.04)',
        display: 'flex', alignItems: 'center',
        padding: '11px 13px', gap: 8, transition: 'box-shadow .15s'
      }}>
        {prefix && <span style={{ fontSize: 13 }}>{prefix}</span>}
        <input
          type={type}
          inputMode={type === 'tel' ? 'tel' : undefined}
          value={value}
          onChange={(e) => onChange(e.target.value)}
          placeholder={placeholder}
          onFocus={() => { setFocused(true); onFocus && onFocus(); }}
          onBlur={() => setFocused(false)}
          style={{
            flex: 1, border: 'none', outline: 'none', background: 'transparent',
            fontFamily: 'inherit', fontSize: 13, color: '#0A2E1F', minWidth: 0
          }} />
        {status === 'ok' && <span style={{ color: '#0B5D3B', fontSize: 14, fontWeight: 700 }}>✓</span>}
        {status === 'error' && <span style={{ color: '#DD3310', fontSize: 11, fontWeight: 500 }}>не совпадает</span>}
      </div>
    </div>);
}

function RecapRow({ label, value }) {
  return (
    <div style={{
      display: 'flex', justifyContent: 'space-between', alignItems: 'baseline',
      gap: 12, padding: '4px 0', fontSize: 12
    }}>
      <span style={{ color: '#5a6660' }}>{label}</span>
      <span style={{ color: '#0A2E1F', fontWeight: 500, textAlign: 'right' }}>{value}</span>
    </div>);
}

function TotalBar({ route, persons, total, discount = 0 }) {
  const personsCount = persons;
  const personsWord = persons === 1 ? 'человек' : persons < 5 ? 'человека' : 'человек';
  const subtotal = route.price * persons;
  const hasDiscount = discount > 0;
  return (
    <div style={{
      background: '#fff', borderRadius: 14, padding: '12px 18px',
      display: 'flex', alignItems: 'center', justifyContent: 'space-between',
      gap: 16, marginBottom: 12, boxShadow: '0 1px 0 rgba(0,0,0,.04)'
    }}>
      <div>
        <div style={{ fontSize: 11, color: '#5a6660', textTransform: 'uppercase', letterSpacing: '.14em' }}>К оплате</div>
        <div style={{ fontSize: 12, color: '#5a6660', marginTop: 3, fontVariantNumeric: 'tabular-nums' }}>
          {fmtRub(route.price)} × {personsCount} {personsWord}{hasDiscount ? ` · −${fmtRub(discount)} промокод` : ''}
        </div>
      </div>
      <div style={{
        fontFamily: '"RF Dewi Extended", "NT Somic", sans-serif', fontSize: 24, fontWeight: 500,
        letterSpacing: '-.01em', fontVariantNumeric: 'tabular-nums',
        display: 'flex', alignItems: 'baseline', gap: 8
      }}>
        {hasDiscount && (
          <span style={{
            fontSize: 16, fontWeight: 400, color: '#9aa39e',
            textDecoration: 'line-through'
          }}>{fmtRub(subtotal)}</span>
        )}
        <span style={{ color: hasDiscount ? '#DD3310' : '#0A2E1F' }}>{fmtRub(total)}</span>
      </div>
    </div>);
}

function DateTimeRow({ icon, label, children }) {
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 10, flex: 1 }}>
      <span style={{ fontSize: 16 }}>{icon}</span>
      <div>
        <div style={{ fontSize: 10, color: '#5a6660', textTransform: 'uppercase', letterSpacing: '.14em' }}>{label}</div>
        {children}
      </div>
    </div>);
}

const dtInputStyle = {
  border: 'none', outline: 'none', background: 'transparent',
  fontFamily: 'inherit', fontSize: 13, fontWeight: 500, color: '#0A2E1F',
  padding: 0, marginTop: 2
};

function formatDate(iso) {
  const months = ['янв', 'фев', 'мар', 'апр', 'май', 'июн', 'июл', 'авг', 'сен', 'окт', 'ноя', 'дек'];
  const d = new Date(iso);
  if (isNaN(d.getTime())) return iso;
  return `${d.getDate()} ${months[d.getMonth()]}`;
}

// Маска для российского номера. Принимает любой ввод, нормализует к 10-значному
// «телу» номера и форматирует как «+7 (XXX) XXX-XX-XX».
// Любые ведущие 7 и 8 трактуются как код страны и стрипаются (российские мобильные
// номера начинаются с 9, поэтому пользовательские «8…», «+7…», «787…» — это всегда
// попытка написать код страны, который мы и так показываем как «+7»).
// Если ввод полностью пустой — возвращает '' (поле остаётся опциональным).
function formatRussianPhone(input) {
  const raw = String(input == null ? '' : input);
  if (!raw.trim()) return '';
  let d = raw.replace(/\D/g, '');
  while (d.length > 0 && (d[0] === '7' || d[0] === '8')) d = d.slice(1);
  d = d.slice(0, 10);
  if (d.length === 0) return '+7 ';
  if (d.length <= 3) return `+7 (${d}`;
  if (d.length <= 6) return `+7 (${d.slice(0,3)}) ${d.slice(3)}`;
  if (d.length <= 8) return `+7 (${d.slice(0,3)}) ${d.slice(3,6)}-${d.slice(6)}`;
  return `+7 (${d.slice(0,3)}) ${d.slice(3,6)}-${d.slice(6,8)}-${d.slice(8,10)}`;
}

function personsLabelLong(n) {
  if (n === 1) return 'На одного человека';
  if (n < 5) return `На ${n} человека`;
  return `На ${n} человек`;
}

// Сохраняет non-PII выбор пользователя в sessionStorage. PII (имя/email/телефон)
// НЕ персистим — они остаются только в памяти текущей сессии.
function useSessionState(key, defaultValue) {
  const [value, setValue] = React.useState(() => {
    if (typeof window === 'undefined') return defaultValue;
    try {
      const raw = window.sessionStorage.getItem(key);
      return raw != null ? JSON.parse(raw) : defaultValue;
    } catch { return defaultValue; }
  });
  React.useEffect(() => {
    try {
      if (value === null || value === undefined) window.sessionStorage.removeItem(key);
      else window.sessionStorage.setItem(key, JSON.stringify(value));
    } catch {}
  }, [key, value]);
  return [value, setValue];
}

function clearWizardSession() {
  try {
    ['fs:routeId', 'fs:persons', 'fs:recipientMode'].forEach((k) =>
      window.sessionStorage.removeItem(k));
  } catch {}
}

function useIsMobile(breakpoint = 768) {
  const get = () => typeof window !== 'undefined' && window.innerWidth < breakpoint;
  const [isMobile, setIsMobile] = React.useState(get);
  React.useEffect(() => {
    const mql = window.matchMedia(`(max-width: ${breakpoint - 1}px)`);
    const handler = (e) => setIsMobile(e.matches);
    handler(mql);
    if (mql.addEventListener) mql.addEventListener('change', handler);
    else mql.addListener(handler);
    return () => {
      if (mql.removeEventListener) mql.removeEventListener('change', handler);
      else mql.removeListener(handler);
    };
  }, [breakpoint]);
  return isMobile;
}

// ─── Mobile widgets ────────────────────────────────────────────────
// Адаптация под iPhone-формат: один столбец, скроллится, sticky-CTA внизу.
// Использует те же ROUTES/HeroCard/FriendsLogo, что и десктоп.

function MStepDots({ active }) {
  return (
    <div style={{
      display: 'flex', alignItems: 'center', gap: 8,
      fontSize: 11, color: '#5a6660', fontWeight: 500
    }}>
      {[
        { n: '1', label: 'Сертификат' },
        { n: '2', label: 'Получатель' },
        { n: '3', label: 'Оплата' }
      ].map((s, i) => {
        const done = i + 1 < active;
        const isActive = i + 1 === active;
        return (
          <React.Fragment key={i}>
            {i > 0 && <span style={{ width: 8, height: 1, background: 'rgba(10,46,31,.16)' }} />}
            <span style={{
              display: 'flex', alignItems: 'center', gap: 5,
              color: isActive ? '#0A2E1F' : '#5a6660',
              opacity: !isActive && !done ? .5 : 1,
              fontWeight: isActive ? 500 : 400
            }}>
              <span style={{
                width: 16, height: 16, borderRadius: '50%',
                background: isActive ? '#DD3310' : done ? '#0A2E1F' : 'rgba(10,46,31,.12)',
                color: isActive || done ? '#fff' : '#0A2E1F',
                display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
                fontSize: 9, fontWeight: 600
              }}>{done ? '✓' : s.n}</span>
              {isActive && s.label}
            </span>
          </React.Fragment>);
      })}
    </div>);
}

function MobileShell({ children, step, cta }) {
  return (
    <div style={{
      height: '100%', minHeight: '100%',
      display: 'flex', flexDirection: 'column',
      background: '#F4F1EA',
      fontFamily: '"NT Somic", system-ui, -apple-system, sans-serif',
      color: '#0A2E1F',
      overflow: 'hidden'
    }}>
      <header style={{
        padding: 'calc(env(safe-area-inset-top, 0px) + 14px) 20px 14px',
        background: '#fff',
        borderBottom: '1px solid rgba(10,46,31,.06)',
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        flexShrink: 0
      }}>
        <FriendsLogo size={20} />
        <MStepDots active={step} />
      </header>
      <div style={{ flex: 1, overflow: 'auto', WebkitOverflowScrolling: 'touch', minHeight: 0 }}>
        {children}
      </div>
      {cta}
    </div>);
}

function MField({ label, value, onChange, placeholder, type = 'text', error, onFocus }) {
  return (
    <label style={{ display: 'block' }}>
      <div style={{ fontSize: 12, color: '#5a6660', marginBottom: 6 }}>{label}</div>
      <input
        type={type}
        inputMode={type === 'tel' ? 'tel' : undefined}
        value={value}
        onChange={(e) => onChange(e.target.value)}
        onFocus={onFocus}
        placeholder={placeholder}
        style={{
          width: '100%', boxSizing: 'border-box',
          background: '#fff', border: 'none', outline: 'none',
          borderRadius: 12, padding: '13px 14px',
          boxShadow: error ? '0 0 0 1.5px #DD3310' : '0 1px 0 rgba(0,0,0,.04)',
          fontFamily: 'inherit', fontSize: 16, color: '#0A2E1F'
        }} />
      {error && <div style={{ fontSize: 11, color: '#DD3310', marginTop: 4 }}>{error}</div>}
    </label>);
}

function MToggleBtn({ active, onClick, children }) {
  return (
    <button onClick={onClick} style={{
      flex: 1, appearance: 'none', border: 'none', cursor: 'pointer',
      background: active ? '#fff' : 'transparent',
      color: '#0A2E1F',
      padding: '10px 14px', borderRadius: 999,
      fontFamily: 'inherit', fontSize: 14, fontWeight: 500,
      boxShadow: active ? '0 1px 3px rgba(10,46,31,.1)' : 'none',
      transition: 'all .2s'
    }}>{children}</button>);
}

function MRecapRow({ label, value }) {
  return (
    <div style={{
      display: 'flex', justifyContent: 'space-between',
      padding: '10px 0', fontSize: 13,
      borderBottom: '1px solid rgba(10,46,31,.06)'
    }}>
      <span style={{ color: '#5a6660' }}>{label}</span>
      <span style={{ color: '#0A2E1F', fontWeight: 500, textAlign: 'right' }}>{value}</span>
    </div>);
}

function StickyCTA({ total, ctaLabel, onCta, ctaDisabled, discount = 0 }) {
  const hasDiscount = discount > 0;
  const subtotal = total + discount;
  return (
    <div style={{
      flexShrink: 0,
      background: '#fff',
      borderTop: '1px solid rgba(10,46,31,.08)',
      padding: '14px 20px calc(env(safe-area-inset-bottom, 0px) + 14px)',
      boxShadow: '0 -4px 16px rgba(10,46,31,.04)'
    }}>
      {hasDiscount && (
        <div style={{
          display: 'flex', justifyContent: 'space-between', alignItems: 'baseline',
          fontSize: 11, color: '#0B5D3B', marginBottom: 4, fontWeight: 500
        }}>
          <span>Скидка</span><span>−{fmtRub(discount)}</span>
        </div>
      )}
      <div style={{
        display: 'flex', justifyContent: 'space-between', alignItems: 'baseline',
        marginBottom: 12, gap: 8
      }}>
        <span style={{ fontSize: 12, color: '#5a6660', letterSpacing: '.1em', textTransform: 'uppercase' }}>К оплате</span>
        <span style={{ display: 'flex', alignItems: 'baseline', gap: 8 }}>
          {hasDiscount && (
            <span style={{
              fontFamily: '"RF Dewi Extended", "NT Somic", sans-serif',
              fontSize: 16, fontWeight: 600, color: '#9aa39e',
              textDecoration: 'line-through', letterSpacing: '-.01em'
            }}>{fmtRub(subtotal)}</span>
          )}
          <span style={{
            fontFamily: '"RF Dewi Extended", "NT Somic", sans-serif',
            fontSize: 24, fontWeight: 800,
            color: hasDiscount ? '#DD3310' : '#0A2E1F',
            letterSpacing: '-.01em'
          }}>{fmtRub(total)}</span>
        </span>
      </div>
      <button
        onClick={onCta}
        disabled={ctaDisabled}
        style={{
          width: '100%', appearance: 'none', border: 'none',
          cursor: ctaDisabled ? 'default' : 'pointer',
          background: ctaDisabled ? 'rgba(10,46,31,.16)' : '#DD3310',
          color: '#fff', padding: '15px 20px', borderRadius: 14,
          fontFamily: 'inherit', fontSize: 15, fontWeight: 600,
          letterSpacing: '.01em', transition: 'background .15s'
        }}>{ctaLabel}</button>
      <div style={{
        textAlign: 'center', marginTop: 12, fontSize: 12, color: '#7a8682'
      }}>
        Нужна помощь?{' '}
        <a href="https://t.me/friendssuphelp" target="_blank" rel="noopener noreferrer" style={{
          color: '#0A2E1F', textDecoration: 'none', fontWeight: 500,
          borderBottom: '1px dashed rgba(10,46,31,.25)', paddingBottom: 1
        }}>Поддержка</a>
      </div>
    </div>);
}

function MobileStep1({ initialRouteId = null, initialPersons = 1, onContinue }) {
  const [routeId, setRouteId] = React.useState(initialRouteId);
  const [persons, setPersons] = React.useState(initialPersons);
  const selectedRoute = routeId ? ROUTES.find((r) => r.id === routeId) : null;
  const previewRoute = selectedRoute || ROUTES[0];
  const total = selectedRoute ? selectedRoute.price * persons : 0;
  const canContinue = !!selectedRoute;

  return (
    <MobileShell
      step={1}
      cta={
        <StickyCTA
          total={total}
          ctaLabel="Продолжить →"
          ctaDisabled={!canContinue}
          onCta={() => canContinue && onContinue && onContinue({ routeId, persons })} />
      }>
      <div style={{ padding: '20px 20px 24px' }}>
        <div style={{ fontSize: 11, letterSpacing: '.18em', textTransform: 'uppercase', color: '#5a6660', marginBottom: 6 }}>
          Шаг 1 из 3
        </div>
        <div style={{
          fontFamily: '"RF Dewi Extended", "NT Somic", sans-serif',
          fontSize: 24, fontWeight: 800, letterSpacing: '-.01em', marginBottom: 16
        }}>Какой сертификат подарить?</div>

        <div style={{ marginBottom: 22 }}>
          <HeroCard
            amount={previewRoute.price} accent="#5c615f"
            routeName={selectedRoute ? previewRoute.name : 'Выберите маршрут'}
            routeSubtitle={selectedRoute ? previewRoute.subtitle : ''}
            persons={persons} photo={previewRoute.photo} />
        </div>

        <div style={{ fontSize: 11, letterSpacing: '.18em', textTransform: 'uppercase', color: '#5a6660', marginBottom: 10 }}>
          Маршруты
        </div>
        <div style={{ display: 'grid', gap: 8, marginBottom: 24 }}>
          {ROUTES.map((r) => {
            const active = r.id === routeId;
            return (
              <button key={r.id} onClick={() => setRouteId(r.id)} style={{
                appearance: 'none', cursor: 'pointer', textAlign: 'left',
                background: active ? '#DD3310' : '#fff',
                color: active ? '#fff' : '#0A2E1F',
                border: 'none', borderRadius: 14, padding: '14px 16px',
                boxShadow: active ? '0 8px 24px -8px rgba(221,51,16,.4)' : '0 1px 0 rgba(0,0,0,.04)',
                display: 'flex', alignItems: 'center', gap: 12, position: 'relative',
                fontFamily: 'inherit'
              }}>
                <span style={{
                  width: 18, height: 18, borderRadius: '50%',
                  border: `1.5px solid ${active ? '#fff' : 'rgba(10,46,31,.25)'}`,
                  background: active ? '#fff' : 'transparent',
                  flexShrink: 0, position: 'relative'
                }}>
                  {active && <span style={{ position: 'absolute', inset: 4, borderRadius: '50%', background: '#DD3310' }} />}
                </span>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 2 }}>
                    <span style={{ fontSize: 14, fontWeight: 600 }}>{r.name}</span>
                    {r.featured && (
                      <span style={{
                        fontSize: 9, fontWeight: 600, letterSpacing: '.1em',
                        padding: '2px 6px', borderRadius: 4,
                        background: active ? '#fff' : '#DD3310',
                        color: active ? '#DD3310' : '#fff'
                      }}>ХИТ</span>
                    )}
                  </div>
                  <div style={{
                    fontSize: 11, opacity: active ? .9 : .65,
                    lineHeight: 1.4, textOverflow: 'ellipsis', overflow: 'hidden',
                    display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical'
                  }}>{r.subtitle}</div>
                </div>
                <div style={{
                  fontFamily: '"RF Dewi Extended", "NT Somic", sans-serif',
                  fontSize: 16, fontWeight: 800, letterSpacing: '-.01em', flexShrink: 0
                }}>{fmtRub(r.price)}<span style={{ fontSize: 10, fontWeight: 500, opacity: .7 }}>/чел</span></div>
              </button>);
          })}
        </div>

        <div style={{ fontSize: 11, letterSpacing: '.18em', textTransform: 'uppercase', color: '#5a6660', marginBottom: 10 }}>
          На сколько человек
        </div>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(6, 1fr)', gap: 6, marginBottom: 8 }}>
          {[1, 2, 3, 4, 5, 6].map((n) => {
            const active = persons === n;
            return (
              <button key={n} onClick={() => setPersons(n)} style={{
                appearance: 'none', cursor: 'pointer', border: 'none',
                background: active ? '#DD3310' : '#fff',
                color: active ? '#fff' : '#0A2E1F',
                padding: '12px 0', borderRadius: 12,
                fontFamily: '"RF Dewi Extended", "NT Somic", sans-serif',
                fontSize: 16, fontWeight: 800,
                boxShadow: active ? 'none' : '0 1px 0 rgba(0,0,0,.04)',
                transition: 'all .15s'
              }}>{n}</button>);
          })}
        </div>
        <div style={{ fontSize: 12, color: '#5a6660' }}>{personsLabelLong(persons)}</div>
      </div>
    </MobileShell>);
}

function MobileStep2({
  routeId = 'universal', persons = 1,
  initialRecipient = 'friend',
  initialData = {},
  onBack, onContinue
}) {
  const route = ROUTES.find((r) => r.id === routeId) || ROUTES[0];
  const [recipient, setRecipient] = React.useState(initialData.recipient || initialRecipient);
  const [name, setName] = React.useState(initialData.name || '');
  const [phone, setPhone] = React.useState(initialData.phone || '');
  const [email, setEmail] = React.useState(initialData.email || '');
  const [wish, setWish] = React.useState(initialData.wish || '');
  const [whenMode, setWhenMode] = React.useState(initialData.whenMode || 'now');
  const [date, setDate] = React.useState(initialData.date || '2026-05-08');
  const [time, setTime] = React.useState(initialData.time || '11:00');
  const total = route.price * persons;

  const canContinue = name.trim().length > 0 && email.trim().length > 0;

  const handle = () => {
    if (!canContinue) return;
    const deliveryMethod = `Email · ${email}`;
    const whenLabel = recipient === 'self' ? 'Сейчас' :
      whenMode === 'now' ? 'Сейчас' : `${formatDate(date)}, ${time}`;
    onContinue && onContinue({
      recipient, name, phone, email, wish,
      whenMode, date, time, deliveryMethod, whenLabel
    });
  };

  return (
    <MobileShell
      step={2}
      cta={
        <StickyCTA
          total={total}
          ctaLabel="К оплате →"
          ctaDisabled={!canContinue}
          onCta={handle} />
      }>
      <div style={{ padding: '16px 20px 24px' }}>
        <div style={{
          display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8,
          fontSize: 11, letterSpacing: '.18em', textTransform: 'uppercase', color: '#5a6660'
        }}>
          <button onClick={onBack} style={{
            appearance: 'none', border: 'none', background: 'transparent',
            cursor: 'pointer', padding: 0, fontSize: 11, color: '#5a6660',
            letterSpacing: '.18em', textTransform: 'uppercase', fontFamily: 'inherit',
            display: 'inline-flex', alignItems: 'center', gap: 4
          }}>← Назад</button>
          <span style={{ opacity: .4 }}>·</span>
          <span>Шаг 2 из 3</span>
        </div>

        <div style={{
          fontFamily: '"RF Dewi Extended", "NT Somic", sans-serif',
          fontSize: 24, fontWeight: 800, letterSpacing: '-.01em', marginBottom: 16
        }}>Кому подарок?</div>

        <div style={{
          display: 'flex', gap: 4, padding: 4,
          background: 'rgba(10,46,31,.06)', borderRadius: 999, marginBottom: 18
        }}>
          <MToggleBtn active={recipient === 'self'} onClick={() => setRecipient('self')}>Себе</MToggleBtn>
          <MToggleBtn active={recipient === 'friend'} onClick={() => setRecipient('friend')}>Другу</MToggleBtn>
        </div>

        <div style={{ display: 'grid', gap: 12, marginBottom: 16 }}>
          <MField label={recipient === 'self' ? 'Ваше имя*' : 'Имя получателя*'} value={name} onChange={setName} placeholder={recipient === 'self' ? 'Михаил' : 'Например, Аня'} />
          <MField label="Телефон" value={phone}
            onChange={(v) => setPhone(formatRussianPhone(v))}
            placeholder="+7 (___) ___-__-__" type="tel" />
          <MField label={recipient === 'self' ? 'Email*' : 'Email получателя*'} value={email} onChange={setEmail} placeholder="name@mail.ru" type="email" />
        </div>

        {recipient === 'friend' ? (
          <>
            <div style={{ marginBottom: 18 }}>
              <div style={{ fontSize: 12, color: '#5a6660', marginBottom: 6 }}>
                Пожелание <span style={{ opacity: .6 }}>· напечатаем на открытке</span>
              </div>
              <textarea
                value={wish}
                onChange={(e) => setWish(e.target.value)}
                rows={4} maxLength={280}
                placeholder="Например: «С днём рождения! Жду тебя на воде ✨»"
                style={{
                  width: '100%', boxSizing: 'border-box',
                  background: '#fff', border: 'none', outline: 'none',
                  borderRadius: 12, padding: '12px 14px',
                  boxShadow: '0 1px 0 rgba(0,0,0,.04)',
                  fontFamily: 'inherit', fontSize: 16, color: '#0A2E1F',
                  resize: 'none', lineHeight: 1.5
                }} />
              <div style={{ fontSize: 11, color: '#7a8682', marginTop: 4, textAlign: 'right' }}>
                {wish.length}/280
              </div>
            </div>

            <div style={{ fontSize: 11, letterSpacing: '.18em', textTransform: 'uppercase', color: '#5a6660', marginBottom: 10 }}>
              Когда отправить
            </div>
            <div style={{ display: 'grid', gap: 8, marginBottom: 16 }}>
              {[
                { id: 'now', label: 'Сейчас', desc: 'Сразу после оплаты' },
                { id: 'pick', label: 'Запланировать', desc: 'Выбрать дату и время' }
              ].map((opt) => {
                const active = whenMode === opt.id;
                return (
                  <button key={opt.id} onClick={() => setWhenMode(opt.id)} style={{
                    appearance: 'none', cursor: 'pointer', textAlign: 'left',
                    background: active ? '#fff' : 'transparent',
                    color: '#0A2E1F',
                    border: `1.5px solid ${active ? '#0A2E1F' : 'rgba(10,46,31,.14)'}`,
                    borderRadius: 12, padding: '12px 14px',
                    fontFamily: 'inherit',
                    display: 'flex', alignItems: 'center', gap: 12
                  }}>
                    <span style={{
                      width: 18, height: 18, borderRadius: '50%',
                      border: `1.5px solid ${active ? '#0A2E1F' : 'rgba(10,46,31,.25)'}`,
                      flexShrink: 0, position: 'relative'
                    }}>
                      {active && <span style={{ position: 'absolute', inset: 3, borderRadius: '50%', background: '#0A2E1F' }} />}
                    </span>
                    <div style={{ flex: 1 }}>
                      <div style={{ fontSize: 14, fontWeight: 500 }}>{opt.label}</div>
                      <div style={{ fontSize: 11, color: '#7a8682' }}>{opt.desc}</div>
                    </div>
                  </button>);
              })}
            </div>

            {whenMode === 'pick' && (
              <>
                <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8, marginBottom: 6 }}>
                  <MField label="Дата" value={date} onChange={setDate} type="date" />
                  <MField label="Время" value={time} onChange={setTime} type="time" />
                </div>
                <div style={{ fontSize: 11, color: '#9aa39e', marginBottom: 16 }}>
                  Время московское (UTC+3)
                </div>
              </>
            )}
          </>
        ) : (
          <div style={{
            fontSize: 12, color: '#5a6660', lineHeight: 1.5,
            padding: '12px 14px', background: 'rgba(10,46,31,.04)', borderRadius: 10,
            marginBottom: 16
          }}>
            Сертификат отправим на ваш email сразу после оплаты.
          </div>
        )}
      </div>
    </MobileShell>);
}

function MobileStep3({
  routeId = 'universal', persons = 1,
  recipientName = '', deliveryMethod = '', whenLabel = 'Сейчас',
  initialPayerName = '', initialPayerEmail = '',
  onBack, onPay
}) {
  const route = ROUTES.find((r) => r.id === routeId) || ROUTES[0];
  const [payerName, setPayerName] = React.useState(initialPayerName);
  const [payerEmail, setPayerEmail] = React.useState(initialPayerEmail);
  const [promo, setPromo] = React.useState('');
  const [promoApplied, setPromoApplied] = React.useState(false);
  const [promoPercent, setPromoPercent] = React.useState(0);
  const [promoError, setPromoError] = React.useState('');
  const [promoChecking, setPromoChecking] = React.useState(false);
  const [submitting, setSubmitting] = React.useState(false);

  const checkPromo = async () => {
    if (!promo.trim()) return;
    setPromoChecking(true);
    setPromoError('');
    try {
      const res = await fetch('/api/promo/check', {
        method: 'POST', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ code: promo })
      });
      const data = await res.json();
      if (data.valid) {
        setPromoApplied(true);
        setPromoPercent(data.discountPercent);
        setPromoError('');
      } else {
        setPromoApplied(false);
        setPromoPercent(0);
        setPromoError(data.error || 'Промокод не найден');
      }
    } catch (err) {
      setPromoApplied(false);
      setPromoError('Не удалось проверить промокод');
    } finally {
      setPromoChecking(false);
    }
  };

  const subtotal = route.price * persons;
  const discount = promoApplied ? Math.round(subtotal * promoPercent / 100) : 0;
  const total = subtotal - discount;
  const personsTxt = personsLabelLong(persons).replace('На ', '').toLowerCase();
  const canPay = payerName.trim().length > 0 && payerEmail.trim().length > 0 && !submitting;

  const handle = async () => {
    if (!canPay) return;
    setSubmitting(true);
    try {
      await (onPay && onPay({ payerName, payerEmail, promo, promoApplied, total, discount }));
    } finally {
      setSubmitting(false);
    }
  };

  return (
    <MobileShell
      step={3}
      cta={
        <StickyCTA
          total={total}
          discount={discount}
          ctaLabel={submitting ? 'Создаём заказ…' : 'Оплатить →'}
          ctaDisabled={!canPay}
          onCta={handle} />
      }>
      <div style={{ padding: '16px 20px 24px' }}>
        <div style={{
          display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8,
          fontSize: 11, letterSpacing: '.18em', textTransform: 'uppercase', color: '#5a6660'
        }}>
          <button onClick={onBack} style={{
            appearance: 'none', border: 'none', background: 'transparent',
            cursor: 'pointer', padding: 0, fontSize: 11, color: '#5a6660',
            letterSpacing: '.18em', textTransform: 'uppercase', fontFamily: 'inherit',
            display: 'inline-flex', alignItems: 'center', gap: 4
          }}>← Назад</button>
          <span style={{ opacity: .4 }}>·</span>
          <span>Шаг 3 из 3</span>
        </div>

        <div style={{
          fontFamily: '"RF Dewi Extended", "NT Somic", sans-serif',
          fontSize: 24, fontWeight: 800, letterSpacing: '-.01em', marginBottom: 4
        }}>Оплата</div>
        <div style={{ fontSize: 13, color: '#5a6660', marginBottom: 18 }}>
          Чек придёт на ваш email
        </div>

        <div style={{
          background: '#fff', borderRadius: 14, padding: '14px 16px',
          boxShadow: '0 1px 0 rgba(0,0,0,.04)', marginBottom: 16
        }}>
          <div style={{ fontSize: 11, letterSpacing: '.18em', textTransform: 'uppercase', color: '#5a6660', marginBottom: 8 }}>
            Заказ
          </div>
          <MRecapRow label="Маршрут" value={route.name} />
          <MRecapRow label="Количество" value={personsTxt} />
          <MRecapRow label="Получатель" value={recipientName || '—'} />
          <MRecapRow label="Способ" value={deliveryMethod || '—'} />
          <div style={{ display: 'flex', justifyContent: 'space-between', padding: '10px 0 0', fontSize: 13 }}>
            <span style={{ color: '#5a6660' }}>Время отправки</span>
            <span style={{ color: '#0A2E1F', fontWeight: 500 }}>{whenLabel}</span>
          </div>
        </div>

        <div style={{ fontSize: 11, letterSpacing: '.18em', textTransform: 'uppercase', color: '#5a6660', marginBottom: 10 }}>
          Ваши контактные данные
        </div>
        <div style={{ display: 'grid', gap: 12, marginBottom: 16 }}>
          <MField label="Имя*" value={payerName} onChange={setPayerName} placeholder="Михаил" />
          <MField label="Email*" value={payerEmail} onChange={setPayerEmail} placeholder="name@mail.ru" type="email" />
        </div>

        <div style={{ fontSize: 11, letterSpacing: '.18em', textTransform: 'uppercase', color: '#5a6660', marginBottom: 10 }}>
          Промокод
        </div>
        {promoApplied ? (
          <div style={{
            background: 'linear-gradient(90deg, #0B5D3B 0%, #0A2E1F 100%)', color: '#fff',
            borderRadius: 12, padding: '12px 14px',
            display: 'flex', alignItems: 'center', gap: 12, marginBottom: 16,
            boxShadow: '0 8px 22px -10px rgba(11,93,59,.5)'
          }}>
            <span style={{
              width: 26, height: 26, borderRadius: '50%',
              background: 'rgba(255,255,255,.18)', display: 'inline-flex',
              alignItems: 'center', justifyContent: 'center', fontSize: 13
            }}>✓</span>
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ fontSize: 13, fontWeight: 500, letterSpacing: '.04em' }}>{promo.toUpperCase()} · −{promoPercent}%</div>
              <div style={{ fontSize: 11, opacity: .8, marginTop: 1 }}>−{fmtRub(discount)}</div>
            </div>
            <button onClick={() => { setPromoApplied(false); setPromoPercent(0); setPromo(''); setPromoError(''); }} style={{
              appearance: 'none', border: 'none', cursor: 'pointer',
              background: 'transparent', color: 'rgba(255,255,255,.7)',
              fontSize: 11, fontFamily: 'inherit',
              textDecoration: 'underline', textUnderlineOffset: 2
            }}>Убрать</button>
          </div>
        ) : (
          <div style={{ marginBottom: 16 }}>
            <div style={{
              background: '#fff', borderRadius: 12, padding: '8px 8px 8px 14px',
              boxShadow: promoError ? '0 0 0 1.5px #DD3310' : '0 1px 0 rgba(0,0,0,.04)',
              display: 'flex', alignItems: 'center', gap: 8
            }}>
              <span style={{ fontSize: 14 }}>🏷</span>
              <input
                value={promo}
                onChange={(e) => { setPromo(e.target.value); setPromoApplied(false); setPromoError(''); }}
                onKeyDown={(e) => { if (e.key === 'Enter') checkPromo(); }}
                placeholder="Введите промокод"
                style={{
                  flex: 1, border: 'none', outline: 'none', background: 'transparent',
                  fontFamily: 'inherit', fontSize: 14, color: '#0A2E1F',
                  letterSpacing: '.04em', textTransform: 'uppercase', minWidth: 0,
                  padding: '6px 0'
                }} />
              <button
                onClick={checkPromo}
                disabled={!promo || promoChecking}
                style={{
                  appearance: 'none', border: 'none', cursor: (promo && !promoChecking) ? 'pointer' : 'default',
                  background: 'rgba(10,46,31,.08)', color: '#0A2E1F',
                  padding: '8px 14px', borderRadius: 999,
                  fontSize: 12, fontWeight: 500, fontFamily: 'inherit',
                  opacity: (promo && !promoChecking) ? 1 : .5, flexShrink: 0
                }}>{promoChecking ? '…' : 'Применить'}</button>
            </div>
            {promoError && (
              <div style={{ fontSize: 11, color: '#DD3310', marginTop: 6 }}>
                {promoError}
              </div>
            )}
          </div>
        )}

        <div style={{ fontSize: 11, color: '#7a8682', lineHeight: 1.5 }}>
          Нажимая «Оплатить», вы соглашаетесь с{' '}
          <a href="https://friends-sup.ru/oferta" target="_blank" rel="noopener noreferrer" style={{
            color: '#7a8682', textDecoration: 'none',
            borderBottom: '1px dotted rgba(90,102,96,.5)'
          }}>офертой</a>
          {' '}и{' '}
          <a href="https://friends-sup.ru/privacy" target="_blank" rel="noopener noreferrer" style={{
            color: '#7a8682', textDecoration: 'none',
            borderBottom: '1px dotted rgba(90,102,96,.5)'
          }}>политикой обработки персональных данных</a>.
        </div>
      </div>
    </MobileShell>);
}

// ─── Payment iframe overlay ────────────────────────────────────────
// Плавающая карточка с iframe Paykeeper и поллингом нашего бэкенда.
// На 'paid' зовёт onPaid, на 'expired'/'canceled' — onFailed.
function PaymentFrame({ orderId, certNumber, invoiceUrl, onCancel, onPaid, onFailed }) {
  const isMobile = useIsMobile();
  const stoppedRef = React.useRef(false);
  const initialIframeLoadRef = React.useRef(true);

  // Один централизованный «спросить статус» — используется и таймером, и при отмене.
  const fetchStatus = React.useCallback(async () => {
    if (stoppedRef.current) return null;
    try {
      const res = await fetch(`/api/orders/${orderId}/status`);
      if (!res.ok) return null;
      return res.json();
    } catch { return null; }
  }, [orderId]);

  // Принудительная сверка с Paykeeper: бэкенд сразу пойдёт спросит у Paykeeper
  // про этот конкретный заказ, не ждёт следующего тика своего поллера.
  const forceRefresh = React.useCallback(async () => {
    if (stoppedRef.current) return null;
    try {
      const res = await fetch(`/api/orders/${orderId}/refresh`, { method: 'POST' });
      if (!res.ok) return null;
      return res.json();
    } catch { return null; }
  }, [orderId]);

  const applyStatus = React.useCallback((data, intervalId) => {
    if (!data) return false;
    if (data.paymentStatus === 'paid') {
      stoppedRef.current = true;
      if (intervalId) clearInterval(intervalId);
      onPaid && onPaid(data);
      return true;
    }
    if (data.paymentStatus === 'expired' || data.paymentStatus === 'canceled') {
      stoppedRef.current = true;
      if (intervalId) clearInterval(intervalId);
      onFailed && onFailed(data.paymentStatus);
      return true;
    }
    return false;
  }, [onPaid, onFailed]);

  // iframe.onload — Paykeeper навигировался (показал юзеру какую-то новую страницу,
  // часто это «Платёж совершён!» или «Не удалось»). Срабатывает сильным сигналом
  // о смене состояния — вызываем force refresh.
  const handleIframeLoad = React.useCallback(async () => {
    if (initialIframeLoadRef.current) {
      initialIframeLoadRef.current = false;
      return; // пропускаем самый первый load (первичная загрузка инвойса)
    }
    if (stoppedRef.current) return;
    const data = await forceRefresh();
    applyStatus(data);
  }, [forceRefresh, applyStatus]);

  React.useEffect(() => {
    stoppedRef.current = false;
    let intervalId;
    const tick = async () => {
      const data = await fetchStatus();
      applyStatus(data, intervalId);
    };
    tick();
    intervalId = setInterval(tick, 1500);
    return () => { stoppedRef.current = true; clearInterval(intervalId); };
  }, [orderId, fetchStatus, applyStatus]);

  // При клике «Отменить» — проверяем статус последний раз перед закрытием:
  // если оплата прошла, пока пользователь думал, не закрываем зря.
  const handleCancel = async () => {
    const data = await fetchStatus();
    if (data && data.paymentStatus === 'paid') {
      onPaid && onPaid(data);
      return;
    }
    if (data && (data.paymentStatus === 'expired' || data.paymentStatus === 'canceled')) {
      onFailed && onFailed(data.paymentStatus);
      return;
    }
    onCancel && onCancel();
  };

  // Слим-полоса между шапкой визарда и iframe: пульсирующий «ждём подтверждение»
  // + кнопка «✕ Отменить». Отделена от чрома, чтобы не конкурировать с лого/breadcrumbs.
  const statusBar = (
    <div style={{
      flexShrink: 0,
      display: 'flex', alignItems: 'center', justifyContent: 'space-between',
      padding: isMobile ? '10px 16px' : '12px 28px',
      background: '#FAF7EF',
      borderBottom: '1px solid rgba(10,46,31,.06)',
      gap: 12
    }}>
      <div style={{
        display: 'flex', alignItems: 'center', gap: 8,
        fontSize: 12, color: '#3a4a44', letterSpacing: '.02em', minWidth: 0
      }}>
        <span style={{
          width: 8, height: 8, borderRadius: '50%', flexShrink: 0,
          background: '#0A2E1F',
          boxShadow: '0 0 0 0 rgba(10,46,31,.4)',
          animation: 'pkPulse 1.4s ease-in-out infinite'
        }} />
        <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
          Ждём подтверждение оплаты · <span style={{ fontVariantNumeric: 'tabular-nums', color: '#0A2E1F', fontWeight: 500 }}>{certNumber}</span>
        </span>
      </div>
      <button onClick={handleCancel} style={{
        appearance: 'none', border: '1px solid rgba(10,46,31,.22)',
        background: 'transparent', color: '#0A2E1F',
        fontSize: 12, padding: '6px 12px', borderRadius: 999,
        cursor: 'pointer', fontFamily: 'inherit', flexShrink: 0
      }}>✕ Отменить</button>
    </div>);

  const iframeEl = (
    <iframe
      src={invoiceUrl}
      title="Оплата"
      onLoad={handleIframeLoad}
      style={{ flex: 1, border: 'none', width: '100%', minHeight: 0, background: '#F0EEE9' }}
      allow="payment" />);

  const pulseStyles = (
    <style>{`
      @keyframes pkPulse {
        0%   { box-shadow: 0 0 0 0 rgba(10,46,31,.4); }
        70%  { box-shadow: 0 0 0 8px rgba(10,46,31,0); }
        100% { box-shadow: 0 0 0 0 rgba(10,46,31,0); }
      }
    `}</style>);

  // Десктоп — рендерим в стандартный chrome визарда (StepShell со step 3).
  if (!isMobile) {
    return (
      <StepShell stepNumber={3}>
        {statusBar}
        {iframeEl}
        {pulseStyles}
      </StepShell>);
  }

  // Мобайл — собственная оболочка с теми же шапкой/футером, но без скролла
  // в теле (iframe заполняет всё доступное пространство).
  return (
    <div style={{
      height: '100%', minHeight: '100%',
      display: 'flex', flexDirection: 'column',
      background: '#F4F1EA',
      fontFamily: '"NT Somic", system-ui, -apple-system, sans-serif',
      color: '#0A2E1F',
      overflow: 'hidden'
    }}>
      <header style={{
        padding: 'calc(env(safe-area-inset-top, 0px) + 14px) 20px 14px',
        background: '#fff',
        borderBottom: '1px solid rgba(10,46,31,.06)',
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        flexShrink: 0
      }}>
        <FriendsLogo size={20} />
        <MStepDots active={3} />
      </header>
      {statusBar}
      {iframeEl}
      {pulseStyles}
    </div>);
}

// ─── Result states (success / timeout / failure) ──────────────────
// Дизайн повторяет шапку и подвал Шагов 1–3. Пять состояний: success-self,
// success-friend (отправлено сразу), success-scheduled (к дате), expired, failed.

function StatusIconBig({ kind, mobile }) {
  const size = mobile ? 72 : 88;
  if (kind === 'success') {
    return (
      <div style={{
        width: size, height: size, borderRadius: '50%',
        background: '#0A2E1F',
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        boxShadow: mobile ? '0 14px 30px -12px rgba(10,46,31,.5)' : '0 18px 40px -16px rgba(10,46,31,.55)'
      }}>
        <svg width={mobile ? 36 : 44} height={mobile ? 36 : 44} viewBox="0 0 44 44" fill="none">
          <path d="M11 22.5 L19 30.5 L33 14"
            stroke="#E9F7B0" strokeWidth="4.5"
            strokeLinecap="round" strokeLinejoin="round" />
        </svg>
      </div>);
  }
  return (
    <div style={{
      width: size, height: size, borderRadius: '50%',
      background: '#F4ECE2',
      display: 'flex', alignItems: 'center', justifyContent: 'center'
    }}>
      <svg width={mobile ? 28 : 36} height={mobile ? 36 : 44} viewBox="0 0 36 44" fill="none">
        <path d="M18 8 V26" stroke="#DD3310" strokeWidth="5" strokeLinecap="round" />
        <circle cx="18" cy="34.5" r="2.7" fill="#DD3310" />
      </svg>
    </div>);
}

function ResultShellDesktop({ statusLabel, children }) {
  return (
    <div style={{
      position: 'relative',
      width: '100%', height: '100%',
      background: '#fff',
      borderRadius: 18,
      overflow: 'hidden',
      boxShadow: '0 1px 0 rgba(0,0,0,.04), 0 30px 80px -30px rgba(0,0,0,.18)',
      fontFamily: '"NT Somic", system-ui, -apple-system, sans-serif',
      color: '#0A2E1F',
      display: 'flex', flexDirection: 'column'
    }}>
      <header style={{
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        padding: '22px 32px', borderBottom: '1px solid rgba(10,46,31,.06)',
        flexShrink: 0
      }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 18 }}>
          <FriendsLogo size={22} />
          <div style={{ width: 1, height: 22, background: 'rgba(10,46,31,.12)' }} />
          <span style={{ fontSize: 14, color: '#3a4a44' }}>Электронные подарочные сертификаты</span>
        </div>
        <div style={{
          fontSize: 11, color: '#5a6660', letterSpacing: '.18em', textTransform: 'uppercase'
        }}>
          {statusLabel}
        </div>
      </header>

      <div style={{
        flex: 1, minHeight: 0,
        background: '#F4F1EA',
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        padding: '32px 40px',
        overflow: 'auto'
      }}>
        {children}
      </div>

      <footer style={{
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        padding: '14px 32px',
        borderTop: '1px solid rgba(10,46,31,.06)',
        flexShrink: 0
      }}>
        <a href="https://friends-sup.ru/" target="_blank" rel="noopener noreferrer" style={{
          fontSize: 13, color: '#5a6660', letterSpacing: '.04em', textDecoration: 'none'
        }}>friends-sup.ru</a>
        <a href="https://t.me/friendssuphelp" target="_blank" rel="noopener noreferrer" style={{
          display: 'flex', alignItems: 'center', gap: 10, textDecoration: 'none'
        }}>
          <div style={{
            width: 30, height: 30, borderRadius: '50%',
            background: '#0A2E1F', display: 'flex', alignItems: 'center', justifyContent: 'center',
            color: '#fff', fontSize: 14
          }}>?</div>
          <span style={{ fontSize: 12, fontWeight: 600, letterSpacing: '.16em', color: '#0A2E1F' }}>ПОДДЕРЖКА</span>
        </a>
        <a href="#" onClick={(e) => e.preventDefault()} style={{ fontSize: 13, color: '#5a6660', textDecoration: 'none' }}>Правила</a>
      </footer>
    </div>);
}

function ResultShellMobile({ statusLabel, children }) {
  return (
    <div style={{
      height: '100%', minHeight: '100%',
      display: 'flex', flexDirection: 'column',
      background: '#F4F1EA',
      fontFamily: '"NT Somic", system-ui, -apple-system, sans-serif',
      color: '#0A2E1F'
    }}>
      <header style={{
        padding: 'calc(env(safe-area-inset-top, 0px) + 14px) 20px 14px',
        background: '#fff',
        borderBottom: '1px solid rgba(10,46,31,.06)',
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        flexShrink: 0
      }}>
        <FriendsLogo size={20} />
        <span style={{
          fontSize: 10, color: '#5a6660',
          letterSpacing: '.18em', textTransform: 'uppercase'
        }}>{statusLabel}</span>
      </header>

      <div style={{
        flex: 1, overflow: 'auto', WebkitOverflowScrolling: 'touch',
        minHeight: 0, display: 'flex', flexDirection: 'column'
      }}>
        <div style={{ flex: 1, padding: '24px 16px 16px', display: 'flex' }}>
          {children}
        </div>

        <div style={{
          padding: '20px 20px calc(env(safe-area-inset-bottom, 0px) + 24px)',
          fontSize: 11, color: '#7a8682',
          display: 'flex', justifyContent: 'space-between', alignItems: 'center',
          flexShrink: 0
        }}>
          <a href="https://friends-sup.ru/" target="_blank" rel="noopener noreferrer" style={{
            color: '#7a8682', textDecoration: 'none', letterSpacing: '.04em'
          }}>friends-sup.ru</a>
          <a href="#" onClick={(e) => e.preventDefault()} style={{
            color: '#7a8682', textDecoration: 'none',
            borderBottom: '1px dashed rgba(10,46,31,.2)'
          }}>Правила</a>
        </div>
      </div>
    </div>);
}

function ResultCardDesktop({ kind, title, description, orderId, primaryLabel, primaryAction, secondaryLabel, secondaryAction }) {
  return (
    <div style={{
      width: '100%', maxWidth: 720,
      background: '#fff',
      borderRadius: 22,
      padding: '56px 64px 52px',
      boxShadow: '0 1px 0 rgba(0,0,0,.04), 0 30px 60px -30px rgba(10,46,31,.16)',
      display: 'flex', flexDirection: 'column', alignItems: 'center',
      textAlign: 'center'
    }}>
      <StatusIconBig kind={kind} />

      <h1 style={{
        margin: '28px 0 0',
        fontFamily: '"RF Dewi Extended", "NT Somic", sans-serif',
        fontSize: 34, fontWeight: 800,
        letterSpacing: '-.01em', color: '#0A2E1F',
        lineHeight: 1.1
      }}>{title}</h1>

      <p style={{
        margin: '14px 0 0',
        fontSize: 15, lineHeight: 1.55,
        color: '#3a4a44', maxWidth: 520
      }}>{description}</p>

      {orderId && (
        <div style={{
          marginTop: 24,
          background: '#F4ECE2',
          padding: '11px 22px',
          borderRadius: 999,
          fontSize: 14, fontWeight: 700,
          color: '#0A2E1F',
          letterSpacing: '.04em',
          fontVariantNumeric: 'tabular-nums'
        }}>
          № {orderId}
        </div>
      )}

      <div style={{
        marginTop: 28,
        display: 'flex', gap: 12, flexWrap: 'wrap', justifyContent: 'center'
      }}>
        {primaryLabel && (
          <button onClick={primaryAction} style={{
            appearance: 'none', border: 'none', cursor: 'pointer',
            background: '#DD3310', color: '#fff',
            borderRadius: 14, padding: '15px 32px',
            fontSize: 15, fontWeight: 600,
            fontFamily: 'inherit',
            boxShadow: '0 14px 28px -10px rgba(221,51,16,.6)'
          }}>{primaryLabel}</button>
        )}
        {secondaryLabel && (
          <button onClick={secondaryAction} style={{
            appearance: 'none', border: 'none', cursor: 'pointer',
            background: '#0A2E1F', color: '#fff',
            borderRadius: 14, padding: '15px 32px',
            fontSize: 15, fontWeight: 600,
            fontFamily: 'inherit',
            boxShadow: '0 14px 28px -10px rgba(10,46,31,.5)'
          }}>{secondaryLabel}</button>
        )}
      </div>

      <div style={{
        marginTop: 28,
        fontSize: 12, color: '#7a8682',
        display: 'flex', alignItems: 'center', gap: 8
      }}>
        <span>Что-то не так?</span>
        <a href="https://t.me/friendssuphelp" target="_blank" rel="noopener noreferrer" style={{
          color: '#0A2E1F', textDecoration: 'none',
          borderBottom: '1px dashed rgba(10,46,31,.3)'
        }}>Написать в поддержку</a>
      </div>
    </div>);
}

function ResultCardMobile({ kind, title, description, orderId, primaryLabel, primaryAction, secondaryLabel, secondaryAction }) {
  return (
    <div style={{
      flex: 1,
      background: '#fff',
      borderRadius: 18,
      padding: '36px 22px 28px',
      boxShadow: '0 1px 0 rgba(0,0,0,.04), 0 16px 40px -22px rgba(10,46,31,.16)',
      display: 'flex', flexDirection: 'column', alignItems: 'center',
      textAlign: 'center'
    }}>
      <StatusIconBig kind={kind} mobile />

      <h1 style={{
        margin: '20px 0 0',
        fontFamily: '"RF Dewi Extended", "NT Somic", sans-serif',
        fontSize: 24, fontWeight: 800,
        letterSpacing: '-.01em', color: '#0A2E1F',
        lineHeight: 1.15
      }}>{title}</h1>

      <p style={{
        margin: '12px 0 0',
        fontSize: 14, lineHeight: 1.55,
        color: '#3a4a44'
      }}>{description}</p>

      {orderId && (
        <div style={{
          marginTop: 18,
          background: '#F4ECE2',
          padding: '9px 18px',
          borderRadius: 999,
          fontSize: 13, fontWeight: 700,
          color: '#0A2E1F',
          letterSpacing: '.04em',
          fontVariantNumeric: 'tabular-nums'
        }}>
          № {orderId}
        </div>
      )}

      <div style={{ flex: 1, minHeight: 16 }} />

      <div style={{
        marginTop: 20,
        width: '100%',
        display: 'flex', flexDirection: 'column', gap: 10
      }}>
        {primaryLabel && (
          <button onClick={primaryAction} style={{
            appearance: 'none', border: 'none', cursor: 'pointer',
            background: '#DD3310', color: '#fff',
            borderRadius: 14, padding: '15px 22px',
            fontSize: 15, fontWeight: 600,
            fontFamily: 'inherit', width: '100%',
            boxShadow: '0 12px 24px -10px rgba(221,51,16,.6)'
          }}>{primaryLabel}</button>
        )}
        {secondaryLabel && (
          <button onClick={secondaryAction} style={{
            appearance: 'none', border: 'none', cursor: 'pointer',
            background: '#0A2E1F', color: '#fff',
            borderRadius: 14, padding: '15px 22px',
            fontSize: 15, fontWeight: 600,
            fontFamily: 'inherit', width: '100%'
          }}>{secondaryLabel}</button>
        )}
      </div>

      <div style={{
        marginTop: 18,
        fontSize: 12, color: '#7a8682',
        display: 'flex', alignItems: 'center', gap: 6
      }}>
        <span>Что-то не так?</span>
        <a href="https://t.me/friendssuphelp" target="_blank" rel="noopener noreferrer" style={{
          color: '#0A2E1F', textDecoration: 'none',
          borderBottom: '1px dashed rgba(10,46,31,.3)'
        }}>Поддержка</a>
      </div>
    </div>);
}

function ResultView({ paymentResult, recipientMode, recipientEmail, certNumber, scheduledSendAt, onClose, onRetry }) {
  const isMobile = useIsMobile();
  const variant = (() => {
    if (paymentResult === 'paid') {
      if (recipientMode === 'friend' && scheduledSendAt) return 'success-scheduled';
      if (recipientMode === 'friend') return 'success-friend';
      return 'success-self';
    }
    if (paymentResult === 'expired') return 'expired';
    return 'failed';
  })();

  const v = (() => {
    const emailB = (e) => <b style={{ color: '#0A2E1F' }}>{e || 'указанный email'}</b>;
    if (variant === 'success-self') {
      return {
        kind: 'success', statusLabel: 'Заказ оформлен', title: 'Оплата прошла',
        description: <>Сертификат уже у вас на email {emailB(recipientEmail)}. Если не пришёл — проверьте «Спам».</>,
        primaryLabel: 'На главную', primaryAction: onClose
      };
    }
    if (variant === 'success-friend') {
      return {
        kind: 'success', statusLabel: 'Заказ оформлен', title: 'Оплата прошла',
        description: <>Сертификат уже на почте у получателя — {emailB(recipientEmail)}. Теперь можно написать ему и обрадовать подарком.</>,
        primaryLabel: 'На главную', primaryAction: onClose
      };
    }
    if (variant === 'success-scheduled') {
      const dt = new Date(scheduledSendAt);
      const formatted = dt.toLocaleString('ru-RU', {
        timeZone: 'Europe/Moscow', day: 'numeric', month: 'long',
        hour: '2-digit', minute: '2-digit'
      });
      return {
        kind: 'success', statusLabel: 'Заказ оформлен', title: 'Оплата прошла',
        description: <>Сертификат улетит получателю <b style={{ color: '#0A2E1F' }}>{formatted}</b> по&nbsp;Москве. До этого момента ничего не пишите другу — пусть будет сюрприз.</>,
        primaryLabel: 'На главную', primaryAction: onClose
      };
    }
    if (variant === 'expired') {
      return {
        kind: 'error', statusLabel: 'Время истекло', title: 'Время истекло',
        description: 'Время на оплату истекло. Можно оформить заказ заново.',
        primaryLabel: 'Попробовать снова', primaryAction: onRetry,
        secondaryLabel: 'На главную', secondaryAction: onClose
      };
    }
    return {
      kind: 'error', statusLabel: 'Оплата не прошла', title: 'Оплата не прошла',
      description: 'Платёж не прошёл. Попробуйте ещё раз — деньги не списаны.',
      primaryLabel: 'Попробовать снова', primaryAction: onRetry,
      secondaryLabel: 'На главную', secondaryAction: onClose
    };
  })();

  if (isMobile) {
    return (
      <ResultShellMobile statusLabel={v.statusLabel}>
        <ResultCardMobile
          kind={v.kind} title={v.title} description={v.description}
          orderId={certNumber}
          primaryLabel={v.primaryLabel} primaryAction={v.primaryAction}
          secondaryLabel={v.secondaryLabel} secondaryAction={v.secondaryAction} />
      </ResultShellMobile>);
  }
  return (
    <ResultShellDesktop statusLabel={v.statusLabel}>
      <ResultCardDesktop
        kind={v.kind} title={v.title} description={v.description}
        orderId={certNumber}
        primaryLabel={v.primaryLabel} primaryAction={v.primaryAction}
        secondaryLabel={v.secondaryLabel} secondaryAction={v.secondaryAction} />
    </ResultShellDesktop>);
}

// ─── Wizard root ───────────────────────────────────────────────────
function genIdemKey() {
  return (typeof crypto !== 'undefined' && crypto.randomUUID)
    ? crypto.randomUUID()
    : 'k-' + Date.now() + '-' + Math.random().toString(36).slice(2);
}

// Dev-режим: открыть страницу успеха/ошибки без реальной оплаты.
// Использование: ?preview=success-self | success-friend | success-scheduled
//                | expired | canceled | index
function PreviewView({ mode }) {
  const noop = () => { window.location.href = '/'; };
  const certNumber = 'FS-2026-PREVIEW';

  if (mode === 'success-self') {
    return <ResultView paymentResult="paid" recipientMode="self"
      recipientEmail="ivan@mail.ru" certNumber={certNumber}
      scheduledSendAt={null} onClose={noop} onRetry={noop} />;
  }
  if (mode === 'success-friend') {
    return <ResultView paymentResult="paid" recipientMode="friend"
      recipientEmail="anna@mail.ru" certNumber={certNumber}
      scheduledSendAt={null} onClose={noop} onRetry={noop} />;
  }
  if (mode === 'success-scheduled') {
    return <ResultView paymentResult="paid" recipientMode="friend"
      recipientEmail="anna@mail.ru" certNumber={certNumber}
      scheduledSendAt="2026-12-25T11:00:00+03:00" onClose={noop} onRetry={noop} />;
  }
  if (mode === 'expired') {
    return <ResultView paymentResult="expired" certNumber={certNumber}
      onRetry={noop} onClose={noop} />;
  }
  if (mode === 'canceled') {
    return <ResultView paymentResult="canceled" certNumber={certNumber}
      onRetry={noop} onClose={noop} />;
  }

  // index — список доступных режимов
  const items = [
    ['success-self',      'Оплата прошла · Себе'],
    ['success-friend',    'Оплата прошла · Другу (отправлено сразу)'],
    ['success-scheduled', 'Оплата прошла · Другу к дате (25 декабря 11:00)'],
    ['expired',           'Время оплаты истекло'],
    ['canceled',          'Оплата отменена']
  ];
  return (
    <div style={{
      position: 'fixed', inset: 0, background: '#F0EEE9',
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      padding: 24, fontFamily: '"NT Somic", system-ui, -apple-system, sans-serif'
    }}>
      <div style={{
        maxWidth: 540, width: '100%', background: '#fff',
        borderRadius: 18, padding: '32px 28px',
        boxShadow: '0 8px 28px rgba(10,46,31,.08)'
      }}>
        <h1 style={{ margin: '0 0 6px', fontSize: 22, fontWeight: 700, letterSpacing: '-.01em' }}>Превью страниц</h1>
        <p style={{ margin: '0 0 18px', fontSize: 13, color: '#5a6660', lineHeight: 1.5 }}>
          Dev-режим — посмотреть, как выглядят финальные экраны без реальной оплаты.
        </p>
        <div style={{ display: 'grid', gap: 8 }}>
          {items.map(([k, label]) => (
            <a key={k} href={`?preview=${k}`} style={{
              display: 'block', background: '#F4F1EA', borderRadius: 10,
              padding: '12px 16px', textDecoration: 'none', color: '#0A2E1F',
              fontSize: 14, fontWeight: 500
            }}>{label}<span style={{ float: 'right', opacity: .5, fontSize: 12, fontWeight: 400 }}>?preview={k}</span></a>
          ))}
        </div>
        <a href="/" style={{
          display: 'inline-block', marginTop: 18, fontSize: 13, color: '#5a6660'
        }}>← К визарду</a>
      </div>
    </div>);
}

function WizardApp() {
  // Превью-режим — раньше всех hooks, чтобы не плодить состояний для preview-вьюх.
  // Доступен только на локалке: на проде ?preview= игнорируется.
  if (typeof window !== 'undefined') {
    const host = window.location.hostname;
    const isLocal = host === 'localhost' || host === '127.0.0.1' || host === '0.0.0.0';
    if (isLocal) {
      const m = new URLSearchParams(window.location.search).get('preview');
      if (m != null) return <PreviewView mode={m} />;
    }
  }

  const [step, setStep] = React.useState(1);
  // Без PII: маршрут / число человек / режим (себе или другу) — переживают reload вкладки.
  const [routeId, setRouteId] = useSessionState('fs:routeId', null);
  const [persons, setPersons] = useSessionState('fs:persons', 1);
  const [recipientMode, setRecipientMode] = useSessionState('fs:recipientMode', 'friend');
  // Чувствительные поля живут только в памяти текущего рендера.
  const [recipientData, setRecipientData] = React.useState(null);
  const [submitted, setSubmitted] = React.useState(false);

  // Идемпотентный ключ — НЕ персистим в sessionStorage. Один загруз страницы =
  // одна попытка оплаты. Это предотвращает «застревание» на старом завершённом или
  // протухшем заказе при повторном открытии: после reload всегда новый ключ →
  // сервер создаст свежий инвойс. Дубль-клики защищены отдельным `submitted`-стейтом.
  const [idempotencyKey, setIdempotencyKey] = React.useState(() => genIdemKey());

  // Состояния оплаты
  const [paymentSession, setPaymentSession] = React.useState(null);
  // null | 'paid' | 'expired' | 'canceled'
  const [paymentResult, setPaymentResult] = React.useState(null);
  const [paymentDetails, setPaymentDetails] = React.useState(null);

  React.useEffect(() => {
    document.documentElement.scrollTop = 0;
  }, [step]);

  const handlePay = async (payer) => {
    if (submitted) return;
    setSubmitted(true);
    const r = recipientData || {};
    const payload = {
      idempotencyKey,
      routeId,
      persons,
      recipient: {
        mode: r.recipient || recipientMode || 'friend',
        name: r.name || '',
        phone: r.phone || '',
        email: r.email || '',
        wish: r.wish || '',
        channel: r.channel || null,
        whenMode: r.whenMode || 'now',
        date: r.whenMode === 'pick' ? r.date : null,
        time: r.whenMode === 'pick' ? r.time : null
      },
      payer: {
        name: payer.payerName,
        email: payer.payerEmail
      },
      promo: {
        code: payer.promo || '',
        applied: !!payer.promoApplied
      }
    };

    try {
      const res = await fetch('/api/orders', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(payload)
      });
      const data = await res.json();
      if (!res.ok) {
        setSubmitted(false);
        alert('Ошибка: ' + (data.error || res.status));
        return;
      }
      if (data.paymentUrl) {
        // Открываем iframe с Paykeeper и параллельно поллим статус заказа.
        setPaymentSession({
          orderId: data.orderId,
          certNumber: data.certNumber,
          invoiceUrl: data.paymentUrl,
          deliveryEmail: r.email || payer.payerEmail
        });
        setSubmitted(false);
      } else {
        setSubmitted(false);
        alert('Не удалось создать заказ.');
      }
    } catch (err) {
      console.error(err);
      setSubmitted(false);
      alert('Ошибка соединения.');
    }
  };

  const goHome = () => {
    clearWizardSession();
    window.location.href = '/';
  };

  const retry = () => {
    // Новый идемп-ключ → следующий клик «Оплатить» создаст новый инвойс
    setIdempotencyKey(genIdemKey());
    setPaymentResult(null);
    setPaymentDetails(null);
    setPaymentSession(null);
    setStep(3);
  };

  const isMobile = useIsMobile();
  const isSelf = recipientData && recipientData.recipient === 'self';

  // Состояния поверх визарда: успех/ошибка/iframe оплаты
  if (paymentResult) {
    return (
      <ResultView
        paymentResult={paymentResult}
        certNumber={paymentDetails && paymentDetails.certNumber}
        recipientMode={(recipientData && recipientData.recipient) || recipientMode}
        recipientEmail={paymentDetails && paymentDetails.deliveryEmail}
        scheduledSendAt={recipientData && recipientData.whenMode === 'pick' ? `${recipientData.date}T${recipientData.time}:00+03:00` : null}
        onClose={goHome}
        onRetry={retry} />);
  }
  if (paymentSession) {
    return (
      <PaymentFrame
        orderId={paymentSession.orderId}
        certNumber={paymentSession.certNumber}
        invoiceUrl={paymentSession.invoiceUrl}
        onCancel={() => setPaymentSession(null)}
        onPaid={() => {
          setPaymentDetails({
            certNumber: paymentSession.certNumber,
            deliveryEmail: paymentSession.deliveryEmail
          });
          setPaymentSession(null);
          setPaymentResult('paid');
          // Заказ закрыт — стираем idempotency-ключ и состояние из sessionStorage,
          // чтобы при reload или новой покупке стартовали с чистого листа.
          clearWizardSession();
        }}
        onFailed={(status) => {
          setPaymentDetails({ certNumber: paymentSession.certNumber });
          setPaymentSession(null);
          setPaymentResult(status);
        }} />);
  }

  // Шаги визарда
  const handleStep2Continue = (data) => {
    setRecipientData(data);
    if (data.recipient) setRecipientMode(data.recipient);
    setStep(3);
  };
  const initialRecipientForStep2 = (recipientData && recipientData.recipient) || recipientMode;

  if (step === 1) {
    if (isMobile) {
      return (
        <MobileStep1
          initialRouteId={routeId}
          initialPersons={persons}
          onContinue={({ routeId: r, persons: p }) => {
            setRouteId(r); setPersons(p); setStep(2);
          }} />);
    }
    return (
      <Step1Widget
        initialRouteId={routeId}
        initialPersons={persons}
        onContinue={({ routeId: r, persons: p }) => {
          setRouteId(r); setPersons(p); setStep(2);
        }} />);
  }
  if (step === 2) {
    if (isMobile) {
      return (
        <MobileStep2
          routeId={routeId}
          persons={persons}
          initialRecipient={initialRecipientForStep2}
          initialData={recipientData || {}}
          onBack={() => setStep(1)}
          onContinue={handleStep2Continue} />);
    }
    return (
      <Step2RecipientWidget
        routeId={routeId}
        persons={persons}
        initialRecipient={initialRecipientForStep2}
        initialData={recipientData || {}}
        onBack={() => setStep(1)}
        onContinue={handleStep2Continue} />);
  }
  if (isMobile) {
    return (
      <MobileStep3
        routeId={routeId}
        persons={persons}
        recipientName={recipientData ? recipientData.name : ''}
        deliveryMethod={recipientData ? recipientData.deliveryMethod : ''}
        whenLabel={recipientData ? recipientData.whenLabel : 'Сейчас'}
        initialPayerName={isSelf ? recipientData.name : ''}
        initialPayerEmail={isSelf ? recipientData.email : ''}
        onBack={() => setStep(2)}
        onPay={handlePay} />);
  }
  return (
    <Step3PaymentWidget
      routeId={routeId}
      persons={persons}
      recipientName={recipientData ? recipientData.name : ''}
      deliveryMethod={recipientData ? recipientData.deliveryMethod : ''}
      whenLabel={recipientData ? recipientData.whenLabel : 'Сейчас'}
      initialPayerName={isSelf ? recipientData.name : ''}
      initialPayerEmail={isSelf ? recipientData.email : ''}
      onBack={() => setStep(2)}
      onPay={handlePay} />);
}

window.WizardApp = WizardApp;
