{"version":3,"file":"content.cjs","sources":["../../sdk/src/core/offer.ts","../../sdk/src/ui/mount.ts","../../sdk/src/ui/i18n/keys.ts","../../sdk/src/ui/i18n/index.tsx","../../sdk/src/ui/Modal.tsx","../../sdk/src/ui/renderer/blocks/AuthPanel.tsx","../../sdk/src/ui/AuthGate.tsx","../../sdk/src/ui/renderer/blocks/OfferBanner.tsx","../../sdk/src/ui/SupportGate.tsx","../../sdk/src/ui/renderer/blocks/CtaButton.tsx","../../sdk/src/ui/renderer/blocks/CurrentSession.tsx","../../sdk/src/ui/renderer/blocks/FeaturesList.tsx","../../sdk/src/ui/renderer/blocks/GuaranteeBadge.tsx","../../sdk/src/ui/renderer/blocks/Heading.tsx","../../sdk/src/ui/renderer/blocks/PriceGrid.tsx","../../sdk/src/ui/renderer/blocks/Text.tsx","../../sdk/src/ui/renderer/blocks/TokenizationGate.tsx","../../sdk/src/ui/renderer/registry.ts","../../sdk/src/ui/renderer/Renderer.tsx","../../sdk/src/ui/PaywallRoot.tsx","../../sdk/src/ui/UserWatcher.ts","../../sdk/src/ui/PaywallUI.ts","../src/content/RemoteTrialStore.ts","../src/content/RemoteBillingClient.ts","../src/content/RemoteAuthClient.ts","../src/content/RemoteEventTracker.ts","../src/content/transport.ts","../src/content/PaywallUI.ts"],"sourcesContent":["import type { PaywallOffer } from './types';\n\n/**\n * Resolved view of a paywall offer — what host UI actually needs to render\n * a strike-through price + countdown without re-implementing the math.\n *\n * `remainingMs` ticks down with wall-clock time and reaches 0 on expiry.\n * `totalMs` stays constant — useful for progress bars / share-of-time UX.\n * `expiresAt` is the Date.now()-comparable epoch ms of expiry.\n *\n * For offers without an expiry mechanism (no `expires_at` and no\n * `duration_minutes`), `remainingMs`/`totalMs`/`expiresAt` are all `null`,\n * but the resolved view is still returned — discount badge / strike-through\n * still make sense for \"perpetual sale\" offers.\n */\nexport interface ResolvedOffer {\n  offer: PaywallOffer;\n  discountPercent: number;\n  remainingMs: number | null;\n  totalMs: number | null;\n  expiresAt: number | null;\n}\n\n/** Storage key under which a relative `duration_minutes` offer records its\n *  first-view timestamp. Shared between the renderer (which writes the\n *  start on first open) and the host SDK helpers (which read it). */\nexport function offerStartStorageKey(offerId: string): string {\n  return `pw-offer-${offerId}-start`;\n}\n\n/** Pick the offer applicable to a price. Targeted (`price_id === id`) wins\n *  over the global default (`price_id === null`). Offers without a positive\n *  `discount_percent` are ignored. */\nexport function findApplicableOffer(\n  offers: PaywallOffer[] | null | undefined,\n  priceId: string\n): PaywallOffer | null {\n  if (!offers || offers.length === 0) return null;\n  // String() on both sides: plain-JS hosts pass numeric price ids despite the\n  // declared string type — a strict === would silently skip the targeted offer\n  // (same silent-drop as the localCurrency lookup in BillingClient.createCheckout).\n  const targeted = offers.find(\n    (o) =>\n      o.price_id != null &&\n      String(o.price_id) === String(priceId) &&\n      (o.discount_percent ?? 0) > 0\n  );\n  if (targeted) return targeted;\n  const global = offers.find(\n    (o) => o.price_id == null && (o.discount_percent ?? 0) > 0\n  );\n  return global ?? null;\n}\n\n/**\n * Like `findApplicableOffer`, but returns an offer only while it is still\n * **alive** (not expired). `findApplicableOffer` on its own filters merely by\n * `price_id` + `discount_percent > 0` and ignores the deadline — so the\n * strike-through/`-X%` in `PriceGrid` inside the modal survived expiry even\n * though the countdown banner was already hidden (an in-modal desync plus a\n * mismatch with host-pricing, which resolves via `resolveOffer`). This wrapper\n * runs the found offer through `resolveOffer` and cuts off expired ones.\n *\n * For a duration_minutes offer without a recorded start (the marker is not yet\n * set), `resolveOffer` returns the offer as perpetual — the discount is shown\n * as before; only genuinely expired offers are cut.\n */\nexport function findLiveOffer(\n  offers: PaywallOffer[] | null | undefined,\n  priceId: string,\n  opts: ResolveOfferOptions = {}\n): PaywallOffer | null {\n  const offer = findApplicableOffer(offers, priceId);\n  if (!offer) return null;\n  return resolveOffer(offer, opts) ? offer : null;\n}\n\nexport interface ResolveOfferOptions {\n  /** Current epoch ms. Inject for deterministic tests; default `Date.now()`. */\n  now?: number;\n  /**\n   * Synchronous reader for the `duration_minutes` start-timestamp ISO string.\n   * Host passes a closure over its sync storage (browser → `localStorage`,\n   * memory → in-process map). Return `null` if no start has been recorded.\n   *\n   * Intentionally synchronous, because consumers call this from UI render —\n   * an async StorageAdapter would force every price card to suspend.\n   *\n   * If omitted, `duration_minutes`-only offers return `expiresAt = null`,\n   * which makes the resolved view treat them as \"not yet started\" (the\n   * renderer is responsible for writing the start on first paywall view).\n   */\n  readStart?: (offerId: string) => string | null;\n}\n\n/** Compute the resolved view of an offer. Pure, no side-effects. */\nexport function resolveOffer(\n  offer: PaywallOffer,\n  opts: ResolveOfferOptions = {}\n): ResolvedOffer | null {\n  const discountPercent = offer.discount_percent ?? 0;\n  if (discountPercent <= 0) return null;\n\n  const now = opts.now ?? Date.now();\n  const expiresAt = resolveExpiresAt(offer, opts.readStart);\n  const totalMs = resolveTotalMs(offer, expiresAt);\n  const remainingMs = expiresAt !== null ? Math.max(0, expiresAt - now) : null;\n\n  // Expired — caller treats null as \"do not show\". `remainingMs` reaching 0\n  // happens one tick before this branch fires; the host UI is expected to\n  // re-render on the next tick and see null here.\n  if (expiresAt !== null && expiresAt <= now) return null;\n\n  return { offer, discountPercent, remainingMs, totalMs, expiresAt };\n}\n\nfunction resolveExpiresAt(\n  offer: PaywallOffer,\n  readStart: ((id: string) => string | null) | undefined\n): number | null {\n  if (offer.expires_at) {\n    const t = Date.parse(offer.expires_at);\n    return Number.isFinite(t) ? t : null;\n  }\n  if (offer.duration_minutes && offer.duration_minutes > 0 && readStart) {\n    const startIso = readStart(offer.id);\n    if (!startIso) return null; // not yet activated for this user\n    const start = Date.parse(startIso);\n    if (!Number.isFinite(start)) return null;\n    return start + offer.duration_minutes * 60_000;\n  }\n  return null;\n}\n\nfunction resolveTotalMs(offer: PaywallOffer, expiresAt: number | null): number | null {\n  if (offer.duration_minutes && offer.duration_minutes > 0) {\n    return offer.duration_minutes * 60_000;\n  }\n  // expires_at-only: total = expires_at - \"now of activation\", which we don't\n  // know. Approximation: use full window from epoch is meaningless, so just\n  // mirror remaining (callers that want progress on `expires_at` offers can\n  // capture the value on first render).\n  if (expiresAt !== null) {\n    return expiresAt - Date.now();\n  }\n  return null;\n}\n\n/** Safe browser localStorage getter — returns null in SSR / private mode. */\nexport function readBrowserOfferStart(offerId: string): string | null {\n  if (typeof window === 'undefined') return null;\n  try {\n    return window.localStorage.getItem(offerStartStorageKey(offerId));\n  } catch {\n    return null;\n  }\n}\n","import { h, render, type ComponentType } from 'preact';\nimport cssText from './styles.css?inline';\n\nexport interface MountHandle {\n  update: (props: Record<string, unknown>) => void;\n  unmount: () => void;\n  shadowRoot: ShadowRoot;\n}\n\n// Tailwind v4 defines utilities like `.border` via `border-style: var(--tw-border-style)`,\n// where the `solid` value is supplied by the registered property `@property --tw-border-style { initial-value: solid }`.\n// In Chromium, `@property` declarations inside a shadow root are not registered document-wide, so the variable\n// stays empty → IACVT → border-style: none → used border-width: 0. To make the shorthands work in the\n// shadow scope, we register the same `@property` at the document level once. `inherits: false`\n// keeps the isolation: the property name is visible globally, but values don't leak onto the host page.\nlet twPropertiesRegistered = false;\nfunction ensureTwPropertiesRegistered(): void {\n  if (twPropertiesRegistered) return;\n  twPropertiesRegistered = true;\n  if (typeof CSS === 'undefined' || typeof CSS.registerProperty !== 'function') return;\n  let rules: CSSRuleList;\n  try {\n    const sheet = new CSSStyleSheet();\n    sheet.replaceSync(cssText);\n    rules = sheet.cssRules;\n  } catch {\n    return;\n  }\n  for (const rule of rules) {\n    if (rule.constructor.name !== 'CSSPropertyRule') continue;\n    const r = rule as CSSRule & { name: string; syntax: string; inherits: boolean; initialValue: string | null };\n    try {\n      CSS.registerProperty({\n        name: r.name,\n        syntax: r.syntax,\n        inherits: r.inherits,\n        ...(r.initialValue != null ? { initialValue: r.initialValue } : {})\n      });\n    } catch {\n      // Already registered by another SDK instance on the same page — fine.\n    }\n  }\n}\n\nexport function mountShadow<P extends object>(\n  Component: ComponentType<P>,\n  props: P,\n  options: {\n    host?: HTMLElement;\n    injectCss?: string;\n    shadowMode?: 'open' | 'closed';\n    /** Inline mode: the host is positioned `absolute inset:0` (not fixed),\n     *  so the modal stays within the bounds of its parent. Used in the\n     *  admin editor's live preview. The parent MUST be positioned\n     *  (`position: relative|absolute|fixed`), otherwise absolute escapes upward. */\n    inline?: boolean;\n  } = {}\n): MountHandle {\n  if (typeof document === 'undefined') {\n    throw new Error('mountShadow called in non-DOM environment');\n  }\n\n  ensureTwPropertiesRegistered();\n\n  const host = options.host ?? document.createElement('div');\n  host.setAttribute('data-paywall-host', '');\n  // `all: initial` neutralises inheritance; the actual layout (position/inset/z-index)\n  // is asserted in the `:host` rule below with `!important`. We can NOT rely on these\n  // inline declarations: the shadow's `:host { all: initial !important }` is an\n  // important author rule and overrides even an `!important` inline style on the host\n  // (verified in Chromium) — so without the `:host` layout the host computes to\n  // `position: static`. Kept here only as a harmless fallback layer.\n  host.style.cssText = options.inline\n    ? 'all: initial; position: absolute; inset: 0; z-index: 1; pointer-events: none;'\n    : 'all: initial; position: fixed; inset: 0; z-index: 2147483647; pointer-events: none;';\n  // Without a host from options and without inline — attach to body. Inline expects\n  // the host to already be in the right parent (the platform passes hostRef).\n  if (!host.isConnected && !options.inline) document.body.appendChild(host);\n\n  // Top layer. `position: fixed` resolves against the viewport ONLY if no ancestor\n  // establishes a containing block for fixed descendants (transform / filter /\n  // perspective / will-change / contain / backdrop-filter). The host page is arbitrary\n  // (content scripts, extension side panels, SPA roots) and such ancestors are common —\n  // when present, the fixed overlay collapses into normal flow and the paywall becomes\n  // invisible. Promoting the host into the top layer via the Popover API detaches it\n  // from ancestor containing blocks entirely, so it always fills the viewport.\n  // `manual` — no light-dismiss / Esc-close; the SDK manages its own lifecycle.\n  // Feature-detected + try/catch: on older engines we silently fall back to the plain\n  // fixed overlay (works on pages without a containing-block trap, the prior behaviour).\n  if (!options.inline && host.isConnected && typeof (host as { showPopover?: unknown }).showPopover === 'function') {\n    try {\n      host.setAttribute('popover', 'manual');\n      (host as { showPopover: () => void }).showPopover();\n    } catch {\n      // Already open, disconnected, or unsupported value — keep the fixed-overlay fallback.\n      host.removeAttribute('popover');\n    }\n  }\n\n  // Default `closed` — isolation from the host page. In e2e/demo tests\n  // we enable `open` via the option, otherwise Playwright can't cross the\n  // shadow boundary with its accessibility snapshot and can't click inner buttons.\n  const shadow = host.attachShadow({ mode: options.shadowMode ?? 'closed' });\n\n  // Guards against inherited properties (color, font, letter-spacing, text-transform,\n  // cursor, visibility) leaking from the host page into the shadow via the host element. `!important`\n  // in the shadow overrides an external `!important` on the host (CSS Scoping spec).\n  // Render filters (filter, transform, opacity) on ancestors can't be guarded —\n  // they apply at the compositing level.\n  // Layout asserted here (not via the inline style) because `:host { all: initial !important }`\n  // is what wins the cascade for the host element. Fixed-viewport in production, or\n  // absolute-within-parent in the inline editor preview.\n  const hostLayout = options.inline\n    ? 'position: absolute !important; inset: 0 !important; z-index: 1 !important; pointer-events: none !important;'\n    : 'position: fixed !important; inset: 0 !important; z-index: 2147483647 !important; pointer-events: none !important;';\n  const hostReset = `\n:host {\n  all: initial !important;\n  display: block !important;\n  ${hostLayout}\n  color: #111827 !important;\n  font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif !important;\n  font-size: 16px !important;\n  font-weight: 400 !important;\n  font-style: normal !important;\n  line-height: 1.5 !important;\n  letter-spacing: normal !important;\n  text-transform: none !important;\n  text-decoration: none !important;\n  text-align: left !important;\n  direction: ltr !important;\n  cursor: auto !important;\n  visibility: visible !important;\n}\n`;\n\n  const style = document.createElement('style');\n  style.textContent = hostReset + cssText + (options.injectCss ?? '');\n  shadow.appendChild(style);\n\n  const mountPoint = document.createElement('div');\n  mountPoint.style.pointerEvents = 'auto';\n  shadow.appendChild(mountPoint);\n\n  let currentProps = props;\n  render(h(Component as ComponentType<object>, currentProps), mountPoint);\n\n  return {\n    shadowRoot: shadow,\n    update(nextProps) {\n      currentProps = { ...currentProps, ...nextProps } as P;\n      render(h(Component as ComponentType<object>, currentProps), mountPoint);\n    },\n    unmount() {\n      render(null, mountPoint);\n      host.remove();\n    }\n  };\n}\n","/**\n * The full list of bundled static-translations languages. Each key corresponds\n * to a file in `./locales/<key>.ts`, which Vite splits into a separate chunk\n * (`chunks/<key>-[hash].js`).\n *\n * The order mirrors legacy `online/lang/static-translations.ts`: 27 languages,\n * parity with the paywall on the old stack. EN is the fallback, always inline in\n * the main chunk, and is not in this list.\n */\nexport const BUNDLED_LOCALES = [\n  'ru',\n  'uk',\n  'de',\n  'es',\n  'fr',\n  'it',\n  'pt',\n  'pl',\n  'cs',\n  'hu',\n  'ro',\n  'nl',\n  'sv',\n  'da',\n  'no',\n  'fi',\n  'el',\n  'tr',\n  'id',\n  'ar',\n  'ja',\n  'ko',\n  'zh',\n  'hi',\n  'th',\n  'vi',\n  'he'\n] as const;\n\nexport type BundledLocale = (typeof BUNDLED_LOCALES)[number];\n\n/** Translation dictionary: key → string. May contain `{param}` placeholders,\n *  which `t()` fills via its second argument. A missing key → fallback from the\n *  inline call `t(key, fallback)`. */\nexport type TranslationDict = Record<string, string>;\n\n/** Signature of the translator function. The inline fallback is mandatory, so EN\n *  works even without a loaded chunk and when the key is missing from the dictionary. */\nexport type TFn = (\n  key: string,\n  fallback: string,\n  params?: Record<string, string | number>\n) => string;\n","/**\n * Static-translations for the SDK v3 UI chrome. The legacy counterpart is\n * `online/components/StaticTranslationContext.tsx` + `online/lang/static-translations.ts`.\n *\n * Architecture:\n *  - EN is hardcoded as a fallback in the components themselves (`t('auth.welcome', 'Welcome back!')`).\n *    If the chunk didn't load — the UI is in English, with no empty strings.\n *  - Non-EN languages are separate modules `./locales/<key>.ts`. Vite splits them\n *    into `chunks/<key>-[hash].js`; the dynamic import loads a single chunk\n *    for the resolved locale.\n *  - Owner-controlled `bootstrap.locales` (layout/prices overrides) is an\n *    independent system; the static chunk is applied only if the owner has any\n *    translations at all (`when-configured` mode, as in legacy). Without\n *    translation intent, the paywall shows plain EN, even if the SDK has `de.ts`.\n *  - The chunk loads in the background and **does not block** `bootstrap()`. Until\n *    it arrives — the UI is in EN; on arrival the provider forces a re-render via\n *    a context update.\n */\nimport { createContext, type ComponentChildren } from 'preact';\nimport { useContext, useEffect, useState } from 'preact/hooks';\nimport { BUNDLED_LOCALES, type BundledLocale, type TFn, type TranslationDict } from './keys';\nimport type { PaywallBootstrap } from '../../core/types';\n\ninterface I18nContextValue {\n  t: TFn;\n  locale: string;\n}\n\nconst defaultT: TFn = (_key, fallback, params) => format(fallback, params);\n\nconst I18nCtx = createContext<I18nContextValue>({ t: defaultT, locale: 'en' });\n\n/** Simple `{name}` → value substitution. Does not escape — all strings go into\n *  textContent via preact, so XSS is impossible. */\nfunction format(s: string, params?: Record<string, string | number>): string {\n  if (!params) return s;\n  let out = s;\n  for (const [k, v] of Object.entries(params)) {\n    out = out.split(`{${k}}`).join(String(v));\n  }\n  return out;\n}\n\n/** Cache of loaded dictionaries, so reopening the paywall doesn't fetch the chunk\n *  again (Vite caches the module internally, but a cache on our side avoids a\n *  micro-stall in the Promise chain). */\nconst dictCache = new Map<BundledLocale, TranslationDict>();\n\n/** Inflight loads, so concurrent mounts (widget + popup) share a single dynamic\n *  import instead of two parallel ones. */\nconst inflight = new Map<BundledLocale, Promise<TranslationDict>>();\n\nfunction isBundledLocale(key: string): key is BundledLocale {\n  return (BUNDLED_LOCALES as readonly string[]).includes(key);\n}\n\n/** Picks the bundled language using the same algorithm as owner-overrides\n *  (`pickLocaleKey` in BillingClient): `navigator.language` → base tag →\n *  `settings.locale_default`. Returns the first key for which we have a chunk\n *  in `BUNDLED_LOCALES`. We don't return EN — that's the inline fallback,\n *  nothing needs to be loaded. */\nexport function pickStaticLocaleKey(bootstrap: PaywallBootstrap): BundledLocale | null {\n  const candidates: string[] = [];\n  if (typeof navigator !== 'undefined' && navigator.language) {\n    candidates.push(navigator.language);\n    const base = navigator.language.split('-')[0];\n    if (base && base !== navigator.language) candidates.push(base);\n  }\n  const fallback = bootstrap.settings.locale_default;\n  if (fallback) {\n    candidates.push(fallback);\n    const base = fallback.split('-')[0];\n    if (base && base !== fallback) candidates.push(base);\n  }\n  for (const c of candidates) {\n    if (isBundledLocale(c)) return c;\n  }\n  return null;\n}\n\n/** Mode='when-configured': static is applied only if the owner has a\n *  dynamic override **specifically for the resolved locale**. Without this, the\n *  user would see a mix — static UI in nl + dynamic content (heading/features/banner)\n *  in canonical EN, because the admin only translated to ru. Users in locales\n *  without overrides get plain EN UI + EN content. */\nexport function hasOwnerTranslationsFor(\n  bootstrap: PaywallBootstrap,\n  locale: string\n): boolean {\n  return !!bootstrap.locales && bootstrap.locales[locale] !== undefined;\n}\n\n/** Loads the dictionary for the given language. Idempotent: repeated calls\n *  return the same cached Promise. On a network/import error it resolves with\n *  an empty dictionary (the UI stays on the EN fallbacks) — the paywall must\n *  not crash because of an unavailable locale chunk. */\nexport async function loadLocale(key: BundledLocale): Promise<TranslationDict> {\n  const cached = dictCache.get(key);\n  if (cached) return cached;\n  const pending = inflight.get(key);\n  if (pending) return pending;\n\n  // Vite splits this dynamic import by chunkFileNames from vite.config.ts.\n  // The template string is needed so the bundler generates all 27 chunks; a\n  // static import('./locales/${key}.ts') without a template would collapse into one file.\n  const promise = import(`./locales/${key}.ts`)\n    .then((mod: { default: TranslationDict }) => {\n      const dict = mod.default ?? {};\n      dictCache.set(key, dict);\n      return dict;\n    })\n    .catch((err) => {\n      console.warn(`[paywall] failed to load locale chunk \"${key}\"`, err);\n      const empty: TranslationDict = {};\n      dictCache.set(key, empty);\n      return empty;\n    })\n    .finally(() => {\n      inflight.delete(key);\n    });\n  inflight.set(key, promise);\n  return promise;\n}\n\ninterface I18nProviderProps {\n  /** The PaywallBootstrap by which the language is resolved. null/undefined — the\n   *  provider works in pure EN-fallback mode, the chunk is not loaded. */\n  bootstrap: PaywallBootstrap | null | undefined;\n  /** Explicit override: forces the language choice, bypassing navigator.language\n   *  and the owner-translations check. Used by the admin's live-preview editor\n   *  (\"Preview as user from <country>\") — there the browser locale is always EN, and\n   *  bootstrap.locales may be empty (the form isn't saved yet). Pass only bundled\n   *  keys from `BUNDLED_LOCALES` — otherwise it falls back to the normal\n   *  resolution path. */\n  forceLocale?: string | null;\n  children: ComponentChildren;\n}\n\n/**\n * Mounts the provider, resolves the language from the bootstrap, and fetches the\n * chunk asynchronously. Until the chunk arrives, t() returns the fallbacks from\n * the inline calls (EN). After — setState triggers a re-render of all consumers.\n *\n * The bootstrap may arrive later (loading state in PaywallRoot) — the useEffect\n * runs on bootstrap change and picks it up. The bootstrap may change (revalidate\n * pulled different locales/locale_default) — the useEffect handles it: if the\n * resolved key changed, we load the new chunk, otherwise we stay on the current one.\n */\nexport function I18nProvider({ bootstrap, forceLocale, children }: I18nProviderProps) {\n  const [locale, setLocale] = useState<string>('en');\n  const [dict, setDict] = useState<TranslationDict | null>(null);\n\n  useEffect(() => {\n    // Explicit override: the admin's preview mode. We load directly — we ignore\n    // the owner-check and navigator.language (the browser locale in the admin is always EN).\n    const explicit = forceLocale && isBundledLocale(forceLocale) ? forceLocale : null;\n    const key = explicit ?? (() => {\n      if (!bootstrap) return null;\n      const resolved = pickStaticLocaleKey(bootstrap);\n      if (!resolved) return null;\n      // Per-locale gate: we load static only if there's a dynamic override for\n      // the resolved locale. Otherwise fall back to EN — without a mix of NL\n      // static + EN dynamic content.\n      if (!hasOwnerTranslationsFor(bootstrap, resolved)) return null;\n      return resolved;\n    })();\n\n    // No resolution (or explicit=null in preview when switching back to an EN\n    // country) — we roll back to the canonical-EN fallback from the inline t()\n    // calls. Without the reset, the old dict stays in state and the UI stays\n    // translated to the previous language — that was exactly the live-preview\n    // bug when switching from RU back to US.\n    if (!key) {\n      if (dict !== null || locale !== 'en') {\n        setLocale('en');\n        setDict(null);\n      }\n      return;\n    }\n    if (key === locale && dict) return;\n\n    let cancelled = false;\n    void loadLocale(key).then((d) => {\n      if (cancelled) return;\n      setLocale(key);\n      setDict(d);\n    });\n    return () => {\n      cancelled = true;\n    };\n  }, [bootstrap, forceLocale]);\n\n  const value: I18nContextValue = {\n    locale,\n    t: dict\n      ? (key, fallback, params) => format(dict[key] ?? fallback, params)\n      : defaultT\n  };\n\n  return <I18nCtx.Provider value={value}>{children}</I18nCtx.Provider>;\n}\n\n/** Hook for blocks: `const { t } = useI18n(); t('auth.welcome', 'Welcome back!')`.\n *  Outside an I18nProvider it returns defaultT (EN fallback) — allowing blocks to\n *  render in tests/preview without a mandatory wrapper. */\nexport function useI18n(): I18nContextValue {\n  return useContext(I18nCtx);\n}\n\nexport type { TFn, TranslationDict, BundledLocale };\nexport { BUNDLED_LOCALES };\n","import type { ComponentChildren } from 'preact';\nimport { useEffect, useRef } from 'preact/hooks';\nimport { useI18n } from './i18n';\n\nconst FOCUSABLE =\n  'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n\nexport interface ModalProps {\n  open: boolean;\n  onClose: () => void;\n  labelledBy?: string;\n  brandColor?: string | null;\n  /** Content that sticks to the top of the dialog inside the overlay\n   *  (rounded-top, with a slight negative margin for a visual overlap). Used\n   *  for the offer-countdown banner (PaywallRoot decides to draw it if\n   *  bootstrap.offers has an active timer). */\n  topBanner?: ComponentChildren;\n  /** Whether the modal can be closed: ESC, overlay click, X button. Defaults to\n   *  true. false — the modal stays open until an explicit host-close() /\n   *  success-purchase. */\n  allowClose?: boolean;\n  /** Hide the X button (but keep ESC/overlay working). Used when a view inside\n   *  the modal draws its own Back button (AuthGate, SupportGate) — two\n   *  simultaneous buttons in the top-right corner confuse the user and overlap\n   *  visually. */\n  hideCloseButton?: boolean;\n  /** Inline mode: the overlay is positioned `absolute inset:0` relative to the\n   *  host (instead of `fixed` relative to the viewport) and doesn't lock\n   *  body-scroll. For the admin panel editor's live-preview. */\n  inline?: boolean;\n  children: ComponentChildren;\n}\n\nexport function Modal({\n  open,\n  onClose,\n  labelledBy,\n  brandColor,\n  topBanner,\n  allowClose = true,\n  hideCloseButton = false,\n  inline = false,\n  children\n}: ModalProps) {\n  const { t } = useI18n();\n  const dialogRef = useRef<HTMLDivElement | null>(null);\n  const previouslyFocused = useRef<HTMLElement | null>(null);\n\n  useEffect(() => {\n    if (!open) return;\n    previouslyFocused.current = (document.activeElement as HTMLElement) ?? null;\n\n    const dialog = dialogRef.current;\n    if (dialog) {\n      // Don't auto-focus the first interactive control. When the paywall\n      // auto-opens (no preceding user gesture), the browser's focus-visible\n      // heuristic draws a ring on whatever we focus — and the first focusable\n      // is the first plan card (e.g. the monthly tariff), while the *selected*\n      // plan is the popular one. The ring then sits on a different card than\n      // the accent-border selection, which reads as two conflicting \"active\"\n      // states and confuses users. Focus the dialog container itself instead\n      // (tabIndex=-1, outline-none → no ring); the focus trap still has its\n      // anchor inside the dialog and Tab walks the focusables normally.\n      // A view that genuinely wants an input focused (e.g. an email field)\n      // opts in explicitly via [data-pw-autofocus].\n      const target = dialog.querySelector<HTMLElement>('[data-pw-autofocus]');\n      (target ?? dialog).focus({ preventScroll: true });\n    }\n\n    const onKey = (e: KeyboardEvent) => {\n      if (e.key === 'Escape') {\n        if (!allowClose) return;\n        e.stopPropagation();\n        onClose();\n        return;\n      }\n      if (e.key !== 'Tab' || !dialogRef.current) return;\n      const focusables = Array.from(\n        dialogRef.current.querySelectorAll<HTMLElement>(FOCUSABLE)\n      ).filter((el) => !el.hasAttribute('disabled') && el.tabIndex !== -1);\n      if (focusables.length === 0) {\n        e.preventDefault();\n        return;\n      }\n      const first = focusables[0];\n      const last = focusables[focusables.length - 1];\n      const active = document.activeElement as HTMLElement | null;\n      if (e.shiftKey && active === first) {\n        e.preventDefault();\n        last.focus();\n      } else if (!e.shiftKey && active === last) {\n        e.preventDefault();\n        first.focus();\n      }\n    };\n\n    document.addEventListener('keydown', onKey, true);\n    // Inline-preview doesn't lock body-scroll: the host page (editor) must stay\n    // clickable/scrollable while the modal lives inline.\n    const prevOverflow = document.body.style.overflow;\n    if (!inline) document.body.style.overflow = 'hidden';\n\n    return () => {\n      document.removeEventListener('keydown', onKey, true);\n      if (!inline) document.body.style.overflow = prevOverflow;\n      previouslyFocused.current?.focus?.({ preventScroll: true });\n    };\n  }, [open, onClose, allowClose, inline]);\n\n  if (!open) return null;\n\n  const onBackdrop = (e: MouseEvent) => {\n    if (!allowClose) return;\n    if (e.target === e.currentTarget) onClose();\n  };\n\n  const accent = brandColor ?? '#3b82f6';\n\n  // Inline: the overlay sits in the host's `absolute inset-0` (the host is\n  // itself absolute in its parent, see mount.ts). Production: `fixed inset-0`\n  // relative to the viewport.\n  const overlayClass = `${inline ? 'absolute z-[1]' : 'fixed z-[2147483647]'} inset-0 flex items-center justify-center bg-slate-950/50 p-2 sm:p-4 backdrop-blur-md animate-[pw-fade-in_180ms_ease-out]`;\n\n  return (\n    <div\n      class={overlayClass}\n      onClick={onBackdrop}\n      data-pw-root\n    >\n      {/* Wrapper over the dialog. topBanner (if passed) renders right here,\n          sticking to the top of the dialog via `-mb-2 pb-5 rounded-t-xl\n          rounded-b-none` — giving a visual overlap (the banner's rounded-top\n          and the dialog's rounded-top are hidden under the banner), as in the\n          legacy PaywallModal. */}\n      {/* --pw-accent is defined on the wrapper (not on the dialog) — so the\n          topBanner sibling inherits it too. Previously the variable sat on the\n          dialog, and OfferTopBanner got an unstyled accent. */}\n      <div\n        class=\"relative flex w-full max-w-[400px] flex-col animate-[pw-scale-in_220ms_cubic-bezier(0.16,1,0.3,1)]\"\n        style={{ '--pw-accent': accent } as unknown as Record<string, string>}\n      >\n        {topBanner}\n        <div\n          ref={dialogRef}\n          role=\"dialog\"\n          aria-modal=\"true\"\n          aria-labelledby={labelledBy}\n          tabIndex={-1}\n          // max-h caps the height at the viewport (uses dvh for the mobile\n          // safe-area); flex-col + overflow on children gives an inner scroll\n          // when content is taller than the viewport — critical for extension\n          // popups (max 600px tall) and narrow containers on websites.\n          class=\"relative flex max-h-[calc(100dvh-1rem)] sm:max-h-[calc(100dvh-2rem)] w-full flex-col overflow-hidden rounded-xl bg-white outline-none\"\n          style={{\n            boxShadow:\n              '0 20px 25px -5px rgba(0,0,0,0.1), 0 8px 10px -6px rgba(0,0,0,0.1)'\n          }}\n        >\n          {/* children structure the scroll/footer zones themselves (see\n              Renderer.tsx): flex-1 min-h-0 overflow-y-auto for the scrollable\n              part, the rest is the footer. Previously Modal wrapped everything\n              in an overflow-y-auto wrapper, but that didn't let us pin the\n              CTA footer to the bottom edge without the scroll overlapping the\n              footer. */}\n          {children}\n          {allowClose && !hideCloseButton ? (\n            <button\n              type=\"button\"\n              onClick={onClose}\n              aria-label={t('modal.close_aria', 'Close')}\n              // Absolute relative to the dialog (not the scrollable area) — the\n              // button is always in the top-right corner of the dialog, doesn't\n              // move with the scroll, and doesn't affect the content flow.\n              class=\"absolute right-3 top-3 z-10 flex h-8 w-8 items-center justify-center rounded-full bg-white/80 text-gray-500 backdrop-blur-sm transition-colors hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--pw-accent)]\"\n            >\n              <svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\">\n                <path\n                  d=\"M3 3l10 10M13 3L3 13\"\n                  stroke=\"currentColor\"\n                  stroke-width=\"1.75\"\n                  stroke-linecap=\"round\"\n                />\n              </svg>\n            </button>\n          ) : null}\n        </div>\n      </div>\n\n      <style>{`\n        @keyframes pw-fade-in { from { opacity: 0 } to { opacity: 1 } }\n        @keyframes pw-scale-in {\n          from { opacity: 0; transform: translateY(12px) scale(0.96) }\n          to { opacity: 1; transform: none }\n        }\n      `}</style>\n    </div>\n  );\n}\n","import { useEffect, useRef, useState } from 'preact/hooks';\nimport type { LastLogin, OAuthProvider } from '../../../core/auth';\nimport type { LayoutBlock } from '../../../core/types';\nimport { PaywallError } from '../../../core/types';\nimport type { BlockProps } from '../types';\nimport { useI18n, type TFn } from '../../i18n';\n\ntype AuthPanelBlock = Extract<LayoutBlock, { type: 'auth_panel' }>;\n\n// `otp` — email entry for the passwordless code flow; `otp_verify` — the\n// 6-digit code entry that follows. Both live entirely in this panel, so\n// verifyOtp mints the session on the host origin — unlike the signup link flow,\n// whose confirmation happens on the paywall's custom domain and can't cross\n// back (see the signup_sent auto-resume effect).\ntype Mode =\n  | 'signin'\n  | 'signup'\n  | 'signup_sent'\n  | 'forgot'\n  | 'reset_sent'\n  | 'reset_verify'\n  | 'otp'\n  | 'otp_verify';\n\nfunction providerLabel(provider: OAuthProvider, t: TFn): string {\n  switch (provider) {\n    case 'google':\n      return t('auth.continue_with_google', 'Continue with Google');\n    case 'apple':\n      return t('auth.continue_with_apple', 'Continue with Apple');\n    case 'github':\n      return t('auth.continue_with_github', 'Continue with GitHub');\n    case 'facebook':\n      return t('auth.continue_with_facebook', 'Continue with Facebook');\n  }\n}\n\n// `err.message` from ApiClient on backend errors without a `message` field = HTTP statusText\n// (\"Unauthorized\", \"Bad Request\") — English and raw. We map stable\n// `err.code` values to i18n keys; for anything unrecognized — a generic fallback instead of\n// statusText. `code` comes from the response body (`payload.code`), or\n// `http_<status>`, or `network_error` (see api.ts).\nfunction authErrorMessage(\n  err: unknown,\n  mode: 'signin' | 'signup' | 'otp' | 'reset',\n  t: TFn\n): string {\n  const fallback =\n    mode === 'signup'\n      ? t('auth.signup_failed', 'Sign-up failed')\n      : t('auth.signin_failed', 'Sign-in failed');\n  if (!(err instanceof PaywallError)) return fallback;\n  switch (err.code) {\n    case 'invalid_credentials':\n      return t('auth.invalid_credentials', 'Invalid email or password');\n    case 'email_not_confirmed':\n      return t('auth.email_not_confirmed', 'Please confirm your email before signing in.');\n    case 'email_exists':\n    case 'user_already_exists':\n      return t('auth.email_exists', 'An account with this email already exists.');\n    case 'weak_password':\n      return t('auth.weak_password', 'Password is too weak.');\n    case 'invalid_otp':\n    case 'otp_expired':\n    case 'token_expired':\n      return t('auth.invalid_otp', 'The code is invalid or has expired.');\n    case 'over_email_send_rate_limit':\n    case 'over_request_rate_limit':\n    case 'rate_limited':\n    case 'http_429':\n      return t('auth.rate_limited', 'Too many requests. Please try again later.');\n    case 'network_error':\n      return t('auth.network_error', 'Network error. Please check your connection and try again.');\n    case 'upstream':\n    case 'upstream_error':\n    case 'http_502':\n    case 'http_503':\n    case 'http_504':\n      return t('auth.service_unavailable', 'Service is temporarily unavailable. Please try again.');\n    // Merchant misconfiguration, not a user error: OAuth redirects and\n    // confirmation/recovery email links are built from the paywall's custom\n    // domain (backend hard-fails without it). Shown as-is so the integrating\n    // developer sees the actionable cause right in the UI during setup.\n    case 'custom_domain_required':\n      return t(\n        'auth.custom_domain_required',\n        'Sign-in is not available: the paywall has no custom domain configured (platform settings → Custom domains).'\n      );\n    default:\n      return fallback;\n  }\n}\n\nexport function AuthPanel({ block, ctx }: BlockProps<AuthPanelBlock>) {\n  const auth = ctx.auth;\n  const session = ctx.authSession;\n  const allowSignup = block.allow_signup !== false;\n  const allowReset = block.allow_password_reset !== false;\n  // Opt-in, not opt-out: a second sign-in route next to the password field makes\n  // the form busier for every paywall that never asked for it.\n  const allowEmailCode = block.allow_email_code === true;\n  const hideWhenAuthed = block.hide_when_authenticated !== false;\n\n  if (!auth) {\n    if (typeof console !== 'undefined') {\n      console.warn('[paywall] auth_panel rendered without AuthClient — pass `auth: true` to PaywallUI');\n    }\n    return null;\n  }\n\n  // An anonymous session means \"not authenticated\": anon is only good for the api-gateway,\n  // purchase/restore require a real signin.\n  const realSession = session && !session.user.is_anonymous ? session : null;\n  if (realSession && hideWhenAuthed) return null;\n\n  if (realSession) {\n    return <SignedIn email={realSession.user.email ?? ''} onSignOut={() => auth.signOut().catch(() => {})} />;\n  }\n\n  return (\n    <AuthForm\n      block={block}\n      allowSignup={allowSignup}\n      allowReset={allowReset}\n      allowEmailCode={allowEmailCode}\n      ctx={ctx}\n    />\n  );\n}\n\nfunction SignedIn({ email, onSignOut }: { email: string; onSignOut: () => void }) {\n  const { t } = useI18n();\n  return (\n    <div class=\"flex items-center justify-between gap-3 rounded-2xl bg-gray-100 px-4 py-3\">\n      <div class=\"flex flex-col\">\n        <span class=\"text-[10px] font-semibold uppercase tracking-wider text-gray-500\">\n          {t('auth.signed_in', 'Signed in')}\n        </span>\n        <span class=\"text-sm font-medium text-gray-900\">{email}</span>\n      </div>\n      <button\n        type=\"button\"\n        onClick={onSignOut}\n        class=\"rounded-md px-1.5 py-0.5 text-xs font-medium text-gray-600 transition-colors hover:bg-white hover:text-gray-900 focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--pw-accent)]\"\n      >\n        {t('auth.sign_out', 'Sign out')}\n      </button>\n    </div>\n  );\n}\n\ninterface FormProps {\n  block: AuthPanelBlock;\n  allowSignup: boolean;\n  allowReset: boolean;\n  allowEmailCode: boolean;\n  ctx: BlockProps<AuthPanelBlock>['ctx'];\n}\n\nfunction AuthForm({ block, allowSignup, allowReset, allowEmailCode, ctx }: FormProps) {\n  const { t } = useI18n();\n  const auth = ctx.auth!;\n  const providers = block.providers ?? [];\n\n  // initialAuthMode from ctx — the host called openSignup() / openSignin().\n  // If the admin disabled signup (allow_signup=false), we ignore 'signup'\n  // and start with 'signin' — respecting the admin setting.\n  const initial: Mode =\n    ctx.initialAuthMode === 'signup' && allowSignup ? 'signup' : 'signin';\n  const [mode, setMode] = useState<Mode>(initial);\n  const [email, setEmail] = useState('');\n  const [password, setPassword] = useState('');\n  const [confirmPassword, setConfirmPassword] = useState('');\n  const [otpCode, setOtpCode] = useState('');\n  const [busy, setBusy] = useState<null | OAuthProvider | 'email' | 'reset'>(null);\n  // Synchronous guard against double-submit: setBusy is an async setState,\n  // and two form-submit events in the same tick (Enter+click, double mount\n  // in demo-ext, transport race) would both pass `if (busy) return`, firing\n  // requestPasswordReset/signIn twice. useRef updates synchronously.\n  const submittingRef = useRef(false);\n  const [error, setError] = useState<string | null>(null);\n  const [info, setInfo] = useState<string | null>(null);\n  // Sign up — progressive disclosure: the first \"Sign Up\" click only reveals\n  // password+confirm; the second click with filled fields does the real signUp.\n  // We reset on mode change — the signin↔signup transition always starts from\n  // the collapsed form.\n  const [signupExpanded, setSignupExpanded] = useState(false);\n  // When an OAuth signin hits identity_already_exists — the chosen Google/Apple\n  // account already belongs to an existing user (not the current anon/guest) and\n  // the seamless in-popup switch-account retry couldn't complete — we surface a\n  // one-tap \"sign in to that account\" button. The click is a fresh user gesture,\n  // so it can open a popup again; switchAccount=true skips linkIdentity.\n  const [switchProvider, setSwitchProvider] = useState<OAuthProvider | null>(null);\n\n  // Signup credentials held for the email-confirm auto-resume (see the\n  // signup_sent effect below). Memory-only (ref, never storage): the confirm\n  // page lives on the paywall's custom domain, so its session can't cross\n  // origins into this tab — the only way to continue without re-typing the\n  // password is to retry signin with the credentials the user just entered.\n  // Cleared on successful signin, on leaving signup_sent, and with the tab.\n  const pendingCredsRef = useRef<{ email: string; password: string } | null>(null);\n\n  // Last-used auth method and email (per-paywall). Async-loaded from storage on mount,\n  // while null — the UI just renders without the badge. Pre-fill email only if\n  // the user hasn't typed anything yet — otherwise we'd overwrite what they're typing.\n  //\n  // Defensive: old builds of @monetize.software/sdk-extension (≤ 3.0.0-alpha.4)\n  // didn't implement getLastLogin in RemoteAuthClient — without the guard the consumer\n  // would get `auth.getLastLogin is not a function` in the console. In that case the badge\n  // simply isn't shown, and signin keeps working.\n  const [lastLogin, setLastLogin] = useState<LastLogin | null>(null);\n  useEffect(() => {\n    if (typeof auth.getLastLogin !== 'function') return;\n    let cancelled = false;\n    auth.getLastLogin().then(\n      (v) => {\n        if (cancelled || !v) return;\n        setLastLogin(v);\n        if (v.email) {\n          setEmail((current) => (current === '' ? v.email! : current));\n        }\n      },\n      () => {\n        /* storage unavailable — UI without the badge, signin works */\n      }\n    );\n    return () => {\n      cancelled = true;\n    };\n  }, [auth]);\n\n  const switchTo = (next: Mode): void => {\n    setMode(next);\n    setError(null);\n    setInfo(null);\n    setSignupExpanded(false);\n    setSwitchProvider(null);\n    // A code belongs to the flow that requested it — never carry it into another\n    // verify screen, where it would be submitted against a different OTP type.\n    setOtpCode('');\n    // Leaving signup_sent by hand (Back to sign in) abandons the auto-resume —\n    // the user will type the password themselves; don't keep it in memory.\n    pendingCredsRef.current = null;\n  };\n\n  // Email-confirm auto-resume. While signup_sent is on screen we silently retry\n  // signin with the in-memory credentials: on tab focus (the confirm page\n  // closes itself after verifying, focus returns here) and on a modest\n  // background interval (confirmation may happen on another device). GoTrue\n  // rejects with email_not_confirmed until the link is clicked — those (and\n  // network hiccups) are swallowed, the view stays as-is. On success SIGNED_IN\n  // reaches PaywallRoot via onAuthChange and the auth-resume flow continues the\n  // pending checkout by itself. Interval stops after ~10 minutes; focus retries\n  // keep working for as long as the view is open.\n  useEffect(() => {\n    if (mode !== 'signup_sent') return;\n    if (!pendingCredsRef.current) return;\n    if (typeof window === 'undefined') return;\n\n    let disposed = false;\n    let inFlight = false;\n    const attempt = async (): Promise<void> => {\n      const creds = pendingCredsRef.current;\n      if (disposed || inFlight || !creds) return;\n      inFlight = true;\n      try {\n        await auth.signInWithEmail({ email: creds.email, password: creds.password });\n        // Signed in — drop the password from memory immediately; the gate\n        // advances via onAuthChange.\n        pendingCredsRef.current = null;\n      } catch {\n        /* not confirmed yet / offline — wait for the next signal */\n      } finally {\n        inFlight = false;\n      }\n    };\n\n    const onFocus = (): void => {\n      if (typeof document !== 'undefined' && document.visibilityState === 'hidden') return;\n      void attempt();\n    };\n    window.addEventListener('focus', onFocus);\n    document.addEventListener('visibilitychange', onFocus);\n    // 20s × 30 ticks ≈ 10 minutes of background polling — enough for the\n    // same-device flow many times over, bounded so an abandoned tab doesn't\n    // poll the auth endpoint forever.\n    let ticks = 0;\n    const iv = window.setInterval(() => {\n      ticks += 1;\n      if (ticks > 30) {\n        window.clearInterval(iv);\n        return;\n      }\n      void attempt();\n    }, 20_000);\n\n    return () => {\n      disposed = true;\n      window.removeEventListener('focus', onFocus);\n      document.removeEventListener('visibilitychange', onFocus);\n      window.clearInterval(iv);\n    };\n  }, [mode, auth]);\n\n  const onSubmit = async (e: Event): Promise<void> => {\n    e.preventDefault();\n    if (submittingRef.current || busy) return;\n    submittingRef.current = true;\n    try {\n      setError(null);\n      setInfo(null);\n\n      // Sign up shortcut: the first submit just reveals the password fields,\n      // without a network request. Email is required at this step, otherwise HTML5\n      // validation marks the field as required itself.\n      if (mode === 'signup' && !signupExpanded) {\n        if (!email.trim()) return;\n        setSignupExpanded(true);\n        return;\n      }\n\n      if (mode === 'signup' && password !== confirmPassword) {\n        setError(t('auth.passwords_mismatch', \"Passwords don't match\"));\n        return;\n      }\n\n      setBusy('email');\n      try {\n        if (mode === 'signin') {\n          await auth.signInWithEmail({ email, password });\n        } else if (mode === 'signup') {\n          const res = await auth.signUp({ email, password });\n          if (res.kind === 'confirmation_required') {\n            // Link flow (like recovery): the prod template sends a confirmation link,\n            // not a code. We show \"check your email → click the link\" instead of\n            // a dead-end code-entry screen. Confirmation completes at\n            // /paywall/v3/auth/confirm on the paywall's custom domain — that\n            // session can NOT cross origins into this tab, so the signup_sent\n            // effect below retries signin with the credentials kept in memory\n            // until the confirmation lands. The password leaves component state\n            // either way (ref only).\n            pendingCredsRef.current = { email, password };\n            setPassword('');\n            setMode('signup_sent');\n          } else if (res.kind === 'already_registered') {\n            // The email is already registered (possibly via Google/Apple). Send\n            // the user to sign in instead of a fake \"check your email\" dead-end —\n            // we keep the email prefilled and reveal the signin form (OAuth\n            // buttons stay visible above it for the social-login case).\n            setMode('signin');\n            setSignupExpanded(false);\n            setConfirmPassword('');\n            setInfo(\n              t(\n                'auth.email_already_registered',\n                'This email is already registered. Sign in below — with your password or the social account you used.'\n              )\n            );\n          }\n        } else if (mode === 'forgot') {\n          await auth.requestPasswordReset({ email });\n          setMode('reset_sent');\n        } else if (mode === 'reset_verify') {\n          await auth.verifyOtp({\n            email,\n            token: otpCode,\n            type: password ? 'recovery' : 'email'\n          });\n          if (password) {\n            await auth.updatePassword({ password });\n          }\n        } else if (mode === 'otp') {\n          // create_user defaults to true server-side, so one flow covers both\n          // sign-in and sign-up. The backend is anti-enumeration (always ok), so\n          // we always advance — a non-existent email fails at verify instead.\n          await auth.sendOtp({ email });\n          setOtpCode('');\n          setMode('otp_verify');\n        } else if (mode === 'otp_verify') {\n          // type 'email' is the signin/signup-by-code variant ('recovery' is the\n          // password-reset one). On success setSession + onAuthChange fire, and\n          // the auth gate advances on its own — same as a password signin.\n          await auth.verifyOtp({ email, token: otpCode, type: 'email' });\n        }\n      } catch (err) {\n        // Signup with email-confirm OFF: GoTrue throws email_exists/user_already_exists\n        // (the confirm-ON variant comes back as kind:'already_registered' above).\n        // Same UX: send the user to sign in with the email prefilled instead of a\n        // bare \"account exists\" error.\n        if (\n          mode === 'signup' &&\n          err instanceof PaywallError &&\n          (err.code === 'email_exists' || err.code === 'user_already_exists')\n        ) {\n          setMode('signin');\n          setSignupExpanded(false);\n          setConfirmPassword('');\n          setInfo(\n            t(\n              'auth.email_already_registered',\n              'This email is already registered. Sign in below with your password or the account you used to sign up.'\n            )\n          );\n          return;\n        }\n        const errMode =\n          mode === 'signup' ? 'signup'\n            : mode === 'reset_verify' || mode === 'otp' || mode === 'otp_verify' ? 'otp'\n            : mode === 'forgot' ? 'reset' : 'signin';\n        setError(authErrorMessage(err, errMode, t));\n      } finally {\n        setBusy(null);\n      }\n    } finally {\n      submittingRef.current = false;\n    }\n  };\n\n  // Resend from the code screen. Shares submittingRef/busy with the form so it\n  // can't race a verify in flight. Errors surface here (unlike sendOtp on the\n  // email step, which is anti-enumeration and always advances) — at this point\n  // the user is already committed to the flow, so a rate-limit needs to be seen.\n  const onResendCode = async (): Promise<void> => {\n    if (submittingRef.current || busy) return;\n    submittingRef.current = true;\n    setBusy('email');\n    setError(null);\n    setInfo(null);\n    try {\n      await auth.sendOtp({ email });\n      setInfo(t('auth.code_resent', 'We sent you a new code.'));\n    } catch (err) {\n      setError(authErrorMessage(err, 'otp', t));\n    } finally {\n      submittingRef.current = false;\n      setBusy(null);\n    }\n  };\n\n  const onOAuth = async (\n    provider: OAuthProvider,\n    opts?: { switchAccount?: boolean }\n  ): Promise<void> => {\n    if (submittingRef.current || busy) return;\n    submittingRef.current = true;\n    setBusy(provider);\n    setError(null);\n    setInfo(null);\n    // Clear a stale switch-account prompt on any fresh non-switch attempt.\n    if (!opts?.switchAccount) setSwitchProvider(null);\n    try {\n      await auth.signInWithOAuth({\n        provider,\n        switchAccount: opts?.switchAccount,\n        onPopupOpened: () => setBusy(null),\n        // Only the extension SDK acts on this: there the sign-in can outlive\n        // this panel (an action popup is destroyed when the provider window\n        // takes focus), and offscreen continues the purchase on its own.\n        resumeCheckout: ctx.resumeCheckout\n      });\n      setSwitchProvider(null);\n    } catch (err) {\n      if (err instanceof PaywallError && (err.code === 'oauth_cancelled' || err.code === 'oauth_timeout')) {\n        return;\n      }\n      // The OAuth identity already belongs to an existing account (the user signed\n      // in with it before, on another device). Offer a one-tap sign-in into that\n      // account — switchAccount drops the current anon/guest session.\n      if (err instanceof PaywallError && err.code === 'oauth_identity_already_linked') {\n        setSwitchProvider(provider);\n        setError(\n          t(\n            'auth.identity_already_linked',\n            'This account is already registered. Sign in to it below.'\n          )\n        );\n        return;\n      }\n      // Surface the real cause to the console — the user-facing string is a\n      // generic fallback, but merchants debugging a failed OAuth need the actual\n      // code/description (e.g. a GoTrue error_description that didn't map to a key).\n      if (typeof console !== 'undefined') {\n        console.warn('[paywall] OAuth sign-in failed', {\n          provider,\n          code: err instanceof PaywallError ? err.code : undefined,\n          message: err instanceof Error ? err.message : String(err)\n        });\n      }\n      setError(authErrorMessage(err, 'signin', t));\n    } finally {\n      submittingRef.current = false;\n      setBusy(null);\n    }\n  };\n\n  const showOAuth = providers.length > 0 && (mode === 'signin' || mode === 'signup');\n  const showEmailField =\n    mode === 'signin' || mode === 'signup' || mode === 'forgot' || mode === 'otp';\n  const showPasswordField =\n    mode === 'signin' || (mode === 'signup' && signupExpanded);\n\n  if (mode === 'reset_sent') {\n    return <ResetSentView email={email} onBack={() => switchTo('signin')} t={t} />;\n  }\n\n  if (mode === 'signup_sent') {\n    return <SignupSentView email={email} onBack={() => switchTo('signin')} t={t} />;\n  }\n\n  return (\n    <div class=\"flex flex-col gap-5\">\n      <Header mode={mode} customHeading={block.heading} customSubheading={block.subheading} />\n\n      {showOAuth ? (\n        <div class=\"flex flex-col gap-2.5\">\n          {providers.map((p) => (\n            <div key={p} class=\"relative\">\n              <button\n                type=\"button\"\n                onClick={() => onOAuth(p)}\n                disabled={busy !== null}\n                class=\"flex h-12 w-full items-center justify-center gap-2.5 rounded-full border-1 border-gray-200 bg-white px-5 text-base font-medium text-gray-900 transition-all hover:border-gray-300 hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-60 focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--pw-accent)]\"\n              >\n                {busy === p ? (\n                  <span class=\"inline-block h-4 w-4 animate-spin rounded-full border-2 border-gray-300 border-t-gray-700\" />\n                ) : (\n                  <ProviderIcon provider={p} />\n                )}\n                <span>{providerLabel(p, t)}</span>\n              </button>\n              {lastLogin?.method === p ? <LastUsedBadge email={lastLogin.email} /> : null}\n            </div>\n          ))}\n          <Divider />\n        </div>\n      ) : null}\n\n      <form onSubmit={onSubmit} class=\"flex flex-col gap-3\">\n        {showEmailField && (\n          <FilledField\n            type=\"email\"\n            placeholder={t('auth.email', 'Email address')}\n            value={email}\n            onInput={setEmail}\n            autocomplete=\"email\"\n            required\n          />\n        )}\n\n        {showPasswordField && (\n          <PasswordField\n            placeholder={t('auth.password', 'Password')}\n            value={password}\n            onInput={setPassword}\n            autocomplete={mode === 'signin' ? 'current-password' : 'new-password'}\n            required\n          />\n        )}\n\n        {mode === 'signup' && signupExpanded && (\n          <PasswordField\n            placeholder={t('auth.repeat_password', 'Repeat password')}\n            value={confirmPassword}\n            onInput={setConfirmPassword}\n            autocomplete=\"new-password\"\n            required\n          />\n        )}\n\n        {(mode === 'reset_verify' || mode === 'otp_verify') && (\n          <FilledField\n            type=\"text\"\n            placeholder={t('auth.confirmation_code', 'Confirmation code')}\n            value={otpCode}\n            onInput={setOtpCode}\n            autocomplete=\"one-time-code\"\n            inputMode=\"numeric\"\n            required\n          />\n        )}\n\n        {mode === 'reset_verify' && (\n          <PasswordField\n            placeholder={t(\n              'auth.new_password_optional',\n              'New password (optional — only for password reset)'\n            )}\n            value={password}\n            onInput={setPassword}\n            autocomplete=\"new-password\"\n          />\n        )}\n\n        {(mode === 'signin' || mode === 'signup') &&\n          (allowEmailCode || (mode === 'signin' && allowReset)) && (\n            <div class=\"flex items-center justify-between gap-3 text-sm\">\n              {allowEmailCode ? (\n                <AccentLink onClick={() => switchTo('otp')}>\n                  {t('auth.use_email_code', 'Sign in with a code')}\n                </AccentLink>\n              ) : (\n                <span />\n              )}\n              {mode === 'signin' && allowReset ? (\n                <AccentLink onClick={() => switchTo('forgot')}>\n                  {t('auth.forgot_password', 'Forgot password?')}\n                </AccentLink>\n              ) : null}\n            </div>\n          )}\n\n        {mode === 'otp_verify' && (\n          <div class=\"flex justify-start text-sm\">\n            <AccentLink onClick={onResendCode}>\n              {t('auth.resend_code', 'Send the code again')}\n            </AccentLink>\n          </div>\n        )}\n\n        {error && <p class=\"text-sm text-red-600\">{error}</p>}\n        {info && <p class=\"text-sm text-gray-500\">{info}</p>}\n\n        {switchProvider && (\n          <button\n            type=\"button\"\n            onClick={() => onOAuth(switchProvider, { switchAccount: true })}\n            disabled={busy !== null}\n            class=\"flex h-12 w-full items-center justify-center gap-2.5 rounded-full border-1 border-gray-200 bg-white px-5 text-base font-medium text-gray-900 transition-all hover:border-gray-300 hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-60 focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--pw-accent)]\"\n          >\n            {busy === switchProvider ? (\n              <span class=\"inline-block h-4 w-4 animate-spin rounded-full border-2 border-gray-300 border-t-gray-700\" />\n            ) : (\n              <ProviderIcon provider={switchProvider} />\n            )}\n            <span>{providerLabel(switchProvider, t)}</span>\n          </button>\n        )}\n\n        <PrimaryButton\n          busy={busy === 'email'}\n          label={submitLabel(mode, signupExpanded, block.submit_label ?? block.heading, t)}\n        />\n      </form>\n\n      <FormFooter\n        mode={mode}\n        allowSignup={allowSignup}\n        onSwitch={switchTo}\n      />\n    </div>\n  );\n}\n\nfunction Header({\n  mode,\n  customHeading,\n  customSubheading\n}: {\n  mode: Mode;\n  customHeading?: string | null;\n  customSubheading?: string | null;\n}) {\n  const { t } = useI18n();\n  // customHeading/customSubheading override the default for the signin+signup modes.\n  // A restore/preauth intent sets its own heading, but when the user clicks\n  // \"Forgot password?\" — the view changes to forgot and must show\n  // the default \"Forgot password?\" title, not the intent-specific string.\n  // Reset views (forgot/reset_sent/reset_verify) always use the defaults.\n  const defaults = defaultHeader(mode, t);\n  const useCustom = mode === 'signin' || mode === 'signup';\n  const title = useCustom && customHeading ? customHeading : defaults.title;\n  const subtitle =\n    useCustom && customSubheading !== undefined\n      ? customSubheading || null\n      : defaults.subtitle;\n  return (\n    <div class=\"flex flex-col gap-2\">\n      <h2 class=\"text-3xl font-bold tracking-tight text-gray-900\">{title}</h2>\n      {subtitle ? (\n        <p class=\"text-base leading-relaxed text-gray-600\">{subtitle}</p>\n      ) : null}\n    </div>\n  );\n}\n\nfunction defaultHeader(mode: Mode, t: TFn): { title: string; subtitle: string | null } {\n  switch (mode) {\n    case 'signin':\n      return {\n        title: t('auth.welcome', 'Welcome back!'),\n        subtitle: t('auth.default_subtitle', 'Sign in to access all features and sync your data.')\n      };\n    case 'signup':\n      return {\n        title: t('auth.welcome_signup', 'Welcome!'),\n        subtitle: t('auth.default_subtitle', 'Sign in to access all features and sync your data.')\n      };\n    case 'forgot':\n      return {\n        title: t('auth.forgot_password_title', 'Forgot password?'),\n        subtitle: t(\n          'auth.forgot_subtitle',\n          \"Enter your email and we'll send you a password reset link.\"\n        )\n      };\n    case 'reset_sent':\n    case 'signup_sent':\n      return {\n        title: t('auth.check_email_title', 'Check your email'),\n        subtitle: null\n      };\n    case 'reset_verify':\n      return {\n        title: t('auth.reset_password_title', 'Reset password'),\n        subtitle: t(\n          'auth.reset_password_subtitle',\n          'Enter the code from your email and a new password.'\n        )\n      };\n    case 'otp':\n      return {\n        title: t('auth.otp_title', 'Sign in with a code'),\n        subtitle: t(\n          'auth.otp_subtitle',\n          \"Enter your email and we'll send you a sign-in code — no password needed.\"\n        )\n      };\n    case 'otp_verify':\n      return {\n        title: t('auth.otp_verify_title', 'Enter the code'),\n        subtitle: t(\n          'auth.otp_verify_subtitle',\n          'We sent a 6-digit code to your email. Enter it here to sign in.'\n        )\n      };\n  }\n}\n\nfunction submitLabel(\n  mode: Mode,\n  signupExpanded: boolean,\n  customHeading: string | undefined,\n  t: TFn\n): string {\n  // If customHeading is set — it's also used as the submit label for signin\n  // (\"Restore Purchases\" → button \"Restore Purchases\"). For the other modes\n  // the submit label is fixed (Sign Up / Send Reset Email / Verify).\n  if (mode === 'signin' && customHeading) return customHeading;\n  switch (mode) {\n    case 'signin':\n      return t('auth.log_in', 'Sign In');\n    case 'signup':\n      return signupExpanded\n        ? t('auth.create_account', 'Create Account')\n        : t('auth.sign_up', 'Sign Up');\n    case 'forgot':\n      return t('auth.send_reset', 'Send Reset Email');\n    case 'otp':\n      return t('auth.send_code', 'Send Code');\n    case 'reset_verify':\n    case 'otp_verify':\n      return t('auth.verify', 'Verify');\n    default:\n      return t('cta.continue', 'Continue');\n  }\n}\n\nfunction FormFooter({\n  mode,\n  allowSignup,\n  onSwitch\n}: {\n  mode: Mode;\n  allowSignup: boolean;\n  onSwitch: (m: Mode) => void;\n}) {\n  const { t } = useI18n();\n  if (mode === 'signin' && allowSignup) {\n    return (\n      <p class=\"text-center text-sm text-gray-600\">\n        {t('auth.no_account', \"Don't have an account?\")}{' '}\n        <AccentLink onClick={() => onSwitch('signup')}>\n          {t('auth.sign_up_link', 'Sign Up')}\n        </AccentLink>\n      </p>\n    );\n  }\n  if (mode === 'signup') {\n    return (\n      <p class=\"text-center text-sm text-gray-600\">\n        {t('auth.have_account', 'Already have an account?')}{' '}\n        <AccentLink onClick={() => onSwitch('signin')}>\n          {t('auth.log_in_link', 'Log In')}\n        </AccentLink>\n      </p>\n    );\n  }\n  if (mode === 'forgot' || mode === 'reset_sent' || mode === 'reset_verify') {\n    return (\n      <p class=\"text-center text-sm text-gray-600\">\n        {t('auth.no_account', \"Don't have an account?\")}{' '}\n        <AccentLink onClick={() => onSwitch('signup')}>\n          {t('auth.sign_up_link', 'Sign Up')}\n        </AccentLink>\n      </p>\n    );\n  }\n  // The code flow signs up and signs in alike, so there's no \"no account?\"\n  // branch to offer here — only the way back to the password form.\n  if (mode === 'otp' || mode === 'otp_verify') {\n    return (\n      <p class=\"text-center text-sm text-gray-600\">\n        <AccentLink onClick={() => onSwitch('signin')}>\n          {t('auth.back_to_login', 'Back to Login')}\n        </AccentLink>\n      </p>\n    );\n  }\n  return null;\n}\n\nfunction AccentLink({\n  onClick,\n  children\n}: {\n  onClick: () => void;\n  children: preact.ComponentChildren;\n}) {\n  return (\n    <button\n      type=\"button\"\n      onClick={onClick}\n      class=\"font-semibold transition-opacity hover:opacity-80 focus:outline-none focus-visible:opacity-80\"\n      style={{ color: 'var(--pw-accent)' }}\n    >\n      {children}\n    </button>\n  );\n}\n\nfunction PrimaryButton({ busy, label }: { busy: boolean; label: string }) {\n  return (\n    <button\n      type=\"submit\"\n      disabled={busy}\n      class=\"pw-cta-shimmer relative mt-1 flex min-h-12 w-full items-center justify-center overflow-hidden rounded-3xl px-5 py-2 text-center text-base font-semibold leading-tight text-white transition-transform duration-150 active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-60 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--pw-accent)]\"\n      style={{\n        background:\n          'linear-gradient(135deg, color-mix(in srgb, var(--pw-accent) 55%, white) 0%, var(--pw-accent) 55%, color-mix(in srgb, var(--pw-accent) 90%, black) 100%)',\n        boxShadow:\n          '0 0 20px 0 color-mix(in srgb, var(--pw-accent) 25%, transparent), inset 0 0 8px 0 color-mix(in srgb, white 25%, transparent)'\n      }}\n    >\n      {busy ? (\n        <span class=\"relative z-10 inline-block h-4 w-4 animate-spin rounded-full border-2 border-white/40 border-t-white\" />\n      ) : (\n        <span class=\"relative z-10\">{label}</span>\n      )}\n    </button>\n  );\n}\n\ninterface FilledFieldProps {\n  type: 'email' | 'text';\n  placeholder: string;\n  value: string;\n  onInput: (v: string) => void;\n  autocomplete?: string;\n  inputMode?: 'numeric' | 'text' | 'email';\n  required?: boolean;\n}\n\nfunction FilledField({ type, placeholder, value, onInput, autocomplete, inputMode, required }: FilledFieldProps) {\n  return (\n    <input\n      type={type}\n      value={value}\n      placeholder={placeholder}\n      onInput={(e) => onInput((e.target as HTMLInputElement).value)}\n      autocomplete={autocomplete}\n      inputMode={inputMode}\n      required={required}\n      class=\"h-14 w-full rounded-2xl bg-gray-100 px-5 text-base text-gray-900 outline-none transition-all placeholder:text-gray-500 hover:bg-gray-200/60 focus:bg-gray-200/60 focus:shadow-[0_0_0_2px_color-mix(in_srgb,var(--pw-accent)_30%,transparent)]\"\n    />\n  );\n}\n\ninterface PasswordFieldProps {\n  placeholder: string;\n  value: string;\n  onInput: (v: string) => void;\n  autocomplete?: string;\n  required?: boolean;\n}\n\nfunction PasswordField({ placeholder, value, onInput, autocomplete, required }: PasswordFieldProps) {\n  const { t } = useI18n();\n  const [visible, setVisible] = useState(false);\n  const inputRef = useRef<HTMLInputElement>(null);\n  // Chrome/Safari clear .value when the type switches between password↔text (autofill-guard).\n  // Preact sees the same value prop and doesn't re-set the DOM — the field stays empty.\n  useEffect(() => {\n    const el = inputRef.current;\n    if (el && el.value !== value) el.value = value;\n  }, [visible, value]);\n  const passwordAriaShow = t('auth.show_password', 'Show password');\n  const passwordAriaHide = t('auth.hide_password', 'Hide password');\n  return (\n    <div class=\"relative\">\n      <input\n        ref={inputRef}\n        type={visible ? 'text' : 'password'}\n        value={value}\n        placeholder={placeholder}\n        onInput={(e) => onInput((e.target as HTMLInputElement).value)}\n        autocomplete={autocomplete}\n        required={required}\n        class=\"h-14 w-full rounded-2xl bg-gray-100 pl-5 pr-12 text-base text-gray-900 outline-none transition-all placeholder:text-gray-500 hover:bg-gray-200/60 focus:bg-gray-200/60 focus:shadow-[0_0_0_2px_color-mix(in_srgb,var(--pw-accent)_30%,transparent)]\"\n      />\n      <button\n        type=\"button\"\n        onClick={() => setVisible((v) => !v)}\n        aria-label={visible ? passwordAriaHide : passwordAriaShow}\n        tabIndex={-1}\n        class=\"absolute right-4 top-1/2 -translate-y-1/2 flex h-6 w-6 items-center justify-center rounded text-gray-500 transition-colors hover:text-gray-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--pw-accent)]\"\n      >\n        {visible ? <EyeOffIcon /> : <EyeIcon />}\n      </button>\n    </div>\n  );\n}\n\nfunction EyeIcon() {\n  return (\n    <svg width=\"18\" height=\"18\" viewBox=\"0 0 20 20\" fill=\"none\" aria-hidden=\"true\">\n      <path\n        d=\"M1.667 10S4.583 4.167 10 4.167 18.333 10 18.333 10 15.417 15.833 10 15.833 1.667 10 1.667 10Z\"\n        stroke=\"currentColor\"\n        stroke-width=\"1.5\"\n        stroke-linecap=\"round\"\n        stroke-linejoin=\"round\"\n      />\n      <circle cx=\"10\" cy=\"10\" r=\"2.5\" stroke=\"currentColor\" stroke-width=\"1.5\" />\n    </svg>\n  );\n}\n\nfunction EyeOffIcon() {\n  return (\n    <svg width=\"18\" height=\"18\" viewBox=\"0 0 20 20\" fill=\"none\" aria-hidden=\"true\">\n      <path\n        d=\"M8.236 4.293A6.96 6.96 0 0 1 10 4.167C15.417 4.167 18.333 10 18.333 10a13.5 13.5 0 0 1-1.92 2.755M11.768 11.768A2.5 2.5 0 0 1 8.233 8.233\"\n        stroke=\"currentColor\"\n        stroke-width=\"1.5\"\n        stroke-linecap=\"round\"\n        stroke-linejoin=\"round\"\n      />\n      <path\n        d=\"M14.953 14.953A8.84 8.84 0 0 1 10 15.833C4.583 15.833 1.667 10 1.667 10a13.5 13.5 0 0 1 3.38-3.953M1.667 1.667l16.666 16.666\"\n        stroke=\"currentColor\"\n        stroke-width=\"1.5\"\n        stroke-linecap=\"round\"\n        stroke-linejoin=\"round\"\n      />\n    </svg>\n  );\n}\n\nfunction LastUsedBadge({ email }: { email: string | null }) {\n  const { t } = useI18n();\n  // A pill in the top-right corner of the button. truncate + max-w guard against a long\n  // email (which would overflow the button edges). pointer-events-none — so the click lands\n  // on the button itself, not on the badge on top.\n  const label = email\n    ? t('auth.last_used', 'Last used · {email}', { email: maskEmail(email) })\n    : t('auth.last_used_no_email', 'Last used');\n  return (\n    <span class=\"pointer-events-none absolute -top-2 right-3 max-w-[75%] truncate rounded-full bg-gray-900 px-2 py-0.5 text-[10px] font-semibold tracking-wide text-white shadow-sm\">\n      {label}\n    </span>\n  );\n}\n\n// alex@example.com → ale*****@example.com. We mask the local part (the\n// first 3 characters are visible) and leave the domain as is — it's public and helps\n// the user recognize the account.\nfunction maskEmail(email: string): string {\n  const [local, domain] = email.split('@');\n  if (!domain) return email;\n  const visible = local.slice(0, 3);\n  return `${visible}*****@${domain}`;\n}\n\nfunction Divider() {\n  const { t } = useI18n();\n  return (\n    <div class=\"flex items-center gap-3 py-1 text-sm text-gray-400\">\n      <div class=\"h-px flex-1 bg-gray-200\" />\n      <span>{t('auth.or', 'or')}</span>\n      <div class=\"h-px flex-1 bg-gray-200\" />\n    </div>\n  );\n}\n\nfunction ProviderIcon({ provider }: { provider: OAuthProvider }) {\n  if (provider === 'google') {\n    return (\n      <svg width=\"20\" height=\"20\" viewBox=\"0 0 18 18\" aria-hidden=\"true\">\n        <path fill=\"#4285F4\" d=\"M17.64 9.2c0-.64-.06-1.25-.16-1.84H9v3.49h4.84a4.14 4.14 0 0 1-1.79 2.71v2.26h2.9c1.7-1.56 2.69-3.87 2.69-6.62Z\" />\n        <path fill=\"#34A853\" d=\"M9 18c2.43 0 4.47-.8 5.96-2.18l-2.9-2.26c-.8.54-1.83.86-3.06.86-2.36 0-4.36-1.59-5.07-3.74H.92v2.33A9 9 0 0 0 9 18Z\" />\n        <path fill=\"#FBBC05\" d=\"M3.93 10.68a5.4 5.4 0 0 1 0-3.36V4.99H.92a9 9 0 0 0 0 8.02l3-2.33Z\" />\n        <path fill=\"#EA4335\" d=\"M9 3.58c1.32 0 2.5.45 3.44 1.34l2.58-2.58A9 9 0 0 0 .92 4.99l3.01 2.33C4.64 5.17 6.64 3.58 9 3.58Z\" />\n      </svg>\n    );\n  }\n  if (provider === 'apple') {\n    return (\n      // viewBox 0 0 24 24 leaves whitespace above/below the path, so visually\n      // the Apple logo looks smaller than Google. We compensate with a larger\n      // width/height — 26×26 gives roughly equal optical size to Google's 20×20.\n      <svg width=\"26\" height=\"26\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n        <path d=\"M17.05 20.28c-.98.95-2.05.8-3.08.35-1.09-.46-2.09-.48-3.24 0-1.44.62-2.2.44-3.06-.35C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8-3.12 1.87-2.38 5.98.48 7.13-.57 1.5-1.31 2.99-2.54 4.09zM12 7.25c-.15-2.23 1.66-4.07 3.74-4.25.29 2.58-2.34 4.5-3.74 4.25z\" />\n      </svg>\n    );\n  }\n  if (provider === 'github') {\n    return (\n      <svg width=\"20\" height=\"20\" viewBox=\"0 0 16 16\" fill=\"currentColor\" aria-hidden=\"true\">\n        <path d=\"M8 0C3.6 0 0 3.6 0 8a8 8 0 0 0 5.5 7.6c.4.1.5-.2.5-.4v-1.5c-2.2.5-2.7-1-2.7-1-.4-.9-.9-1.2-.9-1.2-.7-.5.1-.5.1-.5.8.1 1.2.8 1.2.8.7 1.2 1.9.9 2.4.7 0-.5.3-.9.5-1.1-1.8-.2-3.6-.9-3.6-4 0-.9.3-1.6.8-2.1-.1-.2-.4-1 .1-2.1 0 0 .7-.2 2.2.8a7.6 7.6 0 0 1 4 0c1.5-1 2.2-.8 2.2-.8.4 1.1.2 1.9.1 2.1.5.5.8 1.2.8 2.1 0 3.1-1.9 3.7-3.6 3.9.3.3.6.8.6 1.6V15c0 .2.1.5.6.4A8 8 0 0 0 16 8c0-4.4-3.6-8-8-8Z\" />\n      </svg>\n    );\n  }\n  return (\n    <svg width=\"18\" height=\"20\" viewBox=\"0 0 14 16\" fill=\"currentColor\" aria-hidden=\"true\">\n      <path d=\"M14 2.7C14 1.2 12.8 0 11.3 0H2.7C1.2 0 0 1.2 0 2.7v10.6C0 14.8 1.2 16 2.7 16h4V9.8H4.7v-2H6.7V6.4c0-2 1.2-3.1 3-3.1.9 0 1.7.1 2 .2V5h-1.4c-.8 0-1 .4-1 1v1.5h2.4l-.3 2H9.3V16h2c1.5 0 2.7-1.2 2.7-2.7V2.7Z\" />\n    </svg>\n  );\n}\n\n// The signup confirmation link flow — a mirror of ResetSentView. The prod \"Confirm signup\"\n// email template sends a link (redirect_to → /paywall/v3/auth/confirm), not a code.\n// After clicking the link the user is confirmed on the v3 page, the session syncs\n// cross-tab → the auth gate advances by itself. This screen is \"awaiting confirmation\"\n// + a \"Back to Login\" fallback (email already confirmed → can sign in with a password).\nfunction SignupSentView({\n  email,\n  onBack,\n  t\n}: {\n  email: string;\n  onBack: () => void;\n  t: TFn;\n}) {\n  return (\n    <div class=\"flex flex-col items-center gap-4 py-2 text-center\">\n      <div\n        class=\"flex h-14 w-14 items-center justify-center rounded-full\"\n        style={{\n          background: 'linear-gradient(135deg, #4ade80, #16a34a)',\n          color: '#fff',\n          boxShadow:\n            '0 0 0 8px rgba(74,222,128,0.12), 0 8px 20px -6px rgba(22,163,74,0.45)'\n        }}\n        aria-hidden=\"true\"\n      >\n        <svg width=\"28\" height=\"28\" viewBox=\"0 0 24 24\" fill=\"none\">\n          <path\n            d=\"M5 13l4 4L19 7\"\n            stroke=\"currentColor\"\n            stroke-width=\"2.5\"\n            stroke-linecap=\"round\"\n            stroke-linejoin=\"round\"\n          />\n        </svg>\n      </div>\n\n      <h2 class=\"mt-1 text-3xl font-bold tracking-tight text-gray-900\">\n        {t('auth.check_email_title', 'Check your email')}\n      </h2>\n\n      <p class=\"text-base leading-relaxed text-gray-600\">\n        {t(\n          'auth.signup_sent_subtitle',\n          'We sent a confirmation link to your email. Click it to activate your account — you will be signed in here automatically.'\n        )}\n      </p>\n\n      {email ? (\n        <p class=\"break-all text-base font-semibold text-gray-900\">{email}</p>\n      ) : null}\n\n      <button\n        type=\"button\"\n        onClick={onBack}\n        class=\"pw-cta-shimmer relative mt-2 flex min-h-12 w-full items-center justify-center overflow-hidden rounded-3xl px-5 py-2 text-center text-base font-semibold leading-tight text-white transition-transform duration-150 active:scale-[0.98] focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--pw-accent)]\"\n        style={{\n          background:\n            'linear-gradient(135deg, color-mix(in srgb, var(--pw-accent) 55%, white) 0%, var(--pw-accent) 55%, color-mix(in srgb, var(--pw-accent) 90%, black) 100%)',\n          boxShadow:\n            '0 0 20px 0 color-mix(in srgb, var(--pw-accent) 25%, transparent), inset 0 0 8px 0 color-mix(in srgb, white 25%, transparent)'\n        }}\n      >\n        <span class=\"relative z-10\">\n          {t('auth.back_to_login', 'Back to Login')}\n        </span>\n      </button>\n    </div>\n  );\n}\n\nfunction ResetSentView({\n  email,\n  onBack,\n  t\n}: {\n  email: string;\n  onBack: () => void;\n  t: TFn;\n}) {\n  return (\n    <div class=\"flex flex-col items-center gap-4 py-2 text-center\">\n      <div\n        class=\"flex h-14 w-14 items-center justify-center rounded-full\"\n        style={{\n          background: 'linear-gradient(135deg, #4ade80, #16a34a)',\n          color: '#fff',\n          boxShadow:\n            '0 0 0 8px rgba(74,222,128,0.12), 0 8px 20px -6px rgba(22,163,74,0.45)'\n        }}\n        aria-hidden=\"true\"\n      >\n        <svg width=\"28\" height=\"28\" viewBox=\"0 0 24 24\" fill=\"none\">\n          <path\n            d=\"M5 13l4 4L19 7\"\n            stroke=\"currentColor\"\n            stroke-width=\"2.5\"\n            stroke-linecap=\"round\"\n            stroke-linejoin=\"round\"\n          />\n        </svg>\n      </div>\n\n      <h2 class=\"mt-1 text-3xl font-bold tracking-tight text-gray-900\">\n        {t('auth.check_email_title', 'Check your email')}\n      </h2>\n\n      <p class=\"text-base leading-relaxed text-gray-600\">\n        {t(\n          'auth.reset_sent_subtitle',\n          'We sent a password reset link. Follow the instructions in the email to reset your password.'\n        )}\n      </p>\n\n      {email ? (\n        <p class=\"break-all text-base font-semibold text-gray-900\">{email}</p>\n      ) : null}\n\n      <p class=\"text-sm text-gray-500\">\n        {t('auth.reset_link_valid', 'The link is valid for 1 hour.')}\n      </p>\n\n      <button\n        type=\"button\"\n        onClick={onBack}\n        class=\"pw-cta-shimmer relative mt-2 flex min-h-12 w-full items-center justify-center overflow-hidden rounded-3xl px-5 py-2 text-center text-base font-semibold leading-tight text-white transition-transform duration-150 active:scale-[0.98] focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--pw-accent)]\"\n        style={{\n          background:\n            'linear-gradient(135deg, color-mix(in srgb, var(--pw-accent) 55%, white) 0%, var(--pw-accent) 55%, color-mix(in srgb, var(--pw-accent) 90%, black) 100%)',\n          boxShadow:\n            '0 0 20px 0 color-mix(in srgb, var(--pw-accent) 25%, transparent), inset 0 0 8px 0 color-mix(in srgb, white 25%, transparent)'\n        }}\n      >\n        <span class=\"relative z-10\">\n          {t('auth.back_to_login', 'Back to Login')}\n        </span>\n      </button>\n    </div>\n  );\n}\n","import type { AuthClient, AuthSession, OAuthResumeCheckout } from '../core/auth';\nimport type { LayoutBlock, PaywallBootstrap } from '../core/types';\nimport { AuthPanel } from './renderer/blocks/AuthPanel';\nimport type { BlockContext } from './renderer/types';\nimport { useI18n } from './i18n';\n\ntype AuthPanelBlock = Extract<LayoutBlock, { type: 'auth_panel' }>;\n\n/** The context AuthGate was opened from. Controls the default heading/\n *  subheading so the user immediately understands why they were brought here:\n *  - `restore`  — a \"Restore purchases\" click in current_session\n *  - `preauth`  — checkout_mode=preauth before /start-checkout\n *  - `standalone` — paywall.openAuth() (without any layout context) */\nexport type AuthIntent = 'restore' | 'preauth' | 'standalone';\n\nexport interface AuthGateProps {\n  block: AuthPanelBlock;\n  bootstrap: PaywallBootstrap;\n  auth: AuthClient;\n  authSession: AuthSession | null;\n  onBack: () => void;\n  /** Whether to show the Back button. For preauth/restore flow — true (the\n   *  user came here from the layout). For standalone openAuth() — false: the\n   *  modal is open only for the sake of signin, and ESC plus the modal's X\n   *  already close it. */\n  showBack?: boolean;\n  intent?: AuthIntent;\n  /** Which mode to set in AuthPanel on start. The host called openSignup()\n   *  → 'signup', openSignin()/openAuth() → 'signin' (default). */\n  initialMode?: 'signin' | 'signup';\n  /** The checkout waiting behind this gate, forwarded into OAuth so it can be\n   *  resumed if this surface dies mid-sign-in. */\n  resumeCheckout?: OAuthResumeCheckout;\n}\n\n// Full-screen wrapper over AuthPanel for the AuthGate flow. AuthPanel itself\n// doesn't know about \"back to plans\"; the gate draws a curved-arrow Back button\n// in the top-right (as on the legacy screens) and supplies an intent-specific\n// heading.\nexport function AuthGate({\n  block,\n  bootstrap,\n  auth,\n  authSession,\n  onBack,\n  showBack = true,\n  intent = 'preauth',\n  initialMode,\n  resumeCheckout\n}: AuthGateProps) {\n  const { t } = useI18n();\n  const ctx: BlockContext = {\n    bootstrap,\n    selectedPriceId: null,\n    setSelectedPriceId: () => {},\n    onAction: () => {},\n    auth,\n    authSession,\n    initialAuthMode: initialMode,\n    resumeCheckout\n  };\n\n  // intent overrides the layout block's heading/subheading:\n  //   - 'restore'  → \"Restore Purchases\" / sign-in-to-restore\n  //   - 'preauth'  → \"Log in to continue your purchase\" / link-purchase\n  //   - 'standalone' (paywall.openAuth()) → defaults by mode from AuthPanel\n  // If the admin set a custom heading/subheading in the layout — it's kept only\n  // for the standalone variant (for preauth/restore we know the context better).\n  const effectiveBlock: AuthPanelBlock =\n    intent === 'restore'\n      ? {\n          ...block,\n          heading: t('auth.restore_purchases_heading', 'Restore Purchases'),\n          subheading: t(\n            'auth.restore_purchases_subheading',\n            'Please sign in to restore your purchases.'\n          )\n        }\n      : intent === 'preauth'\n        ? {\n            ...block,\n            heading: t('auth.login_continue_purchase', 'Log in to continue your purchase'),\n            subheading: t(\n              'auth.link_purchase_subheading',\n              \"We'll link the purchase to your account to keep access.\"\n            ),\n            // Preauth heading — a descriptive sentence (\"Log in to continue\n            // your purchase\"), not an action verb. Long localizations (RU:\n            // \"Войдите, чтобы продолжить покупку\") don't fit into the h-12\n            // pill button and wrap onto 2 lines. An explicit short submit_label\n            // solves it.\n            submit_label: t('auth.log_in', 'Sign In')\n          }\n        : block;\n\n  // Padding + overflow-y-auto are delegated here (not to Modal), because the\n  // Modal wrapper is now structurally neutral — Renderer returns its own\n  // sticky-footer layout, while gate-views want a single ordinary scroll zone.\n  return (\n    <div class=\"relative flex-1 min-h-0 overflow-y-auto p-6 sm:p-8\">\n      {showBack ? <BackArrowButton onClick={onBack} ariaLabel={t('nav.back_aria', 'Back')} /> : null}\n      <AuthPanel block={effectiveBlock} ctx={ctx} />\n    </div>\n  );\n}\n\nfunction BackArrowButton({ onClick, ariaLabel }: { onClick: () => void; ariaLabel: string }) {\n  return (\n    <button\n      type=\"button\"\n      onClick={onClick}\n      aria-label={ariaLabel}\n      class=\"absolute right-4 top-4 z-10 flex h-8 w-8 items-center justify-center rounded-full text-gray-400 transition-colors hover:bg-gray-100 hover:text-gray-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--pw-accent)]\"\n    >\n      <svg width=\"18\" height=\"18\" viewBox=\"0 0 20 20\" fill=\"none\" aria-hidden=\"true\">\n        <path\n          d=\"M5 8h8a4 4 0 0 1 0 8H9\"\n          stroke=\"currentColor\"\n          stroke-width=\"1.75\"\n          stroke-linecap=\"round\"\n          stroke-linejoin=\"round\"\n        />\n        <path\n          d=\"M8 4 4 8l4 4\"\n          stroke=\"currentColor\"\n          stroke-width=\"1.75\"\n          stroke-linecap=\"round\"\n          stroke-linejoin=\"round\"\n        />\n      </svg>\n    </button>\n  );\n}\n","import { useEffect, useRef, useState } from 'preact/hooks';\nimport type { LayoutBlock, PaywallOffer } from '../../../core/types';\nimport type { BlockProps } from '../types';\nimport { useI18n, type TFn } from '../../i18n';\n\ntype OfferBannerBlock = Extract<LayoutBlock, { type: 'offer_banner' }>;\n\n// Start storage for relative timers (offer.duration_minutes). The key is\n// tied to offer.id — reopening the paywall does not reset the countdown,\n// and the user cannot \"farm\" the offer banner forever. The key stays in storage\n// even after expiry — it is a forever-marker \"the offer has already started for the user\";\n// without it, reopening after expiry would write a fresh `start` again\n// and the countdown would restart from zero.\nconst STORAGE_KEY = (offerId: string): string => `pw-offer-${offerId}-start`;\n\nexport interface TimeLeft {\n  days: number;\n  hours: number;\n  minutes: number;\n  seconds: number;\n  expired: boolean;\n}\n\nfunction calcTimeLeft(endMs: number): TimeLeft {\n  const distance = endMs - Date.now();\n  if (distance <= 0) {\n    return { days: 0, hours: 0, minutes: 0, seconds: 0, expired: true };\n  }\n  return {\n    days: Math.floor(distance / (1000 * 60 * 60 * 24)),\n    hours: Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)),\n    minutes: Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60)),\n    seconds: Math.floor((distance % (1000 * 60)) / 1000),\n    expired: false\n  };\n}\n\n// Resolves endMs: expires_at (absolute server date) takes priority, otherwise\n// duration_minutes from the first paywall open, stored in localStorage.\n// null — an offer without a timer, no banner needed.\nfunction resolveEndMs(offer: PaywallOffer): number | null {\n  if (offer.expires_at) {\n    const t = Date.parse(offer.expires_at);\n    return Number.isFinite(t) ? t : null;\n  }\n  if (offer.duration_minutes && offer.duration_minutes > 0) {\n    if (typeof window === 'undefined') return null;\n    try {\n      const key = STORAGE_KEY(offer.id);\n      let startIso = window.localStorage.getItem(key);\n      if (!startIso) {\n        startIso = new Date().toISOString();\n        window.localStorage.setItem(key, startIso);\n      }\n      return Date.parse(startIso) + offer.duration_minutes * 60_000;\n    } catch {\n      // Storage unavailable (private mode / SSR) — a relative timer is useless.\n      return null;\n    }\n  }\n  return null;\n}\n\nexport function pickActiveOffer(\n  offers: PaywallOffer[] | undefined,\n  preferredId?: string\n): PaywallOffer | null {\n  if (!offers || offers.length === 0) return null;\n  if (preferredId) {\n    const match = offers.find((o) => o.id === preferredId);\n    if (match) return match;\n  }\n  // The first offer with an active timer. Without a timer the banner makes no sense\n  // (an offer-without-urgency is shown via the PriceGrid discount badge).\n  return offers.find((o) => o.expires_at || o.duration_minutes) ?? null;\n}\n\n/** Hook: ticks every second until the offer is expired. Returns null if the\n *  offer is invalid (no timer). Used both in the layout-block OfferBanner\n *  and in the top-tab OfferTopBanner from PaywallRoot. */\nexport function useOfferCountdown(offer: PaywallOffer | null): TimeLeft | null {\n  const endMs = offer ? resolveEndMs(offer) : null;\n  const [timeLeft, setTimeLeft] = useState<TimeLeft | null>(() =>\n    endMs !== null ? calcTimeLeft(endMs) : null\n  );\n  const endMsRef = useRef(endMs);\n  endMsRef.current = endMs;\n\n  useEffect(() => {\n    if (endMs === null) {\n      setTimeLeft(null);\n      return undefined;\n    }\n    setTimeLeft(calcTimeLeft(endMs));\n    const timer = setInterval(() => {\n      const next = calcTimeLeft(endMsRef.current ?? 0);\n      setTimeLeft(next);\n      // Do NOT delete `pw-offer-<id>-start` on expiry — the key is needed as a\n      // forever-marker \"the offer has already started\"; otherwise re-opening the paywall after\n      // expiry would write a fresh start and the countdown would restart from zero\n      // (offer-farming bug). It is enough to stop the tick.\n      if (next.expired) clearInterval(timer);\n    }, 1000);\n    return () => clearInterval(timer);\n  }, [endMs, offer?.duration_minutes, offer?.id]);\n\n  return timeLeft;\n}\n\nexport function OfferBanner({ block, ctx }: BlockProps<OfferBannerBlock>) {\n  const { t } = useI18n();\n  const offer = pickActiveOffer(ctx.bootstrap.offers, block.offer_id);\n  const timeLeft = useOfferCountdown(offer);\n\n  if (!offer || timeLeft === null) return null;\n  if (timeLeft.expired && !block.force) return null;\n\n  const title = block.title ?? offer.label ?? t('offer.limited_time', 'Limited-time offer');\n  const titleWithDiscount = offer.discount_percent\n    ? `${title} ${offer.discount_percent}%`\n    : title;\n\n  return (\n    <div\n      class=\"flex flex-wrap items-center justify-center gap-2 rounded-2xl px-4 py-3 text-[15px] font-semibold leading-tight text-white\"\n      style={{\n        background:\n          'linear-gradient(135deg, color-mix(in srgb, var(--pw-accent) 55%, white) 0%, var(--pw-accent) 50%, color-mix(in srgb, var(--pw-accent) 85%, black) 100%)',\n        textShadow: '0 0 2px rgba(0, 0, 0, 0.25)'\n      }}\n      role=\"status\"\n    >\n      <FlashIcon />\n      <span>{titleWithDiscount}</span>\n      <Countdown value={timeLeft} t={t} />\n    </div>\n  );\n}\n\nexport function Countdown({ value, t }: { value: TimeLeft; t: TFn }) {\n  return (\n    <div class=\"flex items-center gap-1 font-mono text-sm\">\n      {value.days > 0 ? (\n        <>\n          <Cell>{String(value.days)}</Cell>\n          <span class=\"text-xs\">{t('countdown.d', 'd')}</span>\n        </>\n      ) : null}\n      <Cell>{String(value.hours).padStart(2, '0')}</Cell>\n      <span class=\"text-xs\">{t('countdown.h', 'h')}</span>\n      <Cell>{String(value.minutes).padStart(2, '0')}</Cell>\n      <span class=\"text-xs\">{t('countdown.m', 'm')}</span>\n      <Cell>{String(value.seconds).padStart(2, '0')}</Cell>\n      <span class=\"text-xs\">{t('countdown.s', 's')}</span>\n    </div>\n  );\n}\n\nfunction Cell({ children }: { children: preact.ComponentChildren }) {\n  return (\n    <span class=\"rounded bg-black/20 px-1.5 py-0.5 text-xs font-bold\">\n      {children}\n    </span>\n  );\n}\n\n/** Top-tab variant: sticks to the top of the Modal as a little tab label\n *  (rounded-top, negative margin-bottom for overlap). Mirrors the legacy\n *  PaywallModal:`offer-banner-enter -mb-2 pb-5 rounded-tl-xl rounded-tr-xl`. */\nexport function OfferTopBanner({ offer }: { offer: PaywallOffer }) {\n  const { t } = useI18n();\n  const timeLeft = useOfferCountdown(offer);\n  if (timeLeft === null || timeLeft.expired) return null;\n  const title = offer.label ?? t('offer.limited_time', 'Limited-time offer');\n  const titleWithDiscount = offer.discount_percent\n    ? `${title} ${offer.discount_percent}%`\n    : title;\n  return (\n    <div\n      class=\"-mb-2 flex flex-wrap items-center justify-center gap-2 rounded-t-xl px-4 pb-5 pt-3 text-[15px] font-semibold leading-tight text-white\"\n      style={{\n        background:\n          'linear-gradient(135deg, color-mix(in srgb, var(--pw-accent) 55%, white) 0%, var(--pw-accent) 50%, color-mix(in srgb, var(--pw-accent) 85%, black) 100%)',\n        textShadow: '0 0 2px rgba(0, 0, 0, 0.25)'\n      }}\n      role=\"status\"\n    >\n      <FlashIcon />\n      <span>{titleWithDiscount}</span>\n      <Countdown value={timeLeft} t={t} />\n    </div>\n  );\n}\n\nfunction FlashIcon() {\n  return (\n    <svg\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 12 12\"\n      fill=\"none\"\n      aria-hidden=\"true\"\n    >\n      <path\n        fill=\"currentColor\"\n        d=\"m9.44 5.359-2.394-.895.61-3.036c.062-.31-.345-.531-.57-.291L2.434 6.105a.336.336 0 0 0 .126.537l2.395.894-.61 3.037c-.062.31.345.53.57.29l4.653-4.968a.336.336 0 0 0-.126-.536Z\"\n      />\n    </svg>\n  );\n}\n","import { useRef, useState } from 'preact/hooks';\nimport type { BillingClient } from '../core/BillingClient';\nimport type { AuthSession } from '../core/auth';\nimport { PaywallError } from '../core/types';\nimport { useI18n } from './i18n';\n\nexport interface SupportGateProps {\n  client: BillingClient;\n  authSession: AuthSession | null;\n  // 'standalone' — the modal is open only for support (paywall.openSupport()),\n  // Back/Done close it. 'layout' — arrived from the current_session block,\n  // Back/Done return to the layout (and the paywall with plans stays open).\n  origin: 'layout' | 'standalone';\n  onBack: () => void;\n}\n\nconst SUBJECT_MIN = 3;\nconst SUBJECT_MAX = 200;\nconst CONTENT_MAX = 5000;\nconst MAX_FILES = 5;\n// Keep in sync with the backend route and sdk-extension/shared/support-limits.\nconst MAX_FILE_SIZE_MB = 5;\nconst MAX_FILE_SIZE = MAX_FILE_SIZE_MB * 1024 * 1024;\nconst ACCEPTED_MIME = ['image/jpeg', 'image/png', 'image/webp'];\nconst EMAIL_RE = /.+@.+\\..+/;\n\nexport function SupportGate({ client, authSession, origin, onBack }: SupportGateProps) {\n  const { t } = useI18n();\n  const sessionEmail = authSession?.user.email ?? '';\n  // If there's a session, we lock the email from it; the form doesn't edit it.\n  const lockedEmail = sessionEmail ? sessionEmail : null;\n  const [email, setEmail] = useState<string>(sessionEmail);\n  const [subject, setSubject] = useState('');\n  const [message, setMessage] = useState('');\n  const [files, setFiles] = useState<File[]>([]);\n  const [submitting, setSubmitting] = useState(false);\n  const [submittedEmail, setSubmittedEmail] = useState<string | null>(null);\n  const [errors, setErrors] = useState<{\n    subject?: string;\n    email?: string;\n    message?: string;\n    files?: string;\n    submit?: string;\n  }>({});\n\n  const validate = (): boolean => {\n    const next: typeof errors = {};\n    const e = (lockedEmail ?? email).trim();\n    const s = subject.trim();\n    const m = message.trim();\n    if (!e) next.email = t('support.required', 'Required');\n    else if (!EMAIL_RE.test(e.toLowerCase())) next.email = t('support.invalid_email', 'Invalid email');\n    if (s.length < SUBJECT_MIN || s.length > SUBJECT_MAX) {\n      next.subject = t('support.subject_length', '{min}–{max} characters', {\n        min: SUBJECT_MIN,\n        max: SUBJECT_MAX\n      });\n    }\n    if (m.length < 1 || m.length > CONTENT_MAX) {\n      next.message = t('support.message_length', '{min}–{max} characters', {\n        min: 1,\n        max: CONTENT_MAX\n      });\n    }\n    setErrors(next);\n    return Object.keys(next).length === 0;\n  };\n\n  const onSubmit = async (e: Event): Promise<void> => {\n    e.preventDefault();\n    if (submitting) return;\n    if (!validate()) return;\n    setSubmitting(true);\n    setErrors((prev) => ({ ...prev, submit: undefined }));\n    try {\n      const finalEmail = (lockedEmail ?? email).trim();\n      await client.createSupportTicket({\n        subject: subject.trim(),\n        content: message.trim(),\n        email: finalEmail || undefined,\n        files: files.length > 0 ? files : undefined\n      });\n      setSubmittedEmail(finalEmail);\n    } catch (err) {\n      const msg =\n        err instanceof PaywallError\n          ? err.message || 'Failed to send. Please try again.'\n          : 'Failed to send. Please try again.';\n      setErrors((prev) => ({ ...prev, submit: msg }));\n    } finally {\n      setSubmitting(false);\n    }\n  };\n\n  const resetForm = (): void => {\n    setSubject('');\n    setMessage('');\n    setFiles([]);\n    setErrors({});\n    setSubmittedEmail(null);\n  };\n\n  // Footer-shadow + scroll-area pattern identical to Renderer.tsx — the buttons\n  // are pinned to the bottom of the dialog and remain readable on short viewports\n  // (extension popup ≤600px); only the content above them scrolls.\n  const footerClass = 'flex flex-col gap-3 bg-white px-6 pb-6 pt-3 sm:px-8';\n  const footerStyle = { boxShadow: '0 -4px 12px -4px rgba(15,23,42,0.06)' };\n\n  if (submittedEmail) {\n    return (\n      <div class=\"relative flex-1 min-h-0 flex flex-col\">\n        <div class=\"flex-1 min-h-0 overflow-y-auto flex flex-col items-center gap-4 px-6 pb-3 pt-6 sm:px-8 sm:pb-4 sm:pt-8 text-center\">\n          <div\n            class=\"flex h-14 w-14 items-center justify-center rounded-full\"\n            style={{\n              background:\n                'linear-gradient(135deg, color-mix(in srgb, var(--pw-accent) 85%, white), var(--pw-accent))',\n              color: '#fff',\n              boxShadow:\n                '0 0 0 8px color-mix(in srgb, var(--pw-accent) 12%, transparent), 0 8px 20px -6px color-mix(in srgb, var(--pw-accent) 45%, transparent)'\n            }}\n            aria-hidden=\"true\"\n          >\n            <svg viewBox=\"0 0 24 24\" class=\"h-7 w-7\">\n              <path\n                fill=\"currentColor\"\n                d=\"M12 0a12 12 0 1 0 0 24 12 12 0 0 0 0-24Zm6.93 8.2-6.85 9.29a1.01 1.01 0 0 1-1.43.19L5.76 13.77a1 1 0 1 1 1.25-1.56l4.08 3.26 6.23-8.45a1 1 0 1 1 1.61 1.18Z\"\n              />\n            </svg>\n          </div>\n          <div class=\"text-lg font-semibold tracking-tight text-gray-900\">\n            {t('support.success_heading', 'Request submitted')}\n          </div>\n          <div class=\"max-w-[320px] text-sm leading-relaxed text-gray-500\">\n            {/* We render the email in a separate <b>; prefix-only key — this is\n               enough for languages with the order \"received message will be sent to X\". */}\n            {t(\n              'support.success_message_prefix',\n              \"We've received your message and will respond to\"\n            )}{' '}\n            <b class=\"text-gray-700\">{submittedEmail}</b>.\n          </div>\n        </div>\n        <div class={footerClass} style={footerStyle}>\n          <div class=\"flex items-center justify-center gap-3\">\n            <button\n              type=\"button\"\n              onClick={onBack}\n              class=\"rounded-xl px-3 py-2 text-sm font-medium text-gray-600 transition-colors hover:bg-gray-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--pw-accent)]\"\n            >\n              {origin === 'standalone'\n                ? t('support.done_button', 'Done')\n                : t('nav.back_aria', 'Back')}\n            </button>\n            <button\n              type=\"button\"\n              onClick={resetForm}\n              class=\"flex h-10 items-center justify-center rounded-xl px-4 text-sm font-semibold text-white transition-all hover:-translate-y-px hover:brightness-105 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--pw-accent)]\"\n              style={{\n                background:\n                  'linear-gradient(180deg, color-mix(in srgb, var(--pw-accent) 92%, white), var(--pw-accent))',\n                boxShadow:\n                  '0 1px 2px rgba(15,23,42,0.08), 0 6px 14px -4px color-mix(in srgb, var(--pw-accent) 50%, transparent)'\n              }}\n            >\n              {t('support.send_another', 'Send another request')}\n            </button>\n          </div>\n        </div>\n      </div>\n    );\n  }\n\n  return (\n    <form onSubmit={onSubmit} class=\"relative flex-1 min-h-0 flex flex-col\">\n      <BackArrowButton onClick={onBack} ariaLabel={t('nav.back_aria', 'Back')} />\n      <div class=\"flex-1 min-h-0 overflow-y-auto px-6 pb-3 pt-6 sm:px-8 sm:pb-4 sm:pt-8\">\n        <div class=\"flex flex-col gap-5\">\n          <div class=\"flex flex-col gap-2 pr-10\">\n            <h2 class=\"text-3xl font-bold tracking-tight text-gray-900\">\n              {t('support.heading', 'Support')}\n            </h2>\n            <p class=\"text-base leading-relaxed text-gray-600\">\n              {t('support.instruction', 'Please fill out the form below to submit your support request.')}\n            </p>\n          </div>\n\n          <div class=\"flex flex-col gap-3\">\n            {!lockedEmail ? (\n              <FilledField\n                type=\"email\"\n                placeholder={t('support.email_placeholder', 'Enter your email *')}\n                value={email}\n                onInput={setEmail}\n                error={errors.email}\n                autocomplete=\"email\"\n                required\n              />\n            ) : (\n              <div class=\"rounded-2xl bg-gray-100 px-5 py-3 text-sm text-gray-600\">\n                {t('support.sending_as', 'Sending as')}{' '}\n                <b class=\"font-medium text-gray-900\">{lockedEmail}</b>\n              </div>\n            )}\n            <FilledField\n              type=\"text\"\n              placeholder={t('support.subject_placeholder', 'Enter your subject *')}\n              value={subject}\n              onInput={setSubject}\n              error={errors.subject}\n              required\n            />\n            <FilledTextarea\n              placeholder={t('support.message_placeholder', 'Enter your message *')}\n              value={message}\n              onInput={setMessage}\n              error={errors.message}\n              required\n            />\n            <Dropzone files={files} onChange={setFiles} disabled={submitting} />\n          </div>\n        </div>\n      </div>\n\n      <div class={footerClass} style={footerStyle}>\n        {errors.submit && <p class=\"text-sm text-red-600\">{errors.submit}</p>}\n        <div class=\"flex items-center justify-end gap-3\">\n          <button\n            type=\"button\"\n            onClick={onBack}\n            disabled={submitting}\n            class=\"rounded-full px-4 py-2 text-base font-medium text-gray-700 transition-colors hover:bg-gray-100 disabled:cursor-not-allowed disabled:opacity-60 focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--pw-accent)]\"\n          >\n            {origin === 'standalone'\n              ? t('support.close_button', 'Close')\n              : t('nav.back_aria', 'Back')}\n          </button>\n          <button\n            type=\"submit\"\n            disabled={submitting}\n            class=\"pw-cta-shimmer relative flex h-12 items-center justify-center overflow-hidden rounded-full px-8 text-base font-semibold text-white transition-transform duration-150 active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-60 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--pw-accent)]\"\n            style={{\n              background:\n                'linear-gradient(135deg, color-mix(in srgb, var(--pw-accent) 55%, white) 0%, var(--pw-accent) 55%, color-mix(in srgb, var(--pw-accent) 90%, black) 100%)',\n              boxShadow:\n                '0 0 20px 0 color-mix(in srgb, var(--pw-accent) 25%, transparent), inset 0 0 8px 0 color-mix(in srgb, white 25%, transparent)'\n            }}\n          >\n            {submitting ? (\n              <span class=\"relative z-10 inline-block h-4 w-4 animate-spin rounded-full border-2 border-white/40 border-t-white\" />\n            ) : (\n              <span class=\"relative z-10\">{t('support.send_button', 'Send')}</span>\n            )}\n          </button>\n        </div>\n      </div>\n    </form>\n  );\n}\n\nfunction BackArrowButton({ onClick, ariaLabel }: { onClick: () => void; ariaLabel: string }) {\n  return (\n    <button\n      type=\"button\"\n      onClick={onClick}\n      aria-label={ariaLabel}\n      class=\"absolute right-4 top-4 z-10 flex h-8 w-8 items-center justify-center rounded-full text-gray-400 transition-colors hover:bg-gray-100 hover:text-gray-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--pw-accent)]\"\n    >\n      <svg width=\"18\" height=\"18\" viewBox=\"0 0 20 20\" fill=\"none\" aria-hidden=\"true\">\n        <path\n          d=\"M5 8h8a4 4 0 0 1 0 8H9\"\n          stroke=\"currentColor\"\n          stroke-width=\"1.75\"\n          stroke-linecap=\"round\"\n          stroke-linejoin=\"round\"\n        />\n        <path\n          d=\"M8 4 4 8l4 4\"\n          stroke=\"currentColor\"\n          stroke-width=\"1.75\"\n          stroke-linecap=\"round\"\n          stroke-linejoin=\"round\"\n        />\n      </svg>\n    </button>\n  );\n}\n\ninterface FilledFieldProps {\n  type: 'email' | 'text';\n  placeholder: string;\n  value: string;\n  onInput: (v: string) => void;\n  error?: string;\n  autocomplete?: string;\n  required?: boolean;\n}\n\nfunction FilledField({\n  type,\n  placeholder,\n  value,\n  onInput,\n  error,\n  autocomplete,\n  required\n}: FilledFieldProps) {\n  return (\n    <div>\n      <input\n        type={type}\n        value={value}\n        placeholder={placeholder}\n        onInput={(e) => onInput((e.target as HTMLInputElement).value)}\n        autocomplete={autocomplete}\n        required={required}\n        class={`h-14 w-full rounded-2xl bg-gray-100 px-5 text-base text-gray-900 outline-none transition-all placeholder:text-gray-500 hover:bg-gray-200/60 focus:bg-gray-200/60 ${\n          error\n            ? 'shadow-[0_0_0_2px_rgba(239,68,68,0.5)]'\n            : 'focus:shadow-[0_0_0_2px_color-mix(in_srgb,var(--pw-accent)_30%,transparent)]'\n        }`}\n      />\n      {error && <span class=\"mt-1 ml-2 block text-sm text-red-600\">{error}</span>}\n    </div>\n  );\n}\n\ninterface FilledTextareaProps {\n  placeholder: string;\n  value: string;\n  onInput: (v: string) => void;\n  error?: string;\n  required?: boolean;\n}\n\nfunction FilledTextarea({\n  placeholder,\n  value,\n  onInput,\n  error,\n  required\n}: FilledTextareaProps) {\n  return (\n    <div>\n      <textarea\n        value={value}\n        placeholder={placeholder}\n        onInput={(e) => onInput((e.target as HTMLTextAreaElement).value)}\n        required={required}\n        rows={5}\n        class={`min-h-[120px] w-full rounded-2xl bg-gray-100 px-5 py-3.5 text-base leading-relaxed text-gray-900 outline-none transition-all placeholder:text-gray-500 hover:bg-gray-200/60 focus:bg-gray-200/60 ${\n          error\n            ? 'shadow-[0_0_0_2px_rgba(239,68,68,0.5)]'\n            : 'focus:shadow-[0_0_0_2px_color-mix(in_srgb,var(--pw-accent)_30%,transparent)]'\n        }`}\n      />\n      {error && <span class=\"mt-1 ml-2 block text-sm text-red-600\">{error}</span>}\n    </div>\n  );\n}\n\ninterface DropzoneProps {\n  files: File[];\n  onChange: (next: File[]) => void;\n  disabled?: boolean;\n}\n\nfunction Dropzone({ files, onChange, disabled }: DropzoneProps) {\n  const { t } = useI18n();\n  const inputRef = useRef<HTMLInputElement | null>(null);\n  const [dragOver, setDragOver] = useState(false);\n  const [error, setError] = useState<string | null>(null);\n\n  const handleFiles = (incoming: FileList | null): void => {\n    if (!incoming || disabled) return;\n    setError(null);\n    const arr = Array.from(incoming);\n    if (files.length + arr.length > MAX_FILES) {\n      setError(t('support.too_many_files', 'Up to {max} files', { max: MAX_FILES }));\n      return;\n    }\n    const valid = arr.filter(\n      (f) => ACCEPTED_MIME.includes(f.type) && f.size <= MAX_FILE_SIZE\n    );\n    if (valid.length !== arr.length) {\n      setError(\n        t('support.invalid_file', 'Only JPEG/PNG/WebP, ≤ {size}MB each', {\n          size: MAX_FILE_SIZE_MB\n        })\n      );\n      return;\n    }\n    onChange([...files, ...valid]);\n  };\n\n  return (\n    <div>\n      <span class=\"text-xs font-medium text-gray-700\">\n        {t('support.attachments_label', 'Attachments (optional)')}\n      </span>\n      <div\n        role=\"button\"\n        tabIndex={0}\n        aria-label={t('support.attachments_aria', 'Attachments upload')}\n        onClick={() => !disabled && inputRef.current?.click()}\n        onDragOver={(e) => {\n          e.preventDefault();\n          if (!disabled) setDragOver(true);\n        }}\n        onDragLeave={() => setDragOver(false)}\n        onDrop={(e) => {\n          e.preventDefault();\n          setDragOver(false);\n          handleFiles(e.dataTransfer?.files ?? null);\n        }}\n        class={`mt-1.5 cursor-pointer rounded-2xl border border-dashed p-3.5 text-center transition-all ${\n          dragOver\n            ? 'border-[var(--pw-accent)] bg-[color-mix(in_srgb,var(--pw-accent)_6%,white)]'\n            : 'border-gray-300 hover:border-gray-400 hover:bg-gray-50/60'\n        } ${disabled ? 'cursor-not-allowed opacity-60' : ''}`}\n      >\n        <div class=\"text-xs text-gray-500\">\n          {t('support.dropzone_text', 'Drop images here or click to select')}\n        </div>\n        <div class=\"mt-0.5 text-[11px] text-gray-400\">\n          {t('support.file_requirements', 'JPEG/PNG/WebP, up to {max} files, ≤ {size}MB each', {\n            max: MAX_FILES,\n            size: MAX_FILE_SIZE_MB\n          })}\n        </div>\n      </div>\n      <input\n        ref={inputRef}\n        type=\"file\"\n        multiple\n        accept={ACCEPTED_MIME.join(',')}\n        class=\"hidden\"\n        onChange={(e) => {\n          handleFiles((e.target as HTMLInputElement).files);\n          (e.currentTarget as HTMLInputElement).value = '';\n        }}\n      />\n      {error && <p class=\"mt-1 text-xs text-red-600\">{error}</p>}\n      {files.length > 0 && (\n        <ul class=\"mt-2 flex flex-col gap-1\">\n          {files.map((f, i) => (\n            <li\n              key={`${f.name}-${f.size}-${i}`}\n              class=\"flex items-center justify-between gap-2 rounded bg-gray-50 px-2 py-1 text-xs\"\n            >\n              <span class=\"truncate text-gray-700\">{f.name}</span>\n              <button\n                type=\"button\"\n                onClick={() => {\n                  const next = [...files];\n                  next.splice(i, 1);\n                  onChange(next);\n                }}\n                disabled={disabled}\n                class=\"text-gray-500 hover:text-red-600 disabled:cursor-not-allowed disabled:opacity-60\"\n                aria-label={t('support.remove_file_aria', 'Remove {filename}', { filename: f.name })}\n              >\n                ✕\n              </button>\n            </li>\n          ))}\n        </ul>\n      )}\n    </div>\n  );\n}\n","import { useState } from 'preact/hooks';\nimport type { LayoutBlock, PaywallPrice } from '../../../core/types';\nimport type { BlockProps } from '../types';\nimport { useI18n, type TFn } from '../../i18n';\n\ntype CtaBlock = Extract<LayoutBlock, { type: 'cta_button' }>;\n\n// Plan keys for \"Get X Plan\". If the interval is a known constant,\n// we take the dedicated key (which gives the translator the correct gender/case for each\n// interval). For exotic ones like day/half-year we fall back to the generic with\n// {interval} substitution — it looks slightly worse grammatically in RU/DE, but\n// we don't lose the interval in the UI.\nconst INTERVAL_PLAN_KEY: Record<string, string> = {\n  day: 'cta.get_plan_daily',\n  week: 'cta.get_plan_weekly',\n  month: 'cta.get_plan_monthly',\n  year: 'cta.get_plan_yearly'\n};\nconst INTERVAL_PLAN_FALLBACK: Record<string, string> = {\n  day: 'Get Daily Plan',\n  week: 'Get Weekly Plan',\n  month: 'Get Monthly Plan',\n  year: 'Get Yearly Plan'\n};\n\n// Plan-aware label following the legacy logic from online/PaywallPricing.tsx:\n//   - trial_days > 0, interval !== 'lifetime', user hasn't taken a trial yet →\n//     \"Start N-Day Free Trial\"\n//   - interval === 'lifetime' → \"Get Lifetime Access\"\n//   - otherwise → \"Get {Interval} Plan\"\n// `hadPreviousTrial` suppresses the trial branch — anti-abuse: a user can take a\n// trial on a paywall only once. Server-side enforcement in\n// /start-checkout (utils/checkout-with-acquiring.ts) duplicates this.\nfunction dynamicLabel(\n  price: PaywallPrice | null,\n  action: CtaBlock['action'],\n  hadPreviousTrial: boolean,\n  t: TFn\n): string {\n  if (action === 'close') return t('cta.close', 'Close');\n  if (!price) return t('cta.continue', 'Continue');\n  if (\n    !hadPreviousTrial &&\n    price.trial_days &&\n    price.interval &&\n    price.interval !== 'lifetime'\n  ) {\n    return t('cta.start_trial', 'Start {days}-Day Free Trial', { days: price.trial_days });\n  }\n  if (!price.interval || price.interval === 'lifetime') {\n    return t('cta.get_lifetime_access', 'Get Lifetime Access');\n  }\n  const dedicatedKey = INTERVAL_PLAN_KEY[price.interval];\n  if (dedicatedKey) {\n    return t(dedicatedKey, INTERVAL_PLAN_FALLBACK[price.interval]);\n  }\n  return t('cta.get_plan_generic', 'Get {interval} Plan', {\n    interval: capitalize(price.interval)\n  });\n}\n\nfunction capitalize(s: string): string {\n  return s.length ? s[0].toUpperCase() + s.slice(1) : s;\n}\n\nexport function CtaButton({ block, ctx }: BlockProps<CtaBlock>) {\n  const { t } = useI18n();\n  const [busy, setBusy] = useState(false);\n  const priceId = block.priceId ?? ctx.selectedPriceId;\n  const disabled = busy || (block.action === 'checkout' && !priceId);\n\n  const selectedPrice = priceId\n    ? ctx.bootstrap.prices.find((p) => p.id === priceId) ?? null\n    : null;\n  // `had_previous_trial` comes from the bootstrap.user snapshot. This means that\n  // after signin via the preauth flow (the user was a guest at bootstrap time)\n  // the flag stays false until the next bootstrap-revalidate; the UI will briefly\n  // show \"Start Free Trial\", but the server-side enforcement in /start-checkout\n  // will still create a checkout without a trial — anti-abuse isn't violated.\n  const hadPreviousTrial = ctx.bootstrap.user?.had_previous_trial ?? false;\n  const label =\n    block.label ?? dynamicLabel(selectedPrice, block.action, hadPreviousTrial, t);\n\n  const onClick = async () => {\n    if (disabled) return;\n    setBusy(true);\n    try {\n      await ctx.onAction(block.action, { priceId });\n    } finally {\n      setBusy(false);\n    }\n  };\n\n  return (\n    <button\n      type=\"button\"\n      disabled={disabled}\n      onClick={onClick}\n      class=\"pw-cta-shimmer relative flex min-h-12 w-full items-center justify-center overflow-hidden rounded-3xl px-5 py-2 text-center text-base font-semibold leading-tight text-white transition-transform duration-150 active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-60 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--pw-accent)]\"\n      style={{\n        background:\n          'linear-gradient(135deg, color-mix(in srgb, var(--pw-accent) 55%, white) 0%, var(--pw-accent) 55%, color-mix(in srgb, var(--pw-accent) 90%, black) 100%)',\n        boxShadow:\n          '0 0 20px 0 color-mix(in srgb, var(--pw-accent) 25%, transparent), inset 0 0 8px 0 color-mix(in srgb, white 25%, transparent)'\n      }}\n    >\n      <span\n        class=\"absolute inset-0 opacity-40\"\n        style={{\n          background:\n            'radial-gradient(circle at 50% 0%, color-mix(in srgb, white 40%, transparent) 0%, transparent 70%)'\n        }}\n        aria-hidden=\"true\"\n      />\n      {busy ? (\n        <span class=\"relative z-10 inline-block h-4 w-4 animate-spin rounded-full border-2 border-white/40 border-t-white\" />\n      ) : (\n        <span class=\"relative z-10\">{label}</span>\n      )}\n    </button>\n  );\n}\n","import type { ComponentChildren } from 'preact';\nimport { useState } from 'preact/hooks';\nimport type { LayoutBlock } from '../../../core/types';\nimport type { BlockProps } from '../types';\nimport { useI18n } from '../../i18n';\n\ntype CurrentSessionBlock = Extract<LayoutBlock, { type: 'current_session' }>;\n\n// Footer below cta_button. Mirrors legacy v2 PaywallCurrentSession:\n//   - signed in → \"Signed in as <email>\" + Sign out (calls auth.signOut())\n//                + Contact Support\n//   - guest     → \"Restore purchases\" + Contact Support\n// Without an AuthClient in managed mode we render only Restore + Support\n// (there is nothing to sign out, and restore without an auth client is a no-op in handleAction).\n// An anonymous session (is_anonymous=true) is treated as \"not signed in\": the anon\n// exists only for the api-gateway token, the user has no email and \"Signed in\" makes no UX sense.\nexport function CurrentSession({ ctx }: BlockProps<CurrentSessionBlock>) {\n  const { t } = useI18n();\n  const session = ctx.authSession;\n  const auth = ctx.auth;\n  const [signingOut, setSigningOut] = useState(false);\n\n  const onSupport = (): void => ctx.onAction('support');\n\n  if (session && !session.user.is_anonymous) {\n    const onSignOut = async (): Promise<void> => {\n      if (!auth || signingOut) return;\n      setSigningOut(true);\n      try {\n        await auth.signOut();\n      } catch {\n        /* signOut errors are silent — onAuthChange will fire anyway on refresh-fail */\n      } finally {\n        setSigningOut(false);\n      }\n    };\n\n    // \"Signed in as <email>\" — rendered manually from two parts so the email\n    // stays in bold markup. The {email} placeholder in the locale is ignored —\n    // the string is shown before the email, and the b-tag with the email comes after.\n    return (\n      <div class=\"-mt-3 flex flex-col items-center gap-1.5 pt-1 text-center text-[13px] text-gray-500\">\n        <span>\n          {t('session.signed_in_as_prefix', 'Signed in as')}{' '}\n          <b class=\"font-medium text-gray-700\">{session.user.email}</b>\n        </span>\n        <div class=\"flex items-center justify-center gap-3\">\n          <AccentLink onClick={onSignOut} disabled={!auth || signingOut}>\n            {signingOut\n              ? t('session.signing_out', 'Signing out…')\n              : t('session.sign_out', 'Sign Out')}\n          </AccentLink>\n          <Dot />\n          <AccentLink onClick={onSupport}>\n            {t('session.contact_support', 'Contact Support')}\n          </AccentLink>\n        </div>\n      </div>\n    );\n  }\n\n  return (\n    <div class=\"-mt-3 flex items-center justify-center gap-3 pt-1 text-center text-[13px]\">\n      <AccentLink onClick={() => ctx.onAction('restore')}>\n        {t('session.restore_purchases', 'Restore purchases')}\n      </AccentLink>\n      <Dot />\n      <AccentLink onClick={onSupport}>{t('session.contact_support', 'Contact Support')}</AccentLink>\n    </div>\n  );\n}\n\nfunction AccentLink({\n  onClick,\n  disabled,\n  children\n}: {\n  onClick: () => void;\n  disabled?: boolean;\n  children: ComponentChildren;\n}) {\n  return (\n    <button\n      type=\"button\"\n      onClick={onClick}\n      disabled={disabled}\n      class=\"font-semibold transition-opacity hover:opacity-80 disabled:cursor-not-allowed disabled:opacity-60 focus:outline-none focus-visible:opacity-80\"\n      style={{ color: 'var(--pw-accent)' }}\n    >\n      {children}\n    </button>\n  );\n}\n\nfunction Dot() {\n  return <span class=\"h-1 w-1 rounded-full bg-gray-300\" aria-hidden=\"true\" />;\n}\n","import type { LayoutBlock } from '../../../core/types';\nimport type { BlockProps } from '../types';\n\ntype FeaturesListBlock = Extract<LayoutBlock, { type: 'features_list' }>;\n\nexport function FeaturesList({ block }: BlockProps<FeaturesListBlock>) {\n  if (!block.items.length) return null;\n  return (\n    <ul class=\"flex flex-col gap-2.5\" role=\"list\">\n      {block.items.map((item) => (\n        <li key={item.id} class=\"flex items-start gap-3 text-sm text-gray-700\">\n          <svg\n            width=\"18\"\n            height=\"18\"\n            viewBox=\"0 0 20 20\"\n            fill=\"none\"\n            class=\"mt-0.5 flex-shrink-0 text-emerald-500\"\n            aria-hidden=\"true\"\n          >\n            <path\n              d=\"M4 10.5l3.5 3.5 8.5-8.5\"\n              stroke=\"currentColor\"\n              stroke-width=\"2.5\"\n              stroke-linecap=\"round\"\n              stroke-linejoin=\"round\"\n            />\n          </svg>\n          <div class=\"flex flex-col gap-0.5\">\n            <span class=\"font-medium leading-snug text-gray-900\">{item.name}</span>\n            {item.desc ? (\n              <span class=\"text-xs leading-relaxed text-gray-400\">{item.desc}</span>\n            ) : null}\n          </div>\n        </li>\n      ))}\n    </ul>\n  );\n}\n","import type { LayoutBlock } from '../../../core/types';\nimport type { BlockProps } from '../types';\nimport { useI18n } from '../../i18n';\n\ntype GuaranteeBlock = Extract<LayoutBlock, { type: 'guarantee_badge' }>;\n\n// Money-back guarantee pill below the CtaButton. A compact one-liner: shield-check\n// icon + text. The pill styling (rounded-full + bg-gray-100) visually separates it\n// from the CTA without drawing attention to itself — it is a reassurance element.\n// The subtitle is dropped from the default render: users scan a purchase\n// quickly, and a second line is just noise. If the admin sets block.subtitle\n// explicitly, it is rendered in small gray below the pill.\nexport function GuaranteeBadge({ block }: BlockProps<GuaranteeBlock>) {\n  const { t } = useI18n();\n  const title = block.title ?? t('pricing.money_back', '30-day money-back guarantee');\n  const subtitle = block.subtitle;\n  const showIcon = (block.icon ?? 'dollar_shield') !== 'none';\n\n  // Highlight the \"N-day\" prefix in bold/dark — it is the key info (the period),\n  // the rest in normal weight. The eye catches the number right away instead of a flat block.\n  const parts = splitDaysPrefix(title);\n\n  return (\n    <div class=\"flex flex-col items-center gap-1.5 border-b-1 pb-4 mb-1 border-gray-100\">\n      <div class=\"inline-flex items-center gap-2 text-[12px] text-gray-700\">\n        {showIcon ? <ShieldCheckIcon /> : null}\n        {parts ? (\n          <span>\n            <b class=\"font-bold text-gray-900\">{parts.bold}</b>{' '}\n            <span class=\"font-medium\">{parts.rest}</span>\n          </span>\n        ) : (\n          <span class=\"font-medium\">{title}</span>\n        )}\n      </div>\n      {subtitle ? (\n        <span class=\"text-center text-xs leading-relaxed text-gray-500\">{subtitle}</span>\n      ) : null}\n    </div>\n  );\n}\n\nfunction splitDaysPrefix(title: string): { bold: string; rest: string } | null {\n  const m = title.match(/^(\\d+[-\\s]?days?)\\s+(.+)$/i);\n  if (!m) return null;\n  return { bold: m[1], rest: m[2] };\n}\n\nfunction ShieldCheckIcon() {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      width=\"16\"\n      height=\"16\"\n      // emerald-500 — semantic \"safety/refund\", raises the contrast of the\n      // reassurance signal. Gray gray-500 was skipped over by the eye.\n      class=\"flex-shrink-0 text-emerald-500\"\n      aria-hidden=\"true\"\n    >\n      <path\n        d=\"M12 2 4 5v6c0 5.25 3.5 9.5 8 11 4.5-1.5 8-5.75 8-11V5l-8-3Z\"\n        stroke=\"currentColor\"\n        stroke-width=\"2\"\n        stroke-linejoin=\"round\"\n      />\n      <path\n        d=\"m9 12 2 2 4-4\"\n        stroke=\"currentColor\"\n        stroke-width=\"2\"\n        stroke-linecap=\"round\"\n        stroke-linejoin=\"round\"\n      />\n    </svg>\n  );\n}\n","import { useEffect, useRef } from 'preact/hooks';\nimport type { LayoutBlock } from '../../../core/types';\nimport type { BlockProps } from '../types';\n\ntype HeadingBlock = Extract<LayoutBlock, { type: 'heading' }>;\n\nconst BASE_FONT_PX = 24; // matches sm:text-2xl on h1\nconst MIN_FONT_PX = 16;\nconst MAX_LINES = 2;\n\n// Auto-fit: if the heading does not fit within `MAX_LINES` lines at the base size,\n// we shrink the font-size by 1px steps until it fits or we hit\n// `MIN_FONT_PX`. Used only for h1 — h2/h3 are subheadings and don't need\n// clipping. We measure by the element's real height (scrollHeight) after\n// render — otherwise we'd have to keep a canvas-based measurer.\nfunction fitHeading(el: HTMLElement, lineHeight: number): void {\n  const maxHeight = lineHeight * MAX_LINES;\n  let size = BASE_FONT_PX;\n  el.style.fontSize = `${size}px`;\n  while (el.scrollHeight > maxHeight && size > MIN_FONT_PX) {\n    size -= 1;\n    el.style.fontSize = `${size}px`;\n  }\n}\n\nexport function Heading({ block, ctx }: BlockProps<HeadingBlock>) {\n  const level = block.level ?? 1;\n  const Tag = (`h${level}` as 'h1' | 'h2' | 'h3');\n  const className =\n    level === 1\n      ? 'text-[22px] sm:text-2xl font-semibold leading-tight text-center text-balance text-gray-800'\n      : level === 2\n        ? 'text-xl font-semibold leading-snug text-gray-900 tracking-tight'\n        : 'text-base font-medium text-gray-900';\n\n  const ref = useRef<HTMLHeadingElement | null>(null);\n  const autoFit = level === 1 && !!ctx.bootstrap.settings.title_auto_fit;\n\n  useEffect(() => {\n    if (!autoFit || !ref.current) return;\n    // line-height on text-2xl = 1.5 (Tailwind default). We compute from the current\n    // computed line-height — robust to future CSS changes.\n    const cs = getComputedStyle(ref.current);\n    const lh = parseFloat(cs.lineHeight) || BASE_FONT_PX * 1.5;\n    fitHeading(ref.current, lh);\n  }, [autoFit, block.text]);\n\n  return (\n    <Tag ref={ref} class={className}>\n      {block.text}\n    </Tag>\n  );\n}\n","import type { LayoutBlock, PaywallOffer, PaywallPrice } from '../../../core/types';\nimport { findLiveOffer, readBrowserOfferStart } from '../../../core/offer';\nimport type { BlockProps } from '../types';\nimport { useI18n, type TFn } from '../../i18n';\n\ntype PriceGridBlock = Extract<LayoutBlock, { type: 'price_grid' }>;\n\ninterface FormattedPrice {\n  /** Currency symbol (or ISO code if the symbol could not be resolved). */\n  currency: string;\n  /** Integer part, without fractional separators. */\n  amount: string;\n  /** Original (without discount), formatted — for strike-through. null if\n   *  there is no discount or discount=0%. */\n  originalAmount: string | null;\n}\n\n// The year plan shows the per-month equivalent right in the main price:\n//   YEARLY PLAN €4.99 / month   (instead of €59.99 / year)\n// This is legacy UX from online/PaywallPricing.tsx (`unit_amount / 12`):\n// the user cares about the monthly cost to compare with the monthly plan, while the yearly\n// charge is a detail that should not dominate the typography. planLabel\n// stays \"YEARLY PLAN\", so the billed cadence is still clear from the\n// name.\nfunction displayedAmount(price: PaywallPrice): { amount: number; currency: string } {\n  const display = price.local ?? { currency: price.currency, amount: price.amount };\n  if (price.interval === 'year') {\n    const months = (price.interval_count ?? 1) * 12;\n    return { amount: display.amount / months, currency: display.currency };\n  }\n  return { amount: display.amount, currency: display.currency };\n}\n\n// Formats a number into a currency string without literals, splitting the currency symbol\n// from the numeric part. Used both for the main price and for the strike-through\n// original (in which case no discount needs to be applied — the value is already base).\n// Fractional part is automatic: integer → \"$8\", non-integer → \"$4.99\". Integers without .00 convert\n// better — the eye catches a short number faster.\nfunction formatCurrencyParts(value: number, currency: string): {\n  currency: string;\n  amount: string;\n} {\n  const minFrac = value % 1 !== 0 ? 2 : 0;\n  try {\n    const parts = new Intl.NumberFormat(undefined, {\n      style: 'currency',\n      currency,\n      currencyDisplay: 'narrowSymbol',\n      maximumFractionDigits: minFrac,\n      minimumFractionDigits: minFrac\n    }).formatToParts(value);\n    let cur = '';\n    let amount = '';\n    for (const part of parts) {\n      if (part.type === 'currency') {\n        cur = part.value;\n      } else if (part.type !== 'literal') {\n        amount += part.value;\n      }\n    }\n    return { currency: cur || currency, amount: amount.trim() };\n  } catch {\n    return { currency, amount: String(value) };\n  }\n}\n\nfunction formatPriceParts(price: PaywallPrice, discountPercent: number | null): FormattedPrice {\n  const { amount: base, currency: cur } = displayedAmount(price);\n  if (!discountPercent) {\n    const { currency, amount } = formatCurrencyParts(base, cur);\n    return { currency, amount, originalAmount: null };\n  }\n  const discounted = base * (1 - discountPercent / 100);\n  const main = formatCurrencyParts(discounted, cur);\n  const original = formatCurrencyParts(base, cur);\n  // We show the strike-through in full (`€59.99`/`€9.99` — with the currency sign),\n  // so the user immediately sees the old price in the same currency, no guessing.\n  return {\n    currency: main.currency,\n    amount: main.amount,\n    originalAmount: `${original.currency}${original.amount}`\n  };\n}\n\n// Selecting the active offer is extracted into `core/offer.ts:findLiveOffer` —\n// an expiry-aware wrapper over findApplicableOffer (it drops expired offers,\n// so the strike-through/discount disappear in sync with the countdown banner).\n\nfunction planLabel(price: PaywallPrice, t: TFn): string {\n  if (price.label) return price.label.toUpperCase();\n  if (!price.interval || price.interval === 'lifetime') {\n    return t('pricing.plan_label.lifetime', 'LIFETIME');\n  }\n  const map: Record<string, { key: string; fallback: string }> = {\n    day: { key: 'pricing.plan_label.daily', fallback: 'DAILY PLAN' },\n    week: { key: 'pricing.plan_label.weekly', fallback: 'WEEKLY PLAN' },\n    month: { key: 'pricing.plan_label.monthly', fallback: 'MONTHLY PLAN' },\n    year: { key: 'pricing.plan_label.yearly', fallback: 'YEARLY PLAN' }\n  };\n  const entry = map[price.interval];\n  if (entry) return t(entry.key, entry.fallback);\n  return `${price.interval.toUpperCase()} PLAN`;\n}\n\n// Suffix after the price. Year → \"month\" (because amount is already /12, see\n// displayedAmount). Lifetime → \"lifetime\". Everything else — the singular interval\n// or \"N intervals\" for interval_count > 1.\nfunction intervalSuffix(price: PaywallPrice, t: TFn): string {\n  if (!price.interval || price.interval === 'lifetime') {\n    return t('pricing.interval.lifetime_short', 'lifetime');\n  }\n  if (price.interval === 'year') return t('pricing.interval.month', 'month');\n  const n = price.interval_count ?? 1;\n  if (n === 1) return t(`pricing.interval.${price.interval}`, price.interval);\n  return `${n} ${price.interval}s`;\n}\n\nexport function PriceGrid({ block, ctx }: BlockProps<PriceGridBlock>) {\n  const { t } = useI18n();\n  const filter = block.priceIds && block.priceIds.length > 0 ? new Set(block.priceIds) : null;\n  const prices = ctx.bootstrap.prices.filter((p) => !filter || filter.has(p.id));\n\n  if (prices.length === 0) {\n    return <p class=\"text-sm text-gray-500\">{t('pricing.no_prices', 'No prices available.')}</p>;\n  }\n\n  const popularLabel = block.popular_label ?? t('pricing.most_popular', 'Most popular');\n\n  // Compact mode — a telegram-style list: a thin backing card around\n  // all rows (rounded-xl + light bg + 1px border). Dividers between\n  // rows are `border-b` on the inner label-wrapper of CompactRow (except\n  // the last). Mirrors the legacy PaywallPricing wrapper: for a non-default view\n  // it draws `rounded-xl border-1 border-default-200 bg-default-50` —\n  // separating the price block from the rest of the layout.\n  // The v2 storage key is `view: 'telegram'`, bootstrap normalizes it to 'compact'.\n  if (block.view === 'compact') {\n    return (\n      <div\n        class=\"flex w-full flex-col rounded-xl border border-gray-200 bg-gray-50\"\n        role=\"radiogroup\"\n        aria-label={t('pricing.plans_aria', 'Plans')}\n      >\n        {prices.map((price, idx) => (\n          <CompactRow\n            key={price.id}\n            price={price}\n            isLast={idx === prices.length - 1}\n            isPopular={block.popular_price_id === price.id}\n            popularLabel={popularLabel}\n            offer={findLiveOffer(ctx.bootstrap.offers, price.id, { readStart: readBrowserOfferStart })}\n            selected={ctx.selectedPriceId === price.id}\n            onSelect={() => {\n              ctx.setSelectedPriceId(price.id);\n              ctx.onAction('price_selected', { priceId: price.id, price });\n            }}\n            t={t}\n          />\n        ))}\n      </div>\n    );\n  }\n\n  // Horizontal mode — a real grid of side-by-side cards. The v2 storage key\n  // `view: 'row'` (SDK 3.0 only — old legacy paywalls don't select this\n  // key; bootstrap normalizes it to 'horizontal'). max 3 columns; with 1-2\n  // prices they stretch the row. Tailwind purge does not survive a runtime grid-cols-N,\n  // hence the inline gridTemplateColumns.\n  if (block.view === 'horizontal') {\n    const cols = Math.min(prices.length, 3);\n    // If at least one price in the grid has a discount, we reserve a strike-row\n    // of fixed height in ALL cards (otherwise the main amount without a discount\n    // jumps above its discounted neighbors). If there is no offer at all, the strike-row\n    // collapses to 0 in all of them, and there's no 22px of empty space hanging under the label.\n    const anyHasDiscount = prices.some(\n      (p) => (findLiveOffer(ctx.bootstrap.offers, p.id, { readStart: readBrowserOfferStart })?.discount_percent ?? 0) > 0\n    );\n    return (\n      <div\n        class=\"grid items-stretch gap-2\"\n        style={{ gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))` }}\n        role=\"radiogroup\"\n        aria-label={t('pricing.plans_aria', 'Plans')}\n      >\n        {prices.map((price) => (\n          <RowCard\n            key={price.id}\n            price={price}\n            isPopular={block.popular_price_id === price.id}\n            popularLabel={popularLabel}\n            offer={findLiveOffer(ctx.bootstrap.offers, price.id, { readStart: readBrowserOfferStart })}\n            reserveStrikeRow={anyHasDiscount}\n            selected={ctx.selectedPriceId === price.id}\n            onSelect={() => {\n              ctx.setSelectedPriceId(price.id);\n              ctx.onAction('price_selected', { priceId: price.id, price });\n            }}\n            t={t}\n          />\n        ))}\n      </div>\n    );\n  }\n\n  return (\n    <div\n      class=\"flex flex-col gap-2\"\n      role=\"radiogroup\"\n      aria-label={t('pricing.plans_aria', 'Plans')}\n    >\n      {prices.map((price) => {\n        const selected = ctx.selectedPriceId === price.id;\n        const isPopular = block.popular_price_id === price.id;\n        const offer = findLiveOffer(ctx.bootstrap.offers, price.id, { readStart: readBrowserOfferStart });\n        const discountPercent = offer?.discount_percent ?? null;\n        const { currency, amount, originalAmount } = formatPriceParts(price, discountPercent);\n        return (\n          <button\n            key={price.id}\n            type=\"button\"\n            role=\"radio\"\n            aria-checked={selected}\n            onClick={() => {\n              ctx.setSelectedPriceId(price.id);\n              ctx.onAction('price_selected', { priceId: price.id, price });\n            }}\n            class={[\n              'group relative inline-flex w-full mx-auto items-center justify-between flex-row-reverse gap-4 rounded-2xl border-2 px-4 py-3.5 text-left transition-colors duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--pw-accent)]',\n              // border 2px everywhere — selection is expressed by color only, the layout\n              // does not jump (equal thickness for selected/unselected). The color\n              // difference accent vs gray is strong enough for visual hierarchy.\n              selected\n                ? 'border-[var(--pw-accent)] bg-transparent'\n                : 'border-gray-200 bg-transparent hover:bg-gray-50'\n            ].join(' ')}\n          >\n            <span\n              class={[\n                'flex h-6.5 w-6.5 flex-shrink-0 items-center justify-center rounded-full border transition-colors',\n                selected\n                  ? 'border-[var(--pw-accent)] text-white'\n                  : 'border-gray-300 bg-transparent text-transparent',\n                // The popular-label badge sits absolute at the top-right of the card and\n                // visually shifts the content's center down. flex items-center\n                // on the card keeps the checkmark at the geometric center, which\n                // makes it look too high — we compensate with a small mt.\n                isPopular ? 'mt-3' : ''\n              ].join(' ')}\n              style={\n                selected\n                  ? {\n                      background:\n                        'linear-gradient(135deg, color-mix(in srgb, var(--pw-accent) 70%, white) 0%, var(--pw-accent) 50%, color-mix(in srgb, var(--pw-accent) 85%, black) 100%)'\n                    }\n                  : undefined\n              }\n              aria-hidden=\"true\"\n            >\n              <svg\n                width=\"14\"\n                height=\"10\"\n                viewBox=\"0 0 17 12\"\n                fill=\"none\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n                class={selected ? 'opacity-100' : 'opacity-0'}\n              >\n                <path\n                  d=\"M16.5234 0.476562C16.9805 0.898438 16.9805 1.63672 16.5234 2.05859L7.52344 11.0586C7.10156 11.5156 6.36328 11.5156 5.94141 11.0586L1.44141 6.55859C0.984375 6.13672 0.984375 5.39844 1.44141 4.97656C1.86328 4.51953 2.60156 4.51953 3.02344 4.97656L6.75 8.66797L14.9414 0.476562C15.3633 0.0195312 16.1016 0.0195312 16.5234 0.476562Z\"\n                  fill=\"currentColor\"\n                />\n              </svg>\n            </span>\n            <div class=\"flex flex-1 flex-col gap-0.5\">\n              {/* Label + strike+badge on one line (flex-wrap for narrow\n                  cards) — a compact 2-row layout instead of 3-row. Tags go\n                  to the right of the label with a gap, wrapping on overflow. */}\n              <div class=\"flex flex-wrap items-center gap-x-2 gap-y-1\">\n                <span class=\"text-xs font-normal uppercase tracking-normal text-gray-800/70\">\n                  {planLabel(price, t)}\n                </span>\n                {originalAmount ? (\n                  // opacity-60 mutes the strike: the eye catches the label\n                  // and discount badge first, then the main price; the original \"former price\"\n                  // is tertiary info and should not compete with the label.\n                  <span class=\"text-[15px] font-normal text-gray-400 opacity-60 line-through decoration-gray-400 decoration-[1.5px]\">\n                    {originalAmount}\n                  </span>\n                ) : null}\n                {discountPercent ? (\n                  // Emerald pill — a fixed \"success/savings\", independent of\n                  // brand_color. Readable even on dark brand accents.\n                  <span class=\"rounded-full bg-emerald-100 px-2.5 py-1 text-xs font-bold leading-none text-emerald-700\">\n                    -{discountPercent}%\n                  </span>\n                ) : null}\n              </div>\n              <div class=\"flex items-baseline gap-2 flex-wrap\">\n                <span class=\"text-[26px] leading-tight whitespace-nowrap text-gray-800 font-medium\">\n                  <span class=\"opacity-90\">{currency}</span>{amount}\n                  <span class=\"text-sm font-normal text-gray-500\">\n                    {' '}/ {intervalSuffix(price, t)}\n                  </span>\n                </span>\n              </div>\n              {price.description ? (\n                <span class=\"mt-1 text-xs leading-relaxed text-gray-500\">{price.description}</span>\n              ) : null}\n            </div>\n            {isPopular ? (\n              <span\n                // Solid accent + white text — high contrast; in a glasses-test\n                // the eye picks out the popular choice immediately. The pastel variant\n                // competed in visual weight with the price itself and worked\n                // neither as a highlight nor as information.\n                class=\"absolute -top-[9px] -right-[6px] rounded-[11px] border-[5px] border-white px-2 py-1 text-[12px] font-semibold text-white\"\n                style={{ background: 'var(--pw-accent)' }}\n              >\n                {popularLabel}\n              </span>\n            ) : null}\n          </button>\n        );\n      })}\n    </div>\n  );\n}\n\n// A short one-word label for compact mode (\"Month\" / \"Year\" / \"Lifetime\")\n// instead of the long \"MONTHLY PLAN\". Mirrors the legacy `getIntervalName` for\n// TelegramPricingRadio: the more compact the row, the more compact the label, otherwise\n// the text starts competing with the price.\nfunction compactLabel(price: PaywallPrice, t: TFn): string {\n  if (price.label) return price.label;\n  if (!price.interval || price.interval === 'lifetime') {\n    return t('pricing.interval.lifetime_short', 'lifetime');\n  }\n  return t(`pricing.interval.${price.interval}`, price.interval);\n}\n\n// A compact row for compact mode. Mirrors the legacy `TelegramPricingRadio`:\n//   [radio] | [label + popular-pill]  ······  [strike+badge ▸ price]\n// Dividers live on the inner label-wrapper (`border-b`), the last\n// row without a border. Selection is expressed only by the color of the radio circle —\n// no bg-tint, so it doesn't conflict with the pricing grid. Fonts — text-md\n// without bold, as in legacy (heroui text-md ≈ 16px).\nfunction CompactRow({\n  price,\n  isLast,\n  isPopular,\n  popularLabel,\n  offer,\n  selected,\n  onSelect,\n  t\n}: {\n  price: PaywallPrice;\n  isLast: boolean;\n  isPopular: boolean;\n  popularLabel: string;\n  offer: PaywallOffer | null;\n  selected: boolean;\n  onSelect: () => void;\n  t: TFn;\n}) {\n  const discountPercent = offer?.discount_percent ?? null;\n  const { currency, amount, originalAmount } = formatPriceParts(price, discountPercent);\n  return (\n    <button\n      type=\"button\"\n      role=\"radio\"\n      aria-checked={selected}\n      onClick={onSelect}\n      class=\"group relative inline-flex w-full max-w-[360px] mx-auto items-center justify-between gap-4 px-4 pt-3.5 text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--pw-accent)]\"\n    >\n      <span\n        class={[\n          'flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full border transition-colors mb-3',\n          selected\n            ? 'border-[var(--pw-accent)] text-white'\n            : 'border-gray-300 bg-transparent text-transparent'\n        ].join(' ')}\n        style={\n          selected\n            ? {\n                background:\n                  'linear-gradient(135deg, color-mix(in srgb, var(--pw-accent) 70%, white) 0%, var(--pw-accent) 50%, color-mix(in srgb, var(--pw-accent) 85%, black) 100%)'\n              }\n            : undefined\n        }\n        aria-hidden=\"true\"\n      >\n        <svg\n          width=\"14\"\n          height=\"10\"\n          viewBox=\"0 0 17 12\"\n          fill=\"none\"\n          xmlns=\"http://www.w3.org/2000/svg\"\n          class={selected ? 'opacity-100' : 'opacity-0'}\n        >\n          <path\n            d=\"M16.5234 0.476562C16.9805 0.898438 16.9805 1.63672 16.5234 2.05859L7.52344 11.0586C7.10156 11.5156 6.36328 11.5156 5.94141 11.0586L1.44141 6.55859C0.984375 6.13672 0.984375 5.39844 1.44141 4.97656C1.86328 4.51953 2.60156 4.51953 3.02344 4.97656L6.75 8.66797L14.9414 0.476562C15.3633 0.0195312 16.1016 0.0195312 16.5234 0.476562Z\"\n            fill=\"currentColor\"\n          />\n        </svg>\n      </span>\n      {/* Inner wrapper, carrying `border-b` — the divider between rows.\n          It sits after the radio (by flex-flow), giving a visual bottom line exactly\n          under the label/price columns, as in legacy. */}\n      <div\n        class={[\n          'flex flex-1 items-center gap-1.5 pb-3.5',\n          isLast ? '' : 'border-b border-gray-200'\n        ].join(' ')}\n      >\n        <div class=\"flex flex-wrap items-center gap-1 gap-x-1.5\">\n          <span class=\"text-base font-normal capitalize text-gray-800\">\n            {compactLabel(price, t)}\n          </span>\n          {isPopular ? (\n            // Pastel brand-mix pill — exactly like `badge` in TelegramPricingRadio.\n            // Low visual weight: the pill is about the \"plan name\" (most popular), not\n            // about savings — it should not compete with the -X% discount pill.\n            <span\n              class=\"rounded-[9px] px-2 py-1 text-[10px] font-bold\"\n              style={{\n                background:\n                  'linear-gradient(160deg, color-mix(in srgb, var(--pw-accent) 6%, white) 0%, color-mix(in srgb, var(--pw-accent) 15%, white) 100%)',\n                color: 'var(--pw-accent)'\n              }}\n            >\n              {popularLabel}\n            </span>\n          ) : null}\n          {discountPercent ? (\n            <span class=\"rounded-md bg-emerald-100 px-1.5 py-0.5 text-[10px] font-bold leading-none text-emerald-700\">\n              -{discountPercent}%\n            </span>\n          ) : null}\n        </div>\n        <div class=\"flex-1\" />\n        <span class=\"flex items-baseline gap-1.5 text-base font-normal text-gray-600\">\n          {originalAmount ? (\n            <span class=\"text-xs text-gray-400 line-through decoration-gray-400 decoration-[1.5px]\">\n              {originalAmount}\n            </span>\n          ) : null}\n          <span class=\"whitespace-nowrap\">\n            <span class=\"opacity-90\">{currency}</span>{amount}\n            <span class=\"text-xs text-gray-400\">\n              {' '}/ {intervalSuffix(price, t)}\n            </span>\n          </span>\n        </span>\n      </div>\n    </button>\n  );\n}\n\n// A compact card for the horizontal grid. UX model — Stripe pricing tables:\n// selection is expressed by border color + a tinted bg of the whole card, without a separate\n// radio circle (in a narrow column any icon mark competes with the price for\n// attention). The popular badge is an absolute pill at the top-right (as in the default view):\n// it frees up vertical space inside the card and reads as a premium marker. All\n// cards in a row are aligned via `items-stretch` on the grid (see the call site).\nfunction RowCard({\n  price,\n  isPopular,\n  popularLabel,\n  offer,\n  reserveStrikeRow,\n  selected,\n  onSelect,\n  t\n}: {\n  price: PaywallPrice;\n  isPopular: boolean;\n  popularLabel: string;\n  offer: PaywallOffer | null;\n  /** Reserve height for the strike-row (originalAmount + discount-pill) even\n   *  in this card without a discount. true when the grid has at least one price with\n   *  a discount — otherwise the main amount without a discount jumps above its discounted neighbors.\n   *  false when no price in the grid has an offer — the strike-row collapses\n   *  to 0 in all of them, and there's no 22px of empty space under the label. */\n  reserveStrikeRow: boolean;\n  selected: boolean;\n  onSelect: () => void;\n  t: TFn;\n}) {\n  const discountPercent = offer?.discount_percent ?? null;\n  const { currency, amount, originalAmount } = formatPriceParts(price, discountPercent);\n  return (\n    <button\n      type=\"button\"\n      role=\"radio\"\n      aria-checked={selected}\n      onClick={onSelect}\n      class={[\n        'group relative flex h-full flex-col items-center justify-start gap-1 rounded-2xl border-2 px-3 pb-4 pt-3.5 text-center transition-colors duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--pw-accent)]',\n        selected\n          ? 'border-[var(--pw-accent)]'\n          : 'border-gray-200 hover:bg-gray-50'\n      ].join(' ')}\n      style={\n        selected\n          ? { background: 'color-mix(in srgb, var(--pw-accent) 6%, transparent)' }\n          : undefined\n      }\n    >\n      {/* Label with a fixed min-height of 2 lines — long (\"YEARLY PLAN\")\n          and short (\"LIFETIME\") ones don't shift the price between cards. */}\n      <span class=\"flex min-h-[2.4em] items-center text-[10px] font-normal uppercase leading-tight text-gray-800/70\">\n        {planLabel(price, t)}\n      </span>\n      {/* Strike-row on top BEFORE the main amount: first \"was $10\" + \"-20%\",\n          then \"$8\" large. Height is reserved (h-[22px]) only if the\n          grid has at least one price with a discount — this keeps alignment between\n          discounted and non-discounted cards. If there is no offer at all, we don't\n          render the row, leaving no 22px of empty space under the label in all cards. */}\n      {reserveStrikeRow ? (\n        <div class=\"flex h-[22px] items-center justify-center gap-1.5\">\n          {originalAmount ? (\n            <span class=\"text-[12px] text-gray-400 line-through decoration-gray-400 decoration-[1.5px]\">\n              {originalAmount}\n            </span>\n          ) : null}\n          {discountPercent ? (\n            <span class=\"rounded-md bg-emerald-100 px-1.5 py-0.5 text-[10px] font-bold leading-none text-emerald-700\">\n              -{discountPercent}%\n            </span>\n          ) : null}\n        </div>\n      ) : null}\n      <span class=\"text-[26px] leading-none whitespace-nowrap text-gray-800 font-medium\">\n        <span class=\"opacity-90\">{currency}</span>{amount}\n      </span>\n      <span class=\"text-xs font-normal text-gray-500\">\n        / {intervalSuffix(price, t)}\n      </span>\n      {isPopular ? (\n        <span\n          // Solid accent + white text + white border-ring — separates the badge\n          // from the card's border, imitating a \"sticker\". Mirrors the default view.\n          class=\"absolute -top-[10px] left-1/2 -translate-x-1/2 whitespace-nowrap rounded-[11px] border-[3px] border-white px-2.5 py-0.5 text-[10px] font-semibold uppercase tracking-wider text-white\"\n          style={{ background: 'var(--pw-accent)' }}\n        >\n          {popularLabel}\n        </span>\n      ) : null}\n    </button>\n  );\n}\n","import type { LayoutBlock } from '../../../core/types';\nimport type { BlockProps } from '../types';\n\ntype TextBlock = Extract<LayoutBlock, { type: 'text' }>;\n\nexport function Text({ block }: BlockProps<TextBlock>) {\n  return <p class=\"text-[0.9375rem] leading-relaxed text-gray-600\">{block.text}</p>;\n}\n","import type { LayoutBlock, PaywallPrice } from '../../../core/types';\nimport type { BlockProps } from '../types';\nimport { useI18n, type TFn } from '../../i18n';\n\ntype TokenizationGateBlock = Extract<LayoutBlock, { type: 'tokenization_gate' }>;\n\nconst INTERVAL_MULTIPLIER: Record<string, number> = {\n  week: 0.25,\n  month: 1,\n  year: 12\n};\n\nfunction intervalNoun(interval: PaywallPrice['interval'], t: TFn): string {\n  if (!interval) return t('pricing.interval.period', 'period');\n  return t(`pricing.interval.${interval}`, interval);\n}\n\nexport function TokenizationGate({ block, ctx }: BlockProps<TokenizationGateBlock>) {\n  const { t } = useI18n();\n  if (!block.queries.length) return null;\n\n  const selectedPrice = ctx.bootstrap.prices.find((p) => p.id === ctx.selectedPriceId);\n  const interval = selectedPrice?.interval ?? null;\n  const multiplier = interval ? INTERVAL_MULTIPLIER[interval] : undefined;\n\n  return (\n    <div class=\"flex flex-col gap-2\">\n      <div class=\"text-sm font-semibold text-gray-800\">\n        {!interval || interval === 'lifetime'\n          ? t('pricing.included_total', 'Included for lifetime:')\n          : t('pricing.included_per', 'Included per {interval}:', {\n              interval: intervalNoun(interval, t)\n            })}\n      </div>\n      <ul class=\"flex flex-col gap-2\" role=\"list\">\n        {block.queries.map((q) => {\n          const rawCount = Number.isFinite(q.count as number) ? (q.count as number) : 0;\n          const amount =\n            multiplier !== undefined ? Math.round(rawCount * multiplier) : rawCount;\n          return (\n            <li key={q.id} class={`flex gap-3 ${q.desc ? 'items-start' : 'items-center'}`}>\n              <svg\n                width=\"18\"\n                height=\"18\"\n                viewBox=\"0 0 20 20\"\n                fill=\"none\"\n                class={`flex-shrink-0 text-emerald-500 ${q.desc ? 'mt-0.5' : ''}`}\n                aria-hidden=\"true\"\n              >\n                <path\n                  d=\"M4 10.5l3.5 3.5 8.5-8.5\"\n                  stroke=\"currentColor\"\n                  stroke-width=\"2.5\"\n                  stroke-linecap=\"round\"\n                  stroke-linejoin=\"round\"\n                />\n              </svg>\n              <div>\n                <span class=\"font-semibold text-gray-900 text-sm\">{amount}</span>{' '}\n                <span class=\"text-sm text-gray-800\">{q.name}</span>\n                {q.desc ? (\n                  <>\n                    <br />\n                    <span class=\"text-xs text-gray-400\">{q.desc}</span>\n                  </>\n                ) : null}\n              </div>\n            </li>\n          );\n        })}\n      </ul>\n    </div>\n  );\n}\n","import type { LayoutBlock } from '../../core/types';\nimport type { BlockComponent } from './types';\nimport { AuthPanel } from './blocks/AuthPanel';\nimport { CtaButton } from './blocks/CtaButton';\nimport { CurrentSession } from './blocks/CurrentSession';\nimport { FeaturesList } from './blocks/FeaturesList';\nimport { GuaranteeBadge } from './blocks/GuaranteeBadge';\nimport { Heading } from './blocks/Heading';\nimport { OfferBanner } from './blocks/OfferBanner';\nimport { PriceGrid } from './blocks/PriceGrid';\nimport { Text } from './blocks/Text';\nimport { TokenizationGate } from './blocks/TokenizationGate';\n\nexport const blockRegistry: Record<LayoutBlock['type'], BlockComponent<any>> = {\n  heading: Heading,\n  text: Text,\n  price_grid: PriceGrid,\n  cta_button: CtaButton,\n  auth_panel: AuthPanel,\n  current_session: CurrentSession,\n  features_list: FeaturesList,\n  tokenization_gate: TokenizationGate,\n  guarantee_badge: GuaranteeBadge,\n  offer_banner: OfferBanner\n};\n","import { useMemo, useState } from 'preact/hooks';\nimport type { AuthClient, AuthSession } from '../../core/auth';\nimport type { Layout, PaywallBootstrap } from '../../core/types';\nimport { blockRegistry } from './registry';\nimport type { BlockContext } from './types';\n\nexport interface RendererProps {\n  layout: Layout;\n  bootstrap: PaywallBootstrap;\n  onAction: (action: string, payload?: unknown) => void;\n  auth?: AuthClient;\n  authSession: AuthSession | null;\n  /** True if an OfferTopBanner is rendered above the dialog (it takes on the\n   *  visual top-bleed under the X close button). Without the banner we reduce the\n   *  top padding of the scrollable area — otherwise there's 32px of empty space under the X. */\n  hasTopBanner?: boolean;\n  /** Per-open custom title (OpenOptions.title): replaces the text of the first\n   *  h1 heading block, or prepends one if the layout has none. Applied here —\n   *  over the already locale-resolved layout — so it wins over both the\n   *  configured heading and its translations. */\n  titleOverride?: string | null;\n}\n\n/** Applies the per-open title override to the layout. The layout is treated as\n *  immutable (it's the cached bootstrap) — we return a copy with the first h1\n *  heading replaced, or with a new h1 prepended when the layout has none. */\nfunction applyTitleOverride(layout: Layout, title: string | null | undefined): Layout {\n  if (!title) return layout;\n  const idx = layout.blocks.findIndex(\n    (b) => b.type === 'heading' && (b.level ?? 1) === 1\n  );\n  if (idx === -1) {\n    return {\n      ...layout,\n      blocks: [{ type: 'heading', text: title, level: 1 }, ...layout.blocks]\n    };\n  }\n  const blocks = layout.blocks.slice();\n  blocks[idx] = { ...(blocks[idx] as Extract<Layout['blocks'][number], { type: 'heading' }>), text: title };\n  return { ...layout, blocks };\n}\n\nexport function Renderer({\n  layout: rawLayout,\n  bootstrap,\n  onAction,\n  auth,\n  authSession,\n  hasTopBanner,\n  titleOverride\n}: RendererProps) {\n  const layout = useMemo(\n    () => applyTitleOverride(rawLayout, titleOverride),\n    [rawLayout, titleOverride]\n  );\n  // By default selected = popular_price_id (if it's set in some\n  // price_grid block and actually exists in bootstrap.prices). This\n  // mirrors the legacy paywall UX: the highlighted card is highlighted right away and\n  // ready to purchase, the user doesn't need an extra click on it. Fallback — the first price.\n  const defaultPriceId = useMemo(() => {\n    for (const b of layout.blocks) {\n      if (b.type === 'price_grid' && b.popular_price_id) {\n        if (bootstrap.prices.some((p) => p.id === b.popular_price_id)) {\n          return b.popular_price_id;\n        }\n      }\n    }\n    return bootstrap.prices[0]?.id ?? null;\n  }, [layout.blocks, bootstrap.prices]);\n  const [selectedPriceId, setSelectedPriceId] = useState<string | null>(defaultPriceId);\n\n  const ctx: BlockContext = {\n    bootstrap,\n    selectedPriceId,\n    setSelectedPriceId,\n    onAction,\n    auth,\n    authSession\n  };\n\n  // CTA + everything after it — a pinned footer at the bottom of the dialog: always visible, even\n  // if the content above doesn't fit by height. Split on the first `cta_button`:\n  // no cta → the section isn't rendered, the whole layout scrolls as usual.\n  // We use flex (not position:sticky) — sticky doesn't let the scrollable area\n  // give its height to the footer correctly (content scrolled UNDER the footer instead\n  // of expanding min-h: 0), plus the sticky shadow showed up even\n  // when there's no overflow. Flex is clean: footer auto-height, scroll = flex-1.\n  const ctaIdx = layout.blocks.findIndex((b) => b.type === 'cta_button');\n  const scrollBlocks = ctaIdx === -1 ? layout.blocks : layout.blocks.slice(0, ctaIdx);\n  const footerBlocks = ctaIdx === -1 ? [] : layout.blocks.slice(ctaIdx);\n\n  const renderBlock = (block: Layout['blocks'][number], i: number) => {\n    const Cmp = blockRegistry[block.type];\n    if (!Cmp) {\n      if (typeof console !== 'undefined') {\n        console.warn(`[paywall] unknown block type: ${block.type}`);\n      }\n      return null;\n    }\n    return <Cmp key={`${block.type}-${i}`} block={block as never} ctx={ctx} />;\n  };\n\n  return (\n    <>\n      {/* Scrollable: the top padding visually separates it from the dialog top (and\n          banner), the bottom one is smaller because the footer adds its own pt\n          + border. It used to be `p-8` at the bottom, which gave a ~48px gap before the CTA. */}\n      <div class=\"flex-1 min-h-0 overflow-y-auto px-6 pb-3 pt-6 sm:px-8 sm:pb-4 sm:pt-8\">\n        <div class=\"flex flex-col gap-6\">\n          {scrollBlocks.map(renderBlock)}\n        </div>\n      </div>\n      {footerBlocks.length > 0 ? (\n        // A thin shadow-top instead of border-t — creates depth, reads as\n        // \"the footer is pinned to the bottom of the dialog\". The line looked like a divider\n        // in normal flow and didn't convey the sticky character.\n        <div\n          class=\"flex flex-col gap-4 bg-white px-6 pb-6 pt-3 sm:px-8\"\n          style={{ boxShadow: '0 -4px 12px -4px rgba(15,23,42,0.06)' }}\n        >\n          {footerBlocks.map((b, i) => renderBlock(b, scrollBlocks.length + i))}\n        </div>\n      ) : null}\n    </>\n  );\n}\n","import type { ComponentChildren } from 'preact';\nimport { useEffect, useLayoutEffect, useRef, useState } from 'preact/hooks';\nimport type { BillingClient } from '../core/BillingClient';\nimport type { AuthSession, OAuthResumeCheckout } from '../core/auth';\nimport { findLiveOffer, readBrowserOfferStart } from '../core/offer';\nimport type { LayoutBlock, PaywallBootstrap } from '../core/types';\nimport { PaywallError } from '../core/types';\nimport { Modal } from './Modal';\nimport { AuthGate } from './AuthGate';\nimport { OfferTopBanner, pickActiveOffer } from './renderer/blocks/OfferBanner';\nimport { SupportGate } from './SupportGate';\nimport { Renderer } from './renderer/Renderer';\nimport { I18nProvider, useI18n } from './i18n';\n\nexport type PaywallView =\n  | 'layout'\n  | 'support'\n  | 'auth'\n  | 'awaiting_payment'\n  | 'popup_blocked';\n\n/**\n * Public snapshot of PaywallUI state for the host. Derived from the internal\n * LoadState + GateState + open/purchased flags. Each real change is one onState\n * call; deduplicated (`useSyncExternalStore`-friendly).\n */\nexport interface PaywallStateSnapshot {\n  /** The modal is rendered and visible. False — closed (or never opened yet).\n   *  Can be false while `processing=true` — direct-checkout (paywall.checkout)\n   *  does bootstrap + createCheckout headless before deciding whether to mount\n   *  the modal. */\n  open: boolean;\n  /** What's shown in the modal. null when `open=false`. */\n  view:\n    | 'loading'\n    | 'error'\n    | 'layout'\n    | 'auth'\n    | 'support'\n    | 'awaiting_payment'\n    | 'popup_blocked'\n    | 'purchased'\n    | null;\n  /** Filled only when `view === 'error'`. */\n  error: PaywallError | null;\n  /** The SDK is doing background work for `paywall.checkout(priceId)` —\n   *  bootstrap, visibility/trial gates, createCheckout — before the UI modal is\n   *  actually needed. During this phase the host can disable its button and\n   *  show a spinner right on it, so the user doesn't feel that \"the click did\n   *  nothing\". Reset to false right after mountAndShow (the modal took over the\n   *  UI), or after a headless reject (already-paid, createCheckout error\n   *  without a modal). For the `paywall.open()` flow it's always false: there\n   *  the modal appears instantly with its own LoadingView and a separate\n   *  \"processing\" isn't needed. */\n  processing: boolean;\n}\n\nexport interface PaywallRootProps {\n  client: BillingClient;\n  open: boolean;\n  onClose: () => void;\n  onEvent: (event: string, payload?: unknown) => void;\n  /** Which view to show when open=true. Defaults to 'layout'.\n   *  - 'support' / 'auth' — standalone opens of paywall.openSupport / openSignin.\n   *  - 'awaiting_payment' / 'popup_blocked' — direct-checkout (paywall.checkout):\n   *    PaywallUI does createCheckout headless, then mounts the modal straight to\n   *    the final view (without a loading flash). Requires\n   *    `initialCheckoutPriceId` + `initialCheckoutUrl`. */\n  initialView?: PaywallView;\n  /** AuthPanel mode when `initialView='auth'` — 'signin' (default) or 'signup'.\n   *  Set by PaywallUI from openSignup()/openSignin(). */\n  initialAuthMode?: 'signin' | 'signup';\n  /** Target price for direct-checkout. Used in two modes:\n   *  - `initialView='auth'` + priceId → preauth-flow direct-checkout: the modal\n   *    starts with the auth-gate, and after signIn auto-resumes into\n   *    createCheckout (with the offer-id from cached offers).\n   *  - `initialView='awaiting_payment'|'popup_blocked'` → checkout is already\n   *    created headless in PaywallUI, and the modal shows the final screen.\n   *  Ignored for the other `initialView` values. */\n  initialCheckoutPriceId?: string | null;\n  /** URL of the provider's hosted checkout. Passed together with\n   *  `initialCheckoutPriceId` when initialView='awaiting_payment' or\n   *  'popup_blocked' — used for retry/reopen buttons without re-entering\n   *  createCheckout. */\n  initialCheckoutUrl?: string | null;\n  /** Server-confirmed purchase — show the success view with a Continue button.\n   *  Controlled from the outside (PaywallUI sets true from watcher.onActive),\n   *  reset on open()/close(). Overrides any other view. */\n  purchased?: boolean;\n  /** Renewal/upgrade flow. true — skip all has_active_subscription pre-checks\n   *  (bootstrap-time + post-auth), and on checkout pass\n   *  `ignoreActivePurchase: true` to the backend so /start-checkout doesn't\n   *  return a 409 for an already-subscribed user. See OpenOptions.renew. */\n  renew?: boolean;\n  /** Public state-machine notify. PaywallUI passes a callback here that caches\n   *  the snapshot and emits its own `onStateChange`. If not passed —\n   *  state-tracking is disabled (no overhead for hosts that don't need it). */\n  onState?: (snapshot: PaywallStateSnapshot) => void;\n  /** Inline mode (admin panel editor's live-preview): passed to Modal so the\n   *  overlay is absolute-inside-host instead of fixed-viewport, and doesn't\n   *  lock body-scroll. Defaults to false. */\n  inline?: boolean;\n  /** Explicit language override for I18nProvider. Used by the admin panel\n   *  editor's live-preview — there the browser-locale is always EN, but we need\n   *  to show it as for a user from the chosen country. See\n   *  I18nProviderProps.forceLocale. */\n  locale?: string | null;\n  /** Per-open custom title (OpenOptions.title). Applied by the Renderer over\n   *  the resolved layout (after locale overrides): replaces the text of the\n   *  first h1 heading block, or prepends one if the layout has none. null —\n   *  the layout's own heading is shown. */\n  titleOverride?: string | null;\n}\n\ntype LoadState =\n  | { status: 'idle' }\n  | { status: 'loading' }\n  | { status: 'ready'; data: PaywallBootstrap }\n  | { status: 'error'; error: PaywallError };\n\ntype GateState =\n  | { kind: 'layout' }\n  // pendingCheckout=undefined, origin='layout' — the gate is opened via \"Restore\n  // purchases\" (without a subsequent checkout); after signIn we collapse into\n  // layout. With pendingCheckout — the gate is opened by the preauth-flow from\n  // cta_button and after signIn auto-resumes createCheckout. origin='standalone'\n  // — paywall.openAuth(): the modal is open only for login, and after signIn /\n  // Back we close the modal and don't show the layout at all. direct=true —\n  // pendingCheckout came from paywall.checkout(priceId): on error/already-paid\n  // we close the modal instead of setGate('layout'), because the layout with\n  // plans must never flash in this flow.\n  | {\n      kind: 'auth_gate';\n      pendingCheckout?: { priceId: string; direct?: boolean };\n      origin?: 'layout' | 'standalone';\n      /** The opening context — controls the gate's heading\n       *  (\"Restore Purchases\" vs \"Welcome back!\"). Default — 'preauth'. */\n      intent?: 'restore' | 'preauth' | 'standalone';\n    }\n  // origin='layout' — came from the current_session block, Back returns to layout.\n  // origin='standalone' — the modal is open only for support (paywall.openSupport()),\n  // Back closes the modal.\n  | { kind: 'support'; origin: 'layout' | 'standalone' }\n  // window.open returned a handle — the checkout opened in a new tab. The\n  // paywall stays as an indicator: \"pay in that tab\". We keep priceId so the\n  // retry button can recreate the checkout (Stripe/Paddle URLs expire). We keep\n  // url so the fallback link \"Didn't open? Click here\" reopens the same URL\n  // without another trip to createCheckout — needed for the case where\n  // window.open returned a handle but the tab is actually blocked (aggressive\n  // mobile blockers).\n  | { kind: 'awaiting_payment'; priceId: string; url: string }\n  // window.open returned null — the popup is blocked (happens after an async\n  // post-auth resume, when the transient activation has expired). We do NOT\n  // redirect the current tab: the paywall must stay. The URL is already issued —\n  // the \"Open checkout\" button will call window.open under a fresh gesture,\n  // without a second trip to createCheckout.\n  | { kind: 'popup_blocked'; priceId: string; url: string }\n  // The user is already signed in and has_active_subscription — we show the\n  // success-view. Triggered either after auth-resume (we poll getUser right\n  // after signIn), or when /start-checkout returned 409 hasActivePurchase.\n  // restored=true changes the PurchaseSuccessView text to \"Subscription restored\".\n  | { kind: 'purchase_success'; restored: boolean }\n  // After signIn we wait for getUser({force:true}) until we know whether there's\n  // already an active subscription. Without this intermediate state the user\n  // sees the auth_gate's \"gray screen\" for a few seconds with the form already\n  // hidden.\n  | { kind: 'verifying' };\n\ntype AuthPanelBlock = Extract<LayoutBlock, { type: 'auth_panel' }>;\n\nfunction computePaywallSnapshot(\n  open: boolean,\n  state: LoadState,\n  gate: GateState,\n  purchased: boolean | undefined\n): PaywallStateSnapshot {\n  // `processing` is controlled by PaywallUI (direct-checkout headless prep) and\n  // merged into the snapshot before pushing to applyState. Here we always set\n  // it false — once the modal is actually mounted, the host has nothing to\n  // \"wait\" for beyond the gate views.\n  if (!open) return { open: false, view: null, error: null, processing: false };\n  if (purchased)\n    return { open: true, view: 'purchased', error: null, processing: false };\n  if (state.status === 'idle' || state.status === 'loading') {\n    return { open: true, view: 'loading', error: null, processing: false };\n  }\n  if (state.status === 'error') {\n    return { open: true, view: 'error', error: state.error, processing: false };\n  }\n  if (gate.kind === 'support')\n    return { open: true, view: 'support', error: null, processing: false };\n  if (gate.kind === 'auth_gate')\n    return { open: true, view: 'auth', error: null, processing: false };\n  if (gate.kind === 'awaiting_payment') {\n    return { open: true, view: 'awaiting_payment', error: null, processing: false };\n  }\n  if (gate.kind === 'popup_blocked') {\n    return { open: true, view: 'popup_blocked', error: null, processing: false };\n  }\n  if (gate.kind === 'purchase_success') {\n    return { open: true, view: 'purchased', error: null, processing: false };\n  }\n  if (gate.kind === 'verifying') {\n    return { open: true, view: 'loading', error: null, processing: false };\n  }\n  return { open: true, view: 'layout', error: null, processing: false };\n}\n\nfunction sameSnapshot(a: PaywallStateSnapshot, b: PaywallStateSnapshot): boolean {\n  return (\n    a.open === b.open &&\n    a.view === b.view &&\n    a.error === b.error &&\n    a.processing === b.processing\n  );\n}\n\nexport function PaywallRoot({\n  client,\n  open,\n  onClose,\n  onEvent,\n  initialView,\n  initialAuthMode,\n  initialCheckoutPriceId,\n  initialCheckoutUrl,\n  purchased,\n  renew,\n  onState,\n  inline,\n  locale,\n  titleOverride\n}: PaywallRootProps) {\n  const [state, setState] = useState<LoadState>({ status: 'idle' });\n  // We keep session in state so blocks (auth_panel) re-render on login/logout.\n  // Without this AuthPanel would read the snapshot once and wouldn't collapse\n  // after a successful signin.\n  const [authSession, setAuthSession] = useState<AuthSession | null>(\n    () => client.auth?.getCachedSession() ?? null\n  );\n  const [gate, setGate] = useState<GateState>(() => {\n    if (initialView === 'support') return { kind: 'support', origin: 'standalone' };\n    if (initialView === 'auth') {\n      // initialCheckoutPriceId is set → preauth direct-checkout: after signin\n      // the auth-resume effect assembles createCheckout for this price and\n      // switches to awaiting_payment/popup_blocked. On back/error we must not\n      // fall into layout (the host draws the plans itself) — closes-on-back via\n      // origin='standalone' fits semantically.\n      if (initialCheckoutPriceId) {\n        return {\n          kind: 'auth_gate',\n          pendingCheckout: { priceId: initialCheckoutPriceId, direct: true },\n          origin: 'standalone',\n          intent: 'preauth'\n        };\n      }\n      return { kind: 'auth_gate', origin: 'standalone' };\n    }\n    if (initialView === 'awaiting_payment' && initialCheckoutPriceId && initialCheckoutUrl) {\n      return {\n        kind: 'awaiting_payment',\n        priceId: initialCheckoutPriceId,\n        url: initialCheckoutUrl\n      };\n    }\n    if (initialView === 'popup_blocked' && initialCheckoutPriceId && initialCheckoutUrl) {\n      return {\n        kind: 'popup_blocked',\n        priceId: initialCheckoutPriceId,\n        url: initialCheckoutUrl\n      };\n    }\n    return { kind: 'layout' };\n  });\n  // A stable flag \"the current modal session is direct-checkout\". Taken from\n  // initialView at the mount/reset stage and held until close: on\n  // error/already-paid we don't fall into the layout with plans, but close the\n  // modal and emit an event.\n  const isDirectCheckout =\n    initialView === 'awaiting_payment' ||\n    initialView === 'popup_blocked' ||\n    (initialView === 'auth' && !!initialCheckoutPriceId);\n  // Protection against double auto-resume: the useEffect below depends on\n  // authSession, and the onAuthChange subscription may deliver the same session\n  // again (refresh) — without the flag we'd call createCheckout twice.\n  const resumingRef = useRef(false);\n\n  // State-machine bridge: we emit a snapshot when any of (open, state, gate,\n  // purchased) changes. sameSnapshot suppresses no-ops — e.g. a loading→error\n  // transition changes state.status, but if we're already in the error view\n  // (otherwise impossible), the emit won't repeat.\n  const lastSnapshotRef = useRef<PaywallStateSnapshot | null>(null);\n  useEffect(() => {\n    if (!onState) return;\n    const next = computePaywallSnapshot(open, state, gate, purchased);\n    const prev = lastSnapshotRef.current;\n    if (prev && sameSnapshot(prev, next)) return;\n    lastSnapshotRef.current = next;\n    onState(next);\n  }, [open, state, gate, purchased, onState]);\n\n  useEffect(() => {\n    if (!client.auth) return;\n    return client.auth.onAuthChange((_event, s) => setAuthSession(s));\n  }, [client.auth]);\n\n  // Live bootstrap update: BillingClient.setBootstrap (preview-mode in the admin\n  // panel editor) or cross-tab storage.watch emit onBootstrapChange. We\n  // re-render the modal only if it's already in the ready phase — otherwise the\n  // bootstrap-effect below picks up the fresh cached one on open() itself.\n  // Guard: tests pass a stub client without onBootstrapChange — skip silently.\n  useEffect(() => {\n    if (typeof client.onBootstrapChange !== 'function') return;\n    return client.onBootstrapChange((data) => {\n      setState((prev) =>\n        prev.status === 'ready' ? { status: 'ready', data } : prev\n      );\n    });\n  }, [client]);\n\n  useEffect(() => {\n    if (!open) return;\n    if (state.status === 'ready' || state.status === 'loading') return;\n\n    let cancelled = false;\n    setState({ status: 'loading' });\n    client\n      .bootstrap()\n      .then((data) => {\n        if (cancelled) return;\n        setState({ status: 'ready', data });\n        onEvent('ready', data);\n        // \"Already subscribed\" is NOT handled here: a blind open() for a user\n        // with an active subscription is suppressed by PaywallUI before/right\n        // after mount (openInternal pre-check + runOpenGates/runDelayedGates),\n        // symmetric with the visibility/trial gates. The restored success-view\n        // is reserved for flows where the user explicitly recovered access:\n        // auth-resume after signin, Restore purchases, and the 409\n        // already_purchased catch in runCheckout.\n      })\n      .catch((error: unknown) => {\n        if (cancelled) return;\n        const err =\n          error instanceof PaywallError\n            ? error\n            : new PaywallError('unknown', 'Failed to load paywall', { cause: error });\n        setState({ status: 'error', error: err });\n        onEvent('error', err);\n      });\n    return () => {\n      cancelled = true;\n    };\n  }, [open, client]);\n\n  // Closing/reopening the modal resets the gate. PaywallUI invokes standalone\n  // flows (openSupport / openAuth) on an already-mounted component via\n  // handle.update({initialView: 'support'|'auth'}) — the useState initializer\n  // runs only on the first mount, so without this effect the gate would stay\n  // 'layout' (with plans) on subsequent standalone opens.\n  //\n  // useLayoutEffect (not useEffect): after close the gate goes to 'layout', and\n  // on the next openAuth/openSupport a regular useEffect would run AFTER paint,\n  // so the user would see the plans instead of the auth form for one frame\n  // (especially noticeable in the extension popup, where RemoteAuth+RemoteBilling\n  // add transport RTTs and the main thread yields more often between renders).\n  // useLayoutEffect syncs the gate BEFORE paint — no flicker.\n  useLayoutEffect(() => {\n    if (!open) {\n      setGate({ kind: 'layout' });\n      resumingRef.current = false;\n      // A load cancelled mid-flight leaves state at 'loading' (the bootstrap\n      // effect's cleanup set cancelled=true, so neither 'ready' nor 'error'\n      // ever landed). Reset to 'idle' so the next open() restarts the load\n      // instead of early-returning into a stuck spinner — hit when a delayed\n      // gate closes the first mount-then-load open and the user reopens (e.g.\n      // the trial expiring between two clicks).\n      setState((prev) => (prev.status === 'loading' ? { status: 'idle' } : prev));\n      return;\n    }\n    if (initialView === 'support') {\n      setGate({ kind: 'support', origin: 'standalone' });\n    } else if (initialView === 'auth') {\n      if (initialCheckoutPriceId) {\n        setGate({\n          kind: 'auth_gate',\n          pendingCheckout: { priceId: initialCheckoutPriceId, direct: true },\n          origin: 'standalone',\n          intent: 'preauth'\n        });\n      } else {\n        setGate({ kind: 'auth_gate', origin: 'standalone' });\n      }\n    } else if (\n      initialView === 'awaiting_payment' &&\n      initialCheckoutPriceId &&\n      initialCheckoutUrl\n    ) {\n      setGate({\n        kind: 'awaiting_payment',\n        priceId: initialCheckoutPriceId,\n        url: initialCheckoutUrl\n      });\n    } else if (\n      initialView === 'popup_blocked' &&\n      initialCheckoutPriceId &&\n      initialCheckoutUrl\n    ) {\n      setGate({\n        kind: 'popup_blocked',\n        priceId: initialCheckoutPriceId,\n        url: initialCheckoutUrl\n      });\n    }\n  }, [open, initialView, initialCheckoutPriceId, initialCheckoutUrl]);\n\n  /** The purchase waiting behind the auth gate, in the shape OAuth carries it.\n   *  Resolved exactly like runCheckout does — duration offers tick in client\n   *  storage and the backend cannot re-derive them, so the id has to travel with\n   *  the intent. Undefined when the gate isn't gating a checkout (openAuth,\n   *  restore). */\n  const resumeCheckoutFor = (\n    priceId: string | undefined\n  ): OAuthResumeCheckout | undefined => {\n    if (!priceId) return undefined;\n    const cachedOffers = client.getCachedOffers?.() ?? null;\n    const offer = cachedOffers\n      ? findLiveOffer(cachedOffers, priceId, { readStart: readBrowserOfferStart })\n      : null;\n    return { priceId, offerId: offer?.id, renew: renew === true };\n  };\n\n  const runCheckout = async (\n    priceId: string,\n    // allowAuthGate=false — the auth-resume call site: a 401 right after a\n    // fresh signin can't be fixed by signing in again, re-opening the gate\n    // would loop signin → 401 → gate → signin forever.\n    { allowAuthGate = true }: { allowAuthGate?: boolean } = {}\n  ) => {\n    try {\n      // Resolve the active offer from cached offers. Without this, duration\n      // offers (whose countdown ticks in clientStorage) won't apply at checkout\n      // — the server can't validate them and needs an explicit offerId. We pass\n      // end_date offers too — the backend re-checks applicability and discards\n      // foreign ones. findLiveOffer (not raw findApplicableOffer) — so we do NOT\n      // send the offerId of an expired duration offer: there's no server-side\n      // timer for them, and the backend would accept the id and grant a discount\n      // that's no longer visible in the UI.\n      const cachedOffers = client.getCachedOffers?.() ?? null;\n      const applicableOffer = cachedOffers\n        ? findLiveOffer(cachedOffers, priceId, { readStart: readBrowserOfferStart })\n        : null;\n      const result = await client.createCheckout({\n        priceId,\n        offerId: applicableOffer?.id,\n        ignoreActivePurchase: renew === true\n      });\n      onEvent('checkout_started', { priceId, url: result.url, acquiring: result.acquiring });\n      if (typeof window === 'undefined' || !result.url) return;\n      // Without `noopener,noreferrer` in the features: these flags make\n      // window.open ALWAYS return null (even when the popup actually opened),\n      // and we couldn't tell \"success\" from \"blocked\". We sever manually via\n      // popup.opener=null after success — on the checkout domain (Stripe/Paddle)\n      // opener access is cross-origin-restricted anyway, but an explicit null is\n      // safer.\n      const popup = window.open(result.url, '_blank');\n      if (popup) {\n        try {\n          popup.opener = null;\n        } catch {\n          /* cross-origin already — ok */\n        }\n        setGate({ kind: 'awaiting_payment', priceId, url: result.url });\n      } else {\n        // The popup is blocked — usually due to a stale transient activation\n        // (auto-resume after async signin). We do NOT take the user away via\n        // location.assign: the paywall must stay open. We show inline retry; a\n        // click on the button is a fresh gesture and the popup will open.\n        setGate({ kind: 'popup_blocked', priceId, url: result.url });\n      }\n    } catch (error) {\n      // A 409 hasActivePurchase from the backend isn't a checkout error, it's\n      // \"the user already has an active subscription\". We refresh the cache\n      // (the host's userChange should see has_active_subscription=true) and emit\n      // purchase_completed with restored=true. For the layout flow we switch to\n      // the success-view; for direct-checkout (paywall.checkout) — a headless\n      // reject: we close the modal and the host decides how to tell the user.\n      if (error instanceof PaywallError && error.code === 'already_purchased') {\n        try {\n          await client.getUser({ force: true });\n        } catch {\n          /* offline / 401 — getUser will report to the host itself; here it doesn't block the success-view */\n        }\n        onEvent('purchase_completed', { priceId, sessionId: null, restored: true });\n        if (isDirectCheckout) {\n          onClose();\n        } else {\n          setGate({ kind: 'purchase_success', restored: true });\n        }\n        return;\n      }\n      // 401 = the server rejected our Bearer and ApiClient's forced-refresh\n      // retry didn't save it: the session is dead (revoked in another context /\n      // GoTrue family revocation) and AuthClient has already cleared it. For\n      // preauth paywalls this is recoverable — reopen the auth gate with the\n      // checkout pending: after signin the auto-resume effect re-runs it, and\n      // for a user who already paid getUser / the 409 turn it into the restored\n      // success-view instead of a second charge. Without this branch a paying\n      // user whose session died hits a dead-end \"Request failed\". Guest mode\n      // falls through to the generic path: an auth form mid-guest-checkout is\n      // alien there, and a signin can't restore an anon-owned purchase anyway.\n      if (\n        allowAuthGate &&\n        error instanceof PaywallError &&\n        error.status === 401 &&\n        client.auth &&\n        (state.status === 'ready'\n          ? state.data.settings.checkout_mode ?? 'guest'\n          : 'guest') === 'preauth'\n      ) {\n        // Still reported as 'error' — hosts and the events analytics must see\n        // the auth failure (code `invalid_token`) even though the UI recovers.\n        onEvent('error', error);\n        setGate({\n          kind: 'auth_gate',\n          pendingCheckout: { priceId, direct: isDirectCheckout },\n          intent: 'preauth'\n        });\n        return;\n      }\n      const err =\n        error instanceof PaywallError\n          ? error\n          : new PaywallError('checkout_failed', 'Checkout failed', { cause: error });\n      onEvent('error', err);\n      // Layout flow: return the user to layout — otherwise we'd get stuck in\n      // auth_gate (if we came via the preauth flow) with an already-signed-in\n      // session. Direct-checkout: the layout with plans must never flash — we\n      // close the modal, and the host gets an error event and decides how to\n      // react.\n      if (isDirectCheckout) {\n        onClose();\n      } else {\n        setGate({ kind: 'layout' });\n      }\n    }\n  };\n\n  const reopenCheckout = (priceId: string, url: string) => {\n    if (typeof window === 'undefined') return;\n    const popup = window.open(url, '_blank');\n    if (popup) {\n      try {\n        popup.opener = null;\n      } catch {\n        /* ignore */\n      }\n      setGate({ kind: 'awaiting_payment', priceId, url });\n    }\n    // If it's still null — we leave popup_blocked, the user will click again.\n  };\n\n  // Auto-resume: a session appeared in the open gate → we continue the flow.\n  // Pending preauth-checkout — we do NOT collapse the gate into layout before\n  // runCheckout: otherwise the user sees the plans flicker between submitting\n  // the auth form and opening the checkout tab. runCheckout itself moves the\n  // gate to awaiting_payment / popup_blocked / layout (on error). Restore-flow\n  // without pendingCheckout — we just return to layout. resumingRef protects\n  // against a repeat run if authChange fires several times within one gate\n  // cycle (refresh).\n  useEffect(() => {\n    if (gate.kind !== 'auth_gate') return;\n    // An anonymous session doesn't count as login: the user came to auth_gate\n    // to really sign in. Otherwise openAuth() with an existing anon token would\n    // instantly close the modal via auto-resume, and the user wouldn't see the\n    // form.\n    if (!authSession || authSession.user.is_anonymous) return;\n    if (resumingRef.current) return;\n    resumingRef.current = true;\n    const pending = gate.pendingCheckout;\n    const origin = gate.origin;\n    // We switch to verifying right away — otherwise the modal hangs in\n    // auth_gate with an already-signed-in user (~3s while getUser goes to the\n    // backend), and the user sees an \"empty gray screen\" instead of progress.\n    // The loader more honestly shows that the SDK is doing something.\n    setGate({ kind: 'verifying' });\n    void (async () => {\n      // Before continuing the flow (runCheckout / return to layout / close the\n      // modal), we check — maybe the user already has an active subscription.\n      // Scenarios: the Restore button (they already paid from another account);\n      // preauth signIn (the user remembered they have a subscription);\n      // standalone openAuth; direct-checkout with a preauth gate.\n      // Without this check the user would see the plans, click Buy → 409 from\n      // the backend → fallback to already_purchased. Better not to make them go\n      // through that step. renew=true skips the check — the host is explicitly\n      // doing a renewal flow.\n      if (!renew) {\n        try {\n          const user = await client.getUser({ force: true });\n          if (user.has_active_subscription) {\n            onEvent('purchase_completed', {\n              priceId: pending?.priceId ?? null,\n              sessionId: null,\n              restored: true\n            });\n            // Direct-checkout preauth-resume: we don't show the plans, nor the\n            // restored view (headless reject) — we close the modal. The host\n            // gets purchase_completed{restored:true} and decides how to tell\n            // the user.\n            if (pending?.direct) {\n              onClose();\n            } else {\n              setGate({ kind: 'purchase_success', restored: true });\n            }\n            return;\n          }\n        } catch {\n          /* getUser failed — we continue the normal flow, the user will see the plans */\n        }\n      }\n      if (!pending) {\n        // openAuth standalone: after signIn we close the modal and don't show\n        // the layout. Restore-flow (origin='layout' or undefined): we return to\n        // layout.\n        if (origin === 'standalone') {\n          onClose();\n        } else {\n          setGate({ kind: 'layout' });\n        }\n        return;\n      }\n      await runCheckout(pending.priceId, { allowAuthGate: false });\n    })().finally(() => {\n      resumingRef.current = false;\n    });\n  }, [authSession, gate]);\n\n  const handleAction = async (action: string, payload?: unknown) => {\n    if (action === 'close') {\n      onClose();\n      return;\n    }\n    if (action === 'price_selected') {\n      // Pass it through as-is — the block already assembled { priceId, price }.\n      onEvent('price_selected', payload);\n      return;\n    }\n    if (action === 'restore') {\n      // CurrentSession block: a guest clicked \"Restore purchases\". We open the\n      // gate with intent='restore' — the heading and submit become \"Restore\n      // Purchases\". Without an AuthClient we do nothing (managed-auth not\n      // connected). An anonymous session doesn't count as login (see the\n      // CurrentSession block): it exists only for the api-gateway token, the\n      // user has no email and needs a real signin to link a past purchase.\n      // Without this check the Restore button would silently no-op as soon as\n      // the user got an anon token (which in extensions is almost always).\n      if (!client.auth) return;\n      const session = client.auth.getCachedSession();\n      if (session && !session.user.is_anonymous) return;\n      setGate({ kind: 'auth_gate', intent: 'restore' });\n      return;\n    }\n    if (action === 'support') {\n      // CurrentSession block: open the support form. Visible to both guests and\n      // signed-in users. From layout — Back returns to the plans.\n      setGate({ kind: 'support', origin: 'layout' });\n      return;\n    }\n    if (action === 'checkout' && state.status === 'ready') {\n      const priceId = (payload as { priceId?: string } | undefined)?.priceId;\n      if (!priceId) {\n        onEvent('error', new PaywallError('no_price', 'No price selected'));\n        return;\n      }\n      const mode = state.data.settings.checkout_mode ?? 'guest';\n      // An anonymous session doesn't satisfy the preauth requirement: a\n      // checkout under an anon token would create a subscription on an\n      // account without an email that the user can't restore later. Anon counts\n      // as \"not logged in\", a real signin is required.\n      const cachedSession = client.auth?.getCachedSession() ?? null;\n      const hasRealSession = !!cachedSession && !cachedSession.user.is_anonymous;\n      const needsAuth = mode === 'preauth' && !!client.auth && !hasRealSession;\n      if (needsAuth) {\n        setGate({ kind: 'auth_gate', pendingCheckout: { priceId } });\n        return;\n      }\n      await runCheckout(priceId);\n    }\n  };\n\n  const brand = state.status === 'ready' ? state.data.settings.brand_color : null;\n  // allow_close=undefined is treated as true (the default before bootstrap —\n  // the paywall must be closable during loading/error, otherwise the user gets\n  // trapped). After ready, settings.allow_close=false forbids\n  // ESC/overlay/X-button.\n  const allowClose =\n    state.status === 'ready' ? state.data.settings.allow_close !== false : true;\n\n  // Offer top-tab: only on the main layout view (prices/features). On the\n  // auth/support screens the banner makes no sense — the user is already\n  // outside the \"buy now\" flow, and the urgency timer only distracts. Mirrors\n  // the legacy PaywallModal, where the offer-banner was tied to route='paywall'.\n  const isLayoutView =\n    gate.kind === 'layout' && state.status === 'ready';\n  const activeOffer = isLayoutView ? pickActiveOffer(state.data.offers) : null;\n  const topBanner = activeOffer ? <OfferTopBanner offer={activeOffer} /> : null;\n\n  const gateBlock: AuthPanelBlock = {\n    type: 'auth_panel',\n    // We don't set the heading — AuthGate decides by intent (restore →\n    // \"Restore Purchases\", the rest → the default \"Welcome back!\").\n    allow_signup: true,\n    allow_password_reset: true,\n    // We don't hide it when a session is present — the auto-resume useEffect\n    // runs faster than we'd want to show \"Signed in as ...\" as an intermediate\n    // screen.\n    hide_when_authenticated: false,\n    providers: state.status === 'ready' ? state.data.settings.auth_providers : undefined\n  };\n\n  // The support-view takes priority over bootstrap-state: a standalone open\n  // (paywall.openSupport()) must work even if bootstrap is still loading or\n  // failed — the form itself doesn't depend on settings/prices. From layout\n  // mode Back returns to the plans, from standalone — it closes the modal.\n  const supportView =\n    gate.kind === 'support' ? (\n      <SupportGate\n        client={client}\n        authSession={authSession}\n        origin={gate.origin}\n        onBack={() => {\n          if (gate.origin === 'standalone') onClose();\n          else setGate({ kind: 'layout' });\n        }}\n      />\n    ) : null;\n\n  // In gate-views AuthGate/SupportGate draw their own curved Back button in the\n  // top-right corner. The Modal's X button is there too — the two buttons would\n  // overlap. ESC/overlay-click stay working (if allowClose=true). Standalone\n  // openAuth() — AuthGate doesn't draw Back (the modal is open only for signin,\n  // there's no layout to return to); then the X button is needed, otherwise the\n  // user has nowhere to go but ESC.\n  const hideCloseButton =\n    (gate.kind === 'auth_gate' && gate.origin !== 'standalone') ||\n    gate.kind === 'support';\n\n  const bootstrapForI18n = state.status === 'ready' ? state.data : null;\n\n  return (\n    <I18nProvider bootstrap={bootstrapForI18n} forceLocale={locale}>\n    <Modal\n      open={open}\n      onClose={onClose}\n      brandColor={brand}\n      topBanner={topBanner}\n      allowClose={allowClose}\n      hideCloseButton={hideCloseButton}\n      inline={inline}\n      labelledBy=\"pw-title\"\n    >\n      {/* `Scroll` wraps the self-contained status views (success / loading /\n          error / awaiting-payment / popup-blocked) in a flex-1 scroll zone, so\n          tall content (small viewports, extension popups capped at ~600px, the\n          awaiting-payment screen with its help blocks) scrolls instead of being\n          clipped by the dialog's overflow-hidden. min-h-0 lets the flex child\n          shrink below its content height so overflow-y-auto actually engages.\n          The Renderer / AuthGate / SupportGate views are NOT wrapped — they\n          manage their own flex-1 scroll area + pinned footer, and a second\n          scroll wrapper would break that footer pinning. */}\n      {purchased ? (\n        <Scroll>\n          <PurchaseSuccessView onContinue={onClose} />\n        </Scroll>\n      ) : gate.kind === 'purchase_success' ? (\n        <Scroll>\n          <PurchaseSuccessView restored={gate.restored} onContinue={onClose} />\n        </Scroll>\n      ) : supportView ? (\n        supportView\n      ) : state.status === 'loading' || state.status === 'idle' || gate.kind === 'verifying' ? (\n        <Scroll>\n          <LoadingView verifying={gate.kind === 'verifying'} />\n        </Scroll>\n      ) : state.status === 'error' ? (\n        <Scroll>\n          <ErrorView message={state.error.message} />\n        </Scroll>\n      ) : gate.kind === 'auth_gate' && client.auth ? (\n        <AuthGate\n          block={gateBlock}\n          bootstrap={state.data}\n          auth={client.auth}\n          authSession={authSession}\n          // standalone (paywall.openAuth()) — the modal is open only for\n          // signin, the Back button duplicates ESC/X. Hide it. For\n          // preauth/restore flow Back leads back to layout — keep it.\n          showBack={gate.origin !== 'standalone'}\n          intent={gate.intent ?? (gate.origin === 'standalone' ? 'standalone' : 'preauth')}\n          resumeCheckout={resumeCheckoutFor(gate.pendingCheckout?.priceId)}\n          initialMode={gate.origin === 'standalone' ? initialAuthMode : undefined}\n          onBack={() => {\n            if (gate.origin === 'standalone') onClose();\n            else setGate({ kind: 'layout' });\n          }}\n        />\n      ) : gate.kind === 'awaiting_payment' ? (\n        <Scroll>\n          <AwaitingPaymentView\n            client={client}\n            onBack={() => setGate({ kind: 'layout' })}\n            onReopen={() => {\n              if (typeof window === 'undefined') return;\n              const popup = window.open(gate.url, '_blank');\n              if (popup) {\n                try {\n                  popup.opener = null;\n                } catch {\n                  /* ignore */\n                }\n              }\n            }}\n            onRetry={() => runCheckout(gate.priceId)}\n          />\n        </Scroll>\n      ) : gate.kind === 'popup_blocked' ? (\n        <Scroll>\n          <PopupBlockedView onReopen={() => reopenCheckout(gate.priceId, gate.url)} />\n        </Scroll>\n      ) : (\n        <Renderer\n          layout={state.data.layout!}\n          bootstrap={state.data}\n          onAction={handleAction}\n          auth={client.auth}\n          authSession={authSession}\n          titleOverride={titleOverride}\n        />\n      )}\n    </Modal>\n    </I18nProvider>\n  );\n}\n\n// Scroll zone for the self-contained status views. Mirrors the Renderer's\n// scrollable region (`flex-1 min-h-0 overflow-y-auto`) so content taller than\n// the dialog's capped height (small viewports, ~600px extension popups) becomes\n// scrollable instead of clipped by the dialog's overflow-hidden. flex-col so a\n// child view's own flex layout (centering, gaps) keeps working.\nfunction Scroll({ children }: { children: ComponentChildren }) {\n  return <div class=\"flex min-h-0 flex-1 flex-col overflow-y-auto\">{children}</div>;\n}\n\nfunction LoadingView({ verifying }: { verifying: boolean }) {\n  const { t } = useI18n();\n  return (\n    <div class=\"flex flex-col items-center justify-center gap-3 py-12\">\n      <span class=\"inline-block h-7 w-7 animate-spin rounded-full border-[2.5px] border-gray-200 border-t-[var(--pw-accent)]\" />\n      <span class=\"text-xs font-medium tracking-wide text-gray-500\">\n        {verifying\n          ? t('modal.verifying_subscription', 'Checking your subscription…')\n          : t('modal.loading', 'Loading…')}\n      </span>\n    </div>\n  );\n}\n\nfunction ErrorView({ message }: { message: string }) {\n  const { t } = useI18n();\n  return (\n    <div class=\"flex flex-col items-center gap-2 py-8 text-center\">\n      <div class=\"flex h-11 w-11 items-center justify-center rounded-full bg-red-50\">\n        <svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\" aria-hidden=\"true\">\n          <path d=\"M10 6v5M10 14h.01\" stroke=\"#dc2626\" stroke-width=\"2\" stroke-linecap=\"round\" />\n          <circle cx=\"10\" cy=\"10\" r=\"8\" stroke=\"#dc2626\" stroke-width=\"1.75\" />\n        </svg>\n      </div>\n      <p class=\"text-sm font-semibold tracking-tight text-gray-900\">\n        {t('modal.error_generic', 'Something went wrong')}\n      </p>\n      <p class=\"text-xs leading-relaxed text-gray-500\">{message}</p>\n    </div>\n  );\n}\n\nfunction PopupBlockedView({ onReopen }: { onReopen: () => void }) {\n  const { t } = useI18n();\n  return (\n    <div class=\"flex flex-col items-center gap-3 py-8 text-center\">\n      {/* External-link / open-in-new-window: a window with an arrow going\n       *  up-and-right — the standard \"open in a new tab\" icon. Previously there\n       *  was a check-in-box, which read as \"checked/done\" and didn't convey the\n       *  meaning \"you need to allow the popup\". */}\n      <div\n        class=\"flex h-14 w-14 items-center justify-center rounded-full\"\n        style={{ background: 'color-mix(in srgb, var(--pw-accent) 12%, white)', color: 'var(--pw-accent)' }}\n        aria-hidden=\"true\"\n      >\n        <svg width=\"26\" height=\"26\" viewBox=\"0 0 24 24\" fill=\"none\">\n          <path\n            d=\"M14 4h6v6\"\n            stroke=\"currentColor\"\n            stroke-width=\"2\"\n            stroke-linecap=\"round\"\n            stroke-linejoin=\"round\"\n          />\n          <path\n            d=\"M20 4l-9 9\"\n            stroke=\"currentColor\"\n            stroke-width=\"2\"\n            stroke-linecap=\"round\"\n            stroke-linejoin=\"round\"\n          />\n          <path\n            d=\"M19 13v5a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h5\"\n            stroke=\"currentColor\"\n            stroke-width=\"2\"\n            stroke-linecap=\"round\"\n            stroke-linejoin=\"round\"\n          />\n        </svg>\n      </div>\n      <p\n        id=\"pw-title\"\n        class=\"mt-1 text-lg font-semibold tracking-tight text-gray-900\"\n      >\n        {t('payment.popup_blocked_title', 'Allow popups to continue')}\n      </p>\n      <p class=\"max-w-[20rem] text-sm leading-relaxed text-gray-500\">\n        {t('payment.popup_blocked_message', 'Your browser blocked the checkout tab. Click below to open it.')}\n      </p>\n      <button\n        type=\"button\"\n        onClick={onReopen}\n        class=\"mt-3 rounded-xl px-5 py-2.5 text-sm font-semibold text-white transition-all hover:-translate-y-px hover:brightness-105 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--pw-accent)]\"\n        style={{\n          background:\n            'linear-gradient(180deg, color-mix(in srgb, var(--pw-accent) 92%, white), var(--pw-accent))',\n          boxShadow:\n            '0 1px 2px rgba(15,23,42,0.08), 0 8px 20px -6px color-mix(in srgb, var(--pw-accent) 50%, transparent)'\n        }}\n      >\n        {t('payment.open_checkout_button', 'Open checkout')}\n      </button>\n    </div>\n  );\n}\n\n// Waiting screen after window.open(checkoutUrl). UserWatcher in PaywallUI\n// already polls user-state every 5s (visible tab) — this screen is just a UI\n// wrapper.\n//\n// \"I've paid\" — for the impatient: we force getUser({force:true}) so the cache\n// updates right away, and post a 'paywall_purchase' message into the window —\n// that's what UserWatcher.handleMessage waits for and it immediately triggers\n// its check(). If the subscription isn't active yet (the webhook hasn't\n// arrived), we show an inline timeout for 5s.\n//\n// \"Open checkout again\" — a fallback for the case \"window.open returned a handle\n// but the tab is blocked\" (aggressive mobile blockers). It uses the existing\n// URL without a trip to createCheckout, without disrupting the awaiting_payment\n// state.\n//\n// \"Tab closed? Try again\" — the edge case: a Stripe/Paddle/etc. URL may expire,\n// so we recreate the checkout. A less prominent button.\nfunction AwaitingPaymentView({\n  client,\n  onBack,\n  onReopen,\n  onRetry\n}: {\n  client: BillingClient;\n  onBack: () => void;\n  onReopen: () => void;\n  onRetry: () => void;\n}) {\n  const { t } = useI18n();\n  const [checking, setChecking] = useState(false);\n  const [stillPending, setStillPending] = useState(false);\n  const stillPendingTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n  useEffect(() => {\n    return () => {\n      if (stillPendingTimerRef.current !== null) {\n        clearTimeout(stillPendingTimerRef.current);\n      }\n    };\n  }, []);\n\n  const handleVerify = async () => {\n    if (checking) return;\n    setChecking(true);\n    setStillPending(false);\n    try {\n      const user = await client.getUser({ force: true });\n      if (user.has_active_subscription) {\n        // Wakes UserWatcher — it immediately runs check(), sees the fresh\n        // active user from cache and emits purchase_completed (PaywallUI\n        // switches to PurchaseSuccessView). We don't emit purchase_completed\n        // directly from here — the single source of truth stays in\n        // watcher.onActive.\n        if (typeof window !== 'undefined') {\n          window.postMessage({ type: 'paywall_purchase' }, '*');\n        }\n        return;\n      }\n      // The webhook hasn't arrived yet — we show a hint and collapse it after\n      // 5s so the user can press again. The setTimeout is cancelled on unmount.\n      setStillPending(true);\n      if (stillPendingTimerRef.current !== null) {\n        clearTimeout(stillPendingTimerRef.current);\n      }\n      stillPendingTimerRef.current = setTimeout(() => {\n        setStillPending(false);\n        stillPendingTimerRef.current = null;\n      }, 5000);\n    } catch {\n      setStillPending(true);\n    } finally {\n      setChecking(false);\n    }\n  };\n\n  return (\n    <div class=\"flex flex-col gap-3 px-6 pb-6 pt-4 sm:px-8 sm:pb-8 sm:pt-5\">\n      <button\n        type=\"button\"\n        onClick={onBack}\n        class=\"-ml-1 self-start rounded-md px-1.5 py-0.5 text-xs font-medium text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--pw-accent)]\"\n      >\n        {t('nav.back', '← Back')}\n      </button>\n      <div class=\"flex flex-col items-center gap-3 py-6 text-center\">\n        {/* Icon: a spinner inside a ping-halo. An h-14 container — so it\n         *  matches the success-view in size and reads as a \"primary status\n         *  indicator\" rather than a small inline spinner. */}\n        <div class=\"relative flex h-14 w-14 items-center justify-center\">\n          <span\n            class=\"absolute inset-0 animate-ping rounded-full opacity-40\"\n            style={{ background: 'color-mix(in srgb, var(--pw-accent) 30%, transparent)' }}\n            aria-hidden=\"true\"\n          />\n          <span class=\"relative inline-block h-8 w-8 animate-spin rounded-full border-[2.5px] border-gray-200 border-t-[var(--pw-accent)]\" />\n        </div>\n        <p\n          id=\"pw-title\"\n          class=\"mt-1 text-lg font-semibold tracking-tight text-gray-900\"\n        >\n          {t('payment.awaiting_title', 'Complete payment in the new tab')}\n        </p>\n        <p class=\"max-w-[22rem] text-sm leading-relaxed text-gray-500\">\n          {t(\n            'payment.awaiting_subtitle',\n            \"We'll detect your payment automatically — or click below once you're done.\"\n          )}\n        </p>\n        <button\n          type=\"button\"\n          onClick={handleVerify}\n          disabled={checking}\n          class=\"mt-3 rounded-xl px-5 py-2.5 text-sm font-semibold text-white transition-all hover:-translate-y-px hover:brightness-105 disabled:cursor-not-allowed disabled:opacity-60 disabled:hover:translate-y-0 disabled:hover:brightness-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--pw-accent)]\"\n          style={{\n            background:\n              'linear-gradient(180deg, color-mix(in srgb, var(--pw-accent) 92%, white), var(--pw-accent))',\n            boxShadow:\n              '0 1px 2px rgba(15,23,42,0.08), 0 8px 20px -6px color-mix(in srgb, var(--pw-accent) 50%, transparent)'\n          }}\n        >\n          {checking ? t('payment.checking', 'Checking…') : t('payment.ive_paid', \"I've paid\")}\n        </button>\n        {stillPending ? (\n          <p class=\"text-xs leading-relaxed text-gray-500\">\n            {t('payment.still_processing', 'Payment is still being processed. Please try again in a moment.')}\n          </p>\n        ) : null}\n      </div>\n      <div class=\"rounded-2xl border border-gray-200 bg-gray-50/60 p-3.5\">\n        <p class=\"text-xs leading-relaxed text-gray-600\">\n          {t('payment.popup_help_text', \"Checkout window didn't open or got blocked? Click here to open it again.\")}\n        </p>\n        <button\n          type=\"button\"\n          onClick={onReopen}\n          class=\"mt-2.5 w-full rounded-xl border border-gray-200 bg-white px-3 py-2 text-xs font-semibold text-gray-700 transition-colors hover:border-gray-300 hover:bg-gray-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--pw-accent)]\"\n        >\n          {t('payment.open_checkout_again', 'Open checkout again')}\n        </button>\n      </div>\n      <button\n        type=\"button\"\n        onClick={onRetry}\n        class=\"self-center rounded-md px-2 py-1 text-xs text-gray-500 underline-offset-2 hover:text-gray-900 hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--pw-accent)]\"\n      >\n        {t('payment.tab_closed_retry', 'Tab closed? Try again')}\n      </button>\n    </div>\n  );\n}\n\nfunction PurchaseSuccessView({\n  onContinue,\n  restored = false\n}: {\n  onContinue: () => void;\n  /** true — the user already had an active subscription at the moment of the\n   *  checkout attempt (or it turned out after signIn that a subscription\n   *  exists). Changes the heading to \"Subscription restored\" — without this the\n   *  user thinks they just paid. */\n  restored?: boolean;\n}) {\n  const { t } = useI18n();\n  // Typography/CTA — mirrors the canonical `reset_sent` success-view\n  // (AuthPanel): h-14 icon, text-3xl bold heading, text-base gray-600\n  // subheading, full-width pw-cta-shimmer button. Previously this view used\n  // text-lg/text-sm headings and a small inline button — it stood out from the\n  // rest of the paywall.\n  return (\n    <div class=\"flex flex-col items-center gap-4 px-6 py-6 text-center sm:px-8\">\n      <div\n        class=\"flex h-14 w-14 items-center justify-center rounded-full\"\n        style={{\n          background: 'linear-gradient(135deg, #4ade80, #16a34a)',\n          color: '#fff',\n          boxShadow: '0 0 0 8px rgba(74,222,128,0.12), 0 8px 20px -6px rgba(22,163,74,0.45)'\n        }}\n        aria-hidden=\"true\"\n      >\n        <svg width=\"28\" height=\"28\" viewBox=\"0 0 24 24\" fill=\"none\">\n          <path\n            d=\"M5 13l4 4L19 7\"\n            stroke=\"currentColor\"\n            stroke-width=\"2.5\"\n            stroke-linecap=\"round\"\n            stroke-linejoin=\"round\"\n          />\n        </svg>\n      </div>\n      <p id=\"pw-title\" class=\"mt-1 text-3xl font-bold tracking-tight text-gray-900\">\n        {restored\n          ? t('modal.purchase_restored_title', 'Welcome back')\n          : t('modal.purchase_success_title', 'Payment received')}\n      </p>\n      <p class=\"text-base leading-relaxed text-gray-600\">\n        {restored\n          ? t('modal.purchase_restored_subtitle', \"You're all set — enjoy!\")\n          : t('modal.purchase_success_subtitle', \"You're all set — enjoy!\")}\n      </p>\n      <button\n        type=\"button\"\n        onClick={onContinue}\n        class=\"pw-cta-shimmer relative mt-2 flex min-h-12 w-full items-center justify-center overflow-hidden rounded-3xl px-5 py-2 text-center text-base font-semibold leading-tight text-white transition-transform duration-150 active:scale-[0.98] focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--pw-accent)]\"\n        style={{\n          background:\n            'linear-gradient(135deg, color-mix(in srgb, var(--pw-accent) 55%, white) 0%, var(--pw-accent) 55%, color-mix(in srgb, var(--pw-accent) 90%, black) 100%)',\n          boxShadow:\n            '0 0 20px 0 color-mix(in srgb, var(--pw-accent) 25%, transparent), inset 0 0 8px 0 color-mix(in srgb, white 25%, transparent)'\n        }}\n      >\n        <span class=\"relative z-10\">{t('modal.continue', 'Continue')}</span>\n      </button>\n    </div>\n  );\n}\n","import type { BillingClient } from '../core/BillingClient';\nimport type { PaywallUser } from '../core/types';\n\n// The default parameters are tuned for \"the user pays ~60-90s after clicking\n// Continue, sometimes steps away for a coffee for 5-10 minutes\". See the\n// discussion in TODO.md (the \"What this changes in the architecture\" phase).\nexport interface UserWatcherOptions {\n  client: BillingClient;\n  /** Fired the first time we see has_active_subscription === true. */\n  onActive: (user: PaywallUser) => void;\n  /** Overall watch timeout. On expiry — stop without onActive. */\n  onTimeout?: () => void;\n  timeoutMs?: number;\n  /** Polling interval while the tab is visible. */\n  visibleIntervalMs?: number;\n  /** Polling interval while the tab is hidden (the browser throttles timers). */\n  hiddenIntervalMs?: number;\n}\n\nconst DEFAULT_TIMEOUT_MS = 10 * 60_000;\nconst DEFAULT_VISIBLE_INTERVAL_MS = 5_000;\nconst DEFAULT_HIDDEN_INTERVAL_MS = 30_000;\n\n// Polling after checkout_started.\n//\n// Sources of the \"check now\" signal:\n// 1. visibility change → visible (the user returned to the original tab).\n// 2. window focus.\n// 3. postMessage of the form { type: 'paywall_purchase' } from the success page\n//    (acceleration: a success_url on our origin calls window.opener.postMessage).\n// 4. A regular timer with a visibility-aware schedule.\n//\n// Stop: either has_active_subscription === true, or timeout.\n//\n// Runtime detection: see shouldRunUserWatcher() — the extension popup is discarded,\n// it doesn't survive until the return from checkout. The background service worker\n// is filtered out by the `typeof document` check in start().\nexport class UserWatcher {\n  private opts: Required<Omit<UserWatcherOptions, 'client'>> & { client: BillingClient };\n  private timer: ReturnType<typeof setTimeout> | null = null;\n  private timeoutTimer: ReturnType<typeof setTimeout> | null = null;\n  private visibilityHandler: (() => void) | null = null;\n  private focusHandler: (() => void) | null = null;\n  private messageHandler: ((e: MessageEvent) => void) | null = null;\n  private stopped = false;\n  private checking = false;\n\n  constructor(opts: UserWatcherOptions) {\n    this.opts = {\n      client: opts.client,\n      onActive: opts.onActive,\n      onTimeout: opts.onTimeout ?? (() => {}),\n      timeoutMs: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n      visibleIntervalMs: opts.visibleIntervalMs ?? DEFAULT_VISIBLE_INTERVAL_MS,\n      hiddenIntervalMs: opts.hiddenIntervalMs ?? DEFAULT_HIDDEN_INTERVAL_MS\n    };\n  }\n\n  start(): void {\n    if (this.stopped) return;\n    if (typeof document === 'undefined' || typeof window === 'undefined') return;\n\n    void this.check();\n    this.scheduleNext();\n\n    this.visibilityHandler = () => this.handleVisibilityChange();\n    document.addEventListener('visibilitychange', this.visibilityHandler);\n\n    this.focusHandler = () => void this.check();\n    window.addEventListener('focus', this.focusHandler);\n\n    this.messageHandler = (e: MessageEvent) => this.handleMessage(e);\n    window.addEventListener('message', this.messageHandler);\n\n    this.timeoutTimer = setTimeout(() => {\n      if (this.stopped) return;\n      this.stop();\n      this.opts.onTimeout();\n    }, this.opts.timeoutMs);\n  }\n\n  stop(): void {\n    this.stopped = true;\n    if (this.timer !== null) clearTimeout(this.timer);\n    this.timer = null;\n    if (this.timeoutTimer !== null) clearTimeout(this.timeoutTimer);\n    this.timeoutTimer = null;\n    if (typeof document !== 'undefined' && this.visibilityHandler) {\n      document.removeEventListener('visibilitychange', this.visibilityHandler);\n    }\n    if (typeof window !== 'undefined') {\n      if (this.focusHandler) window.removeEventListener('focus', this.focusHandler);\n      if (this.messageHandler) window.removeEventListener('message', this.messageHandler);\n    }\n    this.visibilityHandler = null;\n    this.focusHandler = null;\n    this.messageHandler = null;\n  }\n\n  private async check(): Promise<void> {\n    if (this.stopped || this.checking) return;\n    this.checking = true;\n    try {\n      const user = await this.opts.client.getUser({ force: true });\n      if (this.stopped) return;\n      if (user.has_active_subscription) {\n        this.stop();\n        this.opts.onActive(user);\n      }\n    } catch {\n      /* transient errors — skip one tick, the poller will fire again */\n    } finally {\n      this.checking = false;\n    }\n  }\n\n  private scheduleNext(): void {\n    if (this.stopped) return;\n    const visible =\n      typeof document !== 'undefined' && document.visibilityState === 'visible';\n    const interval = visible\n      ? this.opts.visibleIntervalMs\n      : this.opts.hiddenIntervalMs;\n    this.timer = setTimeout(async () => {\n      await this.check();\n      this.scheduleNext();\n    }, interval);\n  }\n\n  private handleVisibilityChange(): void {\n    if (typeof document === 'undefined') return;\n    if (document.visibilityState === 'visible') void this.check();\n    // Reschedule the timer with the interval for the new state.\n    if (this.timer !== null) {\n      clearTimeout(this.timer);\n      this.timer = null;\n    }\n    this.scheduleNext();\n  }\n\n  private handleMessage(e: MessageEvent): void {\n    const data = e.data as { type?: string } | null;\n    if (!data || typeof data !== 'object') return;\n    if (data.type !== 'paywall_purchase') return;\n    void this.check();\n  }\n}\n\n// Decide whether it even makes sense to run the watcher in the current runtime.\n// false → the code that should close the paywall on payment relies on a\n// different path (the absence of document — for the MV3 service worker).\n//\n// The watcher needs a DOM + window: it hangs on visibilitychange/focus/message\n// events and a timer. That requirement filters out the service worker.\n//\n// We DON'T gate on the chrome-extension:// protocol. A full extension page /\n// side panel survives the checkout (it opens in a separate tab), so it both\n// can and must poll — gating it out left the awaiting screen with no way to\n// close (the transition funnels through this watcher). The one context this\n// doesn't help is the ephemeral toolbar action-popup: window.open() for the\n// checkout steals focus and Chrome destroys the popup, taking the watcher with\n// it — there the watcher harmlessly tears down and the next-open bootstrap\n// covers detection. So running it everywhere with a DOM is safe.\nexport function shouldRunUserWatcher(): boolean {\n  if (typeof document === 'undefined') return false;\n  if (typeof window === 'undefined') return false;\n  return true;\n}\n","import {\n  AuthClient,\n  type AuthChangeEvent,\n  type AuthClientOptions,\n  type AuthSession\n} from '../core/auth';\nimport { BillingClient, type BillingClientOptions } from '../core/BillingClient';\nimport { EventTracker } from '../core/EventTracker';\nimport { createTrialStore, type TrialStore } from '../core/trial';\nimport {\n  findApplicableOffer,\n  readBrowserOfferStart,\n  resolveOffer,\n  type ResolvedOffer\n} from '../core/offer';\nimport {\n  PaywallError,\n  type Acquiring,\n  type Identity,\n  type PaywallBootstrap,\n  type PaywallOffer,\n  type PaywallPrice,\n  type PaywallUser,\n  type TrialConfig,\n  type TrialStatus,\n  type UserLanguageInfo,\n  type VisibilityStatus\n} from '../core/types';\nimport { STORAGE_KEYS } from '../core/storage';\nimport { mountShadow, type MountHandle } from './mount';\nimport {\n  PaywallRoot,\n  type PaywallRootProps,\n  type PaywallStateSnapshot,\n  type PaywallView\n} from './PaywallRoot';\nimport { UserWatcher, shouldRunUserWatcher } from './UserWatcher';\n\ntype PaywallStateListener = (state: PaywallStateSnapshot) => void;\n\nconst CLOSED_STATE: PaywallStateSnapshot = {\n  open: false,\n  view: null,\n  error: null,\n  processing: false\n};\n\n// The SDK's event contract. The client subscribes via paywall.on(event, handler).\n// Each event is strictly typed — the IDE gives autocomplete on the payload.\nexport interface PaywallEventPayloads {\n  /** The modal is opened (an open request — data may still be loading). */\n  open: void;\n  /** The modal is closed. */\n  close: void;\n  /** Bootstrap is loaded, the modal shows content. Suitable for impression\n   *  metrics. */\n  ready: PaywallBootstrap;\n  /** Any SDK error (bootstrap, checkout). */\n  error: PaywallError;\n  /** The user selected a plan (clicked a plan), hasn't yet initiated checkout. */\n  price_selected: { priceId: string; price: PaywallPrice };\n  /** The checkout URL was received from the backend and opened in a new tab.\n   *  `acquiring` — the name of the payment processor the checkout went to (for\n   *  conversion by acquiring in host analytics). */\n  checkout_started: { priceId: string; url: string; acquiring?: Acquiring };\n  /** The user returned with a successful payment (via URL markers or\n   *  postMessage), or after signIn / a checkout attempt / a suppressed blind\n   *  open() it turned out the subscription is already active (`restored: true`).\n   *  priceId = null when the payment intent wasn't tied to a specific price\n   *  (UserWatcher tick, restore-flow, suppressed open). */\n  purchase_completed: {\n    priceId: string | null;\n    sessionId: string | null;\n    /** true — this isn't a fresh payment but an active subscription that the\n     *  SDK detected and showed the user a success/restored view. Useful for the\n     *  host to distinguish (for metrics — \"restore\" vs \"new purchase\"). */\n    restored?: boolean;\n  };\n  /** The user returned with an error/cancel from the provider. */\n  purchase_failed: { reason: string | null };\n  /** User-state changed (bootstrap snapshot, getUser refresh, watcher tick).\n   *  Also fires right away with the last-known user after the first\n   *  subscription. */\n  userChange: PaywallUser;\n  /** The auth session changed. The payload contains `event` (see\n   *  AuthChangeEvent — INITIAL_SESSION / SIGNED_IN / SIGNED_OUT /\n   *  TOKEN_REFRESHED / USER_UPDATED / PASSWORD_RECOVERY) and `session`\n   *  (null = signed out).\n   *\n   *  Guaranteed contract: the first callback to every subscriber is always\n   *  INITIAL_SESSION with the session restored from storage (or null if none).\n   *  After that — real transitions. A listener with side effects like\n   *  force-refetching balances should catch SIGNED_IN, not any truthy session,\n   *  otherwise a page reload would trigger an extra request. */\n  authChange: { event: AuthChangeEvent; session: AuthSession | null };\n  /** The trial blocked the modal from showing. The payload contains the fresh\n   *  status (after recordBlock). For `mode: 'time'` —\n   *  startedAt/expiresAt/remainingMs; for `mode: 'opens'` —\n   *  remainingActions/totalActions. The host can use the payload to show its\n   *  own UI (\"3 views left\"). */\n  trial_blocked: TrialStatus;\n  /** The trial expired, the paywall is shown for the first time after expiry.\n   *  Emitted once per PaywallUI instance lifetime (not persisted across page\n   *  reloads — on each page-load the event may fire once). */\n  trial_expired: void;\n  /** Targeting didn't match — the paywall doesn't open. The payload contains a\n   *  server-computed snapshot from bootstrap (visible=false + reason + country\n   *  + tier). The host can show its own fallback (\"the service isn't available\n   *  in your country\") or simply log the impression for analytics. */\n  visibility_blocked: VisibilityStatus;\n}\n\nexport type PaywallEvent = keyof PaywallEventPayloads;\n\nexport type PaywallEventHandler<E extends PaywallEvent = PaywallEvent> = (\n  payload: PaywallEventPayloads[E]\n) => void;\n\n// Helper type: a `void` payload is emitted without an argument (`emit('open')`),\n// a non-empty one — with an argument (`emit('ready', bootstrap)`).\ntype EmitArgs<E extends PaywallEvent> = PaywallEventPayloads[E] extends void\n  ? []\n  : [PaywallEventPayloads[E]];\n\nexport interface AnalyticsOptions {\n  enabled?: boolean;\n  /** Full URL to /events. Defaults to `${apiOrigin}/api/v1/paywall/${id}/events`. */\n  endpoint?: string;\n  flushIntervalMs?: number;\n  maxBufferSize?: number;\n  /** Test override for fetch (jsdom/Vitest). */\n  fetch?: typeof fetch;\n  /** Test override for sendBeacon. */\n  sendBeacon?: (url: string, data: BodyInit) => boolean;\n}\n\n/**\n * Managed-auth config. Pass `auth: true` — PaywallUI creates the `AuthClient`\n * itself (with the same `paywallId/apiOrigin/storage` as BillingClient). Pass\n * an object — the same defaults + option overrides. Pass a ready `AuthClient` —\n * PaywallUI just forwards it to BillingClient (useful if the host wants a shared\n * AuthClient across several paywalls / to do manual signIn/signOut from its own\n * UI before opening the modal).\n *\n * Without the `auth` option the SDK works in hybrid mode: identity is passed\n * from outside via `opts.identity` or `paywall.open({identity})`.\n */\nexport type AuthOption = true | AuthClient | Partial<Omit<AuthClientOptions, 'paywallId'>>;\n\nexport interface PaywallUIOptions extends Omit<BillingClientOptions, 'auth'> {\n  client?: BillingClient;\n  host?: HTMLElement;\n  /** Connect the managed-auth layer. See {@link AuthOption}. */\n  auth?: AuthOption;\n  /**\n   * Automatically parse the URL when creating PaywallUI, to catch a return from\n   * a checkout provider (?paywall_status=paid|failed|cancelled). Default: true.\n   * Emits purchase_completed / purchase_failed via a microtask — subscribe\n   * synchronously.\n   */\n  autoDetectReturn?: boolean;\n  /**\n   * Shadow DOM mode. Defaults to `closed` — full isolation from the host. For\n   * e2e tests (Playwright) and live-preview in the admin panel pass `open`.\n   */\n  shadowMode?: 'open' | 'closed';\n  /**\n   * SDK 3.0 analytics. Enabled by default. Pass `false` to fully disable it\n   * (nothing is sent to the backend). Accepts an object with batch settings or\n   * an endpoint override.\n   */\n  analytics?: boolean | AnalyticsOptions;\n  /**\n   * When bootstrap isn't cached — render the modal **immediately** with a\n   * spinner and run the gates (visibility/trial) after the data arrives, or\n   * **wait** for bootstrap and mount only if the gates pass. Default `true` —\n   * a snappy open, the \"open\" button responds instantly.\n   *\n   * Trade-off: with `true` and a blocking gate the modal flickers (opened →\n   * closed after ~200-500ms). On extensions and sites with the targeting\n   * fallback enabled this is a rare path, so the default is optimized for the\n   * main 99% case. Pass `false` if for your use-case a flash on\n   * blocked-countries/devices is worse than the perceived latency.\n   */\n  mountThenLoad?: boolean;\n  /**\n   * Inline mode for the admin panel editor's live-preview. The host is\n   * positioned `absolute inset:0` inside its parent (instead of\n   * fixed-viewport), the Modal's overlay also becomes absolute, and body-scroll\n   * isn't locked. You MUST pass a `host` (HTMLElement) with a positioned parent\n   * — otherwise absolute goes to the nearest positioned ancestor or to html.\n   * Defaults to false.\n   *\n   * @internal Admin-only: used in the monetize.software paywall editor for\n   * live-preview. End SDK integrators don't need to enable it — the modal would\n   * blend into the host's layout instead of being a fullscreen overlay.\n   */\n  inline?: boolean;\n  /**\n   * Explicit language override for I18nProvider. Used by the admin panel\n   * editor's live-preview (\"Preview as user from <country>\") — there the\n   * browser-locale is always EN, but we need to show it as for a user from the\n   * chosen country. Accepts a BCP-47 base-tag from `BUNDLED_LOCALES`\n   * (ru/de/fr/…); EN, null, undefined — fall back to the normal resolution\n   * logic (navigator.language → locale_default).\n   *\n   * Live updates — via {@link PaywallUI.setLocale}.\n   *\n   * @internal Admin-only: for end integrators there's no point forcing the\n   * language — the SDK adapts to the browser-locale itself.\n   */\n  locale?: string | null;\n}\n\n/**\n * Result of `paywall.getAccess()` — answers the host's main question: \"do I\n * need to block the feature for this user?\". No side effects: `recordBlock`\n * isn't called on trial-storage (counters don't move), the modal isn't mounted.\n *\n * `access` semantics:\n *  - `granted` — do NOT block the feature. One of the scenarios:\n *    - `has_subscription` — the user has an active subscription/purchase;\n *    - `visibility_blocked` — targeting (country/device/visibility-flag) didn't\n *       match, the user is outside the paywall's monetization scope →\n *       monetization not applicable;\n *    - `trial_blocked` — the pre-paywall trial is still active.\n *  - `blocked` — block the feature and call `paywall.open()`. The reason is\n *     always `no_subscription`.\n *\n * Discriminated union on `access`: type-narrowing on `result.access === 'blocked'`\n * narrows `reason` to `'no_subscription'`, on `'granted'` — to the three\n * granted variants.\n */\nexport type PaywallAccessResult =\n  | {\n      access: 'granted';\n      reason: 'has_subscription' | 'visibility_blocked' | 'trial_blocked';\n      visibility: VisibilityStatus | null;\n      trial: TrialStatus | null;\n      user: PaywallUser | null;\n    }\n  | {\n      access: 'blocked';\n      reason: 'no_subscription';\n      visibility: VisibilityStatus | null;\n      trial: TrialStatus | null;\n      user: PaywallUser | null;\n    };\n\nexport interface GetAccessOptions {\n  skipTrial?: boolean;\n  skipVisibility?: boolean;\n  signal?: AbortSignal;\n}\n\n/** Internal-only extension of `OpenOptions` — we don't expose `authMode` in the\n *  public API (there are dedicated `openSignin`/`openSignup`), but pass it\n *  through here via private methods plus mountAndShow. */\ntype InternalOpenOptions = OpenOptions & {\n  authMode?: 'signin' | 'signup';\n};\n\nexport interface OpenOptions {\n  identity?: Identity;\n  /** Custom paywall title for this particular open() — replaces the text of\n   *  the h1 `heading` block from the layout (locale overrides included); if the\n   *  layout has no h1, the title is rendered as a new heading at the top.\n   *  Scoped to the call: the next open() without `title` shows the configured\n   *  heading again. Useful for context-specific paywalls (\"Unlock export\",\n   *  \"Projects limit reached\"). Ignored by openSupport/openAuth/checkout —\n   *  those flows don't render the layout. */\n  title?: string;\n  /** Force-open, bypassing the pre-paywall trial check. By default the SDK\n   *  reads `bootstrap.settings.trial` and blocks open() while the trial is\n   *  active. An escape hatch for cases like \"the host decided to show it\n   *  anyway\" or dev mode. */\n  skipTrial?: boolean;\n  /** Force-open, bypassing the targeting gate. By default the SDK reads\n   *  `bootstrap.settings.visibility` and emits `visibility_blocked` without\n   *  opening the modal if visible=false (country/device/visibility-flag didn't\n   *  match). An escape hatch for dev debugging. */\n  skipVisibility?: boolean;\n  /** Renewal/upgrade flow. By default (false) open() for a user with an active\n   *  subscription is suppressed: nothing mounts, and the SDK emits\n   *  `purchase_completed{restored:true}` once per instance — same semantics as\n   *  the visibility/trial gates (\"the feature is already unlocked, no modal\").\n   *  The restored success-view is reserved for flows where the user explicitly\n   *  recovered access (signin auth-resume, Restore purchases, a 409 from\n   *  checkout). With `renew: true` the check is skipped: the plans are always\n   *  shown, and on checkout the SDK passes `ignoreActivePurchase: true` to the\n   *  backend so /start-checkout doesn't return a 409. Use it when the host UI\n   *  explicitly shows a \"Renew\"/\"Upgrade plan\" button. */\n  renew?: boolean;\n}\n\n// URL markers by which the SDK determines the checkout result.\n// The contract is shared with the backend — online adds them to success/cancel\n// URLs.\nconst URL_MARKERS = {\n  status: 'paywall_status',\n  priceId: 'paywall_price_id',\n  sessionId: 'paywall_session_id'\n} as const;\n\n// Storage key holding the purchases already reported to analytics (see\n// sentPurchaseKeys). Per-paywall: one host may run several paywalls.\nconst SENT_PURCHASES_KEY_PREFIX = 'ms_sdk_purchases_reported:';\n// Only the newest keys are kept — the set exists to suppress re-reports of a\n// live subscription, not to be an audit log.\nconst MAX_SENT_PURCHASE_KEYS = 20;\n\nexport class PaywallUI {\n  readonly billing: BillingClient;\n  /** AuthClient (managed-auth) or undefined in hybrid mode. Publicly available:\n   *  the host can call `paywall.auth?.signOut()`, read `getCachedSession()`,\n   *  subscribe to `onAuthChange` directly. */\n  readonly auth: AuthClient | undefined;\n  private ownsAuth: boolean;\n  private host?: HTMLElement;\n  private shadowMode: 'open' | 'closed';\n  private handle: MountHandle | null = null;\n  private isOpen = false;\n  private listeners = new Map<PaywallEvent, Set<PaywallEventHandler>>();\n  private userUnsub: (() => void) | null = null;\n  private authUnsub: (() => void) | null = null;\n  private watcher: UserWatcher | null = null;\n  private tracker: EventTracker | null = null;\n  private purchased = false;\n  /** The view the modal was last mounted with. Gates the `paywall_viewed`/\n   *  `paywall_closed` analytics to the real paywall (`'layout'`): opening\n   *  support / standalone-auth / awaiting_payment emits the public `'ready'`\n   *  and `'close'`, but that's not \"paywall viewed/closed\" — otherwise a\n   *  support click sends a false `paywall_viewed`. Protected: sdk-extension's\n   *  PaywallUI mirrors these tracker bindings (bindAnalytics) and needs the\n   *  same gate. */\n  protected lastMountedView: PaywallView | null = null;\n  /** Whether the CURRENT mount session tracked `paywall_viewed`. Gates\n   *  `paywall_closed`: a delayed gate (visibility/trial/subscription) closing a\n   *  mount-then-load spinner emits the public 'close', but no paywall was ever\n   *  seen — tracking closed without viewed breaks the funnel (closed > viewed).\n   *  Reset on every mountAndShow. Protected: sdk-extension's bindAnalytics\n   *  mirrors the tracker bindings and needs the same gate. */\n  protected viewedTracked = false;\n  /** Whether a delayed gate (mount-then-load) is still deciding on the CURRENT\n   *  mount. While true, `paywall_viewed` is held back instead of tracked: the\n   *  layout may render before the gate resolves (the subscription gate awaits\n   *  getSettledUser — a network round-trip), and a gate that then blocks would\n   *  leave a viewed/closed pair behind for a paywall nobody was allowed to see.\n   *  Released by releaseViewedGate. Protected: sdk-extension's bindAnalytics\n   *  mirrors the tracker bindings and needs the same gate. */\n  protected viewedGatePending = false;\n  /** The bootstrap of a `ready` that arrived while viewedGatePending — replayed\n   *  into `paywall_viewed` if the gates pass (or if the user closes the modal\n   *  first: they did see the layout). */\n  protected pendingViewed: PaywallBootstrap | null = null;\n  /** Whether the CURRENT mount is done accepting views: a gate blocked it, or\n   *  it was closed. Stops a late 'ready' (the layout finished rendering after\n   *  the close) from tracking a view nobody saw. Reset by mountAndShow. */\n  protected viewedGateSettled = false;\n  /** Tracks `paywall_viewed` through whichever transport this instance uses —\n   *  assigned by initTracker (EventTracker) and by sdk-extension's bindAnalytics\n   *  (RemoteEventTracker). Lets releaseViewedGate replay a held view without\n   *  knowing the transport. */\n  protected trackViewedFn: ((b: PaywallBootstrap) => void) | null = null;\n  /** Purchase keys already reported to analytics, mirrored in memory from\n   *  storage. `handlePurchaseDetected` fires whenever an active subscription is\n   *  discovered — not only right after paying — and `this.purchased` only\n   *  dedupes within ONE instance. In an extension every popup open builds a new\n   *  one, so a subscriber re-reported a purchase on every visit (up to 21 per\n   *  visitor; ~1.9x inflation overall). Storage is shared across popup /\n   *  content-script / offscreen, so the dedupe survives instance churn.\n   *  Kept in memory because the check sits on the event path and must stay\n   *  synchronous — an await there would reorder the tracker's batch. */\n  private sentPurchaseKeys = new Set<string>();\n  /** Whether a checkout was started at any point in THIS instance's life.\n   *  Unlike `checkoutStartedSinceMount` (reset by every mountAndShow) this\n   *  survives remounts — it marks the instance as \"the user is actively buying\n   *  here\", which exempts its purchase from the analytics dedupe. */\n  private checkoutStartedSinceInit = false;\n  /** Serializes storage writes of `sentPurchaseKeys` so two quick purchases\n   *  can't interleave read-modify-write and lose a key. */\n  private persistPurchasesChain: Promise<void> = Promise.resolve();\n  /** Per-open custom title (OpenOptions.title) of the current mount. null —\n   *  the layout's own heading is shown. Kept for the `paywall_viewed`\n   *  analytics flag. */\n  private titleOverride: string | null = null;\n  /** Lazy TrialStore instance. Resolved on the first open(), when we already\n   *  know `bootstrap.settings.trial`. null — the trial is disabled in the\n   *  paywall config. */\n  private trialStore: TrialStore | null = null;\n  /** The config the current trialStore was created for — we rebuild it if it\n   *  changed between bootstrap fetches (e.g. the owner switched the mode in the\n   *  admin panel between SDK sessions). */\n  private trialStoreConfig: TrialConfig | null = null;\n  /** In-memory snapshot of the last check() — for synchronous getTrialStatus(). */\n  private lastTrialStatus: TrialStatus | null = null;\n  /** Dedupe flag for the `trial_expired` event within the instance's lifetime. */\n  private trialExpiredFired = false;\n  /** Dedupe flag for the suppressed-open `purchase_completed{restored}` emit —\n   *  a subscriber clicking a gated feature on every popup visit shouldn't spam\n   *  the host (and events analytics) with a restored signal per click. */\n  private restoredEmitted = false;\n  /** Whether the current mount was opened with `renew: true`. The post-mount\n   *  subscription corrective must not close an explicit Renew/Upgrade flow. */\n  private mountedRenew = false;\n  /** Whether a checkout was started within the current mount session. Blocks\n   *  the post-mount corrective: a purchase landing mid-flow is handled by\n   *  handlePurchaseDetected (success view), not by a suppress-close. */\n  private checkoutStartedSinceMount = false;\n  /** In-memory snapshot of the last bootstrap — for synchronous getVisibility(). */\n  private lastVisibility: VisibilityStatus | null = null;\n  /** open() behavior on a cold bootstrap. See PaywallUIOptions.mountThenLoad. */\n  private mountThenLoad: boolean;\n  /** Inline mode (editor's live-preview). See PaywallUIOptions.inline. */\n  private inline: boolean;\n  /** Force-locale for I18nProvider. See PaywallUIOptions.locale. */\n  private forceLocale: string | null;\n  /** The current UI state-machine snapshot. Updated by PaywallRoot via the\n   *  `onState` prop; reset back to CLOSED_STATE on close. */\n  private currentState: PaywallStateSnapshot = CLOSED_STATE;\n  private stateListeners = new Set<PaywallStateListener>();\n\n  constructor(opts: PaywallUIOptions) {\n    // Resolve the AuthClient: a ready instance / managed config (true|object) /\n    // undefined. ownsAuth=true → we created it ourselves and must clean it up\n    // in destroy().\n    const { auth, ownsAuth } = resolveAuth(opts);\n    this.auth = auth;\n    this.ownsAuth = ownsAuth;\n\n    // If auth exists — we forward it to BillingClient (which connects Bearer\n    // and auto-syncs identity via onAuthChange itself). The client from opts\n    // wins — we assume the host already configured it itself and don't overwrite\n    // its auth.\n    this.billing =\n      opts.client ?? new BillingClient({ ...opts, auth: this.auth });\n    this.host = opts.host;\n    this.shadowMode = opts.shadowMode ?? 'closed';\n    this.mountThenLoad = opts.mountThenLoad ?? true;\n    this.inline = opts.inline === true;\n    this.forceLocale = opts.locale ?? null;\n\n    // Forward user-change events from BillingClient to PaywallUI's public API.\n    // One source of truth (BillingClient cache) — two consumers (the host via\n    // paywall.onUserChange and the watcher itself via billing.onUserChange).\n    this.userUnsub = this.billing.onUserChange((user) => {\n      this.emit('userChange', user);\n      // Drive the awaiting→success transition from the user-state itself, not\n      // only from UserWatcher. The manual \"I've paid\" button (getUser → applyUser\n      // → here) and cross-context broadcasts flip cachedUser to active and land\n      // here even where the watcher doesn't run (a full extension page on\n      // chrome-extension://). Guard on the checkout views so we don't transition\n      // when the paywall opens for an already-subscribed user — that path is\n      // getAccess=granted and never mounts awaiting_payment.\n      if (\n        user.has_active_subscription &&\n        (this.lastMountedView === 'awaiting_payment' ||\n          this.lastMountedView === 'popup_blocked')\n      ) {\n        this.handlePurchaseDetected(user);\n        return;\n      }\n      // Post-mount corrective for the subscription gate: the truth about an\n      // active subscription arrived AFTER a blind open() mounted the layout\n      // (the settled user resolved null on a network hiccup; a background\n      // revalidate / cross-context broadcast caught up later). Close the modal\n      // and give the host the same signal as a pre-mount suppress. Strictly a\n      // blind layout mount showing the layout itself: renew flows keep the\n      // picker, a checkout started in this mount is finished by\n      // handlePurchaseDetected (success view), and the internal auth/support/\n      // verifying navigations run their own flows — the post-signin verifying\n      // state maps to view 'loading' and must land on the restored\n      // success-view, not a silent close.\n      if (\n        user.has_active_subscription &&\n        this.isOpen &&\n        !this.purchased &&\n        !this.mountedRenew &&\n        !this.checkoutStartedSinceMount &&\n        this.lastMountedView === 'layout' &&\n        this.currentState.view === 'layout'\n      ) {\n        // Same ordering as the gates in runDelayedGates: drop the held view\n        // BEFORE close(), or the close binding reads a live hold as a\n        // user-initiated close and replays the phantom viewed/closed pair for a\n        // subscriber who was never meant to see the paywall. This is the one\n        // close() outside runDelayedGates that can race a pending gate —\n        // applyUser fires its listeners synchronously, so a cross-context\n        // userChange lands here while the gate is still parked on an await.\n        this.releaseViewedGate(false);\n        this.close();\n        this.emitRestoredOnce();\n      }\n    });\n\n    if (this.auth) {\n      this.authUnsub = this.auth.onAuthChange((event, session) => {\n        this.emit('authChange', { event, session });\n      });\n    }\n\n    // Persist the one-shot checkout-pending marker on every started checkout\n    // (both the PaywallRoot-driven and the headless direct-checkout emits land\n    // here). getSettledUser reads it on the next cold start: while it's fresh,\n    // a persisted \"no subscription\" is re-checked against the network instead\n    // of being trusted — otherwise the first popup open after a purchase\n    // flashes the paywall before the corrective closes it (the popup died\n    // before the purchase confirmation could update the persisted user).\n    this.on('checkout_started', () => {\n      this.checkoutStartedSinceInit = true;\n      this.markCheckoutPending();\n    });\n\n    this.initTracker(opts.analytics);\n    // Warm the purchase-dedupe mirror. Fire-and-forget: the check falls back to\n    // \"report it\" until this resolves, and a purchase can't be discovered\n    // before the first user-state anyway.\n    void this.loadSentPurchases();\n\n    if (opts.autoDetectReturn !== false && typeof window !== 'undefined') {\n      // Microtask — the client has time to subscribe synchronously after the\n      // constructor, before the event actually fires.\n      queueMicrotask(() => this.checkReturn());\n    }\n  }\n\n  /** Identifies the purchase behind a `purchase_completed` for the analytics\n   *  dedupe. The checkout session id when we have one (URL-marker returns);\n   *  otherwise the active purchase ids — so a genuinely NEW purchase (an\n   *  upgrade, a second subscription) produces a different key and is reported,\n   *  while re-discovering the same one is not. null — nothing stable to key on\n   *  (no session, empty purchases): we report rather than risk swallowing. */\n  private purchaseDedupeKey(p: {\n    sessionId?: string | null;\n  }): string | null {\n    if (p.sessionId) return `s:${p.sessionId}`;\n    try {\n      const purchases = this.billing.getCachedUser()?.purchases;\n      // Guard the mapping too, not just the read: a corrupted persisted user or\n      // a host-supplied client could hand back a non-array, and a TypeError\n      // escaping here would be swallowed by emit()'s per-listener catch —\n      // silently dropping the analytics event while the host still gets it.\n      if (!Array.isArray(purchases)) return null;\n      const ids = purchases\n        .map((x) => x?.id)\n        .filter((id): id is string => typeof id === 'string' && id.length > 0)\n        .sort();\n      return ids.length ? `p:${ids.join(',')}` : null;\n    } catch {\n      return null;\n    }\n  }\n\n  /** Analytics gate for `purchase_completed`: true the first time we see this\n   *  purchase, false for every re-discovery. Only the tracker consults it — the\n   *  public event still reaches the host exactly as before. */\n  protected shouldTrackPurchase(p: { sessionId?: string | null }): boolean {\n    // A checkout ran in THIS instance — the user actively bought something, so\n    // report it and never consult the key. Critical for renew/upgrade: an\n    // already-subscribed user keeps their old purchase ids, so the key equals\n    // the one stored when that first subscription was bought, and the dedupe\n    // would swallow the upgrade entirely (the watcher fires on the pre-existing\n    // subscription, marks `purchased`, and no later event makes up for it).\n    // The inflation this dedupe targets comes from PASSIVE discoveries — a\n    // re-created popup finding an existing subscription — where no checkout\n    // ever ran in the instance.\n    if (this.checkoutStartedSinceInit) return true;\n    const key = this.purchaseDedupeKey(p);\n    if (!key) return true;\n    if (this.sentPurchaseKeys.has(key)) return false;\n    this.sentPurchaseKeys.add(key);\n    void this.persistSentPurchases();\n    return true;\n  }\n\n  private sentPurchasesStorageKey(): string {\n    return `${SENT_PURCHASES_KEY_PREFIX}${this.billing.paywallId}`;\n  }\n\n  /** Warm the in-memory mirror from storage. Until it resolves the set is\n   *  empty, so a purchase landing in the first moments of an instance may be\n   *  reported twice — the safe direction (a rare duplicate beats a lost sale).\n   */\n  private async loadSentPurchases(): Promise<void> {\n    try {\n      const raw = await this.billing\n        .getStorage()\n        .getItem(this.sentPurchasesStorageKey());\n      if (!raw) return;\n      const parsed = JSON.parse(raw);\n      if (!Array.isArray(parsed)) return;\n      for (const key of parsed) {\n        if (typeof key === 'string') this.sentPurchaseKeys.add(key);\n      }\n    } catch {\n      /* best-effort: analytics dedupe must never break the paywall */\n    }\n  }\n\n  /** Read-modify-write, serialized. A blind overwrite would drop keys in the\n   *  two cases that matter: a purchase reported before loadSentPurchases()\n   *  resolved (the mirror is still empty, so the write would erase everything\n   *  stored), and two live contexts in an extension (popup + content-script)\n   *  each writing their own view. Both directions produce re-reports — the very\n   *  inflation this exists to stop. Newly added keys go LAST so the tail-slice\n   *  can never evict the key we just recorded. */\n  private persistSentPurchases(): Promise<void> {\n    this.persistPurchasesChain = this.persistPurchasesChain\n      .catch((): undefined => undefined)\n      .then(async () => {\n        try {\n          const storage = this.billing.getStorage();\n          const key = this.sentPurchasesStorageKey();\n          const merged = new Set<string>();\n          const raw = await storage.getItem(key);\n          if (raw) {\n            const parsed = JSON.parse(raw);\n            if (Array.isArray(parsed)) {\n              for (const k of parsed) {\n                if (typeof k === 'string') merged.add(k);\n              }\n            }\n          }\n          // Ours last: Set preserves insertion order, so the tail-slice below\n          // keeps the newest keys rather than the ones just read back.\n          for (const k of this.sentPurchaseKeys) {\n            merged.delete(k);\n            merged.add(k);\n          }\n          for (const k of merged) this.sentPurchaseKeys.add(k);\n          await storage.setItem(\n            key,\n            JSON.stringify(Array.from(merged).slice(-MAX_SENT_PURCHASE_KEYS))\n          );\n        } catch {\n          /* best-effort: analytics dedupe must never break the paywall */\n        }\n      });\n    return this.persistPurchasesChain;\n  }\n\n  /** The `ready` → `paywall_viewed` gate, shared by initTracker and\n   *  sdk-extension's bindAnalytics. Returns false when the view must not be\n   *  tracked right now: a non-layout mount (support/auth/awaiting_payment), or\n   *  a delayed gate still pending — then the bootstrap is held for\n   *  releaseViewedGate instead of dropped. */\n  protected acceptViewed(b: PaywallBootstrap): boolean {\n    if (this.lastMountedView !== 'layout') return false;\n    // This mount is already over — a gate closed the spinner, or the user did.\n    // A 'ready' still arriving (the layout finished rendering after the close)\n    // is not a view. Reset by the next mountAndShow.\n    if (this.viewedGateSettled) return false;\n    if (this.viewedGatePending) {\n      this.pendingViewed = b;\n      return false;\n    }\n    this.viewedTracked = true;\n    return true;\n  }\n\n  /** Ends the delayed-gate hold. `passed=true` — no gate blocked (or the user\n   *  closed the modal themselves, having seen the layout): a held `ready` is\n   *  replayed into `paywall_viewed`, so a later `close` still pairs with it.\n   *  `passed=false` — a gate blocked: the held view is dropped, leaving only\n   *  the gate's own event in analytics. */\n  protected releaseViewedGate(passed: boolean): void {\n    this.viewedGatePending = false;\n    const held = this.pendingViewed;\n    this.pendingViewed = null;\n    // A blocked gate ends this mount for analytics — no later 'ready' counts.\n    if (!passed) this.viewedGateSettled = true;\n    if (!passed || !held || this.lastMountedView !== 'layout') return;\n    this.viewedTracked = true;\n    this.trackViewedFn?.(held);\n  }\n\n  private initTracker(analytics: PaywallUIOptions['analytics']): void {\n    if (analytics === false) return;\n    const cfg: AnalyticsOptions =\n      typeof analytics === 'object' && analytics !== null ? analytics : {};\n    if (cfg.enabled === false) return;\n\n    // A thunk, not a string: after an edge failover (core/edge.ts) events must\n    // follow the origin that is actually reachable, resolved at flush time.\n    const endpoint =\n      cfg.endpoint ??\n      (() => `${this.billing.activeApiOrigin()}/api/v1/paywall/${this.billing.paywallId}/events`);\n\n    this.tracker = new EventTracker({\n      endpoint,\n      paywallId: this.billing.paywallId,\n      capabilities: this.billing.capabilities,\n      getVisitorId: () => this.billing.getVisitorId(),\n      getCachedVisitorId: () => this.billing.getCachedVisitorId(),\n      getUserId: () => this.billing.getIdentity()?.userId ?? null,\n      // A/B: enrich every event with the assigned variant. Read from the\n      // materialized bootstrap (not a dedicated method) so it also works\n      // through proxying clients (sdk-extension RemoteBillingClient mirrors\n      // getCachedBootstrap).\n      getExperimentContext: () => {\n        const experiment = this.billing.getCachedBootstrap()?.experiment;\n        return experiment?.assigned_variant\n          ? { experiment_id: experiment.id, variant: experiment.assigned_variant }\n          : null;\n      },\n      flushIntervalMs: cfg.flushIntervalMs,\n      maxBufferSize: cfg.maxBufferSize,\n      fetch: cfg.fetch,\n      sendBeacon: cfg.sendBeacon\n    });\n\n    // Bind internal SDK events to the analytics transport. One emitter, one\n    // consumer (the tracker) — nobody but the tracker should touch these event\n    // names outside PaywallUI.\n    // paywall_viewed — only for the real paywall ('layout'). The public\n    // 'ready'/'close' are emitted for support/auth/awaiting_payment too, but\n    // that's not \"paywall viewed\" (see lastMountedView). 'open' is no longer\n    // tracked separately: 'viewed' (on 'ready', after bootstrap loads) is the\n    // single signal of a paywall view.\n    this.trackViewedFn = (b) => {\n      this.tracker?.track('paywall_viewed', {\n        is_test_mode: b.settings.is_test_mode,\n        prices_count: b.prices.length,\n        offers_count: b.offers.length,\n        // The host passed open({title}) — the configured heading is replaced.\n        // Lets host-side title experiments be told apart in analytics.\n        ...(this.titleOverride ? { custom_title: true } : {})\n      });\n    };\n    this.on('ready', (b) => {\n      if (!this.acceptViewed(b)) return;\n      this.trackViewedFn?.(b);\n    });\n    this.on('price_selected', (p) =>\n      this.tracker?.track('price_selected', { price_id: p.priceId })\n    );\n    this.on('checkout_started', (p) =>\n      this.tracker?.track('checkout_started', {\n        price_id: p.priceId,\n        acquiring: p.acquiring\n      })\n    );\n    this.on('purchase_completed', (p) => {\n      // restored=true is \"an active subscription was discovered\" (suppressed\n      // open, signin auth-resume, 409 in checkout) — not a purchase. Tracking\n      // it inflates the events dashboard with purchases that have no\n      // transaction behind them; the public event still reaches the host.\n      if (p.restored) return;\n      // Same purchase already reported (a re-discovered subscription in a new\n      // popup / tab) — the host still got the event, analytics doesn't.\n      if (!this.shouldTrackPurchase(p)) return;\n      this.tracker?.track('purchase_completed', {\n        price_id: p.priceId,\n        session_id: p.sessionId\n      });\n    });\n    this.on('purchase_failed', (p) =>\n      this.tracker?.track('purchase_failed', { reason: p.reason })\n    );\n    this.on('close', () => {\n      // A close while the gates are still pending is the USER closing the modal\n      // (a blocking gate drops the hold before calling close()) — the layout\n      // did render, so the held view is released and the pair still lands.\n      if (this.viewedGatePending) this.releaseViewedGate(true);\n      // paywall_closed only when THIS mount session tracked paywall_viewed —\n      // a delayed gate closing the mount-then-load spinner is not \"the user\n      // closed the paywall\" (viewedTracked implies lastMountedView==='layout').\n      if (this.viewedTracked) this.tracker?.track('paywall_closed');\n      this.viewedTracked = false;\n      // The mount is over: a 'ready' still in flight must not add a view.\n      this.viewedGateSettled = true;\n    });\n    this.on('trial_blocked', (s) =>\n      this.tracker?.track('trial_blocked', {\n        mode: s.mode,\n        ...(s.mode === 'time'\n          ? { remaining_ms: s.remainingMs, total_ms: s.totalMs }\n          : s.mode === 'opens'\n            ? { remaining_actions: s.remainingActions, total_actions: s.totalActions }\n            : {})\n      })\n    );\n    this.on('trial_expired', () => this.tracker?.track('trial_expired'));\n    this.on('visibility_blocked', (v) =>\n      this.tracker?.track('visibility_blocked', {\n        reason: v.reason,\n        country: v.country,\n        tier: v.tier\n      })\n    );\n    this.on('error', (e) =>\n      this.tracker?.track('error', { code: e.code, message: e.message })\n    );\n    // auth_signin_success / auth_signout aren't fired yet: authChange is\n    // emitted on session hydration (the UI restores the cache from storage), on\n    // token refresh, and with parallel consumers of one auth-state — it gives\n    // false signins. Real login events should be caught via direct\n    // signInWithEmail/signUp/signInWithOAuth/signOut calls, not via authChange.\n  }\n\n  /**\n   * Send an arbitrary analytics event. Names from the system whitelist\n   * (`app_opened`, `paywall_viewed`, ...) are allowed as-is. Custom ones —\n   * with a `host:` prefix (e.g. `host:user_clicked_upgrade`). The server drops\n   * events with disallowed names.\n   *\n   * The most common case is `track('app_opened')` from the host right after the\n   * app loads, to record the funnel before the paywall opens.\n   */\n  track(name: string, props?: Record<string, unknown>): void {\n    this.tracker?.track(name, props);\n  }\n\n  /**\n   * A convenient shortcut for `paywall.on('userChange', cb)` — the most common\n   * pattern in host code, hence a separate named method. The callback receives\n   * the last-known user from the cache synchronously via a microtask, if any.\n   */\n  onUserChange(handler: PaywallEventHandler<'userChange'>): () => void {\n    return this.on('userChange', handler);\n  }\n\n  /**\n   * Replace cachedBootstrap with live data — for preview mode in the admin\n   * panel editor. If the modal is open, PaywallRoot is subscribed to\n   * onBootstrapChange and re-renders instantly. Before open() — a seed for the\n   * bootstrap() effect.\n   *\n   * See {@link BillingClientOptions.preview} — usually this option is set on\n   * the client to also disable the network revalidate. setBootstrap technically\n   * works in production mode too, but competing with a revalidate from the\n   * network is almost always undesirable.\n   */\n  setBootstrap(partial: Partial<PaywallBootstrap>): void {\n    this.billing.setBootstrap(partial);\n  }\n\n  /**\n   * Change the force-locale on the fly — for the admin panel editor's\n   * live-preview, when the user switches \"Preview as user from <country>\".\n   * Loads the corresponding static chunk and forces a re-render via\n   * handle.update. See PaywallUIOptions.locale.\n   *\n   * Pass `null`/`undefined` to return to the automatic resolution logic\n   * (navigator.language → locale_default).\n   */\n  setLocale(locale: string | null | undefined): void {\n    const next = locale ?? null;\n    if (next === this.forceLocale) return;\n    this.forceLocale = next;\n    // handle exists only if the modal is open; otherwise the locale is picked\n    // up on the next mountAndShow() from the saved this.forceLocale.\n    if (this.handle) {\n      this.handle.update({ locale: next });\n    }\n  }\n\n  on<E extends PaywallEvent>(event: E, handler: PaywallEventHandler<E>): () => void {\n    let set = this.listeners.get(event);\n    if (!set) {\n      set = new Set();\n      this.listeners.set(event, set);\n    }\n    set.add(handler as PaywallEventHandler);\n    return () => set!.delete(handler as PaywallEventHandler);\n  }\n\n  off<E extends PaywallEvent>(event: E, handler: PaywallEventHandler<E>): void {\n    this.listeners.get(event)?.delete(handler as PaywallEventHandler);\n  }\n\n  private emit<E extends PaywallEvent>(event: E, ...args: EmitArgs<E>): void {\n    const set = this.listeners.get(event);\n    if (!set) return;\n    const payload = args[0] as PaywallEventPayloads[E];\n    for (const handler of set) {\n      try {\n        (handler as PaywallEventHandler<E>)(payload);\n      } catch (error) {\n        if (typeof console !== 'undefined') console.error('[paywall] listener error', error);\n      }\n    }\n  }\n\n  open(opts: OpenOptions = {}): void {\n    this.openInternal('layout', opts);\n  }\n\n  /**\n   * Warms up the bootstrap cache and balance cache in advance, without opening\n   * the modal. Useful when the host knows the user will soon open the paywall\n   * (hover on the CTA, component mount) — the first `open()` renders instantly,\n   * without a loading flash.\n   *\n   * Doesn't throw: if the network failed, it silently ignores it (a repeat\n   * open() does a fresh bootstrap with an error-state as usual). `signal` for\n   * cancellation — e.g. if the host unmounts the component faster than bootstrap\n   * returns.\n   *\n   * Can be called any number of times — subsequent calls return a cached\n   * Promise (BillingClient already deduplicates).\n   */\n  async preload(opts: { signal?: AbortSignal } = {}): Promise<void> {\n    try {\n      await this.billing.bootstrap({ signal: opts.signal });\n      // Balances — best-effort: paywalls without `tokenization` return an empty\n      // array, and getBalances doesn't make a network request for an unauth\n      // user.\n      if (this.billing.auth) {\n        await this.billing.getBalances({ signal: opts.signal });\n      }\n    } catch {\n      /* preload is best-effort — open() will show the error-state itself */\n    }\n  }\n\n  /**\n   * Opens the modal straight to the support form (bypassing the layout with\n   * plans). Useful when the host app wants to give the user a \"Help / Support\"\n   * button unrelated to a paywall upgrade. Back/Done in the support form close\n   * the modal (don't return to the plans), because the user came here directly.\n   *\n   * From the regular `paywall.open()` flow support is still available via the\n   * Contact Support link in the `current_session` block (there Back returns to\n   * the layout).\n   */\n  openSupport(opts: OpenOptions = {}): void {\n    this.openInternal('support', opts);\n  }\n\n  /**\n   * Opens the modal straight to the auth-gate (login/registration), without the\n   * layout with plans. Scenario: a returning customer already bought and just\n   * needs to sign in so the SDK picks up their purchases. After signIn the\n   * modal closes; Back also closes it (the user came only to log in).\n   *\n   * Without `auth` (managed-auth not connected) the method is a no-op: there's\n   * no one to do signIn. If the user is already signed in — the modal still\n   * opens and closes via auto-resume in the auth_gate effect (instantly).\n   *\n   * The trial doesn't block this flow — auth isn't connected to the trial\n   * mechanics.\n   */\n  openAuth(opts: OpenOptions = {}): void {\n    if (!this.auth) return;\n    this.openInternal('auth', { ...opts, skipTrial: true });\n  }\n\n  /**\n   * A shortcut over `openAuth()` — opens the modal straight to the signin form.\n   * Equivalent to `openAuth()` (signin is the default). Exists for symmetry with\n   * `openSignup()` and host-code readability:\n   *   - `paywall.openSignin()` — \"log in to an existing account\"\n   *   - `paywall.openSignup()` — \"new registration\"\n   * Without managed-auth — a no-op.\n   */\n  openSignin(opts: OpenOptions = {}): void {\n    if (!this.auth) return;\n    this.openInternal('auth', { ...opts, skipTrial: true, authMode: 'signin' });\n  }\n\n  /**\n   * Opens the modal with the auth-gate straight in registration mode (the\n   * AuthPanel's signup mode — email/password/repeat). If the admin disabled\n   * allow_signup in the paywall layout, AuthPanel ignores the mode and starts\n   * with signin — the admin config is respected.\n   * Without managed-auth — a no-op.\n   */\n  openSignup(opts: OpenOptions = {}): void {\n    if (!this.auth) return;\n    this.openInternal('auth', { ...opts, skipTrial: true, authMode: 'signup' });\n  }\n\n  /**\n   * Direct-checkout: create a checkout URL for a specific price and immediately\n   * open the payment provider, bypassing the layout with plans. Useful when the\n   * host app renders pricing cards/a table with its own UI and wants a click on\n   * \"Buy / Get this plan\" to lead straight to Stripe/Paddle.\n   *\n   * **Late-mount UX.** Unlike `open()`, the modal doesn't appear during the\n   * background work (bootstrap + visibility/trial gates + createCheckout). The\n   * host shows a busy-state right on its own button during this phase (via\n   * `state.processing === true` from `paywall.getState()` — or automatically via\n   * `<PaywallButton priceId>` in sdk-react). The modal is mounted ONLY when the\n   * UI is really needed:\n   *  - `checkout_mode='preauth'` + managed-auth + not signed in → auth-gate\n   *    (the signin form); after success, auto-resume into createCheckout.\n   *  - the provider's popup is blocked by the browser → a popup_blocked view\n   *    with a retry button under a fresh user gesture.\n   *  - the popup opened successfully → an awaiting_payment view (a \"pay in the\n   *    new tab\" indicator + I've paid).\n   *\n   * What's emitted without the modal:\n   *  - `purchase_completed{restored:true, priceId}` when the user is already\n   *    subscribed (cached user, fresh bootstrap, or a 409 hasActivePurchase\n   *    from the backend) — a headless reject;\n   *  - `error` when createCheckout failed or identity.email is missing;\n   *  - `visibility_blocked` / `trial_blocked` — the standard gate events.\n   *\n   * What's emitted together with the modal:\n   *  - `checkout_started{priceId, url, acquiring}` exactly when the headless URL\n   *    is received, BEFORE mounting awaiting_payment/popup_blocked.\n   *\n   * The offer (countdown discount) is automatically resolved from cached offers\n   * via `getOfferForPrice(priceId)` and passed into createCheckout as `offerId`\n   * — so duration_minutes offers also apply on the backend (there's no\n   * server-side timer for them, and without an explicit offer-id the discount is\n   * lost).\n   *\n   * Requirements:\n   *  - `identity.email` must be set (via `opts.identity`, or managed-auth, or an\n   *    early `setIdentity`/`paywall.open({identity})`). Without an email the\n   *    backend `/start-checkout` returns 400; the SDK emits `error`.\n   *  - In `checkout_mode='preauth'` without managed-auth — the backend requires\n   *    an email user; make sure `identity.email` is explicitly set.\n   *\n   * Without a modal at all (when the host renders its own awaiting-payment\n   * screen) — use `paywall.billing.createCheckout({priceId, offerId})` directly,\n   * but then you'll have to draw auth-gate / popup_blocked / awaiting_payment\n   * yourself.\n   */\n  checkout(priceId: string, opts: OpenOptions = {}): void {\n    if (opts.identity) this.billing.setIdentity(opts.identity);\n\n    // Cached user → already-paid: we mount nothing and emit headless.\n    // renew skips all pre-checks — the host is explicitly doing an upgrade.\n    if (opts.renew !== true) {\n      const cachedUser = this.billing.getCachedUser();\n      if (cachedUser?.has_active_subscription) {\n        this.emit('purchase_completed', {\n          priceId,\n          sessionId: null,\n          restored: true\n        });\n        return;\n      }\n    }\n\n    // Late-mount: everything from here is async. We turn on the processing\n    // flag, and via state.processing the host sees \"the SDK is doing something\"\n    // and disables the button. We reset processing to false only in the\n    // no-mount returns (headless reject, gate-block, error). For paths ending in\n    // mountAndShow, PaywallRoot.onState reports processing=false itself with its\n    // very first snapshot — if we did .finally here, there'd be a flicker\n    // \"processing=false, view=null\" between applyProcessing(false) and\n    // PaywallRoot.onState (which looks like \"nothing is happening\").\n    void this.runDirectCheckout(priceId, opts);\n  }\n\n  /** Headless prep-work for `checkout(priceId, opts)`: bootstrap → gates →\n   *  preauth check → createCheckout → mount the modal with the final view.\n   *  Extracted into a separate method for a clean async/await flow instead of\n   *  nested then-chains (5+ branches). Any error isn't propagated outward: we\n   *  emit via `paywall.emit('error')` and exit — the host is subscribed to the\n   *  `error` event. */\n  private async runDirectCheckout(\n    priceId: string,\n    opts: OpenOptions\n  ): Promise<void> {\n    const renew = opts.renew === true;\n    const skipTrial = opts.skipTrial === true;\n    const skipVisibility = opts.skipVisibility === true;\n\n    // Turn on processing for the host's button.\n    this.applyProcessing(true);\n\n    // Helper: emit/headless exit — we must reset processing before returning,\n    // otherwise the host's UI hangs in busy-state forever.\n    const exitHeadless = (): void => {\n      this.applyProcessing(false);\n    };\n\n    // 1. Bootstrap. Cached path — instant; cold — RTT 200-500ms.\n    let bootstrap: PaywallBootstrap;\n    try {\n      bootstrap = await this.billing.bootstrap();\n    } catch (err) {\n      const wrapped =\n        err instanceof PaywallError\n          ? err\n          : new PaywallError('unknown', 'Failed to load paywall', { cause: err });\n      this.emit('error', wrapped);\n      exitHeadless();\n      return;\n    }\n\n    // 2. Gates (visibility → trial). We do NOT mount the modal: a blocking gate\n    //    → emit and exit. Identical semantics to open(): trial_blocked /\n    //    visibility_blocked.\n    if (!skipVisibility) {\n      const v = bootstrap.settings.visibility;\n      if (v) {\n        this.lastVisibility = v;\n        if (!v.visible) {\n          this.emit('visibility_blocked', v);\n          exitHeadless();\n          return;\n        }\n      }\n    }\n    if (!skipTrial) {\n      const trialBlocked = await this.checkTrialBeforeCheckout(bootstrap);\n      if (trialBlocked) {\n        exitHeadless();\n        return;\n      }\n    }\n\n    // 3. Fresh bootstrap user — we re-check the active subscription.\n    //    The cached path above may have been stale (signOut in another tab,\n    //    etc.).\n    if (!renew && bootstrap.user?.has_active_subscription) {\n      this.emit('purchase_completed', {\n        priceId,\n        sessionId: null,\n        restored: true\n      });\n      exitHeadless();\n      return;\n    }\n\n    // 4. Preauth check. If a real signin is required — we mount the modal with\n    //    the auth-gate; after signin PaywallRoot does createCheckout itself\n    //    (runCheckout inside the auth-resume effect), and the offer is resolved\n    //    there the same way. PaywallRoot.onState resets processing with its\n    //    first snapshot (processing=false in computePaywallSnapshot), so we\n    //    don't need to do it by hand here.\n    const mode = bootstrap.settings.checkout_mode ?? 'guest';\n    const cachedSession = this.auth?.getCachedSession() ?? null;\n    const hasRealSession = !!cachedSession && !cachedSession.user.is_anonymous;\n    const needsAuth = mode === 'preauth' && !!this.auth && !hasRealSession;\n    if (needsAuth) {\n      this.purchased = false;\n      this.mountAndShow('auth', {\n        renew,\n        authMode: 'signin',\n        checkoutPriceId: priceId\n      });\n      return;\n    }\n\n    // 5. Headless createCheckout. We resolve the offer right here — without an\n    //    explicit offerId, duration offers (countdown in clientStorage) won't\n    //    apply on the backend.\n    const offer = this.getOfferForPrice(priceId);\n    let result;\n    try {\n      result = await this.billing.createCheckout({\n        priceId,\n        offerId: offer?.offer.id,\n        ignoreActivePurchase: renew\n      });\n    } catch (error) {\n      if (\n        error instanceof PaywallError &&\n        error.code === 'already_purchased'\n      ) {\n        try {\n          await this.billing.getUser({ force: true });\n        } catch {\n          /* offline — getUser will report to the host itself */\n        }\n        this.emit('purchase_completed', {\n          priceId,\n          sessionId: null,\n          restored: true\n        });\n        exitHeadless();\n        return;\n      }\n      // 401 = the Bearer was rejected and ApiClient's forced-refresh retry\n      // didn't save it — the session is dead and AuthClient has cleared it.\n      // Same recovery as the preauth branch in PaywallRoot.runCheckout: for\n      // preauth paywalls mount the auth gate with the checkout pending (after\n      // signin the auth-resume effect re-runs createCheckout; an already-paid\n      // user lands on the restored success-view via getUser/409) instead of a\n      // dead-end error. The 'error' event is still emitted — hosts and the\n      // events analytics must see the auth failure.\n      if (\n        error instanceof PaywallError &&\n        error.status === 401 &&\n        this.auth &&\n        mode === 'preauth'\n      ) {\n        this.emit('error', error);\n        this.purchased = false;\n        this.mountAndShow('auth', {\n          renew,\n          authMode: 'signin',\n          checkoutPriceId: priceId\n        });\n        return;\n      }\n      const wrapped =\n        error instanceof PaywallError\n          ? error\n          : new PaywallError('checkout_failed', 'Checkout failed', { cause: error });\n      this.emit('error', wrapped);\n      exitHeadless();\n      return;\n    }\n\n    // 6. We emit checkout_started BEFORE mounting — the host's analytics\n    //    listener fires synchronously (the modal isn't on screen yet, but the\n    //    event already happened). We also start UserWatcher via onEvent in\n    //    mountAndShow (it attaches a handler to checkout_started), but\n    //    startUserWatcher is idempotent — a repeat call here won't break\n    //    anything.\n    this.emit('checkout_started', {\n      priceId,\n      url: result.url,\n      acquiring: result.acquiring\n    });\n    this.startUserWatcher();\n\n    // 7. Open the popup and mount the corresponding view. SSR/no-window —\n    //    awaiting without attempting window.open (the host redirects from its\n    //    own env).\n    if (typeof window === 'undefined' || !result.url) {\n      this.mountAndShow('awaiting_payment', {\n        renew,\n        checkoutPriceId: priceId,\n        checkoutUrl: result.url\n      });\n      return;\n    }\n    const popup = window.open(result.url, '_blank');\n    this.purchased = false;\n    if (popup) {\n      try {\n        popup.opener = null;\n      } catch {\n        /* cross-origin already — ok */\n      }\n      this.mountAndShow('awaiting_payment', {\n        renew,\n        checkoutPriceId: priceId,\n        checkoutUrl: result.url\n      });\n    } else {\n      // Popup blocked — usually after an async signin (the transient activation\n      // is lost). The modal stays with its retry button; a click = a fresh\n      // gesture and the popup will open.\n      this.mountAndShow('popup_blocked', {\n        renew,\n        checkoutPriceId: priceId,\n        checkoutUrl: result.url\n      });\n    }\n  }\n\n  /** Trial check without mounting (for late-mount direct-checkout). Returns\n   *  true if the trial blocked — the caller must stop the flow. On any storage\n   *  error we log+continue (we don't block the sale). */\n  private async checkTrialBeforeCheckout(\n    bootstrap: PaywallBootstrap\n  ): Promise<boolean> {\n    const trialCfg = bootstrap.settings.trial;\n    if (!trialCfg) return false;\n    const store = this.ensureTrialStore(trialCfg);\n    try {\n      const status = await store.check();\n      this.lastTrialStatus = status;\n      if (status.mode === 'none') return false;\n      if (status.blocked) {\n        const updated = await store.recordBlock();\n        this.lastTrialStatus = updated;\n        this.emit('trial_blocked', updated);\n        return true;\n      }\n      if (!this.trialExpiredFired) {\n        this.trialExpiredFired = true;\n        this.emit('trial_expired');\n      }\n      return false;\n    } catch (e) {\n      if (typeof console !== 'undefined') {\n        console.warn('[paywall] trial check failed', e);\n      }\n      return false;\n    }\n  }\n\n  private applyProcessing(value: boolean): void {\n    if (this.currentState.processing === value) return;\n    // We mutate processing on the current snapshot while keeping the other\n    // fields. PaywallRoot emits its snapshots with processing=false; here we\n    // update the field before/after mounting — between these points the state\n    // doesn't change via PaywallRoot.onState.\n    this.applyState({ ...this.currentState, processing: value });\n  }\n\n  /**\n   * Headless anonymous signin without opening the modal. Internally:\n   * idempotent (if already anon — instant return) → resume via the saved\n   * refresh_token → fresh /auth/anonymous/signin. Deduplicates parallel calls\n   * inside the AuthClient.\n   *\n   * Convenient for host buttons like \"Continue as guest\" — the host manages the\n   * loading-state on its own button, without a half-empty modal with a spinner.\n   * Without managed-auth — resolves with a rejected promise (there's no\n   * AuthClient to do signin).\n   */\n  signInAnonymously(): Promise<AuthSession> {\n    if (!this.auth) {\n      return Promise.reject(\n        new PaywallError(\n          'invalid_config',\n          'signInAnonymously requires managed-auth. Pass `auth: true` to PaywallUI.'\n        )\n      );\n    }\n    return this.auth.signInAnonymously();\n  }\n\n  private openInternal(view: PaywallView, opts: InternalOpenOptions): void {\n    if (opts.identity) this.billing.setIdentity(opts.identity);\n    // Reset the success-view flag — a repeat open should start from the regular\n    // layout, not from a previous \"Payment received\".\n    this.purchased = false;\n\n    // Subscription gate: a blind open() for a user with an active subscription\n    // is suppressed — nothing mounts, the host gets purchase_completed\n    // {restored:true} once per instance (see emitRestoredOnce). Symmetric with\n    // the visibility/trial gates and with how getAccess() ranks the checks\n    // (has_subscription first). The restored success-view is reserved for flows\n    // where the user explicitly recovered access (signin auth-resume, Restore\n    // purchases, 409 in checkout) — a subscriber clicking a gated feature\n    // shouldn't get any modal at all. `renew: true` bypasses (explicit\n    // \"Renew\"/\"Upgrade\" button). getCachedUser() is the single trusted source:\n    // every bootstrap ingress runs applyUser before resolving, and setIdentity\n    // clears it — a stale bootstrap.user snapshot can't leak a previous\n    // identity's subscription into the gate.\n    if (view === 'layout' && opts.renew !== true) {\n      const cachedUser = this.billing.getCachedUser();\n      if (cachedUser?.has_active_subscription) {\n        this.emitRestoredOnce();\n        return;\n      }\n      // The user is not confirmed-subscribed while a source that could reveal\n      // a subscription exists (managed-auth session / explicit identity) —\n      // resolve the settled user first and only then continue the open.\n      // Deciding synchronously here loses two races:\n      //  - cachedUser === null: the extension-popup cold start (auth hydrate →\n      //    INITIAL_SESSION → setIdentity → persisted user / user-state is all\n      //    in flight) — the 3.3.0 leak: the gate fell through on null and the\n      //    hydrated-bootstrap path never re-checked, a subscriber landed on\n      //    the plan picker;\n      //  - cachedUser negative: the snapshot may be a pre-purchase stale (the\n      //    popup died before the checkout tab could confirm) — getSettledUser\n      //    re-checks it against the network while the checkout-pending marker\n      //    is fresh, otherwise answers from cache without a request.\n      // Costs microtask hops (plus one user-state RTT when the cache is empty\n      // or distrusted); a confirmed subscriber above stays fully synchronous.\n      // Hosts with neither auth nor identity skip this — nothing to wait for,\n      // the synchronous mount-then-load contract stays intact for anonymous\n      // visitors.\n      if (this.canResolveUser()) {\n        void this.billing.getSettledUser().then((user) => {\n          if (user?.has_active_subscription) {\n            this.emitRestoredOnce();\n            return;\n          }\n          this.proceedOpen(view, opts);\n        });\n        return;\n      }\n    }\n\n    this.proceedOpen(view, opts);\n  }\n\n  /** Whether some source can still reveal the user for the subscription gate:\n   *  a managed-auth session (Bearer) or an explicitly set identity. Anonymous\n   *  visitors have neither — waiting on getSettledUser would be pointless. */\n  private canResolveUser(): boolean {\n    return !!this.auth || !!this.billing.getIdentity();\n  }\n\n  /** The tail of openInternal after the subscription gate: skip-flag\n   *  normalization and the cached/cold bootstrap paths. Extracted so the\n   *  settled-user branch above can continue the flow asynchronously. */\n  private proceedOpen(view: PaywallView, opts: InternalOpenOptions): void {\n    // The support and auth-standalone flows bypass both gates (trial and\n    // targeting): the user came for support or to log in to an already-bought\n    // subscription — blocking them by trial-stage or targeting is inappropriate.\n    // openAuth additionally passes skipTrial:true for compatibility with the\n    // former semantics; here we normalize the skip flags uniformly.\n    const skipTrial = opts.skipTrial === true || view === 'support';\n    const skipVisibility =\n      opts.skipVisibility === true ||\n      view === 'support' ||\n      view === 'auth';\n    const renew = opts.renew === true;\n    const title = opts.title ?? null;\n\n    if (skipTrial && skipVisibility) {\n      this.mountAndShow(view, { renew, authMode: opts.authMode, title });\n      return;\n    }\n\n    // Cache hit — the sync path, gates before mount as before. No compromises:\n    // when bootstrap is already in memory, we know in one tick whether we can\n    // open or not, without a flash.\n    const cached = this.billing.getCachedBootstrap();\n    if (cached) {\n      this.runOpenGates(view, cached, { skipTrial, skipVisibility, renew, title });\n      return;\n    }\n\n    // Cold bootstrap. Two modes:\n    //\n    // mountThenLoad=true (default): we mount the modal immediately — the user\n    //   sees a spinner, the button responds instantly. Bootstrap runs in\n    //   parallel. When it arrives — we run the gates, and if one blocks, we\n    //   close the modal with a *_blocked emission. The price is a flash \"opened\n    //   → closed\" in the rare case of a visibility/trial block. For extensions\n    //   and sites with targeting enabled most open()s pass, so the flash is an\n    //   edge case.\n    //\n    // mountThenLoad=false (legacy): we wait for bootstrap before mounting.\n    //   Guaranteed no flash on a block, but the button feels \"dead\" for\n    //   200-500ms on a cold cache.\n    if (this.mountThenLoad) {\n      this.mountAndShow(view, { renew, title });\n      // Hold paywall_viewed until the gates below decide. The layout can render\n      // (and emit 'ready') before they do — the subscription gate awaits\n      // getSettledUser, a network round-trip — and a gate that blocks after\n      // that would otherwise leave a phantom viewed/closed pair in analytics.\n      this.viewedGatePending = true;\n      this.billing\n        .bootstrap()\n        .then((b) => this.runDelayedGates(b, { skipTrial, skipVisibility, renew }))\n        .catch(() => {\n          // Bootstrap failed — the modal is already open, PaywallRoot is in the\n          // error-state itself. No gate will run, so lift the hold: a 'ready'\n          // from a later retry must track normally.\n          this.releaseViewedGate(true);\n        });\n      return;\n    }\n\n    this.billing\n      .bootstrap()\n      .then((b) =>\n        this.runOpenGates(view, b, { skipTrial, skipVisibility, renew, title })\n      )\n      .catch(() => {\n        // Bootstrap failed — we open without gates; PaywallRoot shows the error.\n        this.mountAndShow(view, { renew, title });\n      });\n  }\n\n  /** Apply gates AFTER the modal is already mounted (the mount-then-load path).\n   *  If a gate blocks — close() + emit. If the user already closed the modal\n   *  themselves before bootstrap resolved — a no-op (isOpen=false). */\n  private async runDelayedGates(\n    bootstrap: PaywallBootstrap,\n    flags: { skipTrial: boolean; skipVisibility: boolean; renew: boolean }\n  ): Promise<void> {\n    // The user closed the spinner before bootstrap resolved: the 'close'\n    // handler already settled the hold (releasing a held view if the layout had\n    // rendered). Nothing for the gates to decide.\n    if (!this.isOpen) {\n      this.releaseViewedGate(false);\n      return;\n    }\n\n    // Subscription gate first (same ranking as getAccess). On the Bearer path\n    // fetchBootstrap runs applyUser before resolving, so getCachedUser() is\n    // already fresh and the settle resolves instantly. getSettledUser covers\n    // the rest: the bootstrap request may have left before identity synced (no\n    // X-User-Email) with a Bearer the server failed to resolve — a later\n    // /user-state with the synced email still knows the subscription. The\n    // modal is already mounted (mount-then-load) — close it, same trade-off as\n    // a delayed visibility/trial block.\n    if (!flags.renew) {\n      let user = this.billing.getCachedUser();\n      if (!user?.has_active_subscription && this.canResolveUser()) {\n        user = (await this.billing.getSettledUser()) ?? user;\n        if (!this.isOpen) {\n          this.releaseViewedGate(false);\n          return;\n        }\n      }\n      if (user?.has_active_subscription) {\n        // Drop the held view BEFORE close(): the 'close' handler treats a live\n        // hold as a user-initiated close and would replay it.\n        this.releaseViewedGate(false);\n        this.close();\n        this.emitRestoredOnce();\n        return;\n      }\n    }\n\n    if (!flags.skipVisibility) {\n      const v = bootstrap.settings.visibility;\n      if (v) {\n        this.lastVisibility = v;\n        if (!v.visible) {\n          this.releaseViewedGate(false);\n          this.close();\n          this.emit('visibility_blocked', v);\n          return;\n        }\n      }\n    }\n\n    if (flags.skipTrial) {\n      this.releaseViewedGate(true);\n      return;\n    }\n\n    const trialCfg = bootstrap.settings.trial;\n    if (!trialCfg) {\n      this.releaseViewedGate(true);\n      return;\n    }\n    const store = this.ensureTrialStore(trialCfg);\n    void store\n      .check()\n      .then(async (status) => {\n        if (!this.isOpen) {\n          this.releaseViewedGate(false);\n          return;\n        }\n        this.lastTrialStatus = status;\n        if (status.mode === 'none') {\n          this.releaseViewedGate(true);\n          return;\n        }\n        if (status.blocked) {\n          const updated = await store.recordBlock();\n          this.lastTrialStatus = updated;\n          if (!this.isOpen) {\n            this.releaseViewedGate(false);\n            return;\n          }\n          this.releaseViewedGate(false);\n          this.close();\n          this.emit('trial_blocked', updated);\n          return;\n        }\n        // The last gate passed — the paywall stays open, so the view counts.\n        this.releaseViewedGate(true);\n        if (!this.trialExpiredFired) {\n          this.trialExpiredFired = true;\n          this.emit('trial_expired');\n        }\n      })\n      .catch((e) => {\n        // The check failed, so no gate blocks — the paywall stays open and the\n        // view is real. Releasing here also prevents a stuck hold.\n        this.releaseViewedGate(true);\n        if (typeof console !== 'undefined') console.warn('[paywall] trial check failed', e);\n      });\n  }\n\n  // Gate order: visibility → trial. A country-mismatch ≠ a trial-block, and\n  // keeping a trial-state \"N views left\" under a user who shouldn't see the\n  // paywall at all by targeting is pointless: when they return to the correct\n  // country they'd end up with a \"stuck\" trial counter.\n  private runOpenGates(\n    view: PaywallView,\n    bootstrap: PaywallBootstrap,\n    flags: {\n      skipTrial: boolean;\n      skipVisibility: boolean;\n      renew: boolean;\n      title: string | null;\n    }\n  ): void {\n    // Subscription gate first (same ranking as getAccess): the cached path may\n    // reach here with a cachedUser that the openInternal pre-check hasn't seen\n    // yet (bootstrap() resolved between the two on the mountThenLoad=false\n    // path and ran applyUser).\n    if (\n      view === 'layout' &&\n      !flags.renew &&\n      this.billing.getCachedUser()?.has_active_subscription\n    ) {\n      this.emitRestoredOnce();\n      return;\n    }\n\n    if (!flags.skipVisibility) {\n      const v = bootstrap.settings.visibility;\n      if (v) {\n        this.lastVisibility = v;\n        if (!v.visible) {\n          this.emit('visibility_blocked', v);\n          return;\n        }\n      }\n    }\n\n    if (flags.skipTrial) {\n      this.mountAndShow(view, { renew: flags.renew, title: flags.title });\n      return;\n    }\n    this.gateThroughTrial(view, bootstrap, flags.renew, flags.title);\n  }\n\n  private gateThroughTrial(\n    view: PaywallView,\n    bootstrap: PaywallBootstrap,\n    renew: boolean,\n    title: string | null\n  ): void {\n    const trialCfg = bootstrap.settings.trial;\n    if (!trialCfg) {\n      this.mountAndShow(view, { renew, title });\n      return;\n    }\n    const store = this.ensureTrialStore(trialCfg);\n    void store\n      .check()\n      .then(async (status) => {\n        this.lastTrialStatus = status;\n        if (status.mode === 'none') {\n          this.mountAndShow(view, { renew, title });\n          return;\n        }\n        if (status.blocked) {\n          // recordBlock writes (init firstOpen / inc skipTimes) and returns the\n          // updated snapshot — we emit it so the host gets an up-to-date\n          // counter.\n          const updated = await store.recordBlock();\n          this.lastTrialStatus = updated;\n          this.emit('trial_blocked', updated);\n          return;\n        }\n        // The trial is in the config but doesn't block → it expired. We emit\n        // once per session, then open as usual.\n        if (!this.trialExpiredFired) {\n          this.trialExpiredFired = true;\n          this.emit('trial_expired');\n        }\n        this.mountAndShow(view, { renew, title });\n      })\n      .catch((e) => {\n        // Storage is unavailable (privacy mode, quota) — we don't block the\n        // user, we open the modal and don't lose the sale.\n        if (typeof console !== 'undefined') console.warn('[paywall] trial check failed', e);\n        this.mountAndShow(view, { renew, title });\n      });\n  }\n\n  private ensureTrialStore(config: TrialConfig): TrialStore {\n    if (this.trialStore && this.trialStoreConfig && sameTrialConfig(this.trialStoreConfig, config)) {\n      return this.trialStore;\n    }\n    this.trialStoreConfig = config;\n    // Duck-type: if the billing client provides its own factory (the\n    // extension's RemoteBillingClient — an atomic TrialStore via offscreen +\n    // navigator.locks), we use it. Otherwise — the regular path via the\n    // storage-adapter.\n    const factoryFn = (this.billing as { createTrialStore?: (cfg: TrialConfig) => TrialStore })\n      .createTrialStore;\n    this.trialStore =\n      typeof factoryFn === 'function'\n        ? factoryFn.call(this.billing, config)\n        : createTrialStore(this.billing.getStorage(), this.billing.paywallId, config);\n    return this.trialStore;\n  }\n\n  private mountAndShow(\n    view: PaywallView,\n    mountOpts: {\n      renew?: boolean;\n      authMode?: 'signin' | 'signup';\n      /** Per-open custom title (OpenOptions.title). Only meaningful for\n       *  view='layout' — the other views don't render the layout heading. */\n      title?: string | null;\n      /** Direct-checkout context. Passed into PaywallRoot for two modes:\n       *   - `view='auth'` + priceId → preauth-flow: the gate starts in\n       *     auth_gate with pendingCheckout.direct=true;\n       *   - `view='awaiting_payment'|'popup_blocked'` + priceId + url →\n       *     the headless checkout already issued the URL, the modal shows the\n       *     final screen without a loading flash. */\n      checkoutPriceId?: string;\n      checkoutUrl?: string;\n    } = {}\n  ): void {\n    // We remember the view for the analytics gate (paywall_viewed/paywall_closed)\n    // — we emit them only when we actually show the paywall ('layout').\n    this.lastMountedView = view;\n    // Every mount starts un-viewed: paywall_viewed is tracked by the 'ready'\n    // binding once the layout actually renders, and paywall_closed only pairs\n    // with a tracked viewed (see initTracker / extension bindAnalytics).\n    this.viewedTracked = false;\n    // A fresh mount also starts un-held — proceedOpen re-arms the hold right\n    // after this call on the mount-then-load path. Clearing it here keeps a\n    // previous open()'s hold (e.g. a gate that never resolved) from leaking\n    // into this mount and swallowing its view.\n    this.viewedGatePending = false;\n    this.pendingViewed = null;\n    this.viewedGateSettled = false;\n    const renew = mountOpts.renew === true;\n    // Context for the post-mount subscription corrective (see the onUserChange\n    // handler in the constructor): each mount session starts checkout-less and\n    // remembers its renew flag.\n    this.mountedRenew = renew;\n    this.checkoutStartedSinceMount = false;\n    const initialAuthMode = mountOpts.authMode;\n    // The title override only applies to the layout view; on the other views we\n    // normalize it to null so handle.update doesn't carry a stale title from a\n    // previous open({title}) session.\n    const titleOverride = view === 'layout' ? mountOpts.title ?? null : null;\n    this.titleOverride = titleOverride;\n    // priceId only makes sense for auth (preauth direct-checkout) and\n    // awaiting_payment/popup_blocked (post-headless mount). On the other views\n    // we normalize it to null so handle.update doesn't carry a stale priceId\n    // from a previous direct-checkout session.\n    const carriesCheckoutContext =\n      view === 'auth' || view === 'awaiting_payment' || view === 'popup_blocked';\n    const initialCheckoutPriceId = carriesCheckoutContext\n      ? mountOpts.checkoutPriceId ?? null\n      : null;\n    const initialCheckoutUrl =\n      view === 'awaiting_payment' || view === 'popup_blocked'\n        ? mountOpts.checkoutUrl ?? null\n        : null;\n    if (this.handle) {\n      this.isOpen = true;\n      this.handle.update({\n        open: true,\n        initialView: view,\n        initialAuthMode,\n        initialCheckoutPriceId,\n        initialCheckoutUrl,\n        purchased: false,\n        renew,\n        titleOverride\n      });\n      this.emit('open');\n      return;\n    }\n\n    this.isOpen = true;\n    this.handle = mountShadow<PaywallRootProps>(\n      PaywallRoot,\n      {\n        client: this.billing,\n        open: true,\n        initialView: view,\n        initialAuthMode,\n        initialCheckoutPriceId,\n        initialCheckoutUrl,\n        purchased: false,\n        renew,\n        titleOverride,\n        onClose: () => this.close(),\n        onEvent: (event, payload) => {\n          this.emit(event as PaywallEvent, payload as never);\n          // We start the watcher as soon as checkout begins — from here on we\n          // rely on the server-confirmed flow, not URL markers. The flag hands\n          // the flow over to handlePurchaseDetected (see the post-mount\n          // corrective in the constructor).\n          if (event === 'checkout_started') {\n            this.checkoutStartedSinceMount = true;\n            this.startUserWatcher();\n          }\n        },\n        onState: (snapshot) => this.applyState(snapshot),\n        inline: this.inline,\n        locale: this.forceLocale\n      },\n      { host: this.host, shadowMode: this.shadowMode, inline: this.inline }\n    );\n    this.emit('open');\n  }\n\n  private applyState(snapshot: PaywallStateSnapshot): void {\n    if (sameStateSnapshot(this.currentState, snapshot)) return;\n    this.currentState = snapshot;\n    for (const cb of this.stateListeners) {\n      try {\n        cb(snapshot);\n      } catch (e) {\n        console.warn('[paywall] onStateChange listener threw', e);\n      }\n    }\n  }\n\n  /**\n   * A sync snapshot of the modal's current state. Suitable for\n   * `useSyncExternalStore` in React\n   * (`useSyncExternalStore(paywall.onStateChange, paywall.getState)`) and for\n   * one-off checks (\"is the paywall open right now?\").\n   *\n   * The snapshot is stable — as long as the state hasn't changed, a repeat\n   * getState() returns a `===`-equal object (important for useSyncExternalStore\n   * to avoid re-rendering).\n   */\n  getState(): PaywallStateSnapshot {\n    return this.currentState;\n  }\n\n  /**\n   * Subscribe to state changes. The callback is called on every real change\n   * (closed → loading → ready → ...). By default the initial snapshot is\n   * delivered via a microtask after subscribing; via `{immediate: 'sync'|'none'}`\n   * you can do sync delivery (not needed for useSyncExternalStore — there the\n   * snapshot is read via getSnapshot separately) or skip the initial entirely.\n   *\n   * Returns an unsubscribe function.\n   */\n  onStateChange(\n    cb: PaywallStateListener,\n    opts: { immediate?: 'microtask' | 'sync' | 'none' } = {}\n  ): () => void {\n    this.stateListeners.add(cb);\n    const mode = opts.immediate ?? 'microtask';\n    if (mode !== 'none') {\n      const snapshot = this.currentState;\n      if (mode === 'sync') {\n        try {\n          cb(snapshot);\n        } catch (e) {\n          console.warn('[paywall] onStateChange initial sync threw', e);\n        }\n      } else {\n        queueMicrotask(() => {\n          if (this.stateListeners.has(cb)) cb(snapshot);\n        });\n      }\n    }\n    return () => {\n      this.stateListeners.delete(cb);\n    };\n  }\n\n  /** Sync access to the last known trial status. null — `paywall.open()` hasn't\n   *  been called yet or the trial is disabled in the paywall config. Convenient\n   *  for the host's own UI (\"3 views left\", \"the trial expires in 2h\"). */\n  getTrialStatus(): TrialStatus | null {\n    return this.lastTrialStatus;\n  }\n\n  /** Sync access to the last server-computed visibility status. null —\n   *  bootstrap isn't loaded yet or the server doesn't return\n   *  `settings.visibility` (e.g. an old version of online without the targeting\n   *  patch). The host can use it for its own fallback: \"the service isn't\n   *  available in your country\". Updated on every open() that passes through the\n   *  gate. */\n  getVisibility(): VisibilityStatus | null {\n    return this.lastVisibility;\n  }\n\n  /**\n   * The paywall's prices — a shortcut over `bootstrap()`. Locales are already\n   * applied, and the cache and stale-while-revalidate are identical to\n   * `billing.bootstrap()`. Suitable for pricing pages/cards on the site, where\n   * the host wants to show the same prices as in the modal without pulling\n   * bootstrap by hand.\n   */\n  getPrices(opts: { force?: boolean; signal?: AbortSignal } = {}): Promise<PaywallPrice[]> {\n    return this.billing.getPrices(opts);\n  }\n\n  /** A sync snapshot of the prices. null — bootstrap hasn't been loaded yet. */\n  getCachedPrices(): PaywallPrice[] | null {\n    return this.billing.getCachedPrices();\n  }\n\n  /** A sync snapshot of the offers. null = bootstrap not loaded, [] = a paywall\n   *  without offers. The backend already applied server-side targeting\n   *  (countries/email/mode) — only what's applicable to the current user comes\n   *  out. */\n  getCachedOffers(): PaywallOffer[] | null {\n    return this.billing.getCachedOffers();\n  }\n\n  /**\n   * Resolves the active offer for a specific price: price_id targeting +\n   * countdown (`expires_at` OR `duration_minutes` from the first paywall open,\n   * see clientStorage `pw-offer-{id}-start`).\n   *\n   * Read-only — does NOT write the start for `duration_minutes` offers. The\n   * write starts only when the modal is actually open (by the renderer). Before\n   * that `getOfferForPrice` returns `null` for duration-only offers, so host\n   * pages outside the modal (pricing, landing) don't activate the countdown\n   * prematurely.\n   *\n   * A host page that needs a countdown ticking every second should use the React\n   * hook `usePaywallOffer(priceId)` from sdk-react, or a wrapper over\n   * `setInterval(1000)` + a repeat call to this method.\n   */\n  getOfferForPrice(priceId: string): ResolvedOffer | null {\n    const offers = this.billing.getCachedOffers();\n    if (!offers) return null;\n    const offer = findApplicableOffer(offers, priceId);\n    if (!offer) return null;\n    return resolveOffer(offer, {\n      now: Date.now(),\n      readStart: readBrowserOfferStart\n    });\n  }\n\n  /** A snapshot of the current \"user language\" — a proxy over\n   *  `billing.getUserLanguage()`. Use it to sync the host's i18n with what the\n   *  paywall actually shows. See the details in `BillingClient.getUserLanguage`. */\n  getUserLanguage(): UserLanguageInfo {\n    return this.billing.getUserLanguage();\n  }\n\n  /**\n   * Decides whether the feature should be blocked for the current user. No side\n   * effects (`recordBlock` isn't called on trial-storage, the modal isn't\n   * mounted).\n   *\n   * Check order (the first one that triggers is final):\n   *  1. `has_active_subscription` — the strongest signal, overrides the rest.\n   *     A user with a subscription gets access regardless of visibility/trial.\n   *  2. `visibility` (country/device/disabled-flag) — the user is outside the\n   *     paywall's monetization scope, can't be gated.\n   *  3. `trial` — the pre-paywall free period is active.\n   *  4. Otherwise — `blocked`, the host locks the feature and calls\n   *     `paywall.open()`.\n   *\n   * Bootstrap is cached in BillingClient — `getAccess()` can be called on every\n   * render of the host component, /bootstrap isn't duplicated. On a failed\n   * network it falls back to the persistent-cached user from storage: a user\n   * with a past subscription gets `granted` offline, otherwise `blocked` (the\n   * host shows the paywall with an error-state, the user can retry). Side\n   * effect: `lastVisibility` / `lastTrialStatus` are updated so the synchronous\n   * getters `getVisibility()` / `getTrialStatus()` see fresh data after the\n   * first `getAccess()`, not only after the first `open()`.\n   */\n  async getAccess(opts: GetAccessOptions = {}): Promise<PaywallAccessResult> {\n    let bootstrap = this.billing.getCachedBootstrap();\n    if (!bootstrap) {\n      try {\n        bootstrap = await this.billing.bootstrap({ signal: opts.signal });\n      } catch {\n        // The network failed. Fall back to the persistent-cached user (TTL 30\n        // min in storage). A user with a past subscription → granted\n        // (offline-friendly), otherwise → blocked (open() shows the paywall\n        // with an error-state, the user retries).\n        //\n        // Through peekCachedUser, not the synchronous getCachedUser: the\n        // persisted user sits behind storage hydration, and in the extension\n        // the page-side mirror is empty on EVERY fresh load\n        // (RemoteBillingClient starts with cachedUser=null; the real cache\n        // lives in offscreen). Reading the mirror alone answered \"no\n        // subscription\" to paying users whenever bootstrap failed once on\n        // load — the documented offline fallback never actually worked in the\n        // extension build.\n        //\n        // Deliberately NOT getSettledUser: we are already in the failure path,\n        // and a settle would fire a second doomed request, consume the one-shot\n        // checkout-pending marker on it (re-opening the post-purchase paywall\n        // flash), and for an anonymous visitor persist EMPTY_USER + emit\n        // userChange from a method documented as a pure read. peekCachedUser\n        // does none of that.\n        let cached = this.billing.getCachedUser();\n        try {\n          // Duck-typed: `opts.client` may be a host-supplied object predating\n          // this method (the extension passes RemoteBillingClient this way).\n          const peek = (\n            this.billing as {\n              peekCachedUser?: () => Promise<PaywallUser | null>;\n            }\n          ).peekCachedUser;\n          if (typeof peek === 'function') {\n            const persisted = await peek.call(this.billing);\n            // Only ever upgrade the answer, never downgrade: keeps `user` null\n            // (the shape hosts read as \"unknown\") in every branch that was\n            // already answering null.\n            if (persisted?.has_active_subscription) cached = persisted;\n          }\n        } catch {\n          /* pure read, but a broken adapter must not break the gate */\n        }\n        if (cached?.has_active_subscription) {\n          return {\n            access: 'granted',\n            reason: 'has_subscription',\n            visibility: null,\n            trial: null,\n            user: cached\n          };\n        }\n        return {\n          access: 'blocked',\n          reason: 'no_subscription',\n          visibility: null,\n          trial: null,\n          user: cached\n        };\n      }\n    }\n\n    // Cached bootstrap contains a user-snapshot FROM THE MOMENT of its fetch —\n    // after a purchase this snapshot is stale (has_active_subscription=false),\n    // even though UserWatcher already updated `billing.cachedUser` to true and\n    // emitted userChange. `getCachedBootstrap()` intentionally returns the raw\n    // structure (it shouldn't be rebuilt every time), so we do the overlay here:\n    // we prefer cachedUser. When cachedUser isn't loaded yet, the same\n    // cold-start race as the open() subscription gate applies: deciding\n    // \"blocked\" from an absent cache while the session/user are still in\n    // flight told subscribers they have no access (and a hydrated persisted\n    // bootstrap carries no user at all). Wait for the settled user instead;\n    // bootstrap.user stays as the last resort (network down, no cache) — it\n    // may be a stale snapshot of a previous identity, so it must not outrank\n    // the settle.\n    let user: PaywallUser | null = this.billing.getCachedUser();\n    if (!user?.has_active_subscription && this.canResolveUser()) {\n      user = (await this.billing.getSettledUser({ signal: opts.signal })) ?? user;\n    }\n    if (!user) user = bootstrap.user ?? null;\n\n    if (user?.has_active_subscription) {\n      return {\n        access: 'granted',\n        reason: 'has_subscription',\n        visibility: bootstrap.settings.visibility ?? null,\n        trial: null,\n        user\n      };\n    }\n\n    let visibility: VisibilityStatus | null = null;\n    if (!opts.skipVisibility) {\n      const v = bootstrap.settings.visibility;\n      if (v) {\n        visibility = v;\n        this.lastVisibility = v;\n        if (!v.visible) {\n          return { access: 'granted', reason: 'visibility_blocked', visibility, trial: null, user };\n        }\n      }\n    }\n\n    let trial: TrialStatus | null = null;\n    if (!opts.skipTrial) {\n      const trialCfg = bootstrap.settings.trial;\n      if (trialCfg) {\n        try {\n          const store = this.ensureTrialStore(trialCfg);\n          trial = await store.check();\n          this.lastTrialStatus = trial;\n          if (trial.blocked) {\n            return { access: 'granted', reason: 'trial_blocked', visibility, trial, user };\n          }\n        } catch (e) {\n          if (typeof console !== 'undefined') console.warn('[paywall] getAccess: trial check failed', e);\n        }\n      }\n    }\n\n    return { access: 'blocked', reason: 'no_subscription', visibility, trial, user };\n  }\n\n  /** Reset the trial state in storage. Useful for dev mode / an admin button\n   *  \"run the scenario again\". In prod the host usually doesn't call it. */\n  async resetTrial(): Promise<void> {\n    if (!this.trialStore) return;\n    await this.trialStore.reset();\n    this.lastTrialStatus = null;\n    this.trialExpiredFired = false;\n  }\n\n  // Starts polling user-state until has_active_subscription=true or a timeout.\n  // Idempotent: a repeat call on an already-running watcher is a no-op (the user\n  // might press Continue again after returning).\n  //\n  // In the extension popup runtime — a no-op (the popup won't survive). There we\n  // rely on bootstrap on the next open.\n  private startUserWatcher(): void {\n    if (this.watcher) return;\n    if (!shouldRunUserWatcher()) return;\n\n    this.watcher = new UserWatcher({\n      client: this.billing,\n      onActive: (user) => this.handlePurchaseDetected(user),\n      onTimeout: () => {\n        this.watcher = null;\n      }\n    });\n    this.watcher.start();\n  }\n\n  // Single funnel for \"subscription became active during a checkout flow\".\n  // Reached from THREE independent sources:\n  //   1. UserWatcher.onActive — the background poll (where it runs).\n  //   2. billing.onUserChange — the manual \"I've paid\" button (getUser →\n  //      applyUser → onUserChange) and cross-context user-state broadcasts\n  //      (sdk-extension offscreen → RemoteBillingClient → onUserChange).\n  //   3. (future) any other path that flips cachedUser to active.\n  // Idempotent via `this.purchased` (reset to false at the start of every\n  // checkout flow — see the direct-checkout/headless mounts).\n  //\n  // Previously this logic lived ONLY inside watcher.onActive, and the manual\n  // button merely posted a `paywall_purchase` window-message to wake the\n  // watcher. In runtimes where the watcher doesn't run — a full extension page\n  // on chrome-extension:// (shouldRunUserWatcher was false for the whole\n  // protocol) — neither the poll nor the manual button could close the awaiting\n  // screen: the message had no listener. Funneling through onUserChange fixes\n  // both, regardless of whether a watcher exists.\n  private handlePurchaseDetected(user: PaywallUser): void {\n    if (this.purchased) return;\n    this.purchased = true;\n    if (this.watcher) {\n      this.watcher.stop();\n      this.watcher = null;\n    }\n    // Server-confirmed purchase — a consistent signal for the host regardless\n    // of whether there was a URL marker. userChange is emitted by the\n    // billing-listener itself.\n    this.emit('purchase_completed', { priceId: null, sessionId: null });\n    // success_redirect_url from settings — the host explicitly asked to send the\n    // user into its apps-flow after payment. The redirect takes priority over\n    // PurchaseSuccessView: drawing success for 200ms before the transition would\n    // flicker.\n    const redirect = this.billing\n      .getCachedBootstrap()\n      ?.settings.success_redirect_url;\n    if (redirect && typeof window !== 'undefined') {\n      try {\n        window.location.assign(redirect);\n        return;\n      } catch {\n        /* navigation blocked — fall back to the success-view */\n      }\n    }\n    // If the paywall is open — switch to the \"Payment received\" view with a\n    // Continue button. A silent close confused the user: the window just\n    // disappeared, without confirmation that the payment went through. If the\n    // paywall is closed — the event already fired, the host decides itself.\n    if (this.isOpen && this.handle) {\n      this.handle.update({ purchased: true });\n    }\n    void user; // the shape is available via paywall.billing.getCachedUser()\n  }\n\n  /** Fire-and-forget write of the checkout-pending marker (see the\n   *  checkout_started subscription in the constructor). Goes through the\n   *  billing storage adapter, so in the extension it lands in the offscreen\n   *  storage — the same store BillingClient.getSettledUser reads. Storage\n   *  failures degrade to the previous behavior (a one-time flash). */\n  private markCheckoutPending(): void {\n    try {\n      void Promise.resolve(\n        this.billing\n          .getStorage()\n          .setItem(\n            STORAGE_KEYS.checkoutPending(this.billing.paywallId),\n            JSON.stringify({ at: Date.now() })\n          )\n      ).catch((): undefined => undefined);\n    } catch {\n      /* quota / disabled storage — not critical */\n    }\n  }\n\n  /** Blind open() hit the subscription gate: the modal is suppressed, the host\n   *  gets the same signal as every other \"subscription is already active\" path\n   *  — purchase_completed{restored:true}. Once per instance lifetime. */\n  private emitRestoredOnce(): void {\n    if (this.restoredEmitted) return;\n    this.restoredEmitted = true;\n    this.emit('purchase_completed', {\n      priceId: null,\n      sessionId: null,\n      restored: true\n    });\n  }\n\n  close(): void {\n    if (!this.isOpen || !this.handle) return;\n    this.isOpen = false;\n    this.purchased = false;\n    this.handle.update({ open: false, purchased: false });\n    // PaywallRoot emits onState with open=false on handle.update, but due to\n    // microtasks the host may read getState() before PaywallRoot's useEffect\n    // fires. We apply the closed state right away.\n    this.applyState(CLOSED_STATE);\n    this.emit('close');\n  }\n\n  /**\n   * Scans the current URL for checkout-return markers and emits\n   * purchase_completed / purchase_failed. The markers are removed from the URL\n   * via history.replaceState. It looks in both the hash and the search (the\n   * hash takes priority — protection against client SPA routers that intercept\n   * the query).\n   */\n  checkReturn(): void {\n    if (typeof window === 'undefined') return;\n    const url = new URL(window.location.href);\n\n    const hashMarkers = parseMarkers(url.hash.replace(/^#/, ''));\n    const searchMarkers = parseMarkers(url.search.replace(/^\\?/, ''));\n    const markers = hashMarkers ?? searchMarkers;\n    if (!markers) return;\n\n    if (markers.status === 'paid') {\n      this.emit('purchase_completed', {\n        priceId: markers.priceId,\n        sessionId: markers.sessionId\n      });\n      // Acceleration: if the page is loaded in a new tab from the original app\n      // (the typical Stripe success_url flow), we send the opener a postMessage.\n      // The watcher in the original tab reacts instantly, without waiting for a\n      // focus event. If there's no opener (the user closed it / there was none)\n      // — fall back to polling.\n      notifyOpenerOfPurchase(markers);\n    } else if (markers.status === 'failed' || markers.status === 'cancelled') {\n      this.emit('purchase_failed', { reason: markers.status });\n    }\n\n    stripMarkersFromUrl(url);\n  }\n\n  destroy(): void {\n    this.tracker?.destroy();\n    this.tracker = null;\n    this.listeners.clear();\n    this.stateListeners.clear();\n    this.watcher?.stop();\n    this.watcher = null;\n    this.userUnsub?.();\n    this.userUnsub = null;\n    this.authUnsub?.();\n    this.authUnsub = null;\n    // If the AuthClient was supplied by the host — its lifecycle isn't ours, we\n    // don't touch anything. If we created it — we unsubscribe via BillingClient\n    // (which holds the onAuthChange listener itself) and leave the session in\n    // storage so the next open picks it up via hydrate.\n    if (this.ownsAuth && this.auth) {\n      // If we created the AuthClient — we destroy it ourselves so the snapshot\n      // listener unsubscribes and doesn't hang around. We don't touch\n      // externally-supplied auth.\n      this.auth.destroy?.();\n    }\n    this.ownsAuth = false;\n    this.billing.destroy?.();\n    this.handle?.unmount();\n    this.handle = null;\n    this.isOpen = false;\n    this.currentState = CLOSED_STATE;\n  }\n}\n\nfunction resolveAuth(opts: PaywallUIOptions): {\n  auth: AuthClient | undefined;\n  ownsAuth: boolean;\n} {\n  if (!opts.auth) return { auth: undefined, ownsAuth: false };\n  // Duck-typing: AuthClient OR a structural look-alike (RemoteAuthClient from\n  // @monetize/sdk-extension). We check by the public methods PaywallUI uses — if\n  // they're all present, we trust it. This lets the host plug in a proxy\n  // implementation (offscreen architecture) without changes in PaywallUI.\n  // instanceof doesn't fit — the runtime in the content-script and in\n  // sdk-extension are different, so the classes aren't nominally equal.\n  if (opts.auth instanceof AuthClient || isAuthClientLike(opts.auth)) {\n    return { auth: opts.auth as AuthClient, ownsAuth: false };\n  }\n  // true | partial-options → we create our own AuthClient. We pick up\n  // apiOrigin/storage/fetch from PaywallUI's shared options, so the config is\n  // \"one field — the whole system\". The user can override individual fields via\n  // opts.auth = { apiOrigin: ... }.\n  const cfg = opts.auth === true ? {} : opts.auth;\n  return {\n    auth: new AuthClient({\n      paywallId: opts.paywallId,\n      apiOrigin: cfg.apiOrigin ?? opts.apiOrigin,\n      storage: cfg.storage ?? opts.storage,\n      fetch: cfg.fetch ?? opts.fetch,\n      openPopup: cfg.openPopup\n    }),\n    ownsAuth: true\n  };\n}\n\n// Checks the \"AuthClient-likeness\" of the passed object by the public methods\n// PaywallUI touches (`onAuthChange`, `getCachedSession`, `signOut`).\n// Partial<AuthClientOptions> doesn't have these methods — there's no overlap\n// with this union, so there will be no false positives.\nfunction isAuthClientLike(value: unknown): value is AuthClient {\n  if (typeof value !== 'object' || value === null) return false;\n  const v = value as Record<string, unknown>;\n  return (\n    typeof v.onAuthChange === 'function' &&\n    typeof v.getCachedSession === 'function' &&\n    typeof v.signOut === 'function'\n  );\n}\n\nfunction sameStateSnapshot(\n  a: PaywallStateSnapshot,\n  b: PaywallStateSnapshot\n): boolean {\n  return (\n    a.open === b.open &&\n    a.view === b.view &&\n    a.error === b.error &&\n    a.processing === b.processing\n  );\n}\n\nfunction sameTrialConfig(a: TrialConfig, b: TrialConfig): boolean {\n  return a.mode === b.mode && a.payload === b.payload && a.storage === b.storage;\n}\n\nfunction parseMarkers(\n  segment: string\n): { status: string; priceId: string | null; sessionId: string | null } | null {\n  if (!segment) return null;\n  const params = new URLSearchParams(segment);\n  const status = params.get(URL_MARKERS.status);\n  if (!status) return null;\n  return {\n    status,\n    priceId: params.get(URL_MARKERS.priceId),\n    sessionId: params.get(URL_MARKERS.sessionId)\n  };\n}\n\n// The message contract must match UserWatcher.handleMessage:\n// `{ type: 'paywall_purchase' }`. opener — the host's original tab, where\n// PaywallUI lives with an active watcher waiting for this signal.\nfunction notifyOpenerOfPurchase(markers: {\n  status: string;\n  priceId: string | null;\n  sessionId: string | null;\n}): void {\n  if (typeof window === 'undefined' || !window.opener) return;\n  try {\n    window.opener.postMessage(\n      {\n        type: 'paywall_purchase',\n        status: markers.status,\n        priceId: markers.priceId,\n        sessionId: markers.sessionId\n      },\n      '*'\n    );\n  } catch {\n    /* the opener is from another origin or closed — the watcher will catch it via focus */\n  }\n}\n\nfunction stripMarkersFromUrl(url: URL): void {\n  const clean = (raw: string, prefix: '?' | '#'): string => {\n    if (!raw) return '';\n    const p = new URLSearchParams(raw.replace(/^[?#]/, ''));\n    p.delete(URL_MARKERS.status);\n    p.delete(URL_MARKERS.priceId);\n    p.delete(URL_MARKERS.sessionId);\n    const out = p.toString();\n    return out ? prefix + out : '';\n  };\n  const next = url.pathname + clean(url.search, '?') + clean(url.hash, '#');\n  window.history.replaceState(null, '', next);\n}\n","// RemoteTrialStore — a TrialStore-compatible proxy. check / recordBlock / reset\n// go through transport to offscreen, where the real TrialStore runs under\n// navigator.locks — two tabs can't read-modify-write the same counter\n// simultaneously, so there's no drift.\n\nimport type { TrialStore } from '@sdk/core/trial';\nimport type { TrialConfig, TrialStatus } from '@sdk/core/types';\nimport type { TransportClient } from '../shared/transport-client';\n\nexport class RemoteTrialStore implements TrialStore {\n  constructor(\n    private readonly transport: TransportClient,\n    private readonly paywallId: string,\n    private readonly config: TrialConfig\n  ) {}\n\n  async check(): Promise<TrialStatus> {\n    return this.transport.request('trial.check', {\n      paywallId: this.paywallId,\n      config: this.config\n    });\n  }\n\n  async recordBlock(): Promise<TrialStatus> {\n    return this.transport.request('trial.recordBlock', {\n      paywallId: this.paywallId,\n      config: this.config\n    });\n  }\n\n  async reset(): Promise<void> {\n    await this.transport.request('trial.reset', {\n      paywallId: this.paywallId,\n      config: this.config\n    });\n  }\n}\n","// RemoteBillingClient — a structural twin of BillingClient that proxies all\n// methods to offscreen through TransportClient. The public API is identical\n// (the host writes the same code as for @monetize.software/sdk), only the\n// implementation differs. Sync getCached* methods stay sync — they read from a\n// local mirror that is updated by (a) responses to async methods and (b) the\n// userChange/balancesChange broadcast events.\n\nimport {\n  PaywallError,\n  type Balance,\n  type CheckoutResult,\n  type Identity,\n  type PaywallBootstrap,\n  type PaywallOffer,\n  type PaywallPrice,\n  type PaywallPurchaseDetailed,\n  type PaywallUser,\n  type TrialConfig\n} from '@sdk/core/types';\nimport type { StorageAdapter } from '@sdk/core/storage';\nimport type { TrialStore } from '@sdk/core/trial';\nimport { TransportClient } from '../shared/transport-client';\nimport { bytesToBase64 } from '../shared/base64';\nimport { MAX_SUPPORT_FILES, MAX_SUPPORT_FILE_SIZE } from '../shared/support-limits';\nimport { RemoteTrialStore } from './RemoteTrialStore';\n\nexport type UserListener = (user: PaywallUser) => void;\nexport type BalanceListener = (balances: Balance[]) => void;\nexport type BootstrapListener = (bootstrap: PaywallBootstrap) => void;\n\nexport interface RemoteBillingClientOptions {\n  paywallId: string;\n  apiOrigin?: string;\n}\n\nexport class RemoteBillingClient {\n  readonly paywallId: string;\n  readonly apiOrigin: string | undefined;\n\n  // Local mirrors. The source of truth is offscreen; the mirror exists only so\n  // that getCached* methods stay sync. Updated after every async response and\n  // on every broadcast event.\n  private cachedBootstrap: PaywallBootstrap | null = null;\n  private cachedUser: PaywallUser | null = null;\n  private cachedBalances: Balance[] | null = null;\n  private identity: Identity | null = null;\n  /** Storage proxy over transport: get/set/remove go to the offscreen\n   *  StorageAdapter (single source of truth for all tabs). PaywallUI writes\n   *  trial state here — all tabs see the same counter and it doesn't drift\n   *  between them.\n   *\n   *  A read-modify-write race window still exists (two tabs simultaneously read\n   *  N → write N-1, drift of 1). Exact atomicity requires Phase 9: move the\n   *  entire TrialStore into offscreen and do recordBlock as a single handler\n   *  with one atomic operation. This is a rare edge case (opening the paywall\n   *  in multiple tabs within milliseconds). */\n  private remoteStorageAdapter: StorageAdapter;\n\n  private userListeners = new Set<UserListener>();\n  private balanceListeners = new Set<BalanceListener>();\n  private bootstrapListeners = new Set<BootstrapListener>();\n  private unsubUserBroadcast: (() => void) | null = null;\n  private unsubBalancesBroadcast: (() => void) | null = null;\n\n  constructor(\n    private readonly transport: TransportClient,\n    opts: RemoteBillingClientOptions\n  ) {\n    this.paywallId = opts.paywallId;\n    this.apiOrigin = opts.apiOrigin;\n\n    this.remoteStorageAdapter = {\n      getItem: (key) => this.transport.request('storage.get', { key }),\n      setItem: async (key, value) => {\n        await this.transport.request('storage.set', { key, value });\n      },\n      removeItem: async (key) => {\n        await this.transport.request('storage.remove', { key });\n      }\n      // We don't implement watch — for cross-context notifications consumers\n      // (AuthClient, TrialStore) subscribe to broadcast events directly through\n      // transport. If it's ever needed, we'll add a storage.watch broadcast.\n    };\n\n    this.unsubUserBroadcast = this.transport.on('userChange', (user) => {\n      this.applyUser(user);\n    });\n\n    this.unsubBalancesBroadcast = this.transport.on('balancesChange', (balances) => {\n      this.applyBalances([...balances]);\n    });\n  }\n\n  // === Bootstrap ===\n\n  async bootstrap(opts: { force?: boolean; signal?: AbortSignal } = {}): Promise<PaywallBootstrap> {\n    const result = await this.transport.request(\n      'billing.bootstrap',\n      { force: opts.force },\n      { signal: opts.signal }\n    );\n    this.applyBootstrap(result);\n    if (result.user) this.applyUser(result.user);\n    return result;\n  }\n\n  getCachedBootstrap(): PaywallBootstrap | null {\n    return this.cachedBootstrap;\n  }\n\n  /** Mirrors `BillingClient.activeApiOrigin`. The edge failover state lives in\n   *  the offscreen BillingClient and this method must stay sync, so the mirror\n   *  returns the configured origin: the content script never builds sibling\n   *  API URLs itself (the only consumer — the events endpoint — is the\n   *  offscreen tracker, which reads the real BillingClient). */\n  activeApiOrigin(): string {\n    return this.apiOrigin ?? '';\n  }\n\n  /** Sticky A/B assignment of this device. Mirrors\n   *  `BillingClient.getExperimentAssignment`: the offscreen BillingClient\n   *  resolves the assignment and materializes `experiment.assigned_variant`\n   *  into the bootstrap, which arrives here through the proxied bootstrap() —\n   *  so the mirror only needs to read the cached copy. */\n  getExperimentAssignment(): { experimentId: string; variant: string } | null {\n    const experiment = this.cachedBootstrap?.experiment;\n    if (!experiment?.assigned_variant) return null;\n    return { experimentId: experiment.id, variant: experiment.assigned_variant };\n  }\n\n  /** Subscribe to bootstrap state. Structurally compatible with\n   *  `BillingClient.onBootstrapChange` — same microtask semantics for the\n   *  initial snapshot. In extension mode offscreen does not yet broadcast\n   *  bootstrapChange, so the listener fires only on self-initiated `bootstrap()`\n   *  calls within this RemoteBillingClient (popup re-fetches bootstrap → mirror\n   *  updates → listener fires). A cross-surface revalidate (another tab updated\n   *  bootstrap) does not reach the popup — that would require a separate\n   *  bootstrapChange broadcast in protocol.ts/server.ts. */\n  onBootstrapChange(\n    cb: BootstrapListener,\n    opts: { immediate?: 'microtask' | 'sync' | 'none' } = {}\n  ): () => void {\n    this.bootstrapListeners.add(cb);\n    const mode = opts.immediate ?? 'microtask';\n    if (this.cachedBootstrap && mode !== 'none') {\n      const snapshot = this.cachedBootstrap;\n      if (mode === 'sync') {\n        try {\n          cb(snapshot);\n        } catch (e) {\n          console.warn('[paywall] onBootstrapChange initial sync threw', e);\n        }\n      } else {\n        queueMicrotask(() => {\n          if (this.bootstrapListeners.has(cb)) cb(snapshot);\n        });\n      }\n    }\n    return () => {\n      this.bootstrapListeners.delete(cb);\n    };\n  }\n\n  /** Shortcut over `bootstrap()` — returns the paywall prices (locale overrides\n   *  already applied in offscreen). Same caching semantics as `bootstrap()`. */\n  async getPrices(opts: { force?: boolean; signal?: AbortSignal } = {}): Promise<PaywallPrice[]> {\n    const b = await this.bootstrap(opts);\n    return b.prices;\n  }\n\n  /** Sync snapshot of prices from the local bootstrap mirror. null = not loaded yet. */\n  getCachedPrices(): PaywallPrice[] | null {\n    return this.cachedBootstrap?.prices ?? null;\n  }\n\n  /** Sync snapshot of offers. null = bootstrap not loaded, [] = paywall has no\n   *  offers. Server-side targeting (countries/email/mode) is already applied by\n   *  the backend — only what's applicable to the current user is exposed. */\n  getCachedOffers(): PaywallOffer[] | null {\n    return this.cachedBootstrap?.offers ?? null;\n  }\n\n  // === Visitor ===\n\n  async getVisitorId(): Promise<string> {\n    return this.transport.request('billing.getVisitorId', undefined);\n  }\n\n  // === User ===\n\n  async getUser(opts: { force?: boolean; signal?: AbortSignal } = {}): Promise<PaywallUser> {\n    const result = await this.transport.request(\n      'billing.getUser',\n      { force: opts.force },\n      { signal: opts.signal }\n    );\n    this.applyUser(result);\n    return result;\n  }\n\n  getCachedUser(): PaywallUser | null {\n    return this.cachedUser;\n  }\n\n  /** Pure read of the OFFSCREEN cache — no network, no checkout-pending\n   *  consumption, no applyUser. The page-side mirror above starts empty on\n   *  every fresh load, so an offline decision made from it alone answered \"no\n   *  subscription\" to paying users; this asks the context that actually holds\n   *  the persisted user. Degrades to the mirror on a dead port. */\n  async peekCachedUser(): Promise<PaywallUser | null> {\n    try {\n      return (\n        (await this.transport.request('billing.getCachedUser', undefined)) ??\n        this.cachedUser\n      );\n    } catch {\n      return this.cachedUser;\n    }\n  }\n\n  /** Settled user for subscription-gate decisions — proxied to the offscreen\n   *  BillingClient, where auth hydration / identity sync / persisted caches\n   *  actually live (see BillingClient.getSettledUser). The mirror is updated so\n   *  a subsequent getCachedUser() stays consistent with the gate's answer. */\n  async getSettledUser(opts: { signal?: AbortSignal } = {}): Promise<PaywallUser | null> {\n    try {\n      const result = await this.transport.request('billing.getSettledUser', undefined, {\n        signal: opts.signal\n      });\n      if (result) this.applyUser(result);\n      return result;\n    } catch {\n      // The contract is \"never rejects\" — the subscription gate must degrade to\n      // \"unknown\" on a dead port / aborted request, not break open().\n      return this.cachedUser;\n    }\n  }\n\n  /** Subscribe to user state. We mirror the offscreen broadcasts; the initial\n   *  snapshot is delivered via microtask from the local cache (if present) —\n   *  exactly like in BillingClient.onUserChange. Returns an unsubscribe function. */\n  onUserChange(\n    cb: UserListener,\n    opts: { immediate?: 'microtask' | 'sync' | 'none' } = {}\n  ): () => void {\n    this.userListeners.add(cb);\n    const mode = opts.immediate ?? 'microtask';\n    if (this.cachedUser && mode !== 'none') {\n      const snapshot = this.cachedUser;\n      if (mode === 'sync') {\n        try {\n          cb(snapshot);\n        } catch (e) {\n          console.warn('[paywall] onUserChange initial sync threw', e);\n        }\n      } else {\n        queueMicrotask(() => {\n          if (this.userListeners.has(cb)) cb(snapshot);\n        });\n      }\n    }\n    return () => {\n      this.userListeners.delete(cb);\n    };\n  }\n\n  // === Balances ===\n\n  async getBalances(opts: { force?: boolean; signal?: AbortSignal } = {}): Promise<Balance[]> {\n    const result = await this.transport.request(\n      'billing.getBalances',\n      { force: opts.force },\n      { signal: opts.signal }\n    );\n    const arr = [...result];\n    this.applyBalances(arr);\n    return arr;\n  }\n\n  getCachedBalances(): Balance[] | null {\n    return this.cachedBalances;\n  }\n\n  onBalanceChange(\n    cb: BalanceListener,\n    opts: { immediate?: 'microtask' | 'sync' | 'none' } = {}\n  ): () => void {\n    this.balanceListeners.add(cb);\n    const mode = opts.immediate ?? 'microtask';\n    if (this.cachedBalances && mode !== 'none') {\n      const snapshot = this.cachedBalances;\n      if (mode === 'sync') {\n        try {\n          cb(snapshot);\n        } catch (e) {\n          console.warn('[paywall] onBalanceChange initial sync threw', e);\n        }\n      } else {\n        queueMicrotask(() => {\n          if (this.balanceListeners.has(cb)) cb(snapshot);\n        });\n      }\n    }\n    return () => {\n      this.balanceListeners.delete(cb);\n    };\n  }\n\n  // === Checkout ===\n\n  async createCheckout(params: {\n    priceId: string;\n    successUrl?: string;\n    errorUrl?: string;\n    shopUrl?: string;\n    trialDays?: number;\n    idempotencyKey?: string;\n    ignoreActivePurchase?: boolean;\n    signal?: AbortSignal;\n  }): Promise<CheckoutResult> {\n    const { signal, ...payload } = params;\n    return this.transport.request('billing.createCheckout', payload, { signal });\n  }\n\n  // === Customer portal: list/cancel purchases ===\n\n  /** Rich-shape list of the user's purchases (with price, currency, interval,\n   *  discount, cancel metadata). Through offscreen — there the real BillingClient\n   *  hits `/api/v1/paywall/[id]/user` with a Bearer token. Useful for the\n   *  customer-portal UI: cards + Cancel/Renew buttons. */\n  async listPurchases(opts: { signal?: AbortSignal } = {}): Promise<PaywallPurchaseDetailed[]> {\n    const result = await this.transport.request('billing.listPurchases', undefined, {\n      signal: opts.signal\n    });\n    return [...result];\n  }\n\n  /** Support ticket through the offscreen BillingClient. File objects do NOT\n   *  survive chrome.runtime ports (messages are JSON-serialized, a File\n   *  degrades to `{}` — crbug.com/248548), so attachments are shipped as\n   *  base64: each file is staged in offscreen with its own request, then the\n   *  ticket references the staged ids. Bearer token / email substitution is\n   *  done by offscreen, as in the regular BillingClient. */\n  async createSupportTicket(payload: {\n    subject: string;\n    content: string;\n    email?: string;\n    files?: File[];\n  }): Promise<{ ticket: { id: number; status: string } }> {\n    const files = payload.files ?? [];\n    // Mirror of the backend limits (route: too_many_files / invalid_file).\n    // Failing early keeps an oversized payload away from the port — a message\n    // above the runtime cap would kill the shared channel for the whole page.\n    if (files.length > MAX_SUPPORT_FILES) {\n      throw new PaywallError('too_many_files', `Up to ${MAX_SUPPORT_FILES} files`, {\n        status: 400\n      });\n    }\n    for (const f of files) {\n      if (!(f instanceof File) || f.size > MAX_SUPPORT_FILE_SIZE) {\n        throw new PaywallError(\n          'invalid_file',\n          `Each attachment must be a File ≤ ${MAX_SUPPORT_FILE_SIZE} bytes`,\n          { status: 400 }\n        );\n      }\n    }\n\n    // Sequential staging: peak memory in offscreen stays one file, and the\n    // envelopes (~1.33 × size each) never stack up in the port queue.\n    const fileIds: string[] = [];\n    for (const f of files) {\n      const bytes = new Uint8Array(await f.arrayBuffer());\n      const { fileId } = await this.transport.request('billing.stageSupportFile', {\n        name: f.name,\n        type: f.type,\n        dataBase64: bytesToBase64(bytes)\n      });\n      fileIds.push(fileId);\n    }\n\n    return this.transport.request('billing.createSupportTicket', {\n      subject: payload.subject,\n      content: payload.content,\n      email: payload.email,\n      fileIds: fileIds.length > 0 ? fileIds : undefined\n    });\n  }\n\n  /** Cancel a subscription through the backend. By default cancels at the end\n   *  of the current period (the user keeps access until the renewal date).\n   *  reason is required (validated by the backend) — collected via a reason\n   *  selector in the host UI. */\n  async cancelSubscription(params: {\n    subscriptionId: string;\n    reason: string;\n    signal?: AbortSignal;\n  }): Promise<{\n    subscription: {\n      status: string | null;\n      canceled_at: string | null;\n      cancel_at: string | null;\n      cancel_at_period_end: boolean | null;\n    };\n  }> {\n    const { signal, ...payload } = params;\n    return this.transport.request('billing.cancelSubscription', payload, { signal });\n  }\n\n  /** URL of the Stripe/Paddle/Chargebee customer portal. Proxies to the\n   *  offscreen BillingClient, which owns the Bearer session — so this works\n   *  from popup/options/content without access to the token. Same contract as\n   *  `BillingClient.getCustomerPortalUrl`: a backend 403 (no active\n   *  subscription / acquiring without a portal) surfaces as\n   *  PaywallError('forbidden') with `status: 403`. */\n  async getCustomerPortalUrl(\n    opts: { returnUrl?: string; signal?: AbortSignal } = {}\n  ): Promise<{ url: string }> {\n    return this.transport.request(\n      'billing.getCustomerPortalUrl',\n      { returnUrl: opts.returnUrl },\n      { signal: opts.signal }\n    );\n  }\n\n  // === Storage ===\n\n  /** PaywallUI asks the billing client for storage for TrialStore and other\n   *  consumers. Returns a proxy: get/set/remove go through transport to the\n   *  offscreen storage = single source of truth for all tabs. */\n  getStorage(): StorageAdapter {\n    return this.remoteStorageAdapter;\n  }\n\n  /** Factory method for PaywallUI: instead of a local createTrialStore over the\n   *  storage proxy, we return a RemoteTrialStore — it sends each operation as\n   *  one atomic RPC to offscreen, where navigator.locks serializes the\n   *  read-modify-write. PaywallUI duck-types this method and prefers it over the\n   *  local factory when present. */\n  createTrialStore(config: TrialConfig): TrialStore {\n    return new RemoteTrialStore(this.transport, this.paywallId, config);\n  }\n\n  // === Identity ===\n\n  getIdentity(): Identity | null {\n    return this.identity;\n  }\n\n  async setIdentity(identity: Identity | null): Promise<void> {\n    this.identity = identity;\n    await this.transport.request('billing.setIdentity', { identity });\n  }\n\n  /** Load identity from offscreen. Used on the first connection of a\n   *  content-script — if another tab has already logged the user in, the current\n   *  one immediately picks up the identity without waiting for authChange. */\n  async syncIdentity(): Promise<Identity | null> {\n    const result = await this.transport.request('billing.getIdentity', undefined);\n    this.identity = result;\n    return result;\n  }\n\n  destroy(): void {\n    this.unsubUserBroadcast?.();\n    this.unsubBalancesBroadcast?.();\n    this.unsubUserBroadcast = null;\n    this.unsubBalancesBroadcast = null;\n    this.userListeners.clear();\n    this.balanceListeners.clear();\n    this.bootstrapListeners.clear();\n    this.cachedBootstrap = null;\n    this.cachedUser = null;\n    this.cachedBalances = null;\n    this.identity = null;\n  }\n\n  private applyBootstrap(bootstrap: PaywallBootstrap): void {\n    this.cachedBootstrap = bootstrap;\n    for (const cb of [...this.bootstrapListeners]) {\n      try {\n        cb(bootstrap);\n      } catch (e) {\n        console.warn('[paywall] onBootstrapChange listener threw', e);\n      }\n    }\n  }\n\n  /** Update the user mirror and emit to listeners if it actually changed. Used\n   *  both for self-initiated RPCs (bootstrap/getUser) and for broadcasts from\n   *  offscreen — so the host's onUserChange handler gets a signal regardless of\n   *  who triggered the update. */\n  private applyUser(user: PaywallUser): void {\n    if (sameUser(this.cachedUser, user)) return;\n    this.cachedUser = user;\n    this.fireUserListeners(user);\n  }\n\n  private applyBalances(balances: Balance[]): void {\n    if (sameBalances(this.cachedBalances, balances)) return;\n    this.cachedBalances = balances;\n    this.fireBalanceListeners(balances);\n  }\n\n  private fireUserListeners(user: PaywallUser): void {\n    for (const cb of [...this.userListeners]) {\n      try {\n        cb(user);\n      } catch (e) {\n        console.warn('[paywall] onUserChange listener threw', e);\n      }\n    }\n  }\n\n  private fireBalanceListeners(balances: Balance[]): void {\n    for (const cb of [...this.balanceListeners]) {\n      try {\n        cb(balances);\n      } catch (e) {\n        console.warn('[paywall] onBalanceChange listener threw', e);\n      }\n    }\n  }\n}\n\nfunction sameUser(a: PaywallUser | null, b: PaywallUser | null): boolean {\n  if (a === b) return true;\n  if (!a || !b) return false;\n  return (\n    a.has_active_subscription === b.has_active_subscription &&\n    (a.purchases?.length ?? 0) === (b.purchases?.length ?? 0)\n  );\n}\n\nfunction sameBalances(a: Balance[] | null, b: Balance[] | null): boolean {\n  if (a === b) return true;\n  if (!a || !b) return false;\n  if (a.length !== b.length) return false;\n  for (let i = 0; i < a.length; i++) {\n    if (a[i].type !== b[i].type || a[i].count !== b[i].count) return false;\n  }\n  return true;\n}\n","// RemoteAuthClient — a structural match for AuthClient. The public methods are\n// identical, under the hood it's an async proxy through TransportClient into\n// offscreen, where the real session and storage live.\n//\n// Sync getCachedSession is supported via a local mirror, updated (a) on every\n// async-method response, (b) on an authChange broadcast.\n//\n// OAuth (signInWithOAuth) used to throw not-implemented — it required a public\n// split API in @sdk/core/auth (Phase 4.5). For email/password/refresh/signOut\n// and the rest of the network part — everything works.\n\nimport type {\n  AuthChangeEvent,\n  AuthSession,\n  AuthUser,\n  LastLogin,\n  OAuthProvider,\n  OAuthResumeCheckout,\n  OtpVerifyType,\n  SignUpResult\n} from '@sdk/core/auth';\nimport { waitForOAuthResult, isIdentityAlreadyLinked } from '@sdk/core/auth';\nimport { PaywallError } from '@sdk/core/types';\nimport { TransportClient } from '../shared/transport-client';\n\nexport type AuthChangeListener = (event: AuthChangeEvent, session: AuthSession | null) => void;\n\nexport interface RemoteAuthClientOptions {\n  paywallId: string;\n  apiOrigin?: string;\n}\n\nexport class RemoteAuthClient {\n  readonly paywallId: string;\n  readonly apiOrigin: string | undefined;\n\n  private session: AuthSession | null = null;\n  private listeners = new Set<AuthChangeListener>();\n  private unsubBroadcast: (() => void) | null = null;\n  private hydrated: Promise<void>;\n\n  constructor(\n    private readonly transport: TransportClient,\n    opts: RemoteAuthClientOptions\n  ) {\n    this.paywallId = opts.paywallId;\n    this.apiOrigin = opts.apiOrigin;\n\n    this.unsubBroadcast = this.transport.on('authChange', ({ event, session }) => {\n      this.applySession(event, session);\n    });\n\n    // Initial sync from offscreen — bring the restored session into the local\n    // mirror BEFORE the first `getCachedSession()`. Listeners receive the\n    // restored session via their own INITIAL_SESSION microtask from onAuthChange\n    // (see below) — we don't call applySession, so as not to turn \"restore from\n    // storage\" into a looks-like-signin event.\n    this.hydrated = this.transport\n      .request('auth.getCachedSession', undefined)\n      .then((session) => {\n        // Concurrency: if during the request someone already set the session\n        // (a SIGNED_IN broadcast or a local signIn method), don't overwrite —\n        // the hydrate snapshot is stale relative to what's already in the local mirror.\n        if (this.session === null && session !== null) {\n          this.session = session;\n        }\n      })\n      .catch(() => {\n        /* offscreen isn't ready or the transport failed — getCachedSession returns null */\n      });\n  }\n\n  /** A promise that resolves after the initial session sync from offscreen.\n   *  The analog of AuthClient.ready(). */\n  ready(): Promise<void> {\n    return this.hydrated;\n  }\n\n  getCachedSession(): AuthSession | null {\n    return this.session;\n  }\n\n  getCachedUser(): AuthUser | null {\n    return this.session?.user ?? null;\n  }\n\n  onAuthChange(cb: AuthChangeListener): () => void {\n    this.listeners.add(cb);\n    // Always-fire INITIAL_SESSION after hydrate — matches @sdk/core AuthClient.\n    // Contract: the first callback = INITIAL_SESSION with the restored snapshot\n    // (or null), subsequent ones = real transitions via applySession.\n    void this.hydrated.then(() => {\n      if (!this.listeners.has(cb)) return;\n      try {\n        cb('INITIAL_SESSION', this.session);\n      } catch (e) {\n        console.warn('[paywall] onAuthChange INITIAL_SESSION threw', e);\n      }\n    });\n    return () => {\n      this.listeners.delete(cb);\n    };\n  }\n\n  // === Email/password ===\n\n  async signInWithEmail(input: { email: string; password: string }): Promise<AuthSession> {\n    const session = await this.transport.request('auth.signInWithEmail', input);\n    // Local mirror update + emit. The broadcast from offscreen will also arrive\n    // with the same event — the `sameSession` guard in applySession cuts off the\n    // second emit, so listeners aren't called twice.\n    this.applySession('SIGNED_IN', session);\n    return session;\n  }\n\n  async signUp(input: {\n    email: string;\n    password: string;\n    userMeta?: Record<string, string>;\n  }): Promise<SignUpResult> {\n    const result = await this.transport.request('auth.signUp', input);\n    if (result.kind === 'signed_in') this.applySession('SIGNED_IN', result.session);\n    return result;\n  }\n\n  async signOut(): Promise<void> {\n    await this.transport.request('auth.signOut', undefined);\n    // The authChange broadcast will arrive from offscreen with session=null, and\n    // applySession will handle it there. We do nothing here, so as not to call\n    // the listeners twice.\n  }\n\n  async refresh(): Promise<AuthSession | null> {\n    const session = await this.transport.request('auth.refresh', undefined);\n    this.applySession(session ? 'TOKEN_REFRESHED' : 'SIGNED_OUT', session);\n    return session;\n  }\n\n  // === OTP / password reset / confirmation ===\n\n  async sendOtp(input: {\n    email: string;\n    createUser?: boolean;\n    userMeta?: Record<string, unknown>;\n  }): Promise<void> {\n    await this.transport.request('auth.sendOtp', input);\n  }\n\n  async verifyOtp(input: {\n    email: string;\n    token: string;\n    type: OtpVerifyType;\n  }): Promise<AuthSession> {\n    const session = await this.transport.request('auth.verifyOtp', input);\n    this.applySession(input.type === 'recovery' ? 'PASSWORD_RECOVERY' : 'SIGNED_IN', session);\n    return session;\n  }\n\n  async resendConfirmation(input: { email: string }): Promise<void> {\n    await this.transport.request('auth.resendConfirmation', input);\n  }\n\n  async requestPasswordReset(input: { email: string }): Promise<void> {\n    await this.transport.request('auth.requestPasswordReset', input);\n  }\n\n  async updatePassword(input: { password: string }): Promise<void> {\n    await this.transport.request('auth.updatePassword', input);\n  }\n\n  async revokeAllSessions(): Promise<void> {\n    await this.transport.request('auth.revokeAllSessions', undefined);\n  }\n\n  /** Last-used auth method + email — read from offscreen storage. AuthPanel uses\n   *  it for the \"Last used\" badge and email pre-fill. Storage is paywall-scoped,\n   *  and offscreen is the single source of truth for all tabs/popups. */\n  async getLastLogin(): Promise<LastLogin | null> {\n    return this.transport.request('auth.getLastLogin', undefined);\n  }\n\n  // === Anonymous sign-in ===\n\n  /** Anonymous sign-in (a Supabase user without an email). The logic (an\n   *  idempotent check + resume via a stored refresh_token + fresh signin) lives\n   *  in the offscreen AuthClient — content only proxies. captchaToken and\n   *  forceNewAnon are pass-through for forward-compat / the switch-account flow. */\n  async signInAnonymously(input: {\n    captchaToken?: string;\n    userMeta?: Record<string, string>;\n    forceNewAnon?: boolean;\n  } = {}): Promise<AuthSession> {\n    const session = await this.transport.request('auth.signInAnonymously', {\n      captchaToken: input.captchaToken,\n      userMeta: input.userMeta,\n      forceNewAnon: input.forceNewAnon\n    });\n    this.applySession('SIGNED_IN', session);\n    return session;\n  }\n\n  /** The current access token (lazily refreshable in offscreen). content/popup\n   *  uses it for the Bearer in external fetches — the ApiGatewayClient in the\n   *  content-script, direct requests from the demo UI. null if signed out or the\n   *  offscreen AuthClient couldn't refresh. */\n  async getAccessToken(): Promise<string | null> {\n    return this.transport.request('auth.getAccessToken', undefined);\n  }\n\n  // === OAuth (web-flow via split-API) ===\n\n  /** OAuth via the web variant: window.open in the content-script, a provider\n   *  redirect, and the callback page posts the code back to the opener. Under\n   *  the hood it's a split into two requests to offscreen — startOAuthFlow (hit\n   *  /init, get authorize_url) → open the popup → waitForOAuthCode → exchange.\n   *\n   *  The PKCE verifier lives ONLY in offscreen (inside the AuthClient) and never\n   *  crosses the runtime boundary. Content gets only authorize_url and state.\n   *\n   *  Popup gesture: `window.open(authorize_url, ...)` runs in the same synchronous\n   *  flow as the startOAuthFlow response; the user-gesture is preserved because\n   *  the content-script isn't unloaded within that tick (the gesture survives\n   *  through all microtasks of a single call stack). If in some browser the\n   *  gesture is lost anyway — the host gets `popup_blocked` (the same as in @monetize.software/sdk).\n   */\n  async signInWithOAuth(input: {\n    provider: OAuthProvider;\n    scopes?: string;\n    userMeta?: Record<string, string>;\n    onPopupOpened?: () => void;\n    /** Force a plain signin (no anon-upgrade linkIdentity) into the account that\n     *  owns the identity. Passed by the UI \"sign in with that account\" button. */\n    switchAccount?: boolean;\n    /** Purchase to continue with after signing in. Unlike the plain SDK, this is\n     *  acted upon: offscreen creates the checkout and the service worker sends\n     *  the provider tab straight there, so the flow completes even though this\n     *  surface is gone by then. */\n    resumeCheckout?: OAuthResumeCheckout;\n  }): Promise<AuthSession> {\n    if (typeof window === 'undefined') {\n      throw new PaywallError('oauth_unavailable', 'window is required for OAuth');\n    }\n\n    // Open the popup SYNCHRONOUSLY — the user-gesture is preserved only within\n    // the same synchronous frame as the click handler. An async `await` on\n    // transport.request before window.open eats the gesture, and Chrome opens the\n    // popup with an empty URL / blocks it entirely.\n    //\n    // about:blank instead of data:text/html (which used to show the inline loader):\n    // data: URLs trip CWS static scanners and EDRs as suspicious. Instead we open\n    // about:blank (which inherits the opener's origin) and inject the loader DOM\n    // via document.createElement + textContent — exactly the same UX, without a data: URL.\n    const tempName = `pw-oauth-pending-${Math.random().toString(36).slice(2, 10)}`;\n    const popup = window.open('about:blank', tempName, 'width=480,height=640,popup=yes');\n    if (!popup) {\n      throw new PaywallError(\n        'popup_blocked',\n        'browser blocked auth popup — call from a user gesture'\n      );\n    }\n    injectLoaderUI(popup, input.provider);\n\n    // Identity before the flow. If the window disappears we cannot tell a user\n    // closing it from the service-worker rescue path finishing the sign-in and\n    // closing the tab (see sw/oauth-watcher) — comparing against this snapshot\n    // tells us which happened.\n    const before = this.session;\n\n    try {\n      // Async part: hit offscreen for authorize_url and state. For now the popup\n      // shows about:blank.\n      const { authorizeUrl, state } = await this.transport.request('auth.oauthStart', {\n        provider: input.provider,\n        scopes: input.scopes,\n        userMeta: input.userMeta,\n        switchAccount: input.switchAccount,\n        // Offscreen keeps this for the whole flow: if this surface is destroyed\n        // while the user is with the provider, it still knows what to buy.\n        resumeCheckout: input.resumeCheckout\n      });\n\n      // Before navigating, rename the popup to the format the callback page\n      // expects (pw-oauth-<state>) — the name survives cross-origin redirects\n      // (Google → Supabase → our callback). The callback page reads window.name\n      // → extracts state → posts back.\n      popup.name = `pw-oauth-${state}`;\n      popup.location.replace(authorizeUrl);\n\n      input.onPopupOpened?.();\n\n      const result = await waitForOAuthResult(popup, state);\n\n      try {\n        popup.close();\n      } catch {\n        /* ignore */\n      }\n\n      if (result.kind === 'cancelled' || result.kind === 'timeout') {\n        // The window vanishing is ambiguous — ask the session owner before\n        // reporting a failure the user didn't experience.\n        const adopted = await this.adoptedSessionSince(before);\n        if (adopted) {\n          this.applySession('SIGNED_IN', adopted);\n          return adopted;\n        }\n        throw result.kind === 'cancelled'\n          ? new PaywallError('oauth_cancelled', 'auth popup was closed')\n          : new PaywallError('oauth_timeout', 'OAuth flow timed out');\n      }\n      if (result.kind === 'error') {\n        throw new PaywallError(\n          isIdentityAlreadyLinked(result) ? 'oauth_identity_already_linked' : 'oauth_failed',\n          result.description || result.error || 'OAuth provider returned error'\n        );\n      }\n\n      const session = await this.transport.request('auth.oauthExchange', {\n        state,\n        code: result.code\n      });\n      this.applySession('SIGNED_IN', session);\n      return session;\n    } catch (e) {\n      try {\n        popup.close();\n      } catch {\n        /* ignore */\n      }\n      throw e;\n    }\n  }\n\n  /** The session offscreen holds now, if it represents a sign-in that happened\n   *  during this flow rather than whatever we started with. Returns null when\n   *  nothing changed, i.e. the window really was closed by the user.\n   *\n   *  Compared by identity, not by token: a routine background refresh also\n   *  rotates the token, and mistaking that for a completed sign-in would swallow\n   *  a genuine cancellation. A signed-in user re-linking another provider is\n   *  therefore not detected here and still reports cancelled — the conservative\n   *  side of the trade. */\n  private async adoptedSessionSince(before: AuthSession | null): Promise<AuthSession | null> {\n    const current = await this.transport\n      .request('auth.getCachedSession', undefined)\n      .catch((): null => null);\n    if (!current) return null;\n    if (!before) return current;\n    if (before.user.id !== current.user.id) return current;\n    if (before.user.is_anonymous && !current.user.is_anonymous) return current;\n    return null;\n  }\n\n  destroy(): void {\n    this.unsubBroadcast?.();\n    this.unsubBroadcast = null;\n    this.listeners.clear();\n    this.session = null;\n  }\n\n  private applySession(event: AuthChangeEvent, next: AuthSession | null): void {\n    if (sameSession(this.session, next)) return;\n    this.session = next;\n    for (const cb of [...this.listeners]) {\n      try {\n        cb(event, next);\n      } catch (e) {\n        console.warn('[paywall] onAuthChange listener threw', e);\n      }\n    }\n  }\n}\n\nfunction sameSession(a: AuthSession | null, b: AuthSession | null): boolean {\n  if (a === b) return true;\n  if (!a || !b) return false;\n  return (\n    a.access_token === b.access_token &&\n    a.refresh_token === b.refresh_token &&\n    a.expires_at === b.expires_at &&\n    a.user.id === b.user.id\n  );\n}\n\nconst PROVIDER_NAMES: Record<string, string> = {\n  google: 'Google',\n  apple: 'Apple',\n  github: 'GitHub',\n  facebook: 'Facebook'\n};\n\n/** Inject the loader UI into the about:blank popup. Same-origin as the opener,\n *  so we can touch popup.document directly. We use createElement + textContent\n *  (not innerHTML / document.write) so as not to trip XSS scanners even on\n *  hard-coded strings. CSS classes with the pw-oauth-* prefix avoid collisions\n *  with the parent page's styles (the popup is isolated anyway, but just in case).\n *\n *  Defensive try/catch: if in some edge case the popup turns out not to be\n *  same-origin (some extensions intercept this) or the document isn't available\n *  — we quietly give up, and the popup shows the default about:blank for 200-500ms\n *  before redirecting to the provider. */\nfunction injectLoaderUI(popup: Window, provider: string): void {\n  const name = PROVIDER_NAMES[provider] ?? provider;\n  try {\n    const doc = popup.document;\n    doc.title = `Sign in with ${name}`;\n\n    const style = doc.createElement('style');\n    style.textContent =\n      'html,body{margin:0;padding:0;height:100%;font-family:-apple-system,system-ui,sans-serif;background:#fafafa;color:#475569}' +\n      '.pw-oauth-wrap{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;gap:16px}' +\n      '.pw-oauth-spinner{width:36px;height:36px;border:3px solid #e2e8f0;border-top-color:#7c3aed;border-radius:50%;animation:pw-oauth-spin 800ms linear infinite}' +\n      '.pw-oauth-label{font-size:14px;font-weight:500;letter-spacing:-0.01em}' +\n      '@keyframes pw-oauth-spin{to{transform:rotate(360deg)}}';\n    doc.head.appendChild(style);\n\n    const wrap = doc.createElement('div');\n    wrap.className = 'pw-oauth-wrap';\n    const spinner = doc.createElement('div');\n    spinner.className = 'pw-oauth-spinner';\n    const label = doc.createElement('div');\n    label.className = 'pw-oauth-label';\n    label.textContent = `Connecting to ${name}…`;\n    wrap.appendChild(spinner);\n    wrap.appendChild(label);\n    doc.body.appendChild(wrap);\n  } catch {\n    /* popup not same-origin or document not ready — fall back to blank */\n  }\n}\n","// RemoteEventTracker — a fire-and-forget proxy for analytics. All track() calls\n// from all tabs land in the single EventTracker in offscreen, which batches\n// them and sends to /events. The win: one batch per extension, one sendBeacon\n// on unload, no duplicate `app_opened` events.\n//\n// The API is deliberately minimal — just track(name, props). The buffer / flush\n// / destroy logic lives in offscreen; content doesn't control it.\n\nimport { TransportClient } from '../shared/transport-client';\n\nexport class RemoteEventTracker {\n  constructor(private readonly transport: TransportClient) {}\n\n  /** Send an event. Fire-and-forget — returns no Promise and doesn't throw.\n   *  Network/transport errors are logged to the console and don't block the caller. */\n  track(name: string, props?: Record<string, unknown>): void {\n    if (typeof name !== 'string' || name.length === 0) return;\n    this.transport.request('tracker.track', { name, props }).catch((e) => {\n      console.warn('[paywall] track failed', e);\n    });\n  }\n}\n","// Content-side singleton TransportClient. One per content-script, reused by all\n// PaywallUI instances on the same page (usually one per page, but several are\n// technically possible — e.g. one paywall in an overlay, another in the host\n// extension's popup, both inside the content-script of the same page).\n\nimport { TransportClient } from '../shared/transport-client';\nimport { createRuntimeChannel } from '../shared/chrome-port';\nimport { PORT_NAME } from '../shared/port-name';\n\nlet cached: TransportClient | null = null;\n\nexport function getContentTransport(): TransportClient {\n  if (cached) return cached;\n  cached = new TransportClient(() => createRuntimeChannel(PORT_NAME));\n  return cached;\n}\n\n/** Test injection — for unit tests of RemoteBillingClient with a fake channel\n *  (without chrome.runtime). Not used in production. */\nexport function _setContentTransportForTests(client: TransportClient | null): void {\n  cached = client;\n}\n","// Drop-in `PaywallUI` for the extension. The public API is identical to\n// `@monetize.software/sdk` — the host writes the same code, the same options. Under the hood:\n//  - billing — RemoteBillingClient (a proxy into offscreen)\n//  - auth — RemoteAuthClient (when `auth: true`)\n//  - tracker — RemoteEventTracker (events are forwarded into the offscreen EventTracker)\n//\n// The EventTracker is created ONCE per extension, in offscreen. PaywallUI here\n// disables the internal tracker (`analytics: false` in the base constructor) and\n// subscribes to public events itself, proxying them through RemoteEventTracker.\n// It duplicates the bindings from the base PaywallUI.initTracker — but that's the\n// lesser evil compared to two EventTrackers per user.\n\nimport { PaywallUI as BasePaywallUI, type PaywallUIOptions } from '@sdk/ui/PaywallUI';\nimport type { BillingClient } from '@sdk/core/BillingClient';\nimport type { AuthClient } from '@sdk/core/auth';\nimport { RemoteBillingClient } from './RemoteBillingClient';\nimport { RemoteAuthClient } from './RemoteAuthClient';\nimport { RemoteEventTracker } from './RemoteEventTracker';\nimport { getContentTransport } from './transport';\n\n/** Options for the extension's PaywallUI. Removed:\n *  - `client` — RemoteBillingClient is created automatically\n *  - `storage` — storage lives in offscreen, content doesn't see it\n *  - `apiKey` — a server-SDK key, meaningless in a content-script\n *  - `fetch` — all network requests go through offscreen\n *\n *  `auth: true` will wire up RemoteAuthClient. Passing a ready AuthClient from\n *  @monetize.software/sdk here makes no sense (we specifically want the offscreen one). */\nexport interface ExtensionPaywallUIOptions\n  extends Omit<PaywallUIOptions, 'client' | 'storage' | 'apiKey' | 'fetch'> {}\n\nexport class PaywallUI extends BasePaywallUI {\n  /** RemoteEventTracker (a proxy into the offscreen EventTracker). Not to be\n   *  confused with the base class's `tracker` (which is null — we disabled the internal one). */\n  private remoteTracker: RemoteEventTracker | null = null;\n  private trackerUnsubs: Array<() => void> = [];\n\n  constructor(opts: ExtensionPaywallUIOptions) {\n    const transport = getContentTransport();\n\n    const billing = new RemoteBillingClient(transport, {\n      paywallId: opts.paywallId,\n      apiOrigin: opts.apiOrigin\n    });\n\n    // Auth: if the host asked for it — construct a RemoteAuthClient. There's no\n    // point passing a ready AuthClient from @monetize.software/sdk here (we need the\n    // offscreen instance anyway). So we accept only `true` or nothing; an\n    // explicit AuthClient instance logs a warning and is ignored.\n    let auth: RemoteAuthClient | undefined;\n    if (opts.auth === true) {\n      auth = new RemoteAuthClient(transport, {\n        paywallId: opts.paywallId,\n        apiOrigin: opts.apiOrigin\n      });\n    } else if (opts.auth) {\n      console.warn(\n        '[sdk-extension] passing AuthClient instance to PaywallUI.opts.auth ' +\n          'is not supported in extension mode — pass `auth: true` to use ' +\n          'offscreen-shared auth, or omit for hybrid identity-only mode.'\n      );\n    }\n\n    // Pass auth into the billing client: PaywallRoot reads `client.auth` for\n    // restore / preauth-flow / signin-detection. The real BillingClient sets\n    // this field in its constructor — for the Remote variant we do an explicit\n    // assignment before super() so PaywallRoot sees it.\n    if (auth) {\n      (billing as { auth?: typeof auth }).auth = auth;\n    }\n\n    super({\n      ...opts,\n      // The casts are safe: PaywallUI's resolveAuth duck-types auth (see\n      // sdk/src/ui/PaywallUI.ts isAuthClientLike), and the billing param goes\n      // through `opts.client ?? new BillingClient(...)` — RemoteBillingClient is\n      // used there as-is, all the methods line up.\n      client: billing as unknown as BillingClient,\n      auth: auth as unknown as AuthClient | undefined,\n      // Disable the internal EventTracker — the only tracker lives in offscreen.\n      // We subscribe manually below.\n      analytics: false\n    });\n\n    if (opts.analytics !== false) {\n      this.remoteTracker = new RemoteEventTracker(transport);\n      this.bindAnalytics();\n    }\n  }\n\n  /** A mirror of sdk/PaywallUI.initTracker's bindings, but with RemoteEventTracker.\n   *  Once @monetize.software/sdk exposes a public hook for injecting a tracker,\n   *  this method will be replaced with a single line. */\n  private bindAnalytics(): void {\n    const t = this.remoteTracker;\n    if (!t) return;\n\n    // Transport for a paywall_viewed held back by the delayed-gate hold — the\n    // base class replays it through here (see releaseViewedGate).\n    this.trackViewedFn = (b) => {\n      t.track('paywall_viewed', {\n        is_test_mode: b.settings.is_test_mode,\n        prices_count: b.prices.length,\n        offers_count: b.offers.length\n      });\n    };\n\n    this.trackerUnsubs.push(\n      // paywall_viewed/paywall_closed only for the real paywall ('layout') —\n      // same gate as the base initTracker: 'ready'/'close' fire for\n      // support/auth/awaiting_payment mounts too, but that's not a paywall view.\n      this.on('ready', (b) => {\n        if (!this.acceptViewed(b)) return;\n        this.trackViewedFn?.(b);\n      }),\n      this.on('price_selected', (p) =>\n        t.track('price_selected', { price_id: p.priceId })\n      ),\n      this.on('checkout_started', (p) =>\n        t.track('checkout_started', { price_id: p.priceId, acquiring: p.acquiring })\n      ),\n      this.on('purchase_completed', (p) => {\n        // restored=true = \"active subscription discovered\", not a purchase —\n        // same skip as the base initTracker.\n        if (p.restored) return;\n        // And the same storage-backed dedupe: this is the channel where a\n        // re-created popup used to re-report a subscriber's purchase.\n        if (!this.shouldTrackPurchase(p)) return;\n        t.track('purchase_completed', { price_id: p.priceId, session_id: p.sessionId });\n      }),\n      this.on('purchase_failed', (p) => t.track('purchase_failed', { reason: p.reason })),\n      this.on('close', () => {\n        // A close with the hold still live is the user closing the modal (a\n        // blocking gate drops the hold first) — release so the pair lands.\n        if (this.viewedGatePending) this.releaseViewedGate(true);\n        // Closed pairs only with a tracked viewed — a delayed gate closing the\n        // mount-then-load spinner is not \"the user closed the paywall\".\n        if (this.viewedTracked) t.track('paywall_closed');\n        this.viewedTracked = false;\n        // The mount is over: a 'ready' still in flight must not add a view.\n        this.viewedGateSettled = true;\n      }),\n      this.on('trial_blocked', (s) =>\n        t.track('trial_blocked', {\n          mode: s.mode,\n          ...(s.mode === 'time'\n            ? { remaining_ms: s.remainingMs, total_ms: s.totalMs }\n            : s.mode === 'opens'\n              ? { remaining_actions: s.remainingActions, total_actions: s.totalActions }\n              : {})\n        })\n      ),\n      this.on('trial_expired', () => t.track('trial_expired')),\n      this.on('visibility_blocked', (v) =>\n        t.track('visibility_blocked', { reason: v.reason, country: v.country, tier: v.tier })\n      ),\n      this.on('error', (e) => t.track('error', { code: e.code, message: e.message }))\n    );\n\n    // We don't fire auth_signin_success / auth_signout yet: authChange is emitted\n    // both on session hydration (the popup brings up the cache from offscreen)\n    // and on token refresh, and with a parallel content-script + popup it gives\n    // false signins. Real login events should be caught via direct calls to\n    // signInWithEmail/signUp/signInWithOAuth/signOut, not via authChange.\n  }\n\n  /** A proxy through RemoteEventTracker. Hosts can call paywall.track for\n   *  arbitrary analytics events — it flies to the single offscreen tracker\n   *  alongside PaywallUI's auto-emits. */\n  track(name: string, props?: Record<string, unknown>): void {\n    this.remoteTracker?.track(name, props);\n  }\n\n  destroy(): void {\n    for (const fn of this.trackerUnsubs) fn();\n    this.trackerUnsubs = [];\n    this.remoteTracker = null;\n    // Unlike the base tracker (nulled in super.destroy(), so its replay is a\n    // no-op), this closure captures the RemoteEventTracker directly — a gate\n    // resolving after destroy() would otherwise send through a dead transport.\n    this.trackViewedFn = null;\n    super.destroy();\n  }\n}\n"],"names":["offerStartStorageKey","offerId","findApplicableOffer","offers","priceId","targeted","o","findLiveOffer","opts","offer","resolveOffer","discountPercent","now","expiresAt","resolveExpiresAt","totalMs","resolveTotalMs","remainingMs","readStart","startIso","start","readBrowserOfferStart","twPropertiesRegistered","ensureTwPropertiesRegistered","rules","sheet","cssText","rule","r","mountShadow","Component","props","options","host","shadow","hostReset","style","mountPoint","currentProps","render","h","nextProps","BUNDLED_LOCALES","defaultT","_key","fallback","params","format","I18nCtx","createContext","s","out","k","v","dictCache","inflight","isBundledLocale","key","pickStaticLocaleKey","bootstrap","candidates","base","c","hasOwnerTranslationsFor","locale","loadLocale","cached","pending","promise","__variableDynamicImportRuntimeHelper","mod","dict","err","empty","I18nProvider","forceLocale","children","setLocale","useState","setDict","useEffect","resolved","cancelled","d","value","jsx","useI18n","useContext","FOCUSABLE","Modal","open","onClose","labelledBy","brandColor","topBanner","allowClose","hideCloseButton","inline","t","dialogRef","useRef","previouslyFocused","dialog","onKey","e","focusables","el","first","last","active","prevOverflow","onBackdrop","accent","overlayClass","jsxs","providerLabel","provider","authErrorMessage","mode","PaywallError","AuthPanel","block","ctx","auth","session","allowSignup","allowReset","allowEmailCode","hideWhenAuthed","realSession","SignedIn","AuthForm","email","onSignOut","providers","initial","setMode","setEmail","password","setPassword","confirmPassword","setConfirmPassword","otpCode","setOtpCode","busy","setBusy","submittingRef","error","setError","info","setInfo","signupExpanded","setSignupExpanded","switchProvider","setSwitchProvider","pendingCredsRef","lastLogin","setLastLogin","current","switchTo","next","disposed","inFlight","attempt","creds","onFocus","ticks","iv","onSubmit","res","onResendCode","onOAuth","showOAuth","showEmailField","showPasswordField","ResetSentView","SignupSentView","Header","p","ProviderIcon","LastUsedBadge","Divider","FilledField","PasswordField","AccentLink","PrimaryButton","submitLabel","FormFooter","customHeading","customSubheading","defaults","defaultHeader","useCustom","title","subtitle","onSwitch","onClick","label","type","placeholder","onInput","autocomplete","inputMode","required","visible","setVisible","inputRef","passwordAriaShow","passwordAriaHide","EyeOffIcon","EyeIcon","maskEmail","local","domain","onBack","AuthGate","authSession","showBack","intent","initialMode","resumeCheckout","effectiveBlock","BackArrowButton","ariaLabel","STORAGE_KEY","calcTimeLeft","endMs","distance","resolveEndMs","pickActiveOffer","preferredId","match","useOfferCountdown","timeLeft","setTimeLeft","endMsRef","timer","OfferBanner","titleWithDiscount","FlashIcon","Countdown","Fragment","Cell","OfferTopBanner","SUBJECT_MIN","SUBJECT_MAX","CONTENT_MAX","MAX_FILES","MAX_FILE_SIZE_MB","MAX_FILE_SIZE","ACCEPTED_MIME","EMAIL_RE","SupportGate","client","origin","sessionEmail","lockedEmail","subject","setSubject","message","setMessage","files","setFiles","submitting","setSubmitting","submittedEmail","setSubmittedEmail","errors","setErrors","validate","m","prev","finalEmail","msg","resetForm","footerClass","footerStyle","FilledTextarea","Dropzone","onChange","disabled","dragOver","setDragOver","handleFiles","incoming","arr","valid","f","i","INTERVAL_PLAN_KEY","INTERVAL_PLAN_FALLBACK","dynamicLabel","price","action","hadPreviousTrial","dedicatedKey","capitalize","CtaButton","selectedPrice","CurrentSession","signingOut","setSigningOut","onSupport","Dot","FeaturesList","item","GuaranteeBadge","showIcon","parts","splitDaysPrefix","ShieldCheckIcon","BASE_FONT_PX","MIN_FONT_PX","MAX_LINES","fitHeading","lineHeight","maxHeight","size","Heading","level","Tag","className","ref","autoFit","cs","lh","displayedAmount","display","months","formatCurrencyParts","currency","minFrac","cur","amount","part","formatPriceParts","discounted","main","original","planLabel","entry","intervalSuffix","n","PriceGrid","filter","prices","popularLabel","idx","CompactRow","cols","anyHasDiscount","RowCard","selected","isPopular","originalAmount","compactLabel","isLast","onSelect","reserveStrikeRow","Text","INTERVAL_MULTIPLIER","intervalNoun","interval","TokenizationGate","multiplier","q","rawCount","blockRegistry","applyTitleOverride","layout","b","blocks","Renderer","rawLayout","onAction","hasTopBanner","titleOverride","useMemo","defaultPriceId","selectedPriceId","setSelectedPriceId","ctaIdx","scrollBlocks","footerBlocks","renderBlock","Cmp","computePaywallSnapshot","state","gate","purchased","sameSnapshot","a","PaywallRoot","onEvent","initialView","initialAuthMode","initialCheckoutPriceId","initialCheckoutUrl","renew","onState","setState","setAuthSession","setGate","isDirectCheckout","resumingRef","lastSnapshotRef","_event","data","useLayoutEffect","resumeCheckoutFor","cachedOffers","runCheckout","allowAuthGate","applicableOffer","result","popup","reopenCheckout","url","handleAction","payload","cachedSession","hasRealSession","brand","activeOffer","gateBlock","supportView","bootstrapForI18n","Scroll","PurchaseSuccessView","LoadingView","ErrorView","AwaitingPaymentView","PopupBlockedView","verifying","onReopen","onRetry","checking","setChecking","stillPending","setStillPending","stillPendingTimerRef","handleVerify","onContinue","restored","DEFAULT_TIMEOUT_MS","DEFAULT_VISIBLE_INTERVAL_MS","DEFAULT_HIDDEN_INTERVAL_MS","UserWatcher","user","shouldRunUserWatcher","CLOSED_STATE","URL_MARKERS","SENT_PURCHASES_KEY_PREFIX","MAX_SENT_PURCHASE_KEYS","PaywallUI$1","ownsAuth","resolveAuth","BillingClient","event","purchases","ids","x","id","raw","parsed","storage","merged","passed","held","analytics","cfg","endpoint","EventTracker","experiment","name","handler","partial","set","args","skipTrial","skipVisibility","exitHeadless","wrapped","trialCfg","store","status","updated","view","flags","config","sameTrialConfig","factoryFn","createTrialStore","mountOpts","snapshot","sameStateSnapshot","cb","peek","persisted","visibility","trial","redirect","STORAGE_KEYS","hashMarkers","parseMarkers","searchMarkers","markers","notifyOpenerOfPurchase","stripMarkersFromUrl","AuthClient","isAuthClientLike","segment","clean","prefix","RemoteTrialStore","transport","paywallId","RemoteBillingClient","balances","signal","MAX_SUPPORT_FILES","MAX_SUPPORT_FILE_SIZE","fileIds","bytes","fileId","bytesToBase64","identity","sameUser","sameBalances","RemoteAuthClient","input","tempName","injectLoaderUI","before","authorizeUrl","waitForOAuthResult","adopted","isIdentityAlreadyLinked","sameSession","PROVIDER_NAMES","doc","wrap","spinner","RemoteEventTracker","getContentTransport","TransportClient","createRuntimeChannel","PORT_NAME","PaywallUI","BasePaywallUI","billing","fn"],"mappings":"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"}