@palate/cli 0.0.0-stage → 0.0.2

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 (1222) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +153 -2
  3. package/bin/palate.mjs +894 -0
  4. package/kb/anatomy/data-table.md +24 -0
  5. package/kb/anatomy/empty-state.md +16 -0
  6. package/kb/anatomy/filter-toolbar.md +19 -0
  7. package/kb/anatomy/meter.md +16 -0
  8. package/kb/anatomy/product-window.md +18 -0
  9. package/kb/anatomy/settings-page.md +18 -0
  10. package/kb/anatomy/status-indicator.md +17 -0
  11. package/kb/anatomy/toast-undo.md +16 -0
  12. package/kb/bench/briefs.json +618 -0
  13. package/kb/craft/SOURCES.md +48 -0
  14. package/kb/craft/creativity.md +55 -0
  15. package/kb/craft/details.md +75 -0
  16. package/kb/craft/landing-demos.md +46 -0
  17. package/kb/craft/mistakes.md +73 -0
  18. package/kb/craft/motion.md +53 -0
  19. package/kb/craft/principles.md +68 -0
  20. package/kb/index/discovery.json +1 -0
  21. package/kb/inspiration/index.json +79 -0
  22. package/kb/moves/animated-comparison-table.md +58 -0
  23. package/kb/moves/before-after-slider.md +58 -0
  24. package/kb/moves/bento-live-tiles.md +57 -0
  25. package/kb/moves/calm-motion-system.md +63 -0
  26. package/kb/moves/canvas-grain.md +58 -0
  27. package/kb/moves/changelog-as-magazine.md +56 -0
  28. package/kb/moves/code-drawn-diagram.md +56 -0
  29. package/kb/moves/colophon.md +54 -0
  30. package/kb/moves/command-palette-site-nav.md +58 -0
  31. package/kb/moves/confetti-free-celebration.md +58 -0
  32. package/kb/moves/console-easter-egg.md +57 -0
  33. package/kb/moves/copy-confirmation.md +57 -0
  34. package/kb/moves/cursor-reactive-light.md +57 -0
  35. package/kb/moves/dark-band-contrast-section.md +55 -0
  36. package/kb/moves/designed-dark-mode.md +58 -0
  37. package/kb/moves/duotone-photography.md +55 -0
  38. package/kb/moves/editorial-drop-cap.md +58 -0
  39. package/kb/moves/editorial-grid-pull-quotes.md +55 -0
  40. package/kb/moves/engraving-colour-lens.md +64 -0
  41. package/kb/moves/explorable-control.md +57 -0
  42. package/kb/moves/feature-films-on-painted-stages.md +58 -0
  43. package/kb/moves/first-win-onboarding.md +59 -0
  44. package/kb/moves/footer-live-status.md +56 -0
  45. package/kb/moves/footer-rewards-the-end.md +58 -0
  46. package/kb/moves/generative-brand-pattern.md +55 -0
  47. package/kb/moves/gentle-error-nudge.md +59 -0
  48. package/kb/moves/halftone-dot-imagery.md +55 -0
  49. package/kb/moves/hand-drawn-annotations.md +55 -0
  50. package/kb/moves/hero-paints-itself-in.md +65 -0
  51. package/kb/moves/honest-progress-narration.md +58 -0
  52. package/kb/moves/honest-testimonial-wall.md +57 -0
  53. package/kb/moves/inline-sparklines-in-copy.md +57 -0
  54. package/kb/moves/inspect-our-taste.md +58 -0
  55. package/kb/moves/interactive-pricing-calculator.md +59 -0
  56. package/kb/moves/isometric-illustration-system.md +56 -0
  57. package/kb/moves/kinetic-variable-type.md +57 -0
  58. package/kb/moves/konami-secret.md +59 -0
  59. package/kb/moves/layered-paper-depth.md +56 -0
  60. package/kb/moves/lightweight-3d-object-hero.md +59 -0
  61. package/kb/moves/line-engraving-from-image.md +63 -0
  62. package/kb/moves/link-preview-peek.md +57 -0
  63. package/kb/moves/live-data-ticker.md +61 -0
  64. package/kb/moves/made-with-proof.md +51 -0
  65. package/kb/moves/magnetic-buttons.md +55 -0
  66. package/kb/moves/marquee-done-right.md +58 -0
  67. package/kb/moves/moves.yaml +450 -0
  68. package/kb/moves/museum-painting-backdrop.md +59 -0
  69. package/kb/moves/one-colour-brand-world.md +56 -0
  70. package/kb/moves/playful-404.md +58 -0
  71. package/kb/moves/pointer-highlight-word.md +56 -0
  72. package/kb/moves/product-in-context.md +56 -0
  73. package/kb/moves/real-world-data.md +58 -0
  74. package/kb/moves/referral-reward-reveal.md +59 -0
  75. package/kb/moves/riso-print-texture.md +57 -0
  76. package/kb/moves/screenshot-detail-crops.md +56 -0
  77. package/kb/moves/scroll-progress-chapters.md +57 -0
  78. package/kb/moves/scroll-scrubbed-product-film.md +62 -0
  79. package/kb/moves/self-playing-product-demo.md +61 -0
  80. package/kb/moves/shape-continuity-morph.md +58 -0
  81. package/kb/moves/sliding-nav-indicator.md +58 -0
  82. package/kb/moves/sticky-scroll-story.md +56 -0
  83. package/kb/moves/story-led-long-scroll.md +56 -0
  84. package/kb/moves/tactile-button.md +63 -0
  85. package/kb/moves/technical-drawing-lines.md +57 -0
  86. package/kb/moves/time-of-day-theming.md +56 -0
  87. package/kb/moves/tiny-game-easter-egg.md +60 -0
  88. package/kb/moves/type-decode-reveal.md +57 -0
  89. package/kb/moves/typographic-numeral-hero.md +58 -0
  90. package/kb/moves/view-transitions-between-pages.md +57 -0
  91. package/kb/moves/waitlist-position-live.md +60 -0
  92. package/kb/patterns/app-team-members.md +27 -0
  93. package/kb/patterns/chat-agent-approvals.md +31 -0
  94. package/kb/patterns/commerce-product-page.md +34 -0
  95. package/kb/patterns/dashboard-entity-table.md +37 -0
  96. package/kb/patterns/dashboard-home-today.md +24 -0
  97. package/kb/patterns/docs-api-reference.md +31 -0
  98. package/kb/patterns/marketing-product-led-landing.md +30 -0
  99. package/kb/patterns/mobile-daily-checklist.md +26 -0
  100. package/kb/references/index.json +1 -0
  101. package/kb/references/thumbs/004c5e9072.jpg +0 -0
  102. package/kb/references/thumbs/008915eb21.jpg +0 -0
  103. package/kb/references/thumbs/0098cf66c2.jpg +0 -0
  104. package/kb/references/thumbs/012e61b31f.jpg +0 -0
  105. package/kb/references/thumbs/014d4e46fc.jpg +0 -0
  106. package/kb/references/thumbs/01e54f394a.jpg +0 -0
  107. package/kb/references/thumbs/01e99a579c.jpg +0 -0
  108. package/kb/references/thumbs/01fd014575.jpg +0 -0
  109. package/kb/references/thumbs/022778ca72.jpg +0 -0
  110. package/kb/references/thumbs/02717bf1e1.jpg +0 -0
  111. package/kb/references/thumbs/02a5cfdbe9.jpg +0 -0
  112. package/kb/references/thumbs/02c9823f8d.jpg +0 -0
  113. package/kb/references/thumbs/02f3974613.jpg +0 -0
  114. package/kb/references/thumbs/032427f1ce.jpg +0 -0
  115. package/kb/references/thumbs/03ba677d07.jpg +0 -0
  116. package/kb/references/thumbs/04f2476cfe.jpg +0 -0
  117. package/kb/references/thumbs/0563e7c52e.jpg +0 -0
  118. package/kb/references/thumbs/05fa6b8eef.jpg +0 -0
  119. package/kb/references/thumbs/065bce1517.jpg +0 -0
  120. package/kb/references/thumbs/06c017f113.jpg +0 -0
  121. package/kb/references/thumbs/0770e2d8d3.jpg +0 -0
  122. package/kb/references/thumbs/078a26c4cb.jpg +0 -0
  123. package/kb/references/thumbs/08679b6e84.jpg +0 -0
  124. package/kb/references/thumbs/088c45a34c.jpg +0 -0
  125. package/kb/references/thumbs/089e9fd4e4.jpg +0 -0
  126. package/kb/references/thumbs/08f773231e.jpg +0 -0
  127. package/kb/references/thumbs/093ca3e2e3.jpg +0 -0
  128. package/kb/references/thumbs/0963320f74.jpg +0 -0
  129. package/kb/references/thumbs/09fc1ded85.jpg +0 -0
  130. package/kb/references/thumbs/0a368f3963.jpg +0 -0
  131. package/kb/references/thumbs/0a9e0e5201.jpg +0 -0
  132. package/kb/references/thumbs/0aace67ee7.jpg +0 -0
  133. package/kb/references/thumbs/0b61ecf793.jpg +0 -0
  134. package/kb/references/thumbs/0bde18ae09.jpg +0 -0
  135. package/kb/references/thumbs/0bf7ee8143.jpg +0 -0
  136. package/kb/references/thumbs/0ca20a13e0.jpg +0 -0
  137. package/kb/references/thumbs/0cdc0198c4.jpg +0 -0
  138. package/kb/references/thumbs/0cf9790b28.jpg +0 -0
  139. package/kb/references/thumbs/0d0202904b.jpg +0 -0
  140. package/kb/references/thumbs/0d053aba18.jpg +0 -0
  141. package/kb/references/thumbs/0d0c2c13d2.jpg +0 -0
  142. package/kb/references/thumbs/0d30be99b0.jpg +0 -0
  143. package/kb/references/thumbs/0d503da1b7.jpg +0 -0
  144. package/kb/references/thumbs/0ddad7b740.jpg +0 -0
  145. package/kb/references/thumbs/0e0a148101.jpg +0 -0
  146. package/kb/references/thumbs/0e401b6b75.jpg +0 -0
  147. package/kb/references/thumbs/0e8f32ba0f.jpg +0 -0
  148. package/kb/references/thumbs/0ef1966a7d.jpg +0 -0
  149. package/kb/references/thumbs/0efd416d11.jpg +0 -0
  150. package/kb/references/thumbs/0f3af263ae.jpg +0 -0
  151. package/kb/references/thumbs/0f9644c3a5.jpg +0 -0
  152. package/kb/references/thumbs/1020552737.jpg +0 -0
  153. package/kb/references/thumbs/1037626587.jpg +0 -0
  154. package/kb/references/thumbs/10512b7e94.jpg +0 -0
  155. package/kb/references/thumbs/106dc4a7b5.jpg +0 -0
  156. package/kb/references/thumbs/108943f6f7.jpg +0 -0
  157. package/kb/references/thumbs/10a5551d51.jpg +0 -0
  158. package/kb/references/thumbs/10dd01076c.jpg +0 -0
  159. package/kb/references/thumbs/11b06e39da.jpg +0 -0
  160. package/kb/references/thumbs/11baa1ec83.jpg +0 -0
  161. package/kb/references/thumbs/11bbe12167.jpg +0 -0
  162. package/kb/references/thumbs/11ca37feb8.jpg +0 -0
  163. package/kb/references/thumbs/11f14d3f12.jpg +0 -0
  164. package/kb/references/thumbs/124530ceb5.jpg +0 -0
  165. package/kb/references/thumbs/1276402336.jpg +0 -0
  166. package/kb/references/thumbs/1280e5c9d6.jpg +0 -0
  167. package/kb/references/thumbs/12cac0d172.jpg +0 -0
  168. package/kb/references/thumbs/138be6623a.jpg +0 -0
  169. package/kb/references/thumbs/13a0c544d1.jpg +0 -0
  170. package/kb/references/thumbs/13a32cb94c.jpg +0 -0
  171. package/kb/references/thumbs/144035d641.jpg +0 -0
  172. package/kb/references/thumbs/1457e41924.jpg +0 -0
  173. package/kb/references/thumbs/14972e3a86.jpg +0 -0
  174. package/kb/references/thumbs/15a47f2e4c.jpg +0 -0
  175. package/kb/references/thumbs/15aa15d22e.jpg +0 -0
  176. package/kb/references/thumbs/160dcd24b7.jpg +0 -0
  177. package/kb/references/thumbs/1610978529.jpg +0 -0
  178. package/kb/references/thumbs/16db30ba37.jpg +0 -0
  179. package/kb/references/thumbs/16f19ae4f3.jpg +0 -0
  180. package/kb/references/thumbs/16f758b03f.jpg +0 -0
  181. package/kb/references/thumbs/1748e5b751.jpg +0 -0
  182. package/kb/references/thumbs/175d0b0e69.jpg +0 -0
  183. package/kb/references/thumbs/178f571a36.jpg +0 -0
  184. package/kb/references/thumbs/17c43dc025.jpg +0 -0
  185. package/kb/references/thumbs/17c7f05b50.jpg +0 -0
  186. package/kb/references/thumbs/17f97e61dd.jpg +0 -0
  187. package/kb/references/thumbs/1804e17365.jpg +0 -0
  188. package/kb/references/thumbs/195c162ed1.jpg +0 -0
  189. package/kb/references/thumbs/19e3699c29.jpg +0 -0
  190. package/kb/references/thumbs/1a48e0a4d5.jpg +0 -0
  191. package/kb/references/thumbs/1a6cda265e.jpg +0 -0
  192. package/kb/references/thumbs/1a7e012091.jpg +0 -0
  193. package/kb/references/thumbs/1a9173a172.jpg +0 -0
  194. package/kb/references/thumbs/1b34a6b4d2.jpg +0 -0
  195. package/kb/references/thumbs/1b5284f3b2.jpg +0 -0
  196. package/kb/references/thumbs/1b93fbd6d3.jpg +0 -0
  197. package/kb/references/thumbs/1c6728fef6.jpg +0 -0
  198. package/kb/references/thumbs/1c6f101618.jpg +0 -0
  199. package/kb/references/thumbs/1cf7011a94.jpg +0 -0
  200. package/kb/references/thumbs/1d1769e539.jpg +0 -0
  201. package/kb/references/thumbs/1d1fd23199.jpg +0 -0
  202. package/kb/references/thumbs/1d7b6602ac.jpg +0 -0
  203. package/kb/references/thumbs/1dbbc61b9e.jpg +0 -0
  204. package/kb/references/thumbs/1dbdd6d21d.jpg +0 -0
  205. package/kb/references/thumbs/1dd873744b.jpg +0 -0
  206. package/kb/references/thumbs/1df4dd7cf9.jpg +0 -0
  207. package/kb/references/thumbs/1ece8c35b8.jpg +0 -0
  208. package/kb/references/thumbs/1f0e5b9aa4.jpg +0 -0
  209. package/kb/references/thumbs/1f1419651b.jpg +0 -0
  210. package/kb/references/thumbs/1f2a6da92d.jpg +0 -0
  211. package/kb/references/thumbs/1f651f33c0.jpg +0 -0
  212. package/kb/references/thumbs/1f8b697d18.jpg +0 -0
  213. package/kb/references/thumbs/1fee3824cd.jpg +0 -0
  214. package/kb/references/thumbs/1ff7757853.jpg +0 -0
  215. package/kb/references/thumbs/2043182f01.jpg +0 -0
  216. package/kb/references/thumbs/2059dabcd5.jpg +0 -0
  217. package/kb/references/thumbs/209d9dbb95.jpg +0 -0
  218. package/kb/references/thumbs/20d8923554.jpg +0 -0
  219. package/kb/references/thumbs/210dd3b671.jpg +0 -0
  220. package/kb/references/thumbs/2120719116.jpg +0 -0
  221. package/kb/references/thumbs/21a7384429.jpg +0 -0
  222. package/kb/references/thumbs/225f187391.jpg +0 -0
  223. package/kb/references/thumbs/230caf7dd2.jpg +0 -0
  224. package/kb/references/thumbs/235afad355.jpg +0 -0
  225. package/kb/references/thumbs/2378066687.jpg +0 -0
  226. package/kb/references/thumbs/241faf68a1.jpg +0 -0
  227. package/kb/references/thumbs/24d9f1a9b9.jpg +0 -0
  228. package/kb/references/thumbs/2505c8b9a0.jpg +0 -0
  229. package/kb/references/thumbs/25593638ca.jpg +0 -0
  230. package/kb/references/thumbs/25dd454f18.jpg +0 -0
  231. package/kb/references/thumbs/2602e123e2.jpg +0 -0
  232. package/kb/references/thumbs/26095c0b11.jpg +0 -0
  233. package/kb/references/thumbs/26260d6238.jpg +0 -0
  234. package/kb/references/thumbs/26bba21836.jpg +0 -0
  235. package/kb/references/thumbs/26bde3520b.jpg +0 -0
  236. package/kb/references/thumbs/272e22067c.jpg +0 -0
  237. package/kb/references/thumbs/27421b3226.jpg +0 -0
  238. package/kb/references/thumbs/2769ebca41.jpg +0 -0
  239. package/kb/references/thumbs/27e2f6e8b0.jpg +0 -0
  240. package/kb/references/thumbs/28432ba373.jpg +0 -0
  241. package/kb/references/thumbs/2846626f19.jpg +0 -0
  242. package/kb/references/thumbs/28750e821e.jpg +0 -0
  243. package/kb/references/thumbs/289481230f.jpg +0 -0
  244. package/kb/references/thumbs/2897967408.jpg +0 -0
  245. package/kb/references/thumbs/28c0a5fa17.jpg +0 -0
  246. package/kb/references/thumbs/28c181fba5.jpg +0 -0
  247. package/kb/references/thumbs/29806f539f.jpg +0 -0
  248. package/kb/references/thumbs/2997f56ee7.jpg +0 -0
  249. package/kb/references/thumbs/29b34a10fb.jpg +0 -0
  250. package/kb/references/thumbs/29bd78cdb4.jpg +0 -0
  251. package/kb/references/thumbs/29c8c6c22a.jpg +0 -0
  252. package/kb/references/thumbs/2ac23f7fae.jpg +0 -0
  253. package/kb/references/thumbs/2b76e13f79.jpg +0 -0
  254. package/kb/references/thumbs/2b84226479.jpg +0 -0
  255. package/kb/references/thumbs/2bf2dbf737.jpg +0 -0
  256. package/kb/references/thumbs/2bf89abbf6.jpg +0 -0
  257. package/kb/references/thumbs/2c562cd534.jpg +0 -0
  258. package/kb/references/thumbs/2cfe9d48c4.jpg +0 -0
  259. package/kb/references/thumbs/2d49f19b27.jpg +0 -0
  260. package/kb/references/thumbs/2dc8670da7.jpg +0 -0
  261. package/kb/references/thumbs/2de947c469.jpg +0 -0
  262. package/kb/references/thumbs/2e075ca79f.jpg +0 -0
  263. package/kb/references/thumbs/2eb0820e6b.jpg +0 -0
  264. package/kb/references/thumbs/2ec56a9dc0.jpg +0 -0
  265. package/kb/references/thumbs/30bb4ad607.jpg +0 -0
  266. package/kb/references/thumbs/30d19b8444.jpg +0 -0
  267. package/kb/references/thumbs/3173c71d73.jpg +0 -0
  268. package/kb/references/thumbs/31e59e4518.jpg +0 -0
  269. package/kb/references/thumbs/322b8960e8.jpg +0 -0
  270. package/kb/references/thumbs/325c9be6f4.jpg +0 -0
  271. package/kb/references/thumbs/3273338982.jpg +0 -0
  272. package/kb/references/thumbs/32f8387f38.jpg +0 -0
  273. package/kb/references/thumbs/332f6b74c3.jpg +0 -0
  274. package/kb/references/thumbs/334b146b6a.jpg +0 -0
  275. package/kb/references/thumbs/339abd60e4.jpg +0 -0
  276. package/kb/references/thumbs/33e88ec295.jpg +0 -0
  277. package/kb/references/thumbs/340931b5d4.jpg +0 -0
  278. package/kb/references/thumbs/34165a93bd.jpg +0 -0
  279. package/kb/references/thumbs/34d0575dd7.jpg +0 -0
  280. package/kb/references/thumbs/3549e75f0a.jpg +0 -0
  281. package/kb/references/thumbs/35827f1561.jpg +0 -0
  282. package/kb/references/thumbs/37c50280ec.jpg +0 -0
  283. package/kb/references/thumbs/37e66ecf11.jpg +0 -0
  284. package/kb/references/thumbs/3841a04fdc.jpg +0 -0
  285. package/kb/references/thumbs/38490c088f.jpg +0 -0
  286. package/kb/references/thumbs/3850319e9f.jpg +0 -0
  287. package/kb/references/thumbs/3856a64166.jpg +0 -0
  288. package/kb/references/thumbs/3881f211c2.jpg +0 -0
  289. package/kb/references/thumbs/38c7dde2f3.jpg +0 -0
  290. package/kb/references/thumbs/38ce9e486c.jpg +0 -0
  291. package/kb/references/thumbs/390ea8e01b.jpg +0 -0
  292. package/kb/references/thumbs/391b7fc27c.jpg +0 -0
  293. package/kb/references/thumbs/395f7214dc.jpg +0 -0
  294. package/kb/references/thumbs/39e81a5db2.jpg +0 -0
  295. package/kb/references/thumbs/3a67c8d655.jpg +0 -0
  296. package/kb/references/thumbs/3a89c25e3b.jpg +0 -0
  297. package/kb/references/thumbs/3aa0837542.jpg +0 -0
  298. package/kb/references/thumbs/3aae18aa8a.jpg +0 -0
  299. package/kb/references/thumbs/3bdbe767db.jpg +0 -0
  300. package/kb/references/thumbs/3c0616fc7f.jpg +0 -0
  301. package/kb/references/thumbs/3c18e9db1d.jpg +0 -0
  302. package/kb/references/thumbs/3c5a14076c.jpg +0 -0
  303. package/kb/references/thumbs/3ca9ec15c6.jpg +0 -0
  304. package/kb/references/thumbs/3d7756fd35.jpg +0 -0
  305. package/kb/references/thumbs/3dd45bd33d.jpg +0 -0
  306. package/kb/references/thumbs/3de72d5414.jpg +0 -0
  307. package/kb/references/thumbs/3dede597af.jpg +0 -0
  308. package/kb/references/thumbs/3ea948619e.jpg +0 -0
  309. package/kb/references/thumbs/402c12054c.jpg +0 -0
  310. package/kb/references/thumbs/403ab4af90.jpg +0 -0
  311. package/kb/references/thumbs/40773a8c63.jpg +0 -0
  312. package/kb/references/thumbs/407d9583db.jpg +0 -0
  313. package/kb/references/thumbs/415dc1d84c.jpg +0 -0
  314. package/kb/references/thumbs/420b676a4f.jpg +0 -0
  315. package/kb/references/thumbs/42a1ee85eb.jpg +0 -0
  316. package/kb/references/thumbs/43059221aa.jpg +0 -0
  317. package/kb/references/thumbs/432fa0d22c.jpg +0 -0
  318. package/kb/references/thumbs/43826f20e3.jpg +0 -0
  319. package/kb/references/thumbs/43ac55c98a.jpg +0 -0
  320. package/kb/references/thumbs/43d098f3c3.jpg +0 -0
  321. package/kb/references/thumbs/4449d80c2c.jpg +0 -0
  322. package/kb/references/thumbs/44625727dc.jpg +0 -0
  323. package/kb/references/thumbs/4482bb87a1.jpg +0 -0
  324. package/kb/references/thumbs/44b078acdc.jpg +0 -0
  325. package/kb/references/thumbs/44d0305850.jpg +0 -0
  326. package/kb/references/thumbs/456bb6b59e.jpg +0 -0
  327. package/kb/references/thumbs/45df73ce48.jpg +0 -0
  328. package/kb/references/thumbs/45edb45d87.jpg +0 -0
  329. package/kb/references/thumbs/4671d57cd5.jpg +0 -0
  330. package/kb/references/thumbs/46cc108518.jpg +0 -0
  331. package/kb/references/thumbs/470022e614.jpg +0 -0
  332. package/kb/references/thumbs/4719a669ce.jpg +0 -0
  333. package/kb/references/thumbs/47404f5ad4.jpg +0 -0
  334. package/kb/references/thumbs/479d3eb977.jpg +0 -0
  335. package/kb/references/thumbs/479ed92ceb.jpg +0 -0
  336. package/kb/references/thumbs/47baed50c7.jpg +0 -0
  337. package/kb/references/thumbs/47fdcfd7b0.jpg +0 -0
  338. package/kb/references/thumbs/48fd5e94c5.jpg +0 -0
  339. package/kb/references/thumbs/493807917b.jpg +0 -0
  340. package/kb/references/thumbs/49a1c67293.jpg +0 -0
  341. package/kb/references/thumbs/49e2d7fafe.jpg +0 -0
  342. package/kb/references/thumbs/49f540fcb6.jpg +0 -0
  343. package/kb/references/thumbs/4a67ec4028.jpg +0 -0
  344. package/kb/references/thumbs/4a93275b04.jpg +0 -0
  345. package/kb/references/thumbs/4aae6f52da.jpg +0 -0
  346. package/kb/references/thumbs/4b1bae66bd.jpg +0 -0
  347. package/kb/references/thumbs/4b75da15a9.jpg +0 -0
  348. package/kb/references/thumbs/4b77789b88.jpg +0 -0
  349. package/kb/references/thumbs/4bc2f3f3a4.jpg +0 -0
  350. package/kb/references/thumbs/4c48f27b88.jpg +0 -0
  351. package/kb/references/thumbs/4c52bd8bfb.jpg +0 -0
  352. package/kb/references/thumbs/4c9b4acc60.jpg +0 -0
  353. package/kb/references/thumbs/4cbf0752d4.jpg +0 -0
  354. package/kb/references/thumbs/4cd2672517.jpg +0 -0
  355. package/kb/references/thumbs/4dd22a8181.jpg +0 -0
  356. package/kb/references/thumbs/4ee3447314.jpg +0 -0
  357. package/kb/references/thumbs/4ef29c8144.jpg +0 -0
  358. package/kb/references/thumbs/4f82fadfd6.jpg +0 -0
  359. package/kb/references/thumbs/4fc9b643d8.jpg +0 -0
  360. package/kb/references/thumbs/50549524b0.jpg +0 -0
  361. package/kb/references/thumbs/509a615883.jpg +0 -0
  362. package/kb/references/thumbs/50c03ef855.jpg +0 -0
  363. package/kb/references/thumbs/50cf9acad6.jpg +0 -0
  364. package/kb/references/thumbs/51131b025c.jpg +0 -0
  365. package/kb/references/thumbs/51da1124f0.jpg +0 -0
  366. package/kb/references/thumbs/524d5e7288.jpg +0 -0
  367. package/kb/references/thumbs/5251247c81.jpg +0 -0
  368. package/kb/references/thumbs/526b91b7ec.jpg +0 -0
  369. package/kb/references/thumbs/52f02f6c2e.jpg +0 -0
  370. package/kb/references/thumbs/530ab530dc.jpg +0 -0
  371. package/kb/references/thumbs/5335ee4404.jpg +0 -0
  372. package/kb/references/thumbs/5385b90939.jpg +0 -0
  373. package/kb/references/thumbs/53c54654cc.jpg +0 -0
  374. package/kb/references/thumbs/5420794c6c.jpg +0 -0
  375. package/kb/references/thumbs/542a9dce8b.jpg +0 -0
  376. package/kb/references/thumbs/5571530a5f.jpg +0 -0
  377. package/kb/references/thumbs/56cc68d804.jpg +0 -0
  378. package/kb/references/thumbs/577b7f7c44.jpg +0 -0
  379. package/kb/references/thumbs/57adf8f1bb.jpg +0 -0
  380. package/kb/references/thumbs/57c5f5e3a6.jpg +0 -0
  381. package/kb/references/thumbs/57ffe4e839.jpg +0 -0
  382. package/kb/references/thumbs/5817dfc7d6.jpg +0 -0
  383. package/kb/references/thumbs/581a182a7a.jpg +0 -0
  384. package/kb/references/thumbs/58807679c8.jpg +0 -0
  385. package/kb/references/thumbs/589a1d9111.jpg +0 -0
  386. package/kb/references/thumbs/59167391ab.jpg +0 -0
  387. package/kb/references/thumbs/5a70420166.jpg +0 -0
  388. package/kb/references/thumbs/5b10df1b8d.jpg +0 -0
  389. package/kb/references/thumbs/5b7a712fdb.jpg +0 -0
  390. package/kb/references/thumbs/5bc46b92ad.jpg +0 -0
  391. package/kb/references/thumbs/5c0546a120.jpg +0 -0
  392. package/kb/references/thumbs/5c2b6125a8.jpg +0 -0
  393. package/kb/references/thumbs/5c536b9e42.jpg +0 -0
  394. package/kb/references/thumbs/5c8e441d30.jpg +0 -0
  395. package/kb/references/thumbs/5cb6f1baa5.jpg +0 -0
  396. package/kb/references/thumbs/5ce6c1f21e.jpg +0 -0
  397. package/kb/references/thumbs/5cef052c0a.jpg +0 -0
  398. package/kb/references/thumbs/5d08438209.jpg +0 -0
  399. package/kb/references/thumbs/5d0f9af9b0.jpg +0 -0
  400. package/kb/references/thumbs/5d300ed332.jpg +0 -0
  401. package/kb/references/thumbs/5d708a4794.jpg +0 -0
  402. package/kb/references/thumbs/5de8826a98.jpg +0 -0
  403. package/kb/references/thumbs/5df7bff22c.jpg +0 -0
  404. package/kb/references/thumbs/5e087d0f00.jpg +0 -0
  405. package/kb/references/thumbs/5e44e70ca0.jpg +0 -0
  406. package/kb/references/thumbs/5eff5d80ce.jpg +0 -0
  407. package/kb/references/thumbs/5f015c96b1.jpg +0 -0
  408. package/kb/references/thumbs/5f3289b7c4.jpg +0 -0
  409. package/kb/references/thumbs/5f830c40eb.jpg +0 -0
  410. package/kb/references/thumbs/60c4c3527b.jpg +0 -0
  411. package/kb/references/thumbs/60e9e594dd.jpg +0 -0
  412. package/kb/references/thumbs/615421d7be.jpg +0 -0
  413. package/kb/references/thumbs/6176e4004f.jpg +0 -0
  414. package/kb/references/thumbs/61f0fe5e06.jpg +0 -0
  415. package/kb/references/thumbs/6213785db4.jpg +0 -0
  416. package/kb/references/thumbs/62a670ae09.jpg +0 -0
  417. package/kb/references/thumbs/63cc12981b.jpg +0 -0
  418. package/kb/references/thumbs/641f15cc71.jpg +0 -0
  419. package/kb/references/thumbs/642ab2081d.jpg +0 -0
  420. package/kb/references/thumbs/644a59bf6e.jpg +0 -0
  421. package/kb/references/thumbs/644da8a8e3.jpg +0 -0
  422. package/kb/references/thumbs/6458986897.jpg +0 -0
  423. package/kb/references/thumbs/64cfbd7b93.jpg +0 -0
  424. package/kb/references/thumbs/661a57686a.jpg +0 -0
  425. package/kb/references/thumbs/66355a813a.jpg +0 -0
  426. package/kb/references/thumbs/664396dd44.jpg +0 -0
  427. package/kb/references/thumbs/66d832d65f.jpg +0 -0
  428. package/kb/references/thumbs/66de9e9833.jpg +0 -0
  429. package/kb/references/thumbs/6750964fd9.jpg +0 -0
  430. package/kb/references/thumbs/67c7164832.jpg +0 -0
  431. package/kb/references/thumbs/681600b6cd.jpg +0 -0
  432. package/kb/references/thumbs/682e75afe4.jpg +0 -0
  433. package/kb/references/thumbs/69274bc952.jpg +0 -0
  434. package/kb/references/thumbs/696ec08c52.jpg +0 -0
  435. package/kb/references/thumbs/69760111cc.jpg +0 -0
  436. package/kb/references/thumbs/697e32664f.jpg +0 -0
  437. package/kb/references/thumbs/69df92544b.jpg +0 -0
  438. package/kb/references/thumbs/69e48c5229.jpg +0 -0
  439. package/kb/references/thumbs/6a5fb75de1.jpg +0 -0
  440. package/kb/references/thumbs/6a7389e472.jpg +0 -0
  441. package/kb/references/thumbs/6a9b041e02.jpg +0 -0
  442. package/kb/references/thumbs/6ab2fa93ee.jpg +0 -0
  443. package/kb/references/thumbs/6ae3400319.jpg +0 -0
  444. package/kb/references/thumbs/6b21344bf3.jpg +0 -0
  445. package/kb/references/thumbs/6bd1b67a8a.jpg +0 -0
  446. package/kb/references/thumbs/6c99156c4f.jpg +0 -0
  447. package/kb/references/thumbs/6cc9b65384.jpg +0 -0
  448. package/kb/references/thumbs/6d15830a73.jpg +0 -0
  449. package/kb/references/thumbs/6d4c33d180.jpg +0 -0
  450. package/kb/references/thumbs/6e36815799.jpg +0 -0
  451. package/kb/references/thumbs/6eddb9cbfc.jpg +0 -0
  452. package/kb/references/thumbs/6f97968a7c.jpg +0 -0
  453. package/kb/references/thumbs/6fa0716a7d.jpg +0 -0
  454. package/kb/references/thumbs/6facd57184.jpg +0 -0
  455. package/kb/references/thumbs/6fd57b785a.jpg +0 -0
  456. package/kb/references/thumbs/700b3cb185.jpg +0 -0
  457. package/kb/references/thumbs/71197443fa.jpg +0 -0
  458. package/kb/references/thumbs/72082bc9ee.jpg +0 -0
  459. package/kb/references/thumbs/72453db479.jpg +0 -0
  460. package/kb/references/thumbs/72cd64d500.jpg +0 -0
  461. package/kb/references/thumbs/72d1e75283.jpg +0 -0
  462. package/kb/references/thumbs/72e13f7f5b.jpg +0 -0
  463. package/kb/references/thumbs/7459f8e644.jpg +0 -0
  464. package/kb/references/thumbs/7470d55c79.jpg +0 -0
  465. package/kb/references/thumbs/74c468f02a.jpg +0 -0
  466. package/kb/references/thumbs/74fed6eb77.jpg +0 -0
  467. package/kb/references/thumbs/756e05080d.jpg +0 -0
  468. package/kb/references/thumbs/75df7e5f49.jpg +0 -0
  469. package/kb/references/thumbs/76226da6ef.jpg +0 -0
  470. package/kb/references/thumbs/76a34aa056.jpg +0 -0
  471. package/kb/references/thumbs/76c7076589.jpg +0 -0
  472. package/kb/references/thumbs/76cccc396c.jpg +0 -0
  473. package/kb/references/thumbs/774654789f.jpg +0 -0
  474. package/kb/references/thumbs/7746cc494d.jpg +0 -0
  475. package/kb/references/thumbs/7766e36aa3.jpg +0 -0
  476. package/kb/references/thumbs/78049331ef.jpg +0 -0
  477. package/kb/references/thumbs/78808a0ca5.jpg +0 -0
  478. package/kb/references/thumbs/7890b0a310.jpg +0 -0
  479. package/kb/references/thumbs/78911975e6.jpg +0 -0
  480. package/kb/references/thumbs/78d289d5a0.jpg +0 -0
  481. package/kb/references/thumbs/794126267e.jpg +0 -0
  482. package/kb/references/thumbs/7976eb9025.jpg +0 -0
  483. package/kb/references/thumbs/798519ec7c.jpg +0 -0
  484. package/kb/references/thumbs/7a6cc5eecc.jpg +0 -0
  485. package/kb/references/thumbs/7ac8a7cd4a.jpg +0 -0
  486. package/kb/references/thumbs/7b1341d61d.jpg +0 -0
  487. package/kb/references/thumbs/7b9c4031e3.jpg +0 -0
  488. package/kb/references/thumbs/7ba5ad3779.jpg +0 -0
  489. package/kb/references/thumbs/7be6eb2bd0.jpg +0 -0
  490. package/kb/references/thumbs/7c30864f5d.jpg +0 -0
  491. package/kb/references/thumbs/7c379b5e17.jpg +0 -0
  492. package/kb/references/thumbs/7c4050eb02.jpg +0 -0
  493. package/kb/references/thumbs/7ca47b3221.jpg +0 -0
  494. package/kb/references/thumbs/7d43516bd7.jpg +0 -0
  495. package/kb/references/thumbs/7da9dbb98a.jpg +0 -0
  496. package/kb/references/thumbs/7ddda8f57c.jpg +0 -0
  497. package/kb/references/thumbs/7ece6d215c.jpg +0 -0
  498. package/kb/references/thumbs/7eebf029de.jpg +0 -0
  499. package/kb/references/thumbs/7f45483c15.jpg +0 -0
  500. package/kb/references/thumbs/7fde06eaa5.jpg +0 -0
  501. package/kb/references/thumbs/80407b97e9.jpg +0 -0
  502. package/kb/references/thumbs/80f0a6cf4b.jpg +0 -0
  503. package/kb/references/thumbs/8118b451b8.jpg +0 -0
  504. package/kb/references/thumbs/816baed4a8.jpg +0 -0
  505. package/kb/references/thumbs/817d2d15d9.jpg +0 -0
  506. package/kb/references/thumbs/8260cb430d.jpg +0 -0
  507. package/kb/references/thumbs/827b562899.jpg +0 -0
  508. package/kb/references/thumbs/8293cf1e92.jpg +0 -0
  509. package/kb/references/thumbs/8294653169.jpg +0 -0
  510. package/kb/references/thumbs/83061f871f.jpg +0 -0
  511. package/kb/references/thumbs/833df778ca.jpg +0 -0
  512. package/kb/references/thumbs/83d3acaafc.jpg +0 -0
  513. package/kb/references/thumbs/844c6efeff.jpg +0 -0
  514. package/kb/references/thumbs/84563d482a.jpg +0 -0
  515. package/kb/references/thumbs/848082dd94.jpg +0 -0
  516. package/kb/references/thumbs/8494b1da94.jpg +0 -0
  517. package/kb/references/thumbs/84b71014be.jpg +0 -0
  518. package/kb/references/thumbs/84be4a5f3c.jpg +0 -0
  519. package/kb/references/thumbs/84fd0fd25a.jpg +0 -0
  520. package/kb/references/thumbs/853e488e22.jpg +0 -0
  521. package/kb/references/thumbs/858c91585d.jpg +0 -0
  522. package/kb/references/thumbs/85903e8fe9.jpg +0 -0
  523. package/kb/references/thumbs/86597d43fd.jpg +0 -0
  524. package/kb/references/thumbs/866eb99500.jpg +0 -0
  525. package/kb/references/thumbs/86ac050340.jpg +0 -0
  526. package/kb/references/thumbs/86afec7b64.jpg +0 -0
  527. package/kb/references/thumbs/87238127f5.jpg +0 -0
  528. package/kb/references/thumbs/87321df792.jpg +0 -0
  529. package/kb/references/thumbs/87460f70cd.jpg +0 -0
  530. package/kb/references/thumbs/8770dfeaec.jpg +0 -0
  531. package/kb/references/thumbs/8798451a8c.jpg +0 -0
  532. package/kb/references/thumbs/87da1f34a3.jpg +0 -0
  533. package/kb/references/thumbs/882cdf8c05.jpg +0 -0
  534. package/kb/references/thumbs/8839f4106f.jpg +0 -0
  535. package/kb/references/thumbs/8844455212.jpg +0 -0
  536. package/kb/references/thumbs/88abba0497.jpg +0 -0
  537. package/kb/references/thumbs/88b372435d.jpg +0 -0
  538. package/kb/references/thumbs/88bd4f35fa.jpg +0 -0
  539. package/kb/references/thumbs/88c71a075e.jpg +0 -0
  540. package/kb/references/thumbs/892e5f048e.jpg +0 -0
  541. package/kb/references/thumbs/893cc8e920.jpg +0 -0
  542. package/kb/references/thumbs/896c198814.jpg +0 -0
  543. package/kb/references/thumbs/8a8e22f756.jpg +0 -0
  544. package/kb/references/thumbs/8a9d8a35cb.jpg +0 -0
  545. package/kb/references/thumbs/8ab1d67fec.jpg +0 -0
  546. package/kb/references/thumbs/8ab201fe12.jpg +0 -0
  547. package/kb/references/thumbs/8ac372c102.jpg +0 -0
  548. package/kb/references/thumbs/8b15736d62.jpg +0 -0
  549. package/kb/references/thumbs/8b1d8d6a05.jpg +0 -0
  550. package/kb/references/thumbs/8bb6050e20.jpg +0 -0
  551. package/kb/references/thumbs/8c6c9d1577.jpg +0 -0
  552. package/kb/references/thumbs/8d337851e0.jpg +0 -0
  553. package/kb/references/thumbs/8d44b33cc5.jpg +0 -0
  554. package/kb/references/thumbs/8d4536ff3b.jpg +0 -0
  555. package/kb/references/thumbs/8d9b99454a.jpg +0 -0
  556. package/kb/references/thumbs/8dda2303ff.jpg +0 -0
  557. package/kb/references/thumbs/8dec722d62.jpg +0 -0
  558. package/kb/references/thumbs/8e0ea4ca74.jpg +0 -0
  559. package/kb/references/thumbs/8ebba84d69.jpg +0 -0
  560. package/kb/references/thumbs/8ef09140c0.jpg +0 -0
  561. package/kb/references/thumbs/8f2928a1b0.jpg +0 -0
  562. package/kb/references/thumbs/8f88f9ddba.jpg +0 -0
  563. package/kb/references/thumbs/8fa4451009.jpg +0 -0
  564. package/kb/references/thumbs/90109b0e02.jpg +0 -0
  565. package/kb/references/thumbs/90e854e5c9.jpg +0 -0
  566. package/kb/references/thumbs/914e79f893.jpg +0 -0
  567. package/kb/references/thumbs/9161369197.jpg +0 -0
  568. package/kb/references/thumbs/9165c87d6f.jpg +0 -0
  569. package/kb/references/thumbs/9193f6558a.jpg +0 -0
  570. package/kb/references/thumbs/91ac7df877.jpg +0 -0
  571. package/kb/references/thumbs/9216bd4931.jpg +0 -0
  572. package/kb/references/thumbs/922ae8a26a.jpg +0 -0
  573. package/kb/references/thumbs/92a70e6867.jpg +0 -0
  574. package/kb/references/thumbs/92c77ece22.jpg +0 -0
  575. package/kb/references/thumbs/92e5914fbf.jpg +0 -0
  576. package/kb/references/thumbs/92ee36419a.jpg +0 -0
  577. package/kb/references/thumbs/94153a60b0.jpg +0 -0
  578. package/kb/references/thumbs/9449666640.jpg +0 -0
  579. package/kb/references/thumbs/944be6a646.jpg +0 -0
  580. package/kb/references/thumbs/945831476e.jpg +0 -0
  581. package/kb/references/thumbs/94c1af82f4.jpg +0 -0
  582. package/kb/references/thumbs/9599d6d589.jpg +0 -0
  583. package/kb/references/thumbs/960710f4cf.jpg +0 -0
  584. package/kb/references/thumbs/9608f12d64.jpg +0 -0
  585. package/kb/references/thumbs/962dabfaa0.jpg +0 -0
  586. package/kb/references/thumbs/965a3a95c6.jpg +0 -0
  587. package/kb/references/thumbs/9672ed5ae4.jpg +0 -0
  588. package/kb/references/thumbs/96891ae9c1.jpg +0 -0
  589. package/kb/references/thumbs/96e1907c99.jpg +0 -0
  590. package/kb/references/thumbs/96f25b6578.jpg +0 -0
  591. package/kb/references/thumbs/97211df9ae.jpg +0 -0
  592. package/kb/references/thumbs/9769d763a5.jpg +0 -0
  593. package/kb/references/thumbs/9792bd6d05.jpg +0 -0
  594. package/kb/references/thumbs/97d2837e3e.jpg +0 -0
  595. package/kb/references/thumbs/97d90d1d85.jpg +0 -0
  596. package/kb/references/thumbs/97fcd5774a.jpg +0 -0
  597. package/kb/references/thumbs/980f68564e.jpg +0 -0
  598. package/kb/references/thumbs/98511fb393.jpg +0 -0
  599. package/kb/references/thumbs/98f8fa694d.jpg +0 -0
  600. package/kb/references/thumbs/9a42c6bb97.jpg +0 -0
  601. package/kb/references/thumbs/9ab3600a06.jpg +0 -0
  602. package/kb/references/thumbs/9ab48d9130.jpg +0 -0
  603. package/kb/references/thumbs/9ad519197d.jpg +0 -0
  604. package/kb/references/thumbs/9b1382c657.jpg +0 -0
  605. package/kb/references/thumbs/9b6cc1e077.jpg +0 -0
  606. package/kb/references/thumbs/9beede8480.jpg +0 -0
  607. package/kb/references/thumbs/9bf8976952.jpg +0 -0
  608. package/kb/references/thumbs/9cace7b75a.jpg +0 -0
  609. package/kb/references/thumbs/9cdcc5745e.jpg +0 -0
  610. package/kb/references/thumbs/9d0b269074.jpg +0 -0
  611. package/kb/references/thumbs/9d41788b48.jpg +0 -0
  612. package/kb/references/thumbs/9d475bd6af.jpg +0 -0
  613. package/kb/references/thumbs/9e6bc543d0.jpg +0 -0
  614. package/kb/references/thumbs/9ed0e0a206.jpg +0 -0
  615. package/kb/references/thumbs/9ef538de13.jpg +0 -0
  616. package/kb/references/thumbs/9efc373ab4.jpg +0 -0
  617. package/kb/references/thumbs/9f4a939fa5.jpg +0 -0
  618. package/kb/references/thumbs/9f8d12d7b9.jpg +0 -0
  619. package/kb/references/thumbs/9fd5e3efa7.jpg +0 -0
  620. package/kb/references/thumbs/a0a9b9c572.jpg +0 -0
  621. package/kb/references/thumbs/a29ca9b185.jpg +0 -0
  622. package/kb/references/thumbs/a2a42884af.jpg +0 -0
  623. package/kb/references/thumbs/a2f5064e34.jpg +0 -0
  624. package/kb/references/thumbs/a3112ce672.jpg +0 -0
  625. package/kb/references/thumbs/a3437736d1.jpg +0 -0
  626. package/kb/references/thumbs/a372667e76.jpg +0 -0
  627. package/kb/references/thumbs/a39bae94e9.jpg +0 -0
  628. package/kb/references/thumbs/a3fc75d94c.jpg +0 -0
  629. package/kb/references/thumbs/a440487631.jpg +0 -0
  630. package/kb/references/thumbs/a4993d5fb5.jpg +0 -0
  631. package/kb/references/thumbs/a500476a86.jpg +0 -0
  632. package/kb/references/thumbs/a55506bf46.jpg +0 -0
  633. package/kb/references/thumbs/a59a5f9843.jpg +0 -0
  634. package/kb/references/thumbs/a5bce0eede.jpg +0 -0
  635. package/kb/references/thumbs/a5d921e1f0.jpg +0 -0
  636. package/kb/references/thumbs/a5e19d4637.jpg +0 -0
  637. package/kb/references/thumbs/a5f92b97d5.jpg +0 -0
  638. package/kb/references/thumbs/a6562c210a.jpg +0 -0
  639. package/kb/references/thumbs/a6f61a8d09.jpg +0 -0
  640. package/kb/references/thumbs/a719fdf663.jpg +0 -0
  641. package/kb/references/thumbs/a724e5442f.jpg +0 -0
  642. package/kb/references/thumbs/a852b4319f.jpg +0 -0
  643. package/kb/references/thumbs/a896c08448.jpg +0 -0
  644. package/kb/references/thumbs/a8d190a4b8.jpg +0 -0
  645. package/kb/references/thumbs/a8dcdd57d7.jpg +0 -0
  646. package/kb/references/thumbs/a951347e18.jpg +0 -0
  647. package/kb/references/thumbs/a9a1a0bf7d.jpg +0 -0
  648. package/kb/references/thumbs/a9b72986a8.jpg +0 -0
  649. package/kb/references/thumbs/aa11555ba8.jpg +0 -0
  650. package/kb/references/thumbs/aa4aab9093.jpg +0 -0
  651. package/kb/references/thumbs/aa89b96658.jpg +0 -0
  652. package/kb/references/thumbs/aaeb3739b6.jpg +0 -0
  653. package/kb/references/thumbs/ab25a608ec.jpg +0 -0
  654. package/kb/references/thumbs/ab34b62268.jpg +0 -0
  655. package/kb/references/thumbs/ab3b0217bb.jpg +0 -0
  656. package/kb/references/thumbs/ac70c04afb.jpg +0 -0
  657. package/kb/references/thumbs/ace0879117.jpg +0 -0
  658. package/kb/references/thumbs/ad0515ae55.jpg +0 -0
  659. package/kb/references/thumbs/ad66b25b47.jpg +0 -0
  660. package/kb/references/thumbs/ad68229d2f.jpg +0 -0
  661. package/kb/references/thumbs/add6480693.jpg +0 -0
  662. package/kb/references/thumbs/ae5b8c3b8c.jpg +0 -0
  663. package/kb/references/thumbs/ae81247b47.jpg +0 -0
  664. package/kb/references/thumbs/af8aa54379.jpg +0 -0
  665. package/kb/references/thumbs/afa80dedd4.jpg +0 -0
  666. package/kb/references/thumbs/afe04d730f.jpg +0 -0
  667. package/kb/references/thumbs/b09a7dc315.jpg +0 -0
  668. package/kb/references/thumbs/b0c02629f6.jpg +0 -0
  669. package/kb/references/thumbs/b1cb4f91de.jpg +0 -0
  670. package/kb/references/thumbs/b1d4c13c92.jpg +0 -0
  671. package/kb/references/thumbs/b20eef86db.jpg +0 -0
  672. package/kb/references/thumbs/b3136ccdde.jpg +0 -0
  673. package/kb/references/thumbs/b3f5e8d7dd.jpg +0 -0
  674. package/kb/references/thumbs/b44adb81c9.jpg +0 -0
  675. package/kb/references/thumbs/b457a5894b.jpg +0 -0
  676. package/kb/references/thumbs/b461281e0f.jpg +0 -0
  677. package/kb/references/thumbs/b4942644f0.jpg +0 -0
  678. package/kb/references/thumbs/b49f8eccad.jpg +0 -0
  679. package/kb/references/thumbs/b56c69be23.jpg +0 -0
  680. package/kb/references/thumbs/b59786b33c.jpg +0 -0
  681. package/kb/references/thumbs/b5d368ce5a.jpg +0 -0
  682. package/kb/references/thumbs/b5eb442406.jpg +0 -0
  683. package/kb/references/thumbs/b7fdabf4ab.jpg +0 -0
  684. package/kb/references/thumbs/b88ee8d235.jpg +0 -0
  685. package/kb/references/thumbs/b8a4e5be8a.jpg +0 -0
  686. package/kb/references/thumbs/b8c85b661c.jpg +0 -0
  687. package/kb/references/thumbs/ba1b8e7435.jpg +0 -0
  688. package/kb/references/thumbs/ba9a593eed.jpg +0 -0
  689. package/kb/references/thumbs/baa7afc201.jpg +0 -0
  690. package/kb/references/thumbs/baf74398f7.jpg +0 -0
  691. package/kb/references/thumbs/bb3e947c10.jpg +0 -0
  692. package/kb/references/thumbs/bb4c1bb7c1.jpg +0 -0
  693. package/kb/references/thumbs/bbaa25cc8a.jpg +0 -0
  694. package/kb/references/thumbs/bc7e7f88cc.jpg +0 -0
  695. package/kb/references/thumbs/bc9d5b58f5.jpg +0 -0
  696. package/kb/references/thumbs/bcc99a51f7.jpg +0 -0
  697. package/kb/references/thumbs/bce13dd1c1.jpg +0 -0
  698. package/kb/references/thumbs/bd079a6130.jpg +0 -0
  699. package/kb/references/thumbs/bd2d70dda7.jpg +0 -0
  700. package/kb/references/thumbs/bd5895c5f2.jpg +0 -0
  701. package/kb/references/thumbs/bde8e31d62.jpg +0 -0
  702. package/kb/references/thumbs/be084e78f3.jpg +0 -0
  703. package/kb/references/thumbs/be94f170c8.jpg +0 -0
  704. package/kb/references/thumbs/bed33654f7.jpg +0 -0
  705. package/kb/references/thumbs/beeb9a0c18.jpg +0 -0
  706. package/kb/references/thumbs/bef42094dc.jpg +0 -0
  707. package/kb/references/thumbs/bf625496fa.jpg +0 -0
  708. package/kb/references/thumbs/bf771f1398.jpg +0 -0
  709. package/kb/references/thumbs/c0ddefa3d9.jpg +0 -0
  710. package/kb/references/thumbs/c0f01e536e.jpg +0 -0
  711. package/kb/references/thumbs/c1d592b678.jpg +0 -0
  712. package/kb/references/thumbs/c1e83e1b3e.jpg +0 -0
  713. package/kb/references/thumbs/c2168f8096.jpg +0 -0
  714. package/kb/references/thumbs/c249cd6b17.jpg +0 -0
  715. package/kb/references/thumbs/c2e3d4feed.jpg +0 -0
  716. package/kb/references/thumbs/c3cec01df1.jpg +0 -0
  717. package/kb/references/thumbs/c47bedb42d.jpg +0 -0
  718. package/kb/references/thumbs/c4b03b4680.jpg +0 -0
  719. package/kb/references/thumbs/c4e6d18ec3.jpg +0 -0
  720. package/kb/references/thumbs/c5477e2877.jpg +0 -0
  721. package/kb/references/thumbs/c566794674.jpg +0 -0
  722. package/kb/references/thumbs/c5b945afbb.jpg +0 -0
  723. package/kb/references/thumbs/c5e4659497.jpg +0 -0
  724. package/kb/references/thumbs/c622786702.jpg +0 -0
  725. package/kb/references/thumbs/c6250649ca.jpg +0 -0
  726. package/kb/references/thumbs/c6261a9526.jpg +0 -0
  727. package/kb/references/thumbs/c62e609076.jpg +0 -0
  728. package/kb/references/thumbs/c6602d3e85.jpg +0 -0
  729. package/kb/references/thumbs/c6c1a16e40.jpg +0 -0
  730. package/kb/references/thumbs/c77f932157.jpg +0 -0
  731. package/kb/references/thumbs/c79788f01e.jpg +0 -0
  732. package/kb/references/thumbs/c7a511ad1e.jpg +0 -0
  733. package/kb/references/thumbs/c7acfbc3c8.jpg +0 -0
  734. package/kb/references/thumbs/c7bb6d1bb8.jpg +0 -0
  735. package/kb/references/thumbs/c829eadcb9.jpg +0 -0
  736. package/kb/references/thumbs/c891fd9d85.jpg +0 -0
  737. package/kb/references/thumbs/c89f812ee3.jpg +0 -0
  738. package/kb/references/thumbs/c8a85b9f61.jpg +0 -0
  739. package/kb/references/thumbs/c8fdf85c9a.jpg +0 -0
  740. package/kb/references/thumbs/c961ac1031.jpg +0 -0
  741. package/kb/references/thumbs/c9fd3f88d8.jpg +0 -0
  742. package/kb/references/thumbs/ca0d6399c3.jpg +0 -0
  743. package/kb/references/thumbs/ca316c83e4.jpg +0 -0
  744. package/kb/references/thumbs/ca3b93aa0d.jpg +0 -0
  745. package/kb/references/thumbs/cb0596a85b.jpg +0 -0
  746. package/kb/references/thumbs/cb95a5c3ef.jpg +0 -0
  747. package/kb/references/thumbs/cbe238b656.jpg +0 -0
  748. package/kb/references/thumbs/cbf530e608.jpg +0 -0
  749. package/kb/references/thumbs/cc173494ec.jpg +0 -0
  750. package/kb/references/thumbs/ccb664bddf.jpg +0 -0
  751. package/kb/references/thumbs/ccf69d36cd.jpg +0 -0
  752. package/kb/references/thumbs/ccf759db42.jpg +0 -0
  753. package/kb/references/thumbs/cd2a285807.jpg +0 -0
  754. package/kb/references/thumbs/cd4a24a8ca.jpg +0 -0
  755. package/kb/references/thumbs/cdb3fe8938.jpg +0 -0
  756. package/kb/references/thumbs/cdce1b9bc6.jpg +0 -0
  757. package/kb/references/thumbs/cde3aa761a.jpg +0 -0
  758. package/kb/references/thumbs/ce9d5cd9c2.jpg +0 -0
  759. package/kb/references/thumbs/ceb01f8a34.jpg +0 -0
  760. package/kb/references/thumbs/cee15c98d0.jpg +0 -0
  761. package/kb/references/thumbs/cf6944ddae.jpg +0 -0
  762. package/kb/references/thumbs/d15dd21cd8.jpg +0 -0
  763. package/kb/references/thumbs/d1633bf2ed.jpg +0 -0
  764. package/kb/references/thumbs/d16a838000.jpg +0 -0
  765. package/kb/references/thumbs/d1cf87e9f0.jpg +0 -0
  766. package/kb/references/thumbs/d32511d913.jpg +0 -0
  767. package/kb/references/thumbs/d327e9dedd.jpg +0 -0
  768. package/kb/references/thumbs/d35bf6c85e.jpg +0 -0
  769. package/kb/references/thumbs/d37afc9d61.jpg +0 -0
  770. package/kb/references/thumbs/d3b66655c7.jpg +0 -0
  771. package/kb/references/thumbs/d40b4172b6.jpg +0 -0
  772. package/kb/references/thumbs/d4c40d1824.jpg +0 -0
  773. package/kb/references/thumbs/d4cb55a4b6.jpg +0 -0
  774. package/kb/references/thumbs/d4f0c30f7e.jpg +0 -0
  775. package/kb/references/thumbs/d51cce3ae5.jpg +0 -0
  776. package/kb/references/thumbs/d52948f9ba.jpg +0 -0
  777. package/kb/references/thumbs/d62bd3cba4.jpg +0 -0
  778. package/kb/references/thumbs/d668a21c92.jpg +0 -0
  779. package/kb/references/thumbs/d685072c52.jpg +0 -0
  780. package/kb/references/thumbs/d6d7b96093.jpg +0 -0
  781. package/kb/references/thumbs/d7cd874b21.jpg +0 -0
  782. package/kb/references/thumbs/d7e111736a.jpg +0 -0
  783. package/kb/references/thumbs/d88325f832.jpg +0 -0
  784. package/kb/references/thumbs/d9272ded76.jpg +0 -0
  785. package/kb/references/thumbs/d962e7c654.jpg +0 -0
  786. package/kb/references/thumbs/d97f000ee2.jpg +0 -0
  787. package/kb/references/thumbs/d9c1ae2543.jpg +0 -0
  788. package/kb/references/thumbs/d9ccb3cac8.jpg +0 -0
  789. package/kb/references/thumbs/da99019680.jpg +0 -0
  790. package/kb/references/thumbs/da9b8bc615.jpg +0 -0
  791. package/kb/references/thumbs/daafbee2c1.jpg +0 -0
  792. package/kb/references/thumbs/db0f11f143.jpg +0 -0
  793. package/kb/references/thumbs/db5dd367f5.jpg +0 -0
  794. package/kb/references/thumbs/db69677866.jpg +0 -0
  795. package/kb/references/thumbs/db87c1956e.jpg +0 -0
  796. package/kb/references/thumbs/dbb86d8bda.jpg +0 -0
  797. package/kb/references/thumbs/dbd3a8ff2a.jpg +0 -0
  798. package/kb/references/thumbs/dbdf4b2794.jpg +0 -0
  799. package/kb/references/thumbs/dd4c19b4b1.jpg +0 -0
  800. package/kb/references/thumbs/dd98d9f6fa.jpg +0 -0
  801. package/kb/references/thumbs/ddb00e3b6a.jpg +0 -0
  802. package/kb/references/thumbs/dde3541a5c.jpg +0 -0
  803. package/kb/references/thumbs/de2a4da502.jpg +0 -0
  804. package/kb/references/thumbs/de4c387a66.jpg +0 -0
  805. package/kb/references/thumbs/de5618fa82.jpg +0 -0
  806. package/kb/references/thumbs/de6bec2538.jpg +0 -0
  807. package/kb/references/thumbs/dea13cbb1f.jpg +0 -0
  808. package/kb/references/thumbs/deff9202aa.jpg +0 -0
  809. package/kb/references/thumbs/df12835f8d.jpg +0 -0
  810. package/kb/references/thumbs/df57acbb3d.jpg +0 -0
  811. package/kb/references/thumbs/df8265009e.jpg +0 -0
  812. package/kb/references/thumbs/dfc99a1e8e.jpg +0 -0
  813. package/kb/references/thumbs/dfc9fde460.jpg +0 -0
  814. package/kb/references/thumbs/e038348eee.jpg +0 -0
  815. package/kb/references/thumbs/e04c2ae1d5.jpg +0 -0
  816. package/kb/references/thumbs/e070345c43.jpg +0 -0
  817. package/kb/references/thumbs/e0a0c84d47.jpg +0 -0
  818. package/kb/references/thumbs/e0af1e6b9e.jpg +0 -0
  819. package/kb/references/thumbs/e1025942c4.jpg +0 -0
  820. package/kb/references/thumbs/e11b6f5b82.jpg +0 -0
  821. package/kb/references/thumbs/e1553de3fc.jpg +0 -0
  822. package/kb/references/thumbs/e162ebe7cd.jpg +0 -0
  823. package/kb/references/thumbs/e1f1e531d8.jpg +0 -0
  824. package/kb/references/thumbs/e20842e5c9.jpg +0 -0
  825. package/kb/references/thumbs/e24e7d949b.jpg +0 -0
  826. package/kb/references/thumbs/e2a3e5d10a.jpg +0 -0
  827. package/kb/references/thumbs/e2de02615a.jpg +0 -0
  828. package/kb/references/thumbs/e305dad7dd.jpg +0 -0
  829. package/kb/references/thumbs/e52e3ef97f.jpg +0 -0
  830. package/kb/references/thumbs/e570e92d06.jpg +0 -0
  831. package/kb/references/thumbs/e583932fb9.jpg +0 -0
  832. package/kb/references/thumbs/e5da3c038f.jpg +0 -0
  833. package/kb/references/thumbs/e657634af3.jpg +0 -0
  834. package/kb/references/thumbs/e67946e3e1.jpg +0 -0
  835. package/kb/references/thumbs/e69e0f3639.jpg +0 -0
  836. package/kb/references/thumbs/e6ebe66031.jpg +0 -0
  837. package/kb/references/thumbs/e7db9563c9.jpg +0 -0
  838. package/kb/references/thumbs/e84a4c3b7b.jpg +0 -0
  839. package/kb/references/thumbs/e84cf52c27.jpg +0 -0
  840. package/kb/references/thumbs/e8707cf6e8.jpg +0 -0
  841. package/kb/references/thumbs/e888890b40.jpg +0 -0
  842. package/kb/references/thumbs/e8a502b418.jpg +0 -0
  843. package/kb/references/thumbs/e8ba68371d.jpg +0 -0
  844. package/kb/references/thumbs/e9098f54cc.jpg +0 -0
  845. package/kb/references/thumbs/e9144deae9.jpg +0 -0
  846. package/kb/references/thumbs/e93e98d757.jpg +0 -0
  847. package/kb/references/thumbs/e9762dd435.jpg +0 -0
  848. package/kb/references/thumbs/e9db4544c8.jpg +0 -0
  849. package/kb/references/thumbs/e9ddffeacd.jpg +0 -0
  850. package/kb/references/thumbs/e9e2cf1d7e.jpg +0 -0
  851. package/kb/references/thumbs/ea1e23e623.jpg +0 -0
  852. package/kb/references/thumbs/ea41200f5e.jpg +0 -0
  853. package/kb/references/thumbs/eaf4916e1a.jpg +0 -0
  854. package/kb/references/thumbs/eb2c505fc8.jpg +0 -0
  855. package/kb/references/thumbs/eb47943693.jpg +0 -0
  856. package/kb/references/thumbs/eb4ffd6625.jpg +0 -0
  857. package/kb/references/thumbs/eb82ac0a4d.jpg +0 -0
  858. package/kb/references/thumbs/ebeb345ade.jpg +0 -0
  859. package/kb/references/thumbs/ec4e2aba0a.jpg +0 -0
  860. package/kb/references/thumbs/ed683bc9f7.jpg +0 -0
  861. package/kb/references/thumbs/ed9e236a84.jpg +0 -0
  862. package/kb/references/thumbs/ee45e1caea.jpg +0 -0
  863. package/kb/references/thumbs/ee50bbf2e7.jpg +0 -0
  864. package/kb/references/thumbs/eed6ad3b7b.jpg +0 -0
  865. package/kb/references/thumbs/eee247fd70.jpg +0 -0
  866. package/kb/references/thumbs/ef87b6cb99.jpg +0 -0
  867. package/kb/references/thumbs/ef87dccf04.jpg +0 -0
  868. package/kb/references/thumbs/ef941bba1e.jpg +0 -0
  869. package/kb/references/thumbs/effc14fb2c.jpg +0 -0
  870. package/kb/references/thumbs/f04aa5a191.jpg +0 -0
  871. package/kb/references/thumbs/f13f12485f.jpg +0 -0
  872. package/kb/references/thumbs/f16a5c0fd4.jpg +0 -0
  873. package/kb/references/thumbs/f1ba7a7a8f.jpg +0 -0
  874. package/kb/references/thumbs/f1bc4c9c19.jpg +0 -0
  875. package/kb/references/thumbs/f1ee827878.jpg +0 -0
  876. package/kb/references/thumbs/f21736aeff.jpg +0 -0
  877. package/kb/references/thumbs/f22bc41213.jpg +0 -0
  878. package/kb/references/thumbs/f266aaffcc.jpg +0 -0
  879. package/kb/references/thumbs/f2c0ed0d65.jpg +0 -0
  880. package/kb/references/thumbs/f32ac710fd.jpg +0 -0
  881. package/kb/references/thumbs/f32b697ff6.jpg +0 -0
  882. package/kb/references/thumbs/f39c883904.jpg +0 -0
  883. package/kb/references/thumbs/f3b9d55e6e.jpg +0 -0
  884. package/kb/references/thumbs/f3c1763e14.jpg +0 -0
  885. package/kb/references/thumbs/f40853fd43.jpg +0 -0
  886. package/kb/references/thumbs/f4fd07dc4d.jpg +0 -0
  887. package/kb/references/thumbs/f50b756ee5.jpg +0 -0
  888. package/kb/references/thumbs/f51272e9b3.jpg +0 -0
  889. package/kb/references/thumbs/f538895eee.jpg +0 -0
  890. package/kb/references/thumbs/f5d28081b5.jpg +0 -0
  891. package/kb/references/thumbs/f6046c9501.jpg +0 -0
  892. package/kb/references/thumbs/f62e4af316.jpg +0 -0
  893. package/kb/references/thumbs/f6f6bffeb8.jpg +0 -0
  894. package/kb/references/thumbs/f736e00af6.jpg +0 -0
  895. package/kb/references/thumbs/f7932da47d.jpg +0 -0
  896. package/kb/references/thumbs/f879e118ca.jpg +0 -0
  897. package/kb/references/thumbs/f88257215d.jpg +0 -0
  898. package/kb/references/thumbs/f88c45a6d7.jpg +0 -0
  899. package/kb/references/thumbs/f89a7b646e.jpg +0 -0
  900. package/kb/references/thumbs/f8a1e7a274.jpg +0 -0
  901. package/kb/references/thumbs/f9183cf2b6.jpg +0 -0
  902. package/kb/references/thumbs/f995de5ce9.jpg +0 -0
  903. package/kb/references/thumbs/f9ada2c3e1.jpg +0 -0
  904. package/kb/references/thumbs/f9d4616f89.jpg +0 -0
  905. package/kb/references/thumbs/f9f4307bb0.jpg +0 -0
  906. package/kb/references/thumbs/fa070122c7.jpg +0 -0
  907. package/kb/references/thumbs/fa29cc1369.jpg +0 -0
  908. package/kb/references/thumbs/fa791a4dab.jpg +0 -0
  909. package/kb/references/thumbs/fa8060509e.jpg +0 -0
  910. package/kb/references/thumbs/fafeed4003.jpg +0 -0
  911. package/kb/references/thumbs/fb1c024f38.jpg +0 -0
  912. package/kb/references/thumbs/fb77aa28b3.jpg +0 -0
  913. package/kb/references/thumbs/fb9f7f1966.jpg +0 -0
  914. package/kb/references/thumbs/fba8a8c2da.jpg +0 -0
  915. package/kb/references/thumbs/fce2754c36.jpg +0 -0
  916. package/kb/references/thumbs/fce2fb83df.jpg +0 -0
  917. package/kb/references/thumbs/fd4c51bb47.jpg +0 -0
  918. package/kb/references/thumbs/fd8c269817.jpg +0 -0
  919. package/kb/references/thumbs/fdb7b250ca.jpg +0 -0
  920. package/kb/references/thumbs/fe18088de8.jpg +0 -0
  921. package/kb/references/thumbs/fe4f742ba7.jpg +0 -0
  922. package/kb/references/thumbs/fe64742ce7.jpg +0 -0
  923. package/kb/references/thumbs/feb5d62161.jpg +0 -0
  924. package/kb/references/thumbs/fed21b3e98.jpg +0 -0
  925. package/kb/references/thumbs/fed2205951.jpg +0 -0
  926. package/kb/references/thumbs/ffdacf1715.jpg +0 -0
  927. package/kb/sections/_buttons.md +93 -0
  928. package/kb/sections/_finish.md +325 -0
  929. package/kb/sections/about-founder-note.md +85 -0
  930. package/kb/sections/about-team-roster.md +86 -0
  931. package/kb/sections/alternating-rows-live-tiles.md +110 -0
  932. package/kb/sections/annotated-product-shot.md +105 -0
  933. package/kb/sections/asymmetric-bento-dominant.md +114 -0
  934. package/kb/sections/before-after-features.md +104 -0
  935. package/kb/sections/before-after-repair.md +107 -0
  936. package/kb/sections/bento-lead-tile.md +112 -0
  937. package/kb/sections/book-a-slot-honest.md +116 -0
  938. package/kb/sections/capability-prompt-ledger.md +68 -0
  939. package/kb/sections/case-study-band.md +113 -0
  940. package/kb/sections/centred-stack-hero.md +107 -0
  941. package/kb/sections/closing-demo-final-state.md +81 -0
  942. package/kb/sections/closing-full-bleed-line-and-form.md +86 -0
  943. package/kb/sections/closing-result-previewed.md +88 -0
  944. package/kb/sections/closing-three-real-steps.md +87 -0
  945. package/kb/sections/command-reference-table.md +70 -0
  946. package/kb/sections/comparison-old-way-bars.md +88 -0
  947. package/kb/sections/comparison-same-task-two-ways.md +88 -0
  948. package/kb/sections/day-of-the-place.md +102 -0
  949. package/kb/sections/docs-reading-rail.md +88 -0
  950. package/kb/sections/editorial-columns-hero.md +107 -0
  951. package/kb/sections/editorial-essay-pull-quote.md +112 -0
  952. package/kb/sections/events-calendar-list.md +107 -0
  953. package/kb/sections/explorable-control-demo.md +102 -0
  954. package/kb/sections/faq-open-answers-editorial.md +85 -0
  955. package/kb/sections/faq-ruled-accordion-aside.md +87 -0
  956. package/kb/sections/footer-closing-scene.md +84 -0
  957. package/kb/sections/footer-editorial-sitemap.md +85 -0
  958. package/kb/sections/footer-minimal-credits.md +80 -0
  959. package/kb/sections/full-bleed-image-band.md +103 -0
  960. package/kb/sections/full-bleed-image-hero.md +105 -0
  961. package/kb/sections/gallery-first-hero.md +109 -0
  962. package/kb/sections/horizontal-scroll-strip.md +101 -0
  963. package/kb/sections/how-i-work-own-voice.md +111 -0
  964. package/kb/sections/how-it-works-in-place.md +109 -0
  965. package/kb/sections/impact-numbers-sourced.md +109 -0
  966. package/kb/sections/install-steps-per-agent.md +77 -0
  967. package/kb/sections/masonry-real-work.md +109 -0
  968. package/kb/sections/numbered-editorial-columns.md +105 -0
  969. package/kb/sections/object-first-hero.md +106 -0
  970. package/kb/sections/object-timeline.md +105 -0
  971. package/kb/sections/one-huge-number.md +100 -0
  972. package/kb/sections/one-person-story.md +103 -0
  973. package/kb/sections/order-ahead-ticket.md +119 -0
  974. package/kb/sections/press-strip-quoted.md +106 -0
  975. package/kb/sections/pricing-comparison-table-sticky.md +94 -0
  976. package/kb/sections/pricing-free-pro-upgrade-moment.md +85 -0
  977. package/kb/sections/pricing-plan-beside-product.md +88 -0
  978. package/kb/sections/pricing-plans-ledger.md +94 -0
  979. package/kb/sections/pricing-usage-calculator.md +93 -0
  980. package/kb/sections/printed-menu-card.md +110 -0
  981. package/kb/sections/process-happening.md +102 -0
  982. package/kb/sections/project-index-ruled.md +106 -0
  983. package/kb/sections/proof-logo-cloud-with-reasons.md +82 -0
  984. package/kb/sections/proof-measured-numbers.md +88 -0
  985. package/kb/sections/proof-output-wall.md +84 -0
  986. package/kb/sections/proof-single-honest-quote.md +81 -0
  987. package/kb/sections/resources-searchable-list.md +110 -0
  988. package/kb/sections/rooms-and-rates.md +107 -0
  989. package/kb/sections/ruled-facts-table.md +108 -0
  990. package/kb/sections/ruled-list-pinned-visual.md +107 -0
  991. package/kb/sections/scroll-scrubbed-product-demo.md +103 -0
  992. package/kb/sections/self-playing-run-demo.md +104 -0
  993. package/kb/sections/services-price-ledger.md +106 -0
  994. package/kb/sections/signup-form-with-next-steps.md +86 -0
  995. package/kb/sections/signup-waitlist-place-in-line.md +90 -0
  996. package/kb/sections/spec-sheet-features.md +108 -0
  997. package/kb/sections/split-hero-live-object.md +108 -0
  998. package/kb/sections/sticky-feature-narrative.md +104 -0
  999. package/kb/sections/todays-board-hero.md +112 -0
  1000. package/kb/sections/touchable-tool-hero.md +111 -0
  1001. package/kb/sections/typographic-statement-hero.md +99 -0
  1002. package/kb/sections/visit-hours-map.md +110 -0
  1003. package/kb/sections/ways-to-help-honest.md +115 -0
  1004. package/kb/sources/design-reference.md +991 -0
  1005. package/kb/sources/product-and-docs.md +37 -0
  1006. package/kb/systems/1win/DESIGN.md +463 -0
  1007. package/kb/systems/LICENSE-awesome-design-md +21 -0
  1008. package/kb/systems/README.md +7 -0
  1009. package/kb/systems/airbnb/DESIGN.md +545 -0
  1010. package/kb/systems/airtable/DESIGN.md +554 -0
  1011. package/kb/systems/aljazeera/DESIGN.md +594 -0
  1012. package/kb/systems/apple/DESIGN.md +562 -0
  1013. package/kb/systems/binance/DESIGN.md +634 -0
  1014. package/kb/systems/bmw/DESIGN.md +544 -0
  1015. package/kb/systems/bmw-m/DESIGN.md +503 -0
  1016. package/kb/systems/bugatti/DESIGN.md +454 -0
  1017. package/kb/systems/cal/DESIGN.md +542 -0
  1018. package/kb/systems/categories.json +87 -0
  1019. package/kb/systems/claude/DESIGN.md +589 -0
  1020. package/kb/systems/clay/DESIGN.md +541 -0
  1021. package/kb/systems/clickhouse/DESIGN.md +544 -0
  1022. package/kb/systems/cohere/DESIGN.md +451 -0
  1023. package/kb/systems/coinbase/DESIGN.md +570 -0
  1024. package/kb/systems/composio/DESIGN.md +506 -0
  1025. package/kb/systems/cursor/DESIGN.md +537 -0
  1026. package/kb/systems/dell-1996/DESIGN.md +632 -0
  1027. package/kb/systems/dji/DESIGN.md +882 -0
  1028. package/kb/systems/elevenlabs/DESIGN.md +504 -0
  1029. package/kb/systems/expo/DESIGN.md +526 -0
  1030. package/kb/systems/ferrari/DESIGN.md +531 -0
  1031. package/kb/systems/figma/DESIGN.md +578 -0
  1032. package/kb/systems/framer/DESIGN.md +544 -0
  1033. package/kb/systems/hashicorp/DESIGN.md +575 -0
  1034. package/kb/systems/hp/DESIGN.md +670 -0
  1035. package/kb/systems/ibm/DESIGN.md +550 -0
  1036. package/kb/systems/intercom/DESIGN.md +546 -0
  1037. package/kb/systems/jobright/DESIGN.md +525 -0
  1038. package/kb/systems/kraken/DESIGN.md +125 -0
  1039. package/kb/systems/lamborghini/DESIGN.md +288 -0
  1040. package/kb/systems/linear.app/DESIGN.md +548 -0
  1041. package/kb/systems/lovable/DESIGN.md +298 -0
  1042. package/kb/systems/mastercard/DESIGN.md +365 -0
  1043. package/kb/systems/mercury/DESIGN.md +568 -0
  1044. package/kb/systems/meta/DESIGN.md +683 -0
  1045. package/kb/systems/minimax/DESIGN.md +746 -0
  1046. package/kb/systems/mintlify/DESIGN.md +852 -0
  1047. package/kb/systems/miro/DESIGN.md +825 -0
  1048. package/kb/systems/mistral.ai/DESIGN.md +773 -0
  1049. package/kb/systems/mongodb/DESIGN.md +767 -0
  1050. package/kb/systems/mydealz/DESIGN.md +551 -0
  1051. package/kb/systems/nerdwallet/DESIGN.md +722 -0
  1052. package/kb/systems/nike/DESIGN.md +575 -0
  1053. package/kb/systems/nintendo-2001/DESIGN.md +649 -0
  1054. package/kb/systems/notion/DESIGN.md +821 -0
  1055. package/kb/systems/nvidia/DESIGN.md +640 -0
  1056. package/kb/systems/ollama/DESIGN.md +539 -0
  1057. package/kb/systems/opencode.ai/DESIGN.md +521 -0
  1058. package/kb/systems/oracle/DESIGN.md +778 -0
  1059. package/kb/systems/oura/DESIGN.md +616 -0
  1060. package/kb/systems/pinterest/DESIGN.md +597 -0
  1061. package/kb/systems/playstation/DESIGN.md +661 -0
  1062. package/kb/systems/posthog/DESIGN.md +690 -0
  1063. package/kb/systems/raycast/DESIGN.md +669 -0
  1064. package/kb/systems/renault/DESIGN.md +589 -0
  1065. package/kb/systems/replicate/DESIGN.md +616 -0
  1066. package/kb/systems/resend/DESIGN.md +585 -0
  1067. package/kb/systems/revolut/DESIGN.md +636 -0
  1068. package/kb/systems/runwayml/DESIGN.md +244 -0
  1069. package/kb/systems/sanity/DESIGN.md +357 -0
  1070. package/kb/systems/sap/DESIGN.md +639 -0
  1071. package/kb/systems/sentry/DESIGN.md +551 -0
  1072. package/kb/systems/shopify/DESIGN.md +516 -0
  1073. package/kb/systems/slack/DESIGN.md +482 -0
  1074. package/kb/systems/spacex/DESIGN.md +363 -0
  1075. package/kb/systems/spotify/DESIGN.md +246 -0
  1076. package/kb/systems/starbucks/DESIGN.md +580 -0
  1077. package/kb/systems/stripe/DESIGN.md +487 -0
  1078. package/kb/systems/supabase/DESIGN.md +462 -0
  1079. package/kb/systems/superhuman/DESIGN.md +448 -0
  1080. package/kb/systems/tesla/DESIGN.md +286 -0
  1081. package/kb/systems/theverge/DESIGN.md +339 -0
  1082. package/kb/systems/together.ai/DESIGN.md +633 -0
  1083. package/kb/systems/uber/DESIGN.md +636 -0
  1084. package/kb/systems/vercel/DESIGN.md +736 -0
  1085. package/kb/systems/vodafone/DESIGN.md +538 -0
  1086. package/kb/systems/voltagent/DESIGN.md +521 -0
  1087. package/kb/systems/warp/DESIGN.md +526 -0
  1088. package/kb/systems/webflow/DESIGN.md +588 -0
  1089. package/kb/systems/wired/DESIGN.md +497 -0
  1090. package/kb/systems/wise/DESIGN.md +544 -0
  1091. package/kb/systems/x.ai/DESIGN.md +465 -0
  1092. package/kb/systems/zapier/DESIGN.md +537 -0
  1093. package/kb/taste/garvit-by-job.md +46 -0
  1094. package/kb/taste/garvit-portfolio.md +94 -0
  1095. package/kb/universal/color-roles.md +26 -0
  1096. package/kb/universal/copy.md +16 -0
  1097. package/kb/universal/details.md +31 -0
  1098. package/kb/universal/first-screen.md +30 -0
  1099. package/kb/universal/fit-the-codebase.md +29 -0
  1100. package/kb/universal/layout-width.md +28 -0
  1101. package/kb/universal/motion-feedback.md +23 -0
  1102. package/kb/universal/signature.md +29 -0
  1103. package/kb/universal/states.md +17 -0
  1104. package/kb/universal/type-app.md +33 -0
  1105. package/kb/universal/type-marketing.md +28 -0
  1106. package/package.json +37 -4
  1107. package/scripts/pack-assets.mjs +58 -0
  1108. package/skill/palate/SKILL.md +261 -0
  1109. package/src/auth-recheck.mjs +4 -0
  1110. package/src/auth.mjs +416 -0
  1111. package/src/capture.mjs +182 -0
  1112. package/src/catalog/classify.mjs +96 -0
  1113. package/src/catalog/curated.mjs +128 -0
  1114. package/src/catalog/index-build.mjs +67 -0
  1115. package/src/catalog/literal.mjs +146 -0
  1116. package/src/catalog/store.mjs +40 -0
  1117. package/src/catalog/vibrant.mjs +126 -0
  1118. package/src/check.mjs +93 -0
  1119. package/src/concept.mjs +157 -0
  1120. package/src/context.mjs +226 -0
  1121. package/src/copy.mjs +188 -0
  1122. package/src/devserver.mjs +135 -0
  1123. package/src/fontgenre.mjs +44 -0
  1124. package/src/guard-agents.mjs +213 -0
  1125. package/src/guard.mjs +377 -0
  1126. package/src/kb.mjs +91 -0
  1127. package/src/look.mjs +312 -0
  1128. package/src/loop/ambition.mjs +620 -0
  1129. package/src/loop/animate.mjs +70 -0
  1130. package/src/loop/artdirection.mjs +264 -0
  1131. package/src/loop/bench.mjs +184 -0
  1132. package/src/loop/benchpick.mjs +155 -0
  1133. package/src/loop/blueprint.mjs +620 -0
  1134. package/src/loop/board.html +180 -0
  1135. package/src/loop/build.mjs +393 -0
  1136. package/src/loop/buttons.mjs +413 -0
  1137. package/src/loop/common.mjs +206 -0
  1138. package/src/loop/cost.mjs +63 -0
  1139. package/src/loop/craft.mjs +152 -0
  1140. package/src/loop/craftchecks.mjs +148 -0
  1141. package/src/loop/critique.mjs +393 -0
  1142. package/src/loop/details.mjs +225 -0
  1143. package/src/loop/direction.html +168 -0
  1144. package/src/loop/direction.mjs +256 -0
  1145. package/src/loop/directions.json +8 -0
  1146. package/src/loop/explore.mjs +219 -0
  1147. package/src/loop/fail.mjs +110 -0
  1148. package/src/loop/fix.mjs +194 -0
  1149. package/src/loop/floor.mjs +677 -0
  1150. package/src/loop/forget.mjs +52 -0
  1151. package/src/loop/gate.mjs +1053 -0
  1152. package/src/loop/grid.html +123 -0
  1153. package/src/loop/growth.mjs +118 -0
  1154. package/src/loop/icons.mjs +109 -0
  1155. package/src/loop/imagery.mjs +1280 -0
  1156. package/src/loop/judge-api.mjs +245 -0
  1157. package/src/loop/judge.mjs +167 -0
  1158. package/src/loop/layouts.mjs +492 -0
  1159. package/src/loop/library.mjs +82 -0
  1160. package/src/loop/lookplan.mjs +182 -0
  1161. package/src/loop/lookvec.mjs +645 -0
  1162. package/src/loop/match.mjs +923 -0
  1163. package/src/loop/matched.mjs +26 -0
  1164. package/src/loop/myrefs.mjs +151 -0
  1165. package/src/loop/pick.mjs +213 -0
  1166. package/src/loop/picker.html +118 -0
  1167. package/src/loop/plan.mjs +289 -0
  1168. package/src/loop/polish.mjs +39 -0
  1169. package/src/loop/research-learn.mjs +41 -0
  1170. package/src/loop/research.mjs +1130 -0
  1171. package/src/loop/rulereview.mjs +146 -0
  1172. package/src/loop/rules-board.html +199 -0
  1173. package/src/loop/rules.mjs +939 -0
  1174. package/src/loop/similar.mjs +276 -0
  1175. package/src/loop/states-source.mjs +258 -0
  1176. package/src/loop/states.mjs +1547 -0
  1177. package/src/loop/stats.mjs +78 -0
  1178. package/src/loop/taste.mjs +81 -0
  1179. package/src/loop/timeline.mjs +149 -0
  1180. package/src/loop/variants.mjs +228 -0
  1181. package/src/loop/vary.mjs +249 -0
  1182. package/src/mcp/server.mjs +1246 -0
  1183. package/src/motion.mjs +207 -0
  1184. package/src/moves.mjs +242 -0
  1185. package/src/novelty.mjs +116 -0
  1186. package/src/page-ambition.js +181 -0
  1187. package/src/page-craft.js +724 -0
  1188. package/src/page-details.js +415 -0
  1189. package/src/page-devices.js +365 -0
  1190. package/src/page-fingerprint.js +281 -0
  1191. package/src/page-gate.js +1307 -0
  1192. package/src/page-look.js +698 -0
  1193. package/src/page-metrics.js +78 -0
  1194. package/src/page-principles.js +1167 -0
  1195. package/src/page-rules.js +308 -0
  1196. package/src/page-sections.js +211 -0
  1197. package/src/page-states.js +703 -0
  1198. package/src/paths.mjs +13 -0
  1199. package/src/png.mjs +107 -0
  1200. package/src/point/auto.mjs +224 -0
  1201. package/src/point/locate.mjs +114 -0
  1202. package/src/point/overlay.js +1516 -0
  1203. package/src/point/server.mjs +1122 -0
  1204. package/src/point/trail.mjs +219 -0
  1205. package/src/pool.mjs +80 -0
  1206. package/src/principles.mjs +279 -0
  1207. package/src/refs.mjs +299 -0
  1208. package/src/robots.mjs +145 -0
  1209. package/src/sections.mjs +210 -0
  1210. package/src/shoot.mjs +235 -0
  1211. package/src/systems/apply.mjs +355 -0
  1212. package/src/systems/load.mjs +251 -0
  1213. package/src/systems/pick.mjs +213 -0
  1214. package/templates/vite-react-tw/index.html +12 -0
  1215. package/templates/vite-react-tw/package.json +25 -0
  1216. package/templates/vite-react-tw/src/App.tsx +3 -0
  1217. package/templates/vite-react-tw/src/index.css +1 -0
  1218. package/templates/vite-react-tw/src/main.tsx +10 -0
  1219. package/templates/vite-react-tw/tsconfig.app.json +26 -0
  1220. package/templates/vite-react-tw/tsconfig.json +7 -0
  1221. package/templates/vite-react-tw/tsconfig.node.json +23 -0
  1222. package/templates/vite-react-tw/vite.config.ts +8 -0
