const { useState, useRef, useEffect, useMemo } = React;

/* ============ icons ============ */
const I = {
  phone: (p) => <svg width="20" height="20" viewBox="0 0 24 24" fill="none" {...p}><path d="M6.5 3.5h3l1.2 3.2-1.8 1.4a12 12 0 005.5 5.5l1.4-1.8 3.2 1.2v3a2 2 0 01-2.2 2A16 16 0 014.5 5.7 2 2 0 016.5 3.5z" stroke="currentColor" strokeWidth="1.7" strokeLinejoin="round" /></svg>,
  mail: (p) => <svg width="20" height="20" viewBox="0 0 24 24" fill="none" {...p}><rect x="3" y="5.5" width="18" height="13" rx="2.5" stroke="currentColor" strokeWidth="1.7" /><path d="M4 7l8 6 8-6" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" /></svg>,
  arrow: (p) => <svg width="18" height="18" viewBox="0 0 24 24" fill="none" {...p}><path d="M5 12h14M13 6l6 6-6 6" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /></svg>,
  back: (p) => <svg width="17" height="17" viewBox="0 0 24 24" fill="none" {...p}><path d="M19 12H5M11 6l-6 6 6 6" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /></svg>,
  shield: (p) => <svg width="15" height="15" viewBox="0 0 24 24" fill="none" {...p}><path d="M12 3l7 2.4v5.2c0 4.3-2.9 8.1-7 9.4-4.1-1.3-7-5.1-7-9.4V5.4L12 3z" stroke="currentColor" strokeWidth="1.7" strokeLinejoin="round" /><path d="M9.2 12.2l1.9 1.9 3.8-3.9" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" /></svg>,
  globe: (p) => <svg width="15" height="15" viewBox="0 0 24 24" fill="none" {...p}><circle cx="12" cy="12" r="9" stroke="currentColor" strokeWidth="1.7" /><path d="M3 12h18M12 3c2.5 2.5 2.5 15 0 18M12 3c-2.5 2.5-2.5 15 0 18" stroke="currentColor" strokeWidth="1.5" /></svg>,
  lock: (p) => <svg width="15" height="15" viewBox="0 0 24 24" fill="none" {...p}><rect x="5" y="10.5" width="14" height="9.5" rx="2.5" stroke="currentColor" strokeWidth="1.7" /><path d="M8 10.5V8a4 4 0 018 0v2.5" stroke="currentColor" strokeWidth="1.7" /></svg>,
  clock: (p) => <svg width="16" height="16" viewBox="0 0 24 24" fill="none" {...p}><circle cx="12" cy="12" r="9" stroke="currentColor" strokeWidth="1.7" /><path d="M12 7.5V12l3 2" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" /></svg>,
  caret: (p) => <svg width="14" height="14" viewBox="0 0 24 24" fill="none" {...p}><path d="M6 9l6 6 6-6" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /></svg>,
  link: (p) => <svg width="15" height="15" viewBox="0 0 24 24" fill="none" {...p}><path d="M9 15l6-6M10.5 6.5l1-1a3.5 3.5 0 015 5l-1.6 1.6M13.5 17.5l-1 1a3.5 3.5 0 01-5-5l1.6-1.6" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" /></svg>,
  check: (p) => <svg width="15" height="15" viewBox="0 0 24 24" fill="none" {...p}><path d="M5 12.5l4.2 4.2L19 7" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round" /></svg>,
  plus: (p) => <svg width="16" height="16" viewBox="0 0 24 24" fill="none" {...p}><path d="M12 5v14M5 12h14" stroke="currentColor" strokeWidth="2" strokeLinecap="round" /></svg>,
  logout: (p) => <svg width="16" height="16" viewBox="0 0 24 24" fill="none" {...p}><path d="M14 8V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2h6a2 2 0 002-2v-2M9 12h11M17 8l4 4-4 4" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" /></svg>,
  err: (p) => <svg width="14" height="14" viewBox="0 0 24 24" fill="none" {...p}><circle cx="12" cy="12" r="9" stroke="currentColor" strokeWidth="1.8" /><path d="M12 7.5v5M12 16h.01" stroke="currentColor" strokeWidth="2" strokeLinecap="round" /></svg>
};

/* ============ logo mark ============ */
const Mark = () =>
<svg width="26" height="26" viewBox="0 0 32 32" fill="none">
    <circle cx="16" cy="16" r="13" stroke="#fff" strokeWidth="1.8" opacity=".55" />
    <path d="M6 18c4-3 6 3 10 0s6 3 10 0" stroke="#4fc3f7" strokeWidth="2.2" strokeLinecap="round" />
    <path d="M16 5c3.5 3 3.5 18 0 22" stroke="#fff" strokeWidth="1.5" opacity=".5" />
    <path d="M19 7l5 1-1 5" stroke="#fff" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" />
  </svg>;


/* ============ flags ============ */
const F = {
  UA: () => <svg className="flag" viewBox="0 0 22 15"><rect width="22" height="7.5" fill="#0057B7" /><rect y="7.5" width="22" height="7.5" fill="#FFD700" /></svg>,
  EN: () => <svg className="flag" viewBox="0 0 22 15"><rect width="22" height="15" fill="#fff" />{[1, 3, 5, 7, 9, 11, 13].map((y) => <rect key={y} y={y * 15 / 13} width="22" height={15 / 13} fill="#B22234" />)}<rect width="10" height={15 * 7 / 13} fill="#3C3B6E" /></svg>,
  PL: () => <svg className="flag" viewBox="0 0 22 15"><rect width="22" height="7.5" fill="#fff" /><rect y="7.5" width="22" height="7.5" fill="#DC143C" /></svg>,
  GR: () => <svg className="flag" viewBox="0 0 22 15"><rect width="22" height="15" fill="#fff" /><rect x="9.2" width="3.6" height="15" fill="#E8112D" /><rect y="5.7" width="22" height="3.6" fill="#E8112D" /></svg>,
  RU: () => <svg className="flag" viewBox="0 0 22 15"><rect width="22" height="5" fill="#fff" /><rect y="5" width="22" height="5" fill="#0039A6" /><rect y="10" width="22" height="5" fill="#D52B1E" /></svg>,
  UZ: () => <svg className="flag" viewBox="0 0 22 15"><rect width="22" height="5" fill="#1EB53A" /><rect y="5" width="22" height="5" fill="#fff" /><rect y="10" width="22" height="5" fill="#0099B5" /></svg>
};
const LANGS = [
{ code: 'UA', name: 'Українська' }, { code: 'EN', name: 'English' },
{ code: 'PL', name: 'Polski' }, { code: 'GR', name: 'ქართული' },
{ code: 'RU', name: 'Русский' }, { code: 'UZ', name: "O'zbek" }];


