@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,677 @@
1
+ // The floor: deterministic checks for what is visibly wrong with a page whatever the taste. Dead bands of
2
+ // nothing, half-empty sections, every section the same template, one flat colour with no imagery, monospace
3
+ // numbers in a sans page, placeholder copy, clipped text, a phone page that is one endless stacked board, a
4
+ // product board whose columns are mostly blank, a system that drifts from section to section (SYSTEM_DRIFT: H2
5
+ // size and left edge, section padding, button height, card style), a tile with a void inside (TILE_VOID), the
6
+ // same photo shown twice (PHOTO_REPEAT), a bento with no lead tile, identical neighbours or a checkerboard of
7
+ // inverted tiles (BENTO_FLAT, BENTO_TWINS, INVERT_OVERUSE), a hero product window floating small (FRAME_SMALL),
8
+ // real words cut by an edge (WORDS_CUT). Rendered at 1440, 1024, 768 and 390: tablet is where the Express
9
+ // landing page's hero broke (Nov 4 2026 retrospective, palate-site/DESIGN-RETROSPECTIVE.md §2, §3, §7).
10
+ // Research (research_notes/AI design quality floor/metrics_and_blocks.md): visual complexity is U-shaped (too
11
+ // empty is as bad as too busy), so the gates are two-sided; and no single metric predicts quality, so these
12
+ // catch clearly-bad, they don't score taste. A page can pass all of them and still be dull.
13
+ //
14
+ // Calibration (Oct 28 2026, this machine, 32px cells, reduced motion; fails/warns). Kiln v1 is the generated
15
+ // pottery-studio landing that passed every other palate check and looked about 5/10 (its build, served
16
+ // statically); Kiln v2 is its rebuild; the rest are pages people consider well designed (home page only,
17
+ // robots.txt respected, one recorded load each, replayed for every measurement):
18
+ // page 1440 390 what fired
19
+ // Kiln v1 10F/2W 8F/2W fail: DEAD_SPACE (253, 256px; 189, 192px), HALF_EMPTY (§3), SAME_TEMPLATE
20
+ // (§3/§4, §5/§6), MONOTONE (81% sage), NO_MATERIAL (0.8%), MONO_MISUSE (25
21
+ // numbers), PLACEHOLDER ("Your studio"), CLIPPED (stacked avatars), PHONE_TALL
22
+ // (hero 2.3 screens, board 998px); warn: HALF_EMPTY §4, EMPTY_COLUMNS (the
23
+ // board's end state: an empty Glaze firing column, 9%), DEAD_SPACE 136px,
24
+ // CLIPPED (names ellipsed to 27px). Its board as first shown (one card in
25
+ // three of five columns; the fixture in test/floor.test.mjs) fails
26
+ // EMPTY_COLUMNS: 28% ×3, average 39%.
27
+ // Kiln v2 (127.0.0.1:5212 /) 0F/0W 0F/0W (its hero board: 95/95/95/33/80%, quiet)
28
+ // palate-site-kappa.vercel.app 0F/1W 0F/1W warn: TOO_BUSY (its live demo, 76% text and lines), CLIPPED (names
29
+ // ellipsed to 30px in its demo)
30
+ // linear.app 0F/2W 0F/0W warn: SAME_TEMPLATE (two chapter sections), MONO_MISUSE (Fig labels, dates)
31
+ // stripe.com 0F/2W 0F/2W warn: PLACEHOLDER (example.com in a demo), CLIPPED (cycling-language labels)
32
+ // notion.com 0F/0W 0F/0W
33
+ // raycast.com 0F/1W 0F/1W warn: DEAD_SPACE (4 bands of 405–476px on flat black; 9 at 390)
34
+ // vercel.com 0F/2W 0F/1W warn: DEAD_SPACE (its full-screen hero: 270, 257px)
35
+ // Before AIRY_MATERIAL, Raycast failed DEAD_SPACE 14 times and Vercel 6: raw band height alone can't tell air
36
+ // from holes. Before the section finder's Oct 28 pass, palate-site came back as three overlapping background
37
+ // layers over its hero (so its demo measured as part of a 1368px block) and Notion as one 4687px block (its
38
+ // wrappers are display: contents); both are now their real bands. Not caught: Kiln's pricing section's bare
39
+ // lower left (20.5% of the section; palate-site's centred section measures 19.5%).
40
+ //
41
+ // Calibration of the tablet widths and the three Nov 4 checks (Nov 4 2026; local builds through runFloor, real
42
+ // sites one load at 1440 resized to 1024, 768 and 390; fails/warns of these checks and of the older ones at
43
+ // the new widths):
44
+ // page 1440 1024 768 390 what fired
45
+ // Kiln v2 (127.0.0.1:5212) 1F/0W 0F/0W 0F/0W 0F/0W fail: SYSTEM_DRIFT H2s in 3 sizes (40px norm; Pricing 56,
46
+ // its photo band's 28): real drift, kept
47
+ // Tracewell (127.0.0.1:5231) 0F/3W 0F/0W 0F/2W 0F/0W warn: SYSTEM_DRIFT H2s 59.4/58.3/53.4 (close: warn),
48
+ // padding 124/114/94, buttons 44 vs 48; HALF_EMPTY at 768
49
+ // palate-site-kappa.vercel.app 0F/1W 0F/2W 0F/0W 0F/1W (only the older TOO_BUSY, CLIPPED)
50
+ // linear.app 0F/3W 0F/7W 0F/0W 0F/0W warn: SYSTEM_DRIFT closing H2 72 vs 48; at 1024
51
+ // HALF_EMPTY ×5 (section intros over their mocks), DEAD_SPACE
52
+ // stripe.com 0F/4W 0F/1W 0F/1W 0F/2W warn: SYSTEM_DRIFT H2 56 vs 32, a 32px button vs 48
53
+ // vercel.com 0F/2W 0F/1W 0F/2W 0F/1W (only the older DEAD_SPACE, HALF_EMPTY at 768)
54
+ // apple.com 0F/1W 0F/0W 0F/0W 0F/0W warn: SYSTEM_DRIFT buttons 36 vs 44
55
+ // False fails fixed on the way: Linear failed HALF_EMPTY 3× at 1024 (a heading block stacked over a full-width
56
+ // visual is the tablet layout: a bare half there warns); TILE_VOID fired on mock windows whose page is a sibling
57
+ // laid over the frame (Stripe: now what's inside the frame's box counts, whoever owns it), on panels still
58
+ // animating in or lazy pictures not loaded (Linear, Apple: skipped), on a picture bigger than its tile (Apple:
59
+ // counts), on 3px chart bars (Stripe at 390) and on quote cards (Linear: a big quote over a small attribution
60
+ // is a convention); SYSTEM_DRIFT padding read each side alone (palate-site, Vercel 5 values: now one value per
61
+ // section, hero and strips out), a mock's button (Tracewell's "Why slow?", aria-hidden) and centred-copy cards'
62
+ // text inset (Apple, 4 styles: a card without padding matches any inset). PHOTO_REPEAT fired nowhere.
63
+ // Runtime of runFloor, browser start included: Kiln v2 10.3s at 1440+390 before these checks, 11.0s with them,
64
+ // 19.9s at four widths; Tracewell 11.0s, 11.6s, 21.1s. The in-page measure is 0.1–0.4s a width; the rest is
65
+ // loading and scrolling each width. The gate takes its 1024 and 768 views from these renders (no extra loads).
66
+ //
67
+ // Bento grids and product frames (Nov 7 2026; ideas from the stageshow skill, in our own words): BENTO_FLAT, BENTO_TWINS
68
+ // and INVERT_OVERUSE (how a bento is composed: one lead tile, neighbours that contrast, one inverted tile),
69
+ // FRAME_SMALL (the hero's product window floating small) and WORDS_CUT (a heading, paragraph or number cut by a
70
+ // clipping edge). Calibrated through runFloor at all four widths, new findings only (everything else unchanged):
71
+ // page new notes
72
+ // kiln, tracewell, mabels-bakehouse, none (r4 builds; no bento; Tracewell's hero mock spans the width,
73
+ // open-shelf, mira-okafor-photo (r4) Kiln's board 55% and bleeding right)
74
+ // kiln-r3, tracewell-r3 none their asymmetric bentos (5 tiles, lead 821×646 / 821×812) pass
75
+ // mabels-bakehouse r1 1W FRAME_SMALL: its order window 701px, 49% of 1440, margin all round
76
+ // linear.app, vercel.com, apple.com/iphone, raycast.com, clerk.com, supabase.com, cal.com,
77
+ // palate-site-kappa.vercel.app none
78
+ // False positives fixed on the way: WORDS_CUT on Linear's visually hidden H1 copy (a hashed "visuallyHidden" class
79
+ // in a 1px box: any 1–2px clip box or such class is hidden text now) and on text in Raycast's Embla carousel (a clip
80
+ // with an item beside the text's branch mostly outside it is a strip, ticker or carousel); BENTO_TWINS on
81
+ // Raycast's keycap illustration (tiles are 160×120 and up), on Clerk's 12-tile catalogue (twins only in composed
82
+ // grids of 8 tiles or fewer) and on the state cards inside palate-site's demo window (cards inside a framed box of
83
+ // 24+ small texts are product UI, not tiles). True positives on a real bento: kiln-r3's with every tile set to
84
+ // span 4 → BENTO_FLAT; two stacked tiles of one size plus two dark tiles → BENTO_TWINS and INVERT_OVERUSE; a 40px
85
+ // nowrap tile heading → WORDS_CUT fail. The in-page measure stays 0.15–0.3s a width (Clerk, Raycast).
86
+ import fs from "node:fs";
87
+ import path from "node:path";
88
+ import { fileURLToPath } from "node:url";
89
+ import { findSections, openPage, shootFull, withBrowser } from "../look.mjs";
90
+ import { craftChecks, measureCraft } from "./craftchecks.mjs";
91
+
92
+ const here = path.dirname(fileURLToPath(import.meta.url));
93
+ const gateScript = fs.readFileSync(path.join(here, "..", "page-gate.js"), "utf8").trim().replace(/;\s*$/, "");
94
+
95
+ // ---- thresholds
96
+ // Grid cell for ink coverage, in CSS px: coarse enough that a line of text inks its row, fine enough for gaps.
97
+ export const CELL = 32;
98
+ // An empty band (no text, image, surface or line) taller than this is dead space; between 390 and 1440 the limit
99
+ // is interpolated. Kiln's were 253–256px at 1440 and 189–192 at 390. Height alone doesn't separate good from bad:
100
+ // Linear and Notion run 213–224px, Vercel 270, Raycast 405–476 (flat dark, measured by pixels too). What does is
101
+ // whether there is anything to breathe around: see AIRY_MATERIAL.
102
+ export const DEAD_WARN = { 390: 120, 1440: 200 };
103
+ export const DEAD_FAIL = { 390: 160, 1440: 240 };
104
+ // Page imagery + texture at or above this, and big empty bands are a choice (air around strong pictures): only
105
+ // bands over the fail limit are reported, as warnings. Below it (Kiln 0.8%) the same air is dead. Good pages
106
+ // measured 15–46%.
107
+ export const AIRY_MATERIAL = 0.1;
108
+ // The padding of a coloured band (the empty run touches a change of background) may be this much taller: the
109
+ // colour edge itself is structure (Notion's grey CTA band: 262/264px).
110
+ export const EDGE_ALLOW = 1.4;
111
+ // A run of rows where one half of a section is bare beside ink in the other (a left heading with nothing next
112
+ // to it), in px and as a share of the section's content frame (run × half width). Kiln: 192px / 11.5% and
113
+ // 128px / 7.1%. Good pages: Vercel's big two-line heading 160px but 3.3% of a tall section; Linear's changelog
114
+ // 96px / 10%.
115
+ export const SIDE_WARN = { px: 128, share: 0.06 };
116
+ export const SIDE_FAIL = { px: 160, share: 0.08 };
117
+ // Largest empty rectangle beside content (left/right/centre), as a share of the section's content frame.
118
+ export const EMPTY_RECT_WARN = 0.3;
119
+ export const EMPTY_RECT_FAIL = 0.35;
120
+ // ...and at least this tall: two lines of text beside a bare strip in a short band aren't a half-empty section.
121
+ export const EMPTY_RECT_MIN_H = 160;
122
+ // A two-column section whose lighter half has less than this share of the heavier half's ink. A short text
123
+ // block beside a big visual is normal (Raycast's keyboard split: 0.17), so only a near-bare half fails.
124
+ export const BALANCE_FAIL = 0.1;
125
+ // Ink share of a section's content frame (cells): below these it is too empty to hold attention.
126
+ export const TOO_EMPTY_FAIL = 0.1;
127
+ export const TOO_EMPTY_WARN = 0.15;
128
+ // Detail share (text, lines, icons; not surfaces or images) above these is a wall: desktop, then phone (where
129
+ // one column of text inks nearly every cell, so the bar is higher).
130
+ export const TOO_BUSY = { desktop: { warn: 0.75, fail: 0.85 }, phone: { warn: 0.85, fail: 0.92 } };
131
+ // One background colour over this share of the page height, with imagery under MONOTONE_IMAGERY, is one flat
132
+ // ground from top to bottom (Kiln: 81% sage, 0.8% imagery; good pages with one ground had 26–46% imagery).
133
+ export const MONOTONE_SHARE = 0.8;
134
+ export const MONOTONE_IMAGERY = 0.04;
135
+ // A marketing page whose imagery + illustration + texture covers less than this of its area has no material
136
+ // (Kiln 0.8%; every good page measured 25%+).
137
+ export const NO_MATERIAL = 0.04;
138
+ // Monospace numbers outside code: a fail at this many snippets when they are most of the page's mono text.
139
+ export const MONO_FAIL_N = 8;
140
+ export const MONO_FAIL_SHARE = 0.7;
141
+ export const MONO_WARN_N = 3;
142
+ // A page set mostly in monospace chose it; nothing to flag.
143
+ export const MONO_LED = 0.6;
144
+ // At phone width, a section taller than this many viewports that is mostly one stacked run of like items.
145
+ export const PHONE_TALL_VH = 2.2;
146
+ export const PHONE_STACK_VH = 0.9;
147
+ export const PHONE_STACK_SHARE = 0.4;
148
+ export const PHONE_STACK_ITEMS = 4;
149
+ // A board (three or more side-by-side columns inside a big framed panel) whose columns are mostly blank: a column
150
+ // is blank when content (text, media, cards) covers less than COLUMN_BLANK of its height. Fail when at least
151
+ // two columns and COLUMN_BLANK_SHARE of them are blank, or the columns average under COLUMN_MEAN_FAIL; warn on
152
+ // a column that is all but empty, or an average under COLUMN_MEAN_WARN. Kiln v1's hero board as first shown
153
+ // (one small card in three of five 420px columns): 0.29 ×3, mean 0.41. Its reduced-motion end state, with the
154
+ // message toast over two columns: [0.48, 0.49, 0.49, 0.09, 0.87]. Kiln v2's full board: [0.95 ×3, 0.33, 0.8].
155
+ export const COLUMN_BLANK = 0.35;
156
+ export const COLUMN_BLANK_SHARE = 0.4;
157
+ export const COLUMN_MEAN_FAIL = 0.35;
158
+ export const COLUMN_MEAN_WARN = 0.5;
159
+ export const COLUMN_BARE = 0.15;
160
+ // Sections shorter than this (a logo strip, a nav) aren't judged as layouts.
161
+ export const MIN_SECTION_H = 160;
162
+ // Imagery share that makes a section's layout a different thing (a split with a photo vs a split with text).
163
+ export const IMAGERY_DISTINCT = 0.15;
164
+ // The widths the floor renders: phone, tablet portrait, tablet landscape, desktop. Tablet is where a hero photo
165
+ // gets blown up behind its text (the Express landing page broke at ~830 after passing 390 and 1440).
166
+ export const FLOOR_WIDTHS = [1440, 1024, 768, 390];
167
+ // SYSTEM_DRIFT: one written system, measured. Values within these tolerances are the same value: H2 font size
168
+ // (px), H2 left edge (px; centred headings and headings that start in the right half of a split are left out),
169
+ // section padding (px, one value per section: the mean of its top and bottom, not the hero, not strips under
170
+ // PAD_MIN_H; at most PAD_VALUES distinct values, cf. rule R35 "at most two section spacings"), filled button
171
+ // height (px; not in the nav, header, footer or an aria-hidden mock), and card style for cards in sets (radius
172
+ // ±2px, padding ±4px, same border, shadow and fill; at most CARD_VARIANTS). Warn past those; fail when the
173
+ // drift is large: H2s in DRIFT_FAIL_VALUES sizes or more spread by H2_FAIL_SPREAD (Kiln v2: 28–56px; Tracewell's
174
+ // 53.4–59.4 only warns), left edges off in DRIFT_FAIL_VALUES - 1 sections or more, paddings in PAD_FAIL_VALUES
175
+ // values, buttons in DRIFT_FAIL_VALUES heights, cards in CARD_FAIL_VARIANTS styles. Strong sites warned at most
176
+ // (a bigger closing H2, one small button) and never failed.
177
+ export const H2_SIZE_TOL = 1;
178
+ export const H2_LEFT_TOL = 4;
179
+ export const PAD_TOL = 8;
180
+ export const PAD_VALUES = 2;
181
+ export const PAD_FAIL_VALUES = 4;
182
+ export const PAD_MIN_H = 300;
183
+ export const H2_FAIL_SPREAD = 1.25;
184
+ export const BUTTON_TOL = 2;
185
+ export const CARD_VARIANTS = 2;
186
+ export const CARD_FAIL_VARIANTS = 4;
187
+ export const DRIFT_FAIL_VALUES = 3;
188
+ // TILE_VOID: the largest empty vertical run inside a card 200px or taller (between blocks, under or above
189
+ // them, inside its padding), as px and share of the tile's height. The retrospective's case: a stat pinned to the
190
+ // bottom of its tile under a void. Not judged: tiles partly off screen, tiles with content still hidden or
191
+ // pictures not loaded, and a statement card's foot (display text over one short line of small text).
192
+ export const TILE_VOID_WARN = { px: 88, share: 0.3 };
193
+ export const TILE_VOID_FAIL = { px: 120, share: 0.4 };
194
+ // PHOTO_REPEAT: the same photo placed this many times, or this many photos placed twice, fails; once twice warns.
195
+ export const PHOTO_REPEAT_FAIL = 3;
196
+ // BENTO_FLAT, BENTO_TWINS, INVERT_OVERUSE: a bento is tiles edge to edge (gaps of 48px or less) in two rows and two
197
+ // columns or more, at least BENTO_TITLED of them saying something (a title, a sentence, a figure, a quote or a UI
198
+ // fragment: a gallery, a logo wall, decorative grid cells or a shop's product grid is not a bento). FLAT:
199
+ // BENTO_MIN_TILES tiles or more, every width and every height within BENTO_EQUAL of each other, one surface, and
200
+ // BENTO_DESCRIBED of them a feature card (a title over a sentence of six words or more; a set of order tickets or
201
+ // framework names is a collection, equal by nature): nothing leads. TWINS: in a composed grid (BENTO_TWINS_MAX
202
+ // tiles or fewer; past that it is a catalogue whose small tiles repeat on purpose, Clerk's 12) with a lead (its
203
+ // largest tile BENTO_LEAD times the smallest or more), two touching tiles that both say something, of one size
204
+ // (within BENTO_TWIN), one surface and one content type. INVERT_OVERUSE: two speaking tiles or more on the inverse
205
+ // ground (7:1 against the ground under the grid), unless every tile is (then the dark tile is the system, not an
206
+ // accent). All warn, at the page-level width only: a bento that becomes equal tiles at tablet is its responsive
207
+ // layout.
208
+ export const BENTO_MIN_TILES = 4;
209
+ export const BENTO_EQUAL = 1.1;
210
+ export const BENTO_LEAD = 1.5;
211
+ export const BENTO_TWIN = 0.04;
212
+ export const BENTO_TITLED = 0.5;
213
+ export const BENTO_TWINS_MAX = 8;
214
+ export const BENTO_DESCRIBED = 0.75;
215
+ // FRAME_SMALL: the hero's product frame (its largest window, screenshot or framed mock) under this share of the
216
+ // section's width and touching no edge: floating small with margin all round. From FRAME_MIN_W up (at tablet and
217
+ // phone it stacks full width); a portrait device isn't judged by width.
218
+ export const FRAME_MIN_SHARE = 0.5;
219
+ export const FRAME_MIN_W = 1024;
220
+ // WORDS_CUT: a heading, paragraph or number cut by a clipping edge fails; past WORDS_CUT_LIST, one count.
221
+ export const WORDS_CUT_LIST = 4;
222
+
223
+ const PHONE = 768;
224
+ const lerp = (tab, w) => {
225
+ const t = Math.max(0, Math.min(1, (w - 390) / (1440 - 390)));
226
+ return Math.round(tab[390] + t * (tab[1440] - tab[390]));
227
+ };
228
+ export const deadLimits = (width) => ({ warn: lerp(DEAD_WARN, width), fail: lerp(DEAD_FAIL, width) });
229
+
230
+ const STRONG_PLACEHOLDER = /lorem|ipsum|placeholder|^your |\{\{|\[(your|insert)|insert (text|image|copy)|company name|your logo/i;
231
+
232
+ const short = (s, n = 48) => {
233
+ const t = String(s ?? "").replace(/\s+/g, " ").trim();
234
+ return t.length > n ? t.slice(0, n - 1) + "…" : t;
235
+ };
236
+ const ref = (s) => (s ? { index: s.index, heading: short(s.heading ?? s.name, 80) } : null);
237
+ const label = (s) => (s ? `§${s.index} "${short(s.heading ?? s.name, 40)}"` : "the page");
238
+ const isChrome = (s) => ["header", "nav", "footer"].includes(s.tag) || /^(header|footer|navigation)$/i.test(s.name ?? "");
239
+
240
+ // The sections judged as layouts: not page chrome, not tiny, not nested inside an earlier one.
241
+ function contentSections(metrics) {
242
+ const out = [];
243
+ for (const s of metrics.sections ?? []) {
244
+ if (s.empty || isChrome(s) || s.sticky || (s.rect?.height ?? 0) < MIN_SECTION_H) continue;
245
+ const r = s.rect;
246
+ if (out.some((o) => r.y >= o.rect.y - 1 && r.y + r.height <= o.rect.y + o.rect.height + 1)) continue;
247
+ out.push(s);
248
+ }
249
+ return out;
250
+ }
251
+
252
+ // Groups items whose value is within tol of a group's value (the most common value founds its group first).
253
+ // Largest group first.
254
+ export function clusterBy(items, val, tol) {
255
+ const counts = new Map();
256
+ for (const it of items) counts.set(val(it), (counts.get(val(it)) ?? 0) + 1);
257
+ const order = [...items].sort((a, b) => counts.get(val(b)) - counts.get(val(a)) || val(a) - val(b));
258
+ const out = [];
259
+ for (const it of order) {
260
+ const v = val(it);
261
+ const g = out.find((c) => Math.abs(c.value - v) <= tol);
262
+ if (g) g.items.push(it);
263
+ else out.push({ value: v, items: [it] });
264
+ }
265
+ return out.sort((a, b) => b.items.length - a.items.length || b.value - a.value);
266
+ }
267
+
268
+ // SYSTEM_DRIFT findings from page-gate.js's `system` (H2s, section paddings, filled buttons, card styles), for the
269
+ // content sections only. Each names the page's norm and the sections off it.
270
+ function systemDrift(sys, content, byIndex) {
271
+ if (!sys) return [];
272
+ const out = [];
273
+ const inContent = new Set(content.map((s) => s.index));
274
+ const where = (items, fmt) => items.slice(0, 5).map((it) => `${it.section != null ? label(byIndex(it.section)) : "the page"} ${fmt(it)}`).join(", ") + (items.length > 5 ? ` and ${items.length - 5} more` : "");
275
+ const secs = (items) => [...new Set(items.map((it) => it.section).filter((i) => i != null))].map((i) => `§${i}`).join(", ");
276
+
277
+ // H2 size: every section's heading from one step of the scale.
278
+ const hs = (sys.headings ?? []).filter((h) => inContent.has(h.section));
279
+ const sizes = clusterBy(hs, (h) => h.size, H2_SIZE_TOL);
280
+ if (hs.length >= 3 && sizes.length > 1 && sizes[0].items.length >= 2) {
281
+ const off = sizes.slice(1).flatMap((g) => g.items);
282
+ const spread = Math.max(...hs.map((h) => h.size)) / Math.min(...hs.map((h) => h.size));
283
+ out.push({
284
+ severity: sizes.length >= DRIFT_FAIL_VALUES && spread >= H2_FAIL_SPREAD ? "fail" : "warn",
285
+ section: byIndex(off[0].section),
286
+ what: `section H2s in ${sizes.length} sizes: ${sizes[0].value}px in ${secs(sizes[0].items)} (the norm); ${where(off, (h) => `${h.size}px`)}`,
287
+ fix: `Set every section's H2 from one token (${sizes[0].value}px at this width); a section that needs more weight gets it from its content, not a bigger heading.`,
288
+ });
289
+ }
290
+ // H2 left edge: left-aligned headings start on one line (centred ones, and headings that start in the right
291
+ // half of a split, have their own reason).
292
+ const lefts = hs.filter((h) => !h.centred && !h.right);
293
+ const edges = clusterBy(lefts, (h) => h.left, H2_LEFT_TOL);
294
+ if (lefts.length >= 3 && edges.length > 1 && edges[0].items.length >= 2) {
295
+ const off = edges.slice(1).flatMap((g) => g.items);
296
+ out.push({
297
+ severity: off.length >= DRIFT_FAIL_VALUES - 1 ? "fail" : "warn",
298
+ section: byIndex(off[0].section),
299
+ what: `section H2s start at ${edges.length} left edges: x=${edges[0].value}px in ${secs(edges[0].items)} (the norm); ${where(off, (h) => `x=${h.left}px (${h.left - edges[0].value > 0 ? "+" : ""}${h.left - edges[0].value})`)}`,
300
+ fix: `Put every section in the same container (one max-width and gutter) so its heading starts at x=${edges[0].value}px; a narrower reading column aligns its left edge to the container, not to the centre.`,
301
+ });
302
+ }
303
+ // Section padding: at most two values (R35). One value per section, the mean of its top and bottom (a run
304
+ // under 8px is a picture bleeding to the edge, not padding: then the other side alone). The first section sits
305
+ // under the nav and a strip under PAD_MIN_H (a logo row) has its own spacing: neither is the rhythm.
306
+ const first = content[0]?.index;
307
+ const samples = [];
308
+ for (const p of sys.pads ?? []) {
309
+ if (!inContent.has(p.section) || p.section === first || p.height < PAD_MIN_H) continue;
310
+ const sides = [p.top, p.bottom].filter((v) => v >= 8 && v <= 0.45 * p.height);
311
+ if (sides.length) samples.push({ section: p.section, v: Math.round(sides.reduce((a, b) => a + b, 0) / sides.length), sides: `${p.top}/${p.bottom}` });
312
+ }
313
+ const padGroups = clusterBy(samples, (s) => s.v, PAD_TOL);
314
+ if (samples.length >= 3 && padGroups.length > PAD_VALUES) {
315
+ const norm = padGroups.slice(0, PAD_VALUES);
316
+ const off = padGroups.slice(PAD_VALUES).flatMap((g) => g.items);
317
+ out.push({
318
+ severity: padGroups.length >= PAD_FAIL_VALUES ? "fail" : "warn",
319
+ section: byIndex(off[0].section),
320
+ what: `section padding in ${padGroups.length} values: ${norm.map((g) => `${g.value}px (${g.items.length}×)`).join(" and ")} are the rhythm; ${where(off, (s) => `${s.v}px (top/bottom ${s.sides})`)}`,
321
+ fix: `Use one section padding token (${norm.map((g) => `${g.value}px`).join(" or ")}) top and bottom on every section; spacing inside a section comes from its own gaps, not from extra section padding.`,
322
+ });
323
+ }
324
+ // Filled buttons: one height (a nav button is left out already).
325
+ const bs = sys.buttons ?? [];
326
+ const heights = clusterBy(bs, (b) => b.height, BUTTON_TOL);
327
+ if (bs.length >= 2 && heights.length > 1) {
328
+ const off = heights.slice(1).flatMap((g) => g.items);
329
+ out.push({
330
+ severity: heights.length >= DRIFT_FAIL_VALUES ? "fail" : "warn",
331
+ section: byIndex(off[0].section),
332
+ what: `filled buttons in ${heights.length} heights: ${heights[0].value}px (${heights[0].items.length}×, the norm); ${where(off, (b) => `"${short(b.text, 20)}" ${b.height}px`)}`,
333
+ fix: `Give every filled button the same height (${heights[0].value}px): one button component with one size for page actions.`,
334
+ });
335
+ }
336
+ // Card styles: radius, inset, border, shadow and fill, across the page's sets of cards.
337
+ const cards = sys.cards ?? [];
338
+ const variants = [];
339
+ const order = [...cards].sort((a, b) => cards.filter((c) => c.radius === b.radius && c.inset === b.inset).length - cards.filter((c) => c.radius === a.radius && c.inset === a.inset).length);
340
+ for (const c of order) {
341
+ // A card without padding of its own (inset null) matches any inset.
342
+ const v = variants.find((x) => x.border === c.border && x.shadow === c.shadow && x.fill === c.fill && Math.abs(x.radius - c.radius) <= 2 && (x.inset == null || c.inset == null || Math.abs(x.inset - c.inset) <= 4));
343
+ if (v) {
344
+ v.items.push(c);
345
+ v.inset ??= c.inset;
346
+ }
347
+ else variants.push({ radius: c.radius, inset: c.inset, border: c.border, shadow: c.shadow, fill: c.fill, items: [c] });
348
+ }
349
+ variants.sort((a, b) => b.items.length - a.items.length);
350
+ if (variants.length > CARD_VARIANTS) {
351
+ const desc = (v) => `radius ${v.radius}px${v.inset != null ? `, inset ${v.inset}px` : ""}${v.border ? ", border" : ""}${v.shadow ? ", shadow" : ""}${v.fill ? ", filled" : ""}`;
352
+ const off = variants.slice(CARD_VARIANTS);
353
+ out.push({
354
+ severity: variants.length >= CARD_FAIL_VARIANTS ? "fail" : "warn",
355
+ section: byIndex(off[0].items[0].section),
356
+ what: `cards in ${variants.length} styles: ${variants.slice(0, CARD_VARIANTS).map((v) => `${desc(v)} (${v.items.length}×)`).join("; ")} are the norm; ${off.slice(0, 4).map((v) => `${secs(v.items) || "?"}: ${desc(v)}`).join("; ")}`,
357
+ fix: "One card: one radius, one inset, one way of lifting it (a ground, a border or a shadow, not a mix); a tile that should stand out does it with its content or one inverted ground.",
358
+ });
359
+ }
360
+ return out;
361
+ }
362
+
363
+ // [{ id, width, section: { index, heading } | null, severity: "fail" | "warn", what, fix }] for one width's metrics.
364
+ // pageLevel: the width that judges the whole page (runFloor: the widest). Checks that don't change with width
365
+ // (one template repeated, flat ground, mono numbers, placeholder copy) aren't repeated at the tablet widths, and
366
+ // SYSTEM_DRIFT and PHOTO_REPEAT run only there.
367
+ export function floorChecks(metrics, { width = metrics?.width ?? 1440, kind = "marketing", pageLevel = width >= 1200 } = {}) {
368
+ const checks = [];
369
+ const add = (id, severity, section, what, fix) => checks.push({ id, width, section: ref(section), severity, what, fix });
370
+ const phone = width < PHONE;
371
+ const tabletRepeat = !phone && !pageLevel;
372
+ // Tablet (768–1199): a heading block stacked over a full-width visual is the expected layout there, so a bare
373
+ // half beside a heading warns instead of failing (Linear at 1024: 224–320px beside its section intros).
374
+ const tablet = !phone && width < 1200;
375
+ const marketing = kind !== "app";
376
+ const all = metrics.sections ?? [];
377
+ const byIndex = (i) => all.find((s) => s.index === i);
378
+ const content = contentSections(metrics);
379
+
380
+ // DEAD_SPACE: empty bands, between or inside sections.
381
+ const lim = deadLimits(width);
382
+ const airy = (metrics.imagery ?? 0) + (metrics.texture ?? 0) >= AIRY_MATERIAL;
383
+ const deadFix = `Close it to about ${Math.round(lim.warn * 0.6)}px, one step of the section rhythm, or put something there that earns the space (an image, a pulled quote, a change of ground).`;
384
+ const deadWarns = [];
385
+ for (const b of metrics.bands ?? []) {
386
+ const k = b.edge ? EDGE_ALLOW : 1;
387
+ const sev = airy ? (b.height > lim.fail * k ? "warn" : null) : b.height > lim.fail * k ? "fail" : b.height > lim.warn * k ? "warn" : null;
388
+ if (!sev) continue;
389
+ const [a, z] = [byIndex(b.sections?.[0]), byIndex(b.sections?.[b.sections.length - 1])];
390
+ const where = a && z && a !== z ? `between ${label(a)} and ${label(z)}` : a ? `inside ${label(a)}` : `at y=${b.y}`;
391
+ const item = { b, a: a ?? z ?? null, where, limit: Math.round((airy ? lim.fail : lim.warn) * k) };
392
+ if (sev === "warn") deadWarns.push(item);
393
+ else add("DEAD_SPACE", "fail", item.a, `${b.height}px of nothing ${where} (limit ${item.limit}px at ${width})`, deadFix);
394
+ }
395
+ // Several warnings are one finding: the page's rhythm is loose.
396
+ if (deadWarns.length > 2) {
397
+ const big = deadWarns.reduce((x, y) => (y.b.height > x.b.height ? y : x));
398
+ add("DEAD_SPACE", "warn", big.a, `${deadWarns.length} empty bands over ${Math.min(...deadWarns.map((d) => d.limit))}px (largest ${big.b.height}px ${big.where})`, airy ? "Air around strong pictures can be a choice; check each band reads as a pause, not a hole." : deadFix);
399
+ } else for (const d of deadWarns) add("DEAD_SPACE", "warn", d.a, `${d.b.height}px of nothing ${d.where} (limit ${d.limit}px at ${width})`, deadFix);
400
+
401
+ for (const s of content) {
402
+ // HALF_EMPTY (side-by-side layouts only exist on wider screens).
403
+ if (!phone) {
404
+ const se = s.sideEmpty ?? {};
405
+ const le = s.largestEmpty ?? {};
406
+ const seShare = s.frame?.height ? se.px / (2 * s.frame.height) : 0;
407
+ if (se.px >= SIDE_WARN.px && seShare >= SIDE_WARN.share)
408
+ add("HALF_EMPTY", se.px >= SIDE_FAIL.px && seShare >= SIDE_FAIL.share && !tablet ? "fail" : "warn", s, `${label(s)}: the ${se.side} half is bare for ${se.px}px beside the content on the other side`, `Use the ${se.side} side: set the paragraph, a figure or a key number beside the heading, or centre the heading block over what follows.`);
409
+ else if (["left", "right", "centre"].includes(le.where) && le.share >= EMPTY_RECT_WARN && le.height >= EMPTY_RECT_MIN_H)
410
+ add("HALF_EMPTY", le.share >= EMPTY_RECT_FAIL && !tablet ? "fail" : "warn", s, `${label(s)}: an empty ${le.width}×${le.height}px area on the ${le.where} (${Math.round(le.share * 100)}% of the section)`, `Fill or remove the bare ${le.where} area: move content into it, widen what sits beside it, or tighten the section.`);
411
+ else if (s.layout?.columns === 2 && s.balance && s.balance.ratio < BALANCE_FAIL)
412
+ add("HALF_EMPTY", "fail", s, `${label(s)}: two columns, but one holds ${Math.round(s.balance.ratio * 100)}% of the other's ink`, "Balance the columns: give the light side real content (a figure, a list, an image) or make it one column.");
413
+ }
414
+ // TOO_EMPTY / TOO_BUSY: the two sides of the complexity band.
415
+ if (s.ink < TOO_EMPTY_WARN)
416
+ add("TOO_EMPTY", s.ink < TOO_EMPTY_FAIL ? "fail" : "warn", s, `${label(s)}: only ${Math.round(s.ink * 100)}% of it carries anything`, "Give it substance (an image, a figure, real detail) or fold it into its neighbour.");
417
+ const busy = phone ? TOO_BUSY.phone : TOO_BUSY.desktop;
418
+ if (s.detail > busy.warn)
419
+ add("TOO_BUSY", s.detail > busy.fail ? "fail" : "warn", s, `${label(s)}: text and lines cover ${Math.round(s.detail * 100)}% of it`, "Thin it out: fewer items, more air between groups, or split it into two sections.");
420
+ }
421
+
422
+ // EMPTY_COLUMNS: a product board whose columns are mostly blank (cards in a framed panel ink the coarse grid,
423
+ // so TOO_EMPTY and HALF_EMPTY can't see it). Chrome sections too: a board can sit anywhere.
424
+ for (const s of all) {
425
+ if (s.empty) continue;
426
+ for (const b of s.boards ?? []) {
427
+ const cols = b.columns ?? [];
428
+ if (cols.length < 3) continue;
429
+ const blank = cols.filter((c) => c.fill < COLUMN_BLANK);
430
+ const bare = cols.filter((c) => c.fill < COLUMN_BARE);
431
+ const mean = b.mean ?? cols.reduce((a, c) => a + c.fill, 0) / cols.length;
432
+ const fail = (blank.length >= 2 && blank.length >= COLUMN_BLANK_SHARE * cols.length) || mean < COLUMN_MEAN_FAIL;
433
+ const warn = blank.length >= 2 || bare.length >= 1 || mean < COLUMN_MEAN_WARN;
434
+ if (!fail && !warn) continue;
435
+ const pct = (v) => `${Math.round(v * 100)}%`;
436
+ const which = (blank.length ? blank : bare).map((c) => cols.indexOf(c) + 1);
437
+ add(
438
+ "EMPTY_COLUMNS",
439
+ fail ? "fail" : "warn",
440
+ s,
441
+ `${label(s)}: a ${b.width}×${b.height}px board with ${cols.length} columns ${b.columnHeight ?? ""}px tall, content in ${cols.map((c) => pct(c.fill)).join(" / ")} of their height (average ${pct(mean)}); column${which.length > 1 ? "s" : ""} ${which.join(", ")} mostly blank`,
442
+ "Fill the board like a real day in the product (several items per column, a varied mix), shorten the columns to their content, or show fewer columns.",
443
+ );
444
+ }
445
+ }
446
+
447
+ // SAME_TEMPLATE: adjacent sections built the same way, or one build used three times or more.
448
+ if (!phone && marketing && !tabletRepeat) {
449
+ const key = (s) => {
450
+ const sig = s.layout?.signature;
451
+ // A single column of content says nothing about the template.
452
+ if (!sig || !/[2-9]|\d{2}/.test(sig.split(":")[1] ?? "")) return null;
453
+ return sig + ((s.imagery ?? 0) >= IMAGERY_DISTINCT ? "+img" : "");
454
+ };
455
+ const pairs = [];
456
+ for (let i = 1; i < content.length; i++) if (key(content[i]) && key(content[i]) === key(content[i - 1])) pairs.push([content[i - 1], content[i]]);
457
+ const uses = new Map();
458
+ for (const s of content) if (key(s)) uses.set(key(s), [...(uses.get(key(s)) ?? []), s]);
459
+ const triple = [...uses.values()].filter((v) => v.length >= 3);
460
+ const sev = pairs.length >= 2 || triple.length ? "fail" : "warn";
461
+ const describe = (s) => {
462
+ const L = s.layout;
463
+ const rows = L.rows?.filter((r) => r.cols >= 2).map((r) => `${r.cols} side by side`) ?? [];
464
+ return `${L.align === "C" ? "centred" : L.align === "L" ? "left-led" : "headless"} heading over ${rows.join(", then ") || "one column"}`;
465
+ };
466
+ const fix = "Build the next one differently: a full-width figure, a centred statement, a split with an image, a list, a quote; not another heading over the same boxes.";
467
+ for (const [a, b] of pairs) add("SAME_TEMPLATE", sev, b, `${label(a)} and ${label(b)} are built the same way (${describe(b)})`, fix);
468
+ for (const v of triple) if (!pairs.some(([, b]) => v.includes(b))) add("SAME_TEMPLATE", "fail", v[0], `${v.length} sections share one build (${describe(v[0])}): ${v.map((s) => `§${s.index}`).join(", ")}`, fix);
469
+ }
470
+
471
+ // MONOTONE / NO_MATERIAL: one flat ground, nothing to look at.
472
+ const top = tabletRepeat ? null : (metrics.bgRuns ?? [])[0];
473
+ const imagery = metrics.imagery ?? 0;
474
+ if (marketing && top && top.color !== "image" && top.share >= MONOTONE_SHARE && imagery < MONOTONE_IMAGERY)
475
+ add("MONOTONE", "fail", null, `one background (${top.color}) covers ${Math.round(top.share * 100)}% of the page height and imagery ${(imagery * 100).toFixed(1)}%`, "Vary the ground: one or two sections on a contrasting band (dark, tinted or photographic), or imagery that breaks the flat colour.");
476
+ // Product UI built in code counts as material: it is the product on show.
477
+ const material = imagery + (metrics.texture ?? 0) + (metrics.productUi ?? 0);
478
+ if (marketing && !tabletRepeat && material < NO_MATERIAL)
479
+ add("NO_MATERIAL", "fail", null, `imagery, illustration, texture and product UI cover ${(material * 100).toFixed(1)}% of the page`, "Add material: photography, illustration, a texture or a large product image (licensed, or made for this project).");
480
+
481
+ // MONO_MISUSE: monospace for times, prices and figures in a sans/serif page.
482
+ const mono = (!tabletRepeat && metrics.mono) || {};
483
+ if ((mono.share ?? 0) < MONO_LED && (mono.numeric ?? 0) >= MONO_WARN_N) {
484
+ const fail = mono.numeric >= MONO_FAIL_N && mono.numeric / Math.max(1, mono.count) >= MONO_FAIL_SHARE && marketing;
485
+ const eg = (mono.snippets ?? []).filter((m) => m.numeric).slice(0, 4).map((m) => `"${short(m.text, 16)}"`).join(", ");
486
+ const sec = byIndex((mono.snippets ?? []).find((m) => m.numeric)?.section);
487
+ add("MONO_MISUSE", fail ? "fail" : "warn", sec ?? null, `${mono.numeric} numbers set in monospace outside code (${eg}${(mono.snippets ?? [])[0]?.family ? `, ${mono.snippets[0].family}` : ""})`, "Set times, prices and figures in the body face with font-variant-numeric: tabular-nums; keep monospace for code.");
488
+ }
489
+
490
+ // PLACEHOLDER: copy that was never written.
491
+ const ph = tabletRepeat ? [] : (metrics.placeholders ?? []);
492
+ if (ph.length) {
493
+ const strong = ph.filter((p) => STRONG_PLACEHOLDER.test(p.match));
494
+ const list = (strong.length ? strong : ph).slice(0, 3).map((p) => `"${short(p.text, 40)}"`).join(", ");
495
+ add("PLACEHOLDER", strong.length ? "fail" : "warn", byIndex((strong[0] ?? ph[0]).section) ?? null, `placeholder-looking copy (${[...new Set(ph.map((p) => p.match))].join(", ")}): ${list}`, "Write the real thing: a sample studio with a name, a real-sounding person, the product's own domain.");
496
+ }
497
+
498
+ // CLIPPED: text cut off by its box, or painted over by a neighbour.
499
+ const clipped = metrics.clipped ?? [];
500
+ const covered = clipped.filter((c) => c.covered);
501
+ const roundCut = clipped.filter((c) => !c.covered && c.small && c.rounded && !c.intended);
502
+ const smallCut = clipped.filter((c) => !c.covered && c.small && !c.rounded && !c.intended);
503
+ const tinyEllipsis = clipped.filter((c) => c.intended && c.width < 40);
504
+ if (covered.length) add("CLIPPED", "fail", byIndex(covered[0].section) ?? null, `${covered.length} small round label${covered.length > 1 ? "s" : ""} painted over by the next one (${covered.slice(0, 4).map((c) => `"${c.text}"`).join(", ")})`, "Make room for the text: less overlap between the stacked circles, smaller initials, or bigger circles.");
505
+ if (roundCut.length) add("CLIPPED", "fail", byIndex(roundCut[0].section) ?? null, `text cut off inside ${roundCut.length} small rounded element${roundCut.length > 1 ? "s" : ""} (${roundCut.slice(0, 4).map((c) => `"${c.text}" in ${c.width}×${c.height}`).join(", ")})`, "Size the box to its text (or the text to the box) so nothing is cut off.");
506
+ if (smallCut.length) add("CLIPPED", "warn", byIndex(smallCut[0].section) ?? null, `text cut off in ${smallCut.length} small box${smallCut.length > 1 ? "es" : ""} (${smallCut.slice(0, 3).map((c) => `"${short(c.text, 16)}" in ${c.width}×${c.height}`).join(", ")})`, "Check these are meant to be cut (a ticker, a reveal); if not, size the box to its text.");
507
+ if (tinyEllipsis.length) add("CLIPPED", "warn", byIndex(tinyEllipsis[0].section) ?? null, `${tinyEllipsis.length} label${tinyEllipsis.length > 1 ? "s" : ""} truncated to under 40px (${tinyEllipsis.slice(0, 3).map((c) => `"${c.text}" in ${c.width}px`).join(", ")})`, "An ellipsis that leaves two letters says nothing: give the label room, abbreviate it on purpose, or drop it at this width.");
508
+
509
+ // PHONE_TALL: a phone section that is one long stacked board or list.
510
+ if (phone) {
511
+ const vh = metrics.vh ?? 844;
512
+ for (const s of all) {
513
+ const st = s.stack;
514
+ if (s.empty || isChrome(s) || !st || (s.rect?.height ?? 0) <= PHONE_TALL_VH * vh) continue;
515
+ if (st.items < PHONE_STACK_ITEMS || st.height < Math.max(PHONE_STACK_VH * vh, PHONE_STACK_SHARE * s.rect.height)) continue;
516
+ // A reference list (docs commands, questions), each entry deep-linkable, is read by scanning and linked to
517
+ // by entry: hiding entries behind tabs would make it worse, so it's only a note. Seen on palate's own docs.
518
+ if ((st.anchored ?? 0) >= 0.6 * st.items) {
519
+ add("PHONE_TALL", "warn", s, `${label(s)} is ${s.rect.height}px tall at ${width} (${(s.rect.height / vh).toFixed(1)} screens): a reference list of ${st.items} linked entries`, "Fine for reference content; keep each entry compact and the rail or a filter close at hand.");
520
+ continue;
521
+ }
522
+ add("PHONE_TALL", "fail", s, `${label(s)} is ${s.rect.height}px tall at ${width} (${(s.rect.height / vh).toFixed(1)} screens), ${st.height}px of it ${st.items} items stacked in one column`, "On phones, don't stack the whole thing: show one column at a time with a switcher (tabs, a horizontal scroller), or a summary with a link to the rest.");
523
+ }
524
+ }
525
+
526
+ // SYSTEM_DRIFT: the page's own system, measured across its sections.
527
+ if (pageLevel && marketing) for (const d of systemDrift(metrics.system, content, byIndex)) add("SYSTEM_DRIFT", d.severity, d.section, d.what, d.fix);
528
+
529
+ // TILE_VOID: a card whose content leaves a large empty run inside it.
530
+ const voids = (metrics.tiles ?? [])
531
+ .filter((t) => !t.foot)
532
+ .map((t) => ({ t, sev: t.run >= TILE_VOID_FAIL.px && t.share >= TILE_VOID_FAIL.share ? "fail" : t.run >= TILE_VOID_WARN.px && t.share >= TILE_VOID_WARN.share ? "warn" : null }))
533
+ .filter((v) => v.sev)
534
+ .sort((a, b) => (a.sev === b.sev ? b.t.share - a.t.share : a.sev === "fail" ? -1 : 1));
535
+ for (const { t, sev } of voids.slice(0, 4)) {
536
+ const s = byIndex(t.section);
537
+ const where = t.where === "between" ? `between ${t.above} and ${t.below}` : t.where === "under" ? `under ${t.above}` : `above ${t.below}`;
538
+ const fix =
539
+ t.where === "between"
540
+ ? "Close the gap: set the stat or line close under its copy (no margin-top: auto pinning it to the bottom), or give the space to the tile's picture, pinned to the tile's bottom edge and cropped by it."
541
+ : t.where === "under"
542
+ ? "Shorten the tile to its content (align-items: start, or a shorter row), or let the visual fill the space: pinned to the bottom edge and cropped by it, not floating."
543
+ : "Move the content up to the tile's top, or fill the space above with the tile's visual.";
544
+ add("TILE_VOID", sev, s ?? null, `${s ? `${label(s)}: ` : ""}a ${t.width}×${t.height}px tile${t.text ? ` ("${short(t.text, 32)}")` : ""} is ${t.run}px empty ${where} (${Math.round(t.share * 100)}% of its height)`, fix);
545
+ }
546
+ if (voids.length > 4) add("TILE_VOID", "warn", null, `${voids.length - 4} more tile${voids.length - 4 > 1 ? "s" : ""} with a void inside at ${width}`, "Check every tile of that set: one frame, content close together, the visual pinned to an edge.");
547
+
548
+ // PHOTO_REPEAT: the same photo shown more than once.
549
+ const repeats = pageLevel ? (metrics.photos ?? []).filter((p) => p.count >= 2) : [];
550
+ if (repeats.length) {
551
+ const fail = repeats.some((p) => p.count >= PHOTO_REPEAT_FAIL) || repeats.length >= PHOTO_REPEAT_FAIL;
552
+ const list = repeats.slice(0, 4).map((p) => `${short(p.name, 40)} ${p.count}× (${p.sections.map((i) => (i == null ? "?" : `§${i}`)).join(", ")}; ${p.sizes.join(", ")})`).join("; ");
553
+ add("PHOTO_REPEAT", fail ? "fail" : "warn", byIndex(repeats[0].sections.find((i) => i != null)) ?? null, `${repeats.length} photo${repeats.length > 1 ? "s" : ""} shown more than once: ${list}`, "Use each photo once: find another real photo for the second place, or show the subject a different way (a detail crop of a different shot, a diagram, a stat).");
554
+ }
555
+
556
+ // BENTO_FLAT, BENTO_TWINS, INVERT_OVERUSE: how a bento grid is composed.
557
+ if (pageLevel && marketing) for (const f of bentoFindings(metrics.grids ?? [], byIndex)) add(f.id, "warn", f.section, f.what, f.fix);
558
+
559
+ // FRAME_SMALL: the hero's product window floating small in the middle.
560
+ const frame = width >= FRAME_MIN_W && marketing ? (metrics.frames ?? []).find((f) => f.hero) : null;
561
+ if (frame && !frame.portrait && !frame.bleeds?.length && frame.share < FRAME_MIN_SHARE) {
562
+ const s = byIndex(frame.section);
563
+ add("FRAME_SMALL", "warn", s ?? null, `${label(s)}: the product ${frame.kind === "screenshot" ? "screenshot" : "window"}${frame.text ? ` ("${short(frame.text, 28)}")` : ""} is ${frame.width}px wide, ${Math.round(frame.share * 100)}% of the hero's ${frame.stage}px, with a margin on every side`, "Make it the largest thing in the hero: about two thirds of the width, running off one edge (the right edge to the viewport, or the bottom into the next band), cropped through its secondary UI (a sidebar's end, extra rows), never through the part the words describe.");
564
+ }
565
+
566
+ // WORDS_CUT: real words cut by a tile's or section's edge.
567
+ const cuts = metrics.wordsCut ?? [];
568
+ for (const c of cuts.slice(0, WORDS_CUT_LIST)) {
569
+ const s = byIndex(c.section);
570
+ add("WORDS_CUT", "fail", s ?? null, `${s ? `${label(s)}: ` : ""}a ${c.kind} ("${short(c.text, 40)}", ${c.fontSize}px) is cut by ${c.by}: ${c.over != null ? `its line runs ${c.over}px past the ${c.axis === "x" ? "side" : "top or bottom"} edge` : `${c.hiddenLines} of its ${c.lines} lines are hidden`}`, "A tile may crop a picture or a UI fragment at its edge, never words: give the text room (a taller or wider tile, a shorter line), move it inside the tile's padding, or drop overflow: hidden from the box around it.");
571
+ }
572
+ if (cuts.length > WORDS_CUT_LIST) add("WORDS_CUT", "fail", null, `${cuts.length - WORDS_CUT_LIST} more heading${cuts.length - WORDS_CUT_LIST > 1 ? "s, paragraphs or numbers" : ", paragraph or number"} cut by an edge at ${width}`, "Check every tile and band with overflow hidden: words stay whole; only pictures and UI fragments bleed.");
573
+
574
+ const rank = { fail: 0, warn: 1 };
575
+ return checks.sort((a, b) => rank[a.severity] - rank[b.severity]);
576
+ }
577
+
578
+ // Bento findings from page-gate.js's grids. [{ id, section, what, fix }]
579
+ const rgbOf = (h) => (typeof h === "string" && /^#[0-9a-f]{6}$/i.test(h) ? [1, 3, 5].map((i) => parseInt(h.slice(i, i + 2), 16)) : null);
580
+ const sameSurface = (a, b) => {
581
+ if ((a.ground == null) !== (b.ground == null) || a.border !== b.border || a.shadow !== b.shadow) return false;
582
+ if (a.ground == null) return true;
583
+ const [x, y] = [rgbOf(a.ground), rgbOf(b.ground)];
584
+ return Boolean(x && y && Math.hypot(x[0] - y[0], x[1] - y[1], x[2] - y[2]) < 12);
585
+ };
586
+ const surfaceOf = (t) => (t.ground == null ? "a picture" : `${t.ground}${t.border ? " with a border" : ""}${t.shadow ? " with a shadow" : ""}`);
587
+ export function bentoFindings(grids, byIndex = () => null) {
588
+ const out = [];
589
+ for (const g of grids) {
590
+ const ts = g.tiles ?? [];
591
+ const speaks = (t) => t.titled || t.words >= 6 || ["ui", "stat", "quote"].includes(t.type);
592
+ if (ts.length < 3 || ts.filter(speaks).length < BENTO_TITLED * ts.length) continue;
593
+ if (ts.filter((t) => t.price).length >= ts.length / 2) continue;
594
+ const s = byIndex(g.section) ?? null;
595
+ const at = s ? `${label(s)}: ` : "";
596
+ const spread = (k) => Math.max(...ts.map((t) => t[k])) / Math.max(1, Math.min(...ts.map((t) => t[k])));
597
+ const areas = ts.map((t) => t.w * t.h);
598
+ const lead = Math.max(...areas) / Math.max(1, Math.min(...areas));
599
+ const one = ts.every((t) => sameSurface(t, ts[0]));
600
+ if (ts.length >= BENTO_MIN_TILES && spread("w") <= BENTO_EQUAL && spread("h") <= BENTO_EQUAL && one && ts.filter((t) => t.type !== "media" && t.titled && t.words >= 6).length >= BENTO_DESCRIBED * ts.length)
601
+ out.push({
602
+ id: "BENTO_FLAT",
603
+ section: s,
604
+ what: `${at}${ts.length} tiles in ${g.rows} rows and ${g.cols} columns, all about ${ts[0].w}×${ts[0].h}px on one surface (${surfaceOf(ts[0])}): no tile leads`,
605
+ fix: "Give the grid one lead tile carrying the main point (two columns or two rows, with the strongest picture or fragment) and let the rest support it, each a different size or kind; or, if the items are equal, set them as a ruled list instead of boxes.",
606
+ });
607
+ else if (lead >= BENTO_LEAD && ts.length <= BENTO_TWINS_MAX) {
608
+ const twins = (g.pairs ?? []).filter(([i, j]) => {
609
+ const [a, b] = [ts[i], ts[j]];
610
+ return a && b && speaks(a) && speaks(b) && Math.abs(a.w - b.w) <= BENTO_TWIN * Math.max(a.w, b.w) + 2 && Math.abs(a.h - b.h) <= BENTO_TWIN * Math.max(a.h, b.h) + 2 && sameSurface(a, b) && a.type === b.type;
611
+ });
612
+ if (twins.length)
613
+ out.push({
614
+ id: "BENTO_TWINS",
615
+ section: s,
616
+ what: `${at}${twins.length} pair${twins.length > 1 ? "s" : ""} of neighbouring tiles look the same (one size, one surface, one kind of content): ${twins.slice(0, 3).map(([i, j]) => `"${short(ts[i].text, 20)}" and "${short(ts[j].text, 20)}" (${ts[i].w}×${ts[i].h}px, ${ts[i].type}, ${surfaceOf(ts[i])})`).join("; ")}`,
617
+ fix: "Make neighbours contrast: a picture tile beside a text tile, a stat beside a fragment, a different size, or the grid's one inverted tile; never two identical boxes side by side.",
618
+ });
619
+ }
620
+ const inv = ts.filter((t) => t.inverse && speaks(t));
621
+ const grounded = ts.filter((t) => t.ground != null);
622
+ if (inv.length >= 2 && inv.length < grounded.length)
623
+ out.push({
624
+ id: "INVERT_OVERUSE",
625
+ section: s,
626
+ what: `${at}${inv.length} of ${ts.length} tiles are on the inverse ground (${[...new Set(inv.map((t) => t.ground))].slice(0, 3).join(", ")}): ${inv.slice(0, 4).map((t) => `"${short(t.text, 20)}"`).join(", ")}`,
627
+ fix: "Keep one inverted tile per grid, the accent on the tile that matters most; set the others on the grid's normal surface.",
628
+ });
629
+ }
630
+ return out;
631
+ }
632
+
633
+ // Renders a route at each width and runs the floor. { pass, checks, metrics: { [width]: metrics } }.
634
+ // shots: { [width]: [{ path, full }] }: screenshots to take from the page already open at that width (the gate's
635
+ // tablet views), so they cost no extra load.
636
+ export async function runFloor(dir, route = "/", { url, widths = FLOOR_WIDTHS, kind = "marketing", shots = {} } = {}) {
637
+ return withBrowser(dir, { url }, async ({ base, browser }) => {
638
+ const metrics = {};
639
+ const checks = [];
640
+ const widest = Math.max(...widths);
641
+ for (const width of widths) {
642
+ // Reduced motion: the floor judges what is there, not whether a reveal fired before the measurement.
643
+ const { page, ctx } = await openPage(browser, base, route, { width, reducedMotion: true });
644
+ try {
645
+ const sections = await findSections(page);
646
+ const m = await page.evaluate(`(${gateScript})(${JSON.stringify({ sections, cell: CELL })})`);
647
+ metrics[width] = m;
648
+ checks.push(...floorChecks(m, { width, kind, pageLevel: width === widest }));
649
+ // Craft edge cases (photo crops, clipped display ink, hyphen breaks, wrapped labels, double numbering).
650
+ m.craft = await measureCraft(page, { sections });
651
+ checks.push(...craftChecks(m.craft, { width }));
652
+ for (const s of [].concat(shots[width] ?? [])) await (s.full ? shootFull(page, s.path) : page.screenshot({ path: s.path })).catch(() => {});
653
+ } finally {
654
+ await ctx.close();
655
+ }
656
+ }
657
+ return { pass: !checks.some((c) => c.severity === "fail"), checks, metrics };
658
+ });
659
+ }
660
+
661
+ // The floor on a page that's already open (palate section reuses its own page loads instead of loading again).
662
+ export async function floorOnPage(page, { width, sections, kind = "marketing", pageLevel = false } = {}) {
663
+ const m = await page.evaluate(`(${gateScript})(${JSON.stringify({ sections, cell: CELL })})`);
664
+ const checks = floorChecks(m, { width, kind, pageLevel });
665
+ m.craft = await measureCraft(page, { sections });
666
+ checks.push(...craftChecks(m.craft, { width }));
667
+ return checks;
668
+ }
669
+
670
+ // Short Markdown lines for reports: what, where, the fix.
671
+ export function floorLines(result) {
672
+ const checks = result?.checks ?? [];
673
+ if (!checks.length) return ["- Floor: nothing clearly wrong (no dead bands, empty halves, repeated templates, flat ground, placeholder or clipped text)."];
674
+ const fails = checks.filter((c) => c.severity === "fail").length;
675
+ const head = `- Floor: ${fails ? `**${fails} fail${fails > 1 ? "s" : ""}**` : "passes"}${checks.length - fails ? `, ${checks.length - fails} warning${checks.length - fails > 1 ? "s" : ""}` : ""}`;
676
+ return [head, ...checks.map((c) => ` - ${c.severity === "fail" ? "**FAIL**" : "warn"} ${c.id} @${c.width}${c.section ? ` §${c.section.index}` : ""}: ${c.what}. Fix: ${c.fix}`)];
677
+ }