@monetize.software/sdk-extension 3.0.0-alpha.8 → 3.0.0-alpha.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (195) hide show
  1. package/dist/chunks/{ar-nh4l4WDE.js → ar-CHae8g-2.js} +4 -3
  2. package/dist/chunks/ar-CHae8g-2.js.map +1 -0
  3. package/dist/chunks/ar-E1mc8SO_.js +2 -0
  4. package/dist/chunks/ar-E1mc8SO_.js.map +1 -0
  5. package/dist/chunks/{chrome-port-PhlW29BV.js → chrome-port-BzNV1ahF.js} +2 -2
  6. package/dist/chunks/{chrome-port-PhlW29BV.js.map → chrome-port-BzNV1ahF.js.map} +1 -1
  7. package/dist/chunks/{chrome-port-CxW3Pwc2.js → chrome-port-rVd4zwU3.js} +21 -21
  8. package/dist/chunks/{chrome-port-CxW3Pwc2.js.map → chrome-port-rVd4zwU3.js.map} +1 -1
  9. package/dist/chunks/cs-Dccq6LAT.js +2 -0
  10. package/dist/chunks/cs-Dccq6LAT.js.map +1 -0
  11. package/dist/chunks/{cs-2UYO2rWf.js → cs-eZTedzaK.js} +2 -1
  12. package/dist/chunks/cs-eZTedzaK.js.map +1 -0
  13. package/dist/chunks/da-C_4MbEh5.js +2 -0
  14. package/dist/chunks/da-C_4MbEh5.js.map +1 -0
  15. package/dist/chunks/{da-BD0O_HfV.js → da-Dca6j8fp.js} +2 -1
  16. package/dist/chunks/da-Dca6j8fp.js.map +1 -0
  17. package/dist/chunks/{de-BkCiXPsn.js → de-BCjn3PUI.js} +3 -1
  18. package/dist/chunks/de-BCjn3PUI.js.map +1 -0
  19. package/dist/chunks/de-DulxcJj-.js +2 -0
  20. package/dist/chunks/de-DulxcJj-.js.map +1 -0
  21. package/dist/chunks/{el-cIcxTp07.js → el-BpXtDTez.js} +2 -1
  22. package/dist/chunks/el-BpXtDTez.js.map +1 -0
  23. package/dist/chunks/el-CVG_1iKB.js +2 -0
  24. package/dist/chunks/el-CVG_1iKB.js.map +1 -0
  25. package/dist/chunks/{es-CJBC_jrV.js → es-B3P4nlMw.js} +3 -1
  26. package/dist/chunks/es-B3P4nlMw.js.map +1 -0
  27. package/dist/chunks/es-ri0uKzUW.js +2 -0
  28. package/dist/chunks/es-ri0uKzUW.js.map +1 -0
  29. package/dist/chunks/{fi-DyV7ynBf.js → fi-BbQ_Bs0Z.js} +2 -1
  30. package/dist/chunks/fi-BbQ_Bs0Z.js.map +1 -0
  31. package/dist/chunks/fi-CNZqWHjw.js +2 -0
  32. package/dist/chunks/fi-CNZqWHjw.js.map +1 -0
  33. package/dist/chunks/{fr-BLQ4AIu7.js → fr-BKDUXceO.js} +3 -1
  34. package/dist/chunks/fr-BKDUXceO.js.map +1 -0
  35. package/dist/chunks/fr-CfFOw4hD.js +2 -0
  36. package/dist/chunks/fr-CfFOw4hD.js.map +1 -0
  37. package/dist/chunks/{he-Bg-Bqi7r.js → he-BVCHlTtT.js} +2 -1
  38. package/dist/chunks/he-BVCHlTtT.js.map +1 -0
  39. package/dist/chunks/he-DN2JEtQb.js +2 -0
  40. package/dist/chunks/he-DN2JEtQb.js.map +1 -0
  41. package/dist/chunks/hi-DxfOerNP.js +2 -0
  42. package/dist/chunks/hi-DxfOerNP.js.map +1 -0
  43. package/dist/chunks/{hi-wQeE43oY.js → hi-H_Hshh-7.js} +4 -3
  44. package/dist/chunks/hi-H_Hshh-7.js.map +1 -0
  45. package/dist/chunks/hu-BTImywuV.js +2 -0
  46. package/dist/chunks/{hu-DXtscQ8_.js.map → hu-BTImywuV.js.map} +1 -1
  47. package/dist/chunks/{hu-D9qxzu0r.js → hu-ZWiNfzvN.js} +2 -1
  48. package/dist/chunks/{hu-D9qxzu0r.js.map → hu-ZWiNfzvN.js.map} +1 -1
  49. package/dist/chunks/{id-Rx_wYvqy.js → id-DeiRYsJ1.js} +2 -1
  50. package/dist/chunks/id-DeiRYsJ1.js.map +1 -0
  51. package/dist/chunks/id-YFuArJA6.js +2 -0
  52. package/dist/chunks/id-YFuArJA6.js.map +1 -0
  53. package/dist/chunks/{it-B1SRPZ3e.js → it-UH9OzFhg.js} +3 -1
  54. package/dist/chunks/it-UH9OzFhg.js.map +1 -0
  55. package/dist/chunks/it-mhkzXBM9.js +2 -0
  56. package/dist/chunks/it-mhkzXBM9.js.map +1 -0
  57. package/dist/chunks/{ja-CMC3Parn.js → ja-6l_z_G9k.js} +2 -2
  58. package/dist/chunks/ja-6l_z_G9k.js.map +1 -0
  59. package/dist/chunks/{ja-DiItm8te.js → ja-BYC8FRN8.js} +3 -1
  60. package/dist/chunks/ja-BYC8FRN8.js.map +1 -0
  61. package/dist/chunks/{ko-CdGFWAKX.js → ko-Bjs2ZRcF.js} +3 -1
  62. package/dist/chunks/ko-Bjs2ZRcF.js.map +1 -0
  63. package/dist/chunks/ko-YAl4XwHu.js +2 -0
  64. package/dist/chunks/ko-YAl4XwHu.js.map +1 -0
  65. package/dist/chunks/nl-C-9zHtfb.js +2 -0
  66. package/dist/chunks/nl-C-9zHtfb.js.map +1 -0
  67. package/dist/chunks/{nl-CmZ3vEvj.js → nl-Ch5HFWQO.js} +3 -1
  68. package/dist/chunks/nl-Ch5HFWQO.js.map +1 -0
  69. package/dist/chunks/{no-BuxByZpq.js → no-CljpinWz.js} +2 -1
  70. package/dist/chunks/no-CljpinWz.js.map +1 -0
  71. package/dist/chunks/no-qzPitLlx.js +2 -0
  72. package/dist/chunks/no-qzPitLlx.js.map +1 -0
  73. package/dist/chunks/{pl-jZFCnDb8.js → pl-CUcSS0zZ.js} +2 -1
  74. package/dist/chunks/pl-CUcSS0zZ.js.map +1 -0
  75. package/dist/chunks/pl-MAIYeuhW.js +2 -0
  76. package/dist/chunks/pl-MAIYeuhW.js.map +1 -0
  77. package/dist/chunks/{pt-Cld7MwIW.js → pt-BHK0LwkC.js} +3 -1
  78. package/dist/chunks/pt-BHK0LwkC.js.map +1 -0
  79. package/dist/chunks/pt-DqDabE4v.js +2 -0
  80. package/dist/chunks/pt-DqDabE4v.js.map +1 -0
  81. package/dist/chunks/ro-BVs-lHH-.js +2 -0
  82. package/dist/chunks/ro-BVs-lHH-.js.map +1 -0
  83. package/dist/chunks/{ro-CuKDqj3C.js → ro-Bj8cwU2n.js} +2 -1
  84. package/dist/chunks/ro-Bj8cwU2n.js.map +1 -0
  85. package/dist/chunks/{ru-B7cjqJUm.js → ru-CgqNy0Gb.js} +10 -1
  86. package/dist/chunks/ru-CgqNy0Gb.js.map +1 -0
  87. package/dist/chunks/ru-DP7qDAmE.js +2 -0
  88. package/dist/chunks/ru-DP7qDAmE.js.map +1 -0
  89. package/dist/chunks/sv-B3QEYGgd.js +2 -0
  90. package/dist/chunks/sv-B3QEYGgd.js.map +1 -0
  91. package/dist/chunks/{sv-D9y-M1Fo.js → sv-H7jroOQ5.js} +2 -1
  92. package/dist/chunks/sv-H7jroOQ5.js.map +1 -0
  93. package/dist/chunks/{th-D9umBjEy.js → th-Dqm-gpGe.js} +2 -1
  94. package/dist/chunks/th-Dqm-gpGe.js.map +1 -0
  95. package/dist/chunks/th-DzQau9aW.js +2 -0
  96. package/dist/chunks/th-DzQau9aW.js.map +1 -0
  97. package/dist/chunks/{tr-BddMywiw.js → tr-D3zPcNtT.js} +2 -1
  98. package/dist/chunks/tr-D3zPcNtT.js.map +1 -0
  99. package/dist/chunks/tr-cG1YuE1E.js +2 -0
  100. package/dist/chunks/tr-cG1YuE1E.js.map +1 -0
  101. package/dist/chunks/{uk-Dbd31hFt.js → uk-CoIIs3QI.js} +10 -1
  102. package/dist/chunks/uk-CoIIs3QI.js.map +1 -0
  103. package/dist/chunks/uk-Cvbo0IBW.js +2 -0
  104. package/dist/chunks/uk-Cvbo0IBW.js.map +1 -0
  105. package/dist/chunks/vi-BRtYSBUp.js +2 -0
  106. package/dist/chunks/{vi-CJefLP_g.js.map → vi-BRtYSBUp.js.map} +1 -1
  107. package/dist/chunks/{vi-Do3BMOdh.js → vi-C_fruIbh.js} +2 -1
  108. package/dist/chunks/{vi-Do3BMOdh.js.map → vi-C_fruIbh.js.map} +1 -1
  109. package/dist/chunks/zh-CwczPMPp.js +2 -0
  110. package/dist/chunks/zh-CwczPMPp.js.map +1 -0
  111. package/dist/chunks/{zh-gQSYLZI3.js → zh-LDkEV2D9.js} +3 -1
  112. package/dist/chunks/zh-LDkEV2D9.js.map +1 -0
  113. package/dist/content.cjs +3 -3
  114. package/dist/content.cjs.map +1 -1
  115. package/dist/content.js +777 -813
  116. package/dist/content.js.map +1 -1
  117. package/dist/offscreen.cjs +1 -1
  118. package/dist/offscreen.js +1 -1
  119. package/package.json +3 -3
  120. package/dist/chunks/ar-BUQInJ5a.js +0 -2
  121. package/dist/chunks/ar-BUQInJ5a.js.map +0 -1
  122. package/dist/chunks/ar-nh4l4WDE.js.map +0 -1
  123. package/dist/chunks/cs-2UYO2rWf.js.map +0 -1
  124. package/dist/chunks/cs-i5K4KTFV.js +0 -2
  125. package/dist/chunks/cs-i5K4KTFV.js.map +0 -1
  126. package/dist/chunks/da-BD0O_HfV.js.map +0 -1
  127. package/dist/chunks/da-CZbhtVJO.js +0 -2
  128. package/dist/chunks/da-CZbhtVJO.js.map +0 -1
  129. package/dist/chunks/de-Bc-GoLgC.js +0 -2
  130. package/dist/chunks/de-Bc-GoLgC.js.map +0 -1
  131. package/dist/chunks/de-BkCiXPsn.js.map +0 -1
  132. package/dist/chunks/el-CcMEbl_F.js +0 -2
  133. package/dist/chunks/el-CcMEbl_F.js.map +0 -1
  134. package/dist/chunks/el-cIcxTp07.js.map +0 -1
  135. package/dist/chunks/es-BvYmomAz.js +0 -2
  136. package/dist/chunks/es-BvYmomAz.js.map +0 -1
  137. package/dist/chunks/es-CJBC_jrV.js.map +0 -1
  138. package/dist/chunks/fi-DyV7ynBf.js.map +0 -1
  139. package/dist/chunks/fi-aZR-qmZN.js +0 -2
  140. package/dist/chunks/fi-aZR-qmZN.js.map +0 -1
  141. package/dist/chunks/fr-BLQ4AIu7.js.map +0 -1
  142. package/dist/chunks/fr-BgbFAgHx.js +0 -2
  143. package/dist/chunks/fr-BgbFAgHx.js.map +0 -1
  144. package/dist/chunks/he-Bg-Bqi7r.js.map +0 -1
  145. package/dist/chunks/he-CQ4yCuja.js +0 -2
  146. package/dist/chunks/he-CQ4yCuja.js.map +0 -1
  147. package/dist/chunks/hi-DtUsj1c-.js +0 -2
  148. package/dist/chunks/hi-DtUsj1c-.js.map +0 -1
  149. package/dist/chunks/hi-wQeE43oY.js.map +0 -1
  150. package/dist/chunks/hu-DXtscQ8_.js +0 -2
  151. package/dist/chunks/id-BByOYpeo.js +0 -2
  152. package/dist/chunks/id-BByOYpeo.js.map +0 -1
  153. package/dist/chunks/id-Rx_wYvqy.js.map +0 -1
  154. package/dist/chunks/it-52Ip42pa.js +0 -2
  155. package/dist/chunks/it-52Ip42pa.js.map +0 -1
  156. package/dist/chunks/it-B1SRPZ3e.js.map +0 -1
  157. package/dist/chunks/ja-CMC3Parn.js.map +0 -1
  158. package/dist/chunks/ja-DiItm8te.js.map +0 -1
  159. package/dist/chunks/ko-3qY21q40.js +0 -2
  160. package/dist/chunks/ko-3qY21q40.js.map +0 -1
  161. package/dist/chunks/ko-CdGFWAKX.js.map +0 -1
  162. package/dist/chunks/nl-Borc5nw1.js +0 -2
  163. package/dist/chunks/nl-Borc5nw1.js.map +0 -1
  164. package/dist/chunks/nl-CmZ3vEvj.js.map +0 -1
  165. package/dist/chunks/no-BuxByZpq.js.map +0 -1
  166. package/dist/chunks/no-Do2iGedg.js +0 -2
  167. package/dist/chunks/no-Do2iGedg.js.map +0 -1
  168. package/dist/chunks/pl-jZFCnDb8.js.map +0 -1
  169. package/dist/chunks/pl-mRaky_7k.js +0 -2
  170. package/dist/chunks/pl-mRaky_7k.js.map +0 -1
  171. package/dist/chunks/pt-Cld7MwIW.js.map +0 -1
  172. package/dist/chunks/pt-DilDTXfs.js +0 -2
  173. package/dist/chunks/pt-DilDTXfs.js.map +0 -1
  174. package/dist/chunks/ro-CuKDqj3C.js.map +0 -1
  175. package/dist/chunks/ro-DF2uzPIB.js +0 -2
  176. package/dist/chunks/ro-DF2uzPIB.js.map +0 -1
  177. package/dist/chunks/ru-B7cjqJUm.js.map +0 -1
  178. package/dist/chunks/ru-YeaXnTO-.js +0 -2
  179. package/dist/chunks/ru-YeaXnTO-.js.map +0 -1
  180. package/dist/chunks/sv-2pHEvuSe.js +0 -2
  181. package/dist/chunks/sv-2pHEvuSe.js.map +0 -1
  182. package/dist/chunks/sv-D9y-M1Fo.js.map +0 -1
  183. package/dist/chunks/th-D9umBjEy.js.map +0 -1
  184. package/dist/chunks/th-u2QSDF0G.js +0 -2
  185. package/dist/chunks/th-u2QSDF0G.js.map +0 -1
  186. package/dist/chunks/tr-BddMywiw.js.map +0 -1
  187. package/dist/chunks/tr-Bhgm6_ti.js +0 -2
  188. package/dist/chunks/tr-Bhgm6_ti.js.map +0 -1
  189. package/dist/chunks/uk-7vwqsaVK.js +0 -2
  190. package/dist/chunks/uk-7vwqsaVK.js.map +0 -1
  191. package/dist/chunks/uk-Dbd31hFt.js.map +0 -1
  192. package/dist/chunks/vi-CJefLP_g.js +0 -2
  193. package/dist/chunks/zh-D5C3BzYL.js +0 -2
  194. package/dist/chunks/zh-D5C3BzYL.js.map +0 -1
  195. package/dist/chunks/zh-gQSYLZI3.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"content.cjs","sources":["../../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/AnonGate.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/shared/transport-client.ts","../src/content/transport.ts","../src/content/PaywallUI.ts"],"sourcesContent":["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 определяет утилиты вроде `.border` через `border-style: var(--tw-border-style)`,\n// где значение `solid` задаётся реестровой проперти `@property --tw-border-style { initial-value: solid }`.\n// В Chromium `@property`-объявления внутри shadow root не регистрируются document-wide, переменная\n// остаётся пустой → IACVT → border-style: none → used border-width: 0. Чтобы шорткаты работали в\n// shadow scope, регистрируем те же `@property` на уровне document один раз. `inherits: false`\n// держит изоляцию: имя проперти видно глобально, но значения на host-страницу не утекают.\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 // Уже зарегистрирована другим инстансом SDK на той же странице — ок.\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-режим: host позиционируется `absolute inset:0` (не fixed),\n * чтобы модалка осталась в границах своего родителя. Используется в\n * live-preview редактора админки. Родитель ОБЯЗАН быть positioned\n * (`position: relative|absolute|fixed`), иначе absolute уйдёт выше. */\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 // Fixed-viewport (production) или absolute-внутри-host'а (inline preview).\n // pointer-events:none — клики проходят сквозь host'овую обёртку к шахмат-\n // ке формы редактора; mountPoint ниже сам себе включит auto.\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 // Без host'а из options и без inline — крепим к body. Inline ожидает, что\n // host уже находится в нужном parent'е (платформа передаёт hostRef).\n if (!host.isConnected && !options.inline) document.body.appendChild(host);\n\n // Дефолт `closed` — изоляция от хост-страницы. В e2e/demo тестах\n // включаем `open` через опцию, иначе Playwright не проходит через\n // shadow boundary accessibility-снапшотом и не кликает по внутренним кнопкам.\n const shadow = host.attachShadow({ mode: options.shadowMode ?? 'closed' });\n\n // Защита от протечки наследуемых свойств (color, font, letter-spacing, text-transform,\n // cursor, visibility) с host-страницы внутрь shadow через host-элемент. `!important`\n // в shadow перебивает внешний `!important` на host (спека CSS Scoping).\n // Рендер-фильтры (filter, transform, opacity) на ancestors защитить нельзя —\n // они применяются на уровне композитинга.\n const hostReset = `\n:host {\n all: initial !important;\n display: block !important;\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 * Полный список встроенных static-translations языков. Каждый ключ\n * соответствует файлу в `./locales/<key>.ts`, который Vite разносит в\n * отдельный chunk (`chunks/<key>-[hash].js`).\n *\n * Порядок зеркалит legacy `online/lang/static-translations.ts`: 27 языков,\n * паритет с пейволом на старом стеке. EN — fallback, всегда inline в\n * основной чанк, в этом списке его нет.\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/** Словарь переводов: ключ → строка. Может содержать `{param}` placeholder'ы,\n * которые заполняет `t()` через второй аргумент. Отсутствующий ключ → fallback\n * из inline-вызова `t(key, fallback)`. */\nexport type TranslationDict = Record<string, string>;\n\n/** Сигнатура translator-функции. Inline fallback — обязательный, чтобы EN\n * работал даже без загруженного чанка и при отсутствии ключа в словаре. */\nexport type TFn = (\n key: string,\n fallback: string,\n params?: Record<string, string | number>\n) => string;\n","/**\n * Static-translations для UI-chrome SDK v3. Legacy-аналог —\n * `online/components/StaticTranslationContext.tsx` + `online/lang/static-translations.ts`.\n *\n * Архитектура:\n * - EN захардкожен fallback'ом в самих компонентах (`t('auth.welcome', 'Welcome back!')`).\n * Если чанк не загрузился — UI на английском, без пустых строк.\n * - Не-EN языки — отдельные модули `./locales/<key>.ts`. Vite разносит их в\n * `chunks/<key>-[hash].js`, динамический import грузит один чанк\n * по resolved-locale.\n * - Owner-controlled `bootstrap.locales` (layout/prices оверрайды) — независимая\n * система; static-чанк применяется только если у owner'а вообще есть\n * переводы (`when-configured` режим, как в legacy). Без translation-intent\n * у пейвола показывается чистый EN, даже если в SDK есть `de.ts`.\n * - Чанк грузится в фоне, **не блокирует** `bootstrap()`. До прибытия —\n * UI на EN; на arrival провайдер форсит re-render через 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/** Простая подстановка `{name}` → value. Не делает escape — все строки уходят\n * в textContent через preact, XSS невозможен. */\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/** Кеш загруженных словарей, чтобы переоткрытие пейвола не ходило за чанком\n * повторно (Vite кеширует module внутри себя, но cache на нашей стороне\n * избавляет от микро-затыка в Promise-цепочке). */\nconst dictCache = new Map<BundledLocale, TranslationDict>();\n\n/** Inflight-load'ы, чтобы конкурентные mount'ы (виджет + popup) делили один\n * динамический import вместо двух параллельных. */\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/** Подбирает встроенный язык по тому же алгоритму, что owner-overrides\n * (`pickLocaleKey` в BillingClient): `navigator.language` → base-tag →\n * `settings.locale_default`. Возвращает первый ключ, для которого у нас\n * есть чанк в `BUNDLED_LOCALES`. EN не возвращаем — это inline fallback,\n * загружать ничего не надо. */\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' (как legacy): static применяется только если у\n * owner'а есть хоть один локаль-оверрайд в `bootstrap.locales`. Это маркер\n * «пейвол i18n-aware» — без него тянуть static нет смысла (host рисует\n * английский UI вокруг, перевод модалки выглядит инородно). */\nexport function hasOwnerTranslations(bootstrap: PaywallBootstrap): boolean {\n return !!bootstrap.locales && Object.keys(bootstrap.locales).length > 0;\n}\n\n/** Грузит словарь для указанного языка. Идемпотентно: повторные вызовы\n * отдают тот же кешированный Promise. На сетевой/импорт-ошибке резолвится\n * пустым словарём (UI останется на EN-fallback'ах) — пейвол не должен\n * падать из-за недоступного locale-чанка. */\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 разносит этот dynamic import по chunkFileNames из vite.config.ts.\n // Шаблонная строка нужна, чтобы bundler сгенерил все 27 чанков; статичный\n // import('./locales/${key}.ts') без шаблона свернётся в один файл.\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 /** PaywallBootstrap, по которому резолвится язык. null/undefined — провайдер\n * работает в чистом EN-fallback режиме, чанк не грузится. */\n bootstrap: PaywallBootstrap | null | undefined;\n /** Explicit-override: форсит выбор языка минуя navigator.language и\n * owner-translations check. Используется live-preview редактором админки\n * («Preview as user from <country>») — там browser-locale всегда EN, а\n * bootstrap.locales может быть пустым (форма ещё не сохранена). Передавай\n * только bundled-ключи из `BUNDLED_LOCALES` — иначе fallback на обычный\n * путь резолвинга. */\n forceLocale?: string | null;\n children: ComponentChildren;\n}\n\n/**\n * Mount'ит provider, резолвит язык по bootstrap'у и асинхронно тянет чанк.\n * До прибытия чанка t() отдаёт fallback'и из inline-вызовов (EN). После —\n * setState триггерит re-render всех consumer'ов.\n *\n * Bootstrap может прийти позже (loading state в PaywallRoot) — useEffect\n * запустится на bootstrap-change и подхватит. Bootstrap может смениться\n * (revalidate подтянул другие locales/locale_default) — useEffect разрулит:\n * если resolved key изменился, грузим новый чанк, иначе остаёмся на текущем.\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: preview-режим админки. Грузим напрямую — owner-check\n // и navigator.language игнорируем (browser-locale в админке всегда EN).\n const explicit = forceLocale && isBundledLocale(forceLocale) ? forceLocale : null;\n const key = explicit ?? (() => {\n if (!bootstrap) return null;\n if (!hasOwnerTranslations(bootstrap)) return null;\n return pickStaticLocaleKey(bootstrap);\n })();\n\n // Нет резолва (или explicit=null в preview при возврате на EN-страну) —\n // откатываемся на canonical-EN fallback из inline t()-вызовов. Без сброса\n // старый dict остаётся в state и UI остаётся переведённым на предыдущий\n // язык — это и был баг live-preview при переключении со RU обратно на 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/** Хук для блоков: `const { t } = useI18n(); t('auth.welcome', 'Welcome back!')`.\n * Вне I18nProvider'а возвращает defaultT (EN-fallback) — позволяет блокам\n * рендериться в тестах/preview без обязательного 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 /** Контент, который приклеивается сверху dialog'а внутри overlay (rounded-top,\n * с лёгким negative-margin для визуального overlap). Используется для\n * offer-countdown баннера (PaywallRoot решает рисовать его, если в\n * bootstrap.offers есть активный таймер). */\n topBanner?: ComponentChildren;\n /** Можно ли закрыть модалку: ESC, клик по overlay, крестик. По умолчанию true.\n * false — модалка остаётся открытой до явного host-close() / success-purchase. */\n allowClose?: boolean;\n /** Скрыть X-крестик (но оставить ESC/overlay рабочими). Используется когда\n * view внутри модалки рисует свою Back-кнопку (AuthGate, SupportGate) —\n * две одновременные кнопки в правом верхнем углу путают и визуально\n * накладываются. */\n hideCloseButton?: boolean;\n /** Inline-режим: overlay позиционируется `absolute inset:0` относительно host'а\n * (вместо `fixed` относительно viewport'а), не лочит body-scroll. Для live-\n * 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 const first = dialog.querySelector<HTMLElement>(FOCUSABLE);\n (first ?? 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 не лочит body-scroll: host-страница (редактор) должна\n // оставаться кликабельной/скроллабельной, пока модалка живёт 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: overlay сидит в `absolute inset-0` host'а (host сам absolute в parent'е,\n // см. mount.ts). Production: `fixed inset-0` относительно 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 над dialog'ом. topBanner (если передан) рендерится тут же,\n приклеивается к верху dialog'а через `-mb-2 pb-5 rounded-t-xl\n rounded-b-none` — даёт визуальный overlap (rounded-top банера и\n rounded-top dialog'а скрыты под банером), как в легаси PaywallModal. */}\n {/* --pw-accent определяется на wrapper'е (не на dialog'е) — чтобы\n topBanner-sibling тоже его наследовал. Раньше переменная сидела на\n dialog, и OfferTopBanner получал 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 ограничивает высоту вьюпортом (использует dvh для мобильных\n // safe-area), flex-col + overflow на children даёт внутренний скролл\n // когда контент выше viewport'а — критично для extension popup'ов\n // (max 600px высоты) и узких контейнеров на сайтах.\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 сами структурируют scroll/footer-зоны (см. Renderer.tsx):\n flex-1 min-h-0 overflow-y-auto для scrollable, остаток — footer.\n Раньше Modal оборачивал в обёртку overflow-y-auto, но это не\n позволяло прибить CTA-footer к нижней кромке без overlap скролла\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 относительно dialog'а (не scrollable area) — кнопка\n // всегда в правом верхнем углу dialog'а, не двигается со скроллом\n // и не влияет на 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\ntype Mode = 'signin' | 'signup' | 'forgot' | 'reset_sent' | 'reset_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` из ApiClient на ошибках бэка без `message`-поля = HTTP statusText\n// (\"Unauthorized\", \"Bad Request\") — англоязычный и сырой. Маппим стабильные\n// `err.code` на i18n-ключи; для всего непонятного — generic fallback вместо\n// statusText. `code` приходит из тела ответа (`payload.code`) либо\n// `http_<status>`, либо `network_error` (см. 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 in a moment.');\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 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 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 // Анон-сессия — это «нет авторизации»: анон годится только для api-gateway,\n // покупка/restore требуют реального 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 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 ctx: BlockProps<AuthPanelBlock>['ctx'];\n}\n\nfunction AuthForm({ block, allowSignup, allowReset, ctx }: FormProps) {\n const { t } = useI18n();\n const auth = ctx.auth!;\n const providers = block.providers ?? [];\n\n const [mode, setMode] = useState<Mode>('signin');\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 const [error, setError] = useState<string | null>(null);\n const [info, setInfo] = useState<string | null>(null);\n // Sign up — progressive disclosure: первый клик «Sign Up» только раскрывает\n // password+confirm; второй клик с заполненными полями делает реальный signUp.\n // По смене mode сбрасываем — переход signin↔signup всегда начинается с\n // collapsed формы.\n const [signupExpanded, setSignupExpanded] = useState(false);\n\n // Last-used auth метод и email (per-paywall). Async-load из storage на mount,\n // пока null — UI просто рендерится без бейджа. Pre-fill email только если\n // юзер ещё ничего не вводил — иначе перезапишем то, что он печатает.\n //\n // Defensive: старые билды @monetize.software/sdk-extension (≤ 3.0.0-alpha.4)\n // не реализовали getLastLogin в RemoteAuthClient — без guard'а consumer\n // получал бы `auth.getLastLogin is not a function` в консоли. Бейдж в этом\n // случае просто не показывается, signin продолжает работать.\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 недоступен — UI без бейджа, signin работает */\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 };\n\n const onSubmit = async (e: Event): Promise<void> => {\n e.preventDefault();\n if (busy) return;\n setError(null);\n setInfo(null);\n\n // Sign up shortcut: первый сабмит просто раскрывает password-поля,\n // без сетевого запроса. Email обязателен на этом шаге, иначе HTML5\n // validation сама пометит поле required.\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 setMode('reset_verify');\n setInfo(t('auth.check_email_message', 'Check your email for a confirmation code.'));\n }\n } else if (mode === 'forgot') {\n await auth.requestPasswordReset({ email });\n setMode('reset_sent');\n setInfo(\n t('auth.reset_sent_message', 'If that email exists, a reset code has been sent.')\n );\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 }\n } catch (err) {\n const errMode =\n mode === 'signup' ? 'signup' : mode === 'reset_verify' ? 'otp' : mode === 'forgot' ? 'reset' : 'signin';\n setError(authErrorMessage(err, errMode, t));\n } finally {\n setBusy(null);\n }\n };\n\n const onOAuth = async (provider: OAuthProvider): Promise<void> => {\n if (busy) return;\n setBusy(provider);\n setError(null);\n setInfo(null);\n try {\n await auth.signInWithOAuth({\n provider,\n onPopupOpened: () => setBusy(null)\n });\n } catch (err) {\n if (err instanceof PaywallError && (err.code === 'oauth_cancelled' || err.code === 'oauth_timeout')) {\n return;\n }\n setError(authErrorMessage(err, 'signin', t));\n } finally {\n setBusy(null);\n }\n };\n\n const showOAuth = providers.length > 0 && (mode === 'signin' || mode === 'signup');\n const showEmailField = mode === 'signin' || mode === 'signup' || mode === 'forgot';\n const showPasswordField =\n mode === 'signin' || (mode === 'signup' && signupExpanded);\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' && (\n <>\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 <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\n {mode === 'reset_sent' && info && (\n <p class=\"rounded-2xl bg-gray-100 px-4 py-3 text-sm text-gray-600\">{info}</p>\n )}\n\n {mode === 'signin' && allowReset && (\n <div class=\"flex justify-end text-sm\">\n <AccentLink onClick={() => switchTo('forgot')}>\n {t('auth.forgot_password', 'Forgot password?')}\n </AccentLink>\n </div>\n )}\n\n {error && <p class=\"text-sm text-red-600\">{error}</p>}\n {info && mode !== 'reset_sent' && (\n <p class=\"text-sm text-gray-500\">{info}</p>\n )}\n\n {mode !== 'reset_sent' && (\n <div class=\"relative\">\n <PrimaryButton\n busy={busy === 'email'}\n label={submitLabel(mode, signupExpanded, block.submit_label ?? block.heading, t)}\n />\n {/* Email-бейдж рисуем только в signin: для signup/forgot он не имеет\n смысла (юзер целенаправленно идёт в другой flow, \"last used\"\n сбивал бы). */}\n {mode === 'signin' && lastLogin?.method === 'email' ? (\n <LastUsedBadge email={lastLogin.email} />\n ) : null}\n </div>\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'ят default для signin+signup mode'ов.\n // Restore/preauth intent ставит свой heading, но когда юзер кликает\n // \"Forgot password?\" — view меняется на forgot и должен показать\n // дефолтный \"Forgot password?\" заголовок, а не intent-specific строку.\n // Reset views (forgot/reset_sent/reset_verify) всегда используют дефолты.\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 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 }\n}\n\nfunction submitLabel(\n mode: Mode,\n signupExpanded: boolean,\n customHeading: string | undefined,\n t: TFn\n): string {\n // Если задан customHeading — он используется и как submit-лейбл для signin\n // (\"Restore Purchases\" → button \"Restore Purchases\"). Для остальных mode'ов\n // submit-лейбл фиксированный (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 'reset_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 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 при смене type между password↔text стирают .value (autofill-guard).\n // Preact видит ту же value-prop и не ре-сетит DOM — поле остаётся пустым.\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 // Pill в правом верхнем углу кнопки. truncate + max-w защищают от длинного\n // email'а (выйдет за края кнопки). pointer-events-none — чтобы клик попадал\n // в саму кнопку, а не в бейдж сверху.\n const label = email\n ? t('auth.last_used', 'Last · {email}', { email: maskEmail(email) })\n : t('auth.last_used_no_email', 'Last');\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. Маскируем local-part (видны\n// первые 3 символа), domain оставляем как есть — он публичен и помогает\n// юзеру опознать аккаунт.\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 даёт воздух сверху/снизу пути, поэтому визуально\n // Apple-яблоко выглядит меньше Google. Компенсируем увеличенным\n // width/height — 26×26 даёт примерно equal optical size с Google 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","import type { AuthClient, AuthSession } 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/** Контекст, из которого AuthGate был открыт. Управляет дефолтным заголовком/\n * субзаголовком, чтобы юзер сразу понимал зачем его сюда привели:\n * - `restore` — клик \"Restore purchases\" в current_session\n * - `preauth` — checkout_mode=preauth перед /start-checkout\n * - `standalone` — paywall.openAuth() (без всякого layout-контекста) */\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 /** Показывать кнопку Back. Для preauth/restore-flow — true (юзер пришёл сюда\n * из layout). Для standalone openAuth() — false: модалка открыта только\n * ради signin'а, ESC и крестик модалки уже её закрывают. */\n showBack?: boolean;\n intent?: AuthIntent;\n}\n\n// Полноэкранная обёртка над AuthPanel для AuthGate flow. AuthPanel сам не\n// знает про \"вернуться к тарифам\"; gate рисует Back-кнопку curved-arrow\n// в top-right (как на легаси-скринах) и подсовывает intent-specific heading.\nexport function AuthGate({\n block,\n bootstrap,\n auth,\n authSession,\n onBack,\n showBack = true,\n intent = 'preauth'\n}: AuthGateProps) {\n const { t } = useI18n();\n const ctx: BlockContext = {\n bootstrap,\n selectedPriceId: null,\n setSelectedPriceId: () => {},\n onAction: () => {},\n auth,\n authSession\n };\n\n // intent override'ит heading/subheading layout-block'а:\n // - 'restore' → \"Restore Purchases\" / sign-in-to-restore\n // - 'preauth' → \"Log in to continue your purchase\" / link-purchase\n // - 'standalone' (paywall.openAuth()) → дефолты по mode из AuthPanel\n // Если admin задал в layout кастомный heading/subheading — он сохраняется\n // только для standalone-варианта (для preauth/restore мы знаем контекст лучше).\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 — descriptive sentence (\"Log in to continue your\n // purchase\"), а не action verb. Длинные локализации (RU: \"Войдите,\n // чтобы продолжить покупку\") в pill-кнопку h-12 не помещаются и\n // переносятся на 2 строки. Явный короткий submit_label решает.\n submit_label: t('auth.log_in', 'Sign In')\n }\n : block;\n\n // Padding + overflow-y-auto делегированы сюда (а не в Modal), потому что\n // Modal-обёртка теперь structurally нейтральна — Renderer возвращает свой\n // sticky-footer-layout, а gate-views хотят обычный единый scroll-зона.\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 { AuthClient, AuthSession } from '../core/auth';\nimport { useI18n } from './i18n';\n\n// Anonymous sign-in gate. После удаления Turnstile-iframe'а (captcha в Supabase\n// выключена, защита держится на Supabase rate-limit per real-IP + CF Bot Fight\n// Mode) экран превратился в индикатор прогресса:\n// 1. Mount → auth.signInAnonymously() — внутри AuthClient'а сначала проверит\n// idempotent (если уже анон — instant return) и resume (если есть\n// сохранённый refresh_token — silent restore), потом fresh signin.\n// 2. Success → onSuccess(session). Gate схлопывается через PaywallRoot.\n// 3. Error → отображаем сообщение + кнопку «Try again».\n//\n// `forceCaptcha` имя сохранили в API ради host backward-compat (на самом деле\n// — «принудительно создать нового anon-юзера, не resume'ить»).\n\nexport interface AnonGateProps {\n auth: AuthClient;\n /** Вызывается после успешного signin'а (любым путём — idempotent / resume / fresh). */\n onSuccess: (session: AuthSession) => void;\n /** Кнопка «← Back». Опциональна — когда AnonGate смонтирован как\n * standalone (paywallUI.openAnonGate()), `onBack` приведёт к закрытию\n * модалки целиком; для inline-варианта в layout — возврат к тарифам. */\n onBack?: () => void;\n heading?: string;\n description?: string;\n}\n\ntype Phase =\n | { kind: 'signing-in' }\n | { kind: 'error'; message: string };\n\nexport function AnonGate({\n auth,\n onSuccess,\n onBack,\n heading,\n description\n}: AnonGateProps) {\n const { t } = useI18n();\n const resolvedHeading = heading ?? t('anon.heading_default', 'Continue as guest');\n const resolvedDescription = description ?? t('anon.description_default', 'Setting up your guest session…');\n const [phase, setPhase] = useState<Phase>({ kind: 'signing-in' });\n // Защита от race: если юзер закрыл модалку посреди signin'а, не зовём\n // onSuccess из устаревшего промиса.\n const aliveRef = useRef(true);\n useEffect(() => {\n return () => {\n aliveRef.current = false;\n };\n }, []);\n\n const run = (): void => {\n setPhase({ kind: 'signing-in' });\n void (async () => {\n try {\n const session = await auth.signInAnonymously();\n if (!aliveRef.current) return;\n onSuccess(session);\n } catch (e) {\n if (!aliveRef.current) return;\n setPhase({\n kind: 'error',\n message: e instanceof Error ? e.message : 'Anonymous sign-in failed'\n });\n }\n })();\n };\n\n // Один автозапуск на mount. Зависимости — пустые: повторное срабатывание\n // привело бы к лишним сетевым запросам на каждом ре-рендере родителя.\n useEffect(() => {\n run();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return (\n <div class=\"flex flex-col gap-3\">\n {onBack ? (\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 ) : null}\n\n <div class=\"flex flex-col gap-1\">\n <h2 class=\"text-xl font-semibold text-gray-900\">{resolvedHeading}</h2>\n <p class=\"text-sm text-gray-500\">{resolvedDescription}</p>\n </div>\n\n {phase.kind === 'signing-in' ? (\n <div class=\"flex items-center justify-center py-6\">\n <Spinner />\n </div>\n ) : null}\n\n {phase.kind === 'error' ? (\n <div class=\"flex flex-col gap-3\">\n <div class=\"rounded-lg bg-red-50 px-3 py-2 text-sm text-red-700\">\n {phase.message}\n </div>\n <button\n type=\"button\"\n onClick={run}\n class=\"self-start rounded-md bg-[var(--pw-accent)] px-3 py-1.5 text-sm font-medium text-white hover:opacity-90 focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--pw-accent)] focus-visible:ring-offset-2\"\n >\n {t('anon.try_again', 'Try again')}\n </button>\n </div>\n ) : null}\n </div>\n );\n}\n\nfunction Spinner() {\n return (\n <svg class=\"h-5 w-5 animate-spin text-[var(--pw-accent)]\" viewBox=\"0 0 24 24\" fill=\"none\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"3\" stroke-opacity=\"0.2\" />\n <path d=\"M22 12a10 10 0 0 0-10-10\" stroke=\"currentColor\" stroke-width=\"3\" stroke-linecap=\"round\" />\n </svg>\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// Хранилище старта для относительных таймеров (offer.duration_minutes). Ключ\n// привязан к offer.id — повторное открытие пейвола не сбрасывает countdown,\n// юзер не может «фармить» offer-баннер бесконечно. Чистим по истечении.\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// Резолвит endMs: expires_at (абс. серверная дата) — приоритет, иначе\n// duration_minutes от первого open'а пейвола, сохранённого в localStorage.\n// null — offer без таймера, banner не нужен.\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 недоступен (private mode / SSR) — relative timer бесполезен.\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 // Первый offer с активным таймером. Без таймера — banner не имеет смысла\n // (offer-without-urgency показывается через PriceGrid discount badge).\n return offers.find((o) => o.expires_at || o.duration_minutes) ?? null;\n}\n\n/** Hook: tick'ает каждую секунду пока offer не expired. Возвращает null если\n * offer некорректен (нет таймера). Используется и в layout-block OfferBanner,\n * и в top-tab OfferTopBanner из 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 if (next.expired) {\n clearInterval(timer);\n if (offer?.duration_minutes && typeof window !== 'undefined') {\n try {\n window.localStorage.removeItem(STORAGE_KEY(offer.id));\n } catch {\n /* ignore */\n }\n }\n }\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: приклеивается к верху Modal'а как ярлычок-вкладка\n * (rounded-top, negative margin-bottom для overlap). Зеркало легаси\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 { useMemo, 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' — модалка открыта только для саппорта (paywall.openSupport()),\n // Back/Done закрывают её. 'layout' — пришли из current_session-блока,\n // Back/Done возвращают в layout (и пейвол с тарифами остаётся открытым).\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;\nconst MAX_FILE_SIZE = 10 * 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 // Если есть сессия — email фиксируем из неё, форма его не редактирует.\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 isValid = useMemo(() => {\n const e = (lockedEmail ?? email).trim().toLowerCase();\n const s = subject.trim();\n const m = message.trim();\n return (\n EMAIL_RE.test(e) &&\n s.length >= SUBJECT_MIN &&\n s.length <= SUBJECT_MAX &&\n m.length >= 1 &&\n m.length <= CONTENT_MAX\n );\n }, [lockedEmail, email, subject, message]);\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 идентичен Renderer.tsx — кнопки\n // прибиты к низу dialog'а и читабельны на коротких viewport'ах (extension\n // popup ≤600px), скроллится только контент над ними.\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 {/* email рендерим отдельным <b>, prefix-only ключ — для языков с\n порядком \"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={!isValid || 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(t('support.invalid_file', 'Only JPEG/PNG/WebP, ≤ 10MB each'));\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, ≤ 10MB each', {\n max: MAX_FILES\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// Плановые ключи для \"Get X Plan\". Если interval — известная константа,\n// берём dedicated-ключ (даёт переводчику правильный род/падёж для каждого\n// интервала). Для экзотики вроде day/half-year fallback'имся на generic с\n// {interval}-подстановкой — выглядит чуть хуже грамматически в RU/DE, но\n// мы не теряем интервал в 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 по легаси-логике из online/PaywallPricing.tsx:\n// - trial_days > 0, interval !== 'lifetime', юзер ещё не брал trial →\n// \"Start N-Day Free Trial\"\n// - interval === 'lifetime' → \"Get Lifetime Access\"\n// - иначе → \"Get {Interval} Plan\"\n// `hadPreviousTrial` гасит trial-ветку — anti-abuse: один юзер может взять\n// trial по пейволу только один раз. Серверный enforcement в\n// /start-checkout (utils/checkout-with-acquiring.ts) дублирует.\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` берём из bootstrap.user snapshot'а. Это значит, что\n // после signin'а через preauth flow (юзер был гостем на момент bootstrap'а)\n // флаг останется false до следующего bootstrap-revalidate; UI коротко\n // покажет \"Start Free Trial\", но серверный enforcement в /start-checkout\n // всё равно создаст checkout без trial — анти-abuse не нарушится.\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 под cta_button. Зеркалит legacy v2 PaywallCurrentSession:\n// - залогинен → \"Signed in as <email>\" + Sign out (вызывает auth.signOut())\n// + Contact Support\n// - гость → \"Restore purchases\" + Contact Support\n// Без AuthClient в managed-режиме — рендерим только Restore + Support\n// (sign out нечему делать, restore без auth-клиента no-op'нет в handleAction).\n// Анон-сессия (is_anonymous=true) трактуется как «нет логина»: анон существует\n// только ради api-gateway-токена, у юзера нет email и UX-смысла «Signed in».\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 ошибки безшумные — onAuthChange всё равно отработает на refresh-fail */\n } finally {\n setSigningOut(false);\n }\n };\n\n // \"Signed in as <email>\" — рендерим вручную из двух частей, чтобы email\n // оставался в bold-вёрстке. {email} placeholder в локали игнорируется —\n // строка показывается до email, b-тег c email-ом идёт после.\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 под CtaButton. Компактный one-liner: shield-check\n// иконка + текст. Pill-styling (rounded-full + bg-gray-100) визуально отделяет\n// от CTA, но не перетягивает внимание на себя — это reassurance-элемент.\n// Subtitle убран из дефолтного рендера: пользователи сканируют покупку\n// быстро, второй строкой только шум. Если admin задаёт block.subtitle явно —\n// она рендерится мелким серым ниже 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 // Выделяем \"N-day\" префикс жирным/тёмным — главная информация (срок),\n // остальное обычным весом. Глаз сразу ловит цифру вместо ровного блока.\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 «безопасность/возврат», поднимает контрастность\n // reassurance-сигнала. Серый gray-500 пропускался глазом.\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; // соответствует sm:text-2xl у h1\nconst MIN_FONT_PX = 16;\nconst MAX_LINES = 2;\n\n// Авто-fit: если заголовок не вмещается в `MAX_LINES` строк при базовом размере,\n// уменьшаем font-size шагом 1px до тех пор, пока влезает или не упёрлись в\n// `MIN_FONT_PX`. Используется только для h1 — h2/h3 это подзаголовки, им\n// клиппинг не нужен. Считаем по реальной высоте элемента (scrollHeight) после\n// рендера — иначе пришлось бы держать canvas-измеритель.\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 у text-2xl = 1.5 (Tailwind дефолт). Считаем от текущего\n // computed line-height — устойчиво к будущим CSS-изменениям.\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 type { BlockProps } from '../types';\nimport { useI18n, type TFn } from '../../i18n';\n\ntype PriceGridBlock = Extract<LayoutBlock, { type: 'price_grid' }>;\n\ninterface FormattedPrice {\n /** Символ валюты (или ISO-код, если символ не определился). */\n currency: string;\n /** Целая часть, без разделителей дробной. */\n amount: string;\n /** Original (без discount), formatted — для strike-through. null если\n * скидки нет или discount=0%. */\n originalAmount: string | null;\n}\n\n// Year-план показывает per-month эквивалент сразу в основной цене:\n// YEARLY PLAN €4.99 / month (вместо €59.99 / year)\n// Это легаси-UX из online/PaywallPricing.tsx (`unit_amount / 12`):\n// юзеру важна стоимость в месяц для сравнения с monthly-планом, а годовое\n// списание — деталь, которая не должна доминировать в типографике. planLabel\n// остаётся \"YEARLY PLAN\", так что billed-cadence по-прежнему понятен из\n// названия.\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// Форматирует число в currency-string без литералов, разделяет currency-symbol\n// и числовую часть. Используется и для основной цены, и для strike-through\n// original (тогда discount применять не нужно — value уже base).\n// Дробная часть авто: целое → \"$8\", нецелое → \"$4.99\". Целые без .00 заметнее\n// конвертят — глаз быстрее цепляет короткое число.\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 // Strike-through показываем полностью (`€59.99`/`€9.99` — с currency-знаком),\n // чтобы юзер сразу видел старую цену в той же валюте, без догадок.\n return {\n currency: main.currency,\n amount: main.amount,\n originalAmount: `${original.currency}${original.amount}`\n };\n}\n\n// Подбирает offer для конкретной цены. Targeted offer (price_id === priceId)\n// имеет приоритет над глобальным (price_id === null — применяется ко всем\n// ценам пейвола). Возвращает null, если discount_percent отсутствует/0.\nfunction applicableOffer(\n offers: PaywallOffer[] | undefined,\n priceId: string\n): PaywallOffer | null {\n if (!offers || offers.length === 0) return null;\n const targeted = offers.find(\n (o) => o.price_id === priceId && o.discount_percent && o.discount_percent > 0\n );\n if (targeted) return targeted;\n const global = offers.find(\n (o) => o.price_id == null && o.discount_percent && o.discount_percent > 0\n );\n return global ?? null;\n}\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// Суффикс после цены. Year → \"month\" (потому что amount уже /12, см.\n// displayedAmount). Lifetime → \"lifetime\". Прочее — singular interval\n// или \"N intervals\" для 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-режим — telegram-style список: тонкая подложка-карточка вокруг\n // всех строк (rounded-xl + light bg + 1px border). Разделители между\n // строками — `border-b` на внутреннем label-wrapper'е CompactRow (кроме\n // последней). Зеркало legacy PaywallPricing wrapper'а: для не-default view\n // он рисует `rounded-xl border-1 border-default-200 bg-default-50` —\n // отделяет блок цен от остального layout'а.\n // v2 storage-ключ — `view: 'telegram'`, bootstrap нормализует в '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={applicableOffer(ctx.bootstrap.offers, price.id)}\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-режим — реальный grid из карточек side-by-side. v2 storage-ключ\n // `view: 'row'` (SDK 3.0 only — старые legacy-paywall'ы этого ключа не\n // выбирают; bootstrap нормализует в 'horizontal'). max 3 столбца, при 1-2\n // ценах stretch'ат строку. Tailwind purge не переживает runtime grid-cols-N,\n // потому inline gridTemplateColumns.\n if (block.view === 'horizontal') {\n const cols = Math.min(prices.length, 3);\n // Если хоть у одной цены в гриде есть discount — резервируем strike-row\n // фиксированной высотой у ВСЕХ карточек (иначе main amount без скидки\n // прыгает выше соседних со скидкой). Если оффера нет совсем — strike-row\n // схлопывается в 0 у всех, и не висит 22px пустоты под label'ом.\n const anyHasDiscount = prices.some(\n (p) => (applicableOffer(ctx.bootstrap.offers, p.id)?.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={applicableOffer(ctx.bootstrap.offers, price.id)}\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 = applicableOffer(ctx.bootstrap.offers, price.id);\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 — selection выражается только цветом, layout\n // не прыгает (равная толщина у selected/unselected). Цветовая\n // разница accent vs gray достаточно сильная для 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 // Popular-label badge сидит absolute сверху-справа карточки и\n // визуально сдвигает центр content'а вниз. flex items-center\n // на карточке держит галочку по геометрическому центру, что\n // делает её визуально выше — компенсируем небольшим 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 на одной строке (flex-wrap для узких\n карточек) — компактный 2-row layout вместо 3-row. Tags идут\n справа от label с gap'ом, при переполнении переносятся. */}\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 приглушает strike: глаз сначала ловит label\n // и discount-badge, потом main price; original «бывшая цена»\n // — третичная информация, не должна конкурировать с 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 — фиксированный «успех/выгода», не зависит от\n // brand_color. Читается даже на тёмных бренд-акцентах.\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 — высокий contrast, glasses-test'ом\n // глаз сразу выхватывает popular pick. Pastel-вариант\n // конкурировал по visual weight с самой ценой и не работал\n // ни как highlight, ни как информация.\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// Короткий one-word label для compact-режима (\"Month\" / \"Year\" / \"Lifetime\")\n// вместо длинного \"MONTHLY PLAN\". Зеркало legacy `getIntervalName` для\n// TelegramPricingRadio: чем компактнее ряд — тем компактнее лейбл, иначе\n// текст начинает конкурировать с ценой.\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// Компактная строка для compact-режима. Зеркало legacy `TelegramPricingRadio`:\n// [radio] | [label + popular-pill] ······ [strike+badge ▸ price]\n// Разделители живут на внутреннем label-wrapper'е (`border-b`), последняя\n// строка без border'а. Selection выражается только цветом radio-кружочка —\n// никакого bg-tint'а, чтобы не конфликтовало с pricing-сеткой. Шрифты — text-md\n// без жирного, как в 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 {/* Внутренний wrapper, на нём `border-b` — разделитель между строками.\n Сидит за radio'м (по flex-flow), даёт визуальную нижнюю линию ровно\n под label/price колонками, как в 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 — точно как `badge` в TelegramPricingRadio.\n // Низкий visual weight: pill про \"имя плана\" (most popular), а не\n // про savings — не должна конкурировать с -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// Компактная карточка для horizontal-grid'а. UX-модель — Stripe pricing tables:\n// selection выражается border-цветом + tinted bg всей карточки, без отдельного\n// radio-кружочка (в узкой колонке любая icon-метка конкурирует с ценой за\n// внимание). Popular-badge — absolute pill сверху-справа (как в default view):\n// освобождает вертикаль внутри карточки, читается как premium-маркер. Все\n// карточки в ряду выровнены через `items-stretch` на grid'е (см. вызов).\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 /** Резервировать высоту под strike-row (originalAmount + discount-pill) даже\n * у этой карточки без скидки. true когда в гриде есть хотя бы одна цена со\n * скидкой — иначе main amount без скидки прыгает выше соседних со скидкой.\n * false когда оффера нет ни у одной цены в гриде — strike-row коллапсится\n * в 0 у всех, не висит 22px пустоты под 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 с фиксированной min-height на 2 строки — длинные (\"YEARLY PLAN\")\n и короткие (\"LIFETIME\") не сдвигают цену между карточками. */}\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 сверху ПЕРЕД main amount: сначала \"была $10\" + \"-20%\",\n потом крупно \"$8\". Высота резервируется (h-[22px]) только если в\n гриде есть хоть одна цена со скидкой — это держит alignment между\n карточками со скидкой и без. Если оффера нет совсем — row не\n рендерим, не остаётся 22px пустоты под label'ом во всех карточках. */}\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 — отстраивает badge\n // от border'а карточки, имитирует \"наклейку\". Зеркало 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 если над dialog'ом рендерится OfferTopBanner (он берёт на себя\n * visual top-bleed под X-крестиком). Без banner'а уменьшаем top-padding\n * scrollable-зоны — иначе под X остаётся 32px пустоты. */\n hasTopBanner?: boolean;\n}\n\nexport function Renderer({ layout, bootstrap, onAction, auth, authSession, hasTopBanner }: RendererProps) {\n // По умолчанию selected = popular_price_id (если он указан в каком-то\n // price_grid block'е и действительно существует в bootstrap.prices). Это\n // повторяет UX легаси-пейвола: highlighted-карточка сразу подсвечена и\n // готова к покупке, юзеру не нужно её доп. кликать. Fallback — первая цена.\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 + всё после него — pinned footer внизу dialog'а: всегда видимы, даже\n // если контент сверху не помещается по высоте. Сплит по первому `cta_button`:\n // нет cta → секция не рендерится, весь layout скроллится как обычно.\n // Используем flex (а не position:sticky) — sticky не даёт scrollable-области\n // правильно отдать высоту footer'у (контент скроллился ПОД footer вместо\n // того чтобы расширить min-h: 0), плюс shadow от sticky показывался даже\n // когда overflow'а нет. Flex чист: 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: верхний padding визуально отделяет от dialog top (и\n banner'а), нижний — меньше, потому что footer добавляет свой pt\n + border. Раньше было `p-8` снизу, давало ~48px зазор до 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 // Тонкий shadow-top вместо border-t — создаёт depth, читается как\n // «footer закреплён к низу dialog'а». Линия выглядела как divider\n // в обычном flow, не передавала 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 { useEffect, useLayoutEffect, useRef, useState } from 'preact/hooks';\nimport type { BillingClient } from '../core/BillingClient';\nimport type { AuthSession } from '../core/auth';\nimport type { LayoutBlock, PaywallBootstrap } from '../core/types';\nimport { PaywallError } from '../core/types';\nimport { Modal } from './Modal';\nimport { AuthGate } from './AuthGate';\nimport { AnonGate } from './AnonGate';\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 = 'layout' | 'support' | 'auth' | 'anon';\n\n/**\n * Публичный snapshot состояния PaywallUI для host'а. Производится из internal\n * LoadState + GateState + open/purchased флагов. Каждое реальное изменение —\n * один call onState; повторы (`useSyncExternalStore`-friendly).\n */\nexport interface PaywallStateSnapshot {\n /** Модалка отрендерена и видна. False — closed (или ещё не открывалась). */\n open: boolean;\n /** Что показывается в модалке. null когда `open=false`. */\n view:\n | 'loading'\n | 'error'\n | 'layout'\n | 'auth'\n | 'anon'\n | 'support'\n | 'awaiting_payment'\n | 'popup_blocked'\n | 'purchased'\n | null;\n /** Заполнено только когда `view === 'error'`. */\n error: PaywallError | null;\n}\n\n// 'anon' — отдельная вью для signInAnonymously (silent resume / fresh signin).\n// Не сливается с 'auth', потому что UX разный: auth — формы email/oauth,\n// anon — спиннер без интеракции; и flow завершения тоже разный (анон не идёт\n// через has_active_subscription pre-check после signin'а).\n\nexport interface PaywallRootProps {\n client: BillingClient;\n open: boolean;\n onClose: () => void;\n onEvent: (event: string, payload?: unknown) => void;\n /** Какой view показать при open=true. 'support' стартует сразу с саппорт-формой,\n * Back/Done закрывают модалку (origin='standalone'). По умолчанию 'layout'. */\n initialView?: PaywallView;\n /** Server-confirmed покупка — показать success-вью с кнопкой Continue.\n * Управляется снаружи (PaywallUI выставляет true из watcher.onActive),\n * сбрасывается на open()/close(). Перебивает любые другие view. */\n purchased?: boolean;\n /** Renewal/upgrade flow. true — пропускаем все has_active_subscription\n * pre-check'и (bootstrap-time + post-auth), и при checkout передаём\n * `ignoreActivePurchase: true` на бэк, чтобы /start-checkout не вернул\n * 409 для уже подписанного юзера. См. OpenOptions.renew. */\n renew?: boolean;\n /** Публичный state-machine notify. PaywallUI прокидывает сюда колбек, который\n * кэширует snapshot и эмитит свой `onStateChange`. Если не передан —\n * state-tracking отключён (нет оверхеда для host'ов, которым не нужно). */\n onState?: (snapshot: PaywallStateSnapshot) => void;\n /** Inline-режим (live-preview редактора админки): передаётся в Modal, чтобы\n * overlay был absolute-внутри-host'а вместо fixed-viewport'а, и не лочил\n * body-scroll. По умолчанию false. */\n inline?: boolean;\n /** Explicit-override языка для I18nProvider. Используется live-preview\n * редактором админки — там browser-locale всегда EN, а нужно показать как\n * для юзера из выбранной страны. См. I18nProviderProps.forceLocale. */\n locale?: 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' — gate открыт через \"Restore purchases\"\n // (без последующего checkout-а), после signIn схлопываемся в layout. С\n // pendingCheckout — gate открыт по preauth-flow от cta_button и после signIn\n // auto-resume createCheckout. origin='standalone' — paywall.openAuth(): модалка\n // открыта только для логина, после signIn / Back закрываем модалку, layout\n // вообще не показываем.\n | {\n kind: 'auth_gate';\n pendingCheckout?: { priceId: string };\n origin?: 'layout' | 'standalone';\n /** Контекст открытия — управляет заголовком gate'а\n * (\"Restore Purchases\" vs \"Welcome back!\"). Дефолт — 'preauth'. */\n intent?: 'restore' | 'preauth' | 'standalone';\n }\n // origin='standalone' — paywall.openAnonGate(): модалка открыта только ради\n // анонимного логина, после signin'а закрываем модалку. origin='layout' — пока\n // не используется (анон-блока в layout нет), но оставляем для симметрии с\n // auth_gate на случай будущего inline-варианта.\n | {\n kind: 'anon_gate';\n origin: 'layout' | 'standalone';\n }\n // origin='layout' — пришли из current_session-блока, Back возвращает в layout.\n // origin='standalone' — модалка открыта только для саппорта (paywall.openSupport()),\n // Back закрывает модалку.\n | { kind: 'support'; origin: 'layout' | 'standalone' }\n // window.open отдал handle — checkout открылся в новой вкладке. Пейвол остаётся\n // как индикатор: «оплати в той вкладке». priceId храним, чтобы кнопка retry\n // могла пересоздать checkout (URL'ы у Stripe/Paddle expire'ятся). url храним,\n // чтобы fallback-ссылка «Didn't open? Click here» переоткрывала тот же URL без\n // повторного похода в createCheckout — нужно для случая когда window.open\n // отдал handle, но реально таб заблокирован (агрессивные мобильные блокеры).\n | { kind: 'awaiting_payment'; priceId: string; url: string }\n // window.open вернул null — попап заблокирован (бывает после async-резюма\n // post-auth, когда transient activation истёк). НЕ редиректим текущую вкладку:\n // пейвол должен остаться. URL уже выписан — кнопка «Open checkout» дёрнет\n // window.open под фреш-гестуром, без второго похода в createCheckout.\n | { kind: 'popup_blocked'; priceId: string; url: string }\n // Юзер уже залогинен и has_active_subscription — показываем success-view.\n // Срабатывает либо после auth-resume (поллим getUser сразу после signIn),\n // либо когда /start-checkout вернул 409 hasActivePurchase. restored=true\n // меняет текст PurchaseSuccessView на «Subscription restored».\n | { kind: 'purchase_success'; restored: boolean }\n // После signIn ждём getUser({force:true}), пока не узнаем есть ли уже\n // active subscription. Без этого промежуточного state'а юзер видит\n // несколько секунд auth_gate'овский «серый экран» с уже скрытой формой.\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 if (!open) return { open: false, view: null, error: null };\n if (purchased) return { open: true, view: 'purchased', error: null };\n if (state.status === 'idle' || state.status === 'loading') {\n return { open: true, view: 'loading', error: null };\n }\n if (state.status === 'error') {\n return { open: true, view: 'error', error: state.error };\n }\n if (gate.kind === 'support') return { open: true, view: 'support', error: null };\n if (gate.kind === 'auth_gate') return { open: true, view: 'auth', error: null };\n if (gate.kind === 'anon_gate') return { open: true, view: 'anon', error: null };\n if (gate.kind === 'awaiting_payment') {\n return { open: true, view: 'awaiting_payment', error: null };\n }\n if (gate.kind === 'popup_blocked') {\n return { open: true, view: 'popup_blocked', error: null };\n }\n if (gate.kind === 'purchase_success') {\n return { open: true, view: 'purchased', error: null };\n }\n if (gate.kind === 'verifying') {\n return { open: true, view: 'loading', error: null };\n }\n return { open: true, view: 'layout', error: null };\n}\n\nfunction sameSnapshot(a: PaywallStateSnapshot, b: PaywallStateSnapshot): boolean {\n return a.open === b.open && a.view === b.view && a.error === b.error;\n}\n\nexport function PaywallRoot({\n client,\n open,\n onClose,\n onEvent,\n initialView,\n purchased,\n renew,\n onState,\n inline,\n locale\n}: PaywallRootProps) {\n const [state, setState] = useState<LoadState>({ status: 'idle' });\n // session держим в state, чтобы блоки (auth_panel) ре-рендерились на login/logout.\n // Без этого AuthPanel прочитал бы snapshot один раз и не схлопнулся бы после\n // успешного 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') return { kind: 'auth_gate', origin: 'standalone' };\n if (initialView === 'anon') return { kind: 'anon_gate', origin: 'standalone' };\n return { kind: 'layout' };\n });\n // Защита от двойного auto-resume: useEffect ниже зависит от authSession,\n // и подписка onAuthChange может прислать одну и ту же сессию повторно\n // (refresh) — без флага мы дважды дёрнем createCheckout.\n const resumingRef = useRef(false);\n\n // State-machine bridge: эмитим snapshot когда меняется любое из\n // (open, state, gate, purchased). sameSnapshot гасит no-op'ы — например\n // переход loading→error меняет state.status, но если мы уже в error-вью\n // (иначе невозможно), эмит не повторится.\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'а: BillingClient.setBootstrap (preview-mode в\n // редакторе админки) или cross-tab storage.watch эмитят onBootstrapChange.\n // Перерендериваем модалку, только если она уже в ready-фазе — иначе\n // bootstrap-effect ниже сам подхватит свежий cached на open().\n // Guard: тесты передают stub-клиента без 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 // Юзер уже подписан — host вызвал open() вслепую (без getAccess pre-check'а),\n // или просто из popup'а «Open paywall». Не показываем тарифы — переключаемся\n // в restored success-view. Эмитим purchase_completed чтобы host получил\n // согласованный сигнал, как из любых других путей (UserWatcher, 409 в\n // checkout, auth-resume). renew=true пропускает эту проверку — host явно\n // показывает «Renew»/«Upgrade», тарифы должны быть видны.\n if (data.user?.has_active_subscription && !renew) {\n onEvent('purchase_completed', {\n priceId: null,\n sessionId: null,\n restored: true\n });\n setGate({ kind: 'purchase_success', restored: true });\n }\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 // Закрытие/повторное открытие модалки сбрасывает gate. Standalone-flows\n // (openSupport / openAuth) PaywallUI вызывает на уже смонтированном компоненте\n // через handle.update({initialView: 'support'|'auth'}) — useState-initializer\n // отрабатывает только на первом mount'е, поэтому без этого эффекта gate\n // оставался бы 'layout' (с тарифами) при последующих standalone open'ах.\n //\n // useLayoutEffect (не useEffect): после close gate уходит в 'layout', и при\n // следующем openAuth/openSupport обычный useEffect запускался ПОСЛЕ paint'а,\n // из-за чего юзер на один кадр видел тарифы вместо auth-формы (особенно\n // заметно в extension-popup'е, где RemoteAuth+RemoteBilling добавляют\n // транспортные RTT и main thread чаще yield'ит между render'ами).\n // useLayoutEffect синхронизирует gate ДО paint'а — flicker'а нет.\n useLayoutEffect(() => {\n if (!open) {\n setGate({ kind: 'layout' });\n resumingRef.current = false;\n return;\n }\n if (initialView === 'support') {\n setGate({ kind: 'support', origin: 'standalone' });\n } else if (initialView === 'auth') {\n setGate({ kind: 'auth_gate', origin: 'standalone' });\n } else if (initialView === 'anon') {\n setGate({ kind: 'anon_gate', origin: 'standalone' });\n }\n }, [open, initialView]);\n\n const runCheckout = async (priceId: string) => {\n try {\n const result = await client.createCheckout({\n priceId,\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 // Без `noopener,noreferrer` в фичах: эти флаги заставляют window.open\n // ВСЕГДА вернуть null (даже когда попап реально открылся), и мы не\n // могли отличить «успех» от «заблокирован». Severance делаем вручную\n // через popup.opener=null после успеха — на checkout-домене (Stripe/\n // Paddle) opener-доступ всё равно cross-origin-restricted, но явный\n // null безопаснее.\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 // Попап заблокирован — обычно из-за stale transient activation\n // (auto-resume после async signin). НЕ уносим юзера через\n // location.assign: пейвол должен остаться открытым. Показываем\n // inline retry; клик по кнопке — fresh gesture, попап откроется.\n setGate({ kind: 'popup_blocked', priceId, url: result.url });\n }\n } catch (error) {\n // 409 hasActivePurchase от бэка — это не ошибка чекаута, это «у юзера\n // уже есть активная подписка». Освежаем cache (host'овский userChange\n // должен увидеть has_active_subscription=true), эмитим purchase_completed\n // с restored=true и переключаемся в success-view. Не setGate в layout,\n // не onEvent('error') — иначе host увидит false-negative.\n if (error instanceof PaywallError && error.code === 'already_purchased') {\n try {\n await client.getUser({ force: true });\n } catch {\n /* offline / 401 — host'у getUser сам отрапортует, тут это не блокирует success-view */\n }\n onEvent('purchase_completed', { priceId, sessionId: null, restored: true });\n setGate({ kind: 'purchase_success', restored: true });\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 — иначе застрянем в auth_gate\n // (если пришли через preauth-flow) с уже залогиненной сессией.\n setGate({ kind: 'layout' });\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 // Если и сейчас null — оставляем popup_blocked, юзер кликнет ещё раз.\n };\n\n // Auto-resume: появилась сессия в открытом gate → продолжаем флоу.\n // Pending preauth-checkout — НЕ схлопываем gate в layout до runCheckout:\n // иначе юзер видит мигание тарифов между submit'ом auth-формы и открытием\n // чекаут-вкладки. runCheckout сам переведёт gate в awaiting_payment /\n // popup_blocked / layout (на ошибке). Restore-flow без pendingCheckout —\n // просто возвращаемся в layout. resumingRef защищает от повторного запуска,\n // если authChange сработает несколько раз за один gate-цикл (refresh).\n useEffect(() => {\n if (gate.kind !== 'auth_gate') return;\n // Анон-сессия не считается логином: юзер пришёл в auth_gate реально\n // залогиниться. Иначе openAuth() при существующем анон-токене мгновенно\n // закрывал бы модалку через auto-resume, и формы он бы не увидел.\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 // Сразу переключаемся в verifying — иначе модалка висит в auth_gate с\n // уже залогиненным юзером (~3с пока getUser ходит к бэку), и юзер видит\n // «пустой серый экран» вместо progress'а. Loader честнее показывает что\n // SDK что-то делает.\n setGate({ kind: 'verifying' });\n void (async () => {\n // Прежде чем продолжать flow (runCheckout / возврат в layout / закрытие\n // модалки), проверяем — может, у юзера уже есть active subscription.\n // Сценарии: Restore-кнопка (он уже платил с другого аккаунта); preauth\n // signIn (юзер вспомнил, что подписка есть); standalone openAuth.\n // Без этой проверки юзер увидел бы тарифы и кликнул Buy → 409 от бэка\n // → fallback на already_purchased. Лучше не давать ему этот шаг.\n // renew=true пропускает проверку — host явно делает 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 setGate({ kind: 'purchase_success', restored: true });\n return;\n }\n } catch {\n /* getUser упал — продолжаем обычный flow, юзер увидит тарифы */\n }\n }\n if (!pending) {\n // openAuth standalone: после signIn закрываем модалку, layout не показываем.\n // Restore-flow (origin='layout' или undefined): возвращаемся в layout.\n if (origin === 'standalone') {\n onClose();\n } else {\n setGate({ kind: 'layout' });\n }\n return;\n }\n await runCheckout(pending.priceId);\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 // Пробрасываем как есть — блок уже собрал { priceId, price }.\n onEvent('price_selected', payload);\n return;\n }\n if (action === 'restore') {\n // CurrentSession-блок: гость кликнул \"Restore purchases\". Открываем\n // gate с intent='restore' — заголовок и submit станут \"Restore Purchases\".\n // Без AuthClient'а ничего не делаем (managed-auth не подключён).\n // Анон-сессия не считается логином (см. CurrentSession-блок): она\n // существует только для api-gateway-токена, у юзера нет email и\n // ему нужен realsignin чтобы привязать прошлую покупку. Без этой\n // проверки кнопка Restore молча no-op'ила бы как только у юзера\n // появлялся анон-токен (что в extension'ах — почти всегда).\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-блок: открыть саппорт-форму. Видна и гостю, и залогиненному.\n // Из layout — Back возвращает к тарифам.\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 // Анон-сессия не покрывает preauth-требование: чекаут под анон-токеном\n // создаст подписку под аккаунтом без email, который юзер потом не\n // сможет восстановить. Анон считается «нет логина», нужен real signin.\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 трактуем как true (default до bootstrap'а — пейвол\n // должен быть закрываемым во время loading/error, иначе юзера запрёт). После\n // ready settings.allow_close=false запретит ESC/overlay/крестик.\n const allowClose =\n state.status === 'ready' ? state.data.settings.allow_close !== false : true;\n\n // Offer top-tab: только на основном layout-view (цены/фичи). На auth/support\n // экранах banner не имеет смысла — юзер уже за пределами «купить сейчас»\n // flow'а, urgency-таймер только отвлекает. Зеркало легаси PaywallModal,\n // где offer-banner был привязан к 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 // Заголовок не задаём — AuthGate сам решит по intent'у (restore →\n // \"Restore Purchases\", остальные → дефолтный \"Welcome back!\").\n allow_signup: true,\n allow_password_reset: true,\n // Не скрываем при наличии сессии — auto-resume useEffect отрабатывает быстрее,\n // чем хотим показывать \"Signed in as ...\" промежуточным экраном.\n hide_when_authenticated: false,\n providers: state.status === 'ready' ? state.data.settings.auth_providers : undefined\n };\n\n // Support-view имеет приоритет над bootstrap-state: standalone-открытие\n // (paywall.openSupport()) должно работать даже если bootstrap ещё грузится\n // или упал — сама форма от settings/prices не зависит. Из layout-режима\n // Back возвращает к тарифам, из standalone — закрывает модалку.\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 // В gate-view'ах AuthGate/SupportGate сами рисуют curved Back-кнопку в\n // правом верхнем углу. Modal'овский X-крестик там же — две кнопки накладывались\n // бы друг на друга. ESC/overlay-клик остаются рабочими (если allowClose=true).\n // Standalone openAuth() — AuthGate не рисует Back (модалка открыта только\n // ради signin'а, layout некуда возвращаться); тогда X-крестик нужен, иначе\n // юзеру некуда деться кроме 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 {purchased ? (\n <PurchaseSuccessView onContinue={onClose} />\n ) : gate.kind === 'purchase_success' ? (\n <PurchaseSuccessView restored={gate.restored} onContinue={onClose} />\n ) : supportView ? (\n supportView\n ) : state.status === 'loading' || state.status === 'idle' || gate.kind === 'verifying' ? (\n <LoadingView verifying={gate.kind === 'verifying'} />\n ) : state.status === 'error' ? (\n <ErrorView message={state.error.message} />\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()) — модалка открыта только ради\n // signin'а, Back-кнопка дублирует ESC/X. Скрываем. Для preauth/\n // restore-flow Back ведёт обратно в layout — оставляем.\n showBack={gate.origin !== 'standalone'}\n intent={gate.intent ?? (gate.origin === 'standalone' ? 'standalone' : 'preauth')}\n onBack={() => {\n if (gate.origin === 'standalone') onClose();\n else setGate({ kind: 'layout' });\n }}\n />\n ) : gate.kind === 'anon_gate' && client.auth ? (\n <AnonGate\n auth={client.auth}\n // standalone — pure anon-flow (paywall.openAnonGate()). Закрываем\n // модалку после signin'а: host подцепит свежую session через\n // onAuthChange/onUserChange. Для layout-варианта возвращаемся в\n // тарифы; pendingCheckout анону не выписываем (анон по дизайну\n // не покупает — он юзает api-gateway без email).\n onSuccess={() => {\n if (gate.origin === 'standalone') onClose();\n else setGate({ kind: 'layout' });\n }}\n onBack={\n gate.origin === 'standalone'\n ? undefined\n : () => setGate({ kind: 'layout' })\n }\n />\n ) : gate.kind === 'awaiting_payment' ? (\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 ) : gate.kind === 'popup_blocked' ? (\n <PopupBlockedView onReopen={() => reopenCheckout(gate.priceId, gate.url)} />\n ) : (\n <Renderer\n layout={state.data.layout!}\n bootstrap={state.data}\n onAction={handleAction}\n auth={client.auth}\n authSession={authSession}\n />\n )}\n </Modal>\n </I18nProvider>\n );\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 <div\n class=\"flex h-11 w-11 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=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\">\n <path d=\"M4 5h12v10H4z\" stroke=\"currentColor\" stroke-width=\"1.75\" stroke-linejoin=\"round\" />\n <path d=\"M7 9l3 3 4-5\" stroke=\"currentColor\" stroke-width=\"1.75\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n </svg>\n </div>\n <p class=\"text-sm font-semibold tracking-tight text-gray-900\">\n {t('payment.popup_blocked_title', 'Allow popups to continue')}\n </p>\n <p class=\"max-w-[18rem] text-xs 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-1 rounded-xl px-4 py-2 text-xs 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('payment.open_checkout_button', 'Open checkout')}\n </button>\n </div>\n );\n}\n\n// Экран ожидания после window.open(checkoutUrl). UserWatcher в PaywallUI уже\n// poll'ит user-state раз в 5s (visible вкладка) — этот экран только UI-обёртка.\n//\n// «I've paid» — для нетерпеливых: форсим getUser({force:true}), чтобы cache\n// обновился сразу, и постим внутрь окна 'paywall_purchase' message — этого\n// ждёт UserWatcher.handleMessage и сразу же тригерит свой check(). Если\n// подписка ещё не активна (webhook не дошёл), показываем inline-таймаут на 5s.\n//\n// «Open checkout again» — fallback для случая «window.open отдал handle, но таб\n// заблокирован» (агрессивные мобильные блокеры). Дёргает existing URL без\n// похода в createCheckout, не сбивая awaiting_payment state.\n//\n// «Tab closed? Try again» — крайний случай: URL у Stripe/Paddle/etc. может\n// expire'нуться, поэтому пересоздаём checkout. Менее prominent кнопка.\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 // Будит UserWatcher — он сразу же сделает check(), увидит fresh active\n // user из cache и эмитит purchase_completed (PaywallUI переведёт в\n // PurchaseSuccessView). Не эмитим purchase_completed напрямую отсюда —\n // single source of truth остаётся в watcher.onActive.\n if (typeof window !== 'undefined') {\n window.postMessage({ type: 'paywall_purchase' }, '*');\n }\n return;\n }\n // Webhook ещё не дошёл — показываем подсказку и через 5s сворачиваем,\n // чтобы юзер мог нажать ещё раз. setTimeout cancel'нется на 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\">\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 <div class=\"relative flex h-12 w-12 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-7 w-7 animate-spin rounded-full border-[2.5px] border-gray-200 border-t-[var(--pw-accent)]\" />\n </div>\n <p class=\"text-sm font-semibold tracking-tight text-gray-900\">\n {t('payment.awaiting_title', 'Complete payment in the new tab')}\n </p>\n <p class=\"max-w-[20rem] text-xs 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-1 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 6px 14px -4px 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 — у юзера уже была активная подписка на момент попытки checkout\n * (или после signIn выяснилось, что подписка есть). Меняет heading на\n * «Subscription restored» — без этого юзер думает, что только что\n * оплатил. */\n restored?: boolean;\n}) {\n const { t } = useI18n();\n return (\n <div class=\"flex flex-col items-center gap-3 py-8 text-center\">\n <div\n class=\"flex h-14 w-14 items-center justify-center rounded-full ring-8\"\n style={{\n background: 'linear-gradient(135deg, #4ade80, #16a34a)',\n color: '#fff',\n // emerald ring with low alpha for a halo effect\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-lg font-semibold tracking-tight text-gray-900\">\n {restored\n ? t('modal.purchase_restored_title', 'Subscription restored')\n : t('modal.purchase_success_title', 'Payment received')}\n </p>\n <p class=\"text-sm leading-relaxed text-gray-500\">\n {restored\n ? t(\n 'modal.purchase_restored_subtitle',\n 'Welcome back — your subscription is already active.'\n )\n : t('modal.purchase_success_subtitle', 'Your subscription is now active.')}\n </p>\n <button\n type=\"button\"\n onClick={onContinue}\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('modal.continue', 'Continue')}\n </button>\n </div>\n );\n}\n","import type { BillingClient } from '../core/BillingClient';\nimport type { PaywallUser } from '../core/types';\n\n// Параметры по умолчанию подобраны под \"юзер платит ~60-90с после клика\n// Continue, иногда ходит за чашкой 5-10 минут\". См. обсуждение в TODO.md\n// (фаза \"Что это меняет в архитектуре\").\nexport interface UserWatcherOptions {\n client: BillingClient;\n /** Дёрнут, когда впервые увидели has_active_subscription === true. */\n onActive: (user: PaywallUser) => void;\n /** Полный таймаут наблюдения. По истечении — стоп без onActive. */\n onTimeout?: () => void;\n timeoutMs?: number;\n /** Интервал polling, когда вкладка видимая. */\n visibleIntervalMs?: number;\n /** Интервал polling, когда вкладка скрыта (браузер троттлит таймеры). */\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 после checkout_started.\n//\n// Источники сигнала \"проверить сейчас\":\n// 1. visibility change → visible (юзер вернулся в исходную вкладку).\n// 2. window focus.\n// 3. postMessage вида { type: 'paywall_purchase' } от success-страницы\n// (acceleration: success_url на нашем origin делает window.opener.postMessage).\n// 4. Регулярный таймер с visibility-aware расписанием.\n//\n// Стоп: либо has_active_subscription === true, либо таймаут.\n//\n// Runtime detection: см. shouldRunUserWatcher() — extension popup отбрасывается,\n// он не доживает до возврата с checkout. Background service worker отсекается\n// проверкой `typeof document` в 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 /* транзиентные ошибки — пропустим один тик, поллер дёрнет ещё */\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 // Перепланируем таймер с интервалом нового состояния.\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// Решаем, имеет ли смысл вообще запускать watcher в текущем рантайме.\n// false → код, который должен закрывать пейвол на оплату, полагается на\n// другой путь (bootstrap при следующем открытии для extension popup;\n// отсутствие document — для service worker).\nexport function shouldRunUserWatcher(): boolean {\n if (typeof document === 'undefined') return false;\n if (typeof window === 'undefined') return false;\n // Chrome extension popup живёт только пока открыт. window.open()\n // checkout-провайдера сразу ест фокус → popup закрывается → весь JS-context\n // (включая SDK и watcher) уничтожается. Polling тут бесполезен.\n if (typeof location !== 'undefined' && location.protocol === 'chrome-extension:') {\n return false;\n }\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 type {\n Acquiring,\n Identity,\n PaywallBootstrap,\n PaywallError,\n PaywallPrice,\n PaywallUser,\n TrialConfig,\n TrialStatus,\n UserLanguageInfo,\n VisibilityStatus\n} from '../core/types';\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 = { open: false, view: null, error: null };\n\n// Контракт событий SDK. Клиент подписывается через paywall.on(event, handler).\n// Каждый event строго типизирован — IDE даёт автокомплит на payload.\nexport interface PaywallEventPayloads {\n /** Модалка открыта (запрос на открытие — данные могут ещё грузиться). */\n open: void;\n /** Модалка закрыта. */\n close: void;\n /** Bootstrap загружен, модалка показывает контент. Подходит для impression-метрик. */\n ready: PaywallBootstrap;\n /** Любая ошибка SDK (bootstrap, checkout). */\n error: PaywallError;\n /** Юзер выбрал тариф (клик по плану), ещё не инициировал checkout. */\n price_selected: { priceId: string; price: PaywallPrice };\n /** Checkout URL получен с бэка и открыт в новой вкладке. `acquiring` —\n * имя платёжного процессора, на который ушёл checkout (для конверсии\n * по эквайрингам в host-аналитике). */\n checkout_started: { priceId: string; url: string; acquiring?: Acquiring };\n /** Юзер вернулся с успешной оплатой (через URL-маркеры или postMessage),\n * либо после signIn / попытки checkout-а выяснилось, что подписка уже\n * активна (`restored: true`). priceId = null когда payment-интент не\n * был привязан к конкретной цене (UserWatcher-tick, restore-flow). */\n purchase_completed: {\n priceId: string | null;\n sessionId: string | null;\n /** true — это не свежая оплата, а активная подписка, которую SDK обнаружил\n * и показал juзеру success/restored view. Hostу полезно различать (для\n * metrics — «restore» vs «new purchase»). */\n restored?: boolean;\n };\n /** Юзер вернулся с ошибкой/cancel от провайдера. */\n purchase_failed: { reason: string | null };\n /** User-state изменился (bootstrap snapshot, getUser refresh, watcher tick).\n * Дёргается также сразу с last-known user после первой подписки. */\n userChange: PaywallUser;\n /** Auth-session изменилась. Payload содержит `event` (см. AuthChangeEvent —\n * INITIAL_SESSION / SIGNED_IN / SIGNED_OUT / TOKEN_REFRESHED / USER_UPDATED /\n * PASSWORD_RECOVERY) и `session` (null = разлогинен).\n *\n * Гарантированный контракт: первый callback каждому subscriber'у — всегда\n * INITIAL_SESSION с восстановленной из storage сессией (или null если нет).\n * Дальше — реальные переходы. Listener'у с побочными эффектами вроде\n * force-refetch balances ловить SIGNED_IN, а не любой truthy session,\n * иначе reload страницы будет триггерить лишний запрос. */\n authChange: { event: AuthChangeEvent; session: AuthSession | null };\n /** Триал заблокировал показ модалки. payload содержит свежий статус (после\n * recordBlock). Для `mode: 'time'` — startedAt/expiresAt/remainingMs;\n * для `mode: 'opens'` — remainingActions/totalActions. Хост может\n * использовать payload для показа собственного UI («осталось 3 показа»). */\n trial_blocked: TrialStatus;\n /** Триал истёк, паывол показывается впервые после истечения. Эмитится\n * раз за жизнь PaywallUI-инстанса (не персистится между перезагрузками\n * страницы — на каждом page-load событие может стрельнуть один раз). */\n trial_expired: void;\n /** Targeting не сошёлся — паывол не открывается. payload содержит\n * server-computed snapshot из bootstrap (visible=false + reason + country +\n * tier). Хост может показать собственный fallback («сервис недоступен в\n * вашей стране») или просто залогировать impression для аналитики. */\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// Вспомогательный тип: `void` payload эмитится без аргумента (`emit('open')`),\n// непустой — с аргументом (`emit('ready', bootstrap)`).\ntype EmitArgs<E extends PaywallEvent> = PaywallEventPayloads[E] extends void\n ? []\n : [PaywallEventPayloads[E]];\n\nexport interface AnalyticsOptions {\n enabled?: boolean;\n /** Полный URL до /events. По умолчанию — `${apiOrigin}/api/v1/paywall/${id}/events`. */\n endpoint?: string;\n flushIntervalMs?: number;\n maxBufferSize?: number;\n /** Тестовый override fetch'а (jsdom/Vitest). */\n fetch?: typeof fetch;\n /** Тестовый override sendBeacon'а. */\n sendBeacon?: (url: string, data: BodyInit) => boolean;\n}\n\n/**\n * Managed-auth конфиг. Передай `auth: true` — PaywallUI создаёт `AuthClient`\n * сам (с тем же `paywallId/apiOrigin/storage`, как BillingClient). Передай\n * объект — те же дефолты + override опций. Передай готовый `AuthClient` —\n * PaywallUI просто прокинет его в BillingClient (полезно, если хост хочет\n * иметь общий AuthClient на несколько пейволов / делать manual signIn/signOut\n * из своего UI до открытия модалки).\n *\n * Без `auth` опции SDK работает в hybrid-режиме: identity передаётся снаружи\n * через `opts.identity` или `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 /** Подключить managed-auth слой. См. {@link AuthOption}. */\n auth?: AuthOption;\n /**\n * Автоматически парсить URL при создании PaywallUI, чтобы поймать возврат\n * с checkout-провайдера (?paywall_status=paid|failed|cancelled). Дефолт: true.\n * Эмитит purchase_completed / purchase_failed через microtask — подпишись синхронно.\n */\n autoDetectReturn?: boolean;\n /**\n * Режим shadow DOM. По умолчанию `closed` — полная изоляция от хоста.\n * Для e2e тестов (Playwright) и live-preview в админке передавать `open`.\n */\n shadowMode?: 'open' | 'closed';\n /**\n * Аналитика SDK 3.0. По умолчанию включена. Передай `false` для полного\n * отключения (ничего не шлётся на бэк). Принимает объект с настройками\n * batch'а или endpoint-override.\n */\n analytics?: boolean | AnalyticsOptions;\n /**\n * Когда bootstrap не в кеше — модалку рендерить **сразу** со спиннером и\n * прогонять gates (visibility/trial) после получения данных, или **ждать**\n * bootstrap и монтировать только если gates прошли. Дефолт `true` —\n * snappy open, кнопка «открыть» отзывается мгновенно.\n *\n * Trade-off: при `true` и блокирующем gate'е модалка моргнёт (открылась\n * → закрылась через ~200-500мс). На extension'ах и сайтах с включённым\n * targeting-fallback'ом это редкий путь, поэтому дефолт оптимизирован\n * под основной 99%-кейс. Передай `false`, если для вашего use-case'а\n * флеш на blocked-странах/устройствах хуже воспринимаемой латентности.\n */\n mountThenLoad?: boolean;\n /**\n * Inline-режим для live-preview редактора админки. Host позиционируется\n * `absolute inset:0` внутри родителя (вместо fixed-viewport'а), overlay\n * Modal'а тоже становится absolute, body-scroll не лочится. ОБЯЗАТЕЛЬНО\n * передавать `host` (HTMLElement) с positioned parent'ом — иначе absolute\n * уйдёт к ближайшему positioned ancestor'у или к html. По умолчанию false.\n *\n * @internal Admin-only: используется в редакторе пейволов monetize.software\n * для live-preview. Конечным интеграторам SDK включать не нужно — модалка\n * сольётся с host'овым layout'ом вместо fullscreen-overlay'я.\n */\n inline?: boolean;\n /**\n * Explicit-override языка для I18nProvider. Используется live-preview\n * редактором админки («Preview as user from <country>») — там browser-locale\n * всегда EN, а нужно показать как для юзера из выбранной страны. Принимает\n * BCP-47 base-tag из `BUNDLED_LOCALES` (ru/de/fr/…); EN, null, undefined —\n * fallback на обычную резолв-логику (navigator.language → locale_default).\n *\n * Live-обновление — через {@link PaywallUI.setLocale}.\n *\n * @internal Admin-only: для конечных интеграторов нет смысла форсить язык —\n * SDK сам подстраивается под browser-locale.\n */\n locale?: string | null;\n}\n\n/**\n * Результат `paywall.getAccess()` — отвечает на главный вопрос хоста: «нужно\n * ли блокировать фичу для этого юзера?». Без побочных эффектов: на trial-storage\n * `recordBlock` не вызывается (счётчики не двигаются), модалка не монтируется.\n *\n * Семантика `access`:\n * - `granted` — фичу НЕ блокировать. Один из сценариев:\n * - `has_subscription` — у юзера активная подписка/покупка;\n * - `visibility_blocked` — таргетинг (страна/девайс/visibility-флаг) не\n * сошёлся, юзер вне monetization-scope'а пейвола → монетизация неприменима;\n * - `trial_blocked` — пре-пейвольный триал ещё активен.\n * - `blocked` — фичу заблокировать и вызвать `paywall.open()`. Reason всегда\n * `no_subscription`.\n *\n * Discriminated union по `access`: type-narrowing на `result.access === 'blocked'`\n * сужает `reason` до `'no_subscription'`, на `'granted'` — до трёх granted-вариантов.\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\nexport interface OpenOptions {\n identity?: Identity;\n /** Принудительно открыть, минуя pre-paywall trial check. По умолчанию SDK\n * читает `bootstrap.settings.trial` и блокирует open(), пока триал активен.\n * Эскейп-хатч для случаев типа «host решил показать всё-таки» или дев-режим. */\n skipTrial?: boolean;\n /** Принудительно открыть, минуя targeting-gate. По умолчанию SDK читает\n * `bootstrap.settings.visibility` и эмитит `visibility_blocked` без\n * открытия модалки, если visible=false (страна/девайс/visibility-флаг\n * не сошлись). Эскейп-хатч для дев-отладки. */\n skipVisibility?: boolean;\n /** Renewal/upgrade flow. По умолчанию (false) SDK после bootstrap'а или\n * signIn проверяет `user.has_active_subscription` и переключается в\n * restored success-view, не показывая тарифы — open() для уже подписанного\n * юзера превращается в подтверждение «у вас уже есть подписка». С\n * `renew: true` все эти проверки пропускаются: тарифы показываются всегда,\n * и при checkout SDK передаёт `ignoreActivePurchase: true` на бэк, чтобы\n * /start-checkout не вернул 409. Использовать когда host-UI явно\n * показывает кнопку «Renew»/«Upgrade plan». */\n renew?: boolean;\n}\n\n// Маркеры в URL, по которым SDK определяет результат checkout.\n// Контракт общий с бэком — online добавляет их в success/cancel URLs.\nconst URL_MARKERS = {\n status: 'paywall_status',\n priceId: 'paywall_price_id',\n sessionId: 'paywall_session_id'\n} as const;\n\nexport class PaywallUI {\n readonly billing: BillingClient;\n /** AuthClient (managed-auth) или undefined в hybrid-режиме. Доступен публично:\n * host может вызывать `paywall.auth?.signOut()`, читать `getCachedSession()`,\n * подписываться на `onAuthChange` напрямую. */\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 /** Lazy-инстанс TrialStore. Резолвится при первом open(), когда уже знаем\n * `bootstrap.settings.trial`. null — триал отключён в конфиге пейвола. */\n private trialStore: TrialStore | null = null;\n /** Конфиг, под который создан текущий trialStore — пересобираем, если он\n * поменялся между bootstrap-фетчами (например, владелец переключил режим\n * в админке между сессиями SDK). */\n private trialStoreConfig: TrialConfig | null = null;\n /** In-memory snapshot последнего check() — для синхронного getTrialStatus(). */\n private lastTrialStatus: TrialStatus | null = null;\n /** Флаг dedupe для `trial_expired` события в рамках жизни инстанса. */\n private trialExpiredFired = false;\n /** In-memory snapshot последнего bootstrap'а — для синхронного getVisibility(). */\n private lastVisibility: VisibilityStatus | null = null;\n /** Поведение open() при холодном bootstrap'е. См. PaywallUIOptions.mountThenLoad. */\n private mountThenLoad: boolean;\n /** Inline-режим (live-preview редактора). См. PaywallUIOptions.inline. */\n private inline: boolean;\n /** Force-locale для I18nProvider. См. PaywallUIOptions.locale. */\n private forceLocale: string | null;\n /** Текущий snapshot UI state-machine. Обновляется PaywallRoot'ом через\n * `onState` prop; при close сбрасывается обратно в CLOSED_STATE. */\n private currentState: PaywallStateSnapshot = CLOSED_STATE;\n private stateListeners = new Set<PaywallStateListener>();\n\n constructor(opts: PaywallUIOptions) {\n // Резолвим AuthClient: готовый инстанс / managed-конфиг (true|object) /\n // undefined. ownsAuth=true → сами создавали и должны прибрать в destroy().\n const { auth, ownsAuth } = resolveAuth(opts);\n this.auth = auth;\n this.ownsAuth = ownsAuth;\n\n // Если auth есть — прокидываем в BillingClient (он сам подключит Bearer\n // и auto-sync identity через onAuthChange). client из opts побеждает —\n // считаем, что хост уже сконфигурировал его сам, не лезем перетирать 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 // Форвардим user-change события из BillingClient на public-API PaywallUI.\n // Один источник правды (BillingClient cache) — два consumer'а (host через\n // paywall.onUserChange и сам watcher через billing.onUserChange).\n this.userUnsub = this.billing.onUserChange((user) => {\n this.emit('userChange', user);\n });\n\n if (this.auth) {\n this.authUnsub = this.auth.onAuthChange((event, session) => {\n this.emit('authChange', { event, session });\n });\n }\n\n this.initTracker(opts.analytics);\n\n if (opts.autoDetectReturn !== false && typeof window !== 'undefined') {\n // Microtask — клиент успевает подписаться синхронно после конструктора,\n // до того как событие действительно стрельнёт.\n queueMicrotask(() => this.checkReturn());\n }\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 const endpoint =\n cfg.endpoint ?? `${this.billing.apiOrigin}/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 flushIntervalMs: cfg.flushIntervalMs,\n maxBufferSize: cfg.maxBufferSize,\n fetch: cfg.fetch,\n sendBeacon: cfg.sendBeacon\n });\n\n // Биндим внутренние SDK-события на аналитический транспорт. Один эмиттер,\n // один потребитель (трекер) — никто кроме трекера не должен трогать\n // эти имена событий за пределами PaywallUI.\n this.on('open', () => this.tracker?.track('paywall_opened'));\n this.on('ready', (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 })\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 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', () => this.tracker?.track('paywall_closed'));\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 пока не фаерим: authChange эмитится\n // и на гидрации сессии (UI поднимает кеш из storage), и на token refresh,\n // и при параллельных consumer'ах одного auth-state — даёт ложные signin'ы.\n // Реальные login-события нужно ловить через прямые вызовы\n // signInWithEmail/signUp/signInWithOAuth/signOut, а не через authChange.\n }\n\n /**\n * Отправить произвольное аналитическое событие. Имена из системного whitelist'а\n * (`app_opened`, `paywall_viewed`, ...) разрешены как есть. Кастомные —\n * с префиксом `host:` (например `host:user_clicked_upgrade`). Сервер\n * дропает события с неразрешёнными именами.\n *\n * Самый частый кейс — `track('app_opened')` от хоста сразу после загрузки\n * приложения, чтобы зафиксировать воронку до открытия пейвола.\n */\n track(name: string, props?: Record<string, unknown>): void {\n this.tracker?.track(name, props);\n }\n\n /**\n * Удобный шорткат вместо `paywall.on('userChange', cb)` — самый частый\n * паттерн в host-коде, поэтому отдельный named метод. Колбек получает\n * last-known user из кеша синхронно через microtask, если он есть.\n */\n onUserChange(handler: PaywallEventHandler<'userChange'>): () => void {\n return this.on('userChange', handler);\n }\n\n /**\n * Заменить cachedBootstrap живыми данными — для preview-режима в редакторе\n * админки. Если модалка открыта, PaywallRoot подписан на onBootstrapChange\n * и перерендерится мгновенно. До open() — затравка для bootstrap()-effect'а.\n *\n * См. {@link BillingClientOptions.preview} — обычно эту опцию ставят на\n * клиент, чтобы заодно отключить сетевой revalidate. setBootstrap технически\n * работает и в production-режиме, но конкуренция с revalidate'ом из сети\n * почти всегда нежелательна.\n */\n setBootstrap(partial: Partial<PaywallBootstrap>): void {\n this.billing.setBootstrap(partial);\n }\n\n /**\n * Сменить force-locale на лету — для live-preview редактора админки, когда\n * юзер переключает «Preview as user from <country>». Грузит соответствующий\n * static-чанк и форсит re-render через handle.update. См. PaywallUIOptions.locale.\n *\n * Передай `null`/`undefined`, чтобы вернуть автоматическую резолв-логику\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 есть, только если модалка открыта; иначе locale подхватится на\n // следующем mountAndShow() из сохранённого 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 * Прогревает bootstrap-кеш и balance-кеш заранее, без открытия модалки.\n * Полезно когда host знает, что юзер скоро откроет paywall (hover на CTA,\n * mount компонента) — первый `open()` рендерится мгновенно, без loading-flash.\n *\n * Не throw'ает: если сеть упала, тихо игнорирует (повторный open() сделает\n * fresh-bootstrap с error-state как обычно). `signal` для отмены — например,\n * если хост размонтирует компонент быстрее, чем bootstrap вернётся.\n *\n * Вызывать можно сколько угодно раз — последующие вызовы возвращают cached\n * Promise (BillingClient уже дедуплицирует).\n */\n async preload(opts: { signal?: AbortSignal } = {}): Promise<void> {\n try {\n await this.billing.bootstrap({ signal: opts.signal });\n // Балансы — best-effort: пейволы без `tokenization` отдают пустой\n // массив, и getBalances не делает сетевого запроса для unauth-юзера.\n if (this.billing.auth) {\n await this.billing.getBalances({ signal: opts.signal });\n }\n } catch {\n /* preload best-effort — open() сам покажет error-state */\n }\n }\n\n /**\n * Открывает модалку сразу с саппорт-формой (минуя layout с тарифами).\n * Полезно, когда host-приложение хочет дать юзеру кнопку «Help / Support»,\n * не связанную с пейволом-апгрейдом. Back/Done в саппорт-форме закрывают\n * модалку (не возвращают к тарифам), потому что юзер пришёл сюда напрямую.\n *\n * Из обычного `paywall.open()`-flow саппорт всё равно доступен через\n * Contact Support-ссылку в `current_session`-блоке (там Back возвращает\n * к layout).\n */\n openSupport(opts: OpenOptions = {}): void {\n this.openInternal('support', opts);\n }\n\n /**\n * Открывает модалку сразу с auth-gate (логин/регистрация), без layout с\n * тарифами. Сценарий: returning customer уже купил, ему просто нужно\n * залогиниться, чтобы SDK подцепил его purchases. После signIn модалка\n * закрывается; Back тоже закрывает (юзер пришёл только за логином).\n *\n * Без `auth` (managed-auth не подключён) метод — no-op: некому делать\n * signIn. Если юзер уже залогинен — модалка всё равно откроется и\n * закроется через auto-resume в auth_gate effect'е (мгновение).\n *\n * Триал не блокирует этот флоу — auth не connect'ится с trial-механикой.\n */\n openAuth(opts: OpenOptions = {}): void {\n if (!this.auth) return;\n this.openInternal('auth', { ...opts, skipTrial: true });\n }\n\n /**\n * Открывает модалку с anonymous-gate. AnonGate сразу зовёт\n * `auth.signInAnonymously()`:\n * - если в storage есть `anonRefreshToken` — silent resume через\n * /auth/refresh, модалка схлопывается мгновенно (юзер видит лёгкий\n * спиннер ~100мс);\n * - иначе — fresh POST /auth/anonymous/signin без captcha (защита от\n * abuse держится на Supabase per-real-IP rate-limit + CF Bot Fight Mode).\n *\n * После успешного signIn'а модалка закрывается; host подхватывает свежую\n * session через `auth.onAuthChange` или `paywall.onUserChange`. Для UX\n * \"просто залогиниться чтобы api-gateway работал, без покупок\".\n *\n * Без managed-auth (`auth` не подключён) метод — no-op. Триал и\n * visibility-таргетинг этот flow обходит: анон-логин не должен зависеть\n * от страны юзера или trial-стейджа.\n */\n openAnonGate(opts: OpenOptions = {}): void {\n if (!this.auth) return;\n this.openInternal('anon', { ...opts, skipTrial: true, skipVisibility: true });\n }\n\n private openInternal(view: PaywallView, opts: OpenOptions): void {\n if (opts.identity) this.billing.setIdentity(opts.identity);\n // Сбрасываем флаг success-вью — повторное открытие должно стартовать\n // с обычного layout, а не с прошлого \"Payment received\".\n this.purchased = false;\n\n // support и auth-standalone флоу обходят оба гейта (триал и таргетинг):\n // юзер пришёл за саппортом или за логином к уже купленной подписке —\n // блокировать его по trial-stage'у или таргетингу неуместно. openAuth\n // дополнительно передаёт skipTrial:true для совместимости с прежней\n // семантикой; здесь skip-флаги нормализуем единообразно.\n const skipTrial = opts.skipTrial === true || view === 'support';\n const skipVisibility =\n opts.skipVisibility === true ||\n view === 'support' ||\n view === 'auth' ||\n view === 'anon';\n const renew = opts.renew === true;\n\n if (skipTrial && skipVisibility) {\n this.mountAndShow(view, { renew });\n return;\n }\n\n // Cache hit — sync путь, gates до mount как раньше. Никаких компромиссов:\n // когда bootstrap уже в памяти, мы знаем за один tick можно открывать\n // или нет, без флеша.\n const cached = this.billing.getCachedBootstrap();\n if (cached) {\n this.runOpenGates(view, cached, { skipTrial, skipVisibility, renew });\n return;\n }\n\n // Cold bootstrap. Два режима:\n //\n // mountThenLoad=true (default): монтируем модалку немедленно — юзер видит\n // спиннер, кнопка отзывается мгновенно. Bootstrap идёт параллельно.\n // Когда придёт — гоняем gates, и если блокирует, закрываем модалку с\n // эмиссией *_blocked. Цена — флеш «открылась → закрылась» в редком\n // случае visibility/trial-блока. Для extension'ов и сайтов с включённым\n // targeting'ом большинство open()'ов проходят, флеш — edge case.\n //\n // mountThenLoad=false (legacy): ждём bootstrap до mount'а. Гарантия\n // отсутствия флеша на блоке, но кнопка кажется «мёртвой» 200-500мс\n // на холодном кеше.\n if (this.mountThenLoad) {\n this.mountAndShow(view, { renew });\n this.billing\n .bootstrap()\n .then((b) => this.runDelayedGates(b, { skipTrial, skipVisibility }))\n .catch(() => {\n // Bootstrap упал — модалка уже открыта, PaywallRoot сам в error-state.\n });\n return;\n }\n\n this.billing\n .bootstrap()\n .then((b) => this.runOpenGates(view, b, { skipTrial, skipVisibility, renew }))\n .catch(() => {\n // Bootstrap упал — открываем без gates; PaywallRoot покажет error.\n this.mountAndShow(view, { renew });\n });\n }\n\n /** Применить gates ПОСЛЕ того, как модалка уже смонтирована (mount-then-load\n * путь). Если gate блокирует — close() + emit. Если юзер уже сам закрыл\n * модалку до резолва bootstrap'а — no-op (isOpen=false). */\n private runDelayedGates(\n bootstrap: PaywallBootstrap,\n flags: { skipTrial: boolean; skipVisibility: boolean }\n ): void {\n if (!this.isOpen) return;\n\n if (!flags.skipVisibility) {\n const v = bootstrap.settings.visibility;\n if (v) {\n this.lastVisibility = v;\n if (!v.visible) {\n this.close();\n this.emit('visibility_blocked', v);\n return;\n }\n }\n }\n\n if (flags.skipTrial) return;\n\n const trialCfg = bootstrap.settings.trial;\n if (!trialCfg) return;\n const store = this.ensureTrialStore(trialCfg);\n void store\n .check()\n .then(async (status) => {\n if (!this.isOpen) return;\n this.lastTrialStatus = status;\n if (status.mode === 'none') return;\n if (status.blocked) {\n const updated = await store.recordBlock();\n this.lastTrialStatus = updated;\n if (!this.isOpen) return;\n this.close();\n this.emit('trial_blocked', updated);\n return;\n }\n if (!this.trialExpiredFired) {\n this.trialExpiredFired = true;\n this.emit('trial_expired');\n }\n })\n .catch((e) => {\n if (typeof console !== 'undefined') console.warn('[paywall] trial check failed', e);\n });\n }\n\n // Порядок гейтов: visibility → trial. Country-mismatch ≠ trial-block, и\n // вести trial-стейт «осталось N показов» под юзером, который вообще не\n // должен увидеть пейвол по таргетингу — бессмысленно: при возврате в\n // правильную страну он окажется со «слипшимся» триал-счётчиком.\n private runOpenGates(\n view: PaywallView,\n bootstrap: PaywallBootstrap,\n flags: { skipTrial: boolean; skipVisibility: boolean; renew: boolean }\n ): void {\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 });\n return;\n }\n this.gateThroughTrial(view, bootstrap, flags.renew);\n }\n\n private gateThroughTrial(view: PaywallView, bootstrap: PaywallBootstrap, renew: boolean): void {\n const trialCfg = bootstrap.settings.trial;\n if (!trialCfg) {\n this.mountAndShow(view, { renew });\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 });\n return;\n }\n if (status.blocked) {\n // recordBlock делает запись (init firstOpen / inc skipTimes) и\n // возвращает обновлённый snapshot — его и эмитим, чтобы хост\n // получил актуальный счётчик.\n const updated = await store.recordBlock();\n this.lastTrialStatus = updated;\n this.emit('trial_blocked', updated);\n return;\n }\n // Триал в конфиге, но не блокирует → истёк. Эмитим один раз за\n // сессию, дальше открываем как обычно.\n if (!this.trialExpiredFired) {\n this.trialExpiredFired = true;\n this.emit('trial_expired');\n }\n this.mountAndShow(view, { renew });\n })\n .catch((e) => {\n // Storage недоступен (privacy mode, quota) — не блокируем юзера,\n // открываем модалку и не теряем продажу.\n if (typeof console !== 'undefined') console.warn('[paywall] trial check failed', e);\n this.mountAndShow(view, { renew });\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: если billing-клиент предоставляет свой factory (extension'овский\n // RemoteBillingClient — атомарный TrialStore через offscreen + navigator.locks),\n // используем его. Иначе — обычный path через 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(view: PaywallView, mountOpts: { renew?: boolean } = {}): void {\n const renew = mountOpts.renew === true;\n if (this.handle) {\n this.isOpen = true;\n this.handle.update({ open: true, initialView: view, purchased: false, renew });\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 purchased: false,\n renew,\n onClose: () => this.close(),\n onEvent: (event, payload) => {\n this.emit(event as PaywallEvent, payload as never);\n // Поднимаем watcher как только начался checkout — отсюда уже\n // полагаемся на server-confirmed flow, а не URL-маркеры.\n if (event === 'checkout_started') this.startUserWatcher();\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 * Sync-snapshot текущего состояния модалки. Подходит для `useSyncExternalStore`\n * в React (`useSyncExternalStore(paywall.onStateChange, paywall.getState)`)\n * и для одноразовых проверок («открыт ли пейвол сейчас?»).\n *\n * Snapshot стабилен — пока state не изменился, повторный getState() вернёт\n * `===`-равный объект (важно для useSyncExternalStore чтобы не ре-рендерить).\n */\n getState(): PaywallStateSnapshot {\n return this.currentState;\n }\n\n /**\n * Подписка на изменения state. Колбек вызывается при каждом реальном\n * изменении (closed → loading → ready → ...). По умолчанию initial snapshot\n * отдаётся через microtask после подписки; через `{immediate: 'sync'|'none'}`\n * можно сделать sync-доставку (для useSyncExternalStore — там она не нужна,\n * snapshot читается через getSnapshot отдельно) или вовсе пропустить\n * initial.\n *\n * Возвращает unsubscribe.\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-доступ к последнему известному статусу триала. null — `paywall.open()`\n * ещё не вызывался либо триал отключён в конфиге пейвола. Удобно для\n * собственного UI хоста («осталось 3 показа», «триал истечёт через 2ч»). */\n getTrialStatus(): TrialStatus | null {\n return this.lastTrialStatus;\n }\n\n /** Sync-доступ к последнему server-computed visibility-статусу. null —\n * bootstrap ещё не загружен или сервер не отдаёт `settings.visibility`\n * (например, старая версия online без targeting-патча). Хост может\n * использовать для собственного fallback'а: «сервис недоступен в вашей\n * стране». Обновляется на каждом open(), который проходит через gate. */\n getVisibility(): VisibilityStatus | null {\n return this.lastVisibility;\n }\n\n /**\n * Цены пейвола — шорткат над `bootstrap()`. Локали уже применены, кэш и\n * stale-while-revalidate идентичны `billing.bootstrap()`. Подходит для\n * pricing-страниц/карточек на сайте, где host хочет показать те же цены,\n * что и в модалке, не вытаскивая bootstrap руками.\n */\n getPrices(opts: { force?: boolean; signal?: AbortSignal } = {}): Promise<PaywallPrice[]> {\n return this.billing.getPrices(opts);\n }\n\n /** Sync-снимок цен. null — bootstrap ещё не загружали. */\n getCachedPrices(): PaywallPrice[] | null {\n return this.billing.getCachedPrices();\n }\n\n /** Снимок текущего «языка юзера» — proxy над `billing.getUserLanguage()`.\n * Используй, чтобы синхронизировать i18n host'а с тем, что фактически\n * показывает пейвол. См. подробности в `BillingClient.getUserLanguage`. */\n getUserLanguage(): UserLanguageInfo {\n return this.billing.getUserLanguage();\n }\n\n /**\n * Решает, нужно ли блокировать фичу для текущего юзера. Без побочных эффектов\n * (на trial-storage `recordBlock` не вызывается, модалка не монтируется).\n *\n * Порядок проверок (первый сработавший — финальный):\n * 1. `has_active_subscription` — самый сильный сигнал, перебивает остальные.\n * Юзер с подпиской получает доступ независимо от visibility/trial.\n * 2. `visibility` (страна/девайс/disabled-флаг) — юзер вне monetization-scope'а\n * пейвола, гейтить нельзя.\n * 3. `trial` — пре-пейвольный бесплатный период активен.\n * 4. Иначе — `blocked`, host лочит фичу и зовёт `paywall.open()`.\n *\n * Bootstrap кешируется в BillingClient — `getAccess()` можно дёргать на\n * каждый рендер host-компонента, /bootstrap не дублируется. При упавшей сети\n * fallback на persistent-cached user из storage: юзер с прошлой подпиской\n * получает `granted` офлайн, иначе `blocked` (host покажет пейвол с\n * error-state, юзер сможет ретрайнуть). Side-эффект: обновляются\n * `lastVisibility` / `lastTrialStatus`, чтобы синхронные геттеры\n * `getVisibility()` / `getTrialStatus()` видели свежие данные после первого\n * `getAccess()`, а не только после первого `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 // Сеть упала. Fallback на persistent-cached user (TTL 30 мин в storage).\n // Юзер с прошлой подпиской → granted (офлайн-friendly), иначе → blocked\n // (open() покажет пейвол с error-state, юзер ретрайнет).\n const cached = this.billing.getCachedUser();\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 const 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 /** Сбросить состояние триала в storage. Полезно для дев-режима / админ-кнопки\n * «прогнать сценарий заново». В проде хост обычно не дёргает. */\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 // Запускает polling user-state до has_active_subscription=true либо до\n // таймаута. Идемпотентен: повторный вызов на уже работающем watcher'е —\n // no-op (юзер мог нажать Continue повторно после возврата).\n //\n // В extension popup runtime — no-op (popup не доживёт). Там полагаемся на\n // bootstrap при следующем открытии.\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) => {\n this.watcher = null;\n // Серверная правда — эмитим финальный purchase_completed\n // (server-confirmed), чтобы host получил согласованный сигнал\n // независимо от того, был ли URL-маркер. userChange эмитит сам\n // billing-listener.\n this.emit('purchase_completed', { priceId: null, sessionId: null });\n // success_redirect_url из settings — host явно попросил отправить\n // юзера в свой apps-flow после оплаты. Редирект имеет приоритет\n // над PurchaseSuccessView: рисовать success ради 200мс перед\n // переходом — мерцание. Берём snapshot из cached bootstrap\n // (он гарантированно загружен — иначе watcher не запустился бы).\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 заблокирована — fallback на success-view */\n }\n }\n // Если пейвол открыт — переключаем во вью «Payment received» с\n // кнопкой Continue. Молчаливое закрытие сбивало юзера с толку:\n // окно просто исчезало, без подтверждения, что оплата прошла.\n // Если пейвол закрыт — событие уже эмитнуто, host решит сам.\n if (this.isOpen && this.handle) {\n this.purchased = true;\n this.handle.update({ purchased: true });\n }\n void user; // shape доступен через paywall.billing.getCachedUser()\n },\n onTimeout: () => {\n this.watcher = null;\n }\n });\n this.watcher.start();\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 эмитит onState с open=false при handle.update, но из-за\n // microtask'ов хост может прочитать getState() до того, как PaywallRoot\n // useEffect отстреляет. Применяем закрытое состояние сразу.\n this.applyState(CLOSED_STATE);\n this.emit('close');\n }\n\n /**\n * Сканирует текущий URL на маркеры возврата с checkout и эмитит\n * purchase_completed / purchase_failed. Маркеры удаляются из URL\n * через history.replaceState. Ищет и в hash, и в search (hash приоритетнее —\n * защита от клиентских SPA-роутеров, перехватывающих 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: если страница загружена в новой вкладке от исходного\n // приложения (typical Stripe success_url flow), шлём opener'у postMessage.\n // Watcher в исходной вкладке среагирует мгновенно, не дожидаясь focus\n // event. Если opener'а нет (юзер закрыл/не было) — fallback на 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 // Если AuthClient был передан хостом — его жизненный цикл не наш,\n // ничего не дёргаем. Если создавали мы — отписываемся через BillingClient\n // (он сам держит listener на onAuthChange) и оставляем session в storage,\n // чтобы следующее открытие подхватило её через hydrate.\n if (this.ownsAuth && this.auth) {\n // Если AuthClient создавали мы — destroy сами, чтобы snapshot listener\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 ИЛИ структурный совместимец (RemoteAuthClient из\n // @monetize/sdk-extension). Проверяем по public-методам, которые\n // PaywallUI использует — если все на месте, доверяем. Это позволяет host'у\n // подставить proxy-реализацию (offscreen-architecture) без изменений в\n // PaywallUI. instanceof не подходит — runtime в content-script'е и в\n // sdk-extension'е разные, классы не nominally равны.\n if (opts.auth instanceof AuthClient || isAuthClientLike(opts.auth)) {\n return { auth: opts.auth as AuthClient, ownsAuth: false };\n }\n // true | partial-options → создаём свой AuthClient. apiOrigin/storage/fetch\n // подхватываем из общих опций PaywallUI, чтобы конфиг был \"одно поле — вся\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// Проверяет «AuthClient-подобность» переданного объекта по public-методам,\n// которые PaywallUI трогает (`onAuthChange`, `getCachedSession`, `signOut`).\n// Partial<AuthClientOptions> этих методов не имеет — пересечения с этим\n// объединением нет, ложноположительных не будет.\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 a.open === b.open && a.view === b.view && a.error === b.error;\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// Контракт сообщения должен совпадать с UserWatcher.handleMessage:\n// `{ type: 'paywall_purchase' }`. opener — исходная вкладка хоста, на ней живёт\n// PaywallUI с активным watcher'ом, ждущим этого сигнала.\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 /* opener из другого origin или закрыт — watcher через 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 — TrialStore-совместимый proxy. check / recordBlock /\n// reset идут через transport в offscreen, где реальный TrialStore работает\n// под navigator.locks — два таба не могут одновременно read-modify-write\n// один и тот же counter, 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 — структурный совместимец BillingClient, который\n// проксирует все методы в offscreen через TransportClient. Public API\n// идентичен (host пишет тот же код, что для @monetize.software/sdk), реализация\n// другая. Sync-getCached* остаются sync — ходят в локальный mirror, который\n// обновляется (a) ответами на async-методы и (b) broadcast-событиями\n// userChange/balancesChange.\n\nimport type {\n Balance,\n CheckoutResult,\n Identity,\n PaywallBootstrap,\n PaywallPrice,\n PaywallPurchaseDetailed,\n PaywallUser,\n 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 { 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 // Локальные mirror'ы. Источник правды — offscreen; mirror нужен только\n // чтобы getCached* оставались sync. Updated после каждого async-ответа +\n // на каждом broadcast-событии.\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 через transport: get/set/remove идут в offscreen'овский\n * StorageAdapter (single source of truth для всех вкладок). Trial-state\n * PaywallUI пишет сюда — все табы видят один и тот же counter, не\n * drift'ит между вкладками.\n *\n * Race-окно read-modify-write всё ещё существует (две вкладки одновременно\n * читают N → пишут N-1, drift 1). Для exact atomicity нужен Phase 9:\n * TrialStore целиком переехать в offscreen и делать recordBlock как\n * single-handler с одной atomic-операцией. Это редкий edge case\n * (одновременное открытие пейвола в нескольких вкладках в рамках мс). */\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 // watch не реализуем — для cross-context уведомлений consumer'ы (AuthClient,\n // TrialStore) подписываются на broadcast-events напрямую через transport.\n // Если когда-то понадобится — добавим 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 /** Подписка на bootstrap-state. Структурно совместима с\n * `BillingClient.onBootstrapChange` — те же микротаск-семантики для initial\n * snapshot. В extension-режиме offscreen пока не broadcast'ит bootstrapChange,\n * поэтому listener срабатывает только на self-инициированные `bootstrap()`\n * внутри этого RemoteBillingClient'а (popup перезапрашивает bootstrap → mirror\n * обновляется → listener вызывается). Cross-surface revalidate (другая вкладка\n * обновила bootstrap) не доезжает до popup'а — для этого нужен отдельный\n * bootstrapChange-broadcast в 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 /** Шорткат над `bootstrap()` — возвращает цены пейвола (locale-оверрайды\n * уже применены в offscreen'е). Те же кэш-семантики, что у `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-снимок цен из локального mirror'а bootstrap'а. null = ещё не грузили. */\n getCachedPrices(): PaywallPrice[] | null {\n return this.cachedBootstrap?.prices ?? 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 /** Подписка на user-state. Mirror'имся на broadcast'ы offscreen'а; initial\n * snapshot отдаётся через microtask из локального cache (если есть) —\n * ровно как в BillingClient.onUserChange. Возвращает функцию отписки. */\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 список покупок юзера (с ценой, валютой, interval, discount,\n * cancel-метаданными). Через offscreen — там настоящий BillingClient\n * ходит на `/api/v1/paywall/[id]/user` с Bearer'ом. Полезно для\n * customer-portal UI: cards + Cancel/Renew кнопки. */\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 /** Саппорт-тикет через offscreen'овский BillingClient. File-объекты\n * переживают chrome.runtime structured-clone (port forward'ит as-is) —\n * Bearer-токен/email-substitution делает offscreen, как в обычном\n * 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 return this.transport.request('billing.createSupportTicket', payload);\n }\n\n /** Отменить подписку через бэк. По умолчанию cancel в конце текущего\n * периода (юзер сохраняет access до renewal date'ы). reason обязательна\n * (валидируется бэком) — собирается через select причин в 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 // === Storage ===\n\n /** PaywallUI просит storage у billing-клиента для TrialStore и других\n * consumer'ов. Возвращает proxy: get/set/remove идут через transport\n * в offscreen'овский storage = single source of truth для всех вкладок. */\n getStorage(): StorageAdapter {\n return this.remoteStorageAdapter;\n }\n\n /** Factory-метод для PaywallUI: вместо локального createTrialStore'а на\n * storage-proxy, возвращаем RemoteTrialStore — он шлёт каждую операцию\n * одним атомарным RPC в offscreen, где navigator.locks сериализуют\n * read-modify-write. PaywallUI duck-types этот метод и предпочитает его\n * локальной фабрике, если он есть. */\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 /** Подгрузить identity с offscreen'а. Используется при первом подключении\n * content-script'а — если другая вкладка уже залогинила юзера, текущая\n * тут же подхватит identity без ожидания 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 /** Обновить mirror user'а и эмитнуть listener'ам если он реально изменился.\n * Используется и для self-инициированных RPC (bootstrap/getUser), и для\n * broadcast'ов от offscreen — чтобы host'овский onUserChange handler\n * получил signal независимо от того, кто триггернул обновление. */\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 — структурный совместимец AuthClient. Public методы\n// идентичны, под капотом — async-прокси через TransportClient в offscreen,\n// где живёт реальная сессия и storage.\n//\n// Sync-getCachedSession поддерживается через локальный mirror, обновляемый\n// (a) на каждом ответе async-метода, (b) на broadcast'е authChange.\n//\n// OAuth (signInWithOAuth) пока бросает not-implemented — требует publicного\n// split-API в @sdk/core/auth (Phase 4.5). Для email/password/refresh/signOut\n// и прочей сетевой части — всё работает.\n\nimport type {\n AuthChangeEvent,\n AuthSession,\n AuthUser,\n LastLogin,\n OAuthProvider,\n OtpVerifyType,\n SignUpResult\n} from '@sdk/core/auth';\nimport { waitForOAuthCode } 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 с offscreen'а — поднимаем restored session в local mirror\n // ДО первого `getCachedSession()`. listeners получат восстановленную\n // session через свой собственный INITIAL_SESSION microtask из onAuthChange\n // (см. ниже) — не дёргаем applySession, чтобы не превратить «восстановление\n // из storage» в выглядящий как-будто-signin event.\n this.hydrated = this.transport\n .request('auth.getCachedSession', undefined)\n .then((session) => {\n // Конкурентность: если за время request'а кто-то уже выставил session\n // (broadcast SIGNED_IN или локальный signIn-метод), не перетираем —\n // hydrate-снимок stale относительно того, что уже в local mirror.\n if (this.session === null && session !== null) {\n this.session = session;\n }\n })\n .catch(() => {\n /* offscreen не готов или транспорт упал — getCachedSession отдаст null */\n });\n }\n\n /** Promise, который резолвится после первичной синхронизации session с\n * offscreen'а. Аналог 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 после hydrate'а — match @sdk/core AuthClient.\n // Контракт: первый callback = INITIAL_SESSION с restored snapshot'ом\n // (или null), последующие = реальные переходы через 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 // Локальный mirror-update + emit. Broadcast от offscreen'а тоже прилетит\n // с тем же event'ом — `sameSession` guard в applySession отсечёт второй\n // emit, не дёргая listener'ов дважды.\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 // Broadcast authChange придёт от offscreen'а с session=null, applySession\n // там уже отработает. Тут ничего не делаем, чтобы не дёрнуть listener'ы\n // дважды.\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 — читается из offscreen-storage. AuthPanel\n * использует для \"Last used\"-бейджа и pre-fill'а email. Storage paywall-\n * scoped, и offscreen — единый источник правды для всех вкладок/popup'ов. */\n async getLastLogin(): Promise<LastLogin | null> {\n return this.transport.request('auth.getLastLogin', undefined);\n }\n\n // === Anonymous sign-in ===\n\n /** Анонимный sign-in (Supabase user без email). Логика (idempotent-check +\n * resume через сохранённый refresh_token + fresh signin) живёт в\n * offscreen-AuthClient'е — content только проксирует. captchaToken и\n * forceCaptcha — pass-through для forward-compat / switch-account flow. */\n async signInAnonymously(input: {\n captchaToken?: string;\n userMeta?: Record<string, string>;\n forceCaptcha?: boolean;\n } = {}): Promise<AuthSession> {\n const session = await this.transport.request('auth.signInAnonymously', {\n captchaToken: input.captchaToken,\n userMeta: input.userMeta,\n forceCaptcha: input.forceCaptcha\n });\n this.applySession('SIGNED_IN', session);\n return session;\n }\n\n /** Текущий access token (lazy-refreshable в offscreen'е). content/popup\n * использует для Bearer'а в внешние fetch'и — ApiGatewayClient в\n * content-script'е, прямые запросы из demo-UI. null если разлогинен или\n * offscreen'овский AuthClient не смог рефрешнуть. */\n async getAccessToken(): Promise<string | null> {\n return this.transport.request('auth.getAccessToken', undefined);\n }\n\n // === OAuth (web-flow через split-API) ===\n\n /** OAuth через web-вариант: window.open в content-script'е, provider\n * redirect, callback page постит code в opener. Под капотом split на\n * два request'а в offscreen — startOAuthFlow (дёргаем /init, получаем\n * authorize_url) → открываем popup → waitForOAuthCode → exchange.\n *\n * PKCE verifier живёт ТОЛЬКО в offscreen'е (внутри AuthClient'а), через\n * runtime-границу не идёт. Content получает только authorize_url и state.\n *\n * Popup-gesture: `window.open(authorize_url, ...)` идёт в том же synchronous\n * flow'е, что startOAuthFlow ответ; user-gesture сохраняется потому что\n * content-script unloaded не за этот tick (gesture сохраняется через все\n * microtask'и одного call stack'а). Если в каком-то браузере gesture\n * всё-таки теряется — host получит `popup_blocked` (тот же что в @monetize.software/sdk).\n */\n async signInWithOAuth(input: {\n provider: OAuthProvider;\n scopes?: string;\n userMeta?: Record<string, string>;\n onPopupOpened?: () => void;\n }): Promise<AuthSession> {\n if (typeof window === 'undefined') {\n throw new PaywallError('oauth_unavailable', 'window is required for OAuth');\n }\n\n // Открываем popup СИНХРОННО — user-gesture сохраняется только в том же\n // synchronous frame, что click-handler. Async `await` на transport.request\n // до window.open съедает gesture, и Chrome открывает popup с пустым URL'ом\n // / блокирует совсем.\n //\n // about:blank вместо data:text/html (которым раньше показывали inline-loader):\n // data:-URL'ы триггерят static-сканеры CWS и EDR'ы как подозрительные. Вместо\n // этого открываем about:blank (наследует origin opener'а) и инжектим loader-DOM\n // через document.createElement + textContent — ровно то же UX, без 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 try {\n // Async-часть: дёргаем offscreen за authorize_url и state. Popup пока\n // показывает 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 });\n\n // Перед navigate'ом меняем имя popup'а на формат, который ожидает\n // callback page (pw-oauth-<state>) — name переживает cross-origin\n // редиректы (Google → Supabase → наш callback). callback page\n // читает window.name → извлекает state → posts back.\n popup.name = `pw-oauth-${state}`;\n popup.location.replace(authorizeUrl);\n\n input.onPopupOpened?.();\n\n const code = await waitForOAuthCode(popup, state);\n const session = await this.transport.request('auth.oauthExchange', { state, code });\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 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 loader-UI в about:blank popup. Same-origin как opener — мы можем\n * трогать popup.document напрямую. Используем createElement + textContent\n * (не innerHTML / document.write) чтобы не триггерить XSS-сканеры даже на\n * hard-coded строках. CSS-классы с pw-oauth-* префиксом — без коллизий со\n * стилями родительской страницы (popup всё равно изолирован, но на всякий).\n *\n * Defensive try/catch: если в каком-то edge-кейсе popup оказался не\n * same-origin (некоторые расширения это перехватывают) или document не\n * доступен — тихо забиваем, popup покажет дефолтный about:blank на 200-500мс\n * до redirect'а на 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 — fire-and-forget proxy для аналитики. Все track()\n// call'ы из всех вкладок попадают в единственный EventTracker в offscreen'е,\n// который батчит и шлёт в /events. Победа — один батч на расширение,\n// один sendBeacon на unload, никаких дублирующихся `app_opened` событий.\n//\n// API специально минимальный — только track(name, props). Buffer / flush /\n// destroy логика живёт в offscreen'е, content её не контролирует.\n\nimport { TransportClient } from '../shared/transport-client';\n\nexport class RemoteEventTracker {\n constructor(private readonly transport: TransportClient) {}\n\n /** Отправить событие. Fire-and-forget — не возвращает Promise, не throw'ает.\n * Сетевые/транспортные ошибки логируются в console и не блокируют 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","// Client-side транспорт. Используется в content-script'е (поверх chrome.runtime\n// port'а к SW) и в любом другом surface'е, который ходит к offscreen через\n// тот же роутер (popup, extension page, side panel).\n//\n// Контракт:\n// - request<K>(kind, params, signal?) — запрос с типизированным результатом.\n// На disconnect канала pending request'ы reject'аются с reconnect-error;\n// next call воссоздаст канал через ChannelFactory и продолжит работу.\n// - on<K>(kind, handler) — подписка на broadcast от сервера. Переподписки\n// переживают reconnect автоматически — handler'ы хранятся локально, а\n// re-subscribe на сервере не требуется (сервер всегда broadcast'ит всем\n// подключённым каналам).\n//\n// Reconnect стратегия: lazy. Канал поднимается при первом request/on, мёртвый —\n// пересоздаётся в момент следующего запроса. Никаких exponential backoff'ов\n// в фоне — extension контекст это не любит (расход CPU + батарея).\n\nimport type {\n EventEnvelope,\n EventKind,\n EventPayload,\n RequestEnvelope,\n RequestKind,\n RequestParams,\n RequestResult,\n ResponseEnvelope,\n ResponseErr\n} from './protocol';\nimport { PROTOCOL_VERSION } from './protocol';\nimport { reconstructError } from './errors';\nimport type { ChannelFactory, MessageChannel } from './channel';\n\ninterface PendingRequest {\n resolve: (value: unknown) => void;\n reject: (reason: unknown) => void;\n abortListener?: () => void;\n signal?: AbortSignal;\n}\n\nexport class TransportClient {\n private channel: MessageChannel | null = null;\n private channelDisposers: Array<() => void> = [];\n private pending = new Map<string, PendingRequest>();\n private listeners = new Map<EventKind, Set<(payload: unknown) => void>>();\n private destroyed = false;\n private nextId = 0;\n /** Уникальный ID клиента — отправляется в handshake'е, server может логировать\n * для отладки connection-flap'а. */\n private readonly clientId = `c-${Math.random().toString(36).slice(2, 10)}`;\n\n constructor(private readonly factory: ChannelFactory) {}\n\n /** Гарантирует наличие живого канала. Lazy — поднимается при первом request.\n * Сразу после connect'а fire-and-forget шлёт handshake — на mismatch\n * логируем warning, но не блокируем дальнейшие запросы. */\n private ensureChannel(): MessageChannel {\n if (this.destroyed) throw new Error('TransportClient destroyed');\n if (this.channel) return this.channel;\n\n const channel = this.factory();\n this.channel = channel;\n\n const offMsg = channel.onMessage((env) => this.handleMessage(env));\n const offDisc = channel.onDisconnect(() => this.handleDisconnect());\n this.channelDisposers = [offMsg, offDisc];\n\n // Async, без await: основные запросы могут параллельно идти. На mismatch'е\n // ничего не ломаем — server может быть на другой минорной версии (например,\n // host обновил sdk-extension но не sdk).\n void this.request('handshake', {\n protocolVersion: PROTOCOL_VERSION,\n clientId: this.clientId\n })\n .then((res) => {\n if (res.protocolVersion !== PROTOCOL_VERSION) {\n console.warn(\n `[sdk-extension] protocol version mismatch: client=${PROTOCOL_VERSION}, ` +\n `offscreen=${res.protocolVersion}. Update host's @monetize.software/sdk-extension.`\n );\n }\n })\n .catch(() => {\n // Server без handshake-handler'а или умер — best-effort, не падаем.\n });\n\n return channel;\n }\n\n private handleMessage(envelope: unknown): void {\n if (!isEnvelope(envelope)) return;\n if (envelope.type === 'response') {\n const pending = this.pending.get(envelope.id);\n if (!pending) return;\n this.pending.delete(envelope.id);\n pending.signal?.removeEventListener('abort', pending.abortListener!);\n if (envelope.ok) {\n pending.resolve(envelope.result);\n } else {\n // Narrowing на discriminated union с generic'ом теряется в strict-режиме —\n // явный cast стабильнее, ok===false уже проверен.\n pending.reject(reconstructError((envelope as ResponseErr).error));\n }\n return;\n }\n if (envelope.type === 'event') {\n const set = this.listeners.get(envelope.kind);\n if (!set) return;\n // Snapshot, чтобы handler мог отписать сам себя без NaN-итерации.\n for (const handler of [...set]) {\n try {\n handler(envelope.payload);\n } catch (e) {\n console.error('[sdk-extension] event handler threw', e);\n }\n }\n }\n }\n\n private handleDisconnect(): void {\n for (const fn of this.channelDisposers) fn();\n this.channelDisposers = [];\n this.channel = null;\n // Reject все in-flight — они идут с reconnect-кодом, host может ретрайнуть.\n const pending = Array.from(this.pending.values());\n this.pending.clear();\n for (const p of pending) {\n p.signal?.removeEventListener('abort', p.abortListener!);\n p.reject(new TransportDisconnectedError());\n }\n }\n\n request<K extends RequestKind>(\n kind: K,\n params: RequestParams<K>,\n opts: { signal?: AbortSignal } = {}\n ): Promise<RequestResult<K>> {\n if (this.destroyed) {\n return Promise.reject(new Error('TransportClient destroyed'));\n }\n if (opts.signal?.aborted) {\n return Promise.reject(new DOMException('Aborted', 'AbortError'));\n }\n\n const channel = this.ensureChannel();\n const id = `r${++this.nextId}`;\n\n return new Promise<RequestResult<K>>((resolve, reject) => {\n const pending: PendingRequest = {\n resolve: resolve as (value: unknown) => void,\n reject,\n signal: opts.signal\n };\n\n if (opts.signal) {\n pending.abortListener = () => {\n if (this.pending.delete(id)) {\n reject(new DOMException('Aborted', 'AbortError'));\n // Послать cancel в offscreen чтобы там тоже abort'нуть underlying\n // fetch. Best-effort: канал мог отвалиться — тогда send бросит,\n // но pending уже удалён, юзер уже получил abort error.\n try {\n channel.send({ type: 'cancel', id });\n } catch {\n /* channel dead — server'у уже всё равно */\n }\n }\n };\n opts.signal.addEventListener('abort', pending.abortListener);\n }\n\n this.pending.set(id, pending);\n\n const envelope: RequestEnvelope<RequestParams<K>> = {\n type: 'request',\n id,\n kind,\n params\n };\n try {\n channel.send(envelope);\n } catch (e) {\n this.pending.delete(id);\n opts.signal?.removeEventListener('abort', pending.abortListener!);\n reject(e);\n }\n });\n }\n\n on<K extends EventKind>(\n kind: K,\n handler: (payload: EventPayload<K>) => void\n ): () => void {\n let set = this.listeners.get(kind);\n if (!set) {\n set = new Set();\n this.listeners.set(kind, set);\n }\n const wrapped = handler as (payload: unknown) => void;\n set.add(wrapped);\n\n // Lazy ensureChannel: подписка не требует немедленного канала, но первый\n // event может прилететь только если канал жив. Поднимаем заранее.\n this.ensureChannel();\n\n return () => {\n set!.delete(wrapped);\n };\n }\n\n destroy(): void {\n if (this.destroyed) return;\n this.destroyed = true;\n for (const fn of this.channelDisposers) fn();\n this.channelDisposers = [];\n this.listeners.clear();\n const pending = Array.from(this.pending.values());\n this.pending.clear();\n for (const p of pending) {\n p.signal?.removeEventListener('abort', p.abortListener!);\n p.reject(new Error('TransportClient destroyed'));\n }\n this.channel?.close();\n this.channel = null;\n }\n}\n\nexport class TransportDisconnectedError extends Error {\n readonly code = 'transport_disconnected';\n constructor() {\n super('Transport channel disconnected mid-request');\n this.name = 'TransportDisconnectedError';\n }\n}\n\nfunction isEnvelope(value: unknown): value is RequestEnvelope | ResponseEnvelope | EventEnvelope {\n if (typeof value !== 'object' || value === null) return false;\n const t = (value as { type?: unknown }).type;\n return t === 'request' || t === 'response' || t === 'event';\n}\n","// Content-side singleton TransportClient. Один на content-script, переиспользуется\n// всеми инстансами PaywallUI на одной странице (на странице обычно один, но\n// несколько технически возможны — например один пейвол в overlay'е, другой\n// в popup'е host-расширения, оба внутри content-script'а одной страницы).\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/** Тестовая инжекция — для unit-тестов RemoteBillingClient'а с фейковым\n * каналом (без chrome.runtime). В проде не используется. */\nexport function _setContentTransportForTests(client: TransportClient | null): void {\n cached = client;\n}\n","// Drop-in `PaywallUI` для extension'а. Public API идентичен `@monetize.software/sdk`'у —\n// host пишет тот же код, опции те же. Под капотом:\n// - billing — RemoteBillingClient (proxy в offscreen)\n// - auth — RemoteAuthClient (когда `auth: true`)\n// - tracker — RemoteEventTracker (события forward'ятся в offscreen-EventTracker)\n//\n// EventTracker создаётся ОДИН на расширение, в offscreen'е. PaywallUI здесь\n// внутренний tracker отключает (`analytics: false` в base-конструкторе) и\n// сам подписывается на public-события, проксируя их через RemoteEventTracker.\n// Дубликат биндингов из base PaywallUI.initTracker — но это меньшее зло, чем\n// два EventTracker'а на одного юзера.\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/** Опции extension'овского PaywallUI. Убраны:\n * - `client` — RemoteBillingClient создаётся автоматически\n * - `storage` — storage живёт в offscreen'е, content его не видит\n * - `apiKey` — server-SDK key, не имеет смысла в content-script'е\n * - `fetch` — все сетевые запросы идут через offscreen\n *\n * `auth: true` подключит RemoteAuthClient. Передавать готовый AuthClient\n * из @monetize.software/sdk сюда не имеет смысла (мы хотим именно offscreen'овский). */\nexport interface ExtensionPaywallUIOptions\n extends Omit<PaywallUIOptions, 'client' | 'storage' | 'apiKey' | 'fetch'> {}\n\nexport class PaywallUI extends BasePaywallUI {\n /** RemoteEventTracker (proxy в offscreen-EventTracker). Не путать с\n * base-классовым `tracker` (там null — мы отключили внутренний). */\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: если host попросил — конструируем RemoteAuthClient. Готовый\n // AuthClient из @monetize.software/sdk сюда не имеет смысла прокидывать (всё\n // равно нужен offscreen-instance). Поэтому accept только `true` или\n // ничего; явный AuthClient instance логирует warning и игнорится.\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 // Прокидываем auth внутрь billing-клиента: PaywallRoot читает\n // `client.auth` для restore / preauth-flow / signin-detection. Настоящий\n // BillingClient выставляет это поле в конструкторе — у Remote-варианта\n // делаем явное присваивание перед super(), чтобы PaywallRoot увидел.\n if (auth) {\n (billing as { auth?: typeof auth }).auth = auth;\n }\n\n super({\n ...opts,\n // Cast'ы безопасны: PaywallUI'ев resolveAuth duck-type'ит auth (см.\n // sdk/src/ui/PaywallUI.ts isAuthClientLike), а billing-параметр идёт\n // через `opts.client ?? new BillingClient(...)` — RemoteBillingClient\n // там используется как есть, методы все сходятся.\n client: billing as unknown as BillingClient,\n auth: auth as unknown as AuthClient | undefined,\n // Внутренний EventTracker отключаем — единственный tracker живёт в\n // offscreen'е. Манчиально подписываемся ниже.\n analytics: false\n });\n\n if (opts.analytics !== false) {\n this.remoteTracker = new RemoteEventTracker(transport);\n this.bindAnalytics();\n }\n }\n\n /** Зеркало sdk/PaywallUI.initTracker'овских биндингов, но с RemoteEventTracker.\n * Когда @monetize.software/sdk экспоузнет публичный hook для inject'а tracker'а,\n * этот метод заменится на одну строку. */\n private bindAnalytics(): void {\n const t = this.remoteTracker;\n if (!t) return;\n\n this.trackerUnsubs.push(\n this.on('open', () => t.track('paywall_opened')),\n this.on('ready', (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 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 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', () => t.track('paywall_closed')),\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 // auth_signin_success / auth_signout пока не фаерим: authChange эмитится\n // и на гидрации сессии (popup поднимает кеш из offscreen), и на token\n // refresh, и при параллельном content-script + popup — даёт ложные\n // signin'ы. Реальные login-события нужно ловить через прямые вызовы\n // signInWithEmail/signUp/signInWithOAuth/signOut, а не через authChange.\n }\n\n /** Прокси через RemoteEventTracker. Hosts могут вызывать paywall.track\n * для произвольных аналитических событий — летит в единственный\n * offscreen-tracker наряду с auto-emit'ами PaywallUI. */\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 super.destroy();\n }\n}\n"],"names":["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","hasOwnerTranslations","loadLocale","cached","pending","promise","__variableDynamicImportRuntimeHelper","mod","dict","err","empty","I18nProvider","forceLocale","children","locale","setLocale","useState","setDict","useEffect","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","hideWhenAuthed","realSession","SignedIn","AuthForm","email","onSignOut","providers","setMode","setEmail","password","setPassword","confirmPassword","setConfirmPassword","otpCode","setOtpCode","busy","setBusy","error","setError","info","setInfo","signupExpanded","setSignupExpanded","lastLogin","setLastLogin","current","switchTo","next","onSubmit","onOAuth","showOAuth","showEmailField","showPasswordField","Header","p","ProviderIcon","LastUsedBadge","Divider","FilledField","PasswordField","Fragment","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","AuthGate","authSession","onBack","showBack","intent","effectiveBlock","BackArrowButton","ariaLabel","AnonGate","onSuccess","heading","description","resolvedHeading","resolvedDescription","phase","setPhase","aliveRef","run","Spinner","STORAGE_KEY","offerId","calcTimeLeft","endMs","distance","resolveEndMs","offer","startIso","pickActiveOffer","offers","preferredId","match","o","useOfferCountdown","timeLeft","setTimeLeft","endMsRef","timer","OfferBanner","titleWithDiscount","FlashIcon","Countdown","Cell","OfferTopBanner","SUBJECT_MIN","SUBJECT_MAX","CONTENT_MAX","MAX_FILES","MAX_FILE_SIZE","ACCEPTED_MIME","EMAIL_RE","SupportGate","client","origin","sessionEmail","lockedEmail","subject","setSubject","message","setMessage","files","setFiles","submitting","setSubmitting","submittedEmail","setSubmittedEmail","errors","setErrors","isValid","useMemo","m","validate","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","priceId","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","discountPercent","discounted","main","original","applicableOffer","targeted","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","Renderer","layout","onAction","hasTopBanner","defaultPriceId","b","selectedPriceId","setSelectedPriceId","ctaIdx","scrollBlocks","footerBlocks","renderBlock","Cmp","computePaywallSnapshot","state","gate","purchased","sameSnapshot","a","PaywallRoot","onEvent","initialView","renew","onState","setState","setAuthSession","setGate","resumingRef","lastSnapshotRef","_event","data","useLayoutEffect","runCheckout","result","popup","reopenCheckout","url","handleAction","payload","cachedSession","hasRealSession","brand","activeOffer","gateBlock","supportView","bootstrapForI18n","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","opts","user","shouldRunUserWatcher","CLOSED_STATE","URL_MARKERS","PaywallUI$1","ownsAuth","resolveAuth","BillingClient","event","analytics","cfg","endpoint","EventTracker","name","handler","partial","set","args","view","skipTrial","skipVisibility","flags","trialCfg","store","status","updated","config","sameTrialConfig","factoryFn","createTrialStore","mountOpts","snapshot","sameStateSnapshot","cb","visibility","trial","redirect","hashMarkers","parseMarkers","searchMarkers","markers","notifyOpenerOfPurchase","stripMarkersFromUrl","AuthClient","isAuthClientLike","segment","clean","raw","prefix","RemoteTrialStore","transport","paywallId","RemoteBillingClient","balances","signal","identity","sameUser","sameBalances","RemoteAuthClient","input","tempName","injectLoaderUI","authorizeUrl","code","waitForOAuthCode","sameSession","PROVIDER_NAMES","doc","wrap","spinner","RemoteEventTracker","TransportClient","factory","channel","offMsg","env","offDisc","PROTOCOL_VERSION","res","envelope","isEnvelope","reconstructError","fn","TransportDisconnectedError","kind","id","resolve","reject","wrapped","getContentTransport","createRuntimeChannel","PORT_NAME","PaywallUI","BasePaywallUI","billing"],"mappings":"2wrBAeA,IAAIA,GAAyB,GAC7B,SAASC,IAAqC,CAG5C,GAFID,KACJA,GAAyB,GACrB,OAAO,IAAQ,KAAe,OAAO,IAAI,kBAAqB,YAAY,OAC9E,IAAIE,EACJ,GAAI,CACF,MAAMC,EAAQ,IAAI,cAClBA,EAAM,YAAYC,EAAO,EACzBF,EAAQC,EAAM,QAChB,MAAQ,CACN,MACF,CACA,UAAWE,KAAQH,EAAO,CACxB,GAAIG,EAAK,YAAY,OAAS,kBAAmB,SACjD,MAAMC,EAAID,EACV,GAAI,CACF,IAAI,iBAAiB,CACnB,KAAMC,EAAE,KACR,OAAQA,EAAE,OACV,SAAUA,EAAE,SACZ,GAAIA,EAAE,cAAgB,KAAO,CAAE,aAAcA,EAAE,cAAiB,CAAA,CAAC,CAClE,CACH,MAAQ,CAER,CACF,CACF,CAEO,SAASC,GACdC,EACAC,EACAC,EASI,CAAA,EACS,CACb,GAAI,OAAO,SAAa,IACtB,MAAM,IAAI,MAAM,2CAA2C,EAG7DT,GAAA,EAEA,MAAMU,EAAOD,EAAQ,MAAQ,SAAS,cAAc,KAAK,EACzDC,EAAK,aAAa,oBAAqB,EAAE,EAIzCA,EAAK,MAAM,QAAUD,EAAQ,OACzB,gFACA,sFAGA,CAACC,EAAK,aAAe,CAACD,EAAQ,QAAQ,SAAS,KAAK,YAAYC,CAAI,EAKxE,MAAMC,EAASD,EAAK,aAAa,CAAE,KAAMD,EAAQ,YAAc,SAAU,EAOnEG,EAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoBZC,EAAQ,SAAS,cAAc,OAAO,EAC5CA,EAAM,YAAcD,EAAYT,IAAWM,EAAQ,WAAa,IAChEE,EAAO,YAAYE,CAAK,EAExB,MAAMC,EAAa,SAAS,cAAc,KAAK,EAC/CA,EAAW,MAAM,cAAgB,OACjCH,EAAO,YAAYG,CAAU,EAE7B,IAAIC,EAAeP,EACnBQ,OAAAA,EAAAA,OAAOC,EAAAA,EAAEV,EAAoCQ,CAAY,EAAGD,CAAU,EAE/D,CACL,WAAYH,EACZ,OAAOO,EAAW,CAChBH,EAAe,CAAE,GAAGA,EAAc,GAAGG,CAAA,EACrCF,EAAAA,OAAOC,EAAAA,EAAEV,EAAoCQ,CAAY,EAAGD,CAAU,CACxE,EACA,SAAU,CACRE,EAAAA,OAAO,KAAMF,CAAU,EACvBJ,EAAK,OAAA,CACP,CAAA,CAEJ,yUCvHaS,GAAkB,CAC7B,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,IACF,ECVMC,GAAgB,CAACC,EAAMC,EAAUC,IAAWC,GAAOF,EAAUC,CAAM,EAEnEE,GAAUC,EAAAA,cAAgC,CAAE,EAAGN,GAAU,OAAQ,KAAM,EAI7E,SAASI,GAAOG,EAAWJ,EAAkD,CAC3E,GAAI,CAACA,EAAQ,OAAOI,EACpB,IAAIC,EAAMD,EACV,SAAW,CAACE,EAAGC,CAAC,IAAK,OAAO,QAAQP,CAAM,EACxCK,EAAMA,EAAI,MAAM,IAAIC,CAAC,GAAG,EAAE,KAAK,OAAOC,CAAC,CAAC,EAE1C,OAAOF,CACT,CAKA,MAAMG,MAAgB,IAIhBC,MAAe,IAErB,SAASC,GAAgBC,EAAmC,CAC1D,OAAQf,GAAsC,SAASe,CAAG,CAC5D,CAOO,SAASC,GAAoBC,EAAmD,CACrF,MAAMC,EAAuB,CAAA,EAC7B,GAAI,OAAO,UAAc,KAAe,UAAU,SAAU,CAC1DA,EAAW,KAAK,UAAU,QAAQ,EAClC,MAAMC,EAAO,UAAU,SAAS,MAAM,GAAG,EAAE,CAAC,EACxCA,GAAQA,IAAS,UAAU,UAAUD,EAAW,KAAKC,CAAI,CAC/D,CACA,MAAMhB,EAAWc,EAAU,SAAS,eACpC,GAAId,EAAU,CACZe,EAAW,KAAKf,CAAQ,EACxB,MAAMgB,EAAOhB,EAAS,MAAM,GAAG,EAAE,CAAC,EAC9BgB,GAAQA,IAAShB,GAAUe,EAAW,KAAKC,CAAI,CACrD,CACA,UAAWC,KAAKF,EACd,GAAIJ,GAAgBM,CAAC,EAAG,OAAOA,EAEjC,OAAO,IACT,CAMO,SAASC,GAAqBJ,EAAsC,CACzE,MAAO,CAAC,CAACA,EAAU,SAAW,OAAO,KAAKA,EAAU,OAAO,EAAE,OAAS,CACxE,CAMA,eAAsBK,GAAWP,EAA8C,CAC7E,MAAMQ,EAASX,EAAU,IAAIG,CAAG,EAChC,GAAIQ,EAAQ,OAAOA,EACnB,MAAMC,EAAUX,EAAS,IAAIE,CAAG,EAChC,GAAIS,EAAS,OAAOA,EAKpB,MAAMC,EAAUC,2wEAAA,aAAAX,CAAA,MAAA,CAAA,EACb,KAAMY,GAAsC,CAC3C,MAAMC,EAAOD,EAAI,SAAW,CAAA,EAC5B,OAAAf,EAAU,IAAIG,EAAKa,CAAI,EAChBA,CACT,CAAC,EACA,MAAOC,GAAQ,CACd,QAAQ,KAAK,0CAA0Cd,CAAG,IAAKc,CAAG,EAClE,MAAMC,EAAyB,CAAA,EAC/B,OAAAlB,EAAU,IAAIG,EAAKe,CAAK,EACjBA,CACT,CAAC,EACA,QAAQ,IAAM,CACbjB,EAAS,OAAOE,CAAG,CACrB,CAAC,EACH,OAAAF,EAAS,IAAIE,EAAKU,CAAO,EAClBA,CACT,CA0BO,SAASM,GAAa,CAAE,UAAAd,EAAW,YAAAe,EAAa,SAAAC,GAA+B,CACpF,KAAM,CAACC,EAAQC,CAAS,EAAIC,EAAAA,SAAiB,IAAI,EAC3C,CAACR,EAAMS,CAAO,EAAID,EAAAA,SAAiC,IAAI,EAE7DE,EAAAA,UAAU,IAAM,CAId,MAAMvB,GADWiB,GAAelB,GAAgBkB,CAAW,EAAIA,EAAc,QAEvE,CAACf,GACD,CAACI,GAAqBJ,CAAS,EAAU,KACtCD,GAAoBC,CAAS,GAOtC,GAAI,CAACF,EAAK,EACJa,IAAS,MAAQM,IAAW,QAC9BC,EAAU,IAAI,EACdE,EAAQ,IAAI,GAEd,MACF,CACA,GAAItB,IAAQmB,GAAUN,EAAM,OAE5B,IAAIW,EAAY,GAChB,OAAKjB,GAAWP,CAAG,EAAE,KAAMyB,GAAM,CAC3BD,IACJJ,EAAUpB,CAAG,EACbsB,EAAQG,CAAC,EACX,CAAC,EACM,IAAM,CACXD,EAAY,EACd,CACF,EAAG,CAACtB,EAAWe,CAAW,CAAC,EAE3B,MAAMS,EAA0B,CAC9B,OAAAP,EACA,EAAGN,EACC,CAACb,EAAKZ,EAAUC,IAAWC,GAAOuB,EAAKb,CAAG,GAAKZ,EAAUC,CAAM,EAC/DH,EAAA,EAGN,OAAOyC,EAAAA,IAACpC,GAAQ,SAAR,CAAiB,MAAAmC,EAAe,SAAAR,CAAA,CAAS,CACnD,CAKO,SAASU,GAA4B,CAC1C,OAAOC,EAAAA,WAAWtC,EAAO,CAC3B,CChMA,MAAMuC,GACJ,4IA2BK,SAASC,GAAM,CACpB,KAAAC,EACA,QAAAC,EACA,WAAAC,EACA,WAAAC,EACA,UAAAC,EACA,WAAAC,EAAa,GACb,gBAAAC,EAAkB,GAClB,OAAAC,EAAS,GACT,SAAArB,CACF,EAAe,CACb,KAAM,CAAE,EAAAsB,CAAA,EAAMZ,EAAA,EACRa,EAAYC,EAAAA,OAA8B,IAAI,EAC9CC,EAAoBD,EAAAA,OAA2B,IAAI,EAoDzD,GAlDAnB,EAAAA,UAAU,IAAM,CACd,GAAI,CAACS,EAAM,OACXW,EAAkB,QAAW,SAAS,eAAiC,KAEvE,MAAMC,EAASH,EAAU,QACrBG,IACYA,EAAO,cAA2Bd,EAAS,GAC/Cc,GAAQ,MAAM,CAAE,cAAe,GAAM,EAGjD,MAAMC,EAASC,GAAqB,CAClC,GAAIA,EAAE,MAAQ,SAAU,CACtB,GAAI,CAACT,EAAY,OACjBS,EAAE,gBAAA,EACFb,EAAA,EACA,MACF,CACA,GAAIa,EAAE,MAAQ,OAAS,CAACL,EAAU,QAAS,OAC3C,MAAMM,EAAa,MAAM,KACvBN,EAAU,QAAQ,iBAA8BX,EAAS,CAAA,EACzD,OAAQkB,GAAO,CAACA,EAAG,aAAa,UAAU,GAAKA,EAAG,WAAa,EAAE,EACnE,GAAID,EAAW,SAAW,EAAG,CAC3BD,EAAE,eAAA,EACF,MACF,CACA,MAAMG,EAAQF,EAAW,CAAC,EACpBG,EAAOH,EAAWA,EAAW,OAAS,CAAC,EACvCI,EAAS,SAAS,cACpBL,EAAE,UAAYK,IAAWF,GAC3BH,EAAE,eAAA,EACFI,EAAK,MAAA,GACI,CAACJ,EAAE,UAAYK,IAAWD,IACnCJ,EAAE,eAAA,EACFG,EAAM,MAAA,EAEV,EAEA,SAAS,iBAAiB,UAAWJ,EAAO,EAAI,EAGhD,MAAMO,EAAe,SAAS,KAAK,MAAM,SACzC,OAAKb,IAAQ,SAAS,KAAK,MAAM,SAAW,UAErC,IAAM,CACX,SAAS,oBAAoB,UAAWM,EAAO,EAAI,EAC9CN,IAAQ,SAAS,KAAK,MAAM,SAAWa,GAC5CT,EAAkB,SAAS,QAAQ,CAAE,cAAe,GAAM,CAC5D,CACF,EAAG,CAACX,EAAMC,EAASI,EAAYE,CAAM,CAAC,EAElC,CAACP,EAAM,OAAO,KAElB,MAAMqB,EAAcP,GAAkB,CAC/BT,GACDS,EAAE,SAAWA,EAAE,eAAeb,EAAA,CACpC,EAEMqB,EAASnB,GAAc,UAIvBoB,EAAe,GAAGhB,EAAS,iBAAmB,sBAAsB,4HAE1E,OACEiB,EAAAA,KAAC,MAAA,CACC,MAAOD,EACP,QAASF,EACT,eAAY,GASZ,SAAA,CAAAG,EAAAA,KAAC,MAAA,CACC,MAAM,qGACN,MAAO,CAAE,cAAeF,CAAA,EAEvB,SAAA,CAAAlB,EACDoB,EAAAA,KAAC,MAAA,CACC,IAAKf,EACL,KAAK,SACL,aAAW,OACX,kBAAiBP,EACjB,SAAU,GAKV,MAAM,wIACN,MAAO,CACL,UACE,mEAAA,EAQH,SAAA,CAAAhB,EACAmB,GAAc,CAACC,EACdX,EAAAA,IAAC,SAAA,CACC,KAAK,SACL,QAASM,EACT,aAAYO,EAAE,mBAAoB,OAAO,EAIzC,MAAM,qQAEN,SAAAb,EAAAA,IAAC,MAAA,CAAI,MAAM,KAAK,OAAO,KAAK,QAAQ,YAAY,KAAK,OAAO,cAAY,OACtE,SAAAA,EAAAA,IAAC,OAAA,CACC,EAAE,uBACF,OAAO,eACP,eAAa,OACb,iBAAe,OAAA,CAAA,CACjB,CACF,CAAA,CAAA,EAEA,IAAA,CAAA,CAAA,CACN,CAAA,CAAA,QAGD,QAAA,CAAO,SAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,OAAA,CAMN,CAAA,CAAA,CAAA,CAGR,CC3KA,SAAS8B,GAAcC,EAAyBlB,EAAgB,CAC9D,OAAQkB,EAAA,CACN,IAAK,SACH,OAAOlB,EAAE,4BAA6B,sBAAsB,EAC9D,IAAK,QACH,OAAOA,EAAE,2BAA4B,qBAAqB,EAC5D,IAAK,SACH,OAAOA,EAAE,4BAA6B,sBAAsB,EAC9D,IAAK,WACH,OAAOA,EAAE,8BAA+B,wBAAwB,CAAA,CAEtE,CAOA,SAASmB,GACP7C,EACA8C,EACA,EACQ,CACR,MAAMxE,EACJwE,IAAS,SACL,EAAE,qBAAsB,gBAAgB,EACxC,EAAE,qBAAsB,gBAAgB,EAC9C,GAAI,EAAE9C,aAAe+C,gBAAe,OAAOzE,EAC3C,OAAQ0B,EAAI,KAAA,CACV,IAAK,sBACH,OAAO,EAAE,2BAA4B,2BAA2B,EAClE,IAAK,sBACH,OAAO,EAAE,2BAA4B,8CAA8C,EACrF,IAAK,eACL,IAAK,sBACH,OAAO,EAAE,oBAAqB,4CAA4C,EAC5E,IAAK,gBACH,OAAO,EAAE,qBAAsB,uBAAuB,EACxD,IAAK,cACL,IAAK,cACL,IAAK,gBACH,OAAO,EAAE,mBAAoB,qCAAqC,EACpE,IAAK,6BACL,IAAK,0BACL,IAAK,eACL,IAAK,WACH,OAAO,EAAE,oBAAqB,kDAAkD,EAClF,IAAK,gBACH,OAAO,EAAE,qBAAsB,4DAA4D,EAC7F,IAAK,WACL,IAAK,iBACL,IAAK,WACL,IAAK,WACL,IAAK,WACH,OAAO,EAAE,2BAA4B,uDAAuD,EAC9F,QACE,OAAO1B,CAAA,CAEb,CAEO,SAAS0E,GAAU,CAAE,MAAAC,EAAO,IAAAC,GAAmC,CACpE,MAAMC,EAAOD,EAAI,KACXE,EAAUF,EAAI,YACdG,EAAcJ,EAAM,eAAiB,GACrCK,EAAaL,EAAM,uBAAyB,GAC5CM,EAAiBN,EAAM,0BAA4B,GAEzD,GAAI,CAACE,EACH,OAAI,OAAO,QAAY,KACrB,QAAQ,KAAK,mFAAmF,EAE3F,KAKT,MAAMK,EAAcJ,GAAW,CAACA,EAAQ,KAAK,aAAeA,EAAU,KACtE,OAAII,GAAeD,EAAuB,KAEtCC,EACK3C,EAAAA,IAAC4C,GAAA,CAAS,MAAOD,EAAY,KAAK,OAAS,GAAI,UAAW,IAAML,EAAK,QAAA,EAAU,MAAM,IAAM,CAAC,CAAC,CAAA,CAAG,EAIvGtC,EAAAA,IAAC6C,GAAA,CACC,MAAAT,EACA,YAAAI,EACA,WAAAC,EACA,IAAAJ,CAAA,CAAA,CAGN,CAEA,SAASO,GAAS,CAAE,MAAAE,EAAO,UAAAC,GAAuD,CAChF,KAAM,CAAE,CAAA,EAAM9C,EAAA,EACd,OACE4B,EAAAA,KAAC,MAAA,CAAI,MAAM,4EACT,SAAA,CAAAA,EAAAA,KAAC,MAAA,CAAI,MAAM,gBACT,SAAA,CAAA7B,MAAC,QAAK,MAAM,mEACT,SAAA,EAAE,iBAAkB,WAAW,EAClC,EACAA,EAAAA,IAAC,OAAA,CAAK,MAAM,oCAAqC,SAAA8C,CAAA,CAAM,CAAA,EACzD,EACA9C,EAAAA,IAAC,SAAA,CACC,KAAK,SACL,QAAS+C,EACT,MAAM,gMAEL,SAAA,EAAE,gBAAiB,UAAU,CAAA,CAAA,CAChC,EACF,CAEJ,CASA,SAASF,GAAS,CAAE,MAAAT,EAAO,YAAAI,EAAa,WAAAC,EAAY,IAAAJ,GAAkB,CACpE,KAAM,CAAE,EAAAxB,CAAA,EAAMZ,EAAA,EACRqC,EAAOD,EAAI,KACXW,EAAYZ,EAAM,WAAa,CAAA,EAE/B,CAACH,EAAMgB,CAAO,EAAIvD,EAAAA,SAAe,QAAQ,EACzC,CAACoD,EAAOI,CAAQ,EAAIxD,EAAAA,SAAS,EAAE,EAC/B,CAACyD,EAAUC,CAAW,EAAI1D,EAAAA,SAAS,EAAE,EACrC,CAAC2D,EAAiBC,CAAkB,EAAI5D,EAAAA,SAAS,EAAE,EACnD,CAAC6D,EAASC,CAAU,EAAI9D,EAAAA,SAAS,EAAE,EACnC,CAAC+D,EAAMC,CAAO,EAAIhE,EAAAA,SAAmD,IAAI,EACzE,CAACiE,EAAOC,CAAQ,EAAIlE,EAAAA,SAAwB,IAAI,EAChD,CAACmE,EAAMC,CAAO,EAAIpE,EAAAA,SAAwB,IAAI,EAK9C,CAACqE,EAAgBC,CAAiB,EAAItE,EAAAA,SAAS,EAAK,EAUpD,CAACuE,EAAWC,CAAY,EAAIxE,EAAAA,SAA2B,IAAI,EACjEE,EAAAA,UAAU,IAAM,CACd,GAAI,OAAO0C,EAAK,cAAiB,WAAY,OAC7C,IAAIzC,EAAY,GAChB,OAAAyC,EAAK,eAAe,KACjBrE,GAAM,CACD4B,GAAa,CAAC5B,IAClBiG,EAAajG,CAAC,EACVA,EAAE,OACJiF,EAAUiB,GAAaA,IAAY,GAAKlG,EAAE,MAASkG,CAAQ,EAE/D,EACA,IAAM,CAEN,CAAA,EAEK,IAAM,CACXtE,EAAY,EACd,CACF,EAAG,CAACyC,CAAI,CAAC,EAET,MAAM8B,EAAYC,GAAqB,CACrCpB,EAAQoB,CAAI,EACZT,EAAS,IAAI,EACbE,EAAQ,IAAI,EACZE,EAAkB,EAAK,CACzB,EAEMM,EAAW,MAAOnD,GAA4B,CAElD,GADAA,EAAE,eAAA,EACE,CAAAsC,EAOJ,IANAG,EAAS,IAAI,EACbE,EAAQ,IAAI,EAKR7B,IAAS,UAAY,CAAC8B,EAAgB,CACxC,GAAI,CAACjB,EAAM,OAAQ,OACnBkB,EAAkB,EAAI,EACtB,MACF,CAEA,GAAI/B,IAAS,UAAYkB,IAAaE,EAAiB,CACrDO,EAAS/C,EAAE,0BAA2B,uBAAuB,CAAC,EAC9D,MACF,CAEA6C,EAAQ,OAAO,EACf,GAAI,CACEzB,IAAS,SACX,MAAMK,EAAK,gBAAgB,CAAE,MAAAQ,EAAO,SAAAK,EAAU,EACrClB,IAAS,UACN,MAAMK,EAAK,OAAO,CAAE,MAAAQ,EAAO,SAAAK,EAAU,GACzC,OAAS,0BACfF,EAAQ,cAAc,EACtBa,EAAQjD,EAAE,2BAA4B,2CAA2C,CAAC,GAE3EoB,IAAS,UAClB,MAAMK,EAAK,qBAAqB,CAAE,MAAAQ,EAAO,EACzCG,EAAQ,YAAY,EACpBa,EACEjD,EAAE,0BAA2B,mDAAmD,CAAA,GAEzEoB,IAAS,iBAClB,MAAMK,EAAK,UAAU,CACnB,MAAAQ,EACA,MAAOS,EACP,KAAMJ,EAAW,WAAa,OAAA,CAC/B,EACGA,GACF,MAAMb,EAAK,eAAe,CAAE,SAAAa,EAAU,EAG5C,OAAShE,EAAK,CAGZyE,EAAS5B,GAAiB7C,EADxB8C,IAAS,SAAW,SAAWA,IAAS,eAAiB,MAAQA,IAAS,SAAW,QAAU,SACzDpB,CAAC,CAAC,CAC5C,QAAA,CACE6C,EAAQ,IAAI,CACd,EACF,EAEMa,EAAU,MAAOxC,GAA2C,CAChE,GAAI,CAAA0B,EACJ,CAAAC,EAAQ3B,CAAQ,EAChB6B,EAAS,IAAI,EACbE,EAAQ,IAAI,EACZ,GAAI,CACF,MAAMxB,EAAK,gBAAgB,CACzB,SAAAP,EACA,cAAe,IAAM2B,EAAQ,IAAI,CAAA,CAClC,CACH,OAASvE,EAAK,CACZ,GAAIA,aAAe+C,EAAAA,eAAiB/C,EAAI,OAAS,mBAAqBA,EAAI,OAAS,iBACjF,OAEFyE,EAAS5B,GAAiB7C,EAAK,SAAU0B,CAAC,CAAC,CAC7C,QAAA,CACE6C,EAAQ,IAAI,CACd,EACF,EAEMc,EAAYxB,EAAU,OAAS,IAAMf,IAAS,UAAYA,IAAS,UACnEwC,EAAiBxC,IAAS,UAAYA,IAAS,UAAYA,IAAS,SACpEyC,EACJzC,IAAS,UAAaA,IAAS,UAAY8B,EAE7C,OACElC,EAAAA,KAAC,MAAA,CAAI,MAAM,sBACT,SAAA,CAAA7B,MAAC2E,IAAO,KAAA1C,EAAY,cAAeG,EAAM,QAAS,iBAAkBA,EAAM,WAAY,EAErFoC,EACC3C,EAAAA,KAAC,MAAA,CAAI,MAAM,wBACR,SAAA,CAAAmB,EAAU,IAAK4B,GACd/C,EAAAA,KAAC,MAAA,CAAY,MAAM,WACjB,SAAA,CAAAA,EAAAA,KAAC,SAAA,CACC,KAAK,SACL,QAAS,IAAM0C,EAAQK,CAAC,EACxB,SAAUnB,IAAS,KACnB,MAAM,mUAEL,SAAA,CAAAA,IAASmB,QACP,OAAA,CAAK,MAAM,4FAA4F,EAExG5E,EAAAA,IAAC6E,GAAA,CAAa,SAAUD,CAAA,CAAG,EAE7B5E,EAAAA,IAAC,OAAA,CAAM,SAAA8B,GAAc8C,EAAG/D,CAAC,CAAA,CAAE,CAAA,CAAA,CAAA,EAE5BoD,GAAW,SAAWW,EAAI5E,MAAC8E,IAAc,MAAOb,EAAU,MAAO,EAAK,IAAA,CAAA,EAd/DW,CAeV,CACD,QACAG,GAAA,CAAA,CAAQ,CAAA,CAAA,CACX,EACE,KAEJlD,EAAAA,KAAC,OAAA,CAAK,SAAAyC,EAAoB,MAAM,sBAC7B,SAAA,CAAAG,GACCzE,EAAAA,IAACgF,GAAA,CACC,KAAK,QACL,YAAanE,EAAE,aAAc,eAAe,EAC5C,MAAOiC,EACP,QAASI,EACT,aAAa,QACb,SAAQ,EAAA,CAAA,EAIXwB,GACC1E,EAAAA,IAACiF,EAAA,CACC,YAAapE,EAAE,gBAAiB,UAAU,EAC1C,MAAOsC,EACP,QAASC,EACT,aAAcnB,IAAS,SAAW,mBAAqB,eACvD,SAAQ,EAAA,CAAA,EAIXA,IAAS,UAAY8B,GACpB/D,EAAAA,IAACiF,EAAA,CACC,YAAapE,EAAE,uBAAwB,iBAAiB,EACxD,MAAOwC,EACP,QAASC,EACT,aAAa,eACb,SAAQ,EAAA,CAAA,EAIXrB,IAAS,gBACRJ,EAAAA,KAAAqD,EAAAA,SAAA,CACE,SAAA,CAAAlF,EAAAA,IAACgF,GAAA,CACC,KAAK,OACL,YAAanE,EAAE,yBAA0B,mBAAmB,EAC5D,MAAO0C,EACP,QAASC,EACT,aAAa,gBACb,UAAU,UACV,SAAQ,EAAA,CAAA,EAEVxD,EAAAA,IAACiF,EAAA,CACC,YAAapE,EACX,6BACA,mDAAA,EAEF,MAAOsC,EACP,QAASC,EACT,aAAa,cAAA,CAAA,CACf,EACF,EAGDnB,IAAS,cAAgB4B,SACvB,IAAA,CAAE,MAAM,0DAA2D,SAAAA,EAAK,EAG1E5B,IAAS,UAAYQ,SACnB,MAAA,CAAI,MAAM,2BACT,SAAAzC,MAACmF,EAAA,CAAW,QAAS,IAAMf,EAAS,QAAQ,EACzC,WAAE,uBAAwB,kBAAkB,EAC/C,EACF,EAGDT,GAAS3D,EAAAA,IAAC,IAAA,CAAE,MAAM,uBAAwB,SAAA2D,EAAM,EAChDE,GAAQ5B,IAAS,oBACf,IAAA,CAAE,MAAM,wBAAyB,SAAA4B,EAAK,EAGxC5B,IAAS,cACRJ,OAAC,MAAA,CAAI,MAAM,WACT,SAAA,CAAA7B,EAAAA,IAACoF,GAAA,CACC,KAAM3B,IAAS,QACf,MAAO4B,GAAYpD,EAAM8B,EAAgB3B,EAAM,cAAgBA,EAAM,QAASvB,CAAC,CAAA,CAAA,EAKhFoB,IAAS,UAAYgC,GAAW,SAAW,cACzCa,GAAA,CAAc,MAAOb,EAAU,KAAA,CAAO,EACrC,IAAA,CAAA,CACN,CAAA,EAEJ,EAEAjE,EAAAA,IAACsF,GAAA,CACC,KAAArD,EACA,YAAAO,EACA,SAAU4B,CAAA,CAAA,CACZ,EACF,CAEJ,CAEA,SAASO,GAAO,CACd,KAAA1C,EACA,cAAAsD,EACA,iBAAAC,CACF,EAIG,CACD,KAAM,CAAE,EAAA3E,CAAA,EAAMZ,EAAA,EAMRwF,EAAWC,GAAczD,EAAMpB,CAAC,EAChC8E,EAAY1D,IAAS,UAAYA,IAAS,SAC1C2D,EAAQD,GAAaJ,EAAgBA,EAAgBE,EAAS,MAC9DI,EACJF,GAAaH,IAAqB,OAC9BA,GAAoB,KACpBC,EAAS,SACf,OACE5D,EAAAA,KAAC,MAAA,CAAI,MAAM,sBACT,SAAA,CAAA7B,EAAAA,IAAC,KAAA,CAAG,MAAM,kDAAmD,SAAA4F,EAAM,EAClEC,EACC7F,EAAAA,IAAC,IAAA,CAAE,MAAM,0CAA2C,WAAS,EAC3D,IAAA,EACN,CAEJ,CAEA,SAAS0F,GAAczD,EAAYpB,EAAoD,CACrF,OAAQoB,EAAA,CACN,IAAK,SACH,MAAO,CACL,MAAOpB,EAAE,eAAgB,eAAe,EACxC,SAAUA,EAAE,wBAAyB,oDAAoD,CAAA,EAE7F,IAAK,SACH,MAAO,CACL,MAAOA,EAAE,sBAAuB,UAAU,EAC1C,SAAUA,EAAE,wBAAyB,oDAAoD,CAAA,EAE7F,IAAK,SACH,MAAO,CACL,MAAOA,EAAE,6BAA8B,kBAAkB,EACzD,SAAUA,EACR,uBACA,4DAAA,CACF,EAEJ,IAAK,aACH,MAAO,CACL,MAAOA,EAAE,yBAA0B,kBAAkB,EACrD,SAAU,IAAA,EAEd,IAAK,eACH,MAAO,CACL,MAAOA,EAAE,4BAA6B,gBAAgB,EACtD,SAAUA,EACR,+BACA,oDAAA,CACF,CACF,CAEN,CAEA,SAASwE,GACPpD,EACA8B,EACAwB,EACA1E,EACQ,CAIR,GAAIoB,IAAS,UAAYsD,EAAe,OAAOA,EAC/C,OAAQtD,EAAA,CACN,IAAK,SACH,OAAOpB,EAAE,cAAe,SAAS,EACnC,IAAK,SACH,OAAOkD,EACHlD,EAAE,sBAAuB,gBAAgB,EACzCA,EAAE,eAAgB,SAAS,EACjC,IAAK,SACH,OAAOA,EAAE,kBAAmB,kBAAkB,EAChD,IAAK,eACH,OAAOA,EAAE,cAAe,QAAQ,EAClC,QACE,OAAOA,EAAE,eAAgB,UAAU,CAAA,CAEzC,CAEA,SAASyE,GAAW,CAClB,KAAArD,EACA,YAAAO,EACA,SAAAsD,CACF,EAIG,CACD,KAAM,CAAE,EAAAjF,CAAA,EAAMZ,EAAA,EACd,OAAIgC,IAAS,UAAYO,EAErBX,EAAAA,KAAC,IAAA,CAAE,MAAM,oCACN,SAAA,CAAAhB,EAAE,kBAAmB,wBAAwB,EAAG,IACjDb,EAAAA,IAACmF,EAAA,CAAW,QAAS,IAAMW,EAAS,QAAQ,EACzC,SAAAjF,EAAE,oBAAqB,SAAS,CAAA,CACnC,CAAA,EACF,EAGAoB,IAAS,SAETJ,EAAAA,KAAC,IAAA,CAAE,MAAM,oCACN,SAAA,CAAAhB,EAAE,oBAAqB,0BAA0B,EAAG,IACrDb,EAAAA,IAACmF,EAAA,CAAW,QAAS,IAAMW,EAAS,QAAQ,EACzC,SAAAjF,EAAE,mBAAoB,QAAQ,CAAA,CACjC,CAAA,EACF,EAGAoB,IAAS,UAAYA,IAAS,cAAgBA,IAAS,eAEvDJ,EAAAA,KAAC,IAAA,CAAE,MAAM,oCACN,SAAA,CAAAhB,EAAE,kBAAmB,wBAAwB,EAAG,IACjDb,EAAAA,IAACmF,EAAA,CAAW,QAAS,IAAMW,EAAS,QAAQ,EACzC,SAAAjF,EAAE,oBAAqB,SAAS,CAAA,CACnC,CAAA,EACF,EAGG,IACT,CAEA,SAASsE,EAAW,CAClB,QAAAY,EACA,SAAAxG,CACF,EAGG,CACD,OACES,EAAAA,IAAC,SAAA,CACC,KAAK,SACL,QAAA+F,EACA,MAAM,gGACN,MAAO,CAAE,MAAO,kBAAA,EAEf,SAAAxG,CAAA,CAAA,CAGP,CAEA,SAAS6F,GAAc,CAAE,KAAA3B,EAAM,MAAAuC,GAA2C,CACxE,OACEhG,EAAAA,IAAC,SAAA,CACC,KAAK,SACL,SAAUyD,EACV,MAAM,mYACN,MAAO,CACL,WACE,0JACF,UACE,8HAAA,EAGH,SAAAA,EACCzD,EAAAA,IAAC,OAAA,CAAK,MAAM,sGAAA,CAAuG,EAEnHA,EAAAA,IAAC,OAAA,CAAK,MAAM,gBAAiB,SAAAgG,CAAA,CAAM,CAAA,CAAA,CAI3C,CAYA,SAAShB,GAAY,CAAE,KAAAiB,EAAM,YAAAC,EAAa,MAAAnG,EAAO,QAAAoG,EAAS,aAAAC,EAAc,UAAAC,EAAW,SAAAC,GAA8B,CAC/G,OACEtG,EAAAA,IAAC,QAAA,CACC,KAAAiG,EACA,MAAAlG,EACA,YAAAmG,EACA,QAAU/E,GAAMgF,EAAShF,EAAE,OAA4B,KAAK,EAC5D,aAAAiF,EACA,UAAAC,EACA,SAAAC,EACA,MAAM,+OAAA,CAAA,CAGZ,CAUA,SAASrB,EAAc,CAAE,YAAAiB,EAAa,MAAAnG,EAAO,QAAAoG,EAAS,aAAAC,EAAc,SAAAE,GAAgC,CAClG,KAAM,CAAE,EAAAzF,CAAA,EAAMZ,EAAA,EACR,CAACsG,EAASC,CAAU,EAAI9G,EAAAA,SAAS,EAAK,EACtC+G,EAAW1F,EAAAA,OAAyB,IAAI,EAG9CnB,EAAAA,UAAU,IAAM,CACd,MAAMyB,EAAKoF,EAAS,QAChBpF,GAAMA,EAAG,QAAUtB,MAAU,MAAQA,EAC3C,EAAG,CAACwG,EAASxG,CAAK,CAAC,EACnB,MAAM2G,EAAmB7F,EAAE,qBAAsB,eAAe,EAC1D8F,EAAmB9F,EAAE,qBAAsB,eAAe,EAChE,OACEgB,EAAAA,KAAC,MAAA,CAAI,MAAM,WACT,SAAA,CAAA7B,EAAAA,IAAC,QAAA,CACC,IAAKyG,EACL,KAAMF,EAAU,OAAS,WACzB,MAAAxG,EACA,YAAAmG,EACA,QAAU/E,GAAMgF,EAAShF,EAAE,OAA4B,KAAK,EAC5D,aAAAiF,EACA,SAAAE,EACA,MAAM,qPAAA,CAAA,EAERtG,EAAAA,IAAC,SAAA,CACC,KAAK,SACL,QAAS,IAAMwG,EAAYvI,GAAM,CAACA,CAAC,EACnC,aAAYsI,EAAUI,EAAmBD,EACzC,SAAU,GACV,MAAM,+NAEL,SAAAH,EAAUvG,MAAC4G,GAAA,CAAA,CAAW,QAAMC,GAAA,CAAA,CAAQ,CAAA,CAAA,CACvC,EACF,CAEJ,CAEA,SAASA,IAAU,CACjB,OACEhF,EAAAA,KAAC,MAAA,CAAI,MAAM,KAAK,OAAO,KAAK,QAAQ,YAAY,KAAK,OAAO,cAAY,OACtE,SAAA,CAAA7B,EAAAA,IAAC,OAAA,CACC,EAAE,gGACF,OAAO,eACP,eAAa,MACb,iBAAe,QACf,kBAAgB,OAAA,CAAA,EAElBA,EAAAA,IAAC,SAAA,CAAO,GAAG,KAAK,GAAG,KAAK,EAAE,MAAM,OAAO,eAAe,eAAa,KAAA,CAAM,CAAA,EAC3E,CAEJ,CAEA,SAAS4G,IAAa,CACpB,OACE/E,EAAAA,KAAC,MAAA,CAAI,MAAM,KAAK,OAAO,KAAK,QAAQ,YAAY,KAAK,OAAO,cAAY,OACtE,SAAA,CAAA7B,EAAAA,IAAC,OAAA,CACC,EAAE,4IACF,OAAO,eACP,eAAa,MACb,iBAAe,QACf,kBAAgB,OAAA,CAAA,EAElBA,EAAAA,IAAC,OAAA,CACC,EAAE,+HACF,OAAO,eACP,eAAa,MACb,iBAAe,QACf,kBAAgB,OAAA,CAAA,CAClB,EACF,CAEJ,CAEA,SAAS8E,GAAc,CAAE,MAAAhC,GAAmC,CAC1D,KAAM,CAAE,EAAAjC,CAAA,EAAMZ,EAAA,EAIR+F,EAAQlD,EACVjC,EAAE,iBAAkB,iBAAkB,CAAE,MAAOiG,GAAUhE,CAAK,CAAA,CAAG,EACjEjC,EAAE,0BAA2B,MAAM,EACvC,OACEb,EAAAA,IAAC,OAAA,CAAK,MAAM,qKACT,SAAAgG,EACH,CAEJ,CAKA,SAASc,GAAUhE,EAAuB,CACxC,KAAM,CAACiE,EAAOC,CAAM,EAAIlE,EAAM,MAAM,GAAG,EACvC,OAAKkE,EAEE,GADSD,EAAM,MAAM,EAAG,CAAC,CACf,SAASC,CAAM,GAFZlE,CAGtB,CAEA,SAASiC,IAAU,CACjB,KAAM,CAAE,EAAAlE,CAAA,EAAMZ,EAAA,EACd,OACE4B,EAAAA,KAAC,MAAA,CAAI,MAAM,qDACT,SAAA,CAAA7B,EAAAA,IAAC,MAAA,CAAI,MAAM,yBAAA,CAA0B,EACrCA,EAAAA,IAAC,OAAA,CAAM,SAAAa,EAAE,UAAW,IAAI,EAAE,EAC1Bb,EAAAA,IAAC,MAAA,CAAI,MAAM,yBAAA,CAA0B,CAAA,EACvC,CAEJ,CAEA,SAAS6E,GAAa,CAAE,SAAA9C,GAAyC,CAC/D,OAAIA,IAAa,SAEbF,OAAC,OAAI,MAAM,KAAK,OAAO,KAAK,QAAQ,YAAY,cAAY,OAC1D,SAAA,CAAA7B,EAAAA,IAAC,OAAA,CAAK,KAAK,UAAU,EAAE,kHAAkH,EACzIA,EAAAA,IAAC,OAAA,CAAK,KAAK,UAAU,EAAE,sHAAsH,EAC7IA,EAAAA,IAAC,OAAA,CAAK,KAAK,UAAU,EAAE,qEAAqE,EAC5FA,EAAAA,IAAC,OAAA,CAAK,KAAK,UAAU,EAAE,oGAAA,CAAqG,CAAA,EAC9H,EAGA+B,IAAa,cAKZ,MAAA,CAAI,MAAM,KAAK,OAAO,KAAK,QAAQ,YAAY,KAAK,eAAe,cAAY,OAC9E,SAAA/B,EAAAA,IAAC,OAAA,CAAK,EAAE,2TAA2T,CAAA,CACrU,EAGA+B,IAAa,eAEZ,MAAA,CAAI,MAAM,KAAK,OAAO,KAAK,QAAQ,YAAY,KAAK,eAAe,cAAY,OAC9E,SAAA/B,EAAAA,IAAC,OAAA,CAAK,EAAE,yYAAyY,EACnZ,QAID,MAAA,CAAI,MAAM,KAAK,OAAO,KAAK,QAAQ,YAAY,KAAK,eAAe,cAAY,OAC9E,SAAAA,EAAAA,IAAC,OAAA,CAAK,EAAE,6MAA6M,EACvN,CAEJ,CC1sBO,SAASiH,GAAS,CACvB,MAAA7E,EACA,UAAA7D,EACA,KAAA+D,EACA,YAAA4E,EACA,OAAAC,EACA,SAAAC,EAAW,GACX,OAAAC,EAAS,SACX,EAAkB,CAChB,KAAM,CAAE,EAAAxG,CAAA,EAAMZ,EAAA,EACRoC,EAAoB,CACxB,UAAA9D,EACA,gBAAiB,KACjB,mBAAoB,IAAM,CAAC,EAC3B,SAAU,IAAM,CAAC,EACjB,KAAA+D,EACA,YAAA4E,CAAA,EASII,EACJD,IAAW,UACP,CACE,GAAGjF,EACH,QAASvB,EAAE,iCAAkC,mBAAmB,EAChE,WAAYA,EACV,oCACA,2CAAA,CACF,EAEFwG,IAAW,UACT,CACE,GAAGjF,EACH,QAASvB,EAAE,+BAAgC,kCAAkC,EAC7E,WAAYA,EACV,gCACA,yDAAA,EAMF,aAAcA,EAAE,cAAe,SAAS,CAAA,EAE1CuB,EAKR,OACEP,EAAAA,KAAC,MAAA,CAAI,MAAM,qDACR,SAAA,CAAAuF,EAAWpH,EAAAA,IAACuH,IAAgB,QAASJ,EAAQ,UAAWtG,EAAE,gBAAiB,MAAM,CAAA,CAAG,EAAK,KAC1Fb,EAAAA,IAACmC,GAAA,CAAU,MAAOmF,EAAgB,IAAAjF,CAAA,CAAU,CAAA,EAC9C,CAEJ,CAEA,SAASkF,GAAgB,CAAE,QAAAxB,EAAS,UAAAyB,GAAyD,CAC3F,OACExH,EAAAA,IAAC,SAAA,CACC,KAAK,SACL,QAAA+F,EACA,aAAYyB,EACZ,MAAM,wOAEN,SAAA3F,EAAAA,KAAC,MAAA,CAAI,MAAM,KAAK,OAAO,KAAK,QAAQ,YAAY,KAAK,OAAO,cAAY,OACtE,SAAA,CAAA7B,EAAAA,IAAC,OAAA,CACC,EAAE,yBACF,OAAO,eACP,eAAa,OACb,iBAAe,QACf,kBAAgB,OAAA,CAAA,EAElBA,EAAAA,IAAC,OAAA,CACC,EAAE,eACF,OAAO,eACP,eAAa,OACb,iBAAe,QACf,kBAAgB,OAAA,CAAA,CAClB,CAAA,CACF,CAAA,CAAA,CAGN,CCvFO,SAASyH,GAAS,CACvB,KAAAnF,EACA,UAAAoF,EACA,OAAAP,EACA,QAAAQ,EACA,YAAAC,CACF,EAAkB,CAChB,KAAM,CAAE,EAAA/G,CAAA,EAAMZ,EAAA,EACR4H,EAAkBF,GAAW9G,EAAE,uBAAwB,mBAAmB,EAC1EiH,EAAsBF,GAAe/G,EAAE,2BAA4B,gCAAgC,EACnG,CAACkH,EAAOC,CAAQ,EAAItI,EAAAA,SAAgB,CAAE,KAAM,aAAc,EAG1DuI,EAAWlH,EAAAA,OAAO,EAAI,EAC5BnB,EAAAA,UAAU,IACD,IAAM,CACXqI,EAAS,QAAU,EACrB,EACC,CAAA,CAAE,EAEL,MAAMC,EAAM,IAAY,CACtBF,EAAS,CAAE,KAAM,aAAc,GACzB,SAAY,CAChB,GAAI,CACF,MAAMzF,EAAU,MAAMD,EAAK,kBAAA,EAC3B,GAAI,CAAC2F,EAAS,QAAS,OACvBP,EAAUnF,CAAO,CACnB,OAASpB,EAAG,CACV,GAAI,CAAC8G,EAAS,QAAS,OACvBD,EAAS,CACP,KAAM,QACN,QAAS7G,aAAa,MAAQA,EAAE,QAAU,0BAAA,CAC3C,CACH,CACF,GAAA,CACF,EAIAvB,OAAAA,EAAAA,UAAU,IAAM,CACdsI,EAAA,CAEF,EAAG,CAAA,CAAE,EAGHrG,EAAAA,KAAC,MAAA,CAAI,MAAM,sBACR,SAAA,CAAAsF,EACCnH,EAAAA,IAAC,SAAA,CACC,KAAK,SACL,QAASmH,EACT,MAAM,oNAEL,SAAAtG,EAAE,WAAY,QAAQ,CAAA,CAAA,EAEvB,KAEJgB,EAAAA,KAAC,MAAA,CAAI,MAAM,sBACT,SAAA,CAAA7B,EAAAA,IAAC,KAAA,CAAG,MAAM,sCAAuC,SAAA6H,EAAgB,EACjE7H,EAAAA,IAAC,IAAA,CAAE,MAAM,wBAAyB,SAAA8H,CAAA,CAAoB,CAAA,EACxD,EAECC,EAAM,OAAS,aACd/H,EAAAA,IAAC,MAAA,CAAI,MAAM,wCACT,SAAAA,EAAAA,IAACmI,GAAA,CAAA,CAAQ,CAAA,CACX,EACE,KAEHJ,EAAM,OAAS,QACdlG,EAAAA,KAAC,MAAA,CAAI,MAAM,sBACT,SAAA,CAAA7B,EAAAA,IAAC,MAAA,CAAI,MAAM,sDACR,SAAA+H,EAAM,QACT,EACA/H,EAAAA,IAAC,SAAA,CACC,KAAK,SACL,QAASkI,EACT,MAAM,oNAEL,SAAArH,EAAE,iBAAkB,WAAW,CAAA,CAAA,CAClC,CAAA,CACF,EACE,IAAA,EACN,CAEJ,CAEA,SAASsH,IAAU,CACjB,cACG,MAAA,CAAI,MAAM,+CAA+C,QAAQ,YAAY,KAAK,OACjF,SAAA,CAAAnI,EAAAA,IAAC,SAAA,CAAO,GAAG,KAAK,GAAG,KAAK,EAAE,KAAK,OAAO,eAAe,eAAa,IAAI,iBAAe,MAAM,EAC3FA,EAAAA,IAAC,QAAK,EAAE,2BAA2B,OAAO,eAAe,eAAa,IAAI,iBAAe,OAAA,CAAQ,CAAA,EACnG,CAEJ,CClHA,MAAMoI,GAAeC,GAA4B,YAAYA,CAAO,SAUpE,SAASC,EAAaC,EAAyB,CAC7C,MAAMC,EAAWD,EAAQ,KAAK,IAAA,EAC9B,OAAIC,GAAY,EACP,CAAE,KAAM,EAAG,MAAO,EAAG,QAAS,EAAG,QAAS,EAAG,QAAS,EAAA,EAExD,CACL,KAAM,KAAK,MAAMA,GAAY,IAAO,GAAK,GAAK,GAAG,EACjD,MAAO,KAAK,MAAOA,GAAY,IAAO,GAAK,GAAK,KAAQ,IAAO,GAAK,GAAG,EACvE,QAAS,KAAK,MAAOA,GAAY,IAAO,GAAK,KAAQ,IAAO,GAAG,EAC/D,QAAS,KAAK,MAAOA,GAAY,IAAO,IAAO,GAAI,EACnD,QAAS,EAAA,CAEb,CAKA,SAASC,GAAaC,EAAoC,CACxD,GAAIA,EAAM,WAAY,CACpB,MAAM7H,EAAI,KAAK,MAAM6H,EAAM,UAAU,EACrC,OAAO,OAAO,SAAS7H,CAAC,EAAIA,EAAI,IAClC,CACA,GAAI6H,EAAM,kBAAoBA,EAAM,iBAAmB,EAAG,CACxD,GAAI,OAAO,OAAW,IAAa,OAAO,KAC1C,GAAI,CACF,MAAMrK,EAAM+J,GAAYM,EAAM,EAAE,EAChC,IAAIC,EAAW,OAAO,aAAa,QAAQtK,CAAG,EAC9C,OAAKsK,IACHA,EAAW,IAAI,KAAA,EAAO,YAAA,EACtB,OAAO,aAAa,QAAQtK,EAAKsK,CAAQ,GAEpC,KAAK,MAAMA,CAAQ,EAAID,EAAM,iBAAmB,GACzD,MAAQ,CAEN,OAAO,IACT,CACF,CACA,OAAO,IACT,CAEO,SAASE,GACdC,EACAC,EACqB,CACrB,GAAI,CAACD,GAAUA,EAAO,SAAW,EAAG,OAAO,KAC3C,GAAIC,EAAa,CACf,MAAMC,EAAQF,EAAO,KAAMG,GAAMA,EAAE,KAAOF,CAAW,EACrD,GAAIC,EAAO,OAAOA,CACpB,CAGA,OAAOF,EAAO,KAAMG,GAAMA,EAAE,YAAcA,EAAE,gBAAgB,GAAK,IACnE,CAKO,SAASC,GAAkBP,EAA6C,CAC7E,MAAMH,EAAQG,EAAQD,GAAaC,CAAK,EAAI,KACtC,CAACQ,EAAUC,CAAW,EAAIzJ,EAAAA,SAA0B,IACxD6I,IAAU,KAAOD,EAAaC,CAAK,EAAI,IAAA,EAEnCa,EAAWrI,EAAAA,OAAOwH,CAAK,EAC7B,OAAAa,EAAS,QAAUb,EAEnB3I,EAAAA,UAAU,IAAM,CACd,GAAI2I,IAAU,KAAM,CAClBY,EAAY,IAAI,EAChB,MACF,CACAA,EAAYb,EAAaC,CAAK,CAAC,EAC/B,MAAMc,EAAQ,YAAY,IAAM,CAC9B,MAAMhF,EAAOiE,EAAac,EAAS,SAAW,CAAC,EAE/C,GADAD,EAAY9E,CAAI,EACZA,EAAK,UACP,cAAcgF,CAAK,EACfX,GAAO,kBAAoB,OAAO,OAAW,KAC/C,GAAI,CACF,OAAO,aAAa,WAAWN,GAAYM,EAAM,EAAE,CAAC,CACtD,MAAQ,CAER,CAGN,EAAG,GAAI,EACP,MAAO,IAAM,cAAcW,CAAK,CAClC,EAAG,CAACd,EAAOG,GAAO,iBAAkBA,GAAO,EAAE,CAAC,EAEvCQ,CACT,CAEO,SAASI,GAAY,CAAE,MAAAlH,EAAO,IAAAC,GAAqC,CACxE,KAAM,CAAE,CAAA,EAAMpC,EAAA,EACRyI,EAAQE,GAAgBvG,EAAI,UAAU,OAAQD,EAAM,QAAQ,EAC5D8G,EAAWD,GAAkBP,CAAK,EAGxC,GADI,CAACA,GAASQ,IAAa,MACvBA,EAAS,SAAW,CAAC9G,EAAM,MAAO,OAAO,KAE7C,MAAMwD,EAAQxD,EAAM,OAASsG,EAAM,OAAS,EAAE,qBAAsB,oBAAoB,EAClFa,EAAoBb,EAAM,iBAC5B,GAAG9C,CAAK,IAAI8C,EAAM,gBAAgB,IAClC9C,EAEJ,OACE/D,EAAAA,KAAC,MAAA,CACC,MAAM,4HACN,MAAO,CACL,WACE,0JACF,WAAY,6BAAA,EAEd,KAAK,SAEL,SAAA,CAAA7B,EAAAA,IAACwJ,GAAA,EAAU,EACXxJ,EAAAA,IAAC,QAAM,SAAAuJ,CAAA,CAAkB,EACzBvJ,EAAAA,IAACyJ,GAAA,CAAU,MAAOP,EAAU,CAAA,CAAM,CAAA,CAAA,CAAA,CAGxC,CAEO,SAASO,GAAU,CAAE,MAAA1J,EAAO,EAAAc,GAAkC,CACnE,OACEgB,EAAAA,KAAC,MAAA,CAAI,MAAM,4CACR,SAAA,CAAA9B,EAAM,KAAO,EACZ8B,EAAAA,KAAAqD,EAAAA,SAAA,CACE,SAAA,CAAAlF,EAAAA,IAAC0J,EAAA,CAAM,SAAA,OAAO3J,EAAM,IAAI,EAAE,QACzB,OAAA,CAAK,MAAM,UAAW,SAAAc,EAAE,cAAe,GAAG,CAAA,CAAE,CAAA,CAAA,CAC/C,EACE,KACJb,EAAAA,IAAC0J,GAAM,SAAA,OAAO3J,EAAM,KAAK,EAAE,SAAS,EAAG,GAAG,CAAA,CAAE,QAC3C,OAAA,CAAK,MAAM,UAAW,SAAAc,EAAE,cAAe,GAAG,EAAE,EAC7Cb,EAAAA,IAAC0J,GAAM,SAAA,OAAO3J,EAAM,OAAO,EAAE,SAAS,EAAG,GAAG,CAAA,CAAE,QAC7C,OAAA,CAAK,MAAM,UAAW,SAAAc,EAAE,cAAe,GAAG,EAAE,EAC7Cb,EAAAA,IAAC0J,GAAM,SAAA,OAAO3J,EAAM,OAAO,EAAE,SAAS,EAAG,GAAG,CAAA,CAAE,QAC7C,OAAA,CAAK,MAAM,UAAW,SAAAc,EAAE,cAAe,GAAG,CAAA,CAAE,CAAA,EAC/C,CAEJ,CAEA,SAAS6I,EAAK,CAAE,SAAAnK,GAAoD,CAClE,OACES,EAAAA,IAAC,OAAA,CAAK,MAAM,sDACT,SAAAT,CAAA,CACH,CAEJ,CAKO,SAASoK,GAAe,CAAE,MAAAjB,GAAkC,CACjE,KAAM,CAAE,EAAA7H,CAAA,EAAMZ,EAAA,EACRiJ,EAAWD,GAAkBP,CAAK,EACxC,GAAIQ,IAAa,MAAQA,EAAS,QAAS,OAAO,KAClD,MAAMtD,EAAQ8C,EAAM,OAAS7H,EAAE,qBAAsB,oBAAoB,EACnE0I,EAAoBb,EAAM,iBAC5B,GAAG9C,CAAK,IAAI8C,EAAM,gBAAgB,IAClC9C,EACJ,OACE/D,EAAAA,KAAC,MAAA,CACC,MAAM,wIACN,MAAO,CACL,WACE,0JACF,WAAY,6BAAA,EAEd,KAAK,SAEL,SAAA,CAAA7B,EAAAA,IAACwJ,GAAA,EAAU,EACXxJ,EAAAA,IAAC,QAAM,SAAAuJ,CAAA,CAAkB,EACzBvJ,EAAAA,IAACyJ,GAAA,CAAU,MAAOP,EAAU,EAAArI,CAAA,CAAM,CAAA,CAAA,CAAA,CAGxC,CAEA,SAAS2I,IAAY,CACnB,OACExJ,EAAAA,IAAC,MAAA,CACC,MAAM,KACN,OAAO,KACP,QAAQ,YACR,KAAK,OACL,cAAY,OAEZ,SAAAA,EAAAA,IAAC,OAAA,CACC,KAAK,eACL,EAAE,iLAAA,CAAA,CACJ,CAAA,CAGN,CCnMA,MAAM4J,EAAc,EACdC,EAAc,IACdC,EAAc,IACdC,GAAY,EACZC,GAAgB,GAAK,KAAO,KAC5BC,GAAgB,CAAC,aAAc,YAAa,YAAY,EACxDC,GAAW,YAEV,SAASC,GAAY,CAAE,OAAAC,EAAQ,YAAAlD,EAAa,OAAAmD,EAAQ,OAAAlD,GAA4B,CACrF,KAAM,CAAE,EAAAtG,CAAA,EAAMZ,EAAA,EACRqK,EAAepD,GAAa,KAAK,OAAS,GAE1CqD,EAAcD,GAA8B,KAC5C,CAACxH,EAAOI,CAAQ,EAAIxD,EAAAA,SAAiB4K,CAAY,EACjD,CAACE,EAASC,CAAU,EAAI/K,EAAAA,SAAS,EAAE,EACnC,CAACgL,EAASC,CAAU,EAAIjL,EAAAA,SAAS,EAAE,EACnC,CAACkL,EAAOC,CAAQ,EAAInL,EAAAA,SAAiB,CAAA,CAAE,EACvC,CAACoL,EAAYC,CAAa,EAAIrL,EAAAA,SAAS,EAAK,EAC5C,CAACsL,EAAgBC,CAAiB,EAAIvL,EAAAA,SAAwB,IAAI,EAClE,CAACwL,EAAQC,CAAS,EAAIzL,EAAAA,SAMzB,CAAA,CAAE,EAEC0L,EAAUC,EAAAA,QAAQ,IAAM,CAC5B,MAAMlK,GAAKoJ,GAAezH,GAAO,KAAA,EAAO,YAAA,EAClChF,EAAI0M,EAAQ,KAAA,EACZc,EAAIZ,EAAQ,KAAA,EAClB,OACER,GAAS,KAAK/I,CAAC,GACfrD,EAAE,QAAU8L,GACZ9L,EAAE,QAAU+L,GACZyB,EAAE,QAAU,GACZA,EAAE,QAAUxB,CAEhB,EAAG,CAACS,EAAazH,EAAO0H,EAASE,CAAO,CAAC,EAEnCa,EAAW,IAAe,CAC9B,MAAMlH,EAAsB,CAAA,EACtBlD,GAAKoJ,GAAezH,GAAO,KAAA,EAC3BhF,EAAI0M,EAAQ,KAAA,EACZc,EAAIZ,EAAQ,KAAA,EAClB,OAAKvJ,EACK+I,GAAS,KAAK/I,EAAE,YAAA,CAAa,IAAGkD,EAAK,MAAQxD,EAAE,wBAAyB,eAAe,GADzFwD,EAAK,MAAQxD,EAAE,mBAAoB,UAAU,GAEjD/C,EAAE,OAAS8L,GAAe9L,EAAE,OAAS+L,KACvCxF,EAAK,QAAUxD,EAAE,yBAA0B,yBAA0B,CACnE,IAAK+I,EACL,IAAKC,CAAA,CACN,IAECyB,EAAE,OAAS,GAAKA,EAAE,OAASxB,KAC7BzF,EAAK,QAAUxD,EAAE,yBAA0B,yBAA0B,CACnE,IAAK,EACL,IAAKiJ,CAAA,CACN,GAEHqB,EAAU9G,CAAI,EACP,OAAO,KAAKA,CAAI,EAAE,SAAW,CACtC,EAEMC,EAAW,MAAOnD,GAA4B,CAElD,GADAA,EAAE,eAAA,EACE,CAAA2J,GACCS,IACL,CAAAR,EAAc,EAAI,EAClBI,EAAWK,IAAU,CAAE,GAAGA,EAAM,OAAQ,QAAY,EACpD,GAAI,CACF,MAAMC,GAAclB,GAAezH,GAAO,KAAA,EAC1C,MAAMsH,EAAO,oBAAoB,CAC/B,QAASI,EAAQ,KAAA,EACjB,QAASE,EAAQ,KAAA,EACjB,MAAOe,GAAc,OACrB,MAAOb,EAAM,OAAS,EAAIA,EAAQ,MAAA,CACnC,EACDK,EAAkBQ,CAAU,CAC9B,OAAStM,EAAK,CACZ,MAAMuM,EACJvM,aAAe+C,EAAAA,cACX/C,EAAI,SAAW,oCAErBgM,EAAWK,IAAU,CAAE,GAAGA,EAAM,OAAQE,GAAM,CAChD,QAAA,CACEX,EAAc,EAAK,CACrB,EACF,EAEMY,EAAY,IAAY,CAC5BlB,EAAW,EAAE,EACbE,EAAW,EAAE,EACbE,EAAS,CAAA,CAAE,EACXM,EAAU,CAAA,CAAE,EACZF,EAAkB,IAAI,CACxB,EAKMW,EAAc,sDACdC,EAAc,CAAE,UAAW,sCAAA,EAEjC,OAAIb,EAEAnJ,EAAAA,KAAC,MAAA,CAAI,MAAM,wCACT,SAAA,CAAAA,EAAAA,KAAC,MAAA,CAAI,MAAM,qHACT,SAAA,CAAA7B,EAAAA,IAAC,MAAA,CACC,MAAM,0DACN,MAAO,CACL,WACE,6FACF,MAAO,OACP,UACE,wIAAA,EAEJ,cAAY,OAEZ,SAAAA,EAAAA,IAAC,MAAA,CAAI,QAAQ,YAAY,MAAM,UAC7B,SAAAA,EAAAA,IAAC,OAAA,CACC,KAAK,eACL,EAAE,6JAAA,CAAA,CACJ,CACF,CAAA,CAAA,QAED,MAAA,CAAI,MAAM,qDACR,SAAAa,EAAE,0BAA2B,mBAAmB,EACnD,EACAgB,EAAAA,KAAC,MAAA,CAAI,MAAM,sDAGR,SAAA,CAAAhB,EACC,iCACA,iDAAA,EACC,IACHb,EAAAA,IAAC,IAAA,CAAE,MAAM,gBAAiB,SAAAgL,EAAe,EAAI,GAAA,CAAA,CAC/C,CAAA,EACF,EACAhL,EAAAA,IAAC,OAAI,MAAO4L,EAAa,MAAOC,EAC9B,SAAAhK,EAAAA,KAAC,MAAA,CAAI,MAAM,yCACT,SAAA,CAAA7B,EAAAA,IAAC,SAAA,CACC,KAAK,SACL,QAASmH,EACT,MAAM,2KAEL,SAAAkD,IAAW,aACRxJ,EAAE,sBAAuB,MAAM,EAC/BA,EAAE,gBAAiB,MAAM,CAAA,CAAA,EAE/Bb,EAAAA,IAAC,SAAA,CACC,KAAK,SACL,QAAS2L,EACT,MAAM,6PACN,MAAO,CACL,WACE,6FACF,UACE,sGAAA,EAGH,SAAA9K,EAAE,uBAAwB,sBAAsB,CAAA,CAAA,CACnD,CAAA,CACF,CAAA,CACF,CAAA,EACF,EAKFgB,EAAAA,KAAC,OAAA,CAAK,SAAAyC,EAAoB,MAAM,wCAC9B,SAAA,CAAAtE,MAACuH,IAAgB,QAASJ,EAAQ,UAAWtG,EAAE,gBAAiB,MAAM,EAAG,QACxE,MAAA,CAAI,MAAM,wEACT,SAAAgB,EAAAA,KAAC,MAAA,CAAI,MAAM,sBACT,SAAA,CAAAA,EAAAA,KAAC,MAAA,CAAI,MAAM,4BACT,SAAA,CAAA7B,MAAC,MAAG,MAAM,kDACP,SAAAa,EAAE,kBAAmB,SAAS,EACjC,QACC,IAAA,CAAE,MAAM,0CACN,SAAAA,EAAE,sBAAuB,gEAAgE,CAAA,CAC5F,CAAA,EACF,EAEAgB,EAAAA,KAAC,MAAA,CAAI,MAAM,sBACR,SAAA,CAAC0I,EAWA1I,EAAAA,KAAC,MAAA,CAAI,MAAM,0DACR,SAAA,CAAAhB,EAAE,qBAAsB,YAAY,EAAG,IACxCb,EAAAA,IAAC,IAAA,CAAE,MAAM,4BAA6B,SAAAuK,CAAA,CAAY,CAAA,EACpD,EAbAvK,EAAAA,IAACgF,GAAA,CACC,KAAK,QACL,YAAanE,EAAE,4BAA6B,oBAAoB,EAChE,MAAOiC,EACP,QAASI,EACT,MAAOgI,EAAO,MACd,aAAa,QACb,SAAQ,EAAA,CAAA,EAQZlL,EAAAA,IAACgF,GAAA,CACC,KAAK,OACL,YAAanE,EAAE,8BAA+B,sBAAsB,EACpE,MAAO2J,EACP,QAASC,EACT,MAAOS,EAAO,QACd,SAAQ,EAAA,CAAA,EAEVlL,EAAAA,IAAC8L,GAAA,CACC,YAAajL,EAAE,8BAA+B,sBAAsB,EACpE,MAAO6J,EACP,QAASC,EACT,MAAOO,EAAO,QACd,SAAQ,EAAA,CAAA,QAETa,GAAA,CAAS,MAAAnB,EAAc,SAAUC,EAAU,SAAUC,CAAA,CAAY,CAAA,CAAA,CACpE,CAAA,CAAA,CACF,CAAA,CACF,EAEAjJ,EAAAA,KAAC,MAAA,CAAI,MAAO+J,EAAa,MAAOC,EAC7B,SAAA,CAAAX,EAAO,QAAUlL,EAAAA,IAAC,IAAA,CAAE,MAAM,uBAAwB,WAAO,OAAO,EACjE6B,EAAAA,KAAC,MAAA,CAAI,MAAM,sCACT,SAAA,CAAA7B,EAAAA,IAAC,SAAA,CACC,KAAK,SACL,QAASmH,EACT,SAAU2D,EACV,MAAM,+NAEL,SAAAT,IAAW,aACRxJ,EAAE,uBAAwB,OAAO,EACjCA,EAAE,gBAAiB,MAAM,CAAA,CAAA,EAE/Bb,EAAAA,IAAC,SAAA,CACC,KAAK,SACL,SAAU,CAACoL,GAAWN,EACtB,MAAM,qVACN,MAAO,CACL,WACE,0JACF,UACE,8HAAA,EAGH,SAAAA,EACC9K,MAAC,OAAA,CAAK,MAAM,sGAAA,CAAuG,EAEnHA,EAAAA,IAAC,OAAA,CAAK,MAAM,gBAAiB,SAAAa,EAAE,sBAAuB,MAAM,CAAA,CAAE,CAAA,CAAA,CAElE,CAAA,CACF,CAAA,CAAA,CACF,CAAA,EACF,CAEJ,CAEA,SAAS0G,GAAgB,CAAE,QAAAxB,EAAS,UAAAyB,GAAyD,CAC3F,OACExH,EAAAA,IAAC,SAAA,CACC,KAAK,SACL,QAAA+F,EACA,aAAYyB,EACZ,MAAM,wOAEN,SAAA3F,EAAAA,KAAC,MAAA,CAAI,MAAM,KAAK,OAAO,KAAK,QAAQ,YAAY,KAAK,OAAO,cAAY,OACtE,SAAA,CAAA7B,EAAAA,IAAC,OAAA,CACC,EAAE,yBACF,OAAO,eACP,eAAa,OACb,iBAAe,QACf,kBAAgB,OAAA,CAAA,EAElBA,EAAAA,IAAC,OAAA,CACC,EAAE,eACF,OAAO,eACP,eAAa,OACb,iBAAe,QACf,kBAAgB,OAAA,CAAA,CAClB,CAAA,CACF,CAAA,CAAA,CAGN,CAYA,SAASgF,GAAY,CACnB,KAAAiB,EACA,YAAAC,EACA,MAAAnG,EACA,QAAAoG,EACA,MAAAxC,EACA,aAAAyC,EACA,SAAAE,CACF,EAAqB,CACnB,cACG,MAAA,CACC,SAAA,CAAAtG,EAAAA,IAAC,QAAA,CACC,KAAAiG,EACA,MAAAlG,EACA,YAAAmG,EACA,QAAU/E,GAAMgF,EAAShF,EAAE,OAA4B,KAAK,EAC5D,aAAAiF,EACA,SAAAE,EACA,MAAO,oKACL3C,EACI,yCACA,8EACN,EAAA,CAAA,EAEDA,GAAS3D,EAAAA,IAAC,OAAA,CAAK,MAAM,uCAAwC,SAAA2D,CAAA,CAAM,CAAA,EACtE,CAEJ,CAUA,SAASmI,GAAe,CACtB,YAAA5F,EACA,MAAAnG,EACA,QAAAoG,EACA,MAAAxC,EACA,SAAA2C,CACF,EAAwB,CACtB,cACG,MAAA,CACC,SAAA,CAAAtG,EAAAA,IAAC,WAAA,CACC,MAAAD,EACA,YAAAmG,EACA,QAAU/E,GAAMgF,EAAShF,EAAE,OAA+B,KAAK,EAC/D,SAAAmF,EACA,KAAM,EACN,MAAO,oMACL3C,EACI,yCACA,8EACN,EAAA,CAAA,EAEDA,GAAS3D,EAAAA,IAAC,OAAA,CAAK,MAAM,uCAAwC,SAAA2D,CAAA,CAAM,CAAA,EACtE,CAEJ,CAQA,SAASoI,GAAS,CAAE,MAAAnB,EAAO,SAAAoB,EAAU,SAAAC,GAA2B,CAC9D,KAAM,CAAE,EAAApL,CAAA,EAAMZ,EAAA,EACRwG,EAAW1F,EAAAA,OAAgC,IAAI,EAC/C,CAACmL,EAAUC,CAAW,EAAIzM,EAAAA,SAAS,EAAK,EACxC,CAACiE,EAAOC,CAAQ,EAAIlE,EAAAA,SAAwB,IAAI,EAEhD0M,EAAeC,GAAoC,CACvD,GAAI,CAACA,GAAYJ,EAAU,OAC3BrI,EAAS,IAAI,EACb,MAAM0I,EAAM,MAAM,KAAKD,CAAQ,EAC/B,GAAIzB,EAAM,OAAS0B,EAAI,OAASvC,GAAW,CACzCnG,EAAS/C,EAAE,yBAA0B,oBAAqB,CAAE,IAAKkJ,EAAA,CAAW,CAAC,EAC7E,MACF,CACA,MAAMwC,EAAQD,EAAI,OACfE,GAAMvC,GAAc,SAASuC,EAAE,IAAI,GAAKA,EAAE,MAAQxC,EAAA,EAErD,GAAIuC,EAAM,SAAWD,EAAI,OAAQ,CAC/B1I,EAAS/C,EAAE,uBAAwB,iCAAiC,CAAC,EACrE,MACF,CACAmL,EAAS,CAAC,GAAGpB,EAAO,GAAG2B,CAAK,CAAC,CAC/B,EAEA,cACG,MAAA,CACC,SAAA,CAAAvM,MAAC,QAAK,MAAM,oCACT,SAAAa,EAAE,4BAA6B,wBAAwB,EAC1D,EACAgB,EAAAA,KAAC,MAAA,CACC,KAAK,SACL,SAAU,EACV,aAAYhB,EAAE,2BAA4B,oBAAoB,EAC9D,QAAS,IAAM,CAACoL,GAAYxF,EAAS,SAAS,MAAA,EAC9C,WAAatF,GAAM,CACjBA,EAAE,eAAA,EACG8K,GAAUE,EAAY,EAAI,CACjC,EACA,YAAa,IAAMA,EAAY,EAAK,EACpC,OAAShL,GAAM,CACbA,EAAE,eAAA,EACFgL,EAAY,EAAK,EACjBC,EAAYjL,EAAE,cAAc,OAAS,IAAI,CAC3C,EACA,MAAO,2FACL+K,EACI,8EACA,2DACN,IAAID,EAAW,gCAAkC,EAAE,GAEnD,SAAA,CAAAjM,MAAC,OAAI,MAAM,wBACR,SAAAa,EAAE,wBAAyB,qCAAqC,EACnE,QACC,MAAA,CAAI,MAAM,mCACR,SAAAA,EAAE,4BAA6B,gDAAiD,CAC/E,IAAKkJ,EAAA,CACN,CAAA,CACH,CAAA,CAAA,CAAA,EAEF/J,EAAAA,IAAC,QAAA,CACC,IAAKyG,EACL,KAAK,OACL,SAAQ,GACR,OAAQwD,GAAc,KAAK,GAAG,EAC9B,MAAM,SACN,SAAW9I,GAAM,CACfiL,EAAajL,EAAE,OAA4B,KAAK,EAC/CA,EAAE,cAAmC,MAAQ,EAChD,CAAA,CAAA,EAEDwC,GAAS3D,EAAAA,IAAC,IAAA,CAAE,MAAM,4BAA6B,SAAA2D,EAAM,EACrDiH,EAAM,OAAS,GACd5K,EAAAA,IAAC,KAAA,CAAG,MAAM,2BACP,SAAA4K,EAAM,IAAI,CAAC4B,EAAGC,IACb5K,EAAAA,KAAC,KAAA,CAEC,MAAM,+EAEN,SAAA,CAAA7B,EAAAA,IAAC,OAAA,CAAK,MAAM,yBAA0B,SAAAwM,EAAE,KAAK,EAC7CxM,EAAAA,IAAC,SAAA,CACC,KAAK,SACL,QAAS,IAAM,CACb,MAAMqE,EAAO,CAAC,GAAGuG,CAAK,EACtBvG,EAAK,OAAOoI,EAAG,CAAC,EAChBT,EAAS3H,CAAI,CACf,EACA,SAAA4H,EACA,MAAM,mFACN,aAAYpL,EAAE,2BAA4B,oBAAqB,CAAE,SAAU2L,EAAE,KAAM,EACpF,SAAA,GAAA,CAAA,CAED,CAAA,EAhBK,GAAGA,EAAE,IAAI,IAAIA,EAAE,IAAI,IAAIC,CAAC,EAAA,CAkBhC,CAAA,CACH,CAAA,EAEJ,CAEJ,CChdA,MAAMC,GAA4C,CAChD,IAAK,qBACL,KAAM,sBACN,MAAO,uBACP,KAAM,qBACR,EACMC,GAAiD,CACrD,IAAK,iBACL,KAAM,kBACN,MAAO,mBACP,KAAM,iBACR,EAUA,SAASC,GACPC,EACAC,EACAC,EACAlM,EACQ,CACR,GAAIiM,IAAW,QAAS,OAAOjM,EAAE,YAAa,OAAO,EACrD,GAAI,CAACgM,EAAO,OAAOhM,EAAE,eAAgB,UAAU,EAC/C,GACE,CAACkM,GACDF,EAAM,YACNA,EAAM,UACNA,EAAM,WAAa,WAEnB,OAAOhM,EAAE,kBAAmB,8BAA+B,CAAE,KAAMgM,EAAM,WAAY,EAEvF,GAAI,CAACA,EAAM,UAAYA,EAAM,WAAa,WACxC,OAAOhM,EAAE,0BAA2B,qBAAqB,EAE3D,MAAMmM,EAAeN,GAAkBG,EAAM,QAAQ,EACrD,OAAIG,EACKnM,EAAEmM,EAAcL,GAAuBE,EAAM,QAAQ,CAAC,EAExDhM,EAAE,uBAAwB,sBAAuB,CACtD,SAAUoM,GAAWJ,EAAM,QAAQ,CAAA,CACpC,CACH,CAEA,SAASI,GAAWnP,EAAmB,CACrC,OAAOA,EAAE,OAASA,EAAE,CAAC,EAAE,cAAgBA,EAAE,MAAM,CAAC,EAAIA,CACtD,CAEO,SAASoP,GAAU,CAAE,MAAA9K,EAAO,IAAAC,GAA6B,CAC9D,KAAM,CAAE,CAAA,EAAMpC,EAAA,EACR,CAACwD,EAAMC,CAAO,EAAIhE,EAAAA,SAAS,EAAK,EAChCyN,EAAU/K,EAAM,SAAWC,EAAI,gBAC/B4J,EAAWxI,GAASrB,EAAM,SAAW,YAAc,CAAC+K,EAEpDC,EAAgBD,EAClB9K,EAAI,UAAU,OAAO,KAAMuC,GAAMA,EAAE,KAAOuI,CAAO,GAAK,KACtD,KAMEJ,EAAmB1K,EAAI,UAAU,MAAM,oBAAsB,GAC7D2D,EACJ5D,EAAM,OAASwK,GAAaQ,EAAehL,EAAM,OAAQ2K,EAAkB,CAAC,EAExEhH,EAAU,SAAY,CAC1B,GAAI,CAAAkG,EACJ,CAAAvI,EAAQ,EAAI,EACZ,GAAI,CACF,MAAMrB,EAAI,SAASD,EAAM,OAAQ,CAAE,QAAA+K,EAAS,CAC9C,QAAA,CACEzJ,EAAQ,EAAK,CACf,EACF,EAEA,OACE7B,EAAAA,KAAC,SAAA,CACC,KAAK,SACL,SAAAoK,EACA,QAAAlG,EACA,MAAM,8XACN,MAAO,CACL,WACE,0JACF,UACE,8HAAA,EAGJ,SAAA,CAAA/F,EAAAA,IAAC,OAAA,CACC,MAAM,8BACN,MAAO,CACL,WACE,mGAAA,EAEJ,cAAY,MAAA,CAAA,EAEbyD,EACCzD,EAAAA,IAAC,OAAA,CAAK,MAAM,sGAAA,CAAuG,EAEnHA,EAAAA,IAAC,OAAA,CAAK,MAAM,gBAAiB,SAAAgG,CAAA,CAAM,CAAA,CAAA,CAAA,CAI3C,CCzGO,SAASqH,GAAe,CAAE,IAAAhL,GAAwC,CACvE,KAAM,CAAE,EAAAxB,CAAA,EAAMZ,EAAA,EACRsC,EAAUF,EAAI,YACdC,EAAOD,EAAI,KACX,CAACiL,EAAYC,CAAa,EAAI7N,EAAAA,SAAS,EAAK,EAE5C8N,EAAY,IAAYnL,EAAI,SAAS,SAAS,EAEpD,GAAIE,GAAW,CAACA,EAAQ,KAAK,aAAc,CACzC,MAAMQ,EAAY,SAA2B,CAC3C,GAAI,GAACT,GAAQgL,GACb,CAAAC,EAAc,EAAI,EAClB,GAAI,CACF,MAAMjL,EAAK,QAAA,CACb,MAAQ,CAER,QAAA,CACEiL,EAAc,EAAK,CACrB,EACF,EAKA,OACE1L,EAAAA,KAAC,MAAA,CAAI,MAAM,sFACT,SAAA,CAAAA,OAAC,OAAA,CACE,SAAA,CAAAhB,EAAE,8BAA+B,cAAc,EAAG,UAClD,IAAA,CAAE,MAAM,4BAA6B,SAAA0B,EAAQ,KAAK,KAAA,CAAM,CAAA,EAC3D,EACAV,EAAAA,KAAC,MAAA,CAAI,MAAM,yCACT,SAAA,CAAA7B,EAAAA,IAACmF,EAAA,CAAW,QAASpC,EAAW,SAAU,CAACT,GAAQgL,EAChD,SAAAA,EACGzM,EAAE,sBAAuB,cAAc,EACvCA,EAAE,mBAAoB,UAAU,EACtC,QACC4M,GAAA,EAAI,QACJtI,EAAA,CAAW,QAASqI,EAClB,SAAA3M,EAAE,0BAA2B,iBAAiB,CAAA,CACjD,CAAA,CAAA,CACF,CAAA,EACF,CAEJ,CAEA,OACEgB,EAAAA,KAAC,MAAA,CAAI,MAAM,4EACT,SAAA,CAAA7B,EAAAA,IAACmF,EAAA,CAAW,QAAS,IAAM9C,EAAI,SAAS,SAAS,EAC9C,SAAAxB,EAAE,4BAA6B,mBAAmB,CAAA,CACrD,QACC4M,GAAA,EAAI,QACJtI,EAAA,CAAW,QAASqI,EAAY,SAAA3M,EAAE,0BAA2B,iBAAiB,CAAA,CAAE,CAAA,EACnF,CAEJ,CAEA,SAASsE,EAAW,CAClB,QAAAY,EACA,SAAAkG,EACA,SAAA1M,CACF,EAIG,CACD,OACES,EAAAA,IAAC,SAAA,CACC,KAAK,SACL,QAAA+F,EACA,SAAAkG,EACA,MAAM,gJACN,MAAO,CAAE,MAAO,kBAAA,EAEf,SAAA1M,CAAA,CAAA,CAGP,CAEA,SAASkO,IAAM,CACb,OAAOzN,EAAAA,IAAC,OAAA,CAAK,MAAM,mCAAmC,cAAY,OAAO,CAC3E,CC3FO,SAAS0N,GAAa,CAAE,MAAAtL,GAAwC,CACrE,OAAKA,EAAM,MAAM,OAEfpC,EAAAA,IAAC,KAAA,CAAG,MAAM,wBAAwB,KAAK,OACpC,SAAAoC,EAAM,MAAM,IAAKuL,GAChB9L,EAAAA,KAAC,KAAA,CAAiB,MAAM,+CACtB,SAAA,CAAA7B,EAAAA,IAAC,MAAA,CACC,MAAM,KACN,OAAO,KACP,QAAQ,YACR,KAAK,OACL,MAAM,wCACN,cAAY,OAEZ,SAAAA,EAAAA,IAAC,OAAA,CACC,EAAE,0BACF,OAAO,eACP,eAAa,MACb,iBAAe,QACf,kBAAgB,OAAA,CAAA,CAClB,CAAA,EAEF6B,EAAAA,KAAC,MAAA,CAAI,MAAM,wBACT,SAAA,CAAA7B,EAAAA,IAAC,OAAA,CAAK,MAAM,yCAA0C,SAAA2N,EAAK,KAAK,EAC/DA,EAAK,KACJ3N,MAAC,OAAA,CAAK,MAAM,wCAAyC,SAAA2N,EAAK,KAAK,EAC7D,IAAA,CAAA,CACN,CAAA,CAAA,EAtBOA,EAAK,EAuBd,CACD,EACH,EA7B8B,IA+BlC,CCzBO,SAASC,GAAe,CAAE,MAAAxL,GAAqC,CACpE,KAAM,CAAE,EAAAvB,CAAA,EAAMZ,EAAA,EACR2F,EAAQxD,EAAM,OAASvB,EAAE,qBAAsB,6BAA6B,EAC5EgF,EAAWzD,EAAM,SACjByL,GAAYzL,EAAM,MAAQ,mBAAqB,OAI/C0L,EAAQC,GAAgBnI,CAAK,EAEnC,OACE/D,EAAAA,KAAC,MAAA,CAAI,MAAM,0EACT,SAAA,CAAAA,EAAAA,KAAC,MAAA,CAAI,MAAM,2DACR,SAAA,CAAAgM,EAAW7N,EAAAA,IAACgO,KAAgB,EAAK,KACjCF,SACE,OAAA,CACC,SAAA,CAAA9N,EAAAA,IAAC,IAAA,CAAE,MAAM,0BAA2B,SAAA8N,EAAM,KAAK,EAAK,IACpD9N,EAAAA,IAAC,OAAA,CAAK,MAAM,cAAe,WAAM,IAAA,CAAK,CAAA,CAAA,CACxC,EAEAA,EAAAA,IAAC,OAAA,CAAK,MAAM,cAAe,SAAA4F,CAAA,CAAM,CAAA,EAErC,EACCC,EACC7F,EAAAA,IAAC,OAAA,CAAK,MAAM,oDAAqD,WAAS,EACxE,IAAA,EACN,CAEJ,CAEA,SAAS+N,GAAgBnI,EAAsD,CAC7E,MAAM0F,EAAI1F,EAAM,MAAM,4BAA4B,EAClD,OAAK0F,EACE,CAAE,KAAMA,EAAE,CAAC,EAAG,KAAMA,EAAE,CAAC,CAAA,EADf,IAEjB,CAEA,SAAS0C,IAAkB,CACzB,OACEnM,EAAAA,KAAC,MAAA,CACC,MAAM,6BACN,QAAQ,YACR,KAAK,OACL,MAAM,KACN,OAAO,KAGP,MAAM,iCACN,cAAY,OAEZ,SAAA,CAAA7B,EAAAA,IAAC,OAAA,CACC,EAAE,8DACF,OAAO,eACP,eAAa,IACb,kBAAgB,OAAA,CAAA,EAElBA,EAAAA,IAAC,OAAA,CACC,EAAE,gBACF,OAAO,eACP,eAAa,IACb,iBAAe,QACf,kBAAgB,OAAA,CAAA,CAClB,CAAA,CAAA,CAGN,CCtEA,MAAMiO,GAAe,GACfC,GAAc,GACdC,GAAY,EAOlB,SAASC,GAAW/M,EAAiBgN,EAA0B,CAC7D,MAAMC,EAAYD,EAAaF,GAC/B,IAAII,EAAON,GAEX,IADA5M,EAAG,MAAM,SAAW,GAAGkN,CAAI,KACpBlN,EAAG,aAAeiN,GAAaC,EAAOL,IAC3CK,GAAQ,EACRlN,EAAG,MAAM,SAAW,GAAGkN,CAAI,IAE/B,CAEO,SAASC,GAAQ,CAAE,MAAApM,EAAO,IAAAC,GAAiC,CAChE,MAAMoM,EAAQrM,EAAM,OAAS,EACvBsM,EAAO,IAAID,CAAK,GAChBE,EACJF,IAAU,EACN,6FACAA,IAAU,EACR,kEACA,sCAEFG,EAAM7N,EAAAA,OAAkC,IAAI,EAC5C8N,EAAUJ,IAAU,GAAK,CAAC,CAACpM,EAAI,UAAU,SAAS,eAExDzC,OAAAA,EAAAA,UAAU,IAAM,CACd,GAAI,CAACiP,GAAW,CAACD,EAAI,QAAS,OAG9B,MAAME,EAAK,iBAAiBF,EAAI,OAAO,EACjCG,EAAK,WAAWD,EAAG,UAAU,GAAKb,GAAe,IACvDG,GAAWQ,EAAI,QAASG,CAAE,CAC5B,EAAG,CAACF,EAASzM,EAAM,IAAI,CAAC,QAGrBsM,EAAA,CAAI,IAAAE,EAAU,MAAOD,EACnB,WAAM,KACT,CAEJ,CC7BA,SAASK,GAAgBnC,EAA2D,CAClF,MAAMoC,EAAUpC,EAAM,OAAS,CAAE,SAAUA,EAAM,SAAU,OAAQA,EAAM,MAAA,EACzE,GAAIA,EAAM,WAAa,OAAQ,CAC7B,MAAMqC,GAAUrC,EAAM,gBAAkB,GAAK,GAC7C,MAAO,CAAE,OAAQoC,EAAQ,OAASC,EAAQ,SAAUD,EAAQ,QAAA,CAC9D,CACA,MAAO,CAAE,OAAQA,EAAQ,OAAQ,SAAUA,EAAQ,QAAA,CACrD,CAOA,SAASE,GAAoBpP,EAAeqP,EAG1C,CACA,MAAMC,EAAUtP,EAAQ,IAAM,EAAI,EAAI,EACtC,GAAI,CACF,MAAM+N,EAAQ,IAAI,KAAK,aAAa,OAAW,CAC7C,MAAO,WACP,SAAAsB,EACA,gBAAiB,eACjB,sBAAuBC,EACvB,sBAAuBA,CAAA,CACxB,EAAE,cAActP,CAAK,EACtB,IAAIuP,EAAM,GACNC,EAAS,GACb,UAAWC,KAAQ1B,EACb0B,EAAK,OAAS,WAChBF,EAAME,EAAK,MACFA,EAAK,OAAS,YACvBD,GAAUC,EAAK,OAGnB,MAAO,CAAE,SAAUF,GAAOF,EAAU,OAAQG,EAAO,MAAK,CAC1D,MAAQ,CACN,MAAO,CAAE,SAAAH,EAAU,OAAQ,OAAOrP,CAAK,CAAA,CACzC,CACF,CAEA,SAAS0P,GAAiB5C,EAAqB6C,EAAgD,CAC7F,KAAM,CAAE,OAAQjR,EAAM,SAAU6Q,CAAA,EAAQN,GAAgBnC,CAAK,EAC7D,GAAI,CAAC6C,EAAiB,CACpB,KAAM,CAAE,SAAAN,EAAU,OAAAG,CAAA,EAAWJ,GAAoB1Q,EAAM6Q,CAAG,EAC1D,MAAO,CAAE,SAAAF,EAAU,OAAAG,EAAQ,eAAgB,IAAA,CAC7C,CACA,MAAMI,EAAalR,GAAQ,EAAIiR,EAAkB,KAC3CE,EAAOT,GAAoBQ,EAAYL,CAAG,EAC1CO,EAAWV,GAAoB1Q,EAAM6Q,CAAG,EAG9C,MAAO,CACL,SAAUM,EAAK,SACf,OAAQA,EAAK,OACb,eAAgB,GAAGC,EAAS,QAAQ,GAAGA,EAAS,MAAM,EAAA,CAE1D,CAKA,SAASC,EACPjH,EACAsE,EACqB,CACrB,GAAI,CAACtE,GAAUA,EAAO,SAAW,EAAG,OAAO,KAC3C,MAAMkH,EAAWlH,EAAO,KACrBG,GAAMA,EAAE,WAAamE,GAAWnE,EAAE,kBAAoBA,EAAE,iBAAmB,CAAA,EAE9E,OAAI+G,IACWlH,EAAO,KACnBG,GAAMA,EAAE,UAAY,MAAQA,EAAE,kBAAoBA,EAAE,iBAAmB,CAAA,GAEzD,KACnB,CAEA,SAASgH,GAAUnD,EAAqBhM,EAAgB,CACtD,GAAIgM,EAAM,MAAO,OAAOA,EAAM,MAAM,YAAA,EACpC,GAAI,CAACA,EAAM,UAAYA,EAAM,WAAa,WACxC,OAAOhM,EAAE,8BAA+B,UAAU,EAQpD,MAAMoP,EANyD,CAC7D,IAAK,CAAE,IAAK,2BAA4B,SAAU,YAAA,EAClD,KAAM,CAAE,IAAK,4BAA6B,SAAU,aAAA,EACpD,MAAO,CAAE,IAAK,6BAA8B,SAAU,cAAA,EACtD,KAAM,CAAE,IAAK,4BAA6B,SAAU,aAAA,CAAc,EAElDpD,EAAM,QAAQ,EAChC,OAAIoD,EAAcpP,EAAEoP,EAAM,IAAKA,EAAM,QAAQ,EACtC,GAAGpD,EAAM,SAAS,YAAA,CAAa,OACxC,CAKA,SAASqD,GAAerD,EAAqBhM,EAAgB,CAC3D,GAAI,CAACgM,EAAM,UAAYA,EAAM,WAAa,WACxC,OAAOhM,EAAE,kCAAmC,UAAU,EAExD,GAAIgM,EAAM,WAAa,OAAQ,OAAOhM,EAAE,yBAA0B,OAAO,EACzE,MAAMsP,EAAItD,EAAM,gBAAkB,EAClC,OAAIsD,IAAM,EAAUtP,EAAE,oBAAoBgM,EAAM,QAAQ,GAAIA,EAAM,QAAQ,EACnE,GAAGsD,CAAC,IAAItD,EAAM,QAAQ,GAC/B,CAEO,SAASuD,GAAU,CAAE,MAAAhO,EAAO,IAAAC,GAAmC,CACpE,KAAM,CAAE,CAAA,EAAMpC,EAAA,EACRoQ,EAASjO,EAAM,UAAYA,EAAM,SAAS,OAAS,EAAI,IAAI,IAAIA,EAAM,QAAQ,EAAI,KACjFkO,EAASjO,EAAI,UAAU,OAAO,OAAQuC,GAAM,CAACyL,GAAUA,EAAO,IAAIzL,EAAE,EAAE,CAAC,EAE7E,GAAI0L,EAAO,SAAW,EACpB,aAAQ,IAAA,CAAE,MAAM,wBAAyB,SAAA,EAAE,oBAAqB,sBAAsB,EAAE,EAG1F,MAAMC,EAAenO,EAAM,eAAiB,EAAE,uBAAwB,cAAc,EASpF,GAAIA,EAAM,OAAS,UACjB,OACEpC,EAAAA,IAAC,MAAA,CACC,MAAM,oEACN,KAAK,aACL,aAAY,EAAE,qBAAsB,OAAO,EAE1C,SAAAsQ,EAAO,IAAI,CAACzD,EAAO2D,IAClBxQ,EAAAA,IAACyQ,GAAA,CAEC,MAAA5D,EACA,OAAQ2D,IAAQF,EAAO,OAAS,EAChC,UAAWlO,EAAM,mBAAqByK,EAAM,GAC5C,aAAA0D,EACA,MAAOT,EAAgBzN,EAAI,UAAU,OAAQwK,EAAM,EAAE,EACrD,SAAUxK,EAAI,kBAAoBwK,EAAM,GACxC,SAAU,IAAM,CACdxK,EAAI,mBAAmBwK,EAAM,EAAE,EAC/BxK,EAAI,SAAS,iBAAkB,CAAE,QAASwK,EAAM,GAAI,MAAAA,EAAO,CAC7D,EACA,CAAA,EAXKA,EAAM,EAAA,CAad,CAAA,CAAA,EAUP,GAAIzK,EAAM,OAAS,aAAc,CAC/B,MAAMsO,EAAO,KAAK,IAAIJ,EAAO,OAAQ,CAAC,EAKhCK,EAAiBL,EAAO,KAC3B1L,IAAOkL,EAAgBzN,EAAI,UAAU,OAAQuC,EAAE,EAAE,GAAG,kBAAoB,GAAK,CAAA,EAEhF,OACE5E,EAAAA,IAAC,MAAA,CACC,MAAM,2BACN,MAAO,CAAE,oBAAqB,UAAU0Q,CAAI,mBAAA,EAC5C,KAAK,aACL,aAAY,EAAE,qBAAsB,OAAO,EAE1C,SAAAJ,EAAO,IAAKzD,GACX7M,EAAAA,IAAC4Q,GAAA,CAEC,MAAA/D,EACA,UAAWzK,EAAM,mBAAqByK,EAAM,GAC5C,aAAA0D,EACA,MAAOT,EAAgBzN,EAAI,UAAU,OAAQwK,EAAM,EAAE,EACrD,iBAAkB8D,EAClB,SAAUtO,EAAI,kBAAoBwK,EAAM,GACxC,SAAU,IAAM,CACdxK,EAAI,mBAAmBwK,EAAM,EAAE,EAC/BxK,EAAI,SAAS,iBAAkB,CAAE,QAASwK,EAAM,GAAI,MAAAA,EAAO,CAC7D,EACA,CAAA,EAXKA,EAAM,EAAA,CAad,CAAA,CAAA,CAGP,CAEA,OACE7M,EAAAA,IAAC,MAAA,CACC,MAAM,sBACN,KAAK,aACL,aAAY,EAAE,qBAAsB,OAAO,EAE1C,SAAAsQ,EAAO,IAAKzD,GAAU,CACrB,MAAMgE,EAAWxO,EAAI,kBAAoBwK,EAAM,GACzCiE,EAAY1O,EAAM,mBAAqByK,EAAM,GAE7C6C,EADQI,EAAgBzN,EAAI,UAAU,OAAQwK,EAAM,EAAE,GAC7B,kBAAoB,KAC7C,CAAE,SAAAuC,EAAU,OAAAG,EAAQ,eAAAwB,GAAmBtB,GAAiB5C,EAAO6C,CAAe,EACpF,OACE7N,EAAAA,KAAC,SAAA,CAEC,KAAK,SACL,KAAK,QACL,eAAcgP,EACd,QAAS,IAAM,CACbxO,EAAI,mBAAmBwK,EAAM,EAAE,EAC/BxK,EAAI,SAAS,iBAAkB,CAAE,QAASwK,EAAM,GAAI,MAAAA,EAAO,CAC7D,EACA,MAAO,CACL,oRAIAgE,EACI,2CACA,iDAAA,EACJ,KAAK,GAAG,EAEV,SAAA,CAAA7Q,EAAAA,IAAC,OAAA,CACC,MAAO,CACL,mGACA6Q,EACI,uCACA,kDAKJC,EAAY,OAAS,EAAA,EACrB,KAAK,GAAG,EACV,MACED,EACI,CACE,WACE,yJAAA,EAEJ,OAEN,cAAY,OAEZ,SAAA7Q,EAAAA,IAAC,MAAA,CACC,MAAM,KACN,OAAO,KACP,QAAQ,YACR,KAAK,OACL,MAAM,6BACN,MAAO6Q,EAAW,cAAgB,YAElC,SAAA7Q,EAAAA,IAAC,OAAA,CACC,EAAE,2UACF,KAAK,cAAA,CAAA,CACP,CAAA,CACF,CAAA,EAEF6B,EAAAA,KAAC,MAAA,CAAI,MAAM,+BAIT,SAAA,CAAAA,EAAAA,KAAC,MAAA,CAAI,MAAM,8CACT,SAAA,CAAA7B,MAAC,QAAK,MAAM,iEACT,SAAAgQ,GAAUnD,EAAO,CAAC,EACrB,EACCkE,EAIC/Q,EAAAA,IAAC,OAAA,CAAK,MAAM,uGACT,SAAA+Q,CAAA,CACH,EACE,KACHrB,EAGC7N,EAAAA,KAAC,OAAA,CAAK,MAAM,0FAA0F,SAAA,CAAA,IAClG6N,EAAgB,GAAA,CAAA,CACpB,EACE,IAAA,EACN,QACC,MAAA,CAAI,MAAM,sCACT,SAAA7N,EAAAA,KAAC,OAAA,CAAK,MAAM,wEACV,SAAA,CAAA7B,EAAAA,IAAC,OAAA,CAAK,MAAM,aAAc,SAAAoP,EAAS,EAAQG,EAC3C1N,EAAAA,KAAC,OAAA,CAAK,MAAM,oCACT,SAAA,CAAA,IAAI,KAAGqO,GAAerD,EAAO,CAAC,CAAA,CAAA,CACjC,CAAA,CAAA,CACF,CAAA,CACF,EACCA,EAAM,YACL7M,MAAC,OAAA,CAAK,MAAM,6CAA8C,SAAA6M,EAAM,YAAY,EAC1E,IAAA,EACN,EACCiE,EACC9Q,EAAAA,IAAC,OAAA,CAKC,MAAM,2HACN,MAAO,CAAE,WAAY,kBAAA,EAEpB,SAAAuQ,CAAA,CAAA,EAED,IAAA,CAAA,EArGC1D,EAAM,EAAA,CAwGjB,CAAC,CAAA,CAAA,CAGP,CAMA,SAASmE,GAAanE,EAAqBhM,EAAgB,CACzD,OAAIgM,EAAM,MAAcA,EAAM,MAC1B,CAACA,EAAM,UAAYA,EAAM,WAAa,WACjChM,EAAE,kCAAmC,UAAU,EAEjDA,EAAE,oBAAoBgM,EAAM,QAAQ,GAAIA,EAAM,QAAQ,CAC/D,CAQA,SAAS4D,GAAW,CAClB,MAAA5D,EACA,OAAAoE,EACA,UAAAH,EACA,aAAAP,EACA,MAAA7H,EACA,SAAAmI,EACA,SAAAK,EACA,EAAArQ,CACF,EASG,CACD,MAAM6O,EAAkBhH,GAAO,kBAAoB,KAC7C,CAAE,SAAA0G,EAAU,OAAAG,EAAQ,eAAAwB,GAAmBtB,GAAiB5C,EAAO6C,CAAe,EACpF,OACE7N,EAAAA,KAAC,SAAA,CACC,KAAK,SACL,KAAK,QACL,eAAcgP,EACd,QAASK,EACT,MAAM,0NAEN,SAAA,CAAAlR,EAAAA,IAAC,OAAA,CACC,MAAO,CACL,oGACA6Q,EACI,uCACA,iDAAA,EACJ,KAAK,GAAG,EACV,MACEA,EACI,CACE,WACE,yJAAA,EAEJ,OAEN,cAAY,OAEZ,SAAA7Q,EAAAA,IAAC,MAAA,CACC,MAAM,KACN,OAAO,KACP,QAAQ,YACR,KAAK,OACL,MAAM,6BACN,MAAO6Q,EAAW,cAAgB,YAElC,SAAA7Q,EAAAA,IAAC,OAAA,CACC,EAAE,2UACF,KAAK,cAAA,CAAA,CACP,CAAA,CACF,CAAA,EAKF6B,EAAAA,KAAC,MAAA,CACC,MAAO,CACL,0CACAoP,EAAS,GAAK,0BAAA,EACd,KAAK,GAAG,EAEV,SAAA,CAAApP,EAAAA,KAAC,MAAA,CAAI,MAAM,8CACT,SAAA,CAAA7B,MAAC,QAAK,MAAM,iDACT,SAAAgR,GAAanE,EAAOhM,CAAC,EACxB,EACCiQ,EAIC9Q,EAAAA,IAAC,OAAA,CACC,MAAM,gDACN,MAAO,CACL,WACE,mIACF,MAAO,kBAAA,EAGR,SAAAuQ,CAAA,CAAA,EAED,KACHb,EACC7N,EAAAA,KAAC,OAAA,CAAK,MAAM,8FAA8F,SAAA,CAAA,IACtG6N,EAAgB,GAAA,CAAA,CACpB,EACE,IAAA,EACN,EACA1P,EAAAA,IAAC,MAAA,CAAI,MAAM,QAAA,CAAS,EACpB6B,EAAAA,KAAC,OAAA,CAAK,MAAM,kEACT,SAAA,CAAAkP,EACC/Q,EAAAA,IAAC,OAAA,CAAK,MAAM,4EACT,WACH,EACE,KACJ6B,EAAAA,KAAC,OAAA,CAAK,MAAM,oBACV,SAAA,CAAA7B,EAAAA,IAAC,OAAA,CAAK,MAAM,aAAc,SAAAoP,EAAS,EAAQG,EAC3C1N,EAAAA,KAAC,OAAA,CAAK,MAAM,wBACT,SAAA,CAAA,IAAI,KAAGqO,GAAerD,EAAOhM,CAAC,CAAA,CAAA,CACjC,CAAA,CAAA,CACF,CAAA,CAAA,CACF,CAAA,CAAA,CAAA,CACF,CAAA,CAAA,CAGN,CAQA,SAAS+P,GAAQ,CACf,MAAA/D,EACA,UAAAiE,EACA,aAAAP,EACA,MAAA7H,EACA,iBAAAyI,EACA,SAAAN,EACA,SAAAK,EACA,EAAArQ,CACF,EAcG,CACD,MAAM6O,EAAkBhH,GAAO,kBAAoB,KAC7C,CAAE,SAAA0G,EAAU,OAAAG,EAAQ,eAAAwB,GAAmBtB,GAAiB5C,EAAO6C,CAAe,EACpF,OACE7N,EAAAA,KAAC,SAAA,CACC,KAAK,SACL,KAAK,QACL,eAAcgP,EACd,QAASK,EACT,MAAO,CACL,kQACAL,EACI,4BACA,kCAAA,EACJ,KAAK,GAAG,EACV,MACEA,EACI,CAAE,WAAY,wDACd,OAKN,SAAA,CAAA7Q,MAAC,QAAK,MAAM,mGACT,SAAAgQ,GAAUnD,EAAOhM,CAAC,EACrB,EAMCsQ,EACCtP,EAAAA,KAAC,MAAA,CAAI,MAAM,oDACR,SAAA,CAAAkP,EACC/Q,EAAAA,IAAC,OAAA,CAAK,MAAM,gFACT,WACH,EACE,KACH0P,EACC7N,EAAAA,KAAC,OAAA,CAAK,MAAM,8FAA8F,SAAA,CAAA,IACtG6N,EAAgB,GAAA,CAAA,CACpB,EACE,IAAA,CAAA,CACN,EACE,KACJ7N,EAAAA,KAAC,OAAA,CAAK,MAAM,uEACV,SAAA,CAAA7B,EAAAA,IAAC,OAAA,CAAK,MAAM,aAAc,SAAAoP,EAAS,EAAQG,CAAA,EAC7C,EACA1N,EAAAA,KAAC,OAAA,CAAK,MAAM,oCAAoC,SAAA,CAAA,KAC3CqO,GAAerD,EAAOhM,CAAC,CAAA,EAC5B,EACCiQ,EACC9Q,EAAAA,IAAC,OAAA,CAGC,MAAM,wLACN,MAAO,CAAE,WAAY,kBAAA,EAEpB,SAAAuQ,CAAA,CAAA,EAED,IAAA,CAAA,CAAA,CAGV,CC7iBO,SAASa,GAAK,CAAE,MAAAhP,GAAgC,CACrD,OAAOpC,EAAAA,IAAC,IAAA,CAAE,MAAM,iDAAkD,WAAM,KAAK,CAC/E,CCDA,MAAMqR,GAA8C,CAClD,KAAM,IACN,MAAO,EACP,KAAM,EACR,EAEA,SAASC,GAAaC,EAAoC1Q,EAAgB,CACxE,OAAK0Q,EACE1Q,EAAE,oBAAoB0Q,CAAQ,GAAIA,CAAQ,EAD3B1Q,EAAE,0BAA2B,QAAQ,CAE7D,CAEO,SAAS2Q,GAAiB,CAAE,MAAApP,EAAO,IAAAC,GAA0C,CAClF,KAAM,CAAE,CAAA,EAAMpC,EAAA,EACd,GAAI,CAACmC,EAAM,QAAQ,OAAQ,OAAO,KAGlC,MAAMmP,EADgBlP,EAAI,UAAU,OAAO,KAAMuC,GAAMA,EAAE,KAAOvC,EAAI,eAAe,GACnD,UAAY,KACtCoP,EAAaF,EAAWF,GAAoBE,CAAQ,EAAI,OAE9D,OACE1P,EAAAA,KAAC,MAAA,CAAI,MAAM,sBACT,SAAA,CAAA7B,EAAAA,IAAC,MAAA,CAAI,MAAM,sCACR,SAAA,CAACuR,GAAYA,IAAa,WACvB,EAAE,yBAA0B,wBAAwB,EACpD,EAAE,uBAAwB,2BAA4B,CACpD,SAAUD,GAAaC,EAAU,CAAC,CAAA,CACnC,EACP,EACAvR,EAAAA,IAAC,KAAA,CAAG,MAAM,sBAAsB,KAAK,OAClC,SAAAoC,EAAM,QAAQ,IAAKsP,GAAM,CACxB,MAAMC,EAAW,OAAO,SAASD,EAAE,KAAe,EAAKA,EAAE,MAAmB,EACtEnC,EACJkC,IAAe,OAAY,KAAK,MAAME,EAAWF,CAAU,EAAIE,EACjE,OACE9P,OAAC,MAAc,MAAO,cAAc6P,EAAE,KAAO,cAAgB,cAAc,GACzE,SAAA,CAAA1R,EAAAA,IAAC,MAAA,CACC,MAAM,KACN,OAAO,KACP,QAAQ,YACR,KAAK,OACL,MAAO,kCAAkC0R,EAAE,KAAO,SAAW,EAAE,GAC/D,cAAY,OAEZ,SAAA1R,EAAAA,IAAC,OAAA,CACC,EAAE,0BACF,OAAO,eACP,eAAa,MACb,iBAAe,QACf,kBAAgB,OAAA,CAAA,CAClB,CAAA,SAED,MAAA,CACC,SAAA,CAAAA,EAAAA,IAAC,OAAA,CAAK,MAAM,sCAAuC,SAAAuP,EAAO,EAAQ,IAClEvP,EAAAA,IAAC,OAAA,CAAK,MAAM,wBAAyB,WAAE,KAAK,EAC3C0R,EAAE,KACD7P,EAAAA,KAAAqD,EAAAA,SAAA,CACE,SAAA,CAAAlF,EAAAA,IAAC,KAAA,EAAG,EACJA,EAAAA,IAAC,OAAA,CAAK,MAAM,wBAAyB,WAAE,IAAA,CAAK,CAAA,CAAA,CAC9C,EACE,IAAA,CAAA,CACN,CAAA,CAAA,EA1BO0R,EAAE,EA2BX,CAEJ,CAAC,CAAA,CACH,CAAA,EACF,CAEJ,CC5DO,MAAME,GAAkE,CAC7E,QAASpD,GACT,KAAM4C,GACN,WAAYhB,GACZ,WAAYlD,GACZ,WAAY/K,GACZ,gBAAiBkL,GACjB,cAAeK,GACf,kBAAmB8D,GACnB,gBAAiB5D,GACjB,aAActE,EAChB,ECNO,SAASuI,GAAS,CAAE,OAAAC,EAAQ,UAAAvT,EAAW,SAAAwT,EAAU,KAAAzP,EAAM,YAAA4E,EAAa,aAAA8K,GAA+B,CAKxG,MAAMC,EAAiB5G,EAAAA,QAAQ,IAAM,CACnC,UAAW6G,KAAKJ,EAAO,OACrB,GAAII,EAAE,OAAS,cAAgBA,EAAE,kBAC3B3T,EAAU,OAAO,KAAMqG,GAAMA,EAAE,KAAOsN,EAAE,gBAAgB,EAC1D,OAAOA,EAAE,iBAIf,OAAO3T,EAAU,OAAO,CAAC,GAAG,IAAM,IACpC,EAAG,CAACuT,EAAO,OAAQvT,EAAU,MAAM,CAAC,EAC9B,CAAC4T,EAAiBC,CAAkB,EAAI1S,EAAAA,SAAwBuS,CAAc,EAE9E5P,EAAoB,CACxB,UAAA9D,EACA,gBAAA4T,EACA,mBAAAC,EACA,SAAAL,EACA,KAAAzP,EACA,YAAA4E,CAAA,EAUImL,EAASP,EAAO,OAAO,UAAWI,GAAMA,EAAE,OAAS,YAAY,EAC/DI,EAAeD,IAAW,GAAKP,EAAO,OAASA,EAAO,OAAO,MAAM,EAAGO,CAAM,EAC5EE,EAAeF,IAAW,GAAK,CAAA,EAAKP,EAAO,OAAO,MAAMO,CAAM,EAE9DG,EAAc,CAACpQ,EAAiCqK,IAAc,CAClE,MAAMgG,EAAMb,GAAcxP,EAAM,IAAI,EACpC,OAAKqQ,EAMEzS,MAACyS,GAA+B,MAAArQ,EAAuB,IAAAC,CAAA,EAA7C,GAAGD,EAAM,IAAI,IAAIqK,CAAC,EAAqC,GALlE,OAAO,QAAY,KACrB,QAAQ,KAAK,iCAAiCrK,EAAM,IAAI,EAAE,EAErD,KAGX,EAEA,OACEP,EAAAA,KAAAqD,WAAA,CAIE,SAAA,CAAAlF,EAAAA,IAAC,MAAA,CAAI,MAAM,wEACT,SAAAA,EAAAA,IAAC,MAAA,CAAI,MAAM,sBACR,SAAAsS,EAAa,IAAIE,CAAW,CAAA,CAC/B,EACF,EACCD,EAAa,OAAS,EAIrBvS,EAAAA,IAAC,MAAA,CACC,MAAM,sDACN,MAAO,CAAE,UAAW,sCAAA,EAEnB,SAAAuS,EAAa,IAAI,CAACL,EAAGzF,IAAM+F,EAAYN,EAAGI,EAAa,OAAS7F,CAAC,CAAC,CAAA,CAAA,EAEnE,IAAA,EACN,CAEJ,CC4CA,SAASiG,GACPrS,EACAsS,EACAC,EACAC,EACsB,CACtB,OAAKxS,EACDwS,EAAkB,CAAE,KAAM,GAAM,KAAM,YAAa,MAAO,IAAA,EAC1DF,EAAM,SAAW,QAAUA,EAAM,SAAW,UACvC,CAAE,KAAM,GAAM,KAAM,UAAW,MAAO,IAAA,EAE3CA,EAAM,SAAW,QACZ,CAAE,KAAM,GAAM,KAAM,QAAS,MAAOA,EAAM,KAAA,EAE/CC,EAAK,OAAS,UAAkB,CAAE,KAAM,GAAM,KAAM,UAAW,MAAO,IAAA,EACtEA,EAAK,OAAS,YAAoB,CAAE,KAAM,GAAM,KAAM,OAAQ,MAAO,IAAA,EACrEA,EAAK,OAAS,YAAoB,CAAE,KAAM,GAAM,KAAM,OAAQ,MAAO,IAAA,EACrEA,EAAK,OAAS,mBACT,CAAE,KAAM,GAAM,KAAM,mBAAoB,MAAO,IAAA,EAEpDA,EAAK,OAAS,gBACT,CAAE,KAAM,GAAM,KAAM,gBAAiB,MAAO,IAAA,EAEjDA,EAAK,OAAS,mBACT,CAAE,KAAM,GAAM,KAAM,YAAa,MAAO,IAAA,EAE7CA,EAAK,OAAS,YACT,CAAE,KAAM,GAAM,KAAM,UAAW,MAAO,IAAA,EAExC,CAAE,KAAM,GAAM,KAAM,SAAU,MAAO,IAAA,EAvB1B,CAAE,KAAM,GAAO,KAAM,KAAM,MAAO,IAAA,CAwBtD,CAEA,SAASE,GAAaC,EAAyBb,EAAkC,CAC/E,OAAOa,EAAE,OAASb,EAAE,MAAQa,EAAE,OAASb,EAAE,MAAQa,EAAE,QAAUb,EAAE,KACjE,CAEO,SAASc,GAAY,CAC1B,OAAA5I,EACA,KAAA/J,EACA,QAAAC,EACA,QAAA2S,EACA,YAAAC,EACA,UAAAL,EACA,MAAAM,EACA,QAAAC,EACA,OAAAxS,EACA,OAAApB,CACF,EAAqB,CACnB,KAAM,CAACmT,EAAOU,CAAQ,EAAI3T,EAAAA,SAAoB,CAAE,OAAQ,OAAQ,EAI1D,CAACwH,EAAaoM,CAAc,EAAI5T,EAAAA,SACpC,IAAM0K,EAAO,MAAM,oBAAsB,IAAA,EAErC,CAACwI,EAAMW,CAAO,EAAI7T,EAAAA,SAAoB,IACtCwT,IAAgB,UAAkB,CAAE,KAAM,UAAW,OAAQ,YAAA,EAC7DA,IAAgB,OAAe,CAAE,KAAM,YAAa,OAAQ,YAAA,EAC5DA,IAAgB,OAAe,CAAE,KAAM,YAAa,OAAQ,YAAA,EACzD,CAAE,KAAM,QAAA,CAChB,EAIKM,EAAczS,EAAAA,OAAO,EAAK,EAM1B0S,EAAkB1S,EAAAA,OAAoC,IAAI,EAChEnB,EAAAA,UAAU,IAAM,CACd,GAAI,CAACwT,EAAS,OACd,MAAM/O,EAAOqO,GAAuBrS,EAAMsS,EAAOC,EAAMC,CAAS,EAC1DrH,EAAOiI,EAAgB,QACzBjI,GAAQsH,GAAatH,EAAMnH,CAAI,IACnCoP,EAAgB,QAAUpP,EAC1B+O,EAAQ/O,CAAI,EACd,EAAG,CAAChE,EAAMsS,EAAOC,EAAMC,EAAWO,CAAO,CAAC,EAE1CxT,EAAAA,UAAU,IAAM,CACd,GAAKwK,EAAO,KACZ,OAAOA,EAAO,KAAK,aAAa,CAACsJ,EAAQ5V,IAAMwV,EAAexV,CAAC,CAAC,CAClE,EAAG,CAACsM,EAAO,IAAI,CAAC,EAOhBxK,EAAAA,UAAU,IAAM,CACd,GAAI,OAAOwK,EAAO,mBAAsB,WACxC,OAAOA,EAAO,kBAAmBuJ,GAAS,CACxCN,EAAU7H,GACRA,EAAK,SAAW,QAAU,CAAE,OAAQ,QAAS,KAAAmI,GAASnI,CAAA,CAE1D,CAAC,CACH,EAAG,CAACpB,CAAM,CAAC,EAEXxK,EAAAA,UAAU,IAAM,CAEd,GADI,CAACS,GACDsS,EAAM,SAAW,SAAWA,EAAM,SAAW,UAAW,OAE5D,IAAI9S,EAAY,GAChB,OAAAwT,EAAS,CAAE,OAAQ,UAAW,EAC9BjJ,EACG,UAAA,EACA,KAAMuJ,GAAS,CACV9T,IACJwT,EAAS,CAAE,OAAQ,QAAS,KAAAM,CAAA,CAAM,EAClCV,EAAQ,QAASU,CAAI,EAOjBA,EAAK,MAAM,yBAA2B,CAACR,IACzCF,EAAQ,qBAAsB,CAC5B,QAAS,KACT,UAAW,KACX,SAAU,EAAA,CACX,EACDM,EAAQ,CAAE,KAAM,mBAAoB,SAAU,GAAM,GAExD,CAAC,EACA,MAAO5P,GAAmB,CACzB,GAAI9D,EAAW,OACf,MAAMV,EACJwE,aAAiBzB,EAAAA,aACbyB,EACA,IAAIzB,eAAa,UAAW,yBAA0B,CAAE,MAAOyB,CAAA,CAAO,EAC5E0P,EAAS,CAAE,OAAQ,QAAS,MAAOlU,EAAK,EACxC8T,EAAQ,QAAS9T,CAAG,CACtB,CAAC,EACI,IAAM,CACXU,EAAY,EACd,CACF,EAAG,CAACQ,EAAM+J,CAAM,CAAC,EAcjBwJ,EAAAA,gBAAgB,IAAM,CACpB,GAAI,CAACvT,EAAM,CACTkT,EAAQ,CAAE,KAAM,SAAU,EAC1BC,EAAY,QAAU,GACtB,MACF,CACIN,IAAgB,UAClBK,EAAQ,CAAE,KAAM,UAAW,OAAQ,aAAc,EACxCL,IAAgB,OACzBK,EAAQ,CAAE,KAAM,YAAa,OAAQ,aAAc,EAC1CL,IAAgB,QACzBK,EAAQ,CAAE,KAAM,YAAa,OAAQ,aAAc,CAEvD,EAAG,CAAClT,EAAM6S,CAAW,CAAC,EAEtB,MAAMW,EAAc,MAAO1G,GAAoB,CAC7C,GAAI,CACF,MAAM2G,EAAS,MAAM1J,EAAO,eAAe,CACzC,QAAA+C,EACA,qBAAsBgG,IAAU,EAAA,CACjC,EAED,GADAF,EAAQ,mBAAoB,CAAE,QAAA9F,EAAS,IAAK2G,EAAO,IAAK,UAAWA,EAAO,UAAW,EACjF,OAAO,OAAW,KAAe,CAACA,EAAO,IAAK,OAOlD,MAAMC,EAAQ,OAAO,KAAKD,EAAO,IAAK,QAAQ,EAC9C,GAAIC,EAAO,CACT,GAAI,CACFA,EAAM,OAAS,IACjB,MAAQ,CAER,CACAR,EAAQ,CAAE,KAAM,mBAAoB,QAAApG,EAAS,IAAK2G,EAAO,IAAK,CAChE,MAKEP,EAAQ,CAAE,KAAM,gBAAiB,QAAApG,EAAS,IAAK2G,EAAO,IAAK,CAE/D,OAASnQ,EAAO,CAMd,GAAIA,aAAiBzB,EAAAA,cAAgByB,EAAM,OAAS,oBAAqB,CACvE,GAAI,CACF,MAAMyG,EAAO,QAAQ,CAAE,MAAO,GAAM,CACtC,MAAQ,CAER,CACA6I,EAAQ,qBAAsB,CAAE,QAAA9F,EAAS,UAAW,KAAM,SAAU,GAAM,EAC1EoG,EAAQ,CAAE,KAAM,mBAAoB,SAAU,GAAM,EACpD,MACF,CACA,MAAMpU,EACJwE,aAAiBzB,EAAAA,aACbyB,EACA,IAAIzB,eAAa,kBAAmB,kBAAmB,CAAE,MAAOyB,CAAA,CAAO,EAC7EsP,EAAQ,QAAS9T,CAAG,EAGpBoU,EAAQ,CAAE,KAAM,SAAU,CAC5B,CACF,EAEMS,EAAiB,CAAC7G,EAAiB8G,IAAgB,CACvD,GAAI,OAAO,OAAW,IAAa,OACnC,MAAMF,EAAQ,OAAO,KAAKE,EAAK,QAAQ,EACvC,GAAIF,EAAO,CACT,GAAI,CACFA,EAAM,OAAS,IACjB,MAAQ,CAER,CACAR,EAAQ,CAAE,KAAM,mBAAoB,QAAApG,EAAS,IAAA8G,EAAK,CACpD,CAEF,EASArU,EAAAA,UAAU,IAAM,CAMd,GALIgT,EAAK,OAAS,aAId,CAAC1L,GAAeA,EAAY,KAAK,cACjCsM,EAAY,QAAS,OACzBA,EAAY,QAAU,GACtB,MAAM1U,EAAU8T,EAAK,gBACfvI,EAASuI,EAAK,OAKpBW,EAAQ,CAAE,KAAM,YAAa,GACvB,SAAY,CAQhB,GAAI,CAACJ,EACH,GAAI,CAEF,IADa,MAAM/I,EAAO,QAAQ,CAAE,MAAO,GAAM,GACxC,wBAAyB,CAChC6I,EAAQ,qBAAsB,CAC5B,QAASnU,GAAS,SAAW,KAC7B,UAAW,KACX,SAAU,EAAA,CACX,EACDyU,EAAQ,CAAE,KAAM,mBAAoB,SAAU,GAAM,EACpD,MACF,CACF,MAAQ,CAER,CAEF,GAAI,CAACzU,EAAS,CAGRuL,IAAW,aACb/J,EAAA,EAEAiT,EAAQ,CAAE,KAAM,SAAU,EAE5B,MACF,CACA,MAAMM,EAAY/U,EAAQ,OAAO,CACnC,GAAA,EAAK,QAAQ,IAAM,CACjB0U,EAAY,QAAU,EACxB,CAAC,CACH,EAAG,CAACtM,EAAa0L,CAAI,CAAC,EAEtB,MAAMsB,EAAe,MAAOpH,EAAgBqH,IAAsB,CAChE,GAAIrH,IAAW,QAAS,CACtBxM,EAAA,EACA,MACF,CACA,GAAIwM,IAAW,iBAAkB,CAE/BmG,EAAQ,iBAAkBkB,CAAO,EACjC,MACF,CACA,GAAIrH,IAAW,UAAW,CASxB,GAAI,CAAC1C,EAAO,KAAM,OAClB,MAAM7H,EAAU6H,EAAO,KAAK,iBAAA,EAC5B,GAAI7H,GAAW,CAACA,EAAQ,KAAK,aAAc,OAC3CgR,EAAQ,CAAE,KAAM,YAAa,OAAQ,UAAW,EAChD,MACF,CACA,GAAIzG,IAAW,UAAW,CAGxByG,EAAQ,CAAE,KAAM,UAAW,OAAQ,SAAU,EAC7C,MACF,CACA,GAAIzG,IAAW,YAAc6F,EAAM,SAAW,QAAS,CACrD,MAAMxF,EAAWgH,GAA8C,QAC/D,GAAI,CAAChH,EAAS,CACZ8F,EAAQ,QAAS,IAAI/Q,EAAAA,aAAa,WAAY,mBAAmB,CAAC,EAClE,MACF,CACA,MAAMD,EAAO0Q,EAAM,KAAK,SAAS,eAAiB,QAI5CyB,EAAgBhK,EAAO,MAAM,iBAAA,GAAsB,KACnDiK,EAAiB,CAAC,CAACD,GAAiB,CAACA,EAAc,KAAK,aAE9D,GADkBnS,IAAS,WAAa,CAAC,CAACmI,EAAO,MAAQ,CAACiK,EAC3C,CACbd,EAAQ,CAAE,KAAM,YAAa,gBAAiB,CAAE,QAAApG,CAAA,EAAW,EAC3D,MACF,CACA,MAAM0G,EAAY1G,CAAO,CAC3B,CACF,EAEMmH,EAAQ3B,EAAM,SAAW,QAAUA,EAAM,KAAK,SAAS,YAAc,KAIrEjS,EACJiS,EAAM,SAAW,QAAUA,EAAM,KAAK,SAAS,cAAgB,GAAQ,GAQnE4B,EADJ3B,EAAK,OAAS,UAAYD,EAAM,SAAW,QACV/J,GAAgB+J,EAAM,KAAK,MAAM,EAAI,KAClElS,EAAY8T,EAAcvU,MAAC2J,GAAA,CAAe,MAAO4K,EAAa,EAAK,KAEnEC,EAA4B,CAChC,KAAM,aAGN,aAAc,GACd,qBAAsB,GAGtB,wBAAyB,GACzB,UAAW7B,EAAM,SAAW,QAAUA,EAAM,KAAK,SAAS,eAAiB,MAAA,EAOvE8B,EACJ7B,EAAK,OAAS,UACZ5S,EAAAA,IAACmK,GAAA,CACC,OAAAC,EACA,YAAAlD,EACA,OAAQ0L,EAAK,OACb,OAAQ,IAAM,CACRA,EAAK,SAAW,aAActS,EAAA,EAC7BiT,EAAQ,CAAE,KAAM,SAAU,CACjC,CAAA,CAAA,EAEA,KAQA5S,EACHiS,EAAK,OAAS,aAAeA,EAAK,SAAW,cAC9CA,EAAK,OAAS,UAEV8B,EAAmB/B,EAAM,SAAW,QAAUA,EAAM,KAAO,KAEjE,OACE3S,EAAAA,IAACX,GAAA,CAAa,UAAWqV,EAAkB,YAAalV,EACxD,SAAAQ,EAAAA,IAACI,GAAA,CACC,KAAAC,EACA,QAAAC,EACA,WAAYgU,EACZ,UAAA7T,EACA,WAAAC,EACA,gBAAAC,EACA,OAAAC,EACA,WAAW,WAEV,SAAAiS,QACE8B,GAAA,CAAoB,WAAYrU,EAAS,EACxCsS,EAAK,OAAS,mBAChB5S,EAAAA,IAAC2U,GAAA,CAAoB,SAAU/B,EAAK,SAAU,WAAYtS,CAAA,CAAS,EACjEmU,IAEA9B,EAAM,SAAW,WAAaA,EAAM,SAAW,QAAUC,EAAK,OAAS,YACzE5S,EAAAA,IAAC4U,GAAA,CAAY,UAAWhC,EAAK,OAAS,WAAA,CAAa,EACjDD,EAAM,SAAW,QACnB3S,EAAAA,IAAC6U,GAAA,CAAU,QAASlC,EAAM,MAAM,OAAA,CAAS,EACvCC,EAAK,OAAS,aAAexI,EAAO,KACtCpK,EAAAA,IAACiH,GAAA,CACC,MAAOuN,EACP,UAAW7B,EAAM,KACjB,KAAMvI,EAAO,KACb,YAAAlD,EAIA,SAAU0L,EAAK,SAAW,aAC1B,OAAQA,EAAK,SAAWA,EAAK,SAAW,aAAe,aAAe,WACtE,OAAQ,IAAM,CACRA,EAAK,SAAW,aAActS,EAAA,EAC7BiT,EAAQ,CAAE,KAAM,SAAU,CACjC,CAAA,CAAA,EAEAX,EAAK,OAAS,aAAexI,EAAO,KACtCpK,EAAAA,IAACyH,GAAA,CACC,KAAM2C,EAAO,KAMb,UAAW,IAAM,CACXwI,EAAK,SAAW,aAActS,EAAA,EAC7BiT,EAAQ,CAAE,KAAM,SAAU,CACjC,EACA,OACEX,EAAK,SAAW,aACZ,OACA,IAAMW,EAAQ,CAAE,KAAM,QAAA,CAAU,CAAA,CAAA,EAGtCX,EAAK,OAAS,mBAChB5S,EAAAA,IAAC8U,GAAA,CACC,OAAA1K,EACA,OAAQ,IAAMmJ,EAAQ,CAAE,KAAM,SAAU,EACxC,SAAU,IAAM,CACd,GAAI,OAAO,OAAW,IAAa,OACnC,MAAMQ,EAAQ,OAAO,KAAKnB,EAAK,IAAK,QAAQ,EAC5C,GAAImB,EACF,GAAI,CACFA,EAAM,OAAS,IACjB,MAAQ,CAER,CAEJ,EACA,QAAS,IAAMF,EAAYjB,EAAK,OAAO,CAAA,CAAA,EAEvCA,EAAK,OAAS,sBACfmC,GAAA,CAAiB,SAAU,IAAMf,EAAepB,EAAK,QAASA,EAAK,GAAG,EAAG,EAE1E5S,EAAAA,IAAC6R,GAAA,CACC,OAAQc,EAAM,KAAK,OACnB,UAAWA,EAAM,KACjB,SAAUuB,EACV,KAAM9J,EAAO,KACb,YAAAlD,CAAA,CAAA,EACF,CAAA,EAGJ,CAEJ,CAEA,SAAS0N,GAAY,CAAE,UAAAI,GAAqC,CAC1D,KAAM,CAAE,EAAAnU,CAAA,EAAMZ,EAAA,EACd,OACE4B,EAAAA,KAAC,MAAA,CAAI,MAAM,wDACT,SAAA,CAAA7B,EAAAA,IAAC,OAAA,CAAK,MAAM,2GAAA,CAA4G,EACxHA,EAAAA,IAAC,OAAA,CAAK,MAAM,kDACT,SAAAgV,EACGnU,EAAE,+BAAgC,6BAA6B,EAC/DA,EAAE,gBAAiB,UAAU,CAAA,CACnC,CAAA,EACF,CAEJ,CAEA,SAASgU,GAAU,CAAE,QAAAnK,GAAgC,CACnD,KAAM,CAAE,EAAA7J,CAAA,EAAMZ,EAAA,EACd,OACE4B,EAAAA,KAAC,MAAA,CAAI,MAAM,oDACT,SAAA,CAAA7B,MAAC,MAAA,CAAI,MAAM,oEACT,SAAA6B,EAAAA,KAAC,OAAI,MAAM,KAAK,OAAO,KAAK,QAAQ,YAAY,KAAK,OAAO,cAAY,OACtE,SAAA,CAAA7B,EAAAA,IAAC,OAAA,CAAK,EAAE,oBAAoB,OAAO,UAAU,eAAa,IAAI,iBAAe,OAAA,CAAQ,EACrFA,EAAAA,IAAC,SAAA,CAAO,GAAG,KAAK,GAAG,KAAK,EAAE,IAAI,OAAO,UAAU,eAAa,MAAA,CAAO,CAAA,CAAA,CACrE,CAAA,CACF,QACC,IAAA,CAAE,MAAM,qDACN,SAAAa,EAAE,sBAAuB,sBAAsB,EAClD,EACAb,EAAAA,IAAC,IAAA,CAAE,MAAM,wCAAyC,SAAA0K,CAAA,CAAQ,CAAA,EAC5D,CAEJ,CAEA,SAASqK,GAAiB,CAAE,SAAAE,GAAsC,CAChE,KAAM,CAAE,EAAApU,CAAA,EAAMZ,EAAA,EACd,OACE4B,EAAAA,KAAC,MAAA,CAAI,MAAM,oDACT,SAAA,CAAA7B,EAAAA,IAAC,MAAA,CACC,MAAM,0DACN,MAAO,CAAE,WAAY,kDAAmD,MAAO,kBAAA,EAC/E,cAAY,OAEZ,SAAA6B,EAAAA,KAAC,OAAI,MAAM,KAAK,OAAO,KAAK,QAAQ,YAAY,KAAK,OACnD,SAAA,CAAA7B,EAAAA,IAAC,OAAA,CAAK,EAAE,gBAAgB,OAAO,eAAe,eAAa,OAAO,kBAAgB,OAAA,CAAQ,EAC1FA,EAAAA,IAAC,OAAA,CAAK,EAAE,eAAe,OAAO,eAAe,eAAa,OAAO,iBAAe,QAAQ,kBAAgB,OAAA,CAAQ,CAAA,CAAA,CAClH,CAAA,CAAA,QAED,IAAA,CAAE,MAAM,qDACN,SAAAa,EAAE,8BAA+B,0BAA0B,EAC9D,QACC,IAAA,CAAE,MAAM,sDACN,SAAAA,EAAE,gCAAiC,gEAAgE,EACtG,EACAb,EAAAA,IAAC,SAAA,CACC,KAAK,SACL,QAASiV,EACT,MAAM,iOACN,MAAO,CACL,WACE,6FACF,UACE,sGAAA,EAGH,SAAApU,EAAE,+BAAgC,eAAe,CAAA,CAAA,CACpD,EACF,CAEJ,CAgBA,SAASiU,GAAoB,CAC3B,OAAA1K,EACA,OAAAjD,EACA,SAAA8N,EACA,QAAAC,CACF,EAKG,CACD,KAAM,CAAE,EAAArU,CAAA,EAAMZ,EAAA,EACR,CAACkV,EAAUC,CAAW,EAAI1V,EAAAA,SAAS,EAAK,EACxC,CAAC2V,EAAcC,CAAe,EAAI5V,EAAAA,SAAS,EAAK,EAChD6V,EAAuBxU,EAAAA,OAA6C,IAAI,EAE9EnB,EAAAA,UAAU,IACD,IAAM,CACP2V,EAAqB,UAAY,MACnC,aAAaA,EAAqB,OAAO,CAE7C,EACC,CAAA,CAAE,EAEL,MAAMC,EAAe,SAAY,CAC/B,GAAI,CAAAL,EACJ,CAAAC,EAAY,EAAI,EAChBE,EAAgB,EAAK,EACrB,GAAI,CAEF,IADa,MAAMlL,EAAO,QAAQ,CAAE,MAAO,GAAM,GACxC,wBAAyB,CAK5B,OAAO,OAAW,KACpB,OAAO,YAAY,CAAE,KAAM,kBAAA,EAAsB,GAAG,EAEtD,MACF,CAGAkL,EAAgB,EAAI,EAChBC,EAAqB,UAAY,MACnC,aAAaA,EAAqB,OAAO,EAE3CA,EAAqB,QAAU,WAAW,IAAM,CAC9CD,EAAgB,EAAK,EACrBC,EAAqB,QAAU,IACjC,EAAG,GAAI,CACT,MAAQ,CACND,EAAgB,EAAI,CACtB,QAAA,CACEF,EAAY,EAAK,CACnB,EACF,EAEA,OACEvT,EAAAA,KAAC,MAAA,CAAI,MAAM,sBACT,SAAA,CAAA7B,EAAAA,IAAC,SAAA,CACC,KAAK,SACL,QAASmH,EACT,MAAM,oNAEL,SAAAtG,EAAE,WAAY,QAAQ,CAAA,CAAA,EAEzBgB,EAAAA,KAAC,MAAA,CAAI,MAAM,oDACT,SAAA,CAAAA,EAAAA,KAAC,MAAA,CAAI,MAAM,sDACT,SAAA,CAAA7B,EAAAA,IAAC,OAAA,CACC,MAAM,wDACN,MAAO,CAAE,WAAY,uDAAA,EACrB,cAAY,MAAA,CAAA,EAEdA,EAAAA,IAAC,OAAA,CAAK,MAAM,oHAAA,CAAqH,CAAA,EACnI,QACC,IAAA,CAAE,MAAM,qDACN,SAAAa,EAAE,yBAA0B,iCAAiC,EAChE,EACAb,EAAAA,IAAC,IAAA,CAAE,MAAM,sDACN,SAAAa,EACC,4BACA,4EAAA,EAEJ,EACAb,EAAAA,IAAC,SAAA,CACC,KAAK,SACL,QAASwV,EACT,SAAUL,EACV,MAAM,8UACN,MAAO,CACL,WACE,6FACF,UACE,sGAAA,EAGH,WAAWtU,EAAE,mBAAoB,WAAW,EAAIA,EAAE,mBAAoB,WAAW,CAAA,CAAA,EAEnFwU,QACE,IAAA,CAAE,MAAM,wCACN,SAAAxU,EAAE,2BAA4B,iEAAiE,CAAA,CAClG,EACE,IAAA,EACN,EACAgB,EAAAA,KAAC,MAAA,CAAI,MAAM,yDACT,SAAA,CAAA7B,MAAC,KAAE,MAAM,wCACN,SAAAa,EAAE,0BAA2B,0EAA0E,EAC1G,EACAb,EAAAA,IAAC,SAAA,CACC,KAAK,SACL,QAASiV,EACT,MAAM,gPAEL,SAAApU,EAAE,8BAA+B,qBAAqB,CAAA,CAAA,CACzD,EACF,EACAb,EAAAA,IAAC,SAAA,CACC,KAAK,SACL,QAASkV,EACT,MAAM,8LAEL,SAAArU,EAAE,2BAA4B,uBAAuB,CAAA,CAAA,CACxD,EACF,CAEJ,CAEA,SAAS8T,GAAoB,CAC3B,WAAAc,EACA,SAAAC,EAAW,EACb,EAOG,CACD,KAAM,CAAE,CAAA,EAAMzV,EAAA,EACd,OACE4B,EAAAA,KAAC,MAAA,CAAI,MAAM,oDACT,SAAA,CAAA7B,EAAAA,IAAC,MAAA,CACC,MAAM,iEACN,MAAO,CACL,WAAY,4CACZ,MAAO,OAEP,UAAW,uEAAA,EAEb,cAAY,OAEZ,SAAAA,EAAAA,IAAC,OAAI,MAAM,KAAK,OAAO,KAAK,QAAQ,YAAY,KAAK,OACnD,SAAAA,EAAAA,IAAC,OAAA,CACC,EAAE,iBACF,OAAO,eACP,eAAa,MACb,iBAAe,QACf,kBAAgB,OAAA,CAAA,CAClB,CACF,CAAA,CAAA,EAEFA,EAAAA,IAAC,IAAA,CAAE,GAAG,WAAW,MAAM,0DACpB,SAAA0V,EACG,EAAE,gCAAiC,uBAAuB,EAC1D,EAAE,+BAAgC,kBAAkB,EAC1D,EACA1V,EAAAA,IAAC,IAAA,CAAE,MAAM,wCACN,SAAA0V,EACG,EACE,mCACA,qDAAA,EAEF,EAAE,kCAAmC,kCAAkC,EAC7E,EACA1V,EAAAA,IAAC,SAAA,CACC,KAAK,SACL,QAASyV,EACT,MAAM,mOACN,MAAO,CACL,WACE,6FACF,UACE,sGAAA,EAGH,SAAA,EAAE,iBAAkB,UAAU,CAAA,CAAA,CACjC,EACF,CAEJ,CCn3BA,MAAME,GAAqB,GAAK,IAC1BC,GAA8B,IAC9BC,GAA6B,IAgB5B,MAAMC,EAAY,CAUvB,YAAYC,EAA0B,CARtC,KAAQ,MAA8C,KACtD,KAAQ,aAAqD,KAC7D,KAAQ,kBAAyC,KACjD,KAAQ,aAAoC,KAC5C,KAAQ,eAAqD,KAC7D,KAAQ,QAAU,GAClB,KAAQ,SAAW,GAGjB,KAAK,KAAO,CACV,OAAQA,EAAK,OACb,SAAUA,EAAK,SACf,UAAWA,EAAK,YAAc,IAAM,CAAC,GACrC,UAAWA,EAAK,WAAaJ,GAC7B,kBAAmBI,EAAK,mBAAqBH,GAC7C,iBAAkBG,EAAK,kBAAoBF,EAAA,CAE/C,CAEA,OAAc,CACR,KAAK,SACL,OAAO,SAAa,KAAe,OAAO,OAAW,MAEpD,KAAK,MAAA,EACV,KAAK,aAAA,EAEL,KAAK,kBAAoB,IAAM,KAAK,uBAAA,EACpC,SAAS,iBAAiB,mBAAoB,KAAK,iBAAiB,EAEpE,KAAK,aAAe,IAAM,KAAK,KAAK,MAAA,EACpC,OAAO,iBAAiB,QAAS,KAAK,YAAY,EAElD,KAAK,eAAkB,GAAoB,KAAK,cAAc,CAAC,EAC/D,OAAO,iBAAiB,UAAW,KAAK,cAAc,EAEtD,KAAK,aAAe,WAAW,IAAM,CAC/B,KAAK,UACT,KAAK,KAAA,EACL,KAAK,KAAK,UAAA,EACZ,EAAG,KAAK,KAAK,SAAS,EACxB,CAEA,MAAa,CACX,KAAK,QAAU,GACX,KAAK,QAAU,MAAM,aAAa,KAAK,KAAK,EAChD,KAAK,MAAQ,KACT,KAAK,eAAiB,MAAM,aAAa,KAAK,YAAY,EAC9D,KAAK,aAAe,KAChB,OAAO,SAAa,KAAe,KAAK,mBAC1C,SAAS,oBAAoB,mBAAoB,KAAK,iBAAiB,EAErE,OAAO,OAAW,MAChB,KAAK,cAAc,OAAO,oBAAoB,QAAS,KAAK,YAAY,EACxE,KAAK,gBAAgB,OAAO,oBAAoB,UAAW,KAAK,cAAc,GAEpF,KAAK,kBAAoB,KACzB,KAAK,aAAe,KACpB,KAAK,eAAiB,IACxB,CAEA,MAAc,OAAuB,CACnC,GAAI,OAAK,SAAW,KAAK,UACzB,MAAK,SAAW,GAChB,GAAI,CACF,MAAMG,EAAO,MAAM,KAAK,KAAK,OAAO,QAAQ,CAAE,MAAO,GAAM,EAC3D,GAAI,KAAK,QAAS,OACdA,EAAK,0BACP,KAAK,KAAA,EACL,KAAK,KAAK,SAASA,CAAI,EAE3B,MAAQ,CAER,QAAA,CACE,KAAK,SAAW,EAClB,EACF,CAEQ,cAAqB,CAC3B,GAAI,KAAK,QAAS,OAGlB,MAAMzE,EADJ,OAAO,SAAa,KAAe,SAAS,kBAAoB,UAE9D,KAAK,KAAK,kBACV,KAAK,KAAK,iBACd,KAAK,MAAQ,WAAW,SAAY,CAClC,MAAM,KAAK,MAAA,EACX,KAAK,aAAA,CACP,EAAGA,CAAQ,CACb,CAEQ,wBAA+B,CACjC,OAAO,SAAa,MACpB,SAAS,kBAAoB,WAAgB,KAAK,MAAA,EAElD,KAAK,QAAU,OACjB,aAAa,KAAK,KAAK,EACvB,KAAK,MAAQ,MAEf,KAAK,aAAA,EACP,CAEQ,cAAc,EAAuB,CAC3C,MAAMoC,EAAO,EAAE,KACX,CAACA,GAAQ,OAAOA,GAAS,UACzBA,EAAK,OAAS,oBACb,KAAK,MAAA,CACZ,CACF,CAMO,SAASsC,IAAgC,CAM9C,MALI,SAAO,SAAa,KACpB,OAAO,OAAW,KAIlB,OAAO,SAAa,KAAe,SAAS,WAAa,oBAI/D,CClIA,MAAMC,GAAqC,CAAE,KAAM,GAAO,KAAM,KAAM,MAAO,IAAA,EAgOvEC,EAAc,CAClB,OAAQ,iBACR,QAAS,mBACT,UAAW,oBACb,EAEO,IAAAC,GAAA,KAAgB,CAyCrB,YAAYL,EAAwB,CAhCpC,KAAQ,OAA6B,KACrC,KAAQ,OAAS,GACjB,KAAQ,cAAgB,IACxB,KAAQ,UAAiC,KACzC,KAAQ,UAAiC,KACzC,KAAQ,QAA8B,KACtC,KAAQ,QAA+B,KACvC,KAAQ,UAAY,GAGpB,KAAQ,WAAgC,KAIxC,KAAQ,iBAAuC,KAE/C,KAAQ,gBAAsC,KAE9C,KAAQ,kBAAoB,GAE5B,KAAQ,eAA0C,KASlD,KAAQ,aAAqCG,GAC7C,KAAQ,mBAAqB,IAK3B,KAAM,CAAE,KAAA5T,EAAM,SAAA+T,GAAaC,GAAYP,CAAI,EAC3C,KAAK,KAAOzT,EACZ,KAAK,SAAW+T,EAKhB,KAAK,QACHN,EAAK,QAAU,IAAIQ,EAAAA,cAAc,CAAE,GAAGR,EAAM,KAAM,KAAK,IAAA,CAAM,EAC/D,KAAK,KAAOA,EAAK,KACjB,KAAK,WAAaA,EAAK,YAAc,SACrC,KAAK,cAAgBA,EAAK,eAAiB,GAC3C,KAAK,OAASA,EAAK,SAAW,GAC9B,KAAK,YAAcA,EAAK,QAAU,KAKlC,KAAK,UAAY,KAAK,QAAQ,aAAcC,GAAS,CACnD,KAAK,KAAK,aAAcA,CAAI,CAC9B,CAAC,EAEG,KAAK,OACP,KAAK,UAAY,KAAK,KAAK,aAAa,CAACQ,EAAOjU,IAAY,CAC1D,KAAK,KAAK,aAAc,CAAE,MAAAiU,EAAO,QAAAjU,EAAS,CAC5C,CAAC,GAGH,KAAK,YAAYwT,EAAK,SAAS,EAE3BA,EAAK,mBAAqB,IAAS,OAAO,OAAW,KAGvD,eAAe,IAAM,KAAK,aAAa,CAE3C,CAEQ,YAAYU,EAAgD,CAClE,GAAIA,IAAc,GAAO,OACzB,MAAMC,EACJ,OAAOD,GAAc,UAAYA,IAAc,KAAOA,EAAY,CAAA,EACpE,GAAIC,EAAI,UAAY,GAAO,OAE3B,MAAMC,EACJD,EAAI,UAAY,GAAG,KAAK,QAAQ,SAAS,mBAAmB,KAAK,QAAQ,SAAS,UAEpF,KAAK,QAAU,IAAIE,eAAa,CAC9B,SAAAD,EACA,UAAW,KAAK,QAAQ,UACxB,aAAc,KAAK,QAAQ,aAC3B,aAAc,IAAM,KAAK,QAAQ,aAAA,EACjC,mBAAoB,IAAM,KAAK,QAAQ,mBAAA,EACvC,UAAW,IAAM,KAAK,QAAQ,YAAA,GAAe,QAAU,KACvD,gBAAiBD,EAAI,gBACrB,cAAeA,EAAI,cACnB,MAAOA,EAAI,MACX,WAAYA,EAAI,UAAA,CACjB,EAKD,KAAK,GAAG,OAAQ,IAAM,KAAK,SAAS,MAAM,gBAAgB,CAAC,EAC3D,KAAK,GAAG,QAAUxE,GAChB,KAAK,SAAS,MAAM,iBAAkB,CACpC,aAAcA,EAAE,SAAS,aACzB,aAAcA,EAAE,OAAO,OACvB,aAAcA,EAAE,OAAO,MAAA,CACxB,CAAA,EAEH,KAAK,GAAG,iBAAmBtN,GACzB,KAAK,SAAS,MAAM,iBAAkB,CAAE,SAAUA,EAAE,OAAA,CAAS,CAAA,EAE/D,KAAK,GAAG,mBAAqBA,GAC3B,KAAK,SAAS,MAAM,mBAAoB,CACtC,SAAUA,EAAE,QACZ,UAAWA,EAAE,SAAA,CACd,CAAA,EAEH,KAAK,GAAG,qBAAuBA,GAC7B,KAAK,SAAS,MAAM,qBAAsB,CACxC,SAAUA,EAAE,QACZ,WAAYA,EAAE,SAAA,CACf,CAAA,EAEH,KAAK,GAAG,kBAAoBA,GAC1B,KAAK,SAAS,MAAM,kBAAmB,CAAE,OAAQA,EAAE,MAAA,CAAQ,CAAA,EAE7D,KAAK,GAAG,QAAS,IAAM,KAAK,SAAS,MAAM,gBAAgB,CAAC,EAC5D,KAAK,GAAG,gBAAkB9G,GACxB,KAAK,SAAS,MAAM,gBAAiB,CACnC,KAAMA,EAAE,KACR,GAAIA,EAAE,OAAS,OACX,CAAE,aAAcA,EAAE,YAAa,SAAUA,EAAE,OAAA,EAC3CA,EAAE,OAAS,QACT,CAAE,kBAAmBA,EAAE,iBAAkB,cAAeA,EAAE,cAC1D,CAAA,CAAC,CACR,CAAA,EAEH,KAAK,GAAG,gBAAiB,IAAM,KAAK,SAAS,MAAM,eAAe,CAAC,EACnE,KAAK,GAAG,qBAAuBG,GAC7B,KAAK,SAAS,MAAM,qBAAsB,CACxC,OAAQA,EAAE,OACV,QAASA,EAAE,QACX,KAAMA,EAAE,IAAA,CACT,CAAA,EAEH,KAAK,GAAG,QAAUkD,GAChB,KAAK,SAAS,MAAM,QAAS,CAAE,KAAMA,EAAE,KAAM,QAASA,EAAE,QAAS,CAAA,CAOrE,CAWA,MAAM0V,EAAcla,EAAuC,CACzD,KAAK,SAAS,MAAMka,EAAMla,CAAK,CACjC,CAOA,aAAama,EAAwD,CACnE,OAAO,KAAK,GAAG,aAAcA,CAAO,CACtC,CAYA,aAAaC,EAA0C,CACrD,KAAK,QAAQ,aAAaA,CAAO,CACnC,CAUA,UAAUvX,EAAyC,CACjD,MAAM6E,EAAO7E,GAAU,KACnB6E,IAAS,KAAK,cAClB,KAAK,YAAcA,EAGf,KAAK,QACP,KAAK,OAAO,OAAO,CAAE,OAAQA,EAAM,EAEvC,CAEA,GAA2BmS,EAAUM,EAA6C,CAChF,IAAIE,EAAM,KAAK,UAAU,IAAIR,CAAK,EAClC,OAAKQ,IACHA,MAAU,IACV,KAAK,UAAU,IAAIR,EAAOQ,CAAG,GAE/BA,EAAI,IAAIF,CAA8B,EAC/B,IAAME,EAAK,OAAOF,CAA8B,CACzD,CAEA,IAA4BN,EAAUM,EAAuC,CAC3E,KAAK,UAAU,IAAIN,CAAK,GAAG,OAAOM,CAA8B,CAClE,CAEQ,KAA6BN,KAAaS,EAAyB,CACzE,MAAMD,EAAM,KAAK,UAAU,IAAIR,CAAK,EACpC,GAAI,CAACQ,EAAK,OACV,MAAM7C,EAAU8C,EAAK,CAAC,EACtB,UAAWH,KAAWE,EACpB,GAAI,CACDF,EAAmC3C,CAAO,CAC7C,OAASxQ,EAAO,CACV,OAAO,QAAY,KAAa,QAAQ,MAAM,2BAA4BA,CAAK,CACrF,CAEJ,CAEA,KAAKoS,EAAoB,GAAU,CACjC,KAAK,aAAa,SAAUA,CAAI,CAClC,CAcA,MAAM,QAAQA,EAAiC,GAAmB,CAChE,GAAI,CACF,MAAM,KAAK,QAAQ,UAAU,CAAE,OAAQA,EAAK,OAAQ,EAGhD,KAAK,QAAQ,MACf,MAAM,KAAK,QAAQ,YAAY,CAAE,OAAQA,EAAK,OAAQ,CAE1D,MAAQ,CAER,CACF,CAYA,YAAYA,EAAoB,GAAU,CACxC,KAAK,aAAa,UAAWA,CAAI,CACnC,CAcA,SAASA,EAAoB,GAAU,CAChC,KAAK,MACV,KAAK,aAAa,OAAQ,CAAE,GAAGA,EAAM,UAAW,GAAM,CACxD,CAmBA,aAAaA,EAAoB,GAAU,CACpC,KAAK,MACV,KAAK,aAAa,OAAQ,CAAE,GAAGA,EAAM,UAAW,GAAM,eAAgB,GAAM,CAC9E,CAEQ,aAAamB,EAAmBnB,EAAyB,CAC3DA,EAAK,UAAU,KAAK,QAAQ,YAAYA,EAAK,QAAQ,EAGzD,KAAK,UAAY,GAOjB,MAAMoB,EAAYpB,EAAK,YAAc,IAAQmB,IAAS,UAChDE,EACJrB,EAAK,iBAAmB,IACxBmB,IAAS,WACTA,IAAS,QACTA,IAAS,OACL/D,EAAQ4C,EAAK,QAAU,GAE7B,GAAIoB,GAAaC,EAAgB,CAC/B,KAAK,aAAaF,EAAM,CAAE,MAAA/D,CAAA,CAAO,EACjC,MACF,CAKA,MAAMtU,EAAS,KAAK,QAAQ,mBAAA,EAC5B,GAAIA,EAAQ,CACV,KAAK,aAAaqY,EAAMrY,EAAQ,CAAE,UAAAsY,EAAW,eAAAC,EAAgB,MAAAjE,EAAO,EACpE,MACF,CAcA,GAAI,KAAK,cAAe,CACtB,KAAK,aAAa+D,EAAM,CAAE,MAAA/D,CAAA,CAAO,EACjC,KAAK,QACF,UAAA,EACA,KAAMjB,GAAM,KAAK,gBAAgBA,EAAG,CAAE,UAAAiF,EAAW,eAAAC,CAAA,CAAgB,CAAC,EAClE,MAAM,IAAM,CAEb,CAAC,EACH,MACF,CAEA,KAAK,QACF,UAAA,EACA,KAAMlF,GAAM,KAAK,aAAagF,EAAMhF,EAAG,CAAE,UAAAiF,EAAW,eAAAC,EAAgB,MAAAjE,CAAA,CAAO,CAAC,EAC5E,MAAM,IAAM,CAEX,KAAK,aAAa+D,EAAM,CAAE,MAAA/D,CAAA,CAAO,CACnC,CAAC,CACL,CAKQ,gBACN5U,EACA8Y,EACM,CACN,GAAI,CAAC,KAAK,OAAQ,OAElB,GAAI,CAACA,EAAM,eAAgB,CACzB,MAAMpZ,EAAIM,EAAU,SAAS,WAC7B,GAAIN,IACF,KAAK,eAAiBA,EAClB,CAACA,EAAE,SAAS,CACd,KAAK,MAAA,EACL,KAAK,KAAK,qBAAsBA,CAAC,EACjC,MACF,CAEJ,CAEA,GAAIoZ,EAAM,UAAW,OAErB,MAAMC,EAAW/Y,EAAU,SAAS,MACpC,GAAI,CAAC+Y,EAAU,OACf,MAAMC,EAAQ,KAAK,iBAAiBD,CAAQ,EACvCC,EACF,MAAA,EACA,KAAK,MAAOC,GAAW,CACtB,GAAK,KAAK,SACV,KAAK,gBAAkBA,EACnBA,EAAO,OAAS,QACpB,IAAIA,EAAO,QAAS,CAClB,MAAMC,EAAU,MAAMF,EAAM,YAAA,EAE5B,GADA,KAAK,gBAAkBE,EACnB,CAAC,KAAK,OAAQ,OAClB,KAAK,MAAA,EACL,KAAK,KAAK,gBAAiBA,CAAO,EAClC,MACF,CACK,KAAK,oBACR,KAAK,kBAAoB,GACzB,KAAK,KAAK,eAAe,GAE7B,CAAC,EACA,MAAOtW,GAAM,CACR,OAAO,QAAY,KAAa,QAAQ,KAAK,+BAAgCA,CAAC,CACpF,CAAC,CACL,CAMQ,aACN+V,EACA3Y,EACA8Y,EACM,CACN,GAAI,CAACA,EAAM,eAAgB,CACzB,MAAMpZ,EAAIM,EAAU,SAAS,WAC7B,GAAIN,IACF,KAAK,eAAiBA,EAClB,CAACA,EAAE,SAAS,CACd,KAAK,KAAK,qBAAsBA,CAAC,EACjC,MACF,CAEJ,CAEA,GAAIoZ,EAAM,UAAW,CACnB,KAAK,aAAaH,EAAM,CAAE,MAAOG,EAAM,MAAO,EAC9C,MACF,CACA,KAAK,iBAAiBH,EAAM3Y,EAAW8Y,EAAM,KAAK,CACpD,CAEQ,iBAAiBH,EAAmB3Y,EAA6B4U,EAAsB,CAC7F,MAAMmE,EAAW/Y,EAAU,SAAS,MACpC,GAAI,CAAC+Y,EAAU,CACb,KAAK,aAAaJ,EAAM,CAAE,MAAA/D,CAAA,CAAO,EACjC,MACF,CACA,MAAMoE,EAAQ,KAAK,iBAAiBD,CAAQ,EACvCC,EACF,MAAA,EACA,KAAK,MAAOC,GAAW,CAEtB,GADA,KAAK,gBAAkBA,EACnBA,EAAO,OAAS,OAAQ,CAC1B,KAAK,aAAaN,EAAM,CAAE,MAAA/D,CAAA,CAAO,EACjC,MACF,CACA,GAAIqE,EAAO,QAAS,CAIlB,MAAMC,EAAU,MAAMF,EAAM,YAAA,EAC5B,KAAK,gBAAkBE,EACvB,KAAK,KAAK,gBAAiBA,CAAO,EAClC,MACF,CAGK,KAAK,oBACR,KAAK,kBAAoB,GACzB,KAAK,KAAK,eAAe,GAE3B,KAAK,aAAaP,EAAM,CAAE,MAAA/D,CAAA,CAAO,CACnC,CAAC,EACA,MAAOhS,GAAM,CAGR,OAAO,QAAY,KAAa,QAAQ,KAAK,+BAAgCA,CAAC,EAClF,KAAK,aAAa+V,EAAM,CAAE,MAAA/D,CAAA,CAAO,CACnC,CAAC,CACL,CAEQ,iBAAiBuE,EAAiC,CACxD,GAAI,KAAK,YAAc,KAAK,kBAAoBC,GAAgB,KAAK,iBAAkBD,CAAM,EAC3F,OAAO,KAAK,WAEd,KAAK,iBAAmBA,EAIxB,MAAME,EAAa,KAAK,QACrB,iBACH,YAAK,WACH,OAAOA,GAAc,WACjBA,EAAU,KAAK,KAAK,QAASF,CAAM,EACnCG,mBAAiB,KAAK,QAAQ,WAAA,EAAc,KAAK,QAAQ,UAAWH,CAAM,EACzE,KAAK,UACd,CAEQ,aAAaR,EAAmBY,EAAiC,GAAU,CACjF,MAAM3E,EAAQ2E,EAAU,QAAU,GAClC,GAAI,KAAK,OAAQ,CACf,KAAK,OAAS,GACd,KAAK,OAAO,OAAO,CAAE,KAAM,GAAM,YAAaZ,EAAM,UAAW,GAAO,MAAA/D,CAAA,CAAO,EAC7E,KAAK,KAAK,MAAM,EAChB,MACF,CAEA,KAAK,OAAS,GACd,KAAK,OAAS1W,GACZuW,GACA,CACE,OAAQ,KAAK,QACb,KAAM,GACN,YAAakE,EACb,UAAW,GACX,MAAA/D,EACA,QAAS,IAAM,KAAK,MAAA,EACpB,QAAS,CAACqD,EAAOrC,IAAY,CAC3B,KAAK,KAAKqC,EAAuBrC,CAAgB,EAG7CqC,IAAU,oBAAoB,KAAK,iBAAA,CACzC,EACA,QAAUuB,GAAa,KAAK,WAAWA,CAAQ,EAC/C,OAAQ,KAAK,OACb,OAAQ,KAAK,WAAA,EAEf,CAAE,KAAM,KAAK,KAAM,WAAY,KAAK,WAAY,OAAQ,KAAK,MAAA,CAAO,EAEtE,KAAK,KAAK,MAAM,CAClB,CAEQ,WAAWA,EAAsC,CACvD,GAAI,CAAAC,GAAkB,KAAK,aAAcD,CAAQ,EACjD,MAAK,aAAeA,EACpB,UAAWE,KAAM,KAAK,eACpB,GAAI,CACFA,EAAGF,CAAQ,CACb,OAAS5W,EAAG,CACV,QAAQ,KAAK,yCAA0CA,CAAC,CAC1D,EAEJ,CAUA,UAAiC,CAC/B,OAAO,KAAK,YACd,CAYA,cACE8W,EACAlC,EAAsD,GAC1C,CACZ,KAAK,eAAe,IAAIkC,CAAE,EAC1B,MAAMhW,EAAO8T,EAAK,WAAa,YAC/B,GAAI9T,IAAS,OAAQ,CACnB,MAAM8V,EAAW,KAAK,aACtB,GAAI9V,IAAS,OACX,GAAI,CACFgW,EAAGF,CAAQ,CACb,OAAS5W,EAAG,CACV,QAAQ,KAAK,6CAA8CA,CAAC,CAC9D,MAEA,eAAe,IAAM,CACf,KAAK,eAAe,IAAI8W,CAAE,KAAMF,CAAQ,CAC9C,CAAC,CAEL,CACA,MAAO,IAAM,CACX,KAAK,eAAe,OAAOE,CAAE,CAC/B,CACF,CAKA,gBAAqC,CACnC,OAAO,KAAK,eACd,CAOA,eAAyC,CACvC,OAAO,KAAK,cACd,CAQA,UAAUlC,EAAkD,GAA6B,CACvF,OAAO,KAAK,QAAQ,UAAUA,CAAI,CACpC,CAGA,iBAAyC,CACvC,OAAO,KAAK,QAAQ,gBAAA,CACtB,CAKA,iBAAoC,CAClC,OAAO,KAAK,QAAQ,gBAAA,CACtB,CAuBA,MAAM,UAAUA,EAAyB,GAAkC,CACzE,IAAIxX,EAAY,KAAK,QAAQ,mBAAA,EAC7B,GAAI,CAACA,EACH,GAAI,CACFA,EAAY,MAAM,KAAK,QAAQ,UAAU,CAAE,OAAQwX,EAAK,OAAQ,CAClE,MAAQ,CAIN,MAAMlX,EAAS,KAAK,QAAQ,cAAA,EAC5B,OAAIA,GAAQ,wBACH,CACL,OAAQ,UACR,OAAQ,mBACR,WAAY,KACZ,MAAO,KACP,KAAMA,CAAA,EAGH,CACL,OAAQ,UACR,OAAQ,kBACR,WAAY,KACZ,MAAO,KACP,KAAMA,CAAA,CAEV,CAGF,MAAMmX,EAAOzX,EAAU,MAAQ,KAE/B,GAAIyX,GAAM,wBACR,MAAO,CACL,OAAQ,UACR,OAAQ,mBACR,WAAYzX,EAAU,SAAS,YAAc,KAC7C,MAAO,KACP,KAAAyX,CAAA,EAIJ,IAAIkC,EAAsC,KAC1C,GAAI,CAACnC,EAAK,eAAgB,CACxB,MAAM9X,EAAIM,EAAU,SAAS,WAC7B,GAAIN,IACFia,EAAaja,EACb,KAAK,eAAiBA,EAClB,CAACA,EAAE,SACL,MAAO,CAAE,OAAQ,UAAW,OAAQ,qBAAsB,WAAAia,EAAY,MAAO,KAAM,KAAAlC,CAAA,CAGzF,CAEA,IAAImC,EAA4B,KAChC,GAAI,CAACpC,EAAK,UAAW,CACnB,MAAMuB,EAAW/Y,EAAU,SAAS,MACpC,GAAI+Y,EACF,GAAI,CAIF,GAFAa,EAAQ,MADM,KAAK,iBAAiBb,CAAQ,EACxB,MAAA,EACpB,KAAK,gBAAkBa,EACnBA,EAAM,QACR,MAAO,CAAE,OAAQ,UAAW,OAAQ,gBAAiB,WAAAD,EAAY,MAAAC,EAAO,KAAAnC,CAAA,CAE5E,OAAS7U,EAAG,CACN,OAAO,QAAY,KAAa,QAAQ,KAAK,0CAA2CA,CAAC,CAC/F,CAEJ,CAEA,MAAO,CAAE,OAAQ,UAAW,OAAQ,kBAAmB,WAAA+W,EAAY,MAAAC,EAAO,KAAAnC,CAAA,CAC5E,CAIA,MAAM,YAA4B,CAC3B,KAAK,aACV,MAAM,KAAK,WAAW,MAAA,EACtB,KAAK,gBAAkB,KACvB,KAAK,kBAAoB,GAC3B,CAQQ,kBAAyB,CAC3B,KAAK,SACJC,OAEL,KAAK,QAAU,IAAIH,GAAY,CAC7B,OAAQ,KAAK,QACb,SAAWE,GAAS,CAClB,KAAK,QAAU,KAKf,KAAK,KAAK,qBAAsB,CAAE,QAAS,KAAM,UAAW,KAAM,EAMlE,MAAMoC,EAAW,KAAK,QACnB,mBAAA,GACC,SAAS,qBACb,GAAIA,GAAY,OAAO,OAAW,IAChC,GAAI,CACF,OAAO,SAAS,OAAOA,CAAQ,EAC/B,MACF,MAAQ,CAER,CAME,KAAK,QAAU,KAAK,SACtB,KAAK,UAAY,GACjB,KAAK,OAAO,OAAO,CAAE,UAAW,GAAM,EAG1C,EACA,UAAW,IAAM,CACf,KAAK,QAAU,IACjB,CAAA,CACD,EACD,KAAK,QAAQ,MAAA,EACf,CAEA,OAAc,CACR,CAAC,KAAK,QAAU,CAAC,KAAK,SAC1B,KAAK,OAAS,GACd,KAAK,UAAY,GACjB,KAAK,OAAO,OAAO,CAAE,KAAM,GAAO,UAAW,GAAO,EAIpD,KAAK,WAAWlC,EAAY,EAC5B,KAAK,KAAK,OAAO,EACnB,CAQA,aAAoB,CAClB,GAAI,OAAO,OAAW,IAAa,OACnC,MAAMjC,EAAM,IAAI,IAAI,OAAO,SAAS,IAAI,EAElCoE,EAAcC,GAAarE,EAAI,KAAK,QAAQ,KAAM,EAAE,CAAC,EACrDsE,EAAgBD,GAAarE,EAAI,OAAO,QAAQ,MAAO,EAAE,CAAC,EAC1DuE,EAAUH,GAAeE,EAC1BC,IAEDA,EAAQ,SAAW,QACrB,KAAK,KAAK,qBAAsB,CAC9B,QAASA,EAAQ,QACjB,UAAWA,EAAQ,SAAA,CACpB,EAKDC,GAAuBD,CAAO,IACrBA,EAAQ,SAAW,UAAYA,EAAQ,SAAW,cAC3D,KAAK,KAAK,kBAAmB,CAAE,OAAQA,EAAQ,OAAQ,EAGzDE,GAAoBzE,CAAG,EACzB,CAEA,SAAgB,CACd,KAAK,SAAS,QAAA,EACd,KAAK,QAAU,KACf,KAAK,UAAU,MAAA,EACf,KAAK,eAAe,MAAA,EACpB,KAAK,SAAS,KAAA,EACd,KAAK,QAAU,KACf,KAAK,YAAA,EACL,KAAK,UAAY,KACjB,KAAK,YAAA,EACL,KAAK,UAAY,KAKb,KAAK,UAAY,KAAK,MAGxB,KAAK,KAAK,UAAA,EAEZ,KAAK,SAAW,GAChB,KAAK,QAAQ,UAAA,EACb,KAAK,QAAQ,QAAA,EACb,KAAK,OAAS,KACd,KAAK,OAAS,GACd,KAAK,aAAeiC,EACtB,CACF,EAEA,SAASI,GAAYP,EAGnB,CACA,GAAI,CAACA,EAAK,KAAM,MAAO,CAAE,KAAM,OAAW,SAAU,EAAA,EAOpD,GAAIA,EAAK,gBAAgB4C,EAAAA,YAAcC,GAAiB7C,EAAK,IAAI,EAC/D,MAAO,CAAE,KAAMA,EAAK,KAAoB,SAAU,EAAA,EAKpD,MAAMW,EAAMX,EAAK,OAAS,GAAO,CAAA,EAAKA,EAAK,KAC3C,MAAO,CACL,KAAM,IAAI4C,EAAAA,WAAW,CACnB,UAAW5C,EAAK,UAChB,UAAWW,EAAI,WAAaX,EAAK,UACjC,QAASW,EAAI,SAAWX,EAAK,QAC7B,MAAOW,EAAI,OAASX,EAAK,MACzB,UAAWW,EAAI,SAAA,CAChB,EACD,SAAU,EAAA,CAEd,CAMA,SAASkC,GAAiB7Y,EAAqC,CAC7D,GAAI,OAAOA,GAAU,UAAYA,IAAU,KAAM,MAAO,GACxD,MAAM9B,EAAI8B,EACV,OACE,OAAO9B,EAAE,cAAiB,YAC1B,OAAOA,EAAE,kBAAqB,YAC9B,OAAOA,EAAE,SAAY,UAEzB,CAEA,SAAS+Z,GACPjF,EACAb,EACS,CACT,OAAOa,EAAE,OAASb,EAAE,MAAQa,EAAE,OAASb,EAAE,MAAQa,EAAE,QAAUb,EAAE,KACjE,CAEA,SAASyF,GAAgB5E,EAAgBb,EAAyB,CAChE,OAAOa,EAAE,OAASb,EAAE,MAAQa,EAAE,UAAYb,EAAE,SAAWa,EAAE,UAAYb,EAAE,OACzE,CAEA,SAASoG,GACPO,EAC6E,CAC7E,GAAI,CAACA,EAAS,OAAO,KACrB,MAAMnb,EAAS,IAAI,gBAAgBmb,CAAO,EACpCrB,EAAS9Z,EAAO,IAAIyY,EAAY,MAAM,EAC5C,OAAKqB,EACE,CACL,OAAAA,EACA,QAAS9Z,EAAO,IAAIyY,EAAY,OAAO,EACvC,UAAWzY,EAAO,IAAIyY,EAAY,SAAS,CAAA,EAJzB,IAMtB,CAKA,SAASsC,GAAuBD,EAIvB,CACP,GAAI,SAAO,OAAW,KAAe,CAAC,OAAO,QAC7C,GAAI,CACF,OAAO,OAAO,YACZ,CACE,KAAM,mBACN,OAAQA,EAAQ,OAChB,QAASA,EAAQ,QACjB,UAAWA,EAAQ,SAAA,EAErB,GAAA,CAEJ,MAAQ,CAER,CACF,CAEA,SAASE,GAAoBzE,EAAgB,CAC3C,MAAM6E,EAAQ,CAACC,EAAaC,IAA8B,CACxD,GAAI,CAACD,EAAK,MAAO,GACjB,MAAMnU,EAAI,IAAI,gBAAgBmU,EAAI,QAAQ,QAAS,EAAE,CAAC,EACtDnU,EAAE,OAAOuR,EAAY,MAAM,EAC3BvR,EAAE,OAAOuR,EAAY,OAAO,EAC5BvR,EAAE,OAAOuR,EAAY,SAAS,EAC9B,MAAMpY,EAAM6G,EAAE,SAAA,EACd,OAAO7G,EAAMib,EAASjb,EAAM,EAC9B,EACMsG,EAAO4P,EAAI,SAAW6E,EAAM7E,EAAI,OAAQ,GAAG,EAAI6E,EAAM7E,EAAI,KAAM,GAAG,EACxE,OAAO,QAAQ,aAAa,KAAM,GAAI5P,CAAI,CAC5C,CCztCO,MAAM4U,EAAuC,CAClD,YACmBC,EACAC,EACAzB,EACjB,CAHiB,KAAA,UAAAwB,EACA,KAAA,UAAAC,EACA,KAAA,OAAAzB,CAChB,CAEH,MAAM,OAA8B,CAClC,OAAO,KAAK,UAAU,QAAQ,cAAe,CAC3C,UAAW,KAAK,UAChB,OAAQ,KAAK,MAAA,CACd,CACH,CAEA,MAAM,aAAoC,CACxC,OAAO,KAAK,UAAU,QAAQ,oBAAqB,CACjD,UAAW,KAAK,UAChB,OAAQ,KAAK,MAAA,CACd,CACH,CAEA,MAAM,OAAuB,CAC3B,MAAM,KAAK,UAAU,QAAQ,cAAe,CAC1C,UAAW,KAAK,UAChB,OAAQ,KAAK,MAAA,CACd,CACH,CACF,CCLO,MAAM0B,EAAoB,CA6B/B,YACmBF,EACjBnD,EACA,CAFiB,KAAA,UAAAmD,EAvBnB,KAAQ,gBAA2C,KACnD,KAAQ,WAAiC,KACzC,KAAQ,eAAmC,KAC3C,KAAQ,SAA4B,KAapC,KAAQ,kBAAoB,IAC5B,KAAQ,qBAAuB,IAC/B,KAAQ,uBAAyB,IACjC,KAAQ,mBAA0C,KAClD,KAAQ,uBAA8C,KAMpD,KAAK,UAAYnD,EAAK,UACtB,KAAK,UAAYA,EAAK,UAEtB,KAAK,qBAAuB,CAC1B,QAAU1X,GAAQ,KAAK,UAAU,QAAQ,cAAe,CAAE,IAAAA,EAAK,EAC/D,QAAS,MAAOA,EAAK0B,IAAU,CAC7B,MAAM,KAAK,UAAU,QAAQ,cAAe,CAAE,IAAA1B,EAAK,MAAA0B,EAAO,CAC5D,EACA,WAAY,MAAO1B,GAAQ,CACzB,MAAM,KAAK,UAAU,QAAQ,iBAAkB,CAAE,IAAAA,EAAK,CACxD,CAAA,EAMF,KAAK,mBAAqB,KAAK,UAAU,GAAG,aAAe2X,GAAS,CAClE,KAAK,UAAUA,CAAI,CACrB,CAAC,EAED,KAAK,uBAAyB,KAAK,UAAU,GAAG,iBAAmBqD,GAAa,CAC9E,KAAK,cAAc,CAAC,GAAGA,CAAQ,CAAC,CAClC,CAAC,CACH,CAIA,MAAM,UAAUtD,EAAkD,GAA+B,CAC/F,MAAMjC,EAAS,MAAM,KAAK,UAAU,QAClC,oBACA,CAAE,MAAOiC,EAAK,KAAA,EACd,CAAE,OAAQA,EAAK,MAAA,CAAO,EAExB,YAAK,eAAejC,CAAM,EACtBA,EAAO,MAAM,KAAK,UAAUA,EAAO,IAAI,EACpCA,CACT,CAEA,oBAA8C,CAC5C,OAAO,KAAK,eACd,CAUA,kBACEmE,EACAlC,EAAsD,GAC1C,CACZ,KAAK,mBAAmB,IAAIkC,CAAE,EAC9B,MAAMhW,EAAO8T,EAAK,WAAa,YAC/B,GAAI,KAAK,iBAAmB9T,IAAS,OAAQ,CAC3C,MAAM8V,EAAW,KAAK,gBACtB,GAAI9V,IAAS,OACX,GAAI,CACFgW,EAAGF,CAAQ,CACb,OAAS5W,EAAG,CACV,QAAQ,KAAK,iDAAkDA,CAAC,CAClE,MAEA,eAAe,IAAM,CACf,KAAK,mBAAmB,IAAI8W,CAAE,KAAMF,CAAQ,CAClD,CAAC,CAEL,CACA,MAAO,IAAM,CACX,KAAK,mBAAmB,OAAOE,CAAE,CACnC,CACF,CAIA,MAAM,UAAUlC,EAAkD,GAA6B,CAE7F,OADU,MAAM,KAAK,UAAUA,CAAI,GAC1B,MACX,CAGA,iBAAyC,CACvC,OAAO,KAAK,iBAAiB,QAAU,IACzC,CAIA,MAAM,cAAgC,CACpC,OAAO,KAAK,UAAU,QAAQ,uBAAwB,MAAS,CACjE,CAIA,MAAM,QAAQA,EAAkD,GAA0B,CACxF,MAAMjC,EAAS,MAAM,KAAK,UAAU,QAClC,kBACA,CAAE,MAAOiC,EAAK,KAAA,EACd,CAAE,OAAQA,EAAK,MAAA,CAAO,EAExB,YAAK,UAAUjC,CAAM,EACdA,CACT,CAEA,eAAoC,CAClC,OAAO,KAAK,UACd,CAKA,aACEmE,EACAlC,EAAsD,GAC1C,CACZ,KAAK,cAAc,IAAIkC,CAAE,EACzB,MAAMhW,EAAO8T,EAAK,WAAa,YAC/B,GAAI,KAAK,YAAc9T,IAAS,OAAQ,CACtC,MAAM8V,EAAW,KAAK,WACtB,GAAI9V,IAAS,OACX,GAAI,CACFgW,EAAGF,CAAQ,CACb,OAAS5W,EAAG,CACV,QAAQ,KAAK,4CAA6CA,CAAC,CAC7D,MAEA,eAAe,IAAM,CACf,KAAK,cAAc,IAAI8W,CAAE,KAAMF,CAAQ,CAC7C,CAAC,CAEL,CACA,MAAO,IAAM,CACX,KAAK,cAAc,OAAOE,CAAE,CAC9B,CACF,CAIA,MAAM,YAAYlC,EAAkD,GAAwB,CAM1F,MAAMzJ,EAAM,CAAC,GALE,MAAM,KAAK,UAAU,QAClC,sBACA,CAAE,MAAOyJ,EAAK,KAAA,EACd,CAAE,OAAQA,EAAK,MAAA,CAAO,CAEF,EACtB,YAAK,cAAczJ,CAAG,EACfA,CACT,CAEA,mBAAsC,CACpC,OAAO,KAAK,cACd,CAEA,gBACE2L,EACAlC,EAAsD,GAC1C,CACZ,KAAK,iBAAiB,IAAIkC,CAAE,EAC5B,MAAMhW,EAAO8T,EAAK,WAAa,YAC/B,GAAI,KAAK,gBAAkB9T,IAAS,OAAQ,CAC1C,MAAM8V,EAAW,KAAK,eACtB,GAAI9V,IAAS,OACX,GAAI,CACFgW,EAAGF,CAAQ,CACb,OAAS5W,EAAG,CACV,QAAQ,KAAK,+CAAgDA,CAAC,CAChE,MAEA,eAAe,IAAM,CACf,KAAK,iBAAiB,IAAI8W,CAAE,KAAMF,CAAQ,CAChD,CAAC,CAEL,CACA,MAAO,IAAM,CACX,KAAK,iBAAiB,OAAOE,CAAE,CACjC,CACF,CAIA,MAAM,eAAeva,EASO,CAC1B,KAAM,CAAE,OAAA4b,EAAQ,GAAGnF,CAAA,EAAYzW,EAC/B,OAAO,KAAK,UAAU,QAAQ,yBAA0ByW,EAAS,CAAE,OAAAmF,EAAQ,CAC7E,CAQA,MAAM,cAAcvD,EAAiC,GAAwC,CAI3F,MAAO,CAAC,GAHO,MAAM,KAAK,UAAU,QAAQ,wBAAyB,OAAW,CAC9E,OAAQA,EAAK,MAAA,CACd,CACgB,CACnB,CAMA,MAAM,oBAAoB5B,EAK8B,CACtD,OAAO,KAAK,UAAU,QAAQ,8BAA+BA,CAAO,CACtE,CAKA,MAAM,mBAAmBzW,EAWtB,CACD,KAAM,CAAE,OAAA4b,EAAQ,GAAGnF,CAAA,EAAYzW,EAC/B,OAAO,KAAK,UAAU,QAAQ,6BAA8ByW,EAAS,CAAE,OAAAmF,EAAQ,CACjF,CAOA,YAA6B,CAC3B,OAAO,KAAK,oBACd,CAOA,iBAAiB5B,EAAiC,CAChD,OAAO,IAAIuB,GAAiB,KAAK,UAAW,KAAK,UAAWvB,CAAM,CACpE,CAIA,aAA+B,CAC7B,OAAO,KAAK,QACd,CAEA,MAAM,YAAY6B,EAA0C,CAC1D,KAAK,SAAWA,EAChB,MAAM,KAAK,UAAU,QAAQ,sBAAuB,CAAE,SAAAA,EAAU,CAClE,CAKA,MAAM,cAAyC,CAC7C,MAAMzF,EAAS,MAAM,KAAK,UAAU,QAAQ,sBAAuB,MAAS,EAC5E,YAAK,SAAWA,EACTA,CACT,CAEA,SAAgB,CACd,KAAK,qBAAA,EACL,KAAK,yBAAA,EACL,KAAK,mBAAqB,KAC1B,KAAK,uBAAyB,KAC9B,KAAK,cAAc,MAAA,EACnB,KAAK,iBAAiB,MAAA,EACtB,KAAK,mBAAmB,MAAA,EACxB,KAAK,gBAAkB,KACvB,KAAK,WAAa,KAClB,KAAK,eAAiB,KACtB,KAAK,SAAW,IAClB,CAEQ,eAAevV,EAAmC,CACxD,KAAK,gBAAkBA,EACvB,UAAW0Z,IAAM,CAAC,GAAG,KAAK,kBAAkB,EAC1C,GAAI,CACFA,EAAG1Z,CAAS,CACd,OAAS4C,EAAG,CACV,QAAQ,KAAK,6CAA8CA,CAAC,CAC9D,CAEJ,CAMQ,UAAU6U,EAAyB,CACrCwD,GAAS,KAAK,WAAYxD,CAAI,IAClC,KAAK,WAAaA,EAClB,KAAK,kBAAkBA,CAAI,EAC7B,CAEQ,cAAcqD,EAA2B,CAC3CI,GAAa,KAAK,eAAgBJ,CAAQ,IAC9C,KAAK,eAAiBA,EACtB,KAAK,qBAAqBA,CAAQ,EACpC,CAEQ,kBAAkBrD,EAAyB,CACjD,UAAWiC,IAAM,CAAC,GAAG,KAAK,aAAa,EACrC,GAAI,CACFA,EAAGjC,CAAI,CACT,OAAS7U,EAAG,CACV,QAAQ,KAAK,wCAAyCA,CAAC,CACzD,CAEJ,CAEQ,qBAAqBkY,EAA2B,CACtD,UAAWpB,IAAM,CAAC,GAAG,KAAK,gBAAgB,EACxC,GAAI,CACFA,EAAGoB,CAAQ,CACb,OAASlY,EAAG,CACV,QAAQ,KAAK,2CAA4CA,CAAC,CAC5D,CAEJ,CACF,CAEA,SAASqY,GAASzG,EAAuBb,EAAgC,CACvE,OAAIa,IAAMb,EAAU,GAChB,CAACa,GAAK,CAACb,EAAU,GAEnBa,EAAE,0BAA4Bb,EAAE,0BAC/Ba,EAAE,WAAW,QAAU,MAAQb,EAAE,WAAW,QAAU,EAE3D,CAEA,SAASuH,GAAa1G,EAAqBb,EAA8B,CACvE,GAAIa,IAAMb,EAAG,MAAO,GAEpB,GADI,CAACa,GAAK,CAACb,GACPa,EAAE,SAAWb,EAAE,OAAQ,MAAO,GAClC,QAASzF,EAAI,EAAGA,EAAIsG,EAAE,OAAQtG,IAC5B,GAAIsG,EAAEtG,CAAC,EAAE,OAASyF,EAAEzF,CAAC,EAAE,MAAQsG,EAAEtG,CAAC,EAAE,QAAUyF,EAAEzF,CAAC,EAAE,MAAO,MAAO,GAEnE,MAAO,EACT,CCtYO,MAAMiN,EAAiB,CAS5B,YACmBR,EACjBnD,EACA,CAFiB,KAAA,UAAAmD,EANnB,KAAQ,QAA8B,KACtC,KAAQ,cAAgB,IACxB,KAAQ,eAAsC,KAO5C,KAAK,UAAYnD,EAAK,UACtB,KAAK,UAAYA,EAAK,UAEtB,KAAK,eAAiB,KAAK,UAAU,GAAG,aAAc,CAAC,CAAE,MAAAS,EAAO,QAAAjU,KAAc,CAC5E,KAAK,aAAaiU,EAAOjU,CAAO,CAClC,CAAC,EAOD,KAAK,SAAW,KAAK,UAClB,QAAQ,wBAAyB,MAAS,EAC1C,KAAMA,GAAY,CAIb,KAAK,UAAY,MAAQA,IAAY,OACvC,KAAK,QAAUA,EAEnB,CAAC,EACA,MAAM,IAAM,CAEb,CAAC,CACL,CAIA,OAAuB,CACrB,OAAO,KAAK,QACd,CAEA,kBAAuC,CACrC,OAAO,KAAK,OACd,CAEA,eAAiC,CAC/B,OAAO,KAAK,SAAS,MAAQ,IAC/B,CAEA,aAAa0V,EAAoC,CAC/C,YAAK,UAAU,IAAIA,CAAE,EAIhB,KAAK,SAAS,KAAK,IAAM,CAC5B,GAAK,KAAK,UAAU,IAAIA,CAAE,EAC1B,GAAI,CACFA,EAAG,kBAAmB,KAAK,OAAO,CACpC,OAAS9W,EAAG,CACV,QAAQ,KAAK,+CAAgDA,CAAC,CAChE,CACF,CAAC,EACM,IAAM,CACX,KAAK,UAAU,OAAO8W,CAAE,CAC1B,CACF,CAIA,MAAM,gBAAgB0B,EAAkE,CACtF,MAAMpX,EAAU,MAAM,KAAK,UAAU,QAAQ,uBAAwBoX,CAAK,EAI1E,YAAK,aAAa,YAAapX,CAAO,EAC/BA,CACT,CAEA,MAAM,OAAOoX,EAIa,CACxB,MAAM7F,EAAS,MAAM,KAAK,UAAU,QAAQ,cAAe6F,CAAK,EAChE,OAAI7F,EAAO,OAAS,kBAAkB,aAAa,YAAaA,EAAO,OAAO,EACvEA,CACT,CAEA,MAAM,SAAyB,CAC7B,MAAM,KAAK,UAAU,QAAQ,eAAgB,MAAS,CAIxD,CAEA,MAAM,SAAuC,CAC3C,MAAMvR,EAAU,MAAM,KAAK,UAAU,QAAQ,eAAgB,MAAS,EACtE,YAAK,aAAaA,EAAU,kBAAoB,aAAcA,CAAO,EAC9DA,CACT,CAIA,MAAM,QAAQoX,EAII,CAChB,MAAM,KAAK,UAAU,QAAQ,eAAgBA,CAAK,CACpD,CAEA,MAAM,UAAUA,EAIS,CACvB,MAAMpX,EAAU,MAAM,KAAK,UAAU,QAAQ,iBAAkBoX,CAAK,EACpE,YAAK,aAAaA,EAAM,OAAS,WAAa,oBAAsB,YAAapX,CAAO,EACjFA,CACT,CAEA,MAAM,mBAAmBoX,EAAyC,CAChE,MAAM,KAAK,UAAU,QAAQ,0BAA2BA,CAAK,CAC/D,CAEA,MAAM,qBAAqBA,EAAyC,CAClE,MAAM,KAAK,UAAU,QAAQ,4BAA6BA,CAAK,CACjE,CAEA,MAAM,eAAeA,EAA4C,CAC/D,MAAM,KAAK,UAAU,QAAQ,sBAAuBA,CAAK,CAC3D,CAEA,MAAM,mBAAmC,CACvC,MAAM,KAAK,UAAU,QAAQ,yBAA0B,MAAS,CAClE,CAKA,MAAM,cAA0C,CAC9C,OAAO,KAAK,UAAU,QAAQ,oBAAqB,MAAS,CAC9D,CAQA,MAAM,kBAAkBA,EAIpB,GAA0B,CAC5B,MAAMpX,EAAU,MAAM,KAAK,UAAU,QAAQ,yBAA0B,CACrE,aAAcoX,EAAM,aACpB,SAAUA,EAAM,SAChB,aAAcA,EAAM,YAAA,CACrB,EACD,YAAK,aAAa,YAAapX,CAAO,EAC/BA,CACT,CAMA,MAAM,gBAAyC,CAC7C,OAAO,KAAK,UAAU,QAAQ,sBAAuB,MAAS,CAChE,CAkBA,MAAM,gBAAgBoX,EAKG,CACvB,GAAI,OAAO,OAAW,IACpB,MAAM,IAAIzX,EAAAA,aAAa,oBAAqB,8BAA8B,EAY5E,MAAM0X,EAAW,oBAAoB,KAAK,OAAA,EAAS,SAAS,EAAE,EAAE,MAAM,EAAG,EAAE,CAAC,GACtE7F,EAAQ,OAAO,KAAK,cAAe6F,EAAU,gCAAgC,EACnF,GAAI,CAAC7F,EACH,MAAM,IAAI7R,EAAAA,aACR,gBACA,uDAAA,EAGJ2X,GAAe9F,EAAO4F,EAAM,QAAQ,EAEpC,GAAI,CAGF,KAAM,CAAE,aAAAG,EAAc,MAAAnH,CAAA,EAAU,MAAM,KAAK,UAAU,QAAQ,kBAAmB,CAC9E,SAAUgH,EAAM,SAChB,OAAQA,EAAM,OACd,SAAUA,EAAM,QAAA,CACjB,EAMD5F,EAAM,KAAO,YAAYpB,CAAK,GAC9BoB,EAAM,SAAS,QAAQ+F,CAAY,EAEnCH,EAAM,gBAAA,EAEN,MAAMI,EAAO,MAAMC,mBAAiBjG,EAAOpB,CAAK,EAC1CpQ,EAAU,MAAM,KAAK,UAAU,QAAQ,qBAAsB,CAAE,MAAAoQ,EAAO,KAAAoH,EAAM,EAClF,YAAK,aAAa,YAAaxX,CAAO,EAC/BA,CACT,OAASpB,EAAG,CACV,GAAI,CACF4S,EAAM,MAAA,CACR,MAAQ,CAER,CACA,MAAM5S,CACR,CACF,CAEA,SAAgB,CACd,KAAK,iBAAA,EACL,KAAK,eAAiB,KACtB,KAAK,UAAU,MAAA,EACf,KAAK,QAAU,IACjB,CAEQ,aAAaqV,EAAwBnS,EAAgC,CAC3E,GAAI,CAAA4V,GAAY,KAAK,QAAS5V,CAAI,EAClC,MAAK,QAAUA,EACf,UAAW4T,IAAM,CAAC,GAAG,KAAK,SAAS,EACjC,GAAI,CACFA,EAAGzB,EAAOnS,CAAI,CAChB,OAASlD,EAAG,CACV,QAAQ,KAAK,wCAAyCA,CAAC,CACzD,EAEJ,CACF,CAEA,SAAS8Y,GAAYlH,EAAuBb,EAAgC,CAC1E,OAAIa,IAAMb,EAAU,GAChB,CAACa,GAAK,CAACb,EAAU,GAEnBa,EAAE,eAAiBb,EAAE,cACrBa,EAAE,gBAAkBb,EAAE,eACtBa,EAAE,aAAeb,EAAE,YACnBa,EAAE,KAAK,KAAOb,EAAE,KAAK,EAEzB,CAEA,MAAMgI,GAAyC,CAC7C,OAAQ,SACR,MAAO,QACP,OAAQ,SACR,SAAU,UACZ,EAYA,SAASL,GAAe9F,EAAehS,EAAwB,CAC7D,MAAM8U,EAAOqD,GAAenY,CAAQ,GAAKA,EACzC,GAAI,CACF,MAAMoY,EAAMpG,EAAM,SAClBoG,EAAI,MAAQ,gBAAgBtD,CAAI,GAEhC,MAAM7Z,EAAQmd,EAAI,cAAc,OAAO,EACvCnd,EAAM,YACJ,ogBAKFmd,EAAI,KAAK,YAAYnd,CAAK,EAE1B,MAAMod,EAAOD,EAAI,cAAc,KAAK,EACpCC,EAAK,UAAY,gBACjB,MAAMC,EAAUF,EAAI,cAAc,KAAK,EACvCE,EAAQ,UAAY,mBACpB,MAAMrU,EAAQmU,EAAI,cAAc,KAAK,EACrCnU,EAAM,UAAY,iBAClBA,EAAM,YAAc,iBAAiB6Q,CAAI,IACzCuD,EAAK,YAAYC,CAAO,EACxBD,EAAK,YAAYpU,CAAK,EACtBmU,EAAI,KAAK,YAAYC,CAAI,CAC3B,MAAQ,CAER,CACF,CC/VO,MAAME,EAAmB,CAC9B,YAA6BpB,EAA4B,CAA5B,KAAA,UAAAA,CAA6B,CAI1D,MAAMrC,EAAcla,EAAuC,CACrD,OAAOka,GAAS,UAAYA,EAAK,SAAW,GAChD,KAAK,UAAU,QAAQ,gBAAiB,CAAE,KAAAA,EAAM,MAAAla,EAAO,EAAE,MAAOwE,GAAM,CACpE,QAAQ,KAAK,yBAA0BA,CAAC,CAC1C,CAAC,CACH,CACF,CCkBO,MAAMoZ,EAAgB,CAW3B,YAA6BC,EAAyB,CAAzB,KAAA,QAAAA,EAV7B,KAAQ,QAAiC,KACzC,KAAQ,iBAAsC,CAAA,EAC9C,KAAQ,YAAc,IACtB,KAAQ,cAAgB,IACxB,KAAQ,UAAY,GACpB,KAAQ,OAAS,EAGjB,KAAiB,SAAW,KAAK,KAAK,OAAA,EAAS,SAAS,EAAE,EAAE,MAAM,EAAG,EAAE,CAAC,EAEjB,CAK/C,eAAgC,CACtC,GAAI,KAAK,UAAW,MAAM,IAAI,MAAM,2BAA2B,EAC/D,GAAI,KAAK,QAAS,OAAO,KAAK,QAE9B,MAAMC,EAAU,KAAK,QAAA,EACrB,KAAK,QAAUA,EAEf,MAAMC,EAASD,EAAQ,UAAWE,GAAQ,KAAK,cAAcA,CAAG,CAAC,EAC3DC,EAAUH,EAAQ,aAAa,IAAM,KAAK,kBAAkB,EAClE,YAAK,iBAAmB,CAACC,EAAQE,CAAO,EAKnC,KAAK,QAAQ,YAAa,CAC7B,gBAAiBC,EAAAA,iBACjB,SAAU,KAAK,QAAA,CAChB,EACE,KAAMC,GAAQ,CACTA,EAAI,kBAAoBD,oBAC1B,QAAQ,KACN,qDAAqDA,EAAAA,gBAAgB,eACtDC,EAAI,eAAe,mDAAA,CAGxC,CAAC,EACA,MAAM,IAAM,CAEb,CAAC,EAEIL,CACT,CAEQ,cAAcM,EAAyB,CAC7C,GAAKC,GAAWD,CAAQ,EACxB,IAAIA,EAAS,OAAS,WAAY,CAChC,MAAMjc,EAAU,KAAK,QAAQ,IAAIic,EAAS,EAAE,EAC5C,GAAI,CAACjc,EAAS,OACd,KAAK,QAAQ,OAAOic,EAAS,EAAE,EAC/Bjc,EAAQ,QAAQ,oBAAoB,QAASA,EAAQ,aAAc,EAC/Dic,EAAS,GACXjc,EAAQ,QAAQic,EAAS,MAAM,EAI/Bjc,EAAQ,OAAOmc,EAAAA,iBAAkBF,EAAyB,KAAK,CAAC,EAElE,MACF,CACA,GAAIA,EAAS,OAAS,QAAS,CAC7B,MAAM/D,EAAM,KAAK,UAAU,IAAI+D,EAAS,IAAI,EAC5C,GAAI,CAAC/D,EAAK,OAEV,UAAWF,IAAW,CAAC,GAAGE,CAAG,EAC3B,GAAI,CACFF,EAAQiE,EAAS,OAAO,CAC1B,OAAS5Z,EAAG,CACV,QAAQ,MAAM,sCAAuCA,CAAC,CACxD,CAEJ,EACF,CAEQ,kBAAyB,CAC/B,UAAW+Z,KAAM,KAAK,iBAAkBA,EAAA,EACxC,KAAK,iBAAmB,CAAA,EACxB,KAAK,QAAU,KAEf,MAAMpc,EAAU,MAAM,KAAK,KAAK,QAAQ,QAAQ,EAChD,KAAK,QAAQ,MAAA,EACb,UAAW8F,KAAK9F,EACd8F,EAAE,QAAQ,oBAAoB,QAASA,EAAE,aAAc,EACvDA,EAAE,OAAO,IAAIuW,EAA4B,CAE7C,CAEA,QACEC,EACA1d,EACAqY,EAAiC,CAAA,EACN,CAC3B,GAAI,KAAK,UACP,OAAO,QAAQ,OAAO,IAAI,MAAM,2BAA2B,CAAC,EAE9D,GAAIA,EAAK,QAAQ,QACf,OAAO,QAAQ,OAAO,IAAI,aAAa,UAAW,YAAY,CAAC,EAGjE,MAAM0E,EAAU,KAAK,cAAA,EACfY,EAAK,IAAI,EAAE,KAAK,MAAM,GAE5B,OAAO,IAAI,QAA0B,CAACC,EAASC,IAAW,CACxD,MAAMzc,EAA0B,CAC9B,QAAAwc,EACA,OAAAC,EACA,OAAQxF,EAAK,MAAA,EAGXA,EAAK,SACPjX,EAAQ,cAAgB,IAAM,CAC5B,GAAI,KAAK,QAAQ,OAAOuc,CAAE,EAAG,CAC3BE,EAAO,IAAI,aAAa,UAAW,YAAY,CAAC,EAIhD,GAAI,CACFd,EAAQ,KAAK,CAAE,KAAM,SAAU,GAAAY,EAAI,CACrC,MAAQ,CAER,CACF,CACF,EACAtF,EAAK,OAAO,iBAAiB,QAASjX,EAAQ,aAAa,GAG7D,KAAK,QAAQ,IAAIuc,EAAIvc,CAAO,EAE5B,MAAMic,EAA8C,CAClD,KAAM,UACN,GAAAM,EACA,KAAAD,EACA,OAAA1d,CAAA,EAEF,GAAI,CACF+c,EAAQ,KAAKM,CAAQ,CACvB,OAAS5Z,EAAG,CACV,KAAK,QAAQ,OAAOka,CAAE,EACtBtF,EAAK,QAAQ,oBAAoB,QAASjX,EAAQ,aAAc,EAChEyc,EAAOpa,CAAC,CACV,CACF,CAAC,CACH,CAEA,GACEia,EACAtE,EACY,CACZ,IAAIE,EAAM,KAAK,UAAU,IAAIoE,CAAI,EAC5BpE,IACHA,MAAU,IACV,KAAK,UAAU,IAAIoE,EAAMpE,CAAG,GAE9B,MAAMwE,EAAU1E,EAChB,OAAAE,EAAI,IAAIwE,CAAO,EAIf,KAAK,cAAA,EAEE,IAAM,CACXxE,EAAK,OAAOwE,CAAO,CACrB,CACF,CAEA,SAAgB,CACd,GAAI,KAAK,UAAW,OACpB,KAAK,UAAY,GACjB,UAAWN,KAAM,KAAK,iBAAkBA,EAAA,EACxC,KAAK,iBAAmB,CAAA,EACxB,KAAK,UAAU,MAAA,EACf,MAAMpc,EAAU,MAAM,KAAK,KAAK,QAAQ,QAAQ,EAChD,KAAK,QAAQ,MAAA,EACb,UAAW8F,KAAK9F,EACd8F,EAAE,QAAQ,oBAAoB,QAASA,EAAE,aAAc,EACvDA,EAAE,OAAO,IAAI,MAAM,2BAA2B,CAAC,EAEjD,KAAK,SAAS,MAAA,EACd,KAAK,QAAU,IACjB,CACF,CAEO,MAAMuW,WAAmC,KAAM,CAEpD,aAAc,CACZ,MAAM,4CAA4C,EAFpD,KAAS,KAAO,yBAGd,KAAK,KAAO,4BACd,CACF,CAEA,SAASH,GAAWjb,EAA6E,CAC/F,GAAI,OAAOA,GAAU,UAAYA,IAAU,KAAM,MAAO,GACxD,MAAMc,EAAKd,EAA6B,KACxC,OAAOc,IAAM,WAAaA,IAAM,YAAcA,IAAM,OACtD,CCrOA,IAAIhC,EAAiC,KAE9B,SAAS4c,IAAuC,CACrD,OAAI5c,IACJA,EAAS,IAAI0b,GAAgB,IAAMmB,EAAAA,qBAAqBC,GAAAA,SAAS,CAAC,EAC3D9c,EACT,CCgBO,MAAM+c,WAAkBC,EAAc,CAM3C,YAAY9F,EAAiC,CAC3C,MAAMmD,EAAYuC,GAAA,EAEZK,EAAU,IAAI1C,GAAoBF,EAAW,CACjD,UAAWnD,EAAK,UAChB,UAAWA,EAAK,SAAA,CACjB,EAMD,IAAIzT,EACAyT,EAAK,OAAS,GAChBzT,EAAO,IAAIoX,GAAiBR,EAAW,CACrC,UAAWnD,EAAK,UAChB,UAAWA,EAAK,SAAA,CACjB,EACQA,EAAK,MACd,QAAQ,KACN,gMAAA,EAUAzT,IACDwZ,EAAmC,KAAOxZ,GAG7C,MAAM,CACJ,GAAGyT,EAKH,OAAQ+F,EACR,KAAAxZ,EAGA,UAAW,EAAA,CACZ,EAhDH,KAAQ,cAA2C,KACnD,KAAQ,cAAmC,CAAA,EAiDrCyT,EAAK,YAAc,KACrB,KAAK,cAAgB,IAAIuE,GAAmBpB,CAAS,EACrD,KAAK,cAAA,EAET,CAKQ,eAAsB,CAC5B,MAAMrY,EAAI,KAAK,cACVA,GAEL,KAAK,cAAc,KACjB,KAAK,GAAG,OAAQ,IAAMA,EAAE,MAAM,gBAAgB,CAAC,EAC/C,KAAK,GAAG,QAAUqR,GAChBrR,EAAE,MAAM,iBAAkB,CACxB,aAAcqR,EAAE,SAAS,aACzB,aAAcA,EAAE,OAAO,OACvB,aAAcA,EAAE,OAAO,MAAA,CACxB,CAAA,EAEH,KAAK,GAAG,iBAAmBtN,GACzB/D,EAAE,MAAM,iBAAkB,CAAE,SAAU+D,EAAE,OAAA,CAAS,CAAA,EAEnD,KAAK,GAAG,mBAAqBA,GAC3B/D,EAAE,MAAM,mBAAoB,CAAE,SAAU+D,EAAE,QAAS,UAAWA,EAAE,SAAA,CAAW,CAAA,EAE7E,KAAK,GAAG,qBAAuBA,GAC7B/D,EAAE,MAAM,qBAAsB,CAAE,SAAU+D,EAAE,QAAS,WAAYA,EAAE,SAAA,CAAW,CAAA,EAEhF,KAAK,GAAG,kBAAoBA,GAAM/D,EAAE,MAAM,kBAAmB,CAAE,OAAQ+D,EAAE,MAAA,CAAQ,CAAC,EAClF,KAAK,GAAG,QAAS,IAAM/D,EAAE,MAAM,gBAAgB,CAAC,EAChD,KAAK,GAAG,gBAAkB/C,GACxB+C,EAAE,MAAM,gBAAiB,CACvB,KAAM/C,EAAE,KACR,GAAIA,EAAE,OAAS,OACX,CAAE,aAAcA,EAAE,YAAa,SAAUA,EAAE,OAAA,EAC3CA,EAAE,OAAS,QACT,CAAE,kBAAmBA,EAAE,iBAAkB,cAAeA,EAAE,cAC1D,CAAA,CAAC,CACR,CAAA,EAEH,KAAK,GAAG,gBAAiB,IAAM+C,EAAE,MAAM,eAAe,CAAC,EACvD,KAAK,GAAG,qBAAuB5C,GAC7B4C,EAAE,MAAM,qBAAsB,CAAE,OAAQ5C,EAAE,OAAQ,QAASA,EAAE,QAAS,KAAMA,EAAE,KAAM,CAAA,EAEtF,KAAK,GAAG,QAAUkD,GAAMN,EAAE,MAAM,QAAS,CAAE,KAAMM,EAAE,KAAM,QAASA,EAAE,OAAA,CAAS,CAAC,CAAA,CAQlF,CAKA,MAAM0V,EAAcla,EAAuC,CACzD,KAAK,eAAe,MAAMka,EAAMla,CAAK,CACvC,CAEA,SAAgB,CACd,UAAWue,KAAM,KAAK,cAAeA,EAAA,EACrC,KAAK,cAAgB,CAAA,EACrB,KAAK,cAAgB,KACrB,MAAM,QAAA,CACR,CACF"}
1
+ {"version":3,"file":"content.cjs","sources":["../../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/shared/transport-client.ts","../src/content/transport.ts","../src/content/PaywallUI.ts"],"sourcesContent":["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 определяет утилиты вроде `.border` через `border-style: var(--tw-border-style)`,\n// где значение `solid` задаётся реестровой проперти `@property --tw-border-style { initial-value: solid }`.\n// В Chromium `@property`-объявления внутри shadow root не регистрируются document-wide, переменная\n// остаётся пустой → IACVT → border-style: none → used border-width: 0. Чтобы шорткаты работали в\n// shadow scope, регистрируем те же `@property` на уровне document один раз. `inherits: false`\n// держит изоляцию: имя проперти видно глобально, но значения на host-страницу не утекают.\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 // Уже зарегистрирована другим инстансом SDK на той же странице — ок.\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-режим: host позиционируется `absolute inset:0` (не fixed),\n * чтобы модалка осталась в границах своего родителя. Используется в\n * live-preview редактора админки. Родитель ОБЯЗАН быть positioned\n * (`position: relative|absolute|fixed`), иначе absolute уйдёт выше. */\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 // Fixed-viewport (production) или absolute-внутри-host'а (inline preview).\n // pointer-events:none — клики проходят сквозь host'овую обёртку к шахмат-\n // ке формы редактора; mountPoint ниже сам себе включит auto.\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 // Без host'а из options и без inline — крепим к body. Inline ожидает, что\n // host уже находится в нужном parent'е (платформа передаёт hostRef).\n if (!host.isConnected && !options.inline) document.body.appendChild(host);\n\n // Дефолт `closed` — изоляция от хост-страницы. В e2e/demo тестах\n // включаем `open` через опцию, иначе Playwright не проходит через\n // shadow boundary accessibility-снапшотом и не кликает по внутренним кнопкам.\n const shadow = host.attachShadow({ mode: options.shadowMode ?? 'closed' });\n\n // Защита от протечки наследуемых свойств (color, font, letter-spacing, text-transform,\n // cursor, visibility) с host-страницы внутрь shadow через host-элемент. `!important`\n // в shadow перебивает внешний `!important` на host (спека CSS Scoping).\n // Рендер-фильтры (filter, transform, opacity) на ancestors защитить нельзя —\n // они применяются на уровне композитинга.\n const hostReset = `\n:host {\n all: initial !important;\n display: block !important;\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 * Полный список встроенных static-translations языков. Каждый ключ\n * соответствует файлу в `./locales/<key>.ts`, который Vite разносит в\n * отдельный chunk (`chunks/<key>-[hash].js`).\n *\n * Порядок зеркалит legacy `online/lang/static-translations.ts`: 27 языков,\n * паритет с пейволом на старом стеке. EN — fallback, всегда inline в\n * основной чанк, в этом списке его нет.\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/** Словарь переводов: ключ → строка. Может содержать `{param}` placeholder'ы,\n * которые заполняет `t()` через второй аргумент. Отсутствующий ключ → fallback\n * из inline-вызова `t(key, fallback)`. */\nexport type TranslationDict = Record<string, string>;\n\n/** Сигнатура translator-функции. Inline fallback — обязательный, чтобы EN\n * работал даже без загруженного чанка и при отсутствии ключа в словаре. */\nexport type TFn = (\n key: string,\n fallback: string,\n params?: Record<string, string | number>\n) => string;\n","/**\n * Static-translations для UI-chrome SDK v3. Legacy-аналог —\n * `online/components/StaticTranslationContext.tsx` + `online/lang/static-translations.ts`.\n *\n * Архитектура:\n * - EN захардкожен fallback'ом в самих компонентах (`t('auth.welcome', 'Welcome back!')`).\n * Если чанк не загрузился — UI на английском, без пустых строк.\n * - Не-EN языки — отдельные модули `./locales/<key>.ts`. Vite разносит их в\n * `chunks/<key>-[hash].js`, динамический import грузит один чанк\n * по resolved-locale.\n * - Owner-controlled `bootstrap.locales` (layout/prices оверрайды) — независимая\n * система; static-чанк применяется только если у owner'а вообще есть\n * переводы (`when-configured` режим, как в legacy). Без translation-intent\n * у пейвола показывается чистый EN, даже если в SDK есть `de.ts`.\n * - Чанк грузится в фоне, **не блокирует** `bootstrap()`. До прибытия —\n * UI на EN; на arrival провайдер форсит re-render через 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/** Простая подстановка `{name}` → value. Не делает escape — все строки уходят\n * в textContent через preact, XSS невозможен. */\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/** Кеш загруженных словарей, чтобы переоткрытие пейвола не ходило за чанком\n * повторно (Vite кеширует module внутри себя, но cache на нашей стороне\n * избавляет от микро-затыка в Promise-цепочке). */\nconst dictCache = new Map<BundledLocale, TranslationDict>();\n\n/** Inflight-load'ы, чтобы конкурентные mount'ы (виджет + popup) делили один\n * динамический import вместо двух параллельных. */\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/** Подбирает встроенный язык по тому же алгоритму, что owner-overrides\n * (`pickLocaleKey` в BillingClient): `navigator.language` → base-tag →\n * `settings.locale_default`. Возвращает первый ключ, для которого у нас\n * есть чанк в `BUNDLED_LOCALES`. EN не возвращаем — это inline fallback,\n * загружать ничего не надо. */\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 применяется только если у owner'а есть\n * dynamic-override **именно для resolved-локали**. Без этого юзер видел бы\n * смесь — статический UI на nl + dynamic-content (heading/features/banner)\n * на canonical-EN, потому что админ перевёл только на ru. Юзеры в локалях\n * без override'ов получают чистый 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/** Грузит словарь для указанного языка. Идемпотентно: повторные вызовы\n * отдают тот же кешированный Promise. На сетевой/импорт-ошибке резолвится\n * пустым словарём (UI останется на EN-fallback'ах) — пейвол не должен\n * падать из-за недоступного locale-чанка. */\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 разносит этот dynamic import по chunkFileNames из vite.config.ts.\n // Шаблонная строка нужна, чтобы bundler сгенерил все 27 чанков; статичный\n // import('./locales/${key}.ts') без шаблона свернётся в один файл.\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 /** PaywallBootstrap, по которому резолвится язык. null/undefined — провайдер\n * работает в чистом EN-fallback режиме, чанк не грузится. */\n bootstrap: PaywallBootstrap | null | undefined;\n /** Explicit-override: форсит выбор языка минуя navigator.language и\n * owner-translations check. Используется live-preview редактором админки\n * («Preview as user from <country>») — там browser-locale всегда EN, а\n * bootstrap.locales может быть пустым (форма ещё не сохранена). Передавай\n * только bundled-ключи из `BUNDLED_LOCALES` — иначе fallback на обычный\n * путь резолвинга. */\n forceLocale?: string | null;\n children: ComponentChildren;\n}\n\n/**\n * Mount'ит provider, резолвит язык по bootstrap'у и асинхронно тянет чанк.\n * До прибытия чанка t() отдаёт fallback'и из inline-вызовов (EN). После —\n * setState триггерит re-render всех consumer'ов.\n *\n * Bootstrap может прийти позже (loading state в PaywallRoot) — useEffect\n * запустится на bootstrap-change и подхватит. Bootstrap может смениться\n * (revalidate подтянул другие locales/locale_default) — useEffect разрулит:\n * если resolved key изменился, грузим новый чанк, иначе остаёмся на текущем.\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: preview-режим админки. Грузим напрямую — owner-check\n // и navigator.language игнорируем (browser-locale в админке всегда 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: грузим static только если для resolved-локали\n // есть dynamic override. Иначе fallback на EN — без миксa NL static +\n // EN dynamic content.\n if (!hasOwnerTranslationsFor(bootstrap, resolved)) return null;\n return resolved;\n })();\n\n // Нет резолва (или explicit=null в preview при возврате на EN-страну) —\n // откатываемся на canonical-EN fallback из inline t()-вызовов. Без сброса\n // старый dict остаётся в state и UI остаётся переведённым на предыдущий\n // язык — это и был баг live-preview при переключении со RU обратно на 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/** Хук для блоков: `const { t } = useI18n(); t('auth.welcome', 'Welcome back!')`.\n * Вне I18nProvider'а возвращает defaultT (EN-fallback) — позволяет блокам\n * рендериться в тестах/preview без обязательного 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 /** Контент, который приклеивается сверху dialog'а внутри overlay (rounded-top,\n * с лёгким negative-margin для визуального overlap). Используется для\n * offer-countdown баннера (PaywallRoot решает рисовать его, если в\n * bootstrap.offers есть активный таймер). */\n topBanner?: ComponentChildren;\n /** Можно ли закрыть модалку: ESC, клик по overlay, крестик. По умолчанию true.\n * false — модалка остаётся открытой до явного host-close() / success-purchase. */\n allowClose?: boolean;\n /** Скрыть X-крестик (но оставить ESC/overlay рабочими). Используется когда\n * view внутри модалки рисует свою Back-кнопку (AuthGate, SupportGate) —\n * две одновременные кнопки в правом верхнем углу путают и визуально\n * накладываются. */\n hideCloseButton?: boolean;\n /** Inline-режим: overlay позиционируется `absolute inset:0` относительно host'а\n * (вместо `fixed` относительно viewport'а), не лочит body-scroll. Для live-\n * 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 const first = dialog.querySelector<HTMLElement>(FOCUSABLE);\n (first ?? 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 не лочит body-scroll: host-страница (редактор) должна\n // оставаться кликабельной/скроллабельной, пока модалка живёт 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: overlay сидит в `absolute inset-0` host'а (host сам absolute в parent'е,\n // см. mount.ts). Production: `fixed inset-0` относительно 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 над dialog'ом. topBanner (если передан) рендерится тут же,\n приклеивается к верху dialog'а через `-mb-2 pb-5 rounded-t-xl\n rounded-b-none` — даёт визуальный overlap (rounded-top банера и\n rounded-top dialog'а скрыты под банером), как в легаси PaywallModal. */}\n {/* --pw-accent определяется на wrapper'е (не на dialog'е) — чтобы\n topBanner-sibling тоже его наследовал. Раньше переменная сидела на\n dialog, и OfferTopBanner получал 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 ограничивает высоту вьюпортом (использует dvh для мобильных\n // safe-area), flex-col + overflow на children даёт внутренний скролл\n // когда контент выше viewport'а — критично для extension popup'ов\n // (max 600px высоты) и узких контейнеров на сайтах.\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 сами структурируют scroll/footer-зоны (см. Renderer.tsx):\n flex-1 min-h-0 overflow-y-auto для scrollable, остаток — footer.\n Раньше Modal оборачивал в обёртку overflow-y-auto, но это не\n позволяло прибить CTA-footer к нижней кромке без overlap скролла\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 относительно dialog'а (не scrollable area) — кнопка\n // всегда в правом верхнем углу dialog'а, не двигается со скроллом\n // и не влияет на 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\ntype Mode = 'signin' | 'signup' | 'signup_verify' | 'forgot' | 'reset_sent' | 'reset_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` из ApiClient на ошибках бэка без `message`-поля = HTTP statusText\n// (\"Unauthorized\", \"Bad Request\") — англоязычный и сырой. Маппим стабильные\n// `err.code` на i18n-ключи; для всего непонятного — generic fallback вместо\n// statusText. `code` приходит из тела ответа (`payload.code`) либо\n// `http_<status>`, либо `network_error` (см. 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 in a moment.');\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 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 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 // Анон-сессия — это «нет авторизации»: анон годится только для api-gateway,\n // покупка/restore требуют реального 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 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 ctx: BlockProps<AuthPanelBlock>['ctx'];\n}\n\nfunction AuthForm({ block, allowSignup, allowReset, ctx }: FormProps) {\n const { t } = useI18n();\n const auth = ctx.auth!;\n const providers = block.providers ?? [];\n\n // initialAuthMode из ctx — host вызвал openSignup() / openSignin().\n // Если admin отключил signup (allow_signup=false), 'signup' игнорируем\n // и стартуем с 'signin' — соблюдаем admin-настройку.\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 const [error, setError] = useState<string | null>(null);\n const [info, setInfo] = useState<string | null>(null);\n // Sign up — progressive disclosure: первый клик «Sign Up» только раскрывает\n // password+confirm; второй клик с заполненными полями делает реальный signUp.\n // По смене mode сбрасываем — переход signin↔signup всегда начинается с\n // collapsed формы.\n const [signupExpanded, setSignupExpanded] = useState(false);\n\n // Last-used auth метод и email (per-paywall). Async-load из storage на mount,\n // пока null — UI просто рендерится без бейджа. Pre-fill email только если\n // юзер ещё ничего не вводил — иначе перезапишем то, что он печатает.\n //\n // Defensive: старые билды @monetize.software/sdk-extension (≤ 3.0.0-alpha.4)\n // не реализовали getLastLogin в RemoteAuthClient — без guard'а consumer\n // получал бы `auth.getLastLogin is not a function` в консоли. Бейдж в этом\n // случае просто не показывается, signin продолжает работать.\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 недоступен — UI без бейджа, signin работает */\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 };\n\n const onSubmit = async (e: Event): Promise<void> => {\n e.preventDefault();\n if (busy) return;\n setError(null);\n setInfo(null);\n\n // Sign up shortcut: первый сабмит просто раскрывает password-поля,\n // без сетевого запроса. Email обязателен на этом шаге, иначе HTML5\n // validation сама пометит поле required.\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 // Email-confirm flow ≠ password-reset: пароль уже задан, нужен\n // только OTP. Чистим password, чтобы не утащить его в signup_verify\n // submit и не получить ложную ветку type='recovery'.\n setPassword('');\n setMode('signup_verify');\n setInfo(t('auth.check_email_message', 'Check your email for a confirmation code.'));\n }\n } else if (mode === 'forgot') {\n await auth.requestPasswordReset({ email });\n setMode('reset_sent');\n setInfo(\n t('auth.reset_sent_message', 'If that email exists, a reset code has been sent.')\n );\n } else if (mode === 'signup_verify') {\n await auth.verifyOtp({ email, token: otpCode, type: 'email' });\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 }\n } catch (err) {\n const errMode =\n mode === 'signup' ? 'signup'\n : mode === 'signup_verify' || mode === 'reset_verify' ? 'otp'\n : mode === 'forgot' ? 'reset' : 'signin';\n setError(authErrorMessage(err, errMode, t));\n } finally {\n setBusy(null);\n }\n };\n\n const onOAuth = async (provider: OAuthProvider): Promise<void> => {\n if (busy) return;\n setBusy(provider);\n setError(null);\n setInfo(null);\n try {\n await auth.signInWithOAuth({\n provider,\n onPopupOpened: () => setBusy(null)\n });\n } catch (err) {\n if (err instanceof PaywallError && (err.code === 'oauth_cancelled' || err.code === 'oauth_timeout')) {\n return;\n }\n setError(authErrorMessage(err, 'signin', t));\n } finally {\n setBusy(null);\n }\n };\n\n const showOAuth = providers.length > 0 && (mode === 'signin' || mode === 'signup');\n const showEmailField = mode === 'signin' || mode === 'signup' || mode === 'forgot';\n const showPasswordField =\n mode === 'signin' || (mode === 'signup' && signupExpanded);\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 === 'signup_verify' || mode === 'reset_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 === 'reset_sent' && info && (\n <p class=\"rounded-2xl bg-gray-100 px-4 py-3 text-sm text-gray-600\">{info}</p>\n )}\n\n {mode === 'signin' && allowReset && (\n <div class=\"flex justify-end text-sm\">\n <AccentLink onClick={() => switchTo('forgot')}>\n {t('auth.forgot_password', 'Forgot password?')}\n </AccentLink>\n </div>\n )}\n\n {error && <p class=\"text-sm text-red-600\">{error}</p>}\n {info && mode !== 'reset_sent' && (\n <p class=\"text-sm text-gray-500\">{info}</p>\n )}\n\n {mode !== 'reset_sent' && (\n <PrimaryButton\n busy={busy === 'email'}\n label={submitLabel(mode, signupExpanded, block.submit_label ?? block.heading, t)}\n />\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'ят default для signin+signup mode'ов.\n // Restore/preauth intent ставит свой heading, но когда юзер кликает\n // \"Forgot password?\" — view меняется на forgot и должен показать\n // дефолтный \"Forgot password?\" заголовок, а не intent-specific строку.\n // Reset views (forgot/reset_sent/reset_verify) всегда используют дефолты.\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 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 'signup_verify':\n return {\n title: t('auth.confirm_email_title', 'Confirm your email'),\n subtitle: t(\n 'auth.confirm_email_subtitle',\n 'Enter the code we sent to your email to finish creating your account.'\n )\n };\n }\n}\n\nfunction submitLabel(\n mode: Mode,\n signupExpanded: boolean,\n customHeading: string | undefined,\n t: TFn\n): string {\n // Если задан customHeading — он используется и как submit-лейбл для signin\n // (\"Restore Purchases\" → button \"Restore Purchases\"). Для остальных mode'ов\n // submit-лейбл фиксированный (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 'signup_verify':\n case 'reset_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' || mode === 'signup_verify') {\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 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 при смене type между password↔text стирают .value (autofill-guard).\n // Preact видит ту же value-prop и не ре-сетит DOM — поле остаётся пустым.\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 // Pill в правом верхнем углу кнопки. truncate + max-w защищают от длинного\n // email'а (выйдет за края кнопки). pointer-events-none — чтобы клик попадал\n // в саму кнопку, а не в бейдж сверху.\n const label = email\n ? t('auth.last_used', 'Last · {email}', { email: maskEmail(email) })\n : t('auth.last_used_no_email', 'Last');\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. Маскируем local-part (видны\n// первые 3 символа), domain оставляем как есть — он публичен и помогает\n// юзеру опознать аккаунт.\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 даёт воздух сверху/снизу пути, поэтому визуально\n // Apple-яблоко выглядит меньше Google. Компенсируем увеличенным\n // width/height — 26×26 даёт примерно equal optical size с Google 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","import type { AuthClient, AuthSession } 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/** Контекст, из которого AuthGate был открыт. Управляет дефолтным заголовком/\n * субзаголовком, чтобы юзер сразу понимал зачем его сюда привели:\n * - `restore` — клик \"Restore purchases\" в current_session\n * - `preauth` — checkout_mode=preauth перед /start-checkout\n * - `standalone` — paywall.openAuth() (без всякого layout-контекста) */\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 /** Показывать кнопку Back. Для preauth/restore-flow — true (юзер пришёл сюда\n * из layout). Для standalone openAuth() — false: модалка открыта только\n * ради signin'а, ESC и крестик модалки уже её закрывают. */\n showBack?: boolean;\n intent?: AuthIntent;\n /** Какой mode выставить в AuthPanel на старте. Host вызвал openSignup()\n * → 'signup', openSignin()/openAuth() → 'signin' (дефолт). */\n initialMode?: 'signin' | 'signup';\n}\n\n// Полноэкранная обёртка над AuthPanel для AuthGate flow. AuthPanel сам не\n// знает про \"вернуться к тарифам\"; gate рисует Back-кнопку curved-arrow\n// в top-right (как на легаси-скринах) и подсовывает intent-specific heading.\nexport function AuthGate({\n block,\n bootstrap,\n auth,\n authSession,\n onBack,\n showBack = true,\n intent = 'preauth',\n initialMode\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 };\n\n // intent override'ит heading/subheading layout-block'а:\n // - 'restore' → \"Restore Purchases\" / sign-in-to-restore\n // - 'preauth' → \"Log in to continue your purchase\" / link-purchase\n // - 'standalone' (paywall.openAuth()) → дефолты по mode из AuthPanel\n // Если admin задал в layout кастомный heading/subheading — он сохраняется\n // только для standalone-варианта (для preauth/restore мы знаем контекст лучше).\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 — descriptive sentence (\"Log in to continue your\n // purchase\"), а не action verb. Длинные локализации (RU: \"Войдите,\n // чтобы продолжить покупку\") в pill-кнопку h-12 не помещаются и\n // переносятся на 2 строки. Явный короткий submit_label решает.\n submit_label: t('auth.log_in', 'Sign In')\n }\n : block;\n\n // Padding + overflow-y-auto делегированы сюда (а не в Modal), потому что\n // Modal-обёртка теперь structurally нейтральна — Renderer возвращает свой\n // sticky-footer-layout, а gate-views хотят обычный единый scroll-зона.\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// Хранилище старта для относительных таймеров (offer.duration_minutes). Ключ\n// привязан к offer.id — повторное открытие пейвола не сбрасывает countdown,\n// юзер не может «фармить» offer-баннер бесконечно. Чистим по истечении.\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// Резолвит endMs: expires_at (абс. серверная дата) — приоритет, иначе\n// duration_minutes от первого open'а пейвола, сохранённого в localStorage.\n// null — offer без таймера, banner не нужен.\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 недоступен (private mode / SSR) — relative timer бесполезен.\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 // Первый offer с активным таймером. Без таймера — banner не имеет смысла\n // (offer-without-urgency показывается через PriceGrid discount badge).\n return offers.find((o) => o.expires_at || o.duration_minutes) ?? null;\n}\n\n/** Hook: tick'ает каждую секунду пока offer не expired. Возвращает null если\n * offer некорректен (нет таймера). Используется и в layout-block OfferBanner,\n * и в top-tab OfferTopBanner из 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 if (next.expired) {\n clearInterval(timer);\n if (offer?.duration_minutes && typeof window !== 'undefined') {\n try {\n window.localStorage.removeItem(STORAGE_KEY(offer.id));\n } catch {\n /* ignore */\n }\n }\n }\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: приклеивается к верху Modal'а как ярлычок-вкладка\n * (rounded-top, negative margin-bottom для overlap). Зеркало легаси\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' — модалка открыта только для саппорта (paywall.openSupport()),\n // Back/Done закрывают её. 'layout' — пришли из current_session-блока,\n // Back/Done возвращают в layout (и пейвол с тарифами остаётся открытым).\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;\nconst MAX_FILE_SIZE = 10 * 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 // Если есть сессия — email фиксируем из неё, форма его не редактирует.\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 идентичен Renderer.tsx — кнопки\n // прибиты к низу dialog'а и читабельны на коротких viewport'ах (extension\n // popup ≤600px), скроллится только контент над ними.\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 {/* email рендерим отдельным <b>, prefix-only ключ — для языков с\n порядком \"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(t('support.invalid_file', 'Only JPEG/PNG/WebP, ≤ 10MB each'));\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, ≤ 10MB each', {\n max: MAX_FILES\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// Плановые ключи для \"Get X Plan\". Если interval — известная константа,\n// берём dedicated-ключ (даёт переводчику правильный род/падёж для каждого\n// интервала). Для экзотики вроде day/half-year fallback'имся на generic с\n// {interval}-подстановкой — выглядит чуть хуже грамматически в RU/DE, но\n// мы не теряем интервал в 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 по легаси-логике из online/PaywallPricing.tsx:\n// - trial_days > 0, interval !== 'lifetime', юзер ещё не брал trial →\n// \"Start N-Day Free Trial\"\n// - interval === 'lifetime' → \"Get Lifetime Access\"\n// - иначе → \"Get {Interval} Plan\"\n// `hadPreviousTrial` гасит trial-ветку — anti-abuse: один юзер может взять\n// trial по пейволу только один раз. Серверный enforcement в\n// /start-checkout (utils/checkout-with-acquiring.ts) дублирует.\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` берём из bootstrap.user snapshot'а. Это значит, что\n // после signin'а через preauth flow (юзер был гостем на момент bootstrap'а)\n // флаг останется false до следующего bootstrap-revalidate; UI коротко\n // покажет \"Start Free Trial\", но серверный enforcement в /start-checkout\n // всё равно создаст checkout без trial — анти-abuse не нарушится.\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 под cta_button. Зеркалит legacy v2 PaywallCurrentSession:\n// - залогинен → \"Signed in as <email>\" + Sign out (вызывает auth.signOut())\n// + Contact Support\n// - гость → \"Restore purchases\" + Contact Support\n// Без AuthClient в managed-режиме — рендерим только Restore + Support\n// (sign out нечему делать, restore без auth-клиента no-op'нет в handleAction).\n// Анон-сессия (is_anonymous=true) трактуется как «нет логина»: анон существует\n// только ради api-gateway-токена, у юзера нет email и UX-смысла «Signed in».\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 ошибки безшумные — onAuthChange всё равно отработает на refresh-fail */\n } finally {\n setSigningOut(false);\n }\n };\n\n // \"Signed in as <email>\" — рендерим вручную из двух частей, чтобы email\n // оставался в bold-вёрстке. {email} placeholder в локали игнорируется —\n // строка показывается до email, b-тег c email-ом идёт после.\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 под CtaButton. Компактный one-liner: shield-check\n// иконка + текст. Pill-styling (rounded-full + bg-gray-100) визуально отделяет\n// от CTA, но не перетягивает внимание на себя — это reassurance-элемент.\n// Subtitle убран из дефолтного рендера: пользователи сканируют покупку\n// быстро, второй строкой только шум. Если admin задаёт block.subtitle явно —\n// она рендерится мелким серым ниже 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 // Выделяем \"N-day\" префикс жирным/тёмным — главная информация (срок),\n // остальное обычным весом. Глаз сразу ловит цифру вместо ровного блока.\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 «безопасность/возврат», поднимает контрастность\n // reassurance-сигнала. Серый gray-500 пропускался глазом.\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; // соответствует sm:text-2xl у h1\nconst MIN_FONT_PX = 16;\nconst MAX_LINES = 2;\n\n// Авто-fit: если заголовок не вмещается в `MAX_LINES` строк при базовом размере,\n// уменьшаем font-size шагом 1px до тех пор, пока влезает или не упёрлись в\n// `MIN_FONT_PX`. Используется только для h1 — h2/h3 это подзаголовки, им\n// клиппинг не нужен. Считаем по реальной высоте элемента (scrollHeight) после\n// рендера — иначе пришлось бы держать canvas-измеритель.\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 у text-2xl = 1.5 (Tailwind дефолт). Считаем от текущего\n // computed line-height — устойчиво к будущим CSS-изменениям.\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 type { BlockProps } from '../types';\nimport { useI18n, type TFn } from '../../i18n';\n\ntype PriceGridBlock = Extract<LayoutBlock, { type: 'price_grid' }>;\n\ninterface FormattedPrice {\n /** Символ валюты (или ISO-код, если символ не определился). */\n currency: string;\n /** Целая часть, без разделителей дробной. */\n amount: string;\n /** Original (без discount), formatted — для strike-through. null если\n * скидки нет или discount=0%. */\n originalAmount: string | null;\n}\n\n// Year-план показывает per-month эквивалент сразу в основной цене:\n// YEARLY PLAN €4.99 / month (вместо €59.99 / year)\n// Это легаси-UX из online/PaywallPricing.tsx (`unit_amount / 12`):\n// юзеру важна стоимость в месяц для сравнения с monthly-планом, а годовое\n// списание — деталь, которая не должна доминировать в типографике. planLabel\n// остаётся \"YEARLY PLAN\", так что billed-cadence по-прежнему понятен из\n// названия.\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// Форматирует число в currency-string без литералов, разделяет currency-symbol\n// и числовую часть. Используется и для основной цены, и для strike-through\n// original (тогда discount применять не нужно — value уже base).\n// Дробная часть авто: целое → \"$8\", нецелое → \"$4.99\". Целые без .00 заметнее\n// конвертят — глаз быстрее цепляет короткое число.\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 // Strike-through показываем полностью (`€59.99`/`€9.99` — с currency-знаком),\n // чтобы юзер сразу видел старую цену в той же валюте, без догадок.\n return {\n currency: main.currency,\n amount: main.amount,\n originalAmount: `${original.currency}${original.amount}`\n };\n}\n\n// Подбирает offer для конкретной цены. Targeted offer (price_id === priceId)\n// имеет приоритет над глобальным (price_id === null — применяется ко всем\n// ценам пейвола). Возвращает null, если discount_percent отсутствует/0.\nfunction applicableOffer(\n offers: PaywallOffer[] | undefined,\n priceId: string\n): PaywallOffer | null {\n if (!offers || offers.length === 0) return null;\n const targeted = offers.find(\n (o) => o.price_id === priceId && o.discount_percent && o.discount_percent > 0\n );\n if (targeted) return targeted;\n const global = offers.find(\n (o) => o.price_id == null && o.discount_percent && o.discount_percent > 0\n );\n return global ?? null;\n}\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// Суффикс после цены. Year → \"month\" (потому что amount уже /12, см.\n// displayedAmount). Lifetime → \"lifetime\". Прочее — singular interval\n// или \"N intervals\" для 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-режим — telegram-style список: тонкая подложка-карточка вокруг\n // всех строк (rounded-xl + light bg + 1px border). Разделители между\n // строками — `border-b` на внутреннем label-wrapper'е CompactRow (кроме\n // последней). Зеркало legacy PaywallPricing wrapper'а: для не-default view\n // он рисует `rounded-xl border-1 border-default-200 bg-default-50` —\n // отделяет блок цен от остального layout'а.\n // v2 storage-ключ — `view: 'telegram'`, bootstrap нормализует в '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={applicableOffer(ctx.bootstrap.offers, price.id)}\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-режим — реальный grid из карточек side-by-side. v2 storage-ключ\n // `view: 'row'` (SDK 3.0 only — старые legacy-paywall'ы этого ключа не\n // выбирают; bootstrap нормализует в 'horizontal'). max 3 столбца, при 1-2\n // ценах stretch'ат строку. Tailwind purge не переживает runtime grid-cols-N,\n // потому inline gridTemplateColumns.\n if (block.view === 'horizontal') {\n const cols = Math.min(prices.length, 3);\n // Если хоть у одной цены в гриде есть discount — резервируем strike-row\n // фиксированной высотой у ВСЕХ карточек (иначе main amount без скидки\n // прыгает выше соседних со скидкой). Если оффера нет совсем — strike-row\n // схлопывается в 0 у всех, и не висит 22px пустоты под label'ом.\n const anyHasDiscount = prices.some(\n (p) => (applicableOffer(ctx.bootstrap.offers, p.id)?.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={applicableOffer(ctx.bootstrap.offers, price.id)}\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 = applicableOffer(ctx.bootstrap.offers, price.id);\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 — selection выражается только цветом, layout\n // не прыгает (равная толщина у selected/unselected). Цветовая\n // разница accent vs gray достаточно сильная для 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 // Popular-label badge сидит absolute сверху-справа карточки и\n // визуально сдвигает центр content'а вниз. flex items-center\n // на карточке держит галочку по геометрическому центру, что\n // делает её визуально выше — компенсируем небольшим 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 на одной строке (flex-wrap для узких\n карточек) — компактный 2-row layout вместо 3-row. Tags идут\n справа от label с gap'ом, при переполнении переносятся. */}\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 приглушает strike: глаз сначала ловит label\n // и discount-badge, потом main price; original «бывшая цена»\n // — третичная информация, не должна конкурировать с 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 — фиксированный «успех/выгода», не зависит от\n // brand_color. Читается даже на тёмных бренд-акцентах.\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 — высокий contrast, glasses-test'ом\n // глаз сразу выхватывает popular pick. Pastel-вариант\n // конкурировал по visual weight с самой ценой и не работал\n // ни как highlight, ни как информация.\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// Короткий one-word label для compact-режима (\"Month\" / \"Year\" / \"Lifetime\")\n// вместо длинного \"MONTHLY PLAN\". Зеркало legacy `getIntervalName` для\n// TelegramPricingRadio: чем компактнее ряд — тем компактнее лейбл, иначе\n// текст начинает конкурировать с ценой.\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// Компактная строка для compact-режима. Зеркало legacy `TelegramPricingRadio`:\n// [radio] | [label + popular-pill] ······ [strike+badge ▸ price]\n// Разделители живут на внутреннем label-wrapper'е (`border-b`), последняя\n// строка без border'а. Selection выражается только цветом radio-кружочка —\n// никакого bg-tint'а, чтобы не конфликтовало с pricing-сеткой. Шрифты — text-md\n// без жирного, как в 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 {/* Внутренний wrapper, на нём `border-b` — разделитель между строками.\n Сидит за radio'м (по flex-flow), даёт визуальную нижнюю линию ровно\n под label/price колонками, как в 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 — точно как `badge` в TelegramPricingRadio.\n // Низкий visual weight: pill про \"имя плана\" (most popular), а не\n // про savings — не должна конкурировать с -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// Компактная карточка для horizontal-grid'а. UX-модель — Stripe pricing tables:\n// selection выражается border-цветом + tinted bg всей карточки, без отдельного\n// radio-кружочка (в узкой колонке любая icon-метка конкурирует с ценой за\n// внимание). Popular-badge — absolute pill сверху-справа (как в default view):\n// освобождает вертикаль внутри карточки, читается как premium-маркер. Все\n// карточки в ряду выровнены через `items-stretch` на grid'е (см. вызов).\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 /** Резервировать высоту под strike-row (originalAmount + discount-pill) даже\n * у этой карточки без скидки. true когда в гриде есть хотя бы одна цена со\n * скидкой — иначе main amount без скидки прыгает выше соседних со скидкой.\n * false когда оффера нет ни у одной цены в гриде — strike-row коллапсится\n * в 0 у всех, не висит 22px пустоты под 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 с фиксированной min-height на 2 строки — длинные (\"YEARLY PLAN\")\n и короткие (\"LIFETIME\") не сдвигают цену между карточками. */}\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 сверху ПЕРЕД main amount: сначала \"была $10\" + \"-20%\",\n потом крупно \"$8\". Высота резервируется (h-[22px]) только если в\n гриде есть хоть одна цена со скидкой — это держит alignment между\n карточками со скидкой и без. Если оффера нет совсем — row не\n рендерим, не остаётся 22px пустоты под label'ом во всех карточках. */}\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 — отстраивает badge\n // от border'а карточки, имитирует \"наклейку\". Зеркало 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 если над dialog'ом рендерится OfferTopBanner (он берёт на себя\n * visual top-bleed под X-крестиком). Без banner'а уменьшаем top-padding\n * scrollable-зоны — иначе под X остаётся 32px пустоты. */\n hasTopBanner?: boolean;\n}\n\nexport function Renderer({ layout, bootstrap, onAction, auth, authSession, hasTopBanner }: RendererProps) {\n // По умолчанию selected = popular_price_id (если он указан в каком-то\n // price_grid block'е и действительно существует в bootstrap.prices). Это\n // повторяет UX легаси-пейвола: highlighted-карточка сразу подсвечена и\n // готова к покупке, юзеру не нужно её доп. кликать. Fallback — первая цена.\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 + всё после него — pinned footer внизу dialog'а: всегда видимы, даже\n // если контент сверху не помещается по высоте. Сплит по первому `cta_button`:\n // нет cta → секция не рендерится, весь layout скроллится как обычно.\n // Используем flex (а не position:sticky) — sticky не даёт scrollable-области\n // правильно отдать высоту footer'у (контент скроллился ПОД footer вместо\n // того чтобы расширить min-h: 0), плюс shadow от sticky показывался даже\n // когда overflow'а нет. Flex чист: 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: верхний padding визуально отделяет от dialog top (и\n banner'а), нижний — меньше, потому что footer добавляет свой pt\n + border. Раньше было `p-8` снизу, давало ~48px зазор до 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 // Тонкий shadow-top вместо border-t — создаёт depth, читается как\n // «footer закреплён к низу dialog'а». Линия выглядела как divider\n // в обычном flow, не передавала 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 { useEffect, useLayoutEffect, useRef, useState } from 'preact/hooks';\nimport type { BillingClient } from '../core/BillingClient';\nimport type { AuthSession } from '../core/auth';\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 = 'layout' | 'support' | 'auth';\n\n/**\n * Публичный snapshot состояния PaywallUI для host'а. Производится из internal\n * LoadState + GateState + open/purchased флагов. Каждое реальное изменение —\n * один call onState; повторы (`useSyncExternalStore`-friendly).\n */\nexport interface PaywallStateSnapshot {\n /** Модалка отрендерена и видна. False — closed (или ещё не открывалась). */\n open: boolean;\n /** Что показывается в модалке. null когда `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 /** Заполнено только когда `view === 'error'`. */\n error: PaywallError | null;\n}\n\nexport interface PaywallRootProps {\n client: BillingClient;\n open: boolean;\n onClose: () => void;\n onEvent: (event: string, payload?: unknown) => void;\n /** Какой view показать при open=true. 'support' стартует сразу с саппорт-формой,\n * Back/Done закрывают модалку (origin='standalone'). По умолчанию 'layout'. */\n initialView?: PaywallView;\n /** Mode для AuthPanel когда `initialView='auth'` — 'signin' (дефолт) или\n * 'signup'. Выставляется PaywallUI'ем из openSignup()/openSignin(). */\n initialAuthMode?: 'signin' | 'signup';\n /** Server-confirmed покупка — показать success-вью с кнопкой Continue.\n * Управляется снаружи (PaywallUI выставляет true из watcher.onActive),\n * сбрасывается на open()/close(). Перебивает любые другие view. */\n purchased?: boolean;\n /** Renewal/upgrade flow. true — пропускаем все has_active_subscription\n * pre-check'и (bootstrap-time + post-auth), и при checkout передаём\n * `ignoreActivePurchase: true` на бэк, чтобы /start-checkout не вернул\n * 409 для уже подписанного юзера. См. OpenOptions.renew. */\n renew?: boolean;\n /** Публичный state-machine notify. PaywallUI прокидывает сюда колбек, который\n * кэширует snapshot и эмитит свой `onStateChange`. Если не передан —\n * state-tracking отключён (нет оверхеда для host'ов, которым не нужно). */\n onState?: (snapshot: PaywallStateSnapshot) => void;\n /** Inline-режим (live-preview редактора админки): передаётся в Modal, чтобы\n * overlay был absolute-внутри-host'а вместо fixed-viewport'а, и не лочил\n * body-scroll. По умолчанию false. */\n inline?: boolean;\n /** Explicit-override языка для I18nProvider. Используется live-preview\n * редактором админки — там browser-locale всегда EN, а нужно показать как\n * для юзера из выбранной страны. См. I18nProviderProps.forceLocale. */\n locale?: 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' — gate открыт через \"Restore purchases\"\n // (без последующего checkout-а), после signIn схлопываемся в layout. С\n // pendingCheckout — gate открыт по preauth-flow от cta_button и после signIn\n // auto-resume createCheckout. origin='standalone' — paywall.openAuth(): модалка\n // открыта только для логина, после signIn / Back закрываем модалку, layout\n // вообще не показываем.\n | {\n kind: 'auth_gate';\n pendingCheckout?: { priceId: string };\n origin?: 'layout' | 'standalone';\n /** Контекст открытия — управляет заголовком gate'а\n * (\"Restore Purchases\" vs \"Welcome back!\"). Дефолт — 'preauth'. */\n intent?: 'restore' | 'preauth' | 'standalone';\n }\n // origin='layout' — пришли из current_session-блока, Back возвращает в layout.\n // origin='standalone' — модалка открыта только для саппорта (paywall.openSupport()),\n // Back закрывает модалку.\n | { kind: 'support'; origin: 'layout' | 'standalone' }\n // window.open отдал handle — checkout открылся в новой вкладке. Пейвол остаётся\n // как индикатор: «оплати в той вкладке». priceId храним, чтобы кнопка retry\n // могла пересоздать checkout (URL'ы у Stripe/Paddle expire'ятся). url храним,\n // чтобы fallback-ссылка «Didn't open? Click here» переоткрывала тот же URL без\n // повторного похода в createCheckout — нужно для случая когда window.open\n // отдал handle, но реально таб заблокирован (агрессивные мобильные блокеры).\n | { kind: 'awaiting_payment'; priceId: string; url: string }\n // window.open вернул null — попап заблокирован (бывает после async-резюма\n // post-auth, когда transient activation истёк). НЕ редиректим текущую вкладку:\n // пейвол должен остаться. URL уже выписан — кнопка «Open checkout» дёрнет\n // window.open под фреш-гестуром, без второго похода в createCheckout.\n | { kind: 'popup_blocked'; priceId: string; url: string }\n // Юзер уже залогинен и has_active_subscription — показываем success-view.\n // Срабатывает либо после auth-resume (поллим getUser сразу после signIn),\n // либо когда /start-checkout вернул 409 hasActivePurchase. restored=true\n // меняет текст PurchaseSuccessView на «Subscription restored».\n | { kind: 'purchase_success'; restored: boolean }\n // После signIn ждём getUser({force:true}), пока не узнаем есть ли уже\n // active subscription. Без этого промежуточного state'а юзер видит\n // несколько секунд auth_gate'овский «серый экран» с уже скрытой формой.\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 if (!open) return { open: false, view: null, error: null };\n if (purchased) return { open: true, view: 'purchased', error: null };\n if (state.status === 'idle' || state.status === 'loading') {\n return { open: true, view: 'loading', error: null };\n }\n if (state.status === 'error') {\n return { open: true, view: 'error', error: state.error };\n }\n if (gate.kind === 'support') return { open: true, view: 'support', error: null };\n if (gate.kind === 'auth_gate') return { open: true, view: 'auth', error: null };\n if (gate.kind === 'awaiting_payment') {\n return { open: true, view: 'awaiting_payment', error: null };\n }\n if (gate.kind === 'popup_blocked') {\n return { open: true, view: 'popup_blocked', error: null };\n }\n if (gate.kind === 'purchase_success') {\n return { open: true, view: 'purchased', error: null };\n }\n if (gate.kind === 'verifying') {\n return { open: true, view: 'loading', error: null };\n }\n return { open: true, view: 'layout', error: null };\n}\n\nfunction sameSnapshot(a: PaywallStateSnapshot, b: PaywallStateSnapshot): boolean {\n return a.open === b.open && a.view === b.view && a.error === b.error;\n}\n\nexport function PaywallRoot({\n client,\n open,\n onClose,\n onEvent,\n initialView,\n initialAuthMode,\n purchased,\n renew,\n onState,\n inline,\n locale\n}: PaywallRootProps) {\n const [state, setState] = useState<LoadState>({ status: 'idle' });\n // session держим в state, чтобы блоки (auth_panel) ре-рендерились на login/logout.\n // Без этого AuthPanel прочитал бы snapshot один раз и не схлопнулся бы после\n // успешного 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') return { kind: 'auth_gate', origin: 'standalone' };\n return { kind: 'layout' };\n });\n // Защита от двойного auto-resume: useEffect ниже зависит от authSession,\n // и подписка onAuthChange может прислать одну и ту же сессию повторно\n // (refresh) — без флага мы дважды дёрнем createCheckout.\n const resumingRef = useRef(false);\n\n // State-machine bridge: эмитим snapshot когда меняется любое из\n // (open, state, gate, purchased). sameSnapshot гасит no-op'ы — например\n // переход loading→error меняет state.status, но если мы уже в error-вью\n // (иначе невозможно), эмит не повторится.\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'а: BillingClient.setBootstrap (preview-mode в\n // редакторе админки) или cross-tab storage.watch эмитят onBootstrapChange.\n // Перерендериваем модалку, только если она уже в ready-фазе — иначе\n // bootstrap-effect ниже сам подхватит свежий cached на open().\n // Guard: тесты передают stub-клиента без 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 // Юзер уже подписан — host вызвал open() вслепую (без getAccess pre-check'а),\n // или просто из popup'а «Open paywall». Не показываем тарифы — переключаемся\n // в restored success-view. Эмитим purchase_completed чтобы host получил\n // согласованный сигнал, как из любых других путей (UserWatcher, 409 в\n // checkout, auth-resume). renew=true пропускает эту проверку — host явно\n // показывает «Renew»/«Upgrade», тарифы должны быть видны.\n //\n // initialView — standalone-flow (openSupport/openAuth/openSignup). Host\n // явно открыл саппорт/auth-форму, перетирать gate в restored success —\n // нарушение intent'а; host'у саппорт сейчас важнее статуса подписки.\n if (data.user?.has_active_subscription && !renew && !initialView) {\n onEvent('purchase_completed', {\n priceId: null,\n sessionId: null,\n restored: true\n });\n setGate({ kind: 'purchase_success', restored: true });\n }\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 // Закрытие/повторное открытие модалки сбрасывает gate. Standalone-flows\n // (openSupport / openAuth) PaywallUI вызывает на уже смонтированном компоненте\n // через handle.update({initialView: 'support'|'auth'}) — useState-initializer\n // отрабатывает только на первом mount'е, поэтому без этого эффекта gate\n // оставался бы 'layout' (с тарифами) при последующих standalone open'ах.\n //\n // useLayoutEffect (не useEffect): после close gate уходит в 'layout', и при\n // следующем openAuth/openSupport обычный useEffect запускался ПОСЛЕ paint'а,\n // из-за чего юзер на один кадр видел тарифы вместо auth-формы (особенно\n // заметно в extension-popup'е, где RemoteAuth+RemoteBilling добавляют\n // транспортные RTT и main thread чаще yield'ит между render'ами).\n // useLayoutEffect синхронизирует gate ДО paint'а — flicker'а нет.\n useLayoutEffect(() => {\n if (!open) {\n setGate({ kind: 'layout' });\n resumingRef.current = false;\n return;\n }\n if (initialView === 'support') {\n setGate({ kind: 'support', origin: 'standalone' });\n } else if (initialView === 'auth') {\n setGate({ kind: 'auth_gate', origin: 'standalone' });\n }\n }, [open, initialView]);\n\n const runCheckout = async (priceId: string) => {\n try {\n const result = await client.createCheckout({\n priceId,\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 // Без `noopener,noreferrer` в фичах: эти флаги заставляют window.open\n // ВСЕГДА вернуть null (даже когда попап реально открылся), и мы не\n // могли отличить «успех» от «заблокирован». Severance делаем вручную\n // через popup.opener=null после успеха — на checkout-домене (Stripe/\n // Paddle) opener-доступ всё равно cross-origin-restricted, но явный\n // null безопаснее.\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 // Попап заблокирован — обычно из-за stale transient activation\n // (auto-resume после async signin). НЕ уносим юзера через\n // location.assign: пейвол должен остаться открытым. Показываем\n // inline retry; клик по кнопке — fresh gesture, попап откроется.\n setGate({ kind: 'popup_blocked', priceId, url: result.url });\n }\n } catch (error) {\n // 409 hasActivePurchase от бэка — это не ошибка чекаута, это «у юзера\n // уже есть активная подписка». Освежаем cache (host'овский userChange\n // должен увидеть has_active_subscription=true), эмитим purchase_completed\n // с restored=true и переключаемся в success-view. Не setGate в layout,\n // не onEvent('error') — иначе host увидит false-negative.\n if (error instanceof PaywallError && error.code === 'already_purchased') {\n try {\n await client.getUser({ force: true });\n } catch {\n /* offline / 401 — host'у getUser сам отрапортует, тут это не блокирует success-view */\n }\n onEvent('purchase_completed', { priceId, sessionId: null, restored: true });\n setGate({ kind: 'purchase_success', restored: true });\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 — иначе застрянем в auth_gate\n // (если пришли через preauth-flow) с уже залогиненной сессией.\n setGate({ kind: 'layout' });\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 // Если и сейчас null — оставляем popup_blocked, юзер кликнет ещё раз.\n };\n\n // Auto-resume: появилась сессия в открытом gate → продолжаем флоу.\n // Pending preauth-checkout — НЕ схлопываем gate в layout до runCheckout:\n // иначе юзер видит мигание тарифов между submit'ом auth-формы и открытием\n // чекаут-вкладки. runCheckout сам переведёт gate в awaiting_payment /\n // popup_blocked / layout (на ошибке). Restore-flow без pendingCheckout —\n // просто возвращаемся в layout. resumingRef защищает от повторного запуска,\n // если authChange сработает несколько раз за один gate-цикл (refresh).\n useEffect(() => {\n if (gate.kind !== 'auth_gate') return;\n // Анон-сессия не считается логином: юзер пришёл в auth_gate реально\n // залогиниться. Иначе openAuth() при существующем анон-токене мгновенно\n // закрывал бы модалку через auto-resume, и формы он бы не увидел.\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 // Сразу переключаемся в verifying — иначе модалка висит в auth_gate с\n // уже залогиненным юзером (~3с пока getUser ходит к бэку), и юзер видит\n // «пустой серый экран» вместо progress'а. Loader честнее показывает что\n // SDK что-то делает.\n setGate({ kind: 'verifying' });\n void (async () => {\n // Прежде чем продолжать flow (runCheckout / возврат в layout / закрытие\n // модалки), проверяем — может, у юзера уже есть active subscription.\n // Сценарии: Restore-кнопка (он уже платил с другого аккаунта); preauth\n // signIn (юзер вспомнил, что подписка есть); standalone openAuth.\n // Без этой проверки юзер увидел бы тарифы и кликнул Buy → 409 от бэка\n // → fallback на already_purchased. Лучше не давать ему этот шаг.\n // renew=true пропускает проверку — host явно делает 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 setGate({ kind: 'purchase_success', restored: true });\n return;\n }\n } catch {\n /* getUser упал — продолжаем обычный flow, юзер увидит тарифы */\n }\n }\n if (!pending) {\n // openAuth standalone: после signIn закрываем модалку, layout не показываем.\n // Restore-flow (origin='layout' или undefined): возвращаемся в layout.\n if (origin === 'standalone') {\n onClose();\n } else {\n setGate({ kind: 'layout' });\n }\n return;\n }\n await runCheckout(pending.priceId);\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 // Пробрасываем как есть — блок уже собрал { priceId, price }.\n onEvent('price_selected', payload);\n return;\n }\n if (action === 'restore') {\n // CurrentSession-блок: гость кликнул \"Restore purchases\". Открываем\n // gate с intent='restore' — заголовок и submit станут \"Restore Purchases\".\n // Без AuthClient'а ничего не делаем (managed-auth не подключён).\n // Анон-сессия не считается логином (см. CurrentSession-блок): она\n // существует только для api-gateway-токена, у юзера нет email и\n // ему нужен realsignin чтобы привязать прошлую покупку. Без этой\n // проверки кнопка Restore молча no-op'ила бы как только у юзера\n // появлялся анон-токен (что в extension'ах — почти всегда).\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-блок: открыть саппорт-форму. Видна и гостю, и залогиненному.\n // Из layout — Back возвращает к тарифам.\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 // Анон-сессия не покрывает preauth-требование: чекаут под анон-токеном\n // создаст подписку под аккаунтом без email, который юзер потом не\n // сможет восстановить. Анон считается «нет логина», нужен real signin.\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 трактуем как true (default до bootstrap'а — пейвол\n // должен быть закрываемым во время loading/error, иначе юзера запрёт). После\n // ready settings.allow_close=false запретит ESC/overlay/крестик.\n const allowClose =\n state.status === 'ready' ? state.data.settings.allow_close !== false : true;\n\n // Offer top-tab: только на основном layout-view (цены/фичи). На auth/support\n // экранах banner не имеет смысла — юзер уже за пределами «купить сейчас»\n // flow'а, urgency-таймер только отвлекает. Зеркало легаси PaywallModal,\n // где offer-banner был привязан к 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 // Заголовок не задаём — AuthGate сам решит по intent'у (restore →\n // \"Restore Purchases\", остальные → дефолтный \"Welcome back!\").\n allow_signup: true,\n allow_password_reset: true,\n // Не скрываем при наличии сессии — auto-resume useEffect отрабатывает быстрее,\n // чем хотим показывать \"Signed in as ...\" промежуточным экраном.\n hide_when_authenticated: false,\n providers: state.status === 'ready' ? state.data.settings.auth_providers : undefined\n };\n\n // Support-view имеет приоритет над bootstrap-state: standalone-открытие\n // (paywall.openSupport()) должно работать даже если bootstrap ещё грузится\n // или упал — сама форма от settings/prices не зависит. Из layout-режима\n // Back возвращает к тарифам, из standalone — закрывает модалку.\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 // В gate-view'ах AuthGate/SupportGate сами рисуют curved Back-кнопку в\n // правом верхнем углу. Modal'овский X-крестик там же — две кнопки накладывались\n // бы друг на друга. ESC/overlay-клик остаются рабочими (если allowClose=true).\n // Standalone openAuth() — AuthGate не рисует Back (модалка открыта только\n // ради signin'а, layout некуда возвращаться); тогда X-крестик нужен, иначе\n // юзеру некуда деться кроме 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 {purchased ? (\n <PurchaseSuccessView onContinue={onClose} />\n ) : gate.kind === 'purchase_success' ? (\n <PurchaseSuccessView restored={gate.restored} onContinue={onClose} />\n ) : supportView ? (\n supportView\n ) : state.status === 'loading' || state.status === 'idle' || gate.kind === 'verifying' ? (\n <LoadingView verifying={gate.kind === 'verifying'} />\n ) : state.status === 'error' ? (\n <ErrorView message={state.error.message} />\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()) — модалка открыта только ради\n // signin'а, Back-кнопка дублирует ESC/X. Скрываем. Для preauth/\n // restore-flow Back ведёт обратно в layout — оставляем.\n showBack={gate.origin !== 'standalone'}\n intent={gate.intent ?? (gate.origin === 'standalone' ? 'standalone' : 'preauth')}\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 <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 ) : gate.kind === 'popup_blocked' ? (\n <PopupBlockedView onReopen={() => reopenCheckout(gate.priceId, gate.url)} />\n ) : (\n <Renderer\n layout={state.data.layout!}\n bootstrap={state.data}\n onAction={handleAction}\n auth={client.auth}\n authSession={authSession}\n />\n )}\n </Modal>\n </I18nProvider>\n );\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 <div\n class=\"flex h-11 w-11 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=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\">\n <path d=\"M4 5h12v10H4z\" stroke=\"currentColor\" stroke-width=\"1.75\" stroke-linejoin=\"round\" />\n <path d=\"M7 9l3 3 4-5\" stroke=\"currentColor\" stroke-width=\"1.75\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n </svg>\n </div>\n <p class=\"text-sm font-semibold tracking-tight text-gray-900\">\n {t('payment.popup_blocked_title', 'Allow popups to continue')}\n </p>\n <p class=\"max-w-[18rem] text-xs 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-1 rounded-xl px-4 py-2 text-xs 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('payment.open_checkout_button', 'Open checkout')}\n </button>\n </div>\n );\n}\n\n// Экран ожидания после window.open(checkoutUrl). UserWatcher в PaywallUI уже\n// poll'ит user-state раз в 5s (visible вкладка) — этот экран только UI-обёртка.\n//\n// «I've paid» — для нетерпеливых: форсим getUser({force:true}), чтобы cache\n// обновился сразу, и постим внутрь окна 'paywall_purchase' message — этого\n// ждёт UserWatcher.handleMessage и сразу же тригерит свой check(). Если\n// подписка ещё не активна (webhook не дошёл), показываем inline-таймаут на 5s.\n//\n// «Open checkout again» — fallback для случая «window.open отдал handle, но таб\n// заблокирован» (агрессивные мобильные блокеры). Дёргает existing URL без\n// похода в createCheckout, не сбивая awaiting_payment state.\n//\n// «Tab closed? Try again» — крайний случай: URL у Stripe/Paddle/etc. может\n// expire'нуться, поэтому пересоздаём checkout. Менее prominent кнопка.\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 // Будит UserWatcher — он сразу же сделает check(), увидит fresh active\n // user из cache и эмитит purchase_completed (PaywallUI переведёт в\n // PurchaseSuccessView). Не эмитим purchase_completed напрямую отсюда —\n // single source of truth остаётся в watcher.onActive.\n if (typeof window !== 'undefined') {\n window.postMessage({ type: 'paywall_purchase' }, '*');\n }\n return;\n }\n // Webhook ещё не дошёл — показываем подсказку и через 5s сворачиваем,\n // чтобы юзер мог нажать ещё раз. setTimeout cancel'нется на 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 <div class=\"relative flex h-12 w-12 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-7 w-7 animate-spin rounded-full border-[2.5px] border-gray-200 border-t-[var(--pw-accent)]\" />\n </div>\n <p class=\"text-sm font-semibold tracking-tight text-gray-900\">\n {t('payment.awaiting_title', 'Complete payment in the new tab')}\n </p>\n <p class=\"max-w-[20rem] text-xs 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-1 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 6px 14px -4px 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 — у юзера уже была активная подписка на момент попытки checkout\n * (или после signIn выяснилось, что подписка есть). Меняет heading на\n * «Subscription restored» — без этого юзер думает, что только что\n * оплатил. */\n restored?: boolean;\n}) {\n const { t } = useI18n();\n return (\n <div class=\"flex flex-col items-center gap-3 py-8 text-center\">\n <div\n class=\"flex h-14 w-14 items-center justify-center rounded-full ring-8\"\n style={{\n background: 'linear-gradient(135deg, #4ade80, #16a34a)',\n color: '#fff',\n // emerald ring with low alpha for a halo effect\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-lg font-semibold tracking-tight text-gray-900\">\n {restored\n ? t('modal.purchase_restored_title', 'Subscription restored')\n : t('modal.purchase_success_title', 'Payment received')}\n </p>\n <p class=\"text-sm leading-relaxed text-gray-500\">\n {restored\n ? t(\n 'modal.purchase_restored_subtitle',\n 'Welcome back — your subscription is already active.'\n )\n : t('modal.purchase_success_subtitle', 'Your subscription is now active.')}\n </p>\n <button\n type=\"button\"\n onClick={onContinue}\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('modal.continue', 'Continue')}\n </button>\n </div>\n );\n}\n","import type { BillingClient } from '../core/BillingClient';\nimport type { PaywallUser } from '../core/types';\n\n// Параметры по умолчанию подобраны под \"юзер платит ~60-90с после клика\n// Continue, иногда ходит за чашкой 5-10 минут\". См. обсуждение в TODO.md\n// (фаза \"Что это меняет в архитектуре\").\nexport interface UserWatcherOptions {\n client: BillingClient;\n /** Дёрнут, когда впервые увидели has_active_subscription === true. */\n onActive: (user: PaywallUser) => void;\n /** Полный таймаут наблюдения. По истечении — стоп без onActive. */\n onTimeout?: () => void;\n timeoutMs?: number;\n /** Интервал polling, когда вкладка видимая. */\n visibleIntervalMs?: number;\n /** Интервал polling, когда вкладка скрыта (браузер троттлит таймеры). */\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 после checkout_started.\n//\n// Источники сигнала \"проверить сейчас\":\n// 1. visibility change → visible (юзер вернулся в исходную вкладку).\n// 2. window focus.\n// 3. postMessage вида { type: 'paywall_purchase' } от success-страницы\n// (acceleration: success_url на нашем origin делает window.opener.postMessage).\n// 4. Регулярный таймер с visibility-aware расписанием.\n//\n// Стоп: либо has_active_subscription === true, либо таймаут.\n//\n// Runtime detection: см. shouldRunUserWatcher() — extension popup отбрасывается,\n// он не доживает до возврата с checkout. Background service worker отсекается\n// проверкой `typeof document` в 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 /* транзиентные ошибки — пропустим один тик, поллер дёрнет ещё */\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 // Перепланируем таймер с интервалом нового состояния.\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// Решаем, имеет ли смысл вообще запускать watcher в текущем рантайме.\n// false → код, который должен закрывать пейвол на оплату, полагается на\n// другой путь (bootstrap при следующем открытии для extension popup;\n// отсутствие document — для service worker).\nexport function shouldRunUserWatcher(): boolean {\n if (typeof document === 'undefined') return false;\n if (typeof window === 'undefined') return false;\n // Chrome extension popup живёт только пока открыт. window.open()\n // checkout-провайдера сразу ест фокус → popup закрывается → весь JS-context\n // (включая SDK и watcher) уничтожается. Polling тут бесполезен.\n if (typeof location !== 'undefined' && location.protocol === 'chrome-extension:') {\n return false;\n }\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 PaywallError,\n type Acquiring,\n type Identity,\n type PaywallBootstrap,\n type PaywallPrice,\n type PaywallUser,\n type TrialConfig,\n type TrialStatus,\n type UserLanguageInfo,\n type VisibilityStatus\n} from '../core/types';\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 = { open: false, view: null, error: null };\n\n// Контракт событий SDK. Клиент подписывается через paywall.on(event, handler).\n// Каждый event строго типизирован — IDE даёт автокомплит на payload.\nexport interface PaywallEventPayloads {\n /** Модалка открыта (запрос на открытие — данные могут ещё грузиться). */\n open: void;\n /** Модалка закрыта. */\n close: void;\n /** Bootstrap загружен, модалка показывает контент. Подходит для impression-метрик. */\n ready: PaywallBootstrap;\n /** Любая ошибка SDK (bootstrap, checkout). */\n error: PaywallError;\n /** Юзер выбрал тариф (клик по плану), ещё не инициировал checkout. */\n price_selected: { priceId: string; price: PaywallPrice };\n /** Checkout URL получен с бэка и открыт в новой вкладке. `acquiring` —\n * имя платёжного процессора, на который ушёл checkout (для конверсии\n * по эквайрингам в host-аналитике). */\n checkout_started: { priceId: string; url: string; acquiring?: Acquiring };\n /** Юзер вернулся с успешной оплатой (через URL-маркеры или postMessage),\n * либо после signIn / попытки checkout-а выяснилось, что подписка уже\n * активна (`restored: true`). priceId = null когда payment-интент не\n * был привязан к конкретной цене (UserWatcher-tick, restore-flow). */\n purchase_completed: {\n priceId: string | null;\n sessionId: string | null;\n /** true — это не свежая оплата, а активная подписка, которую SDK обнаружил\n * и показал juзеру success/restored view. Hostу полезно различать (для\n * metrics — «restore» vs «new purchase»). */\n restored?: boolean;\n };\n /** Юзер вернулся с ошибкой/cancel от провайдера. */\n purchase_failed: { reason: string | null };\n /** User-state изменился (bootstrap snapshot, getUser refresh, watcher tick).\n * Дёргается также сразу с last-known user после первой подписки. */\n userChange: PaywallUser;\n /** Auth-session изменилась. Payload содержит `event` (см. AuthChangeEvent —\n * INITIAL_SESSION / SIGNED_IN / SIGNED_OUT / TOKEN_REFRESHED / USER_UPDATED /\n * PASSWORD_RECOVERY) и `session` (null = разлогинен).\n *\n * Гарантированный контракт: первый callback каждому subscriber'у — всегда\n * INITIAL_SESSION с восстановленной из storage сессией (или null если нет).\n * Дальше — реальные переходы. Listener'у с побочными эффектами вроде\n * force-refetch balances ловить SIGNED_IN, а не любой truthy session,\n * иначе reload страницы будет триггерить лишний запрос. */\n authChange: { event: AuthChangeEvent; session: AuthSession | null };\n /** Триал заблокировал показ модалки. payload содержит свежий статус (после\n * recordBlock). Для `mode: 'time'` — startedAt/expiresAt/remainingMs;\n * для `mode: 'opens'` — remainingActions/totalActions. Хост может\n * использовать payload для показа собственного UI («осталось 3 показа»). */\n trial_blocked: TrialStatus;\n /** Триал истёк, паывол показывается впервые после истечения. Эмитится\n * раз за жизнь PaywallUI-инстанса (не персистится между перезагрузками\n * страницы — на каждом page-load событие может стрельнуть один раз). */\n trial_expired: void;\n /** Targeting не сошёлся — паывол не открывается. payload содержит\n * server-computed snapshot из bootstrap (visible=false + reason + country +\n * tier). Хост может показать собственный fallback («сервис недоступен в\n * вашей стране») или просто залогировать impression для аналитики. */\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// Вспомогательный тип: `void` payload эмитится без аргумента (`emit('open')`),\n// непустой — с аргументом (`emit('ready', bootstrap)`).\ntype EmitArgs<E extends PaywallEvent> = PaywallEventPayloads[E] extends void\n ? []\n : [PaywallEventPayloads[E]];\n\nexport interface AnalyticsOptions {\n enabled?: boolean;\n /** Полный URL до /events. По умолчанию — `${apiOrigin}/api/v1/paywall/${id}/events`. */\n endpoint?: string;\n flushIntervalMs?: number;\n maxBufferSize?: number;\n /** Тестовый override fetch'а (jsdom/Vitest). */\n fetch?: typeof fetch;\n /** Тестовый override sendBeacon'а. */\n sendBeacon?: (url: string, data: BodyInit) => boolean;\n}\n\n/**\n * Managed-auth конфиг. Передай `auth: true` — PaywallUI создаёт `AuthClient`\n * сам (с тем же `paywallId/apiOrigin/storage`, как BillingClient). Передай\n * объект — те же дефолты + override опций. Передай готовый `AuthClient` —\n * PaywallUI просто прокинет его в BillingClient (полезно, если хост хочет\n * иметь общий AuthClient на несколько пейволов / делать manual signIn/signOut\n * из своего UI до открытия модалки).\n *\n * Без `auth` опции SDK работает в hybrid-режиме: identity передаётся снаружи\n * через `opts.identity` или `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 /** Подключить managed-auth слой. См. {@link AuthOption}. */\n auth?: AuthOption;\n /**\n * Автоматически парсить URL при создании PaywallUI, чтобы поймать возврат\n * с checkout-провайдера (?paywall_status=paid|failed|cancelled). Дефолт: true.\n * Эмитит purchase_completed / purchase_failed через microtask — подпишись синхронно.\n */\n autoDetectReturn?: boolean;\n /**\n * Режим shadow DOM. По умолчанию `closed` — полная изоляция от хоста.\n * Для e2e тестов (Playwright) и live-preview в админке передавать `open`.\n */\n shadowMode?: 'open' | 'closed';\n /**\n * Аналитика SDK 3.0. По умолчанию включена. Передай `false` для полного\n * отключения (ничего не шлётся на бэк). Принимает объект с настройками\n * batch'а или endpoint-override.\n */\n analytics?: boolean | AnalyticsOptions;\n /**\n * Когда bootstrap не в кеше — модалку рендерить **сразу** со спиннером и\n * прогонять gates (visibility/trial) после получения данных, или **ждать**\n * bootstrap и монтировать только если gates прошли. Дефолт `true` —\n * snappy open, кнопка «открыть» отзывается мгновенно.\n *\n * Trade-off: при `true` и блокирующем gate'е модалка моргнёт (открылась\n * → закрылась через ~200-500мс). На extension'ах и сайтах с включённым\n * targeting-fallback'ом это редкий путь, поэтому дефолт оптимизирован\n * под основной 99%-кейс. Передай `false`, если для вашего use-case'а\n * флеш на blocked-странах/устройствах хуже воспринимаемой латентности.\n */\n mountThenLoad?: boolean;\n /**\n * Inline-режим для live-preview редактора админки. Host позиционируется\n * `absolute inset:0` внутри родителя (вместо fixed-viewport'а), overlay\n * Modal'а тоже становится absolute, body-scroll не лочится. ОБЯЗАТЕЛЬНО\n * передавать `host` (HTMLElement) с positioned parent'ом — иначе absolute\n * уйдёт к ближайшему positioned ancestor'у или к html. По умолчанию false.\n *\n * @internal Admin-only: используется в редакторе пейволов monetize.software\n * для live-preview. Конечным интеграторам SDK включать не нужно — модалка\n * сольётся с host'овым layout'ом вместо fullscreen-overlay'я.\n */\n inline?: boolean;\n /**\n * Explicit-override языка для I18nProvider. Используется live-preview\n * редактором админки («Preview as user from <country>») — там browser-locale\n * всегда EN, а нужно показать как для юзера из выбранной страны. Принимает\n * BCP-47 base-tag из `BUNDLED_LOCALES` (ru/de/fr/…); EN, null, undefined —\n * fallback на обычную резолв-логику (navigator.language → locale_default).\n *\n * Live-обновление — через {@link PaywallUI.setLocale}.\n *\n * @internal Admin-only: для конечных интеграторов нет смысла форсить язык —\n * SDK сам подстраивается под browser-locale.\n */\n locale?: string | null;\n}\n\n/**\n * Результат `paywall.getAccess()` — отвечает на главный вопрос хоста: «нужно\n * ли блокировать фичу для этого юзера?». Без побочных эффектов: на trial-storage\n * `recordBlock` не вызывается (счётчики не двигаются), модалка не монтируется.\n *\n * Семантика `access`:\n * - `granted` — фичу НЕ блокировать. Один из сценариев:\n * - `has_subscription` — у юзера активная подписка/покупка;\n * - `visibility_blocked` — таргетинг (страна/девайс/visibility-флаг) не\n * сошёлся, юзер вне monetization-scope'а пейвола → монетизация неприменима;\n * - `trial_blocked` — пре-пейвольный триал ещё активен.\n * - `blocked` — фичу заблокировать и вызвать `paywall.open()`. Reason всегда\n * `no_subscription`.\n *\n * Discriminated union по `access`: type-narrowing на `result.access === 'blocked'`\n * сужает `reason` до `'no_subscription'`, на `'granted'` — до трёх granted-вариантов.\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 расширение `OpenOptions` — `authMode` мы не светим в публичный\n * API (есть dedicated `openSignin`/`openSignup`), но через private-методы\n * плюс mountAndShow прокидываем именно тут. */\ntype InternalOpenOptions = OpenOptions & { authMode?: 'signin' | 'signup' };\n\nexport interface OpenOptions {\n identity?: Identity;\n /** Принудительно открыть, минуя pre-paywall trial check. По умолчанию SDK\n * читает `bootstrap.settings.trial` и блокирует open(), пока триал активен.\n * Эскейп-хатч для случаев типа «host решил показать всё-таки» или дев-режим. */\n skipTrial?: boolean;\n /** Принудительно открыть, минуя targeting-gate. По умолчанию SDK читает\n * `bootstrap.settings.visibility` и эмитит `visibility_blocked` без\n * открытия модалки, если visible=false (страна/девайс/visibility-флаг\n * не сошлись). Эскейп-хатч для дев-отладки. */\n skipVisibility?: boolean;\n /** Renewal/upgrade flow. По умолчанию (false) SDK после bootstrap'а или\n * signIn проверяет `user.has_active_subscription` и переключается в\n * restored success-view, не показывая тарифы — open() для уже подписанного\n * юзера превращается в подтверждение «у вас уже есть подписка». С\n * `renew: true` все эти проверки пропускаются: тарифы показываются всегда,\n * и при checkout SDK передаёт `ignoreActivePurchase: true` на бэк, чтобы\n * /start-checkout не вернул 409. Использовать когда host-UI явно\n * показывает кнопку «Renew»/«Upgrade plan». */\n renew?: boolean;\n}\n\n// Маркеры в URL, по которым SDK определяет результат checkout.\n// Контракт общий с бэком — online добавляет их в success/cancel URLs.\nconst URL_MARKERS = {\n status: 'paywall_status',\n priceId: 'paywall_price_id',\n sessionId: 'paywall_session_id'\n} as const;\n\nexport class PaywallUI {\n readonly billing: BillingClient;\n /** AuthClient (managed-auth) или undefined в hybrid-режиме. Доступен публично:\n * host может вызывать `paywall.auth?.signOut()`, читать `getCachedSession()`,\n * подписываться на `onAuthChange` напрямую. */\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 /** Lazy-инстанс TrialStore. Резолвится при первом open(), когда уже знаем\n * `bootstrap.settings.trial`. null — триал отключён в конфиге пейвола. */\n private trialStore: TrialStore | null = null;\n /** Конфиг, под который создан текущий trialStore — пересобираем, если он\n * поменялся между bootstrap-фетчами (например, владелец переключил режим\n * в админке между сессиями SDK). */\n private trialStoreConfig: TrialConfig | null = null;\n /** In-memory snapshot последнего check() — для синхронного getTrialStatus(). */\n private lastTrialStatus: TrialStatus | null = null;\n /** Флаг dedupe для `trial_expired` события в рамках жизни инстанса. */\n private trialExpiredFired = false;\n /** In-memory snapshot последнего bootstrap'а — для синхронного getVisibility(). */\n private lastVisibility: VisibilityStatus | null = null;\n /** Поведение open() при холодном bootstrap'е. См. PaywallUIOptions.mountThenLoad. */\n private mountThenLoad: boolean;\n /** Inline-режим (live-preview редактора). См. PaywallUIOptions.inline. */\n private inline: boolean;\n /** Force-locale для I18nProvider. См. PaywallUIOptions.locale. */\n private forceLocale: string | null;\n /** Текущий snapshot UI state-machine. Обновляется PaywallRoot'ом через\n * `onState` prop; при close сбрасывается обратно в CLOSED_STATE. */\n private currentState: PaywallStateSnapshot = CLOSED_STATE;\n private stateListeners = new Set<PaywallStateListener>();\n\n constructor(opts: PaywallUIOptions) {\n // Резолвим AuthClient: готовый инстанс / managed-конфиг (true|object) /\n // undefined. ownsAuth=true → сами создавали и должны прибрать в destroy().\n const { auth, ownsAuth } = resolveAuth(opts);\n this.auth = auth;\n this.ownsAuth = ownsAuth;\n\n // Если auth есть — прокидываем в BillingClient (он сам подключит Bearer\n // и auto-sync identity через onAuthChange). client из opts побеждает —\n // считаем, что хост уже сконфигурировал его сам, не лезем перетирать 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 // Форвардим user-change события из BillingClient на public-API PaywallUI.\n // Один источник правды (BillingClient cache) — два consumer'а (host через\n // paywall.onUserChange и сам watcher через billing.onUserChange).\n this.userUnsub = this.billing.onUserChange((user) => {\n this.emit('userChange', user);\n });\n\n if (this.auth) {\n this.authUnsub = this.auth.onAuthChange((event, session) => {\n this.emit('authChange', { event, session });\n });\n }\n\n this.initTracker(opts.analytics);\n\n if (opts.autoDetectReturn !== false && typeof window !== 'undefined') {\n // Microtask — клиент успевает подписаться синхронно после конструктора,\n // до того как событие действительно стрельнёт.\n queueMicrotask(() => this.checkReturn());\n }\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 const endpoint =\n cfg.endpoint ?? `${this.billing.apiOrigin}/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 flushIntervalMs: cfg.flushIntervalMs,\n maxBufferSize: cfg.maxBufferSize,\n fetch: cfg.fetch,\n sendBeacon: cfg.sendBeacon\n });\n\n // Биндим внутренние SDK-события на аналитический транспорт. Один эмиттер,\n // один потребитель (трекер) — никто кроме трекера не должен трогать\n // эти имена событий за пределами PaywallUI.\n this.on('open', () => this.tracker?.track('paywall_opened'));\n this.on('ready', (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 })\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 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', () => this.tracker?.track('paywall_closed'));\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 пока не фаерим: authChange эмитится\n // и на гидрации сессии (UI поднимает кеш из storage), и на token refresh,\n // и при параллельных consumer'ах одного auth-state — даёт ложные signin'ы.\n // Реальные login-события нужно ловить через прямые вызовы\n // signInWithEmail/signUp/signInWithOAuth/signOut, а не через authChange.\n }\n\n /**\n * Отправить произвольное аналитическое событие. Имена из системного whitelist'а\n * (`app_opened`, `paywall_viewed`, ...) разрешены как есть. Кастомные —\n * с префиксом `host:` (например `host:user_clicked_upgrade`). Сервер\n * дропает события с неразрешёнными именами.\n *\n * Самый частый кейс — `track('app_opened')` от хоста сразу после загрузки\n * приложения, чтобы зафиксировать воронку до открытия пейвола.\n */\n track(name: string, props?: Record<string, unknown>): void {\n this.tracker?.track(name, props);\n }\n\n /**\n * Удобный шорткат вместо `paywall.on('userChange', cb)` — самый частый\n * паттерн в host-коде, поэтому отдельный named метод. Колбек получает\n * last-known user из кеша синхронно через microtask, если он есть.\n */\n onUserChange(handler: PaywallEventHandler<'userChange'>): () => void {\n return this.on('userChange', handler);\n }\n\n /**\n * Заменить cachedBootstrap живыми данными — для preview-режима в редакторе\n * админки. Если модалка открыта, PaywallRoot подписан на onBootstrapChange\n * и перерендерится мгновенно. До open() — затравка для bootstrap()-effect'а.\n *\n * См. {@link BillingClientOptions.preview} — обычно эту опцию ставят на\n * клиент, чтобы заодно отключить сетевой revalidate. setBootstrap технически\n * работает и в production-режиме, но конкуренция с revalidate'ом из сети\n * почти всегда нежелательна.\n */\n setBootstrap(partial: Partial<PaywallBootstrap>): void {\n this.billing.setBootstrap(partial);\n }\n\n /**\n * Сменить force-locale на лету — для live-preview редактора админки, когда\n * юзер переключает «Preview as user from <country>». Грузит соответствующий\n * static-чанк и форсит re-render через handle.update. См. PaywallUIOptions.locale.\n *\n * Передай `null`/`undefined`, чтобы вернуть автоматическую резолв-логику\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 есть, только если модалка открыта; иначе locale подхватится на\n // следующем mountAndShow() из сохранённого 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 * Прогревает bootstrap-кеш и balance-кеш заранее, без открытия модалки.\n * Полезно когда host знает, что юзер скоро откроет paywall (hover на CTA,\n * mount компонента) — первый `open()` рендерится мгновенно, без loading-flash.\n *\n * Не throw'ает: если сеть упала, тихо игнорирует (повторный open() сделает\n * fresh-bootstrap с error-state как обычно). `signal` для отмены — например,\n * если хост размонтирует компонент быстрее, чем bootstrap вернётся.\n *\n * Вызывать можно сколько угодно раз — последующие вызовы возвращают cached\n * Promise (BillingClient уже дедуплицирует).\n */\n async preload(opts: { signal?: AbortSignal } = {}): Promise<void> {\n try {\n await this.billing.bootstrap({ signal: opts.signal });\n // Балансы — best-effort: пейволы без `tokenization` отдают пустой\n // массив, и getBalances не делает сетевого запроса для unauth-юзера.\n if (this.billing.auth) {\n await this.billing.getBalances({ signal: opts.signal });\n }\n } catch {\n /* preload best-effort — open() сам покажет error-state */\n }\n }\n\n /**\n * Открывает модалку сразу с саппорт-формой (минуя layout с тарифами).\n * Полезно, когда host-приложение хочет дать юзеру кнопку «Help / Support»,\n * не связанную с пейволом-апгрейдом. Back/Done в саппорт-форме закрывают\n * модалку (не возвращают к тарифам), потому что юзер пришёл сюда напрямую.\n *\n * Из обычного `paywall.open()`-flow саппорт всё равно доступен через\n * Contact Support-ссылку в `current_session`-блоке (там Back возвращает\n * к layout).\n */\n openSupport(opts: OpenOptions = {}): void {\n this.openInternal('support', opts);\n }\n\n /**\n * Открывает модалку сразу с auth-gate (логин/регистрация), без layout с\n * тарифами. Сценарий: returning customer уже купил, ему просто нужно\n * залогиниться, чтобы SDK подцепил его purchases. После signIn модалка\n * закрывается; Back тоже закрывает (юзер пришёл только за логином).\n *\n * Без `auth` (managed-auth не подключён) метод — no-op: некому делать\n * signIn. Если юзер уже залогинен — модалка всё равно откроется и\n * закроется через auto-resume в auth_gate effect'е (мгновение).\n *\n * Триал не блокирует этот флоу — auth не connect'ится с trial-механикой.\n */\n openAuth(opts: OpenOptions = {}): void {\n if (!this.auth) return;\n this.openInternal('auth', { ...opts, skipTrial: true });\n }\n\n /**\n * Шорткат над `openAuth()` — открывает модалку сразу на signin-форме.\n * Эквивалент `openAuth()` (signin — дефолт). Существует для симметрии с\n * `openSignup()` и читаемости host-кода:\n * - `paywall.openSignin()` — «вход в существующий аккаунт»\n * - `paywall.openSignup()` — «новая регистрация»\n * Без managed-auth — 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 * Открывает модалку с auth-gate сразу в режиме регистрации (signup-mode\n * AuthPanel'а — email/password/repeat). Если в paywall layout админ\n * отключил allow_signup, AuthPanel игнорит mode и стартует с signin —\n * соблюдается admin-конфиг.\n * Без managed-auth — 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 * Headless anonymous signin без открытия модалки. Внутри:\n * idempotent (если уже анон — instant return) → resume через сохранённый\n * refresh_token → fresh /auth/anonymous/signin. Дедуплицирует\n * параллельные вызовы внутри AuthClient'а.\n *\n * Удобно для host-кнопок типа «Continue as guest» — host сам управляет\n * loading-стейтом на своей кнопке, без полупустой модалки со спиннером.\n * Без managed-auth — резолвится rejected promise'ом (нет AuthClient'а\n * чтобы делать 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 // Сбрасываем флаг success-вью — повторное открытие должно стартовать\n // с обычного layout, а не с прошлого \"Payment received\".\n this.purchased = false;\n\n // support и auth-standalone флоу обходят оба гейта (триал и таргетинг):\n // юзер пришёл за саппортом или за логином к уже купленной подписке —\n // блокировать его по trial-stage'у или таргетингу неуместно. openAuth\n // дополнительно передаёт skipTrial:true для совместимости с прежней\n // семантикой; здесь skip-флаги нормализуем единообразно.\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\n if (skipTrial && skipVisibility) {\n this.mountAndShow(view, { renew, authMode: opts.authMode });\n return;\n }\n\n // Cache hit — sync путь, gates до mount как раньше. Никаких компромиссов:\n // когда bootstrap уже в памяти, мы знаем за один tick можно открывать\n // или нет, без флеша.\n const cached = this.billing.getCachedBootstrap();\n if (cached) {\n this.runOpenGates(view, cached, { skipTrial, skipVisibility, renew });\n return;\n }\n\n // Cold bootstrap. Два режима:\n //\n // mountThenLoad=true (default): монтируем модалку немедленно — юзер видит\n // спиннер, кнопка отзывается мгновенно. Bootstrap идёт параллельно.\n // Когда придёт — гоняем gates, и если блокирует, закрываем модалку с\n // эмиссией *_blocked. Цена — флеш «открылась → закрылась» в редком\n // случае visibility/trial-блока. Для extension'ов и сайтов с включённым\n // targeting'ом большинство open()'ов проходят, флеш — edge case.\n //\n // mountThenLoad=false (legacy): ждём bootstrap до mount'а. Гарантия\n // отсутствия флеша на блоке, но кнопка кажется «мёртвой» 200-500мс\n // на холодном кеше.\n if (this.mountThenLoad) {\n this.mountAndShow(view, { renew });\n this.billing\n .bootstrap()\n .then((b) => this.runDelayedGates(b, { skipTrial, skipVisibility }))\n .catch(() => {\n // Bootstrap упал — модалка уже открыта, PaywallRoot сам в error-state.\n });\n return;\n }\n\n this.billing\n .bootstrap()\n .then((b) => this.runOpenGates(view, b, { skipTrial, skipVisibility, renew }))\n .catch(() => {\n // Bootstrap упал — открываем без gates; PaywallRoot покажет error.\n this.mountAndShow(view, { renew });\n });\n }\n\n /** Применить gates ПОСЛЕ того, как модалка уже смонтирована (mount-then-load\n * путь). Если gate блокирует — close() + emit. Если юзер уже сам закрыл\n * модалку до резолва bootstrap'а — no-op (isOpen=false). */\n private runDelayedGates(\n bootstrap: PaywallBootstrap,\n flags: { skipTrial: boolean; skipVisibility: boolean }\n ): void {\n if (!this.isOpen) return;\n\n if (!flags.skipVisibility) {\n const v = bootstrap.settings.visibility;\n if (v) {\n this.lastVisibility = v;\n if (!v.visible) {\n this.close();\n this.emit('visibility_blocked', v);\n return;\n }\n }\n }\n\n if (flags.skipTrial) return;\n\n const trialCfg = bootstrap.settings.trial;\n if (!trialCfg) return;\n const store = this.ensureTrialStore(trialCfg);\n void store\n .check()\n .then(async (status) => {\n if (!this.isOpen) return;\n this.lastTrialStatus = status;\n if (status.mode === 'none') return;\n if (status.blocked) {\n const updated = await store.recordBlock();\n this.lastTrialStatus = updated;\n if (!this.isOpen) return;\n this.close();\n this.emit('trial_blocked', updated);\n return;\n }\n if (!this.trialExpiredFired) {\n this.trialExpiredFired = true;\n this.emit('trial_expired');\n }\n })\n .catch((e) => {\n if (typeof console !== 'undefined') console.warn('[paywall] trial check failed', e);\n });\n }\n\n // Порядок гейтов: visibility → trial. Country-mismatch ≠ trial-block, и\n // вести trial-стейт «осталось N показов» под юзером, который вообще не\n // должен увидеть пейвол по таргетингу — бессмысленно: при возврате в\n // правильную страну он окажется со «слипшимся» триал-счётчиком.\n private runOpenGates(\n view: PaywallView,\n bootstrap: PaywallBootstrap,\n flags: { skipTrial: boolean; skipVisibility: boolean; renew: boolean }\n ): void {\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 });\n return;\n }\n this.gateThroughTrial(view, bootstrap, flags.renew);\n }\n\n private gateThroughTrial(view: PaywallView, bootstrap: PaywallBootstrap, renew: boolean): void {\n const trialCfg = bootstrap.settings.trial;\n if (!trialCfg) {\n this.mountAndShow(view, { renew });\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 });\n return;\n }\n if (status.blocked) {\n // recordBlock делает запись (init firstOpen / inc skipTimes) и\n // возвращает обновлённый snapshot — его и эмитим, чтобы хост\n // получил актуальный счётчик.\n const updated = await store.recordBlock();\n this.lastTrialStatus = updated;\n this.emit('trial_blocked', updated);\n return;\n }\n // Триал в конфиге, но не блокирует → истёк. Эмитим один раз за\n // сессию, дальше открываем как обычно.\n if (!this.trialExpiredFired) {\n this.trialExpiredFired = true;\n this.emit('trial_expired');\n }\n this.mountAndShow(view, { renew });\n })\n .catch((e) => {\n // Storage недоступен (privacy mode, quota) — не блокируем юзера,\n // открываем модалку и не теряем продажу.\n if (typeof console !== 'undefined') console.warn('[paywall] trial check failed', e);\n this.mountAndShow(view, { renew });\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: если billing-клиент предоставляет свой factory (extension'овский\n // RemoteBillingClient — атомарный TrialStore через offscreen + navigator.locks),\n // используем его. Иначе — обычный path через 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: { renew?: boolean; authMode?: 'signin' | 'signup' } = {}\n ): void {\n const renew = mountOpts.renew === true;\n const initialAuthMode = mountOpts.authMode;\n if (this.handle) {\n this.isOpen = true;\n this.handle.update({\n open: true,\n initialView: view,\n initialAuthMode,\n purchased: false,\n renew\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 purchased: false,\n renew,\n onClose: () => this.close(),\n onEvent: (event, payload) => {\n this.emit(event as PaywallEvent, payload as never);\n // Поднимаем watcher как только начался checkout — отсюда уже\n // полагаемся на server-confirmed flow, а не URL-маркеры.\n if (event === 'checkout_started') this.startUserWatcher();\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 * Sync-snapshot текущего состояния модалки. Подходит для `useSyncExternalStore`\n * в React (`useSyncExternalStore(paywall.onStateChange, paywall.getState)`)\n * и для одноразовых проверок («открыт ли пейвол сейчас?»).\n *\n * Snapshot стабилен — пока state не изменился, повторный getState() вернёт\n * `===`-равный объект (важно для useSyncExternalStore чтобы не ре-рендерить).\n */\n getState(): PaywallStateSnapshot {\n return this.currentState;\n }\n\n /**\n * Подписка на изменения state. Колбек вызывается при каждом реальном\n * изменении (closed → loading → ready → ...). По умолчанию initial snapshot\n * отдаётся через microtask после подписки; через `{immediate: 'sync'|'none'}`\n * можно сделать sync-доставку (для useSyncExternalStore — там она не нужна,\n * snapshot читается через getSnapshot отдельно) или вовсе пропустить\n * initial.\n *\n * Возвращает unsubscribe.\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-доступ к последнему известному статусу триала. null — `paywall.open()`\n * ещё не вызывался либо триал отключён в конфиге пейвола. Удобно для\n * собственного UI хоста («осталось 3 показа», «триал истечёт через 2ч»). */\n getTrialStatus(): TrialStatus | null {\n return this.lastTrialStatus;\n }\n\n /** Sync-доступ к последнему server-computed visibility-статусу. null —\n * bootstrap ещё не загружен или сервер не отдаёт `settings.visibility`\n * (например, старая версия online без targeting-патча). Хост может\n * использовать для собственного fallback'а: «сервис недоступен в вашей\n * стране». Обновляется на каждом open(), который проходит через gate. */\n getVisibility(): VisibilityStatus | null {\n return this.lastVisibility;\n }\n\n /**\n * Цены пейвола — шорткат над `bootstrap()`. Локали уже применены, кэш и\n * stale-while-revalidate идентичны `billing.bootstrap()`. Подходит для\n * pricing-страниц/карточек на сайте, где host хочет показать те же цены,\n * что и в модалке, не вытаскивая bootstrap руками.\n */\n getPrices(opts: { force?: boolean; signal?: AbortSignal } = {}): Promise<PaywallPrice[]> {\n return this.billing.getPrices(opts);\n }\n\n /** Sync-снимок цен. null — bootstrap ещё не загружали. */\n getCachedPrices(): PaywallPrice[] | null {\n return this.billing.getCachedPrices();\n }\n\n /** Снимок текущего «языка юзера» — proxy над `billing.getUserLanguage()`.\n * Используй, чтобы синхронизировать i18n host'а с тем, что фактически\n * показывает пейвол. См. подробности в `BillingClient.getUserLanguage`. */\n getUserLanguage(): UserLanguageInfo {\n return this.billing.getUserLanguage();\n }\n\n /**\n * Решает, нужно ли блокировать фичу для текущего юзера. Без побочных эффектов\n * (на trial-storage `recordBlock` не вызывается, модалка не монтируется).\n *\n * Порядок проверок (первый сработавший — финальный):\n * 1. `has_active_subscription` — самый сильный сигнал, перебивает остальные.\n * Юзер с подпиской получает доступ независимо от visibility/trial.\n * 2. `visibility` (страна/девайс/disabled-флаг) — юзер вне monetization-scope'а\n * пейвола, гейтить нельзя.\n * 3. `trial` — пре-пейвольный бесплатный период активен.\n * 4. Иначе — `blocked`, host лочит фичу и зовёт `paywall.open()`.\n *\n * Bootstrap кешируется в BillingClient — `getAccess()` можно дёргать на\n * каждый рендер host-компонента, /bootstrap не дублируется. При упавшей сети\n * fallback на persistent-cached user из storage: юзер с прошлой подпиской\n * получает `granted` офлайн, иначе `blocked` (host покажет пейвол с\n * error-state, юзер сможет ретрайнуть). Side-эффект: обновляются\n * `lastVisibility` / `lastTrialStatus`, чтобы синхронные геттеры\n * `getVisibility()` / `getTrialStatus()` видели свежие данные после первого\n * `getAccess()`, а не только после первого `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 // Сеть упала. Fallback на persistent-cached user (TTL 30 мин в storage).\n // Юзер с прошлой подпиской → granted (офлайн-friendly), иначе → blocked\n // (open() покажет пейвол с error-state, юзер ретрайнет).\n const cached = this.billing.getCachedUser();\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 const 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 /** Сбросить состояние триала в storage. Полезно для дев-режима / админ-кнопки\n * «прогнать сценарий заново». В проде хост обычно не дёргает. */\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 // Запускает polling user-state до has_active_subscription=true либо до\n // таймаута. Идемпотентен: повторный вызов на уже работающем watcher'е —\n // no-op (юзер мог нажать Continue повторно после возврата).\n //\n // В extension popup runtime — no-op (popup не доживёт). Там полагаемся на\n // bootstrap при следующем открытии.\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) => {\n this.watcher = null;\n // Серверная правда — эмитим финальный purchase_completed\n // (server-confirmed), чтобы host получил согласованный сигнал\n // независимо от того, был ли URL-маркер. userChange эмитит сам\n // billing-listener.\n this.emit('purchase_completed', { priceId: null, sessionId: null });\n // success_redirect_url из settings — host явно попросил отправить\n // юзера в свой apps-flow после оплаты. Редирект имеет приоритет\n // над PurchaseSuccessView: рисовать success ради 200мс перед\n // переходом — мерцание. Берём snapshot из cached bootstrap\n // (он гарантированно загружен — иначе watcher не запустился бы).\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 заблокирована — fallback на success-view */\n }\n }\n // Если пейвол открыт — переключаем во вью «Payment received» с\n // кнопкой Continue. Молчаливое закрытие сбивало юзера с толку:\n // окно просто исчезало, без подтверждения, что оплата прошла.\n // Если пейвол закрыт — событие уже эмитнуто, host решит сам.\n if (this.isOpen && this.handle) {\n this.purchased = true;\n this.handle.update({ purchased: true });\n }\n void user; // shape доступен через paywall.billing.getCachedUser()\n },\n onTimeout: () => {\n this.watcher = null;\n }\n });\n this.watcher.start();\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 эмитит onState с open=false при handle.update, но из-за\n // microtask'ов хост может прочитать getState() до того, как PaywallRoot\n // useEffect отстреляет. Применяем закрытое состояние сразу.\n this.applyState(CLOSED_STATE);\n this.emit('close');\n }\n\n /**\n * Сканирует текущий URL на маркеры возврата с checkout и эмитит\n * purchase_completed / purchase_failed. Маркеры удаляются из URL\n * через history.replaceState. Ищет и в hash, и в search (hash приоритетнее —\n * защита от клиентских SPA-роутеров, перехватывающих 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: если страница загружена в новой вкладке от исходного\n // приложения (typical Stripe success_url flow), шлём opener'у postMessage.\n // Watcher в исходной вкладке среагирует мгновенно, не дожидаясь focus\n // event. Если opener'а нет (юзер закрыл/не было) — fallback на 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 // Если AuthClient был передан хостом — его жизненный цикл не наш,\n // ничего не дёргаем. Если создавали мы — отписываемся через BillingClient\n // (он сам держит listener на onAuthChange) и оставляем session в storage,\n // чтобы следующее открытие подхватило её через hydrate.\n if (this.ownsAuth && this.auth) {\n // Если AuthClient создавали мы — destroy сами, чтобы snapshot listener\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 ИЛИ структурный совместимец (RemoteAuthClient из\n // @monetize/sdk-extension). Проверяем по public-методам, которые\n // PaywallUI использует — если все на месте, доверяем. Это позволяет host'у\n // подставить proxy-реализацию (offscreen-architecture) без изменений в\n // PaywallUI. instanceof не подходит — runtime в content-script'е и в\n // sdk-extension'е разные, классы не nominally равны.\n if (opts.auth instanceof AuthClient || isAuthClientLike(opts.auth)) {\n return { auth: opts.auth as AuthClient, ownsAuth: false };\n }\n // true | partial-options → создаём свой AuthClient. apiOrigin/storage/fetch\n // подхватываем из общих опций PaywallUI, чтобы конфиг был \"одно поле — вся\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// Проверяет «AuthClient-подобность» переданного объекта по public-методам,\n// которые PaywallUI трогает (`onAuthChange`, `getCachedSession`, `signOut`).\n// Partial<AuthClientOptions> этих методов не имеет — пересечения с этим\n// объединением нет, ложноположительных не будет.\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 a.open === b.open && a.view === b.view && a.error === b.error;\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// Контракт сообщения должен совпадать с UserWatcher.handleMessage:\n// `{ type: 'paywall_purchase' }`. opener — исходная вкладка хоста, на ней живёт\n// PaywallUI с активным watcher'ом, ждущим этого сигнала.\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 /* opener из другого origin или закрыт — watcher через 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 — TrialStore-совместимый proxy. check / recordBlock /\n// reset идут через transport в offscreen, где реальный TrialStore работает\n// под navigator.locks — два таба не могут одновременно read-modify-write\n// один и тот же counter, 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 — структурный совместимец BillingClient, который\n// проксирует все методы в offscreen через TransportClient. Public API\n// идентичен (host пишет тот же код, что для @monetize.software/sdk), реализация\n// другая. Sync-getCached* остаются sync — ходят в локальный mirror, который\n// обновляется (a) ответами на async-методы и (b) broadcast-событиями\n// userChange/balancesChange.\n\nimport type {\n Balance,\n CheckoutResult,\n Identity,\n PaywallBootstrap,\n PaywallPrice,\n PaywallPurchaseDetailed,\n PaywallUser,\n 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 { 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 // Локальные mirror'ы. Источник правды — offscreen; mirror нужен только\n // чтобы getCached* оставались sync. Updated после каждого async-ответа +\n // на каждом broadcast-событии.\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 через transport: get/set/remove идут в offscreen'овский\n * StorageAdapter (single source of truth для всех вкладок). Trial-state\n * PaywallUI пишет сюда — все табы видят один и тот же counter, не\n * drift'ит между вкладками.\n *\n * Race-окно read-modify-write всё ещё существует (две вкладки одновременно\n * читают N → пишут N-1, drift 1). Для exact atomicity нужен Phase 9:\n * TrialStore целиком переехать в offscreen и делать recordBlock как\n * single-handler с одной atomic-операцией. Это редкий edge case\n * (одновременное открытие пейвола в нескольких вкладках в рамках мс). */\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 // watch не реализуем — для cross-context уведомлений consumer'ы (AuthClient,\n // TrialStore) подписываются на broadcast-events напрямую через transport.\n // Если когда-то понадобится — добавим 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 /** Подписка на bootstrap-state. Структурно совместима с\n * `BillingClient.onBootstrapChange` — те же микротаск-семантики для initial\n * snapshot. В extension-режиме offscreen пока не broadcast'ит bootstrapChange,\n * поэтому listener срабатывает только на self-инициированные `bootstrap()`\n * внутри этого RemoteBillingClient'а (popup перезапрашивает bootstrap → mirror\n * обновляется → listener вызывается). Cross-surface revalidate (другая вкладка\n * обновила bootstrap) не доезжает до popup'а — для этого нужен отдельный\n * bootstrapChange-broadcast в 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 /** Шорткат над `bootstrap()` — возвращает цены пейвола (locale-оверрайды\n * уже применены в offscreen'е). Те же кэш-семантики, что у `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-снимок цен из локального mirror'а bootstrap'а. null = ещё не грузили. */\n getCachedPrices(): PaywallPrice[] | null {\n return this.cachedBootstrap?.prices ?? 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 /** Подписка на user-state. Mirror'имся на broadcast'ы offscreen'а; initial\n * snapshot отдаётся через microtask из локального cache (если есть) —\n * ровно как в BillingClient.onUserChange. Возвращает функцию отписки. */\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 список покупок юзера (с ценой, валютой, interval, discount,\n * cancel-метаданными). Через offscreen — там настоящий BillingClient\n * ходит на `/api/v1/paywall/[id]/user` с Bearer'ом. Полезно для\n * customer-portal UI: cards + Cancel/Renew кнопки. */\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 /** Саппорт-тикет через offscreen'овский BillingClient. File-объекты\n * переживают chrome.runtime structured-clone (port forward'ит as-is) —\n * Bearer-токен/email-substitution делает offscreen, как в обычном\n * 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 return this.transport.request('billing.createSupportTicket', payload);\n }\n\n /** Отменить подписку через бэк. По умолчанию cancel в конце текущего\n * периода (юзер сохраняет access до renewal date'ы). reason обязательна\n * (валидируется бэком) — собирается через select причин в 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 // === Storage ===\n\n /** PaywallUI просит storage у billing-клиента для TrialStore и других\n * consumer'ов. Возвращает proxy: get/set/remove идут через transport\n * в offscreen'овский storage = single source of truth для всех вкладок. */\n getStorage(): StorageAdapter {\n return this.remoteStorageAdapter;\n }\n\n /** Factory-метод для PaywallUI: вместо локального createTrialStore'а на\n * storage-proxy, возвращаем RemoteTrialStore — он шлёт каждую операцию\n * одним атомарным RPC в offscreen, где navigator.locks сериализуют\n * read-modify-write. PaywallUI duck-types этот метод и предпочитает его\n * локальной фабрике, если он есть. */\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 /** Подгрузить identity с offscreen'а. Используется при первом подключении\n * content-script'а — если другая вкладка уже залогинила юзера, текущая\n * тут же подхватит identity без ожидания 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 /** Обновить mirror user'а и эмитнуть listener'ам если он реально изменился.\n * Используется и для self-инициированных RPC (bootstrap/getUser), и для\n * broadcast'ов от offscreen — чтобы host'овский onUserChange handler\n * получил signal независимо от того, кто триггернул обновление. */\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 — структурный совместимец AuthClient. Public методы\n// идентичны, под капотом — async-прокси через TransportClient в offscreen,\n// где живёт реальная сессия и storage.\n//\n// Sync-getCachedSession поддерживается через локальный mirror, обновляемый\n// (a) на каждом ответе async-метода, (b) на broadcast'е authChange.\n//\n// OAuth (signInWithOAuth) пока бросает not-implemented — требует publicного\n// split-API в @sdk/core/auth (Phase 4.5). Для email/password/refresh/signOut\n// и прочей сетевой части — всё работает.\n\nimport type {\n AuthChangeEvent,\n AuthSession,\n AuthUser,\n LastLogin,\n OAuthProvider,\n OtpVerifyType,\n SignUpResult\n} from '@sdk/core/auth';\nimport { waitForOAuthCode } 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 с offscreen'а — поднимаем restored session в local mirror\n // ДО первого `getCachedSession()`. listeners получат восстановленную\n // session через свой собственный INITIAL_SESSION microtask из onAuthChange\n // (см. ниже) — не дёргаем applySession, чтобы не превратить «восстановление\n // из storage» в выглядящий как-будто-signin event.\n this.hydrated = this.transport\n .request('auth.getCachedSession', undefined)\n .then((session) => {\n // Конкурентность: если за время request'а кто-то уже выставил session\n // (broadcast SIGNED_IN или локальный signIn-метод), не перетираем —\n // hydrate-снимок stale относительно того, что уже в local mirror.\n if (this.session === null && session !== null) {\n this.session = session;\n }\n })\n .catch(() => {\n /* offscreen не готов или транспорт упал — getCachedSession отдаст null */\n });\n }\n\n /** Promise, который резолвится после первичной синхронизации session с\n * offscreen'а. Аналог 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 после hydrate'а — match @sdk/core AuthClient.\n // Контракт: первый callback = INITIAL_SESSION с restored snapshot'ом\n // (или null), последующие = реальные переходы через 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 // Локальный mirror-update + emit. Broadcast от offscreen'а тоже прилетит\n // с тем же event'ом — `sameSession` guard в applySession отсечёт второй\n // emit, не дёргая listener'ов дважды.\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 // Broadcast authChange придёт от offscreen'а с session=null, applySession\n // там уже отработает. Тут ничего не делаем, чтобы не дёрнуть listener'ы\n // дважды.\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 — читается из offscreen-storage. AuthPanel\n * использует для \"Last used\"-бейджа и pre-fill'а email. Storage paywall-\n * scoped, и offscreen — единый источник правды для всех вкладок/popup'ов. */\n async getLastLogin(): Promise<LastLogin | null> {\n return this.transport.request('auth.getLastLogin', undefined);\n }\n\n // === Anonymous sign-in ===\n\n /** Анонимный sign-in (Supabase user без email). Логика (idempotent-check +\n * resume через сохранённый refresh_token + fresh signin) живёт в\n * offscreen-AuthClient'е — content только проксирует. captchaToken и\n * forceCaptcha — pass-through для forward-compat / switch-account flow. */\n async signInAnonymously(input: {\n captchaToken?: string;\n userMeta?: Record<string, string>;\n forceCaptcha?: boolean;\n } = {}): Promise<AuthSession> {\n const session = await this.transport.request('auth.signInAnonymously', {\n captchaToken: input.captchaToken,\n userMeta: input.userMeta,\n forceCaptcha: input.forceCaptcha\n });\n this.applySession('SIGNED_IN', session);\n return session;\n }\n\n /** Текущий access token (lazy-refreshable в offscreen'е). content/popup\n * использует для Bearer'а в внешние fetch'и — ApiGatewayClient в\n * content-script'е, прямые запросы из demo-UI. null если разлогинен или\n * offscreen'овский AuthClient не смог рефрешнуть. */\n async getAccessToken(): Promise<string | null> {\n return this.transport.request('auth.getAccessToken', undefined);\n }\n\n // === OAuth (web-flow через split-API) ===\n\n /** OAuth через web-вариант: window.open в content-script'е, provider\n * redirect, callback page постит code в opener. Под капотом split на\n * два request'а в offscreen — startOAuthFlow (дёргаем /init, получаем\n * authorize_url) → открываем popup → waitForOAuthCode → exchange.\n *\n * PKCE verifier живёт ТОЛЬКО в offscreen'е (внутри AuthClient'а), через\n * runtime-границу не идёт. Content получает только authorize_url и state.\n *\n * Popup-gesture: `window.open(authorize_url, ...)` идёт в том же synchronous\n * flow'е, что startOAuthFlow ответ; user-gesture сохраняется потому что\n * content-script unloaded не за этот tick (gesture сохраняется через все\n * microtask'и одного call stack'а). Если в каком-то браузере gesture\n * всё-таки теряется — host получит `popup_blocked` (тот же что в @monetize.software/sdk).\n */\n async signInWithOAuth(input: {\n provider: OAuthProvider;\n scopes?: string;\n userMeta?: Record<string, string>;\n onPopupOpened?: () => void;\n }): Promise<AuthSession> {\n if (typeof window === 'undefined') {\n throw new PaywallError('oauth_unavailable', 'window is required for OAuth');\n }\n\n // Открываем popup СИНХРОННО — user-gesture сохраняется только в том же\n // synchronous frame, что click-handler. Async `await` на transport.request\n // до window.open съедает gesture, и Chrome открывает popup с пустым URL'ом\n // / блокирует совсем.\n //\n // about:blank вместо data:text/html (которым раньше показывали inline-loader):\n // data:-URL'ы триггерят static-сканеры CWS и EDR'ы как подозрительные. Вместо\n // этого открываем about:blank (наследует origin opener'а) и инжектим loader-DOM\n // через document.createElement + textContent — ровно то же UX, без 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 try {\n // Async-часть: дёргаем offscreen за authorize_url и state. Popup пока\n // показывает 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 });\n\n // Перед navigate'ом меняем имя popup'а на формат, который ожидает\n // callback page (pw-oauth-<state>) — name переживает cross-origin\n // редиректы (Google → Supabase → наш callback). callback page\n // читает window.name → извлекает state → posts back.\n popup.name = `pw-oauth-${state}`;\n popup.location.replace(authorizeUrl);\n\n input.onPopupOpened?.();\n\n const code = await waitForOAuthCode(popup, state);\n const session = await this.transport.request('auth.oauthExchange', { state, code });\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 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 loader-UI в about:blank popup. Same-origin как opener — мы можем\n * трогать popup.document напрямую. Используем createElement + textContent\n * (не innerHTML / document.write) чтобы не триггерить XSS-сканеры даже на\n * hard-coded строках. CSS-классы с pw-oauth-* префиксом — без коллизий со\n * стилями родительской страницы (popup всё равно изолирован, но на всякий).\n *\n * Defensive try/catch: если в каком-то edge-кейсе popup оказался не\n * same-origin (некоторые расширения это перехватывают) или document не\n * доступен — тихо забиваем, popup покажет дефолтный about:blank на 200-500мс\n * до redirect'а на 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 — fire-and-forget proxy для аналитики. Все track()\n// call'ы из всех вкладок попадают в единственный EventTracker в offscreen'е,\n// который батчит и шлёт в /events. Победа — один батч на расширение,\n// один sendBeacon на unload, никаких дублирующихся `app_opened` событий.\n//\n// API специально минимальный — только track(name, props). Buffer / flush /\n// destroy логика живёт в offscreen'е, content её не контролирует.\n\nimport { TransportClient } from '../shared/transport-client';\n\nexport class RemoteEventTracker {\n constructor(private readonly transport: TransportClient) {}\n\n /** Отправить событие. Fire-and-forget — не возвращает Promise, не throw'ает.\n * Сетевые/транспортные ошибки логируются в console и не блокируют 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","// Client-side транспорт. Используется в content-script'е (поверх chrome.runtime\n// port'а к SW) и в любом другом surface'е, который ходит к offscreen через\n// тот же роутер (popup, extension page, side panel).\n//\n// Контракт:\n// - request<K>(kind, params, signal?) — запрос с типизированным результатом.\n// На disconnect канала pending request'ы reject'аются с reconnect-error;\n// next call воссоздаст канал через ChannelFactory и продолжит работу.\n// - on<K>(kind, handler) — подписка на broadcast от сервера. Переподписки\n// переживают reconnect автоматически — handler'ы хранятся локально, а\n// re-subscribe на сервере не требуется (сервер всегда broadcast'ит всем\n// подключённым каналам).\n//\n// Reconnect стратегия: lazy. Канал поднимается при первом request/on, мёртвый —\n// пересоздаётся в момент следующего запроса. Никаких exponential backoff'ов\n// в фоне — extension контекст это не любит (расход CPU + батарея).\n\nimport type {\n EventEnvelope,\n EventKind,\n EventPayload,\n RequestEnvelope,\n RequestKind,\n RequestParams,\n RequestResult,\n ResponseEnvelope,\n ResponseErr\n} from './protocol';\nimport { PROTOCOL_VERSION } from './protocol';\nimport { reconstructError } from './errors';\nimport type { ChannelFactory, MessageChannel } from './channel';\n\ninterface PendingRequest {\n resolve: (value: unknown) => void;\n reject: (reason: unknown) => void;\n abortListener?: () => void;\n signal?: AbortSignal;\n}\n\nexport class TransportClient {\n private channel: MessageChannel | null = null;\n private channelDisposers: Array<() => void> = [];\n private pending = new Map<string, PendingRequest>();\n private listeners = new Map<EventKind, Set<(payload: unknown) => void>>();\n private destroyed = false;\n private nextId = 0;\n /** Уникальный ID клиента — отправляется в handshake'е, server может логировать\n * для отладки connection-flap'а. */\n private readonly clientId = `c-${Math.random().toString(36).slice(2, 10)}`;\n\n constructor(private readonly factory: ChannelFactory) {}\n\n /** Гарантирует наличие живого канала. Lazy — поднимается при первом request.\n * Сразу после connect'а fire-and-forget шлёт handshake — на mismatch\n * логируем warning, но не блокируем дальнейшие запросы. */\n private ensureChannel(): MessageChannel {\n if (this.destroyed) throw new Error('TransportClient destroyed');\n if (this.channel) return this.channel;\n\n const channel = this.factory();\n this.channel = channel;\n\n const offMsg = channel.onMessage((env) => this.handleMessage(env));\n const offDisc = channel.onDisconnect(() => this.handleDisconnect());\n this.channelDisposers = [offMsg, offDisc];\n\n // Async, без await: основные запросы могут параллельно идти. На mismatch'е\n // ничего не ломаем — server может быть на другой минорной версии (например,\n // host обновил sdk-extension но не sdk).\n void this.request('handshake', {\n protocolVersion: PROTOCOL_VERSION,\n clientId: this.clientId\n })\n .then((res) => {\n if (res.protocolVersion !== PROTOCOL_VERSION) {\n console.warn(\n `[sdk-extension] protocol version mismatch: client=${PROTOCOL_VERSION}, ` +\n `offscreen=${res.protocolVersion}. Update host's @monetize.software/sdk-extension.`\n );\n }\n })\n .catch(() => {\n // Server без handshake-handler'а или умер — best-effort, не падаем.\n });\n\n return channel;\n }\n\n private handleMessage(envelope: unknown): void {\n if (!isEnvelope(envelope)) return;\n if (envelope.type === 'response') {\n const pending = this.pending.get(envelope.id);\n if (!pending) return;\n this.pending.delete(envelope.id);\n pending.signal?.removeEventListener('abort', pending.abortListener!);\n if (envelope.ok) {\n pending.resolve(envelope.result);\n } else {\n // Narrowing на discriminated union с generic'ом теряется в strict-режиме —\n // явный cast стабильнее, ok===false уже проверен.\n pending.reject(reconstructError((envelope as ResponseErr).error));\n }\n return;\n }\n if (envelope.type === 'event') {\n const set = this.listeners.get(envelope.kind);\n if (!set) return;\n // Snapshot, чтобы handler мог отписать сам себя без NaN-итерации.\n for (const handler of [...set]) {\n try {\n handler(envelope.payload);\n } catch (e) {\n console.error('[sdk-extension] event handler threw', e);\n }\n }\n }\n }\n\n private handleDisconnect(): void {\n for (const fn of this.channelDisposers) fn();\n this.channelDisposers = [];\n this.channel = null;\n // Reject все in-flight — они идут с reconnect-кодом, host может ретрайнуть.\n const pending = Array.from(this.pending.values());\n this.pending.clear();\n for (const p of pending) {\n p.signal?.removeEventListener('abort', p.abortListener!);\n p.reject(new TransportDisconnectedError());\n }\n }\n\n request<K extends RequestKind>(\n kind: K,\n params: RequestParams<K>,\n opts: { signal?: AbortSignal } = {}\n ): Promise<RequestResult<K>> {\n if (this.destroyed) {\n return Promise.reject(new Error('TransportClient destroyed'));\n }\n if (opts.signal?.aborted) {\n return Promise.reject(new DOMException('Aborted', 'AbortError'));\n }\n\n const channel = this.ensureChannel();\n const id = `r${++this.nextId}`;\n\n return new Promise<RequestResult<K>>((resolve, reject) => {\n const pending: PendingRequest = {\n resolve: resolve as (value: unknown) => void,\n reject,\n signal: opts.signal\n };\n\n if (opts.signal) {\n pending.abortListener = () => {\n if (this.pending.delete(id)) {\n reject(new DOMException('Aborted', 'AbortError'));\n // Послать cancel в offscreen чтобы там тоже abort'нуть underlying\n // fetch. Best-effort: канал мог отвалиться — тогда send бросит,\n // но pending уже удалён, юзер уже получил abort error.\n try {\n channel.send({ type: 'cancel', id });\n } catch {\n /* channel dead — server'у уже всё равно */\n }\n }\n };\n opts.signal.addEventListener('abort', pending.abortListener);\n }\n\n this.pending.set(id, pending);\n\n const envelope: RequestEnvelope<RequestParams<K>> = {\n type: 'request',\n id,\n kind,\n params\n };\n try {\n channel.send(envelope);\n } catch (e) {\n this.pending.delete(id);\n opts.signal?.removeEventListener('abort', pending.abortListener!);\n reject(e);\n }\n });\n }\n\n on<K extends EventKind>(\n kind: K,\n handler: (payload: EventPayload<K>) => void\n ): () => void {\n let set = this.listeners.get(kind);\n if (!set) {\n set = new Set();\n this.listeners.set(kind, set);\n }\n const wrapped = handler as (payload: unknown) => void;\n set.add(wrapped);\n\n // Lazy ensureChannel: подписка не требует немедленного канала, но первый\n // event может прилететь только если канал жив. Поднимаем заранее.\n this.ensureChannel();\n\n return () => {\n set!.delete(wrapped);\n };\n }\n\n destroy(): void {\n if (this.destroyed) return;\n this.destroyed = true;\n for (const fn of this.channelDisposers) fn();\n this.channelDisposers = [];\n this.listeners.clear();\n const pending = Array.from(this.pending.values());\n this.pending.clear();\n for (const p of pending) {\n p.signal?.removeEventListener('abort', p.abortListener!);\n p.reject(new Error('TransportClient destroyed'));\n }\n this.channel?.close();\n this.channel = null;\n }\n}\n\nexport class TransportDisconnectedError extends Error {\n readonly code = 'transport_disconnected';\n constructor() {\n super('Transport channel disconnected mid-request');\n this.name = 'TransportDisconnectedError';\n }\n}\n\nfunction isEnvelope(value: unknown): value is RequestEnvelope | ResponseEnvelope | EventEnvelope {\n if (typeof value !== 'object' || value === null) return false;\n const t = (value as { type?: unknown }).type;\n return t === 'request' || t === 'response' || t === 'event';\n}\n","// Content-side singleton TransportClient. Один на content-script, переиспользуется\n// всеми инстансами PaywallUI на одной странице (на странице обычно один, но\n// несколько технически возможны — например один пейвол в overlay'е, другой\n// в popup'е host-расширения, оба внутри content-script'а одной страницы).\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/** Тестовая инжекция — для unit-тестов RemoteBillingClient'а с фейковым\n * каналом (без chrome.runtime). В проде не используется. */\nexport function _setContentTransportForTests(client: TransportClient | null): void {\n cached = client;\n}\n","// Drop-in `PaywallUI` для extension'а. Public API идентичен `@monetize.software/sdk`'у —\n// host пишет тот же код, опции те же. Под капотом:\n// - billing — RemoteBillingClient (proxy в offscreen)\n// - auth — RemoteAuthClient (когда `auth: true`)\n// - tracker — RemoteEventTracker (события forward'ятся в offscreen-EventTracker)\n//\n// EventTracker создаётся ОДИН на расширение, в offscreen'е. PaywallUI здесь\n// внутренний tracker отключает (`analytics: false` в base-конструкторе) и\n// сам подписывается на public-события, проксируя их через RemoteEventTracker.\n// Дубликат биндингов из base PaywallUI.initTracker — но это меньшее зло, чем\n// два EventTracker'а на одного юзера.\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/** Опции extension'овского PaywallUI. Убраны:\n * - `client` — RemoteBillingClient создаётся автоматически\n * - `storage` — storage живёт в offscreen'е, content его не видит\n * - `apiKey` — server-SDK key, не имеет смысла в content-script'е\n * - `fetch` — все сетевые запросы идут через offscreen\n *\n * `auth: true` подключит RemoteAuthClient. Передавать готовый AuthClient\n * из @monetize.software/sdk сюда не имеет смысла (мы хотим именно offscreen'овский). */\nexport interface ExtensionPaywallUIOptions\n extends Omit<PaywallUIOptions, 'client' | 'storage' | 'apiKey' | 'fetch'> {}\n\nexport class PaywallUI extends BasePaywallUI {\n /** RemoteEventTracker (proxy в offscreen-EventTracker). Не путать с\n * base-классовым `tracker` (там null — мы отключили внутренний). */\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: если host попросил — конструируем RemoteAuthClient. Готовый\n // AuthClient из @monetize.software/sdk сюда не имеет смысла прокидывать (всё\n // равно нужен offscreen-instance). Поэтому accept только `true` или\n // ничего; явный AuthClient instance логирует warning и игнорится.\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 // Прокидываем auth внутрь billing-клиента: PaywallRoot читает\n // `client.auth` для restore / preauth-flow / signin-detection. Настоящий\n // BillingClient выставляет это поле в конструкторе — у Remote-варианта\n // делаем явное присваивание перед super(), чтобы PaywallRoot увидел.\n if (auth) {\n (billing as { auth?: typeof auth }).auth = auth;\n }\n\n super({\n ...opts,\n // Cast'ы безопасны: PaywallUI'ев resolveAuth duck-type'ит auth (см.\n // sdk/src/ui/PaywallUI.ts isAuthClientLike), а billing-параметр идёт\n // через `opts.client ?? new BillingClient(...)` — RemoteBillingClient\n // там используется как есть, методы все сходятся.\n client: billing as unknown as BillingClient,\n auth: auth as unknown as AuthClient | undefined,\n // Внутренний EventTracker отключаем — единственный tracker живёт в\n // offscreen'е. Манчиально подписываемся ниже.\n analytics: false\n });\n\n if (opts.analytics !== false) {\n this.remoteTracker = new RemoteEventTracker(transport);\n this.bindAnalytics();\n }\n }\n\n /** Зеркало sdk/PaywallUI.initTracker'овских биндингов, но с RemoteEventTracker.\n * Когда @monetize.software/sdk экспоузнет публичный hook для inject'а tracker'а,\n * этот метод заменится на одну строку. */\n private bindAnalytics(): void {\n const t = this.remoteTracker;\n if (!t) return;\n\n this.trackerUnsubs.push(\n this.on('open', () => t.track('paywall_opened')),\n this.on('ready', (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 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 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', () => t.track('paywall_closed')),\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 // auth_signin_success / auth_signout пока не фаерим: authChange эмитится\n // и на гидрации сессии (popup поднимает кеш из offscreen), и на token\n // refresh, и при параллельном content-script + popup — даёт ложные\n // signin'ы. Реальные login-события нужно ловить через прямые вызовы\n // signInWithEmail/signUp/signInWithOAuth/signOut, а не через authChange.\n }\n\n /** Прокси через RemoteEventTracker. Hosts могут вызывать paywall.track\n * для произвольных аналитических событий — летит в единственный\n * offscreen-tracker наряду с auto-emit'ами PaywallUI. */\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 super.destroy();\n }\n}\n"],"names":["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","hideWhenAuthed","realSession","SignedIn","AuthForm","email","onSignOut","providers","initial","setMode","setEmail","password","setPassword","confirmPassword","setConfirmPassword","otpCode","setOtpCode","busy","setBusy","error","setError","info","setInfo","signupExpanded","setSignupExpanded","lastLogin","setLastLogin","current","switchTo","next","onSubmit","onOAuth","showOAuth","showEmailField","showPasswordField","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","AuthGate","authSession","onBack","showBack","intent","initialMode","effectiveBlock","BackArrowButton","ariaLabel","STORAGE_KEY","offerId","calcTimeLeft","endMs","distance","resolveEndMs","offer","startIso","pickActiveOffer","offers","preferredId","match","o","useOfferCountdown","timeLeft","setTimeLeft","endMsRef","timer","OfferBanner","titleWithDiscount","FlashIcon","Countdown","Fragment","Cell","OfferTopBanner","SUBJECT_MIN","SUBJECT_MAX","CONTENT_MAX","MAX_FILES","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","priceId","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","discountPercent","discounted","main","original","applicableOffer","targeted","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","Renderer","layout","onAction","hasTopBanner","defaultPriceId","useMemo","b","selectedPriceId","setSelectedPriceId","ctaIdx","scrollBlocks","footerBlocks","renderBlock","Cmp","computePaywallSnapshot","state","gate","purchased","sameSnapshot","a","PaywallRoot","onEvent","initialView","initialAuthMode","renew","onState","setState","setAuthSession","setGate","resumingRef","lastSnapshotRef","_event","data","useLayoutEffect","runCheckout","result","popup","reopenCheckout","url","handleAction","payload","cachedSession","hasRealSession","brand","activeOffer","gateBlock","supportView","bootstrapForI18n","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","opts","user","shouldRunUserWatcher","CLOSED_STATE","URL_MARKERS","PaywallUI$1","ownsAuth","resolveAuth","BillingClient","event","analytics","cfg","endpoint","EventTracker","name","handler","partial","set","args","view","skipTrial","skipVisibility","flags","trialCfg","store","status","updated","config","sameTrialConfig","factoryFn","createTrialStore","mountOpts","snapshot","sameStateSnapshot","cb","visibility","trial","redirect","hashMarkers","parseMarkers","searchMarkers","markers","notifyOpenerOfPurchase","stripMarkersFromUrl","AuthClient","isAuthClientLike","segment","clean","raw","prefix","RemoteTrialStore","transport","paywallId","RemoteBillingClient","balances","signal","identity","sameUser","sameBalances","RemoteAuthClient","input","tempName","injectLoaderUI","authorizeUrl","code","waitForOAuthCode","sameSession","PROVIDER_NAMES","doc","wrap","spinner","RemoteEventTracker","TransportClient","factory","channel","offMsg","env","offDisc","PROTOCOL_VERSION","res","envelope","isEnvelope","reconstructError","fn","TransportDisconnectedError","kind","id","resolve","reject","wrapped","getContentTransport","createRuntimeChannel","PORT_NAME","PaywallUI","BasePaywallUI","billing"],"mappings":"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"}