/* ============ brand panel ============ */
/* тексти беруться зі словника за поточною мовою */
const stepCopy = (t) => [
{ kick: t.b0k, h: t.b0h, p: t.b0p },
{ kick: t.b1k, h: t.b1h, p: t.b1p },
{ kick: t.b2k, h: t.b2h, p: t.b2p }];


function Brand({ step, lang }) {
  const t = tr(lang);
  const c = stepCopy(t)[step];
  return (
    <aside className="brand" style={{ fontFamily: "Poppins" }}>
      <div className="logo">
        <span className="mark"><Mark /></span>
        <span className="wm"><div className="n">DNIPRO</div><div className="s">LLC</div></span>
      </div>

      <div className="steps">
        {[t.stepLogin, t.stepCode, t.stepCab].map((l, i) =>
        <React.Fragment key={l}>
            <div className={'st ' + (i === step ? 'active' : i < step ? 'done' : '')}>
              <span className="dot">{i < step ? I.check() : i + 1}</span>
              <span className="lbl">{l}</span>
            </div>
            {i < 2 && <span className="bar"></span>}
          </React.Fragment>
        )}
      </div>

      <div className="bhead">
        <div className="anim-key" key={step}>
          <div className="kick">{c.kick}</div>
          <h2>{c.h}</h2>
          <p>{c.p}</p>
        </div>

        <div className="route">
          <svg viewBox="0 0 440 92" preserveAspectRatio="xMidYMid meet">
            <defs>
              <linearGradient id="rg" x1="0" y1="0" x2="1" y2="0">
                <stop offset="0" stopColor="#4fc3f7" stopOpacity="0" />
                <stop offset="0.5" stopColor="#4fc3f7" />
                <stop offset="1" stopColor="#3b82f6" stopOpacity="0" />
              </linearGradient>
            </defs>
            <path className="path" d="M 8 70 C 90 12, 150 118, 250 52 S 380 30, 432 62" />
            <path className="glow" d="M 8 70 C 90 12, 150 118, 250 52 S 380 30, 432 62" />
            <circle className="pin" cx="8" cy="70" r="4.5" />
            <circle className="pin b" cx="432" cy="62" r="5.5" />
            <circle cx="432" cy="62" r="9" fill="none" stroke="#4fc3f7" strokeWidth="1.5" opacity=".5" />
          </svg>
        </div>

        <div className="chips">
          <span className="chip">{I.shield()} {t.chipCustoms}</span>
          <span className="chip">{I.globe()} {t.chipLangs}</span>
          <span className="chip">{I.lock()} {t.chipSecure}</span>
        </div>
      </div>
    </aside>);

}


/* ============ i18n ============
   langvich із URL (?langvich=UK) перемикає інтерфейс і йде в
   getAuthConnectByApplication. Коди UI: UA/EN/PL/GR/RU/UZ,
   на сервер надсилається UK замість UA. */
