@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,923 @@
1
+ // `palate match <image|url|figma link> [--route /x] [--width 1440]`: build from the user's own design, faithfully.
2
+ // Everywhere else palate designs its own look; here the user's screenshot, mockup or Figma frame is the spec, and
3
+ // palate's taste (archetypes, the blueprint, the house-look check, the similarity gate) steps aside. The build
4
+ // mustn't "improve" the design: what differs from it is OFF, however good it looks.
5
+ //
6
+ // 1. The source becomes .palate/match/<route>/reference.png (a PNG or JPG as given, a page captured at the width,
7
+ // or a Figma frame through the REST API when FIGMA_TOKEN is set), plus parts for a tall design.
8
+ // 2. palate measures what a still image shows (the colours by share, the surfaces and the content blocks, the
9
+ // content's span) and writes a fidelity brief: spec.json first, every value tagged observed, inferred or
10
+ // substituted; then the build in the project's own stack; states and the phone layout are inferred and listed.
11
+ // 3. `--check` renders the build at the design's width, writes the two side by side and a difference heatmap
12
+ // (tops aligned; a height difference is said, not hidden), measures what it can (palette distance, text sizes
13
+ // against the spec, content strings, motion, contrast and clipping as warnings) and writes one judge packet
14
+ // for a fresh subagent: content, geometry, type, assets, spacing, colour, details, motion, each MATCH, CLOSE or
15
+ // OFF. `--answers` gives the verdict: READY, READY WITH SUBSTITUTIONS, NOT READY (what's off, most visible
16
+ // first, with the fix) or UNVERIFIED. Two repair rounds at most, like the gate; a worse round can be restored.
17
+ import fs from "node:fs";
18
+ import path from "node:path";
19
+ import { createRequire } from "node:module";
20
+ import { acquireBrowser } from "../pool.mjs";
21
+ import { HIDE_DEV, MOTION_INIT, openPage, scrollThrough, settle, shootFull, stamp, withBrowser } from "../look.mjs";
22
+ import { cropImage, decodePng, writePng } from "../png.mjs";
23
+ import { normalizeRoute } from "../shoot.mjs";
24
+ import { readJson } from "./common.mjs";
25
+ import { matchRoot, matchedRoute } from "./matched.mjs";
26
+
27
+ const slash = (p) => String(p).replace(/\\/g, "/");
28
+ export const MAX_ROUNDS = 2;
29
+ export const ORDER = ["content", "geometry", "type", "assets", "spacing", "colour", "details", "motion"];
30
+ const TAGS = ["observed", "inferred", "substituted"];
31
+ const PART_H = 1200;
32
+ const MAX_PARTS = 6;
33
+
34
+ // ---- the source
35
+
36
+ // A Figma link (file key and node id), a page URL, or an image file.
37
+ export function parseSource(raw) {
38
+ const s = String(raw ?? "").trim();
39
+ if (!s) return null;
40
+ const fig = s.match(/^(?:https?:\/\/)?(?:www\.)?figma\.com\/(?:file|design|proto|board)\/([A-Za-z0-9]+)[^?#]*(?:\?([^#]*))?/i);
41
+ if (fig) {
42
+ const node = new URLSearchParams(fig[2] ?? "").get("node-id");
43
+ return { kind: "figma", file: fig[1], node: node ? node.replace(/-/g, ":") : null, label: s.split("?")[0] + (node ? `?node-id=${node}` : "") };
44
+ }
45
+ if (/^https?:\/\//i.test(s)) return { kind: "url", url: s, label: s };
46
+ if (/\.(png|jpe?g|webp)$/i.test(s)) return { kind: "image", file: path.resolve(s), label: slash(s) };
47
+ return { kind: "unknown", label: s };
48
+ }
49
+
50
+ const NO_TOKEN = "Export the frame as PNG in Figma (select the frame, Export, PNG at 1x) and run palate match <file.png>, or set FIGMA_TOKEN (a personal access token with file read access) so palate can fetch it.";
51
+
52
+ // The frame as PNG through Figma's images endpoint. Never says the token, even in an error.
53
+ export async function figmaFrame(src, { token = process.env.FIGMA_TOKEN, fetchImpl = globalThis.fetch, scale = 1 } = {}) {
54
+ if (!token) return { ok: false, why: `No FIGMA_TOKEN set, so palate can't fetch the frame. ${NO_TOKEN}` };
55
+ if (!src.node) return { ok: false, why: "That link names a file, not a frame. In Figma, right-click the frame, choose Copy link to selection, and pass that link (it carries node-id)." };
56
+ const scrub = (s) => String(s ?? "").split(token).join("[token]");
57
+ const api = `https://api.figma.com/v1/images/${src.file}?ids=${encodeURIComponent(src.node)}&format=png&scale=${scale}`;
58
+ let res;
59
+ try {
60
+ res = await fetchImpl(api, { headers: { "X-Figma-Token": token } });
61
+ } catch (e) {
62
+ return { ok: false, why: `Figma could not be reached (${scrub(e.message)}). ${NO_TOKEN}` };
63
+ }
64
+ if (res.status === 403) return { ok: false, why: "Figma refused the token (403): it may be expired or unable to read this file. Check FIGMA_TOKEN, or export the frame as PNG." };
65
+ if (res.status === 404) return { ok: false, why: "Figma has no file with that key (404). Check the link, or export the frame as PNG." };
66
+ if (res.status === 429) return { ok: false, why: "Figma is rate-limiting requests (429). Wait a minute and run palate match again, or export the frame as PNG." };
67
+ if (!res.ok) return { ok: false, why: `Figma answered ${res.status}. Export the frame as PNG instead.` };
68
+ const body = await res.json().catch(() => ({}));
69
+ if (body.err) return { ok: false, why: `Figma said: ${scrub(body.err)}. Export the frame as PNG instead.` };
70
+ const link = body.images?.[src.node] ?? Object.values(body.images ?? {})[0];
71
+ if (!link) return { ok: false, why: "Figma rendered nothing for that frame (it may be empty, hidden or not a frame). Export it as PNG instead." };
72
+ let img;
73
+ try {
74
+ img = await fetchImpl(link);
75
+ } catch (e) {
76
+ return { ok: false, why: `The rendered frame could not be downloaded (${scrub(e.message)}).` };
77
+ }
78
+ if (!img.ok) return { ok: false, why: `The rendered frame could not be downloaded (${img.status}).` };
79
+ return { ok: true, buffer: Buffer.from(await img.arrayBuffer()) };
80
+ }
81
+
82
+ // The design's width in CSS pixels. A screenshot 2400 px or wider is read as @2x (a retina capture or a 2x
83
+ // export) unless --width says otherwise.
84
+ export function designWidth(natural, given) {
85
+ if (given) return { width: Math.round(given), scale: natural / given, assumed: false };
86
+ if (natural >= 2400) return { width: Math.round(natural / 2), scale: 2, assumed: true };
87
+ return { width: natural, scale: 1, assumed: false };
88
+ }
89
+
90
+ // Any image Chrome can decode, as PNG at its own size and at the design's CSS width. Up to 16,000 CSS px tall.
91
+ async function normalize(browser, buf, mime, given) {
92
+ const page = await browser.newPage();
93
+ try {
94
+ const meta = await page.evaluate(async (src) => {
95
+ const img = new Image();
96
+ img.src = src;
97
+ await img.decode();
98
+ return { w: img.naturalWidth, h: img.naturalHeight };
99
+ }, `data:${mime};base64,${buf.toString("base64")}`);
100
+ const dw = designWidth(meta.w, given);
101
+ const cssH = Math.min(16_000, Math.round(meta.h / dw.scale));
102
+ const out = await page.evaluate(
103
+ async ([src, w, h, scale]) => {
104
+ const img = new Image();
105
+ img.src = src;
106
+ await img.decode();
107
+ const draw = (cw, ch) => {
108
+ const c = document.createElement("canvas");
109
+ c.width = cw;
110
+ c.height = ch;
111
+ const g = c.getContext("2d");
112
+ g.imageSmoothingQuality = "high";
113
+ g.fillStyle = "#fff";
114
+ g.fillRect(0, 0, cw, ch);
115
+ g.drawImage(img, 0, 0, img.naturalWidth, Math.min(img.naturalHeight, ch * (img.naturalWidth / cw)), 0, 0, cw, ch);
116
+ return c.toDataURL("image/png");
117
+ };
118
+ return { full: scale === 1 ? null : draw(img.naturalWidth, Math.round(h * scale)), css: draw(w, h) };
119
+ },
120
+ [`data:${mime};base64,${buf.toString("base64")}`, dw.width, cssH, dw.scale],
121
+ );
122
+ const b64 = (d) => Buffer.from(d.split(",")[1], "base64");
123
+ return { natural: meta, ...dw, height: cssH, cut: Math.round(meta.h / dw.scale) > cssH, css: b64(out.css), full: out.full ? b64(out.full) : null };
124
+ } finally {
125
+ await page.close();
126
+ }
127
+ }
128
+
129
+ async function captureUrl(browser, url, width) {
130
+ const { BOT_WALL } = await import("../robots.mjs");
131
+ const ctx = await browser.newContext({ viewport: { width, height: 900 } });
132
+ try {
133
+ const page = await ctx.newPage();
134
+ await page.addInitScript(HIDE_DEV);
135
+ await page.goto(url, { waitUntil: "networkidle", timeout: 45_000 }).catch(() => page.waitForLoadState("load").catch(() => {}));
136
+ await page.waitForTimeout(700);
137
+ const text = await page.evaluate(() => `${document.title} ${document.body?.innerText?.slice(0, 2000) ?? ""}`).catch(() => "");
138
+ if (BOT_WALL.test(text) && text.length < 1500) throw new Error(`${url} showed a bot check instead of the page. Take a screenshot of it yourself and run palate match <file.png>.`);
139
+ await scrollThrough(page);
140
+ await settle(page);
141
+ const { encodePng } = await import("../png.mjs");
142
+ const { captureFull } = await import("../look.mjs");
143
+ return encodePng(await captureFull(page, { maxHeight: 16_000 }));
144
+ } finally {
145
+ await ctx.close();
146
+ }
147
+ }
148
+
149
+ // ---- what a still image shows, measured (pure: { width, height, channels, data } in, numbers out)
150
+
151
+ const px = (img, x, y) => {
152
+ const i = (y * img.width + x) * img.channels;
153
+ return img.channels >= 3 ? [img.data[i], img.data[i + 1], img.data[i + 2]] : [img.data[i], img.data[i], img.data[i]];
154
+ };
155
+ const dist = (a, b) => Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2]);
156
+ export const hex = (c) => `#${c.map((v) => Math.round(v).toString(16).padStart(2, "0")).join("")}`;
157
+ const unhex = (h) => [1, 3, 5].map((i) => parseInt(String(h).slice(i, i + 2), 16));
158
+
159
+ // CIE76 ΔE in Lab: under 2 can't be seen, under 5 is close, over 10 is another colour.
160
+ export function deltaE(a, b) {
161
+ const lab = (c) => {
162
+ const [r, g, bl] = c.map((v) => {
163
+ v /= 255;
164
+ return v > 0.04045 ? ((v + 0.055) / 1.055) ** 2.4 : v / 12.92;
165
+ });
166
+ const f = (t) => (t > 0.008856 ? Math.cbrt(t) : 7.787 * t + 16 / 116);
167
+ const x = f((r * 0.4124 + g * 0.3576 + bl * 0.1805) / 0.95047), y = f(r * 0.2126 + g * 0.7152 + bl * 0.0722), z = f((r * 0.0193 + g * 0.1192 + bl * 0.9505) / 1.08883);
168
+ return [116 * y - 16, 500 * (x - y), 200 * (y - z)];
169
+ };
170
+ const A = lab(typeof a === "string" ? unhex(a) : a), B = lab(typeof b === "string" ? unhex(b) : b);
171
+ return Math.hypot(A[0] - B[0], A[1] - B[1], A[2] - B[2]);
172
+ }
173
+
174
+ // The image's colours by share: 5 bits a channel, near colours merged into the larger, the top `max` over 0.1%.
175
+ export function palette(img, { max = 12, min = 0.001 } = {}) {
176
+ const step = Math.max(1, Math.round(Math.sqrt((img.width * img.height) / 400_000)));
177
+ const buckets = new Map();
178
+ let n = 0;
179
+ for (let y = 0; y < img.height; y += step)
180
+ for (let x = 0; x < img.width; x += step) {
181
+ const c = px(img, x, y);
182
+ const k = ((c[0] >> 3) << 10) | ((c[1] >> 3) << 5) | (c[2] >> 3);
183
+ const b = buckets.get(k) ?? buckets.set(k, { n: 0, r: 0, g: 0, b: 0 }).get(k);
184
+ b.n++, (b.r += c[0]), (b.g += c[1]), (b.b += c[2]);
185
+ n++;
186
+ }
187
+ const list = [...buckets.values()].sort((a, b) => b.n - a.n).map((b) => ({ n: b.n, rgb: [b.r / b.n, b.g / b.n, b.b / b.n] }));
188
+ const kept = [];
189
+ for (const c of list) {
190
+ const near = kept.find((k) => dist(k.rgb, c.rgb) < 24);
191
+ if (near) near.n += c.n;
192
+ else if (kept.length < 64) kept.push({ ...c });
193
+ }
194
+ return kept.sort((a, b) => b.n - a.n).filter((c) => c.n / n >= min).slice(0, max).map((c) => ({ hex: hex(c.rgb), share: +(c.n / n).toFixed(4) }));
195
+ }
196
+
197
+ // Each row's ground (its most common colour) and where something sits on it.
198
+ function rows(img) {
199
+ const step = img.width > 800 ? 2 : 1;
200
+ const out = [];
201
+ for (let y = 0; y < img.height; y++) {
202
+ const count = new Map();
203
+ for (let x = 0; x < img.width; x += step * 2) {
204
+ const c = px(img, x, y);
205
+ const k = ((c[0] >> 3) << 10) | ((c[1] >> 3) << 5) | (c[2] >> 3);
206
+ count.set(k, (count.get(k) ?? 0) + 1);
207
+ }
208
+ const k = [...count.entries()].sort((a, b) => b[1] - a[1])[0][0];
209
+ // The ground's own colour: the mean of the pixels in its bucket, not the bucket's centre.
210
+ const sum = [0, 0, 0];
211
+ let m = 0;
212
+ for (let x = 0; x < img.width; x += step * 2) {
213
+ const c = px(img, x, y);
214
+ if ((((c[0] >> 3) << 10) | ((c[1] >> 3) << 5) | (c[2] >> 3)) !== k) continue;
215
+ (sum[0] += c[0]), (sum[1] += c[1]), (sum[2] += c[2]), m++;
216
+ }
217
+ const bg = sum.map((v) => v / m);
218
+ let ink = 0, left = -1, right = -1;
219
+ for (let x = 0; x < img.width; x += step) {
220
+ if (dist(px(img, x, y), bg) > 48) {
221
+ ink++;
222
+ if (left < 0) left = x;
223
+ right = x;
224
+ }
225
+ }
226
+ out.push({ bg, ink: ink / Math.ceil(img.width / step), left, right });
227
+ }
228
+ return out;
229
+ }
230
+
231
+ // Rough structure: surfaces (runs of one ground colour) and content blocks (runs of rows with something on them,
232
+ // split where 16 px or more stand empty), each with its span across the width.
233
+ export function measureImage(img) {
234
+ const R = rows(img);
235
+ const surfaces = [];
236
+ for (let y = 0; y < R.length; y++) {
237
+ const last = surfaces.at(-1);
238
+ if (last && dist(last.rgb, R[y].bg) < 18) last.height++;
239
+ else surfaces.push({ y, height: 1, rgb: R[y].bg });
240
+ }
241
+ // Runs under 6 px (a divider, a rule) belong to the surface around them.
242
+ const solid = [];
243
+ for (const s of surfaces) {
244
+ const last = solid.at(-1);
245
+ if (s.height < 6 && last) last.height += s.height;
246
+ else if (last && dist(last.rgb, s.rgb) < 18) last.height += s.height;
247
+ else solid.push({ ...s });
248
+ }
249
+ const blocks = [];
250
+ let open = null, empty = 0;
251
+ for (let y = 0; y < R.length; y++) {
252
+ const on = R[y].ink >= 0.002 && R[y].left >= 0;
253
+ if (on) {
254
+ if (!open) open = { y, end: y, left: R[y].left, right: R[y].right };
255
+ open.end = y;
256
+ open.left = Math.min(open.left, R[y].left);
257
+ open.right = Math.max(open.right, R[y].right);
258
+ empty = 0;
259
+ } else if (open && ++empty >= 16) {
260
+ blocks.push(open);
261
+ open = null;
262
+ }
263
+ }
264
+ if (open) blocks.push(open);
265
+ const B = blocks.filter((b) => b.end - b.y >= 3).map((b) => ({ y: b.y, height: b.end - b.y + 1, left: b.left, right: b.right }));
266
+ const left = B.length ? Math.min(...B.map((b) => b.left)) : 0;
267
+ const right = B.length ? Math.max(...B.map((b) => b.right)) : img.width - 1;
268
+ return {
269
+ width: img.width,
270
+ height: img.height,
271
+ palette: palette(img),
272
+ surfaces: solid.map((s) => ({ y: s.y, height: s.height, colour: hex(s.rgb) })),
273
+ blocks: B,
274
+ span: { left, right, width: right - left + 1 },
275
+ };
276
+ }
277
+
278
+ // ---- the difference
279
+
280
+ // Pixel difference with tops aligned, over the rows both have. heat: the design faded to grey with differing
281
+ // pixels in red; rows only the build has in magenta, rows the build lacks in cyan. Returns the share of changed
282
+ // pixels overall and per 100 px strip.
283
+ export function diffImages(a, b, { threshold = 40 } = {}) {
284
+ const w = Math.min(a.width, b.width);
285
+ const overlap = Math.min(a.height, b.height);
286
+ const H = Math.max(a.height, b.height);
287
+ const data = Buffer.alloc(w * H * 3);
288
+ const strips = [];
289
+ let changed = 0;
290
+ for (let y = 0; y < H; y++) {
291
+ let rowChanged = 0;
292
+ for (let x = 0; x < w; x++) {
293
+ const o = (y * w + x) * 3;
294
+ if (y < overlap) {
295
+ const p = px(a, x, y), q = px(b, x, y);
296
+ const d = Math.max(Math.abs(p[0] - q[0]), Math.abs(p[1] - q[1]), Math.abs(p[2] - q[2]));
297
+ const g = 170 + ((p[0] * 0.3 + p[1] * 0.59 + p[2] * 0.11) / 255) * 70;
298
+ if (d > threshold) {
299
+ rowChanged++;
300
+ const t = Math.min(1, 0.45 + d / 255);
301
+ data[o] = 255 * t + g * (1 - t);
302
+ data[o + 1] = g * (1 - t);
303
+ data[o + 2] = g * (1 - t) + 30 * t;
304
+ } else data[o] = data[o + 1] = data[o + 2] = g;
305
+ } else {
306
+ const only = a.height > b.height ? px(a, x, y) : px(b, x, y);
307
+ const g = 150 + ((only[0] * 0.3 + only[1] * 0.59 + only[2] * 0.11) / 255) * 80;
308
+ if (a.height > b.height) [data[o], data[o + 1], data[o + 2]] = [g * 0.45, g, g];
309
+ else [data[o], data[o + 1], data[o + 2]] = [g, g * 0.45, g];
310
+ }
311
+ }
312
+ if (y < overlap) {
313
+ changed += rowChanged;
314
+ const s = Math.floor(y / 100);
315
+ strips[s] = (strips[s] ?? 0) + rowChanged / w / 100;
316
+ }
317
+ }
318
+ return {
319
+ width: w,
320
+ height: H,
321
+ overlap,
322
+ designHeight: a.height,
323
+ buildHeight: b.height,
324
+ changed: overlap ? +(changed / (w * overlap)).toFixed(4) : 1,
325
+ strips: strips.map((s, i) => ({ y: i * 100, changed: +s.toFixed(3) })),
326
+ image: { width: w, height: H, channels: 3, data },
327
+ };
328
+ }
329
+
330
+ // The strips that differ most, adjacent ones merged: where to look first.
331
+ export function hotspots(strips, { max = 4, min = 0.04 } = {}) {
332
+ const runs = [];
333
+ for (const s of strips) {
334
+ const last = runs.at(-1);
335
+ if (s.changed >= min && last && last.end === s.y) (last.end = s.y + 100), (last.sum += s.changed), last.n++;
336
+ else if (s.changed >= min) runs.push({ y: s.y, end: s.y + 100, sum: s.changed, n: 1 });
337
+ }
338
+ return runs.map((r) => ({ y: r.y, end: r.end, changed: +(r.sum / r.n).toFixed(3) })).sort((a, b) => b.changed * (b.end - b.y) - a.changed * (a.end - a.y)).slice(0, max);
339
+ }
340
+
341
+ // Each main colour of the design (over 1% of it) beside the nearest colour in the build.
342
+ export function palettePairs(design, build) {
343
+ return design.filter((c) => c.share >= 0.01).map((c) => {
344
+ const near = build.map((b) => ({ ...b, d: deltaE(c.hex, b.hex) })).sort((x, y) => x.d - y.d)[0];
345
+ return { design: c.hex, share: c.share, build: near?.hex ?? null, buildShare: near?.share ?? 0, dE: near ? +near.d.toFixed(1) : null };
346
+ });
347
+ }
348
+
349
+ // Half size, side by side: the design, the build and the difference, 16 px apart, on a light grey ground.
350
+ function sideBySide(images, { y, height }) {
351
+ const half = images.map((img) => {
352
+ const h = Math.max(0, Math.min(height, img.height - y));
353
+ const w2 = Math.floor(img.width / 2), h2 = Math.floor(h / 2);
354
+ const data = Buffer.alloc(w2 * h2 * 3);
355
+ for (let r = 0; r < h2; r++)
356
+ for (let c = 0; c < w2; c++) {
357
+ let s0 = 0, s1 = 0, s2 = 0;
358
+ for (const [dx, dy] of [[0, 0], [1, 0], [0, 1], [1, 1]]) {
359
+ const p = px(img, c * 2 + dx, y + r * 2 + dy);
360
+ (s0 += p[0]), (s1 += p[1]), (s2 += p[2]);
361
+ }
362
+ const o = (r * w2 + c) * 3;
363
+ (data[o] = s0 / 4), (data[o + 1] = s1 / 4), (data[o + 2] = s2 / 4);
364
+ }
365
+ return { width: w2, height: h2, channels: 3, data };
366
+ });
367
+ const gap = 16;
368
+ const W = half.reduce((n, h) => n + h.width, 0) + gap * (half.length + 1);
369
+ const H = Math.max(1, ...half.map((h) => h.height)) + gap * 2;
370
+ const data = Buffer.alloc(W * H * 3, 226);
371
+ let x0 = gap;
372
+ for (const h of half) {
373
+ for (let r = 0; r < h.height; r++) h.data.copy(data, ((r + gap) * W + x0) * 3, r * h.width * 3, (r + 1) * h.width * 3);
374
+ x0 += h.width + gap;
375
+ }
376
+ return { width: W, height: H, channels: 3, data };
377
+ }
378
+
379
+ // ---- the spec
380
+
381
+ // spec.json's values checked: each tagged, each substitution with what it replaces and why, the phone layout
382
+ // and the states listed as inferred, text with a size so palate can check the type.
383
+ export function checkSpec(spec) {
384
+ const values = Array.isArray(spec?.values) ? spec.values : [];
385
+ const errors = [];
386
+ const warnings = [];
387
+ if (!values.length) errors.push("spec.json has no values: write what you read from the design before building");
388
+ for (const v of values) {
389
+ const name = v?.what ?? v?.text ?? JSON.stringify(v).slice(0, 40);
390
+ if (!TAGS.includes(v?.tag)) errors.push(`"${name}" isn't tagged observed, inferred or substituted`);
391
+ else if (v.tag === "substituted" && (!v.why || !v.instead)) errors.push(`the substitution "${name}" needs instead (what the design uses) and why`);
392
+ else if (v.tag === "inferred" && !v.why) warnings.push(`the inferred "${name}" has no why`);
393
+ }
394
+ const inferred = values.filter((v) => v?.tag === "inferred");
395
+ const says = (re) => inferred.some((v) => re.test(`${v.what ?? ""} ${v.value ?? ""}`));
396
+ if (!says(/phone|mobile|390|small screen|narrow/i)) warnings.push("the phone layout isn't listed as inferred");
397
+ if (!says(/hover|focus|pressed|active|state/i)) warnings.push("the states (hover, focus, pressed) aren't listed as inferred");
398
+ const texts = values.filter((v) => v?.tag === "observed" && typeof v.text === "string" && v.text.trim());
399
+ if (!texts.some((v) => Number(v.size) > 0)) warnings.push("no observed text has a size: palate can't check the type against the spec");
400
+ return {
401
+ errors,
402
+ warnings,
403
+ texts: texts.map((v) => ({ what: v.what ?? v.text, text: v.text.trim(), size: Number(v.size) || null, y: Number.isFinite(Number(v.y)) && v.y !== null && v.y !== "" ? Number(v.y) : null, weight: v.weight ?? null })),
404
+ substitutions: values.filter((v) => v?.tag === "substituted").map((v) => ({ what: v.what ?? "?", value: v.value ?? "", instead: v.instead ?? "", why: v.why ?? "" })),
405
+ inferred: inferred.map((v) => ({ what: v.what ?? "?", value: v.value ?? "", why: v.why ?? "" })),
406
+ };
407
+ }
408
+
409
+ // Text found in the build against the spec: missing text is OFF; a size off by more than 20% is OFF, by more
410
+ // than 2 px CLOSE; a top more than 48 px away is a geometry hint.
411
+ export function compareTexts(spec, found, { height = 900 } = {}) {
412
+ const far = Math.max(48, Math.round(height * 0.05));
413
+ return spec.map((s, i) => {
414
+ const f = found[i];
415
+ if (!f) return { ...s, result: "OFF", id: "content", what: `"${s.text.slice(0, 60)}" is in the design but not on the page` };
416
+ const notes = [];
417
+ let result = "MATCH";
418
+ let id = "type";
419
+ if (s.size && f.size) {
420
+ const d = Math.abs(f.size - s.size);
421
+ if (d > Math.max(3, s.size * 0.2)) result = "OFF";
422
+ else if (d > 2) result = "CLOSE";
423
+ if (d > 0.5) notes.push(`size ${f.size}px, the design ${s.size}px`);
424
+ }
425
+ const dy = s.y != null && f.y != null ? f.y - s.y : 0;
426
+ if (Math.abs(dy) > 12) notes.push(`top at ${Math.round(f.y)}px, the design ${s.y}px`);
427
+ // Found, but far from where the design has it: moved.
428
+ if (Math.abs(dy) > far) [result, id] = ["OFF", "geometry"];
429
+ return { ...s, result, id, found: f, dy, what: notes.join(", ") || null };
430
+ });
431
+ }
432
+
433
+ // ---- rounds
434
+
435
+ const roundsFile = (dir, route) => path.join(matchRoot(dir, route), "rounds.json");
436
+ const readRounds = (dir, route) => readJson(roundsFile(dir, route), { rounds: [], best: null });
437
+
438
+ // ---- 1. the design in
439
+
440
+ export async function matchImport(dir, rawSource, { route: rawRoute = "/", width } = {}) {
441
+ const route = normalizeRoute(rawRoute);
442
+ const src = parseSource(rawSource);
443
+ if (!src || src.kind === "unknown") throw new Error(`palate match takes a PNG or JPG (a screenshot or a Figma export), a page URL or a Figma frame link; got "${String(rawSource ?? "").slice(0, 80)}".`);
444
+ if (src.kind === "image" && !fs.existsSync(src.file)) throw new Error(`No image at ${slash(src.file)}.`);
445
+ const given = width ? Number(width) : null;
446
+ let buf;
447
+ let mime = "image/png";
448
+ if (src.kind === "figma") {
449
+ const f = await figmaFrame(src);
450
+ if (!f.ok) return { stopped: true, text: [`# Match ${route}: stopped`, "", f.why].join("\n") };
451
+ buf = f.buffer;
452
+ } else if (src.kind === "image") {
453
+ buf = fs.readFileSync(src.file);
454
+ mime = /\.jpe?g$/i.test(src.file) ? "image/jpeg" : /\.webp$/i.test(src.file) ? "image/webp" : "image/png";
455
+ }
456
+ const root = matchRoot(dir, route);
457
+ fs.mkdirSync(root, { recursive: true });
458
+ fs.writeFileSync(path.join(dir, ".palate", ".gitignore"), "*\n");
459
+ for (const f of fs.readdirSync(root)) if (/^(reference|part-\d+)/.test(f)) fs.rmSync(path.join(root, f), { force: true });
460
+ const { browser, release } = await acquireBrowser();
461
+ let n;
462
+ try {
463
+ if (src.kind === "url") buf = await captureUrl(browser, src.url, given ?? 1440);
464
+ n = await normalize(browser, buf, mime, src.kind === "url" ? (given ?? 1440) : given);
465
+ } finally {
466
+ await release();
467
+ }
468
+ const reference = path.join(root, "reference.png");
469
+ fs.writeFileSync(reference, n.css);
470
+ if (n.full) fs.writeFileSync(path.join(root, "reference-full.png"), n.full);
471
+ const img = decodePng(n.css);
472
+ const measure = measureImage(img);
473
+ fs.writeFileSync(path.join(root, "measure.json"), JSON.stringify(measure, null, 1));
474
+ const parts = [];
475
+ if (img.height > PART_H * 1.3)
476
+ for (let y = 0, i = 1; y < img.height && i <= MAX_PARTS; y += PART_H, i++) {
477
+ const f = path.join(root, `part-${i}.png`);
478
+ writePng(f, cropImage(img, { y, height: PART_H }));
479
+ parts.push({ file: f, y, end: Math.min(img.height, y + PART_H) });
480
+ }
481
+ // spec.json: palate's own observations to start from. Kept when it was written for this same source.
482
+ const specFile = path.join(root, "spec.json");
483
+ const prev = readJson(path.join(root, "match.json"), null);
484
+ const sameSource = prev?.source?.label === src.label;
485
+ if (fs.existsSync(specFile) && !sameSource) fs.renameSync(specFile, path.join(root, `spec.before-${stamp()}.json`));
486
+ if (!fs.existsSync(specFile)) fs.writeFileSync(specFile, JSON.stringify(starterSpec(src, n, measure), null, 1));
487
+ const m = { route, source: { kind: src.kind, label: src.label }, width: n.width, height: n.height, natural: n.natural, scale: +n.scale.toFixed(3), assumedScale: n.assumed, cut: n.cut, reference, referenceFull: n.full ? path.join(root, "reference-full.png") : null, parts, spec: specFile, brief: path.join(root, "brief.md"), at: new Date().toISOString() };
488
+ fs.writeFileSync(path.join(root, "match.json"), JSON.stringify(m, null, 1));
489
+ if (!sameSource) fs.rmSync(roundsFile(dir, route), { force: true });
490
+ fs.writeFileSync(m.brief, matchBrief(dir, m, measure));
491
+ const lines = [
492
+ `# Match ${route}: the user's design is the spec`,
493
+ "",
494
+ `- The design: ${slash(reference)} (${n.width}×${n.height} CSS px${n.scale !== 1 ? `, from a ${n.natural.w}×${n.natural.h} image read at ${+n.scale.toFixed(2)}x${n.assumed ? ": assumed @2x; pass --width if it isn't" : ""}` : ""})`,
495
+ n.cut ? `- ⚠ The design is taller than 16,000 px: palate kept the top 16,000.` : null,
496
+ parts.length ? `- In ${parts.length} parts for reading: ${parts.map((p) => slash(p.file)).join(", ")}` : null,
497
+ `- Measured: ${measure.palette.slice(0, 4).map((c) => `${c.hex} ${(c.share * 100).toFixed(c.share < 0.1 ? 1 : 0)}%`).join(", ")}; ${measure.surfaces.length} surface${measure.surfaces.length === 1 ? "" : "s"}, ${measure.blocks.length} content block${measure.blocks.length === 1 ? "" : "s"}; content spans x ${measure.span.left}–${measure.span.right}.`,
498
+ `- Brief: ${slash(m.brief)}`,
499
+ "",
500
+ `Read the brief and follow it: spec.json first (${slash(specFile)}), then the build, then palate match --check --route ${route}. palate's taste, archetypes, blueprint and house-look check don't apply to this route.`,
501
+ ];
502
+ return { text: lines.filter((l) => l != null).join("\n"), match: m };
503
+ }
504
+
505
+ function starterSpec(src, n, measure) {
506
+ const v = (what, value, extra = {}) => ({ what, value, tag: "observed", by: "palate", ...extra });
507
+ return {
508
+ source: src.label,
509
+ width: n.width,
510
+ height: n.height,
511
+ values: [
512
+ v("design size", `${n.width}×${n.height} CSS px`),
513
+ v("ground (most of the image)", measure.palette[0]?.hex ?? null, { share: measure.palette[0]?.share }),
514
+ ...measure.palette.slice(1, 6).map((c, i) => v(`colour ${i + 2} by share`, c.hex, { share: c.share })),
515
+ ...measure.surfaces.filter((s) => s.height >= 24).slice(0, 12).map((s, i) => v(`surface ${i + 1}`, s.colour, { y: s.y, height: s.height })),
516
+ v("content span", `x ${measure.span.left}–${measure.span.right} (${measure.span.width}px)`),
517
+ ],
518
+ };
519
+ }
520
+
521
+ function matchBrief(dir, m, measure) {
522
+ const route = m.route;
523
+ const W = m.width;
524
+ const surfaces = measure.surfaces.filter((s) => s.height >= 24).slice(0, 12);
525
+ const blocks = measure.blocks.slice(0, 24);
526
+ const margin = W - 1 - measure.span.right;
527
+ return [
528
+ `# Match ${route}: build it from the user's design`,
529
+ "",
530
+ "The user's design is the spec. Build it faithfully, in this project's own stack. palate's taste doesn't apply here: no blueprint, no archetypes, no house-look check, no similarity gate against other projects, and no improvements. If something in the design looks wrong to you, build it as designed and say so in your hand-off. Never fix it silently.",
531
+ "",
532
+ "## The design",
533
+ `- Source: ${m.source.label} (${{ image: "an image file", url: `a page captured at ${W} px`, figma: "a Figma frame" }[m.source.kind]})`,
534
+ `- The reference: ${slash(m.reference)} (${W}×${m.height} CSS px${m.scale !== 1 ? `, read at ${m.scale}x from a ${m.natural.w}×${m.natural.h} image${m.referenceFull ? `; full size: ${slash(m.referenceFull)}` : ""}` : ""})`,
535
+ m.parts.length ? `- Read it in parts, top to bottom: ${m.parts.map((p) => `${slash(p.file)} (rows ${p.y}–${p.end})`).join(", ")}` : null,
536
+ `- The design is ${W} px wide: palate renders the build at ${W} px to check it.`,
537
+ "",
538
+ "## What palate measured from the image (observed, rough: confirm each against the image)",
539
+ `- Colours by share: ${measure.palette.map((c) => `${c.hex} ${(c.share * 100).toFixed(1)}%`).join(", ")}. The first is likely the ground; small dark shares are likely text; a saturated small share is likely the accent.`,
540
+ `- Surfaces (bands of one ground colour, y in px): ${surfaces.map((s) => `${s.y}–${s.y + s.height} ${s.colour}`).join(" · ") || "one ground"}`,
541
+ `- Content blocks (rows with something on them, split where 16 px stand empty): ${blocks.map((b) => `y ${b.y}–${b.y + b.height} (x ${b.left}–${b.right})`).join(" · ")}${measure.blocks.length > blocks.length ? ` and ${measure.blocks.length - blocks.length} more in measure.json` : ""}`,
542
+ `- Content spans x ${measure.span.left}–${measure.span.right} (${measure.span.width} px; margins ${measure.span.left} and ${margin} px).`,
543
+ "",
544
+ "## 1. Write spec.json before any code",
545
+ `${slash(m.spec)} starts with palate's measurements. Add every value you read from the design, each tagged:`,
546
+ "- **observed**: read from the image: colours (sample them; don't guess), sizes, spacing, radii, borders, positions. Give each piece of text its exact `text`, its `size` in px and its `y` (top, in px from the top of the design): palate checks the build against them.",
547
+ "- **inferred**: what a still image can't show: hover, focus and pressed states, the phone layout, menus, hidden content, motion. Say what you chose and why.",
548
+ "- **substituted**: what you can't use as it is: a paid or unknown font replaced by the closest Google or OFL font, a photo you don't have, an icon set swapped. Give `instead` (what the design uses) and `why`.",
549
+ "```json",
550
+ '{"values": [',
551
+ ' {"what": "headline", "text": "<the words, exactly>", "size": 56, "y": 118, "value": "56px/1.05, 600, -0.02em, #0f1720", "tag": "observed"},',
552
+ ' {"what": "display font", "value": "Inter Tight", "instead": "<the design\'s font>", "why": "paid font; the closest free grotesk", "tag": "substituted"},',
553
+ ' {"what": "primary button hover", "value": "a step darker", "why": "not visible in a still", "tag": "inferred"},',
554
+ ' {"what": "phone layout", "value": "one column, same order", "why": "the design shows only desktop", "tag": "inferred"}',
555
+ "]}",
556
+ "```",
557
+ "",
558
+ "## 2. Build it",
559
+ "- In this project's stack, with its components and tokens: use a token where its value equals the design's; add tokens for the rest. Don't restyle the project's other pages.",
560
+ "- Order: geometry first (the container, columns and each block's top from the measurements), then type, then spacing, then colour and details.",
561
+ "- Content word for word: every label, number and link as the design shows it. Nothing added, nothing reworded, nothing removed.",
562
+ "- Fonts: the design's own when it is free (Google Fonts, OFL); else the closest free one, listed as substituted. Match size, weight, line height and letter spacing, not only the family.",
563
+ "- Images and icons: the project's own assets when it has them; else a stand-in of the same size and crop, listed as substituted. Icons from the same set when it is free (lucide, Heroicons, Phosphor, Hugeicons…), else the closest, listed.",
564
+ `- While you build, compare your page with the design yourself: \`palate shoot ${route} --widths ${W} --fold\` and open both images.`,
565
+ "",
566
+ "## 3. What the design doesn't show (inferred: list each in spec.json)",
567
+ "- States: hover, focus-visible and pressed on every control, in the design's own language; loading, empty and error where the screen has data.",
568
+ "- The phone layout at 390: the same content in the same order, in the design's spirit; nothing clipped, no sideways scroll.",
569
+ "- Accessibility: alt text, labels, visible focus. If the design itself fails contrast, build it as designed and tell the user: palate warns about it and never changes the design.",
570
+ "",
571
+ "## 4. The check (a fresh judge, never you)",
572
+ `\`palate match --check --route ${route}\`: palate renders the build at ${W} px beside the design, a difference image and its measurements, and writes one judge packet. Run it with a fresh subagent ("Read <packet> and follow it exactly"), then \`palate match --answers <dir> --route ${route}\`. NOT READY: fix what it lists, most visible first, and check again; two repair rounds at most.`,
573
+ "",
574
+ "## Hand-off",
575
+ "Tell the user the verdict in one line, then the substitutions (what you used instead, and why) and what you inferred (states, the phone layout), so they can correct them.",
576
+ ]
577
+ .filter((l) => l != null)
578
+ .join("\n");
579
+ }
580
+
581
+ // ---- 2. the check
582
+
583
+ // Where each spec text sits in the build: the text node that holds it (so the second line of a headline gets
584
+ // its own top), else the deepest element whose text holds it.
585
+ const TEXT_SCRIPT = (items) => {
586
+ const norm = (s) => String(s ?? "").replace(/\s+/g, " ").trim().toLowerCase();
587
+ const visible = (el) => {
588
+ if (!el || el.closest("script,style,noscript,template")) return false;
589
+ const r = el.getBoundingClientRect();
590
+ const cs = getComputedStyle(el);
591
+ return r.width > 0 && r.height > 0 && cs.visibility !== "hidden" && cs.display !== "none";
592
+ };
593
+ const nodes = [];
594
+ const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
595
+ for (let n = walker.nextNode(); n; n = walker.nextNode()) if (n.textContent.trim() && visible(n.parentElement)) nodes.push(n);
596
+ const all = [...document.body.querySelectorAll("*")].filter(visible);
597
+ const facts = (el, rect) => {
598
+ const cs = getComputedStyle(el);
599
+ return { size: parseFloat(cs.fontSize), weight: cs.fontWeight, family: cs.fontFamily.split(",")[0].replace(/["']/g, "").trim(), lineHeight: cs.lineHeight, colour: cs.color, y: Math.round(rect.top + scrollY), x: Math.round(rect.left) };
600
+ };
601
+ return items.map(({ text: want, y: at }) => {
602
+ const w = norm(want);
603
+ if (!w) return null;
604
+ const re = new RegExp(w.split(" ").map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("\\s+"), "i");
605
+ // The same words can appear twice (a nav link and a button): the one nearest the spec's top, a whole match first.
606
+ const hits = [];
607
+ for (const n of nodes) {
608
+ const m = n.textContent.match(re);
609
+ if (!m) continue;
610
+ const range = document.createRange();
611
+ range.setStart(n, m.index);
612
+ range.setEnd(n, m.index + m[0].length);
613
+ const rect = range.getClientRects()[0] ?? range.getBoundingClientRect();
614
+ hits.push({ f: facts(n.parentElement, rect), whole: norm(n.textContent) === w });
615
+ }
616
+ if (hits.length) {
617
+ const score = (h) => (at == null ? 0 : Math.abs(h.f.y - at)) + (h.whole ? 0 : 1);
618
+ return hits.sort((a, b) => score(a) - score(b))[0].f;
619
+ }
620
+ // Text split across elements ("<span>$</span> tracewell init"): the deepest element that holds all of it.
621
+ let best = null;
622
+ for (const el of all) {
623
+ const t = norm(el.innerText);
624
+ if (t.includes(w) && (!best || t.length <= best.len)) best = { el, len: t.length };
625
+ }
626
+ return best ? facts(best.el, best.el.getBoundingClientRect()) : null;
627
+ });
628
+ };
629
+
630
+ async function renderBuild(dir, route, at, { url, width, texts }) {
631
+ const axe = createRequire(import.meta.url).resolve("axe-core/axe.min.js");
632
+ const Floor = await import("./floor.mjs").catch(() => null);
633
+ const CLIP = new Set(["CLIPPED", "TEXT_CLIPPED_EFFECT", "LABEL_WRAP", "HYPHEN_BREAK"]);
634
+ return withBrowser(dir, { url }, async ({ base, browser }) => {
635
+ const out = { warnings: [] };
636
+ // As a reader sees it (motion on, scrolled through so reveals fire, settled), at the design's width.
637
+ const { page, ctx, errors } = await openPage(browser, base, route, { width, init: MOTION_INIT });
638
+ try {
639
+ const overlay = await page.evaluate(() => document.querySelector("vite-error-overlay")?.shadowRoot?.querySelector(".message")?.textContent ?? null).catch(() => null);
640
+ if (overlay) throw new Error(`The page did not build: ${overlay.trim().slice(0, 300)}`);
641
+ out.build = path.join(at, "build.png");
642
+ await shootFull(page, out.build);
643
+ out.texts = await page.evaluate(TEXT_SCRIPT, texts.map((t) => ({ text: t.text, y: t.y }))).catch(() => texts.map(() => null));
644
+ out.motion = await page.evaluate(() => {
645
+ const M = window.__palateMotion;
646
+ const running = (document.getAnimations?.() ?? []).filter((a) => a.playState === "running").length;
647
+ return M ? { entrances: M.entrances, observers: M.io, waapi: M.waapi, running } : { entrances: 0, observers: 0, waapi: 0, running };
648
+ }).catch(() => null);
649
+ out.overflow = await page.evaluate(() => document.documentElement.scrollWidth - innerWidth).catch(() => 0);
650
+ await page.addScriptTag({ path: axe }).catch(() => {});
651
+ out.contrast = await page.evaluate(`axe.run(document, { runOnly: ["color-contrast"] }).then(r => r.violations.flatMap(v => v.nodes.map(n => (n.html.replace(/<[^>]+>/g, '').trim().slice(0, 40)) + ' ' + ((n.any[0] && n.any[0].data && n.any[0].data.contrastRatio) || '?') + ':1')))`).catch(() => []);
652
+ if (Floor?.floorOnPage) {
653
+ const { findSections } = await import("../look.mjs");
654
+ const sections = await findSections(page).catch(() => []);
655
+ out.clipped = (await Floor.floorOnPage(page, { width, sections }).catch(() => [])).filter((c) => CLIP.has(c.id)).map((c) => `${c.id}: ${c.what}`);
656
+ }
657
+ out.errors = errors.slice(0, 3);
658
+ } finally {
659
+ await ctx.close();
660
+ }
661
+ // The phone: inferred, so it only warns.
662
+ const p = await openPage(browser, base, route, { width: 390, reducedMotion: true });
663
+ try {
664
+ out.phone = path.join(at, "phone.png");
665
+ await shootFull(p.page, out.phone, { maxHeight: 8000 });
666
+ out.phoneOverflow = await p.page.evaluate(() => document.documentElement.scrollWidth - innerWidth).catch(() => 0);
667
+ } finally {
668
+ await p.ctx.close();
669
+ }
670
+ return out;
671
+ });
672
+ }
673
+
674
+ export async function matchCheck(dir, rawRoute = "/", { url } = {}) {
675
+ const route = normalizeRoute(rawRoute);
676
+ const m = matchedRoute(dir, route);
677
+ if (!m) throw new Error(`${route} has no design to match yet: run palate match <image|url|figma link> --route ${route} first.`);
678
+ const root = matchRoot(dir, route);
679
+ const rr = readRounds(dir, route);
680
+ const n = rr.rounds.length;
681
+ const at = path.join(root, `check-${n}-${stamp()}`);
682
+ fs.mkdirSync(at, { recursive: true });
683
+ const spec = checkSpec(readJson(m.spec, {}));
684
+ const r = await renderBuild(dir, route, at, { url, width: m.width, texts: spec.texts });
685
+ const ref = decodePng(fs.readFileSync(m.reference));
686
+ const build = decodePng(fs.readFileSync(r.build));
687
+ const d = diffImages(ref, build);
688
+ const diffFile = path.join(at, "diff.png");
689
+ writePng(diffFile, d.image);
690
+ const H = Math.max(ref.height, build.height);
691
+ const parts = [];
692
+ for (let y = 0, i = 1; y < H && i <= MAX_PARTS; y += PART_H, i++) {
693
+ const p = { y, end: Math.min(H, y + PART_H), compare: path.join(at, `compare-${i}.png`) };
694
+ const rc = cropImage(ref, { y, height: PART_H });
695
+ const bc = cropImage(build, { y, height: PART_H });
696
+ if (rc) writePng((p.design = path.join(at, `design-${i}.png`)), rc);
697
+ if (bc) writePng((p.build = path.join(at, `build-${i}.png`)), bc);
698
+ writePng(p.compare, sideBySide([ref, build, d.image], { y, height: PART_H }));
699
+ parts.push(p);
700
+ }
701
+ const bm = measureImage(build);
702
+ const dm = readJson(path.join(root, "measure.json"), null) ?? measureImage(ref);
703
+ const pairs = palettePairs(dm.palette, bm.palette);
704
+ const texts = compareTexts(spec.texts, r.texts ?? [], { height: ref.height });
705
+ // What palate measured that is plainly off, whatever the judge says.
706
+ const measuredOff = [];
707
+ for (const t of texts.filter((x) => x.result === "OFF")) {
708
+ const fix = { content: "Add it word for word, where the design has it", type: `Set it to ${t.size}px as the design has it`, geometry: `Move it ${Math.abs(t.dy)}px ${t.dy > 0 ? "up" : "down"}, to where the design has it` }[t.id];
709
+ measuredOff.push({ id: t.id, by: "palate", where: `"${t.text.slice(0, 40)}"`, what: t.what, fix, visibility: t.id === "type" ? 2 : 3 });
710
+ }
711
+ for (const p of pairs.filter((x) => x.share >= 0.05 && x.dE != null && x.dE > 10)) measuredOff.push({ id: "colour", by: "palate", where: `${(p.share * 100).toFixed(0)}% of the design`, what: `the design's ${p.design} has no near colour in the build (nearest ${p.build}, ΔE ${p.dE})`, fix: `Use ${p.design} where the design has it`, visibility: 3 });
712
+ if (build.height < ref.height * 0.85) measuredOff.push({ id: "geometry", by: "palate", where: "the whole page", what: `the build is ${ref.height - build.height}px shorter than the design (${build.height} vs ${ref.height}): something is missing or squeezed`, fix: "Find the band that's missing or shorter in the difference image (cyan rows)", visibility: 3 });
713
+ const unverified = [];
714
+ if (build.height > ref.height * 1.1 && build.height - ref.height > 100) unverified.push({ id: "below the design", what: `the build is ${build.height}px tall and the design ${ref.height}px: rows ${ref.height}–${build.height} aren't in the design (a crop, or the build adds them)` });
715
+ if (m.cut) unverified.push({ id: "below 16,000 px", what: "the design was cut at 16,000 px" });
716
+ const warnings = [];
717
+ if (spec.errors.length) warnings.push(...spec.errors.map((e) => `spec.json: ${e}`));
718
+ warnings.push(...spec.warnings.map((e) => `spec.json: ${e}`));
719
+ if (r.contrast?.length) warnings.push(`${r.contrast.length} element(s) fail colour contrast (WCAG AA): ${r.contrast.slice(0, 3).join(" · ")}. palate doesn't change the design: tell the user.`);
720
+ for (const c of r.clipped ?? []) warnings.push(c);
721
+ if (r.overflow > 1) warnings.push(`At ${m.width}px the page scrolls sideways by ${r.overflow}px.`);
722
+ if (r.phoneOverflow > 1) warnings.push(`On a phone (390) the page scrolls sideways by ${r.phoneOverflow}px.`);
723
+ if (r.errors?.length) warnings.push(`Console errors: ${r.errors.join(" | ").slice(0, 300)}`);
724
+ const moves = r.motion && (r.motion.entrances > 0 || r.motion.waapi > 0 || r.motion.running > 0);
725
+ const state = { route, n, at, width: m.width, design: { file: m.reference, height: ref.height }, build: { file: r.build, height: build.height }, phone: r.phone, diff: { file: diffFile, changed: d.changed, overlap: d.overlap, hotspots: hotspots(d.strips) }, parts, pairs, texts, motion: r.motion, moves, measuredOff, unverified, warnings, substitutions: spec.substitutions, inferred: spec.inferred, specErrors: spec.errors, created: new Date().toISOString() };
726
+ state.packet = path.join(at, "packet-match.md");
727
+ state.out = path.join(at, "answers-match.json");
728
+ fs.writeFileSync(state.packet, matchPacket(state));
729
+ fs.writeFileSync(path.join(at, "state.json"), JSON.stringify(state, null, 1));
730
+ return [
731
+ `# Match check ${route}: round ${n}`,
732
+ "",
733
+ `- Side by side (the design, the build, the difference, half size): ${parts.map((p) => slash(p.compare)).join(", ")}`,
734
+ `- Difference: ${slash(diffFile)}: ${(d.changed * 100).toFixed(1)}% of pixels differ over the ${d.overlap}px both have${build.height !== ref.height ? `; the build is ${build.height}px tall, the design ${ref.height}px` : ""}.`,
735
+ state.diff.hotspots.length ? `- Most different: ${state.diff.hotspots.map((h) => `rows ${h.y}–${h.end} (${Math.round(h.changed * 100)}%)`).join(", ")}` : null,
736
+ ...measuredOff.map((o) => `- ✗ palate measured [${o.id}] ${o.where ? `${o.where}: ` : ""}${o.what}`),
737
+ ...warnings.slice(0, 8).map((w) => `- ! ${w}`),
738
+ "",
739
+ `Run the packet with one fresh subagent: "Read ${slash(state.packet)} and follow it exactly." Never judge it yourself. Then: palate match --answers ${slash(at)} --route ${route}`,
740
+ ]
741
+ .filter((l) => l != null)
742
+ .join("\n");
743
+ }
744
+
745
+ function matchPacket(s) {
746
+ const pct = (x) => `${(x * 100).toFixed(1)}%`;
747
+ return [
748
+ "# Design review: does the build match the design?",
749
+ "",
750
+ "You are an independent reviewer. You made neither image. The first is the user's design: it is the spec. The second is a build of it. Judge fidelity, not taste: where the build differs from the design it is wrong, even when it looks better. Open every image with your image tool before answering.",
751
+ "",
752
+ `## The images (both ${s.width} px wide, tops aligned)`,
753
+ `- The design, whole: ${slash(s.design.file)} (${s.design.height} px tall)`,
754
+ `- The build, whole: ${slash(s.build.file)} (${s.build.height} px tall)`,
755
+ ...s.parts.map((p, i) => `- Part ${i + 1}, rows ${p.y}–${p.end}: the design ${p.design ? slash(p.design) : "(nothing: the design ends above)"} · the build ${p.build ? slash(p.build) : "(nothing: the build ends above)"} · side by side ${slash(p.compare)} (the design, the build, the difference, half size)`),
756
+ `- The difference: ${slash(s.diff.file)}. Red: pixels that differ. Cyan rows: in the design, missing from the build. Magenta rows: only in the build.`,
757
+ `- The build on a phone (390): ${slash(s.phone)}. The design shows no phone layout, so this is inferred.`,
758
+ "",
759
+ "## What palate measured (hints: trust your eyes over these)",
760
+ `- ${pct(s.diff.changed)} of pixels differ over the ${s.diff.overlap} px both images have${s.diff.hotspots.length ? `; most in ${s.diff.hotspots.map((h) => `rows ${h.y}–${h.end} (${Math.round(h.changed * 100)}%)`).join(", ")}` : ""}. Text shifted by a pixel shows red too: a red area is where to look, not a verdict.`,
761
+ s.build.height !== s.design.height ? `- Heights: the design ${s.design.height} px, the build ${s.build.height} px.` : null,
762
+ s.pairs.length ? `- Main colours, the design's beside the build's nearest (ΔE: under 2 can't be seen, over 10 is another colour): ${s.pairs.map((p) => `${p.design} (${pct(p.share)}) → ${p.build ?? "none"}${p.dE != null ? ` ΔE ${p.dE}` : ""}`).join(", ")}` : null,
763
+ s.texts.length ? `- Text from the spec, found in the build: ${s.texts.map((t) => `"${t.text.slice(0, 30)}" ${t.found ? `${t.found.size}px${t.size ? ` (spec ${t.size}px)` : ""} at y ${t.found.y}${t.y != null ? ` (spec ${t.y})` : ""}, ${t.found.family} ${t.found.weight}` : "NOT FOUND"}`).join("; ")}` : "- The spec gives no text with sizes: judge type by eye.",
764
+ `- Motion in the build: ${s.moves ? `yes (${s.motion.entrances} entrance transitions or animations, ${s.motion.running} still running)` : "none"}.`,
765
+ "",
766
+ "## Agreed in the spec (not OFF by themselves)",
767
+ ...(s.substitutions.length ? s.substitutions.map((x) => `- Substituted: ${x.what}: ${x.value} instead of ${x.instead} (${x.why})`) : ["- No substitutions."]),
768
+ ...(s.inferred.length ? s.inferred.map((x) => `- Inferred: ${x.what}: ${x.value}`) : []),
769
+ "A substitution that shows (a stand-in font, a placeholder photo) is CLOSE at best; it is OFF only when done badly (a substitute font so much wider that lines wrap differently, a stand-in of the wrong size).",
770
+ "",
771
+ "## Check in this order: each MATCH, CLOSE, OFF or UNVERIFIED",
772
+ "1. content: every text, label, number, link and image in the design is in the build, word for word, and nothing was added or removed.",
773
+ "2. geometry: the same columns and order; each element in the same place at the same size, within a few pixels.",
774
+ "3. type: family (or its agreed substitute), size, weight, line height, letter spacing, case.",
775
+ "4. assets: images, icons, logos and illustrations: the same ones (or agreed stand-ins), the same crop and size.",
776
+ "5. spacing: the gaps between elements and the padding inside them.",
777
+ "6. colour: grounds, text, borders and accents (palate's colour pairs above help).",
778
+ "7. details: borders, radii, shadows, dividers and the small marks the design shows.",
779
+ `8. motion: the design is a still image. MATCH if the build doesn't move, or only as the design implies (a hover). UNVERIFIED if the build animates something a still can't confirm.${s.moves ? "" : " palate saw no motion in the build."}`,
780
+ "",
781
+ "MATCH: you can't tell them apart at a glance. CLOSE: a small difference a careful reader sees only side by side. OFF: a difference anyone sees, or anything missing, added or moved. UNVERIFIED: the images can't show it.",
782
+ "For every item that isn't MATCH: `where` (the part: \"hero headline\", \"nav, right side\", with rows if you can), `what` (the design vs the build, under 25 words), `fix` (the change to the build, under 20 words) and `visibility` from 1 to 3 (3: anyone sees it at once).",
783
+ "Then `phone`: does the phone view hold up (nothing clipped, no sideways scroll, readable)? It is inferred, so it only warns.",
784
+ "",
785
+ "Answer only from what you see in the images. Be strict: the user will hold the build up against their design. Write the JSON file exactly as specified and nothing else.",
786
+ `Write ${slash(s.out)} as {"items": [{"id": "content", "result": "MATCH"}, {"id": "geometry", "result": "OFF", "where": "...", "what": "...", "fix": "...", "visibility": 3}, ...all eight in order], "phone": {"ok": true, "note": "..."}, "summary": "two sentences"}`,
787
+ ]
788
+ .filter((l) => l != null)
789
+ .join("\n");
790
+ }
791
+
792
+ // ---- 3. the verdict
793
+
794
+ const RESULTS = ["MATCH", "CLOSE", "OFF", "UNVERIFIED"];
795
+ export function matchVerdict(state, answers) {
796
+ const items = ORDER.map((id) => {
797
+ const a = (answers?.items ?? []).find((x) => String(x?.id ?? "").toLowerCase() === id) ?? null;
798
+ const result = RESULTS.find((r) => r === String(a?.result ?? "").trim().toUpperCase()) ?? "UNVERIFIED";
799
+ return { id, result, where: a?.where ?? null, what: a ? (a.what ?? null) : "the judge didn't answer this", fix: a?.fix ?? null, visibility: Number(a?.visibility) || 2, by: "judge" };
800
+ });
801
+ const off = [...items.filter((x) => x.result === "OFF"), ...(state.measuredOff ?? [])];
802
+ const rank = (x) => ORDER.indexOf(x.id);
803
+ off.sort((a, b) => b.visibility - a.visibility || rank(a) - rank(b));
804
+ const unverified = [...items.filter((x) => x.result === "UNVERIFIED").map((x) => ({ id: x.id, what: x.what ?? "the images can't show it" })), ...(state.unverified ?? [])];
805
+ const close = items.filter((x) => x.result === "CLOSE");
806
+ const warnings = [...(state.warnings ?? [])];
807
+ if (answers?.phone && (answers.phone.ok === false || /^no$/i.test(String(answers.phone.ok)))) warnings.push(`Phone layout (inferred): ${answers.phone.note ?? "doesn't hold up"}`);
808
+ const subs = state.substitutions ?? [];
809
+ const verdict = off.length ? "NOT READY" : unverified.length ? "UNVERIFIED" : subs.length ? "READY WITH SUBSTITUTIONS" : "READY";
810
+ return { verdict, items, off, close, unverified, substitutions: subs, inferred: state.inferred ?? [], warnings, score: off.length * 10 + close.length + unverified.length, summary: answers?.summary ?? null };
811
+ }
812
+
813
+ function saveMatchVerdict(dir, route, v, state, { final = false } = {}) {
814
+ const f = path.join(dir, ".palate", "match.json");
815
+ const all = readJson(f, {});
816
+ all[route] = { verdict: v.verdict, final, round: state.n, at: new Date().toISOString(), off: v.off.map((o) => `[${o.id}] ${o.where ? `${o.where}: ` : ""}${o.what ?? ""}`) };
817
+ fs.mkdirSync(path.dirname(f), { recursive: true });
818
+ fs.writeFileSync(f, JSON.stringify(all, null, 1));
819
+ }
820
+
821
+ function repairBrief(state, v, round) {
822
+ return [
823
+ `# Match repair ${round} of ${MAX_ROUNDS}: ${state.route}`,
824
+ "",
825
+ "Make the build match the design. Change only what's listed, most visible first; don't restyle anything else. palate snapshotted the source before this round.",
826
+ `- The design: ${slash(state.design.file)} · the build now: ${slash(state.build.file)} · the difference: ${slash(state.diff.file)}`,
827
+ "",
828
+ "## What's off",
829
+ ...v.off.map((o, i) => `${i + 1}. [${o.id}]${o.where ? ` ${o.where}:` : ""} ${o.what ?? ""}${o.fix ? ` → ${o.fix}` : ""}${o.by === "palate" ? " (palate measured)" : ""}`),
830
+ ...(v.close.length ? ["", "## Close (fix if it's quick)", ...v.close.map((o) => `- [${o.id}]${o.where ? ` ${o.where}:` : ""} ${o.what ?? ""}${o.fix ? ` → ${o.fix}` : ""}`)] : []),
831
+ "",
832
+ `Then palate match --check --route ${state.route} again, and its packet with a fresh subagent.`,
833
+ ].join("\n");
834
+ }
835
+
836
+ export async function matchAnswers(dir, rawRoute = "/", given) {
837
+ const route = normalizeRoute(rawRoute);
838
+ const root = matchRoot(dir, route);
839
+ const dirGiven = given && fs.existsSync(given) && fs.statSync(given).isDirectory() ? given : null;
840
+ const latest = dirGiven ?? (fs.existsSync(root) ? fs.readdirSync(root).filter((x) => x.startsWith("check-")).map((x) => path.join(root, x)).sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs)[0] : null);
841
+ const state = latest ? readJson(path.join(latest, "state.json"), null) : null;
842
+ if (!state) throw new Error(`No match check for ${route} yet: run palate match --check --route ${route} first.`);
843
+ const file = given && !dirGiven && fs.existsSync(given) ? given : state.out;
844
+ const answers = readJson(file, null);
845
+ if (!answers) throw new Error(`No answers yet: the judge writes ${slash(state.out)}.`);
846
+ const v = matchVerdict(state, answers);
847
+ const rr = readRounds(dir, route);
848
+ const prevBest = rr.best != null ? rr.rounds.find((x) => x.n === rr.best) : null;
849
+ const worse = prevBest && v.off.length > prevBest.off;
850
+ let snap = null;
851
+ if (v.verdict === "NOT READY") {
852
+ const { snapshot } = await import("./gate.mjs");
853
+ const s = snapshot(dir, path.join(state.at, "source"));
854
+ if (s.ok) snap = path.join(state.at, "source");
855
+ }
856
+ rr.rounds = [...rr.rounds.filter((x) => x.n !== state.n), { n: state.n, at: state.at, verdict: v.verdict, off: v.off.length, score: v.score, snapshot: snap }];
857
+ if (!prevBest || !worse) rr.best = state.n;
858
+ fs.mkdirSync(root, { recursive: true });
859
+ fs.writeFileSync(roundsFile(dir, route), JSON.stringify(rr, null, 1));
860
+ const repairs = rr.rounds.length - 1;
861
+ saveMatchVerdict(dir, route, v, state, { final: repairs >= MAX_ROUNDS });
862
+ fs.writeFileSync(path.join(state.at, "verdict.json"), JSON.stringify(v, null, 1));
863
+ const line = (o) => `[${o.id}]${o.where ? ` ${o.where}:` : ""} ${o.what ?? ""}${o.fix ? ` → ${o.fix}` : ""}`;
864
+ const out = [
865
+ `# Match ${route}, round ${state.n}: ${v.verdict}`,
866
+ "",
867
+ `- ${v.items.map((x) => `${x.id} ${x.result}`).join(" · ")}`,
868
+ ...(v.off.length ? ["", "What's off, most visible first:", ...v.off.map((o, i) => `${i + 1}. ${line(o)}${o.by === "palate" ? " (palate measured)" : ""}`)] : []),
869
+ ...(v.close.length ? ["", "Close:", ...v.close.map((o) => `- ${line(o)}`)] : []),
870
+ ...(v.unverified.length ? ["", "Not verified (say so to the user):", ...v.unverified.map((u) => `- ${u.id}: ${u.what}`)] : []),
871
+ ...(v.substitutions.length ? ["", "Substitutions (tell the user):", ...v.substitutions.map((x) => `- ${x.what}: ${x.value} instead of ${x.instead} (${x.why})`)] : []),
872
+ ...(v.inferred.length ? ["", "Inferred, not in the design (tell the user; they may correct these):", ...v.inferred.map((x) => `- ${x.what}: ${x.value}`)] : []),
873
+ ...(v.warnings.length ? ["", "Warnings (palate never changes the design for these; tell the user):", ...v.warnings.slice(0, 10).map((w) => `- ${w}`)] : []),
874
+ "",
875
+ ];
876
+ if (v.verdict !== "NOT READY") {
877
+ out.push({ READY: "The build matches the design. Tell the user in one line.", "READY WITH SUBSTITUTIONS": "The build matches the design, with the substitutions above. Tell the user the verdict and each substitution, so they can send the real font or asset.", UNVERIFIED: "What could be checked matches. Tell the user which parts couldn't be checked, and why." }[v.verdict]);
878
+ return out.join("\n");
879
+ }
880
+ if (worse && prevBest?.snapshot) return [...out, `This round is worse than round ${prevBest.n} (${v.off.length} off vs ${prevBest.off}). Roll it back: palate match --restore ${prevBest.n} --route ${route}, then try the repair another way, or stop if this was the last round.`].join("\n");
881
+ if (repairs >= MAX_ROUNDS) return [...out, `That was the last repair round (${MAX_ROUNDS}). Tell the user honestly what still differs (the list above), and the substitutions.`].join("\n");
882
+ const brief = path.join(state.at, "repair.md");
883
+ fs.writeFileSync(brief, repairBrief(state, v, repairs + 1));
884
+ return [...out, `Repair brief: ${slash(brief)}. Do it yourself or with one subagent, then palate match --check --route ${route} again (repair round ${repairs + 1} of ${MAX_ROUNDS}).`].join("\n");
885
+ }
886
+
887
+ export function matchRestore(dir, rawRoute, round) {
888
+ const route = normalizeRoute(rawRoute);
889
+ const r = readRounds(dir, route).rounds.find((x) => x.n === Number(round));
890
+ if (!r?.snapshot || !fs.existsSync(r.snapshot)) throw new Error(`No snapshot for match round ${round} of ${route}.`);
891
+ let n = 0;
892
+ const walk = (d) => {
893
+ for (const e of fs.readdirSync(d, { withFileTypes: true })) {
894
+ const p = path.join(d, e.name);
895
+ if (e.isDirectory()) walk(p);
896
+ else {
897
+ const to = path.join(dir, path.relative(r.snapshot, p));
898
+ fs.mkdirSync(path.dirname(to), { recursive: true });
899
+ fs.copyFileSync(p, to);
900
+ n++;
901
+ }
902
+ }
903
+ };
904
+ walk(r.snapshot);
905
+ return `Restored ${n} files from match round ${round} (${slash(r.snapshot)}). Files added since then are still there.`;
906
+ }
907
+
908
+ // One entry for the CLI and the MCP tool.
909
+ export async function match(dir, source, { route = "/", width, check, answers, restore, url } = {}) {
910
+ if (restore != null) return { text: matchRestore(dir, route, restore) };
911
+ if (answers) {
912
+ const text = await matchAnswers(dir, route, answers === true ? undefined : answers);
913
+ return { text, notReady: /: NOT READY\n/.test(text) };
914
+ }
915
+ if (check) return { text: await matchCheck(dir, route, { url }) };
916
+ if (!source) {
917
+ const m = matchedRoute(dir, route);
918
+ if (m) return { text: `${normalizeRoute(route)} matches ${m.source.label}. Brief: ${slash(m.brief)}. Check the build: palate match --check --route ${normalizeRoute(route)}.` };
919
+ throw new Error("Usage: palate match <image|url|figma link> [--route /x] [--width 1440] · palate match --check [--route /x] · palate match --answers <dir> [--route /x]");
920
+ }
921
+ const r = await matchImport(dir, source, { route, width });
922
+ return { text: r.text, stopped: Boolean(r.stopped) };
923
+ }