@wix/agent-skills 1.33.0 → 1.34.0

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 (635) hide show
  1. package/README.md +23 -0
  2. package/package.json +2 -2
  3. package/skills/wix-app/references/DASHBOARD_PAGE.md +1 -1
  4. package/skills/wix-export/SKILL.md +83 -0
  5. package/skills/wix-harmony-editor/.publish-manifest.json +8 -0
  6. package/skills/wix-harmony-editor/COPY-SITE.md +494 -0
  7. package/skills/wix-harmony-editor/SKILL.md +317 -0
  8. package/skills/wix-harmony-editor/scripts/copy/build-description.mjs +249 -0
  9. package/skills/wix-harmony-editor/scripts/copy/check-description.mjs +225 -0
  10. package/skills/wix-harmony-editor/scripts/copy/check-mirror.mjs +89 -0
  11. package/skills/wix-harmony-editor/scripts/copy/compare-preview.mjs +76 -0
  12. package/skills/wix-harmony-editor/scripts/copy/deploy-description.mjs +212 -0
  13. package/skills/wix-harmony-editor/scripts/copy/desc-hash.mjs +44 -0
  14. package/skills/wix-harmony-editor/scripts/copy/editor-run.mjs +45 -0
  15. package/skills/wix-harmony-editor/scripts/copy/export-page.mjs +53 -0
  16. package/skills/wix-harmony-editor/scripts/copy/find-interactions.mjs +76 -0
  17. package/skills/wix-harmony-editor/scripts/copy/hide-scrollbars.mjs +16 -0
  18. package/skills/wix-harmony-editor/scripts/copy/import-media.mjs +150 -0
  19. package/skills/wix-harmony-editor/scripts/copy/link-url.mjs +27 -0
  20. package/skills/wix-harmony-editor/scripts/copy/lint-api.mjs +56 -0
  21. package/skills/wix-harmony-editor/scripts/copy/measure-source.mjs +211 -0
  22. package/skills/wix-harmony-editor/scripts/copy/merge-mirrors.mjs +29 -0
  23. package/skills/wix-harmony-editor/scripts/copy/mirror-site.mjs +92 -0
  24. package/skills/wix-harmony-editor/scripts/copy/mobile-stage.mjs +56 -0
  25. package/skills/wix-harmony-editor/scripts/copy/preview-widths.mjs +73 -0
  26. package/skills/wix-harmony-editor/scripts/copy/render-plate.mjs +67 -0
  27. package/skills/wix-harmony-editor/scripts/copy/serve-mirror.mjs +102 -0
  28. package/skills/wix-harmony-editor/scripts/copy/snapshot-capabilities.mjs +40 -0
  29. package/skills/wix-harmony-editor/scripts/copy/split-sections.mjs +191 -0
  30. package/skills/wix-harmony-editor/scripts/copy/upload-media-manager.mjs +61 -0
  31. package/skills/wix-harmony-editor/scripts/launch.sh +359 -0
  32. package/skills/wix-headless/SKILL.md +7 -2
  33. package/skills/wix-headless/references/CAPABILITIES.md +6 -1
  34. package/skills/wix-headless/references/DISCOVERY.md +2 -1
  35. package/skills/wix-headless/references/SDK_HANDOFF.md +6 -1
  36. package/skills/wix-headless/references/SEED.md +3 -2
  37. package/skills/wix-headless/references/SETUP.md +1 -0
  38. package/skills/wix-headless/references/inline-recipes/experience-donations.md +102 -0
  39. package/skills/wix-headless/references/inline-recipes/how-to-code-donations.md +146 -0
  40. package/skills/wix-headless/references/inline-recipes/setup-donations.md +15 -0
  41. package/skills/wix-headless/references/managed/CREATE.md +1 -1
  42. package/skills/wix-headless-kit/SKILL.md +110 -21
  43. package/skills/wix-headless-kit/guides/api-run.md +15 -13
  44. package/skills/wix-headless-kit/guides/existing-site.md +31 -0
  45. package/skills/wix-headless-kit/guides/migration.md +49 -17
  46. package/skills/wix-headless-kit/install/agents-md.mjs +1 -1
  47. package/skills/wix-headless-kit/install/context.mjs +61 -6
  48. package/skills/wix-headless-kit/install/pins.json +1 -1
  49. package/skills/wix-headless-kit/install/setup.mjs +107 -12
  50. package/skills/wix-headless-replatform/.publish-manifest.json +8 -0
  51. package/skills/wix-headless-replatform/SKILL.md +116 -0
  52. package/skills/wix-headless-replatform/agents/openai.yaml +6 -0
  53. package/skills/wix-headless-replatform/references/control-and-visual-fidelity-contract.md +43 -0
  54. package/skills/wix-headless-replatform/references/extraction-schema.md +187 -0
  55. package/skills/wix-headless-replatform/references/interaction-runtime-contract.md +137 -0
  56. package/skills/wix-headless-replatform/references/layout-blueprint-contract.md +41 -0
  57. package/skills/wix-headless-replatform/references/output-contract.md +93 -0
  58. package/skills/wix-headless-replatform/references/post-build-gap-contract.md +125 -0
  59. package/skills/wix-headless-replatform/references/qa-checklist.md +83 -0
  60. package/skills/wix-headless-replatform/references/safety.md +19 -0
  61. package/skills/wix-headless-replatform/references/ui-normalization-contract.md +70 -0
  62. package/skills/wix-headless-replatform/references/workflow.md +157 -0
  63. package/skills/wix-headless-replatform/registry/README.md +20 -0
  64. package/skills/wix-headless-replatform/registry/approvals/button/shadcn-default-ac60ef5.json +28 -0
  65. package/skills/wix-headless-replatform/registry/items/button/shadcn-default-ac60ef5/button.tsx +54 -0
  66. package/skills/wix-headless-replatform/registry/items/button/shadcn-default-ac60ef5/capabilities.json +60 -0
  67. package/skills/wix-headless-replatform/registry/items/button/shadcn-default-ac60ef5/contract.md +48 -0
  68. package/skills/wix-headless-replatform/registry/licenses/shadcn-ui-ac60ef5-MIT.txt +21 -0
  69. package/skills/wix-headless-replatform/registry/registry.json +36 -0
  70. package/skills/wix-headless-replatform/resources/rp-facelift/SKILL.md +32 -0
  71. package/skills/wix-headless-replatform/resources/rp-project-context/SKILL.md +62 -0
  72. package/skills/wix-headless-replatform/resources/rp-qa-gap-loop/SKILL.md +64 -0
  73. package/skills/wix-headless-replatform/resources/rp-source-evidence/SKILL.md +42 -0
  74. package/skills/wix-headless-replatform/resources/rp-ui-implementation/SKILL.md +73 -0
  75. package/skills/wix-headless-replatform/schemas/build-plan.schema.json +10 -0
  76. package/skills/wix-headless-replatform/schemas/candidate.schema.json +19 -0
  77. package/skills/wix-headless-replatform/schemas/common.schema.json +33 -0
  78. package/skills/wix-headless-replatform/schemas/decision-patch.schema.json +10 -0
  79. package/skills/wix-headless-replatform/schemas/decision-request.schema.json +10 -0
  80. package/skills/wix-headless-replatform/schemas/extraction-gap.schema.json +28 -0
  81. package/skills/wix-headless-replatform/schemas/extraction-manifest.schema.json +34 -0
  82. package/skills/wix-headless-replatform/schemas/final-report.schema.json +10 -0
  83. package/skills/wix-headless-replatform/schemas/foundation.schema.json +11 -0
  84. package/skills/wix-headless-replatform/schemas/metadata.schema.json +11 -0
  85. package/skills/wix-headless-replatform/schemas/observation-packet.schema.json +17 -0
  86. package/skills/wix-headless-replatform/schemas/page-capture.schema.json +11 -0
  87. package/skills/wix-headless-replatform/schemas/page-resolution.schema.json +33 -0
  88. package/skills/wix-headless-replatform/schemas/shared-chrome.schema.json +11 -0
  89. package/skills/wix-headless-replatform/schemas/unit.schema.json +40 -0
  90. package/skills/wix-headless-replatform/scripts/assemble-extraction.mjs +844 -0
  91. package/skills/wix-headless-replatform/scripts/benchmark-extraction.mjs +163 -0
  92. package/skills/wix-headless-replatform/scripts/browser-extraction-preflight.mjs +42 -0
  93. package/skills/wix-headless-replatform/scripts/build-extracted-homepage.mjs +228 -0
  94. package/skills/wix-headless-replatform/scripts/clear-wix-starter.mjs +87 -0
  95. package/skills/wix-headless-replatform/scripts/create-wix-project.mjs +505 -0
  96. package/skills/wix-headless-replatform/scripts/discover-urls.mjs +142 -0
  97. package/skills/wix-headless-replatform/scripts/extract-assets.mjs +140 -0
  98. package/skills/wix-headless-replatform/scripts/extract-design-system.mjs +637 -0
  99. package/skills/wix-headless-replatform/scripts/extract-interactions.mjs +1903 -0
  100. package/skills/wix-headless-replatform/scripts/extract-page.mjs +2461 -0
  101. package/skills/wix-headless-replatform/scripts/extract-seo.mjs +25 -0
  102. package/skills/wix-headless-replatform/scripts/extract-tokens.mjs +63 -0
  103. package/skills/wix-headless-replatform/scripts/finalize-extraction-report.mjs +126 -0
  104. package/skills/wix-headless-replatform/scripts/finalize-gap-review.mjs +180 -0
  105. package/skills/wix-headless-replatform/scripts/finalize-gap-review.test.mjs +98 -0
  106. package/skills/wix-headless-replatform/scripts/finalize-migration-website.mjs +103 -0
  107. package/skills/wix-headless-replatform/scripts/generate-control-state-contract.mjs +176 -0
  108. package/skills/wix-headless-replatform/scripts/generate-design-md.mjs +261 -0
  109. package/skills/wix-headless-replatform/scripts/generate-extraction-validators.mjs +25 -0
  110. package/skills/wix-headless-replatform/scripts/generate-invoice-download-backend.mjs +342 -0
  111. package/skills/wix-headless-replatform/scripts/generate-layout-blueprint.mjs +140 -0
  112. package/skills/wix-headless-replatform/scripts/generate-repeater-cms.mjs +22 -0
  113. package/skills/wix-headless-replatform/scripts/generate-route-plan.mjs +143 -0
  114. package/skills/wix-headless-replatform/scripts/generate-scene-contract.mjs +288 -0
  115. package/skills/wix-headless-replatform/scripts/generate-ui-normalization.mjs +266 -0
  116. package/skills/wix-headless-replatform/scripts/generate-visual-assets.mjs +111 -0
  117. package/skills/wix-headless-replatform/scripts/lib/browser-tooling.mjs +570 -0
  118. package/skills/wix-headless-replatform/scripts/lib/common.mjs +329 -0
  119. package/skills/wix-headless-replatform/scripts/lib/component-capabilities.mjs +121 -0
  120. package/skills/wix-headless-replatform/scripts/lib/component-registry.mjs +282 -0
  121. package/skills/wix-headless-replatform/scripts/lib/content-boundary.mjs +61 -0
  122. package/skills/wix-headless-replatform/scripts/lib/extraction-contract.mjs +374 -0
  123. package/skills/wix-headless-replatform/scripts/lib/extraction-detectors.mjs +243 -0
  124. package/skills/wix-headless-replatform/scripts/lib/extraction-validators.generated.mjs +1139 -0
  125. package/skills/wix-headless-replatform/scripts/lib/font-contract.mjs +326 -0
  126. package/skills/wix-headless-replatform/scripts/lib/frontend-automation-state.mjs +184 -0
  127. package/skills/wix-headless-replatform/scripts/lib/gap-analysis.mjs +396 -0
  128. package/skills/wix-headless-replatform/scripts/lib/html-extract.mjs +168 -0
  129. package/skills/wix-headless-replatform/scripts/lib/interaction-bootstrap.mjs +10 -0
  130. package/skills/wix-headless-replatform/scripts/lib/interaction-runtime.mjs +194 -0
  131. package/skills/wix-headless-replatform/scripts/lib/interaction-timeline.mjs +348 -0
  132. package/skills/wix-headless-replatform/scripts/lib/layout-blueprint/classifiers.mjs +141 -0
  133. package/skills/wix-headless-replatform/scripts/lib/layout-blueprint/geometry.mjs +43 -0
  134. package/skills/wix-headless-replatform/scripts/lib/layout-blueprint/vocabulary.mjs +18 -0
  135. package/skills/wix-headless-replatform/scripts/lib/ui-normalization.css +64 -0
  136. package/skills/wix-headless-replatform/scripts/optimize-svg.mjs +31 -0
  137. package/skills/wix-headless-replatform/scripts/post-build-gap.mjs +358 -0
  138. package/skills/wix-headless-replatform/scripts/provision-repeater-cms.mjs +25 -0
  139. package/skills/wix-headless-replatform/scripts/record-gap-decision.mjs +39 -0
  140. package/skills/wix-headless-replatform/scripts/replay-extraction.mjs +107 -0
  141. package/skills/wix-headless-replatform/scripts/resolve-home-page.mjs +96 -0
  142. package/skills/wix-headless-replatform/scripts/site-clone.mjs +658 -0
  143. package/skills/wix-headless-replatform/scripts/validate-font-contract.mjs +29 -0
  144. package/skills/wix-headless-replatform/scripts/verify-interactions.mjs +669 -0
  145. package/skills/wix-headless-replatform/scripts/visual-qa.mjs +370 -0
  146. package/skills/wix-headless-templates/donations/INSTRUCTIONS.md +37 -24
  147. package/skills/wix-headless-templates/donations/app/components/donations/DonateForm.tsx +49 -23
  148. package/skills/wix-headless-templates/donations/app/components/donations/DonationReceipt.tsx +6 -5
  149. package/skills/wix-headless-templates/donations/app/components/donations/GoalProgress.tsx +5 -5
  150. package/skills/wix-headless-templates/donations/app/wix/donations/campaigns.ts +4 -4
  151. package/skills/wix-headless-templates/donations/app/wix/donations/donate.ts +7 -3
  152. package/skills/wix-headless-templates/donations/app/wix/donations/donation-store.ts +2 -2
  153. package/skills/wix-headless-templates/donations/app/wix/donations/donations-core.ts +12 -9
  154. package/skills/wix-headless-templates/donations/project/src/components/donations/DonateForm.tsx +49 -23
  155. package/skills/wix-headless-templates/donations/project/src/components/donations/DonationReceipt.tsx +6 -5
  156. package/skills/wix-headless-templates/donations/project/src/components/donations/GoalProgress.tsx +5 -5
  157. package/skills/wix-headless-templates/donations/project/src/wix/donations/campaigns.ts +4 -4
  158. package/skills/wix-headless-templates/donations/project/src/wix/donations/donate.ts +7 -3
  159. package/skills/wix-headless-templates/donations/project/src/wix/donations/donation-store.ts +2 -2
  160. package/skills/wix-headless-templates/donations/project/src/wix/donations/donations-core.ts +12 -9
  161. package/skills/wix-headless-templates/donations/rest/campaigns.ts +4 -4
  162. package/skills/wix-headless-templates/donations/rest/donate.ts +7 -3
  163. package/skills/wix-headless-templates/donations/seed/SEED.md +12 -7
  164. package/skills/wix-headless-templates/donations/tests/donation-contract.test.mjs +71 -0
  165. package/skills/wix-headless-templates/donations/tests/donation-form.test.cjs +137 -0
  166. package/skills/wix-headless-templates/restaurants/INSTRUCTIONS.md +28 -14
  167. package/skills/wix-headless-templates/restaurants/app/components/restaurants/OrderCartDrawer.tsx +87 -5
  168. package/skills/wix-headless-templates/restaurants/app/hooks/restaurants/useOrderCart.ts +9 -1
  169. package/skills/wix-headless-templates/restaurants/app/wix/restaurants/order-store.ts +33 -4
  170. package/skills/wix-headless-templates/restaurants/app/wix/restaurants/ordering-core.ts +100 -5
  171. package/skills/wix-headless-templates/restaurants/app/wix/restaurants/ordering.ts +101 -15
  172. package/skills/wix-headless-templates/restaurants/app/wix/restaurants/types.ts +35 -1
  173. package/skills/wix-headless-templates/restaurants/project/src/components/restaurants/OrderCartDrawer.tsx +87 -5
  174. package/skills/wix-headless-templates/restaurants/project/src/hooks/restaurants/useOrderCart.ts +9 -1
  175. package/skills/wix-headless-templates/restaurants/project/src/wix/restaurants/order-store.ts +33 -4
  176. package/skills/wix-headless-templates/restaurants/project/src/wix/restaurants/ordering-core.ts +100 -5
  177. package/skills/wix-headless-templates/restaurants/project/src/wix/restaurants/ordering.ts +101 -15
  178. package/skills/wix-headless-templates/restaurants/project/src/wix/restaurants/types.ts +35 -1
  179. package/skills/wix-headless-templates/restaurants/rest/ordering.ts +91 -14
  180. package/skills/wix-import/SKILL.md +90 -0
  181. package/skills/wix-manage/SKILL.md +38 -10
  182. package/skills/wix-manage/references/automations/actions.md +11 -11
  183. package/skills/wix-manage/references/automations/activation-status.md +6 -9
  184. package/skills/wix-manage/references/automations/api-catalog.md +48 -32
  185. package/skills/wix-manage/references/automations/automation-model.md +97 -22
  186. package/skills/wix-manage/references/automations/bracket-expressions.md +7 -7
  187. package/skills/wix-manage/references/automations/build-and-manage-automations.md +140 -309
  188. package/skills/wix-manage/references/automations/conditions.md +187 -0
  189. package/skills/wix-manage/references/automations/schemas-and-scheduling.md +191 -0
  190. package/skills/wix-manage/references/automations/special-actions.md +210 -0
  191. package/skills/wix-manage/references/automations/triggers.md +47 -10
  192. package/skills/wix-manage/references/automations/validation-and-verification.md +320 -0
  193. package/skills/wix-manage/references/custom-apps/use-app-tools.md +124 -0
  194. package/skills/wix-manage/references/dashboard-navigation/dashboard-navigation.md +2 -1
  195. package/skills/wix-manage/references/dashboard-navigation/routes.json +249 -0
  196. package/skills/wix-manage/references/donations/create-and-update-donation-campaigns.md +139 -0
  197. package/skills/wix-manage/references/events/create-wix-event.md +136 -73
  198. package/skills/wix-manage/references/events/events-dashboard-navigation.md +81 -0
  199. package/skills/wix-manage/references/events/manage-event-guests.md +366 -0
  200. package/skills/wix-manage/references/events/manage-event-tickets.md +293 -0
  201. package/skills/wix-manage/references/events/manage-wix-events.md +313 -55
  202. package/skills/wix-manage/references/sites/manage-oauth-apps.md +1 -0
  203. package/skills/wix-manage/references/sites/upload-static-site.md +127 -8
  204. package/skills/wix-manage/references/stores/update-product-with-options.md +58 -2
  205. package/skills/wix-replatform/.publish-manifest.json +5 -5
  206. package/skills/wix-replatform/CLI.md +162 -0
  207. package/skills/wix-replatform/README.md +52 -0
  208. package/skills/wix-replatform/SKILL.md +229 -684
  209. package/skills/wix-replatform/console/account.css +1 -0
  210. package/skills/wix-replatform/console/app.css +1 -0
  211. package/skills/wix-replatform/console/app.js +117 -0
  212. package/skills/wix-replatform/console/index.html +4 -0
  213. package/skills/wix-replatform/console/json.css +11 -0
  214. package/skills/wix-replatform/lib/agent-adapters/index.js +71 -0
  215. package/skills/wix-replatform/lib/app-page-routing.js +75 -0
  216. package/skills/wix-replatform/lib/artifact-freshness.js +11 -1
  217. package/skills/wix-replatform/lib/blocked-data-requests.js +427 -0
  218. package/skills/wix-replatform/lib/census-reconciliation.js +426 -0
  219. package/skills/wix-replatform/lib/completion-report.js +1393 -13
  220. package/skills/wix-replatform/lib/config-env.js +85 -11
  221. package/skills/wix-replatform/lib/confirmed-pictures.js +212 -0
  222. package/skills/wix-replatform/lib/dependency-readiness.js +423 -0
  223. package/skills/wix-replatform/lib/dependency-recovery.js +21 -0
  224. package/skills/wix-replatform/lib/destination-choice.js +163 -0
  225. package/skills/wix-replatform/lib/destination-handoff.js +62 -0
  226. package/skills/wix-replatform/lib/destination-http.js +81 -0
  227. package/skills/wix-replatform/lib/destination-route.js +98 -0
  228. package/skills/wix-replatform/lib/destination.js +919 -0
  229. package/skills/wix-replatform/lib/discovered-census.js +231 -0
  230. package/skills/wix-replatform/lib/discovered-set-status.js +255 -0
  231. package/skills/wix-replatform/lib/document-url-guard.js +203 -0
  232. package/skills/wix-replatform/lib/entity-qa-attestation.js +339 -0
  233. package/skills/wix-replatform/lib/entity-verification.js +18 -0
  234. package/skills/wix-replatform/lib/execution-capability.js +120 -0
  235. package/skills/wix-replatform/lib/execution-log.js +170 -0
  236. package/skills/wix-replatform/lib/execution-manifest.js +20 -2
  237. package/skills/wix-replatform/lib/execution-state.js +22 -0
  238. package/skills/wix-replatform/lib/expectations-ledger.js +96 -0
  239. package/skills/wix-replatform/lib/generated-plugin-handoff.js +140 -0
  240. package/skills/wix-replatform/lib/import-readiness-contract.js +23 -0
  241. package/skills/wix-replatform/lib/import-recovery.js +800 -6
  242. package/skills/wix-replatform/lib/imported-redirect-targets.js +608 -0
  243. package/skills/wix-replatform/lib/internal-link-reconciliation.js +166 -0
  244. package/skills/wix-replatform/lib/link-audit.js +173 -0
  245. package/skills/wix-replatform/lib/link-census.js +194 -0
  246. package/skills/wix-replatform/lib/link-plan.js +148 -0
  247. package/skills/wix-replatform/lib/link-verification.js +274 -0
  248. package/skills/wix-replatform/lib/link-write-journal.js +42 -0
  249. package/skills/wix-replatform/lib/local-state.js +869 -30
  250. package/skills/wix-replatform/lib/member-completion.js +658 -0
  251. package/skills/wix-replatform/lib/member-crosswalk.js +69 -0
  252. package/skills/wix-replatform/lib/member-outcome-registry.js +265 -0
  253. package/skills/wix-replatform/lib/migration-completion.js +311 -0
  254. package/skills/wix-replatform/lib/operator-identity.js +47 -0
  255. package/skills/wix-replatform/lib/orchestration-decisions.js +248 -0
  256. package/skills/wix-replatform/lib/orchestration-preflight.js +401 -19
  257. package/skills/wix-replatform/lib/orchestration-router.js +608 -32
  258. package/skills/wix-replatform/lib/orchestration-state.js +188 -12
  259. package/skills/wix-replatform/lib/order-reference-readiness.js +30 -0
  260. package/skills/wix-replatform/lib/payments-outcome-registry.js +200 -0
  261. package/skills/wix-replatform/lib/plugin-cms-fallback.js +618 -0
  262. package/skills/wix-replatform/lib/preservation-approval-stamp.js +54 -0
  263. package/skills/wix-replatform/lib/product-media-deferral.js +96 -0
  264. package/skills/wix-replatform/lib/progress-log.js +19 -3
  265. package/skills/wix-replatform/lib/read-back-approval.js +184 -0
  266. package/skills/wix-replatform/lib/read-back-comparison.js +136 -0
  267. package/skills/wix-replatform/lib/read-back-contract.js +243 -0
  268. package/skills/wix-replatform/lib/read-back-extract.js +118 -0
  269. package/skills/wix-replatform/lib/read-back-gate.js +734 -0
  270. package/skills/wix-replatform/lib/read-back-reads.js +94 -0
  271. package/skills/wix-replatform/lib/read-back-state.js +107 -0
  272. package/skills/wix-replatform/lib/read-back-summary.js +81 -0
  273. package/skills/wix-replatform/lib/rerun-conflicts.js +334 -0
  274. package/skills/wix-replatform/lib/run-controller.js +401 -0
  275. package/skills/wix-replatform/lib/seo-outcome-registry.js +45 -0
  276. package/skills/wix-replatform/lib/setup-verification.js +148 -0
  277. package/skills/wix-replatform/lib/source-access.js +231 -0
  278. package/skills/wix-replatform/lib/source-coverage.js +1130 -0
  279. package/skills/wix-replatform/lib/source-secrets.js +277 -0
  280. package/skills/wix-replatform/lib/structured-link-patch.js +59 -0
  281. package/skills/wix-replatform/lib/task-state.js +110 -0
  282. package/skills/wix-replatform/lib/url-preservation-report.js +319 -0
  283. package/skills/wix-replatform/lib/url-preservation-state.js +349 -1
  284. package/skills/wix-replatform/lib/url-redirect-plan.js +728 -0
  285. package/skills/wix-replatform/lib/url-verification.js +315 -0
  286. package/skills/wix-replatform/lib/user-action-effects.js +42 -0
  287. package/skills/wix-replatform/lib/user-actions.js +264 -0
  288. package/skills/wix-replatform/lib/website-handoff.js +1220 -0
  289. package/skills/wix-replatform/lib/wix-client-callers.js +54 -0
  290. package/skills/wix-replatform/lib/wix-dashboard-url.js +35 -0
  291. package/skills/wix-replatform/lib/write-verification-state.js +92 -0
  292. package/skills/wix-replatform/lib/write-verification.js +274 -0
  293. package/skills/wix-replatform/resources/rp-destination/SKILL.md +137 -0
  294. package/skills/wix-replatform/resources/rp-discovery/SKILL.md +189 -16
  295. package/skills/wix-replatform/resources/rp-discovery/scripts/promote-source-coverage.js +47 -0
  296. package/skills/wix-replatform/resources/rp-execute-import/SKILL.md +869 -29
  297. package/skills/wix-replatform/resources/rp-execute-import/scripts/shipping-estimate-audit.js +70 -0
  298. package/skills/wix-replatform/resources/rp-execute-setup/SKILL.md +195 -29
  299. package/skills/wix-replatform/resources/rp-execute-setup/scripts/execute-setup.js +442 -0
  300. package/skills/wix-replatform/resources/rp-execution-policy/SKILL.md +112 -0
  301. package/skills/wix-replatform/resources/rp-execution-policy/lib/coupon-currency-plan-report.js +143 -0
  302. package/skills/wix-replatform/resources/rp-execution-policy/lib/payments-report.js +244 -0
  303. package/skills/wix-replatform/resources/rp-import-codegen/SKILL.md +2082 -38
  304. package/skills/wix-replatform/resources/rp-mapper/SKILL.md +528 -11
  305. package/skills/wix-replatform/resources/rp-mapper/lib/structure-bridge-planning.js +303 -0
  306. package/skills/wix-replatform/resources/rp-mapper/scripts/coverage-reconcile.js +88 -0
  307. package/skills/wix-replatform/resources/rp-mapper/wix-business-solutions-taxonomy.md +6 -5
  308. package/skills/wix-replatform/resources/rp-preflight/SKILL.md +40 -0
  309. package/skills/wix-replatform/resources/rp-project-intake/SKILL.md +55 -0
  310. package/skills/wix-replatform/resources/rp-quick-runtime/SKILL.md +60 -0
  311. package/skills/wix-replatform/resources/rp-quick-runtime/lib/import-progress.js +89 -0
  312. package/skills/wix-replatform/resources/rp-quick-runtime/lib/limit-recovery.js +36 -0
  313. package/skills/wix-replatform/resources/rp-quick-shopify/SKILL.md +123 -0
  314. package/skills/wix-replatform/resources/rp-quick-shopify/quick-mode.json +124 -0
  315. package/skills/wix-replatform/resources/rp-quick-shopify/scripts/quick-mode.js +522 -0
  316. package/skills/wix-replatform/resources/rp-quick-woocommerce/SKILL.md +56 -0
  317. package/skills/wix-replatform/resources/rp-quick-woocommerce/quick-mode.json +91 -0
  318. package/skills/wix-replatform/resources/rp-quick-woocommerce/scripts/quick-mode.js +255 -0
  319. package/skills/wix-replatform/resources/rp-setup-discovery/SKILL.md +80 -10
  320. package/skills/wix-replatform/resources/rp-setup-discovery/scripts/quick-setup-plan.js +44 -0
  321. package/skills/wix-replatform/resources/rp-source-csv/SKILL.md +3 -0
  322. package/skills/wix-replatform/resources/rp-source-csv/lib/csv-fileset.js +0 -0
  323. package/skills/wix-replatform/resources/rp-source-csv/scripts/csv-discovery.js +21 -1
  324. package/skills/wix-replatform/resources/rp-source-inputs/SKILL.md +66 -0
  325. package/skills/wix-replatform/resources/rp-source-sfcc/SKILL.md +153 -0
  326. package/skills/wix-replatform/resources/rp-source-shopify/SKILL.md +394 -0
  327. package/skills/wix-replatform/resources/rp-source-shopify/lib/discovery-reads.js +200 -0
  328. package/skills/wix-replatform/resources/rp-source-shopify/lib/import-findings.js +34 -0
  329. package/skills/wix-replatform/resources/rp-source-shopify/lib/shopify-bulk-sqlite-worker.js +44 -0
  330. package/skills/wix-replatform/resources/rp-source-shopify/lib/shopify-bulk.js +1470 -0
  331. package/skills/wix-replatform/resources/rp-source-shopify/lib/shopify-cli.js +446 -0
  332. package/skills/wix-replatform/resources/rp-source-shopify/lib/shopify-gift-cards.js +474 -0
  333. package/skills/wix-replatform/resources/rp-source-shopify/lib/shopify-protected-fields.js +879 -0
  334. package/skills/wix-replatform/resources/rp-source-shopify/lib/shopify-session.js +426 -0
  335. package/skills/wix-replatform/resources/rp-source-shopify/queries/articles.graphql +24 -0
  336. package/skills/wix-replatform/resources/rp-source-shopify/queries/blogs.graphql +18 -0
  337. package/skills/wix-replatform/resources/rp-source-shopify/queries/collections.graphql +40 -0
  338. package/skills/wix-replatform/resources/rp-source-shopify/queries/customers.graphql +76 -0
  339. package/skills/wix-replatform/resources/rp-source-shopify/queries/delivery-profiles.graphql +51 -0
  340. package/skills/wix-replatform/resources/rp-source-shopify/queries/discounts.graphql +35 -0
  341. package/skills/wix-replatform/resources/rp-source-shopify/queries/files.graphql +20 -0
  342. package/skills/wix-replatform/resources/rp-source-shopify/queries/gift-cards.graphql +18 -0
  343. package/skills/wix-replatform/resources/rp-source-shopify/queries/inventory.graphql +31 -0
  344. package/skills/wix-replatform/resources/rp-source-shopify/queries/locations.graphql +21 -0
  345. package/skills/wix-replatform/resources/rp-source-shopify/queries/menus.graphql +21 -0
  346. package/skills/wix-replatform/resources/rp-source-shopify/queries/metaobjects.graphql +30 -0
  347. package/skills/wix-replatform/resources/rp-source-shopify/queries/orders-core.graphql +150 -0
  348. package/skills/wix-replatform/resources/rp-source-shopify/queries/orders-fulfillments.graphql +36 -0
  349. package/skills/wix-replatform/resources/rp-source-shopify/queries/orders-refunds.graphql +48 -0
  350. package/skills/wix-replatform/resources/rp-source-shopify/queries/pages.graphql +20 -0
  351. package/skills/wix-replatform/resources/rp-source-shopify/queries/probe-customers-address.graphql +8 -0
  352. package/skills/wix-replatform/resources/rp-source-shopify/queries/probe-customers-email.graphql +6 -0
  353. package/skills/wix-replatform/resources/rp-source-shopify/queries/probe-customers-name.graphql +8 -0
  354. package/skills/wix-replatform/resources/rp-source-shopify/queries/probe-customers-phone.graphql +8 -0
  355. package/skills/wix-replatform/resources/rp-source-shopify/queries/probe-orders-address.graphql +12 -0
  356. package/skills/wix-replatform/resources/rp-source-shopify/queries/probe-orders-email.graphql +6 -0
  357. package/skills/wix-replatform/resources/rp-source-shopify/queries/probe-orders-name.graphql +13 -0
  358. package/skills/wix-replatform/resources/rp-source-shopify/queries/probe-orders-phone.graphql +13 -0
  359. package/skills/wix-replatform/resources/rp-source-shopify/queries/products.graphql +73 -0
  360. package/skills/wix-replatform/resources/rp-source-shopify/queries/url-redirects.graphql +13 -0
  361. package/skills/wix-replatform/resources/rp-source-shopify/scripts/shopify-discovery.js +693 -0
  362. package/skills/wix-replatform/resources/rp-source-shopify/scripts/shopify-extract.js +283 -0
  363. package/skills/wix-replatform/resources/rp-source-shopify/scripts/shopify-gift-card-remedy.js +191 -0
  364. package/skills/wix-replatform/resources/rp-source-shopify/scripts/shopify-session-check.js +115 -0
  365. package/skills/wix-replatform/resources/rp-source-shopify/source-schema.example.json +201 -0
  366. package/skills/wix-replatform/resources/rp-source-wordpress/PLUGIN-READ-PLANS.md +146 -0
  367. package/skills/wix-replatform/resources/rp-source-wordpress/SKILL.md +736 -22
  368. package/skills/wix-replatform/resources/rp-source-wordpress/lib/blocked-data-handlers.js +73 -0
  369. package/skills/wix-replatform/resources/rp-source-wordpress/lib/checkout-field-csv-parser.js +115 -0
  370. package/skills/wix-replatform/resources/rp-source-wordpress/lib/checkout-field-csv-parser.test-fixture.js +91 -0
  371. package/skills/wix-replatform/resources/rp-source-wordpress/lib/coupon-discovery.js +440 -0
  372. package/skills/wix-replatform/resources/rp-source-wordpress/lib/currency-gate.js +125 -0
  373. package/skills/wix-replatform/resources/rp-source-wordpress/lib/gateway-source-keys.js +252 -0
  374. package/skills/wix-replatform/resources/rp-source-wordpress/lib/gateway-source-keys.json +183 -0
  375. package/skills/wix-replatform/resources/rp-source-wordpress/lib/gateway-summary.mjs +333 -0
  376. package/skills/wix-replatform/resources/rp-source-wordpress/lib/invoice-discovery.js +426 -0
  377. package/skills/wix-replatform/resources/rp-source-wordpress/lib/plugin-disposition.js +400 -0
  378. package/skills/wix-replatform/resources/rp-source-wordpress/lib/plugin-entity-read-plan.js +633 -0
  379. package/skills/wix-replatform/resources/rp-source-wordpress/lib/plugin-knowledge.js +1127 -0
  380. package/skills/wix-replatform/resources/rp-source-wordpress/lib/refund-corroboration.js +194 -0
  381. package/skills/wix-replatform/resources/rp-source-wordpress/lib/refund-corroboration.json +100 -0
  382. package/skills/wix-replatform/resources/rp-source-wordpress/lib/sampled-ids-batch.js +157 -0
  383. package/skills/wix-replatform/resources/rp-source-wordpress/lib/secret-value-guard.js +234 -0
  384. package/skills/wix-replatform/resources/rp-source-wordpress/lib/store-currency.js +101 -0
  385. package/skills/wix-replatform/resources/rp-source-wordpress/lib/structure-bridge-reader.js +755 -0
  386. package/skills/wix-replatform/resources/rp-source-wordpress/lib/wix-wp-plugin-v2-client.js +1074 -0
  387. package/skills/wix-replatform/resources/rp-source-wordpress/lib/wix-wp-plugin-v2-client.test-fixture.js +149 -0
  388. package/skills/wix-replatform/resources/rp-source-wordpress/lib/woo-inventory.js +25 -0
  389. package/skills/wix-replatform/resources/rp-source-wordpress/lib/wp-http.js +204 -9
  390. package/skills/wix-replatform/resources/rp-source-wordpress/lib/wp-image-url.js +186 -0
  391. package/skills/wix-replatform/resources/rp-source-wordpress/lib/wp-plugin-detect.js +1422 -0
  392. package/skills/wix-replatform/resources/rp-source-wordpress/lib/wp-redirect-sources.js +343 -0
  393. package/skills/wix-replatform/resources/rp-source-wordpress/lib/wp-route-classifier.js +262 -26
  394. package/skills/wix-replatform/resources/rp-source-wordpress/lib/yoast-product-seo-evidence.js +272 -0
  395. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/README.md +373 -0
  396. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/advanced-custom-fields.json +101 -0
  397. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/back-in-stock-notifier-for-woocommerce.json +81 -0
  398. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/capabilities-pending-decision.json +128 -0
  399. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/cms-fallback-eligible.json +18 -0
  400. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/contact-form-7.json +104 -0
  401. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/facebook-for-woocommerce.json +44 -0
  402. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/fingerprint-aliases.json +49 -0
  403. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/free-gifts-for-woocommerce.json +94 -0
  404. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/index.json +863 -0
  405. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/mailpoet.json +52 -0
  406. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/membership-for-woocommerce.json +161 -0
  407. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/mwb-bookings-for-woocommerce.json +180 -0
  408. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/no-migration-needed.json +286 -0
  409. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/pricing-deals-for-woocommerce.json +79 -0
  410. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/product-extras-for-woocommerce.json +120 -0
  411. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/pw-woocommerce-gift-cards.json +128 -0
  412. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/requires-development.json +88 -0
  413. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/seo-by-rank-math.json +80 -0
  414. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/seriously-simple-podcasting.json +46 -0
  415. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/strong-testimonials.json +55 -0
  416. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/subscriptions-for-woocommerce.json +99 -0
  417. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/tablepress.json +55 -0
  418. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/the-events-calendar.json +160 -0
  419. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/ti-woocommerce-wishlist.json +81 -0
  420. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/woo-checkout-field-editor-pro.json +60 -0
  421. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/woo-discount-rules.json +74 -0
  422. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/woo-product-bundle.json +75 -0
  423. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/woocommerce-gift-cards.json +60 -0
  424. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/woocommerce-other-payment-gateway.json +44 -0
  425. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/woocommerce-pdf-invoices-packing-slips.json +57 -0
  426. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/woocommerce-product-addon.json +141 -0
  427. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/woocommerce-progressive-discounts.json +54 -0
  428. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/wordpress-seo.json +158 -0
  429. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/wpc-composite-products.json +97 -0
  430. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/wpforms-lite.json +86 -0
  431. package/skills/wix-replatform/resources/rp-source-wordpress/scripts/capture-store-settings.js +90 -0
  432. package/skills/wix-replatform/resources/rp-source-wordpress/scripts/plugin-knowledge-validate.js +51 -0
  433. package/skills/wix-replatform/resources/rp-source-wordpress/scripts/plugin-knowledge.js +103 -0
  434. package/skills/wix-replatform/resources/rp-source-wordpress/scripts/wp-discovery.js +1668 -33
  435. package/skills/wix-replatform/resources/rp-source-wordpress/scripts/wp-plugin-inventory.js +273 -0
  436. package/skills/wix-replatform/resources/rp-source-wordpress/secret-value-patterns.v1.json +11 -0
  437. package/skills/wix-replatform/resources/rp-source-wordpress/sub-parts.json +13 -0
  438. package/skills/wix-replatform/resources/rp-target-wix/SKILL.md +446 -32
  439. package/skills/wix-replatform/resources/rp-target-wix/app-page-routing-capabilities.json +36 -0
  440. package/skills/wix-replatform/resources/rp-target-wix/domains/README.md +38 -2
  441. package/skills/wix-replatform/resources/rp-target-wix/domains/SERVICE-PLUGINS.md +322 -0
  442. package/skills/wix-replatform/resources/rp-target-wix/domains/blog/docs-survey.json +70 -0
  443. package/skills/wix-replatform/resources/rp-target-wix/domains/blog/domain.json +19 -9
  444. package/skills/wix-replatform/resources/rp-target-wix/domains/blog/entities/category.json +42 -8
  445. package/skills/wix-replatform/resources/rp-target-wix/domains/blog/entities/comment.json +54 -8
  446. package/skills/wix-replatform/resources/rp-target-wix/domains/blog/entities/post.json +66 -10
  447. package/skills/wix-replatform/resources/rp-target-wix/domains/blog/entities/tag.json +38 -7
  448. package/skills/wix-replatform/resources/rp-target-wix/domains/bookings/docs-survey.json +332 -0
  449. package/skills/wix-replatform/resources/rp-target-wix/domains/bookings/domain.json +75 -0
  450. package/skills/wix-replatform/resources/rp-target-wix/domains/bookings/entities/booking.json +181 -0
  451. package/skills/wix-replatform/resources/rp-target-wix/domains/bookings/entities/service-add-on.json +130 -0
  452. package/skills/wix-replatform/resources/rp-target-wix/domains/bookings/entities/service.json +180 -0
  453. package/skills/wix-replatform/resources/rp-target-wix/domains/cms/docs-survey.json +161 -0
  454. package/skills/wix-replatform/resources/rp-target-wix/domains/cms/domain.json +15 -4
  455. package/skills/wix-replatform/resources/rp-target-wix/domains/cms/entities/collection.json +39 -7
  456. package/skills/wix-replatform/resources/rp-target-wix/domains/cms/entities/data-item.json +47 -8
  457. package/skills/wix-replatform/resources/rp-target-wix/domains/cms/entities/reference.json +7 -1
  458. package/skills/wix-replatform/resources/rp-target-wix/domains/crm/docs-survey.json +204 -0
  459. package/skills/wix-replatform/resources/rp-target-wix/domains/crm/domain.json +17 -4
  460. package/skills/wix-replatform/resources/rp-target-wix/domains/crm/entities/contact.json +159 -15
  461. package/skills/wix-replatform/resources/rp-target-wix/domains/crm/entities/email-subscription.json +80 -12
  462. package/skills/wix-replatform/resources/rp-target-wix/domains/crm/entities/label.json +48 -8
  463. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/docs-survey.json +306 -0
  464. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/domain.json +30 -7
  465. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/delivery-profile.json +171 -0
  466. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/discount-rule.json +182 -0
  467. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order-applied-discount.json +129 -0
  468. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order-invoice-document.json +192 -0
  469. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order-line-item-options.json +123 -0
  470. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order-line-item.json +60 -12
  471. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order-merchant-note.json +112 -0
  472. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order-payment.json +191 -0
  473. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order-refund.json +140 -13
  474. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order.json +383 -18
  475. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/pickup-location.json +122 -0
  476. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/shipping-option.json +98 -0
  477. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/wishlist-item.json +86 -0
  478. package/skills/wix-replatform/resources/rp-target-wix/domains/events/docs-survey.json +128 -0
  479. package/skills/wix-replatform/resources/rp-target-wix/domains/events/domain.json +38 -0
  480. package/skills/wix-replatform/resources/rp-target-wix/domains/events/entities/category.json +73 -0
  481. package/skills/wix-replatform/resources/rp-target-wix/domains/events/entities/event.json +107 -0
  482. package/skills/wix-replatform/resources/rp-target-wix/domains/events/entities/registration-form.json +89 -0
  483. package/skills/wix-replatform/resources/rp-target-wix/domains/events/entities/rsvp.json +118 -0
  484. package/skills/wix-replatform/resources/rp-target-wix/domains/events/entities/ticket-definition.json +93 -0
  485. package/skills/wix-replatform/resources/rp-target-wix/domains/forms/docs-survey.json +86 -0
  486. package/skills/wix-replatform/resources/rp-target-wix/domains/forms/domain.json +56 -0
  487. package/skills/wix-replatform/resources/rp-target-wix/domains/forms/entities/form-submission.json +121 -0
  488. package/skills/wix-replatform/resources/rp-target-wix/domains/forms/entities/form.json +134 -0
  489. package/skills/wix-replatform/resources/rp-target-wix/domains/gift-cards/docs-survey.json +41 -0
  490. package/skills/wix-replatform/resources/rp-target-wix/domains/gift-cards/domain.json +50 -0
  491. package/skills/wix-replatform/resources/rp-target-wix/domains/gift-cards/entities/gift-card-product.json +87 -0
  492. package/skills/wix-replatform/resources/rp-target-wix/domains/gift-cards/entities/gift-card.json +130 -0
  493. package/skills/wix-replatform/resources/rp-target-wix/domains/index.json +914 -24
  494. package/skills/wix-replatform/resources/rp-target-wix/domains/media/docs-survey.json +41 -0
  495. package/skills/wix-replatform/resources/rp-target-wix/domains/media/domain.json +14 -4
  496. package/skills/wix-replatform/resources/rp-target-wix/domains/media/entities/media-file.json +45 -8
  497. package/skills/wix-replatform/resources/rp-target-wix/domains/media/entities/media-folder.json +41 -7
  498. package/skills/wix-replatform/resources/rp-target-wix/domains/members/docs-survey.json +119 -0
  499. package/skills/wix-replatform/resources/rp-target-wix/domains/members/domain.json +13 -3
  500. package/skills/wix-replatform/resources/rp-target-wix/domains/members/entities/badge.json +41 -7
  501. package/skills/wix-replatform/resources/rp-target-wix/domains/members/entities/custom-field.json +82 -0
  502. package/skills/wix-replatform/resources/rp-target-wix/domains/members/entities/member-about.json +77 -0
  503. package/skills/wix-replatform/resources/rp-target-wix/domains/members/entities/member.json +165 -13
  504. package/skills/wix-replatform/resources/rp-target-wix/domains/pricing-plans/docs-survey.json +41 -0
  505. package/skills/wix-replatform/resources/rp-target-wix/domains/pricing-plans/domain.json +46 -0
  506. package/skills/wix-replatform/resources/rp-target-wix/domains/pricing-plans/entities/plan-order.json +137 -0
  507. package/skills/wix-replatform/resources/rp-target-wix/domains/pricing-plans/entities/plan.json +133 -0
  508. package/skills/wix-replatform/resources/rp-target-wix/domains/sales-channels/docs-survey.json +7 -0
  509. package/skills/wix-replatform/resources/rp-target-wix/domains/sales-channels/domain.json +36 -0
  510. package/skills/wix-replatform/resources/rp-target-wix/domains/sales-channels/entities/amazon-shop-connect.json +71 -0
  511. package/skills/wix-replatform/resources/rp-target-wix/domains/sales-channels/entities/ebay-shop-connect.json +67 -0
  512. package/skills/wix-replatform/resources/rp-target-wix/domains/sales-channels/entities/google-youtube-connect.json +72 -0
  513. package/skills/wix-replatform/resources/rp-target-wix/domains/sales-channels/entities/meta-catalog-connect.json +74 -0
  514. package/skills/wix-replatform/resources/rp-target-wix/domains/sales-channels/entities/pinterest-catalog-connect.json +69 -0
  515. package/skills/wix-replatform/resources/rp-target-wix/domains/sales-channels/entities/tiktok-catalog-connect.json +61 -0
  516. package/skills/wix-replatform/resources/rp-target-wix/domains/schema.json +28 -1
  517. package/skills/wix-replatform/resources/rp-target-wix/domains/seo/docs-survey.json +109 -0
  518. package/skills/wix-replatform/resources/rp-target-wix/domains/seo/domain.json +73 -0
  519. package/skills/wix-replatform/resources/rp-target-wix/domains/seo/entities/item-seo-tags.json +148 -0
  520. package/skills/wix-replatform/resources/rp-target-wix/domains/seo/entities/seo-pattern.json +91 -0
  521. package/skills/wix-replatform/resources/rp-target-wix/domains/seo/entities/site-seo-tags.json +79 -0
  522. package/skills/wix-replatform/resources/rp-target-wix/domains/site/docs-survey.json +32 -0
  523. package/skills/wix-replatform/resources/rp-target-wix/domains/site/domain.json +49 -0
  524. package/skills/wix-replatform/resources/rp-target-wix/domains/site/entities/business-contact.json +103 -0
  525. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/docs-survey.json +203 -0
  526. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/domain.json +27 -10
  527. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/back-in-stock-request.json +119 -0
  528. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/brand.json +111 -0
  529. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/category.json +52 -8
  530. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/coupon.json +214 -10
  531. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/inventory-item.json +93 -13
  532. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/product-customization.json +111 -0
  533. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/product-tag.json +52 -10
  534. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/product.json +145 -14
  535. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/promotion.json +84 -0
  536. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/review.json +178 -0
  537. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/subscription-product.json +87 -0
  538. package/skills/wix-replatform/resources/rp-target-wix/domains/tax/docs-survey.json +106 -0
  539. package/skills/wix-replatform/resources/rp-target-wix/domains/tax/domain.json +64 -0
  540. package/skills/wix-replatform/resources/rp-target-wix/domains/tax/entities/manual-tax-mapping.json +119 -0
  541. package/skills/wix-replatform/resources/rp-target-wix/domains/tax/entities/tax-group.json +94 -0
  542. package/skills/wix-replatform/resources/rp-target-wix/domains/tax/entities/tax-region.json +113 -0
  543. package/skills/wix-replatform/resources/rp-target-wix/domains/tax/entities/tax-settings.json +65 -0
  544. package/skills/wix-replatform/resources/rp-target-wix/lib/blog-author-attribution.js +165 -0
  545. package/skills/wix-replatform/resources/rp-target-wix/lib/blog-post-build-client.js +60 -0
  546. package/skills/wix-replatform/resources/rp-target-wix/lib/blog-post-build-duplicates.js +201 -0
  547. package/skills/wix-replatform/resources/rp-target-wix/lib/blog-post-build-ledger.js +306 -0
  548. package/skills/wix-replatform/resources/rp-target-wix/lib/blog-post-build-report.js +229 -0
  549. package/skills/wix-replatform/resources/rp-target-wix/lib/blog-post-build-stage.js +55 -0
  550. package/skills/wix-replatform/resources/rp-target-wix/lib/blog-post-build.js +715 -0
  551. package/skills/wix-replatform/resources/rp-target-wix/lib/blog-post-media.js +567 -0
  552. package/skills/wix-replatform/resources/rp-target-wix/lib/bulk-results.js +73 -0
  553. package/skills/wix-replatform/resources/rp-target-wix/lib/contact-extended-fields.js +194 -0
  554. package/skills/wix-replatform/resources/rp-target-wix/lib/contact-import.js +133 -0
  555. package/skills/wix-replatform/resources/rp-target-wix/lib/contract-ledger.js +13 -4
  556. package/skills/wix-replatform/resources/rp-target-wix/lib/coupon-redemption-drift.js +491 -0
  557. package/skills/wix-replatform/resources/rp-target-wix/lib/customer-member.js +104 -0
  558. package/skills/wix-replatform/resources/rp-target-wix/lib/data-extension-schema.js +673 -0
  559. package/skills/wix-replatform/resources/rp-target-wix/lib/domain-knowledge.js +453 -2
  560. package/skills/wix-replatform/resources/rp-target-wix/lib/gift-card-balance-reconciliation.js +119 -0
  561. package/skills/wix-replatform/resources/rp-target-wix/lib/gift-card-build.js +566 -0
  562. package/skills/wix-replatform/resources/rp-target-wix/lib/gift-card-redemption-build.js +249 -0
  563. package/skills/wix-replatform/resources/rp-target-wix/lib/order-applied-discount-build.js +63 -0
  564. package/skills/wix-replatform/resources/rp-target-wix/lib/order-buyer.js +187 -0
  565. package/skills/wix-replatform/resources/rp-target-wix/lib/order-channel-build.js +64 -0
  566. package/skills/wix-replatform/resources/rp-target-wix/lib/order-history.js +1027 -0
  567. package/skills/wix-replatform/resources/rp-target-wix/lib/order-invoice-contract.js +90 -0
  568. package/skills/wix-replatform/resources/rp-target-wix/lib/order-payment-mapping.js +420 -0
  569. package/skills/wix-replatform/resources/rp-target-wix/lib/pricing-plan-definition.js +177 -0
  570. package/skills/wix-replatform/resources/rp-target-wix/lib/product-import.js +151 -0
  571. package/skills/wix-replatform/resources/rp-target-wix/lib/product-seo.js +723 -0
  572. package/skills/wix-replatform/resources/rp-target-wix/lib/seo-redirects.js +353 -0
  573. package/skills/wix-replatform/resources/rp-target-wix/lib/shipping-build.js +1437 -0
  574. package/skills/wix-replatform/resources/rp-target-wix/lib/shipping-option-stage.js +354 -0
  575. package/skills/wix-replatform/resources/rp-target-wix/lib/store-config-build.js +134 -0
  576. package/skills/wix-replatform/resources/rp-target-wix/lib/stores-category-attach.js +221 -0
  577. package/skills/wix-replatform/resources/rp-target-wix/lib/stores-inventory.js +248 -0
  578. package/skills/wix-replatform/resources/rp-target-wix/lib/stores-verification.js +86 -3
  579. package/skills/wix-replatform/resources/rp-target-wix/lib/tax-build.js +231 -0
  580. package/skills/wix-replatform/resources/rp-target-wix/lib/url-mapper-transport.js +28 -0
  581. package/skills/wix-replatform/resources/rp-target-wix/lib/value-utils.js +14 -0
  582. package/skills/wix-replatform/resources/rp-target-wix/lib/variant-correlation.js +34 -0
  583. package/skills/wix-replatform/resources/rp-target-wix/lib/wix-build.js +166 -15
  584. package/skills/wix-replatform/resources/rp-target-wix/lib/wix-target-spec.js +19 -2
  585. package/skills/wix-replatform/resources/rp-target-wix/lib/wix-writers.js +4079 -257
  586. package/skills/wix-replatform/resources/rp-target-wix/lib/write-verification.js +3 -0
  587. package/skills/wix-replatform/resources/rp-target-wix/scripts/docs-survey-sync.js +150 -0
  588. package/skills/wix-replatform/resources/rp-target-wix/scripts/domain-knowledge-validate.js +40 -2
  589. package/skills/wix-replatform/resources/rp-target-wix/scripts/verify-stores.js +6 -1
  590. package/skills/wix-replatform/resources/rp-telemetry/SKILL.md +148 -8
  591. package/skills/wix-replatform/resources/rp-telemetry/lib/bi-sink.js +83 -15
  592. package/skills/wix-replatform/resources/rp-telemetry/lib/telemetry-recorder.js +1119 -215
  593. package/skills/wix-replatform/resources/rp-telemetry/lib/transcript-digest.js +318 -0
  594. package/skills/wix-replatform/resources/rp-telemetry/scripts/meter-from-audit.js +2 -2
  595. package/skills/wix-replatform/resources/rp-telemetry/scripts/rp-telemetry.js +106 -4
  596. package/skills/wix-replatform/resources/rp-website-continuation/SKILL.md +94 -0
  597. package/skills/wix-replatform/scripts/census-reconciliation.js +25 -0
  598. package/skills/wix-replatform/scripts/cms-fallback.js +89 -0
  599. package/skills/wix-replatform/scripts/discovered-census.js +26 -0
  600. package/skills/wix-replatform/scripts/discovered-set-status.js +33 -0
  601. package/skills/wix-replatform/scripts/entity-qa-attestation.js +130 -0
  602. package/skills/wix-replatform/scripts/execution-health-check.js +415 -0
  603. package/skills/wix-replatform/scripts/execution-manifest-validate.js +1 -1
  604. package/skills/wix-replatform/scripts/execution-state-prepare.js +1 -1
  605. package/skills/wix-replatform/scripts/expectations-ledger.js +44 -0
  606. package/skills/wix-replatform/scripts/import-contract-verify.js +183 -0
  607. package/skills/wix-replatform/scripts/internal-link-audit.js +90 -0
  608. package/skills/wix-replatform/scripts/item-seo-tags-verify.js +205 -0
  609. package/skills/wix-replatform/scripts/migration-completion.js +76 -0
  610. package/skills/wix-replatform/scripts/orchestration-preflight.js +21 -2
  611. package/skills/wix-replatform/scripts/orchestration-route.js +32 -1
  612. package/skills/wix-replatform/scripts/orchestration-state.js +146 -10
  613. package/skills/wix-replatform/scripts/read-back-gate.js +175 -0
  614. package/skills/wix-replatform/scripts/replatform-console.js +146 -0
  615. package/skills/wix-replatform/scripts/replatform.js +436 -0
  616. package/skills/wix-replatform/scripts/source-secrets.js +77 -0
  617. package/skills/wix-replatform/scripts/url-redirects.js +286 -0
  618. package/skills/wix-replatform/scripts/user-action.js +58 -0
  619. package/skills/wix-replatform/scripts/website-handoff-generate.js +64 -0
  620. package/skills/wix-replatform/scripts/wix-dashboard-url.js +54 -0
  621. package/skills/wix-replatform/scripts/wix-destination.js +143 -0
  622. package/skills/wix-replatform/scripts/wix-headless-scaffold.js +762 -0
  623. package/skills/wix-vibe-headless/SKILL.md +7 -3
  624. package/skills/wix-vibe-headless/install/deploy.cjs +61 -34
  625. package/skills/wix-vibe-headless/platforms/base44.md +1 -0
  626. package/skills/wix-vibe-headless/references/donations/INSTRUCTIONS.md +88 -0
  627. package/skills/wix-vibe-headless/references/donations/app/rest/wix-donations.js +112 -0
  628. package/skills/wix-vibe-headless/references/donations/seed/SEED.md +11 -0
  629. package/skills/wix-vibe-headless/references/donations/tests/donations.test.mjs +258 -0
  630. package/skills/wix-vibe-headless/references/restaurants/INSTRUCTIONS.md +5 -2
  631. package/skills/wix-vibe-headless/references/restaurants/app/context/OrderCartContext.jsx +4 -1
  632. package/skills/wix-vibe-headless/references/restaurants/app/rest/wix-restaurants-ordering.js +67 -3
  633. package/skills/wix-vibe-headless/references/shared/app/rest/wix-client.js +4 -3
  634. package/skills/wix-manage/references/custom-apps/use-app-skills-and-app-tools.md +0 -208
  635. package/skills/wix-replatform/resources/rp-target-wix/SPEC-product-reviews-and-blog-authorship.md +0 -330