const T = {
  UA: {
    stepLogin: 'Вхід', stepCode: 'Код', stepCab: 'Кабінет',
    b0k: 'Особистий кабінет', b0h: 'Вітаємо у DNIPRO', b0p: 'Один акаунт для всіх ваших міжнародних відправлень — швидко, прозоро й під надійним захистом.',
    b1k: 'Безпека входу', b1h: 'Майже готово', b1p: 'Введіть код підтвердження, який ми щойно надіслали на ваш контакт.',
    b2k: 'Ваші профілі', b2h: 'З поверненням!', b2p: 'Оберіть кабінет, щоб продовжити роботу з відправленнями.',
    chipCustoms: 'Митне оформлення', chipLangs: '6 мов інтерфейсу', chipSecure: 'Захищений вхід',
    eyebrow: 'Вхід або реєстрація', hello: 'Раді вас бачити',
    ledeBoth: 'Впишіть номер телефону або пошту — ми надішлемо одноразовий PIN-код для входу.',
    ledePhone: 'Впишіть номер телефону — ми надішлемо одноразовий PIN-код у SMS.',
    ledeMail: 'Впишіть пошту — ми надішлемо одноразовий PIN-код листом.',
    labelBoth: 'Телефон або e-mail', labelPhone: 'Номер телефону', prefixReq: 'Номер має починатися з', labelMail: 'E-mail',
    phMail: 'mail@mail.com', kindPhone: 'Телефон', kindMail: 'Пошта',
    agree1: 'Я приймаю', agreeLink: 'ліцензійну угоду користувача', agree2: 'та політику конфіденційності.',
    send: 'Надіслати PIN-код', sending: 'Надсилаємо…',
    altBoth: 'Код надійде у SMS або на пошту', altPhone: 'Код надійде у SMS', altMail: 'Код надійде на пошту',
    back: 'Назад', confirm: 'Підтвердження', pinTitle: 'Введіть код',
    pinLede: 'Ми надіслали 6-значний код на', pinAll: 'Введіть усі 6 цифр коду',
    yourContact: 'ваш контакт', enterBelow: 'Введіть його нижче.',
    valid: 'Код дійсний ще', expired: 'Термін дії коду минув', resend: 'Надіслати знову',
    verify: 'Підтвердити', verifying: 'Перевіряємо…', attemptsLeft: 'залишилось спроб',
    noCode: 'Не отримали код? Дочекайтесь таймера й надішліть знову', resent: 'Код надіслано повторно',
    profiles: 'Профілі', pickCab: 'Оберіть кабінет', noClientId: 'Кабінет без ідентифікатора — зверніться до підтримки.', makeCab: 'Створімо кабінет',
    pickLede: 'Підключіться до одного з ваших профілів, щоб продовжити.',
    makeLede: 'Номер підтверджено, але профілю ще немає. Створимо його зараз.',
    connect: 'Підключити', connecting: 'Підключення', connected: 'Підключено',
    addCab: 'Додати новий кабінет', createCab: 'Створити кабінет', creating: 'Створюємо…',
    logout: 'Вийти з аплікації', signedIn: 'Вхід виконано', cabCreated: 'Кабінет створено', sessionEnded: 'Сесію завершено',
    checking: 'Перевіряємо сесію…', setup: 'Налаштування', noEntry: 'Вхід недоступний',
    license: 'Ліцензійна угода', apiDown: 'API: недоступне',
  },
  EN: {
    stepLogin: 'Sign in', stepCode: 'Code', stepCab: 'Cabinet',
    b0k: 'Personal cabinet', b0h: 'Welcome to DNIPRO', b0p: 'One account for all your international shipments — fast, transparent and securely protected.',
    b1k: 'Sign-in security', b1h: 'Almost there', b1p: 'Enter the confirmation code we have just sent to your contact.',
    b2k: 'Your profiles', b2h: 'Welcome back!', b2p: 'Choose a cabinet to continue working with your shipments.',
    chipCustoms: 'Customs clearance', chipLangs: '6 interface languages', chipSecure: 'Secure sign-in',
    eyebrow: 'Sign in or register', hello: 'Good to see you',
    ledeBoth: 'Enter your phone number or email — we will send a one-time PIN code.',
    ledePhone: 'Enter your phone number — we will send a one-time PIN code by SMS.',
    ledeMail: 'Enter your email — we will send a one-time PIN code by mail.',
    labelBoth: 'Phone or email', labelPhone: 'Phone number', prefixReq: 'The number must start with', labelMail: 'Email',
    phMail: 'mail@mail.com', kindPhone: 'Phone', kindMail: 'Email',
    agree1: 'I accept the', agreeLink: 'end-user licence agreement', agree2: 'and the privacy policy.',
    send: 'Send PIN code', sending: 'Sending…',
    altBoth: 'The code will arrive by SMS or email', altPhone: 'The code will arrive by SMS', altMail: 'The code will arrive by email',
    back: 'Back', confirm: 'Confirmation', pinTitle: 'Enter the code',
    pinLede: 'We sent a 6-digit code to', pinAll: 'Enter all 6 digits',
    yourContact: 'your contact', enterBelow: 'Enter it below.',
    valid: 'Code valid for', expired: 'The code has expired', resend: 'Send again',
    verify: 'Confirm', verifying: 'Verifying…', attemptsLeft: 'attempts left',
    noCode: 'No code? Wait for the timer and send again', resent: 'Code sent again',
    profiles: 'Profiles', pickCab: 'Choose a cabinet', noClientId: 'Cabinet has no identifier — contact support.', makeCab: 'Let us create a cabinet',
    pickLede: 'Connect to one of your profiles to continue.',
    makeLede: 'Your number is confirmed, but there is no profile yet. Let us create one.',
    connect: 'Connect', connecting: 'Connecting', connected: 'Connected',
    addCab: 'Add a new cabinet', createCab: 'Create a cabinet', creating: 'Creating…',
    logout: 'Sign out', signedIn: 'Signed in', cabCreated: 'Cabinet created', sessionEnded: 'Session ended',
    checking: 'Checking session…', setup: 'Configuration', noEntry: 'Sign-in unavailable',
    license: 'Licence agreement', apiDown: 'API: unavailable',
  },
  PL: {
    stepLogin: 'Logowanie', stepCode: 'Kod', stepCab: 'Gabinet',
    b0k: 'Gabinet osobisty', b0h: 'Witamy w DNIPRO', b0p: 'Jedno konto dla wszystkich przesyłek międzynarodowych — szybko, przejrzyście i bezpiecznie.',
    b1k: 'Bezpieczeństwo logowania', b1h: 'Już prawie', b1p: 'Wpisz kod potwierdzający, który właśnie wysłaliśmy.',
    b2k: 'Twoje profile', b2h: 'Witamy ponownie!', b2p: 'Wybierz gabinet, aby kontynuować pracę z przesyłkami.',
    chipCustoms: 'Odprawa celna', chipLangs: '6 języków interfejsu', chipSecure: 'Bezpieczne logowanie',
    eyebrow: 'Logowanie lub rejestracja', hello: 'Miło Cię widzieć',
    ledeBoth: 'Wpisz numer telefonu lub e-mail — wyślemy jednorazowy kod PIN.',
    ledePhone: 'Wpisz numer telefonu — wyślemy jednorazowy kod PIN w SMS.',
    ledeMail: 'Wpisz e-mail — wyślemy jednorazowy kod PIN.',
    labelBoth: 'Telefon lub e-mail', labelPhone: 'Numer telefonu', prefixReq: 'Numer musi zaczynać się od', labelMail: 'E-mail',
    phMail: 'mail@mail.com', kindPhone: 'Telefon', kindMail: 'E-mail',
    agree1: 'Akceptuję', agreeLink: 'umowę licencyjną użytkownika', agree2: 'oraz politykę prywatności.',
    send: 'Wyślij kod PIN', sending: 'Wysyłamy…',
    altBoth: 'Kod przyjdzie SMS-em lub e-mailem', altPhone: 'Kod przyjdzie SMS-em', altMail: 'Kod przyjdzie e-mailem',
    back: 'Wstecz', confirm: 'Potwierdzenie', pinTitle: 'Wpisz kod',
    pinLede: 'Wysłaliśmy 6-cyfrowy kod na', pinAll: 'Wpisz wszystkie 6 cyfr',
    yourContact: 'Twój kontakt', enterBelow: 'Wpisz go poniżej.',
    valid: 'Kod ważny jeszcze', expired: 'Kod wygasł', resend: 'Wyślij ponownie',
    verify: 'Potwierdź', verifying: 'Sprawdzamy…', attemptsLeft: 'pozostało prób',
    noCode: 'Brak kodu? Poczekaj na licznik i wyślij ponownie', resent: 'Kod wysłany ponownie',
    profiles: 'Profile', pickCab: 'Wybierz gabinet', noClientId: 'Gabinet bez identyfikatora — skontaktuj się z pomocą.', makeCab: 'Utwórzmy gabinet',
    pickLede: 'Połącz się z jednym ze swoich profili, aby kontynuować.',
    makeLede: 'Numer potwierdzony, ale nie ma jeszcze profilu. Utwórzmy go teraz.',
    connect: 'Połącz', connecting: 'Łączenie', connected: 'Połączono',
    addCab: 'Dodaj nowy gabinet', createCab: 'Utwórz gabinet', creating: 'Tworzymy…',
    logout: 'Wyloguj się', signedIn: 'Zalogowano', cabCreated: 'Gabinet utworzony', sessionEnded: 'Sesja zakończona',
    checking: 'Sprawdzamy sesję…', setup: 'Konfiguracja', noEntry: 'Logowanie niedostępne',
    license: 'Umowa licencyjna', apiDown: 'API: niedostępne',
  },
  GR: {
    stepLogin: 'შესვლა', stepCode: 'კოდი', stepCab: 'კაბინეტი',
    b0k: 'პირადი კაბინეტი', b0h: 'კეთილი იყოს თქვენი მობრძანება DNIPRO-ში', b0p: 'ერთი ანგარიში ყველა საერთაშორისო გზავნილისთვის — სწრაფად, გამჭვირვალედ და დაცულად.',
    b1k: 'შესვლის უსაფრთხოება', b1h: 'თითქმის მზადაა', b1p: 'შეიყვანეთ დადასტურების კოდი, რომელიც ახლახან გამოგიგზავნეთ.',
    b2k: 'თქვენი პროფილები', b2h: 'კვლავ მოგესალმებით!', b2p: 'აირჩიეთ კაბინეტი გზავნილებთან სამუშაოდ.',
    chipCustoms: 'საბაჟო გაფორმება', chipLangs: 'ინტერფეისის 6 ენა', chipSecure: 'დაცული შესვლა',
    eyebrow: 'შესვლა ან რეგისტრაცია', hello: 'მოხარული ვართ',
    ledeBoth: 'შეიყვანეთ ტელეფონი ან ელფოსტა — გამოგიგზავნით ერთჯერად PIN-კოდს.',
    ledePhone: 'შეიყვანეთ ტელეფონი — გამოგიგზავნით PIN-კოდს SMS-ით.',
    ledeMail: 'შეიყვანეთ ელფოსტა — გამოგიგზავნით PIN-კოდს.',
    labelBoth: 'ტელეფონი ან ელფოსტა', labelPhone: 'ტელეფონის ნომერი', prefixReq: 'ნომერი უნდა იწყებოდეს', labelMail: 'ელფოსტა',
    phMail: 'mail@mail.com', kindPhone: 'ტელეფონი', kindMail: 'ელფოსტა',
    agree1: 'ვეთანხმები', agreeLink: 'მომხმარებლის სალიცენზიო შეთანხმებას', agree2: 'და კონფიდენციალურობის პოლიტიკას.',
    send: 'PIN-კოდის გაგზავნა', sending: 'იგზავნება…',
    altBoth: 'კოდი მოვა SMS-ით ან ელფოსტით', altPhone: 'კოდი მოვა SMS-ით', altMail: 'კოდი მოვა ელფოსტით',
    back: 'უკან', confirm: 'დადასტურება', pinTitle: 'შეიყვანეთ კოდი',
    pinLede: 'გამოგიგზავნეთ 6-ნიშნა კოდი', pinAll: 'შეიყვანეთ ექვსივე ციფრი',
    yourContact: 'თქვენი კონტაქტი', enterBelow: 'შეიყვანეთ ქვემოთ.',
    valid: 'კოდი მოქმედია კიდევ', expired: 'კოდის ვადა ამოიწურა', resend: 'ხელახლა გაგზავნა',
    verify: 'დადასტურება', verifying: 'მოწმდება…', attemptsLeft: 'დარჩა მცდელობა',
    noCode: 'კოდი ვერ მიიღეთ? დაელოდეთ ტაიმერს და გააგზავნეთ ხელახლა', resent: 'კოდი ხელახლა გაიგზავნა',
    profiles: 'პროფილები', pickCab: 'აირჩიეთ კაბინეტი', noClientId: 'კაბინეტს იდენტიფიკატორი არ აქვს — მიმართეთ მხარდაჭერას.', makeCab: 'შევქმნათ კაბინეტი',
    pickLede: 'გასაგრძელებლად დაუკავშირდით ერთ-ერთ პროფილს.',
    makeLede: 'ნომერი დადასტურებულია, მაგრამ პროფილი ჯერ არ არის. შევქმნათ ახლა.',
    connect: 'დაკავშირება', connecting: 'მიმდინარეობს', connected: 'დაკავშირებულია',
    addCab: 'ახალი კაბინეტის დამატება', createCab: 'კაბინეტის შექმნა', creating: 'იქმნება…',
    logout: 'გასვლა', signedIn: 'შესვლა შესრულდა', cabCreated: 'კაბინეტი შეიქმნა', sessionEnded: 'სესია დასრულდა',
    checking: 'სესია მოწმდება…', setup: 'კონფიგურაცია', noEntry: 'შესვლა მიუწვდომელია',
    license: 'სალიცენზიო შეთანხმება', apiDown: 'API: მიუწვდომელია',
  },
  RU: {
    stepLogin: 'Вход', stepCode: 'Код', stepCab: 'Кабинет',
    b0k: 'Личный кабинет', b0h: 'Добро пожаловать в DNIPRO', b0p: 'Один аккаунт для всех ваших международных отправлений — быстро, прозрачно и под надёжной защитой.',
    b1k: 'Безопасность входа', b1h: 'Почти готово', b1p: 'Введите код подтверждения, который мы только что отправили.',
    b2k: 'Ваши профили', b2h: 'С возвращением!', b2p: 'Выберите кабинет, чтобы продолжить работу с отправлениями.',
    chipCustoms: 'Таможенное оформление', chipLangs: '6 языков интерфейса', chipSecure: 'Защищённый вход',
    eyebrow: 'Вход или регистрация', hello: 'Рады вас видеть',
    ledeBoth: 'Введите номер телефона или почту — мы отправим одноразовый PIN-код.',
    ledePhone: 'Введите номер телефона — мы отправим одноразовый PIN-код в SMS.',
    ledeMail: 'Введите почту — мы отправим одноразовый PIN-код письмом.',
    labelBoth: 'Телефон или e-mail', labelPhone: 'Номер телефона', prefixReq: 'Номер должен начинаться с', labelMail: 'E-mail',
    phMail: 'mail@mail.com', kindPhone: 'Телефон', kindMail: 'Почта',
    agree1: 'Я принимаю', agreeLink: 'лицензионное соглашение пользователя', agree2: 'и политику конфиденциальности.',
    send: 'Отправить PIN-код', sending: 'Отправляем…',
    altBoth: 'Код придёт в SMS или на почту', altPhone: 'Код придёт в SMS', altMail: 'Код придёт на почту',
    back: 'Назад', confirm: 'Подтверждение', pinTitle: 'Введите код',
    pinLede: 'Мы отправили 6-значный код на', pinAll: 'Введите все 6 цифр кода',
    yourContact: 'ваш контакт', enterBelow: 'Введите его ниже.',
    valid: 'Код действителен ещё', expired: 'Срок действия кода истёк', resend: 'Отправить снова',
    verify: 'Подтвердить', verifying: 'Проверяем…', attemptsLeft: 'осталось попыток',
    noCode: 'Не получили код? Дождитесь таймера и отправьте снова', resent: 'Код отправлен повторно',
    profiles: 'Профили', pickCab: 'Выберите кабинет', noClientId: 'Кабинет без идентификатора — обратитесь в поддержку.', makeCab: 'Создадим кабинет',
    pickLede: 'Подключитесь к одному из ваших профилей, чтобы продолжить.',
    makeLede: 'Номер подтверждён, но профиля ещё нет. Создадим его сейчас.',
    connect: 'Подключить', connecting: 'Подключение', connected: 'Подключено',
    addCab: 'Добавить новый кабинет', createCab: 'Создать кабинет', creating: 'Создаём…',
    logout: 'Выйти из приложения', signedIn: 'Вход выполнен', cabCreated: 'Кабинет создан', sessionEnded: 'Сессия завершена',
    checking: 'Проверяем сессию…', setup: 'Настройки', noEntry: 'Вход недоступен',
    license: 'Лицензионное соглашение', apiDown: 'API: недоступно',
  },
  UZ: {
    stepLogin: 'Kirish', stepCode: 'Kod', stepCab: 'Kabinet',
    b0k: 'Shaxsiy kabinet', b0h: 'DNIPRO ga xush kelibsiz', b0p: 'Barcha xalqaro joʻnatmalaringiz uchun bitta hisob — tez, shaffof va ishonchli himoyada.',
    b1k: 'Kirish xavfsizligi', b1h: 'Deyarli tayyor', b1p: 'Hozirgina yuborilgan tasdiqlash kodini kiriting.',
    b2k: 'Sizning profillaringiz', b2h: 'Qaytganingiz bilan!', b2p: 'Joʻnatmalar bilan ishlashni davom ettirish uchun kabinetni tanlang.',
    chipCustoms: 'Bojxona rasmiylashtiruvi', chipLangs: 'Interfeysning 6 tili', chipSecure: 'Himoyalangan kirish',
    eyebrow: 'Kirish yoki roʻyxatdan oʻtish', hello: 'Sizni koʻrganimizdan xursandmiz',
    ledeBoth: 'Telefon raqami yoki pochtani kiriting — bir martalik PIN-kod yuboramiz.',
    ledePhone: 'Telefon raqamini kiriting — SMS orqali PIN-kod yuboramiz.',
    ledeMail: 'Pochtani kiriting — xat orqali PIN-kod yuboramiz.',
    labelBoth: 'Telefon yoki e-pochta', labelPhone: 'Telefon raqami', prefixReq: 'Raqam quyidagi bilan boshlanishi kerak:', labelMail: 'E-pochta',
    phMail: 'mail@mail.com', kindPhone: 'Telefon', kindMail: 'Pochta',
    agree1: 'Men qabul qilaman', agreeLink: 'foydalanuvchi litsenziya shartnomasini', agree2: 'va maxfiylik siyosatini.',
    send: 'PIN-kod yuborish', sending: 'Yuborilmoqda…',
    altBoth: 'Kod SMS yoki pochta orqali keladi', altPhone: 'Kod SMS orqali keladi', altMail: 'Kod pochta orqali keladi',
    back: 'Orqaga', confirm: 'Tasdiqlash', pinTitle: 'Kodni kiriting',
    pinLede: '6 xonali kodni yubordik', pinAll: 'Barcha 6 raqamni kiriting',
    yourContact: 'kontaktingiz', enterBelow: 'Uni quyida kiriting.',
    valid: 'Kod amal qiladi', expired: 'Kod muddati tugadi', resend: 'Qayta yuborish',
    verify: 'Tasdiqlash', verifying: 'Tekshirilmoqda…', attemptsLeft: 'urinish qoldi',
    noCode: 'Kod kelmadimi? Taymerni kuting va qayta yuboring', resent: 'Kod qayta yuborildi',
    profiles: 'Profillar', pickCab: 'Kabinetni tanlang', noClientId: 'Kabinetda identifikator yoʻq — qoʻllab-quvvatlashga murojaat qiling.', makeCab: 'Kabinet yarataylik',
    pickLede: 'Davom etish uchun profillaringizdan biriga ulaning.',
    makeLede: 'Raqam tasdiqlandi, ammo profil hali yoʻq. Hozir yarataylik.',
    connect: 'Ulash', connecting: 'Ulanmoqda', connected: 'Ulandi',
    addCab: 'Yangi kabinet qoʻshish', createCab: 'Kabinet yaratish', creating: 'Yaratilmoqda…',
    logout: 'Chiqish', signedIn: 'Kirish bajarildi', cabCreated: 'Kabinet yaratildi', sessionEnded: 'Sessiya tugadi',
    checking: 'Sessiya tekshirilmoqda…', setup: 'Sozlamalar', noEntry: 'Kirish mavjud emas',
    license: 'Litsenziya shartnomasi', apiDown: 'API: mavjud emas',
  },
};
const tr = (lang) => T[lang] || T.UA;
/* UI-код → langvich сервера (UA → UK) */
const LANG_TO_SRV = { UA: 'UK', EN: 'EN', PL: 'PL', GR: 'GR', RU: 'RU', UZ: 'UZ' };
const SRV_TO_LANG = { UK: 'UA', UA: 'UA', EN: 'EN', PL: 'PL', GR: 'GR', KA: 'GR', RU: 'RU', UZ: 'UZ' };