@@ -0,0 +1,1307 @@
1
+ // Runs inside the page (read as text and evaluated) with { sections, cell }. Measures the floor of a page,
2
+ // the things that are visibly wrong whatever the taste: how much of each section carries ink (text, media,
3
+ // surfaces, borders, shadows) on a coarse grid, the largest empty rectangle and where it sits, left/right
4
+ // balance, imagery, a layout signature, boards (side-by-side columns in a framed panel) and how full each column
5
+ // is, monospace and placeholder text, clipped text, and for the page the empty vertical bands and the
6
+ // background colour runs. Also the page's system (each section's H2 size and left edge, its padding, filled
7
+ // button heights, card styles), tiles with a void inside, photos placed more than once, bento grids (each tile's
8
+ // size, surface and content type), the product frame in the hero (its share of the stage and the edges it bleeds
9
+ // off) and real words cut by a clipping edge. Boxes are in page coordinates.
10
+ ({ sections, cell = 32 }) => {
11
+ const docW = document.documentElement.clientWidth;
12
+ const pageH = Math.max(document.documentElement.scrollHeight, document.body.scrollHeight);
13
+ const vh = innerHeight;
14
+ const SKIP = new Set(["SCRIPT", "STYLE", "NOSCRIPT", "TEMPLATE", "LINK", "META", "HEAD", "TITLE", "BR", "WBR", "SOURCE", "TRACK", "OPTION", "DATALIST"]);
15
+
16
+ // ---- colour: any computed CSS colour (rgb, hex, hsl, oklab, oklch, lab, lch, color(), color-mix(), which
17
+ // computes to oklab() or color(srgb …)) as [r, g, b, a(0–1)] in sRGB, or null when it can't be read. Never
18
+ // pull digits out of the string: "oklab(0.62 0.1 0.05)" read as rgb is near black. The same helper is in
19
+ // page-rules.js, page-devices.js, page-details.js and page-gate.js (test/colour.test.mjs keeps them identical).
20
+ const colourCtx = Object.assign(document.createElement("canvas"), { width: 1, height: 1 }).getContext("2d", { willReadFrequently: true });
21
+ const colourMemo = new Map();
22
+ const RGB_FN = /^rgba?\(\s*(-?[\d.]+)(%?)[,\s]+(-?[\d.]+)(%?)[,\s]+(-?[\d.]+)(%?)(?:\s*[,/]\s*([\d.]+)(%?))?\s*\)$/i;
23
+ const fromRgbFn = (m) => [1, 3, 5].map((i) => Math.max(0, Math.min(255, m[i + 1] ? (parseFloat(m[i]) * 255) / 100 : parseFloat(m[i])))).concat(m[7] === undefined ? 1 : m[8] ? parseFloat(m[7]) / 100 : parseFloat(m[7]));
24
+ const fromHex = (h) => {
25
+ const x = h.length <= 5 ? [...h.slice(1)].map((c) => c + c).join("") : h.slice(1);
26
+ return [0, 2, 4].map((i) => parseInt(x.slice(i, i + 2), 16)).concat(x.length === 8 ? parseInt(x.slice(6, 8), 16) / 255 : 1);
27
+ };
28
+ // The spaces Chrome computes to, converted by hand for when a canvas can't parse them.
29
+ const fromSpace = (c) => {
30
+ const m = c.match(/^(oklab|oklch|lab|lch|color)\(\s*([^)]*)\)$/i);
31
+ if (!m) return null;
32
+ const [body, alpha] = m[2].split("/");
33
+ const p = body.trim().split(/[\s,]+/);
34
+ const space = m[1].toLowerCase() === "color" ? (p.shift() ?? "").toLowerCase() : m[1].toLowerCase();
35
+ const n = (s, full = 1) => (s == null || s === "none" ? 0 : s.endsWith("%") ? (parseFloat(s) / 100) * full : parseFloat(s));
36
+ const a = alpha === undefined ? 1 : n(alpha.trim());
37
+ const enc = (v) => 255 * Math.max(0, Math.min(1, v <= 0.0031308 ? 12.92 * v : 1.055 * Math.pow(v, 1 / 2.4) - 0.055));
38
+ const polar = (C, h) => [C * Math.cos((h * Math.PI) / 180), C * Math.sin((h * Math.PI) / 180)];
39
+ let lin;
40
+ if (space === "oklab" || space === "oklch") {
41
+ const L = n(p[0]);
42
+ const [A, B] = space === "oklab" ? [n(p[1], 0.4), n(p[2], 0.4)] : polar(n(p[1], 0.4), n(p[2]));
43
+ const l = (L + 0.3963377774 * A + 0.2158037573 * B) ** 3, k = (L - 0.1055613458 * A - 0.0638541728 * B) ** 3, s = (L - 0.0894841775 * A - 1.291485548 * B) ** 3;
44
+ lin = [4.0767416621 * l - 3.3077115913 * k + 0.2309699292 * s, -1.2684380046 * l + 2.6097574011 * k - 0.3413193965 * s, -0.0041960863 * l - 0.7034186147 * k + 1.707614701 * s];
45
+ } else if (space === "lab" || space === "lch") {
46
+ const L = n(p[0], 100);
47
+ const [A, B] = space === "lab" ? [n(p[1], 125), n(p[2], 125)] : polar(n(p[1], 150), n(p[2]));
48
+ const fy = (L + 16) / 116, fx = fy + A / 500, fz = fy - B / 200, e = 216 / 24389, kk = 24389 / 27;
49
+ const X = 0.96422 * (fx ** 3 > e ? fx ** 3 : (116 * fx - 16) / kk), Y = L > kk * e ? fy ** 3 : L / kk, Z = 0.82521 * (fz ** 3 > e ? fz ** 3 : (116 * fz - 16) / kk);
50
+ lin = [3.1338561 * X - 1.6168667 * Y - 0.4906146 * Z, -0.9787684 * X + 1.9161415 * Y + 0.033454 * Z, 0.0719453 * X - 0.2289914 * Y + 1.4052427 * Z];
51
+ } else if (space === "srgb" || space === "display-p3" || space === "a98-rgb" || space === "rec2020") return p.slice(0, 3).map((v) => 255 * Math.max(0, Math.min(1, n(v)))).concat(a);
52
+ else if (space === "srgb-linear") lin = p.slice(0, 3).map((v) => n(v));
53
+ else return null;
54
+ return lin.map(enc).concat(a);
55
+ };
56
+ const toRgba = (c) => {
57
+ const s = String(c ?? "").trim();
58
+ if (!s || s === "none") return null;
59
+ if (colourMemo.has(s)) return colourMemo.get(s);
60
+ let v = null;
61
+ const m = s.match(RGB_FN);
62
+ if (m) v = fromRgbFn(m);
63
+ else if (/^transparent$/i.test(s)) v = [0, 0, 0, 0];
64
+ else if (/^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(s)) v = fromHex(s);
65
+ else {
66
+ // The canvas parses anything the browser does; a value it rejects leaves the sentinel in place.
67
+ colourCtx.fillStyle = "#010203";
68
+ colourCtx.fillStyle = s;
69
+ const got = String(colourCtx.fillStyle);
70
+ if (got !== "#010203") {
71
+ const g = got.match(RGB_FN);
72
+ if (g) v = fromRgbFn(g);
73
+ else if (/^#[0-9a-f]{6}$/i.test(got)) v = fromHex(got);
74
+ else {
75
+ colourCtx.clearRect(0, 0, 1, 1);
76
+ colourCtx.fillRect(0, 0, 1, 1);
77
+ const d = colourCtx.getImageData(0, 0, 1, 1).data;
78
+ // Alpha from the colour itself when it says (the canvas rounds translucent channels).
79
+ v = [d[0], d[1], d[2], fromSpace(got)?.[3] ?? d[3] / 255];
80
+ }
81
+ } else v = fromSpace(s);
82
+ }
83
+ colourMemo.set(s, v);
84
+ return v;
85
+ };
86
+ // Every colour named in a background-image (gradient stops), in order.
87
+ const colourStops = (bi) => (String(bi ?? "").match(/(?:rgba?|hsla?|oklab|oklch|lab|lch|color)\([^()]*\)|#[0-9a-f]{3,8}\b/gi) ?? []).map(toRgba).filter(Boolean);
88
+ // [r, g, b, a(0–1)] or null when (nearly) fully transparent or unreadable.
89
+ const rgba = (c) => {
90
+ const v = toRgba(c);
91
+ return v && v[3] >= 3 / 255 ? v : null;
92
+ };
93
+ const hex = (rgb) => (rgb ? "#" + rgb.slice(0, 3).map((v) => Math.round(v).toString(16).padStart(2, "0")).join("") : null);
94
+ const dist = (a, b) => Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2]);
95
+ const over = (top, under) => [0, 1, 2].map((i) => top[i] * top[3] + under[i] * (1 - top[3]));
96
+ // The colour actually painted behind an element: its own background and its ancestors', composited.
97
+ const bgMemo = new Map();
98
+ const paintedBg = (el) => {
99
+ if (!el || el.nodeType !== 1) return [255, 255, 255];
100
+ if (bgMemo.has(el)) return bgMemo.get(el);
101
+ const own = rgba(getComputedStyle(el).backgroundColor);
102
+ const out = own && own[3] >= 0.98 ? own.slice(0, 3) : own ? over(own, paintedBg(el.parentElement)) : paintedBg(el.parentElement);
103
+ bgMemo.set(el, out);
104
+ return out;
105
+ };
106
+ // A gradient with colours that differ (a real texture, not linear-gradient(#fff, #fff)).
107
+ const gradientVaries = (bi) => {
108
+ const cs = colourStops(bi).filter((c) => c[3] >= 3 / 255);
109
+ for (let i = 1; i < cs.length; i++) if (dist(cs[0], cs[i]) > 14 || Math.abs(cs[0][3] - cs[i][3]) > 0.2) return true;
110
+ return false;
111
+ };
112
+
113
+ // ---- visibility (opacity of the element and its ancestors)
114
+ const visMemo = new Map();
115
+ const shown = (el) => {
116
+ if (!el || el.nodeType !== 1 || el === document.documentElement) return true;
117
+ if (visMemo.has(el)) return visMemo.get(el);
118
+ const cs = getComputedStyle(el);
119
+ const v = cs.display !== "none" && parseFloat(cs.opacity) > 0.05 && shown(el.parentElement);
120
+ visMemo.set(el, v);
121
+ return v;
122
+ };
123
+ // A box with a ground of its own (a fill that differs from what is behind it) or a shadow.
124
+ const hasSurface = (el, cs) => {
125
+ const own = rgba(cs.backgroundColor);
126
+ if (own && own[3] > 0.08 && dist(over(own, paintedBg(el.parentElement)), paintedBg(el.parentElement)) > 9) return true;
127
+ return Boolean(cs.boxShadow && cs.boxShadow !== "none" && !/^rgba\(0, 0, 0, 0\)/.test(cs.boxShadow));
128
+ };
129
+ // Drawn border sides (visible against what is behind them).
130
+ const borderSides = (el, cs) => {
131
+ const under = paintedBg(el);
132
+ let n = 0;
133
+ for (const side of ["Top", "Right", "Bottom", "Left"]) {
134
+ if (!(parseFloat(cs[`border${side}Width`]) >= 0.5) || /none|hidden/.test(cs[`border${side}Style`])) continue;
135
+ const c = rgba(cs[`border${side}Color`]);
136
+ if (c && c[3] >= 0.12 && dist(over(c, under), under) >= 9) n++;
137
+ }
138
+ return n;
139
+ };
140
+ const pbox = (r) => ({ x: r.left + scrollX, y: r.top + scrollY, w: r.width, h: r.height });
141
+ const clean = (s) => (s ?? "").replace(/\s+/g, " ").trim();
142
+
143
+ // ---- the ink grid: bit 1 ink (anything visible), 2 detail (text, borders, small media), 4 imagery, 8 texture
144
+ const cols = Math.max(1, Math.ceil(docW / cell));
145
+ const rows = Math.max(1, Math.ceil(pageH / cell));
146
+ const grid = new Uint8Array(cols * rows);
147
+ const inks = []; // { x, y, w, h } of everything that inks, for the page's empty bands
148
+ // band: false keeps it out of the empty-band measure (a texture over a gap is still a gap).
149
+ const mark = (b, bits, min = 3, band = true) => {
150
+ const x0 = Math.max(0, b.x), y0 = Math.max(0, b.y), x1 = Math.min(docW, b.x + b.w), y1 = Math.min(pageH, b.y + b.h);
151
+ if (x1 - x0 < 0.5 || y1 - y0 < 0.5) return;
152
+ if (band) inks.push({ x: x0, y: y0, w: x1 - x0, h: y1 - y0 });
153
+ const need = (lo, hi, a) => Math.min(min, hi - lo) - 0.01;
154
+ for (let r = Math.floor(y0 / cell); r <= Math.min(rows - 1, Math.floor((y1 - 0.01) / cell)); r++) {
155
+ const oy = Math.min(y1, (r + 1) * cell) - Math.max(y0, r * cell);
156
+ if (oy < need(y0, y1)) continue;
157
+ for (let c = Math.floor(x0 / cell); c <= Math.min(cols - 1, Math.floor((x1 - 0.01) / cell)); c++) {
158
+ const ox = Math.min(x1, (c + 1) * cell) - Math.max(x0, c * cell);
159
+ if (ox < need(x0, x1)) continue;
160
+ grid[r * cols + c] |= bits;
161
+ }
162
+ }
163
+ };
164
+
165
+ const sectionEls = sections.map((s) => {
166
+ try {
167
+ return document.querySelector(s.selector);
168
+ } catch {
169
+ return null;
170
+ }
171
+ });
172
+ const sectionOf = (y) => sections.find((s) => y >= s.box.y && y < s.box.y + s.box.height) ?? null;
173
+ const media = [];
174
+ const surfaces = []; // framed boxes (cards, panels, product boards) big enough to hold a layout of their own
175
+ const framed = []; // big boxes with their own ground, shadow or a border all round: where boards live
176
+ const solid = []; // every media box and every box with a ground, shadow or a border all round (tiles, chips, inputs)
177
+ const pictures = []; // raster images 64px and up, by source: for PHOTO_REPEAT
178
+ const all = document.body.querySelectorAll("*");
179
+ for (const el of all) {
180
+ if (SKIP.has(el.tagName)) continue;
181
+ const tag = el.tagName.toLowerCase();
182
+ if (el.parentElement?.closest("svg")) continue;
183
+ const r = el.getBoundingClientRect();
184
+ if (r.width < 1 || r.height < 1) continue;
185
+ if (r.bottom + scrollY < 0 || r.right < 0 || r.left > docW) continue;
186
+ const cs = getComputedStyle(el);
187
+ if (cs.visibility === "hidden" || cs.visibility === "collapse" || !shown(el)) continue;
188
+ const b = pbox(r);
189
+ const sec = sectionOf(b.y + b.h / 2);
190
+ const secArea = sec ? sec.box.width * sec.box.height : docW * pageH;
191
+ // A wrapper that is (nearly) the whole section, or a full-bleed band: its colour is the section's, not ink.
192
+ const wrapper = b.w * b.h >= 0.85 * secArea || (b.w >= docW - 2 && b.h > 0.5 * vh);
193
+ if (["img", "video", "canvas", "iframe", "svg", "object", "embed"].includes(tag)) {
194
+ const big = b.w * b.h >= 4096 && Math.max(b.w, b.h) > 64;
195
+ mark(b, 1 | (big ? 4 : 2));
196
+ if (big) media.push({ ...b, tag });
197
+ solid.push({ ...b, el, media: true });
198
+ if (tag === "img" && b.w >= 64 && b.h >= 64) pictures.push({ ...b, el, src: el.currentSrc || el.src });
199
+ continue;
200
+ }
201
+ const bi = cs.backgroundImage;
202
+ let bgPicture = false;
203
+ if (bi && bi !== "none") {
204
+ if (/url\(/.test(bi)) {
205
+ const big = b.w * b.h >= 4096 && Math.max(b.w, b.h) > 64;
206
+ mark(b, 1 | (big ? 4 : 2));
207
+ if (big) media.push({ ...b, tag: "background-image" });
208
+ bgPicture = true;
209
+ solid.push({ ...b, el, media: true });
210
+ // A photo set as a background (cover or contain, not a repeated pattern tile).
211
+ const u = bi.match(/url\(["']?([^"')]+)["']?\)/)?.[1];
212
+ if (u && b.w >= 64 && b.h >= 64 && (/no-repeat/.test(cs.backgroundRepeat) || /cover|contain/.test(cs.backgroundSize))) pictures.push({ ...b, el, src: u, bg: true });
213
+ } else if (gradientVaries(bi)) mark(b, wrapper ? 8 : 1 | 8);
214
+ }
215
+ // A texture painted by a ::before / ::after overlay (palate's own recipe: .texture-paper-over::after {
216
+ // background: url(textures/paper.svg) }) is material over the area it covers, though not ink.
217
+ for (const pseudo of ["::before", "::after"]) {
218
+ const ps = getComputedStyle(el, pseudo);
219
+ if (!ps.content || ps.content === "none" || ps.content === "normal" || ps.display === "none" || ps.visibility === "hidden" || parseFloat(ps.opacity) < 0.02) continue;
220
+ const pbi = ps.backgroundImage;
221
+ if (!pbi || pbi === "none" || !(/url\(/.test(pbi) || gradientVaries(pbi))) continue;
222
+ // Its box: an overlay (absolute, inset 0) covers the element; else its own size. Kept inside the element.
223
+ const w = parseFloat(ps.width), h = parseFloat(ps.height);
224
+ const out = ps.position === "absolute" || ps.position === "fixed";
225
+ const pb = Number.isFinite(w) && Number.isFinite(h) && w > 0 && h > 0 ? { x: b.x + (out ? parseFloat(ps.left) || 0 : 0), y: b.y + (out ? parseFloat(ps.top) || 0 : 0), w, h } : b;
226
+ const x0 = Math.max(b.x, pb.x), y0 = Math.max(b.y, pb.y), x1 = Math.min(b.x + b.w, pb.x + pb.w), y1 = Math.min(b.y + b.h, pb.y + pb.h);
227
+ // Grain over a photo is the photo's finish, not more material: it is already counted as imagery.
228
+ const onMedia = [...el.querySelectorAll("img, picture, video, canvas")].some((m) => {
229
+ const mr = m.getBoundingClientRect();
230
+ return mr.width * mr.height >= 0.5 * (x1 - x0) * (y1 - y0);
231
+ });
232
+ if (!onMedia && x1 - x0 >= 8 && y1 - y0 >= 8) mark({ x: x0, y: y0, w: x1 - x0, h: y1 - y0 }, 8, 3, false);
233
+ }
234
+ let surface = false;
235
+ if (!wrapper) {
236
+ surface = hasSurface(el, cs);
237
+ if (surface) {
238
+ mark(b, 1);
239
+ if (b.w >= 240 && b.h >= 200) surfaces.push({ ...b, el });
240
+ }
241
+ }
242
+ // Borders: only the lines themselves are ink.
243
+ const under = paintedBg(el);
244
+ let sides = 0;
245
+ for (const [side, line] of [
246
+ ["Top", { x: b.x, y: b.y, w: b.w, h: 1 }],
247
+ ["Bottom", { x: b.x, y: b.y + b.h - 1, w: b.w, h: 1 }],
248
+ ["Left", { x: b.x, y: b.y, w: 1, h: b.h }],
249
+ ["Right", { x: b.x + b.w - 1, y: b.y, w: 1, h: b.h }],
250
+ ]) {
251
+ const w = parseFloat(cs[`border${side}Width`]);
252
+ if (!(w >= 0.5) || cs[`border${side}Style`] === "none" || cs[`border${side}Style`] === "hidden") continue;
253
+ const c = rgba(cs[`border${side}Color`]);
254
+ if (!c || c[3] < 0.12 || dist(over(c, under), under) < 9) continue;
255
+ mark(line, 1 | 2, 0.5);
256
+ sides++;
257
+ }
258
+ if (!wrapper && (surface || sides >= 3) && b.w >= 320 && b.h >= 240) framed.push({ ...b, el });
259
+ if (!wrapper && (surface || sides >= 3) && !bgPicture) solid.push({ ...b, el, surface, sides, cs });
260
+ }
261
+
262
+ // Text: the line boxes of every visible text node.
263
+ const texts = [];
264
+ const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
265
+ const range = document.createRange();
266
+ for (let n = walker.nextNode(); n; n = walker.nextNode()) {
267
+ const t = n.data;
268
+ if (!t || !t.trim()) continue;
269
+ const p = n.parentElement;
270
+ if (!p || SKIP.has(p.tagName) || p.closest("svg, script, style, noscript, template, select, textarea")) continue;
271
+ const cs = getComputedStyle(p);
272
+ if (cs.visibility !== "visible" || !shown(p)) continue;
273
+ const col = rgba(cs.color);
274
+ if (!col || col[3] < 0.1) continue;
275
+ range.selectNodeContents(n);
276
+ const rects = [...range.getClientRects()].filter((r) => r.width >= 1 && r.height >= 1).map(pbox);
277
+ if (!rects.length) continue;
278
+ for (const r of rects) mark(r, 1 | 2, 4);
279
+ texts.push({ node: n, el: p, text: t, rects, cs });
280
+ }
281
+
282
+ const at = (r, c) => grid[r * cols + c];
283
+ // Rows/columns of the grid inside a page box.
284
+ const span = (b) => ({ c0: Math.max(0, Math.floor(b.x / cell)), c1: Math.min(cols, Math.ceil((b.x + b.w) / cell)), r0: Math.max(0, Math.floor(b.y / cell)), r1: Math.min(rows, Math.ceil((b.y + b.h) / cell)) });
285
+
286
+ // ---- the page's content column: the median section's ink extent
287
+ const extents = sections.map((s) => {
288
+ const { c0, c1, r0, r1 } = span({ x: s.box.x, y: s.box.y, w: s.box.width, h: s.box.height });
289
+ let lo = Infinity, hi = -Infinity;
290
+ for (let r = r0; r < r1; r++) for (let c = c0; c < c1; c++) if (at(r, c) & 1) (lo = Math.min(lo, c)), (hi = Math.max(hi, c));
291
+ return lo <= hi ? [lo, hi + 1] : null;
292
+ });
293
+ const med = (xs) => {
294
+ const s = xs.filter((v) => v != null).sort((a, b) => a - b);
295
+ return s.length ? s[Math.floor(s.length / 2)] : null;
296
+ };
297
+ const colL = med(extents.map((e) => e?.[0])) ?? 0;
298
+ const colR = med(extents.map((e) => e?.[1])) ?? cols;
299
+
300
+ // Largest all-empty rectangle (in cells) of a sub-grid, by the histogram method.
301
+ const largestEmpty = (r0, r1, c0, c1, mask = 1) => {
302
+ const w = c1 - c0;
303
+ const h = new Array(w).fill(0);
304
+ let best = { area: 0, r0, r1: r0, c0, c1: c0 };
305
+ for (let r = r0; r < r1; r++) {
306
+ for (let c = 0; c < w; c++) h[c] = at(r, c0 + c) & mask ? 0 : h[c] + 1;
307
+ const st = [];
308
+ for (let c = 0; c <= w; c++) {
309
+ const hc = c < w ? h[c] : 0;
310
+ let start = c;
311
+ while (st.length && st[st.length - 1][1] >= hc) {
312
+ const [s0, hs] = st.pop();
313
+ if (hs * (c - s0) > best.area) best = { area: hs * (c - s0), r0: r - hs + 1, r1: r + 1, c0: c0 + s0, c1: c0 + c };
314
+ start = s0;
315
+ }
316
+ st.push([start, hc]);
317
+ }
318
+ }
319
+ return best;
320
+ };
321
+
322
+ // ---- layout signature from the DOM
323
+ const blockKids = (el) =>
324
+ [...el.children].filter((k) => {
325
+ if (SKIP.has(k.tagName)) return false;
326
+ const r = k.getBoundingClientRect();
327
+ const cs = getComputedStyle(k);
328
+ return r.width > 8 && r.height > 8 && cs.display !== "none" && cs.visibility !== "hidden" && cs.position !== "absolute" && cs.position !== "fixed" && shown(k);
329
+ });
330
+ const unwrap = (el) => {
331
+ let e = el;
332
+ for (let i = 0; i < 10; i++) {
333
+ const k = blockKids(e);
334
+ if (k.length !== 1) break;
335
+ e = k[0];
336
+ }
337
+ return e;
338
+ };
339
+ const rowsOf = (els) => {
340
+ const items = els.map((e) => ({ e, r: e.getBoundingClientRect() })).sort((a, b) => a.r.top - b.r.top);
341
+ const out = [];
342
+ for (const it of items) {
343
+ const row = out.find((g) => g.some((o) => Math.min(o.r.bottom, it.r.bottom) - Math.max(o.r.top, it.r.top) > 0.5 * Math.min(o.r.height, it.r.height) && (Math.min(o.r.right, it.r.right) - Math.max(o.r.left, it.r.left)) < 0.5 * Math.min(o.r.width, it.r.width)));
344
+ if (row) row.push(it);
345
+ else out.push([it]);
346
+ }
347
+ return out;
348
+ };
349
+ const layoutOf = (el, s) => {
350
+ if (!el) return null;
351
+ const root = unwrap(el);
352
+ const rowsDesc = [];
353
+ let cards = 0;
354
+ for (const row of rowsOf(blockKids(root))) {
355
+ const h = Math.max(...row.map((i) => i.r.height));
356
+ if (h < 40) continue;
357
+ let n = row.length;
358
+ let items = row.map((i) => i.r);
359
+ if (n === 1) {
360
+ const inner = blockKids(unwrap(row[0].e));
361
+ const ir = inner.length >= 2 ? rowsOf(inner).filter((g) => Math.max(...g.map((i) => i.r.height)) >= 40) : [];
362
+ if (ir.length && ir[0].length >= 2) {
363
+ n = ir[0].length;
364
+ items = ir[0].map((i) => i.r);
365
+ }
366
+ }
367
+ const ws = items.map((r) => r.width), hs = items.map((r) => r.height);
368
+ const equal = n >= 2 && Math.max(...ws) / Math.max(1, Math.min(...ws)) < 1.08 && Math.max(...hs) / Math.max(1, Math.min(...hs)) < 1.25;
369
+ if (equal) cards = Math.max(cards, n);
370
+ rowsDesc.push({ cols: n, equal, h: Math.round(h) });
371
+ }
372
+ // Collapse a run of single-column rows (eyebrow, heading, paragraph, buttons) into one.
373
+ const seq = [];
374
+ for (const r of rowsDesc) if (!(r.cols === 1 && seq[seq.length - 1] === 1)) seq.push(r.cols);
375
+ const head = [...el.querySelectorAll("h1, h2, h3")].find((h) => h.getBoundingClientRect().width > 0 && shown(h) && clean(h.textContent));
376
+ let align = "N";
377
+ if (head) {
378
+ range.selectNodeContents(head);
379
+ const lr = [...range.getClientRects()].filter((r) => r.width > 1);
380
+ const l = Math.min(...lr.map((r) => r.left)), rr = Math.max(...lr.map((r) => r.right));
381
+ const mid = (l + rr) / 2 + scrollX;
382
+ const fmid = s.box.x + s.box.width / 2;
383
+ const ta = getComputedStyle(head).textAlign;
384
+ align = lr.length && Math.abs(mid - fmid) < 0.06 * s.box.width && (ta === "center" || rr - l < 0.8 * s.box.width) ? "C" : "L";
385
+ }
386
+ const headingGrid = rowsDesc.length >= 2 && rowsDesc[0].cols === 1 && head && root.firstElementChild?.contains(head) !== false && rowsDesc.slice(1).some((r) => r.equal && r.cols >= 2) ? rowsDesc.slice(1).find((r) => r.equal && r.cols >= 2).cols : 0;
387
+ const columns = Math.max(1, ...rowsDesc.map((r) => r.cols));
388
+ return { align, rows: rowsDesc, columns, cards, headingGrid, signature: `${align}:${seq.join("/") || "0"}` };
389
+ };
390
+ // The tallest single-column run of like items (a board, a list) inside a section: what a phone stacks.
391
+ const stackOf = (el) => {
392
+ if (!el) return null;
393
+ let best = null;
394
+ for (const p of [el, ...el.querySelectorAll("*")].slice(0, 3000)) {
395
+ const ks = blockKids(p);
396
+ if (ks.length < 3) continue;
397
+ const key = (k) => k.tagName + "." + (typeof k.className === "string" ? k.className.split(/\s+/).sort().join(".") : "");
398
+ const counts = new Map();
399
+ for (const k of ks) counts.set(key(k), (counts.get(key(k)) ?? 0) + 1);
400
+ const like = Math.max(...counts.values());
401
+ if (like < 3 || like < 0.75 * ks.length) continue;
402
+ const rs = ks.map((k) => k.getBoundingClientRect()).sort((a, b) => a.top - b.top);
403
+ if (!rs.every((r, i) => !i || r.top >= rs[i - 1].bottom - 2)) continue;
404
+ const h = rs[rs.length - 1].bottom - rs[0].top;
405
+ // Reference entries (commands, questions, changelog items) each carry their own id for deep links.
406
+ const anchored = ks.filter((k) => k.id || k.querySelector(":scope > [id], :scope > * > [id]")).length;
407
+ if (!best || h > best.height) best = { height: Math.round(h), items: ks.length, anchored, tag: p.tagName.toLowerCase(), cls: typeof p.className === "string" ? p.className.slice(0, 60) : "" };
408
+ }
409
+ return best;
410
+ };
411
+
412
+ // ---- boards: side-by-side columns inside a big framed panel (a kanban, a schedule, a comparison) and how much
413
+ // of each column's height holds anything. Cards inside a panel ink the coarse grid, so a board whose columns
414
+ // are one small card each over a tall blank reads as full there; here each column is measured on its own.
415
+ const boardOf = (p, claimed) => {
416
+ // The row of columns: three to eight block children side by side, of similar width, spanning most of the
417
+ // panel's width and at least half its height.
418
+ let best = null;
419
+ for (const e of [p.el, ...p.el.querySelectorAll("*")].slice(0, 1500)) {
420
+ if (claimed.has(e)) continue;
421
+ const ks = blockKids(e);
422
+ if (ks.length < 3 || ks.length > 8) continue;
423
+ const rs = ks.map((k) => pbox(k.getBoundingClientRect())).sort((a, z) => a.x - z.x);
424
+ // Only what shows inside the panel (a board that scrolls sideways shows some of its columns).
425
+ const vis = rs.map((r) => ({ ...r, vw: Math.min(r.x + r.w, p.x + p.w) - Math.max(r.x, p.x) })).filter((r) => r.vw >= 0.5 * r.w);
426
+ if (vis.length < 3) continue;
427
+ const top = Math.min(...vis.map((r) => r.y)), bot = Math.max(...vis.map((r) => r.y + r.h)), rh = bot - top;
428
+ if (rh < Math.max(200, 0.5 * p.h)) continue;
429
+ if (!vis.every((r, k) => r.w >= 120 && r.y - top <= 0.1 * rh && (!k || r.x >= vis[k - 1].x + vis[k - 1].w - 2))) continue;
430
+ const ws = vis.map((r) => r.w);
431
+ if (Math.max(...ws) / Math.min(...ws) > 1.6 || ws.reduce((a, z) => a + z, 0) < 0.6 * p.w) continue;
432
+ if (!best || rh * vis.length > best.rh * best.cols.length) best = { e, cols: vis, top, rh };
433
+ }
434
+ if (!best) return null;
435
+ // What fills a column: text lines, media, and boxes with a surface of their own (cards, chips, inputs), from
436
+ // anywhere in the panel (a toast laid over the board fills what it covers).
437
+ const boxes = [];
438
+ for (const t of texts) if (p.el.contains(t.el)) boxes.push(...t.rects);
439
+ const colArea = Math.min(...best.cols.map((c) => c.w)) * best.rh;
440
+ for (const el of p.el.querySelectorAll("*")) {
441
+ if (SKIP.has(el.tagName) || el.parentElement?.closest("svg")) continue;
442
+ const r = el.getBoundingClientRect();
443
+ if (r.width < 4 || r.height < 4) continue;
444
+ const cs = getComputedStyle(el);
445
+ if (cs.visibility !== "visible" || !shown(el)) continue;
446
+ const media = /^(IMG|SVG|CANVAS|VIDEO|PICTURE|IFRAME|OBJECT|EMBED)$/i.test(el.tagName) || /url\(/.test(cs.backgroundImage);
447
+ if (!media && !(hasSurface(el, cs) || borderSides(el, cs) >= 3)) continue;
448
+ if (r.width * r.height >= 0.8 * colArea) continue; // the column itself, or a frame around several
449
+ boxes.push(pbox(r));
450
+ }
451
+ const columns = best.cols.map((c) => {
452
+ const spans = boxes
453
+ .filter((q) => Math.min(q.x + q.w, c.x + c.w) - Math.max(q.x, c.x) >= Math.min(16, 0.5 * q.w))
454
+ .map((q) => [Math.max(best.top, q.y), Math.min(best.top + best.rh, q.y + q.h)])
455
+ .filter(([a, z]) => z > a)
456
+ .sort((a, z) => a[0] - z[0]);
457
+ // Content runs closer than 16px apart (cards in a stack, a label over its value) are one run.
458
+ let covered = 0, cur = null, last = best.top;
459
+ for (const [a, z] of spans) {
460
+ if (cur && a <= cur[1] + 16) cur[1] = Math.max(cur[1], z);
461
+ else {
462
+ if (cur) covered += cur[1] - cur[0];
463
+ cur = [a, z];
464
+ }
465
+ last = Math.max(last, z);
466
+ }
467
+ if (cur) covered += cur[1] - cur[0];
468
+ return { x: Math.round(c.x), width: Math.round(c.w), fill: +(covered / best.rh).toFixed(2), tail: +((best.top + best.rh - last) / best.rh).toFixed(2) };
469
+ });
470
+ claimed.add(best.e);
471
+ const fills = columns.map((c) => c.fill);
472
+ return { x: Math.round(p.x), y: Math.round(p.y), width: Math.round(p.w), height: Math.round(p.h), top: Math.round(best.top), columnHeight: Math.round(best.rh), columns, mean: +(fills.reduce((a, z) => a + z, 0) / fills.length).toFixed(2) };
473
+ };
474
+ const claimed = new Set();
475
+ // Innermost panels first, so a row is credited to the frame that holds it.
476
+ const boards = framed
477
+ .sort((a, z) => a.w * a.h - z.w * z.h)
478
+ .map((p) => ({ p, sec: sectionOf(p.y + p.h / 2) }))
479
+ .filter(({ p, sec }) => p.w >= 0.4 * (sec ? sec.box.width : docW))
480
+ .map(({ p, sec }) => ({ sec, board: boardOf(p, claimed) }))
481
+ .filter((x) => x.board);
482
+
483
+ // ---- per section
484
+ const out = sections.map((s, i) => {
485
+ const el = sectionEls[i];
486
+ const b = { x: s.box.x, y: s.box.y, w: s.box.width, h: s.box.height };
487
+ const sp = span(b);
488
+ // The frame: the section's rows that hold ink, across the page's content column (or wider, if it is).
489
+ const own = extents[i];
490
+ const c0 = Math.max(sp.c0, Math.min(colL, own?.[0] ?? colL)), c1 = Math.min(sp.c1, Math.max(colR, own?.[1] ?? colR));
491
+ let r0 = sp.r1, r1 = sp.r0;
492
+ for (let r = sp.r0; r < sp.r1; r++) for (let c = c0; c < c1; c++) if (at(r, c) & 1) (r0 = Math.min(r0, r)), (r1 = Math.max(r1, r + 1));
493
+ const bg = hex(paintedBg(el ?? document.body));
494
+ if (r0 >= r1 || c0 >= c1) return { index: s.index, tag: s.tag, name: s.name, sticky: Boolean(s.sticky), heading: s.heading ?? s.name, rect: s.box, bg, empty: true, ink: 0, detail: 0, imagery: 0, texture: 0 };
495
+ let ink = 0, detail = 0, img = 0, tex = 0, left = 0, right = 0;
496
+ const mid = (c0 + c1) / 2;
497
+ for (let r = r0; r < r1; r++)
498
+ for (let c = c0; c < c1; c++) {
499
+ const v = at(r, c);
500
+ if (v & 1) {
501
+ ink++;
502
+ if (c + 0.5 < mid) left++;
503
+ else right++;
504
+ }
505
+ if (v & 2) detail++;
506
+ if (v & 4) img++;
507
+ if (v & 8) tex++;
508
+ }
509
+ const n = (r1 - r0) * (c1 - c0);
510
+ const e = largestEmpty(r0, r1, c0, c1);
511
+ const ew = e.c1 - e.c0, eh = e.r1 - e.r0;
512
+ const fw = c1 - c0, fh = r1 - r0;
513
+ const ex = (e.c0 + e.c1) / 2 - c0, ey = (e.r0 + e.r1) / 2 - r0;
514
+ const where = ew >= 0.6 * fw ? (ey < fh / 2 ? "top" : "bottom") : ex < 0.4 * fw ? "left" : ex > 0.6 * fw ? "right" : eh >= 0.6 * fh ? "centre" : ey < fh / 2 ? "top" : "bottom";
515
+ // The longest run of rows where one half of the frame is bare while the other holds ink (a left-aligned
516
+ // heading with nothing beside it).
517
+ let run = 0, best = { px: 0, side: null, y: null }, side = null;
518
+ const midC = Math.floor((c0 + c1) / 2);
519
+ for (let r = r0; r <= r1; r++) {
520
+ let L = false, R = false;
521
+ if (r < r1) for (let c = c0; c < c1; c++) if (at(r, c) & 1) c < midC ? (L = true) : (R = true);
522
+ const s1 = r < r1 && L !== R ? (L ? "right" : "left") : null;
523
+ if (s1 && s1 === side) run++;
524
+ else {
525
+ if (side && run * cell > best.px) best = { px: run * cell, side, y: (r - run) * cell };
526
+ run = s1 ? 1 : 0;
527
+ }
528
+ side = s1;
529
+ }
530
+ // Panels (product boards, big cards): how much of each holds content, and its largest bare area.
531
+ const panels = surfaces
532
+ .filter((p) => p.y + p.h / 2 >= b.y && p.y + p.h / 2 < b.y + b.h && p.w * p.h >= 0.12 * fw * fh * cell * cell)
533
+ .filter((p, k, arr) => !arr.some((q) => q !== p && q.el.contains(p.el) && q.w * q.h >= p.w * p.h))
534
+ .sort((p, q) => q.w * q.h - p.w * p.h)
535
+ .slice(0, 3)
536
+ .map((p) => {
537
+ const q = span(p);
538
+ const pr0 = q.r0 + 1, pr1 = q.r1 - 1, pc0 = q.c0 + 1, pc1 = q.c1 - 1;
539
+ if (pr1 - pr0 < 3 || pc1 - pc0 < 3) return null;
540
+ let fill = 0;
541
+ for (let r = pr0; r < pr1; r++) for (let c = pc0; c < pc1; c++) if (at(r, c) & 6) fill++;
542
+ const pn = (pr1 - pr0) * (pc1 - pc0);
543
+ const pe = largestEmpty(pr0, pr1, pc0, pc1, 6);
544
+ return { x: Math.round(p.x), y: Math.round(p.y), width: Math.round(p.w), height: Math.round(p.h), fill: +(fill / pn).toFixed(3), largestEmpty: +(pe.area / pn).toFixed(3) };
545
+ })
546
+ .filter(Boolean);
547
+ // Imagery over the whole section (a full-bleed photo counts even outside the column).
548
+ let simg = 0, stex = 0, scells = 0;
549
+ for (let r = sp.r0; r < sp.r1; r++) for (let c = sp.c0; c < sp.c1; c++) (scells++, (simg += at(r, c) & 4 ? 1 : 0), (stex += at(r, c) & 8 ? 1 : 0));
550
+ return {
551
+ index: s.index,
552
+ tag: s.tag, name: s.name, sticky: Boolean(s.sticky),
553
+ heading: s.heading ?? s.name,
554
+ rect: s.box,
555
+ frame: { x: c0 * cell, y: r0 * cell, width: fw * cell, height: fh * cell },
556
+ bg,
557
+ ink: +(ink / n).toFixed(3),
558
+ detail: +(detail / n).toFixed(3),
559
+ empty: false,
560
+ largestEmpty: { share: +((ew * eh) / n).toFixed(3), where, x: e.c0 * cell, y: e.r0 * cell, width: ew * cell, height: eh * cell },
561
+ sideEmpty: best,
562
+ panels,
563
+ boards: boards.filter((x) => x.sec?.index === s.index).map((x) => x.board),
564
+ balance: { left: +(left / n).toFixed(3), right: +(right / n).toFixed(3), ratio: +(Math.min(left, right) / Math.max(1, left, right)).toFixed(3) },
565
+ imagery: +(simg / Math.max(1, scells)).toFixed(3),
566
+ texture: +(stex / Math.max(1, scells)).toFixed(3),
567
+ layout: layoutOf(el, s),
568
+ stack: stackOf(el),
569
+ };
570
+ });
571
+
572
+ // ---- page background runs: what wide elements paint, row by row (document order is paint order here)
573
+ const painters = [{ y0: 0, y1: pageH, color: hex(paintedBg(document.body)) }];
574
+ for (const el of all) {
575
+ if (SKIP.has(el.tagName) || el.closest("svg")) continue;
576
+ const r = el.getBoundingClientRect();
577
+ if (r.width < 0.9 * docW || r.height < 8) continue;
578
+ const cs = getComputedStyle(el);
579
+ if (cs.visibility === "hidden" || !shown(el) || cs.position === "fixed" || cs.position === "sticky") continue;
580
+ const y0 = r.top + scrollY, y1 = r.bottom + scrollY;
581
+ if (cs.backgroundImage && /url\(/.test(cs.backgroundImage) && r.height > 120) painters.push({ y0, y1, color: "image" });
582
+ const c = rgba(cs.backgroundColor);
583
+ if (c && c[3] > 0.5) painters.push({ y0, y1, color: hex(paintedBg(el)) });
584
+ }
585
+ const cuts = [...new Set(painters.flatMap((p) => [p.y0, p.y1]).map((v) => Math.max(0, Math.min(pageH, Math.round(v)))))].sort((a, b) => a - b);
586
+ const runs = [];
587
+ for (let k = 0; k < cuts.length - 1; k++) {
588
+ const y0 = cuts[k], y1 = cuts[k + 1];
589
+ if (y1 - y0 < 1) continue;
590
+ const m = (y0 + y1) / 2;
591
+ let color = null;
592
+ for (const p of painters) if (m >= p.y0 && m < p.y1) color = p.color;
593
+ const last = runs[runs.length - 1];
594
+ if (last && last.color === color) last.y1 = y1;
595
+ else runs.push({ y0, y1, color });
596
+ }
597
+ const near = (a, b) => a === b || (a !== "image" && b !== "image" && a && b && dist(rgba(a), rgba(b)) < 10);
598
+ const colours = [];
599
+ for (const r of runs) {
600
+ const c = colours.find((x) => near(x.color, r.color));
601
+ if (c) c.px += r.y1 - r.y0;
602
+ else colours.push({ color: r.color, px: r.y1 - r.y0 });
603
+ }
604
+ const bgRuns = colours.map((c) => ({ color: c.color, share: +(c.px / pageH).toFixed(3) })).sort((a, b) => b.share - a.share);
605
+ const edges = runs.slice(1).filter((r, k) => !near(r.color, runs[k].color)).map((r) => r.y0);
606
+
607
+ // ---- empty vertical bands: gaps between ink (long hairlines at the page edge don't count), cut at colour changes
608
+ const ys = inks.filter((r) => !(r.w <= 3 && r.h > 600)).map((r) => [r.y, r.y + r.h]).sort((a, b) => a[0] - b[0]);
609
+ const merged = [];
610
+ for (const [a, b] of ys) {
611
+ const last = merged[merged.length - 1];
612
+ if (last && a <= last[1]) last[1] = Math.max(last[1], b);
613
+ else merged.push([a, b]);
614
+ }
615
+ const bands = [];
616
+ for (let k = 1; k < merged.length; k++) {
617
+ let a = merged[k - 1][1];
618
+ const z = merged[k][0];
619
+ for (const cut of [...edges.filter((y) => y > a && y < z), z]) {
620
+ if (cut - a >= 8) {
621
+ const from = sectionOf(a + 1), to = sectionOf(cut - 1);
622
+ // edge: the band runs up to a change of background colour (the padding of a coloured band).
623
+ const edge = edges.some((y) => Math.abs(y - a) < 2 || Math.abs(y - cut) < 2);
624
+ bands.push({ y: Math.round(a), height: Math.round(cut - a), sections: [...new Set([from?.index, to?.index].filter(Boolean))], between: (from?.index ?? 0) !== (to?.index ?? 0), edge });
625
+ }
626
+ a = cut;
627
+ }
628
+ }
629
+
630
+ // ---- text checks
631
+ const monoFamily = (ff) => {
632
+ const fams = ff.split(",").map((f) => f.replace(/["']/g, "").trim().toLowerCase());
633
+ return /mono|courier|consolas|menlo|monaco|code|terminal|typewriter|plex mono|inconsolata/.test(fams[0] ?? "") || fams[0] === "monospace";
634
+ };
635
+ let mono = 0, totalChars = 0;
636
+ const monoSnips = new Map();
637
+ for (const t of texts) {
638
+ const len = clean(t.text).length;
639
+ totalChars += len;
640
+ if (!monoFamily(t.cs.fontFamily) || t.el.closest("code, pre, kbd, samp, tt, var")) continue;
641
+ mono += len;
642
+ const key = t.el;
643
+ if (!monoSnips.has(key)) monoSnips.set(key, { text: clean(t.el.textContent).slice(0, 60), line: clean((t.el.parentElement ?? t.el).textContent).slice(0, 60), section: sectionOf(t.rects[0].y)?.index ?? null, family: t.cs.fontFamily.split(",")[0].replace(/["']/g, "").trim() });
644
+ }
645
+ const numeric = (s) => (s.match(/[\d°%$€£:]/g) ?? []).length >= Math.max(1, 0.3 * s.replace(/\s/g, "").length);
646
+ // Code, not numbers: a git hash ("a3f9c21"), a shell prompt ("$ tracewell init", a lone "$" or ">" before a
647
+ // command), an HTTP route ("POST /checkout"), a path ("src/app.ts"), identifiers and calls.
648
+ const HASH = /^(?=[0-9a-f]*[a-f])(?=[0-9a-f]*\d)[0-9a-f]{7,40}$/i;
649
+ const PROMPT = /^[$>❯›#%](\s|$)/;
650
+ const HTTP = /\b(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\b/;
651
+ const codeLike = (s, line = "") => HASH.test(s) || PROMPT.test(s) || PROMPT.test(line) || HTTP.test(s) || (/[_~/{}()<>=;@]|\w\.\w+\(|[a-z][A-Z]|^[a-z]+(-[a-z]+)+$/.test(s) && !/^[\d\s.,:°%$€£+\-–]+$/.test(s));
652
+ const monoList = [...monoSnips.values()].map(({ line, ...m }) => ({ ...m, numeric: numeric(m.text) && !codeLike(m.text, line), code: codeLike(m.text, line) }));
653
+
654
+ const PLACE = /lorem ipsum|\blorem\b|ipsum dolor|\bplaceholder\b|example\.(com|org|net)|\bjohn doe\b|\bjane doe\b|\bacme\b|\{\{|\[(your|insert)\b|\binsert (text|image|copy)\b|\bcompany name\b|\byour logo\b/i;
655
+ const PROPER = /\bYour (Studio|studio|Company|company|Name|name|Brand|brand|Business|business|Team|team|Logo|logo|Shop|shop|Store|store|Product|product|App|app)\b(?!['’]s)/g;
656
+ const blockOf = (el) => {
657
+ for (let e = el; e && e !== document.body; e = e.parentElement) if (!/^(inline|contents)/.test(getComputedStyle(e).display)) return e;
658
+ return document.body;
659
+ };
660
+ const placeholders = [];
661
+ const seenPh = new Set();
662
+ for (const t of texts) {
663
+ const blk = blockOf(t.el);
664
+ if (seenPh.has(blk)) continue;
665
+ const full = clean(blk.textContent);
666
+ let hit = full.match(PLACE)?.[0] ?? null;
667
+ // The bare word in a sentence about placeholders ("flags hype, filler, placeholder text and …") isn't one: real
668
+ // placeholder copy is short ("Placeholder", "Placeholder text here").
669
+ if (hit && /^placeholder$/i.test(hit) && full.split(/\s+/).length > 8) hit = null;
670
+ if (!hit) {
671
+ // "Your studio" as a name, mid-sentence ("at Your studio", "Kiln · Your studio"), not "Your studio, sorted."
672
+ for (const m of full.matchAll(PROPER)) {
673
+ const before = full.slice(0, m.index).trim();
674
+ if (before && !/[.!?:;"“(\]]$/.test(before)) {
675
+ hit = m[0];
676
+ break;
677
+ }
678
+ }
679
+ }
680
+ seenPh.add(blk);
681
+ if (hit) placeholders.push({ match: hit, text: full.slice(0, 100), section: sectionOf(t.rects[0].y)?.index ?? null });
682
+ }
683
+
684
+ const clipped = [];
685
+ const seenClip = new Set();
686
+ for (const el of all) {
687
+ if (SKIP.has(el.tagName) || el.closest("svg") || el === document.body) continue;
688
+ const cs = getComputedStyle(el);
689
+ if (!/hidden|clip/.test(cs.overflowX + cs.overflowY)) continue;
690
+ const r = el.getBoundingClientRect();
691
+ if (r.width < 8 || r.height < 8 || !shown(el) || cs.visibility !== "visible") continue;
692
+ // Visually hidden on purpose (skip links, screen-reader text).
693
+ if ((cs.clip && cs.clip !== "auto") || /inset\(50%|inset\(100%/.test(cs.clipPath) || el.closest(".sr-only, .visually-hidden")) continue;
694
+ const text = clean(el.textContent);
695
+ if (!text || text.length > 160) continue;
696
+ if (el.scrollWidth <= el.clientWidth + 1 && el.scrollHeight <= el.clientHeight + 1) continue;
697
+ const intended = cs.textOverflow === "ellipsis" || (cs.webkitLineClamp && cs.webkitLineClamp !== "none");
698
+ // Text partly outside the box (wholly outside is a hidden slide, not clipping).
699
+ const box = { l: r.left + el.clientLeft, t: r.top + el.clientTop, r: r.left + el.clientLeft + el.clientWidth, b: r.top + el.clientTop + el.clientHeight };
700
+ range.selectNodeContents(el);
701
+ const lr = [...range.getClientRects()].filter((q) => q.width > 1 && q.height > 1);
702
+ const partly = lr.some((q) => {
703
+ const inside = Math.min(q.right, box.r) - Math.max(q.left, box.l) > 0 && Math.min(q.bottom, box.b) - Math.max(q.top, box.t) > 0;
704
+ const beyond = q.left < box.l - 1.5 || q.right > box.r + 1.5 || q.top < box.t - 1.5 || q.bottom > box.b + 1.5;
705
+ return inside && beyond;
706
+ });
707
+ if (!partly) continue;
708
+ if ([...seenClip].some((o) => o.contains(el) || el.contains(o))) continue;
709
+ seenClip.add(el);
710
+ const small = r.width <= 72 && r.height <= 72;
711
+ const rounded = parseFloat(cs.borderTopLeftRadius) > 0;
712
+ clipped.push({ text: text.slice(0, 40), width: Math.round(r.width), height: Math.round(r.height), small, rounded, intended: Boolean(intended), section: sectionOf(r.top + scrollY + r.height / 2)?.index ?? null });
713
+ }
714
+
715
+ // Small round things (avatars, badges) whose text a later sibling paints over: stacked avatars with
716
+ // their initials cut off.
717
+ for (const el of all) {
718
+ if (SKIP.has(el.tagName) || el.closest("svg")) continue;
719
+ const r = el.getBoundingClientRect();
720
+ if (r.width < 8 || r.width > 72 || r.height > 72) continue;
721
+ const cs = getComputedStyle(el);
722
+ if (parseFloat(cs.borderTopLeftRadius) < Math.min(r.width, r.height) / 4) continue;
723
+ const text = clean(el.textContent);
724
+ if (!text || text.length > 4 || !shown(el)) continue;
725
+ range.selectNodeContents(el);
726
+ const tr = [...range.getClientRects()].filter((q) => q.width > 1);
727
+ if (!tr.length) continue;
728
+ const t = { l: Math.min(...tr.map((q) => q.left)), r: Math.max(...tr.map((q) => q.right)), t: Math.min(...tr.map((q) => q.top)), b: Math.max(...tr.map((q) => q.bottom)) };
729
+ let hidden = 0;
730
+ for (let sib = el.nextElementSibling; sib; sib = sib.nextElementSibling) {
731
+ const scs = getComputedStyle(sib);
732
+ // A ring (box-shadow spread) paints beyond the box.
733
+ const spread = scs.boxShadow && scs.boxShadow !== "none" ? Math.max(0, ...scs.boxShadow.split(/,(?![^(]*\))/).map((sh) => parseFloat((sh.replace(/rgba?\([^)]*\)|inset/g, "").trim().split(/\s+/)[3] ?? "0")) || 0)) : 0;
734
+ const sr = sib.getBoundingClientRect();
735
+ const q = { left: sr.left - spread, right: sr.right + spread, top: sr.top - spread, bottom: sr.bottom + spread };
736
+ const opaque = (rgba(scs.backgroundColor)?.[3] ?? 0) > 0.6 || /^(IMG|SVG|CANVAS|VIDEO)$/i.test(sib.tagName) || scs.backgroundImage !== "none";
737
+ if (!opaque || !shown(sib)) continue;
738
+ if (Math.min(q.bottom, t.b) - Math.max(q.top, t.t) <= 1) continue;
739
+ hidden = Math.max(hidden, Math.min(q.right, t.r) - Math.max(q.left, t.l));
740
+ }
741
+ if (hidden > 1.5 && ![...seenClip].some((o) => o.contains(el) || el.contains(o))) {
742
+ seenClip.add(el);
743
+ clipped.push({ text, width: Math.round(r.width), height: Math.round(r.height), small: true, rounded: true, intended: false, covered: Math.round(hidden), section: sectionOf(r.top + scrollY + r.height / 2)?.index ?? null });
744
+ }
745
+ }
746
+
747
+ // ---- the system, measured across sections (SYSTEM_DRIFT): each section's H2 (size, left edge of its ink,
748
+ // centred or not), its vertical padding (from its edges to its first and last content), the filled buttons'
749
+ // heights and the style of repeated cards (radius, inset, border, shadow, fill). Page chrome is left out: a
750
+ // nav button is smaller on purpose.
751
+ const px = (v) => parseFloat(v) || 0;
752
+ const chromeSection = (s) => ["header", "nav", "footer"].includes(s.tag) || /^(header|footer|navigation)$/i.test(s.name ?? "");
753
+ const chromeEl = (el) => {
754
+ if (el.closest("nav, [role=navigation], [role=banner], [role=contentinfo]")) return true;
755
+ const c = el.closest("header, footer");
756
+ return Boolean(c && !c.parentElement?.closest("main, section, article"));
757
+ };
758
+ const inkOf = (el) => {
759
+ range.selectNodeContents(el);
760
+ const lr = [...range.getClientRects()].filter((q) => q.width > 1 && q.height > 1);
761
+ return lr.length ? { l: Math.min(...lr.map((q) => q.left)) + scrollX, r: Math.max(...lr.map((q) => q.right)) + scrollX } : null;
762
+ };
763
+ const colMid = ((colL + colR) / 2) * cell;
764
+ const headings = [];
765
+ const pads = [];
766
+ sections.forEach((s, i) => {
767
+ const el = sectionEls[i];
768
+ if (!el || chromeSection(s) || s.sticky || s.box.height < 160) return;
769
+ // The section's heading: its largest visible H2 (an H2 under 20px is a card's label, not a section heading).
770
+ const h2 = [...el.querySelectorAll("h2")]
771
+ .filter((h) => shown(h) && getComputedStyle(h).visibility === "visible" && clean(h.textContent) && h.getBoundingClientRect().width > 0 && px(getComputedStyle(h).fontSize) >= 20)
772
+ .sort((a, z) => px(getComputedStyle(z).fontSize) - px(getComputedStyle(a).fontSize))[0];
773
+ const ink = h2 && inkOf(h2);
774
+ if (ink) {
775
+ const hcs = getComputedStyle(h2);
776
+ const mid = (ink.l + ink.r) / 2, secMid = s.box.x + s.box.width / 2;
777
+ const centred = (hcs.textAlign === "center" || Math.abs(mid - secMid) < 0.03 * s.box.width) && ink.l > s.box.x + 0.08 * s.box.width;
778
+ // right: a heading that starts in the right half (the text side of an image-left split) has its own edge.
779
+ headings.push({ section: s.index, text: clean(h2.textContent).slice(0, 48), size: +px(hcs.fontSize).toFixed(1), left: Math.round(ink.l), centred, right: !centred && ink.l > colMid - 8 });
780
+ }
781
+ // Padding: the section's edge to its first and last content (text, media, cards). A full-width inner band
782
+ // is the section's own ground, not content; an absolutely placed box is decoration, not where content starts
783
+ // (a picture is content wherever it sits: a full-bleed photo makes the padding 0, a bleed, not a rhythm).
784
+ const inSec = (q) => q.y + q.h / 2 >= s.box.y && q.y + q.h / 2 < s.box.y + s.box.height;
785
+ let top = Infinity, bot = -Infinity;
786
+ for (const t of texts) if (el.contains(t.el)) for (const q of t.rects) if (q.h >= 4 && inSec(q)) (top = Math.min(top, q.y)), (bot = Math.max(bot, q.y + q.h));
787
+ for (const q of solid) {
788
+ if (!el.contains(q.el) || !inSec(q)) continue;
789
+ if (!q.media && (q.el === el || q.w >= 0.98 * s.box.width || /absolute|fixed/.test(getComputedStyle(q.el).position))) continue;
790
+ (top = Math.min(top, Math.max(s.box.y, q.y))), (bot = Math.max(bot, Math.min(s.box.y + s.box.height, q.y + q.h)));
791
+ }
792
+ if (top < bot) pads.push({ section: s.index, top: Math.round(top - s.box.y), bottom: Math.round(s.box.y + s.box.height - bot), height: Math.round(s.box.height) });
793
+ });
794
+ // A product mock drawn in HTML (aria-hidden, role=img, inert) has its own UI, not the page's.
795
+ const MOCK = "[aria-hidden=true], [role=img], [inert]";
796
+ const buttons = [];
797
+ const btnEls = [];
798
+ for (const el of document.body.querySelectorAll("a, button, [role=button], input[type=submit], input[type=button]")) {
799
+ if (btnEls.some((o) => o.contains(el)) || chromeEl(el) || el.closest(MOCK) || !shown(el)) continue;
800
+ const r = el.getBoundingClientRect();
801
+ if (r.height < 24 || r.height > 96 || r.width < 48 || r.width > 480) continue;
802
+ const cs = getComputedStyle(el);
803
+ const text = clean(el.textContent || el.value);
804
+ if (cs.visibility !== "visible" || text.length < 2 || text.length > 40 || px(cs.fontSize) < 13) continue;
805
+ const own = rgba(cs.backgroundColor);
806
+ const under = paintedBg(el.parentElement);
807
+ const grad = cs.backgroundImage !== "none" && !/url\(/.test(cs.backgroundImage) && gradientVaries(cs.backgroundImage);
808
+ if (!grad && !(own && own[3] >= 0.5 && dist(over(own, under), under) > 24)) continue;
809
+ btnEls.push(el);
810
+ const b = pbox(r);
811
+ buttons.push({ section: sectionOf(b.y + b.h / 2)?.index ?? null, height: Math.round(r.height * 2) / 2, width: Math.round(r.width), text: text.slice(0, 24) });
812
+ }
813
+ // Cards: boxes with a ground, a shadow or a border all round, in a set (two or more under one parent), not
814
+ // inside another such box (the chips of a product mock are its own business).
815
+ const cardCands = solid.filter((q) => !q.media && q.cs && q.w >= 160 && q.h >= 120 && q.w <= 0.9 * docW && !/^(BUTTON|INPUT|SELECT|TEXTAREA|IFRAME)$/.test(q.el.tagName) && !chromeEl(q.el) && !q.el.closest(MOCK));
816
+ const topCards = cardCands.filter((q) => !cardCands.some((o) => o !== q && o.el !== q.el && o.el.contains(q.el)));
817
+ const byParent = new Map();
818
+ for (const q of topCards) byParent.set(q.el.parentElement, [...(byParent.get(q.el.parentElement) ?? []), q]);
819
+ const cards = [];
820
+ let setN = 0;
821
+ for (const set of byParent.values()) {
822
+ if (set.length < 2) continue;
823
+ setN++;
824
+ for (const q of set) {
825
+ const cs = q.cs;
826
+ // The horizontal inset: its own padding (a tall padding-top is often room kept for a picture). No padding
827
+ // (an image-top card padding an inner block, centred copy on a picture) says nothing: null matches any.
828
+ const inset = px(cs.paddingLeft) || null;
829
+ const shadow = Boolean(cs.boxShadow && cs.boxShadow !== "none" && !/^rgba\(0, 0, 0, 0\)/.test(cs.boxShadow));
830
+ const own = rgba(cs.backgroundColor), under = paintedBg(q.el.parentElement);
831
+ const fill = Boolean(own && own[3] > 0.08 && dist(over(own, under), under) > 9);
832
+ cards.push({ section: sectionOf(q.y + q.h / 2)?.index ?? null, set: setN, radius: Math.round(px(cs.borderTopLeftRadius)), inset: inset && Math.round(inset), border: q.sides >= 3, shadow, fill, width: Math.round(q.w), height: Math.round(q.h), text: clean(q.el.textContent).slice(0, 32) });
833
+ if (cards.length >= 240) break;
834
+ }
835
+ }
836
+
837
+ // A framed box full of small UI text: a product window drawn in code, marked as a mock or not. uiFrameOf(el, n)
838
+ // is the nearest ancestor of el that is one (a framed box 240×120 or larger holding n or more texts of 14.5px or
839
+ // less); its cards are the product's UI, not the page's tiles, and its small text may be cropped like any fragment.
840
+ const uiSmall = new Map();
841
+ const smallIn = (e) => {
842
+ if (uiSmall.has(e)) return uiSmall.get(e);
843
+ const r = e.getBoundingClientRect();
844
+ let n = 0;
845
+ if (r.width >= 240 && r.height >= 120 && r.width < 0.98 * docW) {
846
+ const cs = getComputedStyle(e);
847
+ if (hasSurface(e, cs) || borderSides(e, cs) >= 3) for (const t of texts) if (e.contains(t.el) && px(t.cs.fontSize) <= 14.5 && ++n >= 64) break;
848
+ }
849
+ uiSmall.set(e, n);
850
+ return n;
851
+ };
852
+ const uiFrameOf = (el, min = 8) => {
853
+ for (let e = el.parentElement; e && e !== document.body; e = e.parentElement) if (smallIn(e) >= min) return e;
854
+ return null;
855
+ };
856
+
857
+ // ---- bento grids (BENTO_FLAT, BENTO_TWINS, INVERT_OVERUSE): tiles (boxes with a ground, a shadow or a border
858
+ // all round, and pictures) that sit edge to edge with gaps of 48px or less, in two rows and two columns or more.
859
+ // Tiles are 160×120 or larger (a key, a chip or a badge is not a tile) and not inside a product window (its
860
+ // cards are its UI: palate-site's demo); a box holding three or more tiles is a panel, not a tile. Each tile: its
861
+ // size, its surface (the ground it paints, border, shadow; null ground for a picture), its content type (media,
862
+ // ui, stat, quote, text), whether it carries a title and whether its ground is the inverse of the ground under
863
+ // the grid (contrast 7:1 or more).
864
+ const lum = (c) => {
865
+ const f = (v) => ((v /= 255) <= 0.04045 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4);
866
+ return 0.2126 * f(c[0]) + 0.7152 * f(c[1]) + 0.0722 * f(c[2]);
867
+ };
868
+ const contrast = (a, b) => {
869
+ const [x, y] = [lum(a), lum(b)].sort((p, q) => q - p);
870
+ return (x + 0.05) / (y + 0.05);
871
+ };
872
+ const groundOf = (el, cs) => {
873
+ const under = paintedBg(el.parentElement);
874
+ const bi = cs.backgroundImage;
875
+ if (bi && bi !== "none" && /url\(/.test(bi)) return null;
876
+ const own = rgba(cs.backgroundColor);
877
+ const base = own ? over(own, under) : under;
878
+ const stops = bi && bi !== "none" ? colourStops(bi).filter((c) => c[3] > 0.02) : [];
879
+ return stops.length ? [0, 1, 2].map((i) => stops.reduce((a, c) => a + over(c, base)[i], 0) / stops.length) : base;
880
+ };
881
+ const boxIn = (r, q, k) => (Math.max(0, Math.min(r.x + r.w, q.x + q.w) - Math.max(r.x, q.x)) * Math.max(0, Math.min(r.y + r.h, q.y + q.h) - Math.max(r.y, q.y))) / Math.max(1, r.w * r.h) >= k;
882
+ const PRICE = /[$€£¥]\s?\d|\d\s?(€|kr|zł)\b/;
883
+ const tileCand = solid.filter((q) => q.w >= 160 && q.h >= 120 && q.w <= 0.9 * docW && q.h <= 1.6 * vh && q.x >= -2 && q.x + q.w <= docW + 2 && !/^(BUTTON|INPUT|SELECT|TEXTAREA|IFRAME|LABEL)$/.test(q.el.tagName) && !chromeEl(q.el) && !q.el.closest(MOCK) && !q.el.closest("form") && !uiFrameOf(q.el, 24));
884
+ const panel = (q) => {
885
+ const inner = tileCand.filter((o) => o !== q && o.el !== q.el && q.el.contains(o.el) && o.w * o.h < 0.8 * q.w * q.h);
886
+ return inner.length >= 3 && inner.reduce((a, o) => a + o.w * o.h, 0) >= 0.45 * q.w * q.h;
887
+ };
888
+ const leaves = tileCand.filter((q) => !panel(q));
889
+ const tileSet = leaves.filter((q) => !leaves.some((o) => o !== q && o.el !== q.el && o.el.contains(q.el)));
890
+ const bentoTiles = tileSet.map((q) => {
891
+ const cs = q.cs ?? getComputedStyle(q.el);
892
+ const A = q.w * q.h;
893
+ let mediaA = q.media ? A : 0, small = 0, title = 0, words = 0, bigNum = false, quote = false, chars = 0;
894
+ const smallEls = new Set();
895
+ for (const t of texts) {
896
+ const r = t.rects[0];
897
+ if (!q.el.contains(t.el) && !boxIn(r, q, 0.6)) continue;
898
+ const f = px(t.cs.fontSize), s = clean(t.text);
899
+ chars += s.length;
900
+ if (f <= 14.5) smallEls.add(t.el);
901
+ const h = t.el.closest("h1, h2, h3, h4, h5, h6, [role=heading]");
902
+ if ((h && f >= 13) || (f >= 15 && px(t.cs.fontWeight) >= 600) || f >= 17) title = Math.max(title, f);
903
+ if (f >= 30 && numeric(s) && /\d/.test(s)) bigNum = true;
904
+ if (t.el.closest("blockquote, q") || /^[“"«„]/.test(s)) quote = true;
905
+ // A description: a paragraph inside the tile (the tile itself being an <li> says nothing).
906
+ const pe = t.el.closest("p, li, dd, blockquote");
907
+ if (pe && pe !== q.el && q.el.contains(pe)) words = Math.max(words, clean(pe.textContent).split(/\s+/).filter(Boolean).length);
908
+ }
909
+ small = smallEls.size;
910
+ let boxes = 0;
911
+ for (const o of solid) {
912
+ if (o.el === q.el || !q.el.contains(o.el) || o.w * o.h < 0.01 * A) continue;
913
+ if (o.media) mediaA += Math.min(o.w * o.h, A) * (boxIn(o, q, 0.5) ? 1 : 0);
914
+ else boxes++;
915
+ }
916
+ const share = Math.min(1, mediaA / A);
917
+ const ground = q.media ? null : groundOf(q.el, cs);
918
+ const type = share >= 0.45 ? "media" : bigNum ? "stat" : boxes >= 2 || small >= 6 ? "ui" : quote ? "quote" : "text";
919
+ const under = paintedBg(q.el.parentElement);
920
+ const shadow = Boolean(cs.boxShadow && cs.boxShadow !== "none" && !/^rgba\(0, 0, 0, 0\)/.test(cs.boxShadow));
921
+ return {
922
+ el: q.el, section: sectionOf(q.y + q.h / 2)?.index ?? null, x: Math.round(q.x), y: Math.round(q.y), w: Math.round(q.w), h: Math.round(q.h),
923
+ type, titled: title > 0 && !q.media, words, chars, price: PRICE.test(clean(q.el.textContent).slice(0, 400)),
924
+ ground: ground && hex(ground), border: (q.sides ?? 0) >= 3, shadow, radius: Math.round(px(cs.borderTopLeftRadius)),
925
+ inverse: Boolean(ground && contrast(ground, under) >= 7), text: clean(q.el.textContent).slice(0, 32),
926
+ };
927
+ });
928
+ const sameSurface = (a, b) => (a.ground == null) === (b.ground == null) && (a.ground == null || dist(rgba(a.ground), rgba(b.ground)) < 12) && a.border === b.border && a.shadow === b.shadow;
929
+ const touching = (a, b) => {
930
+ const ov = (a0, a1, b0, b1) => Math.min(a1, b1) - Math.max(a0, b0);
931
+ const vy = ov(a.y, a.y + a.h, b.y, b.y + b.h), vx = ov(a.x, a.x + a.w, b.x, b.x + b.w);
932
+ const gx = Math.max(a.x, b.x) - Math.min(a.x + a.w, b.x + b.w), gy = Math.max(a.y, b.y) - Math.min(a.y + a.h, b.y + b.h);
933
+ return (vy >= 0.5 * Math.min(a.h, b.h) && gx >= -2 && gx <= 48) || (vx >= 0.5 * Math.min(a.w, b.w) && gy >= -2 && gy <= 48);
934
+ };
935
+ const grids = [];
936
+ const placed = new Set();
937
+ for (const t of bentoTiles) {
938
+ if (placed.has(t)) continue;
939
+ const comp = [t];
940
+ placed.add(t);
941
+ for (let k = 0; k < comp.length; k++)
942
+ for (const o of bentoTiles) if (!placed.has(o) && o.section === comp[k].section && touching(comp[k], o)) (placed.add(o), comp.push(o));
943
+ if (comp.length < 3) continue;
944
+ const lines = (v) => clusterLines(comp.map(v), 24);
945
+ const rowsN = lines((x) => x.y), colsN = lines((x) => x.x);
946
+ if (rowsN < 2 || colsN < 2) continue;
947
+ comp.sort((a, b) => a.y - b.y || a.x - b.x);
948
+ const pairs = [];
949
+ for (let i = 0; i < comp.length; i++) for (let j = i + 1; j < comp.length; j++) if (touching(comp[i], comp[j])) pairs.push([i, j]);
950
+ const box = { x: Math.min(...comp.map((c) => c.x)), y: Math.min(...comp.map((c) => c.y)) };
951
+ box.w = Math.max(...comp.map((c) => c.x + c.w)) - box.x;
952
+ box.h = Math.max(...comp.map((c) => c.y + c.h)) - box.y;
953
+ grids.push({ section: comp[0].section, ...box, rows: rowsN, cols: colsN, pairs, tiles: comp.map(({ el, ...c }) => c) });
954
+ if (grids.length >= 12) break;
955
+ }
956
+ function clusterLines(vals, tol) {
957
+ const s = [...vals].sort((a, b) => a - b);
958
+ let n = 0, last = -Infinity;
959
+ for (const v of s) if (v - last > tol) (n++, (last = v));
960
+ return n;
961
+ }
962
+
963
+ // ---- the product frame in the hero (FRAME_SMALL): the largest product window, screenshot or framed mock in the
964
+ // section that holds the first headline. How much of the section's width it shows, and which edges it bleeds off
965
+ // (touches the section's edge, or is cut by a clipping box or the screen). Marked mocks ([data-product],
966
+ // role=img, aria-hidden or inert with a frame), framed boxes full of small UI text, and screenshots (an image
967
+ // whose name or alt says UI, or a PNG/WebP in UI proportions in a rounded frame) count; photos and forms don't.
968
+ const frames = [];
969
+ const h1 = [...document.querySelectorAll("h1")].find((h) => shown(h) && h.getBoundingClientRect().height > 0 && h.getBoundingClientRect().top + scrollY < 1.5 * vh);
970
+ const heroSec = (h1 && sectionOf(pbox(h1.getBoundingClientRect()).y + 1)) || sections.find((s) => !chromeSection(s) && !s.sticky && s.box.height >= 300 && s.box.y < vh);
971
+ const heroEl = heroSec ? sectionEls[sections.indexOf(heroSec)] : null;
972
+ if (heroSec && heroEl && heroSec.box.height >= 300) {
973
+ const UIWORDS = /screen ?shot|dashboard|\bapp\b|\bui\b|interface|console|inbox|editor|product/i;
974
+ const framedLook = (e) => {
975
+ const cs = getComputedStyle(e);
976
+ return hasSurface(e, cs) || borderSides(e, cs) >= 3;
977
+ };
978
+ const smallUi = (e) => {
979
+ let n = 0;
980
+ for (const t of texts) if (e.contains(t.el) && px(t.cs.fontSize) <= 14.5) if (++n >= 8) break;
981
+ return n;
982
+ };
983
+ const formy = (e) => e.closest("form") || e.querySelectorAll("input:not([type=hidden]), textarea, select").length >= 2;
984
+ const cands = new Set();
985
+ for (const e of heroEl.querySelectorAll("[data-product], [role=img], [aria-hidden=true], [inert], figure, img, div, article, aside, picture, video")) {
986
+ const r = e.getBoundingClientRect();
987
+ if (r.width < 240 || r.height < 160 || !shown(e) || chromeEl(e)) continue;
988
+ if (r.width >= 0.98 * heroSec.box.width && r.height >= 0.8 * heroSec.box.height) continue;
989
+ const tag = e.tagName;
990
+ let ok = false;
991
+ if (tag === "IMG" || tag === "VIDEO") {
992
+ const file = (e.currentSrc || e.src || "").toLowerCase();
993
+ const alt = clean(e.getAttribute("alt") ?? e.getAttribute("aria-label"));
994
+ const ratio = r.width / r.height;
995
+ let frame = false;
996
+ for (let p = e, k = 0; p && k < 4 && !frame; p = p.parentElement, k++) frame = px(getComputedStyle(p).borderTopLeftRadius) > 4 && framedLook(p);
997
+ ok = !/\.(jpe?g)(\?|#|$)/.test(file) && (UIWORDS.test(alt) || UIWORDS.test(file.replace(/^.*\/\/[^/]+/, "")) || (tag === "IMG" && /\.(png|webp|avif)(\?|#|$)/.test(file) && ratio >= 1.2 && ratio <= 2.4 && frame) || (tag === "VIDEO" && frame));
998
+ } else if (e.matches("[data-product]")) ok = true;
999
+ else if (e.matches("[role=img], [aria-hidden=true], [inert], figure")) ok = (framedLook(e) || (e.firstElementChild && framedLook(e.firstElementChild))) && smallUi(e) >= 6 && !formy(e);
1000
+ else ok = framedLook(e) && smallUi(e) >= 8 && !formy(e);
1001
+ if (ok) cands.add(e);
1002
+ }
1003
+ // The outermost of nested candidates is the window.
1004
+ const tops = [...cands].filter((e) => ![...cands].some((o) => o !== e && o.contains(e)));
1005
+ for (const e of tops) {
1006
+ const r = pbox(e.getBoundingClientRect());
1007
+ let v = { x0: r.x, y0: r.y, x1: r.x + r.w, y1: r.y + r.h };
1008
+ for (let a = e.parentElement; a && a !== document.documentElement; a = a.parentElement) {
1009
+ const acs = getComputedStyle(a);
1010
+ if (acs.overflowX === "visible" && acs.overflowY === "visible") continue;
1011
+ const ar = pbox(a.getBoundingClientRect());
1012
+ if (acs.overflowX !== "visible") (v.x0 = Math.max(v.x0, ar.x)), (v.x1 = Math.min(v.x1, ar.x + ar.w));
1013
+ if (acs.overflowY !== "visible") (v.y0 = Math.max(v.y0, ar.y)), (v.y1 = Math.min(v.y1, ar.y + ar.h));
1014
+ }
1015
+ v.x0 = Math.max(0, v.x0);
1016
+ v.x1 = Math.min(docW, v.x1);
1017
+ if (v.x1 - v.x0 < 120 || v.y1 - v.y0 < 80) continue;
1018
+ const S = heroSec.box, E = 3;
1019
+ const bleeds = [];
1020
+ if (v.x0 <= S.x + E || r.x < v.x0 - E) bleeds.push("left");
1021
+ if (v.x1 >= S.x + S.width - E || r.x + r.w > v.x1 + E) bleeds.push("right");
1022
+ if (v.y1 >= S.y + S.height - E || r.y + r.h > v.y1 + E) bleeds.push("bottom");
1023
+ if (r.y < v.y0 - E) bleeds.push("top");
1024
+ const cs = getComputedStyle(e);
1025
+ frames.push({ section: heroSec.index, hero: true, x: Math.round(v.x0), y: Math.round(v.y0), width: Math.round(v.x1 - v.x0), height: Math.round(v.y1 - v.y0), stage: Math.round(S.width), share: +((v.x1 - v.x0) / S.width).toFixed(2), bleeds, portrait: r.h > 1.15 * r.w, kind: e.tagName === "IMG" || e.tagName === "VIDEO" ? "screenshot" : e.matches("[data-product], [role=img], [aria-hidden=true], [inert], figure") ? "marked mock" : "framed UI", radius: Math.round(px(cs.borderTopLeftRadius)), text: clean(e.getAttribute("aria-label") || e.getAttribute("alt") || e.textContent).slice(0, 40) });
1026
+ }
1027
+ frames.sort((a, b) => b.width * b.height - a.width * a.height);
1028
+ }
1029
+
1030
+ // ---- real words cut by an edge (WORDS_CUT): a heading (18px+), a paragraph (four words or more, 14px+) or a
1031
+ // number (24px+) whose line is partly outside a clipping box (overflow hidden or clip), or part of whose lines
1032
+ // are hidden by it. Not judged: product mocks (decorative fragments may be cropped), scrollers and strips
1033
+ // (scrolling brings it in), intended truncation (ellipsis, line clamp), small boxes (CLIPPED), visually
1034
+ // hidden text, and text wholly outside its clip (a hidden slide).
1035
+ const wordsCut = [];
1036
+ const cutSeen = new Set();
1037
+ const rootClipX = [document.documentElement, document.body].some((e) => /hidden|clip/.test(getComputedStyle(e).overflowX));
1038
+ const HIDDEN_CLASS = /(^|[-_\s])(visually-?hidden|sr-only|screen-?reader(-text)?|visuallyHidden|srOnly)([-_\s]|$)/i;
1039
+ const clipBox = (c) => {
1040
+ if (c.root) return { l: 0, r: docW, t: -1e9, b: 1e9 };
1041
+ const l = c.ar.left + scrollX + c.a.clientLeft, tp = c.ar.top + scrollY + c.a.clientTop;
1042
+ return { l: c.x ? l : -1e9, r: c.x ? l + c.a.clientWidth : 1e9, t: c.y ? tp : -1e9, b: c.y ? tp + c.a.clientHeight : 1e9 };
1043
+ };
1044
+ const carousel = (c, el) => {
1045
+ const box = clipBox(c);
1046
+ const top = c.root ? document.body : c.a;
1047
+ for (let e = el; e && e !== top; e = e.parentElement) {
1048
+ const p = e.parentElement;
1049
+ if (!p) break;
1050
+ for (const k of p.children) {
1051
+ if (k === e || SKIP.has(k.tagName)) continue;
1052
+ const r = pbox(k.getBoundingClientRect());
1053
+ if (r.w < 8 || r.h < 8) continue;
1054
+ const ix = Math.max(0, Math.min(r.x + r.w, box.r) - Math.max(r.x, box.l)), iy = Math.max(0, Math.min(r.y + r.h, box.b) - Math.max(r.y, box.t));
1055
+ if ((ix * iy) / (r.w * r.h) <= 0.4) return true;
1056
+ }
1057
+ }
1058
+ return false;
1059
+ };
1060
+ for (const t of texts) {
1061
+ const el = t.el;
1062
+ if (cutSeen.has(el) || wordsCut.length >= 24) continue;
1063
+ if (el.closest(`${MOCK}, [data-product], marquee, .sr-only, .visually-hidden`)) continue;
1064
+ const f = px(t.cs.fontSize);
1065
+ const s = clean(t.text);
1066
+ const blk = el.closest("h1, h2, h3, h4, h5, h6, p, li, blockquote, dd, figcaption");
1067
+ const heading = blk && /^H[1-6]$/.test(blk.tagName) && f >= 18;
1068
+ const para = blk && !heading && f >= 14 && clean(blk.textContent).split(/\s+/).length >= 4;
1069
+ const number = f >= 24 && /\d/.test(s) && numeric(s);
1070
+ if (!heading && !para && !number) continue;
1071
+ // Visually hidden text (a 1px clip box, clip: rect, or a class that says so, hashed or not).
1072
+ let hidden = false;
1073
+ for (let a = el; a && a !== document.body && !hidden; a = a.parentElement) {
1074
+ const acs = a === el ? t.cs : getComputedStyle(a);
1075
+ const ar = a.getBoundingClientRect();
1076
+ hidden = (typeof a.className === "string" && HIDDEN_CLASS.test(a.className)) || (acs.clip && acs.clip !== "auto") || /inset\(50%|inset\(100%/.test(acs.clipPath) || (acs.overflowX !== "visible" && (ar.width <= 2 || ar.height <= 2));
1077
+ }
1078
+ if (hidden) continue;
1079
+ if (f <= 16 && uiFrameOf(el)) continue;
1080
+ if (t.cs.textOverflow === "ellipsis" || (t.cs.webkitLineClamp && t.cs.webkitLineClamp !== "none")) continue;
1081
+ let hit = null;
1082
+ const clipsOf = [];
1083
+ for (let a = el; a && a !== document.documentElement; a = a.parentElement) {
1084
+ const acs = a === el ? t.cs : getComputedStyle(a);
1085
+ const ox = acs.overflowX, oy = acs.overflowY;
1086
+ if (ox === "visible" && oy === "visible") continue;
1087
+ if (/ellipsis/.test(acs.textOverflow) || (acs.webkitLineClamp && acs.webkitLineClamp !== "none")) {
1088
+ hit = "intended";
1089
+ break;
1090
+ }
1091
+ const ar = a.getBoundingClientRect();
1092
+ if (ar.width <= 72 && ar.height <= 72) continue;
1093
+ clipsOf.push({ a, acs, ar, x: /hidden|clip/.test(ox), y: /hidden|clip/.test(oy) });
1094
+ }
1095
+ if (hit === "intended") continue;
1096
+ if (rootClipX) clipsOf.push({ a: document.body, ar: { left: -scrollX, top: -1e7, width: docW, height: 2e7 }, x: true, y: false, root: true });
1097
+ for (const c of clipsOf) {
1098
+ if (!c.x && !c.y) continue;
1099
+ const box = clipBox(c);
1100
+ let partial = null, inside = 0, outside = 0;
1101
+ for (const q of t.rects) {
1102
+ const ix = Math.min(q.x + q.w, box.r) - Math.max(q.x, box.l), iy = Math.min(q.y + q.h, box.b) - Math.max(q.y, box.t);
1103
+ if (ix <= 0 || iy <= 0) {
1104
+ outside++;
1105
+ continue;
1106
+ }
1107
+ const bx = Math.max(box.l - q.x, q.x + q.w - box.r), by = Math.max(box.t - q.y, q.y + q.h - box.b);
1108
+ if (bx > Math.max(4, 0.3 * f) || by > Math.max(3, 0.25 * f)) partial ??= { axis: bx > Math.max(4, 0.3 * f) ? "x" : "y", px: Math.round(Math.max(bx, by)) };
1109
+ else inside++;
1110
+ }
1111
+ if (!partial && !(inside && outside)) continue;
1112
+ // A strip, ticker, carousel or word rotator moves its words into view: inside one of the clips around the
1113
+ // text, an item beside the text's branch lies mostly outside that clip (the next slide, the ticker's second
1114
+ // run, the rotator's other words), or something between them moves.
1115
+ if (clipsOf.slice(clipsOf.indexOf(c)).some((o) => carousel(o, el)) || (() => {
1116
+ for (let e = el.parentElement; e && e !== c.a && e !== document.body; e = e.parentElement) if ((e.getAnimations?.().length ?? 0) > 0) return true;
1117
+ return false;
1118
+ })()) break;
1119
+ hit = { by: c.root ? "the screen edge" : `${c.a.tagName.toLowerCase()}${c.a.classList.length ? "." + [...c.a.classList].slice(0, 2).join(".") : ""}`, partial: outside ? null : partial, hiddenLines: outside, lines: t.rects.length };
1120
+ break;
1121
+ }
1122
+ if (!hit) continue;
1123
+ cutSeen.add(el);
1124
+ wordsCut.push({ kind: heading ? "heading" : number ? "number" : "paragraph", text: clean((blk ?? el).textContent).slice(0, 48), fontSize: Math.round(f), by: hit.by, axis: hit.partial?.axis ?? "y", over: hit.partial?.px ?? null, hiddenLines: hit.hiddenLines, lines: hit.lines, section: sectionOf(t.rects[0].y + t.rects[0].h / 2)?.index ?? null });
1125
+ }
1126
+
1127
+ // ---- tiles with a void inside (TILE_VOID): a card 200px or taller whose content leaves a large empty run
1128
+ // between blocks (a stat pinned to the bottom under a void), under it (a visual floating with space below)
1129
+ // or above it. Content is text, media and inner boxes; runs are measured inside the tile's padding.
1130
+ const tiles = [];
1131
+ for (const q of solid) {
1132
+ if (q.media || !q.cs || q.h < 200 || q.w < 160 || q.w > 0.9 * docW || q.h > 2 * vh) continue;
1133
+ if (/^(INPUT|SELECT|TEXTAREA|BUTTON|IFRAME)$/.test(q.el.tagName)) continue;
1134
+ // A tile partly off the screen (a carousel's next card) isn't seen whole.
1135
+ if (q.x < -2 || q.x + q.w > docW + 2) continue;
1136
+ const cs = q.cs;
1137
+ if (/auto|scroll/.test(cs.overflowY) && q.el.scrollHeight > q.el.clientHeight + 4) continue;
1138
+ const top = q.y + px(cs.borderTopWidth) + px(cs.paddingTop), bottom = q.y + q.h - px(cs.borderBottomWidth) - px(cs.paddingBottom);
1139
+ if (bottom - top < 120) continue;
1140
+ // Content still hidden inside (a block at opacity 0 waiting for its animation, a lazy panel): the tile isn't
1141
+ // in its end state, so its space can't be judged.
1142
+ let pending = false, n = 0;
1143
+ for (const d of q.el.querySelectorAll("*")) {
1144
+ if (++n > 600) break;
1145
+ const r = d.getBoundingClientRect();
1146
+ if (r.width * r.height < 0.12 * q.w * q.h) continue;
1147
+ const dcs = getComputedStyle(d);
1148
+ if (dcs.display === "none") continue;
1149
+ // A picture not loaded yet (lazy, or its <img> not inserted) is a hole now and a photo later.
1150
+ const unloaded = (d.tagName === "IMG" && (!d.complete || !d.naturalWidth)) || (d.tagName === "PICTURE" && !d.querySelector("img")?.naturalWidth);
1151
+ if (unloaded || dcs.visibility === "hidden" || parseFloat(dcs.opacity) <= 0.05) {
1152
+ pending = true;
1153
+ break;
1154
+ }
1155
+ }
1156
+ if (pending) continue;
1157
+ // What the eye sees inside the frame, whoever owns it in the DOM: a mock window often draws its frame in one
1158
+ // element and lays its page over it as a sibling (Stripe's checkout and dashboard).
1159
+ const inside = (r, k) => (Math.max(0, Math.min(r.x + r.w, q.x + q.w) - Math.max(r.x, q.x)) * Math.max(0, Math.min(r.y + r.h, q.y + q.h) - Math.max(r.y, q.y))) / Math.max(1, r.w * r.h) >= k;
1160
+ const spans = [];
1161
+ for (const t of texts) for (const r of t.rects) if (r.w >= 2 && r.h >= 4 && inside(r, 0.5)) spans.push({ a: r.y, z: r.y + r.h, font: px(t.cs.fontSize), what: `"${clean(t.text).slice(0, 28)}"` });
1162
+ // A box mostly inside, or a picture bigger than the tile and cropped by it (pinned to an edge: the fix, not a void).
1163
+ const covers = (o) => (Math.max(0, Math.min(o.x + o.w, q.x + q.w) - Math.max(o.x, q.x)) * Math.max(0, Math.min(o.y + o.h, q.y + q.h) - Math.max(o.y, q.y))) / (q.w * q.h) >= 0.25;
1164
+ for (const o of solid) if (o.el !== q.el && !o.el.contains(q.el) && o.w * o.h >= 16 && (inside(o, 0.6) || (o.media && covers(o)))) spans.push({ a: o.y, z: o.y + o.h, font: 0, what: o.media ? "a picture" : "a box" });
1165
+ const kept = spans.map((x) => ({ ...x, a: Math.max(q.y, x.a), z: Math.min(q.y + q.h, x.z) })).filter((x) => x.z > x.a).sort((x, y) => x.a - y.a);
1166
+ if (!kept.length) continue;
1167
+ const merged = [];
1168
+ for (const x of kept) {
1169
+ const last = merged[merged.length - 1];
1170
+ if (last && x.a <= last.z) {
1171
+ if (x.z > last.z) (last.z = x.z), (last.lastWhat = x.what);
1172
+ last.font = Math.max(last.font, x.font);
1173
+ last.media ||= x.font === 0;
1174
+ } else merged.push({ a: x.a, z: x.z, what: x.what, lastWhat: x.what, font: x.font, media: x.font === 0 });
1175
+ }
1176
+ let best = { run: Math.max(0, merged[0].a - top), where: "above", below: merged[0].what };
1177
+ for (let k = 1; k < merged.length; k++) {
1178
+ const run = merged[k].a - merged[k - 1].z;
1179
+ if (run <= best.run) continue;
1180
+ // A statement card's foot: display-size text above, and below only a short line of small text (the
1181
+ // attribution under a big quote) closing the tile. A convention, not a void.
1182
+ const last = k === merged.length - 1;
1183
+ const lead = Math.max(...merged.slice(0, k).map((m) => m.font));
1184
+ const foot = last && merged[k].z - merged[k].a <= 72 && merged[k].font > 0 && merged[k].font <= 16 && lead >= 20 && lead >= 1.5 * merged[k].font;
1185
+ best = { run, where: "between", above: merged[k - 1].lastWhat, below: merged[k].what, foot };
1186
+ }
1187
+ const tail = bottom - merged[merged.length - 1].z;
1188
+ if (tail > best.run) best = { run: tail, where: "under", above: merged[merged.length - 1].lastWhat };
1189
+ if (best.run < 64 || best.run < 0.2 * q.h) continue;
1190
+ tiles.push({ el: q.el, section: sectionOf(q.y + q.h / 2)?.index ?? null, x: Math.round(q.x), y: Math.round(q.y), width: Math.round(q.w), height: Math.round(q.h), run: Math.round(best.run), share: +(best.run / q.h).toFixed(2), where: best.where, above: best.above ?? null, below: best.below ?? null, foot: Boolean(best.foot), text: clean(q.el.textContent).slice(0, 40) });
1191
+ }
1192
+ // A wrapper and its inner box with the same frame are one tile: keep the inner one.
1193
+ const tileList = tiles
1194
+ .filter((t) => !tiles.some((o) => o !== t && t.el.contains(o.el) && Math.abs(o.width - t.width) <= 8 && Math.abs(o.height - t.height) <= 8))
1195
+ .sort((a, b) => b.share - a.share)
1196
+ .slice(0, 24)
1197
+ .map(({ el, ...t }) => t);
1198
+
1199
+ // ---- each photo once (PHOTO_REPEAT): raster images 64px and up, keyed by their source without size
1200
+ // parameters (and by file name), placed more than once. Not counted: copies stacked on each other (a blurred
1201
+ // glow behind a photo), clones out of sight (a marquee's second run, aria-hidden), and small thumbnails of a
1202
+ // larger copy in a gallery strip.
1203
+ const GENERIC = /^(image|img|photo|picture|pic|bg|background|hero|cover|thumb|thumbnail|default|placeholder|logo|icon|avatar|index|file|download|\d{1,4}|[a-f0-9]{1,6})$/i;
1204
+ const photoKey = (raw, depth = 0) => {
1205
+ if (!raw || /^(data|blob):/i.test(raw)) return null;
1206
+ let u;
1207
+ try {
1208
+ u = new URL(raw, location.href);
1209
+ } catch {
1210
+ return null;
1211
+ }
1212
+ // Image CDNs that carry the original in a parameter (Next.js /_next/image?url=…).
1213
+ for (const p of ["url", "src", "image", "img"]) {
1214
+ const inner = u.searchParams.get(p);
1215
+ if (inner && depth < 2 && /^(https?:|\/)/.test(inner)) return photoKey(inner, depth + 1);
1216
+ }
1217
+ let file = u.pathname.split("/").pop() || "";
1218
+ try {
1219
+ file = decodeURIComponent(file);
1220
+ } catch {}
1221
+ if (/\.svg$/i.test(file)) return null;
1222
+ const stem = file.replace(/\.(jpe?g|png|webp|avif|gif|jxl|heic)$/i, "").replace(/(?:[-_.@](?:\d{2,4}w|\d{2,4}x\d{0,4}|\d(?:\.\d+)?x|scaled|thumb(?:nail)?|small|medium|large|xl|lg|md|sm|q\d+))+$/i, "").toLowerCase();
1223
+ return { url: u.origin + u.pathname, stem: stem.length >= 5 && !GENERIC.test(stem) ? stem : null, name: file || u.pathname };
1224
+ };
1225
+ // How much of a box survives the clipping of its ancestors (a carousel's track, a marquee's mask).
1226
+ const visibleShare = (p) => {
1227
+ let x0 = p.x, y0 = p.y, x1 = p.x + p.w, y1 = p.y + p.h;
1228
+ for (let a = p.el.parentElement; a && a !== document.body; a = a.parentElement) {
1229
+ const acs = getComputedStyle(a);
1230
+ if (acs.overflowX === "visible" && acs.overflowY === "visible") continue;
1231
+ const r = pbox(a.getBoundingClientRect());
1232
+ (x0 = Math.max(x0, r.x)), (y0 = Math.max(y0, r.y)), (x1 = Math.min(x1, r.x + r.w)), (y1 = Math.min(y1, r.y + r.h));
1233
+ if (x1 <= x0 || y1 <= y0) return 0;
1234
+ }
1235
+ x0 = Math.max(0, x0);
1236
+ x1 = Math.min(docW, x1);
1237
+ return x1 > x0 && y1 > y0 ? ((x1 - x0) * (y1 - y0)) / (p.w * p.h) : 0;
1238
+ };
1239
+ const overlapArea = (a, b) => Math.max(0, Math.min(a.x + a.w, b.x + b.w) - Math.max(a.x, b.x)) * Math.max(0, Math.min(a.y + a.h, b.y + b.h) - Math.max(a.y, b.y));
1240
+ const photoGroups = [];
1241
+ for (const p of pictures) {
1242
+ if (p.el.closest("[aria-hidden=true]") || visibleShare(p) < 0.3) continue;
1243
+ const k = photoKey(p.src);
1244
+ if (!k) continue;
1245
+ let g = photoGroups.find((x) => x.url === k.url || (k.stem && x.stem === k.stem));
1246
+ if (!g) photoGroups.push((g = { url: k.url, stem: k.stem, name: k.name, items: [] }));
1247
+ if (g.items.some((o) => overlapArea(o, p) > 0.5 * Math.min(o.w * o.h, p.w * p.h))) continue;
1248
+ g.items.push(p);
1249
+ }
1250
+ const inStrip = (p) => {
1251
+ if (p.el.closest("button, [role=tab], [role=option], [aria-selected], [aria-controls]")) return true;
1252
+ const row = p.el.parentElement?.parentElement;
1253
+ return Boolean(row && [...row.querySelectorAll("img")].filter((i) => Math.abs(i.getBoundingClientRect().height - p.h) < 4).length >= 3);
1254
+ };
1255
+ const photos = photoGroups
1256
+ .map((g) => {
1257
+ const maxA = Math.max(...g.items.map((p) => p.w * p.h));
1258
+ const items = g.items.filter((p) => !(p.w * p.h <= 0.25 * maxA && inStrip(p)));
1259
+ return { name: g.name, url: g.url, count: items.length, sections: items.map((p) => sectionOf(p.y + p.h / 2)?.index ?? null), sizes: items.map((p) => `${Math.round(p.w)}×${Math.round(p.h)}`) };
1260
+ })
1261
+ .filter((g) => g.count >= 2)
1262
+ .sort((a, b) => b.count - a.count)
1263
+ .slice(0, 12);
1264
+
1265
+ // Product UI drawn in code (a clinic diary, a dashboard, an editor in its own framed panel) is something to look
1266
+ // at, like a screenshot of it would be: a Moveday hero that was nearly all live product measured 0% material and
1267
+ // failed. A panel counts when it is big and dense with small interface text (most labels 14px or under), so a
1268
+ // grid of feature cards in 16px copy doesn't. Bit 16, kept apart from imagery so the imagery checks don't change.
1269
+ const outerFramed = framed.filter((f) => !framed.some((g) => g !== f && g.el.contains(f.el)));
1270
+ for (const f of outerFramed) {
1271
+ if (f.w < Math.min(400, docW * 0.8) || f.h < 240) continue; // a phone-width panel (358 of 390) counts too
1272
+ const leaves = [...f.el.querySelectorAll("*")].filter((e) => e.children.length === 0 && (e.textContent?.trim() || ["svg", "img", "input", "button"].includes(e.tagName.toLowerCase())));
1273
+ const texts = leaves.filter((e) => e.textContent?.trim());
1274
+ if (leaves.length < 24 || texts.length < 12) continue;
1275
+ const small = texts.filter((e) => parseFloat(getComputedStyle(e).fontSize) <= 14).length;
1276
+ if (small / texts.length < 0.6) continue;
1277
+ mark(f, 16, 3, false);
1278
+ }
1279
+
1280
+ // Whole-page imagery and texture over every cell.
1281
+ let pimg = 0, ptex = 0, pui = 0;
1282
+ for (let k = 0; k < grid.length; k++) (pimg += grid[k] & 4 ? 1 : 0), (ptex += grid[k] & 8 ? 1 : 0), (pui += grid[k] & 16 && !(grid[k] & 4) ? 1 : 0);
1283
+
1284
+ return {
1285
+ width: docW,
1286
+ vh,
1287
+ pageHeight: pageH,
1288
+ cell,
1289
+ column: { x: colL * cell, width: (colR - colL) * cell },
1290
+ sections: out,
1291
+ bands,
1292
+ bgRuns,
1293
+ imagery: +(pimg / grid.length).toFixed(3),
1294
+ texture: +(ptex / grid.length).toFixed(3),
1295
+ productUi: +(pui / grid.length).toFixed(3),
1296
+ media: media.length,
1297
+ mono: { chars: mono, share: +(mono / Math.max(1, totalChars)).toFixed(3), count: monoList.length, numeric: monoList.filter((m) => m.numeric).length, code: monoList.filter((m) => m.code).length, snippets: [...monoList.filter((m) => m.numeric), ...monoList.filter((m) => !m.numeric)].slice(0, 12) },
1298
+ placeholders: placeholders.slice(0, 12),
1299
+ clipped: clipped.slice(0, 12),
1300
+ system: { headings, pads, buttons: buttons.slice(0, 80), cards },
1301
+ tiles: tileList,
1302
+ photos,
1303
+ grids,
1304
+ frames: frames.slice(0, 6),
1305
+ wordsCut,
1306
+ };
1307
+ };