@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.
- package/LICENSE +20 -0
- package/README.md +153 -2
- package/bin/palate.mjs +894 -0
- package/kb/anatomy/data-table.md +24 -0
- package/kb/anatomy/empty-state.md +16 -0
- package/kb/anatomy/filter-toolbar.md +19 -0
- package/kb/anatomy/meter.md +16 -0
- package/kb/anatomy/product-window.md +18 -0
- package/kb/anatomy/settings-page.md +18 -0
- package/kb/anatomy/status-indicator.md +17 -0
- package/kb/anatomy/toast-undo.md +16 -0
- package/kb/bench/briefs.json +618 -0
- package/kb/craft/SOURCES.md +48 -0
- package/kb/craft/creativity.md +55 -0
- package/kb/craft/details.md +75 -0
- package/kb/craft/landing-demos.md +46 -0
- package/kb/craft/mistakes.md +73 -0
- package/kb/craft/motion.md +53 -0
- package/kb/craft/principles.md +68 -0
- package/kb/index/discovery.json +1 -0
- package/kb/inspiration/index.json +79 -0
- package/kb/moves/animated-comparison-table.md +58 -0
- package/kb/moves/before-after-slider.md +58 -0
- package/kb/moves/bento-live-tiles.md +57 -0
- package/kb/moves/calm-motion-system.md +63 -0
- package/kb/moves/canvas-grain.md +58 -0
- package/kb/moves/changelog-as-magazine.md +56 -0
- package/kb/moves/code-drawn-diagram.md +56 -0
- package/kb/moves/colophon.md +54 -0
- package/kb/moves/command-palette-site-nav.md +58 -0
- package/kb/moves/confetti-free-celebration.md +58 -0
- package/kb/moves/console-easter-egg.md +57 -0
- package/kb/moves/copy-confirmation.md +57 -0
- package/kb/moves/cursor-reactive-light.md +57 -0
- package/kb/moves/dark-band-contrast-section.md +55 -0
- package/kb/moves/designed-dark-mode.md +58 -0
- package/kb/moves/duotone-photography.md +55 -0
- package/kb/moves/editorial-drop-cap.md +58 -0
- package/kb/moves/editorial-grid-pull-quotes.md +55 -0
- package/kb/moves/engraving-colour-lens.md +64 -0
- package/kb/moves/explorable-control.md +57 -0
- package/kb/moves/feature-films-on-painted-stages.md +58 -0
- package/kb/moves/first-win-onboarding.md +59 -0
- package/kb/moves/footer-live-status.md +56 -0
- package/kb/moves/footer-rewards-the-end.md +58 -0
- package/kb/moves/generative-brand-pattern.md +55 -0
- package/kb/moves/gentle-error-nudge.md +59 -0
- package/kb/moves/halftone-dot-imagery.md +55 -0
- package/kb/moves/hand-drawn-annotations.md +55 -0
- package/kb/moves/hero-paints-itself-in.md +65 -0
- package/kb/moves/honest-progress-narration.md +58 -0
- package/kb/moves/honest-testimonial-wall.md +57 -0
- package/kb/moves/inline-sparklines-in-copy.md +57 -0
- package/kb/moves/inspect-our-taste.md +58 -0
- package/kb/moves/interactive-pricing-calculator.md +59 -0
- package/kb/moves/isometric-illustration-system.md +56 -0
- package/kb/moves/kinetic-variable-type.md +57 -0
- package/kb/moves/konami-secret.md +59 -0
- package/kb/moves/layered-paper-depth.md +56 -0
- package/kb/moves/lightweight-3d-object-hero.md +59 -0
- package/kb/moves/line-engraving-from-image.md +63 -0
- package/kb/moves/link-preview-peek.md +57 -0
- package/kb/moves/live-data-ticker.md +61 -0
- package/kb/moves/made-with-proof.md +51 -0
- package/kb/moves/magnetic-buttons.md +55 -0
- package/kb/moves/marquee-done-right.md +58 -0
- package/kb/moves/moves.yaml +450 -0
- package/kb/moves/museum-painting-backdrop.md +59 -0
- package/kb/moves/one-colour-brand-world.md +56 -0
- package/kb/moves/playful-404.md +58 -0
- package/kb/moves/pointer-highlight-word.md +56 -0
- package/kb/moves/product-in-context.md +56 -0
- package/kb/moves/real-world-data.md +58 -0
- package/kb/moves/referral-reward-reveal.md +59 -0
- package/kb/moves/riso-print-texture.md +57 -0
- package/kb/moves/screenshot-detail-crops.md +56 -0
- package/kb/moves/scroll-progress-chapters.md +57 -0
- package/kb/moves/scroll-scrubbed-product-film.md +62 -0
- package/kb/moves/self-playing-product-demo.md +61 -0
- package/kb/moves/shape-continuity-morph.md +58 -0
- package/kb/moves/sliding-nav-indicator.md +58 -0
- package/kb/moves/sticky-scroll-story.md +56 -0
- package/kb/moves/story-led-long-scroll.md +56 -0
- package/kb/moves/tactile-button.md +63 -0
- package/kb/moves/technical-drawing-lines.md +57 -0
- package/kb/moves/time-of-day-theming.md +56 -0
- package/kb/moves/tiny-game-easter-egg.md +60 -0
- package/kb/moves/type-decode-reveal.md +57 -0
- package/kb/moves/typographic-numeral-hero.md +58 -0
- package/kb/moves/view-transitions-between-pages.md +57 -0
- package/kb/moves/waitlist-position-live.md +60 -0
- package/kb/patterns/app-team-members.md +27 -0
- package/kb/patterns/chat-agent-approvals.md +31 -0
- package/kb/patterns/commerce-product-page.md +34 -0
- package/kb/patterns/dashboard-entity-table.md +37 -0
- package/kb/patterns/dashboard-home-today.md +24 -0
- package/kb/patterns/docs-api-reference.md +31 -0
- package/kb/patterns/marketing-product-led-landing.md +30 -0
- package/kb/patterns/mobile-daily-checklist.md +26 -0
- package/kb/references/index.json +1 -0
- package/kb/references/thumbs/004c5e9072.jpg +0 -0
- package/kb/references/thumbs/008915eb21.jpg +0 -0
- package/kb/references/thumbs/0098cf66c2.jpg +0 -0
- package/kb/references/thumbs/012e61b31f.jpg +0 -0
- package/kb/references/thumbs/014d4e46fc.jpg +0 -0
- package/kb/references/thumbs/01e54f394a.jpg +0 -0
- package/kb/references/thumbs/01e99a579c.jpg +0 -0
- package/kb/references/thumbs/01fd014575.jpg +0 -0
- package/kb/references/thumbs/022778ca72.jpg +0 -0
- package/kb/references/thumbs/02717bf1e1.jpg +0 -0
- package/kb/references/thumbs/02a5cfdbe9.jpg +0 -0
- package/kb/references/thumbs/02c9823f8d.jpg +0 -0
- package/kb/references/thumbs/02f3974613.jpg +0 -0
- package/kb/references/thumbs/032427f1ce.jpg +0 -0
- package/kb/references/thumbs/03ba677d07.jpg +0 -0
- package/kb/references/thumbs/04f2476cfe.jpg +0 -0
- package/kb/references/thumbs/0563e7c52e.jpg +0 -0
- package/kb/references/thumbs/05fa6b8eef.jpg +0 -0
- package/kb/references/thumbs/065bce1517.jpg +0 -0
- package/kb/references/thumbs/06c017f113.jpg +0 -0
- package/kb/references/thumbs/0770e2d8d3.jpg +0 -0
- package/kb/references/thumbs/078a26c4cb.jpg +0 -0
- package/kb/references/thumbs/08679b6e84.jpg +0 -0
- package/kb/references/thumbs/088c45a34c.jpg +0 -0
- package/kb/references/thumbs/089e9fd4e4.jpg +0 -0
- package/kb/references/thumbs/08f773231e.jpg +0 -0
- package/kb/references/thumbs/093ca3e2e3.jpg +0 -0
- package/kb/references/thumbs/0963320f74.jpg +0 -0
- package/kb/references/thumbs/09fc1ded85.jpg +0 -0
- package/kb/references/thumbs/0a368f3963.jpg +0 -0
- package/kb/references/thumbs/0a9e0e5201.jpg +0 -0
- package/kb/references/thumbs/0aace67ee7.jpg +0 -0
- package/kb/references/thumbs/0b61ecf793.jpg +0 -0
- package/kb/references/thumbs/0bde18ae09.jpg +0 -0
- package/kb/references/thumbs/0bf7ee8143.jpg +0 -0
- package/kb/references/thumbs/0ca20a13e0.jpg +0 -0
- package/kb/references/thumbs/0cdc0198c4.jpg +0 -0
- package/kb/references/thumbs/0cf9790b28.jpg +0 -0
- package/kb/references/thumbs/0d0202904b.jpg +0 -0
- package/kb/references/thumbs/0d053aba18.jpg +0 -0
- package/kb/references/thumbs/0d0c2c13d2.jpg +0 -0
- package/kb/references/thumbs/0d30be99b0.jpg +0 -0
- package/kb/references/thumbs/0d503da1b7.jpg +0 -0
- package/kb/references/thumbs/0ddad7b740.jpg +0 -0
- package/kb/references/thumbs/0e0a148101.jpg +0 -0
- package/kb/references/thumbs/0e401b6b75.jpg +0 -0
- package/kb/references/thumbs/0e8f32ba0f.jpg +0 -0
- package/kb/references/thumbs/0ef1966a7d.jpg +0 -0
- package/kb/references/thumbs/0efd416d11.jpg +0 -0
- package/kb/references/thumbs/0f3af263ae.jpg +0 -0
- package/kb/references/thumbs/0f9644c3a5.jpg +0 -0
- package/kb/references/thumbs/1020552737.jpg +0 -0
- package/kb/references/thumbs/1037626587.jpg +0 -0
- package/kb/references/thumbs/10512b7e94.jpg +0 -0
- package/kb/references/thumbs/106dc4a7b5.jpg +0 -0
- package/kb/references/thumbs/108943f6f7.jpg +0 -0
- package/kb/references/thumbs/10a5551d51.jpg +0 -0
- package/kb/references/thumbs/10dd01076c.jpg +0 -0
- package/kb/references/thumbs/11b06e39da.jpg +0 -0
- package/kb/references/thumbs/11baa1ec83.jpg +0 -0
- package/kb/references/thumbs/11bbe12167.jpg +0 -0
- package/kb/references/thumbs/11ca37feb8.jpg +0 -0
- package/kb/references/thumbs/11f14d3f12.jpg +0 -0
- package/kb/references/thumbs/124530ceb5.jpg +0 -0
- package/kb/references/thumbs/1276402336.jpg +0 -0
- package/kb/references/thumbs/1280e5c9d6.jpg +0 -0
- package/kb/references/thumbs/12cac0d172.jpg +0 -0
- package/kb/references/thumbs/138be6623a.jpg +0 -0
- package/kb/references/thumbs/13a0c544d1.jpg +0 -0
- package/kb/references/thumbs/13a32cb94c.jpg +0 -0
- package/kb/references/thumbs/144035d641.jpg +0 -0
- package/kb/references/thumbs/1457e41924.jpg +0 -0
- package/kb/references/thumbs/14972e3a86.jpg +0 -0
- package/kb/references/thumbs/15a47f2e4c.jpg +0 -0
- package/kb/references/thumbs/15aa15d22e.jpg +0 -0
- package/kb/references/thumbs/160dcd24b7.jpg +0 -0
- package/kb/references/thumbs/1610978529.jpg +0 -0
- package/kb/references/thumbs/16db30ba37.jpg +0 -0
- package/kb/references/thumbs/16f19ae4f3.jpg +0 -0
- package/kb/references/thumbs/16f758b03f.jpg +0 -0
- package/kb/references/thumbs/1748e5b751.jpg +0 -0
- package/kb/references/thumbs/175d0b0e69.jpg +0 -0
- package/kb/references/thumbs/178f571a36.jpg +0 -0
- package/kb/references/thumbs/17c43dc025.jpg +0 -0
- package/kb/references/thumbs/17c7f05b50.jpg +0 -0
- package/kb/references/thumbs/17f97e61dd.jpg +0 -0
- package/kb/references/thumbs/1804e17365.jpg +0 -0
- package/kb/references/thumbs/195c162ed1.jpg +0 -0
- package/kb/references/thumbs/19e3699c29.jpg +0 -0
- package/kb/references/thumbs/1a48e0a4d5.jpg +0 -0
- package/kb/references/thumbs/1a6cda265e.jpg +0 -0
- package/kb/references/thumbs/1a7e012091.jpg +0 -0
- package/kb/references/thumbs/1a9173a172.jpg +0 -0
- package/kb/references/thumbs/1b34a6b4d2.jpg +0 -0
- package/kb/references/thumbs/1b5284f3b2.jpg +0 -0
- package/kb/references/thumbs/1b93fbd6d3.jpg +0 -0
- package/kb/references/thumbs/1c6728fef6.jpg +0 -0
- package/kb/references/thumbs/1c6f101618.jpg +0 -0
- package/kb/references/thumbs/1cf7011a94.jpg +0 -0
- package/kb/references/thumbs/1d1769e539.jpg +0 -0
- package/kb/references/thumbs/1d1fd23199.jpg +0 -0
- package/kb/references/thumbs/1d7b6602ac.jpg +0 -0
- package/kb/references/thumbs/1dbbc61b9e.jpg +0 -0
- package/kb/references/thumbs/1dbdd6d21d.jpg +0 -0
- package/kb/references/thumbs/1dd873744b.jpg +0 -0
- package/kb/references/thumbs/1df4dd7cf9.jpg +0 -0
- package/kb/references/thumbs/1ece8c35b8.jpg +0 -0
- package/kb/references/thumbs/1f0e5b9aa4.jpg +0 -0
- package/kb/references/thumbs/1f1419651b.jpg +0 -0
- package/kb/references/thumbs/1f2a6da92d.jpg +0 -0
- package/kb/references/thumbs/1f651f33c0.jpg +0 -0
- package/kb/references/thumbs/1f8b697d18.jpg +0 -0
- package/kb/references/thumbs/1fee3824cd.jpg +0 -0
- package/kb/references/thumbs/1ff7757853.jpg +0 -0
- package/kb/references/thumbs/2043182f01.jpg +0 -0
- package/kb/references/thumbs/2059dabcd5.jpg +0 -0
- package/kb/references/thumbs/209d9dbb95.jpg +0 -0
- package/kb/references/thumbs/20d8923554.jpg +0 -0
- package/kb/references/thumbs/210dd3b671.jpg +0 -0
- package/kb/references/thumbs/2120719116.jpg +0 -0
- package/kb/references/thumbs/21a7384429.jpg +0 -0
- package/kb/references/thumbs/225f187391.jpg +0 -0
- package/kb/references/thumbs/230caf7dd2.jpg +0 -0
- package/kb/references/thumbs/235afad355.jpg +0 -0
- package/kb/references/thumbs/2378066687.jpg +0 -0
- package/kb/references/thumbs/241faf68a1.jpg +0 -0
- package/kb/references/thumbs/24d9f1a9b9.jpg +0 -0
- package/kb/references/thumbs/2505c8b9a0.jpg +0 -0
- package/kb/references/thumbs/25593638ca.jpg +0 -0
- package/kb/references/thumbs/25dd454f18.jpg +0 -0
- package/kb/references/thumbs/2602e123e2.jpg +0 -0
- package/kb/references/thumbs/26095c0b11.jpg +0 -0
- package/kb/references/thumbs/26260d6238.jpg +0 -0
- package/kb/references/thumbs/26bba21836.jpg +0 -0
- package/kb/references/thumbs/26bde3520b.jpg +0 -0
- package/kb/references/thumbs/272e22067c.jpg +0 -0
- package/kb/references/thumbs/27421b3226.jpg +0 -0
- package/kb/references/thumbs/2769ebca41.jpg +0 -0
- package/kb/references/thumbs/27e2f6e8b0.jpg +0 -0
- package/kb/references/thumbs/28432ba373.jpg +0 -0
- package/kb/references/thumbs/2846626f19.jpg +0 -0
- package/kb/references/thumbs/28750e821e.jpg +0 -0
- package/kb/references/thumbs/289481230f.jpg +0 -0
- package/kb/references/thumbs/2897967408.jpg +0 -0
- package/kb/references/thumbs/28c0a5fa17.jpg +0 -0
- package/kb/references/thumbs/28c181fba5.jpg +0 -0
- package/kb/references/thumbs/29806f539f.jpg +0 -0
- package/kb/references/thumbs/2997f56ee7.jpg +0 -0
- package/kb/references/thumbs/29b34a10fb.jpg +0 -0
- package/kb/references/thumbs/29bd78cdb4.jpg +0 -0
- package/kb/references/thumbs/29c8c6c22a.jpg +0 -0
- package/kb/references/thumbs/2ac23f7fae.jpg +0 -0
- package/kb/references/thumbs/2b76e13f79.jpg +0 -0
- package/kb/references/thumbs/2b84226479.jpg +0 -0
- package/kb/references/thumbs/2bf2dbf737.jpg +0 -0
- package/kb/references/thumbs/2bf89abbf6.jpg +0 -0
- package/kb/references/thumbs/2c562cd534.jpg +0 -0
- package/kb/references/thumbs/2cfe9d48c4.jpg +0 -0
- package/kb/references/thumbs/2d49f19b27.jpg +0 -0
- package/kb/references/thumbs/2dc8670da7.jpg +0 -0
- package/kb/references/thumbs/2de947c469.jpg +0 -0
- package/kb/references/thumbs/2e075ca79f.jpg +0 -0
- package/kb/references/thumbs/2eb0820e6b.jpg +0 -0
- package/kb/references/thumbs/2ec56a9dc0.jpg +0 -0
- package/kb/references/thumbs/30bb4ad607.jpg +0 -0
- package/kb/references/thumbs/30d19b8444.jpg +0 -0
- package/kb/references/thumbs/3173c71d73.jpg +0 -0
- package/kb/references/thumbs/31e59e4518.jpg +0 -0
- package/kb/references/thumbs/322b8960e8.jpg +0 -0
- package/kb/references/thumbs/325c9be6f4.jpg +0 -0
- package/kb/references/thumbs/3273338982.jpg +0 -0
- package/kb/references/thumbs/32f8387f38.jpg +0 -0
- package/kb/references/thumbs/332f6b74c3.jpg +0 -0
- package/kb/references/thumbs/334b146b6a.jpg +0 -0
- package/kb/references/thumbs/339abd60e4.jpg +0 -0
- package/kb/references/thumbs/33e88ec295.jpg +0 -0
- package/kb/references/thumbs/340931b5d4.jpg +0 -0
- package/kb/references/thumbs/34165a93bd.jpg +0 -0
- package/kb/references/thumbs/34d0575dd7.jpg +0 -0
- package/kb/references/thumbs/3549e75f0a.jpg +0 -0
- package/kb/references/thumbs/35827f1561.jpg +0 -0
- package/kb/references/thumbs/37c50280ec.jpg +0 -0
- package/kb/references/thumbs/37e66ecf11.jpg +0 -0
- package/kb/references/thumbs/3841a04fdc.jpg +0 -0
- package/kb/references/thumbs/38490c088f.jpg +0 -0
- package/kb/references/thumbs/3850319e9f.jpg +0 -0
- package/kb/references/thumbs/3856a64166.jpg +0 -0
- package/kb/references/thumbs/3881f211c2.jpg +0 -0
- package/kb/references/thumbs/38c7dde2f3.jpg +0 -0
- package/kb/references/thumbs/38ce9e486c.jpg +0 -0
- package/kb/references/thumbs/390ea8e01b.jpg +0 -0
- package/kb/references/thumbs/391b7fc27c.jpg +0 -0
- package/kb/references/thumbs/395f7214dc.jpg +0 -0
- package/kb/references/thumbs/39e81a5db2.jpg +0 -0
- package/kb/references/thumbs/3a67c8d655.jpg +0 -0
- package/kb/references/thumbs/3a89c25e3b.jpg +0 -0
- package/kb/references/thumbs/3aa0837542.jpg +0 -0
- package/kb/references/thumbs/3aae18aa8a.jpg +0 -0
- package/kb/references/thumbs/3bdbe767db.jpg +0 -0
- package/kb/references/thumbs/3c0616fc7f.jpg +0 -0
- package/kb/references/thumbs/3c18e9db1d.jpg +0 -0
- package/kb/references/thumbs/3c5a14076c.jpg +0 -0
- package/kb/references/thumbs/3ca9ec15c6.jpg +0 -0
- package/kb/references/thumbs/3d7756fd35.jpg +0 -0
- package/kb/references/thumbs/3dd45bd33d.jpg +0 -0
- package/kb/references/thumbs/3de72d5414.jpg +0 -0
- package/kb/references/thumbs/3dede597af.jpg +0 -0
- package/kb/references/thumbs/3ea948619e.jpg +0 -0
- package/kb/references/thumbs/402c12054c.jpg +0 -0
- package/kb/references/thumbs/403ab4af90.jpg +0 -0
- package/kb/references/thumbs/40773a8c63.jpg +0 -0
- package/kb/references/thumbs/407d9583db.jpg +0 -0
- package/kb/references/thumbs/415dc1d84c.jpg +0 -0
- package/kb/references/thumbs/420b676a4f.jpg +0 -0
- package/kb/references/thumbs/42a1ee85eb.jpg +0 -0
- package/kb/references/thumbs/43059221aa.jpg +0 -0
- package/kb/references/thumbs/432fa0d22c.jpg +0 -0
- package/kb/references/thumbs/43826f20e3.jpg +0 -0
- package/kb/references/thumbs/43ac55c98a.jpg +0 -0
- package/kb/references/thumbs/43d098f3c3.jpg +0 -0
- package/kb/references/thumbs/4449d80c2c.jpg +0 -0
- package/kb/references/thumbs/44625727dc.jpg +0 -0
- package/kb/references/thumbs/4482bb87a1.jpg +0 -0
- package/kb/references/thumbs/44b078acdc.jpg +0 -0
- package/kb/references/thumbs/44d0305850.jpg +0 -0
- package/kb/references/thumbs/456bb6b59e.jpg +0 -0
- package/kb/references/thumbs/45df73ce48.jpg +0 -0
- package/kb/references/thumbs/45edb45d87.jpg +0 -0
- package/kb/references/thumbs/4671d57cd5.jpg +0 -0
- package/kb/references/thumbs/46cc108518.jpg +0 -0
- package/kb/references/thumbs/470022e614.jpg +0 -0
- package/kb/references/thumbs/4719a669ce.jpg +0 -0
- package/kb/references/thumbs/47404f5ad4.jpg +0 -0
- package/kb/references/thumbs/479d3eb977.jpg +0 -0
- package/kb/references/thumbs/479ed92ceb.jpg +0 -0
- package/kb/references/thumbs/47baed50c7.jpg +0 -0
- package/kb/references/thumbs/47fdcfd7b0.jpg +0 -0
- package/kb/references/thumbs/48fd5e94c5.jpg +0 -0
- package/kb/references/thumbs/493807917b.jpg +0 -0
- package/kb/references/thumbs/49a1c67293.jpg +0 -0
- package/kb/references/thumbs/49e2d7fafe.jpg +0 -0
- package/kb/references/thumbs/49f540fcb6.jpg +0 -0
- package/kb/references/thumbs/4a67ec4028.jpg +0 -0
- package/kb/references/thumbs/4a93275b04.jpg +0 -0
- package/kb/references/thumbs/4aae6f52da.jpg +0 -0
- package/kb/references/thumbs/4b1bae66bd.jpg +0 -0
- package/kb/references/thumbs/4b75da15a9.jpg +0 -0
- package/kb/references/thumbs/4b77789b88.jpg +0 -0
- package/kb/references/thumbs/4bc2f3f3a4.jpg +0 -0
- package/kb/references/thumbs/4c48f27b88.jpg +0 -0
- package/kb/references/thumbs/4c52bd8bfb.jpg +0 -0
- package/kb/references/thumbs/4c9b4acc60.jpg +0 -0
- package/kb/references/thumbs/4cbf0752d4.jpg +0 -0
- package/kb/references/thumbs/4cd2672517.jpg +0 -0
- package/kb/references/thumbs/4dd22a8181.jpg +0 -0
- package/kb/references/thumbs/4ee3447314.jpg +0 -0
- package/kb/references/thumbs/4ef29c8144.jpg +0 -0
- package/kb/references/thumbs/4f82fadfd6.jpg +0 -0
- package/kb/references/thumbs/4fc9b643d8.jpg +0 -0
- package/kb/references/thumbs/50549524b0.jpg +0 -0
- package/kb/references/thumbs/509a615883.jpg +0 -0
- package/kb/references/thumbs/50c03ef855.jpg +0 -0
- package/kb/references/thumbs/50cf9acad6.jpg +0 -0
- package/kb/references/thumbs/51131b025c.jpg +0 -0
- package/kb/references/thumbs/51da1124f0.jpg +0 -0
- package/kb/references/thumbs/524d5e7288.jpg +0 -0
- package/kb/references/thumbs/5251247c81.jpg +0 -0
- package/kb/references/thumbs/526b91b7ec.jpg +0 -0
- package/kb/references/thumbs/52f02f6c2e.jpg +0 -0
- package/kb/references/thumbs/530ab530dc.jpg +0 -0
- package/kb/references/thumbs/5335ee4404.jpg +0 -0
- package/kb/references/thumbs/5385b90939.jpg +0 -0
- package/kb/references/thumbs/53c54654cc.jpg +0 -0
- package/kb/references/thumbs/5420794c6c.jpg +0 -0
- package/kb/references/thumbs/542a9dce8b.jpg +0 -0
- package/kb/references/thumbs/5571530a5f.jpg +0 -0
- package/kb/references/thumbs/56cc68d804.jpg +0 -0
- package/kb/references/thumbs/577b7f7c44.jpg +0 -0
- package/kb/references/thumbs/57adf8f1bb.jpg +0 -0
- package/kb/references/thumbs/57c5f5e3a6.jpg +0 -0
- package/kb/references/thumbs/57ffe4e839.jpg +0 -0
- package/kb/references/thumbs/5817dfc7d6.jpg +0 -0
- package/kb/references/thumbs/581a182a7a.jpg +0 -0
- package/kb/references/thumbs/58807679c8.jpg +0 -0
- package/kb/references/thumbs/589a1d9111.jpg +0 -0
- package/kb/references/thumbs/59167391ab.jpg +0 -0
- package/kb/references/thumbs/5a70420166.jpg +0 -0
- package/kb/references/thumbs/5b10df1b8d.jpg +0 -0
- package/kb/references/thumbs/5b7a712fdb.jpg +0 -0
- package/kb/references/thumbs/5bc46b92ad.jpg +0 -0
- package/kb/references/thumbs/5c0546a120.jpg +0 -0
- package/kb/references/thumbs/5c2b6125a8.jpg +0 -0
- package/kb/references/thumbs/5c536b9e42.jpg +0 -0
- package/kb/references/thumbs/5c8e441d30.jpg +0 -0
- package/kb/references/thumbs/5cb6f1baa5.jpg +0 -0
- package/kb/references/thumbs/5ce6c1f21e.jpg +0 -0
- package/kb/references/thumbs/5cef052c0a.jpg +0 -0
- package/kb/references/thumbs/5d08438209.jpg +0 -0
- package/kb/references/thumbs/5d0f9af9b0.jpg +0 -0
- package/kb/references/thumbs/5d300ed332.jpg +0 -0
- package/kb/references/thumbs/5d708a4794.jpg +0 -0
- package/kb/references/thumbs/5de8826a98.jpg +0 -0
- package/kb/references/thumbs/5df7bff22c.jpg +0 -0
- package/kb/references/thumbs/5e087d0f00.jpg +0 -0
- package/kb/references/thumbs/5e44e70ca0.jpg +0 -0
- package/kb/references/thumbs/5eff5d80ce.jpg +0 -0
- package/kb/references/thumbs/5f015c96b1.jpg +0 -0
- package/kb/references/thumbs/5f3289b7c4.jpg +0 -0
- package/kb/references/thumbs/5f830c40eb.jpg +0 -0
- package/kb/references/thumbs/60c4c3527b.jpg +0 -0
- package/kb/references/thumbs/60e9e594dd.jpg +0 -0
- package/kb/references/thumbs/615421d7be.jpg +0 -0
- package/kb/references/thumbs/6176e4004f.jpg +0 -0
- package/kb/references/thumbs/61f0fe5e06.jpg +0 -0
- package/kb/references/thumbs/6213785db4.jpg +0 -0
- package/kb/references/thumbs/62a670ae09.jpg +0 -0
- package/kb/references/thumbs/63cc12981b.jpg +0 -0
- package/kb/references/thumbs/641f15cc71.jpg +0 -0
- package/kb/references/thumbs/642ab2081d.jpg +0 -0
- package/kb/references/thumbs/644a59bf6e.jpg +0 -0
- package/kb/references/thumbs/644da8a8e3.jpg +0 -0
- package/kb/references/thumbs/6458986897.jpg +0 -0
- package/kb/references/thumbs/64cfbd7b93.jpg +0 -0
- package/kb/references/thumbs/661a57686a.jpg +0 -0
- package/kb/references/thumbs/66355a813a.jpg +0 -0
- package/kb/references/thumbs/664396dd44.jpg +0 -0
- package/kb/references/thumbs/66d832d65f.jpg +0 -0
- package/kb/references/thumbs/66de9e9833.jpg +0 -0
- package/kb/references/thumbs/6750964fd9.jpg +0 -0
- package/kb/references/thumbs/67c7164832.jpg +0 -0
- package/kb/references/thumbs/681600b6cd.jpg +0 -0
- package/kb/references/thumbs/682e75afe4.jpg +0 -0
- package/kb/references/thumbs/69274bc952.jpg +0 -0
- package/kb/references/thumbs/696ec08c52.jpg +0 -0
- package/kb/references/thumbs/69760111cc.jpg +0 -0
- package/kb/references/thumbs/697e32664f.jpg +0 -0
- package/kb/references/thumbs/69df92544b.jpg +0 -0
- package/kb/references/thumbs/69e48c5229.jpg +0 -0
- package/kb/references/thumbs/6a5fb75de1.jpg +0 -0
- package/kb/references/thumbs/6a7389e472.jpg +0 -0
- package/kb/references/thumbs/6a9b041e02.jpg +0 -0
- package/kb/references/thumbs/6ab2fa93ee.jpg +0 -0
- package/kb/references/thumbs/6ae3400319.jpg +0 -0
- package/kb/references/thumbs/6b21344bf3.jpg +0 -0
- package/kb/references/thumbs/6bd1b67a8a.jpg +0 -0
- package/kb/references/thumbs/6c99156c4f.jpg +0 -0
- package/kb/references/thumbs/6cc9b65384.jpg +0 -0
- package/kb/references/thumbs/6d15830a73.jpg +0 -0
- package/kb/references/thumbs/6d4c33d180.jpg +0 -0
- package/kb/references/thumbs/6e36815799.jpg +0 -0
- package/kb/references/thumbs/6eddb9cbfc.jpg +0 -0
- package/kb/references/thumbs/6f97968a7c.jpg +0 -0
- package/kb/references/thumbs/6fa0716a7d.jpg +0 -0
- package/kb/references/thumbs/6facd57184.jpg +0 -0
- package/kb/references/thumbs/6fd57b785a.jpg +0 -0
- package/kb/references/thumbs/700b3cb185.jpg +0 -0
- package/kb/references/thumbs/71197443fa.jpg +0 -0
- package/kb/references/thumbs/72082bc9ee.jpg +0 -0
- package/kb/references/thumbs/72453db479.jpg +0 -0
- package/kb/references/thumbs/72cd64d500.jpg +0 -0
- package/kb/references/thumbs/72d1e75283.jpg +0 -0
- package/kb/references/thumbs/72e13f7f5b.jpg +0 -0
- package/kb/references/thumbs/7459f8e644.jpg +0 -0
- package/kb/references/thumbs/7470d55c79.jpg +0 -0
- package/kb/references/thumbs/74c468f02a.jpg +0 -0
- package/kb/references/thumbs/74fed6eb77.jpg +0 -0
- package/kb/references/thumbs/756e05080d.jpg +0 -0
- package/kb/references/thumbs/75df7e5f49.jpg +0 -0
- package/kb/references/thumbs/76226da6ef.jpg +0 -0
- package/kb/references/thumbs/76a34aa056.jpg +0 -0
- package/kb/references/thumbs/76c7076589.jpg +0 -0
- package/kb/references/thumbs/76cccc396c.jpg +0 -0
- package/kb/references/thumbs/774654789f.jpg +0 -0
- package/kb/references/thumbs/7746cc494d.jpg +0 -0
- package/kb/references/thumbs/7766e36aa3.jpg +0 -0
- package/kb/references/thumbs/78049331ef.jpg +0 -0
- package/kb/references/thumbs/78808a0ca5.jpg +0 -0
- package/kb/references/thumbs/7890b0a310.jpg +0 -0
- package/kb/references/thumbs/78911975e6.jpg +0 -0
- package/kb/references/thumbs/78d289d5a0.jpg +0 -0
- package/kb/references/thumbs/794126267e.jpg +0 -0
- package/kb/references/thumbs/7976eb9025.jpg +0 -0
- package/kb/references/thumbs/798519ec7c.jpg +0 -0
- package/kb/references/thumbs/7a6cc5eecc.jpg +0 -0
- package/kb/references/thumbs/7ac8a7cd4a.jpg +0 -0
- package/kb/references/thumbs/7b1341d61d.jpg +0 -0
- package/kb/references/thumbs/7b9c4031e3.jpg +0 -0
- package/kb/references/thumbs/7ba5ad3779.jpg +0 -0
- package/kb/references/thumbs/7be6eb2bd0.jpg +0 -0
- package/kb/references/thumbs/7c30864f5d.jpg +0 -0
- package/kb/references/thumbs/7c379b5e17.jpg +0 -0
- package/kb/references/thumbs/7c4050eb02.jpg +0 -0
- package/kb/references/thumbs/7ca47b3221.jpg +0 -0
- package/kb/references/thumbs/7d43516bd7.jpg +0 -0
- package/kb/references/thumbs/7da9dbb98a.jpg +0 -0
- package/kb/references/thumbs/7ddda8f57c.jpg +0 -0
- package/kb/references/thumbs/7ece6d215c.jpg +0 -0
- package/kb/references/thumbs/7eebf029de.jpg +0 -0
- package/kb/references/thumbs/7f45483c15.jpg +0 -0
- package/kb/references/thumbs/7fde06eaa5.jpg +0 -0
- package/kb/references/thumbs/80407b97e9.jpg +0 -0
- package/kb/references/thumbs/80f0a6cf4b.jpg +0 -0
- package/kb/references/thumbs/8118b451b8.jpg +0 -0
- package/kb/references/thumbs/816baed4a8.jpg +0 -0
- package/kb/references/thumbs/817d2d15d9.jpg +0 -0
- package/kb/references/thumbs/8260cb430d.jpg +0 -0
- package/kb/references/thumbs/827b562899.jpg +0 -0
- package/kb/references/thumbs/8293cf1e92.jpg +0 -0
- package/kb/references/thumbs/8294653169.jpg +0 -0
- package/kb/references/thumbs/83061f871f.jpg +0 -0
- package/kb/references/thumbs/833df778ca.jpg +0 -0
- package/kb/references/thumbs/83d3acaafc.jpg +0 -0
- package/kb/references/thumbs/844c6efeff.jpg +0 -0
- package/kb/references/thumbs/84563d482a.jpg +0 -0
- package/kb/references/thumbs/848082dd94.jpg +0 -0
- package/kb/references/thumbs/8494b1da94.jpg +0 -0
- package/kb/references/thumbs/84b71014be.jpg +0 -0
- package/kb/references/thumbs/84be4a5f3c.jpg +0 -0
- package/kb/references/thumbs/84fd0fd25a.jpg +0 -0
- package/kb/references/thumbs/853e488e22.jpg +0 -0
- package/kb/references/thumbs/858c91585d.jpg +0 -0
- package/kb/references/thumbs/85903e8fe9.jpg +0 -0
- package/kb/references/thumbs/86597d43fd.jpg +0 -0
- package/kb/references/thumbs/866eb99500.jpg +0 -0
- package/kb/references/thumbs/86ac050340.jpg +0 -0
- package/kb/references/thumbs/86afec7b64.jpg +0 -0
- package/kb/references/thumbs/87238127f5.jpg +0 -0
- package/kb/references/thumbs/87321df792.jpg +0 -0
- package/kb/references/thumbs/87460f70cd.jpg +0 -0
- package/kb/references/thumbs/8770dfeaec.jpg +0 -0
- package/kb/references/thumbs/8798451a8c.jpg +0 -0
- package/kb/references/thumbs/87da1f34a3.jpg +0 -0
- package/kb/references/thumbs/882cdf8c05.jpg +0 -0
- package/kb/references/thumbs/8839f4106f.jpg +0 -0
- package/kb/references/thumbs/8844455212.jpg +0 -0
- package/kb/references/thumbs/88abba0497.jpg +0 -0
- package/kb/references/thumbs/88b372435d.jpg +0 -0
- package/kb/references/thumbs/88bd4f35fa.jpg +0 -0
- package/kb/references/thumbs/88c71a075e.jpg +0 -0
- package/kb/references/thumbs/892e5f048e.jpg +0 -0
- package/kb/references/thumbs/893cc8e920.jpg +0 -0
- package/kb/references/thumbs/896c198814.jpg +0 -0
- package/kb/references/thumbs/8a8e22f756.jpg +0 -0
- package/kb/references/thumbs/8a9d8a35cb.jpg +0 -0
- package/kb/references/thumbs/8ab1d67fec.jpg +0 -0
- package/kb/references/thumbs/8ab201fe12.jpg +0 -0
- package/kb/references/thumbs/8ac372c102.jpg +0 -0
- package/kb/references/thumbs/8b15736d62.jpg +0 -0
- package/kb/references/thumbs/8b1d8d6a05.jpg +0 -0
- package/kb/references/thumbs/8bb6050e20.jpg +0 -0
- package/kb/references/thumbs/8c6c9d1577.jpg +0 -0
- package/kb/references/thumbs/8d337851e0.jpg +0 -0
- package/kb/references/thumbs/8d44b33cc5.jpg +0 -0
- package/kb/references/thumbs/8d4536ff3b.jpg +0 -0
- package/kb/references/thumbs/8d9b99454a.jpg +0 -0
- package/kb/references/thumbs/8dda2303ff.jpg +0 -0
- package/kb/references/thumbs/8dec722d62.jpg +0 -0
- package/kb/references/thumbs/8e0ea4ca74.jpg +0 -0
- package/kb/references/thumbs/8ebba84d69.jpg +0 -0
- package/kb/references/thumbs/8ef09140c0.jpg +0 -0
- package/kb/references/thumbs/8f2928a1b0.jpg +0 -0
- package/kb/references/thumbs/8f88f9ddba.jpg +0 -0
- package/kb/references/thumbs/8fa4451009.jpg +0 -0
- package/kb/references/thumbs/90109b0e02.jpg +0 -0
- package/kb/references/thumbs/90e854e5c9.jpg +0 -0
- package/kb/references/thumbs/914e79f893.jpg +0 -0
- package/kb/references/thumbs/9161369197.jpg +0 -0
- package/kb/references/thumbs/9165c87d6f.jpg +0 -0
- package/kb/references/thumbs/9193f6558a.jpg +0 -0
- package/kb/references/thumbs/91ac7df877.jpg +0 -0
- package/kb/references/thumbs/9216bd4931.jpg +0 -0
- package/kb/references/thumbs/922ae8a26a.jpg +0 -0
- package/kb/references/thumbs/92a70e6867.jpg +0 -0
- package/kb/references/thumbs/92c77ece22.jpg +0 -0
- package/kb/references/thumbs/92e5914fbf.jpg +0 -0
- package/kb/references/thumbs/92ee36419a.jpg +0 -0
- package/kb/references/thumbs/94153a60b0.jpg +0 -0
- package/kb/references/thumbs/9449666640.jpg +0 -0
- package/kb/references/thumbs/944be6a646.jpg +0 -0
- package/kb/references/thumbs/945831476e.jpg +0 -0
- package/kb/references/thumbs/94c1af82f4.jpg +0 -0
- package/kb/references/thumbs/9599d6d589.jpg +0 -0
- package/kb/references/thumbs/960710f4cf.jpg +0 -0
- package/kb/references/thumbs/9608f12d64.jpg +0 -0
- package/kb/references/thumbs/962dabfaa0.jpg +0 -0
- package/kb/references/thumbs/965a3a95c6.jpg +0 -0
- package/kb/references/thumbs/9672ed5ae4.jpg +0 -0
- package/kb/references/thumbs/96891ae9c1.jpg +0 -0
- package/kb/references/thumbs/96e1907c99.jpg +0 -0
- package/kb/references/thumbs/96f25b6578.jpg +0 -0
- package/kb/references/thumbs/97211df9ae.jpg +0 -0
- package/kb/references/thumbs/9769d763a5.jpg +0 -0
- package/kb/references/thumbs/9792bd6d05.jpg +0 -0
- package/kb/references/thumbs/97d2837e3e.jpg +0 -0
- package/kb/references/thumbs/97d90d1d85.jpg +0 -0
- package/kb/references/thumbs/97fcd5774a.jpg +0 -0
- package/kb/references/thumbs/980f68564e.jpg +0 -0
- package/kb/references/thumbs/98511fb393.jpg +0 -0
- package/kb/references/thumbs/98f8fa694d.jpg +0 -0
- package/kb/references/thumbs/9a42c6bb97.jpg +0 -0
- package/kb/references/thumbs/9ab3600a06.jpg +0 -0
- package/kb/references/thumbs/9ab48d9130.jpg +0 -0
- package/kb/references/thumbs/9ad519197d.jpg +0 -0
- package/kb/references/thumbs/9b1382c657.jpg +0 -0
- package/kb/references/thumbs/9b6cc1e077.jpg +0 -0
- package/kb/references/thumbs/9beede8480.jpg +0 -0
- package/kb/references/thumbs/9bf8976952.jpg +0 -0
- package/kb/references/thumbs/9cace7b75a.jpg +0 -0
- package/kb/references/thumbs/9cdcc5745e.jpg +0 -0
- package/kb/references/thumbs/9d0b269074.jpg +0 -0
- package/kb/references/thumbs/9d41788b48.jpg +0 -0
- package/kb/references/thumbs/9d475bd6af.jpg +0 -0
- package/kb/references/thumbs/9e6bc543d0.jpg +0 -0
- package/kb/references/thumbs/9ed0e0a206.jpg +0 -0
- package/kb/references/thumbs/9ef538de13.jpg +0 -0
- package/kb/references/thumbs/9efc373ab4.jpg +0 -0
- package/kb/references/thumbs/9f4a939fa5.jpg +0 -0
- package/kb/references/thumbs/9f8d12d7b9.jpg +0 -0
- package/kb/references/thumbs/9fd5e3efa7.jpg +0 -0
- package/kb/references/thumbs/a0a9b9c572.jpg +0 -0
- package/kb/references/thumbs/a29ca9b185.jpg +0 -0
- package/kb/references/thumbs/a2a42884af.jpg +0 -0
- package/kb/references/thumbs/a2f5064e34.jpg +0 -0
- package/kb/references/thumbs/a3112ce672.jpg +0 -0
- package/kb/references/thumbs/a3437736d1.jpg +0 -0
- package/kb/references/thumbs/a372667e76.jpg +0 -0
- package/kb/references/thumbs/a39bae94e9.jpg +0 -0
- package/kb/references/thumbs/a3fc75d94c.jpg +0 -0
- package/kb/references/thumbs/a440487631.jpg +0 -0
- package/kb/references/thumbs/a4993d5fb5.jpg +0 -0
- package/kb/references/thumbs/a500476a86.jpg +0 -0
- package/kb/references/thumbs/a55506bf46.jpg +0 -0
- package/kb/references/thumbs/a59a5f9843.jpg +0 -0
- package/kb/references/thumbs/a5bce0eede.jpg +0 -0
- package/kb/references/thumbs/a5d921e1f0.jpg +0 -0
- package/kb/references/thumbs/a5e19d4637.jpg +0 -0
- package/kb/references/thumbs/a5f92b97d5.jpg +0 -0
- package/kb/references/thumbs/a6562c210a.jpg +0 -0
- package/kb/references/thumbs/a6f61a8d09.jpg +0 -0
- package/kb/references/thumbs/a719fdf663.jpg +0 -0
- package/kb/references/thumbs/a724e5442f.jpg +0 -0
- package/kb/references/thumbs/a852b4319f.jpg +0 -0
- package/kb/references/thumbs/a896c08448.jpg +0 -0
- package/kb/references/thumbs/a8d190a4b8.jpg +0 -0
- package/kb/references/thumbs/a8dcdd57d7.jpg +0 -0
- package/kb/references/thumbs/a951347e18.jpg +0 -0
- package/kb/references/thumbs/a9a1a0bf7d.jpg +0 -0
- package/kb/references/thumbs/a9b72986a8.jpg +0 -0
- package/kb/references/thumbs/aa11555ba8.jpg +0 -0
- package/kb/references/thumbs/aa4aab9093.jpg +0 -0
- package/kb/references/thumbs/aa89b96658.jpg +0 -0
- package/kb/references/thumbs/aaeb3739b6.jpg +0 -0
- package/kb/references/thumbs/ab25a608ec.jpg +0 -0
- package/kb/references/thumbs/ab34b62268.jpg +0 -0
- package/kb/references/thumbs/ab3b0217bb.jpg +0 -0
- package/kb/references/thumbs/ac70c04afb.jpg +0 -0
- package/kb/references/thumbs/ace0879117.jpg +0 -0
- package/kb/references/thumbs/ad0515ae55.jpg +0 -0
- package/kb/references/thumbs/ad66b25b47.jpg +0 -0
- package/kb/references/thumbs/ad68229d2f.jpg +0 -0
- package/kb/references/thumbs/add6480693.jpg +0 -0
- package/kb/references/thumbs/ae5b8c3b8c.jpg +0 -0
- package/kb/references/thumbs/ae81247b47.jpg +0 -0
- package/kb/references/thumbs/af8aa54379.jpg +0 -0
- package/kb/references/thumbs/afa80dedd4.jpg +0 -0
- package/kb/references/thumbs/afe04d730f.jpg +0 -0
- package/kb/references/thumbs/b09a7dc315.jpg +0 -0
- package/kb/references/thumbs/b0c02629f6.jpg +0 -0
- package/kb/references/thumbs/b1cb4f91de.jpg +0 -0
- package/kb/references/thumbs/b1d4c13c92.jpg +0 -0
- package/kb/references/thumbs/b20eef86db.jpg +0 -0
- package/kb/references/thumbs/b3136ccdde.jpg +0 -0
- package/kb/references/thumbs/b3f5e8d7dd.jpg +0 -0
- package/kb/references/thumbs/b44adb81c9.jpg +0 -0
- package/kb/references/thumbs/b457a5894b.jpg +0 -0
- package/kb/references/thumbs/b461281e0f.jpg +0 -0
- package/kb/references/thumbs/b4942644f0.jpg +0 -0
- package/kb/references/thumbs/b49f8eccad.jpg +0 -0
- package/kb/references/thumbs/b56c69be23.jpg +0 -0
- package/kb/references/thumbs/b59786b33c.jpg +0 -0
- package/kb/references/thumbs/b5d368ce5a.jpg +0 -0
- package/kb/references/thumbs/b5eb442406.jpg +0 -0
- package/kb/references/thumbs/b7fdabf4ab.jpg +0 -0
- package/kb/references/thumbs/b88ee8d235.jpg +0 -0
- package/kb/references/thumbs/b8a4e5be8a.jpg +0 -0
- package/kb/references/thumbs/b8c85b661c.jpg +0 -0
- package/kb/references/thumbs/ba1b8e7435.jpg +0 -0
- package/kb/references/thumbs/ba9a593eed.jpg +0 -0
- package/kb/references/thumbs/baa7afc201.jpg +0 -0
- package/kb/references/thumbs/baf74398f7.jpg +0 -0
- package/kb/references/thumbs/bb3e947c10.jpg +0 -0
- package/kb/references/thumbs/bb4c1bb7c1.jpg +0 -0
- package/kb/references/thumbs/bbaa25cc8a.jpg +0 -0
- package/kb/references/thumbs/bc7e7f88cc.jpg +0 -0
- package/kb/references/thumbs/bc9d5b58f5.jpg +0 -0
- package/kb/references/thumbs/bcc99a51f7.jpg +0 -0
- package/kb/references/thumbs/bce13dd1c1.jpg +0 -0
- package/kb/references/thumbs/bd079a6130.jpg +0 -0
- package/kb/references/thumbs/bd2d70dda7.jpg +0 -0
- package/kb/references/thumbs/bd5895c5f2.jpg +0 -0
- package/kb/references/thumbs/bde8e31d62.jpg +0 -0
- package/kb/references/thumbs/be084e78f3.jpg +0 -0
- package/kb/references/thumbs/be94f170c8.jpg +0 -0
- package/kb/references/thumbs/bed33654f7.jpg +0 -0
- package/kb/references/thumbs/beeb9a0c18.jpg +0 -0
- package/kb/references/thumbs/bef42094dc.jpg +0 -0
- package/kb/references/thumbs/bf625496fa.jpg +0 -0
- package/kb/references/thumbs/bf771f1398.jpg +0 -0
- package/kb/references/thumbs/c0ddefa3d9.jpg +0 -0
- package/kb/references/thumbs/c0f01e536e.jpg +0 -0
- package/kb/references/thumbs/c1d592b678.jpg +0 -0
- package/kb/references/thumbs/c1e83e1b3e.jpg +0 -0
- package/kb/references/thumbs/c2168f8096.jpg +0 -0
- package/kb/references/thumbs/c249cd6b17.jpg +0 -0
- package/kb/references/thumbs/c2e3d4feed.jpg +0 -0
- package/kb/references/thumbs/c3cec01df1.jpg +0 -0
- package/kb/references/thumbs/c47bedb42d.jpg +0 -0
- package/kb/references/thumbs/c4b03b4680.jpg +0 -0
- package/kb/references/thumbs/c4e6d18ec3.jpg +0 -0
- package/kb/references/thumbs/c5477e2877.jpg +0 -0
- package/kb/references/thumbs/c566794674.jpg +0 -0
- package/kb/references/thumbs/c5b945afbb.jpg +0 -0
- package/kb/references/thumbs/c5e4659497.jpg +0 -0
- package/kb/references/thumbs/c622786702.jpg +0 -0
- package/kb/references/thumbs/c6250649ca.jpg +0 -0
- package/kb/references/thumbs/c6261a9526.jpg +0 -0
- package/kb/references/thumbs/c62e609076.jpg +0 -0
- package/kb/references/thumbs/c6602d3e85.jpg +0 -0
- package/kb/references/thumbs/c6c1a16e40.jpg +0 -0
- package/kb/references/thumbs/c77f932157.jpg +0 -0
- package/kb/references/thumbs/c79788f01e.jpg +0 -0
- package/kb/references/thumbs/c7a511ad1e.jpg +0 -0
- package/kb/references/thumbs/c7acfbc3c8.jpg +0 -0
- package/kb/references/thumbs/c7bb6d1bb8.jpg +0 -0
- package/kb/references/thumbs/c829eadcb9.jpg +0 -0
- package/kb/references/thumbs/c891fd9d85.jpg +0 -0
- package/kb/references/thumbs/c89f812ee3.jpg +0 -0
- package/kb/references/thumbs/c8a85b9f61.jpg +0 -0
- package/kb/references/thumbs/c8fdf85c9a.jpg +0 -0
- package/kb/references/thumbs/c961ac1031.jpg +0 -0
- package/kb/references/thumbs/c9fd3f88d8.jpg +0 -0
- package/kb/references/thumbs/ca0d6399c3.jpg +0 -0
- package/kb/references/thumbs/ca316c83e4.jpg +0 -0
- package/kb/references/thumbs/ca3b93aa0d.jpg +0 -0
- package/kb/references/thumbs/cb0596a85b.jpg +0 -0
- package/kb/references/thumbs/cb95a5c3ef.jpg +0 -0
- package/kb/references/thumbs/cbe238b656.jpg +0 -0
- package/kb/references/thumbs/cbf530e608.jpg +0 -0
- package/kb/references/thumbs/cc173494ec.jpg +0 -0
- package/kb/references/thumbs/ccb664bddf.jpg +0 -0
- package/kb/references/thumbs/ccf69d36cd.jpg +0 -0
- package/kb/references/thumbs/ccf759db42.jpg +0 -0
- package/kb/references/thumbs/cd2a285807.jpg +0 -0
- package/kb/references/thumbs/cd4a24a8ca.jpg +0 -0
- package/kb/references/thumbs/cdb3fe8938.jpg +0 -0
- package/kb/references/thumbs/cdce1b9bc6.jpg +0 -0
- package/kb/references/thumbs/cde3aa761a.jpg +0 -0
- package/kb/references/thumbs/ce9d5cd9c2.jpg +0 -0
- package/kb/references/thumbs/ceb01f8a34.jpg +0 -0
- package/kb/references/thumbs/cee15c98d0.jpg +0 -0
- package/kb/references/thumbs/cf6944ddae.jpg +0 -0
- package/kb/references/thumbs/d15dd21cd8.jpg +0 -0
- package/kb/references/thumbs/d1633bf2ed.jpg +0 -0
- package/kb/references/thumbs/d16a838000.jpg +0 -0
- package/kb/references/thumbs/d1cf87e9f0.jpg +0 -0
- package/kb/references/thumbs/d32511d913.jpg +0 -0
- package/kb/references/thumbs/d327e9dedd.jpg +0 -0
- package/kb/references/thumbs/d35bf6c85e.jpg +0 -0
- package/kb/references/thumbs/d37afc9d61.jpg +0 -0
- package/kb/references/thumbs/d3b66655c7.jpg +0 -0
- package/kb/references/thumbs/d40b4172b6.jpg +0 -0
- package/kb/references/thumbs/d4c40d1824.jpg +0 -0
- package/kb/references/thumbs/d4cb55a4b6.jpg +0 -0
- package/kb/references/thumbs/d4f0c30f7e.jpg +0 -0
- package/kb/references/thumbs/d51cce3ae5.jpg +0 -0
- package/kb/references/thumbs/d52948f9ba.jpg +0 -0
- package/kb/references/thumbs/d62bd3cba4.jpg +0 -0
- package/kb/references/thumbs/d668a21c92.jpg +0 -0
- package/kb/references/thumbs/d685072c52.jpg +0 -0
- package/kb/references/thumbs/d6d7b96093.jpg +0 -0
- package/kb/references/thumbs/d7cd874b21.jpg +0 -0
- package/kb/references/thumbs/d7e111736a.jpg +0 -0
- package/kb/references/thumbs/d88325f832.jpg +0 -0
- package/kb/references/thumbs/d9272ded76.jpg +0 -0
- package/kb/references/thumbs/d962e7c654.jpg +0 -0
- package/kb/references/thumbs/d97f000ee2.jpg +0 -0
- package/kb/references/thumbs/d9c1ae2543.jpg +0 -0
- package/kb/references/thumbs/d9ccb3cac8.jpg +0 -0
- package/kb/references/thumbs/da99019680.jpg +0 -0
- package/kb/references/thumbs/da9b8bc615.jpg +0 -0
- package/kb/references/thumbs/daafbee2c1.jpg +0 -0
- package/kb/references/thumbs/db0f11f143.jpg +0 -0
- package/kb/references/thumbs/db5dd367f5.jpg +0 -0
- package/kb/references/thumbs/db69677866.jpg +0 -0
- package/kb/references/thumbs/db87c1956e.jpg +0 -0
- package/kb/references/thumbs/dbb86d8bda.jpg +0 -0
- package/kb/references/thumbs/dbd3a8ff2a.jpg +0 -0
- package/kb/references/thumbs/dbdf4b2794.jpg +0 -0
- package/kb/references/thumbs/dd4c19b4b1.jpg +0 -0
- package/kb/references/thumbs/dd98d9f6fa.jpg +0 -0
- package/kb/references/thumbs/ddb00e3b6a.jpg +0 -0
- package/kb/references/thumbs/dde3541a5c.jpg +0 -0
- package/kb/references/thumbs/de2a4da502.jpg +0 -0
- package/kb/references/thumbs/de4c387a66.jpg +0 -0
- package/kb/references/thumbs/de5618fa82.jpg +0 -0
- package/kb/references/thumbs/de6bec2538.jpg +0 -0
- package/kb/references/thumbs/dea13cbb1f.jpg +0 -0
- package/kb/references/thumbs/deff9202aa.jpg +0 -0
- package/kb/references/thumbs/df12835f8d.jpg +0 -0
- package/kb/references/thumbs/df57acbb3d.jpg +0 -0
- package/kb/references/thumbs/df8265009e.jpg +0 -0
- package/kb/references/thumbs/dfc99a1e8e.jpg +0 -0
- package/kb/references/thumbs/dfc9fde460.jpg +0 -0
- package/kb/references/thumbs/e038348eee.jpg +0 -0
- package/kb/references/thumbs/e04c2ae1d5.jpg +0 -0
- package/kb/references/thumbs/e070345c43.jpg +0 -0
- package/kb/references/thumbs/e0a0c84d47.jpg +0 -0
- package/kb/references/thumbs/e0af1e6b9e.jpg +0 -0
- package/kb/references/thumbs/e1025942c4.jpg +0 -0
- package/kb/references/thumbs/e11b6f5b82.jpg +0 -0
- package/kb/references/thumbs/e1553de3fc.jpg +0 -0
- package/kb/references/thumbs/e162ebe7cd.jpg +0 -0
- package/kb/references/thumbs/e1f1e531d8.jpg +0 -0
- package/kb/references/thumbs/e20842e5c9.jpg +0 -0
- package/kb/references/thumbs/e24e7d949b.jpg +0 -0
- package/kb/references/thumbs/e2a3e5d10a.jpg +0 -0
- package/kb/references/thumbs/e2de02615a.jpg +0 -0
- package/kb/references/thumbs/e305dad7dd.jpg +0 -0
- package/kb/references/thumbs/e52e3ef97f.jpg +0 -0
- package/kb/references/thumbs/e570e92d06.jpg +0 -0
- package/kb/references/thumbs/e583932fb9.jpg +0 -0
- package/kb/references/thumbs/e5da3c038f.jpg +0 -0
- package/kb/references/thumbs/e657634af3.jpg +0 -0
- package/kb/references/thumbs/e67946e3e1.jpg +0 -0
- package/kb/references/thumbs/e69e0f3639.jpg +0 -0
- package/kb/references/thumbs/e6ebe66031.jpg +0 -0
- package/kb/references/thumbs/e7db9563c9.jpg +0 -0
- package/kb/references/thumbs/e84a4c3b7b.jpg +0 -0
- package/kb/references/thumbs/e84cf52c27.jpg +0 -0
- package/kb/references/thumbs/e8707cf6e8.jpg +0 -0
- package/kb/references/thumbs/e888890b40.jpg +0 -0
- package/kb/references/thumbs/e8a502b418.jpg +0 -0
- package/kb/references/thumbs/e8ba68371d.jpg +0 -0
- package/kb/references/thumbs/e9098f54cc.jpg +0 -0
- package/kb/references/thumbs/e9144deae9.jpg +0 -0
- package/kb/references/thumbs/e93e98d757.jpg +0 -0
- package/kb/references/thumbs/e9762dd435.jpg +0 -0
- package/kb/references/thumbs/e9db4544c8.jpg +0 -0
- package/kb/references/thumbs/e9ddffeacd.jpg +0 -0
- package/kb/references/thumbs/e9e2cf1d7e.jpg +0 -0
- package/kb/references/thumbs/ea1e23e623.jpg +0 -0
- package/kb/references/thumbs/ea41200f5e.jpg +0 -0
- package/kb/references/thumbs/eaf4916e1a.jpg +0 -0
- package/kb/references/thumbs/eb2c505fc8.jpg +0 -0
- package/kb/references/thumbs/eb47943693.jpg +0 -0
- package/kb/references/thumbs/eb4ffd6625.jpg +0 -0
- package/kb/references/thumbs/eb82ac0a4d.jpg +0 -0
- package/kb/references/thumbs/ebeb345ade.jpg +0 -0
- package/kb/references/thumbs/ec4e2aba0a.jpg +0 -0
- package/kb/references/thumbs/ed683bc9f7.jpg +0 -0
- package/kb/references/thumbs/ed9e236a84.jpg +0 -0
- package/kb/references/thumbs/ee45e1caea.jpg +0 -0
- package/kb/references/thumbs/ee50bbf2e7.jpg +0 -0
- package/kb/references/thumbs/eed6ad3b7b.jpg +0 -0
- package/kb/references/thumbs/eee247fd70.jpg +0 -0
- package/kb/references/thumbs/ef87b6cb99.jpg +0 -0
- package/kb/references/thumbs/ef87dccf04.jpg +0 -0
- package/kb/references/thumbs/ef941bba1e.jpg +0 -0
- package/kb/references/thumbs/effc14fb2c.jpg +0 -0
- package/kb/references/thumbs/f04aa5a191.jpg +0 -0
- package/kb/references/thumbs/f13f12485f.jpg +0 -0
- package/kb/references/thumbs/f16a5c0fd4.jpg +0 -0
- package/kb/references/thumbs/f1ba7a7a8f.jpg +0 -0
- package/kb/references/thumbs/f1bc4c9c19.jpg +0 -0
- package/kb/references/thumbs/f1ee827878.jpg +0 -0
- package/kb/references/thumbs/f21736aeff.jpg +0 -0
- package/kb/references/thumbs/f22bc41213.jpg +0 -0
- package/kb/references/thumbs/f266aaffcc.jpg +0 -0
- package/kb/references/thumbs/f2c0ed0d65.jpg +0 -0
- package/kb/references/thumbs/f32ac710fd.jpg +0 -0
- package/kb/references/thumbs/f32b697ff6.jpg +0 -0
- package/kb/references/thumbs/f39c883904.jpg +0 -0
- package/kb/references/thumbs/f3b9d55e6e.jpg +0 -0
- package/kb/references/thumbs/f3c1763e14.jpg +0 -0
- package/kb/references/thumbs/f40853fd43.jpg +0 -0
- package/kb/references/thumbs/f4fd07dc4d.jpg +0 -0
- package/kb/references/thumbs/f50b756ee5.jpg +0 -0
- package/kb/references/thumbs/f51272e9b3.jpg +0 -0
- package/kb/references/thumbs/f538895eee.jpg +0 -0
- package/kb/references/thumbs/f5d28081b5.jpg +0 -0
- package/kb/references/thumbs/f6046c9501.jpg +0 -0
- package/kb/references/thumbs/f62e4af316.jpg +0 -0
- package/kb/references/thumbs/f6f6bffeb8.jpg +0 -0
- package/kb/references/thumbs/f736e00af6.jpg +0 -0
- package/kb/references/thumbs/f7932da47d.jpg +0 -0
- package/kb/references/thumbs/f879e118ca.jpg +0 -0
- package/kb/references/thumbs/f88257215d.jpg +0 -0
- package/kb/references/thumbs/f88c45a6d7.jpg +0 -0
- package/kb/references/thumbs/f89a7b646e.jpg +0 -0
- package/kb/references/thumbs/f8a1e7a274.jpg +0 -0
- package/kb/references/thumbs/f9183cf2b6.jpg +0 -0
- package/kb/references/thumbs/f995de5ce9.jpg +0 -0
- package/kb/references/thumbs/f9ada2c3e1.jpg +0 -0
- package/kb/references/thumbs/f9d4616f89.jpg +0 -0
- package/kb/references/thumbs/f9f4307bb0.jpg +0 -0
- package/kb/references/thumbs/fa070122c7.jpg +0 -0
- package/kb/references/thumbs/fa29cc1369.jpg +0 -0
- package/kb/references/thumbs/fa791a4dab.jpg +0 -0
- package/kb/references/thumbs/fa8060509e.jpg +0 -0
- package/kb/references/thumbs/fafeed4003.jpg +0 -0
- package/kb/references/thumbs/fb1c024f38.jpg +0 -0
- package/kb/references/thumbs/fb77aa28b3.jpg +0 -0
- package/kb/references/thumbs/fb9f7f1966.jpg +0 -0
- package/kb/references/thumbs/fba8a8c2da.jpg +0 -0
- package/kb/references/thumbs/fce2754c36.jpg +0 -0
- package/kb/references/thumbs/fce2fb83df.jpg +0 -0
- package/kb/references/thumbs/fd4c51bb47.jpg +0 -0
- package/kb/references/thumbs/fd8c269817.jpg +0 -0
- package/kb/references/thumbs/fdb7b250ca.jpg +0 -0
- package/kb/references/thumbs/fe18088de8.jpg +0 -0
- package/kb/references/thumbs/fe4f742ba7.jpg +0 -0
- package/kb/references/thumbs/fe64742ce7.jpg +0 -0
- package/kb/references/thumbs/feb5d62161.jpg +0 -0
- package/kb/references/thumbs/fed21b3e98.jpg +0 -0
- package/kb/references/thumbs/fed2205951.jpg +0 -0
- package/kb/references/thumbs/ffdacf1715.jpg +0 -0
- package/kb/sections/_buttons.md +93 -0
- package/kb/sections/_finish.md +325 -0
- package/kb/sections/about-founder-note.md +85 -0
- package/kb/sections/about-team-roster.md +86 -0
- package/kb/sections/alternating-rows-live-tiles.md +110 -0
- package/kb/sections/annotated-product-shot.md +105 -0
- package/kb/sections/asymmetric-bento-dominant.md +114 -0
- package/kb/sections/before-after-features.md +104 -0
- package/kb/sections/before-after-repair.md +107 -0
- package/kb/sections/bento-lead-tile.md +112 -0
- package/kb/sections/book-a-slot-honest.md +116 -0
- package/kb/sections/capability-prompt-ledger.md +68 -0
- package/kb/sections/case-study-band.md +113 -0
- package/kb/sections/centred-stack-hero.md +107 -0
- package/kb/sections/closing-demo-final-state.md +81 -0
- package/kb/sections/closing-full-bleed-line-and-form.md +86 -0
- package/kb/sections/closing-result-previewed.md +88 -0
- package/kb/sections/closing-three-real-steps.md +87 -0
- package/kb/sections/command-reference-table.md +70 -0
- package/kb/sections/comparison-old-way-bars.md +88 -0
- package/kb/sections/comparison-same-task-two-ways.md +88 -0
- package/kb/sections/day-of-the-place.md +102 -0
- package/kb/sections/docs-reading-rail.md +88 -0
- package/kb/sections/editorial-columns-hero.md +107 -0
- package/kb/sections/editorial-essay-pull-quote.md +112 -0
- package/kb/sections/events-calendar-list.md +107 -0
- package/kb/sections/explorable-control-demo.md +102 -0
- package/kb/sections/faq-open-answers-editorial.md +85 -0
- package/kb/sections/faq-ruled-accordion-aside.md +87 -0
- package/kb/sections/footer-closing-scene.md +84 -0
- package/kb/sections/footer-editorial-sitemap.md +85 -0
- package/kb/sections/footer-minimal-credits.md +80 -0
- package/kb/sections/full-bleed-image-band.md +103 -0
- package/kb/sections/full-bleed-image-hero.md +105 -0
- package/kb/sections/gallery-first-hero.md +109 -0
- package/kb/sections/horizontal-scroll-strip.md +101 -0
- package/kb/sections/how-i-work-own-voice.md +111 -0
- package/kb/sections/how-it-works-in-place.md +109 -0
- package/kb/sections/impact-numbers-sourced.md +109 -0
- package/kb/sections/install-steps-per-agent.md +77 -0
- package/kb/sections/masonry-real-work.md +109 -0
- package/kb/sections/numbered-editorial-columns.md +105 -0
- package/kb/sections/object-first-hero.md +106 -0
- package/kb/sections/object-timeline.md +105 -0
- package/kb/sections/one-huge-number.md +100 -0
- package/kb/sections/one-person-story.md +103 -0
- package/kb/sections/order-ahead-ticket.md +119 -0
- package/kb/sections/press-strip-quoted.md +106 -0
- package/kb/sections/pricing-comparison-table-sticky.md +94 -0
- package/kb/sections/pricing-free-pro-upgrade-moment.md +85 -0
- package/kb/sections/pricing-plan-beside-product.md +88 -0
- package/kb/sections/pricing-plans-ledger.md +94 -0
- package/kb/sections/pricing-usage-calculator.md +93 -0
- package/kb/sections/printed-menu-card.md +110 -0
- package/kb/sections/process-happening.md +102 -0
- package/kb/sections/project-index-ruled.md +106 -0
- package/kb/sections/proof-logo-cloud-with-reasons.md +82 -0
- package/kb/sections/proof-measured-numbers.md +88 -0
- package/kb/sections/proof-output-wall.md +84 -0
- package/kb/sections/proof-single-honest-quote.md +81 -0
- package/kb/sections/resources-searchable-list.md +110 -0
- package/kb/sections/rooms-and-rates.md +107 -0
- package/kb/sections/ruled-facts-table.md +108 -0
- package/kb/sections/ruled-list-pinned-visual.md +107 -0
- package/kb/sections/scroll-scrubbed-product-demo.md +103 -0
- package/kb/sections/self-playing-run-demo.md +104 -0
- package/kb/sections/services-price-ledger.md +106 -0
- package/kb/sections/signup-form-with-next-steps.md +86 -0
- package/kb/sections/signup-waitlist-place-in-line.md +90 -0
- package/kb/sections/spec-sheet-features.md +108 -0
- package/kb/sections/split-hero-live-object.md +108 -0
- package/kb/sections/sticky-feature-narrative.md +104 -0
- package/kb/sections/todays-board-hero.md +112 -0
- package/kb/sections/touchable-tool-hero.md +111 -0
- package/kb/sections/typographic-statement-hero.md +99 -0
- package/kb/sections/visit-hours-map.md +110 -0
- package/kb/sections/ways-to-help-honest.md +115 -0
- package/kb/sources/design-reference.md +991 -0
- package/kb/sources/product-and-docs.md +37 -0
- package/kb/systems/1win/DESIGN.md +463 -0
- package/kb/systems/LICENSE-awesome-design-md +21 -0
- package/kb/systems/README.md +7 -0
- package/kb/systems/airbnb/DESIGN.md +545 -0
- package/kb/systems/airtable/DESIGN.md +554 -0
- package/kb/systems/aljazeera/DESIGN.md +594 -0
- package/kb/systems/apple/DESIGN.md +562 -0
- package/kb/systems/binance/DESIGN.md +634 -0
- package/kb/systems/bmw/DESIGN.md +544 -0
- package/kb/systems/bmw-m/DESIGN.md +503 -0
- package/kb/systems/bugatti/DESIGN.md +454 -0
- package/kb/systems/cal/DESIGN.md +542 -0
- package/kb/systems/categories.json +87 -0
- package/kb/systems/claude/DESIGN.md +589 -0
- package/kb/systems/clay/DESIGN.md +541 -0
- package/kb/systems/clickhouse/DESIGN.md +544 -0
- package/kb/systems/cohere/DESIGN.md +451 -0
- package/kb/systems/coinbase/DESIGN.md +570 -0
- package/kb/systems/composio/DESIGN.md +506 -0
- package/kb/systems/cursor/DESIGN.md +537 -0
- package/kb/systems/dell-1996/DESIGN.md +632 -0
- package/kb/systems/dji/DESIGN.md +882 -0
- package/kb/systems/elevenlabs/DESIGN.md +504 -0
- package/kb/systems/expo/DESIGN.md +526 -0
- package/kb/systems/ferrari/DESIGN.md +531 -0
- package/kb/systems/figma/DESIGN.md +578 -0
- package/kb/systems/framer/DESIGN.md +544 -0
- package/kb/systems/hashicorp/DESIGN.md +575 -0
- package/kb/systems/hp/DESIGN.md +670 -0
- package/kb/systems/ibm/DESIGN.md +550 -0
- package/kb/systems/intercom/DESIGN.md +546 -0
- package/kb/systems/jobright/DESIGN.md +525 -0
- package/kb/systems/kraken/DESIGN.md +125 -0
- package/kb/systems/lamborghini/DESIGN.md +288 -0
- package/kb/systems/linear.app/DESIGN.md +548 -0
- package/kb/systems/lovable/DESIGN.md +298 -0
- package/kb/systems/mastercard/DESIGN.md +365 -0
- package/kb/systems/mercury/DESIGN.md +568 -0
- package/kb/systems/meta/DESIGN.md +683 -0
- package/kb/systems/minimax/DESIGN.md +746 -0
- package/kb/systems/mintlify/DESIGN.md +852 -0
- package/kb/systems/miro/DESIGN.md +825 -0
- package/kb/systems/mistral.ai/DESIGN.md +773 -0
- package/kb/systems/mongodb/DESIGN.md +767 -0
- package/kb/systems/mydealz/DESIGN.md +551 -0
- package/kb/systems/nerdwallet/DESIGN.md +722 -0
- package/kb/systems/nike/DESIGN.md +575 -0
- package/kb/systems/nintendo-2001/DESIGN.md +649 -0
- package/kb/systems/notion/DESIGN.md +821 -0
- package/kb/systems/nvidia/DESIGN.md +640 -0
- package/kb/systems/ollama/DESIGN.md +539 -0
- package/kb/systems/opencode.ai/DESIGN.md +521 -0
- package/kb/systems/oracle/DESIGN.md +778 -0
- package/kb/systems/oura/DESIGN.md +616 -0
- package/kb/systems/pinterest/DESIGN.md +597 -0
- package/kb/systems/playstation/DESIGN.md +661 -0
- package/kb/systems/posthog/DESIGN.md +690 -0
- package/kb/systems/raycast/DESIGN.md +669 -0
- package/kb/systems/renault/DESIGN.md +589 -0
- package/kb/systems/replicate/DESIGN.md +616 -0
- package/kb/systems/resend/DESIGN.md +585 -0
- package/kb/systems/revolut/DESIGN.md +636 -0
- package/kb/systems/runwayml/DESIGN.md +244 -0
- package/kb/systems/sanity/DESIGN.md +357 -0
- package/kb/systems/sap/DESIGN.md +639 -0
- package/kb/systems/sentry/DESIGN.md +551 -0
- package/kb/systems/shopify/DESIGN.md +516 -0
- package/kb/systems/slack/DESIGN.md +482 -0
- package/kb/systems/spacex/DESIGN.md +363 -0
- package/kb/systems/spotify/DESIGN.md +246 -0
- package/kb/systems/starbucks/DESIGN.md +580 -0
- package/kb/systems/stripe/DESIGN.md +487 -0
- package/kb/systems/supabase/DESIGN.md +462 -0
- package/kb/systems/superhuman/DESIGN.md +448 -0
- package/kb/systems/tesla/DESIGN.md +286 -0
- package/kb/systems/theverge/DESIGN.md +339 -0
- package/kb/systems/together.ai/DESIGN.md +633 -0
- package/kb/systems/uber/DESIGN.md +636 -0
- package/kb/systems/vercel/DESIGN.md +736 -0
- package/kb/systems/vodafone/DESIGN.md +538 -0
- package/kb/systems/voltagent/DESIGN.md +521 -0
- package/kb/systems/warp/DESIGN.md +526 -0
- package/kb/systems/webflow/DESIGN.md +588 -0
- package/kb/systems/wired/DESIGN.md +497 -0
- package/kb/systems/wise/DESIGN.md +544 -0
- package/kb/systems/x.ai/DESIGN.md +465 -0
- package/kb/systems/zapier/DESIGN.md +537 -0
- package/kb/taste/garvit-by-job.md +46 -0
- package/kb/taste/garvit-portfolio.md +94 -0
- package/kb/universal/color-roles.md +26 -0
- package/kb/universal/copy.md +16 -0
- package/kb/universal/details.md +31 -0
- package/kb/universal/first-screen.md +30 -0
- package/kb/universal/fit-the-codebase.md +29 -0
- package/kb/universal/layout-width.md +28 -0
- package/kb/universal/motion-feedback.md +23 -0
- package/kb/universal/signature.md +29 -0
- package/kb/universal/states.md +17 -0
- package/kb/universal/type-app.md +33 -0
- package/kb/universal/type-marketing.md +28 -0
- package/package.json +37 -4
- package/scripts/pack-assets.mjs +58 -0
- package/skill/palate/SKILL.md +261 -0
- package/src/auth-recheck.mjs +4 -0
- package/src/auth.mjs +416 -0
- package/src/capture.mjs +182 -0
- package/src/catalog/classify.mjs +96 -0
- package/src/catalog/curated.mjs +128 -0
- package/src/catalog/index-build.mjs +67 -0
- package/src/catalog/literal.mjs +146 -0
- package/src/catalog/store.mjs +40 -0
- package/src/catalog/vibrant.mjs +126 -0
- package/src/check.mjs +93 -0
- package/src/concept.mjs +157 -0
- package/src/context.mjs +226 -0
- package/src/copy.mjs +188 -0
- package/src/devserver.mjs +135 -0
- package/src/fontgenre.mjs +44 -0
- package/src/guard-agents.mjs +213 -0
- package/src/guard.mjs +377 -0
- package/src/kb.mjs +91 -0
- package/src/look.mjs +312 -0
- package/src/loop/ambition.mjs +620 -0
- package/src/loop/animate.mjs +70 -0
- package/src/loop/artdirection.mjs +264 -0
- package/src/loop/bench.mjs +184 -0
- package/src/loop/benchpick.mjs +155 -0
- package/src/loop/blueprint.mjs +620 -0
- package/src/loop/board.html +180 -0
- package/src/loop/build.mjs +393 -0
- package/src/loop/buttons.mjs +413 -0
- package/src/loop/common.mjs +206 -0
- package/src/loop/cost.mjs +63 -0
- package/src/loop/craft.mjs +152 -0
- package/src/loop/craftchecks.mjs +148 -0
- package/src/loop/critique.mjs +393 -0
- package/src/loop/details.mjs +225 -0
- package/src/loop/direction.html +168 -0
- package/src/loop/direction.mjs +256 -0
- package/src/loop/directions.json +8 -0
- package/src/loop/explore.mjs +219 -0
- package/src/loop/fail.mjs +110 -0
- package/src/loop/fix.mjs +194 -0
- package/src/loop/floor.mjs +677 -0
- package/src/loop/forget.mjs +52 -0
- package/src/loop/gate.mjs +1053 -0
- package/src/loop/grid.html +123 -0
- package/src/loop/growth.mjs +118 -0
- package/src/loop/icons.mjs +109 -0
- package/src/loop/imagery.mjs +1280 -0
- package/src/loop/judge-api.mjs +245 -0
- package/src/loop/judge.mjs +167 -0
- package/src/loop/layouts.mjs +492 -0
- package/src/loop/library.mjs +82 -0
- package/src/loop/lookplan.mjs +182 -0
- package/src/loop/lookvec.mjs +645 -0
- package/src/loop/match.mjs +923 -0
- package/src/loop/matched.mjs +26 -0
- package/src/loop/myrefs.mjs +151 -0
- package/src/loop/pick.mjs +213 -0
- package/src/loop/picker.html +118 -0
- package/src/loop/plan.mjs +289 -0
- package/src/loop/polish.mjs +39 -0
- package/src/loop/research-learn.mjs +41 -0
- package/src/loop/research.mjs +1130 -0
- package/src/loop/rulereview.mjs +146 -0
- package/src/loop/rules-board.html +199 -0
- package/src/loop/rules.mjs +939 -0
- package/src/loop/similar.mjs +276 -0
- package/src/loop/states-source.mjs +258 -0
- package/src/loop/states.mjs +1547 -0
- package/src/loop/stats.mjs +78 -0
- package/src/loop/taste.mjs +81 -0
- package/src/loop/timeline.mjs +149 -0
- package/src/loop/variants.mjs +228 -0
- package/src/loop/vary.mjs +249 -0
- package/src/mcp/server.mjs +1246 -0
- package/src/motion.mjs +207 -0
- package/src/moves.mjs +242 -0
- package/src/novelty.mjs +116 -0
- package/src/page-ambition.js +181 -0
- package/src/page-craft.js +724 -0
- package/src/page-details.js +415 -0
- package/src/page-devices.js +365 -0
- package/src/page-fingerprint.js +281 -0
- package/src/page-gate.js +1307 -0
- package/src/page-look.js +698 -0
- package/src/page-metrics.js +78 -0
- package/src/page-principles.js +1167 -0
- package/src/page-rules.js +308 -0
- package/src/page-sections.js +211 -0
- package/src/page-states.js +703 -0
- package/src/paths.mjs +13 -0
- package/src/png.mjs +107 -0
- package/src/point/auto.mjs +224 -0
- package/src/point/locate.mjs +114 -0
- package/src/point/overlay.js +1516 -0
- package/src/point/server.mjs +1122 -0
- package/src/point/trail.mjs +219 -0
- package/src/pool.mjs +80 -0
- package/src/principles.mjs +279 -0
- package/src/refs.mjs +299 -0
- package/src/robots.mjs +145 -0
- package/src/sections.mjs +210 -0
- package/src/shoot.mjs +235 -0
- package/src/systems/apply.mjs +355 -0
- package/src/systems/load.mjs +251 -0
- package/src/systems/pick.mjs +213 -0
- package/templates/vite-react-tw/index.html +12 -0
- package/templates/vite-react-tw/package.json +25 -0
- package/templates/vite-react-tw/src/App.tsx +3 -0
- package/templates/vite-react-tw/src/index.css +1 -0
- package/templates/vite-react-tw/src/main.tsx +10 -0
- package/templates/vite-react-tw/tsconfig.app.json +26 -0
- package/templates/vite-react-tw/tsconfig.json +7 -0
- package/templates/vite-react-tw/tsconfig.node.json +23 -0
- package/templates/vite-react-tw/vite.config.ts +8 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
// The similarity gate: a page may not look like the user's other projects, or copy a reference captured for it.
|
|
2
|
+
// Repeating your own last look is the quiet way AI output converges. Used by palate shoot (it fails), palate
|
|
3
|
+
// ambition and palate check (a page isn't done while it fails). The agent fixes it; it isn't a question for
|
|
4
|
+
// the user, unless they asked to match another project (--like <project>, or "match <project>" in the job).
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { readJson } from "./common.mjs";
|
|
8
|
+
import { ARCH_NAME, compare, describe, firstScreenSimilarity, isBenchRecord, pngThumb, projectId, readLayouts, sameScreen, screenKindOf, shapeOf, skeletonSimilarity, structuralSimilarity, styleSimilarity, visualSimilarity } from "./layouts.mjs";
|
|
9
|
+
import { logGate } from "./growth.mjs";
|
|
10
|
+
import { MATCHED_NOTE, matchedRoute } from "./matched.mjs";
|
|
11
|
+
|
|
12
|
+
// Thresholds on compare()'s 0–1 scale, measured on this machine's layout memory (50 screens, Oct 26 2026):
|
|
13
|
+
// 290 pairs of screens of the same group (marketing or app) made independently in different projects score
|
|
14
|
+
// median 0.23, 95th percentile 0.41, 99th 0.47, highest 0.51 (a pricing page against a landing variant). The
|
|
15
|
+
// Tidepool landing that reused palate-site's emerald, centred hero and section rhythm scored 0.69. 0.50 sits
|
|
16
|
+
// above 99% of independent pairs and well below that repeat (and below SIMILAR, 0.60, where shoot only warned).
|
|
17
|
+
export const GATE = 0.5;
|
|
18
|
+
// A captured reference is someone else's product, so the bar is stricter: about the independent pairs' 95th
|
|
19
|
+
// percentile (0.41). A reference with only a screenshot (no fingerprint) is judged as a picture: its first
|
|
20
|
+
// screen alike above 0.72 (98% of independent pairs score lower) and its type and colour alike too.
|
|
21
|
+
export const REF_GATE = 0.42;
|
|
22
|
+
// Structure alone (structuralSimilarity: no colour), against the user's other projects. On the layout memory
|
|
23
|
+
// (Oct 27 2026, 321 pairs of independently made screens): median 0.12, 95th percentile 0.44, 99th 0.48. Kiln,
|
|
24
|
+
// which passed the colour-inclusive gate at 50% by repainting itself sage, scored 0.46–0.48 against six
|
|
25
|
+
// palate-site pages: the same skeleton in a new colour. 0.45 catches that and passes 95% of independent pairs.
|
|
26
|
+
export const STRUCTURE_GATE = 0.45;
|
|
27
|
+
export const REF_PICTURE_GATE = 0.72;
|
|
28
|
+
|
|
29
|
+
const pct = (v) => `${Math.round((v ?? 0) * 100)}%`;
|
|
30
|
+
// A score beside its gate, with a decimal when whole percents would make them look equal (49.6% vs a 50% gate).
|
|
31
|
+
const pctBy = (v, gate) => (Math.round((v ?? 0) * 100) === Math.round(gate * 100) ? `${((v ?? 0) * 100).toFixed(1)}%` : pct(v));
|
|
32
|
+
const hexRgb = (h) => (h && /^#[0-9a-f]{6}$/i.test(h) ? [1, 3, 5].map((i) => parseInt(h.slice(i, i + 2), 16)) : null);
|
|
33
|
+
const dist = (a, b) => Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2]);
|
|
34
|
+
const weightWord = (w) => (w <= 300 ? "light" : w <= 450 ? "regular" : w <= 650 ? "semibold" : "heavy");
|
|
35
|
+
const headWords = (h) => (h ? `${h.size}px ${weightWord(h.weight)}${h.align === "center" ? " centred" : ""}` : "?");
|
|
36
|
+
export const whereKey = (r) => `${r.project} ${r.route}`;
|
|
37
|
+
const group = (kind) => (kind === "app" ? "app" : "marketing");
|
|
38
|
+
|
|
39
|
+
// Why two screens are alike, part by part (0–1 each, most alike first), with what to change.
|
|
40
|
+
export function whySimilar(a, b) {
|
|
41
|
+
const parts = [];
|
|
42
|
+
if (a.skeleton?.length && b.skeleton?.length)
|
|
43
|
+
parts.push({ id: "skeleton", label: "page skeleton", score: skeletonSimilarity(a.skeleton, b.skeleton), same: `${a.skeleton.length} bands vs ${b.skeleton.length}, in nearly the same order (${a.skeleton.join(" ")} | ${b.skeleton.join(" ")})`, change: "a different section rhythm: change the order and number of bands and how each is built (not every section a heading over a grid of cards)" });
|
|
44
|
+
if (a.shape && b.shape) {
|
|
45
|
+
const fs1 = firstScreenSimilarity(a.firstScreen, b.firstScreen) ?? 0;
|
|
46
|
+
const same = a.shape === b.shape;
|
|
47
|
+
const alt = a.shape === "centred" ? "a left-led or split first screen, or the product itself as the hero" : `a first screen that isn't ${ARCH_NAME[b.shape] ?? b.shape}`;
|
|
48
|
+
parts.push({ id: "hero", label: "hero structure", score: (same ? 0.6 : 0) + 0.4 * fs1, same: `${ARCH_NAME[a.shape] ?? a.shape} vs ${ARCH_NAME[b.shape] ?? b.shape}; headline ${headWords(a.style?.headline)} vs ${headWords(b.style?.headline)}`, change: `${alt}, built from this product's own metaphor` });
|
|
49
|
+
}
|
|
50
|
+
const sa = a.style ?? {}, sb = b.style ?? {};
|
|
51
|
+
const pal = [];
|
|
52
|
+
const [aa, ab] = [hexRgb(sa.accent), hexRgb(sb.accent)];
|
|
53
|
+
if (aa && ab) pal.push(1 - Math.min(1, dist(aa, ab) / 150));
|
|
54
|
+
const [ba, bb] = [hexRgb(sa.bg), hexRgb(sb.bg)];
|
|
55
|
+
if (ba && bb) pal.push(1 - Math.min(1, dist(ba, bb) / 120));
|
|
56
|
+
if (pal.length) parts.push({ id: "palette", label: "palette", score: pal.reduce((x, y) => x + y, 0) / pal.length, same: `accent ${sa.accent ?? "?"} vs ${sb.accent ?? "?"}, canvas ${sa.bg ?? "?"} vs ${sb.bg ?? "?"}`, change: `this product's own colour, from its name, users and direction${sb.accent ? ` (not ${sb.accent})` : ""}` });
|
|
57
|
+
const fa = (sa.fonts ?? []).map((f) => f.toLowerCase()), fb = (sb.fonts ?? []).map((f) => f.toLowerCase());
|
|
58
|
+
const type = [];
|
|
59
|
+
if (fa.length && fb.length) type.push(fa.filter((f) => fb.includes(f)).length / new Set([...fa, ...fb]).size);
|
|
60
|
+
if (sa.headline && sb.headline) {
|
|
61
|
+
type.push(1 - Math.min(1, (2 * Math.abs(sa.headline.size - sb.headline.size)) / Math.max(sa.headline.size, sb.headline.size)));
|
|
62
|
+
type.push(1 - Math.min(1, Math.abs(sa.headline.weight - sb.headline.weight) / 400));
|
|
63
|
+
}
|
|
64
|
+
if (type.length) parts.push({ id: "type", label: "type", score: type.reduce((x, y) => x + y, 0) / type.length, same: `${(sa.fonts ?? []).join(" + ") || "?"} vs ${(sb.fonts ?? []).join(" + ") || "?"}; headline ${headWords(sa.headline)} vs ${headWords(sb.headline)}`, change: "a display face, weight and scale with this product's character (not the same family at the same size and weight)" });
|
|
65
|
+
const vis = visualSimilarity(a.thumb, b.thumb);
|
|
66
|
+
if (vis != null) parts.push({ id: "picture", label: "first screen as a picture", score: vis, same: "content sits in the same places, in the same colours", change: "follows from changing the first screen's structure and colour" });
|
|
67
|
+
return parts.sort((x, y) => y.score - x.score);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ---- what the user asked to match
|
|
71
|
+
// "match palate-site", "like palate-site", "same look as palate-site", "consistent with palate-site": only
|
|
72
|
+
// names of projects palate knows count.
|
|
73
|
+
export function matchNote(text, projects) {
|
|
74
|
+
const t = String(text ?? "").toLowerCase();
|
|
75
|
+
return projects.filter((p) => new RegExp(`\\b(match(es|ing)?|like|same (look|style|design|feel) as|consistent with|in the style of|the look of)\\s+(the\\s+)?${p.toLowerCase().replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`).test(t));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const knownProjects = () => [...new Set(readLayouts().map((r) => r.project))];
|
|
79
|
+
|
|
80
|
+
// The newest build loop for this route: its like list and its job's match note.
|
|
81
|
+
export function likeOf(dir, route) {
|
|
82
|
+
const base = path.join(dir, ".palate", "variants");
|
|
83
|
+
if (!fs.existsSync(base)) return [];
|
|
84
|
+
const loops = fs.readdirSync(base).map((n) => readJson(path.join(base, n, "loop.json"), null)).filter((m) => m && m.mode === "build" && m.route === route).sort((a, b) => String(b.created).localeCompare(String(a.created)));
|
|
85
|
+
const m = loops[0];
|
|
86
|
+
if (!m) return [];
|
|
87
|
+
return [...new Set([...(m.like ?? []), ...matchNote(m.job, knownProjects())])];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ---- research.json (the live research step), read defensively: field names vary between versions.
|
|
91
|
+
const strings = (v) => (v == null ? [] : typeof v === "string" ? [v] : Array.isArray(v) ? v.flatMap(strings) : typeof v === "object" ? Object.values(v).flatMap(strings) : []);
|
|
92
|
+
const hostOf = (u) => {
|
|
93
|
+
try {
|
|
94
|
+
return new URL(u).hostname.replace(/^www\./, "");
|
|
95
|
+
} catch {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
export function readResearch(dir, { name } = {}) {
|
|
100
|
+
const base = path.join(dir, ".palate", "research");
|
|
101
|
+
if (!fs.existsSync(base)) return null;
|
|
102
|
+
const files = (name ? [name] : fs.readdirSync(base))
|
|
103
|
+
.map((n) => path.join(base, n, "research.json"))
|
|
104
|
+
.filter((f) => fs.existsSync(f))
|
|
105
|
+
.sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs);
|
|
106
|
+
if (!files.length) return null;
|
|
107
|
+
const file = files[0];
|
|
108
|
+
const j = readJson(file, null);
|
|
109
|
+
if (!j) return null;
|
|
110
|
+
const at = path.dirname(file);
|
|
111
|
+
const resolve = (p) => (!p || /^https?:/.test(p) ? null : path.isAbsolute(p) ? (fs.existsSync(p) ? p : null) : [path.join(at, p), path.join(dir, p)].find((f) => fs.existsSync(f)) ?? null);
|
|
112
|
+
const images = (v) => [...new Set(strings(v).filter((s) => /\.(png|jpe?g|webp)$/i.test(s)).map(resolve).filter(Boolean))];
|
|
113
|
+
// palate research keeps its references in `kept` (with their layout records); older shapes are read too.
|
|
114
|
+
const list = j.kept ?? j.references ?? j.refs ?? j.captured ?? j.captures ?? [];
|
|
115
|
+
const refs = (Array.isArray(list) ? list : Object.values(list)).map((r, i) => {
|
|
116
|
+
const shots = images([r.fold, r.screenshot, r.screenshots, r.shots, r.files, r.images, r.full, r.capture]);
|
|
117
|
+
const fold = shots.find((f) => /fold/i.test(f) && /\.png$/i.test(f)) ?? shots.find((f) => /\.png$/i.test(f)) ?? shots[0] ?? null;
|
|
118
|
+
const sections = (r.sections ?? []).map((s) => ({ kind: s.kind ?? s.name ?? "", name: s.name ?? s.kind ?? "", file: images([s.file, s.crop, s.screenshot, s.shot])[0] ?? null })).filter((s) => s.file);
|
|
119
|
+
return { id: r.id ?? `ref${i + 1}`, name: r.name ?? r.title ?? r.site ?? hostOf(r.url) ?? `reference ${i + 1}`, url: r.url ?? null, fold, shots, sections, layout: r.layout?.skeleton ? r.layout : null, fp: r.fingerprint ?? r.fp ?? null, measures: r.measures ?? r.measurements ?? null, notes: r.notes ?? r.why ?? r.take ?? r.works ?? null };
|
|
120
|
+
});
|
|
121
|
+
const d = j.direction ?? j.chosen ?? j.chosenDirection ?? j.choice ?? null;
|
|
122
|
+
const signature = normSignature(d?.signature ?? d?.signatureMove ?? d?.signature_move ?? j.signature ?? j.signatureMove ?? null);
|
|
123
|
+
return { file, name: path.basename(at), direction: d ? { name: d.name ?? d.title ?? null, summary: d.summary ?? d.why ?? d.what ?? null } : null, signature, refs, tags: j.tags ?? j.understanding?.tags ?? d?.tags ?? [], moves: j.moves ?? d?.moves ?? [] };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// A signature move in one shape: { name, what, move, selector, words }. Art directions write { move, moment }.
|
|
127
|
+
export function normSignature(sig) {
|
|
128
|
+
if (!sig) return null;
|
|
129
|
+
if (typeof sig === "string") return { name: sig, move: sig };
|
|
130
|
+
return { name: sig.name ?? sig.title ?? sig.move ?? sig.id ?? "the signature move", what: sig.what ?? sig.moment ?? sig.description ?? sig.do ?? sig.summary ?? null, move: sig.move ?? sig.id ?? null, selector: sig.selector ?? null, words: sig.words ?? sig.keywords ?? null };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const rgbHex = (c) => {
|
|
134
|
+
const m = String(c ?? "").match(/[\d.]+/g);
|
|
135
|
+
if (!m || m.length < 3 || (m[3] != null && Number(m[3]) < 0.5)) return /^#[0-9a-f]{6}$/i.test(c ?? "") ? c : null;
|
|
136
|
+
return "#" + m.slice(0, 3).map((v) => Math.round(Number(v)).toString(16).padStart(2, "0")).join("");
|
|
137
|
+
};
|
|
138
|
+
// A reference as a layout record: its fingerprint when research kept one, else its measures and screenshot.
|
|
139
|
+
export function refRecord(r, kind = "marketing") {
|
|
140
|
+
if (r.layout?.skeleton) return { ...r.layout, project: r.name, route: r.url ?? "" };
|
|
141
|
+
const thumb = r.fold && /\.png$/i.test(r.fold) ? pngThumb(r.fold) : null;
|
|
142
|
+
if (r.fp?.skeleton) return { project: r.name, route: r.url ?? "", skeleton: r.fp.skeleton, firstScreen: r.fp.firstScreen ?? null, shape: shapeOf(r.fp, kind), style: r.fp.style ?? null, thumb };
|
|
143
|
+
const m = r.measures?.[1440] ?? r.measures ?? null;
|
|
144
|
+
const style = m ? { fonts: m.fonts ?? [], headline: m.headline?.size ? { size: Math.round(parseFloat(m.headline.size)), weight: Number(m.headline.weight) || 400, align: m.headline.align ?? null } : null, bg: rgbHex(m.backgrounds?.[0] ?? m.bg), accent: rgbHex(m.accent) } : null;
|
|
145
|
+
if (!thumb && !style) return null;
|
|
146
|
+
return { project: r.name, route: r.url ?? "", skeleton: [], firstScreen: null, shape: null, style, thumb };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ---- the gate
|
|
150
|
+
// rec: this page as a layout record (layoutRecord). like: projects the user asked to match.
|
|
151
|
+
export function similarityGate(dir, route, rec, { like = [] } = {}) {
|
|
152
|
+
// A route built from the user's own design (palate match) may look like anything, their other projects included.
|
|
153
|
+
if (matchedRoute(dir, route)) return { pass: true, exempt: true, screen: screenKindOf(rec), compared: 0, threshold: GATE, structureThreshold: STRUCTURE_GATE, refThreshold: REF_GATE, hits: [], refHits: [], matched: [], best: null, research: null, partsFor: () => null };
|
|
154
|
+
const pid = projectId(dir);
|
|
155
|
+
const likes = new Set([...like, ...likeOf(dir, route)].map((x) => String(x).toLowerCase()));
|
|
156
|
+
// Like against like: marketing pages against marketing pages (vary loops record a section kind, e.g. "hero"), app
|
|
157
|
+
// screens against app screens, and only the same kind of screen (a landing page against landing pages, not a
|
|
158
|
+
// three-band pricing variant: those skeletons can't be made unlike without breaking either page).
|
|
159
|
+
// Benchmark pages are palate's own test runs, not the user's projects: they don't count against a page.
|
|
160
|
+
const others = readLayouts().filter((h) => h.pid !== pid && !isBenchRecord(h) && group(h.kind) === group(rec.kind) && h.skeleton?.length && sameScreen(rec, h));
|
|
161
|
+
const scored = others.map((h) => ({ h, ...compare(rec, h), structural: structuralSimilarity(rec, h) })).sort((a, b) => Math.max(b.total, b.structural) - Math.max(a.total, a.structural));
|
|
162
|
+
const asked = (p) => likes.has(String(p).toLowerCase());
|
|
163
|
+
const over = (m) => m.total > GATE || m.structural > STRUCTURE_GATE;
|
|
164
|
+
// overall / structure: the two scores; overOverall / structural: which gate each one failed.
|
|
165
|
+
const hit = (m) => ({ where: whereKey(m.h), project: m.h.project, route: m.h.route, source: m.h.picked ? "picked" : m.h.source === "screen" ? "shipped" : "variant", at: m.h.at, total: m.structural > STRUCTURE_GATE && m.total <= GATE ? m.structural : m.total, overall: m.total, structure: m.structural, overOverall: m.total > GATE, structural: m.structural > STRUCTURE_GATE, parts: whySimilar(rec, m.h), look: describe(m.h) });
|
|
166
|
+
// One match per other project (its closest screen): three variants of one page are one problem.
|
|
167
|
+
const seen = new Set();
|
|
168
|
+
const hits = scored.filter((m) => over(m) && !asked(m.h.project) && !seen.has(m.h.project) && seen.add(m.h.project)).slice(0, 3).map(hit);
|
|
169
|
+
const matched = scored.filter((m) => asked(m.h.project)).slice(0, 1).map(hit);
|
|
170
|
+
const research = readResearch(dir);
|
|
171
|
+
const refHits = [];
|
|
172
|
+
for (const r of research?.refs ?? []) {
|
|
173
|
+
if (asked(r.name)) continue;
|
|
174
|
+
const rr = refRecord(r, rec.kind);
|
|
175
|
+
if (!rr) continue;
|
|
176
|
+
const c = compare(rec, rr);
|
|
177
|
+
const full = Boolean(rr.skeleton.length);
|
|
178
|
+
const style = styleSimilarity(rec.style, rr.style) ?? 0;
|
|
179
|
+
const over = full ? c.total > REF_GATE : (c.visual ?? 0) > REF_PICTURE_GATE && style >= 0.6;
|
|
180
|
+
if (over) refHits.push({ where: `reference ${r.name}`, project: r.name, route: r.url ?? "", source: "reference", total: full ? c.total : c.visual, parts: whySimilar(rec, rr), picture: !full });
|
|
181
|
+
}
|
|
182
|
+
const best = scored[0] ? { where: whereKey(scored[0].h), total: scored[0].total, structure: scored[0].structural } : null;
|
|
183
|
+
const pass = !hits.length && !refHits.length;
|
|
184
|
+
const partsFor = (where) => {
|
|
185
|
+
const m = scored.find((x) => whereKey(x.h) === where);
|
|
186
|
+
return m ? { total: m.total, parts: Object.fromEntries(whySimilar(rec, m.h).map((p) => [p.id, p.score])) } : null;
|
|
187
|
+
};
|
|
188
|
+
return { pass, screen: screenKindOf(rec), compared: others.length, threshold: GATE, structureThreshold: STRUCTURE_GATE, refThreshold: REF_GATE, hits, refHits, matched, best, research: research ? research.file : null, partsFor };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// The gate in words, for shoot, ambition and check. Every number says which gate it is measured against: the
|
|
192
|
+
// overall gate (structure, picture, type and colour) at 50%, the structure gate (colour left out) at 45%, a
|
|
193
|
+
// captured reference's gate at 42% (or 72% for its screenshot alone).
|
|
194
|
+
export function gateLines(g, route) {
|
|
195
|
+
const overallGate = g.threshold ?? GATE, structureGate = g.structureThreshold ?? STRUCTURE_GATE;
|
|
196
|
+
const gates = `overall gate ${pct(overallGate)}, structure gate ${pct(structureGate)}`;
|
|
197
|
+
const kind = g.screen ? `${g.screen} screen` : "screen of this kind";
|
|
198
|
+
if (g.exempt) return [`## Similarity gate: not applied`, `- ${MATCHED_NOTE(route)}`];
|
|
199
|
+
if (g.pass)
|
|
200
|
+
return [
|
|
201
|
+
`## Similarity gate: pass`,
|
|
202
|
+
`- ${g.best ? `Closest ${kind} in another project: ${g.best.where}: ${pctBy(g.best.total, overallGate)} overall${g.best.structure != null ? `, ${pctBy(g.best.structure, structureGate)} in structure` : ""} (${gates}).` : `No ${kind}s in your other projects yet (${gates}; other kinds of screen aren't compared).`}${g.research ? " No captured reference is too close." : ""}`,
|
|
203
|
+
...g.matched.map((m) => `- Matches ${m.where} (${pct(m.total)}), as asked: that project's look is allowed here.`),
|
|
204
|
+
];
|
|
205
|
+
const out = [`## Similarity gate: FAIL (palate shoot fails until this passes; the page isn't done)`];
|
|
206
|
+
const item = (m, limit, gateName) => {
|
|
207
|
+
const close = m.parts.filter((p) => p.score >= 0.6);
|
|
208
|
+
const list = (close.length ? close : m.parts.slice(0, 2)).slice(0, 4);
|
|
209
|
+
const where = m.source === "reference" ? `${m.where}${m.picture ? " (its screenshot)" : ""}` : `${m.where} (${m.source}, ${String(m.at ?? "").slice(0, 10)}), another of your projects`;
|
|
210
|
+
const recolour = "A new colour won't fix this: change the skeleton, the first screen's shape and the section archetypes";
|
|
211
|
+
let score;
|
|
212
|
+
if (m.source === "reference") score = `${pctBy(m.total, limit)} like ${where}; the ${gateName} is ${pct(limit)}`;
|
|
213
|
+
else if (m.structural && m.overOverall) score = `${pctBy(m.overall, overallGate)} overall like ${where} (the overall gate is ${pct(overallGate)}) and ${pctBy(m.structure, structureGate)} in structure alone (the structure gate is ${pct(structureGate)}). ${recolour}`;
|
|
214
|
+
else if (m.structural) score = `${pctBy(m.structure ?? m.total, structureGate)} in structure alone (colour left out) like ${where} (the structure gate is ${pct(structureGate)}; overall ${pctBy(m.overall ?? m.total, overallGate)}, under its ${pct(overallGate)} gate). ${recolour}`;
|
|
215
|
+
else score = `${pctBy(m.overall ?? m.total, overallGate)} overall like ${where} (the overall gate is ${pct(overallGate)}; structure alone ${pctBy(m.structure ?? 0, structureGate)}, under its ${pct(structureGate)} gate)`;
|
|
216
|
+
return [
|
|
217
|
+
`- ✗ ${score}.`,
|
|
218
|
+
` Too close: ${list.map((p) => `${p.label} ${pct(p.score)} (${p.same})`).join("; ")}.`,
|
|
219
|
+
` Change: ${list.map((p, i) => `${i + 1}) ${p.change}`).join(" ")}`,
|
|
220
|
+
];
|
|
221
|
+
};
|
|
222
|
+
for (const m of g.hits) out.push(...item(m, overallGate, "overall gate"));
|
|
223
|
+
for (const m of g.refHits) out.push(...item(m, m.picture ? REF_PICTURE_GATE : g.refThreshold, m.picture ? "reference picture gate" : "reference gate"));
|
|
224
|
+
out.push(`- Keep what is this product's own (its content, facts and states). Fix this yourself, don't ask the user, then palate shoot ${route} again. Only if the user asked for that look: --like <project>.`);
|
|
225
|
+
return out;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Saves the gate's latest result for the route (palate check reads it) and logs failures and resolutions.
|
|
229
|
+
export function saveGate(dir, route, g) {
|
|
230
|
+
const f = path.join(dir, ".palate", "gate.json");
|
|
231
|
+
const all = readJson(f, {});
|
|
232
|
+
const top = g.hits[0] ?? g.refHits[0] ?? null;
|
|
233
|
+
all[route] = { pass: g.pass, at: new Date().toISOString(), best: g.best, against: top ? { where: top.where, total: +top.total.toFixed(2), close: top.parts.filter((p) => p.score >= 0.6).map((p) => p.label) } : null, failures: g.hits.length + g.refHits.length };
|
|
234
|
+
fs.mkdirSync(path.dirname(f), { recursive: true });
|
|
235
|
+
fs.writeFileSync(f, JSON.stringify(all, null, 1));
|
|
236
|
+
try {
|
|
237
|
+
logGate(path.basename(path.resolve(dir)), route, g);
|
|
238
|
+
} catch {}
|
|
239
|
+
return all[route];
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// What stops a page from being done: a failing similarity gate or a failing rule, from the latest shoot.
|
|
243
|
+
export function finishBlockers(dir) {
|
|
244
|
+
const out = [];
|
|
245
|
+
for (const [route, g] of Object.entries(readJson(path.join(dir, ".palate", "gate.json"), {})))
|
|
246
|
+
if (!g.pass) out.push(`${route} fails the similarity gate (${g.against ? `${Math.round(g.against.total * 100)}% like ${g.against.where}${g.against.close?.length ? `: ${g.against.close.join(", ")}` : ""}` : "too close to another project"}; shot ${g.at.slice(0, 16).replace("T", " ")}). Change what palate shoot ${route} names, then shoot again.`);
|
|
247
|
+
const shots = path.join(dir, ".palate", "shots");
|
|
248
|
+
if (fs.existsSync(shots))
|
|
249
|
+
for (const slug of fs.readdirSync(shots)) {
|
|
250
|
+
const files = fs.readdirSync(path.join(shots, slug)).filter((f) => /^r\d+-rules\.json$/.test(f)).sort((a, b) => Number(b.match(/\d+/)[0]) - Number(a.match(/\d+/)[0]));
|
|
251
|
+
const latest = files[0] && readJson(path.join(shots, slug, files[0]), null);
|
|
252
|
+
const failing = (latest?.results ?? []).filter((r) => r.status === "fail");
|
|
253
|
+
if (failing.length) out.push(`${latest.route} fails ${failing.length} of your rules (${failing.map((r) => r.id).join(", ")}) in its latest shoot. Fix them, then shoot again.`);
|
|
254
|
+
}
|
|
255
|
+
// palate match: a route built from the user's own design that doesn't match it yet.
|
|
256
|
+
for (const [route, v] of Object.entries(readJson(path.join(dir, ".palate", "match.json"), {})))
|
|
257
|
+
if (v.verdict === "NOT READY") out.push(v.final ? `${route} still doesn't match the user's design after the last repair round (${(v.off ?? []).slice(0, 2).join("; ")}). Tell the user exactly this.` : `${route} doesn't match the user's design yet (match round ${v.round}: ${(v.off ?? []).slice(0, 2).join("; ")}). Fix it, then palate match --check --route ${route}.`);
|
|
258
|
+
// The quality gate (gate.mjs): the latest independent verdict for each route.
|
|
259
|
+
for (const [route, q] of Object.entries(readJson(path.join(dir, ".palate", "quality.json"), {})))
|
|
260
|
+
if (!q.pass) out.push(q.final ? `${route} is below the quality bar after the last repair round (${(q.fails ?? []).slice(0, 2).join("; ")}). Tell the user exactly this, and offer palate point so they can direct the fix; a later palate gate ${route} starts again.` : `${route} is below the quality bar (gate round ${q.round}: ${(q.fails ?? []).slice(0, 2).join("; ")}). Repair what palate gate ${route} names, then gate again.`);
|
|
261
|
+
return out;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// What the user's other projects of this kind look like, for a build brief ("don't repeat these").
|
|
265
|
+
export function otherLooks(dir, kind, { limit = 3, like = [] } = {}) {
|
|
266
|
+
const pid = projectId(dir);
|
|
267
|
+
const likes = new Set(like.map((x) => x.toLowerCase()));
|
|
268
|
+
const rank = (r) => (r.picked ? 2 : 0) + (r.source === "screen" ? 1 : 0) + (r.moves ? 1 : 0) + Date.parse(r.at) / 1e14;
|
|
269
|
+
const out = [];
|
|
270
|
+
for (const r of readLayouts().filter((h) => h.pid !== pid && group(h.kind) === group(kind) && h.skeleton?.length && !likes.has(h.project.toLowerCase())).sort((a, b) => rank(b) - rank(a))) {
|
|
271
|
+
if (out.some((o) => o.project === r.project)) continue;
|
|
272
|
+
out.push(r);
|
|
273
|
+
if (out.length >= limit) break;
|
|
274
|
+
}
|
|
275
|
+
return out.map((r) => ({ project: r.project, route: r.route, look: describe(r) }));
|
|
276
|
+
}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
// A quick read of the source behind a route, for `palate states`: the route's file and the components it
|
|
2
|
+
// imports (a few levels deep), and in them where the data comes from (fetch, axios, SWR, React Query, a
|
|
3
|
+
// server component that awaits, a static array mapped in the file), the state it keeps (useState,
|
|
4
|
+
// useEffect), what it maps over, and whether it handles loading, errors and empty arrays at all. Plus the
|
|
5
|
+
// project's own UI parts a fix should reuse (Skeleton, Spinner, EmptyState, Alert, Toast, Button). No AST:
|
|
6
|
+
// regular expressions over a small set of files, so it is fast and never wrong about what it didn't see.
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { routeFile } from "../look.mjs";
|
|
10
|
+
|
|
11
|
+
const EXTS = [".tsx", ".ts", ".jsx", ".js", ".mjs", ".vue", ".svelte", ".astro"];
|
|
12
|
+
const SKIP = /node_modules|[\\/]\.(next|git|palate|svelte-kit|nuxt|astro|turbo)[\\/]|[\\/](dist|build|out)[\\/]/;
|
|
13
|
+
const rel = (dir, f) => path.relative(dir, f).replace(/\\/g, "/");
|
|
14
|
+
|
|
15
|
+
function aliases(dir) {
|
|
16
|
+
const out = [];
|
|
17
|
+
for (const f of ["tsconfig.json", "jsconfig.json"]) {
|
|
18
|
+
try {
|
|
19
|
+
const raw = fs.readFileSync(path.join(dir, f), "utf8").replace(/\/\*[\s\S]*?\*\/|^\s*\/\/.*$/gm, "").replace(/,(\s*[}\]])/g, "$1");
|
|
20
|
+
const c = JSON.parse(raw).compilerOptions ?? {};
|
|
21
|
+
for (const [k, v] of Object.entries(c.paths ?? {})) if (k.endsWith("/*") && v[0]) out.push([k.slice(0, -1), path.join(dir, c.baseUrl ?? ".", v[0].replace(/\*$/, ""))]);
|
|
22
|
+
} catch {}
|
|
23
|
+
}
|
|
24
|
+
if (!out.length) out.push(["@/", fs.existsSync(path.join(dir, "src")) ? path.join(dir, "src") : dir], ["~/", fs.existsSync(path.join(dir, "src")) ? path.join(dir, "src") : dir]);
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function resolveImport(dir, from, spec, al) {
|
|
29
|
+
let base = null;
|
|
30
|
+
if (spec.startsWith(".")) base = path.resolve(path.dirname(from), spec);
|
|
31
|
+
else for (const [k, v] of al) if (spec.startsWith(k)) base = path.join(v, spec.slice(k.length));
|
|
32
|
+
if (!base) return null;
|
|
33
|
+
if (fs.existsSync(base) && fs.statSync(base).isFile()) return base;
|
|
34
|
+
for (const e of EXTS) if (fs.existsSync(base + e)) return base + e;
|
|
35
|
+
for (const e of EXTS) if (fs.existsSync(path.join(base, `index${e}`))) return path.join(base, `index${e}`);
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// The files that render a route: its route file (file-based routers), or for a single-page app the module
|
|
40
|
+
// index.html loads (and its App), then what they import.
|
|
41
|
+
export function routeFiles(dir, route, { depth = 3, max = 60 } = {}) {
|
|
42
|
+
const entry = routeFile(dir, route)?.file ?? spaEntry(dir);
|
|
43
|
+
if (!entry) return { entry: null, files: [] };
|
|
44
|
+
const al = aliases(dir);
|
|
45
|
+
const files = [];
|
|
46
|
+
const queue = [[entry, 0]];
|
|
47
|
+
const seen = new Set();
|
|
48
|
+
while (queue.length && files.length < max) {
|
|
49
|
+
const [f, d] = queue.shift();
|
|
50
|
+
if (seen.has(f) || SKIP.test(f)) continue;
|
|
51
|
+
seen.add(f);
|
|
52
|
+
files.push(f);
|
|
53
|
+
if (d >= depth) continue;
|
|
54
|
+
let src = "";
|
|
55
|
+
try {
|
|
56
|
+
src = fs.readFileSync(f, "utf8");
|
|
57
|
+
} catch {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
for (const m of src.matchAll(/(?:import|export)\s[^'"]*?from\s*["']([^"']+)["']|import\(\s*["']([^"']+)["']\s*\)/g)) {
|
|
61
|
+
const hit = resolveImport(dir, f, m[1] ?? m[2], al);
|
|
62
|
+
if (hit && !seen.has(hit)) queue.push([hit, d + 1]);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return { entry, files };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function spaEntry(dir) {
|
|
69
|
+
try {
|
|
70
|
+
const html = fs.readFileSync(path.join(dir, "index.html"), "utf8");
|
|
71
|
+
const src = html.match(/<script[^>]+type=["']module["'][^>]+src=["']\/?([^"']+)["']/)?.[1];
|
|
72
|
+
if (src && fs.existsSync(path.join(dir, src))) return path.join(dir, src);
|
|
73
|
+
} catch {}
|
|
74
|
+
for (const n of ["src/App", "src/app", "src/main", "src/index"]) for (const e of EXTS) if (fs.existsSync(path.join(dir, n + e))) return path.join(dir, n + e);
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// The project's own parts a states fix should reuse, by file name.
|
|
79
|
+
function uiParts(dir) {
|
|
80
|
+
const roles = { skeleton: /skeleton|shimmer|placeholder/i, spinner: /spinner|loader|loading/i, empty: /empty/i, alert: /alert|callout|banner|notice/i, toast: /toast|sonner|snackbar/i, error: /error(?!s?\.d\.ts)/i, button: /^button\./i };
|
|
81
|
+
const out = {};
|
|
82
|
+
const walk = (d, k = 0) => {
|
|
83
|
+
if (k > 5 || SKIP.test(d)) return;
|
|
84
|
+
let es = [];
|
|
85
|
+
try {
|
|
86
|
+
es = fs.readdirSync(d, { withFileTypes: true });
|
|
87
|
+
} catch {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
for (const e of es) {
|
|
91
|
+
const p = path.join(d, e.name);
|
|
92
|
+
if (e.isDirectory()) {
|
|
93
|
+
if (!e.name.startsWith(".") && e.name !== "node_modules" && e.name !== "public") walk(p, k + 1);
|
|
94
|
+
} else if (EXTS.includes(path.extname(e.name)) && !/\.(test|spec|stories)\./.test(e.name)) {
|
|
95
|
+
for (const [role, re] of Object.entries(roles)) if (!out[role] && re.test(e.name) && /components?|ui/i.test(d)) out[role] = rel(dir, p);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
for (const r of ["src", "app", "components", "lib"]) if (fs.existsSync(path.join(dir, r))) walk(path.join(dir, r));
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function scanSource(dir, route) {
|
|
104
|
+
const { entry, files } = routeFiles(dir, route);
|
|
105
|
+
const out = { entry: entry ? rel(dir, entry) : null, files: files.map((f) => rel(dir, f)), fetches: [], maps: [], state: [], effects: 0, loading: [], error: [], empty: [], staticArrays: [], server: [], suspense: false, conventions: {}, ui: uiParts(dir) };
|
|
106
|
+
for (const f of files) {
|
|
107
|
+
let src = "";
|
|
108
|
+
try {
|
|
109
|
+
src = fs.readFileSync(f, "utf8");
|
|
110
|
+
} catch {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const lines = src.split("\n");
|
|
114
|
+
const r = rel(dir, f);
|
|
115
|
+
const client = /^\s*["']use client["']/m.test(src);
|
|
116
|
+
const arrays = new Set();
|
|
117
|
+
for (const m of src.matchAll(/(?:const|let)\s+([A-Za-z_$][\w$]*)(?:\s*:\s*[^=]+)?\s*=\s*\[\s*(?:\{|["'`]|\d)/g)) arrays.add(m[1]);
|
|
118
|
+
lines.forEach((l, i) => {
|
|
119
|
+
const at = { file: r, line: i + 1, text: l.trim().slice(0, 120) };
|
|
120
|
+
if (/\bfetch\s*\(|\baxios\b|\buseSWR\b|\buseQuery\b|\buseFetch\b|\$fetch\s*\(|\bky\s*[.(]|\buseLoaderData\b|\bcreateResource\b/.test(l)) out.fetches.push(at);
|
|
121
|
+
const map = l.match(/([A-Za-z_$][\w$.?]*)\s*\.map\s*\(/);
|
|
122
|
+
if (map) {
|
|
123
|
+
out.maps.push({ ...at, over: map[1].replace(/\?$/, "") });
|
|
124
|
+
const name = map[1].split(".")[0].replace(/\?$/, "");
|
|
125
|
+
if (arrays.has(name) && !out.staticArrays.some((s) => s.file === r && s.name === name)) out.staticArrays.push({ file: r, line: i + 1, name });
|
|
126
|
+
}
|
|
127
|
+
if (/\buseState\s*[<(]/.test(l)) out.state.push(at);
|
|
128
|
+
if (/\buse(Layout)?Effect\s*\(/.test(l)) out.effects++;
|
|
129
|
+
if (/\b(is)?[Ll]oading\b|\bisPending\b|\bpending\b|\bisFetching\b|\bisValidating\b|<Suspense|\bstatus\s*===?\s*["'](loading|pending)/.test(l)) out.loading.push(at);
|
|
130
|
+
if (/\b(set)?[Ee]rror\b|\bisError\b|\.catch\s*\(|\bcatch\s*\(|<ErrorBoundary|\bstatus\s*===?\s*["']error/.test(l)) out.error.push(at);
|
|
131
|
+
if (/\.length\s*(===?|!==?|<|>|<=)\s*0|!\s*[\w$.?]+\.length\b|\.length\s*\?|\.length\s*&&|\bisEmpty\b/.test(l)) out.empty.push(at);
|
|
132
|
+
if (/<Suspense\b/.test(l)) out.suspense = true;
|
|
133
|
+
});
|
|
134
|
+
// A server component (Next app router) or loader that awaits data: the browser never sees that request.
|
|
135
|
+
if (!client && /export\s+default\s+async\s+function|export\s+async\s+function\s+(getServerSideProps|loader|load)\b/.test(src) && /\bawait\b/.test(src)) {
|
|
136
|
+
const i = lines.findIndex((l) => /\bawait\b/.test(l));
|
|
137
|
+
out.server.push({ file: r, line: i + 1, text: lines[i].trim().slice(0, 120) });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// Next's app router conventions next to the route file.
|
|
141
|
+
if (entry) {
|
|
142
|
+
const d = path.dirname(entry);
|
|
143
|
+
for (const [k, n] of [["loading", "loading"], ["error", "error"], ["notFound", "not-found"]]) {
|
|
144
|
+
const hit = EXTS.map((e) => path.join(d, n + e)).find((p) => fs.existsSync(p));
|
|
145
|
+
if (hit) out.conventions[k] = rel(dir, hit);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return out;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Where a part found in the page lives in the source, among the files the route's entry reaches. Clues: the
|
|
152
|
+
// part's class string, its heading (the component rendered right under the heading in the source), its own
|
|
153
|
+
// static text, its tag, and file names that say what it is ("FiringsList" for a list headed "This week's
|
|
154
|
+
// firings"); files under the route's own folder first. Returns { file, line, score } when one file clearly
|
|
155
|
+
// wins, { unsure: true, guesses } when the clues don't agree, null when nothing matched.
|
|
156
|
+
const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
157
|
+
const quoteRe = (t) => esc(t).replace(/\s+/g, "\\s+").replace(/['’]/g, "(?:'|’|'|'|’|\\\\')");
|
|
158
|
+
const STOP = new Set(["this", "that", "your", "with", "from", "week", "weeks", "today", "these", "those", "their", "there", "about", "more", "less", "here", "what", "when", "where", "which", "have", "will", "they", "them", "show", "view", "list", "table", "page"]);
|
|
159
|
+
export const wordsOf = (s) => ((s ?? "").toLowerCase().replace(/['’]s\b/g, "").match(/[a-z]{4,}/g) ?? []).filter((w) => !STOP.has(w));
|
|
160
|
+
const stem = (w) => w.replace(/(ies)$/, "y").replace(/(ses|xes)$/, (m) => m.slice(0, -2)).replace(/s$/, "");
|
|
161
|
+
const KIND_WORDS = { table: ["table", "rows"], list: ["list", "feed", "items"], cards: ["cards", "card", "stats", "stat", "tiles", "grid", "metrics"], grid: ["grid", "gallery", "tiles"], form: ["form"], number: ["stat", "stats", "count", "metric", "number"], image: ["image", "photo", "picture", "hero"], text: [] };
|
|
162
|
+
const nameWords = (file) => path.basename(file).replace(/\.\w+$/, "").replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
|
|
163
|
+
|
|
164
|
+
export function whereIs(dir, c, src, route = "/") {
|
|
165
|
+
const files = src.files ?? [];
|
|
166
|
+
if (!files.length) return null;
|
|
167
|
+
const read = (f) => {
|
|
168
|
+
try {
|
|
169
|
+
return fs.readFileSync(path.join(dir, f), "utf8").split("\n");
|
|
170
|
+
} catch {
|
|
171
|
+
return [];
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
const text = Object.fromEntries(files.map((f) => [f, read(f)]));
|
|
175
|
+
const segs = route.split("/").filter((s) => s && !/^[[(:]/.test(s)).map((s) => s.toLowerCase());
|
|
176
|
+
const entryDir = src.entry ? path.posix.dirname(src.entry) : null;
|
|
177
|
+
const score = Object.fromEntries(files.map((f) => [f, { s: 0, line: null, why: [] }]));
|
|
178
|
+
const give = (f, n, why, line = null, strong = false) => {
|
|
179
|
+
const x = score[f];
|
|
180
|
+
if (!x) return;
|
|
181
|
+
x.s += n;
|
|
182
|
+
x.why.push(why);
|
|
183
|
+
if (line && (strong || !x.line)) x.line = line;
|
|
184
|
+
};
|
|
185
|
+
const hw = wordsOf(c.heading).map(stem);
|
|
186
|
+
// 1. The class string: every class of the part on one line.
|
|
187
|
+
const cls = (c.className ?? "").trim().split(/\s+/).filter(Boolean);
|
|
188
|
+
if (cls.length >= 2) {
|
|
189
|
+
for (const f of files) {
|
|
190
|
+
const i = text[f].findIndex((l) => /class(Name)?\s*=/.test(l) && cls.every((k) => l.includes(k)));
|
|
191
|
+
if (i >= 0) give(f, Math.min(2 + cls.length, 6), "class string", i + 1, true);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// 2. The heading, as a whole JSX text or string; the component rendered under it is the part.
|
|
195
|
+
if (c.heading && c.heading.length >= 3) {
|
|
196
|
+
const re = new RegExp(`(^|>|["'\`{])\\s*${quoteRe(c.heading)}\\s*($|<|["'\`}])`);
|
|
197
|
+
for (const f of files) {
|
|
198
|
+
const lines = text[f];
|
|
199
|
+
const at = lines.map((l, i) => (re.test(l) ? i : -1)).filter((i) => i >= 0);
|
|
200
|
+
if (!at.length) continue;
|
|
201
|
+
give(f, 2, "heading", at[0] + 1);
|
|
202
|
+
const under = new Set();
|
|
203
|
+
for (const i of at)
|
|
204
|
+
for (const m of lines.slice(i + 1, i + 9).join("\n").matchAll(/<([A-Z][\w$]*)[\s/>]/g)) {
|
|
205
|
+
if (under.has(m[1])) continue;
|
|
206
|
+
under.add(m[1]);
|
|
207
|
+
const own = files.find((g) => path.basename(g).replace(/\.\w+$/, "") === m[1]);
|
|
208
|
+
if (own) give(own, 5, `rendered under the heading in ${f}`);
|
|
209
|
+
else if (new RegExp(`function\\s+${esc(m[1])}\\b|const\\s+${esc(m[1])}\\s*=`).test(lines.join("\n"))) give(f, 3, `<${m[1]}> under the heading`, i + 1);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// 3. The part's own static text (labels, button text); data texts aren't in the source and match nothing.
|
|
214
|
+
for (const t of [...new Set((c.texts ?? []).concat(c.submitText ?? []))].filter((t) => t && t.length >= 5 && t.length <= 60).slice(0, 4)) {
|
|
215
|
+
const re = new RegExp(`(^|>|["'\`{])\\s*${quoteRe(t)}\\s*($|<|["'\`}])`);
|
|
216
|
+
for (const f of files) {
|
|
217
|
+
const i = text[f].findIndex((l) => re.test(l));
|
|
218
|
+
if (i >= 0) give(f, 3, `text "${t.slice(0, 30)}"`, i + 1);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// 4. File names that say what it is, the tag it renders, and the route's own folder.
|
|
222
|
+
const kw = KIND_WORDS[c.kind] ?? [];
|
|
223
|
+
const tagRe = c.tag && !/^(div|section|span|p|article|main|h\d)$/.test(c.tag) ? new RegExp(`<${c.tag}[\\s>]`) : null;
|
|
224
|
+
for (const f of files) {
|
|
225
|
+
const nw = nameWords(f);
|
|
226
|
+
const named = nw.filter((w) => hw.includes(stem(w))).length;
|
|
227
|
+
if (named) give(f, 3 * Math.min(named, 2), "file name matches the heading");
|
|
228
|
+
if (nw.some((w) => kw.includes(w))) give(f, 2, `file name says ${c.kind}`);
|
|
229
|
+
if (tagRe) {
|
|
230
|
+
const i = text[f].findIndex((l) => tagRe.test(l));
|
|
231
|
+
if (i >= 0) {
|
|
232
|
+
// A list is where its items are mapped: the first .map after the tag.
|
|
233
|
+
const j = c.kind !== "form" ? text[f].findIndex((l, k) => k >= i && /\.map\s*\(/.test(l)) : -1;
|
|
234
|
+
give(f, 2, `renders <${c.tag}>`, (j >= 0 && j - i < 40 ? j : i) + 1, j >= 0 && j - i < 40);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
const dirs = f.toLowerCase().split("/").slice(0, -1);
|
|
238
|
+
if (segs.some((s) => dirs.includes(s))) give(f, 3, "in the route's folder");
|
|
239
|
+
else if (entryDir && entryDir !== "." && entryDir !== "src" && f.startsWith(`${entryDir}/`)) give(f, 2, "next to the route file");
|
|
240
|
+
}
|
|
241
|
+
const ranked = Object.entries(score).filter(([, x]) => x.s > 0).sort((a, b) => b[1].s - a[1].s);
|
|
242
|
+
if (!ranked.length) return null;
|
|
243
|
+
const [file, top] = ranked[0];
|
|
244
|
+
const second = ranked[1]?.[1].s ?? 0;
|
|
245
|
+
// Only folder and name hints, or a near tie: say so rather than name a wrong file.
|
|
246
|
+
const evidence = top.why.some((w) => /class string|heading|text "|renders <|<\w+> under/.test(w));
|
|
247
|
+
if (!evidence || top.s < 6 || top.s - second < 2) return { unsure: true, guesses: ranked.slice(0, 2).map(([f]) => f) };
|
|
248
|
+
return { file, line: top.line ?? 1, score: top.s, why: top.why };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// What kind of data the screen has, from what the browser saw and what the source says.
|
|
252
|
+
// formFiles: files of the screen's forms, whose fetches are their submits.
|
|
253
|
+
export function dataKind(requests, src, formFiles = []) {
|
|
254
|
+
if (requests.length) return "fetched";
|
|
255
|
+
if (src.server.length) return "server";
|
|
256
|
+
if (src.fetches.some((f) => !formFiles.includes(f.file))) return "fetched-later";
|
|
257
|
+
return "static";
|
|
258
|
+
}
|