/* ============ language switcher ============ */
function Lang({ lang, setLang }) {
  const [open, setOpen] = useState(false);
  const ref = useRef(null);
  useEffect(() => {
    const h = (e) => {if (ref.current && !ref.current.contains(e.target)) setOpen(false);};
    document.addEventListener('mousedown', h);
    return () => document.removeEventListener('mousedown', h);
  }, []);
  const Cur = F[lang];
  return (
    <div className={'lang' + (open ? ' open' : '')} ref={ref}>
      <button className="btn" onClick={() => setOpen((o) => !o)}>
        <Cur /> <span>{lang}</span> <span className="caret">{I.caret()}</span>
      </button>
      {open &&
      <div className="menu">
          {LANGS.map((l) => {
          const Fl = F[l.code];
          return (
            <button key={l.code} className={'item' + (l.code === lang ? ' sel' : '')} onClick={() => {setLang(l.code);setOpen(false);}}>
                <Fl /> <span>{l.name}</span>
              </button>);

        })}
        </div>
      }
    </div>);

}

/* ============ login ============ */
function Login({ contact, setContact, agreed, setAgreed, onNext, busy, error, connect, lang }) {
  const api = window.DniproAuth;
  const t = tr(lang);
  const det = api.detectLoginType(contact);
  const allowed = api.allowedLoginTypes();
  const onlyPhone = allowed.length === 1 && allowed[0] === 'PHONE';
  const onlyMail = allowed.length === 1 && allowed[0] === 'EMAIL';

  const isMail = det.type === 'EMAIL' || (!onlyPhone && contact.includes('@'));
  const kind = contact.trim() ? det.type === 'EMAIL' ? t.kindMail : det.type === 'PHONE' ? t.kindPhone : null : null;
  const ready = !!det.type && agreed && !busy;

  /* Підписи залежать від method_login застосунку */
  const label = onlyPhone ? t.labelPhone : onlyMail ? t.labelMail : t.labelBoth;
  const prefix = connect && connect.phone_prefix ? connect.phone_prefix : '+380';
  const ph = onlyPhone ? `${prefix} 50 000 00 00` :
  onlyMail ? t.phMail :
  `${prefix} 50 000 00 00  ·  ${t.phMail}`;
  const lede = onlyPhone ? t.ledePhone : onlyMail ? t.ledeMail : t.ledeBoth;
  const alt = onlyPhone ? t.altPhone : onlyMail ? t.altMail : t.altBoth;

  return (
    <div className="form anim-key" key="login">
      <div className="eyebrow">{t.eyebrow}</div>
      <h1>{t.hello}</h1>
      <p className="lede">{lede}</p>

      <div className="fld">
        <label>{label}</label>
        <div className="inputwrap">
          <span className="lead">{isMail ? I.mail() : I.phone()}</span>
          <input
            value={contact}
            onChange={(e) => setContact(e.target.value)}
            placeholder={ph}
            inputMode={onlyPhone ? 'tel' : 'text'}
            onKeyDown={(e) => {if (e.key === 'Enter' && ready) onNext();}}
            disabled={busy}
            autoFocus />
          
          {kind && <span className="kindchip">{kind}</span>}
        </div>
      </div>

      {!error && det.code === 'PHONE_PREFIX_REQUIRED' &&
      <div className="errnote">{I.err()} {t.prefixReq} {connect.phone_prefix}</div>
      }
      {error && <div className="errnote">{I.err()} {error}</div>}

      <div className="agree">
        <button type="button" className={'switch' + (agreed ? ' on' : '')} onClick={() => setAgreed((a) => !a)} aria-pressed={agreed}></button>
        <span>{t.agree1} <a href="https://privacycenter.dniprollc.com/license-agreement/index.html" target="_blank" rel="noopener noreferrer">{t.agreeLink}</a> {t.agree2}</span>
      </div>

      <button className="cta" disabled={!ready} onClick={onNext}>
        {busy ? <><span className="spin"></span> {t.sending}</> : <>{t.send} <span className="arrow">{I.arrow()}</span></>}
      </button>

      <div className="altline">{alt}</div>
    </div>);

}

