@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,413 @@
|
|
|
1
|
+
// The button language: how a page's actions look, chosen per product from its research, as part of the look.
|
|
2
|
+
// Why: on the Nov 6 benchmark five very different products (a tracing tool, a bakery, a tool library, a pottery
|
|
3
|
+
// studio app, a wedding photographer) got their own ground, type, hero and header, and the same button: a filled
|
|
4
|
+
// box about 48px tall, a 15–16px semi-bold sans label in sentence case, a small radius, a darker ring and a soft
|
|
5
|
+
// contact shadow, beside a bold underlined link. Only the colour and the radius changed, and the look check
|
|
6
|
+
// compared only fill and shape, so it never counted as a repeat. A button is a small thing that carries a lot of
|
|
7
|
+
// a brand's voice (a stamped ticket at a bakery, a keycap in a terminal, a line of display type at a studio).
|
|
8
|
+
//
|
|
9
|
+
// Six axes, a library of named languages as starting points (kb/sections/_buttons.md says when each fits),
|
|
10
|
+
// similarity between two languages (planned or measured), words for both, and the CSS palate generates for the
|
|
11
|
+
// chosen one. The craft (hover, pressed, focus, disabled, contrast, tap area, reduced motion) never changes.
|
|
12
|
+
|
|
13
|
+
export const BUTTON_AXES = {
|
|
14
|
+
shape: ["square", "soft", "pill", "none"],
|
|
15
|
+
fill: ["solid", "outline", "tinted", "underline", "ghost"],
|
|
16
|
+
size: ["compact", "standard", "large"],
|
|
17
|
+
label: ["sentence", "upper-tracked", "display", "mono"],
|
|
18
|
+
adornment: ["none", "arrow", "icon", "detail", "keycap"],
|
|
19
|
+
secondary: ["text-link", "ghost", "solid", "none", "inline"],
|
|
20
|
+
};
|
|
21
|
+
export const AXIS_NAMES = Object.keys(BUTTON_AXES);
|
|
22
|
+
const BOXLESS = new Set(["underline", "ghost"]);
|
|
23
|
+
|
|
24
|
+
// The library: starting points, each with the products it fits. A plan names one (and may change an axis or
|
|
25
|
+
// two), or says "custom" and sets all six.
|
|
26
|
+
export const BUTTON_LANGUAGES = {
|
|
27
|
+
"plain-solid": {
|
|
28
|
+
shape: "soft", fill: "solid", size: "standard", label: "sentence", adornment: "none", secondary: "text-link",
|
|
29
|
+
words: "a filled box with a sentence-case label and a text link beside it",
|
|
30
|
+
fits: "software where the action is routine and nothing in the product's world suggests another; palate's recent pages all used it, so only when the research shows the category's best sites do",
|
|
31
|
+
tags: ["saas", "software", "b2b"],
|
|
32
|
+
},
|
|
33
|
+
"editorial-arrow": {
|
|
34
|
+
shape: "none", fill: "underline", size: "large", label: "display", adornment: "arrow", secondary: "inline",
|
|
35
|
+
words: "no box: the action is a line in the display face, underlined, with a trailing arrow; the second action sits inside the copy",
|
|
36
|
+
fits: "publications, studios, architects, writers, editorial and wedding photography, culture: pages where type is the material",
|
|
37
|
+
tags: ["editorial", "studio", "portfolio", "photography", "writing", "culture", "architecture", "agency", "museum"],
|
|
38
|
+
},
|
|
39
|
+
"stamped-tag": {
|
|
40
|
+
shape: "square", fill: "outline", size: "standard", label: "upper-tracked", adornment: "detail", secondary: "text-link",
|
|
41
|
+
words: "a square ticket in ink with a hard offset edge, a small uppercase tracked label and a detail inside (the day, the price, a number)",
|
|
42
|
+
fits: "bakeries, cafés, markets, local shops, libraries of things, small events: anything sold over a counter or printed on a tag",
|
|
43
|
+
tags: ["food", "bakery", "cafe", "restaurant", "local", "market", "shop", "library", "community", "events", "print"],
|
|
44
|
+
},
|
|
45
|
+
"tracked-outline": {
|
|
46
|
+
shape: "square", fill: "outline", size: "large", label: "upper-tracked", adornment: "none", secondary: "none",
|
|
47
|
+
words: "a thin square outline, wide and quiet, with an uppercase tracked label and no second action",
|
|
48
|
+
fits: "fashion, photography, hotels, galleries, jewellery: the work is loud, the button steps back",
|
|
49
|
+
tags: ["fashion", "photography", "hotel", "hospitality", "gallery", "luxury", "wedding", "jewellery", "interior"],
|
|
50
|
+
},
|
|
51
|
+
"keycap-command": {
|
|
52
|
+
shape: "soft", fill: "solid", size: "compact", label: "mono", adornment: "keycap", secondary: "ghost",
|
|
53
|
+
words: "a compact key with a monospace label and a keycap hint (↵, ⌘K) or the command itself; the second action a ghost button",
|
|
54
|
+
fits: "developer tools, CLIs, tracing and observability, terminals, APIs: the audience lives on the keyboard",
|
|
55
|
+
tags: ["developer", "dev", "devtools", "cli", "api", "tracing", "observability", "infrastructure", "code", "terminal"],
|
|
56
|
+
},
|
|
57
|
+
"oversized-pill": {
|
|
58
|
+
shape: "pill", fill: "solid", size: "large", label: "display", adornment: "arrow", secondary: "text-link",
|
|
59
|
+
words: "a big pill in the display face with a trailing arrow",
|
|
60
|
+
fits: "consumer apps, launches, fitness, playful brands, festivals: one confident action and nothing else",
|
|
61
|
+
tags: ["consumer", "app", "launch", "playful", "fitness", "music", "festival", "kids", "game"],
|
|
62
|
+
},
|
|
63
|
+
"tinted-soft": {
|
|
64
|
+
shape: "soft", fill: "tinted", size: "standard", label: "sentence", adornment: "icon", secondary: "ghost",
|
|
65
|
+
words: "a soft tinted button (the accent at low strength, the label in its dark shade) with a leading icon",
|
|
66
|
+
fits: "community tools, health, education, non-profits, friendly dashboards: inviting, never shouting",
|
|
67
|
+
tags: ["community", "health", "education", "nonprofit", "charity", "care", "wellbeing", "friendly", "tool"],
|
|
68
|
+
},
|
|
69
|
+
"price-in-button": {
|
|
70
|
+
shape: "soft", fill: "solid", size: "large", label: "sentence", adornment: "detail", secondary: "none",
|
|
71
|
+
words: "a large solid button that carries the price or the time after a divider (\"Pre-order the box · £18\")",
|
|
72
|
+
fits: "commerce, booking, pre-orders, tickets, services with a set price: the visitor sees what they agree to",
|
|
73
|
+
tags: ["commerce", "shop", "booking", "ticket", "preorder", "service", "store", "subscription"],
|
|
74
|
+
},
|
|
75
|
+
"poster-block": {
|
|
76
|
+
shape: "square", fill: "solid", size: "large", label: "upper-tracked", adornment: "none", secondary: "solid",
|
|
77
|
+
words: "a square block of colour with a bold uppercase label, a second block in ink beside it",
|
|
78
|
+
fits: "civic campaigns, sport, charities with one urgent ask, festivals, bold posters",
|
|
79
|
+
tags: ["civic", "sport", "campaign", "charity", "protest", "festival", "poster", "union", "politics"],
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
export const LANGUAGE_NAMES = Object.keys(BUTTON_LANGUAGES);
|
|
83
|
+
|
|
84
|
+
// The anatomy of a language name plus the plan's own changes: { language, shape, fill, size, label, adornment,
|
|
85
|
+
// secondary } (unknown axes left out). null when nothing is said.
|
|
86
|
+
export function resolveButtons(b) {
|
|
87
|
+
if (!b) return null;
|
|
88
|
+
if (typeof b === "string") b = { language: b };
|
|
89
|
+
if (typeof b !== "object") return null;
|
|
90
|
+
const base = BUTTON_LANGUAGES[b.language] ?? {};
|
|
91
|
+
const out = { language: b.language ?? null };
|
|
92
|
+
for (const a of AXIS_NAMES) {
|
|
93
|
+
const v = b[a] ?? base[a];
|
|
94
|
+
if (v != null) out[a] = v;
|
|
95
|
+
}
|
|
96
|
+
if (BOXLESS.has(out.fill)) out.shape = "none";
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// The blueprint's checks on look.buttons: { problems, warnings }.
|
|
101
|
+
export function buttonProblems(b) {
|
|
102
|
+
const problems = [];
|
|
103
|
+
const warnings = [];
|
|
104
|
+
if (!b || typeof b !== "object") {
|
|
105
|
+
problems.push(`look.buttons: the page's button language, chosen from the research: { "language": one of ${LANGUAGE_NAMES.join(", ")} or "custom", plus any axis you change (${AXIS_NAMES.join(", ")}), "why" }`);
|
|
106
|
+
return { problems, warnings };
|
|
107
|
+
}
|
|
108
|
+
if (b.language != null && b.language !== "custom" && !BUTTON_LANGUAGES[b.language]) problems.push(`look.buttons.language "${b.language}" isn't in the library: one of ${LANGUAGE_NAMES.join(", ")}, or "custom" with all six axes`);
|
|
109
|
+
if ((b.language == null || b.language === "custom") && AXIS_NAMES.some((a) => b[a] == null)) problems.push(`look.buttons: a custom language sets all six axes (${AXIS_NAMES.filter((a) => b[a] == null).join(", ")} missing)`);
|
|
110
|
+
for (const a of AXIS_NAMES) if (b[a] != null && !BUTTON_AXES[a].includes(b[a])) problems.push(`look.buttons.${a} "${b[a]}": one of ${BUTTON_AXES[a].join(", ")}`);
|
|
111
|
+
const r = resolveButtons(b);
|
|
112
|
+
if (b.shape === "none" && r.fill && !BOXLESS.has(r.fill)) problems.push(`look.buttons: shape none has no box, so the fill can't be ${r.fill} (use underline or ghost, or give it a shape)`);
|
|
113
|
+
if (b.shape && b.shape !== "none" && BOXLESS.has(r.fill)) warnings.push(`look.buttons: a ${r.fill} button has no box, so shape ${b.shape} is ignored`);
|
|
114
|
+
if (!b.why) warnings.push("look.buttons.why: which research reference or product truth the buttons come from");
|
|
115
|
+
return { problems, warnings };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// The coarse call-to-action class (lookvec's cta axis) of a language, and a partial language from a cta.
|
|
119
|
+
export function ctaOf(b) {
|
|
120
|
+
const r = resolveButtons(b);
|
|
121
|
+
if (!r?.fill) return null;
|
|
122
|
+
if (BOXLESS.has(r.fill)) return "text-link";
|
|
123
|
+
if (r.fill === "outline") return "outline";
|
|
124
|
+
return r.shape === "pill" ? "solid-pill" : r.shape === "square" ? "solid-square" : "solid-rounded";
|
|
125
|
+
}
|
|
126
|
+
export function buttonsFromCta(cta, secondary) {
|
|
127
|
+
const style = cta?.style ?? cta;
|
|
128
|
+
const sec = { "underlined-link": "text-link", "text-link": "text-link", ghost: "ghost", solid: "solid", none: "none" }[cta?.secondary ?? secondary];
|
|
129
|
+
const base =
|
|
130
|
+
{
|
|
131
|
+
"solid-rounded": { shape: "soft", fill: "solid" },
|
|
132
|
+
"solid-pill": { shape: "pill", fill: "solid" },
|
|
133
|
+
"solid-square": { shape: "square", fill: "solid" },
|
|
134
|
+
gradient: { fill: "solid" },
|
|
135
|
+
outline: { fill: "outline" },
|
|
136
|
+
"outline-pill": { shape: "pill", fill: "outline" },
|
|
137
|
+
"text-link": { shape: "none", fill: "underline" },
|
|
138
|
+
}[style] ?? null;
|
|
139
|
+
if (!base) return null;
|
|
140
|
+
return { ...base, ...(sec ? { secondary: sec } : {}), derived: true };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ---- similarity: weighted axes, near values half, the choices nearly every button makes half (two buttons with
|
|
144
|
+
// sentence-case labels and no adornment say less), and for two measured buttons, how close their finish is
|
|
145
|
+
// (height, radius as a share of height, label size, weight and tracking).
|
|
146
|
+
// Shape weighs least: the five benchmark pages changed their radius and kept the same button (soft and square
|
|
147
|
+
// are close; a pill is further).
|
|
148
|
+
export const BUTTON_WEIGHTS = { shape: 0.75, fill: 1.5, size: 1, label: 1.5, adornment: 0.75, secondary: 0.75 };
|
|
149
|
+
const BUTTON_NEAR = new Set(
|
|
150
|
+
[
|
|
151
|
+
["shape", "soft", "pill"],
|
|
152
|
+
["fill", "solid", "tinted"],
|
|
153
|
+
["fill", "outline", "ghost"],
|
|
154
|
+
["fill", "underline", "ghost"],
|
|
155
|
+
["size", "compact", "standard"],
|
|
156
|
+
["size", "standard", "large"],
|
|
157
|
+
["adornment", "arrow", "icon"],
|
|
158
|
+
["adornment", "arrow", "none"],
|
|
159
|
+
["secondary", "text-link", "inline"],
|
|
160
|
+
["secondary", "ghost", "none"],
|
|
161
|
+
].flatMap(([a, x, y]) => [`${a}:${x}:${y}`, `${a}:${y}:${x}`]),
|
|
162
|
+
);
|
|
163
|
+
export const BUTTON_COMMON = { label: ["sentence"], adornment: ["none"] };
|
|
164
|
+
export const COMMON_BUTTON_CREDIT = 0.75;
|
|
165
|
+
// Pairs that are nearly the same: three quarters.
|
|
166
|
+
const BUTTON_CLOSE = new Set(["shape:soft:square", "shape:square:soft"]);
|
|
167
|
+
const ok = (x) => x != null && x !== "unknown";
|
|
168
|
+
// Axes both languages state.
|
|
169
|
+
export const comparableButtonAxes = (a, b) => AXIS_NAMES.filter((k) => ok(a?.[k]) && ok(b?.[k]));
|
|
170
|
+
const finishScore = (a, b) => {
|
|
171
|
+
const fa = a?.finish, fb = b?.finish;
|
|
172
|
+
if (!fa || !fb) return null;
|
|
173
|
+
const close = (x, y, span) => (x == null || y == null ? null : Math.max(0, 1 - Math.abs(x - y) / span));
|
|
174
|
+
// The edge (a ring, a contact shadow, both, none): the benchmark button's darker ring and soft shadow are half of
|
|
175
|
+
// what makes it one button; a shared part counts half.
|
|
176
|
+
const edges = (e) => new Set(String(e).split("+").filter((x) => x && x !== "none"));
|
|
177
|
+
const edge = fa.edge == null || fb.edge == null ? null : (() => {
|
|
178
|
+
const [x, y] = [edges(fa.edge), edges(fb.edge)];
|
|
179
|
+
if (!x.size && !y.size) return 1;
|
|
180
|
+
const shared = [...x].filter((k) => y.has(k)).length;
|
|
181
|
+
return shared / Math.max(x.size, y.size);
|
|
182
|
+
})();
|
|
183
|
+
const parts = [close(fa.h, fb.h, 12), close(fa.radiusShare, fb.radiusShare, 0.25), close(fa.px, fb.px, 4), close(fa.weight, fb.weight, 200), close(fa.tracking, fb.tracking, 0.08), edge].filter((x) => x != null);
|
|
184
|
+
return parts.length ? parts.reduce((x, y) => x + y, 0) / parts.length : null;
|
|
185
|
+
};
|
|
186
|
+
export const FINISH_SHARE = 0.4;
|
|
187
|
+
|
|
188
|
+
// { score 0–1, same: axes equal, near: axes close, axes: comparable count, parts }.
|
|
189
|
+
export function buttonSimilarity(a, b) {
|
|
190
|
+
const [x, y] = [a?.language && !a.shape ? resolveButtons(a) : a, b?.language && !b.shape ? resolveButtons(b) : b];
|
|
191
|
+
let sum = 0, weight = 0;
|
|
192
|
+
const same = [], near = [], parts = {};
|
|
193
|
+
for (const k of comparableButtonAxes(x, y)) {
|
|
194
|
+
const w = BUTTON_WEIGHTS[k];
|
|
195
|
+
let s = 0;
|
|
196
|
+
if (x[k] === y[k]) s = BUTTON_COMMON[k]?.includes(x[k]) ? COMMON_BUTTON_CREDIT : 1;
|
|
197
|
+
else if (BUTTON_CLOSE.has(`${k}:${x[k]}:${y[k]}`)) s = 0.75;
|
|
198
|
+
else if (BUTTON_NEAR.has(`${k}:${x[k]}:${y[k]}`)) s = 0.5;
|
|
199
|
+
if (x[k] === y[k]) same.push(k);
|
|
200
|
+
else if (s > 0) near.push(k);
|
|
201
|
+
parts[k] = s;
|
|
202
|
+
sum += w * s;
|
|
203
|
+
weight += w;
|
|
204
|
+
}
|
|
205
|
+
if (!weight) return { score: 0, same, near, axes: 0, parts };
|
|
206
|
+
let score = sum / weight;
|
|
207
|
+
const f = finishScore(x, y);
|
|
208
|
+
if (f != null) {
|
|
209
|
+
parts.finish = +f.toFixed(2);
|
|
210
|
+
score = (1 - FINISH_SHARE) * score + FINISH_SHARE * f;
|
|
211
|
+
}
|
|
212
|
+
return { score: +score.toFixed(3), same, near, axes: comparableButtonAxes(x, y).length, parts };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ---- words
|
|
216
|
+
const SHAPE_W = { square: "square", soft: "soft-cornered", pill: "pill", none: "boxless" };
|
|
217
|
+
const FILL_W = { solid: "solid", outline: "outline", tinted: "tinted", underline: "underlined text", ghost: "ghost" };
|
|
218
|
+
const SIZE_W = { compact: "compact", standard: "standard", large: "large" };
|
|
219
|
+
const LABEL_W = { sentence: "sentence case", "upper-tracked": "uppercase tracked", display: "in the display face", mono: "monospace" };
|
|
220
|
+
const ADORN_W = { none: "", arrow: ", trailing arrow", icon: ", leading icon", detail: ", a detail inside", keycap: ", a keycap" };
|
|
221
|
+
const SEC_W = { "text-link": ", with a text link", ghost: ", with a ghost button", solid: ", with a second solid", none: ", no second action", inline: ", with a link in the copy", underlined: ", with an underlined link" };
|
|
222
|
+
const WEIGHT_W = (w) => (w == null ? "" : w >= 700 ? "bold" : w >= 600 ? "semi-bold" : w >= 500 ? "medium" : w <= 350 ? "light" : "regular");
|
|
223
|
+
|
|
224
|
+
// A planned language in words: "stamped-tag: square outline, standard, uppercase tracked, a detail inside, with a text link".
|
|
225
|
+
export function describeButtons(b) {
|
|
226
|
+
if (!b) return "";
|
|
227
|
+
if (b.finish) return describeMeasuredButtons(b);
|
|
228
|
+
const r = b.language && !b.shape ? resolveButtons(b) : b;
|
|
229
|
+
const box = r.fill && BOXLESS.has(r.fill) ? FILL_W[r.fill] : [SHAPE_W[r.shape], FILL_W[r.fill]].filter(Boolean).join(" ");
|
|
230
|
+
const body = [box, SIZE_W[r.size], LABEL_W[r.label]].filter(Boolean).join(", ") + (ADORN_W[r.adornment] ?? "") + (SEC_W[r.secondary] ?? "");
|
|
231
|
+
return `${r.language && r.language !== "custom" && !r.derived ? `${r.language}: ` : ""}${body}`;
|
|
232
|
+
}
|
|
233
|
+
// A measured button in words: "48px solid, 16px semi-bold sentence case, small radius, with an underlined link".
|
|
234
|
+
export function describeMeasuredButtons(b) {
|
|
235
|
+
const f = b.finish ?? {};
|
|
236
|
+
const radius = b.shape === "none" ? null : b.shape === "pill" ? "pill" : b.shape === "square" ? "square corners" : (f.radiusShare ?? 0) < 0.2 ? "small radius" : "rounded";
|
|
237
|
+
const box = BOXLESS.has(b.fill) ? `${FILL_W[b.fill]} (no box)` : `${f.h ? `${f.h}px ` : ""}${FILL_W[b.fill] ?? b.fill}`;
|
|
238
|
+
const label = `${f.px ? `${f.px}px ` : ""}${[WEIGHT_W(f.weight), LABEL_W[b.label] ?? b.label].filter(Boolean).join(" ")}`;
|
|
239
|
+
const sec = b.secondary === "text-link" && f.secondaryUnderlined ? SEC_W.underlined : SEC_W[b.secondary] ?? "";
|
|
240
|
+
return [box, label, radius].filter(Boolean).join(", ") + (ADORN_W[b.adornment] ?? "") + sec;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Library languages that differ from b on most axes, best fit first for the product's tags.
|
|
244
|
+
export function otherLanguages(b, { tags = [], limit = 2 } = {}) {
|
|
245
|
+
const t = new Set(tags.map((x) => String(x).toLowerCase()));
|
|
246
|
+
return LANGUAGE_NAMES.filter((n) => n !== b?.language)
|
|
247
|
+
.map((n) => ({ n, sim: buttonSimilarity(resolveButtons(n), b ?? {}).score, fit: BUTTON_LANGUAGES[n].tags.filter((x) => t.has(x)).length }))
|
|
248
|
+
.filter((x) => x.sim < 0.55)
|
|
249
|
+
.sort((x, y) => y.fit - x.fit || x.sim - y.sim)
|
|
250
|
+
.slice(0, limit)
|
|
251
|
+
.map((x) => x.n);
|
|
252
|
+
}
|
|
253
|
+
// The library ranked for a product's tags (for the blueprint brief).
|
|
254
|
+
export function rankLanguages(tags = []) {
|
|
255
|
+
const t = new Set(tags.map((x) => String(x).toLowerCase()));
|
|
256
|
+
// plain-solid last: it is the habit this library exists to break.
|
|
257
|
+
return LANGUAGE_NAMES.map((n) => ({ n, fit: n === "plain-solid" ? -1 : BUTTON_LANGUAGES[n].tags.filter((x) => t.has(x)).length })).sort((a, b) => b.fit - a.fit).map((x) => x.n);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// ---- the CSS for a language. c: the blueprint's colours. Craft stays constant: hover, pressed, focus-visible,
|
|
261
|
+
// disabled, a label at 4.5:1 or more on its fill, a tap target of at least 44px, motion off when reduced.
|
|
262
|
+
const hexRgb = (h) => (h && /^#[0-9a-f]{6}$/i.test(h) ? [1, 3, 5].map((i) => parseInt(h.slice(i, i + 2), 16)) : null);
|
|
263
|
+
const rgbHex = (rgb) => "#" + rgb.map((v) => Math.max(0, Math.min(255, Math.round(v))).toString(16).padStart(2, "0")).join("");
|
|
264
|
+
const mix = (a, b, t) => {
|
|
265
|
+
const [x, y] = [hexRgb(a), hexRgb(b)];
|
|
266
|
+
return x && y ? rgbHex(x.map((v, i) => v + (y[i] - v) * t)) : a;
|
|
267
|
+
};
|
|
268
|
+
const relLum = (rgb) => {
|
|
269
|
+
const [r, g, b] = rgb.map((v) => {
|
|
270
|
+
const c = v / 255;
|
|
271
|
+
return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
|
272
|
+
});
|
|
273
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
274
|
+
};
|
|
275
|
+
export function contrastOf(a, b) {
|
|
276
|
+
const [x, y] = [hexRgb(a), hexRgb(b)];
|
|
277
|
+
if (!x || !y) return null;
|
|
278
|
+
const [l1, l2] = [relLum(x), relLum(y)].sort((p, q) => q - p);
|
|
279
|
+
return +((l1 + 0.05) / (l2 + 0.05)).toFixed(2);
|
|
280
|
+
}
|
|
281
|
+
// The first colour that reads at min:1 on the ground, else the better of black and white.
|
|
282
|
+
export function readable(candidates, ground, min = 4.5) {
|
|
283
|
+
for (const c of candidates.filter((x) => hexRgb(x))) if (contrastOf(c, ground) >= min) return c;
|
|
284
|
+
return contrastOf("#000000", ground) >= contrastOf("#ffffff", ground) ? "#000000" : "#ffffff";
|
|
285
|
+
}
|
|
286
|
+
export const SIZES = { compact: { h: 36, px: 14, pad: 14 }, standard: { h: 44, px: 15, pad: 20 }, large: { h: 56, px: 17, pad: 28 } };
|
|
287
|
+
export const TAP = 44;
|
|
288
|
+
|
|
289
|
+
// opts.rules: the user's rule texts; they win over the language (no pills, no uppercase, no gradients…).
|
|
290
|
+
export function buttonCss(buttons, c = {}, { rules = [] } = {}) {
|
|
291
|
+
const r = resolveButtons(buttons) ?? resolveButtons("plain-solid");
|
|
292
|
+
const notes = [];
|
|
293
|
+
const said = (re) => rules.some((x) => re.test(x) && /button|cta|action/i.test(x) && /\b(no|never|avoid|don'?t|without)\b/i.test(x));
|
|
294
|
+
if (r.shape === "pill" && said(/pill/i)) (r.shape = "soft"), notes.push("pill → soft (your rule)");
|
|
295
|
+
if (r.label === "upper-tracked" && said(/upper ?case|all caps|caps/i)) (r.label = "sentence"), notes.push("uppercase → sentence case (your rule)");
|
|
296
|
+
if (r.fill === "outline" && said(/outline|ghost/i)) (r.fill = "solid"), notes.push("outline → solid (your rule)");
|
|
297
|
+
if (r.adornment === "arrow" && said(/arrow/i)) (r.adornment = "none"), notes.push("no arrow (your rule)");
|
|
298
|
+
const size = SIZES[r.size] ?? SIZES.standard;
|
|
299
|
+
const bg = c.bg ?? "#ffffff";
|
|
300
|
+
const ink = c.ink ?? "#111111";
|
|
301
|
+
const accent = c.accent ?? ink;
|
|
302
|
+
const onAccent = readable([c["on-accent"], "#ffffff", ink], accent);
|
|
303
|
+
// Text actions and outlines: the accent when it reads on the ground, else a darker (or lighter) accent, else ink.
|
|
304
|
+
const dark = (contrastOf(ink, "#ffffff") ?? 21) > (contrastOf(ink, "#000000") ?? 1);
|
|
305
|
+
const toward = dark ? "#000000" : "#ffffff";
|
|
306
|
+
const lineInk = readable([accent, mix(accent, toward, 0.25), mix(accent, toward, 0.45), ink], bg);
|
|
307
|
+
const tint = mix(accent, bg, 0.86);
|
|
308
|
+
const tintInk = readable([mix(accent, toward, 0.35), mix(accent, toward, 0.55), ink], tint);
|
|
309
|
+
const boxless = BOXLESS.has(r.fill);
|
|
310
|
+
const radius = r.shape === "pill" ? "999px" : r.shape === "square" ? "2px" : r.shape === "soft" ? `${Math.round(size.h * 0.2)}px` : "0";
|
|
311
|
+
const font =
|
|
312
|
+
r.label === "upper-tracked" ? `600 ${size.px - 2}px/1 var(--font-body)` : r.label === "display" ? `${r.size === "large" ? 500 : 500} ${size.px + (boxless ? 5 : 2)}px/1.1 var(--font-display)` : r.label === "mono" ? `500 ${size.px - 1}px/1 var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace)` : `500 ${size.px}px/1 var(--font-body)`;
|
|
313
|
+
const tracking = r.label === "upper-tracked" ? "0.09em" : r.label === "display" ? "-0.01em" : "0";
|
|
314
|
+
const lbl = r.fill === "solid" ? onAccent : r.fill === "tinted" ? tintInk : lineInk;
|
|
315
|
+
const ground = r.fill === "solid" ? accent : r.fill === "tinted" ? tint : bg;
|
|
316
|
+
const ratio = contrastOf(lbl, ground);
|
|
317
|
+
const L = [];
|
|
318
|
+
L.push(`/* The page's button language: ${describeButtons(r)}${notes.length ? ` (${notes.join("; ")})` : ""}.`);
|
|
319
|
+
L.push(` Label ${lbl} on ${ground}: ${ratio}:1. Generated from blueprint.json's look.buttons; change the plan, not this block. */`);
|
|
320
|
+
L.push(":root {");
|
|
321
|
+
L.push(` --btn-h: ${size.h}px;`);
|
|
322
|
+
L.push(` --btn-pad: ${boxless ? 0 : size.pad}px;`);
|
|
323
|
+
L.push(` --btn-radius: ${radius};`);
|
|
324
|
+
L.push(` --btn-font: ${font};`);
|
|
325
|
+
L.push(` --btn-tracking: ${tracking};`);
|
|
326
|
+
L.push(` --btn-label: ${lbl};`);
|
|
327
|
+
L.push("}");
|
|
328
|
+
L.push(":is(.btn, .btn-primary, .btn-secondary) {");
|
|
329
|
+
L.push(" position: relative; display: inline-flex; align-items: center; justify-content: center; gap: 0.5em;");
|
|
330
|
+
L.push(` min-height: ${boxless ? `${TAP}px` : "var(--btn-h)"}; padding-inline: var(--btn-pad); border: 0; border-radius: var(--btn-radius);`);
|
|
331
|
+
L.push(` font: var(--btn-font); letter-spacing: var(--btn-tracking);${r.label === "upper-tracked" ? " text-transform: uppercase;" : ""}`);
|
|
332
|
+
L.push(" text-decoration: none; white-space: nowrap; cursor: pointer; -webkit-tap-highlight-color: transparent;");
|
|
333
|
+
L.push(" transition: transform 160ms cubic-bezier(.23,1,.32,1), background-color 160ms ease, box-shadow 160ms ease, color 160ms ease, text-decoration-color 160ms ease;");
|
|
334
|
+
L.push("}");
|
|
335
|
+
// A compact box is under 44px: its hit area grows with a pseudo-element, not its look.
|
|
336
|
+
if (!boxless && size.h < TAP) L.push(`:is(.btn, .btn-primary, .btn-secondary)::after { content: ""; position: absolute; inset: ${-(TAP - size.h) / 2}px -4px; } /* tap target ${TAP}px */`);
|
|
337
|
+
// Primary, by fill.
|
|
338
|
+
if (r.fill === "solid") {
|
|
339
|
+
L.push(`.btn-primary { background: var(--color-accent, ${accent}); color: ${onAccent === c["on-accent"] ? `var(--color-on-accent, ${onAccent})` : onAccent}; }`);
|
|
340
|
+
// Hover moves the fill away from its label, so the label never loses contrast.
|
|
341
|
+
const lift = (contrastOf(onAccent, "#ffffff") ?? 1) < (contrastOf(onAccent, "#000000") ?? 1) ? "#000000" : "#ffffff";
|
|
342
|
+
L.push(`.btn-primary:hover { background: ${mix(accent, lift, 0.08)}; }`);
|
|
343
|
+
L.push(`.btn-primary:active { transform: translateY(1px); background: ${mix(accent, lift, 0.14)}; }`);
|
|
344
|
+
} else if (r.fill === "tinted") {
|
|
345
|
+
L.push(`.btn-primary { background: ${tint}; color: ${tintInk}; }`);
|
|
346
|
+
L.push(`.btn-primary:hover { background: ${mix(accent, bg, 0.78)}; }`);
|
|
347
|
+
L.push(`.btn-primary:active { transform: translateY(1px); background: ${mix(accent, bg, 0.7)}; }`);
|
|
348
|
+
} else if (r.fill === "outline") {
|
|
349
|
+
L.push(`.btn-primary { background: transparent; color: ${lineInk}; box-shadow: inset 0 0 0 ${r.adornment === "detail" || r.label === "upper-tracked" ? "1.5px" : "1px"} currentColor; }`);
|
|
350
|
+
L.push(`.btn-primary:hover { background: ${mix(lineInk, bg, 0.92)}; }`);
|
|
351
|
+
L.push(`.btn-primary:active { transform: translateY(1px); background: ${mix(lineInk, bg, 0.86)}; }`);
|
|
352
|
+
} else if (r.fill === "underline") {
|
|
353
|
+
L.push(`.btn-primary { color: ${lineInk}; text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 0.22em; }`);
|
|
354
|
+
L.push(".btn-primary:hover { text-decoration-thickness: 2px; }");
|
|
355
|
+
L.push(".btn-primary:active { transform: translateY(1px); }");
|
|
356
|
+
} else {
|
|
357
|
+
L.push(`.btn-primary { color: ${lineInk}; padding-inline: 12px; }`);
|
|
358
|
+
L.push(`.btn-primary:hover { background: ${mix(ink, bg, 0.93)}; }`);
|
|
359
|
+
L.push(".btn-primary:active { transform: translateY(1px); }");
|
|
360
|
+
}
|
|
361
|
+
// The stamped ticket: a hard offset edge that the press closes.
|
|
362
|
+
if (r.shape === "square" && r.fill === "outline" && r.adornment === "detail") {
|
|
363
|
+
L.push(`.btn-primary { box-shadow: inset 0 0 0 1.5px currentColor, 3px 3px 0 0 currentColor; }`);
|
|
364
|
+
L.push(".btn-primary:active { transform: translate(3px, 3px); box-shadow: inset 0 0 0 1.5px currentColor, 0 0 0 0 currentColor; }");
|
|
365
|
+
}
|
|
366
|
+
// A key: a darker bottom edge that the press flattens.
|
|
367
|
+
if (r.adornment === "keycap" && r.fill === "solid") {
|
|
368
|
+
L.push(`.btn-primary { box-shadow: inset 0 -2px 0 ${mix(accent, "#000000", 0.3)}; }`);
|
|
369
|
+
L.push(`.btn-primary:active { box-shadow: inset 0 0 0 ${mix(accent, "#000000", 0.3)}; transform: translateY(1px); }`);
|
|
370
|
+
}
|
|
371
|
+
// Secondary, by the language's second action.
|
|
372
|
+
const sec = r.secondary ?? "text-link";
|
|
373
|
+
const secInk = readable([ink], bg);
|
|
374
|
+
if (sec === "solid") {
|
|
375
|
+
L.push(`.btn-secondary { background: ${ink}; color: ${readable([bg, "#ffffff", "#000000"], ink)}; }`);
|
|
376
|
+
L.push(`.btn-secondary:hover { background: ${mix(ink, bg, 0.12)}; }`);
|
|
377
|
+
L.push(".btn-secondary:active { transform: translateY(1px); }");
|
|
378
|
+
} else if (sec === "ghost") {
|
|
379
|
+
L.push(`.btn-secondary { background: transparent; color: ${secInk}; box-shadow: inset 0 0 0 1px ${mix(ink, bg, 0.75)}; }`);
|
|
380
|
+
L.push(`.btn-secondary:hover { background: ${mix(ink, bg, 0.94)}; }`);
|
|
381
|
+
L.push(".btn-secondary:active { transform: translateY(1px); }");
|
|
382
|
+
} else {
|
|
383
|
+
// A text link (or the language has none and a section still needs one): no box, a hit area of 44px.
|
|
384
|
+
L.push(`.btn-secondary { min-height: ${TAP}px; padding-inline: 0; background: none; color: ${secInk}; font: 500 ${size.px}px/1.2 var(--font-body); letter-spacing: 0; text-transform: none; text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 0.22em; text-decoration-color: ${mix(ink, bg, 0.5)}; }`);
|
|
385
|
+
L.push(".btn-secondary:hover { text-decoration-color: currentColor; }");
|
|
386
|
+
if (sec === "inline") L.push(".btn-secondary.is-inline, p .btn-secondary { display: inline; min-height: 0; font: inherit; } /* inside the copy: the sentence carries it */");
|
|
387
|
+
}
|
|
388
|
+
// Adornment hooks: markup the builder adds when the language asks.
|
|
389
|
+
L.push(".btn-arrow { display: inline-block; transition: transform 160ms cubic-bezier(.23,1,.32,1); }");
|
|
390
|
+
L.push(":is(.btn, .btn-primary, .btn-secondary):hover .btn-arrow { transform: translateX(3px); }");
|
|
391
|
+
L.push(".btn-icon { width: 1.1em; height: 1.1em; flex: none; }");
|
|
392
|
+
L.push(".btn-detail { font-variant-numeric: tabular-nums; padding-left: 0.75em; margin-left: 0.25em; border-left: 1px solid color-mix(in oklab, currentColor 35%, transparent); }");
|
|
393
|
+
L.push(".btn-key, :is(.btn, .btn-primary) kbd { font: 500 0.8em/1 var(--font-mono, ui-monospace, monospace); padding: 0.25em 0.45em; border-radius: 4px; background: color-mix(in oklab, currentColor 14%, transparent); }");
|
|
394
|
+
// Craft that never changes.
|
|
395
|
+
L.push(`:is(.btn, .btn-primary, .btn-secondary):focus-visible { outline: 2px solid ${readable([accent, ink], bg, 3)}; outline-offset: ${boxless ? 4 : 2}px; }`);
|
|
396
|
+
L.push(':is(.btn, .btn-primary, .btn-secondary):is([disabled], [aria-disabled="true"]) { opacity: 0.5; cursor: not-allowed; transform: none; }');
|
|
397
|
+
L.push("@media (prefers-reduced-motion: reduce) { :is(.btn, .btn-primary, .btn-secondary, .btn-arrow) { transition: none; } :is(.btn, .btn-primary, .btn-secondary):active, :is(.btn, .btn-primary, .btn-secondary):hover .btn-arrow { transform: none; } }");
|
|
398
|
+
return { css: L.join("\n"), language: r, label: lbl, ground, ratio, notes };
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// How to write the markup for a language's adornment (for the build briefs).
|
|
402
|
+
export function markupHint(b) {
|
|
403
|
+
const r = resolveButtons(b);
|
|
404
|
+
if (!r) return "";
|
|
405
|
+
const hint = {
|
|
406
|
+
arrow: '<a class="btn-primary" href="…">See the spring menu <span class="btn-arrow" aria-hidden="true">→</span></a>',
|
|
407
|
+
icon: '<a class="btn-primary" href="…"><svg class="btn-icon" aria-hidden="true">…</svg>Join the Thursday session</a>',
|
|
408
|
+
detail: '<a class="btn-primary" href="…">Pre-order the box <span class="btn-detail">Sat · £18</span></a>',
|
|
409
|
+
keycap: '<a class="btn-primary" href="…">Start tracing <kbd aria-hidden="true">↵</kbd></a>',
|
|
410
|
+
none: '<a class="btn-primary" href="…">Book the walkthrough</a>',
|
|
411
|
+
}[r.adornment ?? "none"];
|
|
412
|
+
return `${hint}${r.secondary && r.secondary !== "none" ? ` · second action: \`.btn-secondary\` (${{ "text-link": "a text link", ghost: "a ghost button", solid: "a second solid in ink", inline: "a link inside the sentence" }[r.secondary]})` : " · no second action beside it"}`;
|
|
413
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
// Shared pieces of the design loop (variants → judge → pick → polish): where a loop's files live, which
|
|
2
|
+
// routes are its variants, rendering them all with one dev server, and the user's taste log.
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { PALATE_HOME } from "../catalog/store.mjs";
|
|
7
|
+
import { HIDE_DEV, routeFile } from "../look.mjs";
|
|
8
|
+
import { acquireBrowser, acquireServer } from "../pool.mjs";
|
|
9
|
+
import { normalizeRoute } from "../shoot.mjs";
|
|
10
|
+
import { loopOfRoute, loopTimeline, mark } from "./timeline.mjs";
|
|
11
|
+
import { activeRules, measureRules } from "./rules.mjs";
|
|
12
|
+
|
|
13
|
+
export const TASTE_DIR = path.join(PALATE_HOME, "taste");
|
|
14
|
+
const fingerprintScript = fs.readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "page-fingerprint.js"), "utf8").trim().replace(/;\s*$/, "");
|
|
15
|
+
|
|
16
|
+
export function sessionDir(dir, name) {
|
|
17
|
+
const d = path.join(dir, ".palate", "variants", name);
|
|
18
|
+
fs.mkdirSync(d, { recursive: true });
|
|
19
|
+
fs.writeFileSync(path.join(dir, ".palate", ".gitignore"), "*\n");
|
|
20
|
+
return d;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const readJson = (f, fallback) => {
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse(fs.readFileSync(f, "utf8"));
|
|
26
|
+
} catch {
|
|
27
|
+
return fallback;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Variants of a loop are routes named <name>-<direction letter><number>, e.g. pricing-a1, pricing-b2: either a
|
|
32
|
+
// route file (src/routes/pricing-a1.tsx, pages/pricing-a1.vue …) or a route folder (app/pricing-a1/page.tsx,
|
|
33
|
+
// src/routes/pricing-a1/+page.svelte …).
|
|
34
|
+
export function variantRoutes(dir, name) {
|
|
35
|
+
const roots = ["src/routes", "src/pages", "src/app", "app", "pages", "app/routes"].map((r) => path.join(dir, r)).filter((r) => fs.existsSync(r));
|
|
36
|
+
const esc = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
37
|
+
const fileRe = new RegExp(`^${esc}-([a-z]\\d+)\\.(tsx|jsx|ts|js|vue|svelte|astro)$`);
|
|
38
|
+
const dirRe = new RegExp(`^${esc}-([a-z]\\d+)$`);
|
|
39
|
+
const pageRe = /^(\+?page|index|route)\.(tsx|jsx|ts|js|vue|svelte|astro)$/;
|
|
40
|
+
const found = new Set();
|
|
41
|
+
for (const r of roots)
|
|
42
|
+
for (const e of fs.readdirSync(r, { withFileTypes: true })) {
|
|
43
|
+
if (e.isFile() && fileRe.test(e.name)) found.add(`/${e.name.replace(/\.[a-z]+$/, "")}`);
|
|
44
|
+
if (e.isDirectory() && dirRe.test(e.name) && fs.readdirSync(path.join(r, e.name)).some((f) => pageRe.test(f))) found.add(`/${e.name}`);
|
|
45
|
+
}
|
|
46
|
+
return [...found].sort();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Marketing sections are judged as a whole section; app screens and heroes on the first screen.
|
|
50
|
+
export function screenKind(name, job = "") {
|
|
51
|
+
const t = `${name} ${job}`.toLowerCase();
|
|
52
|
+
if (/\bhero\b/.test(t)) return { kind: "hero", full: false };
|
|
53
|
+
if (/pricing|features?|section|testimonial|faq|footer|cta|landing/.test(t)) return { kind: "marketing", full: true };
|
|
54
|
+
return { kind: "app", full: false };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// The newest modification time of the files that make up a route (its file, or every file in its folder).
|
|
58
|
+
export function routeMtime(dir, route) {
|
|
59
|
+
const found = routeFile(dir, route);
|
|
60
|
+
if (!found) return null;
|
|
61
|
+
const folder = path.dirname(found.file);
|
|
62
|
+
const own = folder !== found.root && path.basename(folder) === route.replace(/^\//, "").split("/").pop();
|
|
63
|
+
const files = own ? walkFiles(folder) : [found.file];
|
|
64
|
+
return Math.max(...files.map((f) => fs.statSync(f).mtimeMs));
|
|
65
|
+
}
|
|
66
|
+
const walkFiles = (d) => fs.readdirSync(d, { withFileTypes: true }).flatMap((e) => (e.isDirectory() ? walkFiles(path.join(d, e.name)) : [path.join(d, e.name)]));
|
|
67
|
+
const slugFile = (outDir, route) => path.join(outDir, `${route.replace(/^\//, "").replace(/[^a-z0-9-]+/gi, "-")}.png`);
|
|
68
|
+
|
|
69
|
+
// A render made earlier (by judge, for pick) that is still current: same settings, route files unchanged since.
|
|
70
|
+
function cached(dir, route, dirs, full) {
|
|
71
|
+
const mtime = routeMtime(dir, route);
|
|
72
|
+
if (mtime == null) return null;
|
|
73
|
+
for (const d of dirs) {
|
|
74
|
+
const file = slugFile(d, route);
|
|
75
|
+
const info = readJson(file.replace(/\.png$/, ".render.json"), null);
|
|
76
|
+
if (!info || info.full !== full || !fs.existsSync(file) || mtime > info.mtime) continue;
|
|
77
|
+
const fp = readJson(file.replace(/\.png$/, ".fp.json"), null)?.fp ?? null;
|
|
78
|
+
return { route, file, fp, cached: true };
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Loads a route like a reader would see it. A dev server may reload once while it optimises dependencies;
|
|
84
|
+
// then it waits for the reload instead of loading twice every time.
|
|
85
|
+
async function load(page, url) {
|
|
86
|
+
let navs = 0;
|
|
87
|
+
page.on("framenavigated", (f) => f === page.mainFrame() && navs++);
|
|
88
|
+
const res = await page.goto(url, { waitUntil: "networkidle", timeout: 60_000 }).catch(() => null);
|
|
89
|
+
await page.waitForTimeout(600);
|
|
90
|
+
if (navs > 1) {
|
|
91
|
+
await page.waitForLoadState("networkidle", { timeout: 30_000 }).catch(() => {});
|
|
92
|
+
await page.waitForTimeout(600);
|
|
93
|
+
}
|
|
94
|
+
return res;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Renders every route the same way (1440 wide) into outDir/<slug>.png, three at a time in one browser.
|
|
98
|
+
// Reports routes that fail to build. Each page's fingerprint (skeleton, first-screen shape, type and
|
|
99
|
+
// colour) is kept in outDir/<slug>.fp.json for the layout memory. reuse: folders whose renders may stand
|
|
100
|
+
// in when the route's files haven't changed since (pick reuses judge's).
|
|
101
|
+
// rules: also measure the facts the user's rules read (outDir/<slug>.facts.json), for the judge.
|
|
102
|
+
export async function render(dir, routes, outDir, { full = false, url, reuse = [], concurrency = 3, rules = false } = {}) {
|
|
103
|
+
const ruleList = rules ? activeRules() : [];
|
|
104
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
105
|
+
const out = new Array(routes.length);
|
|
106
|
+
const todo = [];
|
|
107
|
+
routes.forEach((raw, i) => {
|
|
108
|
+
const route = normalizeRoute(raw);
|
|
109
|
+
const hit = reuse.length ? cached(dir, route, reuse, full) : null;
|
|
110
|
+
if (hit) out[i] = hit;
|
|
111
|
+
else todo.push([i, route]);
|
|
112
|
+
});
|
|
113
|
+
if (!todo.length) return out;
|
|
114
|
+
const server = await acquireServer(dir, { url });
|
|
115
|
+
const { browser, release } = await acquireBrowser();
|
|
116
|
+
const one = async ([i, route]) => {
|
|
117
|
+
const mtime = routeMtime(dir, route);
|
|
118
|
+
// Reduced motion, so judge and picker see each page's finished state rather than an animation caught mid-play.
|
|
119
|
+
const page = await browser.newPage({ viewport: { width: 1440, height: 900 }, reducedMotion: "reduce" });
|
|
120
|
+
try {
|
|
121
|
+
await page.addInitScript(HIDE_DEV);
|
|
122
|
+
const res = await load(page, server.base + route);
|
|
123
|
+
const broken =
|
|
124
|
+
(await page.evaluate(() => document.querySelector("vite-error-overlay")?.shadowRoot?.querySelector(".message")?.textContent ?? null).catch(() => null)) ??
|
|
125
|
+
(res && res.status() >= 500 ? `the page returned ${res.status()} (a build or runtime error)` : null);
|
|
126
|
+
const file = slugFile(outDir, route);
|
|
127
|
+
if (broken) return (out[i] = { route, error: broken.trim().slice(0, 200) });
|
|
128
|
+
const fp = await page.evaluate(`(${fingerprintScript})()`).catch(() => null);
|
|
129
|
+
if (fp) fs.writeFileSync(file.replace(/\.png$/, ".fp.json"), JSON.stringify({ route, fp }));
|
|
130
|
+
if (ruleList.length) {
|
|
131
|
+
const m = await measureRules(page, ruleList).catch(() => null);
|
|
132
|
+
if (m) fs.writeFileSync(file.replace(/\.png$/, ".facts.json"), JSON.stringify({ route, facts: m.facts }));
|
|
133
|
+
}
|
|
134
|
+
if (full) {
|
|
135
|
+
const h = await page.evaluate(() => Math.min(document.documentElement.scrollHeight, 2400));
|
|
136
|
+
await page.setViewportSize({ width: 1440, height: Math.max(900, h) });
|
|
137
|
+
await page.waitForTimeout(300);
|
|
138
|
+
}
|
|
139
|
+
await page.screenshot({ path: file });
|
|
140
|
+
fs.writeFileSync(file.replace(/\.png$/, ".render.json"), JSON.stringify({ route, full, mtime, at: new Date().toISOString() }));
|
|
141
|
+
const loop = loopOfRoute(dir, route);
|
|
142
|
+
if (loop) mark(dir, loop, "first_render", { route });
|
|
143
|
+
out[i] = { route, file, fp };
|
|
144
|
+
} finally {
|
|
145
|
+
await page.close().catch(() => {});
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
try {
|
|
149
|
+
const queue = [...todo];
|
|
150
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, queue.length) }, async () => {
|
|
151
|
+
while (queue.length) {
|
|
152
|
+
const item = queue.shift();
|
|
153
|
+
await one(item).catch((e) => (out[item[0]] = { route: item[1], error: String(e.message).slice(0, 200) }));
|
|
154
|
+
}
|
|
155
|
+
}));
|
|
156
|
+
} finally {
|
|
157
|
+
await release();
|
|
158
|
+
server.stop();
|
|
159
|
+
}
|
|
160
|
+
return out.filter(Boolean);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Waits until `n` of the loop's variants are built: the route exists, its files have settled (unchanged
|
|
164
|
+
// for 15s after its builder's shoot, or 60s if it never shot), and it answers without an error. Returns
|
|
165
|
+
// the ready routes in the order they became ready; with fewer than n after `timeout`, whatever is ready
|
|
166
|
+
// (at least two). Every route seen ready is logged to the loop's timeline.
|
|
167
|
+
export async function waitReady(dir, name, n, { url, timeout = 30 * 60_000, poll = 4000, log = (s) => console.error(s) } = {}) {
|
|
168
|
+
const server = await acquireServer(dir, { url });
|
|
169
|
+
const ready = [];
|
|
170
|
+
const until = Date.now() + timeout;
|
|
171
|
+
const isVary = Boolean(readJson(path.join(sessionDir(dir, name), "loop.json"), {})?.vary);
|
|
172
|
+
let said = "";
|
|
173
|
+
try {
|
|
174
|
+
for (;;) {
|
|
175
|
+
const shotRoutes = new Set(Object.keys(loopTimeline(dir, name)?.shot ?? {}));
|
|
176
|
+
const routes = variantRoutes(dir, name).filter((r) => !ready.includes(r));
|
|
177
|
+
for (const r of routes) {
|
|
178
|
+
const m = routeMtime(dir, r);
|
|
179
|
+
if (m == null || Date.now() - m < (shotRoutes.has(r) ? 15_000 : 60_000)) continue;
|
|
180
|
+
// vary starts each route as an exact copy of the page; it is built only once its builder marks the new section.
|
|
181
|
+
const rf = isVary ? routeFile(dir, r)?.file : null;
|
|
182
|
+
// The attribute on a JSX/HTML tag, not the copy's header comment that mentions it.
|
|
183
|
+
if (isVary && !(rf && /<[a-z][\w.-]*\b[^<>]*\sdata-palate-vary\b/i.test(fs.readFileSync(rf, "utf8")))) continue;
|
|
184
|
+
const res = await fetch(server.base + r, { signal: AbortSignal.timeout(90_000) }).catch(() => null);
|
|
185
|
+
if (!res || res.status >= 400) continue;
|
|
186
|
+
ready.push(r);
|
|
187
|
+
mark(dir, name, "ready", { route: r });
|
|
188
|
+
}
|
|
189
|
+
const status = `${ready.length} of ${n} variants ready${ready.length ? `: ${ready.join(", ")}` : ""}`;
|
|
190
|
+
if (status !== said) log((said = status));
|
|
191
|
+
if (ready.length >= n) return ready;
|
|
192
|
+
if (Date.now() > until) {
|
|
193
|
+
if (ready.length >= 2) return ready;
|
|
194
|
+
throw new Error(`Only ${ready.length} variant(s) of "${name}" were ready after ${Math.round(timeout / 60000)} min.`);
|
|
195
|
+
}
|
|
196
|
+
await new Promise((r) => setTimeout(r, poll));
|
|
197
|
+
}
|
|
198
|
+
} finally {
|
|
199
|
+
server.stop();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function appendTaste(record) {
|
|
204
|
+
fs.mkdirSync(TASTE_DIR, { recursive: true });
|
|
205
|
+
fs.appendFileSync(path.join(TASTE_DIR, "picks.jsonl"), JSON.stringify({ ...record, at: new Date().toISOString() }) + "\n");
|
|
206
|
+
}
|