@palate/cli 0.0.0-stage → 0.0.1
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 +151 -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 +36 -4
- package/scripts/pack-assets.mjs +48 -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,1516 @@
|
|
|
1
|
+
// palate point: the overlay palate's proxy injects into the user's running app. Vanilla JS in a shadow root
|
|
2
|
+
// (nothing leaks into the page, nothing from the page leaks in), fixed-position only (no layout shift).
|
|
3
|
+
// Point mode: hover highlights an element (box, name, margin and padding), arrows walk up and down the
|
|
4
|
+
// tree, a click opens a small dialog anchored to it. Each request becomes a numbered pin whose status
|
|
5
|
+
// (queued, agent working, done, failed) updates live; a done pin shows before and after. Beside the palate
|
|
6
|
+
// button, a small status says whether an agent is listening, so the user knows their pins will be picked up.
|
|
7
|
+
(() => {
|
|
8
|
+
if (window.__palatePoint || window.top !== window) return;
|
|
9
|
+
window.__palatePoint = { version: 1 };
|
|
10
|
+
const API = "/__palate/api";
|
|
11
|
+
const isMac = /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent);
|
|
12
|
+
const coarse = matchMedia("(pointer: coarse)").matches;
|
|
13
|
+
const CHIPS = ["Fix spacing", "Fix alignment", "Type hierarchy", "Make it premium", "Add motion", "Real content & avatars", "Variants of this"];
|
|
14
|
+
const STUCK = "couldn't reopen";
|
|
15
|
+
const STATUS = { queued: "Queued", working: "Agent working", done: "Done", canceled: "Canceled", failed: "Failed" };
|
|
16
|
+
const START_AGENT = "In Claude Code run <b>/mcp__palate__point</b>, or start <b>palate point --auto</b>.";
|
|
17
|
+
const S = { on: false, leaf: null, target: null, dialog: null, requests: [], pop: null, els: new Map(), route: location.pathname, sending: false, agent: null };
|
|
18
|
+
|
|
19
|
+
const ICON = {
|
|
20
|
+
mark: `<svg viewBox="0 0 16 16" aria-hidden="true"><rect x="1.5" y="1.5" width="5.6" height="5.6" rx="1.5" class="m1"/><rect x="8.9" y="1.5" width="5.6" height="5.6" rx="1.5" opacity=".42"/><rect x="1.5" y="8.9" width="5.6" height="5.6" rx="1.5" opacity=".42"/><rect x="8.9" y="8.9" width="5.6" height="5.6" rx="1.5" opacity=".42"/></svg>`,
|
|
21
|
+
x: `<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M4 4l8 8M12 4l-8 8"/></svg>`,
|
|
22
|
+
up: `<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M8 12.5V3.5M4 7.5l4-4 4 4"/></svg>`,
|
|
23
|
+
down: `<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M8 3.5v9M4 8.5l4 4 4-4"/></svg>`,
|
|
24
|
+
check: `<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M3.5 8.5l3 3 6-7"/></svg>`,
|
|
25
|
+
send: `<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M8 13V3.5M4 7.5l4-4 4 4"/></svg>`,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const STYLES = `
|
|
29
|
+
:host { all: initial; }
|
|
30
|
+
* { box-sizing: border-box; }
|
|
31
|
+
[hidden] { display: none !important; }
|
|
32
|
+
.root {
|
|
33
|
+
--surface: #161615; --surface-2: #21211f; --surface-3: #2b2b28;
|
|
34
|
+
--line: rgba(255,255,255,.08); --line-2: rgba(255,255,255,.15);
|
|
35
|
+
--text: #f4f3ef; --muted: #a3a29b; --faint: #75746d;
|
|
36
|
+
--accent: #0f7a57; --accent-soft: rgba(15,122,87,.16); --accent-ring: rgba(15,122,87,.55);
|
|
37
|
+
--ok: #1f9e5a; --warn: #e0a43a; --margin: rgba(246,166,72,.30); --padding: rgba(72,196,128,.2);
|
|
38
|
+
--shadow: 0 1px 1px rgba(0,0,0,.12), 0 6px 16px -4px rgba(0,0,0,.28), 0 24px 48px -12px rgba(0,0,0,.32);
|
|
39
|
+
--ease: cubic-bezier(.2,.8,.2,1);
|
|
40
|
+
font: 500 12px/1.4 ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI Variable Text", "Segoe UI", Inter, Roboto, sans-serif;
|
|
41
|
+
color: var(--text); -webkit-font-smoothing: antialiased; letter-spacing: 0;
|
|
42
|
+
}
|
|
43
|
+
.mono { font-family: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, Consolas, monospace; }
|
|
44
|
+
button { font: inherit; color: inherit; background: none; border: 0; padding: 0; margin: 0; cursor: pointer; -webkit-tap-highlight-color: transparent; }
|
|
45
|
+
button:focus-visible, textarea:focus-visible { outline: 2px solid var(--accent-ring); outline-offset: 2px; }
|
|
46
|
+
svg { width: 16px; height: 16px; display: block; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
|
|
47
|
+
|
|
48
|
+
/* Highlight */
|
|
49
|
+
.hl { position: fixed; inset: 0; pointer-events: none; }
|
|
50
|
+
.box { position: fixed; top: 0; left: 0; border: 1.5px solid var(--accent); background: rgba(15,122,87,.07); border-radius: 2px; transition: transform .07s var(--ease), width .07s var(--ease), height .07s var(--ease); will-change: transform; }
|
|
51
|
+
.box.frozen { background: rgba(15,122,87,.1); box-shadow: 0 0 0 4px rgba(15,122,87,.14); }
|
|
52
|
+
.band { position: fixed; top: 0; left: 0; }
|
|
53
|
+
.band.m { background: repeating-linear-gradient(135deg, var(--margin) 0 3px, transparent 3px 6px); }
|
|
54
|
+
.band.p { background: var(--padding); }
|
|
55
|
+
.label { position: fixed; top: 0; left: 0; display: flex; flex-direction: column; gap: 1px; max-width: min(460px, calc(100vw - 16px)); padding: 4px 8px 5px; border-radius: 7px; background: var(--surface); box-shadow: var(--shadow); white-space: nowrap; }
|
|
56
|
+
.label .row { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
|
|
57
|
+
.label b { font-weight: 600; color: var(--text); }
|
|
58
|
+
.label .tag { color: #86dcb8; font-size: 11px; }
|
|
59
|
+
.label .txt { color: var(--muted); overflow: hidden; text-overflow: ellipsis; min-width: 0; }
|
|
60
|
+
.label .dim { color: var(--faint); font-variant-numeric: tabular-nums; margin-left: auto; padding-left: 8px; }
|
|
61
|
+
.label .sp { color: var(--faint); font-size: 11px; font-variant-numeric: tabular-nums; }
|
|
62
|
+
.label .sp i { font-style: normal; color: var(--muted); }
|
|
63
|
+
|
|
64
|
+
/* Floating button */
|
|
65
|
+
.fab { position: fixed; right: 20px; bottom: 20px; height: 40px; min-width: 40px; padding: 0 12px; display: flex; align-items: center; gap: 8px; border-radius: 999px; background: var(--surface); color: var(--text); border: 1px solid var(--line-2); box-shadow: var(--shadow); pointer-events: auto; transition: background-color .15s ease-out, transform .15s var(--ease); }
|
|
66
|
+
.fab:hover { background: var(--surface-2); }
|
|
67
|
+
.fab:active { transform: scale(.96); }
|
|
68
|
+
.fab .m1 { fill: currentColor; transition: fill .15s ease-out; }
|
|
69
|
+
.fab rect { fill: currentColor; }
|
|
70
|
+
.fab .word { display: none; font-weight: 600; }
|
|
71
|
+
.fab kbd { display: none; }
|
|
72
|
+
.fab.on .m1 { fill: var(--accent); }
|
|
73
|
+
.fab.on .word { display: inline; }
|
|
74
|
+
.fab.on kbd { display: inline-block; }
|
|
75
|
+
.fab:not(.on) { padding: 0; justify-content: center; width: 40px; }
|
|
76
|
+
.badge { position: absolute; top: -4px; right: -4px; min-width: 18px; height: 18px; padding: 0 5px; border-radius: 999px; background: var(--accent); color: #fff; font-size: 11px; font-weight: 600; line-height: 18px; text-align: center; font-variant-numeric: tabular-nums; box-shadow: 0 0 0 2px var(--surface); }
|
|
77
|
+
kbd { font: 500 11px/1 ui-monospace, "SF Mono", "Cascadia Mono", Menlo, Consolas, monospace; color: var(--muted); padding: 3px 5px; border-radius: 5px; border: 1px solid var(--line-2); background: var(--surface-2); }
|
|
78
|
+
|
|
79
|
+
/* Agent status, beside the button */
|
|
80
|
+
.agent { position: fixed; right: 68px; bottom: 26px; height: 28px; display: flex; align-items: center; gap: 7px; padding: 0 10px; border-radius: 999px; background: var(--surface); border: 1px solid var(--line); box-shadow: var(--shadow); color: var(--muted); font-size: 12px; white-space: nowrap; pointer-events: auto; }
|
|
81
|
+
.agent .ad { position: relative; width: 7px; height: 7px; border-radius: 50%; background: var(--faint); flex: none; }
|
|
82
|
+
.agent[data-state="listening"] .ad { background: #3ccf86; box-shadow: 0 0 0 3px rgba(60,207,134,.18); }
|
|
83
|
+
.agent[data-state="busy"] .ad { background: var(--accent); }
|
|
84
|
+
.agent[data-state="busy"] .ad::after { content: ""; position: absolute; inset: -4px; border-radius: 50%; border: 1.5px solid transparent; border-top-color: #3ccf86; animation: spin .9s linear infinite; }
|
|
85
|
+
.agent[data-state="none"].waiting .ad { background: #e0a43a; box-shadow: 0 0 0 3px rgba(224,164,58,.18); }
|
|
86
|
+
.agent[data-state="busy"] .at, .agent[data-state="listening"] .at { color: var(--text); }
|
|
87
|
+
.agent .at { font-variant-numeric: tabular-nums; }
|
|
88
|
+
.agent .stop { height: 20px; margin-right: -6px; padding: 0 8px; border-radius: 999px; color: var(--muted); font-size: 11px; font-weight: 600; border: 1px solid var(--line-2); transition: background-color .12s ease-out, color .12s ease-out; }
|
|
89
|
+
.agent .stop:hover { background: var(--surface-2); color: var(--text); }
|
|
90
|
+
|
|
91
|
+
/* Hint and toast */
|
|
92
|
+
.hint, .toast { position: fixed; left: 50%; bottom: 20px; transform: translateX(-50%); display: flex; align-items: center; gap: 8px; height: 34px; padding: 0 12px; border-radius: 999px; background: var(--surface); border: 1px solid var(--line); box-shadow: var(--shadow); color: var(--muted); white-space: nowrap; pointer-events: none; max-width: calc(100vw - 128px); overflow: hidden; }
|
|
93
|
+
.hint b, .toast b { color: var(--text); font-weight: 600; }
|
|
94
|
+
.hint .sep { width: 1px; height: 14px; background: var(--line-2); }
|
|
95
|
+
.hint .ext { display: contents; }
|
|
96
|
+
.toast { bottom: 64px; color: var(--text); animation: rise .18s var(--ease); }
|
|
97
|
+
.toast .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); flex: none; }
|
|
98
|
+
.toast.ok .dot { background: #3ccf86; }
|
|
99
|
+
.toast.warn .dot { background: #e0a43a; }
|
|
100
|
+
.toast.long { height: auto; min-height: 34px; padding: 8px 14px; border-radius: 17px; white-space: normal; line-height: 1.4; width: max-content; max-width: min(520px, calc(100vw - 32px)); }
|
|
101
|
+
|
|
102
|
+
/* Dialog */
|
|
103
|
+
.dialog { position: fixed; top: 0; left: 0; width: 360px; border-radius: 14px; background: var(--surface); border: 1px solid var(--line-2); box-shadow: var(--shadow); pointer-events: auto; animation: pop .16s var(--ease); transform-origin: var(--ox, 0) var(--oy, 0); }
|
|
104
|
+
.dh { display: flex; align-items: center; gap: 6px; padding: 10px 8px 0 12px; min-width: 0; }
|
|
105
|
+
.dh .name { display: flex; align-items: baseline; gap: 6px; min-width: 0; flex: 1; white-space: nowrap; }
|
|
106
|
+
.dh .name b { font-weight: 600; }
|
|
107
|
+
.dh .name .tag { color: #86dcb8; font-size: 11px; }
|
|
108
|
+
.dh .name .txt { color: var(--muted); overflow: hidden; text-overflow: ellipsis; min-width: 0; }
|
|
109
|
+
.ib { width: 26px; height: 26px; display: grid; place-items: center; border-radius: 7px; color: var(--muted); flex: none; transition: background-color .12s ease-out, color .12s ease-out; }
|
|
110
|
+
.ib:hover { background: var(--surface-2); color: var(--text); }
|
|
111
|
+
.ib:disabled { opacity: .35; cursor: default; background: none; }
|
|
112
|
+
textarea { display: block; width: 100%; resize: none; border: 0; outline: 0; background: transparent; color: var(--text); font: 400 14px/1.45 inherit; font-family: inherit; padding: 8px 12px 6px; min-height: 52px; max-height: 160px; }
|
|
113
|
+
textarea:focus-visible { outline: 0; }
|
|
114
|
+
textarea::placeholder { color: var(--faint); }
|
|
115
|
+
.chips { display: flex; flex-wrap: wrap; gap: 6px; padding: 2px 12px 12px; }
|
|
116
|
+
.chip { height: 26px; padding: 0 10px; border-radius: 999px; border: 1px solid var(--line-2); color: var(--muted); font-size: 12px; transition: background-color .12s ease-out, border-color .12s ease-out, color .12s ease-out; }
|
|
117
|
+
.chip:hover { color: var(--text); border-color: rgba(255,255,255,.26); }
|
|
118
|
+
.chip[aria-pressed="true"] { background: var(--accent-soft); border-color: var(--accent); color: var(--text); }
|
|
119
|
+
.df { display: flex; align-items: center; gap: 8px; padding: 8px 8px 8px 12px; border-top: 1px solid var(--line); }
|
|
120
|
+
.df .note { color: var(--faint); flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
121
|
+
.df .steps { color: var(--muted); white-space: nowrap; flex: none; cursor: help; }
|
|
122
|
+
.send { height: 30px; padding: 0 10px 0 12px; display: flex; align-items: center; gap: 8px; border-radius: 8px; background: var(--accent); color: #fff; font-weight: 600; transition: opacity .12s ease-out, transform .12s var(--ease); }
|
|
123
|
+
.send:active { transform: scale(.97); }
|
|
124
|
+
.send:disabled { opacity: .4; cursor: default; transform: none; }
|
|
125
|
+
.send kbd { color: rgba(255,255,255,.8); background: rgba(255,255,255,.12); border-color: rgba(255,255,255,.18); }
|
|
126
|
+
|
|
127
|
+
/* Pins */
|
|
128
|
+
.pins { position: fixed; inset: 0; pointer-events: none; }
|
|
129
|
+
.pin { position: fixed; top: 0; left: 0; width: 24px; height: 24px; border-radius: 50%; display: grid; place-items: center; background: var(--surface); color: var(--text); font-size: 11px; font-weight: 600; font-variant-numeric: tabular-nums; box-shadow: 0 0 0 2px #fff, 0 2px 8px rgba(0,0,0,.3); pointer-events: auto; transition: background-color .2s ease-out; }
|
|
130
|
+
.pin:hover { z-index: 2; }
|
|
131
|
+
.pin.working { background: var(--accent); }
|
|
132
|
+
.pin.working::after { content: ""; position: absolute; inset: -5px; border-radius: 50%; border: 2px solid transparent; border-top-color: var(--accent); border-right-color: var(--accent); animation: spin .9s linear infinite; }
|
|
133
|
+
.pin.done { background: var(--ok); animation: settle .32s var(--ease); }
|
|
134
|
+
.pin.done svg { width: 13px; height: 13px; stroke-width: 2.2; }
|
|
135
|
+
.pin.failed { background: #b7432f; }
|
|
136
|
+
.pin.broke::before { content: ""; position: absolute; top: -3px; right: -3px; width: 9px; height: 9px; border-radius: 50%; background: var(--warn); box-shadow: 0 0 0 2px var(--surface); }
|
|
137
|
+
.ghost { position: fixed; top: 0; left: 0; border: 1.5px dashed var(--accent); border-radius: 3px; pointer-events: none; }
|
|
138
|
+
|
|
139
|
+
/* Pin popover */
|
|
140
|
+
.pop { position: fixed; top: 0; left: 0; width: 300px; max-height: calc(100vh - 24px); border-radius: 12px; background: var(--surface); border: 1px solid var(--line-2); box-shadow: var(--shadow); pointer-events: auto; animation: pop .14s var(--ease); overflow: hidden auto; overscroll-behavior: contain; }
|
|
141
|
+
.ph { display: flex; align-items: center; gap: 8px; padding: 10px 6px 0 12px; }
|
|
142
|
+
.vpc { height: 20px; padding: 0 7px; border-radius: 999px; border: 1px solid var(--line-2); color: var(--muted); font-size: 11px; line-height: 18px; font-variant-numeric: tabular-nums; white-space: nowrap; flex: none; }
|
|
143
|
+
.dh .vp { color: var(--faint); font-size: 11px; font-variant-numeric: tabular-nums; white-space: nowrap; padding: 0 4px; flex: none; }
|
|
144
|
+
/* Before and after at both widths */
|
|
145
|
+
.bx { display: grid; gap: 6px; }
|
|
146
|
+
.bx .cols, .bx .pair2 { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
|
|
147
|
+
.bx .cols span { color: var(--faint); font-size: 11px; }
|
|
148
|
+
.bx .rl { display: flex; align-items: baseline; gap: 6px; margin-top: 2px; color: var(--muted); font-size: 11px; }
|
|
149
|
+
.bx .rl b { color: var(--text); font-weight: 600; }
|
|
150
|
+
.bx .rl .tg { margin-left: auto; color: var(--faint); }
|
|
151
|
+
.bx .rl .tg.broke { color: var(--warn); }
|
|
152
|
+
.cell { position: relative; height: 84px; border-radius: 7px; overflow: hidden; background: #fff; border: 1px solid var(--line); cursor: zoom-in; }
|
|
153
|
+
.cell img { display: block; width: 100%; height: 100%; object-fit: contain; object-position: top; user-select: none; -webkit-user-drag: none; }
|
|
154
|
+
.cell .cap { position: absolute; left: 4px; bottom: 4px; padding: 1px 5px; border-radius: 4px; background: rgba(22,22,21,.82); color: #fff; font-size: 10px; font-weight: 600; }
|
|
155
|
+
.cell.empty { display: grid; place-items: center; background: var(--surface-2); border-style: dashed; border-color: var(--line-2); color: var(--muted); font-size: 11px; }
|
|
156
|
+
.warnbox { display: flex; align-items: flex-start; gap: 8px; margin: 0; padding: 8px 10px; border-radius: 8px; background: rgba(224,164,58,.1); border: 1px solid rgba(224,164,58,.3); color: var(--text); line-height: 1.4; overflow-wrap: anywhere; }
|
|
157
|
+
.warnbox .wd { width: 7px; height: 7px; margin-top: 5px; border-radius: 50%; background: var(--warn); flex: none; }
|
|
158
|
+
.warnbox b { font-weight: 600; }
|
|
159
|
+
.onote { margin: 0; color: var(--faint); font-size: 11px; overflow-wrap: anywhere; }
|
|
160
|
+
.onote.stuck { color: var(--muted); }
|
|
161
|
+
.ph .st { display: flex; align-items: center; gap: 6px; flex: 1; color: var(--muted); }
|
|
162
|
+
.ph .st b { color: var(--text); font-weight: 600; }
|
|
163
|
+
.ph .st .d { width: 7px; height: 7px; border-radius: 50%; background: var(--faint); }
|
|
164
|
+
.ph .st .d.working { background: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
|
|
165
|
+
.ph .st .d.done { background: #3ccf86; }
|
|
166
|
+
.ph .st .d.failed { background: #e5664f; }
|
|
167
|
+
.pb { padding: 6px 12px 12px; display: grid; gap: 8px; }
|
|
168
|
+
.pb .ask { font-size: 13px; line-height: 1.45; color: var(--text); margin: 0; overflow-wrap: anywhere; }
|
|
169
|
+
.pb .tags { display: flex; flex-wrap: wrap; gap: 4px; }
|
|
170
|
+
.pb .tags span { height: 20px; line-height: 18px; padding: 0 7px; border-radius: 999px; border: 1px solid var(--line-2); color: var(--muted); font-size: 11px; }
|
|
171
|
+
.pb .sum { margin: 0; color: var(--muted); overflow-wrap: anywhere; }
|
|
172
|
+
.pb .sum b { color: var(--text); font-weight: 600; }
|
|
173
|
+
.pb .err { margin: 0; max-height: 120px; overflow: auto; padding: 6px 8px; border-radius: 7px; background: var(--surface-2); color: var(--muted); font-size: 11px; line-height: 1.45; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
174
|
+
.ba { display: grid; gap: 6px; }
|
|
175
|
+
.seg { display: inline-flex; padding: 2px; border-radius: 8px; background: var(--surface-2); justify-self: start; }
|
|
176
|
+
.seg button { height: 22px; padding: 0 9px; border-radius: 6px; color: var(--muted); font-size: 11px; font-weight: 600; }
|
|
177
|
+
.seg button[aria-pressed="true"] { background: var(--surface-3); color: var(--text); }
|
|
178
|
+
.shot { position: relative; border-radius: 8px; overflow: hidden; background: #fff; border: 1px solid var(--line); cursor: zoom-in; }
|
|
179
|
+
.shot img { display: block; width: 100%; max-height: 220px; object-fit: contain; object-position: top; user-select: none; -webkit-user-drag: none; }
|
|
180
|
+
.shot .cap { position: absolute; right: 6px; top: 6px; padding: 2px 6px; border-radius: 5px; background: rgba(22,22,21,.82); color: #fff; font-size: 10px; font-weight: 600; letter-spacing: .02em; }
|
|
181
|
+
.ba .hold { color: var(--faint); font-size: 11px; }
|
|
182
|
+
.ba .segs { display: flex; justify-content: space-between; gap: 6px; }
|
|
183
|
+
.gone { display: grid; place-items: center; gap: 2px; min-height: 96px; padding: 16px; border-radius: 8px; border: 1px dashed var(--line-2); color: var(--muted); text-align: center; }
|
|
184
|
+
.gone b { color: var(--text); font-weight: 600; }
|
|
185
|
+
.zoom .gone { min-width: 240px; min-height: 160px; background: var(--surface); }
|
|
186
|
+
.pa { display: flex; gap: 6px; }
|
|
187
|
+
.btn { height: 26px; padding: 0 10px; border-radius: 7px; border: 1px solid var(--line-2); color: var(--muted); font-size: 12px; }
|
|
188
|
+
.btn:hover { color: var(--text); background: var(--surface-2); }
|
|
189
|
+
.spin { width: 12px; height: 12px; border-radius: 50%; border: 2px solid var(--accent-soft); border-top-color: var(--accent); animation: spin .8s linear infinite; flex: none; }
|
|
190
|
+
.zoom { position: fixed; inset: 0; background: rgba(10,10,10,.72); display: grid; place-items: center; padding: 24px; pointer-events: auto; animation: fade .14s ease-out; cursor: zoom-out; }
|
|
191
|
+
.zoom .pair { display: grid; grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); gap: 16px; max-width: min(1400px, 100%); max-height: 100%; }
|
|
192
|
+
.zoom figure { margin: 0; display: grid; gap: 8px; min-width: 0; }
|
|
193
|
+
.zoom figcaption { color: #fff; font-weight: 600; }
|
|
194
|
+
.zoom img { max-width: 100%; max-height: calc(100vh - 96px); object-fit: contain; border-radius: 8px; background: #fff; }
|
|
195
|
+
|
|
196
|
+
@keyframes pop { from { opacity: 0; transform: translateY(4px) scale(.98); } }
|
|
197
|
+
@keyframes rise { from { opacity: 0; transform: translate(-50%, 6px); } }
|
|
198
|
+
@keyframes fade { from { opacity: 0; } }
|
|
199
|
+
@keyframes spin { to { transform: rotate(360deg); } }
|
|
200
|
+
@keyframes settle { 0% { transform: var(--t) scale(.6); } 60% { transform: var(--t) scale(1.12); } 100% { transform: var(--t) scale(1); } }
|
|
201
|
+
|
|
202
|
+
@media (max-width: 520px) {
|
|
203
|
+
.dialog { width: auto !important; left: 8px !important; right: 8px; top: auto !important; bottom: 8px; }
|
|
204
|
+
.pop { width: auto !important; left: 8px !important; right: 8px; top: auto !important; bottom: 8px; max-height: calc(100vh - 16px); }
|
|
205
|
+
.cell { height: 96px; }
|
|
206
|
+
.hint { bottom: 72px; max-width: calc(100vw - 32px); }
|
|
207
|
+
.toast { bottom: 112px; max-width: calc(100vw - 32px); width: max-content; height: auto; min-height: 34px; padding: 8px 14px; border-radius: 17px; white-space: normal; line-height: 1.35; }
|
|
208
|
+
.hint .ext { display: none; }
|
|
209
|
+
.fab { right: 16px; bottom: 16px; }
|
|
210
|
+
.agent { right: 64px; bottom: 22px; }
|
|
211
|
+
.label { max-width: calc(100vw - 16px); }
|
|
212
|
+
}
|
|
213
|
+
@media (min-width: 521px) and (max-width: 1000px) {
|
|
214
|
+
.hint { bottom: 64px; }
|
|
215
|
+
.toast { bottom: 108px; }
|
|
216
|
+
}
|
|
217
|
+
@media (prefers-reduced-motion: reduce) {
|
|
218
|
+
.box, .fab, .send, .chip { transition: none; }
|
|
219
|
+
.agent[data-state="busy"] .ad::after { animation-duration: 2.4s; }
|
|
220
|
+
.dialog, .pop, .toast, .zoom, .pin.done { animation: none; }
|
|
221
|
+
.pin.working::after, .spin { animation-duration: 2.4s; }
|
|
222
|
+
}`;
|
|
223
|
+
|
|
224
|
+
// The same names the server uses: every pin is checked at the width it was pinned at and the other one.
|
|
225
|
+
const wname = (w) => (w <= 500 ? "phone" : w < 1024 ? "tablet" : "desktop");
|
|
226
|
+
const otherOf = (w) => (w >= 768 ? 390 : 1440);
|
|
227
|
+
const esc = (s) => String(s ?? "").replace(/[<>&"']/g, (c) => ({ "<": "<", ">": ">", "&": "&", '"': """, "'": "'" })[c]);
|
|
228
|
+
const host = document.createElement("palate-point");
|
|
229
|
+
host.setAttribute("data-palate", "");
|
|
230
|
+
host.style.cssText = "all:initial;position:fixed;top:0;left:0;width:0;height:0;z-index:2147483647;pointer-events:none;";
|
|
231
|
+
const sr = host.attachShadow({ mode: "open" });
|
|
232
|
+
sr.innerHTML = `<style>${STYLES}</style><div class="root">
|
|
233
|
+
<div class="pins"></div>
|
|
234
|
+
<div class="hl" hidden><div class="band m"></div><div class="band m"></div><div class="band m"></div><div class="band m"></div><div class="band p"></div><div class="band p"></div><div class="band p"></div><div class="band p"></div><div class="box"></div><div class="label"></div></div>
|
|
235
|
+
<div class="hint" hidden></div>
|
|
236
|
+
<div class="toast" hidden role="status" aria-live="polite"></div>
|
|
237
|
+
<div class="agent" data-state="none" role="status" aria-live="polite" hidden><span class="ad" aria-hidden="true"></span><span class="at">No agent listening</span><button class="stop" type="button" hidden title="I'm done pointing: the agent finishes the pins already sent, then stops listening">Done</button></div>
|
|
238
|
+
<button class="fab" type="button" aria-pressed="false" title="Point at something to fix (${isMac ? "⌥" : "Alt+"}P)">${ICON.mark}<span class="word">Pointing</span><kbd>Esc</kbd><span class="badge" hidden></span></button>
|
|
239
|
+
<div class="dialog" role="dialog" aria-label="Ask the agent to change this element" hidden></div>
|
|
240
|
+
<div class="pop" role="dialog" hidden></div>
|
|
241
|
+
</div>`;
|
|
242
|
+
const $ = (s) => sr.querySelector(s);
|
|
243
|
+
const ui = { root: $(".root"), hl: $(".hl"), box: $(".box"), label: $(".label"), bands: [...sr.querySelectorAll(".band")], fab: $(".fab"), badge: $(".badge"), hint: $(".hint"), toast: $(".toast"), dialog: $(".dialog"), pins: $(".pins"), pop: $(".pop"), agent: $(".agent") };
|
|
244
|
+
const ours = (e) => e.composedPath().includes(host);
|
|
245
|
+
|
|
246
|
+
// ---------- What an element is ----------
|
|
247
|
+
const INTERNAL = /^(InnerLayoutRouter|OuterLayoutRouter|RedirectErrorBoundary|RedirectBoundary|HTTPAccessFallbackErrorBoundary|HTTPAccessFallbackBoundary|DevRootHTTPAccessFallbackBoundary|LoadingBoundary|ErrorBoundary|ErrorBoundaryHandler|GlobalError|ScrollAndFocusHandler|InnerScrollAndFocusHandler|RenderFromTemplateContext|AppRouter|Router|HotReload|ReactDevOverlay|AppDevOverlay|AppDevOverlayErrorBoundary|ServerRoot|Root|ClientPageRoot|ClientSegmentRoot|SegmentViewNode|SegmentViewStateNode|SegmentStateProvider|SegmentBoundaryTriggerNode|SegmentTrieNode|Suspense|Fragment|StrictMode|Profiler|Head|MetadataOutlet|MetadataBoundary|ViewportBoundary|OutletBoundary|RootLayoutBoundary|Link|LinkComponent|Image|ImageElement|Script|Portal|Presence|PresenceChild|AnimatePresence|LayoutGroup|MotionConfig|LazyMotion|PopChild|PopChildMeasure|Slot|SlotClone|Primitive|Provider|Consumer|ThemeProvider)$/;
|
|
248
|
+
const fiberName = (t) => {
|
|
249
|
+
if (!t) return null;
|
|
250
|
+
if (typeof t === "string") return null;
|
|
251
|
+
const n = t.displayName || t.name || t.render?.displayName || t.render?.name || t.type?.displayName || t.type?.name || null;
|
|
252
|
+
return n && /^[A-Z]/.test(n) && !/[.$]|^Motion|^Styled|(Context|Provider|Boundary|Handler|Router|Outlet|Root)$/.test(n) && !INTERNAL.test(n) ? n : null;
|
|
253
|
+
};
|
|
254
|
+
function framework(el) {
|
|
255
|
+
const out = {};
|
|
256
|
+
let node = el;
|
|
257
|
+
let key = null;
|
|
258
|
+
for (; node && node !== document.documentElement; node = node.parentElement) {
|
|
259
|
+
key = Object.keys(node).find((k) => k.startsWith("__reactFiber$") || k.startsWith("__reactInternalInstance$"));
|
|
260
|
+
if (key) break;
|
|
261
|
+
}
|
|
262
|
+
if (key) {
|
|
263
|
+
const f = node[key];
|
|
264
|
+
const components = [];
|
|
265
|
+
for (let x = f; x && components.length < 8; x = x.return) {
|
|
266
|
+
const n = fiberName(x.type);
|
|
267
|
+
if (n && components.at(-1) !== n) components.push(n);
|
|
268
|
+
}
|
|
269
|
+
// The owner chain: who rendered this element (includes server components in React 19 dev builds).
|
|
270
|
+
const owners = [];
|
|
271
|
+
const seen = new Set();
|
|
272
|
+
for (let o = f._debugOwner; o && owners.length < 8 && !seen.has(o); o = o._debugOwner || o.owner) {
|
|
273
|
+
seen.add(o);
|
|
274
|
+
const n = o.name ?? fiberName(o.type);
|
|
275
|
+
if (n && !INTERNAL.test(n) && owners.at(-1)?.name !== n) owners.push({ name: n, server: o.env ? o.env === "Server" : undefined });
|
|
276
|
+
}
|
|
277
|
+
const nearest = owners[0]?.name ?? components[0] ?? null;
|
|
278
|
+
out.react = { component: nearest, components, owners };
|
|
279
|
+
}
|
|
280
|
+
for (let e = el; e && !out.vue; e = e.parentElement) {
|
|
281
|
+
const c = e.__vueParentComponent;
|
|
282
|
+
if (c) {
|
|
283
|
+
const chain = [];
|
|
284
|
+
for (let x = c; x && chain.length < 6; x = x.parent) chain.push({ name: x.type?.__name || x.type?.name || null, file: x.type?.__file || null });
|
|
285
|
+
out.vue = { component: chain[0]?.name ?? null, file: chain[0]?.file ?? null, chain };
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
for (let e = el; e && !out.svelte; e = e.parentElement) {
|
|
289
|
+
const m = e.__svelte_meta;
|
|
290
|
+
if (m?.loc) out.svelte = { file: m.loc.file, line: m.loc.line, column: m.loc.column };
|
|
291
|
+
}
|
|
292
|
+
return out;
|
|
293
|
+
}
|
|
294
|
+
const componentOf = (el) => {
|
|
295
|
+
const f = framework(el);
|
|
296
|
+
return f.react?.component || f.vue?.component || null;
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
const stableId = (id) => id && /^[A-Za-z][\w-]*$/.test(id) && !/^(radix|headlessui|react-aria|mui|chakra)|\d{3,}/i.test(id);
|
|
300
|
+
const unique = (s) => {
|
|
301
|
+
try {
|
|
302
|
+
return document.querySelectorAll(s).length === 1;
|
|
303
|
+
} catch {
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
// The shortest child-combinator path that matches only this element, anchored at an id or test id when there is one.
|
|
308
|
+
function selectorOf(el) {
|
|
309
|
+
const parts = [];
|
|
310
|
+
for (let e = el; e && e.nodeType === 1 && e !== document.documentElement; e = e.parentElement) {
|
|
311
|
+
if (stableId(e.id)) {
|
|
312
|
+
parts.unshift(`#${CSS.escape(e.id)}`);
|
|
313
|
+
if (unique(parts.join(" > "))) return parts.join(" > ");
|
|
314
|
+
parts[0] = `${e.tagName.toLowerCase()}#${CSS.escape(e.id)}`;
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
const tid = ["data-testid", "data-test", "data-cy"].map((a) => [a, e.getAttribute(a)]).find(([, v]) => v);
|
|
318
|
+
let part = tid ? `[${tid[0]}="${CSS.escape(tid[1])}"]` : e.tagName.toLowerCase();
|
|
319
|
+
if (!tid && e.parentElement) {
|
|
320
|
+
const same = [...e.parentElement.children].filter((c) => c.tagName === e.tagName);
|
|
321
|
+
if (same.length > 1) part += `:nth-of-type(${same.indexOf(e) + 1})`;
|
|
322
|
+
}
|
|
323
|
+
parts.unshift(part);
|
|
324
|
+
const s = parts.join(" > ");
|
|
325
|
+
if ((parts.length >= 2 || tid) && unique(s)) return s;
|
|
326
|
+
if (e === document.body) break;
|
|
327
|
+
}
|
|
328
|
+
return parts.join(" > ");
|
|
329
|
+
}
|
|
330
|
+
function domPath(el) {
|
|
331
|
+
const out = [];
|
|
332
|
+
for (let e = el; e && e.nodeType === 1 && out.length < 12; e = e.parentElement) {
|
|
333
|
+
const cls = typeof e.className === "string" ? e.className.trim().split(/\s+/)[0] : "";
|
|
334
|
+
out.unshift(`${e.tagName.toLowerCase()}${e.id ? `#${e.id}` : ""}${cls ? `.${cls}` : ""}`);
|
|
335
|
+
if (e === document.body) break;
|
|
336
|
+
}
|
|
337
|
+
return out.join(" > ");
|
|
338
|
+
}
|
|
339
|
+
const shortText = (el, n = 34) => {
|
|
340
|
+
const t = (el.innerText || el.textContent || el.getAttribute("aria-label") || el.getAttribute("alt") || "").replace(/\s+/g, " ").trim();
|
|
341
|
+
return t.length > n ? `${t.slice(0, n - 1).trimEnd()}…` : t;
|
|
342
|
+
};
|
|
343
|
+
const textNodes = (el) => {
|
|
344
|
+
const out = [];
|
|
345
|
+
const w = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
|
|
346
|
+
for (let n = w.nextNode(); n && out.length < 40; n = w.nextNode()) {
|
|
347
|
+
const t = n.textContent.replace(/\s+/g, " ").trim();
|
|
348
|
+
if (t.length > 2 && !/^(script|style)$/i.test(n.parentElement?.tagName)) out.push(t.slice(0, 200));
|
|
349
|
+
}
|
|
350
|
+
for (const a of ["aria-label", "alt", "placeholder", "title"]) if (el.getAttribute?.(a)) out.push(el.getAttribute(a));
|
|
351
|
+
return out;
|
|
352
|
+
};
|
|
353
|
+
function trimHTML(el) {
|
|
354
|
+
const c = el.cloneNode(true);
|
|
355
|
+
// No values from private fields (some frameworks mirror a field's value into its attribute).
|
|
356
|
+
const live = [el, ...el.querySelectorAll("*")];
|
|
357
|
+
[c, ...c.querySelectorAll("*")].forEach((n, i) => {
|
|
358
|
+
if (/^(INPUT|TEXTAREA)$/.test(n.tagName) && live[i] && secret(live[i])) {
|
|
359
|
+
n.removeAttribute("value");
|
|
360
|
+
n.textContent = "";
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
c.querySelectorAll("script, style, noscript, template, palate-point").forEach((n) => n.remove());
|
|
364
|
+
c.querySelectorAll("svg").forEach((s) => s.replaceChildren(document.createTextNode("…")));
|
|
365
|
+
const walk = (n, d) => {
|
|
366
|
+
const kids = [...n.children];
|
|
367
|
+
if (d >= 5) return kids.length && n.replaceChildren(document.createTextNode(`…${kids.length} child${kids.length > 1 ? "ren" : ""}`));
|
|
368
|
+
kids.slice(12).forEach((k) => k.remove());
|
|
369
|
+
if (kids.length > 12) n.appendChild(document.createComment(` …${kids.length - 12} more `));
|
|
370
|
+
kids.slice(0, 12).forEach((k) => walk(k, d + 1));
|
|
371
|
+
};
|
|
372
|
+
walk(c, 0);
|
|
373
|
+
for (const n of [c, ...c.querySelectorAll("*")]) for (const a of [...n.attributes]) if (a.value.length > 180) n.setAttribute(a.name, `${a.value.slice(0, 180)}…`);
|
|
374
|
+
const tw = document.createTreeWalker(c, NodeFilter.SHOW_TEXT);
|
|
375
|
+
for (let t = tw.nextNode(); t; t = tw.nextNode()) if (t.textContent.length > 240) t.textContent = `${t.textContent.slice(0, 240)}…`;
|
|
376
|
+
const h = c.outerHTML;
|
|
377
|
+
return h.length > 6000 ? `${h.slice(0, 6000)}\n<!-- …trimmed -->` : h;
|
|
378
|
+
}
|
|
379
|
+
const STYLE_KEYS = ["display", "position", "width", "height", "max-width", "margin", "padding", "gap", "flex-direction", "justify-content", "align-items", "grid-template-columns", "font-family", "font-size", "font-weight", "line-height", "letter-spacing", "text-align", "color", "background-color", "background-image", "border", "border-radius", "box-shadow", "opacity", "transform", "transition"];
|
|
380
|
+
const DEFAULTS = /^(none|normal|auto|all|0px|0px 0px|0px 0px 0px 0px|rgba\(0, 0, 0, 0\)|static|visible|1|0s|all 0s ease 0s|start|stretch|0px (none|solid).*|row|matrix\(1, 0, 0, 1, 0, 0\))$/;
|
|
381
|
+
function keyStyles(el) {
|
|
382
|
+
const cs = getComputedStyle(el);
|
|
383
|
+
const out = {};
|
|
384
|
+
for (const k of STYLE_KEYS) {
|
|
385
|
+
let v = cs.getPropertyValue(k);
|
|
386
|
+
if (!v || (DEFAULTS.test(v) && !["display", "font-size", "color", "width", "height"].includes(k))) continue;
|
|
387
|
+
if (k === "font-family") v = v.split(",")[0].replace(/["']/g, "");
|
|
388
|
+
if (k === "background-image" && v.length > 120) v = `${v.slice(0, 120)}…`;
|
|
389
|
+
out[k] = v;
|
|
390
|
+
}
|
|
391
|
+
return out;
|
|
392
|
+
}
|
|
393
|
+
const px = (v) => Math.round(parseFloat(v) || 0);
|
|
394
|
+
const quad = (a) => (a.every((x) => x === a[0]) ? `${a[0]}` : a[0] === a[2] && a[1] === a[3] ? `${a[0]} ${a[1]}` : a.join(" "));
|
|
395
|
+
function spacing(cs) {
|
|
396
|
+
const m = [cs.marginTop, cs.marginRight, cs.marginBottom, cs.marginLeft].map(px);
|
|
397
|
+
const p = [cs.paddingTop, cs.paddingRight, cs.paddingBottom, cs.paddingLeft].map(px);
|
|
398
|
+
const g = /flex|grid/.test(cs.display) ? px(cs.rowGap === cs.columnGap ? cs.rowGap : cs.rowGap) : 0;
|
|
399
|
+
const out = [];
|
|
400
|
+
if (p.some(Boolean)) out.push(`<i>padding</i> ${quad(p)}`);
|
|
401
|
+
if (m.some(Boolean)) out.push(`<i>margin</i> ${quad(m)}`);
|
|
402
|
+
if (g) out.push(`<i>gap</i> ${g}`);
|
|
403
|
+
return out.join(" · ");
|
|
404
|
+
}
|
|
405
|
+
function describe(el) {
|
|
406
|
+
const comp = componentOf(el);
|
|
407
|
+
const tag = el.tagName.toLowerCase() + (stableId(el.id) ? `#${el.id}` : "");
|
|
408
|
+
return { comp, tag, text: shortText(el) };
|
|
409
|
+
}
|
|
410
|
+
const nameHTML = ({ comp, tag, text }) => `${comp ? `<b>${esc(comp)}</b>` : ""}<span class="tag mono">${esc(tag)}</span>${text ? `<span class="txt">${esc(text)}</span>` : ""}`;
|
|
411
|
+
|
|
412
|
+
// ---------- Highlight ----------
|
|
413
|
+
const visible = (el) => el && el.isConnected && el.getClientRects().length > 0;
|
|
414
|
+
function draw(el, { bands = true, frozen = false } = {}) {
|
|
415
|
+
if (!visible(el)) return void (ui.hl.hidden = true);
|
|
416
|
+
ui.hl.hidden = false;
|
|
417
|
+
const r = el.getBoundingClientRect();
|
|
418
|
+
const cs = getComputedStyle(el);
|
|
419
|
+
const b = ui.box.style;
|
|
420
|
+
b.transform = `translate(${r.left}px, ${r.top}px)`;
|
|
421
|
+
b.width = `${Math.max(r.width, 2)}px`;
|
|
422
|
+
b.height = `${Math.max(r.height, 2)}px`;
|
|
423
|
+
b.borderRadius = cs.borderRadius && cs.borderRadius !== "0px" ? cs.borderRadius : "2px";
|
|
424
|
+
ui.box.classList.toggle("frozen", frozen);
|
|
425
|
+
const m = [cs.marginTop, cs.marginRight, cs.marginBottom, cs.marginLeft].map((v) => Math.max(0, px(v)));
|
|
426
|
+
const bw = [cs.borderTopWidth, cs.borderRightWidth, cs.borderBottomWidth, cs.borderLeftWidth].map(px);
|
|
427
|
+
const p = [cs.paddingTop, cs.paddingRight, cs.paddingBottom, cs.paddingLeft].map((v) => Math.max(0, px(v)));
|
|
428
|
+
const rects = bands
|
|
429
|
+
? [
|
|
430
|
+
[r.left, r.top - m[0], r.width, m[0]],
|
|
431
|
+
[r.right, r.top, m[1], r.height],
|
|
432
|
+
[r.left, r.bottom, r.width, m[2]],
|
|
433
|
+
[r.left - m[3], r.top, m[3], r.height],
|
|
434
|
+
[r.left + bw[3], r.top + bw[0], r.width - bw[1] - bw[3], p[0]],
|
|
435
|
+
[r.right - bw[1] - p[1], r.top + bw[0] + p[0], p[1], r.height - bw[0] - bw[2] - p[0] - p[2]],
|
|
436
|
+
[r.left + bw[3], r.bottom - bw[2] - p[2], r.width - bw[1] - bw[3], p[2]],
|
|
437
|
+
[r.left + bw[3], r.top + bw[0] + p[0], p[3], r.height - bw[0] - bw[2] - p[0] - p[2]],
|
|
438
|
+
]
|
|
439
|
+
: [];
|
|
440
|
+
ui.bands.forEach((d, i) => {
|
|
441
|
+
const q = rects[i];
|
|
442
|
+
if (!q || q[2] <= 0 || q[3] <= 0) return void (d.style.display = "none");
|
|
443
|
+
d.style.display = "block";
|
|
444
|
+
d.style.transform = `translate(${q[0]}px, ${q[1]}px)`;
|
|
445
|
+
d.style.width = `${q[2]}px`;
|
|
446
|
+
d.style.height = `${q[3]}px`;
|
|
447
|
+
});
|
|
448
|
+
const d = describe(el);
|
|
449
|
+
const sp = bands ? spacing(cs) : "";
|
|
450
|
+
const key = `${d.comp}|${d.tag}|${d.text}|${Math.round(r.width)}|${Math.round(r.height)}|${sp}`;
|
|
451
|
+
if (ui.label.dataset.key !== key) {
|
|
452
|
+
ui.label.dataset.key = key;
|
|
453
|
+
ui.label.innerHTML = `<div class="row">${nameHTML(d)}<span class="dim">${Math.round(r.width)} × ${Math.round(r.height)}</span></div>${sp ? `<div class="sp">${sp}</div>` : ""}`;
|
|
454
|
+
}
|
|
455
|
+
const lh = ui.label.offsetHeight || 24;
|
|
456
|
+
const lw = ui.label.offsetWidth || 120;
|
|
457
|
+
let ly = r.top - lh - 6;
|
|
458
|
+
if (ly < 4) ly = r.bottom + 6 + lh < innerHeight ? r.bottom + 6 : Math.max(4, r.top + 4);
|
|
459
|
+
const lx = Math.min(Math.max(4, r.left), innerWidth - lw - 4);
|
|
460
|
+
ui.label.style.transform = `translate(${lx}px, ${ly}px)`;
|
|
461
|
+
ui.label.hidden = Boolean(S.dialog) && innerWidth <= 520;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function leafAt(x, y) {
|
|
465
|
+
for (const el of document.elementsFromPoint(x, y)) {
|
|
466
|
+
if (el === host || host.contains(el) || el === document.documentElement) continue;
|
|
467
|
+
return el;
|
|
468
|
+
}
|
|
469
|
+
return null;
|
|
470
|
+
}
|
|
471
|
+
function hoverAt(x, y) {
|
|
472
|
+
const leaf = leafAt(x, y);
|
|
473
|
+
if (!leaf) return;
|
|
474
|
+
// After walking up with the arrows, stay on that ancestor while the pointer is inside it.
|
|
475
|
+
if (S.target && S.target !== S.leaf && S.target.contains(leaf)) S.leaf = leaf;
|
|
476
|
+
else S.leaf = S.target = leaf;
|
|
477
|
+
draw(S.target);
|
|
478
|
+
}
|
|
479
|
+
function up() {
|
|
480
|
+
const el = S.dialog ? S.dialog.el : S.target;
|
|
481
|
+
const p = el?.parentElement;
|
|
482
|
+
if (!p || p === document.documentElement) return;
|
|
483
|
+
setTarget(p);
|
|
484
|
+
}
|
|
485
|
+
function down() {
|
|
486
|
+
const el = S.dialog ? S.dialog.el : S.target;
|
|
487
|
+
if (!el || !S.leaf || el === S.leaf || !el.contains(S.leaf)) {
|
|
488
|
+
const c = el && [...el.children].find(visible);
|
|
489
|
+
if (c) setTarget(c);
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
let c = S.leaf;
|
|
493
|
+
while (c.parentElement && c.parentElement !== el) c = c.parentElement;
|
|
494
|
+
setTarget(c);
|
|
495
|
+
}
|
|
496
|
+
function setTarget(el) {
|
|
497
|
+
S.target = el;
|
|
498
|
+
if (S.dialog) {
|
|
499
|
+
S.dialog.el = el;
|
|
500
|
+
renderDialogHead();
|
|
501
|
+
draw(el, { bands: false, frozen: true });
|
|
502
|
+
} else draw(el);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// ---------- The trail: the steps that open a state ----------
|
|
506
|
+
// While palate's overlay is on the page (pointing on or off), the user's own steps since the last navigation
|
|
507
|
+
// are kept in a small in-memory list: clicks on controls, Enter/Space on a control, Esc, Tab, arrows in menus,
|
|
508
|
+
// typing (merged per field) and selects. Never a value from a private field (password, card, one-time code,
|
|
509
|
+
// token…), and never palate's own clicks or keys. A pin carries the few steps that lead to its element's
|
|
510
|
+
// state (trimTrail in trail.mjs explains which), and palate replays them before every crop.
|
|
511
|
+
// Form submits aren't recorded: replaying them would send the form again on every check.
|
|
512
|
+
const T = /*@trail*/ ({ trimTrail: () => [], stepShort: (s) => s.text || s.type, SECRET: /passw|card|cvc|token|secret|ssn/i, SECRET_AUTOCOMPLETE: /cc-|one-time-code|password/i, MAX: 10 }) /*@end*/;
|
|
513
|
+
const trail = { steps: [], base: null, url: location.pathname + location.search, down: null, born: new WeakMap(), secret: new WeakSet() };
|
|
514
|
+
const LAYER = 'dialog[open], [role="dialog"], [role="alertdialog"], [aria-modal="true"], [role="menu"], [role="listbox"], [role="tooltip"], [data-state="open"]:not([aria-expanded]):not(button), details[open]';
|
|
515
|
+
const ACTION = 'a[href], button, input, select, textarea, summary, label, [role="button"], [role="link"], [role="tab"], [role="menuitem"], [role="menuitemcheckbox"], [role="menuitemradio"], [role="option"], [role="switch"], [role="checkbox"], [role="radio"], [role="combobox"], [role="treeitem"], [aria-haspopup], [aria-expanded], [aria-controls], [onclick], [tabindex]:not([tabindex="-1"])';
|
|
516
|
+
const NAV_KEYS = '[role="menu"], [role="menubar"], [role="listbox"], [role="tablist"], [role="radiogroup"], [role="tree"], [role="grid"], [role="combobox"], select';
|
|
517
|
+
const shows = (el) => el.isConnected && (el.checkVisibility ? el.checkVisibility({ opacityProperty: true, visibilityProperty: true }) : el.getClientRects().length > 0);
|
|
518
|
+
const regionOf = (t) => {
|
|
519
|
+
const id = (t.getAttribute("aria-controls") || "").split(/\s+/)[0];
|
|
520
|
+
return (id && document.getElementById(id)) || t.parentElement;
|
|
521
|
+
};
|
|
522
|
+
const modal = (e) => {
|
|
523
|
+
try {
|
|
524
|
+
return e.matches(":modal");
|
|
525
|
+
} catch {
|
|
526
|
+
return false;
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
const kindOf = (e) => {
|
|
530
|
+
const role = e.getAttribute("role");
|
|
531
|
+
if (e.tagName === "DIALOG" || role === "dialog" || role === "alertdialog" || e.getAttribute("aria-modal") === "true") return e.getAttribute("aria-modal") === "true" || role === "alertdialog" || modal(e) ? "modal" : "dialog";
|
|
532
|
+
if (role === "menu" || role === "listbox" || role === "tooltip") return role;
|
|
533
|
+
return "expanded part";
|
|
534
|
+
};
|
|
535
|
+
// Open dialogs, menus, popovers, listboxes and expanded parts on the page now (element → kind).
|
|
536
|
+
function layers() {
|
|
537
|
+
const out = new Map();
|
|
538
|
+
for (const e of document.querySelectorAll(LAYER)) if (shows(e)) out.set(e, kindOf(e));
|
|
539
|
+
try {
|
|
540
|
+
for (const e of document.querySelectorAll(":popover-open")) out.set(e, out.get(e) ?? "popover");
|
|
541
|
+
} catch {}
|
|
542
|
+
for (const t of document.querySelectorAll('[aria-expanded="true"]')) {
|
|
543
|
+
const r = regionOf(t);
|
|
544
|
+
const pop = t.getAttribute("aria-haspopup");
|
|
545
|
+
if (r && shows(r) && !out.has(r)) out.set(r, pop && pop !== "false" ? (pop === "dialog" ? "dialog" : pop === "listbox" ? "listbox" : "menu") : "expanded part");
|
|
546
|
+
}
|
|
547
|
+
return out;
|
|
548
|
+
}
|
|
549
|
+
// What was already open when the user first did anything here (a banner, a menu bar) doesn't count.
|
|
550
|
+
const ensureBase = () => (trail.base ??= new Set(layers().keys()));
|
|
551
|
+
const atRest = () => {
|
|
552
|
+
if (!trail.base) return true;
|
|
553
|
+
for (const e of layers().keys()) if (!trail.base.has(e)) return false;
|
|
554
|
+
return true;
|
|
555
|
+
};
|
|
556
|
+
const resetTrail = () => {
|
|
557
|
+
trail.steps = [];
|
|
558
|
+
trail.base = null;
|
|
559
|
+
trail.down = null;
|
|
560
|
+
trail.url = location.pathname + location.search;
|
|
561
|
+
};
|
|
562
|
+
const onNav = () => location.pathname + location.search !== trail.url && resetTrail();
|
|
563
|
+
for (const k of ["pushState", "replaceState"]) {
|
|
564
|
+
const orig = history[k];
|
|
565
|
+
if (typeof orig === "function")
|
|
566
|
+
history[k] = function (...a) {
|
|
567
|
+
const r = orig.apply(this, a);
|
|
568
|
+
onNav();
|
|
569
|
+
return r;
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
addEventListener("popstate", onNav);
|
|
573
|
+
// When parts of the page appear (added, or unhidden): a panel rendered on click counts as opened by it.
|
|
574
|
+
new MutationObserver((list) => {
|
|
575
|
+
const now = Date.now();
|
|
576
|
+
for (const m of list) {
|
|
577
|
+
if (m.type === "childList") {
|
|
578
|
+
for (const n of m.addedNodes) if (n.nodeType === 1 && n !== host) trail.born.set(n, now);
|
|
579
|
+
} else {
|
|
580
|
+
const t = m.target;
|
|
581
|
+
const v = t.getAttribute(m.attributeName);
|
|
582
|
+
if (m.attributeName === "hidden" ? v === null : m.attributeName === "aria-hidden" ? v !== "true" : m.attributeName === "open" ? v !== null : v === "open" || v === "active") trail.born.set(t, now);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}).observe(document.documentElement, { childList: true, subtree: true, attributes: true, attributeFilter: ["hidden", "open", "aria-hidden", "data-state"] });
|
|
586
|
+
|
|
587
|
+
const labelOf = (el) => (el.getAttribute("aria-label") || (el.labels && el.labels[0]?.innerText) || el.getAttribute("placeholder") || el.getAttribute("name") || el.getAttribute("title") || "").replace(/\s+/g, " ").trim().slice(0, 80);
|
|
588
|
+
// A field whose value is never recorded.
|
|
589
|
+
function secret(el) {
|
|
590
|
+
if (trail.secret.has(el)) return true;
|
|
591
|
+
const hint = [el.getAttribute("name"), el.id, el.getAttribute("aria-label"), el.getAttribute("placeholder"), el.getAttribute("data-testid"), el.labels?.[0]?.innerText].filter(Boolean).join(" ");
|
|
592
|
+
const s = el.type === "password" || T.SECRET_AUTOCOMPLETE.test(el.getAttribute("autocomplete") || "") || T.SECRET.test(hint);
|
|
593
|
+
// Once private, always private (a "show password" toggle turns it into a text field).
|
|
594
|
+
if (s) trail.secret.add(el);
|
|
595
|
+
return s;
|
|
596
|
+
}
|
|
597
|
+
const isTextField = (el) => (el.tagName === "INPUT" && /^(text|search|email|url|tel|number|password|)$/i.test(el.getAttribute("type") || "")) || el.tagName === "TEXTAREA";
|
|
598
|
+
const submits = (el) => {
|
|
599
|
+
const b = el.closest?.('button, input[type="submit"], input[type="image"]');
|
|
600
|
+
return Boolean(b && b.form && (b.tagName === "INPUT" || (b.getAttribute("type") || "submit").toLowerCase() === "submit"));
|
|
601
|
+
};
|
|
602
|
+
const IMPLICIT = { A: "link", BUTTON: "button", SUMMARY: "button", SELECT: "combobox", TEXTAREA: "textbox" };
|
|
603
|
+
const roleOf = (el) => {
|
|
604
|
+
const r = el.getAttribute("role");
|
|
605
|
+
if (r) return r.split(/\s+/)[0];
|
|
606
|
+
if (el.tagName === "INPUT") {
|
|
607
|
+
const t = (el.getAttribute("type") || "text").toLowerCase();
|
|
608
|
+
return { checkbox: "checkbox", radio: "radio", button: "button", submit: "button", reset: "button", range: "slider", number: "spinbutton" }[t] ?? (el.hasAttribute("list") ? "combobox" : "textbox");
|
|
609
|
+
}
|
|
610
|
+
return el.tagName === "A" && !el.hasAttribute("href") ? null : (IMPLICIT[el.tagName] ?? null);
|
|
611
|
+
};
|
|
612
|
+
// How the server finds a step's target again: a robust selector (id, test id, aria-label, else a path),
|
|
613
|
+
// and its role and name, tag and words to fall back on.
|
|
614
|
+
function locateStep(el) {
|
|
615
|
+
const tag = el.tagName.toLowerCase();
|
|
616
|
+
const field = /^(INPUT|TEXTAREA|SELECT)$/.test(el.tagName);
|
|
617
|
+
const tidAttr = ["data-testid", "data-test", "data-cy"].find((a) => el.getAttribute(a));
|
|
618
|
+
const aria = el.getAttribute("aria-label");
|
|
619
|
+
let selector;
|
|
620
|
+
if (stableId(el.id) && unique(`#${CSS.escape(el.id)}`)) selector = `#${CSS.escape(el.id)}`;
|
|
621
|
+
else if (tidAttr && unique(`[${tidAttr}="${CSS.escape(el.getAttribute(tidAttr))}"]`)) selector = `[${tidAttr}="${CSS.escape(el.getAttribute(tidAttr))}"]`;
|
|
622
|
+
else if (aria && unique(`${tag}[aria-label="${CSS.escape(aria)}"]`)) selector = `${tag}[aria-label="${CSS.escape(aria)}"]`;
|
|
623
|
+
else selector = selectorOf(el);
|
|
624
|
+
const words = field ? labelOf(el) : (aria || (el.innerText || el.textContent || "").replace(/\s+/g, " ").trim() || el.getAttribute("title") || el.getAttribute("alt") || "").slice(0, 80);
|
|
625
|
+
return { selector, testid: el.getAttribute("data-testid") || undefined, role: roleOf(el) || undefined, name: words || undefined, tag, text: words || undefined };
|
|
626
|
+
}
|
|
627
|
+
function push(step, el, rest) {
|
|
628
|
+
trail.steps.push({ ...step, at: Date.now(), rest, _el: el });
|
|
629
|
+
if (trail.steps.length > 40) trail.steps.shift();
|
|
630
|
+
}
|
|
631
|
+
// The control a click was meant for: the nearest interactive ancestor, or something that looks clickable.
|
|
632
|
+
function actionOf(t) {
|
|
633
|
+
const a = t.closest?.(ACTION);
|
|
634
|
+
if (a && a !== document.body) return a;
|
|
635
|
+
for (let e = t, k = 0; e && e !== document.body && k < 4; e = e.parentElement, k++) if (getComputedStyle(e).cursor === "pointer") return e;
|
|
636
|
+
return null;
|
|
637
|
+
}
|
|
638
|
+
const quiet = (e) => S.on || S.dialog || S.zoom || ours(e) || !e.isTrusted;
|
|
639
|
+
addEventListener(
|
|
640
|
+
"pointerdown",
|
|
641
|
+
(e) => {
|
|
642
|
+
if (quiet(e)) return;
|
|
643
|
+
ensureBase();
|
|
644
|
+
// Some menus open on pointerdown, before the click: whether the page was at rest is read here.
|
|
645
|
+
trail.down = { t: Date.now(), rest: atRest() };
|
|
646
|
+
},
|
|
647
|
+
true,
|
|
648
|
+
);
|
|
649
|
+
addEventListener(
|
|
650
|
+
"click",
|
|
651
|
+
(e) => {
|
|
652
|
+
if (quiet(e)) return;
|
|
653
|
+
ensureBase();
|
|
654
|
+
const rest = trail.down && Date.now() - trail.down.t < 1500 ? trail.down.rest : atRest();
|
|
655
|
+
trail.down = null;
|
|
656
|
+
const el = actionOf(e.target);
|
|
657
|
+
if (!el || submits(el)) return;
|
|
658
|
+
// The click that follows Enter/Space on the same control, or a label's click passed to its field.
|
|
659
|
+
const prev = trail.steps.at(-1);
|
|
660
|
+
if (prev && Date.now() - prev.at < 800 && (prev._el === el ? e.detail === 0 || prev.type === "key" : prev._el?.control === el)) return;
|
|
661
|
+
push({ type: "click", ...locateStep(el) }, el, rest);
|
|
662
|
+
},
|
|
663
|
+
true,
|
|
664
|
+
);
|
|
665
|
+
addEventListener(
|
|
666
|
+
"keydown",
|
|
667
|
+
(e) => {
|
|
668
|
+
if (quiet(e) || S.pop || e.repeat || e.ctrlKey || e.metaKey || e.altKey) return;
|
|
669
|
+
const k = e.key;
|
|
670
|
+
const el = document.activeElement && document.activeElement !== document.body ? document.activeElement : null;
|
|
671
|
+
let ok = k === "Escape" || k === "Tab";
|
|
672
|
+
if (k === "Enter" || k === " ") ok = Boolean(el) && !(el.isContentEditable || (isTextField(el) && (k === " " || el.form))) && !submits(el);
|
|
673
|
+
else if (/^(Arrow(Up|Down|Left|Right)|Home|End)$/.test(k)) ok = Boolean(el?.closest?.(NAV_KEYS));
|
|
674
|
+
if (!ok) return;
|
|
675
|
+
ensureBase();
|
|
676
|
+
push({ type: "key", key: k, ...(k === "Tab" && e.shiftKey ? { shift: true } : {}), ...(el && k !== "Tab" ? locateStep(el) : {}) }, el, atRest());
|
|
677
|
+
},
|
|
678
|
+
true,
|
|
679
|
+
);
|
|
680
|
+
addEventListener(
|
|
681
|
+
"input",
|
|
682
|
+
(e) => {
|
|
683
|
+
const el = e.target;
|
|
684
|
+
if (quiet(e) || !el || !isTextField(el)) return;
|
|
685
|
+
ensureBase();
|
|
686
|
+
const priv = secret(el);
|
|
687
|
+
const prev = trail.steps.at(-1);
|
|
688
|
+
// Typing into the same field is one step that keeps its latest value.
|
|
689
|
+
if (prev?.type === "type" && prev._el === el) {
|
|
690
|
+
if (!priv) prev.value = el.value.slice(0, 200);
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
push({ type: "type", ...locateStep(el), ...(priv ? { redacted: true } : { value: el.value.slice(0, 200) }) }, el, atRest());
|
|
694
|
+
},
|
|
695
|
+
true,
|
|
696
|
+
);
|
|
697
|
+
addEventListener(
|
|
698
|
+
"change",
|
|
699
|
+
(e) => {
|
|
700
|
+
const el = e.target;
|
|
701
|
+
if (quiet(e) || el?.tagName !== "SELECT") return;
|
|
702
|
+
ensureBase();
|
|
703
|
+
const priv = secret(el);
|
|
704
|
+
push({ type: "select", ...locateStep(el), ...(priv ? { redacted: true } : { value: el.value, label: el.selectedOptions?.[0]?.text?.trim().slice(0, 80) }) }, el, atRest());
|
|
705
|
+
},
|
|
706
|
+
true,
|
|
707
|
+
);
|
|
708
|
+
|
|
709
|
+
// A step clicked earlier that still holds over the element: a selected tab whose panel holds it, or an
|
|
710
|
+
// expander (aria-expanded, details) still open around it.
|
|
711
|
+
function holds(s, el) {
|
|
712
|
+
if (s.type !== "click" && s.type !== "key") return false;
|
|
713
|
+
let t = s._el;
|
|
714
|
+
if (!t?.isConnected) {
|
|
715
|
+
try {
|
|
716
|
+
t = document.querySelector(s.selector);
|
|
717
|
+
} catch {
|
|
718
|
+
t = null;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
if (!t) return false;
|
|
722
|
+
const tab = t.closest('[role="tab"]');
|
|
723
|
+
if (tab) {
|
|
724
|
+
if (tab.getAttribute("aria-selected") !== "true" && tab.getAttribute("data-state") !== "active") return false;
|
|
725
|
+
const p = tab.getAttribute("aria-controls") && document.getElementById(tab.getAttribute("aria-controls"));
|
|
726
|
+
return p ? p.contains(el) : Boolean(el.closest('[role="tabpanel"]'));
|
|
727
|
+
}
|
|
728
|
+
const sum = t.closest("summary");
|
|
729
|
+
if (sum) return sum.parentElement?.tagName === "DETAILS" && sum.parentElement.open && sum.parentElement.contains(el);
|
|
730
|
+
const x = t.closest("[aria-expanded]");
|
|
731
|
+
return Boolean(x && x.getAttribute("aria-expanded") === "true" && regionOf(x)?.contains(el));
|
|
732
|
+
}
|
|
733
|
+
// An element that shows only while something is hovered: a CSS rule like ".nav li:hover > ul" (or Tailwind's
|
|
734
|
+
// group-hover) reveals the box it sits in. Read when the user clicks to pin, while the hover still holds.
|
|
735
|
+
function hoverOf(el) {
|
|
736
|
+
let n = 0;
|
|
737
|
+
const reveals = (st) => (st.display && st.display !== "none") || st.visibility === "visible" || (st.opacity && Number(st.opacity) > 0) || st.maxHeight || st.transform || st.clipPath || st.pointerEvents === "auto";
|
|
738
|
+
const walk = (rules) => {
|
|
739
|
+
for (const r of rules) {
|
|
740
|
+
if (++n > 6000) return null;
|
|
741
|
+
if (r.cssRules && !r.selectorText) {
|
|
742
|
+
const x = walk(r.cssRules);
|
|
743
|
+
if (x) return x;
|
|
744
|
+
continue;
|
|
745
|
+
}
|
|
746
|
+
if (!r.selectorText?.includes(":hover") || !reveals(r.style)) continue;
|
|
747
|
+
for (const part of r.selectorText.split(/,(?![^(]*\))/)) {
|
|
748
|
+
const i = part.lastIndexOf(":hover");
|
|
749
|
+
if (i < 0) continue;
|
|
750
|
+
const tail = part.slice(i + 6);
|
|
751
|
+
const own = /^[^\s>+~]*/.exec(tail)[0];
|
|
752
|
+
// :hover on the shown element itself isn't a reveal.
|
|
753
|
+
if (own.length === tail.length) continue;
|
|
754
|
+
const head = part.slice(0, i + 6) + own;
|
|
755
|
+
let shown = null;
|
|
756
|
+
try {
|
|
757
|
+
shown = el.closest(part.trim());
|
|
758
|
+
} catch {}
|
|
759
|
+
if (!shown) continue;
|
|
760
|
+
for (let a = shown; a && a !== document.documentElement; a = a.parentElement) {
|
|
761
|
+
const cands = a === shown ? [] : [a];
|
|
762
|
+
for (let s = a.previousElementSibling; s; s = s.previousElementSibling) cands.push(s);
|
|
763
|
+
for (const c of cands)
|
|
764
|
+
try {
|
|
765
|
+
if (c.matches(head)) return { trigger: c, shown };
|
|
766
|
+
} catch {}
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
return null;
|
|
771
|
+
};
|
|
772
|
+
for (const sh of document.styleSheets) {
|
|
773
|
+
let rules = null;
|
|
774
|
+
try {
|
|
775
|
+
rules = sh.cssRules;
|
|
776
|
+
} catch {}
|
|
777
|
+
const x = rules && walk(rules);
|
|
778
|
+
if (x) {
|
|
779
|
+
// The trigger's own words, without the menu it reveals: "Products", not "Products Analytics Billing".
|
|
780
|
+
const own = [...x.trigger.childNodes].filter((c) => !c.contains(x.shown)).map((c) => c.textContent).join(" ").replace(/\s+/g, " ").trim().slice(0, 80);
|
|
781
|
+
const at = locateStep(x.trigger);
|
|
782
|
+
return { ...x, step: { type: "hover", ...at, ...(own ? { name: own, text: own } : {}), at: Date.now(), rest: false, _el: x.trigger } };
|
|
783
|
+
}
|
|
784
|
+
if (n > 6000) break;
|
|
785
|
+
}
|
|
786
|
+
return null;
|
|
787
|
+
}
|
|
788
|
+
// The steps a pin carries (see trimTrail in trail.mjs), and what the element sits in: the modal, the menu…
|
|
789
|
+
function pinTrail(el, hover) {
|
|
790
|
+
const base = trail.base ?? new Set();
|
|
791
|
+
let layer = null;
|
|
792
|
+
for (const [e, kind] of layers()) {
|
|
793
|
+
if (base.has(e) || !e.contains(el)) continue;
|
|
794
|
+
// A layer the hover reveals is the hover's, not a click's.
|
|
795
|
+
if (hover && (hover.shown.contains(e) || e === hover.shown)) continue;
|
|
796
|
+
if (!layer || layer.el.contains(e)) layer = { el: e, kind };
|
|
797
|
+
}
|
|
798
|
+
const t0 = trail.steps[0]?.at ?? Infinity;
|
|
799
|
+
const revealedAt = [];
|
|
800
|
+
for (let a = el; a && a !== document.body; a = a.parentElement) {
|
|
801
|
+
const t = trail.born.get(a);
|
|
802
|
+
if (t >= t0) revealedAt.push(t);
|
|
803
|
+
}
|
|
804
|
+
let steps = T.trimTrail(
|
|
805
|
+
trail.steps.map((s) => ({ ...s, keep: holds(s, el) })),
|
|
806
|
+
{ inOpen: Boolean(layer), revealedAt },
|
|
807
|
+
);
|
|
808
|
+
let kind = layer?.kind ?? null;
|
|
809
|
+
if (hover && hover.shown.contains(el)) {
|
|
810
|
+
steps = [...steps.filter((s) => s._el !== hover.trigger), hover.step].slice(-T.MAX);
|
|
811
|
+
kind ??= "hover menu";
|
|
812
|
+
}
|
|
813
|
+
if (!kind && steps.length) kind = el.closest('[role="tabpanel"]') ? "tab panel" : null;
|
|
814
|
+
return { steps: steps.map(({ _el, rest, keep, ...s }) => s), layer: kind };
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// palate's own UI never tells the page that focus or a press went outside it, so an open modal or menu
|
|
818
|
+
// (focus traps, close on click outside) stays open while the user writes their pin.
|
|
819
|
+
for (const t of ["focusin", "focusout"])
|
|
820
|
+
addEventListener(
|
|
821
|
+
t,
|
|
822
|
+
(e) => {
|
|
823
|
+
if (e.target === host || e.relatedTarget === host) e.stopImmediatePropagation();
|
|
824
|
+
},
|
|
825
|
+
true,
|
|
826
|
+
);
|
|
827
|
+
for (const t of ["pointerdown", "mousedown", "touchstart", "focusin", "focusout"]) sr.addEventListener(t, (e) => e.stopPropagation());
|
|
828
|
+
|
|
829
|
+
// ---------- Point mode ----------
|
|
830
|
+
function setMode(on) {
|
|
831
|
+
S.on = on;
|
|
832
|
+
ui.fab.classList.toggle("on", on);
|
|
833
|
+
ui.fab.setAttribute("aria-pressed", String(on));
|
|
834
|
+
document.documentElement.classList.toggle("palate-pointing", on);
|
|
835
|
+
if (!on) {
|
|
836
|
+
closeDialog();
|
|
837
|
+
ui.hl.hidden = true;
|
|
838
|
+
ui.hint.hidden = true;
|
|
839
|
+
S.target = S.leaf = null;
|
|
840
|
+
} else {
|
|
841
|
+
closePop();
|
|
842
|
+
ui.hint.hidden = false;
|
|
843
|
+
ui.hint.innerHTML = coarse ? `<b>Tap</b> an element to point at it` : `<b>Click</b> to point at it<span class="ext"><span class="sep"></span><kbd>↑</kbd><kbd>↓</kbd> parent / child</span><span class="sep"></span><kbd>Esc</kbd> stop`;
|
|
844
|
+
}
|
|
845
|
+
loop();
|
|
846
|
+
}
|
|
847
|
+
const cursorStyle = document.createElement("style");
|
|
848
|
+
cursorStyle.textContent = "html.palate-pointing, html.palate-pointing * { cursor: crosshair !important; }";
|
|
849
|
+
|
|
850
|
+
for (const t of ["pointerdown", "mousedown", "pointerup", "mouseup", "dblclick", "contextmenu", "auxclick"])
|
|
851
|
+
window.addEventListener(
|
|
852
|
+
t,
|
|
853
|
+
(e) => {
|
|
854
|
+
if (!S.on || ours(e)) return;
|
|
855
|
+
e.preventDefault();
|
|
856
|
+
e.stopImmediatePropagation();
|
|
857
|
+
},
|
|
858
|
+
true,
|
|
859
|
+
);
|
|
860
|
+
window.addEventListener(
|
|
861
|
+
"click",
|
|
862
|
+
(e) => {
|
|
863
|
+
if (!S.on || ours(e)) return;
|
|
864
|
+
e.preventDefault();
|
|
865
|
+
e.stopImmediatePropagation();
|
|
866
|
+
if (S.dialog) {
|
|
867
|
+
// A click elsewhere moves the dialog (and its draft) to that element.
|
|
868
|
+
const leaf = leafAt(e.clientX, e.clientY);
|
|
869
|
+
if (!leaf) return;
|
|
870
|
+
S.leaf = S.target = leaf;
|
|
871
|
+
openDialog(leaf, { x: e.clientX, y: e.clientY });
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
if (coarse || !S.target) hoverAt(e.clientX, e.clientY);
|
|
875
|
+
if (S.target) openDialog(S.target, { x: e.clientX, y: e.clientY });
|
|
876
|
+
},
|
|
877
|
+
true,
|
|
878
|
+
);
|
|
879
|
+
window.addEventListener(
|
|
880
|
+
"pointermove",
|
|
881
|
+
(e) => {
|
|
882
|
+
if (!S.on || S.dialog || e.pointerType === "touch") return;
|
|
883
|
+
if (ours(e)) return void (ui.hl.hidden = true);
|
|
884
|
+
hoverAt(e.clientX, e.clientY);
|
|
885
|
+
},
|
|
886
|
+
{ capture: true, passive: true },
|
|
887
|
+
);
|
|
888
|
+
window.addEventListener(
|
|
889
|
+
"keydown",
|
|
890
|
+
(e) => {
|
|
891
|
+
if (e.altKey && !e.ctrlKey && !e.metaKey && e.code === "KeyP") {
|
|
892
|
+
e.preventDefault();
|
|
893
|
+
e.stopImmediatePropagation();
|
|
894
|
+
return setMode(!S.on);
|
|
895
|
+
}
|
|
896
|
+
if (e.key === "Escape") {
|
|
897
|
+
if (S.zoom) return void closeZoom();
|
|
898
|
+
if (S.pop) return void closePop();
|
|
899
|
+
if (!S.on) return;
|
|
900
|
+
e.preventDefault();
|
|
901
|
+
e.stopImmediatePropagation();
|
|
902
|
+
if (S.dialog) closeDialog();
|
|
903
|
+
else setMode(false);
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
if (!S.on || ours(e)) return;
|
|
907
|
+
if (e.key === "ArrowUp" || e.key === "ArrowDown") {
|
|
908
|
+
e.preventDefault();
|
|
909
|
+
e.stopImmediatePropagation();
|
|
910
|
+
e.key === "ArrowUp" ? up() : down();
|
|
911
|
+
} else if (e.key === "Enter" && S.target && !S.dialog) {
|
|
912
|
+
e.preventDefault();
|
|
913
|
+
e.stopImmediatePropagation();
|
|
914
|
+
const r = S.target.getBoundingClientRect();
|
|
915
|
+
openDialog(S.target, { x: r.left + 12, y: r.top + 12 });
|
|
916
|
+
}
|
|
917
|
+
},
|
|
918
|
+
true,
|
|
919
|
+
);
|
|
920
|
+
// Keys typed in palate's own UI never reach the page's shortcuts.
|
|
921
|
+
for (const t of ["keydown", "keyup", "keypress"]) sr.addEventListener(t, (e) => e.stopPropagation());
|
|
922
|
+
ui.fab.addEventListener("click", () => setMode(!S.on));
|
|
923
|
+
|
|
924
|
+
// ---------- Dialog ----------
|
|
925
|
+
function renderDialogHead() {
|
|
926
|
+
const d = S.dialog;
|
|
927
|
+
const head = ui.dialog.querySelector(".name");
|
|
928
|
+
if (head) head.innerHTML = nameHTML(describe(d.el));
|
|
929
|
+
renderViewport();
|
|
930
|
+
const p = d.el.parentElement;
|
|
931
|
+
ui.dialog.querySelector('[data-a="up"]').disabled = !p || p === document.documentElement;
|
|
932
|
+
// The steps that opened the state this element sits in: "+2 steps to open", so the user knows.
|
|
933
|
+
d.trail = pinTrail(d.el, d.hover);
|
|
934
|
+
const k = d.trail.steps.length;
|
|
935
|
+
ui.dialog.querySelector(".note").textContent = `Pin ${d.n} · queued${k ? "" : " for the agent"}`;
|
|
936
|
+
const steps = ui.dialog.querySelector(".steps");
|
|
937
|
+
steps.hidden = !k;
|
|
938
|
+
steps.textContent = `+${k} step${k > 1 ? "s" : ""} to open`;
|
|
939
|
+
steps.title = k ? `palate reopens this for every check: ${d.trail.steps.map(T.stepShort).join(" → ")}${d.trail.layer ? ` → the ${d.trail.layer}` : ""}` : "";
|
|
940
|
+
}
|
|
941
|
+
// Which width the user is pinning at, small and muted in the dialog's header.
|
|
942
|
+
function renderViewport() {
|
|
943
|
+
const vp = ui.dialog.querySelector(".vp");
|
|
944
|
+
if (!vp) return;
|
|
945
|
+
vp.textContent = `${innerWidth} × ${innerHeight}`;
|
|
946
|
+
vp.title = `You're pinning at ${innerWidth} × ${innerHeight} (${wname(innerWidth)}). The fix is also checked at ${otherOf(innerWidth)} (${wname(otherOf(innerWidth))}).`;
|
|
947
|
+
}
|
|
948
|
+
function openDialog(el, at) {
|
|
949
|
+
closePop();
|
|
950
|
+
const draft = S.dialog ? { text: ui.dialog.querySelector("textarea").value, chips: [...S.dialog.chips] } : { text: "", chips: [] };
|
|
951
|
+
const n = (S.requests.reduce((a, r) => Math.max(a, r.n), 0) || 0) + 1;
|
|
952
|
+
// Read now, while the pointer still holds any hover that shows the element.
|
|
953
|
+
S.dialog = { el, at, chips: new Set(draft.chips), n, hover: hoverOf(el) };
|
|
954
|
+
ui.hint.hidden = true;
|
|
955
|
+
const mod = isMac ? "⌘" : "Ctrl";
|
|
956
|
+
ui.dialog.innerHTML = `
|
|
957
|
+
<div class="dh"><div class="name"></div><span class="vp"></span>
|
|
958
|
+
<button class="ib" type="button" data-a="up" title="Select the parent (↑)" aria-label="Select the parent element">${ICON.up}</button>
|
|
959
|
+
<button class="ib" type="button" data-a="down" title="Select a child (↓)" aria-label="Select a child element">${ICON.down}</button>
|
|
960
|
+
<button class="ib" type="button" data-a="close" title="Close (Esc)" aria-label="Close">${ICON.x}</button></div>
|
|
961
|
+
<textarea rows="2" placeholder="What should change?" aria-label="What should change?"></textarea>
|
|
962
|
+
<div class="chips" role="group" aria-label="Quick requests">${CHIPS.map((c) => `<button class="chip" type="button" aria-pressed="${S.dialog.chips.has(c)}">${esc(c)}</button>`).join("")}</div>
|
|
963
|
+
<div class="df"><span class="note">Pin ${n} · queued for the agent</span><span class="steps" hidden></span><button class="send" type="button" disabled>Send <kbd>${mod} ↵</kbd></button></div>`;
|
|
964
|
+
ui.dialog.hidden = false;
|
|
965
|
+
const ta = ui.dialog.querySelector("textarea");
|
|
966
|
+
ta.value = draft.text;
|
|
967
|
+
renderDialogHead();
|
|
968
|
+
draw(el, { bands: false, frozen: true });
|
|
969
|
+
placeDialog();
|
|
970
|
+
const sendBtn = ui.dialog.querySelector(".send");
|
|
971
|
+
const sync = () => {
|
|
972
|
+
sendBtn.disabled = S.sending || (!ta.value.trim() && !S.dialog.chips.size);
|
|
973
|
+
ta.style.height = "auto";
|
|
974
|
+
ta.style.height = `${Math.min(ta.scrollHeight, 160)}px`;
|
|
975
|
+
};
|
|
976
|
+
ta.addEventListener("input", sync);
|
|
977
|
+
ta.addEventListener("keydown", (e) => {
|
|
978
|
+
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
|
979
|
+
e.preventDefault();
|
|
980
|
+
send();
|
|
981
|
+
} else if (e.key === "Escape") {
|
|
982
|
+
e.preventDefault();
|
|
983
|
+
closeDialog();
|
|
984
|
+
}
|
|
985
|
+
});
|
|
986
|
+
ui.dialog.querySelectorAll(".chip").forEach((b) =>
|
|
987
|
+
b.addEventListener("click", () => {
|
|
988
|
+
const c = b.textContent;
|
|
989
|
+
S.dialog.chips.has(c) ? S.dialog.chips.delete(c) : S.dialog.chips.add(c);
|
|
990
|
+
b.setAttribute("aria-pressed", String(S.dialog.chips.has(c)));
|
|
991
|
+
sync();
|
|
992
|
+
ta.focus();
|
|
993
|
+
}),
|
|
994
|
+
);
|
|
995
|
+
ui.dialog.querySelector('[data-a="up"]').addEventListener("click", up);
|
|
996
|
+
ui.dialog.querySelector('[data-a="down"]').addEventListener("click", down);
|
|
997
|
+
ui.dialog.querySelector('[data-a="close"]').addEventListener("click", closeDialog);
|
|
998
|
+
sendBtn.addEventListener("click", send);
|
|
999
|
+
sync();
|
|
1000
|
+
requestAnimationFrame(() => ta.focus({ preventScroll: true }));
|
|
1001
|
+
loop();
|
|
1002
|
+
}
|
|
1003
|
+
function placeDialog() {
|
|
1004
|
+
if (!S.dialog || innerWidth <= 520) return;
|
|
1005
|
+
const r = S.dialog.el.getBoundingClientRect();
|
|
1006
|
+
const w = ui.dialog.offsetWidth || 360;
|
|
1007
|
+
const h = ui.dialog.offsetHeight || 200;
|
|
1008
|
+
const big = r.height > innerHeight * 0.55 || r.width > innerWidth * 0.8;
|
|
1009
|
+
let x;
|
|
1010
|
+
let y;
|
|
1011
|
+
if (big) {
|
|
1012
|
+
// Sections: open by the pointer, not at an edge far away.
|
|
1013
|
+
x = S.dialog.at.x - 24;
|
|
1014
|
+
y = S.dialog.at.y + 16;
|
|
1015
|
+
if (y + h > innerHeight - 12) y = S.dialog.at.y - h - 16;
|
|
1016
|
+
} else {
|
|
1017
|
+
x = r.left;
|
|
1018
|
+
y = r.bottom + 10;
|
|
1019
|
+
if (y + h > innerHeight - 12) y = r.top - h - 10;
|
|
1020
|
+
if (y < 12) y = Math.min(innerHeight - h - 12, Math.max(12, r.top + 12));
|
|
1021
|
+
}
|
|
1022
|
+
x = Math.min(Math.max(12, x), innerWidth - w - 12);
|
|
1023
|
+
y = Math.min(Math.max(12, y), innerHeight - h - 12);
|
|
1024
|
+
ui.dialog.style.left = `${x}px`;
|
|
1025
|
+
ui.dialog.style.top = `${y}px`;
|
|
1026
|
+
ui.dialog.style.setProperty("--ox", `${Math.min(Math.max(0, S.dialog.at.x - x), w)}px`);
|
|
1027
|
+
ui.dialog.style.setProperty("--oy", `${y < r.top ? h : 0}px`);
|
|
1028
|
+
}
|
|
1029
|
+
function closeDialog() {
|
|
1030
|
+
if (!S.dialog) return;
|
|
1031
|
+
S.dialog = null;
|
|
1032
|
+
ui.dialog.hidden = true;
|
|
1033
|
+
ui.dialog.innerHTML = "";
|
|
1034
|
+
ui.hl.hidden = true;
|
|
1035
|
+
if (S.on) ui.hint.hidden = false;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
function payload(el) {
|
|
1039
|
+
const r = el.getBoundingClientRect();
|
|
1040
|
+
return {
|
|
1041
|
+
route: location.pathname + location.search,
|
|
1042
|
+
href: location.href,
|
|
1043
|
+
title: document.title,
|
|
1044
|
+
selector: selectorOf(el),
|
|
1045
|
+
path: domPath(el),
|
|
1046
|
+
tag: el.tagName.toLowerCase(),
|
|
1047
|
+
id: el.id || null,
|
|
1048
|
+
className: typeof el.className === "string" ? el.className : el.getAttribute("class") || "",
|
|
1049
|
+
label: describe(el),
|
|
1050
|
+
text: (el.innerText || el.textContent || "").replace(/\s+/g, " ").trim().slice(0, 600),
|
|
1051
|
+
textNodes: textNodes(el),
|
|
1052
|
+
outerHTML: trimHTML(el),
|
|
1053
|
+
styles: keyStyles(el),
|
|
1054
|
+
framework: framework(el),
|
|
1055
|
+
box: { x: Math.round(r.left), y: Math.round(r.top), width: Math.round(r.width), height: Math.round(r.height) },
|
|
1056
|
+
viewport: { width: innerWidth, height: innerHeight },
|
|
1057
|
+
dpr: devicePixelRatio || 1,
|
|
1058
|
+
scroll: { x: Math.round(scrollX), y: Math.round(scrollY) },
|
|
1059
|
+
colorScheme: matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light",
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
async function send() {
|
|
1063
|
+
const d = S.dialog;
|
|
1064
|
+
if (!d || S.sending) return;
|
|
1065
|
+
const ta = ui.dialog.querySelector("textarea");
|
|
1066
|
+
const prompt = ta.value.trim();
|
|
1067
|
+
const chips = CHIPS.filter((c) => d.chips.has(c));
|
|
1068
|
+
if (!prompt && !chips.length) return;
|
|
1069
|
+
S.sending = true;
|
|
1070
|
+
const btn = ui.dialog.querySelector(".send");
|
|
1071
|
+
btn.disabled = true;
|
|
1072
|
+
try {
|
|
1073
|
+
const res = await fetch(`${API}/request`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ ...payload(d.el), prompt, chips, trail: d.trail?.steps ?? [], layer: d.trail?.layer ?? null }) });
|
|
1074
|
+
if (!res.ok) throw new Error(await res.text());
|
|
1075
|
+
const { agent, ...r } = await res.json();
|
|
1076
|
+
S.els.set(r.id, d.el);
|
|
1077
|
+
if (agent) setAgent(agent);
|
|
1078
|
+
upsert(r);
|
|
1079
|
+
closeDialog();
|
|
1080
|
+
// Nothing is lost without an agent: the pin waits in the queue and the next agent takes it in order.
|
|
1081
|
+
if (noAgent()) toast(`<b>Pin ${r.n} queued, but your agent isn't listening.</b> ${START_AGENT}`, "warn", 9000);
|
|
1082
|
+
else toast(`<b>Pin ${r.n} queued.</b> Point at another element, or ${coarse ? "tap the button" : "Esc"} when you're done.`);
|
|
1083
|
+
} catch (e) {
|
|
1084
|
+
console.warn("palate point:", e);
|
|
1085
|
+
toast(`<b>Couldn't send.</b> Is palate point still running?`);
|
|
1086
|
+
btn.disabled = false;
|
|
1087
|
+
} finally {
|
|
1088
|
+
S.sending = false;
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
// ---------- Pins ----------
|
|
1093
|
+
const here = (r) => r.route.split("?")[0] === location.pathname && r.status !== "canceled";
|
|
1094
|
+
function elementOf(r) {
|
|
1095
|
+
let el = S.els.get(r.id);
|
|
1096
|
+
if (el && el.isConnected) return el;
|
|
1097
|
+
try {
|
|
1098
|
+
el = document.querySelector(r.selector);
|
|
1099
|
+
} catch {
|
|
1100
|
+
el = null;
|
|
1101
|
+
}
|
|
1102
|
+
if (el) S.els.set(r.id, el);
|
|
1103
|
+
return el;
|
|
1104
|
+
}
|
|
1105
|
+
function upsert(r) {
|
|
1106
|
+
const i = S.requests.findIndex((x) => x.id === r.id);
|
|
1107
|
+
const before = i >= 0 ? S.requests[i] : null;
|
|
1108
|
+
if (i >= 0) S.requests[i] = r;
|
|
1109
|
+
else S.requests.push(r);
|
|
1110
|
+
if (before && before.status !== "done" && r.status === "done") {
|
|
1111
|
+
S.els.delete(r.id);
|
|
1112
|
+
if (here(r)) toast(`<b>Pin ${r.n} done.</b> ${esc(r.summary || "")}`, true);
|
|
1113
|
+
}
|
|
1114
|
+
if (before && before.status !== "failed" && r.status === "failed") toast(`<b>Pin ${r.n} failed.</b> Open the pin to see why, or retry.`, "warn", 6000);
|
|
1115
|
+
if (S.pop?.id === r.id) renderPop();
|
|
1116
|
+
renderPins();
|
|
1117
|
+
renderAgent();
|
|
1118
|
+
}
|
|
1119
|
+
function renderPins() {
|
|
1120
|
+
const list = S.requests.filter(here);
|
|
1121
|
+
const open = S.requests.filter((r) => r.status === "queued" || r.status === "working").length;
|
|
1122
|
+
ui.badge.hidden = !open;
|
|
1123
|
+
ui.badge.textContent = String(open);
|
|
1124
|
+
for (const p of [...ui.pins.children]) if (!list.some((r) => r.id === p.dataset.id)) p.remove();
|
|
1125
|
+
for (const r of list) {
|
|
1126
|
+
let p = ui.pins.querySelector(`[data-id="${CSS.escape(r.id)}"]`);
|
|
1127
|
+
if (!p) {
|
|
1128
|
+
p = document.createElement("button");
|
|
1129
|
+
p.type = "button";
|
|
1130
|
+
p.className = "pin";
|
|
1131
|
+
p.dataset.id = r.id;
|
|
1132
|
+
p.addEventListener("click", (e) => {
|
|
1133
|
+
e.stopPropagation();
|
|
1134
|
+
S.pop?.id === r.id ? closePop() : openPop(r.id);
|
|
1135
|
+
});
|
|
1136
|
+
p.addEventListener("pointerenter", () => ghost(r.id));
|
|
1137
|
+
p.addEventListener("pointerleave", () => ghost(null));
|
|
1138
|
+
ui.pins.appendChild(p);
|
|
1139
|
+
}
|
|
1140
|
+
if (p.dataset.status !== r.status) {
|
|
1141
|
+
p.dataset.status = r.status;
|
|
1142
|
+
p.className = `pin ${r.status}`;
|
|
1143
|
+
p.innerHTML = r.status === "done" ? ICON.check : String(r.n);
|
|
1144
|
+
}
|
|
1145
|
+
// The other width broke: a small amber dot on the pin.
|
|
1146
|
+
const broke = r.other?.verdict === "broke" && (r.status === "done" || r.status === "working");
|
|
1147
|
+
p.classList.toggle("broke", broke);
|
|
1148
|
+
p.setAttribute("aria-label", `Pin ${r.n}: ${STATUS[r.status] ?? r.status}${broke ? `, broke at ${r.other.width}` : ""}`);
|
|
1149
|
+
p.title = `${r.n}. ${r.prompt || r.chips.join(", ")} (${r.status === "failed" ? "Failed: retry" : (STATUS[r.status] ?? r.status)})${broke ? ` · Broke at ${r.other.width}` : ""}`;
|
|
1150
|
+
}
|
|
1151
|
+
loop();
|
|
1152
|
+
}
|
|
1153
|
+
// Where a pin sits: the element's top-right corner, or where the element was when it can't be found.
|
|
1154
|
+
function anchorOf(r) {
|
|
1155
|
+
const el = elementOf(r);
|
|
1156
|
+
if (visible(el)) return el.getBoundingClientRect();
|
|
1157
|
+
const x = r.box.x + r.scroll.x - scrollX;
|
|
1158
|
+
const y = r.box.y + r.scroll.y - scrollY;
|
|
1159
|
+
return { left: x, top: y, right: x + r.box.width, bottom: y + r.box.height, width: r.box.width, height: r.box.height };
|
|
1160
|
+
}
|
|
1161
|
+
function placePins() {
|
|
1162
|
+
const used = new Map();
|
|
1163
|
+
for (const p of ui.pins.children) {
|
|
1164
|
+
const r = S.requests.find((x) => x.id === p.dataset.id);
|
|
1165
|
+
if (!r) continue;
|
|
1166
|
+
const a = anchorOf(r);
|
|
1167
|
+
const key = `${Math.round(a.right)}|${Math.round(a.top)}`;
|
|
1168
|
+
const k = used.get(key) ?? 0;
|
|
1169
|
+
used.set(key, k + 1);
|
|
1170
|
+
const off = a.bottom < 0 || a.top > innerHeight || a.right < 0 || a.left > innerWidth;
|
|
1171
|
+
p.style.display = off ? "none" : "grid";
|
|
1172
|
+
const x = Math.min(Math.max(4, a.right - 12 - k * 28), innerWidth - 28);
|
|
1173
|
+
const y = Math.min(Math.max(4, a.top - 12), innerHeight - 28);
|
|
1174
|
+
const t = `translate(${x}px, ${y}px)`;
|
|
1175
|
+
p.style.transform = t;
|
|
1176
|
+
p.style.setProperty("--t", t);
|
|
1177
|
+
}
|
|
1178
|
+
if (S.ghost) {
|
|
1179
|
+
const r = S.requests.find((x) => x.id === S.ghost.id);
|
|
1180
|
+
const a = r && anchorOf(r);
|
|
1181
|
+
if (a) Object.assign(S.ghost.el.style, { transform: `translate(${a.left}px, ${a.top}px)`, width: `${a.width}px`, height: `${a.height}px` });
|
|
1182
|
+
}
|
|
1183
|
+
if (S.pop) placePop();
|
|
1184
|
+
}
|
|
1185
|
+
function ghost(id) {
|
|
1186
|
+
S.ghost?.el.remove();
|
|
1187
|
+
S.ghost = null;
|
|
1188
|
+
if (!id || S.on) return;
|
|
1189
|
+
const el = document.createElement("div");
|
|
1190
|
+
el.className = "ghost";
|
|
1191
|
+
ui.pins.prepend(el);
|
|
1192
|
+
S.ghost = { id, el };
|
|
1193
|
+
placePins();
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
// ---------- Pin popover ----------
|
|
1197
|
+
function openPop(id) {
|
|
1198
|
+
if (S.dialog) closeDialog();
|
|
1199
|
+
S.pop = { id, view: "after" };
|
|
1200
|
+
ui.pop.hidden = false;
|
|
1201
|
+
renderPop();
|
|
1202
|
+
placePop();
|
|
1203
|
+
}
|
|
1204
|
+
function closePop() {
|
|
1205
|
+
S.pop = null;
|
|
1206
|
+
ui.pop.hidden = true;
|
|
1207
|
+
ui.pop.innerHTML = "";
|
|
1208
|
+
}
|
|
1209
|
+
function renderPop() {
|
|
1210
|
+
const r = S.requests.find((x) => x.id === S.pop?.id);
|
|
1211
|
+
if (!r) return closePop();
|
|
1212
|
+
const v = r.doneAt || r.created;
|
|
1213
|
+
const img = (k) => `/__palate/img/${encodeURIComponent(r.id)}/${k}.png?v=${encodeURIComponent(v)}`;
|
|
1214
|
+
const st = r.status === "working" ? `<span class="spin" aria-hidden="true"></span>` : `<span class="d ${r.status}"></span>`;
|
|
1215
|
+
const view = S.pop.view;
|
|
1216
|
+
// A request about another width ("on mobile") has crops at that width too; they show first.
|
|
1217
|
+
const alt = r.alt?.width ? r.alt : null;
|
|
1218
|
+
const size = alt && S.pop.size !== "main" ? "alt" : "main";
|
|
1219
|
+
const name = (k) => (size === "alt" ? `${k}-${alt.width}` : k);
|
|
1220
|
+
const hasBefore = size === "alt" ? alt.before : r.hasCrop;
|
|
1221
|
+
const gone = r.status === "done" && Boolean(size === "alt" ? alt.removed : r.removed);
|
|
1222
|
+
const hasAfter = r.status === "done" && (size === "alt" ? alt.after : r.hasAfter);
|
|
1223
|
+
const compare = hasAfter || gone;
|
|
1224
|
+
const goneHTML = `<div class="gone"><b>Removed</b><span>The element is gone, as asked.</span></div>`;
|
|
1225
|
+
const sizes = alt ? `<div class="seg" role="group" aria-label="Width"><button type="button" data-s="alt" aria-pressed="${size === "alt"}">${alt.width} wide</button><button type="button" data-s="main" aria-pressed="${size === "main"}">${r.viewport?.width ?? "Desktop"} wide</button></div>` : "";
|
|
1226
|
+
// Before and after at both widths, once done: rows for desktop and phone (widest first), the pinned one marked.
|
|
1227
|
+
const o = r.other?.width ? r.other : null;
|
|
1228
|
+
const vw = r.viewport?.width;
|
|
1229
|
+
const rows =
|
|
1230
|
+
r.status === "done" && o?.after && vw
|
|
1231
|
+
? [
|
|
1232
|
+
{ w: vw, pinned: true, before: r.hasCrop ? img("crop") : null, after: r.removed ? "gone" : r.hasAfter ? img("after") : null, bs: r.reopen?.before === STUCK ? STUCK : null, as: r.reopen?.after === STUCK ? STUCK : null },
|
|
1233
|
+
{ w: o.width, pinned: false, before: o.before?.has ? img(`crop-${o.width}`) : null, after: o.after.removed ? "gone" : o.after.has ? img(`after-${o.width}`) : null, bs: o.before?.reopen === STUCK ? STUCK : o.before?.state, as: o.after.reopen === STUCK ? STUCK : o.after.state },
|
|
1234
|
+
].sort((a, b) => b.w - a.w)
|
|
1235
|
+
: null;
|
|
1236
|
+
const where = (s) => (s === STUCK ? `<span class="cap">Couldn't reopen</span>` : s === "hidden" ? `<span class="cap">Hidden here</span>` : s === "missing" ? `<span class="cap">Not on the page</span>` : "");
|
|
1237
|
+
const cell = (src, i, state, label) =>
|
|
1238
|
+
src === "gone" ? `<button class="cell empty" type="button" data-z="${i}" aria-label="${label}: removed">Removed</button>` : src ? `<button class="cell" type="button" data-z="${i}" aria-label="${label}, enlarge"><img alt="" src="${src}">${where(state)}</button>` : `<div class="cell empty">No crop</div>`;
|
|
1239
|
+
const grid = rows
|
|
1240
|
+
? `<div class="bx"><div class="cols"><span>Before</span><span>After</span></div>${rows
|
|
1241
|
+
.map((x, i) => {
|
|
1242
|
+
const name = wname(x.w);
|
|
1243
|
+
const tag = x.pinned ? "pinned" : o.verdict && o.verdict !== "unknown" ? o.verdict : "";
|
|
1244
|
+
return `<div class="rl"><b>${name[0].toUpperCase()}${name.slice(1)}</b><span>${x.w}</span><span class="tg ${x.pinned ? "" : esc(o.verdict ?? "")}">${tag}</span></div><div class="pair2">${cell(x.before, i, x.bs, `${name} before`)}${cell(x.after, i, x.as, `${name} after`)}</div>`;
|
|
1245
|
+
})
|
|
1246
|
+
.join("")}</div>`
|
|
1247
|
+
: "";
|
|
1248
|
+
// The other width's verdict: a calm warning when it broke, a quiet note otherwise.
|
|
1249
|
+
const verdict =
|
|
1250
|
+
o?.verdict === "broke" && (r.status === "done" || r.status === "working")
|
|
1251
|
+
? `<p class="warnbox" role="note"><span class="wd" aria-hidden="true"></span><span>${r.status === "working" ? "The agent's last check: " : ""}<b>Broke at ${o.width}:</b> ${esc((o.problems ?? []).join("; "))}.</span></p>`
|
|
1252
|
+
: r.status === "done" && o?.verdict === "changed"
|
|
1253
|
+
? `<p class="onote">Changed at ${o.width}: ${esc((o.changes ?? []).join("; "))}.</p>`
|
|
1254
|
+
: r.status === "done" && o?.verdict === "unchanged"
|
|
1255
|
+
? `<p class="onote">Unchanged at ${o.width}.</p>`
|
|
1256
|
+
: "";
|
|
1257
|
+
// The state this pin sits in, and whether palate could open it again for the checks.
|
|
1258
|
+
const opened = r.trail?.length ? `<p class="onote">Opened by: ${esc(r.trail.map(T.stepShort).join(" → "))}${r.layer ? ` → the ${esc(r.layer)}` : ""}</p>` : "";
|
|
1259
|
+
const stuck = r.reopen?.fail ? `<p class="onote stuck">Couldn't reopen this state for the check${r.reopen.fail.width ? ` at ${r.reopen.fail.width}` : ""}: ${esc(r.reopen.fail.why)}</p>` : "";
|
|
1260
|
+
const vpc = vw ? `<span class="vpc" title="Pinned at ${vw} × ${r.viewport.height} (${wname(vw)})${o ? `; checked at ${o.width} too` : ""}">${vw} × ${r.viewport.height}</span>` : "";
|
|
1261
|
+
ui.pop.setAttribute("aria-label", `Pin ${r.n}`);
|
|
1262
|
+
ui.pop.innerHTML = `
|
|
1263
|
+
<div class="ph"><div class="st">${st}<b>Pin ${r.n}</b><span>${STATUS[r.status] ?? r.status}</span></div>${vpc}<button class="ib" type="button" data-a="close" aria-label="Close">${ICON.x}</button></div>
|
|
1264
|
+
<div class="pb">
|
|
1265
|
+
${r.prompt ? `<p class="ask">${esc(r.prompt)}</p>` : ""}
|
|
1266
|
+
${r.chips.length ? `<div class="tags">${r.chips.map((c) => `<span>${esc(c)}</span>`).join("")}</div>` : ""}
|
|
1267
|
+
${opened}
|
|
1268
|
+
${r.status === "done" && r.summary ? `<p class="sum"><b>Agent:</b> ${esc(r.summary)}</p>` : ""}
|
|
1269
|
+
${verdict}
|
|
1270
|
+
${stuck}
|
|
1271
|
+
${r.status === "working" ? `<p class="sum">The agent is changing this element. The page reloads by itself when it saves.</p>` : ""}
|
|
1272
|
+
${r.status === "queued" ? `<p class="sum">${noAgent() ? `Queued${r.position ? ` (${r.position} in line)` : ""}. No agent is listening: ${START_AGENT}` : `Waiting for the agent${r.position ? ` (${r.position} in line)` : ""}.`}</p>` : ""}
|
|
1273
|
+
${r.status === "failed" ? `<p class="sum">The agent stopped without finishing this one. Its last lines:</p>${r.error ? `<pre class="err mono">${esc(r.error)}</pre>` : ""}` : ""}
|
|
1274
|
+
${
|
|
1275
|
+
grid
|
|
1276
|
+
? grid
|
|
1277
|
+
: compare
|
|
1278
|
+
? `<div class="ba"><div class="segs"><div class="seg" role="group" aria-label="Compare"><button type="button" data-v="before" aria-pressed="${view === "before"}">Before</button><button type="button" data-v="after" aria-pressed="${view === "after"}">After</button></div>${sizes}</div>
|
|
1279
|
+
${view === "after" && gone ? goneHTML : `<button class="shot" type="button" title="Hold to see before · click to enlarge"><img alt="${view === "after" ? "After" : "Before"} the change" src="${img(name(view === "after" ? "after" : "crop"))}"></button>${gone ? "" : `<span class="hold">Hold the picture to see before</span>`}`}</div>`
|
|
1280
|
+
: hasBefore
|
|
1281
|
+
? `<div class="ba">${sizes ? `<div class="segs">${sizes}</div>` : ""}<button class="shot" type="button" title="Click to enlarge"><img alt="The element when you pointed at it" src="${img(name("crop"))}"><span class="cap">Before</span></button></div>`
|
|
1282
|
+
: sizes ? `<div class="ba"><div class="segs">${sizes}</div><span class="hold">No crop at this width.</span></div>` : ""
|
|
1283
|
+
}
|
|
1284
|
+
${r.status === "queued" ? `<div class="pa"><button class="btn" type="button" data-a="cancel">Remove pin</button></div>` : ""}
|
|
1285
|
+
${r.status === "failed" ? `<div class="pa"><button class="btn" type="button" data-a="retry">Retry</button><button class="btn" type="button" data-a="cancel">Remove pin</button></div>` : ""}
|
|
1286
|
+
${r.status === "done" ? `<div class="pa"><button class="btn" type="button" data-a="again">Ask again</button></div>` : ""}
|
|
1287
|
+
</div>`;
|
|
1288
|
+
ui.pop.querySelector('[data-a="close"]').addEventListener("click", closePop);
|
|
1289
|
+
ui.pop.querySelectorAll("[data-v]").forEach((b) => b.addEventListener("click", () => ((S.pop.view = b.dataset.v), renderPop())));
|
|
1290
|
+
ui.pop.querySelectorAll("[data-s]").forEach((b) => b.addEventListener("click", () => ((S.pop.size = b.dataset.s), renderPop())));
|
|
1291
|
+
ui.pop.querySelectorAll("[data-z]").forEach((b) =>
|
|
1292
|
+
b.addEventListener("click", () => {
|
|
1293
|
+
const x = rows[Number(b.dataset.z)];
|
|
1294
|
+
const at = `${x.w}${x.pinned ? ", pinned" : ""}`;
|
|
1295
|
+
zoom([
|
|
1296
|
+
[`Before · ${at}`, x.before, "No crop"],
|
|
1297
|
+
[`After · ${at}`, x.after === "gone" ? null : x.after, x.after === "gone" ? null : "No crop"],
|
|
1298
|
+
]);
|
|
1299
|
+
}),
|
|
1300
|
+
);
|
|
1301
|
+
const shot = ui.pop.querySelector(".shot");
|
|
1302
|
+
if (shot) {
|
|
1303
|
+
const im = shot.querySelector("img");
|
|
1304
|
+
const seg = ui.pop.querySelectorAll("[data-v]");
|
|
1305
|
+
const show = (v) => seg.forEach((b) => b.setAttribute("aria-pressed", String(b.dataset.v === v)));
|
|
1306
|
+
let held = false;
|
|
1307
|
+
let timer = 0;
|
|
1308
|
+
if (hasAfter && !gone) {
|
|
1309
|
+
shot.addEventListener("pointerdown", () => {
|
|
1310
|
+
timer = setTimeout(() => {
|
|
1311
|
+
held = true;
|
|
1312
|
+
im.src = img(name("crop"));
|
|
1313
|
+
show("before");
|
|
1314
|
+
}, 160);
|
|
1315
|
+
});
|
|
1316
|
+
const release = () => {
|
|
1317
|
+
clearTimeout(timer);
|
|
1318
|
+
if (!held) return;
|
|
1319
|
+
setTimeout(() => (held = false), 0);
|
|
1320
|
+
im.src = img(name(view === "after" ? "after" : "crop"));
|
|
1321
|
+
show(view);
|
|
1322
|
+
};
|
|
1323
|
+
shot.addEventListener("pointerup", release);
|
|
1324
|
+
shot.addEventListener("pointerleave", release);
|
|
1325
|
+
}
|
|
1326
|
+
shot.addEventListener("click", () => {
|
|
1327
|
+
if (held) return;
|
|
1328
|
+
zoom(compare ? [["Before", img(name("crop"))], ["After", gone ? null : img(name("after"))]] : [["Before", img(name("crop"))]]);
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
1331
|
+
ui.pop.querySelector('[data-a="cancel"]')?.addEventListener("click", async () => {
|
|
1332
|
+
await fetch(`${API}/cancel`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ id: r.id }) }).catch(() => {});
|
|
1333
|
+
closePop();
|
|
1334
|
+
});
|
|
1335
|
+
ui.pop.querySelector('[data-a="retry"]')?.addEventListener("click", async () => {
|
|
1336
|
+
const res = await fetch(`${API}/retry`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ id: r.id }) }).catch(() => null);
|
|
1337
|
+
const j = await res?.json().catch(() => null);
|
|
1338
|
+
if (j?.agent) setAgent(j.agent);
|
|
1339
|
+
if (noAgent()) toast(`<b>Pin ${r.n} is queued again, but your agent isn't listening.</b> ${START_AGENT}`, "warn", 9000);
|
|
1340
|
+
});
|
|
1341
|
+
ui.pop.querySelector('[data-a="again"]')?.addEventListener("click", () => {
|
|
1342
|
+
const el = elementOf(r);
|
|
1343
|
+
closePop();
|
|
1344
|
+
if (!el) return toast("<b>That element has changed.</b> Point at it again.");
|
|
1345
|
+
setMode(true);
|
|
1346
|
+
S.leaf = S.target = el;
|
|
1347
|
+
const a = el.getBoundingClientRect();
|
|
1348
|
+
openDialog(el, { x: a.left + 12, y: a.top + 12 });
|
|
1349
|
+
});
|
|
1350
|
+
placePop();
|
|
1351
|
+
}
|
|
1352
|
+
function placePop() {
|
|
1353
|
+
if (!S.pop || innerWidth <= 520) return;
|
|
1354
|
+
const p = ui.pins.querySelector(`[data-id="${CSS.escape(S.pop.id)}"]`);
|
|
1355
|
+
if (!p) return;
|
|
1356
|
+
const pr = p.getBoundingClientRect();
|
|
1357
|
+
const w = ui.pop.offsetWidth || 300;
|
|
1358
|
+
const h = ui.pop.offsetHeight || 160;
|
|
1359
|
+
// Beside the pin when there's room, otherwise under it (right edges aligned), so it covers less of the element.
|
|
1360
|
+
let x = pr.right + 8;
|
|
1361
|
+
let y = pr.top - 8;
|
|
1362
|
+
if (x + w > innerWidth - 12) {
|
|
1363
|
+
x = pr.right - w;
|
|
1364
|
+
y = pr.bottom + 8;
|
|
1365
|
+
if (y + h > innerHeight - 12) y = pr.top - h - 8;
|
|
1366
|
+
}
|
|
1367
|
+
x = Math.min(Math.max(12, x), innerWidth - w - 12);
|
|
1368
|
+
y = Math.min(Math.max(12, y), innerHeight - h - 12);
|
|
1369
|
+
ui.pop.style.left = `${x}px`;
|
|
1370
|
+
ui.pop.style.top = `${y}px`;
|
|
1371
|
+
}
|
|
1372
|
+
function zoom(images) {
|
|
1373
|
+
closeZoom();
|
|
1374
|
+
const z = document.createElement("div");
|
|
1375
|
+
z.className = "zoom";
|
|
1376
|
+
z.setAttribute("role", "dialog");
|
|
1377
|
+
z.setAttribute("aria-label", "Before and after");
|
|
1378
|
+
// [caption, src, text when there's no picture (default: removed, as asked)]
|
|
1379
|
+
z.innerHTML = `<div class="pair">${images.map(([c, src, none]) => `<figure><figcaption>${c}</figcaption>${src ? `<img src="${src}" alt="${c}">` : none ? `<div class="gone"><b>${none}</b></div>` : `<div class="gone"><b>Removed</b><span>The element is gone, as asked.</span></div>`}</figure>`).join("")}</div>`;
|
|
1380
|
+
z.addEventListener("click", closeZoom);
|
|
1381
|
+
ui.root.appendChild(z);
|
|
1382
|
+
S.zoom = z;
|
|
1383
|
+
}
|
|
1384
|
+
function closeZoom() {
|
|
1385
|
+
S.zoom?.remove();
|
|
1386
|
+
S.zoom = null;
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
// ---------- Toast, frame loop, live status ----------
|
|
1390
|
+
let toastTimer = 0;
|
|
1391
|
+
// tone: true or "ok" (done), "warn" (needs the user); long messages wrap and stay longer.
|
|
1392
|
+
function toast(html, tone = false, ms = 3600) {
|
|
1393
|
+
ui.toast.innerHTML = `<span class="dot"></span><span>${html}</span>`;
|
|
1394
|
+
ui.toast.classList.toggle("ok", tone === true || tone === "ok");
|
|
1395
|
+
ui.toast.classList.toggle("warn", tone === "warn");
|
|
1396
|
+
ui.toast.classList.toggle("long", ms > 3600);
|
|
1397
|
+
ui.toast.hidden = false;
|
|
1398
|
+
clearTimeout(toastTimer);
|
|
1399
|
+
toastTimer = setTimeout(() => (ui.toast.hidden = true), ms);
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
// ---------- Is an agent listening? ----------
|
|
1403
|
+
// Only what the server says: a point server without the heartbeat (an older palate) shows no status at all.
|
|
1404
|
+
const noAgent = () => Boolean(S.agent) && (S.agent.state === "none" || S.agent.state === "stopped");
|
|
1405
|
+
function setAgent(a) {
|
|
1406
|
+
if (!a) return;
|
|
1407
|
+
S.agent = a;
|
|
1408
|
+
renderAgent();
|
|
1409
|
+
if (S.pop) renderPop();
|
|
1410
|
+
}
|
|
1411
|
+
function renderAgent() {
|
|
1412
|
+
const a = S.agent;
|
|
1413
|
+
ui.agent.hidden = !a;
|
|
1414
|
+
if (!a) return;
|
|
1415
|
+
const queued = S.requests.filter((r) => r.status === "queued").length;
|
|
1416
|
+
const who = a.auto ? "palate point --auto" : a.label === "cli" ? "palate point next" : "your agent";
|
|
1417
|
+
const text = { listening: "Agent listening", busy: `Agent working · pin ${a.pin}`, stopped: "Agent stopped", none: "No agent listening" }[a.state] ?? "No agent listening";
|
|
1418
|
+
ui.agent.dataset.state = a.state;
|
|
1419
|
+
ui.agent.classList.toggle("waiting", a.state === "none" && queued > 0);
|
|
1420
|
+
ui.agent.querySelector(".at").textContent = text;
|
|
1421
|
+
ui.agent.title =
|
|
1422
|
+
a.state === "listening" ? `${who} is waiting for your pins.` : a.state === "busy" ? `${who} is working on pin ${a.pin}.` : a.state === "stopped" ? "You pressed Done. Send a pin to start again." : "Your pins wait in the queue until an agent takes them. In Claude Code run /mcp__palate__point, or start palate point --auto.";
|
|
1423
|
+
ui.agent.querySelector(".stop").hidden = !(a.state === "listening" || a.state === "busy");
|
|
1424
|
+
}
|
|
1425
|
+
ui.agent.querySelector(".stop").addEventListener("click", async () => {
|
|
1426
|
+
const res = await fetch(`${API}/stop`, { method: "POST", headers: { "content-type": "application/json" }, body: "{}" }).catch(() => null);
|
|
1427
|
+
const j = await res?.json().catch(() => null);
|
|
1428
|
+
if (j?.agent) setAgent(j.agent);
|
|
1429
|
+
const open = S.requests.filter((r) => r.status === "queued" || r.status === "working").length;
|
|
1430
|
+
toast(open ? `<b>Done.</b> The agent finishes the ${open} pin${open > 1 ? "s" : ""} you sent, then stops listening.` : "<b>Done.</b> The agent stops listening. Send a pin to start again.");
|
|
1431
|
+
});
|
|
1432
|
+
let raf = 0;
|
|
1433
|
+
function loop() {
|
|
1434
|
+
if (raf) return;
|
|
1435
|
+
const tick = () => {
|
|
1436
|
+
raf = 0;
|
|
1437
|
+
if (S.dialog) {
|
|
1438
|
+
draw(S.dialog.el, { bands: false, frozen: true });
|
|
1439
|
+
placeDialog();
|
|
1440
|
+
} else if (S.on && S.target) draw(S.target);
|
|
1441
|
+
if (ui.pins.children.length) placePins();
|
|
1442
|
+
if (S.on || S.dialog || ui.pins.children.length || S.pop) raf = requestAnimationFrame(tick);
|
|
1443
|
+
};
|
|
1444
|
+
raf = requestAnimationFrame(tick);
|
|
1445
|
+
}
|
|
1446
|
+
function setState(list, agent) {
|
|
1447
|
+
S.requests = list;
|
|
1448
|
+
if (agent) S.agent = agent;
|
|
1449
|
+
renderPins();
|
|
1450
|
+
renderAgent();
|
|
1451
|
+
if (S.pop) renderPop();
|
|
1452
|
+
}
|
|
1453
|
+
let polling = 0;
|
|
1454
|
+
async function poll() {
|
|
1455
|
+
try {
|
|
1456
|
+
const r = await fetch(`${API}/state`).then((x) => x.json());
|
|
1457
|
+
setState(r.requests ?? [], r.agent);
|
|
1458
|
+
} catch {}
|
|
1459
|
+
}
|
|
1460
|
+
function connect() {
|
|
1461
|
+
if (!window.EventSource) return void (polling ||= setInterval(poll, 2000), poll());
|
|
1462
|
+
const es = new EventSource("/__palate/events");
|
|
1463
|
+
es.onmessage = (m) => {
|
|
1464
|
+
try {
|
|
1465
|
+
const d = JSON.parse(m.data);
|
|
1466
|
+
if (d.type === "state") setState(d.requests ?? [], d.agent);
|
|
1467
|
+
else if (d.type === "update" && d.request) upsert(d.request);
|
|
1468
|
+
else if (d.type === "agent") setAgent(d.agent);
|
|
1469
|
+
} catch {}
|
|
1470
|
+
};
|
|
1471
|
+
es.onerror = () => {
|
|
1472
|
+
es.close();
|
|
1473
|
+
polling ||= setInterval(poll, 2500);
|
|
1474
|
+
setTimeout(() => {
|
|
1475
|
+
clearInterval(polling);
|
|
1476
|
+
polling = 0;
|
|
1477
|
+
connect();
|
|
1478
|
+
}, 4000);
|
|
1479
|
+
};
|
|
1480
|
+
}
|
|
1481
|
+
// Client-side navigation: pins belong to their route.
|
|
1482
|
+
setInterval(() => {
|
|
1483
|
+
// A router that kept history's own pushState still resets the trail here.
|
|
1484
|
+
onNav();
|
|
1485
|
+
if (S.route === location.pathname) return;
|
|
1486
|
+
S.route = location.pathname;
|
|
1487
|
+
closeDialog();
|
|
1488
|
+
closePop();
|
|
1489
|
+
S.els.clear();
|
|
1490
|
+
renderPins();
|
|
1491
|
+
}, 400);
|
|
1492
|
+
addEventListener("resize", () => {
|
|
1493
|
+
renderViewport();
|
|
1494
|
+
placeDialog();
|
|
1495
|
+
placePop();
|
|
1496
|
+
});
|
|
1497
|
+
|
|
1498
|
+
function mount() {
|
|
1499
|
+
if (!document.body) return;
|
|
1500
|
+
(document.head || document.documentElement).appendChild(cursorStyle);
|
|
1501
|
+
document.body.appendChild(host);
|
|
1502
|
+
// Some frameworks replace the body on navigation; come back if that happens.
|
|
1503
|
+
new MutationObserver(() => {
|
|
1504
|
+
if (!host.isConnected && document.body) document.body.appendChild(host);
|
|
1505
|
+
if (!cursorStyle.isConnected) (document.head || document.documentElement).appendChild(cursorStyle);
|
|
1506
|
+
}).observe(document.documentElement, { childList: true, subtree: false });
|
|
1507
|
+
new MutationObserver(() => {
|
|
1508
|
+
if (!host.isConnected && document.body) document.body.appendChild(host);
|
|
1509
|
+
}).observe(document.body, { childList: true });
|
|
1510
|
+
connect();
|
|
1511
|
+
}
|
|
1512
|
+
// After hydration, so the page's framework never sees palate's element.
|
|
1513
|
+
const later = () => (window.requestIdleCallback ? requestIdleCallback(mount, { timeout: 1200 }) : setTimeout(mount, 300));
|
|
1514
|
+
if (document.readyState === "complete") later();
|
|
1515
|
+
else addEventListener("load", later, { once: true });
|
|
1516
|
+
})();
|