/* ============ pin ============ */
function Pin({ contact, onBack, onNext, toast, api, lang }) {
  const t = tr(lang);
  const PIN_TTL = window.DniproAuth.config.pinTtlSec;
  const MAX_ATTEMPTS = window.DniproAuth.config.maxAttempts;
  const [otp, setOtp] = useState(Array(6).fill(''));
  const [err, setErr] = useState(false);
  const [msg, setMsg] = useState(null);
  const [busy, setBusy] = useState(false);
  const [attempts, setAttempts] = useState(0);
  const [locked, setLocked] = useState(false);
  const [secs, setSecs] = useState(PIN_TTL);
  const refs = useRef([]);

  useEffect(() => {
    if (secs <= 0) return;
    const t = setTimeout(() => setSecs((s) => s - 1), 1000);
    return () => clearTimeout(t);
  }, [secs]);

  const set = (i, v) => {
    v = v.replace(/\D/g, '');
    setErr(false);
    if (!v) {const n = [...otp];n[i] = '';setOtp(n);return;}
    const n = [...otp];
    if (v.length > 1) {// paste
      v.slice(0, 6).split('').forEach((ch, k) => {if (i + k < 6) n[i + k] = ch;});
      setOtp(n);
      const last = Math.min(i + v.length, 5);
      refs.current[last]?.focus();
      return;
    }
    n[i] = v;setOtp(n);
    if (i < 5) refs.current[i + 1]?.focus();
  };
  const key = (i, e) => {
    if (e.key === 'Backspace' && !otp[i] && i > 0) refs.current[i - 1]?.focus();
    if (e.key === 'ArrowLeft' && i > 0) refs.current[i - 1]?.focus();
    if (e.key === 'ArrowRight' && i < 5) refs.current[i + 1]?.focus();
  };
  const code = otp.join('');
  const full = code.length === 6;

  const verify = async () => {
    if (!full || busy || locked) {if (!full) {setErr(true);setMsg(t.pinAll);}return;}
    setBusy(true);setErr(false);setMsg(null);
    try {
      const res = await api.validate(code);
      onNext(res);
    } catch (e) {
      setErr(true);
      setMsg(e.message);
      if (e.isAttemptsExceeded) {
        setLocked(true);
      } else if (e.isInvalidPin) {
        setAttempts((a) => {
          const n = a + 1;
          if (n >= MAX_ATTEMPTS) setLocked(true);
          return n;
        });
        setOtp(Array(6).fill(''));
        refs.current[0]?.focus();
      } else if (e.isPinExpired) {
        setSecs(0);
      } else if (e.isSessionDead) {
        toast(e.message);
        onBack();
      }
    } finally {
      setBusy(false);
    }
  };

  const resend = async () => {
    if (secs > 0 && !locked || busy) return;
    setBusy(true);setErr(false);setMsg(null);
    try {
      await api.resendPin();
      setSecs(PIN_TTL);setOtp(Array(6).fill(''));
      setAttempts(0);setLocked(false);
      refs.current[0]?.focus();
      toast(t.resent);
    } catch (e) {
      setErr(true);setMsg(e.message);
      if (e.isSessionDead) {toast(e.message);onBack();}
    } finally {
      setBusy(false);
    }
  };

  const left = MAX_ATTEMPTS - attempts;
  const mm = Math.floor(secs / 60),ss = String(secs % 60).padStart(2, '0');

  return (
    <div className="form anim-key" key="pin">
      <button className="back" onClick={onBack}>{I.back()} {t.back}</button>
      <div className="eyebrow">{t.confirm}</div>
      <h1>{t.pinTitle}</h1>
      <p className="lede">{t.pinLede} <b>{contact || t.yourContact}</b>. {t.enterBelow}</p>

      <div className={'otp' + (err ? ' err' : '')}>
        {otp.map((d, i) =>
        <input key={i} ref={(el) => refs.current[i] = el}
        className={d ? 'filled' : ''}
        inputMode="numeric" maxLength={6} value={d}
        onChange={(e) => set(i, e.target.value)}
        onKeyDown={(e) => key(i, e)}
        disabled={busy || locked || secs === 0}
        autoFocus={i === 0} />
        )}
      </div>

      {msg && <div className="errnote">{I.err()} {msg}{!locked && attempts > 0 && left > 0 ? ` · ${t.attemptsLeft}: ${left}` : ''}</div>}

      <div className="timer">
        <span className={'clk' + (secs < 15 ? ' warn' : '')}>{I.clock()} {secs > 0 ? `${t.valid} ${mm}:${ss}` : t.expired}</span>
        <button className="linkbtn" disabled={secs > 0 && !locked || busy} onClick={resend}>{t.resend}</button>
      </div>

      <button className="cta" disabled={!full || busy || locked || secs === 0} onClick={verify}>
        {busy ? <><span className="spin"></span> {t.verifying}</> : <>{t.verify} <span className="arrow">{I.arrow()}</span></>}
      </button>
      <div className="altline">{t.noCode}</div>
    </div>);

}

