@aglyn/plugins-ai 1.0.0-beta.270 → 1.0.0-beta.272

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 (281) hide show
  1. package/package.json +17 -17
  2. package/src/lib/activity/ai-activity-actions.d.ts +4 -3
  3. package/src/lib/activity/ai-activity-actions.js +4 -0
  4. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  5. package/src/lib/assets/site-styles/beauty.webp +0 -0
  6. package/src/lib/assets/site-styles/blog.webp +0 -0
  7. package/src/lib/assets/site-styles/business.webp +0 -0
  8. package/src/lib/assets/site-styles/coming-soon.webp +0 -0
  9. package/src/lib/assets/site-styles/education.webp +0 -0
  10. package/src/lib/assets/site-styles/events.webp +0 -0
  11. package/src/lib/assets/site-styles/fitness.webp +0 -0
  12. package/src/lib/assets/site-styles/landing.webp +0 -0
  13. package/src/lib/assets/site-styles/music.webp +0 -0
  14. package/src/lib/assets/site-styles/nonprofit.webp +0 -0
  15. package/src/lib/assets/site-styles/personal.webp +0 -0
  16. package/src/lib/assets/site-styles/photography.webp +0 -0
  17. package/src/lib/assets/site-styles/portfolio.webp +0 -0
  18. package/src/lib/assets/site-styles/professional.webp +0 -0
  19. package/src/lib/assets/site-styles/realestate.webp +0 -0
  20. package/src/lib/assets/site-styles/restaurant.webp +0 -0
  21. package/src/lib/assets/site-styles/store.webp +0 -0
  22. package/src/lib/assets/site-styles/studio.webp +0 -0
  23. package/src/lib/assets/site-styles/trades.webp +0 -0
  24. package/src/lib/assets/site-styles/wellness.webp +0 -0
  25. package/src/lib/assets/site-styles/yoga.webp +0 -0
  26. package/src/lib/components/ai-credits-card.component.d.ts +4 -3
  27. package/src/lib/components/ai-credits-card.component.js +15 -15
  28. package/src/lib/components/ai-credits-card.component.js.map +1 -1
  29. package/src/lib/components/ai-job-input-form.component.d.ts +36 -0
  30. package/src/lib/components/ai-job-input-form.component.js +100 -0
  31. package/src/lib/components/ai-job-input-form.component.js.map +1 -0
  32. package/src/lib/components/ai-job-requests.d.ts +4 -0
  33. package/src/lib/components/ai-job-requests.js +4 -0
  34. package/src/lib/components/ai-job-requests.js.map +1 -1
  35. package/src/lib/components/ai-site-build-page.component.js +18 -0
  36. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  37. package/src/lib/components/ai-site-import-job.component.d.ts +41 -0
  38. package/src/lib/components/ai-site-import-job.component.js +707 -0
  39. package/src/lib/components/ai-site-import-job.component.js.map +1 -0
  40. package/src/lib/components/ai-site-import-start.component.d.ts +64 -0
  41. package/src/lib/components/ai-site-import-start.component.js +335 -0
  42. package/src/lib/components/ai-site-import-start.component.js.map +1 -0
  43. package/src/lib/components/ai-site-ownership-proof.component.d.ts +42 -0
  44. package/src/lib/components/ai-site-ownership-proof.component.js +353 -0
  45. package/src/lib/components/ai-site-ownership-proof.component.js.map +1 -0
  46. package/src/lib/components/ai-site-start-card.component.d.ts +1 -1
  47. package/src/lib/components/ai-site-start-card.component.js +261 -119
  48. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  49. package/src/lib/components/ai-site-start-steps.component.d.ts +43 -2
  50. package/src/lib/components/ai-site-start-steps.component.js +651 -15
  51. package/src/lib/components/ai-site-start-steps.component.js.map +1 -1
  52. package/src/lib/components/ai-site-start-tracking.d.ts +10 -0
  53. package/src/lib/components/ai-site-start-tracking.js +14 -0
  54. package/src/lib/components/ai-site-start-tracking.js.map +1 -1
  55. package/src/lib/components/ai-site-style-previews.js +26 -22
  56. package/src/lib/components/ai-site-style-previews.js.map +1 -1
  57. package/src/lib/components/ai-upsell-dialog.component.d.ts +7 -5
  58. package/src/lib/components/ai-upsell-dialog.component.js +8 -6
  59. package/src/lib/components/ai-upsell-dialog.component.js.map +1 -1
  60. package/src/lib/components/assist-build-card.component.js +11 -1
  61. package/src/lib/components/assist-build-card.component.js.map +1 -1
  62. package/src/lib/components/assist-jobs-drawer.component.js +9 -1
  63. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  64. package/src/lib/components/assist-panel.component.js +8 -4
  65. package/src/lib/components/assist-panel.component.js.map +1 -1
  66. package/src/lib/components/staff-org-ai-card.component.js +4 -7
  67. package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
  68. package/src/lib/declarations.d.ts +2 -2
  69. package/src/lib/declarations.js +9 -5
  70. package/src/lib/declarations.js.map +1 -1
  71. package/src/lib/jobs/ai-build-capabilities.js +3 -1
  72. package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
  73. package/src/lib/jobs/ai-business-profile-prefill.d.ts +1 -1
  74. package/src/lib/jobs/ai-business-profile-prefill.js +14 -3
  75. package/src/lib/jobs/ai-business-profile-prefill.js.map +1 -1
  76. package/src/lib/jobs/ai-job-build-step.js +5 -3
  77. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  78. package/src/lib/jobs/ai-job-drafts.d.ts +14 -0
  79. package/src/lib/jobs/ai-job-drafts.js +24 -0
  80. package/src/lib/jobs/ai-job-drafts.js.map +1 -1
  81. package/src/lib/jobs/ai-job-form-step.d.ts +15 -1
  82. package/src/lib/jobs/ai-job-form-step.js +75 -8
  83. package/src/lib/jobs/ai-job-form-step.js.map +1 -1
  84. package/src/lib/jobs/ai-job-import-step.d.ts +136 -0
  85. package/src/lib/jobs/ai-job-import-step.js +809 -0
  86. package/src/lib/jobs/ai-job-import-step.js.map +1 -0
  87. package/src/lib/jobs/ai-job-page-language.d.ts +52 -1
  88. package/src/lib/jobs/ai-job-page-language.js +99 -5
  89. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  90. package/src/lib/jobs/ai-job-page-step.d.ts +19 -2
  91. package/src/lib/jobs/ai-job-page-step.js +42 -18
  92. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  93. package/src/lib/jobs/ai-job-plan-step.d.ts +25 -2
  94. package/src/lib/jobs/ai-job-plan-step.js +103 -29
  95. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  96. package/src/lib/jobs/ai-job-settings-step.js +4 -3
  97. package/src/lib/jobs/ai-job-settings-step.js.map +1 -1
  98. package/src/lib/jobs/ai-job-site-datasets.d.ts +27 -0
  99. package/src/lib/jobs/ai-job-site-datasets.js +140 -14
  100. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
  101. package/src/lib/jobs/ai-job-site-look.js +18 -5
  102. package/src/lib/jobs/ai-job-site-look.js.map +1 -1
  103. package/src/lib/jobs/ai-job-site-step.d.ts +16 -1
  104. package/src/lib/jobs/ai-job-site-step.js +95 -22
  105. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  106. package/src/lib/jobs/ai-job-text-step.d.ts +15 -1
  107. package/src/lib/jobs/ai-job-text-step.js.map +1 -1
  108. package/src/lib/jobs/ai-job-tracking-step.d.ts +14 -3
  109. package/src/lib/jobs/ai-job-tracking-step.js +28 -5
  110. package/src/lib/jobs/ai-job-tracking-step.js.map +1 -1
  111. package/src/lib/jobs/ai-jobs.d.ts +13 -1
  112. package/src/lib/jobs/ai-jobs.js +77 -10
  113. package/src/lib/jobs/ai-jobs.js.map +1 -1
  114. package/src/lib/jobs/ai-layout-owner-photos.d.ts +16 -1
  115. package/src/lib/jobs/ai-layout-owner-photos.js +47 -0
  116. package/src/lib/jobs/ai-layout-owner-photos.js.map +1 -1
  117. package/src/lib/jobs/ai-layout-stock-photos.d.ts +27 -2
  118. package/src/lib/jobs/ai-layout-stock-photos.js +190 -19
  119. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  120. package/src/lib/jobs/ai-site-inspiration-signals.d.ts +98 -0
  121. package/src/lib/jobs/ai-site-inspiration-signals.js +585 -0
  122. package/src/lib/jobs/ai-site-inspiration-signals.js.map +1 -0
  123. package/src/lib/jobs/ai-site-inspiration.d.ts +157 -0
  124. package/src/lib/jobs/ai-site-inspiration.js +346 -0
  125. package/src/lib/jobs/ai-site-inspiration.js.map +1 -0
  126. package/src/lib/jobs/ai-site-publish.d.ts +26 -1
  127. package/src/lib/jobs/ai-site-publish.js +162 -13
  128. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  129. package/src/lib/jobs/ai-theme-brand-inputs.d.ts +77 -3
  130. package/src/lib/jobs/ai-theme-brand-inputs.js +135 -31
  131. package/src/lib/jobs/ai-theme-brand-inputs.js.map +1 -1
  132. package/src/lib/jobs/fixtures/ai-candle-shop-free-plan-recording.d.ts +97 -0
  133. package/src/lib/jobs/fixtures/ai-candle-shop-free-plan-recording.js +137 -0
  134. package/src/lib/jobs/fixtures/ai-candle-shop-free-plan-recording.js.map +1 -0
  135. package/src/lib/jobs/fixtures/ai-site-inspiration-pages.d.ts +38 -0
  136. package/src/lib/jobs/fixtures/ai-site-inspiration-pages.js +103 -0
  137. package/src/lib/jobs/fixtures/ai-site-inspiration-pages.js.map +1 -0
  138. package/src/lib/jobs/fixtures/ai-stock-photo-agl3988-recordings.d.ts +25 -0
  139. package/src/lib/jobs/fixtures/ai-stock-photo-agl3988-recordings.js +174 -0
  140. package/src/lib/jobs/fixtures/ai-stock-photo-agl3988-recordings.js.map +1 -0
  141. package/src/lib/layout-language/ai-layout-compiler.d.ts +1 -1
  142. package/src/lib/layout-language/ai-layout-compiler.js +438 -25
  143. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  144. package/src/lib/layout-language/ai-layout-contact.d.ts +57 -0
  145. package/src/lib/layout-language/ai-layout-contact.js +137 -0
  146. package/src/lib/layout-language/ai-layout-contact.js.map +1 -0
  147. package/src/lib/layout-language/ai-layout-copy.d.ts +12 -0
  148. package/src/lib/layout-language/ai-layout-copy.js +34 -2
  149. package/src/lib/layout-language/ai-layout-copy.js.map +1 -1
  150. package/src/lib/layout-language/ai-layout-design.d.ts +65 -8
  151. package/src/lib/layout-language/ai-layout-design.js +411 -97
  152. package/src/lib/layout-language/ai-layout-design.js.map +1 -1
  153. package/src/lib/layout-language/ai-layout-frame.js +23 -0
  154. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  155. package/src/lib/layout-language/ai-layout-labels.d.ts +2 -0
  156. package/src/lib/layout-language/ai-layout-labels.js +12 -6
  157. package/src/lib/layout-language/ai-layout-labels.js.map +1 -1
  158. package/src/lib/layout-language/ai-layout-language.d.ts +3 -2
  159. package/src/lib/layout-language/ai-layout-language.js +4 -3
  160. package/src/lib/layout-language/ai-layout-language.js.map +1 -1
  161. package/src/lib/layout-language/ai-layout-links.d.ts +13 -0
  162. package/src/lib/layout-language/ai-layout-links.js +30 -0
  163. package/src/lib/layout-language/ai-layout-links.js.map +1 -1
  164. package/src/lib/layout-language/ai-layout-pictures.d.ts +49 -6
  165. package/src/lib/layout-language/ai-layout-pictures.js +145 -22
  166. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  167. package/src/lib/model/ai-build-job.d.ts +16 -1
  168. package/src/lib/model/ai-build-job.js +40 -0
  169. package/src/lib/model/ai-build-job.js.map +1 -1
  170. package/src/lib/model/ai-credit-estimate.d.ts +20 -0
  171. package/src/lib/model/ai-credit-estimate.js +13 -0
  172. package/src/lib/model/ai-credit-estimate.js.map +1 -1
  173. package/src/lib/model/ai-job-activity.d.ts +2 -2
  174. package/src/lib/model/ai-job-activity.js +6 -4
  175. package/src/lib/model/ai-job-activity.js.map +1 -1
  176. package/src/lib/model/ai-job-input.d.ts +33 -0
  177. package/src/lib/model/ai-job-input.js +59 -0
  178. package/src/lib/model/ai-job-input.js.map +1 -0
  179. package/src/lib/model/ai-job-notice.d.ts +4 -0
  180. package/src/lib/model/ai-job-notice.js +28 -4
  181. package/src/lib/model/ai-job-notice.js.map +1 -1
  182. package/src/lib/model/ai-jobs.types.d.ts +69 -3
  183. package/src/lib/model/ai-jobs.types.js +2 -1
  184. package/src/lib/model/ai-jobs.types.js.map +1 -1
  185. package/src/lib/model/ai-site-details.d.ts +122 -0
  186. package/src/lib/model/ai-site-details.js +300 -0
  187. package/src/lib/model/ai-site-details.js.map +1 -0
  188. package/src/lib/model/ai-site-entity.d.ts +181 -0
  189. package/src/lib/model/ai-site-entity.js +390 -0
  190. package/src/lib/model/ai-site-entity.js.map +1 -0
  191. package/src/lib/model/ai-site-import-view.d.ts +130 -0
  192. package/src/lib/model/ai-site-import-view.js +182 -0
  193. package/src/lib/model/ai-site-import-view.js.map +1 -0
  194. package/src/lib/model/ai-site-import.d.ts +302 -0
  195. package/src/lib/model/ai-site-import.js +526 -0
  196. package/src/lib/model/ai-site-import.js.map +1 -0
  197. package/src/lib/model/ai-site-inspiration.d.ts +72 -0
  198. package/src/lib/model/ai-site-inspiration.js +109 -0
  199. package/src/lib/model/ai-site-inspiration.js.map +1 -0
  200. package/src/lib/model/ai-site-job.d.ts +13 -1
  201. package/src/lib/model/ai-site-job.js +15 -2
  202. package/src/lib/model/ai-site-job.js.map +1 -1
  203. package/src/lib/model/ai-site-kinds.js +109 -21
  204. package/src/lib/model/ai-site-kinds.js.map +1 -1
  205. package/src/lib/model/ai-site-look.d.ts +10 -0
  206. package/src/lib/model/ai-site-look.js +24 -0
  207. package/src/lib/model/ai-site-look.js.map +1 -1
  208. package/src/lib/model/ai-site-start-seo.d.ts +19 -5
  209. package/src/lib/model/ai-site-start-seo.js +25 -7
  210. package/src/lib/model/ai-site-start-seo.js.map +1 -1
  211. package/src/lib/model/ai-site-start-steps.d.ts +9 -3
  212. package/src/lib/model/ai-site-start-steps.js +67 -5
  213. package/src/lib/model/ai-site-start-steps.js.map +1 -1
  214. package/src/lib/model/ai-site-start.d.ts +81 -4
  215. package/src/lib/model/ai-site-start.js +207 -13
  216. package/src/lib/model/ai-site-start.js.map +1 -1
  217. package/src/lib/model/ai-upsell-copy.js +4 -1
  218. package/src/lib/model/ai-upsell-copy.js.map +1 -1
  219. package/src/lib/plan-entitlements.d.ts +4 -3
  220. package/src/lib/plan-entitlements.js +3 -8
  221. package/src/lib/plan-entitlements.js.map +1 -1
  222. package/src/lib/runtime/ai-dataset-generation.d.ts +8 -1
  223. package/src/lib/runtime/ai-dataset-generation.js +12 -2
  224. package/src/lib/runtime/ai-dataset-generation.js.map +1 -1
  225. package/src/lib/runtime/ai-doctrine-validators.d.ts +26 -33
  226. package/src/lib/runtime/ai-doctrine-validators.js +54 -44
  227. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  228. package/src/lib/runtime/ai-doctrine.js +1 -1
  229. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  230. package/src/lib/runtime/ai-node-tree.d.ts +7 -0
  231. package/src/lib/runtime/ai-node-tree.js +9 -1
  232. package/src/lib/runtime/ai-node-tree.js.map +1 -1
  233. package/src/lib/server/ai-create-route.d.ts +1 -1
  234. package/src/lib/server/ai-create-route.js +3 -4
  235. package/src/lib/server/ai-create-route.js.map +1 -1
  236. package/src/lib/server/ai-jobs-resume.js +27 -3
  237. package/src/lib/server/ai-jobs-resume.js.map +1 -1
  238. package/src/lib/server/ai-jobs-route.js +18 -0
  239. package/src/lib/server/ai-jobs-route.js.map +1 -1
  240. package/src/lib/server/ai-site-import-route.d.ts +89 -0
  241. package/src/lib/server/ai-site-import-route.js +356 -0
  242. package/src/lib/server/ai-site-import-route.js.map +1 -0
  243. package/src/lib/server.js +14 -0
  244. package/src/lib/server.js.map +1 -1
  245. package/src/lib/site-import/fixtures/site-import-crawl-recordings.d.ts +45 -0
  246. package/src/lib/site-import/fixtures/site-import-crawl-recordings.js +313 -0
  247. package/src/lib/site-import/fixtures/site-import-crawl-recordings.js.map +1 -0
  248. package/src/lib/site-import/fixtures/site-import-wave-recordings.d.ts +50 -0
  249. package/src/lib/site-import/fixtures/site-import-wave-recordings.js +217 -0
  250. package/src/lib/site-import/fixtures/site-import-wave-recordings.js.map +1 -0
  251. package/src/lib/site-import/site-import-crawl.d.ts +163 -0
  252. package/src/lib/site-import/site-import-crawl.js +467 -0
  253. package/src/lib/site-import/site-import-crawl.js.map +1 -0
  254. package/src/lib/site-import/site-import-flag.d.ts +23 -0
  255. package/src/lib/site-import/site-import-flag.js +23 -0
  256. package/src/lib/site-import/site-import-flag.js.map +1 -0
  257. package/src/lib/site-import/site-import-html.d.ts +94 -0
  258. package/src/lib/site-import/site-import-html.js +462 -0
  259. package/src/lib/site-import/site-import-html.js.map +1 -0
  260. package/src/lib/site-import/site-import-images.d.ts +54 -0
  261. package/src/lib/site-import/site-import-images.js +204 -0
  262. package/src/lib/site-import/site-import-images.js.map +1 -0
  263. package/src/lib/site-import/site-import-page.d.ts +48 -0
  264. package/src/lib/site-import/site-import-page.js +150 -0
  265. package/src/lib/site-import/site-import-page.js.map +1 -0
  266. package/src/lib/site-import/site-import-review.d.ts +40 -0
  267. package/src/lib/site-import/site-import-review.js +149 -0
  268. package/src/lib/site-import/site-import-review.js.map +1 -0
  269. package/src/lib/site-import/site-import-robots.d.ts +52 -0
  270. package/src/lib/site-import/site-import-robots.js +126 -0
  271. package/src/lib/site-import/site-import-robots.js.map +1 -0
  272. package/src/lib/usage/assist-credits.d.ts +4 -4
  273. package/src/lib/usage/assist-credits.js +4 -4
  274. package/src/lib/usage/assist-credits.js.map +1 -1
  275. package/src/lib/usage/staff-org-ai-view.js +6 -3
  276. package/src/lib/usage/staff-org-ai-view.js.map +1 -1
  277. package/src/lib/usage/staff-org-ai.d.ts +4 -2
  278. package/src/lib/usage/staff-org-ai.js +7 -7
  279. package/src/lib/usage/staff-org-ai.js.map +1 -1
  280. package/src/lib/usage/staff-org-usage-ai.js +2 -2
  281. package/src/lib/usage/staff-org-usage-ai.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-site-start-steps.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport {\n mdiBookOpenPageVariant,\n mdiBriefcaseOutline,\n mdiCalendarHeart,\n mdiCreation,\n mdiHandHeart,\n mdiScaleBalance,\n mdiShopping,\n mdiSilverwareForkKnife,\n mdiViewGridOutline,\n mdiWrench,\n} from '@aglyn/shared-data-mdi'\nimport { HelpTip } from '@aglyn/shared-ui-jsx'\nimport { OptionCardGrid } from '@aglyn/shared-ui-jsx/components/option-card-grid.component'\nimport {\n Box,\n Divider,\n LinearProgress,\n List,\n ListItemButton,\n MenuItem,\n Stack,\n Step,\n StepButton,\n StepLabel,\n Stepper,\n TextField,\n Typography,\n} from '@mui/material'\nimport { Fragment, useId, type ReactNode, type Ref } from 'react'\nimport { AI_SITE_FREE_PAGES_NOTE, AI_SITE_SUBMISSION_CHOICES, type AiSiteSubmissions } from '../model/ai-site-job'\nimport { AI_SITE_KINDS } from '../model/ai-site-kinds'\nimport { AI_SITE_START_TYPES, aiSiteStartKind, type AiSiteStartAnswers } from '../model/ai-site-start'\nimport {\n AI_SITE_START_STEP_LABELS,\n AI_SITE_START_STEPS,\n aiSiteStartStepIndex,\n type AiSiteStartField,\n type AiSiteStartFieldError,\n type AiSiteStartStep,\n} from '../model/ai-site-start-steps'\nimport type { AiSiteStartTracker } from './ai-site-start-tracking'\nimport { aiSiteStylePreview } from './ai-site-style-previews'\n\n/**\n * The guided start's question steps (AGL-3898), one decision each. Each step\n * draws its own answers and writes them through `answer`, exactly the patch\n * the single form wrote, so the answers object is the same whichever way it\n * was filled in. The dialog around them (`ai-site-start-card.component.tsx`)\n * owns the step, Back and Next, validation and the job.\n */\n\n/** Each part of the guided start links its own section of the Start with AI guide (AGL-3660). */\nexport const START_STEP_HELP = {\n kind: pluginDocsHelp('aiStart', {\n anchor: '#your-business',\n excerpt:\n 'What kind of site it is picks its style and pages; who it is for is who every page and the contact form are written for.',\n }),\n audience: pluginDocsHelp('aiStart', {\n anchor: '#audience',\n excerpt: 'Who the site is for is who every page, every search listing and the contact form are written for. Optional.',\n }),\n review: pluginDocsHelp('aiStart', {\n anchor: '#review',\n excerpt: 'Every answer, each with Edit to change it. Plan my site creates the site, plans it, writes the pages and publishes it.',\n }),\n style: pluginDocsHelp('aiSiteLooks', {\n anchor: '#choose-a-style',\n excerpt:\n 'The style decides the look — theme, colors, fonts, buttons — and which pages the site usually has. Every site gets its own variation.',\n }),\n details: pluginDocsHelp('aiStart', {\n anchor: '#details',\n excerpt:\n 'Where contact form messages go, how many pages to write, and on paid plans whether to draft a welcome email. All can be changed later.',\n }),\n}\n\n/** The icon on each kind-of-site card: the icon of the style that answer picks. */\nconst START_TYPE_ICONS: Readonly<Record<string, string>> = {\n 'a local service business': mdiBriefcaseOutline.path,\n 'a restaurant or cafe': mdiSilverwareForkKnife.path,\n 'a professional practice': mdiScaleBalance.path,\n 'a trades business': mdiWrench.path,\n 'a shop': mdiShopping.path,\n 'a nonprofit': mdiHandHeart.path,\n 'a personal portfolio': mdiViewGridOutline.path,\n 'an event': mdiCalendarHeart.path,\n}\n\n/** \"a restaurant or cafe\" as a card title: \"Restaurant or cafe\". The answer itself keeps its words. */\nexport function startTypeTitle(type: string): string {\n const words = type.replace(/^(a|an)\\s+/i, '')\n return words.charAt(0).toUpperCase() + words.slice(1)\n}\n\n/** The id the style grid gives \"Pick for me\", which is no kind: the answers' `kind: null`. */\nexport const AI_SITE_STYLE_AUTO = 'auto'\n\n/**\n * The progress through the steps: labeled steps on a wide screen, a bar and\n * \"Step 3 of 6\" on a phone. On a wide screen a step already answered is a\n * button back to it (`onStep`), because a checked step reads as a link: the\n * guided start's dead clicks were people clicking it to go back.\n */\nexport function StartProgress({\n step,\n compact,\n onStep,\n}: {\n step: AiSiteStartStep\n compact: boolean\n onStep?: (step: AiSiteStartStep) => void\n}) {\n const index = aiSiteStartStepIndex(step)\n const count = AI_SITE_START_STEPS.length\n const name = `Step ${index + 1} of ${count}`\n if (compact) {\n return (\n <Stack spacing={1} data-start-area=\"progress\" role=\"group\" aria-label={name}>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${name}: ${AI_SITE_START_STEP_LABELS[step]}`}\n </Typography>\n <LinearProgress variant=\"determinate\" value={((index + 1) / count) * 100} aria-hidden />\n </Stack>\n )\n }\n return (\n <Stepper activeStep={index} alternativeLabel aria-label={name} data-start-area=\"progress\">\n {AI_SITE_START_STEPS.map((id, at) => (\n <Step key={id}>\n {onStep && at < index ? (\n <StepButton onClick={() => onStep(id)} aria-label={`Back to ${AI_SITE_START_STEP_LABELS[id].toLowerCase()}`}>\n {AI_SITE_START_STEP_LABELS[id]}\n </StepButton>\n ) : (\n <StepLabel>{AI_SITE_START_STEP_LABELS[id]}</StepLabel>\n )}\n </Step>\n ))}\n </Stepper>\n )\n}\n\ninterface StepProps {\n answers: AiSiteStartAnswers\n answer: (patch: Partial<AiSiteStartAnswers>) => void\n tracker: AiSiteStartTracker\n /** Each field's reason not to go forward, once the person has tried to. */\n errors: Partial<Record<AiSiteStartField, AiSiteStartFieldError>>\n /** Where each field's input is, so a refused Next can put the cursor in it. */\n fieldRef: (field: AiSiteStartField) => Ref<HTMLInputElement>\n}\n\n/** A text answer's tracking: each change, and leaving the field, which ends one edit. */\nconst typed = (tracker: AiSiteStartTracker, field: AiSiteStartField) => ({\n onBlur: () => tracker.fieldLeft(field),\n})\n\n/** Step: what kind of site it is — the one required answer — typed, or picked from cards. */\nexport function KindStep({ answers, answer, tracker, errors, fieldRef }: StepProps) {\n const picked = AI_SITE_START_TYPES.includes(answers.siteType) ? answers.siteType : null\n return (\n <Stack spacing={3}>\n <Box data-start-area=\"field-siteType\">\n <TextField\n fullWidth\n required\n inputRef={fieldRef('siteType')}\n label=\"What kind of site are you creating?\"\n placeholder=\"a neighborhood dog groomer that takes bookings\"\n value={answers.siteType}\n onChange={(event) => {\n answer({ siteType: event.target.value })\n tracker.fieldChanged('siteType')\n }}\n {...typed(tracker, 'siteType')}\n error={Boolean(errors.siteType)}\n helperText={errors.siteType?.message ?? 'A few words is enough, or pick one below.'}\n />\n </Box>\n <Stack spacing={1.5}>\n <Typography variant=\"overline\" color=\"text.secondary\" component=\"h2\">\n {'Or pick the closest'}\n </Typography>\n <OptionCardGrid\n label=\"Kind of site\"\n data-start-area=\"kind-cards\"\n columns={{ xs: 2, sm: 2, md: 4 }}\n options={AI_SITE_START_TYPES.map((type) => ({\n id: type,\n title: startTypeTitle(type),\n icon: START_TYPE_ICONS[type] ?? mdiBookOpenPageVariant.path,\n }))}\n value={picked}\n onChange={(type) => {\n answer({ siteType: type })\n tracker.fieldPicked('siteType')\n }}\n />\n </Stack>\n </Stack>\n )\n}\n\n/** Step: who the site is for. Optional. */\nexport function AudienceStep({ answers, answer, tracker, errors, fieldRef }: StepProps) {\n return (\n <Box data-start-area=\"field-audience\">\n <TextField\n fullWidth\n inputRef={fieldRef('audience')}\n label=\"Who is it for?\"\n placeholder=\"local dog owners who want a regular groom booked online\"\n value={answers.audience}\n onChange={(event) => {\n answer({ audience: event.target.value })\n tracker.fieldChanged('audience')\n }}\n {...typed(tracker, 'audience')}\n error={Boolean(errors.audience)}\n helperText={errors.audience?.message ?? 'Optional. It narrows who the pages are written for.'}\n />\n </Box>\n )\n}\n\n/**\n * Step: the style, from cards that show it. \"Pick for me\" leaves the kind\n * unset, which is what the single form's unpicked state was: the kind the\n * first answer suggests is the one used, and the card says which.\n */\nexport function StyleStep({ answers, answer, tracker }: StepProps) {\n const suggested = aiSiteStartKind({ ...answers, kind: null })\n return (\n <OptionCardGrid\n label=\"Style of site\"\n data-start-area=\"style-cards\"\n columns={{ xs: 2, sm: 2, md: 3 }}\n options={[\n {\n id: AI_SITE_STYLE_AUTO,\n title: 'Pick for me',\n description: `From your answer: ${suggested.label}`,\n icon: mdiCreation.path,\n },\n ...AI_SITE_KINDS.map((kind) => {\n const image = aiSiteStylePreview(kind.id)\n return { id: kind.id, title: kind.label, description: kind.blurb, ...(image ? { image } : {}) }\n }),\n ]}\n value={answers.kind ?? AI_SITE_STYLE_AUTO}\n onChange={(id) => {\n answer({ kind: id === AI_SITE_STYLE_AUTO ? null : id })\n tracker.fieldPicked('kind')\n }}\n />\n )\n}\n\n/** Step: where leads go, how many pages, and on a paid plan the welcome email. */\nexport function DetailsStep({\n answers,\n answer,\n tracker,\n errors,\n fieldRef,\n freeTaste,\n band,\n}: StepProps & { freeTaste: boolean; band: { min: number; max: number } }) {\n return (\n <Stack spacing={3}>\n {/*\n The one setting a new site owner has to make, asked where they are\n already answering: a contact form nobody routed files its messages\n somewhere the person has not looked.\n */}\n <Box data-start-area=\"field-submissions\">\n <TextField\n select\n fullWidth\n inputRef={fieldRef('submissions')}\n label=\"Where do form submissions go?\"\n value={answers.submissions}\n onChange={(event) => {\n answer({ submissions: event.target.value as AiSiteSubmissions })\n tracker.fieldPicked('submissions')\n }}\n error={Boolean(errors.submissions)}\n helperText={errors.submissions?.message ?? 'You can change this on the form itself afterwards.'}\n >\n {AI_SITE_SUBMISSION_CHOICES.map((option) => (\n <MenuItem key={option.id} value={option.id}>\n {`${option.label} — ${option.blurb}`}\n </MenuItem>\n ))}\n </TextField>\n </Box>\n <Stack direction={{ xs: 'column', sm: 'row' }} spacing={2}>\n <Box data-start-area=\"field-pages\" sx={{ flex: 1 }}>\n <TextField\n select\n fullWidth\n inputRef={fieldRef('pages')}\n label=\"Pages\"\n value={answers.pages}\n onChange={(event) => {\n answer({ pages: Number(event.target.value) })\n tracker.fieldPicked('pages')\n }}\n error={Boolean(errors.pages)}\n helperText={errors.pages?.message ?? (freeTaste ? AI_SITE_FREE_PAGES_NOTE : undefined)}\n >\n {Array.from({ length: band.max - band.min + 1 }, (_, index) => band.min + index).map((count) => (\n <MenuItem key={count} value={count}>\n {count}\n </MenuItem>\n ))}\n </TextField>\n </Box>\n {!freeTaste && (\n <Box data-start-area=\"field-welcomeEmail\" sx={{ flex: 1 }}>\n <TextField\n select\n fullWidth\n label=\"Welcome email\"\n value={answers.welcomeEmail ? 'yes' : 'no'}\n onChange={(event) => {\n answer({ welcomeEmail: event.target.value === 'yes' })\n tracker.fieldPicked('welcomeEmail')\n }}\n >\n <MenuItem value=\"yes\">{'Draft one'}</MenuItem>\n <MenuItem value=\"no\">{'No'}</MenuItem>\n </TextField>\n </Box>\n )}\n </Stack>\n </Stack>\n )\n}\n\n/**\n * One answer on the review, as one button back to the step that asks it. The\n * whole row is the target, not only \"Edit\" at its end: people clicked the\n * answer itself to change it, and nothing happened.\n */\nfunction ReviewRow({ label, value, step, onEdit }: { label: string; value: ReactNode; step: AiSiteStartStep; onEdit: (step: AiSiteStartStep) => void }) {\n const valueId = useId()\n return (\n <ListItemButton\n onClick={() => onEdit(step)}\n aria-label={`Edit ${label.toLowerCase()}`}\n aria-describedby={valueId}\n sx={{ alignItems: 'flex-start', gap: 2, py: 1.5, px: 1, mx: -1, borderRadius: 1 }}\n >\n <Box sx={{ flex: 1, minWidth: 0 }}>\n <Typography variant=\"body2\" color=\"text.secondary\" component=\"span\" sx={{ display: 'block' }}>\n {label}\n </Typography>\n <Typography id={valueId} variant=\"body1\" component=\"span\" sx={{ display: 'block', overflowWrap: 'anywhere' }}>\n {value}\n </Typography>\n </Box>\n <Typography variant=\"button\" color=\"primary\" component=\"span\" aria-hidden sx={{ fontSize: '0.8125rem', pt: 0.25 }}>\n {'Edit'}\n </Typography>\n </ListItemButton>\n )\n}\n\n/** Step: every answer, each with Edit, before \"Plan my site\". */\nexport function ReviewStep({\n answers,\n freeTaste,\n onEdit,\n children,\n}: {\n answers: AiSiteStartAnswers\n freeTaste: boolean\n onEdit: (step: AiSiteStartStep) => void\n children?: ReactNode\n}) {\n const kind = aiSiteStartKind(answers)\n const submissions = AI_SITE_SUBMISSION_CHOICES.find((option) => option.id === answers.submissions)\n const rows: Array<{ label: string; value: ReactNode; step: AiSiteStartStep }> = [\n { label: 'Kind of site', value: answers.siteType.trim(), step: 'kind' },\n { label: 'Who it is for', value: answers.audience.trim() || 'Anyone (not said)', step: 'audience' },\n { label: 'Style', value: answers.kind ? kind.label : `${kind.label}, picked for you`, step: 'style' },\n { label: 'Form submissions', value: submissions?.label ?? answers.submissions, step: 'details' },\n { label: 'Pages', value: String(answers.pages), step: 'details' },\n ...(freeTaste ? [] : [{ label: 'Welcome email', value: answers.welcomeEmail ? 'Draft one' : 'No', step: 'details' as const }]),\n ]\n return (\n <Stack spacing={3}>\n <List disablePadding data-start-area=\"review\" aria-label=\"Your answers\">\n {rows.map((row, index) => (\n <Fragment key={row.label}>\n {index > 0 && <Divider component=\"li\" aria-hidden />}\n <li>\n <ReviewRow {...row} onEdit={onEdit} />\n </li>\n </Fragment>\n ))}\n </List>\n {children}\n </Stack>\n )\n}\n\n/** A step's help beside its heading. */\nexport function StepHelp({ step }: { step: AiSiteStartStep }) {\n const help = step === 'choose' ? null : START_STEP_HELP[step]\n return help ? <HelpTip sx={{ float: 'right' }} {...help} /> : null\n}\n"],"names":["pluginDocsHelp","mdiBookOpenPageVariant","mdiBriefcaseOutline","mdiCalendarHeart","mdiCreation","mdiHandHeart","mdiScaleBalance","mdiShopping","mdiSilverwareForkKnife","mdiViewGridOutline","mdiWrench","HelpTip","OptionCardGrid","Box","Divider","LinearProgress","List","ListItemButton","MenuItem","Stack","Step","StepButton","StepLabel","Stepper","TextField","Typography","Fragment","useId","AI_SITE_FREE_PAGES_NOTE","AI_SITE_SUBMISSION_CHOICES","AI_SITE_KINDS","AI_SITE_START_TYPES","aiSiteStartKind","AI_SITE_START_STEP_LABELS","AI_SITE_START_STEPS","aiSiteStartStepIndex","aiSiteStylePreview","START_STEP_HELP","kind","anchor","excerpt","audience","review","style","details","START_TYPE_ICONS","path","startTypeTitle","type","words","replace","charAt","toUpperCase","slice","AI_SITE_STYLE_AUTO","StartProgress","step","compact","onStep","index","count","length","name","spacing","data-start-area","role","aria-label","variant","color","value","aria-hidden","activeStep","alternativeLabel","map","id","at","onClick","toLowerCase","typed","tracker","field","onBlur","fieldLeft","KindStep","answers","answer","errors","fieldRef","picked","includes","siteType","fullWidth","required","inputRef","label","placeholder","onChange","event","target","fieldChanged","error","Boolean","helperText","message","component","columns","xs","sm","md","options","title","icon","fieldPicked","AudienceStep","StyleStep","suggested","description","image","blurb","DetailsStep","freeTaste","band","select","submissions","option","direction","sx","flex","pages","Number","undefined","Array","from","max","min","_","welcomeEmail","ReviewRow","onEdit","valueId","aria-describedby","alignItems","gap","py","px","mx","borderRadius","minWidth","display","overflowWrap","fontSize","pt","ReviewStep","children","find","rows","trim","String","disablePadding","row","li","StepHelp","help","float"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-site-start-steps.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport {\n mdiBookOpenPageVariant,\n mdiBriefcaseOutline,\n mdiCalendarHeart,\n mdiCheck,\n mdiChevronDown,\n mdiCreation,\n mdiHandHeart,\n mdiScaleBalance,\n mdiShopping,\n mdiSilverwareForkKnife,\n mdiViewGridOutline,\n mdiWrench,\n} from '@aglyn/shared-data-mdi'\nimport { HelpTip, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { OptionCardGrid } from '@aglyn/shared-ui-jsx/components/option-card-grid.component'\nimport { BlockedControl } from '@aglyn/shared-ui-jsx/components/blocked-control.component'\nimport { PrerequisiteNotice } from '@aglyn/shared-ui-jsx/components/prerequisite-notice.component'\nimport { OpeningHoursPicker } from '@aglyn/shared-ui-jsx/components/weekly-hours-picker.component'\nimport {\n Accordion,\n AccordionDetails,\n AccordionSummary,\n Box,\n Divider,\n LinearProgress,\n List,\n ListItemButton,\n MenuItem,\n Stack,\n Step,\n StepButton,\n StepLabel,\n Stepper,\n TextField,\n Tooltip,\n Typography,\n} from '@mui/material'\nimport { Fragment, useId, useState, type ReactNode, type Ref } from 'react'\nimport { aiSiteHoursSentence, aiSiteServiceAreas, aiSiteSocialLinks } from '../model/ai-site-details'\nimport { AI_SITE_FREE_PAGES_NOTE, AI_SITE_SUBMISSION_CHOICES, type AiSiteSubmissions } from '../model/ai-site-job'\nimport { AI_SITE_KINDS } from '../model/ai-site-kinds'\nimport {\n AI_SITE_INSPIRATION_LIMITS,\n AI_SITE_INSPIRATION_MAX,\n AI_SITE_INSPIRATION_PAID_NOTE,\n aiSiteInspirationHost,\n} from '../model/ai-site-inspiration'\nimport {\n AI_SITE_START_TYPES,\n aiSiteStartInspirationTyped,\n aiSiteStartKind,\n type AiSiteStartAnswers,\n type AiSiteStartExtraKey,\n} from '../model/ai-site-start'\nimport {\n AI_SITE_START_MORE_FIELDS,\n AI_SITE_START_STEP_LABELS,\n AI_SITE_START_STEPS,\n aiSiteStartStepIndex,\n type AiSiteStartField,\n type AiSiteStartFieldError,\n type AiSiteStartStep,\n} from '../model/ai-site-start-steps'\nimport type { AiSiteStartTracker } from './ai-site-start-tracking'\nimport { aiSiteStylePreview } from './ai-site-style-previews'\n\n/**\n * The guided start's question steps (AGL-3898), one decision each. Each step\n * draws its own answers and writes them through `answer`, exactly the patch\n * the single form wrote, so the answers object is the same whichever way it\n * was filled in. The dialog around them (`ai-site-start-card.component.tsx`)\n * owns the step, Back and Next, validation and the job.\n */\n\n/** Each part of the guided start links its own section of the Start with AI guide (AGL-3660). */\nexport const START_STEP_HELP = {\n kind: pluginDocsHelp('aiStart', {\n anchor: '#your-business',\n excerpt:\n 'What kind of site it is picks its style and pages; who it is for is who every page and the contact form are written for.',\n }),\n audience: pluginDocsHelp('aiStart', {\n anchor: '#audience',\n excerpt: 'Who the site is for is who every page, every search listing and the contact form are written for. Optional.',\n }),\n review: pluginDocsHelp('aiStart', {\n anchor: '#review',\n excerpt: 'Every answer, each with Edit to change it. Plan my site creates the site, plans it, writes the pages and publishes it.',\n }),\n style: pluginDocsHelp('aiSiteLooks', {\n anchor: '#choose-a-style',\n excerpt:\n 'The style decides the look — theme, colors, fonts, buttons — and which pages the site usually has. Every site gets its own variation.',\n }),\n details: pluginDocsHelp('aiStart', {\n anchor: '#details',\n excerpt:\n 'Your phone number and email, which your pages show, where contact form messages go, how many pages to write, and on paid plans whether to draft a welcome email. All can be changed later.',\n }),\n}\n\n/** The icon on each kind-of-site card: the icon of the style that answer picks. */\nconst START_TYPE_ICONS: Readonly<Record<string, string>> = {\n 'a local service business': mdiBriefcaseOutline.path,\n 'a restaurant or cafe': mdiSilverwareForkKnife.path,\n 'a professional practice': mdiScaleBalance.path,\n 'a trades business': mdiWrench.path,\n 'a shop': mdiShopping.path,\n 'a nonprofit': mdiHandHeart.path,\n 'a personal portfolio': mdiViewGridOutline.path,\n 'an event': mdiCalendarHeart.path,\n}\n\n/** \"a restaurant or cafe\" as a card title: \"Restaurant or cafe\". The answer itself keeps its words. */\nexport function startTypeTitle(type: string): string {\n const words = type.replace(/^(a|an)\\s+/i, '')\n return words.charAt(0).toUpperCase() + words.slice(1)\n}\n\n/** The id the style grid gives \"Pick for me\", which is no kind: the answers' `kind: null`. */\nexport const AI_SITE_STYLE_AUTO = 'auto'\n\n/**\n * The progress through the steps: labeled steps on a wide screen, a bar and\n * \"Step 3 of 6\" on a phone. On a wide screen a step already answered is a\n * button back to it (`onStep`), because a checked step reads as a link: the\n * guided start's dead clicks were people clicking it to go back.\n */\nexport function StartProgress({\n step,\n compact,\n onStep,\n}: {\n step: AiSiteStartStep\n compact: boolean\n onStep?: (step: AiSiteStartStep) => void\n}) {\n const index = aiSiteStartStepIndex(step)\n const count = AI_SITE_START_STEPS.length\n const name = `Step ${index + 1} of ${count}`\n if (compact) {\n return (\n <Stack spacing={1} data-start-area=\"progress\" role=\"group\" aria-label={name}>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${name}: ${AI_SITE_START_STEP_LABELS[step]}`}\n </Typography>\n <LinearProgress variant=\"determinate\" value={((index + 1) / count) * 100} aria-hidden />\n </Stack>\n )\n }\n return (\n <Stepper activeStep={index} alternativeLabel aria-label={name} data-start-area=\"progress\">\n {AI_SITE_START_STEPS.map((id, at) => (\n <Step key={id}>\n {onStep && at < index ? (\n <StepButton onClick={() => onStep(id)} aria-label={`Back to ${AI_SITE_START_STEP_LABELS[id].toLowerCase()}`}>\n {AI_SITE_START_STEP_LABELS[id]}\n </StepButton>\n ) : (\n <StepLabel>{AI_SITE_START_STEP_LABELS[id]}</StepLabel>\n )}\n </Step>\n ))}\n </Stepper>\n )\n}\n\ninterface StepProps {\n answers: AiSiteStartAnswers\n answer: (patch: Partial<AiSiteStartAnswers>) => void\n tracker: AiSiteStartTracker\n /** Each field's reason not to go forward, once the person has tried to. */\n errors: Partial<Record<AiSiteStartField, AiSiteStartFieldError>>\n /** Where each field's input is, so a refused Next can put the cursor in it. */\n fieldRef: (field: AiSiteStartField) => Ref<HTMLInputElement>\n}\n\n/** A text answer's tracking: each change, and leaving the field, which ends one edit. */\nconst typed = (tracker: AiSiteStartTracker, field: AiSiteStartField) => ({\n onBlur: () => tracker.fieldLeft(field),\n})\n\n/** Step: what kind of site it is — the one required answer — typed, or picked from cards. */\nexport function KindStep({ answers, answer, tracker, errors, fieldRef }: StepProps) {\n const picked = AI_SITE_START_TYPES.includes(answers.siteType) ? answers.siteType : null\n return (\n <Stack spacing={3}>\n <Box data-start-area=\"field-siteType\">\n <TextField\n fullWidth\n required\n inputRef={fieldRef('siteType')}\n label=\"What kind of site are you creating?\"\n placeholder=\"a neighborhood dog groomer that takes bookings\"\n value={answers.siteType}\n onChange={(event) => {\n answer({ siteType: event.target.value })\n tracker.fieldChanged('siteType')\n }}\n {...typed(tracker, 'siteType')}\n error={Boolean(errors.siteType)}\n helperText={errors.siteType?.message ?? 'A few words is enough, or pick one below.'}\n />\n </Box>\n <Stack spacing={1.5}>\n <Typography variant=\"overline\" color=\"text.secondary\" component=\"h2\">\n {'Or pick the closest'}\n </Typography>\n <OptionCardGrid\n label=\"Kind of site\"\n data-start-area=\"kind-cards\"\n columns={{ xs: 2, sm: 2, md: 4 }}\n options={AI_SITE_START_TYPES.map((type) => ({\n id: type,\n title: startTypeTitle(type),\n icon: START_TYPE_ICONS[type] ?? mdiBookOpenPageVariant.path,\n }))}\n value={picked}\n onChange={(type) => {\n answer({ siteType: type })\n tracker.fieldPicked('siteType')\n }}\n />\n </Stack>\n </Stack>\n )\n}\n\n/** Step: who the site is for. Optional. */\nexport function AudienceStep({ answers, answer, tracker, errors, fieldRef }: StepProps) {\n return (\n <Box data-start-area=\"field-audience\">\n <TextField\n fullWidth\n inputRef={fieldRef('audience')}\n label=\"Who is it for?\"\n placeholder=\"local dog owners who want a regular groom booked online\"\n value={answers.audience}\n onChange={(event) => {\n answer({ audience: event.target.value })\n tracker.fieldChanged('audience')\n }}\n {...typed(tracker, 'audience')}\n error={Boolean(errors.audience)}\n helperText={errors.audience?.message ?? 'Optional. It narrows who the pages are written for.'}\n />\n </Box>\n )\n}\n\n/**\n * Step: the style, from cards that show it. \"Pick for me\" leaves the kind\n * unset, which is what the single form's unpicked state was: the kind the\n * first answer suggests is the one used, and the card says which.\n */\nexport function StyleStep({\n answers,\n answer,\n tracker,\n errors,\n fieldRef,\n inspiration,\n}: StepProps & { inspiration?: AiSiteStartInspirationOffer | null }) {\n const suggested = aiSiteStartKind({ ...answers, kind: null })\n const cards = (\n <OptionCardGrid\n label=\"Style of site\"\n data-start-area=\"style-cards\"\n columns={{ xs: 2, sm: 2, md: 3 }}\n options={[\n {\n id: AI_SITE_STYLE_AUTO,\n title: 'Pick for me',\n description: `From your answer: ${suggested.label}`,\n icon: mdiCreation.path,\n },\n ...AI_SITE_KINDS.map((kind) => {\n const image = aiSiteStylePreview(kind.id)\n return { id: kind.id, title: kind.label, description: kind.blurb, ...(image ? { image } : {}) }\n }),\n ]}\n value={answers.kind ?? AI_SITE_STYLE_AUTO}\n onChange={(id) => {\n answer({ kind: id === AI_SITE_STYLE_AUTO ? null : id })\n tracker.fieldPicked('kind')\n }}\n />\n )\n if (!inspiration) return cards\n return (\n <Stack spacing={4}>\n {cards}\n <InspirationFields answers={answers} answer={answer} tracker={tracker} errors={errors} fieldRef={fieldRef} offer={inspiration} />\n </Stack>\n )\n}\n\n/**\n * What the guided start offers of Sites I like (AGL-3975), while\n * `release_ai_site_inspiration` is on: how many sites the workspace may name,\n * and where it upgrades for more.\n */\nexport interface AiSiteStartInspirationOffer {\n limit: number\n upgradeHref: string | null\n}\n\n/** Why a Free workspace's second and third site fields are locked. */\nexport const AI_SITE_INSPIRATION_LOCKED_REASON = `A Free workspace names ${AI_SITE_INSPIRATION_LIMITS.free} site you like. Paid plans name up to ${AI_SITE_INSPIRATION_MAX}.`\n\n/**\n * Sites I like (AGL-3975): up to three addresses under the style cards. A\n * Free workspace's fields past its one are shown disabled with the reason\n * and an upgrade link, never hidden, so the person sees what a paid plan adds.\n */\nfunction InspirationFields({\n answers,\n answer,\n tracker,\n errors,\n fieldRef,\n offer,\n}: StepProps & { offer: AiSiteStartInspirationOffer }) {\n const values = answers.inspiration ?? []\n const change = (index: number, value: string) => {\n const next = Array.from({ length: offer.limit }, (_, at) => values[at] ?? '')\n next[index] = value\n answer({ inspiration: next })\n tracker.fieldChanged('inspiration')\n }\n return (\n <Stack spacing={1.5} data-start-area=\"field-inspiration\">\n <Typography variant=\"overline\" color=\"text.secondary\" component=\"h2\">\n {'Sites you like (optional)'}\n </Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {\n 'We take only how they are laid out and styled: the order of sections, colors, type, buttons and motion. Never their words, photos or logos, so your site stays your own.'\n }\n </Typography>\n {Array.from({ length: AI_SITE_INSPIRATION_MAX }, (_, index) => {\n const locked = index >= offer.limit\n const field = (\n <TextField\n fullWidth\n type=\"url\"\n inputMode=\"url\"\n label={`Site ${index + 1}`}\n placeholder=\"https://example.com\"\n value={locked ? '' : (values[index] ?? '')}\n onChange={(event) => change(index, event.target.value)}\n onBlur={() => tracker.fieldLeft('inspiration')}\n {...(index === 0 ? { inputRef: fieldRef('inspiration') } : {})}\n error={index === 0 && Boolean(errors.inspiration)}\n {...(index === 0 && errors.inspiration ? { helperText: errors.inspiration.message } : {})}\n />\n )\n return locked ? (\n <BlockedControl key={index} blocked reason={AI_SITE_INSPIRATION_LOCKED_REASON} fullWidth>\n {field}\n </BlockedControl>\n ) : (\n <Box key={index}>{field}</Box>\n )\n })}\n {offer.limit < AI_SITE_INSPIRATION_MAX && (\n <PrerequisiteNotice {...(offer.upgradeHref ? { href: offer.upgradeHref } : {})} actionLabel=\"Upgrade\">\n {AI_SITE_INSPIRATION_PAID_NOTE}\n </PrerequisiteNotice>\n )}\n </Stack>\n )\n}\n\n/** Step: where leads go, how many pages, and on a paid plan the welcome email. */\nexport function DetailsStep({\n answers,\n answer,\n tracker,\n errors,\n fieldRef,\n freeTaste,\n band,\n}: StepProps & { freeTaste: boolean; band: { min: number; max: number } }) {\n return (\n <Stack spacing={3}>\n {/*\n How a customer reaches the business (AGL-3979), first: given, the\n pages show them as links — the contact page, the footer, the buttons\n that ask a visitor to call or write — and the site's search listing\n and AI-agent details carry them. Optional, and never a placeholder.\n */}\n <Stack direction={{ xs: 'column', sm: 'row' }} spacing={2}>\n <Box data-start-area=\"field-phone\" sx={{ flex: 1 }}>\n <TextField\n fullWidth\n type=\"tel\"\n inputRef={fieldRef('phone')}\n label=\"Business phone\"\n placeholder=\"(512) 555-0142\"\n autoComplete=\"tel\"\n value={answers.phone}\n onChange={(event) => {\n answer({ phone: event.target.value })\n tracker.fieldChanged('phone')\n }}\n {...typed(tracker, 'phone')}\n error={Boolean(errors.phone)}\n helperText={errors.phone?.message ?? 'Optional. Shown on your site so customers can call.'}\n slotProps={{ htmlInput: { inputMode: 'tel' } }}\n />\n </Box>\n <Box data-start-area=\"field-email\" sx={{ flex: 1 }}>\n <TextField\n fullWidth\n type=\"email\"\n inputRef={fieldRef('email')}\n label=\"Business email\"\n placeholder=\"hello@yourbusiness.com\"\n autoComplete=\"email\"\n value={answers.email}\n onChange={(event) => {\n answer({ email: event.target.value })\n tracker.fieldChanged('email')\n }}\n {...typed(tracker, 'email')}\n error={Boolean(errors.email)}\n helperText={errors.email?.message ?? 'Optional. Shown on your site so customers can write.'}\n />\n </Box>\n </Stack>\n {/*\n The one setting a new site owner has to make, asked where they are\n already answering: a contact form nobody routed files its messages\n somewhere the person has not looked.\n */}\n <Box data-start-area=\"field-submissions\">\n <TextField\n select\n fullWidth\n inputRef={fieldRef('submissions')}\n label=\"Where do form submissions go?\"\n value={answers.submissions}\n onChange={(event) => {\n answer({ submissions: event.target.value as AiSiteSubmissions })\n tracker.fieldPicked('submissions')\n }}\n error={Boolean(errors.submissions)}\n helperText={errors.submissions?.message ?? 'You can change this on the form itself afterwards.'}\n >\n {AI_SITE_SUBMISSION_CHOICES.map((option) => (\n <MenuItem key={option.id} value={option.id}>\n {`${option.label} — ${option.blurb}`}\n </MenuItem>\n ))}\n </TextField>\n </Box>\n <Stack direction={{ xs: 'column', sm: 'row' }} spacing={2}>\n <Box data-start-area=\"field-pages\" sx={{ flex: 1 }}>\n <TextField\n select\n fullWidth\n inputRef={fieldRef('pages')}\n label=\"Pages\"\n value={answers.pages}\n onChange={(event) => {\n answer({ pages: Number(event.target.value) })\n tracker.fieldPicked('pages')\n }}\n error={Boolean(errors.pages)}\n helperText={errors.pages?.message ?? (freeTaste ? AI_SITE_FREE_PAGES_NOTE : undefined)}\n >\n {Array.from({ length: band.max - band.min + 1 }, (_, index) => band.min + index).map((count) => (\n <MenuItem key={count} value={count}>\n {count}\n </MenuItem>\n ))}\n </TextField>\n </Box>\n {!freeTaste && (\n <Box data-start-area=\"field-welcomeEmail\" sx={{ flex: 1 }}>\n <TextField\n select\n fullWidth\n label=\"Welcome email\"\n value={answers.welcomeEmail ? 'yes' : 'no'}\n onChange={(event) => {\n answer({ welcomeEmail: event.target.value === 'yes' })\n tracker.fieldPicked('welcomeEmail')\n }}\n >\n <MenuItem value=\"yes\">{'Draft one'}</MenuItem>\n <MenuItem value=\"no\">{'No'}</MenuItem>\n </TextField>\n </Box>\n )}\n </Stack>\n </Stack>\n )\n}\n\n/**\n * The brand colors \"More details\" offers on the Style step (AGL-3979), by\n * name: picking one makes it the site's primary color, held to contrast by\n * the look like any brand color; none leaves the style to choose. Picked,\n * never typed: a color code is not something a site owner should need.\n */\nexport const AI_SITE_START_BRAND_COLORS: ReadonlyArray<{ label: string; hex: string }> = [\n { label: 'Red', hex: '#c62828' },\n { label: 'Orange', hex: '#e65100' },\n { label: 'Gold', hex: '#b28704' },\n { label: 'Green', hex: '#2e7d32' },\n { label: 'Teal', hex: '#00796b' },\n { label: 'Blue', hex: '#1565c0' },\n { label: 'Navy', hex: '#1a237e' },\n { label: 'Purple', hex: '#6a1b9a' },\n { label: 'Pink', hex: '#ad1457' },\n { label: 'Brown', hex: '#5d4037' },\n { label: 'Charcoal', hex: '#37474f' },\n]\n\n/** The price ranges offered, as a search listing writes them. */\nconst PRICE_RANGES: ReadonlyArray<{ value: string; label: string }> = [\n { value: '$', label: '$ — budget' },\n { value: '$$', label: '$$ — moderate' },\n { value: '$$$', label: '$$$ — premium' },\n { value: '$$$$', label: '$$$$ — luxury' },\n]\n\n/** What each step's \"More details\" says it holds, under its title. */\nconst MORE_SUMMARY: Readonly<Partial<Record<AiSiteStartStep, string>>> = {\n kind: 'Optional: a tagline and the year you started',\n audience: 'Optional: areas you serve, price range and languages',\n style: 'Optional: your brand color',\n details: 'Optional: address, opening hours and social profiles',\n}\n\n/** A typed extra's field. */\nfunction ExtraText({\n field,\n answers,\n answer,\n tracker,\n errors,\n fieldRef,\n label,\n placeholder,\n helper,\n autoComplete = 'off',\n multiline = false,\n inputMode,\n}: StepProps & {\n field: AiSiteStartExtraKey\n label: string\n placeholder?: string\n helper?: string\n autoComplete?: string\n multiline?: boolean\n inputMode?: 'numeric' | 'url'\n}) {\n const error = errors[field]\n return (\n <Box data-start-area={`field-${field}`} sx={{ flex: 1, minWidth: 0 }}>\n <TextField\n fullWidth\n inputRef={fieldRef(field)}\n label={label}\n placeholder={placeholder}\n autoComplete={autoComplete}\n multiline={multiline}\n minRows={multiline ? 2 : undefined}\n value={answers[field]}\n onChange={(event) => {\n answer({ [field]: event.target.value })\n tracker.fieldChanged(field)\n }}\n {...typed(tracker, field)}\n error={Boolean(error)}\n helperText={error?.message ?? helper}\n slotProps={inputMode ? { htmlInput: { inputMode } } : undefined}\n />\n </Box>\n )\n}\n\n/** The price range, picked from how search results show one. */\nfunction PriceRangeField({ answers, answer, tracker, errors, fieldRef }: StepProps) {\n const own = answers.priceRange && !PRICE_RANGES.some((option) => option.value === answers.priceRange)\n const options = own ? [...PRICE_RANGES, { value: answers.priceRange, label: answers.priceRange }] : PRICE_RANGES\n return (\n <Box data-start-area=\"field-priceRange\" sx={{ flex: 1, minWidth: 0 }}>\n <TextField\n select\n fullWidth\n inputRef={fieldRef('priceRange')}\n label=\"Price range\"\n value={answers.priceRange}\n onChange={(event) => {\n answer({ priceRange: event.target.value })\n tracker.fieldPicked('priceRange')\n }}\n error={Boolean(errors.priceRange)}\n helperText={errors.priceRange?.message ?? 'How your prices compare, as search results show it.'}\n >\n <MenuItem value=\"\">{'Not said'}</MenuItem>\n {options.map((option) => (\n <MenuItem key={option.value} value={option.value}>\n {option.label}\n </MenuItem>\n ))}\n </TextField>\n </Box>\n )\n}\n\n/** The brand color, picked from named swatches. */\nfunction BrandField({ answers, answer, tracker }: StepProps) {\n const labelId = useId()\n return (\n <Stack spacing={1} data-start-area=\"field-brand\">\n <Typography variant=\"body2\" color=\"text.secondary\" id={labelId}>\n {'Brand color'}\n </Typography>\n <Stack direction=\"row\" role=\"radiogroup\" aria-labelledby={labelId} sx={{ flexWrap: 'wrap', gap: 1 }}>\n {[{ label: 'Let the style choose', hex: '' }, ...AI_SITE_START_BRAND_COLORS].map((color) => {\n const picked = (answers.brand || '') === color.hex\n return (\n <Tooltip key={color.label} title={color.label}>\n <Box\n component=\"button\"\n type=\"button\"\n role=\"radio\"\n aria-checked={picked}\n aria-label={color.label}\n onClick={() => {\n answer({ brand: color.hex })\n tracker.fieldPicked('brand')\n }}\n sx={{\n width: 36,\n height: 36,\n p: 0,\n borderRadius: '50%',\n border: 2,\n borderColor: picked ? 'text.primary' : 'divider',\n bgcolor: color.hex || 'background.paper',\n color: color.hex ? 'common.white' : 'text.secondary',\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n cursor: 'pointer',\n }}\n >\n {picked ? <MdiIcon path={mdiCheck.path} fontSize=\"small\" /> : null}\n </Box>\n </Tooltip>\n )\n })}\n </Stack>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Picked, it is your site’s main color, kept readable everywhere it is used.'}\n </Typography>\n </Stack>\n )\n}\n\n/** The opening hours, picked a day at a time: the same editor as Setup → SEO. */\nfunction HoursField({ answers, answer, tracker, errors }: StepProps) {\n return (\n <Box data-start-area=\"field-hours\">\n <OpeningHoursPicker\n label=\"Opening hours\"\n value={answers.hours}\n onChange={(hours) => {\n answer({ hours })\n tracker.fieldPicked('hours')\n }}\n helperText={errors.hours?.message ?? 'Leave every day closed if you work by appointment.'}\n />\n </Box>\n )\n}\n\n/** One extra's field, as \"More details\" draws it. */\nfunction ExtraField({ field, ...props }: StepProps & { field: AiSiteStartExtraKey }) {\n switch (field) {\n case 'tagline':\n return <ExtraText {...props} field={field} label=\"Tagline\" placeholder=\"Calm, gentle grooming your dog will love\" helper=\"A short line about the business, in your words.\" />\n case 'founded':\n return <ExtraText {...props} field={field} label=\"Year founded\" placeholder=\"2015\" inputMode=\"numeric\" />\n case 'serviceArea':\n return <ExtraText {...props} field={field} label=\"Areas you serve\" placeholder={'Austin\\nRound Rock'} helper=\"Cities, towns or neighborhoods, one a line.\" multiline />\n case 'priceRange':\n return <PriceRangeField {...props} />\n case 'languages':\n return <ExtraText {...props} field={field} label=\"Languages you serve customers in\" placeholder=\"English, Spanish\" />\n case 'brand':\n return <BrandField {...props} />\n case 'street':\n return <ExtraText {...props} field={field} label=\"Street address\" placeholder=\"214 Main Street\" autoComplete=\"street-address\" />\n case 'city':\n return <ExtraText {...props} field={field} label=\"City or town\" placeholder=\"Austin\" autoComplete=\"address-level2\" />\n case 'region':\n return <ExtraText {...props} field={field} label=\"State or region\" placeholder=\"TX\" autoComplete=\"address-level1\" />\n case 'postalCode':\n return <ExtraText {...props} field={field} label=\"ZIP or postal code\" placeholder=\"78701\" autoComplete=\"postal-code\" />\n case 'country':\n return <ExtraText {...props} field={field} label=\"Country\" placeholder=\"United States\" autoComplete=\"country-name\" />\n case 'hours':\n return <HoursField {...props} />\n case 'social':\n return (\n <ExtraText\n {...props}\n field={field}\n label=\"Social profiles\"\n placeholder=\"instagram.com/yourbusiness\"\n helper=\"One link a line. Your site links them, and its search listing names them as yours.\"\n multiline\n inputMode=\"url\"\n />\n )\n default:\n return null\n }\n}\n\n/** Fields \"More details\" draws side by side. */\nconst PAIRS: ReadonlyArray<readonly [AiSiteStartExtraKey, AiSiteStartExtraKey]> = [\n ['tagline', 'founded'],\n ['priceRange', 'languages'],\n ['city', 'region'],\n ['postalCode', 'country'],\n]\n\n/**\n * A step's \"More details\" (AGL-3979): a collapsed section of optional extras\n * that fit the step, so a person who wants the site to say more can, and one\n * who does not never has to open it. Open from the start where something in\n * it is already filled in (a start reopened on its answers), and opened by a\n * Next refused on one of its fields.\n */\nexport function MoreDetails({ step, ...props }: StepProps & { step: AiSiteStartStep }) {\n const fields = AI_SITE_START_MORE_FIELDS[step]\n const { answers, errors, tracker } = props\n const [open, setOpen] = useState(() => fields.some((field) => (answers[field] ?? '').trim() !== ''))\n if (!fields.length) return null\n const expanded = open || fields.some((field) => errors[field])\n const rows: Array<readonly AiSiteStartExtraKey[]> = []\n for (const field of fields) {\n if (rows.some((row) => row.includes(field))) continue\n const pair = PAIRS.find((entry) => entry[0] === field && fields.includes(entry[1]))\n rows.push(pair ?? [field])\n }\n return (\n <Accordion\n disableGutters\n variant=\"outlined\"\n // The same outlined, rounded section as the build page's plan.\n sx={{ borderRadius: 2, '&::before': { display: 'none' } }}\n expanded={expanded}\n onChange={(_, next) => {\n setOpen(next)\n if (next) tracker.moreDetails()\n }}\n data-start-area=\"more-details\"\n >\n <AccordionSummary expandIcon={<MdiIcon path={mdiChevronDown.path} />}>\n <Stack spacing={0.25}>\n <Typography variant=\"subtitle2\" component=\"span\">\n {'More details'}\n </Typography>\n <Typography variant=\"body2\" color=\"text.secondary\" component=\"span\">\n {MORE_SUMMARY[step]}\n </Typography>\n </Stack>\n </AccordionSummary>\n <AccordionDetails>\n <Stack spacing={2.5}>\n {rows.map((row) =>\n row.length > 1 ? (\n <Stack key={row.join('-')} direction={{ xs: 'column', sm: 'row' }} spacing={2}>\n {row.map((field) => (\n <ExtraField key={field} field={field} {...props} />\n ))}\n </Stack>\n ) : (\n <ExtraField key={row[0]} field={row[0]} {...props} />\n ),\n )}\n </Stack>\n </AccordionDetails>\n </Accordion>\n )\n}\n\n/** The extras on the review, each as a person reads it, with the step that asks it. */\nexport function aiSiteStartExtraRows(answers: AiSiteStartAnswers): Array<{ label: string; value: string; step: AiSiteStartStep }> {\n const rows: Array<{ label: string; value: string; step: AiSiteStartStep }> = []\n const add = (label: string, value: string, step: AiSiteStartStep) => {\n if (value.trim()) rows.push({ label, value: value.trim(), step })\n }\n add('Phone', answers.phone, 'details')\n add('Email', answers.email, 'details')\n add('Tagline', answers.tagline, 'kind')\n add('Year founded', answers.founded, 'kind')\n add('Areas you serve', aiSiteServiceAreas(answers.serviceArea).join(', '), 'audience')\n add('Price range', answers.priceRange, 'audience')\n add('Languages', answers.languages, 'audience')\n const brand = AI_SITE_START_BRAND_COLORS.find((color) => color.hex === answers.brand)\n add('Brand color', brand?.label ?? answers.brand, 'style')\n const regional = [answers.region, answers.postalCode].map((part) => part.trim()).filter(Boolean).join(' ')\n const address = [answers.street, answers.city, regional, answers.country]\n .map((part) => part.trim())\n .filter(Boolean)\n .join(', ')\n add('Address', address, 'details')\n add('Opening hours', aiSiteHoursSentence(answers.hours), 'details')\n const links = aiSiteSocialLinks(answers.social) ?? []\n add('Social profiles', links.map((link) => link.replace(/^https:\\/\\//, '')).join(', '), 'details')\n return rows\n}\n\n/**\n * One answer on the review, as one button back to the step that asks it. The\n * whole row is the target, not only \"Edit\" at its end: people clicked the\n * answer itself to change it, and nothing happened.\n */\nfunction ReviewRow({ label, value, step, onEdit }: { label: string; value: ReactNode; step: AiSiteStartStep; onEdit: (step: AiSiteStartStep) => void }) {\n const valueId = useId()\n return (\n <ListItemButton\n onClick={() => onEdit(step)}\n aria-label={`Edit ${label.toLowerCase()}`}\n aria-describedby={valueId}\n sx={{ alignItems: 'flex-start', gap: 2, py: 1.5, px: 1, mx: -1, borderRadius: 1 }}\n >\n <Box sx={{ flex: 1, minWidth: 0 }}>\n <Typography variant=\"body2\" color=\"text.secondary\" component=\"span\" sx={{ display: 'block' }}>\n {label}\n </Typography>\n <Typography id={valueId} variant=\"body1\" component=\"span\" sx={{ display: 'block', overflowWrap: 'anywhere' }}>\n {value}\n </Typography>\n </Box>\n <Typography variant=\"button\" color=\"primary\" component=\"span\" aria-hidden sx={{ fontSize: '0.8125rem', pt: 0.25 }}>\n {'Edit'}\n </Typography>\n </ListItemButton>\n )\n}\n\n/** Step: every answer, each with Edit, before \"Plan my site\". */\nexport function ReviewStep({\n answers,\n freeTaste,\n onEdit,\n children,\n inspiration = false,\n}: {\n answers: AiSiteStartAnswers\n freeTaste: boolean\n onEdit: (step: AiSiteStartStep) => void\n children?: ReactNode\n /** Whether the start asks for the sites the person likes (AGL-3975). */\n inspiration?: boolean\n}) {\n const liked = aiSiteStartInspirationTyped(answers)\n const kind = aiSiteStartKind(answers)\n const submissions = AI_SITE_SUBMISSION_CHOICES.find((option) => option.id === answers.submissions)\n const rows: Array<{ label: string; value: ReactNode; step: AiSiteStartStep }> = [\n { label: 'Kind of site', value: answers.siteType.trim(), step: 'kind' },\n { label: 'Who it is for', value: answers.audience.trim() || 'Anyone (not said)', step: 'audience' },\n { label: 'Style', value: answers.kind ? kind.label : `${kind.label}, picked for you`, step: 'style' },\n ...(inspiration\n ? [{ label: 'Sites you like', value: liked.length ? liked.map(aiSiteInspirationHost).join(', ') : 'None', step: 'style' as const }]\n : []),\n { label: 'Form submissions', value: submissions?.label ?? answers.submissions, step: 'details' },\n { label: 'Pages', value: String(answers.pages), step: 'details' },\n ...(freeTaste ? [] : [{ label: 'Welcome email', value: answers.welcomeEmail ? 'Draft one' : 'No', step: 'details' as const }]),\n // Everything \"More details\" and the contact fields were given (AGL-3979), each with Edit.\n ...aiSiteStartExtraRows(answers),\n ]\n return (\n <Stack spacing={3}>\n <List disablePadding data-start-area=\"review\" aria-label=\"Your answers\">\n {rows.map((row, index) => (\n <Fragment key={row.label}>\n {index > 0 && <Divider component=\"li\" aria-hidden />}\n <li>\n <ReviewRow {...row} onEdit={onEdit} />\n </li>\n </Fragment>\n ))}\n </List>\n {children}\n </Stack>\n )\n}\n\n/** A step's help beside its heading. */\nexport function StepHelp({ step }: { step: AiSiteStartStep }) {\n const help = step === 'choose' ? null : START_STEP_HELP[step]\n return help ? <HelpTip sx={{ float: 'right' }} {...help} /> : null\n}\n"],"names":["pluginDocsHelp","mdiBookOpenPageVariant","mdiBriefcaseOutline","mdiCalendarHeart","mdiCheck","mdiChevronDown","mdiCreation","mdiHandHeart","mdiScaleBalance","mdiShopping","mdiSilverwareForkKnife","mdiViewGridOutline","mdiWrench","HelpTip","MdiIcon","OptionCardGrid","BlockedControl","PrerequisiteNotice","OpeningHoursPicker","Accordion","AccordionDetails","AccordionSummary","Box","Divider","LinearProgress","List","ListItemButton","MenuItem","Stack","Step","StepButton","StepLabel","Stepper","TextField","Tooltip","Typography","Fragment","useId","useState","aiSiteHoursSentence","aiSiteServiceAreas","aiSiteSocialLinks","AI_SITE_FREE_PAGES_NOTE","AI_SITE_SUBMISSION_CHOICES","AI_SITE_KINDS","AI_SITE_INSPIRATION_LIMITS","AI_SITE_INSPIRATION_MAX","AI_SITE_INSPIRATION_PAID_NOTE","aiSiteInspirationHost","AI_SITE_START_TYPES","aiSiteStartInspirationTyped","aiSiteStartKind","AI_SITE_START_MORE_FIELDS","AI_SITE_START_STEP_LABELS","AI_SITE_START_STEPS","aiSiteStartStepIndex","aiSiteStylePreview","START_STEP_HELP","kind","anchor","excerpt","audience","review","style","details","START_TYPE_ICONS","path","startTypeTitle","type","words","replace","charAt","toUpperCase","slice","AI_SITE_STYLE_AUTO","StartProgress","step","compact","onStep","index","count","length","name","spacing","data-start-area","role","aria-label","variant","color","value","aria-hidden","activeStep","alternativeLabel","map","id","at","onClick","toLowerCase","typed","tracker","field","onBlur","fieldLeft","KindStep","answers","answer","errors","fieldRef","picked","includes","siteType","fullWidth","required","inputRef","label","placeholder","onChange","event","target","fieldChanged","error","Boolean","helperText","message","component","columns","xs","sm","md","options","title","icon","fieldPicked","AudienceStep","StyleStep","inspiration","suggested","cards","description","image","blurb","InspirationFields","offer","AI_SITE_INSPIRATION_LOCKED_REASON","free","values","change","next","Array","from","limit","_","locked","inputMode","blocked","reason","upgradeHref","href","actionLabel","DetailsStep","freeTaste","band","direction","sx","flex","autoComplete","phone","slotProps","htmlInput","email","select","submissions","option","pages","Number","undefined","max","min","welcomeEmail","AI_SITE_START_BRAND_COLORS","hex","PRICE_RANGES","MORE_SUMMARY","ExtraText","helper","multiline","minWidth","minRows","PriceRangeField","own","priceRange","some","BrandField","labelId","aria-labelledby","flexWrap","gap","brand","aria-checked","width","height","p","borderRadius","border","borderColor","bgcolor","display","alignItems","justifyContent","cursor","fontSize","HoursField","hours","ExtraField","props","PAIRS","MoreDetails","fields","open","setOpen","trim","expanded","rows","row","pair","find","entry","push","disableGutters","moreDetails","expandIcon","join","aiSiteStartExtraRows","add","tagline","founded","serviceArea","languages","regional","region","postalCode","part","filter","address","street","city","country","links","social","link","ReviewRow","onEdit","valueId","aria-describedby","py","px","mx","overflowWrap","pt","ReviewStep","children","liked","String","disablePadding","li","StepHelp","help","float"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;AAEA,SAASA,cAAc,QAAQ,mCAAkC;AACjE,SACEC,sBAAsB,EACtBC,mBAAmB,EACnBC,gBAAgB,EAChBC,QAAQ,EACRC,cAAc,EACdC,WAAW,EACXC,YAAY,EACZC,eAAe,EACfC,WAAW,EACXC,sBAAsB,EACtBC,kBAAkB,EAClBC,SAAS,QACJ,yBAAwB;AAC/B,SAASC,OAAO,EAAEC,OAAO,QAAQ,uBAAsB;AACvD,SAASC,cAAc,QAAQ,6DAA4D;AAC3F,SAASC,cAAc,QAAQ,4DAA2D;AAC1F,SAASC,kBAAkB,QAAQ,gEAA+D;AAClG,SAASC,kBAAkB,QAAQ,gEAA+D;AAClG,SACEC,SAAS,EACTC,gBAAgB,EAChBC,gBAAgB,EAChBC,GAAG,EACHC,OAAO,EACPC,cAAc,EACdC,IAAI,EACJC,cAAc,EACdC,QAAQ,EACRC,KAAK,EACLC,IAAI,EACJC,UAAU,EACVC,SAAS,EACTC,OAAO,EACPC,SAAS,EACTC,OAAO,EACPC,UAAU,QACL,gBAAe;AACtB,SAASC,QAAQ,EAAEC,KAAK,EAAEC,QAAQ,QAAkC,QAAO;AAC3E,SAASC,mBAAmB,EAAEC,kBAAkB,EAAEC,iBAAiB,QAAQ,8BAA0B;AACrG,SAASC,uBAAuB,EAAEC,0BAA0B,QAAgC,0BAAsB;AAClH,SAASC,aAAa,QAAQ,4BAAwB;AACtD,SACEC,0BAA0B,EAC1BC,uBAAuB,EACvBC,6BAA6B,EAC7BC,qBAAqB,QAChB,kCAA8B;AACrC,SACEC,mBAAmB,EACnBC,2BAA2B,EAC3BC,eAAe,QAGV,4BAAwB;AAC/B,SACEC,yBAAyB,EACzBC,yBAAyB,EACzBC,mBAAmB,EACnBC,oBAAoB,QAIf,kCAA8B;AAErC,SAASC,kBAAkB,QAAQ,8BAA0B;AAE7D;;;;;;CAMC,GAED,+FAA+F,GAC/F,OAAO,MAAMC,kBAAkB;IAC7BC,MAAM1D,eAAe,WAAW;QAC9B2D,QAAQ;QACRC,SACE;IACJ;IACAC,UAAU7D,eAAe,WAAW;QAClC2D,QAAQ;QACRC,SAAS;IACX;IACAE,QAAQ9D,eAAe,WAAW;QAChC2D,QAAQ;QACRC,SAAS;IACX;IACAG,OAAO/D,eAAe,eAAe;QACnC2D,QAAQ;QACRC,SACE;IACJ;IACAI,SAAShE,eAAe,WAAW;QACjC2D,QAAQ;QACRC,SACE;IACJ;AACF,EAAC;AAED,iFAAiF,GACjF,MAAMK,mBAAqD;IACzD,4BAA4B/D,oBAAoBgE,IAAI;IACpD,wBAAwBxD,uBAAuBwD,IAAI;IACnD,2BAA2B1D,gBAAgB0D,IAAI;IAC/C,qBAAqBtD,UAAUsD,IAAI;IACnC,UAAUzD,YAAYyD,IAAI;IAC1B,eAAe3D,aAAa2D,IAAI;IAChC,wBAAwBvD,mBAAmBuD,IAAI;IAC/C,YAAY/D,iBAAiB+D,IAAI;AACnC;AAEA,qGAAqG,GACrG,OAAO,SAASC,eAAeC,IAAY;IACzC,MAAMC,QAAQD,KAAKE,OAAO,CAAC,eAAe;IAC1C,OAAOD,MAAME,MAAM,CAAC,GAAGC,WAAW,KAAKH,MAAMI,KAAK,CAAC;AACrD;AAEA,4FAA4F,GAC5F,OAAO,MAAMC,qBAAqB,OAAM;AAExC;;;;;CAKC,GACD,OAAO,SAASC,cAAc,EAC5BC,IAAI,EACJC,OAAO,EACPC,MAAM,EAKP;IACC,MAAMC,QAAQxB,qBAAqBqB;IACnC,MAAMI,QAAQ1B,oBAAoB2B,MAAM;IACxC,MAAMC,OAAO,CAAC,KAAK,EAAEH,QAAQ,EAAE,IAAI,EAAEC,OAAO;IAC5C,IAAIH,SAAS;QACX,qBACE,MAACjD;YAAMuD,SAAS;YAAGC,mBAAgB;YAAWC,MAAK;YAAQC,cAAYJ;;8BACrE,KAAC/C;oBAAWoD,SAAQ;oBAAUC,OAAM;8BACjC,GAAGN,KAAK,EAAE,EAAE7B,yBAAyB,CAACuB,KAAK,EAAE;;8BAEhD,KAACpD;oBAAe+D,SAAQ;oBAAcE,OAAO,AAAEV,CAAAA,QAAQ,CAAA,IAAKC,QAAS;oBAAKU,aAAW;;;;IAG3F;IACA,qBACE,KAAC1D;QAAQ2D,YAAYZ;QAAOa,gBAAgB;QAACN,cAAYJ;QAAME,mBAAgB;kBAC5E9B,oBAAoBuC,GAAG,CAAC,CAACC,IAAIC,mBAC5B,KAAClE;0BACEiD,UAAUiB,KAAKhB,sBACd,KAACjD;oBAAWkE,SAAS,IAAMlB,OAAOgB;oBAAKR,cAAY,CAAC,QAAQ,EAAEjC,yBAAyB,CAACyC,GAAG,CAACG,WAAW,IAAI;8BACxG5C,yBAAyB,CAACyC,GAAG;mCAGhC,KAAC/D;8BAAWsB,yBAAyB,CAACyC,GAAG;;eANlCA;;AAYnB;AAYA,uFAAuF,GACvF,MAAMI,QAAQ,CAACC,SAA6BC,QAA6B,CAAA;QACvEC,QAAQ,IAAMF,QAAQG,SAAS,CAACF;IAClC,CAAA;AAEA,2FAA2F,GAC3F,OAAO,SAASG,SAAS,EAAEC,OAAO,EAAEC,MAAM,EAAEN,OAAO,EAAEO,MAAM,EAAEC,QAAQ,EAAa;;QAkB5DD;IAjBpB,MAAME,SAAS3D,oBAAoB4D,QAAQ,CAACL,QAAQM,QAAQ,IAAIN,QAAQM,QAAQ,GAAG;IACnF,qBACE,MAAClF;QAAMuD,SAAS;;0BACd,KAAC7D;gBAAI8D,mBAAgB;0BACnB,cAAA,KAACnD;oBACC8E,SAAS;oBACTC,QAAQ;oBACRC,UAAUN,SAAS;oBACnBO,OAAM;oBACNC,aAAY;oBACZ1B,OAAOe,QAAQM,QAAQ;oBACvBM,UAAU,CAACC;wBACTZ,OAAO;4BAAEK,UAAUO,MAAMC,MAAM,CAAC7B,KAAK;wBAAC;wBACtCU,QAAQoB,YAAY,CAAC;oBACvB;mBACIrB,MAAMC,SAAS;oBACnBqB,OAAOC,QAAQf,OAAOI,QAAQ;oBAC9BY,UAAU,WAAEhB,mBAAAA,OAAOI,QAAQ,qBAAfJ,iBAAiBiB,OAAO,mBAAI;;;0BAG5C,MAAC/F;gBAAMuD,SAAS;;kCACd,KAAChD;wBAAWoD,SAAQ;wBAAWC,OAAM;wBAAiBoC,WAAU;kCAC7D;;kCAEH,KAAC7G;wBACCmG,OAAM;wBACN9B,mBAAgB;wBAChByC,SAAS;4BAAEC,IAAI;4BAAGC,IAAI;4BAAGC,IAAI;wBAAE;wBAC/BC,SAAShF,oBAAoB4C,GAAG,CAAC,CAACzB;gCAG1BH;mCAHoC;gCAC1C6B,IAAI1B;gCACJ8D,OAAO/D,eAAeC;gCACtB+D,IAAI,GAAElE,yBAAAA,gBAAgB,CAACG,KAAK,YAAtBH,yBAA0BhE,uBAAuBiE,IAAI;4BAC7D;;wBACAuB,OAAOmB;wBACPQ,UAAU,CAAChD;4BACTqC,OAAO;gCAAEK,UAAU1C;4BAAK;4BACxB+B,QAAQiC,WAAW,CAAC;wBACtB;;;;;;AAKV;AAEA,yCAAyC,GACzC,OAAO,SAASC,aAAa,EAAE7B,OAAO,EAAEC,MAAM,EAAEN,OAAO,EAAEO,MAAM,EAAEC,QAAQ,EAAa;;QAelED;IAdlB,qBACE,KAACpF;QAAI8D,mBAAgB;kBACnB,cAAA,KAACnD;YACC8E,SAAS;YACTE,UAAUN,SAAS;YACnBO,OAAM;YACNC,aAAY;YACZ1B,OAAOe,QAAQ3C,QAAQ;YACvBuD,UAAU,CAACC;gBACTZ,OAAO;oBAAE5C,UAAUwD,MAAMC,MAAM,CAAC7B,KAAK;gBAAC;gBACtCU,QAAQoB,YAAY,CAAC;YACvB;WACIrB,MAAMC,SAAS;YACnBqB,OAAOC,QAAQf,OAAO7C,QAAQ;YAC9B6D,UAAU,WAAEhB,mBAAAA,OAAO7C,QAAQ,qBAAf6C,iBAAiBiB,OAAO,mBAAI;;;AAIhD;AAEA;;;;CAIC,GACD,OAAO,SAASW,UAAU,EACxB9B,OAAO,EACPC,MAAM,EACNN,OAAO,EACPO,MAAM,EACNC,QAAQ,EACR4B,WAAW,EACsD;QAmBtD/B;IAlBX,MAAMgC,YAAYrF,gBAAgB,aAAKqD;QAAS9C,MAAM;;IACtD,MAAM+E,sBACJ,KAAC1H;QACCmG,OAAM;QACN9B,mBAAgB;QAChByC,SAAS;YAAEC,IAAI;YAAGC,IAAI;YAAGC,IAAI;QAAE;QAC/BC,SAAS;YACP;gBACEnC,IAAIpB;gBACJwD,OAAO;gBACPQ,aAAa,CAAC,kBAAkB,EAAEF,UAAUtB,KAAK,EAAE;gBACnDiB,MAAM7H,YAAY4D,IAAI;YACxB;eACGtB,cAAciD,GAAG,CAAC,CAACnC;gBACpB,MAAMiF,QAAQnF,mBAAmBE,KAAKoC,EAAE;gBACxC,OAAO;oBAAEA,IAAIpC,KAAKoC,EAAE;oBAAEoC,OAAOxE,KAAKwD,KAAK;oBAAEwB,aAAahF,KAAKkF,KAAK;mBAAMD,QAAQ;oBAAEA;gBAAM,IAAI,CAAC;YAC7F;SACD;QACDlD,KAAK,GAAEe,gBAAAA,QAAQ9C,IAAI,YAAZ8C,gBAAgB9B;QACvB0C,UAAU,CAACtB;YACTW,OAAO;gBAAE/C,MAAMoC,OAAOpB,qBAAqB,OAAOoB;YAAG;YACrDK,QAAQiC,WAAW,CAAC;QACtB;;IAGJ,IAAI,CAACG,aAAa,OAAOE;IACzB,qBACE,MAAC7G;QAAMuD,SAAS;;YACbsD;0BACD,KAACI;gBAAkBrC,SAASA;gBAASC,QAAQA;gBAAQN,SAASA;gBAASO,QAAQA;gBAAQC,UAAUA;gBAAUmC,OAAOP;;;;AAGxH;AAYA,oEAAoE,GACpE,OAAO,MAAMQ,oCAAoC,CAAC,uBAAuB,EAAElG,2BAA2BmG,IAAI,CAAC,sCAAsC,EAAElG,wBAAwB,CAAC,CAAC,CAAA;AAE7K;;;;CAIC,GACD,SAAS+F,kBAAkB,EACzBrC,OAAO,EACPC,MAAM,EACNN,OAAO,EACPO,MAAM,EACNC,QAAQ,EACRmC,KAAK,EAC8C;QACpCtC;IAAf,MAAMyC,UAASzC,uBAAAA,QAAQ+B,WAAW,YAAnB/B,uBAAuB,EAAE;IACxC,MAAM0C,SAAS,CAACnE,OAAeU;QAC7B,MAAM0D,OAAOC,MAAMC,IAAI,CAAC;YAAEpE,QAAQ6D,MAAMQ,KAAK;QAAC,GAAG,CAACC,GAAGxD;gBAAOkD;oBAAAA,aAAAA,MAAM,CAAClD,GAAG,YAAVkD,aAAc;;QAC1EE,IAAI,CAACpE,MAAM,GAAGU;QACdgB,OAAO;YAAE8B,aAAaY;QAAK;QAC3BhD,QAAQoB,YAAY,CAAC;IACvB;IACA,qBACE,MAAC3F;QAAMuD,SAAS;QAAKC,mBAAgB;;0BACnC,KAACjD;gBAAWoD,SAAQ;gBAAWC,OAAM;gBAAiBoC,WAAU;0BAC7D;;0BAEH,KAACzF;gBAAWoD,SAAQ;gBAAQC,OAAM;0BAE9B;;YAGH4D,MAAMC,IAAI,CAAC;gBAAEpE,QAAQnC;YAAwB,GAAG,CAACyG,GAAGxE;oBASzBkE;gBAR1B,MAAMO,SAASzE,SAAS+D,MAAMQ,KAAK;gBACnC,MAAMlD,sBACJ,KAACnE;oBACC8E,SAAS;oBACT3C,MAAK;oBACLqF,WAAU;oBACVvC,OAAO,CAAC,KAAK,EAAEnC,QAAQ,GAAG;oBAC1BoC,aAAY;oBACZ1B,OAAO+D,SAAS,MAAMP,gBAAAA,MAAM,CAAClE,MAAM,YAAbkE,gBAAiB;oBACvC7B,UAAU,CAACC,QAAU6B,OAAOnE,OAAOsC,MAAMC,MAAM,CAAC7B,KAAK;oBACrDY,QAAQ,IAAMF,QAAQG,SAAS,CAAC;mBAC3BvB,UAAU,IAAI;oBAAEkC,UAAUN,SAAS;gBAAe,IAAI,CAAC;oBAC5Da,OAAOzC,UAAU,KAAK0C,QAAQf,OAAO6B,WAAW;mBAC3CxD,UAAU,KAAK2B,OAAO6B,WAAW,GAAG;oBAAEb,YAAYhB,OAAO6B,WAAW,CAACZ,OAAO;gBAAC,IAAI,CAAC;gBAG3F,OAAO6B,uBACL,KAACxI;oBAA2B0I,OAAO;oBAACC,QAAQZ;oBAAmChC,SAAS;8BACrFX;mBADkBrB,uBAIrB,KAACzD;8BAAiB8E;mBAARrB;YAEd;YACC+D,MAAMQ,KAAK,GAAGxG,yCACb,KAAC7B,iCAAwB6H,MAAMc,WAAW,GAAG;gBAAEC,MAAMf,MAAMc,WAAW;YAAC,IAAI,CAAC;gBAAIE,aAAY;0BACzF/G;;;;AAKX;AAEA,gFAAgF,GAChF,OAAO,SAASgH,YAAY,EAC1BvD,OAAO,EACPC,MAAM,EACNN,OAAO,EACPO,MAAM,EACNC,QAAQ,EACRqD,SAAS,EACTC,IAAI,EACmE;;QAyBjDvD,eAmBAA,eAqBFA,qBAsBEA;IAtFtB,qBACE,MAAC9E;QAAMuD,SAAS;;0BAOd,MAACvD;gBAAMsI,WAAW;oBAAEpC,IAAI;oBAAUC,IAAI;gBAAM;gBAAG5C,SAAS;;kCACtD,KAAC7D;wBAAI8D,mBAAgB;wBAAc+E,IAAI;4BAAEC,MAAM;wBAAE;kCAC/C,cAAA,KAACnI;4BACC8E,SAAS;4BACT3C,MAAK;4BACL6C,UAAUN,SAAS;4BACnBO,OAAM;4BACNC,aAAY;4BACZkD,cAAa;4BACb5E,OAAOe,QAAQ8D,KAAK;4BACpBlD,UAAU,CAACC;gCACTZ,OAAO;oCAAE6D,OAAOjD,MAAMC,MAAM,CAAC7B,KAAK;gCAAC;gCACnCU,QAAQoB,YAAY,CAAC;4BACvB;2BACIrB,MAAMC,SAAS;4BACnBqB,OAAOC,QAAQf,OAAO4D,KAAK;4BAC3B5C,UAAU,WAAEhB,gBAAAA,OAAO4D,KAAK,qBAAZ5D,cAAciB,OAAO,mBAAI;4BACrC4C,WAAW;gCAAEC,WAAW;oCAAEf,WAAW;gCAAM;4BAAE;;;kCAGjD,KAACnI;wBAAI8D,mBAAgB;wBAAc+E,IAAI;4BAAEC,MAAM;wBAAE;kCAC/C,cAAA,KAACnI;4BACC8E,SAAS;4BACT3C,MAAK;4BACL6C,UAAUN,SAAS;4BACnBO,OAAM;4BACNC,aAAY;4BACZkD,cAAa;4BACb5E,OAAOe,QAAQiE,KAAK;4BACpBrD,UAAU,CAACC;gCACTZ,OAAO;oCAAEgE,OAAOpD,MAAMC,MAAM,CAAC7B,KAAK;gCAAC;gCACnCU,QAAQoB,YAAY,CAAC;4BACvB;2BACIrB,MAAMC,SAAS;4BACnBqB,OAAOC,QAAQf,OAAO+D,KAAK;4BAC3B/C,UAAU,YAAEhB,gBAAAA,OAAO+D,KAAK,qBAAZ/D,cAAciB,OAAO,oBAAI;;;;;0BAS3C,KAACrG;gBAAI8D,mBAAgB;0BACnB,cAAA,KAACnD;oBACCyI,MAAM;oBACN3D,SAAS;oBACTE,UAAUN,SAAS;oBACnBO,OAAM;oBACNzB,OAAOe,QAAQmE,WAAW;oBAC1BvD,UAAU,CAACC;wBACTZ,OAAO;4BAAEkE,aAAatD,MAAMC,MAAM,CAAC7B,KAAK;wBAAsB;wBAC9DU,QAAQiC,WAAW,CAAC;oBACtB;oBACAZ,OAAOC,QAAQf,OAAOiE,WAAW;oBACjCjD,UAAU,YAAEhB,sBAAAA,OAAOiE,WAAW,qBAAlBjE,oBAAoBiB,OAAO,oBAAI;8BAE1ChF,2BAA2BkD,GAAG,CAAC,CAAC+E,uBAC/B,KAACjJ;4BAAyB8D,OAAOmF,OAAO9E,EAAE;sCACvC,GAAG8E,OAAO1D,KAAK,CAAC,GAAG,EAAE0D,OAAOhC,KAAK,EAAE;2BADvBgC,OAAO9E,EAAE;;;0BAM9B,MAAClE;gBAAMsI,WAAW;oBAAEpC,IAAI;oBAAUC,IAAI;gBAAM;gBAAG5C,SAAS;;kCACtD,KAAC7D;wBAAI8D,mBAAgB;wBAAc+E,IAAI;4BAAEC,MAAM;wBAAE;kCAC/C,cAAA,KAACnI;4BACCyI,MAAM;4BACN3D,SAAS;4BACTE,UAAUN,SAAS;4BACnBO,OAAM;4BACNzB,OAAOe,QAAQqE,KAAK;4BACpBzD,UAAU,CAACC;gCACTZ,OAAO;oCAAEoE,OAAOC,OAAOzD,MAAMC,MAAM,CAAC7B,KAAK;gCAAE;gCAC3CU,QAAQiC,WAAW,CAAC;4BACtB;4BACAZ,OAAOC,QAAQf,OAAOmE,KAAK;4BAC3BnD,UAAU,YAAEhB,gBAAAA,OAAOmE,KAAK,qBAAZnE,cAAciB,OAAO,oBAAKqC,YAAYtH,0BAA0BqI;sCAE3E3B,MAAMC,IAAI,CAAC;gCAAEpE,QAAQgF,KAAKe,GAAG,GAAGf,KAAKgB,GAAG,GAAG;4BAAE,GAAG,CAAC1B,GAAGxE,QAAUkF,KAAKgB,GAAG,GAAGlG,OAAOc,GAAG,CAAC,CAACb,sBACpF,KAACrD;oCAAqB8D,OAAOT;8CAC1BA;mCADYA;;;oBAMpB,CAACgF,2BACA,KAAC1I;wBAAI8D,mBAAgB;wBAAqB+E,IAAI;4BAAEC,MAAM;wBAAE;kCACtD,cAAA,MAACnI;4BACCyI,MAAM;4BACN3D,SAAS;4BACTG,OAAM;4BACNzB,OAAOe,QAAQ0E,YAAY,GAAG,QAAQ;4BACtC9D,UAAU,CAACC;gCACTZ,OAAO;oCAAEyE,cAAc7D,MAAMC,MAAM,CAAC7B,KAAK,KAAK;gCAAM;gCACpDU,QAAQiC,WAAW,CAAC;4BACtB;;8CAEA,KAACzG;oCAAS8D,OAAM;8CAAO;;8CACvB,KAAC9D;oCAAS8D,OAAM;8CAAM;;;;;;;;;AAOpC;AAEA;;;;;CAKC,GACD,OAAO,MAAM0F,6BAA4E;IACvF;QAAEjE,OAAO;QAAOkE,KAAK;IAAU;IAC/B;QAAElE,OAAO;QAAUkE,KAAK;IAAU;IAClC;QAAElE,OAAO;QAAQkE,KAAK;IAAU;IAChC;QAAElE,OAAO;QAASkE,KAAK;IAAU;IACjC;QAAElE,OAAO;QAAQkE,KAAK;IAAU;IAChC;QAAElE,OAAO;QAAQkE,KAAK;IAAU;IAChC;QAAElE,OAAO;QAAQkE,KAAK;IAAU;IAChC;QAAElE,OAAO;QAAUkE,KAAK;IAAU;IAClC;QAAElE,OAAO;QAAQkE,KAAK;IAAU;IAChC;QAAElE,OAAO;QAASkE,KAAK;IAAU;IACjC;QAAElE,OAAO;QAAYkE,KAAK;IAAU;CACrC,CAAA;AAED,+DAA+D,GAC/D,MAAMC,eAAgE;IACpE;QAAE5F,OAAO;QAAKyB,OAAO;IAAa;IAClC;QAAEzB,OAAO;QAAMyB,OAAO;IAAgB;IACtC;QAAEzB,OAAO;QAAOyB,OAAO;IAAgB;IACvC;QAAEzB,OAAO;QAAQyB,OAAO;IAAgB;CACzC;AAED,oEAAoE,GACpE,MAAMoE,eAAmE;IACvE5H,MAAM;IACNG,UAAU;IACVE,OAAO;IACPC,SAAS;AACX;AAEA,2BAA2B,GAC3B,SAASuH,UAAU,EACjBnF,KAAK,EACLI,OAAO,EACPC,MAAM,EACNN,OAAO,EACPO,MAAM,EACNC,QAAQ,EACRO,KAAK,EACLC,WAAW,EACXqE,MAAM,EACNnB,eAAe,KAAK,EACpBoB,YAAY,KAAK,EACjBhC,SAAS,EASV;;IACC,MAAMjC,QAAQd,MAAM,CAACN,MAAM;IAC3B,qBACE,KAAC9E;QAAI8D,mBAAiB,CAAC,MAAM,EAAEgB,OAAO;QAAE+D,IAAI;YAAEC,MAAM;YAAGsB,UAAU;QAAE;kBACjE,cAAA,KAACzJ;YACC8E,SAAS;YACTE,UAAUN,SAASP;YACnBc,OAAOA;YACPC,aAAaA;YACbkD,cAAcA;YACdoB,WAAWA;YACXE,SAASF,YAAY,IAAIV;YACzBtF,OAAOe,OAAO,CAACJ,MAAM;YACrBgB,UAAU,CAACC;gBACTZ,OAAO;oBAAE,CAACL,MAAM,EAAEiB,MAAMC,MAAM,CAAC7B,KAAK;gBAAC;gBACrCU,QAAQoB,YAAY,CAACnB;YACvB;WACIF,MAAMC,SAASC;YACnBoB,OAAOC,QAAQD;YACfE,UAAU,UAAEF,yBAAAA,MAAOG,OAAO,mBAAI6D;YAC9BjB,WAAWd,YAAY;gBAAEe,WAAW;oBAAEf;gBAAU;YAAE,IAAIsB;;;AAI9D;AAEA,8DAA8D,GAC9D,SAASa,gBAAgB,EAAEpF,OAAO,EAAEC,MAAM,EAAEN,OAAO,EAAEO,MAAM,EAAEC,QAAQ,EAAa;;QAgB9DD;IAflB,MAAMmF,MAAMrF,QAAQsF,UAAU,IAAI,CAACT,aAAaU,IAAI,CAAC,CAACnB,SAAWA,OAAOnF,KAAK,KAAKe,QAAQsF,UAAU;IACpG,MAAM7D,UAAU4D,MAAM;WAAIR;QAAc;YAAE5F,OAAOe,QAAQsF,UAAU;YAAE5E,OAAOV,QAAQsF,UAAU;QAAC;KAAE,GAAGT;IACpG,qBACE,KAAC/J;QAAI8D,mBAAgB;QAAmB+E,IAAI;YAAEC,MAAM;YAAGsB,UAAU;QAAE;kBACjE,cAAA,MAACzJ;YACCyI,MAAM;YACN3D,SAAS;YACTE,UAAUN,SAAS;YACnBO,OAAM;YACNzB,OAAOe,QAAQsF,UAAU;YACzB1E,UAAU,CAACC;gBACTZ,OAAO;oBAAEqF,YAAYzE,MAAMC,MAAM,CAAC7B,KAAK;gBAAC;gBACxCU,QAAQiC,WAAW,CAAC;YACtB;YACAZ,OAAOC,QAAQf,OAAOoF,UAAU;YAChCpE,UAAU,WAAEhB,qBAAAA,OAAOoF,UAAU,qBAAjBpF,mBAAmBiB,OAAO,mBAAI;;8BAE1C,KAAChG;oBAAS8D,OAAM;8BAAI;;gBACnBwC,QAAQpC,GAAG,CAAC,CAAC+E,uBACZ,KAACjJ;wBAA4B8D,OAAOmF,OAAOnF,KAAK;kCAC7CmF,OAAO1D,KAAK;uBADA0D,OAAOnF,KAAK;;;;AAOrC;AAEA,iDAAiD,GACjD,SAASuG,WAAW,EAAExF,OAAO,EAAEC,MAAM,EAAEN,OAAO,EAAa;IACzD,MAAM8F,UAAU5J;IAChB,qBACE,MAACT;QAAMuD,SAAS;QAAGC,mBAAgB;;0BACjC,KAACjD;gBAAWoD,SAAQ;gBAAQC,OAAM;gBAAiBM,IAAImG;0BACpD;;0BAEH,KAACrK;gBAAMsI,WAAU;gBAAM7E,MAAK;gBAAa6G,mBAAiBD;gBAAS9B,IAAI;oBAAEgC,UAAU;oBAAQC,KAAK;gBAAE;0BAC/F;oBAAC;wBAAElF,OAAO;wBAAwBkE,KAAK;oBAAG;uBAAMD;iBAA2B,CAACtF,GAAG,CAAC,CAACL;oBAChF,MAAMoB,SAAS,AAACJ,CAAAA,QAAQ6F,KAAK,IAAI,EAAC,MAAO7G,MAAM4F,GAAG;oBAClD,qBACE,KAAClJ;wBAA0BgG,OAAO1C,MAAM0B,KAAK;kCAC3C,cAAA,KAAC5F;4BACCsG,WAAU;4BACVxD,MAAK;4BACLiB,MAAK;4BACLiH,gBAAc1F;4BACdtB,cAAYE,MAAM0B,KAAK;4BACvBlB,SAAS;gCACPS,OAAO;oCAAE4F,OAAO7G,MAAM4F,GAAG;gCAAC;gCAC1BjF,QAAQiC,WAAW,CAAC;4BACtB;4BACA+B,IAAI;gCACFoC,OAAO;gCACPC,QAAQ;gCACRC,GAAG;gCACHC,cAAc;gCACdC,QAAQ;gCACRC,aAAahG,SAAS,iBAAiB;gCACvCiG,SAASrH,MAAM4F,GAAG,IAAI;gCACtB5F,OAAOA,MAAM4F,GAAG,GAAG,iBAAiB;gCACpC0B,SAAS;gCACTC,YAAY;gCACZC,gBAAgB;gCAChBC,QAAQ;4BACV;sCAECrG,uBAAS,KAAC9F;gCAAQoD,MAAM9D,SAAS8D,IAAI;gCAAEgJ,UAAS;iCAAa;;uBA1BpD1H,MAAM0B,KAAK;gBA8B7B;;0BAEF,KAAC/E;gBAAWoD,SAAQ;gBAAUC,OAAM;0BACjC;;;;AAIT;AAEA,+EAA+E,GAC/E,SAAS2H,WAAW,EAAE3G,OAAO,EAAEC,MAAM,EAAEN,OAAO,EAAEO,MAAM,EAAa;;QAU/CA;IATlB,qBACE,KAACpF;QAAI8D,mBAAgB;kBACnB,cAAA,KAAClE;YACCgG,OAAM;YACNzB,OAAOe,QAAQ4G,KAAK;YACpBhG,UAAU,CAACgG;gBACT3G,OAAO;oBAAE2G;gBAAM;gBACfjH,QAAQiC,WAAW,CAAC;YACtB;YACAV,UAAU,WAAEhB,gBAAAA,OAAO0G,KAAK,qBAAZ1G,cAAciB,OAAO,mBAAI;;;AAI7C;AAEA,mDAAmD,GACnD,SAAS0F,WAAW,EAA+D;QAA/D,EAAEjH,KAAK,EAAwD,OAAnDkH;;;IAC9B,OAAQlH;QACN,KAAK;YACH,qBAAO,KAACmF,wBAAc+B;gBAAOlH,OAAOA;gBAAOc,OAAM;gBAAUC,aAAY;gBAA2CqE,QAAO;;QAC3H,KAAK;YACH,qBAAO,KAACD,wBAAc+B;gBAAOlH,OAAOA;gBAAOc,OAAM;gBAAeC,aAAY;gBAAOsC,WAAU;;QAC/F,KAAK;YACH,qBAAO,KAAC8B,wBAAc+B;gBAAOlH,OAAOA;gBAAOc,OAAM;gBAAkBC,aAAa;gBAAsBqE,QAAO;gBAA8CC,SAAS;;QACtK,KAAK;YACH,qBAAO,KAACG,8BAAoB0B;QAC9B,KAAK;YACH,qBAAO,KAAC/B,wBAAc+B;gBAAOlH,OAAOA;gBAAOc,OAAM;gBAAmCC,aAAY;;QAClG,KAAK;YACH,qBAAO,KAAC6E,yBAAesB;QACzB,KAAK;YACH,qBAAO,KAAC/B,wBAAc+B;gBAAOlH,OAAOA;gBAAOc,OAAM;gBAAiBC,aAAY;gBAAkBkD,cAAa;;QAC/G,KAAK;YACH,qBAAO,KAACkB,wBAAc+B;gBAAOlH,OAAOA;gBAAOc,OAAM;gBAAeC,aAAY;gBAASkD,cAAa;;QACpG,KAAK;YACH,qBAAO,KAACkB,wBAAc+B;gBAAOlH,OAAOA;gBAAOc,OAAM;gBAAkBC,aAAY;gBAAKkD,cAAa;;QACnG,KAAK;YACH,qBAAO,KAACkB,wBAAc+B;gBAAOlH,OAAOA;gBAAOc,OAAM;gBAAqBC,aAAY;gBAAQkD,cAAa;;QACzG,KAAK;YACH,qBAAO,KAACkB,wBAAc+B;gBAAOlH,OAAOA;gBAAOc,OAAM;gBAAUC,aAAY;gBAAgBkD,cAAa;;QACtG,KAAK;YACH,qBAAO,KAAC8C,yBAAeG;QACzB,KAAK;YACH,qBACE,KAAC/B,wBACK+B;gBACJlH,OAAOA;gBACPc,OAAM;gBACNC,aAAY;gBACZqE,QAAO;gBACPC,SAAS;gBACThC,WAAU;;QAGhB;YACE,OAAO;IACX;AACF;AAEA,8CAA8C,GAC9C,MAAM8D,QAA4E;IAChF;QAAC;QAAW;KAAU;IACtB;QAAC;QAAc;KAAY;IAC3B;QAAC;QAAQ;KAAS;IAClB;QAAC;QAAc;KAAU;CAC1B;AAED;;;;;;CAMC,GACD,OAAO,SAASC,YAAY,EAAyD;QAAzD,EAAE5I,IAAI,EAAmD,OAA9C0I;;;IACrC,MAAMG,SAASrK,yBAAyB,CAACwB,KAAK;IAC9C,MAAM,EAAE4B,OAAO,EAAEE,MAAM,EAAEP,OAAO,EAAE,GAAGmH;IACrC,MAAM,CAACI,MAAMC,QAAQ,GAAGrL,SAAS,IAAMmL,OAAO1B,IAAI,CAAC,CAAC3F;gBAAWI;mBAAD,EAACA,iBAAAA,OAAO,CAACJ,MAAM,YAAdI,iBAAkB,IAAIoH,IAAI,OAAO;;IAChG,IAAI,CAACH,OAAOxI,MAAM,EAAE,OAAO;IAC3B,MAAM4I,WAAWH,QAAQD,OAAO1B,IAAI,CAAC,CAAC3F,QAAUM,MAAM,CAACN,MAAM;IAC7D,MAAM0H,OAA8C,EAAE;IACtD,KAAK,MAAM1H,SAASqH,OAAQ;QAC1B,IAAIK,KAAK/B,IAAI,CAAC,CAACgC,MAAQA,IAAIlH,QAAQ,CAACT,SAAS;QAC7C,MAAM4H,OAAOT,MAAMU,IAAI,CAAC,CAACC,QAAUA,KAAK,CAAC,EAAE,KAAK9H,SAASqH,OAAO5G,QAAQ,CAACqH,KAAK,CAAC,EAAE;QACjFJ,KAAKK,IAAI,CAACH,eAAAA,OAAQ;YAAC5H;SAAM;IAC3B;IACA,qBACE,MAACjF;QACCiN,cAAc;QACd7I,SAAQ;QACR,+DAA+D;QAC/D4E,IAAI;YAAEuC,cAAc;YAAG,aAAa;gBAAEI,SAAS;YAAO;QAAE;QACxDe,UAAUA;QACVzG,UAAU,CAACmC,GAAGJ;YACZwE,QAAQxE;YACR,IAAIA,MAAMhD,QAAQkI,WAAW;QAC/B;QACAjJ,mBAAgB;;0BAEhB,KAAC/D;gBAAiBiN,0BAAY,KAACxN;oBAAQoD,MAAM7D,eAAe6D,IAAI;;0BAC9D,cAAA,MAACtC;oBAAMuD,SAAS;;sCACd,KAAChD;4BAAWoD,SAAQ;4BAAYqC,WAAU;sCACvC;;sCAEH,KAACzF;4BAAWoD,SAAQ;4BAAQC,OAAM;4BAAiBoC,WAAU;sCAC1D0D,YAAY,CAAC1G,KAAK;;;;;0BAIzB,KAACxD;0BACC,cAAA,KAACQ;oBAAMuD,SAAS;8BACb2I,KAAKjI,GAAG,CAAC,CAACkI,MACTA,IAAI9I,MAAM,GAAG,kBACX,KAACrD;4BAA0BsI,WAAW;gCAAEpC,IAAI;gCAAUC,IAAI;4BAAM;4BAAG5C,SAAS;sCACzE4I,IAAIlI,GAAG,CAAC,CAACO,sBACR,KAACiH;oCAAuBjH,OAAOA;mCAAWkH,QAAzBlH;2BAFT2H,IAAIQ,IAAI,CAAC,sBAMrB,KAAClB;4BAAwBjH,OAAO2H,GAAG,CAAC,EAAE;2BAAMT,QAA3BS,GAAG,CAAC,EAAE;;;;;AAOrC;AAEA,qFAAqF,GACrF,OAAO,SAASS,qBAAqBhI,OAA2B;cAqBhD/D;IApBd,MAAMqL,OAAuE,EAAE;IAC/E,MAAMW,MAAM,CAACvH,OAAezB,OAAeb;QACzC,IAAIa,MAAMmI,IAAI,IAAIE,KAAKK,IAAI,CAAC;YAAEjH;YAAOzB,OAAOA,MAAMmI,IAAI;YAAIhJ;QAAK;IACjE;IACA6J,IAAI,SAASjI,QAAQ8D,KAAK,EAAE;IAC5BmE,IAAI,SAASjI,QAAQiE,KAAK,EAAE;IAC5BgE,IAAI,WAAWjI,QAAQkI,OAAO,EAAE;IAChCD,IAAI,gBAAgBjI,QAAQmI,OAAO,EAAE;IACrCF,IAAI,mBAAmBjM,mBAAmBgE,QAAQoI,WAAW,EAAEL,IAAI,CAAC,OAAO;IAC3EE,IAAI,eAAejI,QAAQsF,UAAU,EAAE;IACvC2C,IAAI,aAAajI,QAAQqI,SAAS,EAAE;IACpC,MAAMxC,QAAQlB,2BAA2B8C,IAAI,CAAC,CAACzI,QAAUA,MAAM4F,GAAG,KAAK5E,QAAQ6F,KAAK;IACpFoC,IAAI,uBAAepC,yBAAAA,MAAOnF,KAAK,mBAAIV,QAAQ6F,KAAK,EAAE;IAClD,MAAMyC,WAAW;QAACtI,QAAQuI,MAAM;QAAEvI,QAAQwI,UAAU;KAAC,CAACnJ,GAAG,CAAC,CAACoJ,OAASA,KAAKrB,IAAI,IAAIsB,MAAM,CAACzH,SAAS8G,IAAI,CAAC;IACtG,MAAMY,UAAU;QAAC3I,QAAQ4I,MAAM;QAAE5I,QAAQ6I,IAAI;QAAEP;QAAUtI,QAAQ8I,OAAO;KAAC,CACtEzJ,GAAG,CAAC,CAACoJ,OAASA,KAAKrB,IAAI,IACvBsB,MAAM,CAACzH,SACP8G,IAAI,CAAC;IACRE,IAAI,WAAWU,SAAS;IACxBV,IAAI,iBAAiBlM,oBAAoBiE,QAAQ4G,KAAK,GAAG;IACzD,MAAMmC,SAAQ9M,qBAAAA,kBAAkB+D,QAAQgJ,MAAM,aAAhC/M,qBAAqC,EAAE;IACrDgM,IAAI,mBAAmBc,MAAM1J,GAAG,CAAC,CAAC4J,OAASA,KAAKnL,OAAO,CAAC,eAAe,KAAKiK,IAAI,CAAC,OAAO;IACxF,OAAOT;AACT;AAEA;;;;CAIC,GACD,SAAS4B,UAAU,EAAExI,KAAK,EAAEzB,KAAK,EAAEb,IAAI,EAAE+K,MAAM,EAAuG;IACpJ,MAAMC,UAAUvN;IAChB,qBACE,MAACX;QACCsE,SAAS,IAAM2J,OAAO/K;QACtBU,cAAY,CAAC,KAAK,EAAE4B,MAAMjB,WAAW,IAAI;QACzC4J,oBAAkBD;QAClBzF,IAAI;YAAE4C,YAAY;YAAcX,KAAK;YAAG0D,IAAI;YAAKC,IAAI;YAAGC,IAAI,CAAC;YAAGtD,cAAc;QAAE;;0BAEhF,MAACpL;gBAAI6I,IAAI;oBAAEC,MAAM;oBAAGsB,UAAU;gBAAE;;kCAC9B,KAACvJ;wBAAWoD,SAAQ;wBAAQC,OAAM;wBAAiBoC,WAAU;wBAAOuC,IAAI;4BAAE2C,SAAS;wBAAQ;kCACxF5F;;kCAEH,KAAC/E;wBAAW2D,IAAI8J;wBAASrK,SAAQ;wBAAQqC,WAAU;wBAAOuC,IAAI;4BAAE2C,SAAS;4BAASmD,cAAc;wBAAW;kCACxGxK;;;;0BAGL,KAACtD;gBAAWoD,SAAQ;gBAASC,OAAM;gBAAUoC,WAAU;gBAAOlC,aAAW;gBAACyE,IAAI;oBAAE+C,UAAU;oBAAagD,IAAI;gBAAK;0BAC7G;;;;AAIT;AAEA,+DAA+D,GAC/D,OAAO,SAASC,WAAW,EACzB3J,OAAO,EACPwD,SAAS,EACT2F,MAAM,EACNS,QAAQ,EACR7H,cAAc,KAAK,EAQpB;;IACC,MAAM8H,QAAQnN,4BAA4BsD;IAC1C,MAAM9C,OAAOP,gBAAgBqD;IAC7B,MAAMmE,cAAchI,2BAA2BsL,IAAI,CAAC,CAACrD,SAAWA,OAAO9E,EAAE,KAAKU,QAAQmE,WAAW;IACjG,MAAMmD,OAA0E;QAC9E;YAAE5G,OAAO;YAAgBzB,OAAOe,QAAQM,QAAQ,CAAC8G,IAAI;YAAIhJ,MAAM;QAAO;QACtE;YAAEsC,OAAO;YAAiBzB,OAAOe,QAAQ3C,QAAQ,CAAC+J,IAAI,MAAM;YAAqBhJ,MAAM;QAAW;QAClG;YAAEsC,OAAO;YAASzB,OAAOe,QAAQ9C,IAAI,GAAGA,KAAKwD,KAAK,GAAG,GAAGxD,KAAKwD,KAAK,CAAC,gBAAgB,CAAC;YAAEtC,MAAM;QAAQ;WAChG2D,cACA;YAAC;gBAAErB,OAAO;gBAAkBzB,OAAO4K,MAAMpL,MAAM,GAAGoL,MAAMxK,GAAG,CAAC7C,uBAAuBuL,IAAI,CAAC,QAAQ;gBAAQ3J,MAAM;YAAiB;SAAE,GACjI,EAAE;QACN;YAAEsC,OAAO;YAAoBzB,KAAK,UAAEkF,+BAAAA,YAAazD,KAAK,mBAAIV,QAAQmE,WAAW;YAAE/F,MAAM;QAAU;QAC/F;YAAEsC,OAAO;YAASzB,OAAO6K,OAAO9J,QAAQqE,KAAK;YAAGjG,MAAM;QAAU;WAC5DoF,YAAY,EAAE,GAAG;YAAC;gBAAE9C,OAAO;gBAAiBzB,OAAOe,QAAQ0E,YAAY,GAAG,cAAc;gBAAMtG,MAAM;YAAmB;SAAE;QAC7H,0FAA0F;WACvF4J,qBAAqBhI;KACzB;IACD,qBACE,MAAC5E;QAAMuD,SAAS;;0BACd,KAAC1D;gBAAK8O,cAAc;gBAACnL,mBAAgB;gBAASE,cAAW;0BACtDwI,KAAKjI,GAAG,CAAC,CAACkI,KAAKhJ,sBACd,MAAC3C;;4BACE2C,QAAQ,mBAAK,KAACxD;gCAAQqG,WAAU;gCAAKlC,aAAW;;0CACjD,KAAC8K;0CACC,cAAA,KAACd,wBAAc3B;oCAAK4B,QAAQA;;;;uBAHjB5B,IAAI7G,KAAK;;YAQ3BkJ;;;AAGP;AAEA,sCAAsC,GACtC,OAAO,SAASK,SAAS,EAAE7L,IAAI,EAA6B;IAC1D,MAAM8L,OAAO9L,SAAS,WAAW,OAAOnB,eAAe,CAACmB,KAAK;IAC7D,OAAO8L,qBAAO,KAAC7P;QAAQsJ,IAAI;YAAEwG,OAAO;QAAQ;OAAOD,SAAW;AAChE"}
@@ -88,6 +88,16 @@ export interface AiSiteStartTracker {
88
88
  fieldLeft(field: AiSiteStartField): void;
89
89
  /** A choice was picked: input, but not an edit (comparing cards is not a struggle). */
90
90
  fieldPicked(field: AiSiteStartField): void;
91
+ /** The step's "More details" was opened (AGL-3979). */
92
+ moreDetails(): void;
93
+ /**
94
+ * The site was asked for with these optional extras given (AGL-3979): one
95
+ * event a field, naming the step that asks it, never what was typed.
96
+ */
97
+ detailsGiven(fields: ReadonlyArray<{
98
+ step: AiSiteStartStep;
99
+ field: AiSiteStartField;
100
+ }>): void;
91
101
  /** Any input on the step, which puts off the idle signal. */
92
102
  activity(): void;
93
103
  /** A click anywhere in the dialog. */
@@ -145,6 +145,20 @@ export function createAiSiteStartTracker(options = {}) {
145
145
  // Only a typed answer rewritten is a struggle.
146
146
  armIdle();
147
147
  },
148
+ moreDetails () {
149
+ if (done || !step) return;
150
+ send('site_start_more_details', {
151
+ step
152
+ });
153
+ armIdle();
154
+ },
155
+ detailsGiven (fields) {
156
+ if (done) return;
157
+ for (const { step: at, field } of fields)send('site_start_detail_given', {
158
+ step: at,
159
+ field
160
+ });
161
+ },
148
162
  activity () {
149
163
  armIdle();
150
164
  },
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-site-start-tracking.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport { trackEvent, type AnalyticsEventName, type AnalyticsEventParams } from '@aglyn/aglyn/app-utils/analytics-events'\nimport { useEffect, useRef } from 'react'\nimport {\n AI_SITE_START_STEPS,\n aiSiteStartStepIndex,\n type AiSiteStartErrorCode,\n type AiSiteStartField,\n type AiSiteStartStep,\n} from '../model/ai-site-start-steps'\n\n/**\n * Where people get lost in the guided start (AGL-3898), as the console's own\n * product analytics: each step drawn, left forward or back and how long it\n * took, the step the start was abandoned on, the field a step refused on,\n * and the signs of a struggle — one field changed again and again, a step\n * left idle, fast repeated clicks on one spot (rage) and clicks on words, a\n * picture or an icon in the form that do nothing (dead). A click on empty\n * space or on a popup's backdrop is not a dead click (2026-10-18).\n *\n * Every event goes through `trackEvent`, the one door the console's GA4\n * taxonomy has, so it is consent-gated exactly as every other console event\n * is (`firebase-app.layout.tsx` registers the transport, or the refusal that\n * drops it) and its params are sanitized there too. What is sent is only ever\n * a step id, a field id, a closed error code, a closed area id, a count or a\n * duration: never anything typed, picked or named.\n *\n * The detector is a plain object over a clock and a timer so its rules are\n * tested with fake timers; {@link useAiSiteStartTracking} binds it to the\n * dialog.\n */\n\n/** No input on a step for this long is a struggle. */\nexport const AI_SITE_START_IDLE_MS = 30_000\n/** This many clicks on one spot… */\nexport const AI_SITE_START_RAGE_CLICKS = 3\n/** …within this long… */\nexport const AI_SITE_START_RAGE_WINDOW_MS = 700\n/** …and within this distance of the first, is rage. */\nexport const AI_SITE_START_RAGE_RADIUS_PX = 30\n/** One field changed this many times is a struggle. */\nexport const AI_SITE_START_STRUGGLE_EDITS = 3\n/** At most this many dead clicks are reported on one visit to a step. */\nexport const AI_SITE_START_DEAD_CLICKS_PER_STEP = 3\n\n/** Why the start was left without a site started. */\nexport type AiSiteStartAbandonReason = 'skip' | 'close' | 'unload' | 'route'\n\ntype Send = <K extends AnalyticsEventName>(name: K, params: AnalyticsEventParams[K]) => void\n\nexport interface AiSiteStartTrackerOptions {\n send?: Send\n now?: () => number\n /** Whether the page is out of sight, when an idle step is not a struggle. */\n hidden?: () => boolean\n}\n\n/** A click as the detector reads it: where, and what it landed on. */\nexport interface AiSiteStartClick {\n x: number\n y: number\n /** Whether it landed on something that acts on a click. */\n interactive: boolean\n /** The closed id of the part of the form it landed in. */\n area: string\n /**\n * Whether it landed on nothing a person could mistake for a control: empty\n * space, a popup's backdrop (the click that closes a menu or a help\n * popover), or anything outside the form's named parts. A blank click is\n * never a dead click.\n */\n blank?: boolean\n}\n\nexport interface AiSiteStartTracker {\n /** A step was drawn. */\n view(step: AiSiteStartStep): void\n /** The person went forward from the step on screen. */\n complete(): void\n /** The person went back from the step on screen. */\n back(): void\n /** A step refused to go forward on a field. */\n fieldError(field: AiSiteStartField, code: AiSiteStartErrorCode): void\n /** A text answer changed; counted as one edit when the field is left. */\n fieldChanged(field: AiSiteStartField): void\n /** A text answer was left. */\n fieldLeft(field: AiSiteStartField): void\n /** A choice was picked: input, but not an edit (comparing cards is not a struggle). */\n fieldPicked(field: AiSiteStartField): void\n /** Any input on the step, which puts off the idle signal. */\n activity(): void\n /** A click anywhere in the dialog. */\n click(click: AiSiteStartClick): void\n /** The start was left without a site started; reported once. */\n abandon(reason: AiSiteStartAbandonReason): void\n /** A site was started (or the starter chosen): nothing after it is an abandonment. */\n finish(): void\n /** Stops the idle timer. */\n dispose(): void\n}\n\nexport function createAiSiteStartTracker(options: AiSiteStartTrackerOptions = {}): AiSiteStartTracker {\n const send: Send = options.send ?? ((name, params) => trackEvent(name, params))\n const now = options.now ?? (() => Date.now())\n const hidden = options.hidden ?? (() => typeof document !== 'undefined' && document.visibilityState === 'hidden')\n const opened = now()\n let step: AiSiteStartStep | null = null\n let enteredAt = opened\n let done = false\n let idleTimer: ReturnType<typeof setTimeout> | null = null\n let idleReported = false\n let deadClicks = 0\n let burst: { x: number; y: number; at: number; count: number; reported: boolean } | null = null\n const edits = new Map<AiSiteStartField, number>()\n const dirty = new Set<AiSiteStartField>()\n const struggled = new Set<AiSiteStartField>()\n\n const index = () => (step ? aiSiteStartStepIndex(step) : 0)\n const spent = () => Math.max(0, Math.round(now() - enteredAt))\n const stopIdle = () => {\n if (idleTimer) clearTimeout(idleTimer)\n idleTimer = null\n }\n const armIdle = () => {\n stopIdle()\n if (done || !step || idleReported) return\n idleTimer = setTimeout(() => {\n idleTimer = null\n if (done || !step || idleReported || hidden()) return\n idleReported = true\n send('site_start_struggle', { step, signal: 'idle', duration_ms: AI_SITE_START_IDLE_MS })\n }, AI_SITE_START_IDLE_MS)\n }\n const edited = (field: AiSiteStartField) => {\n if (!step) return\n const count = (edits.get(field) ?? 0) + 1\n edits.set(field, count)\n if (count >= AI_SITE_START_STRUGGLE_EDITS && !struggled.has(field)) {\n struggled.add(field)\n send('site_start_struggle', { step, signal: 'edits', field, count })\n }\n }\n\n return {\n view(next) {\n if (done) return\n step = next\n enteredAt = now()\n idleReported = false\n deadClicks = 0\n burst = null\n send('site_start_step_viewed', { step: next, step_index: aiSiteStartStepIndex(next), step_count: AI_SITE_START_STEPS.length })\n armIdle()\n },\n complete() {\n if (done || !step) return\n send('site_start_step_completed', { step, step_index: index(), duration_ms: spent() })\n },\n back() {\n if (done || !step) return\n send('site_start_step_back', { step, step_index: index(), duration_ms: spent() })\n },\n fieldError(field, code) {\n if (done || !step) return\n send('site_start_field_error', { step, field, error: code })\n },\n fieldChanged(field) {\n dirty.add(field)\n },\n fieldLeft(field) {\n if (!dirty.delete(field)) return\n edited(field)\n },\n fieldPicked() {\n // Picking card after card is how a person compares styles or kinds; it\n // read as \"one answer changed again and again\" (2026-10-18) and is not.\n // Only a typed answer rewritten is a struggle.\n armIdle()\n },\n activity() {\n armIdle()\n },\n click(click) {\n if (done || !step) return\n const at = now()\n const near =\n burst &&\n at - burst.at <= AI_SITE_START_RAGE_WINDOW_MS &&\n Math.hypot(click.x - burst.x, click.y - burst.y) <= AI_SITE_START_RAGE_RADIUS_PX\n if (near && burst) {\n burst.count += 1\n if (burst.count >= AI_SITE_START_RAGE_CLICKS && !burst.reported) {\n burst.reported = true\n send('site_start_rage_click', { step, area: click.area, clicks: burst.count })\n }\n } else {\n burst = { x: click.x, y: click.y, at, count: 1, reported: false }\n }\n if (!click.interactive && !click.blank && deadClicks < AI_SITE_START_DEAD_CLICKS_PER_STEP) {\n deadClicks += 1\n send('site_start_dead_click', { step, area: click.area })\n }\n armIdle()\n },\n abandon(reason) {\n if (done) return\n done = true\n stopIdle()\n const at = step ?? AI_SITE_START_STEPS[0]\n send('site_start_abandoned', {\n step: at,\n step_index: aiSiteStartStepIndex(at),\n reason,\n duration_ms: spent(),\n total_ms: Math.max(0, Math.round(now() - opened)),\n })\n },\n finish() {\n done = true\n stopIdle()\n },\n dispose() {\n stopIdle()\n },\n }\n}\n\n/** What clicking acts on: a click on anything else in the form is a dead click. */\nconst INTERACTIVE = [\n 'button',\n 'a[href]',\n 'input',\n 'select',\n 'textarea',\n 'label',\n 'summary',\n '[role=\"button\"]',\n '[role=\"radio\"]',\n '[role=\"checkbox\"]',\n '[role=\"switch\"]',\n '[role=\"option\"]',\n '[role=\"tab\"]',\n '[role=\"link\"]',\n '[role=\"combobox\"]',\n '[role=\"menuitem\"]',\n '[role=\"slider\"]',\n '[contenteditable=\"true\"]',\n '.MuiInputBase-root',\n '[tabindex]:not([tabindex=\"-1\"])',\n].join(', ')\n\n/** The attribute naming the part of the form an element is in (a closed id, never content). */\nexport const AI_SITE_START_AREA_ATTRIBUTE = 'data-start-area'\n\n/** A popup's backdrop (a menu's, a select's, a help popover's): a click on it closes the popup. */\nconst BACKDROP = '.MuiBackdrop-root'\n\n/**\n * Whether an element is something a person sees and might click: its own\n * words, a picture or an icon. A bare layout box (the space around and\n * between things) is not.\n */\nfunction isContent(element: Element): boolean {\n if (element.closest('img, svg, picture, video, canvas')) return true\n for (const node of Array.from(element.childNodes)) {\n if (node.nodeType === 3 && (node.textContent ?? '').trim()) return true\n }\n return false\n}\n\n/** A DOM click, as the detector reads it. */\nexport function aiSiteStartClickOf(event: { clientX: number; clientY: number; target: EventTarget | null }): AiSiteStartClick {\n const target = event.target instanceof Element ? event.target : null\n const named = target?.closest(`[${AI_SITE_START_AREA_ATTRIBUTE}]`) ?? null\n const area = named?.getAttribute(AI_SITE_START_AREA_ATTRIBUTE) ?? 'form'\n const blank = !target || !named || Boolean(target.closest(BACKDROP)) || !isContent(target)\n return { x: event.clientX, y: event.clientY, interactive: Boolean(target?.closest(INTERACTIVE)), area, blank }\n}\n\n/**\n * The tracker for one open guided start: views each step as it is drawn, and\n * reports the start abandoned when the tab goes away or the page changes\n * under it. A start that is finished ({@link AiSiteStartTracker.finish}) or\n * already abandoned reports nothing more.\n *\n * The unmount report waits a tick, so a development double mount (React's\n * StrictMode runs an effect's cleanup and then the effect again) is not read\n * as the person leaving.\n */\nexport function useAiSiteStartTracking(step: AiSiteStartStep | null, active: boolean): AiSiteStartTracker {\n const ref = useRef<AiSiteStartTracker | null>(null)\n if (!ref.current) ref.current = createAiSiteStartTracker()\n const tracker = ref.current\n const leaving = useRef<ReturnType<typeof setTimeout> | null>(null)\n\n useEffect(() => {\n if (active && step) tracker.view(step)\n }, [active, step, tracker])\n\n useEffect(() => {\n if (!active) return\n if (leaving.current) clearTimeout(leaving.current)\n leaving.current = null\n const onPageHide = () => tracker.abandon('unload')\n window.addEventListener('pagehide', onPageHide)\n return () => {\n window.removeEventListener('pagehide', onPageHide)\n leaving.current = setTimeout(() => {\n tracker.abandon('route')\n tracker.dispose()\n }, 0)\n }\n }, [active, tracker])\n\n return tracker\n}\n"],"names":["trackEvent","useEffect","useRef","AI_SITE_START_STEPS","aiSiteStartStepIndex","AI_SITE_START_IDLE_MS","AI_SITE_START_RAGE_CLICKS","AI_SITE_START_RAGE_WINDOW_MS","AI_SITE_START_RAGE_RADIUS_PX","AI_SITE_START_STRUGGLE_EDITS","AI_SITE_START_DEAD_CLICKS_PER_STEP","createAiSiteStartTracker","options","send","name","params","now","Date","hidden","document","visibilityState","opened","step","enteredAt","done","idleTimer","idleReported","deadClicks","burst","edits","Map","dirty","Set","struggled","index","spent","Math","max","round","stopIdle","clearTimeout","armIdle","setTimeout","signal","duration_ms","edited","field","count","get","set","has","add","view","next","step_index","step_count","length","complete","back","fieldError","code","error","fieldChanged","fieldLeft","delete","fieldPicked","activity","click","at","near","hypot","x","y","reported","area","clicks","interactive","blank","abandon","reason","total_ms","finish","dispose","INTERACTIVE","join","AI_SITE_START_AREA_ATTRIBUTE","BACKDROP","isContent","element","closest","node","Array","from","childNodes","nodeType","textContent","trim","aiSiteStartClickOf","event","target","Element","named","getAttribute","Boolean","clientX","clientY","useAiSiteStartTracking","active","ref","current","tracker","leaving","onPageHide","window","addEventListener","removeEventListener"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-site-start-tracking.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport { trackEvent, type AnalyticsEventName, type AnalyticsEventParams } from '@aglyn/aglyn/app-utils/analytics-events'\nimport { useEffect, useRef } from 'react'\nimport {\n AI_SITE_START_STEPS,\n aiSiteStartStepIndex,\n type AiSiteStartErrorCode,\n type AiSiteStartField,\n type AiSiteStartStep,\n} from '../model/ai-site-start-steps'\n\n/**\n * Where people get lost in the guided start (AGL-3898), as the console's own\n * product analytics: each step drawn, left forward or back and how long it\n * took, the step the start was abandoned on, the field a step refused on,\n * and the signs of a struggle — one field changed again and again, a step\n * left idle, fast repeated clicks on one spot (rage) and clicks on words, a\n * picture or an icon in the form that do nothing (dead). A click on empty\n * space or on a popup's backdrop is not a dead click (2026-10-18).\n *\n * Every event goes through `trackEvent`, the one door the console's GA4\n * taxonomy has, so it is consent-gated exactly as every other console event\n * is (`firebase-app.layout.tsx` registers the transport, or the refusal that\n * drops it) and its params are sanitized there too. What is sent is only ever\n * a step id, a field id, a closed error code, a closed area id, a count or a\n * duration: never anything typed, picked or named.\n *\n * The detector is a plain object over a clock and a timer so its rules are\n * tested with fake timers; {@link useAiSiteStartTracking} binds it to the\n * dialog.\n */\n\n/** No input on a step for this long is a struggle. */\nexport const AI_SITE_START_IDLE_MS = 30_000\n/** This many clicks on one spot… */\nexport const AI_SITE_START_RAGE_CLICKS = 3\n/** …within this long… */\nexport const AI_SITE_START_RAGE_WINDOW_MS = 700\n/** …and within this distance of the first, is rage. */\nexport const AI_SITE_START_RAGE_RADIUS_PX = 30\n/** One field changed this many times is a struggle. */\nexport const AI_SITE_START_STRUGGLE_EDITS = 3\n/** At most this many dead clicks are reported on one visit to a step. */\nexport const AI_SITE_START_DEAD_CLICKS_PER_STEP = 3\n\n/** Why the start was left without a site started. */\nexport type AiSiteStartAbandonReason = 'skip' | 'close' | 'unload' | 'route'\n\ntype Send = <K extends AnalyticsEventName>(name: K, params: AnalyticsEventParams[K]) => void\n\nexport interface AiSiteStartTrackerOptions {\n send?: Send\n now?: () => number\n /** Whether the page is out of sight, when an idle step is not a struggle. */\n hidden?: () => boolean\n}\n\n/** A click as the detector reads it: where, and what it landed on. */\nexport interface AiSiteStartClick {\n x: number\n y: number\n /** Whether it landed on something that acts on a click. */\n interactive: boolean\n /** The closed id of the part of the form it landed in. */\n area: string\n /**\n * Whether it landed on nothing a person could mistake for a control: empty\n * space, a popup's backdrop (the click that closes a menu or a help\n * popover), or anything outside the form's named parts. A blank click is\n * never a dead click.\n */\n blank?: boolean\n}\n\nexport interface AiSiteStartTracker {\n /** A step was drawn. */\n view(step: AiSiteStartStep): void\n /** The person went forward from the step on screen. */\n complete(): void\n /** The person went back from the step on screen. */\n back(): void\n /** A step refused to go forward on a field. */\n fieldError(field: AiSiteStartField, code: AiSiteStartErrorCode): void\n /** A text answer changed; counted as one edit when the field is left. */\n fieldChanged(field: AiSiteStartField): void\n /** A text answer was left. */\n fieldLeft(field: AiSiteStartField): void\n /** A choice was picked: input, but not an edit (comparing cards is not a struggle). */\n fieldPicked(field: AiSiteStartField): void\n /** The step's \"More details\" was opened (AGL-3979). */\n moreDetails(): void\n /**\n * The site was asked for with these optional extras given (AGL-3979): one\n * event a field, naming the step that asks it, never what was typed.\n */\n detailsGiven(fields: ReadonlyArray<{ step: AiSiteStartStep; field: AiSiteStartField }>): void\n /** Any input on the step, which puts off the idle signal. */\n activity(): void\n /** A click anywhere in the dialog. */\n click(click: AiSiteStartClick): void\n /** The start was left without a site started; reported once. */\n abandon(reason: AiSiteStartAbandonReason): void\n /** A site was started (or the starter chosen): nothing after it is an abandonment. */\n finish(): void\n /** Stops the idle timer. */\n dispose(): void\n}\n\nexport function createAiSiteStartTracker(options: AiSiteStartTrackerOptions = {}): AiSiteStartTracker {\n const send: Send = options.send ?? ((name, params) => trackEvent(name, params))\n const now = options.now ?? (() => Date.now())\n const hidden = options.hidden ?? (() => typeof document !== 'undefined' && document.visibilityState === 'hidden')\n const opened = now()\n let step: AiSiteStartStep | null = null\n let enteredAt = opened\n let done = false\n let idleTimer: ReturnType<typeof setTimeout> | null = null\n let idleReported = false\n let deadClicks = 0\n let burst: { x: number; y: number; at: number; count: number; reported: boolean } | null = null\n const edits = new Map<AiSiteStartField, number>()\n const dirty = new Set<AiSiteStartField>()\n const struggled = new Set<AiSiteStartField>()\n\n const index = () => (step ? aiSiteStartStepIndex(step) : 0)\n const spent = () => Math.max(0, Math.round(now() - enteredAt))\n const stopIdle = () => {\n if (idleTimer) clearTimeout(idleTimer)\n idleTimer = null\n }\n const armIdle = () => {\n stopIdle()\n if (done || !step || idleReported) return\n idleTimer = setTimeout(() => {\n idleTimer = null\n if (done || !step || idleReported || hidden()) return\n idleReported = true\n send('site_start_struggle', { step, signal: 'idle', duration_ms: AI_SITE_START_IDLE_MS })\n }, AI_SITE_START_IDLE_MS)\n }\n const edited = (field: AiSiteStartField) => {\n if (!step) return\n const count = (edits.get(field) ?? 0) + 1\n edits.set(field, count)\n if (count >= AI_SITE_START_STRUGGLE_EDITS && !struggled.has(field)) {\n struggled.add(field)\n send('site_start_struggle', { step, signal: 'edits', field, count })\n }\n }\n\n return {\n view(next) {\n if (done) return\n step = next\n enteredAt = now()\n idleReported = false\n deadClicks = 0\n burst = null\n send('site_start_step_viewed', { step: next, step_index: aiSiteStartStepIndex(next), step_count: AI_SITE_START_STEPS.length })\n armIdle()\n },\n complete() {\n if (done || !step) return\n send('site_start_step_completed', { step, step_index: index(), duration_ms: spent() })\n },\n back() {\n if (done || !step) return\n send('site_start_step_back', { step, step_index: index(), duration_ms: spent() })\n },\n fieldError(field, code) {\n if (done || !step) return\n send('site_start_field_error', { step, field, error: code })\n },\n fieldChanged(field) {\n dirty.add(field)\n },\n fieldLeft(field) {\n if (!dirty.delete(field)) return\n edited(field)\n },\n fieldPicked() {\n // Picking card after card is how a person compares styles or kinds; it\n // read as \"one answer changed again and again\" (2026-10-18) and is not.\n // Only a typed answer rewritten is a struggle.\n armIdle()\n },\n moreDetails() {\n if (done || !step) return\n send('site_start_more_details', { step })\n armIdle()\n },\n detailsGiven(fields) {\n if (done) return\n for (const { step: at, field } of fields) send('site_start_detail_given', { step: at, field })\n },\n activity() {\n armIdle()\n },\n click(click) {\n if (done || !step) return\n const at = now()\n const near =\n burst &&\n at - burst.at <= AI_SITE_START_RAGE_WINDOW_MS &&\n Math.hypot(click.x - burst.x, click.y - burst.y) <= AI_SITE_START_RAGE_RADIUS_PX\n if (near && burst) {\n burst.count += 1\n if (burst.count >= AI_SITE_START_RAGE_CLICKS && !burst.reported) {\n burst.reported = true\n send('site_start_rage_click', { step, area: click.area, clicks: burst.count })\n }\n } else {\n burst = { x: click.x, y: click.y, at, count: 1, reported: false }\n }\n if (!click.interactive && !click.blank && deadClicks < AI_SITE_START_DEAD_CLICKS_PER_STEP) {\n deadClicks += 1\n send('site_start_dead_click', { step, area: click.area })\n }\n armIdle()\n },\n abandon(reason) {\n if (done) return\n done = true\n stopIdle()\n const at = step ?? AI_SITE_START_STEPS[0]\n send('site_start_abandoned', {\n step: at,\n step_index: aiSiteStartStepIndex(at),\n reason,\n duration_ms: spent(),\n total_ms: Math.max(0, Math.round(now() - opened)),\n })\n },\n finish() {\n done = true\n stopIdle()\n },\n dispose() {\n stopIdle()\n },\n }\n}\n\n/** What clicking acts on: a click on anything else in the form is a dead click. */\nconst INTERACTIVE = [\n 'button',\n 'a[href]',\n 'input',\n 'select',\n 'textarea',\n 'label',\n 'summary',\n '[role=\"button\"]',\n '[role=\"radio\"]',\n '[role=\"checkbox\"]',\n '[role=\"switch\"]',\n '[role=\"option\"]',\n '[role=\"tab\"]',\n '[role=\"link\"]',\n '[role=\"combobox\"]',\n '[role=\"menuitem\"]',\n '[role=\"slider\"]',\n '[contenteditable=\"true\"]',\n '.MuiInputBase-root',\n '[tabindex]:not([tabindex=\"-1\"])',\n].join(', ')\n\n/** The attribute naming the part of the form an element is in (a closed id, never content). */\nexport const AI_SITE_START_AREA_ATTRIBUTE = 'data-start-area'\n\n/** A popup's backdrop (a menu's, a select's, a help popover's): a click on it closes the popup. */\nconst BACKDROP = '.MuiBackdrop-root'\n\n/**\n * Whether an element is something a person sees and might click: its own\n * words, a picture or an icon. A bare layout box (the space around and\n * between things) is not.\n */\nfunction isContent(element: Element): boolean {\n if (element.closest('img, svg, picture, video, canvas')) return true\n for (const node of Array.from(element.childNodes)) {\n if (node.nodeType === 3 && (node.textContent ?? '').trim()) return true\n }\n return false\n}\n\n/** A DOM click, as the detector reads it. */\nexport function aiSiteStartClickOf(event: { clientX: number; clientY: number; target: EventTarget | null }): AiSiteStartClick {\n const target = event.target instanceof Element ? event.target : null\n const named = target?.closest(`[${AI_SITE_START_AREA_ATTRIBUTE}]`) ?? null\n const area = named?.getAttribute(AI_SITE_START_AREA_ATTRIBUTE) ?? 'form'\n const blank = !target || !named || Boolean(target.closest(BACKDROP)) || !isContent(target)\n return { x: event.clientX, y: event.clientY, interactive: Boolean(target?.closest(INTERACTIVE)), area, blank }\n}\n\n/**\n * The tracker for one open guided start: views each step as it is drawn, and\n * reports the start abandoned when the tab goes away or the page changes\n * under it. A start that is finished ({@link AiSiteStartTracker.finish}) or\n * already abandoned reports nothing more.\n *\n * The unmount report waits a tick, so a development double mount (React's\n * StrictMode runs an effect's cleanup and then the effect again) is not read\n * as the person leaving.\n */\nexport function useAiSiteStartTracking(step: AiSiteStartStep | null, active: boolean): AiSiteStartTracker {\n const ref = useRef<AiSiteStartTracker | null>(null)\n if (!ref.current) ref.current = createAiSiteStartTracker()\n const tracker = ref.current\n const leaving = useRef<ReturnType<typeof setTimeout> | null>(null)\n\n useEffect(() => {\n if (active && step) tracker.view(step)\n }, [active, step, tracker])\n\n useEffect(() => {\n if (!active) return\n if (leaving.current) clearTimeout(leaving.current)\n leaving.current = null\n const onPageHide = () => tracker.abandon('unload')\n window.addEventListener('pagehide', onPageHide)\n return () => {\n window.removeEventListener('pagehide', onPageHide)\n leaving.current = setTimeout(() => {\n tracker.abandon('route')\n tracker.dispose()\n }, 0)\n }\n }, [active, tracker])\n\n return tracker\n}\n"],"names":["trackEvent","useEffect","useRef","AI_SITE_START_STEPS","aiSiteStartStepIndex","AI_SITE_START_IDLE_MS","AI_SITE_START_RAGE_CLICKS","AI_SITE_START_RAGE_WINDOW_MS","AI_SITE_START_RAGE_RADIUS_PX","AI_SITE_START_STRUGGLE_EDITS","AI_SITE_START_DEAD_CLICKS_PER_STEP","createAiSiteStartTracker","options","send","name","params","now","Date","hidden","document","visibilityState","opened","step","enteredAt","done","idleTimer","idleReported","deadClicks","burst","edits","Map","dirty","Set","struggled","index","spent","Math","max","round","stopIdle","clearTimeout","armIdle","setTimeout","signal","duration_ms","edited","field","count","get","set","has","add","view","next","step_index","step_count","length","complete","back","fieldError","code","error","fieldChanged","fieldLeft","delete","fieldPicked","moreDetails","detailsGiven","fields","at","activity","click","near","hypot","x","y","reported","area","clicks","interactive","blank","abandon","reason","total_ms","finish","dispose","INTERACTIVE","join","AI_SITE_START_AREA_ATTRIBUTE","BACKDROP","isContent","element","closest","node","Array","from","childNodes","nodeType","textContent","trim","aiSiteStartClickOf","event","target","Element","named","getAttribute","Boolean","clientX","clientY","useAiSiteStartTracking","active","ref","current","tracker","leaving","onPageHide","window","addEventListener","removeEventListener"],"mappings":"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"}
@@ -42,8 +42,12 @@ import comingSoonPreview from "../assets/site-styles/coming-soon.webp";
42
42
  * store's products, a photographer's gallery, a band's tour dates — drawn in
43
43
  * the kind's look by `libs/plugins/ai/scripts/render-site-style-previews.mts`
44
44
  * from the stand-in pages in `site-style-previews.json`, with the real
45
- * compiler and node renderer and the starter photos. Each is 800 × 800;
46
- * regenerate them all with that script when a kind's look changes.
45
+ * compiler and node renderer, the starter photos and the previews' own
46
+ * credited photos (`scripts/style-preview-photos/`). Each opens on its kind's
47
+ * signature hero (`aiLayoutKindSignature`, AGL-3985) with a photo of its own
48
+ * kind of business.
49
+ * Each is 800 × 800; regenerate them all with that script when a kind's look
50
+ * or layout changes.
47
51
  */ const PREVIEWS = {
48
52
  business: businessPreview,
49
53
  trades: tradesPreview,
@@ -68,27 +72,27 @@ import comingSoonPreview from "../assets/site-styles/coming-soon.webp";
68
72
  'coming-soon': comingSoonPreview
69
73
  };
70
74
  /** What each picture shows, as its alt text: the example page and what leads it. */ export const AI_SITE_STYLE_PREVIEW_ALTS = {
71
- business: "A cleaning company's home page: its heading beside a photo of a tidy desk, then its services",
72
- trades: "A roofer's home page: a bold heading on a dark band, then roofing services as cards",
73
- professional: "A consulting firm's home page: its heading beside a photo of a team at a planning board",
74
- wellness: "A therapy practice's home page: its heading over a plant-filled room, then a list of sessions",
75
- restaurant: "A café's home page: its heading over a photo of the owner at the counter, then menu highlights",
76
- store: "A handmade goods shop's home page: its heading beside a photo of a workbench, then featured products",
75
+ business: "A cleaning company's home page: its heading beside a photo of gloved hands wiping a desk, then its services as cards",
76
+ trades: "A roofer's home page: a bold heading over a full-width photo of a roofer carrying shingles, then roofing services",
77
+ professional: "A consulting firm's home page: its heading on a white card over a full-width photo of a team at a planning board",
78
+ wellness: "A therapy practice's home page: half the page a photo of a calm, sunlit room, its heading in the other half, then its sessions",
79
+ restaurant: "A café's home page: its heading centered over a photo of the owner at the counter, then menu highlights",
80
+ store: "A handmade goods shop's home page: its heading beside two overlapping photos of wax seals, then featured products",
77
81
  portfolio: "A prop stylist's home page: a large heading over a grid of three styled sets",
78
- studio: "A design studio's home page: a large heading on a dark band, then its selected work",
79
- photography: "A photographer's home page: its heading on black over a gallery of three photos",
80
- blog: "A food journal's home page: its heading on a dark band, then a list of the latest posts",
81
- events: "A festival's home page: its heading beside the three days' program on a colored band",
82
- fitness: "A gym's home page: a bold heading on a dark band, then this week's class times",
83
- yoga: "A yoga studio's home page: its heading beside this week's class schedule",
84
- beauty: "A hair salon's home page: its heading beside a photo of roses and jars at a styling station",
85
- realestate: "A real estate agent's home page: its heading over three listings with photos",
86
- education: "A tutoring center's home page: its heading over a photo of a tutor at the board, then its courses",
87
- nonprofit: "A food bank's home page: its heading on a dark band, then the ways to help",
88
- music: "A band's home page: its name on a dark band, then its tour dates",
89
- personal: "A product manager's personal page: her introduction beside a photo of her with a teammate",
90
- landing: "A budgeting app's landing page: its pitch and sign-up button on a dark band, then its benefits",
91
- 'coming-soon': "A café's coming soon page: its heading and notify button over a photo of the café shelves"
82
+ studio: "A design studio's home page: an oversized heading on black over a wide photo of the team at a brand board",
83
+ photography: "A photographer's home page: its heading on black over a gallery of a wedding, a portrait and a maker",
84
+ blog: "A food journal's home page: its heading centered in a narrow column over a wide photo of vegetables on a cutting board",
85
+ events: "A festival's home page: two overlapping photos of a lantern-lit night market beside its dates, then the program",
86
+ fitness: "A gym's home page: a bold heading on black beside a photo of rows of dumbbells, then this week's classes",
87
+ yoga: "A yoga studio's home page: a quiet centered heading over a wide photo of a class stretching on mats",
88
+ beauty: "A hair salon's home page: its heading beside two overlapping photos of a stylist at work and the salon's chairs",
89
+ realestate: "A real estate agent's home page: its heading on a white card over a photo of a white house, then new listings",
90
+ education: "A tutoring center's home page: a photo of a tutor at the whiteboard beside its heading, then its courses",
91
+ nonprofit: "A food bank's home page: its heading over a full-width photo of volunteers sorting donations, then the ways to help",
92
+ music: "A band's home page: its name in large type over a photo of a band under stage lights, then its tour dates",
93
+ personal: "A product manager's personal page: a round portrait over her centered introduction, then her experience",
94
+ landing: "A budgeting app's landing page: its pitch centered on a colored band over a photo of a couple planning on a phone",
95
+ 'coming-soon': "A café's coming soon page: its heading and notify button centered over a photo of the café's coffee shelves"
92
96
  };
93
97
  /** The width and height every preview is drawn at. */ export const AI_SITE_STYLE_PREVIEW_SIZE = {
94
98
  width: 800,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-site-style-previews.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { OptionCardImage } from '@aglyn/shared-ui-jsx/components/option-card-grid.component'\nimport { AI_SITE_KINDS } from '../model/ai-site-kinds'\nimport businessPreview from '../assets/site-styles/business.webp'\nimport tradesPreview from '../assets/site-styles/trades.webp'\nimport professionalPreview from '../assets/site-styles/professional.webp'\nimport wellnessPreview from '../assets/site-styles/wellness.webp'\nimport restaurantPreview from '../assets/site-styles/restaurant.webp'\nimport storePreview from '../assets/site-styles/store.webp'\nimport portfolioPreview from '../assets/site-styles/portfolio.webp'\nimport studioPreview from '../assets/site-styles/studio.webp'\nimport photographyPreview from '../assets/site-styles/photography.webp'\nimport blogPreview from '../assets/site-styles/blog.webp'\nimport eventsPreview from '../assets/site-styles/events.webp'\nimport fitnessPreview from '../assets/site-styles/fitness.webp'\nimport yogaPreview from '../assets/site-styles/yoga.webp'\nimport beautyPreview from '../assets/site-styles/beauty.webp'\nimport realestatePreview from '../assets/site-styles/realestate.webp'\nimport educationPreview from '../assets/site-styles/education.webp'\nimport nonprofitPreview from '../assets/site-styles/nonprofit.webp'\nimport musicPreview from '../assets/site-styles/music.webp'\nimport personalPreview from '../assets/site-styles/personal.webp'\nimport landingPreview from '../assets/site-styles/landing.webp'\nimport comingSoonPreview from '../assets/site-styles/coming-soon.webp'\n\n/**\n * The picture on each style card of the guided start (AGL-3898): an example\n * home page of that kind of site, in its own shape — a café's menu, a\n * store's products, a photographer's gallery, a band's tour dates — drawn in\n * the kind's look by `libs/plugins/ai/scripts/render-site-style-previews.mts`\n * from the stand-in pages in `site-style-previews.json`, with the real\n * compiler and node renderer and the starter photos. Each is 800 × 800;\n * regenerate them all with that script when a kind's look changes.\n */\nconst PREVIEWS: Readonly<Record<string, unknown>> = {\n business: businessPreview,\n trades: tradesPreview,\n professional: professionalPreview,\n wellness: wellnessPreview,\n restaurant: restaurantPreview,\n store: storePreview,\n portfolio: portfolioPreview,\n studio: studioPreview,\n photography: photographyPreview,\n blog: blogPreview,\n events: eventsPreview,\n fitness: fitnessPreview,\n yoga: yogaPreview,\n beauty: beautyPreview,\n realestate: realestatePreview,\n education: educationPreview,\n nonprofit: nonprofitPreview,\n music: musicPreview,\n personal: personalPreview,\n landing: landingPreview,\n 'coming-soon': comingSoonPreview,\n}\n\n/** What each picture shows, as its alt text: the example page and what leads it. */\nexport const AI_SITE_STYLE_PREVIEW_ALTS: Readonly<Record<string, string>> = {\n business: \"A cleaning company's home page: its heading beside a photo of a tidy desk, then its services\",\n trades: \"A roofer's home page: a bold heading on a dark band, then roofing services as cards\",\n professional: \"A consulting firm's home page: its heading beside a photo of a team at a planning board\",\n wellness: \"A therapy practice's home page: its heading over a plant-filled room, then a list of sessions\",\n restaurant: \"A café's home page: its heading over a photo of the owner at the counter, then menu highlights\",\n store: \"A handmade goods shop's home page: its heading beside a photo of a workbench, then featured products\",\n portfolio: \"A prop stylist's home page: a large heading over a grid of three styled sets\",\n studio: \"A design studio's home page: a large heading on a dark band, then its selected work\",\n photography: \"A photographer's home page: its heading on black over a gallery of three photos\",\n blog: \"A food journal's home page: its heading on a dark band, then a list of the latest posts\",\n events: \"A festival's home page: its heading beside the three days' program on a colored band\",\n fitness: \"A gym's home page: a bold heading on a dark band, then this week's class times\",\n yoga: \"A yoga studio's home page: its heading beside this week's class schedule\",\n beauty: \"A hair salon's home page: its heading beside a photo of roses and jars at a styling station\",\n realestate: \"A real estate agent's home page: its heading over three listings with photos\",\n education: \"A tutoring center's home page: its heading over a photo of a tutor at the board, then its courses\",\n nonprofit: \"A food bank's home page: its heading on a dark band, then the ways to help\",\n music: \"A band's home page: its name on a dark band, then its tour dates\",\n personal: \"A product manager's personal page: her introduction beside a photo of her with a teammate\",\n landing: \"A budgeting app's landing page: its pitch and sign-up button on a dark band, then its benefits\",\n 'coming-soon': \"A café's coming soon page: its heading and notify button over a photo of the café shelves\",\n}\n\n/** The width and height every preview is drawn at. */\nexport const AI_SITE_STYLE_PREVIEW_SIZE = { width: 800, height: 800 } as const\n\n/** A bundler hands an imported picture over as its URL, or as an object carrying it. */\nfunction urlOf(imported: unknown): string | null {\n if (typeof imported === 'string') return imported\n if (imported && typeof imported === 'object' && typeof (imported as { src?: unknown }).src === 'string') {\n return (imported as { src: string }).src\n }\n return null\n}\n\n/** The style card picture for a kind of site, by its `AI_SITE_KINDS` id; `null` for a kind with none. */\nexport function aiSiteStylePreview(kindId: string): OptionCardImage | null {\n const src = urlOf(PREVIEWS[kindId])\n const kind = AI_SITE_KINDS.find((entry) => entry.id === kindId)\n if (!src || !kind) return null\n return {\n src,\n alt: AI_SITE_STYLE_PREVIEW_ALTS[kindId] ?? `An example home page in the ${kind.label} style`,\n ...AI_SITE_STYLE_PREVIEW_SIZE,\n }\n}\n"],"names":["AI_SITE_KINDS","businessPreview","tradesPreview","professionalPreview","wellnessPreview","restaurantPreview","storePreview","portfolioPreview","studioPreview","photographyPreview","blogPreview","eventsPreview","fitnessPreview","yogaPreview","beautyPreview","realestatePreview","educationPreview","nonprofitPreview","musicPreview","personalPreview","landingPreview","comingSoonPreview","PREVIEWS","business","trades","professional","wellness","restaurant","store","portfolio","studio","photography","blog","events","fitness","yoga","beauty","realestate","education","nonprofit","music","personal","landing","AI_SITE_STYLE_PREVIEW_ALTS","AI_SITE_STYLE_PREVIEW_SIZE","width","height","urlOf","imported","src","aiSiteStylePreview","kindId","kind","find","entry","id","alt","label"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC;AAGD,SAASA,aAAa,QAAQ,4BAAwB;AACtD,OAAOC,qBAAqB,sCAAqC;AACjE,OAAOC,mBAAmB,oCAAmC;AAC7D,OAAOC,yBAAyB,0CAAyC;AACzE,OAAOC,qBAAqB,sCAAqC;AACjE,OAAOC,uBAAuB,wCAAuC;AACrE,OAAOC,kBAAkB,mCAAkC;AAC3D,OAAOC,sBAAsB,uCAAsC;AACnE,OAAOC,mBAAmB,oCAAmC;AAC7D,OAAOC,wBAAwB,yCAAwC;AACvE,OAAOC,iBAAiB,kCAAiC;AACzD,OAAOC,mBAAmB,oCAAmC;AAC7D,OAAOC,oBAAoB,qCAAoC;AAC/D,OAAOC,iBAAiB,kCAAiC;AACzD,OAAOC,mBAAmB,oCAAmC;AAC7D,OAAOC,uBAAuB,wCAAuC;AACrE,OAAOC,sBAAsB,uCAAsC;AACnE,OAAOC,sBAAsB,uCAAsC;AACnE,OAAOC,kBAAkB,mCAAkC;AAC3D,OAAOC,qBAAqB,sCAAqC;AACjE,OAAOC,oBAAoB,qCAAoC;AAC/D,OAAOC,uBAAuB,yCAAwC;AAEtE;;;;;;;;CAQC,GACD,MAAMC,WAA8C;IAClDC,UAAUtB;IACVuB,QAAQtB;IACRuB,cAActB;IACduB,UAAUtB;IACVuB,YAAYtB;IACZuB,OAAOtB;IACPuB,WAAWtB;IACXuB,QAAQtB;IACRuB,aAAatB;IACbuB,MAAMtB;IACNuB,QAAQtB;IACRuB,SAAStB;IACTuB,MAAMtB;IACNuB,QAAQtB;IACRuB,YAAYtB;IACZuB,WAAWtB;IACXuB,WAAWtB;IACXuB,OAAOtB;IACPuB,UAAUtB;IACVuB,SAAStB;IACT,eAAeC;AACjB;AAEA,kFAAkF,GAClF,OAAO,MAAMsB,6BAA+D;IAC1EpB,UAAU;IACVC,QAAQ;IACRC,cAAc;IACdC,UAAU;IACVC,YAAY;IACZC,OAAO;IACPC,WAAW;IACXC,QAAQ;IACRC,aAAa;IACbC,MAAM;IACNC,QAAQ;IACRC,SAAS;IACTC,MAAM;IACNC,QAAQ;IACRC,YAAY;IACZC,WAAW;IACXC,WAAW;IACXC,OAAO;IACPC,UAAU;IACVC,SAAS;IACT,eAAe;AACjB,EAAC;AAED,oDAAoD,GACpD,OAAO,MAAME,6BAA6B;IAAEC,OAAO;IAAKC,QAAQ;AAAI,EAAU;AAE9E,sFAAsF,GACtF,SAASC,MAAMC,QAAiB;IAC9B,IAAI,OAAOA,aAAa,UAAU,OAAOA;IACzC,IAAIA,YAAY,OAAOA,aAAa,YAAY,OAAO,AAACA,SAA+BC,GAAG,KAAK,UAAU;QACvG,OAAO,AAACD,SAA6BC,GAAG;IAC1C;IACA,OAAO;AACT;AAEA,uGAAuG,GACvG,OAAO,SAASC,mBAAmBC,MAAc;QAMxCR;IALP,MAAMM,MAAMF,MAAMzB,QAAQ,CAAC6B,OAAO;IAClC,MAAMC,OAAOpD,cAAcqD,IAAI,CAAC,CAACC,QAAUA,MAAMC,EAAE,KAAKJ;IACxD,IAAI,CAACF,OAAO,CAACG,MAAM,OAAO;IAC1B,OAAO;QACLH;QACAO,GAAG,GAAEb,qCAAAA,0BAA0B,CAACQ,OAAO,YAAlCR,qCAAsC,CAAC,4BAA4B,EAAES,KAAKK,KAAK,CAAC,MAAM,CAAC;OACzFb;AAEP"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-site-style-previews.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { OptionCardImage } from '@aglyn/shared-ui-jsx/components/option-card-grid.component'\nimport { AI_SITE_KINDS } from '../model/ai-site-kinds'\nimport businessPreview from '../assets/site-styles/business.webp'\nimport tradesPreview from '../assets/site-styles/trades.webp'\nimport professionalPreview from '../assets/site-styles/professional.webp'\nimport wellnessPreview from '../assets/site-styles/wellness.webp'\nimport restaurantPreview from '../assets/site-styles/restaurant.webp'\nimport storePreview from '../assets/site-styles/store.webp'\nimport portfolioPreview from '../assets/site-styles/portfolio.webp'\nimport studioPreview from '../assets/site-styles/studio.webp'\nimport photographyPreview from '../assets/site-styles/photography.webp'\nimport blogPreview from '../assets/site-styles/blog.webp'\nimport eventsPreview from '../assets/site-styles/events.webp'\nimport fitnessPreview from '../assets/site-styles/fitness.webp'\nimport yogaPreview from '../assets/site-styles/yoga.webp'\nimport beautyPreview from '../assets/site-styles/beauty.webp'\nimport realestatePreview from '../assets/site-styles/realestate.webp'\nimport educationPreview from '../assets/site-styles/education.webp'\nimport nonprofitPreview from '../assets/site-styles/nonprofit.webp'\nimport musicPreview from '../assets/site-styles/music.webp'\nimport personalPreview from '../assets/site-styles/personal.webp'\nimport landingPreview from '../assets/site-styles/landing.webp'\nimport comingSoonPreview from '../assets/site-styles/coming-soon.webp'\n\n/**\n * The picture on each style card of the guided start (AGL-3898): an example\n * home page of that kind of site, in its own shape — a café's menu, a\n * store's products, a photographer's gallery, a band's tour dates — drawn in\n * the kind's look by `libs/plugins/ai/scripts/render-site-style-previews.mts`\n * from the stand-in pages in `site-style-previews.json`, with the real\n * compiler and node renderer, the starter photos and the previews' own\n * credited photos (`scripts/style-preview-photos/`). Each opens on its kind's\n * signature hero (`aiLayoutKindSignature`, AGL-3985) with a photo of its own\n * kind of business.\n * Each is 800 × 800; regenerate them all with that script when a kind's look\n * or layout changes.\n */\nconst PREVIEWS: Readonly<Record<string, unknown>> = {\n business: businessPreview,\n trades: tradesPreview,\n professional: professionalPreview,\n wellness: wellnessPreview,\n restaurant: restaurantPreview,\n store: storePreview,\n portfolio: portfolioPreview,\n studio: studioPreview,\n photography: photographyPreview,\n blog: blogPreview,\n events: eventsPreview,\n fitness: fitnessPreview,\n yoga: yogaPreview,\n beauty: beautyPreview,\n realestate: realestatePreview,\n education: educationPreview,\n nonprofit: nonprofitPreview,\n music: musicPreview,\n personal: personalPreview,\n landing: landingPreview,\n 'coming-soon': comingSoonPreview,\n}\n\n/** What each picture shows, as its alt text: the example page and what leads it. */\nexport const AI_SITE_STYLE_PREVIEW_ALTS: Readonly<Record<string, string>> = {\n business: \"A cleaning company's home page: its heading beside a photo of gloved hands wiping a desk, then its services as cards\",\n trades: \"A roofer's home page: a bold heading over a full-width photo of a roofer carrying shingles, then roofing services\",\n professional: \"A consulting firm's home page: its heading on a white card over a full-width photo of a team at a planning board\",\n wellness: \"A therapy practice's home page: half the page a photo of a calm, sunlit room, its heading in the other half, then its sessions\",\n restaurant: \"A café's home page: its heading centered over a photo of the owner at the counter, then menu highlights\",\n store: \"A handmade goods shop's home page: its heading beside two overlapping photos of wax seals, then featured products\",\n portfolio: \"A prop stylist's home page: a large heading over a grid of three styled sets\",\n studio: \"A design studio's home page: an oversized heading on black over a wide photo of the team at a brand board\",\n photography: \"A photographer's home page: its heading on black over a gallery of a wedding, a portrait and a maker\",\n blog: \"A food journal's home page: its heading centered in a narrow column over a wide photo of vegetables on a cutting board\",\n events: \"A festival's home page: two overlapping photos of a lantern-lit night market beside its dates, then the program\",\n fitness: \"A gym's home page: a bold heading on black beside a photo of rows of dumbbells, then this week's classes\",\n yoga: \"A yoga studio's home page: a quiet centered heading over a wide photo of a class stretching on mats\",\n beauty: \"A hair salon's home page: its heading beside two overlapping photos of a stylist at work and the salon's chairs\",\n realestate: \"A real estate agent's home page: its heading on a white card over a photo of a white house, then new listings\",\n education: \"A tutoring center's home page: a photo of a tutor at the whiteboard beside its heading, then its courses\",\n nonprofit: \"A food bank's home page: its heading over a full-width photo of volunteers sorting donations, then the ways to help\",\n music: \"A band's home page: its name in large type over a photo of a band under stage lights, then its tour dates\",\n personal: \"A product manager's personal page: a round portrait over her centered introduction, then her experience\",\n landing: \"A budgeting app's landing page: its pitch centered on a colored band over a photo of a couple planning on a phone\",\n 'coming-soon': \"A café's coming soon page: its heading and notify button centered over a photo of the café's coffee shelves\",\n}\n\n/** The width and height every preview is drawn at. */\nexport const AI_SITE_STYLE_PREVIEW_SIZE = { width: 800, height: 800 } as const\n\n/** A bundler hands an imported picture over as its URL, or as an object carrying it. */\nfunction urlOf(imported: unknown): string | null {\n if (typeof imported === 'string') return imported\n if (imported && typeof imported === 'object' && typeof (imported as { src?: unknown }).src === 'string') {\n return (imported as { src: string }).src\n }\n return null\n}\n\n/** The style card picture for a kind of site, by its `AI_SITE_KINDS` id; `null` for a kind with none. */\nexport function aiSiteStylePreview(kindId: string): OptionCardImage | null {\n const src = urlOf(PREVIEWS[kindId])\n const kind = AI_SITE_KINDS.find((entry) => entry.id === kindId)\n if (!src || !kind) return null\n return {\n src,\n alt: AI_SITE_STYLE_PREVIEW_ALTS[kindId] ?? `An example home page in the ${kind.label} style`,\n ...AI_SITE_STYLE_PREVIEW_SIZE,\n }\n}\n"],"names":["AI_SITE_KINDS","businessPreview","tradesPreview","professionalPreview","wellnessPreview","restaurantPreview","storePreview","portfolioPreview","studioPreview","photographyPreview","blogPreview","eventsPreview","fitnessPreview","yogaPreview","beautyPreview","realestatePreview","educationPreview","nonprofitPreview","musicPreview","personalPreview","landingPreview","comingSoonPreview","PREVIEWS","business","trades","professional","wellness","restaurant","store","portfolio","studio","photography","blog","events","fitness","yoga","beauty","realestate","education","nonprofit","music","personal","landing","AI_SITE_STYLE_PREVIEW_ALTS","AI_SITE_STYLE_PREVIEW_SIZE","width","height","urlOf","imported","src","aiSiteStylePreview","kindId","kind","find","entry","id","alt","label"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC;AAGD,SAASA,aAAa,QAAQ,4BAAwB;AACtD,OAAOC,qBAAqB,sCAAqC;AACjE,OAAOC,mBAAmB,oCAAmC;AAC7D,OAAOC,yBAAyB,0CAAyC;AACzE,OAAOC,qBAAqB,sCAAqC;AACjE,OAAOC,uBAAuB,wCAAuC;AACrE,OAAOC,kBAAkB,mCAAkC;AAC3D,OAAOC,sBAAsB,uCAAsC;AACnE,OAAOC,mBAAmB,oCAAmC;AAC7D,OAAOC,wBAAwB,yCAAwC;AACvE,OAAOC,iBAAiB,kCAAiC;AACzD,OAAOC,mBAAmB,oCAAmC;AAC7D,OAAOC,oBAAoB,qCAAoC;AAC/D,OAAOC,iBAAiB,kCAAiC;AACzD,OAAOC,mBAAmB,oCAAmC;AAC7D,OAAOC,uBAAuB,wCAAuC;AACrE,OAAOC,sBAAsB,uCAAsC;AACnE,OAAOC,sBAAsB,uCAAsC;AACnE,OAAOC,kBAAkB,mCAAkC;AAC3D,OAAOC,qBAAqB,sCAAqC;AACjE,OAAOC,oBAAoB,qCAAoC;AAC/D,OAAOC,uBAAuB,yCAAwC;AAEtE;;;;;;;;;;;;CAYC,GACD,MAAMC,WAA8C;IAClDC,UAAUtB;IACVuB,QAAQtB;IACRuB,cAActB;IACduB,UAAUtB;IACVuB,YAAYtB;IACZuB,OAAOtB;IACPuB,WAAWtB;IACXuB,QAAQtB;IACRuB,aAAatB;IACbuB,MAAMtB;IACNuB,QAAQtB;IACRuB,SAAStB;IACTuB,MAAMtB;IACNuB,QAAQtB;IACRuB,YAAYtB;IACZuB,WAAWtB;IACXuB,WAAWtB;IACXuB,OAAOtB;IACPuB,UAAUtB;IACVuB,SAAStB;IACT,eAAeC;AACjB;AAEA,kFAAkF,GAClF,OAAO,MAAMsB,6BAA+D;IAC1EpB,UAAU;IACVC,QAAQ;IACRC,cAAc;IACdC,UAAU;IACVC,YAAY;IACZC,OAAO;IACPC,WAAW;IACXC,QAAQ;IACRC,aAAa;IACbC,MAAM;IACNC,QAAQ;IACRC,SAAS;IACTC,MAAM;IACNC,QAAQ;IACRC,YAAY;IACZC,WAAW;IACXC,WAAW;IACXC,OAAO;IACPC,UAAU;IACVC,SAAS;IACT,eAAe;AACjB,EAAC;AAED,oDAAoD,GACpD,OAAO,MAAME,6BAA6B;IAAEC,OAAO;IAAKC,QAAQ;AAAI,EAAU;AAE9E,sFAAsF,GACtF,SAASC,MAAMC,QAAiB;IAC9B,IAAI,OAAOA,aAAa,UAAU,OAAOA;IACzC,IAAIA,YAAY,OAAOA,aAAa,YAAY,OAAO,AAACA,SAA+BC,GAAG,KAAK,UAAU;QACvG,OAAO,AAACD,SAA6BC,GAAG;IAC1C;IACA,OAAO;AACT;AAEA,uGAAuG,GACvG,OAAO,SAASC,mBAAmBC,MAAc;QAMxCR;IALP,MAAMM,MAAMF,MAAMzB,QAAQ,CAAC6B,OAAO;IAClC,MAAMC,OAAOpD,cAAcqD,IAAI,CAAC,CAACC,QAAUA,MAAMC,EAAE,KAAKJ;IACxD,IAAI,CAACF,OAAO,CAACG,MAAM,OAAO;IAC1B,OAAO;QACLH;QACAO,GAAG,GAAEb,qCAAAA,0BAA0B,CAACQ,OAAO,YAAlCR,qCAAsC,CAAC,4BAA4B,EAAES,KAAKK,KAAK,CAAC,MAAM,CAAC;OACzFb;AAEP"}
@@ -2,11 +2,13 @@ import type { ConsoleWidgetUpgrade } from '@aglyn/aglyn/plugin-manager/feature-p
2
2
  import { type ReactNode } from 'react';
3
3
  import { AI_UPSELL_COPY, AI_UPSELL_CREATE_LABEL, aiUpsellLabel, aiUpsellOffer, type AiUpsellKind } from '../model/ai-upsell-copy';
4
4
  /**
5
- * "Create with AI" on a plan that could buy the AI add-on and has not
6
- * (AGL-3601). The shell mounts the entry with `entitled={false}` only where
7
- * the add-on is sold to this workspace, and the jobs route has said the plan
8
- * is the one thing missing; the entry then keeps its place on the page and
9
- * opens this instead of the brief, so the reader sees where the add-on is used.
5
+ * "Create with AI" where generation is switched off for a workspace that
6
+ * could buy an Aglyn AI tier (AGL-3601, AGL-3965). Since AGL-3965 every paid
7
+ * plan's own credits pay for generation, so this is reached only where a
8
+ * per-org setting closed it. The shell mounts the entry with
9
+ * `entitled={false}` only where a tier is sold to this workspace, and the
10
+ * jobs route has said that is the one thing missing; the entry then keeps its
11
+ * place on the page and opens this instead of the brief.
10
12
  */
11
13
  export { AI_UPSELL_COPY, AI_UPSELL_CREATE_LABEL, aiUpsellLabel, aiUpsellOffer };
12
14
  export type { AiUpsellCopy, AiUpsellKind } from '../model/ai-upsell-copy';
@@ -25,17 +25,19 @@ import { Button, Dialog, DialogActions, DialogContent, DialogTitle, Stack, Typog
25
25
  import { useEffect, useState } from "react";
26
26
  import { AI_UPSELL_COPY, AI_UPSELL_CREATE_LABEL, aiUpsellLabel, aiUpsellOffer } from "../model/ai-upsell-copy.js";
27
27
  /**
28
- * "Create with AI" on a plan that could buy the AI add-on and has not
29
- * (AGL-3601). The shell mounts the entry with `entitled={false}` only where
30
- * the add-on is sold to this workspace, and the jobs route has said the plan
31
- * is the one thing missing; the entry then keeps its place on the page and
32
- * opens this instead of the brief, so the reader sees where the add-on is used.
28
+ * "Create with AI" where generation is switched off for a workspace that
29
+ * could buy an Aglyn AI tier (AGL-3601, AGL-3965). Since AGL-3965 every paid
30
+ * plan's own credits pay for generation, so this is reached only where a
31
+ * per-org setting closed it. The shell mounts the entry with
32
+ * `entitled={false}` only where a tier is sold to this workspace, and the
33
+ * jobs route has said that is the one thing missing; the entry then keeps its
34
+ * place on the page and opens this instead of the brief.
33
35
  */ // The entries' words are the model's, which the native apps' door reads too
34
36
  // (AGL-3854); re-exported for this dialog's existing readers.
35
37
  export { AI_UPSELL_COPY, AI_UPSELL_CREATE_LABEL, aiUpsellLabel, aiUpsellOffer };
36
38
  /** The add-on's own section of the Create with AI guide (AGL-3660). */ const aiUpsellHelp = ()=>pluginDocsHelp('aiCreate', {
37
39
  anchor: '#without-the-add-on',
38
- excerpt: `Create with AI needs the ${aiAddonName()} add-on on a paid plan. An owner or admin adds it from Billing; on Free, the monthly AI credits cover it.`
40
+ excerpt: `On every paid plan, Create with AI runs on the AI credits the plan includes. Where it is switched off for a workspace, an ${aiAddonName()} tier switches it on; an owner or admin chooses one in Billing.`
39
41
  });
40
42
  export function AiUpsellDialog({ kind, open, onClose, upgrade }) {
41
43
  const copy = AI_UPSELL_COPY[kind];