@@ -0,0 +1,2461 @@
1
+ #!/usr/bin/env node
2
+ import path from "node:path";
3
+ import {
4
+ DEFAULT_VIEWPORTS,
5
+ classifyUrl,
6
+ docsDir,
7
+ ensureDir,
8
+ extractLinks,
9
+ fetchText,
10
+ normalizeUrl,
11
+ parseArgs,
12
+ resolveOutputDir,
13
+ slugForUrl,
14
+ writeJson,
15
+ } from "./lib/common.mjs";
16
+ import { loadPlaywrightFromContext, resolveBrowserToolingContext } from "./lib/browser-tooling.mjs";
17
+ import { extractSeo, extractSectionsFromHtml, inferTokensFromHtml } from "./lib/html-extract.mjs";
18
+
19
+ async function main() {
20
+ const args = parseArgs();
21
+ const url = normalizeUrl(args._[0] || args.url).toString();
22
+ const outputDir = resolveOutputDir(url, args.out);
23
+ const page = await extractPage(url, { outputDir, screenshots: args.screenshots !== "false" });
24
+ const pageDir = path.join(docsDir(outputDir), "pages");
25
+ await writeJson(path.join(pageDir, `${slugForUrl(url)}.json`), page);
26
+ if (args.json) process.stdout.write(`${JSON.stringify(page, null, 2)}\n`);
27
+ }
28
+
29
+ export async function extractPage(url, options = {}) {
30
+ try {
31
+ return await extractWithPlaywright(url, options);
32
+ } catch (error) {
33
+ throw new Error(
34
+ `Browser extraction failed for ${url}. The site clone skill now requires Playwright-based extraction instead of HTML fallback because shared chrome, first-viewport evidence, and repeater detection depend on a real browser. Details: ${error.message}`,
35
+ );
36
+ }
37
+ }
38
+
39
+ async function extractWithPlaywright(url, { outputDir, screenshots = true, browserTooling, screenshotDir: suppliedScreenshotDir, screenshotPrefix = "source" } = {}) {
40
+ const toolingContext = browserTooling || await resolveBrowserToolingContext({ startDir: process.cwd() });
41
+ const playwright = await loadPlaywrightFromContext(toolingContext);
42
+ const browser = await playwright.chromium.launch({ headless: true });
43
+ const page = await browser.newPage();
44
+ const screenshotMap = {};
45
+ try {
46
+ const htmlByViewport = {};
47
+ const navigationProfiles = [];
48
+ const responsiveTextGeometry = {};
49
+ for (const viewport of DEFAULT_VIEWPORTS) {
50
+ await page.setViewportSize({ width: viewport.width, height: viewport.height });
51
+ const navigation = await navigateWithAdaptiveProfile(page, url);
52
+ navigationProfiles.push({ viewport: viewport.name, ...navigation });
53
+ responsiveTextGeometry[viewport.name] = await captureImportantTextGeometry(page);
54
+ if (screenshots && outputDir) {
55
+ const screenshotDir = suppliedScreenshotDir || path.join(docsDir(outputDir), "screenshots");
56
+ await ensureDir(screenshotDir);
57
+ const screenshotPath = path.join(screenshotDir, `${screenshotPrefix}-${slugForUrl(url)}-${viewport.name}.png`);
58
+ await page.screenshot({ path: screenshotPath, fullPage: true });
59
+ screenshotMap[viewport.name] = screenshotPath;
60
+ }
61
+ htmlByViewport[viewport.name] = await page.content();
62
+ }
63
+ const html = htmlByViewport.desktop || Object.values(htmlByViewport)[0];
64
+ const seo = extractSeo(html, url);
65
+ const desktopViewport = DEFAULT_VIEWPORTS.find((viewport) => viewport.name === "desktop") || DEFAULT_VIEWPORTS[0];
66
+ await page.setViewportSize({ width: desktopViewport.width, height: desktopViewport.height });
67
+ const desktopNavigation = await navigateWithAdaptiveProfile(page, url);
68
+ const repeaterPlan = await detectRepeaterSignals(page);
69
+ const browserData = await page.evaluate(async (repeaterPlan) => {
70
+ const cleanText = (value) => String(value || "").replace(/\s+/g, " ").trim();
71
+ const nonContentSelector = "script,style,noscript,template";
72
+ const semanticText = (node) => {
73
+ if (!node || node.nodeType !== Node.ELEMENT_NODE || node.matches(nonContentSelector)) return "";
74
+ const clone = node.cloneNode(true);
75
+ clone.querySelectorAll(nonContentSelector).forEach((child) => child.remove());
76
+ return cleanText(clone.textContent);
77
+ };
78
+ const renderedText = (node) => {
79
+ if (!node || node.nodeType !== Node.ELEMENT_NODE || node.matches(nonContentSelector)) return "";
80
+ const excludedSelector = `${nonContentSelector},[hidden],[inert],[aria-hidden='true' i]`;
81
+ const walker = document.createTreeWalker(node, NodeFilter.SHOW_TEXT);
82
+ const values = [];
83
+ for (let textNode = walker.nextNode(); textNode; textNode = walker.nextNode()) {
84
+ if (!cleanText(textNode.nodeValue)) continue;
85
+ const parent = textNode.parentElement;
86
+ if (!parent || parent.closest(excludedSelector)) continue;
87
+ let current = parent;
88
+ let excluded = false;
89
+ while (current) {
90
+ const styles = getComputedStyle(current);
91
+ if (styles.display === "none" || styles.visibility === "hidden" || Number(styles.opacity || 1) <= 0) {
92
+ excluded = true;
93
+ break;
94
+ }
95
+ if (current === node) break;
96
+ current = current.parentElement;
97
+ }
98
+ if (!excluded) values.push(textNode.nodeValue);
99
+ }
100
+ return cleanText(values.join(" "));
101
+ };
102
+ const rectFor = (node) => {
103
+ const rect = node.getBoundingClientRect();
104
+ return { x: rect.x, y: rect.y, width: rect.width, height: rect.height, top: rect.top, bottom: rect.bottom };
105
+ };
106
+ const isRawVisible = (node) => {
107
+ const rect = node.getBoundingClientRect();
108
+ const styles = getComputedStyle(node);
109
+ return rect.width > 0 && rect.height > 0 && styles.display !== "none" && styles.visibility !== "hidden" && Number(styles.opacity || 1) > 0;
110
+ };
111
+ const visibleCodeTexts = (node) => Array.from(node?.querySelectorAll?.("pre,code") || [])
112
+ .filter((candidate) => isRawVisible(candidate))
113
+ .map((candidate) => renderedText(candidate))
114
+ .filter(Boolean)
115
+ .filter((value, index, values) => values.indexOf(value) === index)
116
+ .slice(0, 12);
117
+ const consentSelectors = [
118
+ "#onetrust-banner-sdk",
119
+ "#onetrust-consent-sdk",
120
+ "[class*='onetrust' i]",
121
+ "[id*='cookie-banner' i]",
122
+ "[class*='cookie-banner' i]",
123
+ "[id*='consent-banner' i]",
124
+ "[class*='consent-banner' i]",
125
+ "[role='dialog']",
126
+ ].join(",");
127
+ const consentCandidates = Array.from(document.querySelectorAll(consentSelectors)).filter(isRawVisible).filter((node) => {
128
+ const text = renderedText(node).toLowerCase();
129
+ const identity = `${node.id || ""} ${node.className || ""} ${node.getAttribute("aria-label") || ""}`.toLowerCase();
130
+ const controls = Array.from(node.querySelectorAll("button,a,[role='button']")).map((control) => renderedText(control)).join(" ").toLowerCase();
131
+ const style = getComputedStyle(node);
132
+ const rect = node.getBoundingClientRect();
133
+ const consentSignal = /cookie|consent|privacy preferences|tracking preferences/.test(`${identity} ${text}`);
134
+ const actionSignal = /accept|reject|decline|customi[sz]e|preferences|allow all/.test(controls);
135
+ const overlaySignal = style.position === "fixed" || style.position === "sticky" || node.getAttribute("role") === "dialog" || rect.width >= window.innerWidth * 0.55;
136
+ return /onetrust|cookiebot|quantcast/.test(identity) || consentSignal && actionSignal && overlaySignal;
137
+ });
138
+ const consentRoots = consentCandidates.filter((node, index, all) => !all.some((candidate, candidateIndex) => candidateIndex !== index && candidate.contains(node)));
139
+ const ignoredSurfaces = consentRoots.map((node, index) => {
140
+ node.setAttribute("data-rp-ignored-surface", "consent-management");
141
+ const rect = node.getBoundingClientRect();
142
+ const identity = `${node.id || ""} ${node.className || ""}`.toLowerCase();
143
+ return {
144
+ id: `ignored-surface-${String(index + 1).padStart(3, "0")}`,
145
+ kind: "consent-management",
146
+ provider: /onetrust|ot-sdk/.test(identity) ? "onetrust" : /cookiebot/.test(identity) ? "cookiebot" : /quantcast/.test(identity) ? "quantcast" : "unknown",
147
+ creationPolicy: "ignore",
148
+ reason: "Destination consent must be implemented as functional infrastructure, not cloned page content.",
149
+ textFingerprint: renderedText(node).slice(0, 240),
150
+ rect: { top: Math.round(rect.top), left: Math.round(rect.left), width: Math.round(rect.width), height: Math.round(rect.height) },
151
+ domRef: {
152
+ tag: node.tagName.toLowerCase(),
153
+ id: node.id || "",
154
+ classTokens: String(node.className || "").split(/\s+/).filter(Boolean).slice(0, 10),
155
+ },
156
+ };
157
+ });
158
+ const isVisible = (node) => {
159
+ if (!(node instanceof Element) || node.closest("[data-rp-ignored-surface]")) return false;
160
+ return isRawVisible(node);
161
+ };
162
+ const headerSelectors = "header,[role='banner'],#header,.header,.site-header,.main-header,.masthead,.sticky-header";
163
+ const navSelectors = "nav,[role='navigation'],.menu,.nav,.navbar,.navigation";
164
+ const visualAssetSelectors = "img,svg";
165
+
166
+ function pickHeader() {
167
+ const candidates = Array.from(document.querySelectorAll(headerSelectors))
168
+ .filter(isVisible)
169
+ .map((node) => ({ node, rect: node.getBoundingClientRect(), styles: getComputedStyle(node) }))
170
+ .filter(({ rect }) => rect.bottom > -8 && rect.top < Math.max(window.innerHeight * 0.55, 260));
171
+ candidates.sort((a, b) => {
172
+ const aTop = Math.abs(a.rect.top);
173
+ const bTop = Math.abs(b.rect.top);
174
+ if (aTop !== bTop) return aTop - bTop;
175
+ return b.rect.height - a.rect.height;
176
+ });
177
+ return candidates[0] || null;
178
+ }
179
+
180
+ function visualAssetRecord(node, context = "page") {
181
+ if (!(node instanceof Element)) return null;
182
+ const rect = node.getBoundingClientRect();
183
+ const styles = getComputedStyle(node);
184
+ const tag = node.tagName.toLowerCase();
185
+ const use = tag === "svg" ? node.querySelector("use") : null;
186
+ const useHref = use?.getAttribute("href") || use?.getAttribute("xlink:href") || "";
187
+ const sourceUrl = tag === "img" ? (node.currentSrc || node.src || "") : useHref;
188
+ const accessibleName = cleanText(node.getAttribute("aria-label") || node.getAttribute("title") || node.getAttribute("alt") || node.closest("a,button")?.getAttribute("aria-label") || "");
189
+ const identity = `${node.id || ""} ${node.className?.baseVal || node.className || ""} ${sourceUrl} ${accessibleName}`.toLowerCase();
190
+ if (/cookielaw|onetrust|ot-sdk|cookiebot|quantcast/.test(identity)) return null;
191
+ const owner = node.closest("header,footer,button,a,[role='button']");
192
+ const ownerIdentity = `${owner?.className || ""} ${owner?.id || ""} ${owner?.getAttribute?.("aria-label") || ""} ${owner ? semanticText(owner) : ""}`.toLowerCase();
193
+ const homeLink = node.closest("a")?.href && new URL(node.closest("a").href, location.href).pathname.replace(/\/+$/, "") === "";
194
+ const social = /linkedin|instagram|facebook|twitter|youtube|tiktok|x\.com|social/.test(`${identity} ${ownerIdentity}`);
195
+ const looksLikeLogo = /logo|wordmark|brandmark|brand-mark/.test(identity)
196
+ || (!social && homeLink && (rect.width >= 72 || Number(node.getAttribute("width")) >= 72))
197
+ || (!social && rect.width >= 120 && rect.width >= rect.height * 1.8 && /header|footer/.test(context));
198
+ const kind = looksLikeLogo ? "logo" : "icon";
199
+ const fragmentId = useHref.startsWith("#") ? useHref.slice(1) : "";
200
+ const symbol = fragmentId ? document.getElementById(fragmentId) : null;
201
+ const variantText = `${identity} ${context}`;
202
+ const variant = /footer/.test(variantText) ? "footer"
203
+ : /solid/.test(variantText) ? "solid"
204
+ : /dark|black/.test(variantText) ? "dark"
205
+ : /light|white/.test(variantText) ? "light"
206
+ : /mobile|menu|drawer/.test(variantText) ? "menu"
207
+ : "default";
208
+ return {
209
+ kind,
210
+ context,
211
+ variant,
212
+ sourceType: tag === "img" ? (/\.svg(?:$|[?#])/i.test(sourceUrl) ? "external-svg" : "image") : useHref ? "svg-sprite-use" : "inline-svg",
213
+ sourceUrl,
214
+ useHref,
215
+ accessibleName,
216
+ visible: isRawVisible(node),
217
+ renderedSize: { width: Math.round(rect.width * 100) / 100, height: Math.round(rect.height * 100) / 100 },
218
+ intrinsicSize: {
219
+ width: node.getAttribute("width") || "",
220
+ height: node.getAttribute("height") || "",
221
+ viewBox: node.getAttribute("viewBox") || symbol?.getAttribute?.("viewBox") || "",
222
+ },
223
+ presentation: {
224
+ color: styles.color,
225
+ fill: styles.fill,
226
+ stroke: styles.stroke,
227
+ },
228
+ svgMarkup: tag === "svg" ? node.outerHTML.slice(0, 24000) : "",
229
+ symbolMarkup: symbol?.outerHTML?.slice(0, 24000) || "",
230
+ domRef: {
231
+ tag,
232
+ id: node.id || "",
233
+ classTokens: String(node.className?.baseVal || node.className || "").split(/\s+/).filter(Boolean).slice(0, 10),
234
+ },
235
+ };
236
+ }
237
+
238
+ function collectVisualAssets() {
239
+ const records = [];
240
+ const seen = new Set();
241
+ const scopes = [
242
+ ...Array.from(document.querySelectorAll("header,[role='banner']")).map((scope) => ({ scope, context: "header" })),
243
+ ...Array.from(document.querySelectorAll("footer,[role='contentinfo']")).map((scope) => ({ scope, context: "footer" })),
244
+ ...Array.from(document.querySelectorAll("button,[role='button'],a[href]")).map((scope) => ({ scope, context: scope.closest("footer") ? "footer-control" : scope.closest("header") ? "header-control" : "control" })),
245
+ ];
246
+ for (const { scope, context } of scopes) {
247
+ const nodes = scope.matches?.(visualAssetSelectors) ? [scope] : Array.from(scope.querySelectorAll(visualAssetSelectors));
248
+ for (const node of nodes) {
249
+ if (node.closest("[data-rp-ignored-surface]")) continue;
250
+ const record = visualAssetRecord(node, context);
251
+ if (!record) continue;
252
+ if (record.kind === "icon" && !record.visible && !record.useHref && !record.sourceUrl) continue;
253
+ const key = `${record.kind}\u0000${record.sourceType}\u0000${record.sourceUrl}\u0000${record.domRef.id}\u0000${record.domRef.classTokens.join(".")}\u0000${context}`;
254
+ if (seen.has(key)) continue;
255
+ seen.add(key);
256
+ records.push(record);
257
+ if (records.length >= 120) return records;
258
+ }
259
+ }
260
+ return records;
261
+ }
262
+
263
+ function headerVariant(name) {
264
+ const candidate = pickHeader();
265
+ if (!candidate) return { name, position: "unknown", height: 0, visibleAtScrollY: window.scrollY, visible: false };
266
+ const { node, rect, styles } = candidate;
267
+ const logo = Array.from(node.querySelectorAll(visualAssetSelectors))
268
+ .map((asset) => ({ asset, record: visualAssetRecord(asset, `header-${name}`) }))
269
+ .find(({ record }) => record?.kind === "logo") || null;
270
+ const logoNode = logo?.asset || null;
271
+ const logoRect = logoNode?.getBoundingClientRect();
272
+ return {
273
+ name,
274
+ position: styles.position || "static",
275
+ height: Math.round(rect.height),
276
+ visibleAtScrollY: Math.round(window.scrollY),
277
+ visible: true,
278
+ rect: rectFor(node),
279
+ logo: logoNode && logoRect ? {
280
+ ...logo.record,
281
+ sourceUrl: logo.record.sourceUrl,
282
+ alt: logoNode.getAttribute("alt") || logo.record.accessibleName || "",
283
+ renderedWidth: Math.round(logoRect.width),
284
+ renderedHeight: Math.round(logoRect.height),
285
+ } : null,
286
+ };
287
+ }
288
+
289
+ function linkRecord(anchor) {
290
+ return {
291
+ label: semanticText(anchor).slice(0, 120),
292
+ href: anchor.href || anchor.getAttribute("href") || "",
293
+ };
294
+ }
295
+
296
+ function isSameOriginHref(href) {
297
+ try {
298
+ return new URL(href, window.location.href).origin === window.location.origin;
299
+ } catch {
300
+ return false;
301
+ }
302
+ }
303
+
304
+ function navScore(scope, headerNode) {
305
+ const anchors = Array.from(scope.querySelectorAll("a[href]")).filter(isVisible);
306
+ if (!anchors.length) return Number.NEGATIVE_INFINITY;
307
+ const records = anchors.map(linkRecord);
308
+ const sameOriginCount = records.filter((item) => isSameOriginHref(item.href)).length;
309
+ const nonEmptyLabelCount = records.filter((item) => item.label).length;
310
+ const longLabelCount = records.filter((item) => item.label.length >= 2).length;
311
+ const externalCount = records.length - sameOriginCount;
312
+ const socialCount = anchors.filter((anchor) => {
313
+ const text = semanticText(anchor).toLowerCase();
314
+ const classText = `${anchor.className || ""} ${anchor.parentElement?.className || ""}`.toLowerCase();
315
+ const href = String(anchor.href || "").toLowerCase();
316
+ return /facebook|instagram|pinterest|youtube|tiktok|whatsapp|social|icon-/.test(`${text} ${classText} ${href}`);
317
+ }).length;
318
+ const topLevelListItems = Array.from(scope.querySelectorAll(":scope > ul > li, :scope > ol > li, :scope > li"))
319
+ .filter(isVisible);
320
+ const nestedListItems = Array.from(scope.querySelectorAll("li li")).filter(isVisible);
321
+ const uniqueHrefs = new Set(records.map((item) => item.href)).size;
322
+ const inHeader = Boolean(headerNode && headerNode.contains(scope));
323
+ const ariaLabel = cleanText(scope.getAttribute("aria-label") || "").toLowerCase();
324
+ const classText = `${scope.className || ""} ${scope.id || ""}`.toLowerCase();
325
+ const textSample = semanticText(scope).toLowerCase();
326
+
327
+ let score = 0;
328
+ score += sameOriginCount * 10;
329
+ score += nonEmptyLabelCount * 6;
330
+ score += longLabelCount * 3;
331
+ score += Math.min(topLevelListItems.length, 12) * 8;
332
+ score += Math.min(nestedListItems.length, 24) * 3;
333
+ score += Math.min(uniqueHrefs, 20) * 2;
334
+ if (inHeader) score += 30;
335
+ if (/main|primary|menu|navigation/.test(`${ariaLabel} ${classText}`)) score += 20;
336
+ if (/social/.test(`${ariaLabel} ${classText}`)) score -= 40;
337
+ if (sameOriginCount <= 1 && externalCount > 0) score -= 35;
338
+ score -= externalCount * 6;
339
+ score -= socialCount * 25;
340
+ if (records.length <= 2 && sameOriginCount === 0) score -= 50;
341
+ if (!topLevelListItems.length && records.length <= 3) score -= 15;
342
+ if (/facebook|instagram/.test(textSample) && sameOriginCount === 0) score -= 50;
343
+ return score;
344
+ }
345
+
346
+ function navigationItems() {
347
+ const scopes = Array.from(document.querySelectorAll(`header ${navSelectors}, ${navSelectors}`)).filter(isVisible);
348
+ const headerNode = pickHeader()?.node || null;
349
+ const scoredScopes = scopes
350
+ .map((scope) => ({ scope, score: navScore(scope, headerNode) }))
351
+ .sort((a, b) => b.score - a.score);
352
+ const scope = scoredScopes[0]?.scope;
353
+ if (!scope) return [];
354
+ const listItems = Array.from(scope.querySelectorAll("li")).filter((item) => {
355
+ const parentLi = item.parentElement?.closest("li");
356
+ return !parentLi || !scope.contains(parentLi);
357
+ });
358
+ const items = listItems.length ? listItems.map((item) => {
359
+ const anchor = Array.from(item.children).find((child) => child.matches?.("a[href]")) || item.querySelector("a[href]");
360
+ if (!anchor) return null;
361
+ const children = Array.from(item.querySelectorAll("li a[href]"))
362
+ .filter((childAnchor) => childAnchor !== anchor)
363
+ .map(linkRecord)
364
+ .filter((child) => child.label || child.href);
365
+ return { ...linkRecord(anchor), children: dedupeNav(children).slice(0, 24) };
366
+ }) : Array.from(scope.querySelectorAll("a[href]")).map((anchor) => ({ ...linkRecord(anchor), children: [] }));
367
+ return dedupeNav(items.filter((item) => item && (item.label || item.href))).slice(0, 40);
368
+ }
369
+
370
+ function dedupeNav(items) {
371
+ const seen = new Set();
372
+ return items.filter((item) => {
373
+ const key = `${item.label}\u0000${item.href}`;
374
+ if (seen.has(key)) return false;
375
+ seen.add(key);
376
+ return true;
377
+ });
378
+ }
379
+
380
+ function firstViewportText() {
381
+ const nodes = Array.from(document.querySelectorAll("main *:not(script):not(style), body > *:not(header):not(nav):not(footer):not(script):not(style)"));
382
+ const texts = [];
383
+ const seen = new Set();
384
+ for (const node of nodes) {
385
+ if (!isVisible(node)) continue;
386
+ if (node.closest("header,nav,footer")) continue;
387
+ const rect = node.getBoundingClientRect();
388
+ if (rect.bottom < 0 || rect.top > window.innerHeight) continue;
389
+ if (Array.from(node.children).some((child) => renderedText(child) === renderedText(node))) continue;
390
+ const text = renderedText(node);
391
+ if (!text || text.length < 2 || seen.has(text)) continue;
392
+ seen.add(text);
393
+ texts.push(text.slice(0, 180));
394
+ if (texts.length >= 80) break;
395
+ }
396
+ return texts;
397
+ }
398
+
399
+ const initialHeader = headerVariant("initial");
400
+ const navigation = navigationItems();
401
+ const visibleText = firstViewportText();
402
+ const scrollTarget = Math.max(260, Math.min(document.documentElement.scrollHeight - window.innerHeight, window.innerHeight * 0.75));
403
+ window.scrollTo(0, scrollTarget);
404
+ await new Promise((resolve) => setTimeout(resolve, 250));
405
+ const scrolledHeader = headerVariant("scrolled");
406
+ window.scrollTo(0, 0);
407
+
408
+ function domPath(node, stopNode) {
409
+ if (!node) return "";
410
+ const parts = [];
411
+ let current = node;
412
+ while (current && current.nodeType === Node.ELEMENT_NODE && current !== stopNode && parts.length < 8) {
413
+ const tag = current.tagName.toLowerCase();
414
+ const id = current.id ? `#${current.id}` : "";
415
+ const classTokens = String(current.className || "")
416
+ .split(/\s+/)
417
+ .filter(Boolean)
418
+ .slice(0, 2)
419
+ .map((token) => `.${token}`)
420
+ .join("");
421
+ const siblings = current.parentElement
422
+ ? Array.from(current.parentElement.children).filter((sibling) => sibling.tagName === current.tagName)
423
+ : [];
424
+ const nthOfType = siblings.length > 1 ? `:nth-of-type(${siblings.indexOf(current) + 1})` : "";
425
+ parts.unshift(`${tag}${id}${classTokens}${id ? "" : nthOfType}`);
426
+ current = current.parentElement;
427
+ }
428
+ return parts.join(" > ");
429
+ }
430
+
431
+ function visibleChildren(node) {
432
+ return Array.from(node?.children || []).filter((child) => isVisible(child));
433
+ }
434
+
435
+ function visibleHeadingText(node) {
436
+ return Array.from(node.querySelectorAll("h1,h2,h3,h4"))
437
+ .filter(isVisible)
438
+ .map((heading) => renderedText(heading))
439
+ .find(Boolean) || "";
440
+ }
441
+
442
+ function visibleHeadings(node) {
443
+ return Array.from(node.querySelectorAll("h1,h2,h3,h4"))
444
+ .filter(isVisible)
445
+ .map((heading) => ({
446
+ level: Number(heading.tagName.replace(/^H/i, "")) || null,
447
+ text: renderedText(heading),
448
+ }))
449
+ .filter((heading) => heading.text);
450
+ }
451
+
452
+ function isTransparentColor(value) {
453
+ const normalized = String(value || "").replace(/\s+/g, "").toLowerCase();
454
+ return !normalized || normalized === "transparent" || normalized === "rgba(0,0,0,0)";
455
+ }
456
+
457
+ function backgroundInfo(node) {
458
+ const styles = getComputedStyle(node);
459
+ return {
460
+ color: styles.backgroundColor,
461
+ image: styles.backgroundImage && styles.backgroundImage !== "none" ? styles.backgroundImage : "",
462
+ };
463
+ }
464
+
465
+ function roundedBox(rect) {
466
+ return {
467
+ top: Math.round(rect.top + window.scrollY),
468
+ left: Math.round(rect.left),
469
+ width: Math.round(rect.width),
470
+ height: Math.round(rect.height),
471
+ };
472
+ }
473
+
474
+ function normalizedBox(rect, sectionRect) {
475
+ return {
476
+ x: Number(((rect.left - sectionRect.left) / Math.max(sectionRect.width, 1)).toFixed(3)),
477
+ y: Number(((rect.top - sectionRect.top) / Math.max(sectionRect.height, 1)).toFixed(3)),
478
+ width: Number((rect.width / Math.max(sectionRect.width, 1)).toFixed(3)),
479
+ height: Number((rect.height / Math.max(sectionRect.height, 1)).toFixed(3)),
480
+ };
481
+ }
482
+
483
+ function textGeometry(node, rect) {
484
+ const styles = getComputedStyle(node);
485
+ const lineTops = [];
486
+ const walker = document.createTreeWalker(node, NodeFilter.SHOW_TEXT, {
487
+ acceptNode(textNode) {
488
+ return cleanText(textNode.nodeValue).length ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
489
+ },
490
+ });
491
+ for (let textNode = walker.nextNode(); textNode; textNode = walker.nextNode()) {
492
+ const range = document.createRange();
493
+ range.selectNodeContents(textNode);
494
+ for (const lineRect of range.getClientRects()) {
495
+ if (lineRect.width > 0 && lineRect.height > 0 && !lineTops.some((top) => Math.abs(top - lineRect.top) <= 2)) lineTops.push(lineRect.top);
496
+ }
497
+ }
498
+ const lineCount = Math.max(1, lineTops.length);
499
+ const maxWidth = styles.maxWidth;
500
+ return {
501
+ inlineSize: Math.round(rect.width * 100) / 100,
502
+ blockSize: Math.round(rect.height * 100) / 100,
503
+ lineCount,
504
+ wrapPolicy: lineCount === 1 ? "single-line" : styles.whiteSpace === "nowrap" ? "clipped-or-overflowing" : "wrapped",
505
+ whiteSpace: styles.whiteSpace,
506
+ maxWidth,
507
+ minWidth: styles.minWidth,
508
+ fontSize: styles.fontSize,
509
+ lineHeight: styles.lineHeight,
510
+ letterSpacing: styles.letterSpacing,
511
+ };
512
+ }
513
+
514
+ function layoutRole(node) {
515
+ const tag = node.tagName.toLowerCase();
516
+ const role = node.getAttribute("role") || "";
517
+ const classText = `${node.className || ""} ${node.id || ""}`.toLowerCase();
518
+ const peerControls = Array.from(node.parentElement?.children || []).filter((candidate) => {
519
+ const candidateTag = candidate.tagName?.toLowerCase?.();
520
+ return candidateTag === "button" || candidate.getAttribute?.("role") === "tab";
521
+ });
522
+ const childControls = Array.from(node.children || []).filter((candidate) => {
523
+ const candidateTag = candidate.tagName?.toLowerCase?.();
524
+ return candidateTag === "button" || candidate.getAttribute?.("role") === "tab";
525
+ });
526
+ if (role === "tablist" || /\btabs?\b|tab-list|tablist/.test(classText) || childControls.length >= 3) return "tabs";
527
+ if (role === "tab" || /tab__btn|tab-button/.test(classText) || (tag === "button" && peerControls.length >= 3)) return "tab";
528
+ if (/^h[1-6]$/.test(tag)) return "heading";
529
+ if (tag === "p" || tag === "blockquote") return "body-copy";
530
+ if (tag === "video" || tag === "iframe" || tag === "picture" || tag === "img") return "media";
531
+ if (tag === "button" || role === "button" || (tag === "a" && /button|cta|contact|learn|read|discover|explore/.test(classText))) return "action";
532
+ if (tag === "nav" || role === "navigation") return "navigation";
533
+ return "content";
534
+ }
535
+
536
+ const STYLEABLE_CATEGORY_PROPERTIES = {
537
+ text: ["color", "font-family", "font-size", "font-style", "font-weight", "letter-spacing", "line-height", "text-align", "text-decoration", "text-transform", "white-space"],
538
+ action: ["color", "background-color", "border-color", "border-radius", "border-style", "border-width", "box-shadow", "cursor", "min-height", "padding", "transition"],
539
+ media: ["aspect-ratio", "border-radius", "height", "object-fit", "object-position", "opacity", "width"],
540
+ container: ["align-items", "background-color", "background-image", "border-color", "border-radius", "box-shadow", "display", "gap", "grid-template-columns", "justify-content", "overflow", "padding", "position", "z-index"],
541
+ };
542
+
543
+ function styleableCategories(role) {
544
+ if (["heading", "body-copy", "tab"].includes(role)) return role === "tab" ? ["text", "action"] : ["text"];
545
+ if (role === "action") return ["text", "action"];
546
+ if (["media", "background-media"].includes(role)) return ["media"];
547
+ return ["container"];
548
+ }
549
+
550
+ function computedPropertyUnion(styles, categories) {
551
+ const properties = [...new Set(categories.flatMap((category) => STYLEABLE_CATEGORY_PROPERTIES[category] || []))].sort();
552
+ return Object.fromEntries(properties.map((property) => [property, styles.getPropertyValue(property)]));
553
+ }
554
+
555
+ function authoredCssContext(node, categories) {
556
+ const allowed = new Set(categories.flatMap((category) => STYLEABLE_CATEGORY_PROPERTIES[category] || []));
557
+ const matches = [];
558
+ for (const sheet of Array.from(document.styleSheets).slice(0, 80)) {
559
+ let rules;
560
+ try { rules = Array.from(sheet.cssRules || []); } catch { continue; }
561
+ for (const rule of rules.slice(0, 800)) {
562
+ if (!rule.selectorText || !rule.style) continue;
563
+ let matched = false;
564
+ try { matched = node.matches(rule.selectorText); } catch { continue; }
565
+ if (!matched) continue;
566
+ const declarations = {};
567
+ for (const property of Array.from(rule.style)) if (allowed.has(property)) declarations[property] = rule.style.getPropertyValue(property);
568
+ if (Object.keys(declarations).length) matches.push({ selector: rule.selectorText, href: sheet.href || "inline", declarations });
569
+ if (matches.length >= 20) return matches;
570
+ }
571
+ }
572
+ return matches;
573
+ }
574
+
575
+ function collectLayoutEvidence(sectionRoot) {
576
+ const sectionRect = sectionRoot.getBoundingClientRect();
577
+ const sectionStyles = getComputedStyle(sectionRoot);
578
+ const selector = [
579
+ "h1", "h2", "h3", "h4", "h5", "h6", "p", "blockquote",
580
+ "a[href]", "button", "[role='button']", "[role='tablist']", "[role='tab']",
581
+ "nav", "[class*='tabs' i]", "[class*='tab-list' i]",
582
+ "video", "iframe", "picture", "img",
583
+ ].join(",");
584
+ const semanticCandidates = Array.from(sectionRoot.querySelectorAll(selector));
585
+ const repeatedControlGroups = [...new Set(Array.from(sectionRoot.querySelectorAll("button, [role='tab']"))
586
+ .map((control) => control.parentElement)
587
+ .filter(Boolean)
588
+ .filter((parent) => Array.from(parent.children).filter((child) => child.tagName?.toLowerCase?.() === "button" || child.getAttribute?.("role") === "tab").length >= 3))];
589
+ const regionCandidates = [...new Set([...semanticCandidates, ...repeatedControlGroups])]
590
+ .filter(isVisible)
591
+ .filter((node) => {
592
+ const rect = node.getBoundingClientRect();
593
+ if (rect.width < 20 || rect.height < 12) return false;
594
+ if (["p", "blockquote"].includes(node.tagName.toLowerCase()) && renderedText(node).length < 16) return false;
595
+ return true;
596
+ });
597
+ const regions = [];
598
+ for (const node of regionCandidates) {
599
+ const role = layoutRole(node);
600
+ if (role === "tab" && regionCandidates.some((candidate) => candidate !== node && layoutRole(candidate) === "tabs" && candidate.contains(node))) continue;
601
+ let rect = node.getBoundingClientRect();
602
+ if (role === "tabs") {
603
+ const tabRects = Array.from(node.querySelectorAll("[role='tab'], button, a"))
604
+ .filter(isVisible)
605
+ .filter((candidate) => layoutRole(candidate) === "tab")
606
+ .map((candidate) => candidate.getBoundingClientRect());
607
+ if (tabRects.length >= 2) {
608
+ const left = Math.min(...tabRects.map((candidate) => candidate.left));
609
+ const top = Math.min(...tabRects.map((candidate) => candidate.top));
610
+ const right = Math.max(...tabRects.map((candidate) => candidate.right));
611
+ const bottom = Math.max(...tabRects.map((candidate) => candidate.bottom));
612
+ rect = { left, top, right, bottom, width: right - left, height: bottom - top };
613
+ }
614
+ }
615
+ const styles = getComputedStyle(node);
616
+ const categories = styleableCategories(role);
617
+ regions.push({
618
+ role,
619
+ text: ["heading", "body-copy", "action", "tab", "tabs"].includes(role) ? renderedText(node).slice(0, 180) : "",
620
+ rect: roundedBox(rect),
621
+ normalizedRect: normalizedBox(rect, sectionRect),
622
+ position: styles.position,
623
+ display: styles.display,
624
+ textAlign: styles.textAlign,
625
+ zIndex: styles.zIndex,
626
+ styleableCategories: categories,
627
+ computedStyle: computedPropertyUnion(styles, categories),
628
+ authoredCss: authoredCssContext(node, categories),
629
+ ...(["heading", "body-copy", "action", "tab", "tabs"].includes(role) ? { textGeometry: textGeometry(node, rect) } : {}),
630
+ domRef: {
631
+ tag: node.tagName.toLowerCase(),
632
+ id: node.id || "",
633
+ classTokens: String(node.className || "").split(/\s+/).filter(Boolean).slice(0, 6),
634
+ },
635
+ });
636
+ if (regions.length >= 36) break;
637
+ }
638
+
639
+ const layerCandidates = Array.from(sectionRoot.querySelectorAll("video, iframe, picture, img, [class*='background' i], [class*='overlay' i], [class*='curtain' i], [class*='mask' i], [class*='panel' i], [class*='frame' i], [class*='border' i]"))
640
+ .filter(isVisible)
641
+ .map((node) => ({ node, rect: node.getBoundingClientRect(), styles: getComputedStyle(node) }))
642
+ .filter(({ rect, styles }) => {
643
+ const areaRatio = (rect.width * rect.height) / Math.max(sectionRect.width * Math.min(sectionRect.height, window.innerHeight), 1);
644
+ return areaRatio >= 0.18 || ["absolute", "fixed", "sticky"].includes(styles.position);
645
+ })
646
+ .slice(0, 20)
647
+ .map(({ node, rect, styles }) => {
648
+ const classText = String(node.className || "").toLowerCase();
649
+ const kind = ["video", "iframe"].includes(node.tagName.toLowerCase()) ? "video" : ["picture", "img"].includes(node.tagName.toLowerCase()) ? "image" : "decorative-layer";
650
+ const role = /partial.*border|border.*frame|\bframe\b/.test(classText) ? "partial-border-frame"
651
+ : /copy.*panel|content.*panel/.test(classText) ? "copy-panel"
652
+ : /overlay|curtain|mask|scrim/.test(classText) ? "overlay"
653
+ : /background|\bbg\b/.test(classText) ? "background"
654
+ : kind === "image" || kind === "video" ? "media" : "decoration";
655
+ return {
656
+ kind,
657
+ role,
658
+ rect: roundedBox(rect),
659
+ normalizedRect: normalizedBox(rect, sectionRect),
660
+ position: styles.position,
661
+ zIndex: styles.zIndex,
662
+ opacity: styles.opacity,
663
+ objectFit: styles.objectFit,
664
+ overflow: styles.overflow,
665
+ background: backgroundInfo(node),
666
+ src: node.currentSrc || node.src || "",
667
+ classTokens: String(node.className || "").split(/\s+/).filter(Boolean).slice(0, 8),
668
+ };
669
+ });
670
+
671
+ const pseudoLayers = ["::before", "::after"].map((pseudo) => {
672
+ const styles = getComputedStyle(sectionRoot, pseudo);
673
+ if (!styles || styles.content === "none" || (isTransparentColor(styles.backgroundColor) && (!styles.backgroundImage || styles.backgroundImage === "none"))) return null;
674
+ return {
675
+ kind: "pseudo-layer",
676
+ pseudo,
677
+ position: styles.position,
678
+ zIndex: styles.zIndex,
679
+ opacity: styles.opacity,
680
+ background: {
681
+ color: styles.backgroundColor,
682
+ image: styles.backgroundImage && styles.backgroundImage !== "none" ? styles.backgroundImage : "",
683
+ },
684
+ };
685
+ }).filter(Boolean);
686
+
687
+ return {
688
+ vocabularyVersion: 1,
689
+ viewport: { width: window.innerWidth, height: window.innerHeight },
690
+ canvas: {
691
+ display: sectionStyles.display,
692
+ position: sectionStyles.position,
693
+ overflowX: sectionStyles.overflowX,
694
+ overflowY: sectionStyles.overflowY,
695
+ minHeight: sectionStyles.minHeight,
696
+ background: backgroundInfo(sectionRoot),
697
+ },
698
+ regions,
699
+ layers: [...layerCandidates, ...pseudoLayers],
700
+ };
701
+ }
702
+
703
+ function nodeFingerprint(node) {
704
+ const heading = visibleHeadingText(node);
705
+ const textPrefix = renderedText(node).slice(0, 160);
706
+ return {
707
+ heading,
708
+ textPrefix,
709
+ imageCount: node.querySelectorAll("img").length,
710
+ linkCount: node.querySelectorAll("a[href]").length,
711
+ };
712
+ }
713
+
714
+ function findContentRoot() {
715
+ const candidateDefs = [
716
+ { selector: "main", label: "main" },
717
+ { selector: "[role='main']", label: "role-main" },
718
+ { selector: "article", label: "article" },
719
+ { selector: "#primary", label: "primary-id" },
720
+ { selector: ".content-area", label: "content-area" },
721
+ { selector: ".site-main", label: "site-main" },
722
+ { selector: "body", label: "body" },
723
+ ];
724
+ const candidates = [];
725
+ for (const def of candidateDefs) {
726
+ for (const node of Array.from(document.querySelectorAll(def.selector)).filter(isVisible).slice(0, 3)) {
727
+ const rect = node.getBoundingClientRect();
728
+ const headingCount = node.querySelectorAll("h1,h2,h3,h4").length;
729
+ const imageCount = node.querySelectorAll("img").length;
730
+ const linkCount = node.querySelectorAll("a[href]").length;
731
+ let score = 0;
732
+ score += 40;
733
+ score += Math.min(rect.width / Math.max(window.innerWidth, 1), 1.2) * 35;
734
+ score += Math.min(rect.height / Math.max(window.innerHeight, 1), 4) * 10;
735
+ score += Math.min(headingCount, 8) * 4;
736
+ score += Math.min(imageCount, 20) * 1.5;
737
+ if (linkCount > 3) score += 5;
738
+ if (def.label === "main") score += 20;
739
+ else if (def.label === "role-main") score += 18;
740
+ else if (def.label === "article") score += 10;
741
+ else if (def.label === "body") score -= 30;
742
+ if (rect.height > document.documentElement.scrollHeight * 0.9) score -= 25;
743
+ candidates.push({ node, label: def.label, score });
744
+ }
745
+ }
746
+ candidates.sort((a, b) => b.score - a.score);
747
+ return candidates[0] || { node: document.body, label: "body", score: 0 };
748
+ }
749
+
750
+ function collectModuleStats(node, root) {
751
+ const rect = node.getBoundingClientRect();
752
+ const children = visibleChildren(node);
753
+ const headings = visibleHeadings(node);
754
+ const images = Array.from(node.querySelectorAll("img")).filter((img) => isVisible(img));
755
+ const links = Array.from(node.querySelectorAll("a[href]")).filter((link) => isVisible(link));
756
+ const buttons = Array.from(node.querySelectorAll("button")).filter((button) => isVisible(button));
757
+ const paragraphs = Array.from(node.querySelectorAll("p, li, blockquote, figcaption")).filter((item) => isVisible(item));
758
+ const childRects = children.map((child) => child.getBoundingClientRect());
759
+ const rowCountEstimate = new Set(childRects.map((item) => Math.round(item.top / 24))).size;
760
+ const background = backgroundInfo(node);
761
+ const role = node.getAttribute("role") || "";
762
+ const classText = `${node.className || ""} ${node.id || ""}`.toLowerCase();
763
+ const ancestorText = [];
764
+ let ancestor = node.parentElement;
765
+ for (let depth = 0; ancestor && depth < 4; depth += 1) {
766
+ ancestorText.push(`${ancestor.className || ""} ${ancestor.id || ""}`.toLowerCase());
767
+ if (ancestor === root) break;
768
+ ancestor = ancestor.parentElement;
769
+ }
770
+ const ancestryText = ancestorText.join(" ");
771
+ const reviewPattern = /review|testimonial|verified customer|productreview|based on [\d,.\s]+ reviews|what our customers/i;
772
+ const reviewLike = reviewPattern.test(classText)
773
+ || headings.some((heading) => reviewPattern.test(heading.text))
774
+ || reviewPattern.test(renderedText(node))
775
+ || reviewPattern.test(ancestryText);
776
+ const pricePattern = /\$\s?\d|\bprice\b|original price|current price|sale\b/i;
777
+ const addToCartPattern = /add to cart|quantity|buy now|shop now/i;
778
+ const productLike = pricePattern.test(renderedText(node))
779
+ || addToCartPattern.test(renderedText(node))
780
+ || /product|products|woocommerce|add-to-cart|price|shopify-buy/.test(`${classText} ${ancestryText}`);
781
+ const reviewWidgetLike = /reviewsio|judge\.me|stamped|loox|yotpo|trustpilot|productreview/.test(`${classText} ${ancestryText}`);
782
+ const galleryLike = /gallery|slider|embla|swiper|carousel|featured/.test(classText);
783
+ const logoLikeChildren = images.filter((image) => {
784
+ const imageRect = image.getBoundingClientRect();
785
+ return imageRect.width <= 240 && imageRect.height <= 140;
786
+ }).length;
787
+ return {
788
+ node,
789
+ tag: node.tagName.toLowerCase(),
790
+ role,
791
+ rect,
792
+ heading: headings[0]?.text || "",
793
+ headings,
794
+ text: renderedText(node).slice(0, 1800) || "",
795
+ textLength: renderedText(node).length,
796
+ visibleCodeTexts: visibleCodeTexts(node),
797
+ imageCount: images.length,
798
+ linkCount: links.length,
799
+ buttonCount: buttons.length,
800
+ paragraphCount: paragraphs.length,
801
+ childCount: children.length,
802
+ cardLikeChildren: children.filter((child) => {
803
+ const childRect = child.getBoundingClientRect();
804
+ return childRect.width >= 140 && childRect.height >= 90;
805
+ }).length,
806
+ rowCountEstimate,
807
+ background,
808
+ hasBackgroundChange: !isTransparentColor(background.color) || Boolean(background.image),
809
+ reviewLike,
810
+ reviewWidgetLike,
811
+ productLike,
812
+ galleryLike,
813
+ logoLikeChildren,
814
+ path: domPath(node, root?.parentElement || document.body.parentElement),
815
+ parentPath: domPath(node.parentElement, root?.parentElement || document.body.parentElement),
816
+ nthOfType: node.parentElement
817
+ ? Array.from(node.parentElement.children).filter((sibling) => sibling.tagName === node.tagName).indexOf(node) + 1
818
+ : 1,
819
+ fingerprint: nodeFingerprint(node),
820
+ };
821
+ }
822
+
823
+ function statsClassText(stats) {
824
+ return `${stats.node?.className || ""} ${stats.node?.id || ""} ${stats.path || ""}`.toLowerCase();
825
+ }
826
+
827
+ function sectionCapabilities({ hasCta, hasMedia, repeatingItems = false, isCarouselLike = false }) {
828
+ return { hasCta, hasMedia, repeatingItems, isCarouselLike };
829
+ }
830
+
831
+ const SECTION_DETECTORS = [
832
+ {
833
+ kind: "header",
834
+ match: ({ stats, classText, hasCta }) => (stats.tag === "header" || /\bheader\b|masthead|top-bar|site-header/.test(classText))
835
+ ? {
836
+ kind: "header",
837
+ variant: /top-bar/.test(classText) ? "top-bar" : "generic",
838
+ capabilities: sectionCapabilities({ hasCta, hasMedia: stats.imageCount >= 1 }),
839
+ }
840
+ : null,
841
+ },
842
+ {
843
+ kind: "footer",
844
+ match: ({ stats, classText, hasCta }) => (stats.tag === "footer" || /\bfooter\b|site-info|copyright|privacy|terms|site-map/.test(classText))
845
+ ? {
846
+ kind: "footer",
847
+ variant: "site-footer",
848
+ capabilities: sectionCapabilities({ hasCta, hasMedia: stats.imageCount >= 1 }),
849
+ }
850
+ : null,
851
+ },
852
+ {
853
+ kind: "hero",
854
+ match: ({ stats, classText, hasCta, atTopOfPage, hasStrongHeroMedia }) => (
855
+ hasStrongHeroMedia
856
+ && hasCta
857
+ && stats.rect.height >= Math.max(window.innerHeight * 0.22, 220)
858
+ && (atTopOfPage || /\bhero\b|banner|masthead|billboard|home-section/.test(classText) || stats.headings.some((heading) => heading.level === 1))
859
+ )
860
+ ? {
861
+ kind: "hero",
862
+ variant: stats.galleryLike || stats.cardLikeChildren >= 2 ? "carousel-hero" : "generic-hero",
863
+ capabilities: sectionCapabilities({ hasCta: true, hasMedia: true, repeatingItems: stats.cardLikeChildren >= 2, isCarouselLike: stats.galleryLike }),
864
+ }
865
+ : null,
866
+ },
867
+ {
868
+ kind: "reviews",
869
+ match: ({ stats, hasCta }) => (
870
+ (stats.reviewWidgetLike || stats.reviewLike)
871
+ && !(stats.productLike && !stats.reviewWidgetLike)
872
+ && (stats.cardLikeChildren >= 1 || stats.imageCount >= 2 || stats.textLength >= 80)
873
+ )
874
+ ? {
875
+ kind: "reviews",
876
+ variant: stats.galleryLike || stats.cardLikeChildren >= 2 ? "review-carousel" : "review-list",
877
+ capabilities: sectionCapabilities({ hasCta, hasMedia: stats.imageCount >= 1, repeatingItems: true, isCarouselLike: stats.galleryLike }),
878
+ }
879
+ : null,
880
+ },
881
+ {
882
+ kind: "category-strip",
883
+ match: ({ stats }) => (
884
+ stats.imageCount === 0
885
+ && stats.linkCount === 0
886
+ && stats.buttonCount === 0
887
+ && stats.paragraphCount >= 4
888
+ && stats.textLength >= 40
889
+ && stats.textLength <= 280
890
+ )
891
+ ? {
892
+ kind: "category-strip",
893
+ variant: "text-category-grid",
894
+ capabilities: sectionCapabilities({ hasCta: false, hasMedia: false, repeatingItems: true }),
895
+ }
896
+ : null,
897
+ },
898
+ {
899
+ kind: "card-collection",
900
+ match: ({ stats, hasCta }) => (
901
+ (stats.logoLikeChildren >= 4 && stats.imageCount >= 4 && stats.textLength < 420)
902
+ || (stats.galleryLike && stats.imageCount >= 5 && stats.textLength < 180)
903
+ )
904
+ ? {
905
+ kind: "card-collection",
906
+ variant: "logo-gallery",
907
+ capabilities: sectionCapabilities({ hasCta, hasMedia: true, repeatingItems: true, isCarouselLike: stats.galleryLike }),
908
+ }
909
+ : null,
910
+ },
911
+ {
912
+ kind: "card-collection",
913
+ match: ({ stats }) => (stats.productLike && (stats.cardLikeChildren >= 3 || stats.imageCount >= 4) && (stats.buttonCount >= 2 || stats.linkCount >= 6))
914
+ ? {
915
+ kind: "card-collection",
916
+ variant: stats.galleryLike ? "product-carousel" : "product-grid",
917
+ capabilities: sectionCapabilities({ hasCta: true, hasMedia: true, repeatingItems: true, isCarouselLike: stats.galleryLike }),
918
+ }
919
+ : null,
920
+ },
921
+ {
922
+ kind: "card-collection",
923
+ match: ({ stats, hasCta }) => (stats.cardLikeChildren >= 3 && (stats.imageCount >= 3 || stats.linkCount >= 3))
924
+ ? {
925
+ kind: "card-collection",
926
+ variant: stats.galleryLike ? "card-carousel" : "card-grid",
927
+ capabilities: sectionCapabilities({ hasCta, hasMedia: stats.imageCount >= 1, repeatingItems: true, isCarouselLike: stats.galleryLike }),
928
+ }
929
+ : null,
930
+ },
931
+ {
932
+ kind: "text-media",
933
+ match: ({ stats, classText, hasCta }) => (stats.imageCount >= 1 && stats.headings.length >= 1 && stats.paragraphCount >= 1)
934
+ ? {
935
+ kind: "text-media",
936
+ variant: /has-media-on-the-right|media-right/.test(classText) ? "media-right" : "media-left",
937
+ capabilities: sectionCapabilities({ hasCta, hasMedia: true }),
938
+ }
939
+ : null,
940
+ },
941
+ {
942
+ kind: "stat-group",
943
+ match: ({ stats }) => (stats.headings.length >= 3 && stats.imageCount === 0 && stats.linkCount === 0 && stats.buttonCount === 0)
944
+ ? {
945
+ kind: "stat-group",
946
+ variant: "step-strip",
947
+ capabilities: sectionCapabilities({ hasCta: false, hasMedia: false, repeatingItems: true }),
948
+ }
949
+ : null,
950
+ },
951
+ {
952
+ kind: "promo-band",
953
+ match: ({ stats, hasCta }) => (
954
+ stats.imageCount <= 1
955
+ && stats.textLength >= 40
956
+ && stats.textLength <= 220
957
+ && stats.paragraphCount <= 2
958
+ && stats.headings.length <= 1
959
+ && (stats.hasBackgroundChange || stats.imageCount >= 1)
960
+ )
961
+ ? {
962
+ kind: "promo-band",
963
+ variant: hasCta ? "value-prop-cta" : "value-prop-band",
964
+ capabilities: sectionCapabilities({ hasCta, hasMedia: stats.imageCount >= 1 }),
965
+ }
966
+ : null,
967
+ },
968
+ {
969
+ kind: "cta-strip",
970
+ match: ({ stats, hasCta }) => (
971
+ stats.imageCount === 0
972
+ && hasCta
973
+ && stats.textLength <= 220
974
+ && stats.paragraphCount <= 1
975
+ && stats.headings.length <= 2
976
+ )
977
+ ? {
978
+ kind: "cta-strip",
979
+ variant: "inline-cta-strip",
980
+ capabilities: sectionCapabilities({ hasCta: true, hasMedia: false }),
981
+ }
982
+ : null,
983
+ },
984
+ {
985
+ kind: "cta-strip",
986
+ match: ({ stats, hasCta, atTopOfPage }) => ((stats.buttonCount + stats.linkCount) >= 2 && stats.imageCount >= 1 && stats.rect.height >= 120)
987
+ ? {
988
+ kind: "cta-strip",
989
+ variant: atTopOfPage ? "promo-hero-strip" : "promo-strip",
990
+ capabilities: sectionCapabilities({ hasCta: true, hasMedia: true }),
991
+ }
992
+ : null,
993
+ },
994
+ {
995
+ kind: "cta-strip",
996
+ match: ({ stats, hasCta }) => (stats.headings.length === 1 && stats.imageCount === 0 && stats.paragraphCount === 0)
997
+ ? {
998
+ kind: "cta-strip",
999
+ variant: "centered-heading-strip",
1000
+ capabilities: sectionCapabilities({ hasCta, hasMedia: false }),
1001
+ }
1002
+ : null,
1003
+ },
1004
+ {
1005
+ kind: "rich-text",
1006
+ match: ({ stats, hasCta }) => (stats.textLength >= 60 || stats.headings.length || stats.imageCount)
1007
+ ? {
1008
+ kind: "rich-text",
1009
+ variant: "generic",
1010
+ capabilities: sectionCapabilities({ hasCta, hasMedia: stats.imageCount >= 1 }),
1011
+ }
1012
+ : null,
1013
+ },
1014
+ ];
1015
+
1016
+ function classifySection(stats) {
1017
+ const classText = statsClassText(stats);
1018
+ const atTopOfPage = (stats.rect.top + window.scrollY) < Math.max(window.innerHeight * 0.35, 320);
1019
+ const hasCta = (stats.buttonCount + stats.linkCount) >= 1;
1020
+ const hasStrongHeroMedia = stats.imageCount >= 1 || stats.hasBackgroundChange;
1021
+ for (const detector of SECTION_DETECTORS) {
1022
+ const matched = detector.match({ stats, classText, atTopOfPage, hasCta, hasStrongHeroMedia });
1023
+ if (matched) return matched;
1024
+ }
1025
+ return { kind: "unknown", variant: "generic", capabilities: sectionCapabilities({ hasCta, hasMedia: stats.imageCount >= 1 }) };
1026
+ }
1027
+
1028
+ function sectionScore(stats, root) {
1029
+ let score = 0;
1030
+ score += 30;
1031
+ score += Math.min(stats.rect.width / Math.max(root.getBoundingClientRect().width, 1), 1.2) * 30;
1032
+ score += Math.min(stats.rect.height / Math.max(window.innerHeight, 1), 1.5) * 20;
1033
+ score += Math.min(stats.headings.length, 4) * 10;
1034
+ score += Math.min(stats.imageCount, 6) * 4;
1035
+ score += Math.min(stats.cardLikeChildren, 6) * 5;
1036
+ if (stats.hasBackgroundChange) score += 10;
1037
+ if (stats.rect.width < root.getBoundingClientRect().width * 0.55) score -= 20;
1038
+ if (stats.rect.height < 80) score -= 40;
1039
+ if (stats.textLength < 15 && stats.imageCount === 0) score -= 60;
1040
+ if (/nav|menu|breadcrumb|popup|modal|drawer|cookie/.test(`${stats.role} ${stats.path}`.toLowerCase())) score -= 35;
1041
+ if (stats.tag === "main" || stats.tag === "article") score -= 20;
1042
+ if (stats.rect.height > root.getBoundingClientRect().height * 0.8) score -= 50;
1043
+ if (stats.childCount === 1 && stats.cardLikeChildren <= 1 && stats.headings.length <= 1 && !stats.hasBackgroundChange) score -= 40;
1044
+ if (stats.textLength > 3200 && stats.rect.height > root.getBoundingClientRect().height * 0.55 && !stats.hasBackgroundChange) score -= 45;
1045
+ return score;
1046
+ }
1047
+
1048
+ function isWrapperLike(stats, root) {
1049
+ return (
1050
+ stats.childCount <= 2
1051
+ && stats.cardLikeChildren <= 1
1052
+ && stats.headings.length <= 1
1053
+ && !stats.hasBackgroundChange
1054
+ && stats.rect.height > Math.max(window.innerHeight * 1.25, root.getBoundingClientRect().height * 0.45)
1055
+ );
1056
+ }
1057
+
1058
+ function findSegmentationRoot(contentRoot) {
1059
+ const warnings = [];
1060
+ let current = contentRoot;
1061
+ for (let depth = 0; depth < 10; depth += 1) {
1062
+ const scopedContentChild = Array.from(current.children || []).find((child) => {
1063
+ if (!isVisible(child)) return false;
1064
+ const classText = `${child.className || ""} ${child.id || ""}`.toLowerCase();
1065
+ return /single-content|entry-content|post-content|article-content|main-content/.test(classText);
1066
+ });
1067
+ if (scopedContentChild) {
1068
+ const childStats = collectModuleStats(scopedContentChild, contentRoot);
1069
+ current = scopedContentChild;
1070
+ warnings.push(`segmentation-root-descended:${childStats.path}`);
1071
+ continue;
1072
+ }
1073
+ const children = visibleChildren(current).filter((child) => {
1074
+ const rect = child.getBoundingClientRect();
1075
+ return rect.height >= 24 && (renderedText(child).length >= 20 || child.querySelector("img,svg,a[href],button") || rect.height >= 120);
1076
+ });
1077
+ if (!children.length) break;
1078
+ if (children.length === 1) {
1079
+ const childStats = collectModuleStats(children[0], contentRoot);
1080
+ if (childStats.rect.width >= current.getBoundingClientRect().width * 0.72) {
1081
+ current = children[0];
1082
+ warnings.push(`segmentation-root-descended:${childStats.path}`);
1083
+ continue;
1084
+ }
1085
+ }
1086
+ const dominant = children.find((child) => {
1087
+ const rect = child.getBoundingClientRect();
1088
+ return rect.width >= current.getBoundingClientRect().width * 0.72 && rect.height >= current.getBoundingClientRect().height * 0.72;
1089
+ });
1090
+ if (children.length <= 3 && dominant) {
1091
+ const dominantStats = collectModuleStats(dominant, contentRoot);
1092
+ if (!dominantStats.hasBackgroundChange && dominantStats.cardLikeChildren <= 1) {
1093
+ current = dominant;
1094
+ warnings.push(`segmentation-root-descended:${dominantStats.path}`);
1095
+ continue;
1096
+ }
1097
+ }
1098
+ const dominantCandidate = children
1099
+ .map((child) => ({ child, rect: child.getBoundingClientRect() }))
1100
+ .sort((a, b) => b.rect.height - a.rect.height)[0];
1101
+ if (dominantCandidate && children.length <= 5) {
1102
+ const otherHeight = children
1103
+ .filter((child) => child !== dominantCandidate.child)
1104
+ .reduce((sum, child) => sum + child.getBoundingClientRect().height, 0);
1105
+ const dominantClassText = `${dominantCandidate.child.className || ""} ${dominantCandidate.child.id || ""}`.toLowerCase();
1106
+ if (dominantCandidate.rect.height >= current.getBoundingClientRect().height * 0.6 && otherHeight <= dominantCandidate.rect.height * 0.35) {
1107
+ const dominantStats = collectModuleStats(dominantCandidate.child, contentRoot);
1108
+ if (!dominantStats.hasBackgroundChange) {
1109
+ current = dominantCandidate.child;
1110
+ warnings.push(`segmentation-root-descended:${dominantStats.path}`);
1111
+ continue;
1112
+ }
1113
+ }
1114
+ if (
1115
+ dominantCandidate.rect.width >= current.getBoundingClientRect().width * 0.68
1116
+ && dominantCandidate.rect.height >= current.getBoundingClientRect().height * 0.45
1117
+ && otherHeight <= dominantCandidate.rect.height * 0.8
1118
+ && /content|entry|article|post|main/.test(dominantClassText)
1119
+ ) {
1120
+ const dominantStats = collectModuleStats(dominantCandidate.child, contentRoot);
1121
+ current = dominantCandidate.child;
1122
+ warnings.push(`segmentation-root-descended:${dominantStats.path}`);
1123
+ continue;
1124
+ }
1125
+ }
1126
+ break;
1127
+ }
1128
+ return { node: current, warnings };
1129
+ }
1130
+
1131
+ function isStructuralBand(stats, segmentationRoot) {
1132
+ const lowerTag = stats.tag.toLowerCase();
1133
+ if (/^h[1-6]$/.test(lowerTag) || lowerTag === "p") return false;
1134
+ if (lowerTag === "div" && stats.rect.height < 100 && !stats.hasBackgroundChange && stats.imageCount === 0 && stats.cardLikeChildren === 0) return false;
1135
+ if (stats.rect.width < segmentationRoot.getBoundingClientRect().width * 0.55 && stats.imageCount === 0 && stats.headings.length === 0) return false;
1136
+ return true;
1137
+ }
1138
+
1139
+ function overlaps(a, b) {
1140
+ const top = Math.max(a.rect.top, b.rect.top);
1141
+ const bottom = Math.min(a.rect.top + a.rect.height, b.rect.top + b.rect.height);
1142
+ const overlapHeight = Math.max(0, bottom - top);
1143
+ return overlapHeight >= Math.min(a.rect.height, b.rect.height) * 0.6;
1144
+ }
1145
+
1146
+ function classifyModule(stats, sectionType = null) {
1147
+ const classText = statsClassText(stats);
1148
+ const text = String(stats.text || "").toLowerCase();
1149
+ const hasCta = stats.linkCount + stats.buttonCount >= 1;
1150
+ const sectionKey = sectionType ? `${sectionType.kind}:${sectionType.variant || "*"}` : "";
1151
+ const moduleDetectors = {
1152
+ "reviews:*": () => ({ kind: "review-card", variant: "customer-review", capabilities: { hasCta, hasMedia: stats.imageCount >= 1 } }),
1153
+ "stat-group:*": () => ({ kind: "stat-item", variant: "step-or-stat", capabilities: { hasCta: false, hasMedia: false } }),
1154
+ "category-strip:*": () => ({ kind: "text-block", variant: "category-item", capabilities: { hasCta, hasMedia: false } }),
1155
+ "promo-band:*": () => (stats.imageCount >= 1 && stats.headings.length === 0 && stats.paragraphCount === 0
1156
+ ? { kind: "media-item", variant: "promo-media", capabilities: { hasCta, hasMedia: true } }
1157
+ : { kind: "text-block", variant: "promo-copy", capabilities: { hasCta, hasMedia: stats.imageCount >= 1 } }),
1158
+ "cta-strip:*": () => ({ kind: "text-block", variant: hasCta ? "cta-copy" : "strip-copy", capabilities: { hasCta, hasMedia: false } }),
1159
+ "text-media:*": () => {
1160
+ if (stats.imageCount >= 1 && stats.paragraphCount === 0 && stats.headings.length === 0) return { kind: "media-item", variant: "image-tile", capabilities: { hasCta, hasMedia: true } };
1161
+ if (stats.headings.length >= 1 || stats.paragraphCount >= 1) return { kind: "text-block", variant: "copy-block", capabilities: { hasCta, hasMedia: stats.imageCount >= 1 } };
1162
+ return null;
1163
+ },
1164
+ "card-collection:logo-gallery": () => ({ kind: "logo-item", variant: "brand-mark", capabilities: { hasCta, hasMedia: stats.imageCount >= 1 } }),
1165
+ "card-collection:*": () => ({ kind: "card", variant: /product|price|add to cart|book rental|rent/.test(text) ? "product-card" : "content-card", capabilities: { hasCta, hasMedia: stats.imageCount >= 1 } }),
1166
+ "footer:*": () => ({ kind: "link-group", variant: "footer-links", capabilities: { hasCta: stats.linkCount >= 1, hasMedia: false } }),
1167
+ "hero:*": () => {
1168
+ if (stats.headings.length >= 1 || stats.paragraphCount >= 1) return { kind: "text-block", variant: "hero-copy", capabilities: { hasCta, hasMedia: stats.imageCount >= 1 } };
1169
+ if (stats.imageCount >= 1) return { kind: "media-item", variant: "hero-media", capabilities: { hasCta, hasMedia: true } };
1170
+ return null;
1171
+ },
1172
+ };
1173
+ const specialized = (moduleDetectors[sectionKey] || moduleDetectors[`${sectionType?.kind}:*`])?.();
1174
+ if (specialized) return specialized;
1175
+ if (stats.reviewLike || /review|testimonial|verified customer/.test(`${classText} ${text}`)) return { kind: "review-card", variant: "customer-review", capabilities: { hasCta, hasMedia: stats.imageCount >= 1 } };
1176
+ if (stats.productLike) return { kind: "card", variant: "product-card", capabilities: { hasCta, hasMedia: stats.imageCount >= 1 } };
1177
+ if (stats.logoLikeChildren >= 1 || (stats.imageCount >= 1 && stats.textLength < 40 && stats.rect.width <= 260)) return { kind: "logo-item", variant: "brand-mark", capabilities: { hasCta, hasMedia: stats.imageCount >= 1 } };
1178
+ if (stats.imageCount >= 1 && stats.headings.length === 0 && stats.paragraphCount === 0) return { kind: "media-item", variant: "image-tile", capabilities: { hasCta, hasMedia: true } };
1179
+ if (stats.headings.length >= 1 || stats.paragraphCount >= 1) return { kind: "text-block", variant: "copy-block", capabilities: { hasCta, hasMedia: stats.imageCount >= 1 } };
1180
+ return { kind: "unknown", variant: "generic", capabilities: { hasCta, hasMedia: stats.imageCount >= 1 } };
1181
+ }
1182
+
1183
+ function moduleSummary(stats, sectionRoot, sectionType = null) {
1184
+ const moduleType = classifyModule(stats, sectionType);
1185
+ return {
1186
+ id: "",
1187
+ kind: moduleType.kind,
1188
+ variant: moduleType.variant,
1189
+ capabilities: moduleType.capabilities,
1190
+ heading: stats.heading,
1191
+ visibleCodeTexts: stats.visibleCodeTexts,
1192
+ order: 0,
1193
+ confidence: Number(Math.max(0.2, Math.min(0.95, 0.45 + ((stats.imageCount + stats.headings.length + stats.cardLikeChildren) * 0.04))).toFixed(2)),
1194
+ rect: {
1195
+ top: Math.round(stats.rect.top + window.scrollY),
1196
+ left: Math.round(stats.rect.left),
1197
+ width: Math.round(stats.rect.width),
1198
+ height: Math.round(stats.rect.height),
1199
+ },
1200
+ counts: {
1201
+ headings: stats.headings.length,
1202
+ paragraphs: stats.paragraphCount,
1203
+ images: stats.imageCount,
1204
+ links: stats.linkCount,
1205
+ buttons: stats.buttonCount,
1206
+ },
1207
+ domRef: {
1208
+ tag: stats.tag,
1209
+ id: stats.node.id || "",
1210
+ classTokens: String(stats.node.className || "").split(/\s+/).filter(Boolean).slice(0, 6),
1211
+ path: domPath(stats.node, sectionRoot.parentElement || document.body.parentElement),
1212
+ parentPath: domPath(stats.node.parentElement, sectionRoot.parentElement || document.body.parentElement),
1213
+ nthOfType: stats.nthOfType,
1214
+ fingerprint: stats.fingerprint,
1215
+ },
1216
+ warnings: [],
1217
+ };
1218
+ }
1219
+
1220
+ function moduleCandidatesFrom(containerRoot, sectionRoot, filters = {}) {
1221
+ const minHeight = filters.minHeight || 60;
1222
+ const minWidth = filters.minWidth || Math.min(sectionRoot.getBoundingClientRect().width * 0.22, 220);
1223
+ return visibleChildren(containerRoot)
1224
+ .map((child) => collectModuleStats(child, sectionRoot))
1225
+ .filter((stats) => stats.rect.height >= minHeight)
1226
+ .filter((stats) => stats.rect.width >= minWidth || stats.imageCount >= 1 || stats.headings.length >= 1)
1227
+ .filter((stats) => stats.textLength >= 10 || stats.imageCount >= 1)
1228
+ .slice(0, 36);
1229
+ }
1230
+
1231
+ function resolveRepeatingContainer(sectionRoot) {
1232
+ let current = sectionRoot;
1233
+ for (let depth = 0; depth < 3; depth += 1) {
1234
+ const children = visibleChildren(current);
1235
+ if (!children.length) return current;
1236
+ const repeatedChild = children.find((child) => {
1237
+ const classText = `${child.className || ""} ${child.id || ""}`.toLowerCase();
1238
+ return /slider|carousel|embla|swiper|viewport|container|grid|gallery|reviews|products|list/.test(classText);
1239
+ });
1240
+ if (repeatedChild) {
1241
+ current = repeatedChild;
1242
+ continue;
1243
+ }
1244
+ if (children.length === 1) {
1245
+ current = children[0];
1246
+ continue;
1247
+ }
1248
+ return current;
1249
+ }
1250
+ return current;
1251
+ }
1252
+
1253
+ function extractRepeatingItemModules(sectionRoot, sectionType) {
1254
+ const container = resolveRepeatingContainer(sectionRoot);
1255
+ const children = moduleCandidatesFrom(container, sectionRoot, { minHeight: 80, minWidth: 120 });
1256
+ const accepted = [];
1257
+ for (const stats of children) {
1258
+ if (stats.rect.height > sectionRoot.getBoundingClientRect().height * 0.95 && stats.cardLikeChildren <= 1) continue;
1259
+ if (accepted.some((existing) => overlaps(existing, stats))) continue;
1260
+ accepted.push(stats);
1261
+ }
1262
+ return accepted.slice(0, 12).map((stats, index) => {
1263
+ const module = moduleSummary(stats, sectionRoot, sectionType);
1264
+ module.id = `module-${index + 1}`;
1265
+ module.order = index + 1;
1266
+ return module;
1267
+ }).filter((module) => module.kind !== "unknown");
1268
+ }
1269
+
1270
+ function dedupeModuleStats(statsList) {
1271
+ const accepted = [];
1272
+ for (const stats of statsList) {
1273
+ if (accepted.some((existing) => overlaps(existing, stats))) continue;
1274
+ accepted.push(stats);
1275
+ }
1276
+ return accepted;
1277
+ }
1278
+
1279
+ function extractLogoModules(sectionRoot, sectionType) {
1280
+ const imageNodes = uniqueElements(
1281
+ Array.from(sectionRoot.querySelectorAll("img"))
1282
+ .filter(isVisible)
1283
+ .map((image) => image.closest("a, figure, li, div") || image),
1284
+ );
1285
+ const statsList = imageNodes
1286
+ .map((node) => collectModuleStats(node, sectionRoot))
1287
+ .filter((stats) => stats.imageCount >= 1)
1288
+ .filter((stats) => stats.rect.width >= 40 && stats.rect.height >= 24)
1289
+ .sort((a, b) => (a.rect.width * a.rect.height) - (b.rect.width * b.rect.height));
1290
+ return dedupeModuleStats(statsList).slice(0, 16).map((stats, index) => {
1291
+ const module = moduleSummary(stats, sectionRoot, sectionType);
1292
+ module.id = `module-${index + 1}`;
1293
+ module.order = index + 1;
1294
+ return module;
1295
+ }).filter((module) => module.kind !== "unknown");
1296
+ }
1297
+
1298
+ function extractReviewModules(sectionRoot, sectionType) {
1299
+ const reviewNodes = uniqueElements(
1300
+ Array.from(sectionRoot.querySelectorAll("[class*='review'], [id*='review'], [class*='testimonial'], [class*='customer']"))
1301
+ .filter(isVisible),
1302
+ );
1303
+ const statsList = reviewNodes
1304
+ .map((node) => collectModuleStats(node, sectionRoot))
1305
+ .filter((stats) => stats.textLength >= 30 || stats.imageCount >= 1)
1306
+ .filter((stats) => stats.rect.height >= 60)
1307
+ .sort((a, b) => (a.rect.width * a.rect.height) - (b.rect.width * b.rect.height));
1308
+ const accepted = dedupeModuleStats(statsList)
1309
+ .filter((stats) => !stats.node.contains(sectionRoot))
1310
+ .slice(0, 12);
1311
+ return accepted.map((stats, index) => {
1312
+ const module = moduleSummary(stats, sectionRoot, sectionType);
1313
+ module.id = `module-${index + 1}`;
1314
+ module.order = index + 1;
1315
+ return module;
1316
+ }).filter((module) => module.kind !== "unknown");
1317
+ }
1318
+
1319
+ function resolveTextMediaScope(sectionRoot) {
1320
+ let current = sectionRoot;
1321
+ for (let depth = 0; depth < 3; depth += 1) {
1322
+ const children = visibleChildren(current).filter((child) => child.getBoundingClientRect().height >= 24);
1323
+ if (children.length !== 1) return current;
1324
+ const onlyChild = children[0];
1325
+ const onlyRect = onlyChild.getBoundingClientRect();
1326
+ if (onlyRect.width < current.getBoundingClientRect().width * 0.72) return current;
1327
+ current = onlyChild;
1328
+ }
1329
+ return current;
1330
+ }
1331
+
1332
+ function extractTextMediaModules(sectionRoot, sectionType) {
1333
+ const scope = resolveTextMediaScope(sectionRoot);
1334
+ const children = moduleCandidatesFrom(scope, sectionRoot, { minHeight: 90, minWidth: 120 })
1335
+ .sort((a, b) => (a.rect.width * a.rect.height) - (b.rect.width * b.rect.height));
1336
+ const accepted = [];
1337
+ for (const stats of children) {
1338
+ const moduleType = classifyModule(stats, sectionType);
1339
+ if (moduleType.kind === "unknown") continue;
1340
+ if (accepted.some((existing) => existing.node.contains(stats.node) && classifyModule(existing, sectionType).kind === moduleType.kind)) continue;
1341
+ if (accepted.some((existing) => overlaps(existing, stats))) continue;
1342
+ accepted.push(stats);
1343
+ }
1344
+ return accepted.slice(0, 6).map((stats, index) => {
1345
+ const module = moduleSummary(stats, sectionRoot, sectionType);
1346
+ module.id = `module-${index + 1}`;
1347
+ module.order = index + 1;
1348
+ return module;
1349
+ });
1350
+ }
1351
+
1352
+ function extractStatModules(sectionRoot, sectionType) {
1353
+ const children = visibleChildren(sectionRoot)
1354
+ .map((child) => collectModuleStats(child, sectionRoot))
1355
+ .filter((stats) => stats.headings.length >= 1 || stats.textLength >= 20)
1356
+ .filter((stats) => stats.rect.height >= 40)
1357
+ .slice(0, 12);
1358
+ return children.map((stats, index) => {
1359
+ const module = moduleSummary(stats, sectionRoot, sectionType);
1360
+ module.id = `module-${index + 1}`;
1361
+ module.order = index + 1;
1362
+ return module;
1363
+ }).filter((module) => module.kind !== "unknown");
1364
+ }
1365
+
1366
+ function extractGenericModules(sectionRoot, sectionType) {
1367
+ const children = visibleChildren(sectionRoot)
1368
+ .map((child) => collectModuleStats(child, sectionRoot))
1369
+ .filter((stats) => stats.rect.width >= Math.min(sectionRoot.getBoundingClientRect().width * 0.22, 220) || stats.imageCount >= 1)
1370
+ .filter((stats) => stats.rect.height >= 90)
1371
+ .filter((stats) => stats.textLength >= 10 || stats.imageCount >= 1)
1372
+ .slice(0, 16);
1373
+ const accepted = [];
1374
+ for (const stats of children) {
1375
+ if (stats.rect.height > sectionRoot.getBoundingClientRect().height * 0.92 && stats.cardLikeChildren <= 1) continue;
1376
+ if (accepted.some((existing) => overlaps(existing, stats))) continue;
1377
+ accepted.push(stats);
1378
+ }
1379
+ return accepted.slice(0, 8).map((stats, index) => {
1380
+ const module = moduleSummary(stats, sectionRoot, sectionType);
1381
+ module.id = `module-${index + 1}`;
1382
+ module.order = index + 1;
1383
+ return module;
1384
+ }).filter((module) => module.kind !== "unknown");
1385
+ }
1386
+
1387
+ const MODULE_EXTRACTORS = {
1388
+ "reviews:*": extractReviewModules,
1389
+ "card-collection:logo-gallery": extractLogoModules,
1390
+ "card-collection:*": extractRepeatingItemModules,
1391
+ "text-media:*": extractTextMediaModules,
1392
+ "hero:*": extractTextMediaModules,
1393
+ "stat-group:*": extractStatModules,
1394
+ "footer:*": extractStatModules,
1395
+ "category-strip:*": extractStatModules,
1396
+ "cta-strip:*": extractStatModules,
1397
+ "promo-band:*": extractStatModules,
1398
+ };
1399
+
1400
+ function extractSectionModules(sectionRoot, sectionType) {
1401
+ const exact = MODULE_EXTRACTORS[`${sectionType.kind}:${sectionType.variant}`];
1402
+ const family = MODULE_EXTRACTORS[`${sectionType.kind}:*`];
1403
+ const extractor = exact || family || extractGenericModules;
1404
+ return extractor(sectionRoot, sectionType);
1405
+ }
1406
+
1407
+ function extractSections() {
1408
+ const contentRootChoice = findContentRoot();
1409
+ const contentRoot = contentRootChoice.node || document.body;
1410
+ const segmentationRootChoice = findSegmentationRoot(contentRoot);
1411
+ const segmentationRoot = segmentationRootChoice.node || contentRoot;
1412
+ const diagnostics = {
1413
+ source: "dom-layout+a11y",
1414
+ contentRoot: contentRootChoice.label || "body",
1415
+ segmentationRootPath: domPath(segmentationRoot, contentRoot.parentElement || document.body.parentElement),
1416
+ accessibilitySnapshotAvailable: Boolean(repeaterPlan?.accessibilitySnapshotAvailable),
1417
+ accessibilityRoot: repeaterPlan?.accessibilityRoot || null,
1418
+ candidateCount: 0,
1419
+ acceptedSectionCount: 0,
1420
+ acceptedModuleCount: 0,
1421
+ warnings: [],
1422
+ };
1423
+ if (contentRootChoice.label !== "main" && contentRootChoice.label !== "role-main") {
1424
+ diagnostics.warnings.push(`content-root-fallback-used:${contentRootChoice.label}`);
1425
+ }
1426
+ diagnostics.warnings.push(...segmentationRootChoice.warnings);
1427
+ const primaryChildren = visibleChildren(segmentationRoot)
1428
+ .map((node) => collectModuleStats(node, segmentationRoot))
1429
+ .filter((stats) => stats.rect.height >= 24)
1430
+ .filter((stats) => stats.textLength >= 10 || stats.imageCount >= 1 || stats.buttonCount >= 1 || stats.linkCount >= 1);
1431
+ const primaryBandStats = primaryChildren
1432
+ .filter((stats) => isStructuralBand(stats, segmentationRoot))
1433
+ .map((stats) => ({
1434
+ ...stats,
1435
+ score: sectionScore(stats, segmentationRoot) + 22,
1436
+ isDirectChild: true,
1437
+ }))
1438
+ .filter((stats) => stats.score >= 30)
1439
+ .filter((stats) => stats.rect.height >= 40)
1440
+ .sort((a, b) => a.rect.top - b.rect.top);
1441
+ const candidateNodes = uniqueElements([
1442
+ ...primaryBandStats.map((stats) => stats.node),
1443
+ ...Array.from(segmentationRoot.querySelectorAll("section, article, aside, [role='region'], [data-testid], div"))
1444
+ .filter(isVisible)
1445
+ .slice(0, 260),
1446
+ ]).filter((node) => node !== contentRoot && node !== segmentationRoot);
1447
+ const candidateStats = candidateNodes
1448
+ .map((node) => collectModuleStats(node, segmentationRoot))
1449
+ .filter((stats) => stats.rect.width >= Math.min(segmentationRoot.getBoundingClientRect().width * 0.45, 520))
1450
+ .filter((stats) => stats.rect.height >= 80)
1451
+ .filter((stats) => stats.textLength >= 20 || stats.imageCount >= 1)
1452
+ .map((stats) => {
1453
+ let score = sectionScore(stats, segmentationRoot);
1454
+ if (stats.node.parentElement === segmentationRoot) score += 18;
1455
+ if (isStructuralBand(stats, segmentationRoot)) score += 8;
1456
+ return { ...stats, score, isDirectChild: stats.node.parentElement === segmentationRoot };
1457
+ })
1458
+ .sort((a, b) => a.rect.top - b.rect.top || b.score - a.score);
1459
+ diagnostics.candidateCount = candidateStats.length;
1460
+ const accepted = [...primaryBandStats];
1461
+ for (const stats of candidateStats) {
1462
+ if (stats.score < 35) continue;
1463
+ if (isWrapperLike(stats, segmentationRoot)) continue;
1464
+ const duplicate = accepted.find((existing) => overlaps(existing, stats));
1465
+ if (!duplicate) {
1466
+ accepted.push(stats);
1467
+ continue;
1468
+ }
1469
+ if (duplicate.isDirectChild && !stats.isDirectChild && duplicate.score >= stats.score - 15) continue;
1470
+ if (stats.isDirectChild && !duplicate.isDirectChild && stats.score >= duplicate.score - 15) {
1471
+ accepted.splice(accepted.indexOf(duplicate), 1, stats);
1472
+ continue;
1473
+ }
1474
+ const statsContainsDuplicate = stats.node.contains(duplicate.node);
1475
+ const duplicateContainsStats = duplicate.node.contains(stats.node);
1476
+ if (isWrapperLike(duplicate, segmentationRoot) && duplicateContainsStats) {
1477
+ accepted.splice(accepted.indexOf(duplicate), 1, stats);
1478
+ } else if (isWrapperLike(stats, segmentationRoot) && statsContainsDuplicate) {
1479
+ diagnostics.warnings.push(`nested-candidates-collapsed:${stats.path}`);
1480
+ continue;
1481
+ } else if (duplicateContainsStats && duplicate.rect.height > stats.rect.height * 1.5 && stats.score >= duplicate.score - 8) {
1482
+ accepted.splice(accepted.indexOf(duplicate), 1, stats);
1483
+ } else if (statsContainsDuplicate && stats.rect.height > duplicate.rect.height * 1.5) {
1484
+ diagnostics.warnings.push(`nested-candidates-collapsed:${stats.path}`);
1485
+ continue;
1486
+ } else if (stats.score > duplicate.score) {
1487
+ accepted.splice(accepted.indexOf(duplicate), 1, stats);
1488
+ }
1489
+ }
1490
+ if (!accepted.length) {
1491
+ diagnostics.warnings.push("no-strong-section-boundaries");
1492
+ }
1493
+ const sections = accepted.slice(0, 18).map((stats, index) => {
1494
+ const sectionRoot = stats.node;
1495
+ const sectionType = classifySection(stats);
1496
+ const modules = extractSectionModules(sectionRoot, sectionType).map((module, moduleIndex) => ({
1497
+ ...module,
1498
+ id: `section-${String(index + 1).padStart(3, "0")}-module-${String(moduleIndex + 1).padStart(3, "0")}`,
1499
+ order: moduleIndex + 1,
1500
+ }));
1501
+ diagnostics.acceptedModuleCount += modules.length;
1502
+ return {
1503
+ id: `section-${String(index + 1).padStart(3, "0")}`,
1504
+ kind: sectionType.kind,
1505
+ variant: sectionType.variant,
1506
+ capabilities: sectionType.capabilities,
1507
+ source: "dom-layout+a11y",
1508
+ tag: stats.tag,
1509
+ idAttr: sectionRoot.id || "",
1510
+ className: sectionRoot.className?.toString?.() || "",
1511
+ heading: stats.heading,
1512
+ text: stats.text,
1513
+ visibleCodeTexts: stats.visibleCodeTexts,
1514
+ order: index + 1,
1515
+ confidence: Number(Math.max(0.3, Math.min(0.98, stats.score / 100)).toFixed(2)),
1516
+ rect: {
1517
+ x: Math.round(stats.rect.x),
1518
+ y: Math.round(stats.rect.y),
1519
+ width: Math.round(stats.rect.width),
1520
+ height: Math.round(stats.rect.height),
1521
+ top: Math.round(stats.rect.top + window.scrollY),
1522
+ left: Math.round(stats.rect.left),
1523
+ },
1524
+ layoutHints: {
1525
+ fullWidth: stats.rect.width >= segmentationRoot.getBoundingClientRect().width * 0.85,
1526
+ columnCount: Math.max(1, Math.min(stats.rowCountEstimate > 1 ? Math.ceil(stats.cardLikeChildren / stats.rowCountEstimate) : stats.cardLikeChildren, 4)) || 1,
1527
+ hasGrid: stats.cardLikeChildren >= 3 && stats.rowCountEstimate >= 1,
1528
+ hasRepeatingChildren: stats.cardLikeChildren >= 3,
1529
+ },
1530
+ counts: {
1531
+ headings: stats.headings.length,
1532
+ paragraphs: stats.paragraphCount,
1533
+ images: stats.imageCount,
1534
+ links: stats.linkCount,
1535
+ buttons: stats.buttonCount,
1536
+ },
1537
+ background: stats.background,
1538
+ layoutEvidence: collectLayoutEvidence(sectionRoot),
1539
+ domRef: {
1540
+ tag: stats.tag,
1541
+ id: sectionRoot.id || "",
1542
+ classTokens: String(sectionRoot.className || "").split(/\s+/).filter(Boolean).slice(0, 6),
1543
+ path: stats.path,
1544
+ parentPath: stats.parentPath,
1545
+ nthOfType: stats.nthOfType,
1546
+ fingerprint: stats.fingerprint,
1547
+ },
1548
+ a11y: {
1549
+ role: stats.role || null,
1550
+ headingLevels: stats.headings.map((heading) => heading.level).filter(Boolean),
1551
+ containsList: sectionRoot.querySelectorAll("ul,ol,[role='list']").length > 0,
1552
+ },
1553
+ modules,
1554
+ warnings: [],
1555
+ };
1556
+ });
1557
+ diagnostics.acceptedSectionCount = sections.length;
1558
+ return { sections, diagnostics };
1559
+ }
1560
+
1561
+ const sectionResult = extractSections();
1562
+ const footer = (() => {
1563
+ const node = Array.from(document.querySelectorAll("footer, .site-footer, [role='contentinfo']")).find(isVisible);
1564
+ if (!node) return null;
1565
+ const links = Array.from(node.querySelectorAll("a[href]")).filter(isVisible).map(linkRecord);
1566
+ const cleanNodeText = (source) => renderedText(source);
1567
+ const text = cleanNodeText(node).slice(0, 6000);
1568
+ const legalText = Array.from(node.querySelectorAll("p, small, [class*='legal' i], [class*='disclaimer' i]"))
1569
+ .filter(isVisible)
1570
+ .map(cleanNodeText)
1571
+ .filter((value) => value.length >= 120 || /disclaimer|portfolio companies|copyright|©/i.test(value))
1572
+ .filter((value, index, values) => values.indexOf(value) === index)
1573
+ .join("\n\n") || text;
1574
+ return {
1575
+ text,
1576
+ legalText: legalText.slice(0, 6000),
1577
+ links: dedupeNav(links).slice(0, 80),
1578
+ domRef: {
1579
+ tag: node.tagName.toLowerCase(),
1580
+ id: node.id || "",
1581
+ classTokens: String(node.className || "").split(/\s+/).filter(Boolean).slice(0, 8),
1582
+ path: domPath(node, document.body.parentElement),
1583
+ },
1584
+ };
1585
+ })();
1586
+ const computedTokens = Array.from(document.querySelectorAll("body, h1, h2, h3, p, a, button")).slice(0, 80).map((node) => {
1587
+ const styles = getComputedStyle(node);
1588
+ return {
1589
+ tag: node.tagName.toLowerCase(),
1590
+ text: renderedText(node).slice(0, 120) || "",
1591
+ color: styles.color,
1592
+ backgroundColor: styles.backgroundColor,
1593
+ fontFamily: styles.fontFamily,
1594
+ fontSize: styles.fontSize,
1595
+ fontWeight: styles.fontWeight,
1596
+ borderRadius: styles.borderRadius,
1597
+ boxShadow: styles.boxShadow,
1598
+ };
1599
+ });
1600
+ const direction = document.documentElement.dir || document.body.dir || getComputedStyle(document.body).direction || "ltr";
1601
+ function nextVisibleNonHeadingSibling(node) {
1602
+ let current = node?.nextElementSibling || null;
1603
+ while (current) {
1604
+ if (isVisible(current) && !/^H[1-6]$/.test(current.tagName)) return current;
1605
+ current = current.nextElementSibling;
1606
+ }
1607
+ return null;
1608
+ }
1609
+
1610
+ function findContentContainerForHeading(heading) {
1611
+ const direct = nextVisibleNonHeadingSibling(heading);
1612
+ if (direct) return direct;
1613
+ const parentDirect = nextVisibleNonHeadingSibling(heading.parentElement);
1614
+ if (parentDirect) return parentDirect;
1615
+ return null;
1616
+ }
1617
+
1618
+ function nearestCommonAncestor(a, b, limit) {
1619
+ let current = a || null;
1620
+ while (current) {
1621
+ if (current.contains(b)) return current;
1622
+ if (limit && current === limit) break;
1623
+ current = current.parentElement;
1624
+ }
1625
+ return null;
1626
+ }
1627
+
1628
+ function uniqueElements(nodes) {
1629
+ const seen = new Set();
1630
+ const result = [];
1631
+ for (const node of nodes || []) {
1632
+ if (!node || seen.has(node)) continue;
1633
+ seen.add(node);
1634
+ result.push(node);
1635
+ }
1636
+ return result;
1637
+ }
1638
+
1639
+ function queryScopeNodes(scopeNodes, selector) {
1640
+ const seen = new Set();
1641
+ const matches = [];
1642
+ for (const node of scopeNodes || []) {
1643
+ if (node?.matches?.(selector) && !seen.has(node)) {
1644
+ seen.add(node);
1645
+ matches.push(node);
1646
+ }
1647
+ for (const child of node?.querySelectorAll?.(selector) || []) {
1648
+ if (seen.has(child)) continue;
1649
+ seen.add(child);
1650
+ matches.push(child);
1651
+ }
1652
+ }
1653
+ return matches;
1654
+ }
1655
+
1656
+ function collectContentNodes(content, nextHeadingNode) {
1657
+ if (!content) return [];
1658
+ const nodes = [];
1659
+ if (nextHeadingNode && content.parentElement && nextHeadingNode.parentElement === content.parentElement) {
1660
+ let current = content;
1661
+ while (current && current !== nextHeadingNode) {
1662
+ if (isVisible(current)) nodes.push(current);
1663
+ current = current.nextElementSibling;
1664
+ }
1665
+ }
1666
+ if (!nodes.length) nodes.push(content);
1667
+ return uniqueElements(nodes);
1668
+ }
1669
+
1670
+ function nodeSignature(node) {
1671
+ if (!node) return "";
1672
+ const tag = node.tagName.toLowerCase();
1673
+ const classTokens = String(node.className || "")
1674
+ .split(/\s+/)
1675
+ .filter(Boolean)
1676
+ .slice(0, 4)
1677
+ .sort()
1678
+ .join(".");
1679
+ const childTags = Array.from(node.children).slice(0, 8).map((child) => child.tagName.toLowerCase()).join(",");
1680
+ return `${tag}:${classTokens}:${childTags}`;
1681
+ }
1682
+
1683
+ function normalized(value) {
1684
+ return cleanText(value).toLowerCase();
1685
+ }
1686
+
1687
+ function extractParagraphs(scopeNodes, heading) {
1688
+ const seen = new Set();
1689
+ const paragraphs = [];
1690
+ const candidates = queryScopeNodes(scopeNodes, "p, li, blockquote, figcaption").filter(isVisible);
1691
+ for (const node of candidates) {
1692
+ const text = renderedText(node);
1693
+ if (!text || text.length < 20) continue;
1694
+ const key = normalized(text);
1695
+ if (seen.has(key)) continue;
1696
+ seen.add(key);
1697
+ paragraphs.push(text);
1698
+ }
1699
+ if (!paragraphs.length) {
1700
+ const fallbackBlocks = queryScopeNodes(scopeNodes, "div, span").filter((node) => {
1701
+ if (!isVisible(node) || node === heading || heading?.contains(node)) return false;
1702
+ if (node.querySelector("p, li, blockquote, figcaption")) return false;
1703
+ if (node.querySelector("img")) return false;
1704
+ const text = renderedText(node);
1705
+ if (!text || text.length < 30) return false;
1706
+ if (Array.from(node.children).some((child) => renderedText(child) === text)) return false;
1707
+ const display = getComputedStyle(node).display;
1708
+ return /block|flex|grid|inline-block|table/.test(display);
1709
+ });
1710
+ for (const node of fallbackBlocks) {
1711
+ const text = renderedText(node);
1712
+ const key = normalized(text);
1713
+ if (seen.has(key)) continue;
1714
+ seen.add(key);
1715
+ paragraphs.push(text);
1716
+ }
1717
+ }
1718
+ if (paragraphs.length) return paragraphs;
1719
+ const fallback = cleanText(scopeNodes.map((node) => renderedText(node)).join(" "));
1720
+ return fallback ? [fallback] : [];
1721
+ }
1722
+
1723
+ function extractImages(scopeNodes) {
1724
+ function bestSrcFromSet(value) {
1725
+ const candidates = String(value || "")
1726
+ .split(",")
1727
+ .map((item) => item.trim())
1728
+ .map((item) => {
1729
+ const match = item.match(/^(\S+)(?:\s+(\d+)w)?/);
1730
+ return match ? { url: match[1], width: Number(match[2] || 0) } : null;
1731
+ })
1732
+ .filter(Boolean)
1733
+ .sort((a, b) => b.width - a.width);
1734
+ return candidates[0]?.url || "";
1735
+ }
1736
+
1737
+ function bestImageSrc(img) {
1738
+ return (
1739
+ img.getAttribute("data-src")
1740
+ || img.getAttribute("data-lazy-src")
1741
+ || bestSrcFromSet(img.getAttribute("data-srcset"))
1742
+ || bestSrcFromSet(img.getAttribute("srcset"))
1743
+ || img.currentSrc
1744
+ || img.src
1745
+ || ""
1746
+ );
1747
+ }
1748
+
1749
+ const seen = new Set();
1750
+ return queryScopeNodes(scopeNodes, "img")
1751
+ .filter((img) => {
1752
+ const rect = img.getBoundingClientRect();
1753
+ return (isVisible(img) || Boolean(bestImageSrc(img))) && rect.width >= 24 && rect.height >= 24;
1754
+ })
1755
+ .map((img) => {
1756
+ const rect = img.getBoundingClientRect();
1757
+ return {
1758
+ src: bestImageSrc(img),
1759
+ alt: cleanText(img.getAttribute("alt") || ""),
1760
+ width: Math.round(rect.width),
1761
+ height: Math.round(rect.height),
1762
+ rect: {
1763
+ left: rect.left,
1764
+ top: rect.top,
1765
+ width: rect.width,
1766
+ height: rect.height,
1767
+ },
1768
+ };
1769
+ })
1770
+ .filter((image) => image.src && !image.src.startsWith("data:image/svg+xml"))
1771
+ .filter((image) => {
1772
+ if (seen.has(image.src)) return false;
1773
+ seen.add(image.src);
1774
+ return true;
1775
+ });
1776
+ }
1777
+
1778
+ function extractLinksFromContainer(scopeNodes) {
1779
+ const seen = new Set();
1780
+ return queryScopeNodes(scopeNodes, "a[href]")
1781
+ .map((anchor) => ({
1782
+ href: anchor.href || anchor.getAttribute("href") || "",
1783
+ label: semanticText(anchor).slice(0, 160),
1784
+ }))
1785
+ .filter((link) => link.href)
1786
+ .filter((link) => {
1787
+ const key = `${link.label}\u0000${link.href}`;
1788
+ if (seen.has(key)) return false;
1789
+ seen.add(key);
1790
+ return true;
1791
+ })
1792
+ .slice(0, 24);
1793
+ }
1794
+
1795
+ function inferItemLayoutHints(heading, scopeNodes, images, paragraphs) {
1796
+ const descendants = queryScopeNodes(scopeNodes, "img, p, li, blockquote, figcaption, div, span").filter((node) => {
1797
+ const text = renderedText(node);
1798
+ if (node.tagName === "IMG") return true;
1799
+ return text.length >= 20 && isVisible(node);
1800
+ });
1801
+ const firstKind = descendants[0]?.tagName === "IMG" ? "media-first" : descendants[0] ? "text-first" : "unknown";
1802
+ const textNodes = queryScopeNodes(scopeNodes, "p, li, blockquote, figcaption, div, span").filter((node) => {
1803
+ if (!isVisible(node) || node === heading || heading?.contains(node)) return false;
1804
+ const text = renderedText(node);
1805
+ if (!text || text.length < 20) return false;
1806
+ if (Array.from(node.children).some((child) => renderedText(child) === text)) return false;
1807
+ return true;
1808
+ });
1809
+ const averageCenter = (nodes) => {
1810
+ if (!nodes.length) return null;
1811
+ const centers = nodes.map((node) => {
1812
+ const rect = node.getBoundingClientRect();
1813
+ return rect.left + (rect.width / 2);
1814
+ });
1815
+ return centers.reduce((sum, value) => sum + value, 0) / centers.length;
1816
+ };
1817
+ const textCenter = averageCenter(textNodes);
1818
+ const imageCenter = images.length
1819
+ ? images.reduce((sum, image) => sum + (image.rect.left + (image.rect.width / 2)), 0) / images.length
1820
+ : null;
1821
+ let textPosition = "stacked";
1822
+ let mediaPosition = "stacked";
1823
+ if (textCenter != null && imageCenter != null) {
1824
+ const delta = imageCenter - textCenter;
1825
+ const scopeWidth = Math.max(...scopeNodes.map((node) => node.getBoundingClientRect().width), 0);
1826
+ const threshold = Math.max(scopeWidth * 0.12, 60);
1827
+ if (Math.abs(delta) > threshold) {
1828
+ mediaPosition = delta < 0 ? "left" : "right";
1829
+ textPosition = delta < 0 ? "right" : "left";
1830
+ }
1831
+ }
1832
+ return {
1833
+ mediaCount: images.length,
1834
+ paragraphCount: paragraphs.length,
1835
+ firstContentKind: firstKind,
1836
+ headingLevel: Number(heading.tagName.replace(/^H/i, "")) || null,
1837
+ textPosition,
1838
+ mediaPosition,
1839
+ };
1840
+ }
1841
+
1842
+ function scoreItemContainer(node, { heading, content, main }) {
1843
+ if (!node || !main.contains(node)) return Number.NEGATIVE_INFINITY;
1844
+ if (!node.contains(heading) || !node.contains(content)) return Number.NEGATIVE_INFINITY;
1845
+ const rect = node.getBoundingClientRect();
1846
+ if (rect.width < 180 || rect.height < 80) return Number.NEGATIVE_INFINITY;
1847
+ const classText = `${node.className || ""} ${node.id || ""}`.toLowerCase();
1848
+ const imageCount = node.querySelectorAll("img").length;
1849
+ const paragraphCount = node.querySelectorAll("p, li, blockquote, figcaption").length;
1850
+ const headingCount = node.querySelectorAll("h1,h2,h3,h4,h5,h6").length;
1851
+ let score = 0;
1852
+ score += 40;
1853
+ score += Math.min(imageCount, 6) * 6;
1854
+ score += Math.min(paragraphCount, 6) * 5;
1855
+ if (headingCount === 1) score += 18;
1856
+ else score -= Math.max(0, headingCount - 1) * 12;
1857
+ if (node === content) score -= 10;
1858
+ if (/slider|carousel|gallery|nav|menu|footer|share/.test(classText)) score -= 35;
1859
+ if (rect.height > window.innerHeight * 2.5) score -= 25;
1860
+ if (rect.width < Math.min(window.innerWidth * 0.45, 320)) score -= 20;
1861
+ return score;
1862
+ }
1863
+
1864
+ function findItemContainerForHeading(heading, main) {
1865
+ const content = findContentContainerForHeading(heading);
1866
+ if (!content) return null;
1867
+ const common = nearestCommonAncestor(heading, content, main) || nearestCommonAncestor(heading, content, document.body);
1868
+ const candidates = uniqueElements([
1869
+ common,
1870
+ content,
1871
+ content.parentElement,
1872
+ heading.parentElement,
1873
+ heading.parentElement?.parentElement,
1874
+ heading.closest("section,article,li,[data-testid],[class]"),
1875
+ content.closest("section,article,li,[data-testid],[class]"),
1876
+ ]).filter((node) => node && main.contains(node));
1877
+ const scored = candidates
1878
+ .map((node) => ({ node, score: scoreItemContainer(node, { heading, content, main }) }))
1879
+ .filter((entry) => Number.isFinite(entry.score))
1880
+ .sort((a, b) => b.score - a.score);
1881
+ const best = scored[0];
1882
+ if (!best) return null;
1883
+ return { container: best.node, content, score: best.score, signature: nodeSignature(best.node) };
1884
+ }
1885
+
1886
+ function dominantParentGroup(matches) {
1887
+ const groups = [];
1888
+ for (const match of matches) {
1889
+ const parent = match.container.parentElement;
1890
+ if (!parent) continue;
1891
+ const existing = groups.find((group) => group.parent === parent);
1892
+ if (existing) existing.items.push(match);
1893
+ else groups.push({ parent, items: [match] });
1894
+ }
1895
+ groups.sort((a, b) => b.items.length - a.items.length);
1896
+ return groups[0] || null;
1897
+ }
1898
+
1899
+ function extractRepeaters(plan) {
1900
+ const signals = Array.isArray(plan?.signals) ? plan.signals : [];
1901
+ const main = document.querySelector("main");
1902
+ const diagnostics = {
1903
+ source: "accessibility+dom",
1904
+ accessibilitySnapshotAvailable: Boolean(plan?.accessibilitySnapshotAvailable),
1905
+ signalCount: signals.length,
1906
+ candidateCount: plan?.candidateCount || signals.length,
1907
+ acceptedRepeaterCount: 0,
1908
+ signals: [],
1909
+ warnings: [...(plan?.warnings || [])],
1910
+ };
1911
+ if (!main) {
1912
+ diagnostics.warnings.push("main-content-missing: manual repeater review required");
1913
+ return { repeaters: [], diagnostics };
1914
+ }
1915
+ const headingNodes = Array.from(main.querySelectorAll("h1,h2,h3,h4,h5,h6")).filter((node) => renderedText(node));
1916
+ const headingRegistry = headingNodes.map((node, index) => ({
1917
+ node,
1918
+ index,
1919
+ text: renderedText(node),
1920
+ normalized: normalized(renderedText(node)),
1921
+ level: Number(node.tagName.replace(/^H/i, "")) || 0,
1922
+ }));
1923
+ const repeaters = signals.map((signal, signalIndex) => {
1924
+ let minIndex = -1;
1925
+ const matched = [];
1926
+ for (const expected of signal.items || []) {
1927
+ const expectedText = normalized(expected.heading);
1928
+ const match = headingRegistry.find((entry) => entry.index > minIndex && entry.level === signal.headingLevel && entry.normalized === expectedText);
1929
+ if (!match) continue;
1930
+ minIndex = match.index;
1931
+ const resolved = findItemContainerForHeading(match.node, main);
1932
+ if (!resolved) continue;
1933
+ matched.push({ ...resolved, heading: match });
1934
+ }
1935
+ const dominantParent = dominantParentGroup(matched);
1936
+ const resolvedItems = dominantParent && dominantParent.items.length >= 3 ? dominantParent.items : matched;
1937
+ const items = resolvedItems.map((resolved, resolvedIndex) => {
1938
+ const nextHeadingNode = resolvedItems[resolvedIndex + 1]?.heading?.node || null;
1939
+ const scopeNodes = collectContentNodes(resolved.content, nextHeadingNode);
1940
+ const paragraphs = extractParagraphs(scopeNodes, resolved.heading.node);
1941
+ const images = extractImages(scopeNodes);
1942
+ return {
1943
+ heading: resolved.heading.text,
1944
+ paragraphs,
1945
+ images,
1946
+ links: extractLinksFromContainer(scopeNodes),
1947
+ layoutHints: inferItemLayoutHints(resolved.heading.node, scopeNodes, images, paragraphs),
1948
+ };
1949
+ }).filter((item) => item.paragraphs.length && item.images.length);
1950
+ const signalDiagnostics = {
1951
+ label: signal.label || `main-repeater-${signalIndex + 1}`,
1952
+ roleSignature: signal.roleSignature || null,
1953
+ headingLevel: signal.headingLevel || null,
1954
+ expectedItemCount: signal.items?.length || 0,
1955
+ matchedHeadingCount: matched.length,
1956
+ resolvedItemCount: items.length,
1957
+ dominantParentTag: dominantParent?.parent?.tagName?.toLowerCase() || null,
1958
+ dominantParentClass: dominantParent?.parent?.className?.toString?.() || "",
1959
+ containerSignatures: Array.from(new Set(resolvedItems.map((item) => item.signature))).slice(0, 6),
1960
+ warnings: [],
1961
+ };
1962
+ diagnostics.signals.push(signalDiagnostics);
1963
+ if (items.length < 3) {
1964
+ signalDiagnostics.warnings.push("dom-resolution-ambiguous");
1965
+ diagnostics.warnings.push(`repeater-resolution-ambiguous:${signalDiagnostics.label}`);
1966
+ return null;
1967
+ }
1968
+ const mediaCounts = items.map((item) => item.images.length);
1969
+ const repeater = {
1970
+ kind: "content-repeater",
1971
+ source: "accessibility+dom",
1972
+ label: signal.label || `main-repeater-${signalIndex + 1}`,
1973
+ itemCount: items.length,
1974
+ schema: {
1975
+ hasHeading: items.every((item) => Boolean(item.heading)),
1976
+ hasParagraphs: items.every((item) => item.paragraphs.length > 0),
1977
+ hasImages: items.every((item) => item.images.length > 0),
1978
+ maxImagesPerItem: Math.max(...mediaCounts),
1979
+ },
1980
+ layoutHints: {
1981
+ alternating: new Set(items.map((item) => item.layoutHints.textPosition || item.layoutHints.firstContentKind)).size > 1,
1982
+ },
1983
+ items,
1984
+ };
1985
+ diagnostics.acceptedRepeaterCount += 1;
1986
+ return repeater;
1987
+ }).filter(Boolean);
1988
+ if (!signals.length) {
1989
+ const mainHeadingCount = main.querySelectorAll("h2,h3,h4,h5,h6").length;
1990
+ const mainImageCount = main.querySelectorAll("img").length;
1991
+ if (mainHeadingCount >= 4 && mainImageCount >= 4) {
1992
+ diagnostics.warnings.push("possible-repeated-content-without-high-confidence-structure: manual review required");
1993
+ }
1994
+ } else if (!repeaters.length) {
1995
+ diagnostics.warnings.push("accessibility-signals-found-but-no-structured-repeaters-resolved: manual review required");
1996
+ }
1997
+ return { repeaters, diagnostics };
1998
+ }
1999
+
2000
+ function extractAccordionRepeaters(main) {
2001
+ if (!main) return [];
2002
+ const groups = [];
2003
+ const candidates = Array.from(main.querySelectorAll("details")).filter((node) => renderedText(node.querySelector("summary")));
2004
+ for (const node of candidates) {
2005
+ const parent = node.parentElement;
2006
+ if (!parent) continue;
2007
+ const existing = groups.find((group) => group.parent === parent);
2008
+ if (existing) existing.items.push(node);
2009
+ else groups.push({ parent, items: [node] });
2010
+ }
2011
+ const nativeRecords = groups.filter((group) => group.items.length >= 2).map((group, groupIndex) => {
2012
+ const items = group.items.map((node) => {
2013
+ const summary = node.querySelector("summary");
2014
+ const clone = node.cloneNode(true);
2015
+ clone.querySelector("summary")?.remove();
2016
+ const paragraphs = Array.from(clone.querySelectorAll("p,li")).map((child) => semanticText(child)).filter(Boolean);
2017
+ const fallbackText = semanticText(clone);
2018
+ const images = Array.from(clone.querySelectorAll("img")).map((image) => ({ src: image.currentSrc || image.src || "", alt: image.alt || "" })).filter((image) => image.src && !/(emoji|twemoji|wp-smiley)/i.test(image.src));
2019
+ const links = Array.from(clone.querySelectorAll("a[href]")).map((link) => ({ text: semanticText(link), url: link.href })).filter((link) => link.url);
2020
+ const content = paragraphs.length ? paragraphs : fallbackText ? [fallbackText] : [];
2021
+ return {
2022
+ heading: renderedText(summary),
2023
+ paragraphs: content,
2024
+ images,
2025
+ links,
2026
+ initialExpanded: Boolean(node.open),
2027
+ itemStructure: images.length ? content.length ? "media-and-text" : "media-only" : "text-only",
2028
+ layoutHints: { mediaCount: images.length, paragraphCount: content.length, firstContentKind: images.length ? "media-first" : "text-first", headingLevel: null },
2029
+ };
2030
+ });
2031
+ const mediaCounts = items.map((item) => item.images.length);
2032
+ const expanded = items.map((item, index) => item.initialExpanded ? index : null).filter((index) => index !== null);
2033
+ return {
2034
+ kind: "accordion-repeater",
2035
+ source: "dom:details",
2036
+ label: cleanText(group.parent.getAttribute("aria-label")) || `accordion-${groupIndex + 1}`,
2037
+ itemCount: items.length,
2038
+ schema: { hasHeading: true, hasParagraphs: items.every((item) => item.paragraphs.length > 0), hasImages: items.some((item) => item.images.length > 0), maxImagesPerItem: Math.max(...mediaCounts, 0) },
2039
+ state: { initialExpandedIndices: expanded, multipleOpen: expanded.length > 1, openBehavior: "requires-interaction-probe" },
2040
+ items,
2041
+ };
2042
+ });
2043
+ const customRoots = Array.from(main.querySelectorAll(".tatsu-accordion, [class*='accordion' i]")).filter((root, index, all) =>
2044
+ root.querySelectorAll(".accordion-head").length >= 2 && !all.some((other) => other !== root && other.contains(root) && other.querySelectorAll(".accordion-head").length >= 2),
2045
+ );
2046
+ const customRecords = customRoots.map((root, rootIndex) => {
2047
+ const heads = Array.from(root.querySelectorAll(".accordion-head")).filter((head) => renderedText(head));
2048
+ const items = heads.map((head) => {
2049
+ const contentRoot = head.nextElementSibling?.matches?.(".accordion-content,[role='region']") ? head.nextElementSibling : document.getElementById(head.getAttribute("aria-controls") || "");
2050
+ const paragraphs = Array.from(contentRoot?.querySelectorAll?.("p,li") || []).map((child) => semanticText(child)).filter(Boolean);
2051
+ const fallbackText = contentRoot ? semanticText(contentRoot) : "";
2052
+ const content = paragraphs.length ? paragraphs : fallbackText ? [fallbackText] : [];
2053
+ const images = Array.from(contentRoot?.querySelectorAll?.("img") || []).map((image) => ({ src: image.currentSrc || image.src || "", alt: image.alt || "" })).filter((image) => image.src && !/(emoji|twemoji|wp-smiley)/i.test(image.src));
2054
+ const links = Array.from(contentRoot?.querySelectorAll?.("a[href]") || []).map((link) => ({ text: semanticText(link), url: link.href })).filter((link) => link.url);
2055
+ const expanded = head.getAttribute("aria-expanded") === "true" || contentRoot && getComputedStyle(contentRoot).display !== "none" && getComputedStyle(contentRoot).visibility !== "hidden" && contentRoot.getBoundingClientRect().height > 0;
2056
+ return { heading: renderedText(head), paragraphs: content, images, links, initialExpanded: Boolean(expanded), itemStructure: images.length ? content.length ? "media-and-text" : "media-only" : "text-only", layoutHints: { mediaCount: images.length, paragraphCount: content.length, firstContentKind: images.length ? "media-first" : "text-first", headingLevel: Number(head.tagName.slice(1)) || null } };
2057
+ });
2058
+ const expanded = items.map((item, index) => item.initialExpanded ? index : null).filter((index) => index !== null);
2059
+ return { kind: "accordion-repeater", source: "dom:accordion-head", label: cleanText(root.getAttribute("aria-label")) || `accordion-${rootIndex + 1}`, itemCount: items.length, schema: { hasHeading: true, hasParagraphs: items.every((item) => item.paragraphs.length > 0), hasImages: items.some((item) => item.images.length > 0), maxImagesPerItem: Math.max(0, ...items.map((item) => item.images.length)) }, state: { initialExpandedIndices: expanded, multipleOpen: expanded.length > 1, openBehavior: root.classList.contains("tatsu-accordion") ? "single" : "requires-interaction-probe" }, items };
2060
+ }).filter((record) => record.itemCount >= 2 && record.items.every((item) => item.paragraphs.length));
2061
+ return [...nativeRecords, ...customRecords];
2062
+ }
2063
+
2064
+ const repeaterResult = extractRepeaters(repeaterPlan);
2065
+ const accordionRepeaters = extractAccordionRepeaters(document.querySelector("main, [role='main'], #main, #content") || document.body);
2066
+ const visualAssets = collectVisualAssets();
2067
+ const visibleLinks = dedupeNav(Array.from(document.querySelectorAll("a[href]")).filter(isVisible).map(linkRecord)).slice(0, 240)
2068
+ .map((link) => ({ text: link.label, url: link.href }));
2069
+ return {
2070
+ sections: sectionResult.sections,
2071
+ sectionDiagnostics: sectionResult.diagnostics,
2072
+ footer,
2073
+ computedTokens,
2074
+ repeaters: [...repeaterResult.repeaters, ...accordionRepeaters],
2075
+ repeaterDiagnostics: repeaterResult.diagnostics,
2076
+ ignoredSurfaces,
2077
+ visualAssets,
2078
+ links: visibleLinks,
2079
+ chrome: {
2080
+ direction,
2081
+ viewport: { width: window.innerWidth, height: window.innerHeight },
2082
+ header: {
2083
+ variants: [initialHeader, scrolledHeader],
2084
+ maxStickyViewportRatio: 0.16,
2085
+ },
2086
+ navigation,
2087
+ heroEvidence: {
2088
+ visibleText,
2089
+ forbiddenSeoOnlyText: [],
2090
+ },
2091
+ },
2092
+ };
2093
+ }, repeaterPlan);
2094
+ const normalizedDesktopDom = await page.content();
2095
+ browserData.chrome.heroEvidence.forbiddenSeoOnlyText = seoOnlyText({ seo, visibleText: browserData.chrome.heroEvidence.visibleText });
2096
+ const extractionWarnings = [
2097
+ ...((desktopNavigation.warnings || []).map((warning) => `navigation: ${warning}`)),
2098
+ ...(browserData.sectionDiagnostics?.warnings || []),
2099
+ ...(browserData.repeaterDiagnostics?.warnings || []),
2100
+ ];
2101
+ return {
2102
+ url,
2103
+ path: new URL(url).pathname || "/",
2104
+ area: classifyUrl(url),
2105
+ title: seo.title,
2106
+ seo,
2107
+ navigation: desktopNavigation,
2108
+ sections: browserData.sections.length ? browserData.sections : extractSectionsFromHtml(html),
2109
+ sectionDiagnostics: browserData.sectionDiagnostics || {
2110
+ source: "dom-layout+a11y",
2111
+ contentRoot: "unknown",
2112
+ accessibilitySnapshotAvailable: Boolean(repeaterPlan?.accessibilitySnapshotAvailable),
2113
+ accessibilityRoot: repeaterPlan?.accessibilityRoot || null,
2114
+ candidateCount: 0,
2115
+ acceptedSectionCount: 0,
2116
+ acceptedModuleCount: 0,
2117
+ warnings: ["no-strong-section-boundaries"],
2118
+ },
2119
+ repeaters: browserData.repeaters || [],
2120
+ repeaterDiagnostics: browserData.repeaterDiagnostics || { source: "accessibility+dom", signalCount: 0, acceptedRepeaterCount: 0, warnings: [] },
2121
+ footer: browserData.footer || null,
2122
+ ignoredSurfaces: browserData.ignoredSurfaces || [],
2123
+ normalizationActions: (browserData.ignoredSurfaces || []).flatMap((surface) => [
2124
+ {
2125
+ phase: "before-scroll-sweep",
2126
+ action: "quarantine-obstructive-surface",
2127
+ surfaceId: surface.id,
2128
+ kind: surface.kind,
2129
+ reason: surface.reason,
2130
+ reversible: true,
2131
+ },
2132
+ {
2133
+ phase: "after-scroll-sweep",
2134
+ action: "verify-surface-remains-quarantined",
2135
+ surfaceId: surface.id,
2136
+ kind: surface.kind,
2137
+ reversible: true,
2138
+ },
2139
+ ]),
2140
+ visualAssets: browserData.visualAssets || [],
2141
+ links: browserData.links?.length ? browserData.links : extractLinks(html, url),
2142
+ tokens: { ...inferTokensFromHtml(html), computed: browserData.computedTokens },
2143
+ chrome: browserData.chrome,
2144
+ responsiveTextGeometry,
2145
+ screenshots: screenshotMap,
2146
+ navigationProfiles,
2147
+ rawDomSnapshots: htmlByViewport,
2148
+ normalizedDomSnapshots: { desktop: normalizedDesktopDom },
2149
+ accessibilityEvidence: {
2150
+ snapshotAvailable: Boolean(repeaterPlan?.accessibilitySnapshotAvailable),
2151
+ root: repeaterPlan?.accessibilityRoot || null,
2152
+ signalCount: repeaterPlan?.signals?.length || 0,
2153
+ },
2154
+ extractionWarnings: extractionWarnings.length ? extractionWarnings : undefined,
2155
+ extractedAt: new Date().toISOString(),
2156
+ };
2157
+ } finally {
2158
+ await browser.close();
2159
+ }
2160
+ }
2161
+
2162
+ async function captureImportantTextGeometry(page) {
2163
+ return page.evaluate(() => {
2164
+ const clean = (value) => String(value || "").replace(/\s+/g, " ").trim();
2165
+ const visible = (node) => {
2166
+ const rect = node.getBoundingClientRect();
2167
+ const style = getComputedStyle(node);
2168
+ return rect.width > 0 && rect.height > 0 && style.display !== "none" && style.visibility !== "hidden" && Number(style.opacity || 1) > 0;
2169
+ };
2170
+ const lineCount = (node) => {
2171
+ const tops = [];
2172
+ const walker = document.createTreeWalker(node, NodeFilter.SHOW_TEXT);
2173
+ for (let textNode = walker.nextNode(); textNode; textNode = walker.nextNode()) {
2174
+ if (!clean(textNode.nodeValue)) continue;
2175
+ const range = document.createRange();
2176
+ range.selectNodeContents(textNode);
2177
+ for (const rect of range.getClientRects()) {
2178
+ if (rect.width > 0 && rect.height > 0 && !tops.some((top) => Math.abs(top - rect.top) <= 2)) tops.push(rect.top);
2179
+ }
2180
+ }
2181
+ return Math.max(1, tops.length);
2182
+ };
2183
+ return Array.from(document.querySelectorAll("h1,h2,h3"))
2184
+ .filter(visible)
2185
+ .map((node) => {
2186
+ const rect = node.getBoundingClientRect();
2187
+ const style = getComputedStyle(node);
2188
+ const lines = lineCount(node);
2189
+ return {
2190
+ text: clean(node.textContent).slice(0, 240),
2191
+ tag: node.tagName.toLowerCase(),
2192
+ inlineSize: Math.round(rect.width * 100) / 100,
2193
+ blockSize: Math.round(rect.height * 100) / 100,
2194
+ lineCount: lines,
2195
+ wrapPolicy: lines === 1 ? "single-line" : style.whiteSpace === "nowrap" ? "clipped-or-overflowing" : "wrapped",
2196
+ fontSize: style.fontSize,
2197
+ lineHeight: style.lineHeight,
2198
+ maxWidth: style.maxWidth,
2199
+ };
2200
+ })
2201
+ .filter((item) => item.text)
2202
+ .slice(0, 48);
2203
+ });
2204
+ }
2205
+
2206
+ async function extractWithFetch(url) {
2207
+ const html = await fetchText(url);
2208
+ const seo = extractSeo(html, url);
2209
+ return {
2210
+ url,
2211
+ path: new URL(url).pathname || "/",
2212
+ area: classifyUrl(url),
2213
+ title: seo.title,
2214
+ seo,
2215
+ sections: extractSectionsFromHtml(html),
2216
+ sectionDiagnostics: {
2217
+ source: "none",
2218
+ contentRoot: "none",
2219
+ accessibilitySnapshotAvailable: false,
2220
+ accessibilityRoot: null,
2221
+ candidateCount: 0,
2222
+ acceptedSectionCount: 0,
2223
+ acceptedModuleCount: 0,
2224
+ warnings: ["browser-unavailable: section extraction requires browser-based DOM, layout, and accessibility evidence"],
2225
+ },
2226
+ repeaters: [],
2227
+ repeaterDiagnostics: {
2228
+ source: "none",
2229
+ accessibilitySnapshotAvailable: false,
2230
+ signalCount: 0,
2231
+ acceptedRepeaterCount: 0,
2232
+ warnings: ["browser-unavailable: repeater extraction requires browser-based accessibility and DOM evidence"],
2233
+ },
2234
+ footer: null,
2235
+ links: extractLinks(html, url),
2236
+ tokens: inferTokensFromHtml(html),
2237
+ chrome: {
2238
+ direction: "unknown",
2239
+ header: { variants: [], maxStickyViewportRatio: 0.16 },
2240
+ navigation: [],
2241
+ heroEvidence: {
2242
+ visibleText: [],
2243
+ forbiddenSeoOnlyText: seoOnlyText({ seo, visibleText: [] }),
2244
+ },
2245
+ warnings: ["browser-unavailable: header behavior, dropdown hierarchy, logo footprint, and hero visible-text evidence require manual screenshot review"],
2246
+ },
2247
+ screenshots: {},
2248
+ extractionWarnings: ["browser-unavailable: repeater extraction requires browser-based accessibility and DOM evidence"],
2249
+ extractedAt: new Date().toISOString(),
2250
+ };
2251
+ }
2252
+
2253
+ async function detectRepeaterSignals(page) {
2254
+ const candidates = [
2255
+ { selector: 'main, [role="main"]', label: "main" },
2256
+ { selector: "article", label: "article" },
2257
+ { selector: "body", label: "body" },
2258
+ ];
2259
+ const failures = [];
2260
+ for (const candidate of candidates) {
2261
+ const locator = page.locator(candidate.selector).first();
2262
+ const count = await locator.count();
2263
+ if (count === 0) {
2264
+ failures.push(`${candidate.label}: selector did not match any element`);
2265
+ continue;
2266
+ }
2267
+ try {
2268
+ const snapshot = await locator.ariaSnapshot({ mode: "ai" });
2269
+ const detection = detectRepeaterSignalsFromAria(snapshot);
2270
+ return {
2271
+ accessibilitySnapshotAvailable: true,
2272
+ accessibilityRoot: candidate.label,
2273
+ candidateCount: detection.candidateCount,
2274
+ signals: detection.signals,
2275
+ warnings: detection.signals.length
2276
+ ? []
2277
+ : [`accessibility-snapshot-captured-on-${candidate.label}-but-no-high-confidence-repeater-patterns-found`],
2278
+ };
2279
+ } catch (error) {
2280
+ failures.push(`${candidate.label}: ${error.message}`);
2281
+ }
2282
+ }
2283
+ return {
2284
+ accessibilitySnapshotAvailable: false,
2285
+ accessibilityRoot: null,
2286
+ candidateCount: 0,
2287
+ signals: [],
2288
+ warnings: [`accessibility-snapshot-unavailable: ${failures.join("; ")}`],
2289
+ };
2290
+ }
2291
+
2292
+ async function navigateWithAdaptiveProfile(page, url) {
2293
+ const profiles = [
2294
+ { name: "normal", waitUntil: "networkidle", timeoutMs: 45000, settleMs: 0 },
2295
+ { name: "slow-site", waitUntil: "domcontentloaded", timeoutMs: 90000, settleMs: 2000 },
2296
+ ];
2297
+ const warnings = [];
2298
+ let lastError = null;
2299
+ for (const profile of profiles) {
2300
+ try {
2301
+ await page.goto(url, { waitUntil: profile.waitUntil, timeout: profile.timeoutMs });
2302
+ if (profile.settleMs > 0) {
2303
+ await page.waitForTimeout(profile.settleMs);
2304
+ }
2305
+ if (profile.name !== "normal") {
2306
+ warnings.push(`used ${profile.name} navigation profile after the default profile did not settle cleanly`);
2307
+ }
2308
+ return {
2309
+ profile: profile.name,
2310
+ waitUntil: profile.waitUntil,
2311
+ timeoutMs: profile.timeoutMs,
2312
+ warnings,
2313
+ };
2314
+ } catch (error) {
2315
+ lastError = error;
2316
+ warnings.push(`${profile.name} failed: ${error.message}`);
2317
+ }
2318
+ }
2319
+ throw lastError;
2320
+ }
2321
+
2322
+ function detectRepeaterSignalsFromAria(snapshot) {
2323
+ const tree = parseAriaSnapshot(snapshot);
2324
+ if (!tree) return { signals: [], candidateCount: 0 };
2325
+ const mains = [];
2326
+ walkAriaTree(tree, (node) => {
2327
+ if (node.role === "main") mains.push(node);
2328
+ });
2329
+ const root = mains.at(-1) || tree;
2330
+ const candidates = [];
2331
+ walkAriaTree(root, (node) => {
2332
+ const grouped = groupHeadingContentPairs(node.children || []);
2333
+ for (const group of grouped) {
2334
+ if (group.items.length >= 3) candidates.push(group);
2335
+ }
2336
+ });
2337
+ candidates.sort((a, b) => {
2338
+ if (b.items.length !== a.items.length) return b.items.length - a.items.length;
2339
+ return averageMediaCount(b.items) - averageMediaCount(a.items);
2340
+ });
2341
+ return {
2342
+ candidateCount: candidates.length,
2343
+ signals: candidates.slice(0, 3).map((candidate, index) => ({
2344
+ label: `main-repeated-content-${index + 1}`,
2345
+ headingLevel: candidate.headingLevel,
2346
+ roleSignature: `heading-${candidate.headingLevel}+generic`,
2347
+ items: candidate.items.map((item) => ({
2348
+ heading: item.heading,
2349
+ paragraphCount: item.paragraphCount,
2350
+ imageCount: item.imageCount,
2351
+ })),
2352
+ })),
2353
+ };
2354
+ }
2355
+
2356
+ function averageMediaCount(items) {
2357
+ return items.reduce((sum, item) => sum + item.imageCount, 0) / Math.max(items.length, 1);
2358
+ }
2359
+
2360
+ function groupHeadingContentPairs(children = []) {
2361
+ const grouped = new Map();
2362
+ for (let index = 0; index < children.length - 1; index += 1) {
2363
+ const heading = children[index];
2364
+ const content = children[index + 1];
2365
+ if (heading.role !== "heading" || content.role !== "generic") continue;
2366
+ const headingLevel = heading.level || 0;
2367
+ if (headingLevel < 2) continue;
2368
+ const paragraphCount = countAriaRole(content, "paragraph");
2369
+ const imageCount = countAriaRole(content, "img");
2370
+ if (!heading.name || paragraphCount < 1 || imageCount < 1) continue;
2371
+ const key = String(headingLevel);
2372
+ if (!grouped.has(key)) grouped.set(key, { headingLevel, items: [] });
2373
+ grouped.get(key).items.push({
2374
+ heading: heading.name,
2375
+ paragraphCount,
2376
+ imageCount,
2377
+ });
2378
+ }
2379
+ return Array.from(grouped.values());
2380
+ }
2381
+
2382
+ function countAriaRole(node, role) {
2383
+ let count = 0;
2384
+ walkAriaTree(node, (current) => {
2385
+ if (current.role === role) count += 1;
2386
+ });
2387
+ return count;
2388
+ }
2389
+
2390
+ function walkAriaTree(node, visit) {
2391
+ if (!node) return;
2392
+ visit(node);
2393
+ for (const child of node.children || []) walkAriaTree(child, visit);
2394
+ }
2395
+
2396
+ function parseAriaSnapshot(snapshot) {
2397
+ const lines = String(snapshot || "")
2398
+ .split("\n")
2399
+ .map((line) => line.match(/^(\s*)- (.*)$/))
2400
+ .filter(Boolean)
2401
+ .map(([, indent, content]) => ({ depth: Math.floor(indent.length / 2), content }));
2402
+ if (!lines.length) return null;
2403
+ const root = { role: "root", children: [] };
2404
+ const stack = [root];
2405
+ for (const line of lines) {
2406
+ const node = parseAriaLine(line.content);
2407
+ if (!node) continue;
2408
+ while (stack.length > line.depth + 1) stack.pop();
2409
+ const parent = stack[stack.length - 1];
2410
+ parent.children.push(node);
2411
+ stack.push(node);
2412
+ }
2413
+ return root;
2414
+ }
2415
+
2416
+ function parseAriaLine(content) {
2417
+ const trimmed = String(content || "").trim();
2418
+ if (!trimmed) return null;
2419
+ if (trimmed.startsWith("text:")) {
2420
+ return { role: "text", name: trimmed.slice(5).trim(), children: [] };
2421
+ }
2422
+ const roleMatch = trimmed.match(/^([a-zA-Z0-9-]+)/);
2423
+ if (!roleMatch) return null;
2424
+ const role = roleMatch[1];
2425
+ const nameMatch = trimmed.match(/"([^"]+)"/);
2426
+ const levelMatch = trimmed.match(/\[level=(\d+)\]/);
2427
+ return {
2428
+ role,
2429
+ name: nameMatch?.[1] || "",
2430
+ level: levelMatch ? Number(levelMatch[1]) : null,
2431
+ raw: trimmed,
2432
+ children: [],
2433
+ };
2434
+ }
2435
+
2436
+ function seoOnlyText({ seo, visibleText }) {
2437
+ const visible = new Set((visibleText || []).map(normalizeEvidenceText));
2438
+ return [
2439
+ seo?.title,
2440
+ seo?.openGraph?.["og:title"],
2441
+ seo?.jsonLd?.name,
2442
+ seo?.jsonLd?.description,
2443
+ seo?.description,
2444
+ ]
2445
+ .flat()
2446
+ .filter(Boolean)
2447
+ .map((value) => String(value).replace(/&#039;/g, "'").replace(/&quot;/g, "\"").replace(/&amp;/g, "&").trim())
2448
+ .filter((value, index, values) => values.indexOf(value) === index)
2449
+ .filter((value) => !visible.has(normalizeEvidenceText(value)));
2450
+ }
2451
+
2452
+ function normalizeEvidenceText(value) {
2453
+ return String(value || "").replace(/\s+/g, " ").trim().toLowerCase();
2454
+ }
2455
+
2456
+ if (import.meta.url === `file://${process.argv[1]}`) {
2457
+ main().catch((error) => {
2458
+ console.error(error.stack || error.message);
2459
+ process.exit(1);
2460
+ });
2461
+ }