/* ============ cabinets ============ */
const initials = (n) => n.split(' ').filter((w) => /[А-Яа-яA-Za-z]/.test(w[0])).slice(0, 2).map((w) => w[0]).join('').toUpperCase();

function Cabinets({ cabinets, onLogout, onDone, toast, api, locale, lang, connect }) {
  const t = tr(lang);
  const [connecting, setConnecting] = useState(null);
  const [connected, setConnected] = useState(null);
  const [registering, setRegistering] = useState(false);
  const [msg, setMsg] = useState(null);

  /* Реєстрація контрагента дозволена лише при connect = 'CONTRAGENT' */
  const canRegister = !connect || connect.connect === 'CONTRAGENT';

  const go = async (c) => {
    if (connected || connecting || registering) return;
    if (!c.clientidref) {
      /* Ідентифікатора немає у відповіді — показуємо, що саме прийшло */
      console.error('[auth] кабінет без clientidref:', c.raw || c);
      setMsg(t.noClientId);
      return;
    }
    setConnecting(c.id);setMsg(null);
    try {
      await api.linkClientAndSession(c.clientidref);
      setConnecting(null);setConnected(c.id);
      toast(t.signedIn);
      onDone && onDone(c);
    } catch (e) {
      setConnecting(null);setMsg(e.message);
      if (e.isSessionDead) {toast(e.message);onLogout();}
    }
  };

  const register = async () => {
    if (connected || connecting || registering) return;
    setRegistering(true);setMsg(null);
    try {
      const res = await api.registerContragent(locale);
      toast(t.cabCreated);
      onDone && onDone({ id: 'new', name: t.createCab, clientidref: res.clientidref });
    } catch (e) {
      setMsg(e.message);
      if (e.isSessionDead) {toast(e.message);onLogout();}
    } finally {
      setRegistering(false);
    }
  };

  const list = cabinets || [];

  return (
    <div className="form anim-key" key="cab">
      <div className="eyebrow">{t.profiles}</div>
      <h1>{list.length ? t.pickCab : t.makeCab}</h1>
      <p className="lede">
        {list.length
          ? t.pickLede
          : t.makeLede}
      </p>

      {msg && <div className="errnote">{I.err()} {msg}</div>}

      {list.length > 0 &&
      <div className="cab-list">
        {list.map((c) => {
          const st = connected === c.id ? 'connected' : connecting === c.id ? 'connecting' : '';
          return (
            <button key={c.id} className={'cab ' + st} onClick={() => go(c)} disabled={!!connected || registering}>
              <span className="av" style={{ background: c.c || 'linear-gradient(145deg,#3b82f6,#1d4ed8)' }}>{initials(c.name)}</span>
              <span className="info">
                <span className="nm">{c.name}</span>
                <span className="meta"><span>{c.phone}</span>{c.pbox && <span className="pbox">{c.pbox}</span>}</span>
              </span>
              <span className="act">
                {connected === c.id ? <>{I.check()} {t.connected}</> :
                connecting === c.id ? <><span className="spin"></span> {t.connecting}</> :
                <>{I.link()} {t.connect}</>}
              </span>
            </button>);

        })}
      </div>
      }

      {canRegister &&
      <button className="cab-add" onClick={register} disabled={registering || !!connected}>
        {registering ? <><span className="spin"></span> {t.creating}</> : <>{I.plus()} {list.length ? t.addCab : t.createCab}</>}
      </button>
      }
      <button className="logout" onClick={onLogout}>{I.logout()} {t.logout}</button>
    </div>);

}

/* ============ app ============ */
/* Потік за AUTH_DOCUMENTATION.md:
   є session? → sessionIsValid → кабінет : login → PIN → validate → кабінет.
   Сесія живе 10 хвилин і не продовжується перевіркою. */
function App() {
  const api = window.DniproAuth;
  const [step, setStep] = useState(0);
  const [contact, setContact] = useState('');
  const [agreed, setAgreed] = useState(false);
  /* мова з URL (?langvich=UK) → код інтерфейсу */
  const [lang, setLang] = useState(() => {
    try {
      const q = new URLSearchParams(window.location.search);
      const v = (q.get('langvich') || q.get('lang') || '').trim().toUpperCase();
      if (v && SRV_TO_LANG[v]) return SRV_TO_LANG[v];
    } catch (_) {}
    return 'UA';
  });
  /* Параметри вже зчитані (мова тут, application/fingerprint — в auth-api),
     тож прибираємо query з адресного рядка: у стрічці, історії та Referer
     їх більше немає. */
  useEffect(() => {api.cleanUrl && api.cleanUrl();}, []);
  const [toast, setToast] = useState(null);
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState(null);
  const [booting, setBooting] = useState(true);
  const [offline, setOffline] = useState(false);
  const [fatal, setFatal] = useState(null);
  const [connect, setConnect] = useState(null);
  const [cabinets, setCabinets] = useState([]);
  const tT = useRef(null);
  const fire = (m) => {setToast(m);clearTimeout(tT.current);tT.current = setTimeout(() => setToast(null), 2400);};

  const LOCALE = { UA: 'uk', EN: 'en', PL: 'pl', GR: 'ka', RU: 'ru', UZ: 'uz' };
  const t = tr(lang);

  /* Результат підключення (data) → список кабінетів для UI */
  const toCabinets = (res) => {
    const payload = api.getApiResult(res) || res || {};
    const raw = payload.Items || payload.clients || payload.Clients || payload.contragents ||
    (Array.isArray(payload) ? payload : []);
    const palette = [
    'linear-gradient(145deg,#3b82f6,#1d4ed8)', 'linear-gradient(145deg,#8b5cf6,#6d28d9)',
    'linear-gradient(145deg,#06b6d4,#0e7490)', 'linear-gradient(145deg,#f97316,#ea580c)'];

    /* Ідентифікатор може приходити під різними іменами — беремо перше непорожнє.
       Порядок за розділом 5 документації, плюс поширені варіанти написання. */
    const idOf = (r) => r.ContragentIdRef || r.contragentidref || r.ClientIdRef ||
    r.clientidref || r.ClientIDRef || r.IdRef || r.idref || r.Idref ||
    r.ID || r.Id || r.id || null;

    return (Array.isArray(raw) ? raw : [raw]).filter(Boolean).map((r, i) => ({
      id: idOf(r) || i,
      clientidref: idOf(r),
      raw: r,
      name: r.Name || r.name || r.FullName || 'Кабінет',
      phone: r.Phone || r.phone || r.Login || '',
      pbox: r.PBox || r.pbox || r.Pbox || '',
      c: palette[i % palette.length] }));

  };

  /* відновлення сесії на старті */
  useEffect(() => {
    let alive = true;
    (async () => {
      /* 1. параметри зі сторінки: ?application=MY&fingerprint=TEST-TEST-TEST */
      api.readUrlParams();

      /* 2. конфігурація застосунку — звідси формати логіна */
      try {
        const c = await api.getAuthConnect(undefined, LANG_TO_SRV[lang] || 'EN');
        if (alive) setConnect(c);
      } catch (e) {
        if (!alive) return;
        if (e.status === 0 && !e.code) setOffline(true);
        /* застосунок не знайдено або вимкнено — вхід неможливий */
        if (['APPLICATION_NOT_FOUND', 'APPLICATION_DISABLED', 'INVALID_APPLICATION'].includes(e.code)) {
          setFatal(e.message);
          setBooting(false);
          return;
        }
        /* інші причини (немає маршруту тощо) — працюємо без конфігурації */
        console.warn('[auth] getAuthConnectByApplication:', e.code || e.message);
      }

      /* 3. чи є жива сесія */
      try {
        const res = await api.sessionIsValid();
        if (!alive) return;
        if (res.valid) {
          setCabinets(toCabinets(res));
          setContact(api.store.login || '');
          setStep(2);
        }
      } catch (e) {
        if (alive && e.status === 0) setOffline(true);
      } finally {
        if (alive) setBooting(false);
      }
    })();
    return () => {alive = false;};
  }, []);

  /* зміна мови → перечитуємо конфігурацію застосунку з новим langvich */
  useEffect(() => {
    if (booting) return;
    let alive = true;
    api.getAuthConnect(undefined, LANG_TO_SRV[lang] || 'EN')
      .then((c) => {if (alive) setConnect(c);})
      .catch(() => {});
    return () => {alive = false;};
  }, [lang]);

  const doLogin = async () => {
    setBusy(true);setError(null);
    try {
      await api.login(contact);
      setStep(1);
    } catch (e) {
      setError(e.message);
      if (e.status === 0) setOffline(true);
    } finally {
      setBusy(false);
    }
  };

  const afterPin = (res) => {
    setCabinets(toCabinets(res));
    setStep(2);
  };

  /* Після linkClientAndSession — перехід на redirect із api_auth_connect,
     session передаємо параметром. */
  const afterConnect = () => {
    const url = connect && connect.redirect;
    if (!url) {
      console.warn('[auth] redirect не заданий у конфігурації застосунку');
      return;
    }
    /* Адресу кабінету знає лише BFF. Він же передає session прихованою
       POST-формою, тож вона не потрапляє в адресний рядок, історію,
       Referer чи логи проксі. */
    setTimeout(() => {
      window.location.replace((api.config.baseUrl || '/api') + '/redirect');
    }, 600);
  };

  const doLogout = async () => {
    await api.logout();
    setStep(0);setContact('');setAgreed(false);
    setCabinets([]);setError(null);
    fire(t.sessionEnded);
  };

  return (
    <div className="shell">
      <Brand step={step} lang={lang} />
      <div className="panel">
        <div className="topbar"><Lang lang={lang} setLang={setLang} /></div>
        <div className="stage">
          {booting ?
          <div className="form"><div className="lede" style={{ display: 'flex', alignItems: 'center', gap: 10 }}><span className="spin"></span> {t.checking}</div></div> :
          fatal ?
          <div className="form anim-key" key="fatal">
              <div className="eyebrow">{t.setup}</div>
              <h1>{t.noEntry}</h1>
              <p className="lede">{fatal}</p>
              <div className="errnote">{I.err()} application: <b>{api.config.application}</b></div>
            </div> :

          <>
              {step === 0 && <Login contact={contact} setContact={setContact} agreed={agreed} setAgreed={setAgreed} onNext={doLogin} busy={busy} error={error} connect={connect} lang={lang} />}
              {step === 1 && <Pin contact={contact} onBack={() => setStep(0)} onNext={afterPin} toast={fire} api={api} lang={lang} />}
              {step === 2 && <Cabinets cabinets={cabinets} onLogout={doLogout} onDone={afterConnect} toast={fire} api={api} locale={LOCALE[lang]} lang={lang} connect={connect} />}
            </>
          }
        </div>
        <div className="foot">
          <div className="links">
            <a href="https://privacycenter.dniprollc.com/license-agreement/index.html" target="_blank" rel="noopener noreferrer">{t.license}</a>
            <a href="https://privacycenter.dniprollc.com/cookie/index.html" target="_blank" rel="noopener noreferrer">Cookie</a>
          </div>
          <div>© 2026 DNIPRO LLC · 645 West 1st Avenue, Roselle</div>
        </div>
      </div>
      {toast && <div className="toast">{I.check({ style: { color: '#4ade80' } })} {toast}</div>}
    </div>);

}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);